diff --git a/resources/builtin/image-200x200.png b/resources/builtin/image-200x200.png new file mode 100644 index 0000000000..53bc1e785c Binary files /dev/null and b/resources/builtin/image-200x200.png differ diff --git a/resources/builtin/projects/fa-map-marker.png b/resources/builtin/projects/fa-map-marker.png new file mode 100644 index 0000000000..865175bd9a Binary files /dev/null and b/resources/builtin/projects/fa-map-marker.png differ diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 56aee8abdc..9003476d2d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,8 +7,8 @@ */ return array( 'names' => array( - 'core.pkg.css' => '5e4df064', - 'core.pkg.js' => 'a79eed25', + 'core.pkg.css' => 'e33b14a4', + 'core.pkg.js' => 'ef5e33db', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '2de124c9', 'differential.pkg.js' => '5c2ba922', @@ -36,7 +36,7 @@ return array( 'rsrc/css/application/base/notification-menu.css' => 'f31c0bde', 'rsrc/css/application/base/phabricator-application-launch-view.css' => '95351601', 'rsrc/css/application/base/phui-theme.css' => 'ab7b848c', - 'rsrc/css/application/base/standard-page-view.css' => '7b0d68d8', + 'rsrc/css/application/base/standard-page-view.css' => 'c4467133', 'rsrc/css/application/chatlog/chatlog.css' => 'd295b020', 'rsrc/css/application/conduit/conduit-api.css' => '7bc725c4', 'rsrc/css/application/config/config-options.css' => '0ede4c9b', @@ -81,7 +81,7 @@ return array( 'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b', 'rsrc/css/application/paste/paste.css' => 'a5157c48', 'rsrc/css/application/people/people-profile.css' => '2473d929', - 'rsrc/css/application/phame/phame.css' => '6d5b3682', + 'rsrc/css/application/phame/phame.css' => '1dbbacf9', 'rsrc/css/application/pholio/pholio-edit.css' => '3ad9d1ee', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49', 'rsrc/css/application/pholio/pholio.css' => '95174bdd', @@ -93,7 +93,8 @@ return array( 'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43', 'rsrc/css/application/policy/policy.css' => '957ea14c', 'rsrc/css/application/ponder/ponder-view.css' => '7b0df4da', - 'rsrc/css/application/project/project-view.css' => '22f7ed0e', + 'rsrc/css/application/project/project-card-view.css' => '9c3631e5', + 'rsrc/css/application/project/project-view.css' => '4693497c', 'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733', 'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5', 'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd', @@ -105,14 +106,13 @@ return array( 'rsrc/css/core/core.css' => '5b3563c8', 'rsrc/css/core/remarkup.css' => 'e1c8b32f', 'rsrc/css/core/syntax.css' => '9fd11da8', - 'rsrc/css/core/z-index.css' => 'a36a45da', + 'rsrc/css/core/z-index.css' => '5c7025bf', 'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa', 'rsrc/css/font/font-aleo.css' => '8bdb2835', 'rsrc/css/font/font-awesome.css' => 'c43323c5', 'rsrc/css/font/font-lato.css' => 'c7ccd872', 'rsrc/css/font/phui-font-icon-base.css' => 'ecbbb4c2', 'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82', - 'rsrc/css/layout/phabricator-hovercard-view.css' => '1239cd52', 'rsrc/css/layout/phabricator-side-menu-view.css' => '3a3d9f41', 'rsrc/css/layout/phabricator-source-code-view.css' => 'cbeef983', 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93', @@ -125,15 +125,17 @@ return array( 'rsrc/css/phui/phui-big-info-view.css' => 'bd903741', 'rsrc/css/phui/phui-box.css' => '6e8ac7fd', 'rsrc/css/phui/phui-button.css' => 'd6ac72db', + 'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-crumbs-view.css' => '414406b5', 'rsrc/css/phui/phui-document-pro.css' => '8799acf7', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document.css' => '9c71d2bf', - 'rsrc/css/phui/phui-feed-story.css' => 'b7b26d23', + 'rsrc/css/phui/phui-feed-story.css' => '04aec08f', 'rsrc/css/phui/phui-fontkit.css' => '9cda225e', 'rsrc/css/phui/phui-form-view.css' => '4a1a0f5e', 'rsrc/css/phui/phui-form.css' => '0b98e572', 'rsrc/css/phui/phui-header-view.css' => 'd53cc835', + 'rsrc/css/phui/phui-hovercard.css' => 'de1a2119', 'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad', 'rsrc/css/phui/phui-icon.css' => '3f33ab57', 'rsrc/css/phui/phui-image-mask.css' => '5a8b09c8', @@ -141,7 +143,7 @@ return array( 'rsrc/css/phui/phui-info-view.css' => '6d7c3509', 'rsrc/css/phui/phui-list.css' => '9da2aa00', 'rsrc/css/phui/phui-object-box.css' => '407eaf5a', - 'rsrc/css/phui/phui-object-item-list-view.css' => '0d484a97', + 'rsrc/css/phui/phui-object-item-list-view.css' => 'fe594a65', 'rsrc/css/phui/phui-pager.css' => 'bea33d23', 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 'rsrc/css/phui/phui-profile-menu.css' => 'ab4fcf5f', @@ -149,15 +151,16 @@ return array( 'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591', 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => '888cedb8', - 'rsrc/css/phui/phui-tag-view.css' => 'e60e227b', + 'rsrc/css/phui/phui-tag-view.css' => '9d5d4400', 'rsrc/css/phui/phui-timeline-view.css' => '2efceff8', 'rsrc/css/phui/phui-two-column-view.css' => 'c75bfc5b', 'rsrc/css/phui/workboards/phui-workboard.css' => 'b07a5524', - 'rsrc/css/phui/workboards/phui-workcard.css' => 'ffb55371', - 'rsrc/css/phui/workboards/phui-workpanel.css' => 'e9339dc3', + 'rsrc/css/phui/workboards/phui-workcard.css' => 'b4322ca7', + 'rsrc/css/phui/workboards/phui-workpanel.css' => 'e1bd8d04', 'rsrc/css/sprite-login.css' => '60e8560e', 'rsrc/css/sprite-menu.css' => '9dd65b92', 'rsrc/css/sprite-tokens.css' => '4f399012', + 'rsrc/externals/d3/d3.min.js' => 'a11a5ff2', 'rsrc/externals/font/aleo/aleo-bold.eot' => 'd3d3bed7', 'rsrc/externals/font/aleo/aleo-bold.svg' => '45899c8e', 'rsrc/externals/font/aleo/aleo-bold.ttf' => '4b08bef0', @@ -252,9 +255,6 @@ return array( 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '54f314a0', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '1bc11c4a', 'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '6c0e62fa', - 'rsrc/externals/raphael/g.raphael.js' => '40dde778', - 'rsrc/externals/raphael/g.raphael.line.js' => '40da039e', - 'rsrc/externals/raphael/raphael.js' => '51ee6b43', 'rsrc/favicons/apple-touch-icon-120x120.png' => '43742962', 'rsrc/favicons/apple-touch-icon-152x152.png' => '669eaec3', 'rsrc/favicons/apple-touch-icon-76x76.png' => 'ecdef672', @@ -369,7 +369,7 @@ return array( 'rsrc/js/application/countdown/timer.js' => 'e4cc26b3', 'rsrc/js/application/daemon/behavior-bulk-job-reload.js' => 'edf8a145', 'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e', - 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '82439934', + 'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '019f36c4', 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/differential/ChangesetViewManager.js' => 'a2828756', @@ -401,7 +401,7 @@ return array( 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', 'rsrc/js/application/maniphest/behavior-batch-editor.js' => '782ab6e7', 'rsrc/js/application/maniphest/behavior-batch-selector.js' => '7b98d7c5', - 'rsrc/js/application/maniphest/behavior-line-chart.js' => '88f0c5b3', + 'rsrc/js/application/maniphest/behavior-line-chart.js' => 'e4232876', 'rsrc/js/application/maniphest/behavior-list-edit.js' => 'a9f88de2', 'rsrc/js/application/maniphest/behavior-subpriorityeditor.js' => '71237763', 'rsrc/js/application/owners/OwnersPathEditor.js' => 'aa1733d0', @@ -412,9 +412,9 @@ return array( 'rsrc/js/application/phortune/behavior-stripe-payment-form.js' => '3f5d6dbf', 'rsrc/js/application/phortune/behavior-test-payment-form.js' => 'fc91ab6c', '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-control.js' => 'd0c516d5', 'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '5e9f347c', - 'rsrc/js/application/projects/behavior-project-boards.js' => 'ba4fa35c', + 'rsrc/js/application/projects/behavior-project-boards.js' => '48470f95', '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', @@ -447,9 +447,9 @@ return array( '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' => '8905523d', 'rsrc/js/core/FileUpload.js' => '477359c8', - 'rsrc/js/core/Hovercard.js' => 'c6f720ff', + 'rsrc/js/core/Hovercard.js' => '1bd28176', 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', 'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f', 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', @@ -458,7 +458,7 @@ return array( 'rsrc/js/core/ShapedRequest.js' => '7cbe244b', 'rsrc/js/core/TextAreaUtils.js' => '9e54692d', 'rsrc/js/core/Title.js' => 'df5e11d2', - 'rsrc/js/core/ToolTip.js' => '1d298e3a', + 'rsrc/js/core/ToolTip.js' => '6323f942', 'rsrc/js/core/behavior-active-nav.js' => 'e379b58e', 'rsrc/js/core/behavior-audio-source.js' => '59b251eb', 'rsrc/js/core/behavior-autofocus.js' => '7319e029', @@ -475,7 +475,7 @@ return array( 'rsrc/js/core/behavior-global-drag-and-drop.js' => 'c8e57404', 'rsrc/js/core/behavior-high-security-warning.js' => 'a464fe03', 'rsrc/js/core/behavior-history-install.js' => '7ee2b591', - 'rsrc/js/core/behavior-hovercard.js' => '66dd6e9e', + 'rsrc/js/core/behavior-hovercard.js' => 'bcaccd64', 'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0', 'rsrc/js/core/behavior-keyboard-shortcuts.js' => 'd75709e6', 'rsrc/js/core/behavior-lightbox-attachments.js' => 'f8ba29d7', @@ -490,7 +490,7 @@ return array( 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 'rsrc/js/core/behavior-scrollbar.js' => '834a1173', - 'rsrc/js/core/behavior-search-typeahead.js' => '0b7a4f6e', + 'rsrc/js/core/behavior-search-typeahead.js' => '06c32383', 'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6', 'rsrc/js/core/behavior-time-typeahead.js' => 'f80d6bf0', 'rsrc/js/core/behavior-toggle-class.js' => '5d7c9f33', @@ -535,6 +535,7 @@ return array( 'conpherence-transaction-css' => '85d0974c', 'conpherence-update-css' => 'faf6be09', 'conpherence-widget-pane-css' => '775eaaba', + 'd3' => 'a11a5ff2', 'differential-changeset-view-css' => 'b6b0d1bb', 'differential-core-view-css' => '7ac3cabc', 'differential-inline-comment-editor' => '64a5550f', @@ -579,7 +580,7 @@ return array( 'javelin-behavior-countdown-timer' => 'e4cc26b3', 'javelin-behavior-dark-console' => 'f411b6ae', 'javelin-behavior-dashboard-async-panel' => '469c0d9e', - 'javelin-behavior-dashboard-move-panels' => '82439934', + 'javelin-behavior-dashboard-move-panels' => '019f36c4', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', 'javelin-behavior-day-view' => '5c46cff2', @@ -615,7 +616,7 @@ return array( 'javelin-behavior-icon-composer' => '8499b6ab', 'javelin-behavior-launch-icon-composer' => '48086888', 'javelin-behavior-lightbox-attachments' => 'f8ba29d7', - 'javelin-behavior-line-chart' => '88f0c5b3', + 'javelin-behavior-line-chart' => 'e4232876', 'javelin-behavior-load-blame' => '42126667', 'javelin-behavior-maniphest-batch-editor' => '782ab6e7', 'javelin-behavior-maniphest-batch-selector' => '7b98d7c5', @@ -630,7 +631,6 @@ return array( 'javelin-behavior-phabricator-file-tree' => '88236f00', 'javelin-behavior-phabricator-gesture' => '3ab51e2c', 'javelin-behavior-phabricator-gesture-example' => '558829c2', - 'javelin-behavior-phabricator-hovercards' => '66dd6e9e', 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', 'javelin-behavior-phabricator-keyboard-shortcuts' => 'd75709e6', 'javelin-behavior-phabricator-line-linker' => '1499a8cb', @@ -640,7 +640,7 @@ return array( 'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-remarkup-assist' => '340c8eff', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', - 'javelin-behavior-phabricator-search-typeahead' => '0b7a4f6e', + 'javelin-behavior-phabricator-search-typeahead' => '06c32383', 'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6', 'javelin-behavior-phabricator-tooltips' => '3ee3408b', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', @@ -649,11 +649,12 @@ return array( 'javelin-behavior-pholio-mock-edit' => '246dc085', 'javelin-behavior-pholio-mock-view' => 'fbe497e7', 'javelin-behavior-phui-dropdown-menu' => '54733475', + 'javelin-behavior-phui-hovercards' => 'bcaccd64', 'javelin-behavior-phui-object-box-tabs' => '2bfa2836', 'javelin-behavior-phui-profile-menu' => '12884df9', - 'javelin-behavior-policy-control' => 'ae45872f', + 'javelin-behavior-policy-control' => 'd0c516d5', 'javelin-behavior-policy-rule-editor' => '5e9f347c', - 'javelin-behavior-project-boards' => 'ba4fa35c', + 'javelin-behavior-project-boards' => '48470f95', 'javelin-behavior-project-create' => '065227cc', 'javelin-behavior-quicksand-blacklist' => '7927a7d3', 'javelin-behavior-recurring-edit' => '5f1c4d5f', @@ -741,14 +742,12 @@ return array( 'phabricator-countdown-css' => 'e7544472', 'phabricator-dashboard-css' => 'eb458607', 'phabricator-drag-and-drop-file-upload' => 'ad10aeac', - 'phabricator-draggable-list' => 'a16ec1c6', + 'phabricator-draggable-list' => '8905523d', 'phabricator-fatal-config-template-css' => '8e6c6fcd', 'phabricator-feed-css' => 'ecd4ec57', 'phabricator-file-upload' => '477359c8', 'phabricator-filetree-view-css' => 'fccf9f82', 'phabricator-flag-css' => '5337623f', - 'phabricator-hovercard' => 'c6f720ff', - 'phabricator-hovercard-view-css' => '1239cd52', 'phabricator-keyboard-shortcut' => '1ae869f2', 'phabricator-keyboard-shortcut-manager' => 'c1700f6f', 'phabricator-main-menu-view' => 'd00a795a', @@ -765,10 +764,10 @@ return array( 'phabricator-side-menu-view-css' => '3a3d9f41', 'phabricator-slowvote-css' => 'da0afb1b', 'phabricator-source-code-view-css' => 'cbeef983', - 'phabricator-standard-page-view' => '7b0d68d8', + 'phabricator-standard-page-view' => 'c4467133', 'phabricator-textareautils' => '9e54692d', 'phabricator-title' => 'df5e11d2', - 'phabricator-tooltip' => '1d298e3a', + 'phabricator-tooltip' => '6323f942', 'phabricator-ui-example-css' => '528b19de', 'phabricator-uiexample-javelin-view' => 'd4a14807', 'phabricator-uiexample-reactor-button' => 'd19198c8', @@ -780,8 +779,8 @@ return array( 'phabricator-uiexample-reactor-select' => 'a155550f', 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', - 'phabricator-zindex-css' => 'a36a45da', - 'phame-css' => '6d5b3682', + 'phabricator-zindex-css' => '5c7025bf', + 'phame-css' => '1dbbacf9', 'pholio-css' => '95174bdd', 'pholio-edit-css' => '3ad9d1ee', 'pholio-inline-comments-css' => '8e545e49', @@ -799,16 +798,19 @@ return array( 'phui-calendar-day-css' => 'd1cf6f93', 'phui-calendar-list-css' => 'c1c7f338', 'phui-calendar-month-css' => '476be7e0', + 'phui-chart-css' => '6bf6f78e', 'phui-crumbs-view-css' => '414406b5', 'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-view-css' => '9c71d2bf', 'phui-document-view-pro-css' => '8799acf7', - 'phui-feed-story-css' => 'b7b26d23', + 'phui-feed-story-css' => '04aec08f', 'phui-font-icon-base-css' => 'ecbbb4c2', 'phui-fontkit-css' => '9cda225e', 'phui-form-css' => '0b98e572', 'phui-form-view-css' => '4a1a0f5e', 'phui-header-view-css' => 'd53cc835', + 'phui-hovercard' => '1bd28176', + 'phui-hovercard-view-css' => 'de1a2119', 'phui-icon-set-selector-css' => '1ab67aad', 'phui-icon-view-css' => '3f33ab57', 'phui-image-mask-css' => '5a8b09c8', @@ -817,7 +819,7 @@ return array( 'phui-inline-comment-view-css' => '0fdb3667', 'phui-list-view-css' => '9da2aa00', 'phui-object-box-css' => '407eaf5a', - 'phui-object-item-list-view-css' => '0d484a97', + 'phui-object-item-list-view-css' => 'fe594a65', 'phui-pager-css' => 'bea33d23', 'phui-pinboard-view-css' => '2495140e', 'phui-profile-menu-css' => 'ab4fcf5f', @@ -825,13 +827,13 @@ return array( 'phui-remarkup-preview-css' => '1a8f2591', 'phui-spacing-css' => '042804d6', 'phui-status-list-view-css' => '888cedb8', - 'phui-tag-view-css' => 'e60e227b', + 'phui-tag-view-css' => '9d5d4400', 'phui-theme-css' => 'ab7b848c', 'phui-timeline-view-css' => '2efceff8', 'phui-two-column-view-css' => 'c75bfc5b', 'phui-workboard-view-css' => 'b07a5524', - 'phui-workcard-view-css' => 'ffb55371', - 'phui-workpanel-view-css' => 'e9339dc3', + 'phui-workcard-view-css' => 'b4322ca7', + 'phui-workpanel-view-css' => 'e1bd8d04', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '8cf6d262', 'phuix-autocomplete' => '9196fb06', @@ -842,10 +844,8 @@ return array( 'policy-edit-css' => '815c66f7', 'policy-transaction-detail-css' => '82100a43', 'ponder-view-css' => '7b0df4da', - 'project-view-css' => '22f7ed0e', - 'raphael-core' => '51ee6b43', - 'raphael-g' => '40dde778', - 'raphael-g-line' => '40da039e', + 'project-card-view-css' => '9c3631e5', + 'project-view-css' => '4693497c', 'releeph-core' => '9b3c5733', 'releeph-preview-branch' => 'b7a6f4a5', 'releeph-request-differential-create-dialog' => '8d8b92cd', @@ -877,6 +877,14 @@ return array( 'javelin-behavior-device', 'javelin-vector', ), + '019f36c4' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-workflow', + 'phabricator-draggable-list', + ), '031cee25' => array( 'javelin-behavior', 'javelin-request', @@ -901,6 +909,17 @@ return array( 'javelin-stratcom', 'javelin-workflow', ), + '06c32383' => array( + 'javelin-behavior', + 'javelin-typeahead-ondemand-source', + 'javelin-typeahead', + 'javelin-dom', + 'javelin-uri', + 'javelin-util', + 'javelin-stratcom', + 'phabricator-prefab', + 'phuix-icon-view', + ), '087e919c' => array( 'javelin-install', 'javelin-dom', @@ -914,16 +933,6 @@ return array( 'javelin-dom', 'javelin-router', ), - '0b7a4f6e' => array( - 'javelin-behavior', - 'javelin-typeahead-ondemand-source', - 'javelin-typeahead', - 'javelin-dom', - 'javelin-uri', - 'javelin-util', - 'javelin-stratcom', - 'phabricator-prefab', - ), '0f764c35' => array( 'javelin-install', 'javelin-util', @@ -964,11 +973,12 @@ return array( 'javelin-dom', 'javelin-typeahead-normalizer', ), - '1d298e3a' => array( + '1bd28176' => array( 'javelin-install', - 'javelin-util', 'javelin-dom', 'javelin-vector', + 'javelin-request', + 'javelin-uri', ), '1d45c74d' => array( 'javelin-behavior', @@ -1144,6 +1154,15 @@ return array( 'javelin-dom', 'javelin-workflow', ), + '48470f95' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + 'javelin-stratcom', + 'javelin-workflow', + 'phabricator-draggable-list', + ), '49b73b36' => array( 'javelin-behavior', 'javelin-dom', @@ -1292,6 +1311,12 @@ return array( 'javelin-install', 'javelin-util', ), + '6323f942' => array( + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-vector', + ), '635de1ec' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1306,13 +1331,6 @@ return array( 'javelin-request', 'javelin-workflow', ), - '66dd6e9e' => array( - 'javelin-behavior', - 'javelin-behavior-device', - 'javelin-stratcom', - 'javelin-vector', - 'phabricator-hovercard', - ), '6882e80a' => array( 'javelin-dom', ), @@ -1432,14 +1450,6 @@ return array( 'javelin-vector', 'javelin-stratcom', ), - 82439934 => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-workflow', - 'phabricator-draggable-list', - ), '834a1173' => array( 'javelin-behavior', 'javelin-scrollbar', @@ -1477,10 +1487,13 @@ return array( 'javelin-stratcom', 'javelin-dom', ), - '88f0c5b3' => array( - 'javelin-behavior', + '8905523d' => array( + 'javelin-install', 'javelin-dom', + 'javelin-stratcom', + 'javelin-util', 'javelin-vector', + 'javelin-magical-init', ), '8a41885b' => array( 'javelin-install', @@ -1594,14 +1607,6 @@ return array( 'javelin-dom', 'javelin-reactor-dom', ), - 'a16ec1c6' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-util', - 'javelin-vector', - 'javelin-magical-init', - ), 'a2828756' => array( 'javelin-dom', 'javelin-util', @@ -1677,15 +1682,6 @@ return array( 'javelin-uri', 'phabricator-file-upload', ), - 'ae45872f' => array( - 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'phuix-dropdown-menu', - 'phuix-action-list-view', - 'phuix-action-view', - 'javelin-workflow', - ), 'b064af76' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1759,14 +1755,12 @@ return array( 'b6b0d1bb' => array( 'phui-inline-comment-view-css', ), - 'ba4fa35c' => array( + 'bcaccd64' => array( 'javelin-behavior', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', + 'javelin-behavior-device', 'javelin-stratcom', - 'javelin-workflow', - 'phabricator-draggable-list', + 'javelin-vector', + 'phui-hovercard', ), 'bd4c8dca' => array( 'javelin-install', @@ -1792,13 +1786,6 @@ return array( 'javelin-dom', 'javelin-vector', ), - 'c6f720ff' => array( - 'javelin-install', - 'javelin-dom', - 'javelin-vector', - 'javelin-request', - 'javelin-uri', - ), 'c72aa091' => array( 'javelin-behavior', 'javelin-dom', @@ -1854,6 +1841,16 @@ return array( 'd00a795a' => array( 'phui-theme-css', ), + 'd0c516d5' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-util', + 'phuix-dropdown-menu', + 'phuix-action-list-view', + 'phuix-action-view', + 'javelin-workflow', + 'phuix-icon-view', + ), 'd19198c8' => array( 'javelin-install', 'javelin-dom', @@ -1921,6 +1918,9 @@ return array( 'javelin-dom', 'phabricator-prefab', ), + 'e1bd8d04' => array( + 'phui-workcard-view-css', + ), 'e1d25dfb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1950,6 +1950,12 @@ return array( 'javelin-dom', 'javelin-uri', ), + 'e4232876' => array( + 'javelin-behavior', + 'javelin-dom', + 'javelin-vector', + 'phui-chart-css', + ), 'e4cc26b3' => array( 'javelin-behavior', 'javelin-dom', @@ -1982,9 +1988,6 @@ return array( 'e6e25838' => array( 'javelin-install', ), - 'e9339dc3' => array( - 'phui-workcard-view-css', - ), 'e9581f08' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2221,8 +2224,8 @@ return array( 'phabricator-file-upload', 'javelin-behavior-global-drag-and-drop', 'javelin-behavior-phabricator-reveal-content', - 'phabricator-hovercard', - 'javelin-behavior-phabricator-hovercards', + 'phui-hovercard', + 'javelin-behavior-phui-hovercards', 'javelin-color', 'javelin-fx', 'phabricator-draggable-list', diff --git a/resources/celerity/packages.php b/resources/celerity/packages.php index 5a3e296a8c..2b0f8b9ff0 100644 --- a/resources/celerity/packages.php +++ b/resources/celerity/packages.php @@ -59,8 +59,8 @@ return array( 'phabricator-file-upload', 'javelin-behavior-global-drag-and-drop', 'javelin-behavior-phabricator-reveal-content', - 'phabricator-hovercard', - 'javelin-behavior-phabricator-hovercards', + 'phui-hovercard', + 'javelin-behavior-phui-hovercards', 'javelin-color', 'javelin-fx', 'phabricator-draggable-list', diff --git a/resources/sql/autopatches/20160202.board.1.proxy.sql b/resources/sql/autopatches/20160202.board.1.proxy.sql new file mode 100644 index 0000000000..a3e5965f26 --- /dev/null +++ b/resources/sql/autopatches/20160202.board.1.proxy.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_project.project_column + ADD proxyPHID VARBINARY(64); diff --git a/resources/sql/autopatches/20160202.ipv6.1.sql b/resources/sql/autopatches/20160202.ipv6.1.sql new file mode 100644 index 0000000000..d6a3ee5ccc --- /dev/null +++ b/resources/sql/autopatches/20160202.ipv6.1.sql @@ -0,0 +1,5 @@ +ALTER TABLE {$NAMESPACE}_repository.repository_pullevent + CHANGE remoteAddress remoteAddress VARBINARY(64); + +ALTER TABLE {$NAMESPACE}_repository.repository_pushevent + CHANGE remoteAddress remoteAddress VARBINARY(64); diff --git a/resources/sql/autopatches/20160202.ipv6.2.php b/resources/sql/autopatches/20160202.ipv6.2.php new file mode 100644 index 0000000000..50def09444 --- /dev/null +++ b/resources/sql/autopatches/20160202.ipv6.2.php @@ -0,0 +1,39 @@ +establishConnection('w'); + +$log_types = array($pull, $push); +foreach ($log_types as $log) { + foreach (new LiskMigrationIterator($log) as $row) { + $addr = $row->getRemoteAddress(); + + $addr = (string)$addr; + if (!strlen($addr)) { + continue; + } + + if (!ctype_digit($addr)) { + continue; + } + + if (!(int)$addr) { + continue; + } + + $ip = long2ip($addr); + if (!is_string($ip) || !strlen($ip)) { + continue; + } + + $id = $row->getID(); + queryfx( + $conn_w, + 'UPDATE %T SET remoteAddress = %s WHERE id = %d', + $log->getTableName(), + $ip, + $id); + } +} diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6339e6c564..763b41e99b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1493,6 +1493,8 @@ phutil_register_library_map(array( 'PHUIHandleTagListView' => 'applications/phid/view/PHUIHandleTagListView.php', 'PHUIHandleView' => 'applications/phid/view/PHUIHandleView.php', 'PHUIHeaderView' => 'view/phui/PHUIHeaderView.php', + 'PHUIHovercardUIExample' => 'applications/uiexample/examples/PHUIHovercardUIExample.php', + 'PHUIHovercardView' => 'view/phui/PHUIHovercardView.php', 'PHUIIconCircleView' => 'view/phui/PHUIIconCircleView.php', 'PHUIIconExample' => 'applications/uiexample/examples/PHUIIconExample.php', 'PHUIIconView' => 'view/phui/PHUIIconView.php', @@ -1585,6 +1587,7 @@ phutil_register_library_map(array( 'PasteSearchConduitAPIMethod' => 'applications/paste/conduit/PasteSearchConduitAPIMethod.php', 'PeopleBrowseUserDirectoryCapability' => 'applications/people/capability/PeopleBrowseUserDirectoryCapability.php', 'PeopleCreateUsersCapability' => 'applications/people/capability/PeopleCreateUsersCapability.php', + 'PeopleHovercardEngineExtension' => 'applications/people/engineextension/PeopleHovercardEngineExtension.php', 'PeopleUserLogGarbageCollector' => 'applications/people/garbagecollector/PeopleUserLogGarbageCollector.php', 'Phabricator404Controller' => 'applications/base/controller/Phabricator404Controller.php', 'PhabricatorAWSConfigOptions' => 'applications/config/option/PhabricatorAWSConfigOptions.php', @@ -1811,6 +1814,7 @@ phutil_register_library_map(array( 'PhabricatorBcryptPasswordHasher' => 'infrastructure/util/password/PhabricatorBcryptPasswordHasher.php', 'PhabricatorBinariesSetupCheck' => 'applications/config/check/PhabricatorBinariesSetupCheck.php', 'PhabricatorBitbucketAuthProvider' => 'applications/auth/provider/PhabricatorBitbucketAuthProvider.php', + 'PhabricatorBoardLayoutEngine' => 'applications/project/engine/PhabricatorBoardLayoutEngine.php', 'PhabricatorBot' => 'infrastructure/daemon/bot/PhabricatorBot.php', 'PhabricatorBotChannel' => 'infrastructure/daemon/bot/target/PhabricatorBotChannel.php', 'PhabricatorBotDebugLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php', @@ -1885,6 +1889,7 @@ phutil_register_library_map(array( 'PhabricatorChatLogQuery' => 'applications/chatlog/query/PhabricatorChatLogQuery.php', 'PhabricatorChunkedFileStorageEngine' => 'applications/files/engine/PhabricatorChunkedFileStorageEngine.php', 'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php', + 'PhabricatorColumnProxyInterface' => 'applications/project/interface/PhabricatorColumnProxyInterface.php', 'PhabricatorCommentEditEngineExtension' => 'applications/transactions/engineextension/PhabricatorCommentEditEngineExtension.php', 'PhabricatorCommentEditField' => 'applications/transactions/editfield/PhabricatorCommentEditField.php', 'PhabricatorCommentEditType' => 'applications/transactions/edittype/PhabricatorCommentEditType.php', @@ -2388,8 +2393,6 @@ phutil_register_library_map(array( 'PhabricatorHomeQuickCreateController' => 'applications/home/controller/PhabricatorHomeQuickCreateController.php', 'PhabricatorHovercardEngineExtension' => 'applications/search/engineextension/PhabricatorHovercardEngineExtension.php', 'PhabricatorHovercardEngineExtensionModule' => 'applications/search/engineextension/PhabricatorHovercardEngineExtensionModule.php', - 'PhabricatorHovercardUIExample' => 'applications/uiexample/examples/PhabricatorHovercardUIExample.php', - 'PhabricatorHovercardView' => 'view/widget/hovercard/PhabricatorHovercardView.php', 'PhabricatorHunksManagementMigrateWorkflow' => 'applications/differential/management/PhabricatorHunksManagementMigrateWorkflow.php', 'PhabricatorHunksManagementWorkflow' => 'applications/differential/management/PhabricatorHunksManagementWorkflow.php', 'PhabricatorIDsSearchEngineExtension' => 'applications/search/engineextension/PhabricatorIDsSearchEngineExtension.php', @@ -2735,7 +2738,6 @@ phutil_register_library_map(array( 'PhabricatorPeopleDisableController' => 'applications/people/controller/PhabricatorPeopleDisableController.php', 'PhabricatorPeopleEmpowerController' => 'applications/people/controller/PhabricatorPeopleEmpowerController.php', 'PhabricatorPeopleExternalPHIDType' => 'applications/people/phid/PhabricatorPeopleExternalPHIDType.php', - 'PhabricatorPeopleHovercardEngineExtension' => 'applications/people/engineextension/PhabricatorPeopleHovercardEngineExtension.php', 'PhabricatorPeopleIconSet' => 'applications/people/icon/PhabricatorPeopleIconSet.php', 'PhabricatorPeopleInviteController' => 'applications/people/controller/PhabricatorPeopleInviteController.php', 'PhabricatorPeopleInviteListController' => 'applications/people/controller/PhabricatorPeopleInviteListController.php', @@ -2860,6 +2862,7 @@ phutil_register_library_map(array( 'PhabricatorProjectBoardImportController' => 'applications/project/controller/PhabricatorProjectBoardImportController.php', 'PhabricatorProjectBoardReorderController' => 'applications/project/controller/PhabricatorProjectBoardReorderController.php', 'PhabricatorProjectBoardViewController' => 'applications/project/controller/PhabricatorProjectBoardViewController.php', + 'PhabricatorProjectCardView' => 'applications/project/view/PhabricatorProjectCardView.php', 'PhabricatorProjectColorsConfigOptionType' => 'applications/project/config/PhabricatorProjectColorsConfigOptionType.php', 'PhabricatorProjectColumn' => 'applications/project/storage/PhabricatorProjectColumn.php', 'PhabricatorProjectColumnDetailController' => 'applications/project/controller/PhabricatorProjectColumnDetailController.php', @@ -2913,7 +2916,6 @@ phutil_register_library_map(array( 'PhabricatorProjectMembersProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectMembersProfilePanel.php', 'PhabricatorProjectMembersRemoveController' => 'applications/project/controller/PhabricatorProjectMembersRemoveController.php', 'PhabricatorProjectMembersViewController' => 'applications/project/controller/PhabricatorProjectMembersViewController.php', - 'PhabricatorProjectMilestonesController' => 'applications/project/controller/PhabricatorProjectMilestonesController.php', 'PhabricatorProjectMoveController' => 'applications/project/controller/PhabricatorProjectMoveController.php', 'PhabricatorProjectNameContextFreeGrammar' => 'applications/project/lipsum/PhabricatorProjectNameContextFreeGrammar.php', 'PhabricatorProjectNoProjectsDatasource' => 'applications/project/typeahead/PhabricatorProjectNoProjectsDatasource.php', @@ -2937,7 +2939,9 @@ phutil_register_library_map(array( 'PhabricatorProjectSlug' => 'applications/project/storage/PhabricatorProjectSlug.php', 'PhabricatorProjectStandardCustomField' => 'applications/project/customfield/PhabricatorProjectStandardCustomField.php', 'PhabricatorProjectStatus' => 'applications/project/constants/PhabricatorProjectStatus.php', + 'PhabricatorProjectSubprojectWarningController' => 'applications/project/controller/PhabricatorProjectSubprojectWarningController.php', 'PhabricatorProjectSubprojectsController' => 'applications/project/controller/PhabricatorProjectSubprojectsController.php', + 'PhabricatorProjectSubprojectsProfilePanel' => 'applications/project/profilepanel/PhabricatorProjectSubprojectsProfilePanel.php', 'PhabricatorProjectTestDataGenerator' => 'applications/project/lipsum/PhabricatorProjectTestDataGenerator.php', 'PhabricatorProjectTransaction' => 'applications/project/storage/PhabricatorProjectTransaction.php', 'PhabricatorProjectTransactionEditor' => 'applications/project/editor/PhabricatorProjectTransactionEditor.php', @@ -3813,6 +3817,7 @@ phutil_register_library_map(array( 'ProjectDefaultJoinCapability' => 'applications/project/capability/ProjectDefaultJoinCapability.php', 'ProjectDefaultViewCapability' => 'applications/project/capability/ProjectDefaultViewCapability.php', 'ProjectEditConduitAPIMethod' => 'applications/project/conduit/ProjectEditConduitAPIMethod.php', + 'ProjectHovercardEngineExtension' => 'applications/project/events/ProjectHovercardEngineExtension.php', 'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php', 'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php', 'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php', @@ -5668,6 +5673,8 @@ phutil_register_library_map(array( 'PHUIHandleTagListView' => 'AphrontTagView', 'PHUIHandleView' => 'AphrontView', 'PHUIHeaderView' => 'AphrontTagView', + 'PHUIHovercardUIExample' => 'PhabricatorUIExample', + 'PHUIHovercardView' => 'AphrontTagView', 'PHUIIconCircleView' => 'AphrontTagView', 'PHUIIconExample' => 'PhabricatorUIExample', 'PHUIIconView' => 'AphrontTagView', @@ -5769,6 +5776,7 @@ phutil_register_library_map(array( 'PasteSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PeopleBrowseUserDirectoryCapability' => 'PhabricatorPolicyCapability', 'PeopleCreateUsersCapability' => 'PhabricatorPolicyCapability', + 'PeopleHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'PeopleUserLogGarbageCollector' => 'PhabricatorGarbageCollector', 'Phabricator404Controller' => 'PhabricatorController', 'PhabricatorAWSConfigOptions' => 'PhabricatorApplicationConfigOptions', @@ -6034,6 +6042,7 @@ phutil_register_library_map(array( 'PhabricatorBcryptPasswordHasher' => 'PhabricatorPasswordHasher', 'PhabricatorBinariesSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorBitbucketAuthProvider' => 'PhabricatorOAuth1AuthProvider', + 'PhabricatorBoardLayoutEngine' => 'Phobject', 'PhabricatorBot' => 'PhabricatorDaemon', 'PhabricatorBotChannel' => 'PhabricatorBotTarget', 'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler', @@ -6716,8 +6725,6 @@ phutil_register_library_map(array( 'PhabricatorHomeQuickCreateController' => 'PhabricatorHomeController', 'PhabricatorHovercardEngineExtension' => 'Phobject', 'PhabricatorHovercardEngineExtensionModule' => 'PhabricatorConfigModule', - 'PhabricatorHovercardUIExample' => 'PhabricatorUIExample', - 'PhabricatorHovercardView' => 'AphrontView', 'PhabricatorHunksManagementMigrateWorkflow' => 'PhabricatorHunksManagementWorkflow', 'PhabricatorHunksManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorIDsSearchEngineExtension' => 'PhabricatorSearchEngineExtension', @@ -7108,7 +7115,6 @@ phutil_register_library_map(array( 'PhabricatorPeopleDisableController' => 'PhabricatorPeopleController', 'PhabricatorPeopleEmpowerController' => 'PhabricatorPeopleController', 'PhabricatorPeopleExternalPHIDType' => 'PhabricatorPHIDType', - 'PhabricatorPeopleHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'PhabricatorPeopleIconSet' => 'PhabricatorIconSet', 'PhabricatorPeopleInviteController' => 'PhabricatorPeopleController', 'PhabricatorPeopleInviteListController' => 'PhabricatorPeopleInviteController', @@ -7259,6 +7265,7 @@ phutil_register_library_map(array( 'PhabricatorDestructibleInterface', 'PhabricatorFulltextInterface', 'PhabricatorConduitResultInterface', + 'PhabricatorColumnProxyInterface', ), 'PhabricatorProjectAddHeraldAction' => 'PhabricatorProjectHeraldAction', 'PhabricatorProjectApplication' => 'PhabricatorApplication', @@ -7267,6 +7274,7 @@ phutil_register_library_map(array( 'PhabricatorProjectBoardImportController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectBoardReorderController' => 'PhabricatorProjectBoardController', 'PhabricatorProjectBoardViewController' => 'PhabricatorProjectBoardController', + 'PhabricatorProjectCardView' => 'AphrontTagView', 'PhabricatorProjectColorsConfigOptionType' => 'PhabricatorConfigJSONOptionType', 'PhabricatorProjectColumn' => array( 'PhabricatorProjectDAO', @@ -7330,7 +7338,6 @@ phutil_register_library_map(array( 'PhabricatorProjectMembersProfilePanel' => 'PhabricatorProfilePanel', 'PhabricatorProjectMembersRemoveController' => 'PhabricatorProjectController', 'PhabricatorProjectMembersViewController' => 'PhabricatorProjectController', - 'PhabricatorProjectMilestonesController' => 'PhabricatorProjectController', 'PhabricatorProjectMoveController' => 'PhabricatorProjectController', 'PhabricatorProjectNameContextFreeGrammar' => 'PhutilContextFreeGrammar', 'PhabricatorProjectNoProjectsDatasource' => 'PhabricatorTypeaheadDatasource', @@ -7357,7 +7364,9 @@ phutil_register_library_map(array( 'PhabricatorStandardCustomFieldInterface', ), 'PhabricatorProjectStatus' => 'Phobject', + 'PhabricatorProjectSubprojectWarningController' => 'PhabricatorProjectController', 'PhabricatorProjectSubprojectsController' => 'PhabricatorProjectController', + 'PhabricatorProjectSubprojectsProfilePanel' => 'PhabricatorProfilePanel', 'PhabricatorProjectTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorProjectTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorProjectTransactionEditor' => 'PhabricatorApplicationTransactionEditor', @@ -8443,6 +8452,7 @@ phutil_register_library_map(array( 'ProjectDefaultJoinCapability' => 'PhabricatorPolicyCapability', 'ProjectDefaultViewCapability' => 'PhabricatorPolicyCapability', 'ProjectEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', + 'ProjectHovercardEngineExtension' => 'PhabricatorHovercardEngineExtension', 'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod', 'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase', diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php index e88a5a5e36..f8b01eb94c 100644 --- a/src/aphront/AphrontRequest.php +++ b/src/aphront/AphrontRequest.php @@ -542,8 +542,12 @@ final class AphrontRequest extends Phobject { return $this->isFormPost() && $this->getStr('__dialog__'); } - public function getRemoteAddr() { - return $_SERVER['REMOTE_ADDR']; + public function getRemoteAddress() { + $address = $_SERVER['REMOTE_ADDR']; + if (!strlen($address)) { + return null; + } + return substr($address, 0, 64); } public function isHTTPS() { diff --git a/src/applications/celerity/CelerityResourceTransformer.php b/src/applications/celerity/CelerityResourceTransformer.php index ee4685f4f7..6d86a8806a 100644 --- a/src/applications/celerity/CelerityResourceTransformer.php +++ b/src/applications/celerity/CelerityResourceTransformer.php @@ -66,7 +66,7 @@ final class CelerityResourceTransformer extends Phobject { return $data; } - // Some resources won't survive minification (like Raphael.js), and are + // Some resources won't survive minification (like d3.min.js), and are // marked so as not to be minified. if (strpos($data, '@'.'do-not-minify') !== false) { return $data; diff --git a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php index 5b48fcbd03..740402524a 100644 --- a/src/applications/config/schema/PhabricatorConfigSchemaSpec.php +++ b/src/applications/config/schema/PhabricatorConfigSchemaSpec.php @@ -322,6 +322,7 @@ abstract class PhabricatorConfigSchemaSpec extends Phobject { case 'phid': case 'policy'; case 'hashpath64': + case 'ipaddress': $column_type = 'varbinary(64)'; break; case 'bytes64': diff --git a/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php b/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php index 325e99f860..d0bd5917dc 100644 --- a/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php +++ b/src/applications/differential/engineextension/DifferentialHovercardEngineExtension.php @@ -35,7 +35,7 @@ final class DifferentialHovercardEngineExtension } public function renderHovercard( - PhabricatorHovercardView $hovercard, + PHUIHovercardView $hovercard, PhabricatorObjectHandle $handle, $object, $data) { diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index e5d2f7bdfa..a2e9a3a4b3 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -1180,7 +1180,7 @@ final class DiffusionBrowseController extends DiffusionController { if ($this->coverage) { require_celerity_resource('differential-changeset-view-css'); - $cov_index = $line['line'] - 1; + $cov_index = $line_index; if (isset($this->coverage[$cov_index])) { $cov_class = $this->coverage[$cov_index]; diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php index 13290b9f41..8f3eb364f6 100644 --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -76,8 +76,7 @@ final class DiffusionServeController extends DiffusionController { } try { - $remote_addr = $request->getRemoteAddr(); - $remote_addr = ip2long($remote_addr); + $remote_addr = $request->getRemoteAddress(); $pull_event = id(new PhabricatorRepositoryPullEvent()) ->setEpoch(PhabricatorTime::getNow()) @@ -720,11 +719,11 @@ final class DiffusionServeController extends DiffusionController { } private function getCommonEnvironment(PhabricatorUser $viewer) { - $remote_addr = $this->getRequest()->getRemoteAddr(); + $remote_address = $this->getRequest()->getRemoteAddress(); return array( DiffusionCommitHookEngine::ENV_USER => $viewer->getUsername(), - DiffusionCommitHookEngine::ENV_REMOTE_ADDRESS => $remote_addr, + DiffusionCommitHookEngine::ENV_REMOTE_ADDRESS => $remote_address, DiffusionCommitHookEngine::ENV_REMOTE_PROTOCOL => 'http', ); } diff --git a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php index a723591212..741b21bd19 100644 --- a/src/applications/diffusion/engine/DiffusionCommitHookEngine.php +++ b/src/applications/diffusion/engine/DiffusionCommitHookEngine.php @@ -56,15 +56,6 @@ final class DiffusionCommitHookEngine extends Phobject { return $this->remoteAddress; } - private function getRemoteAddressForLog() { - // If whatever we have here isn't a valid IPv4 address, just store `null`. - // Older versions of PHP return `-1` on failure instead of `false`. - $remote_address = $this->getRemoteAddress(); - $remote_address = max(0, ip2long($remote_address)); - $remote_address = nonempty($remote_address, null); - return $remote_address; - } - public function setSubversionTransactionInfo($transaction, $repository) { $this->subversionTransaction = $transaction; $this->subversionRepository = $repository; @@ -1078,7 +1069,7 @@ final class DiffusionCommitHookEngine extends Phobject { $viewer = $this->getViewer(); return PhabricatorRepositoryPushEvent::initializeNewEvent($viewer) ->setRepositoryPHID($this->getRepository()->getPHID()) - ->setRemoteAddress($this->getRemoteAddressForLog()) + ->setRemoteAddress($this->getRemoteAddress()) ->setRemoteProtocol($this->getRemoteProtocol()) ->setEpoch(time()); } diff --git a/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php b/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php index 64caee879f..0711028796 100644 --- a/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php +++ b/src/applications/diffusion/engineextension/DiffusionHovercardEngineExtension.php @@ -19,7 +19,7 @@ final class DiffusionHovercardEngineExtension } public function renderHovercard( - PhabricatorHovercardView $hovercard, + PHUIHovercardView $hovercard, PhabricatorObjectHandle $handle, $commit, $data) { diff --git a/src/applications/diffusion/typeahead/DiffusionSymbolDatasource.php b/src/applications/diffusion/typeahead/DiffusionSymbolDatasource.php index 1c7e463410..1c29c0dde4 100644 --- a/src/applications/diffusion/typeahead/DiffusionSymbolDatasource.php +++ b/src/applications/diffusion/typeahead/DiffusionSymbolDatasource.php @@ -47,7 +47,10 @@ final class DiffusionSymbolDatasource ->setPHID(md5($symbol->getURI())) // Just needs to be unique. ->setDisplayName($name) ->setDisplayType(strtoupper($lang).' '.ucwords($type).' ('.$repo.')') - ->setPriorityType('symb'); + ->setPriorityType('symb') + ->setImageSprite( + 'phabricator-search-icon phui-font-fa phui-icon-view fa-code '. + 'lightgreytext'); } } diff --git a/src/applications/diffusion/view/DiffusionPushLogListView.php b/src/applications/diffusion/view/DiffusionPushLogListView.php index 860320e624..7cc02a49b4 100644 --- a/src/applications/diffusion/view/DiffusionPushLogListView.php +++ b/src/applications/diffusion/view/DiffusionPushLogListView.php @@ -42,12 +42,9 @@ final class DiffusionPushLogListView extends AphrontView { $repository = $log->getRepository(); // Reveal this if it's valid and the user can edit the repository. - $remote_addr = '-'; + $remote_address = '-'; if (isset($editable_repos[$log->getRepositoryPHID()])) { - $remote_long = $log->getPushEvent()->getRemoteAddress(); - if ($remote_long) { - $remote_addr = long2ip($remote_long); - } + $remote_address = $log->getPushEvent()->getRemoteAddress(); } $event_id = $log->getPushEvent()->getID(); @@ -76,7 +73,7 @@ final class DiffusionPushLogListView extends AphrontView { ), $repository->getDisplayName()), $handles[$log->getPusherPHID()]->renderLink(), - $remote_addr, + $remote_address, $log->getPushEvent()->getRemoteProtocol(), $log->getRefType(), $log->getRefName(), diff --git a/src/applications/fact/controller/PhabricatorFactChartController.php b/src/applications/fact/controller/PhabricatorFactChartController.php index d6f3bc0c43..18f769cc47 100644 --- a/src/applications/fact/controller/PhabricatorFactChartController.php +++ b/src/applications/fact/controller/PhabricatorFactChartController.php @@ -30,8 +30,7 @@ final class PhabricatorFactChartController extends PhabricatorFactController { } if (!$points) { - // NOTE: Raphael crashes Safari if you hand it series with no points. - throw new Exception(pht('No data to show!')); + throw new Exception('No data to show!'); } // Limit amount of data passed to browser. @@ -56,16 +55,12 @@ final class PhabricatorFactChartController extends PhabricatorFactController { 'div', array( 'id' => $id, - 'style' => 'border: 1px solid #6f6f6f; '. - 'margin: 1em 2em; '. - 'background: #ffffff; '. - 'height: 400px; ', + 'style' => 'background: #ffffff; '. + 'height: 480px; ', ), ''); - require_celerity_resource('raphael-core'); - require_celerity_resource('raphael-g'); - require_celerity_resource('raphael-g-line'); + require_celerity_resource('d3'); Javelin::initBehavior('line-chart', array( 'hardpoint' => $id, @@ -75,9 +70,9 @@ final class PhabricatorFactChartController extends PhabricatorFactController { 'colors' => array('#0000ff'), )); - $panel = new PHUIObjectBoxView(); - $panel->setHeaderText(pht('Count of %s', $spec->getName())); - $panel->appendChild($chart); + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Count of %s', $spec->getName())) + ->appendChild($chart); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Chart')); @@ -85,7 +80,7 @@ final class PhabricatorFactChartController extends PhabricatorFactController { return $this->buildApplicationPage( array( $crumbs, - $panel, + $box, ), array( 'title' => pht('Chart'), diff --git a/src/applications/files/builtin/PhabricatorFilesComposeIconBuiltinFile.php b/src/applications/files/builtin/PhabricatorFilesComposeIconBuiltinFile.php index 9025a0b53d..0bb56490cc 100644 --- a/src/applications/files/builtin/PhabricatorFilesComposeIconBuiltinFile.php +++ b/src/applications/files/builtin/PhabricatorFilesComposeIconBuiltinFile.php @@ -179,6 +179,7 @@ final class PhabricatorFilesComposeIconBuiltinFile 'fa-legal' => pht('Hired Protection'), 'fa-linux' => pht('M\'Lady'), 'fa-lock' => pht('Policy'), + 'fa-map-marker' => pht('Destination Beacon'), 'fa-microphone' => pht('Podcasting'), 'fa-mobile' => pht('Tiny Pocket Cat Meme Machine'), 'fa-money' => pht('1 of 99 Problems'), diff --git a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php index 74b8e32389..086fd3345e 100644 --- a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php +++ b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php @@ -92,21 +92,27 @@ abstract class PhabricatorFileUploadSource $threshold = PhabricatorFileStorageEngine::getChunkThreshold(); - // If we don't know how large the file is, we're going to read some data - // from it until we know whether it's a small file or not. This will give - // us enough information to make a decision about chunking. - $length = $this->getDataLength(); - if ($length === null) { - $rope = $this->getRope(); - while ($this->readFileData()) { - $length = $rope->getByteLength(); - if ($length > $threshold) { - break; + if ($threshold === null) { + // If there are no chunk engines available, we clearly can't chunk the + // file. + $this->shouldChunk = false; + } else { + // If we don't know how large the file is, we're going to read some data + // from it until we know whether it's a small file or not. This will give + // us enough information to make a decision about chunking. + $length = $this->getDataLength(); + if ($length === null) { + $rope = $this->getRope(); + while ($this->readFileData()) { + $length = $rope->getByteLength(); + if ($length > $threshold) { + break; + } } } - } - $this->shouldChunk = ($length > $threshold); + $this->shouldChunk = ($length > $threshold); + } return $this->shouldChunk; } diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index 6c2ce9fec3..c7f0cf2186 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -290,9 +290,8 @@ final class ManiphestReportController extends ManiphestController { list($burn_x, $burn_y) = $this->buildSeries($data); - require_celerity_resource('raphael-core'); - require_celerity_resource('raphael-g'); - require_celerity_resource('raphael-g-line'); + require_celerity_resource('d3'); + require_celerity_resource('phui-chart-css'); Javelin::initBehavior('line-chart', array( 'hardpoint' => $id, @@ -306,7 +305,11 @@ final class ManiphestReportController extends ManiphestController { 'yformat' => 'int', )); - return array($filter, $chart, $panel); + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Burnup Rate')) + ->appendChild($chart); + + return array($filter, $box, $panel); } private function renderReportFilters(array $tokens, $has_window) { diff --git a/src/applications/maniphest/editor/ManiphestEditEngine.php b/src/applications/maniphest/editor/ManiphestEditEngine.php index b7e3cee965..3f88a9abe3 100644 --- a/src/applications/maniphest/editor/ManiphestEditEngine.php +++ b/src/applications/maniphest/editor/ManiphestEditEngine.php @@ -280,20 +280,35 @@ final class ManiphestEditEngine return new Aphront404Response(); } - // If the workboard's project has been removed from the card's project - // list, we are going to remove it from the board completely. + // If the workboard's project and all descendant projects have been removed + // from the card's project list, we are going to remove it from the board + // completely. + + // TODO: If the user did something sneaky and changed a subproject, we'll + // currently leave the card where it was but should really move it to the + // proper new column. + + $descendant_projects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withAncestorProjectPHIDs(array($column->getProjectPHID())) + ->execute(); + $board_phids = mpull($descendant_projects, 'getPHID', 'getPHID'); + $board_phids[$column->getProjectPHID()] = $column->getProjectPHID(); + $project_map = array_fuse($task->getProjectPHIDs()); - $remove_card = empty($project_map[$column->getProjectPHID()]); + $remove_card = !array_intersect_key($board_phids, $project_map); $positions = id(new PhabricatorProjectColumnPositionQuery()) ->setViewer($viewer) - ->withColumns(array($column)) + ->withBoardPHIDs(array($column->getProjectPHID())) + ->withColumnPHIDs(array($column->getPHID())) ->execute(); $task_phids = mpull($positions, 'getObjectPHID'); $column_tasks = id(new ManiphestTaskQuery()) ->setViewer($viewer) ->withPHIDs($task_phids) + ->needProjectPHIDs(true) ->execute(); if ($order == PhabricatorProjectColumn::ORDER_NATURAL) { @@ -329,13 +344,23 @@ final class ManiphestEditEngine ->executeOne(); } + $handle_phids = $task->getProjectPHIDs(); + $handle_phids = array_fuse($handle_phids); + $handle_phids = array_diff_key($handle_phids, $board_phids); + + $project_handles = $viewer->loadHandles($handle_phids); + $project_handles = iterator_to_array($project_handles); + $tasks = id(new ProjectBoardTaskCard()) ->setViewer($viewer) ->setTask($task) ->setOwner($owner) + ->setProjectHandles($project_handles) ->setCanEdit(true) ->getItem(); + $tasks->addClass('phui-workcard'); + $payload = array( 'tasks' => $tasks, 'data' => $data, diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php index 72979d3a4e..46e2a617e8 100644 --- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php +++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php @@ -200,18 +200,6 @@ final class ManiphestTransactionEditor 'columnPHIDs')); } - $columns = id(new PhabricatorProjectColumnQuery()) - ->setViewer($this->requireActor()) - ->withPHIDs($new_phids) - ->execute(); - $columns = mpull($columns, null, 'getPHID'); - - $positions = id(new PhabricatorProjectColumnPositionQuery()) - ->setViewer($this->requireActor()) - ->withObjectPHIDs(array($object->getPHID())) - ->withBoardPHIDs(array($board_phid)) - ->execute(); - $before_phid = idx($xaction->getNewValue(), 'beforePHID'); $after_phid = idx($xaction->getNewValue(), 'afterPHID'); @@ -227,111 +215,86 @@ final class ManiphestTransactionEditor // object's position in the "natural" ordering, so we do need to update // some rows. + $object_phid = $object->getPHID(); + + // We're doing layout with the ominpotent viewer to make sure we don't + // remove positions in columns that exist, but which the actual actor + // can't see. + $omnipotent_viewer = PhabricatorUser::getOmnipotentUser(); + + $select_phids = array($board_phid); + + $descendants = id(new PhabricatorProjectQuery()) + ->setViewer($omnipotent_viewer) + ->withAncestorProjectPHIDs($select_phids) + ->execute(); + foreach ($descendants as $descendant) { + $select_phids[] = $descendant->getPHID(); + } + + $board_tasks = id(new ManiphestTaskQuery()) + ->setViewer($omnipotent_viewer) + ->withEdgeLogicPHIDs( + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + PhabricatorQueryConstraint::OPERATOR_ANCESTOR, + array($select_phids)) + ->execute(); + + $object_phids = mpull($board_tasks, 'getPHID'); + $object_phids[] = $object_phid; + + $engine = id(new PhabricatorBoardLayoutEngine()) + ->setViewer($omnipotent_viewer) + ->setBoardPHIDs(array($board_phid)) + ->setObjectPHIDs($object_phids) + ->executeLayout(); + + // TODO: This logic needs to be revised if we legitimately support + // multiple column positions. + + // NOTE: When a task is newly created, it's implicitly added to the + // backlog but we don't currently record that in the "$old_phids". Just + // clean it up for now. + $columns = $engine->getObjectColumns($board_phid, $object_phid); + foreach ($columns as $column) { + $engine->queueRemovePosition( + $board_phid, + $column->getPHID(), + $object_phid); + } + // Remove all existing column positions on the board. - - foreach ($positions as $position) { - $position->delete(); + foreach ($old_phids as $column_phid) { + $engine->queueRemovePosition( + $board_phid, + $column_phid, + $object_phid); } - // Add the new column positions. - - foreach ($new_phids as $phid) { - $column = idx($columns, $phid); - if (!$column) { - throw new Exception( - pht('No such column "%s" exists!', $phid)); - } - - // Load the other object positions in the column. Note that we must - // skip implicit column creation to avoid generating a new position - // if the target column is a backlog column. - - $other_positions = id(new PhabricatorProjectColumnPositionQuery()) - ->setViewer($this->requireActor()) - ->withColumns(array($column)) - ->withBoardPHIDs(array($board_phid)) - ->setSkipImplicitCreate(true) - ->execute(); - $other_positions = msort($other_positions, 'getOrderingKey'); - - // Set up the new position object. We're going to figure out the - // right sequence number and then persist this object with that - // sequence number. - $new_position = id(new PhabricatorProjectColumnPosition()) - ->setBoardPHID($board_phid) - ->setColumnPHID($column->getPHID()) - ->setObjectPHID($object->getPHID()); - - $updates = array(); - $sequence = 0; - - // If we're just dropping this into the column without any specific - // position information, put it at the top. - if (!$before_phid && !$after_phid) { - $new_position->setSequence($sequence)->save(); - $sequence++; - } - - foreach ($other_positions as $position) { - $object_phid = $position->getObjectPHID(); - - // If this is the object we're moving before and we haven't - // saved yet, insert here. - if (($before_phid == $object_phid) && !$new_position->getID()) { - $new_position->setSequence($sequence)->save(); - $sequence++; - } - - // This object goes here in the sequence; we might need to update - // the row. - if ($sequence != $position->getSequence()) { - $updates[$position->getID()] = $sequence; - } - $sequence++; - - // If this is the object we're moving after and we haven't saved - // yet, insert here. - if (($after_phid == $object_phid) && !$new_position->getID()) { - $new_position->setSequence($sequence)->save(); - $sequence++; - } - } - - // We should have found a place to put it. - if (!$new_position->getID()) { - throw new Exception( - pht('Unable to find a place to insert object on column!')); - } - - // If we changed other objects' column positions, bulk reorder them. - - if ($updates) { - $position = new PhabricatorProjectColumnPosition(); - $conn_w = $position->establishConnection('w'); - - $pairs = array(); - foreach ($updates as $id => $sequence) { - // This is ugly because MySQL gets upset with us if it is - // configured strictly and we attempt inserts which can't work. - // We'll never actually do these inserts since they'll always - // collide (triggering the ON DUPLICATE KEY logic), so we just - // provide dummy values in order to get there. - - $pairs[] = qsprintf( - $conn_w, - '(%d, %d, "", "", "")', - $id, - $sequence); - } - - queryfx( - $conn_w, - 'INSERT INTO %T (id, sequence, boardPHID, columnPHID, objectPHID) - VALUES %Q ON DUPLICATE KEY UPDATE sequence = VALUES(sequence)', - $position->getTableName(), - implode(', ', $pairs)); + // Add new positions. + foreach ($new_phids as $column_phid) { + if ($before_phid) { + $engine->queueAddPositionBefore( + $board_phid, + $column_phid, + $object_phid, + $before_phid); + } else if ($after_phid) { + $engine->queueAddPositionAfter( + $board_phid, + $column_phid, + $object_phid, + $after_phid); + } else { + $engine->queueAddPosition( + $board_phid, + $column_phid, + $object_phid); } } + + $engine->applyPositionUpdates(); + break; default: break; diff --git a/src/applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php b/src/applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php index 5215232a7d..c49c7b6201 100644 --- a/src/applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php +++ b/src/applications/maniphest/engineextension/ManiphestHovercardEngineExtension.php @@ -19,7 +19,7 @@ final class ManiphestHovercardEngineExtension } public function renderHovercard( - PhabricatorHovercardView $hovercard, + PHUIHovercardView $hovercard, PhabricatorObjectHandle $handle, $task, $data) { diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php index 1453ab7d71..45125c28b0 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -54,23 +54,21 @@ final class PhabricatorPeopleProfileViewController $feed = $this->buildPeopleFeed($user, $viewer); $feed = phutil_tag_div('project-view-feed', $feed); + $projects = $this->buildProjectsView($user); $badges = $this->buildBadgesView($user); - if ($badges) { - $columns = id(new PHUITwoColumnView()) - ->addClass('project-view-badges') - ->setMainColumn( - array( - $properties, - $feed, - )) - ->setSideColumn( - array( - $badges, - )); - } else { - $columns = array($properties, $feed); - } + $columns = id(new PHUITwoColumnView()) + ->addClass('project-view-badges') + ->setMainColumn( + array( + $properties, + $feed, + )) + ->setSideColumn( + array( + $projects, + $badges, + )); $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorPeopleProfilePanelEngine::PANEL_PROFILE); @@ -112,7 +110,7 @@ final class PhabricatorPeopleProfileViewController PhabricatorCustomField::ROLE_VIEW); $field_list->appendFieldsToPropertyList($user, $viewer, $view); - if ($view->isEmpty()) { + if (!$view->hasAnyProperties()) { return null; } @@ -124,39 +122,101 @@ final class PhabricatorPeopleProfileViewController return $view; } - private function buildBadgesView( + private function buildProjectsView( PhabricatorUser $user) { $viewer = $this->getViewer(); - $class = 'PhabricatorBadgesApplication'; - $box = null; + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withMemberPHIDs(array($user->getPHID())) + ->needImages(true) + ->withStatus(PhabricatorProjectQuery::STATUS_OPEN) + ->execute(); - if (PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) { - $badge_phids = $user->getBadgePHIDs(); - if ($badge_phids) { - $badges = id(new PhabricatorBadgesQuery()) - ->setViewer($viewer) - ->withPHIDs($badge_phids) - ->withStatuses(array(PhabricatorBadgesBadge::STATUS_ACTIVE)) - ->execute(); + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Projects')); - $flex = new PHUIBadgeBoxView(); - foreach ($badges as $badge) { - $item = id(new PHUIBadgeView()) - ->setIcon($badge->getIcon()) - ->setHeader($badge->getName()) - ->setSubhead($badge->getFlavor()) - ->setQuality($badge->getQuality()); - $flex->addItem($item); - } + if (!empty($projects)) { + $limit = 5; + $render_phids = array_slice($projects, 0, $limit); + $list = id(new PhabricatorProjectListView()) + ->setUser($viewer) + ->setProjects($render_phids); + + if (count($projects) > $limit) { + $header_text = pht( + 'Projects (%s)', + phutil_count($projects)); + + $header = id(new PHUIHeaderView()) + ->setHeader($header_text) + ->addActionLink( + id(new PHUIButtonView()) + ->setTag('a') + ->setIcon('fa-list-ul') + ->setText(pht('View All')) + ->setHref('/project/?member='.$user->getPHID())); - $box = id(new PHUIObjectBoxView()) - ->setHeaderText(pht('Badges')) - ->appendChild($flex) - ->setBackground(PHUIBoxView::GREY); } + + } else { + $error = id(new PHUIBoxView()) + ->addClass('mlb') + ->appendChild(pht('User does not belong to any projects.')); + $list = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NODATA) + ->appendChild($error); } + $box = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($list) + ->setBackground(PHUIBoxView::GREY); + + return $box; + } + + private function buildBadgesView(PhabricatorUser $user) { + + $viewer = $this->getViewer(); + $class = 'PhabricatorBadgesApplication'; + + if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) { + return null; + } + + $badge_phids = $user->getBadgePHIDs(); + if ($badge_phids) { + $badges = id(new PhabricatorBadgesQuery()) + ->setViewer($viewer) + ->withPHIDs($badge_phids) + ->withStatuses(array(PhabricatorBadgesBadge::STATUS_ACTIVE)) + ->execute(); + + $flex = new PHUIBadgeBoxView(); + foreach ($badges as $badge) { + $item = id(new PHUIBadgeView()) + ->setIcon($badge->getIcon()) + ->setHeader($badge->getName()) + ->setSubhead($badge->getFlavor()) + ->setQuality($badge->getQuality()); + $flex->addItem($item); + } + + } else { + $error = id(new PHUIBoxView()) + ->addClass('mlb') + ->appendChild(pht('User does not have any badges.')); + $flex = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NODATA) + ->appendChild($error); + } + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Badges')) + ->appendChild($flex) + ->setBackground(PHUIBoxView::GREY); + return $box; } diff --git a/src/applications/people/customfield/PhabricatorUserBlurbField.php b/src/applications/people/customfield/PhabricatorUserBlurbField.php index 3221a33202..04fb419918 100644 --- a/src/applications/people/customfield/PhabricatorUserBlurbField.php +++ b/src/applications/people/customfield/PhabricatorUserBlurbField.php @@ -58,6 +58,13 @@ final class PhabricatorUserBlurbField ->setLabel($this->getFieldName()); } + public function getApplicationTransactionRemarkupBlocks( + PhabricatorApplicationTransaction $xaction) { + return array( + $xaction->getNewValue(), + ); + } + public function renderPropertyViewLabel() { return null; } @@ -67,10 +74,11 @@ final class PhabricatorUserBlurbField if (!strlen($blurb)) { return null; } - return PhabricatorMarkupEngine::renderOneObject( - id(new PhabricatorMarkupOneOff())->setContent($blurb), - 'default', - $this->getViewer()); + + $viewer = $this->getViewer(); + $view = new PHUIRemarkupView($viewer, $blurb); + + return $view; } public function getStyleForPropertyView() { diff --git a/src/applications/people/engineextension/PhabricatorPeopleHovercardEngineExtension.php b/src/applications/people/engineextension/PeopleHovercardEngineExtension.php similarity index 96% rename from src/applications/people/engineextension/PhabricatorPeopleHovercardEngineExtension.php rename to src/applications/people/engineextension/PeopleHovercardEngineExtension.php index 8ce8c25a49..e986ec87f7 100644 --- a/src/applications/people/engineextension/PhabricatorPeopleHovercardEngineExtension.php +++ b/src/applications/people/engineextension/PeopleHovercardEngineExtension.php @@ -1,6 +1,6 @@ 'fa-heart', 'name' => pht('Resources'), ), + array( + 'key' => 'camera', + 'icon' => 'fa-camera-retro', + 'name' => pht('Design'), + ), + array( + 'key' => 'music', + 'icon' => 'fa-headphones', + 'name' => pht('Musician'), + ), + array( + 'key' => 'spy', + 'icon' => 'fa-user-secret', + 'name' => pht('Spy'), + ), + array( + 'key' => 'android', + 'icon' => 'fa-android', + 'name' => pht('Bot'), + ), array( 'key' => 'relationships', 'icon' => 'fa-glass', diff --git a/src/applications/people/markup/PhabricatorMentionRemarkupRule.php b/src/applications/people/markup/PhabricatorMentionRemarkupRule.php index eb224aae48..42efb05603 100644 --- a/src/applications/people/markup/PhabricatorMentionRemarkupRule.php +++ b/src/applications/people/markup/PhabricatorMentionRemarkupRule.php @@ -93,7 +93,7 @@ final class PhabricatorMentionRemarkupRule extends PhutilRemarkupRule { if ($exists) { $user = $actual_users[$username]; - Javelin::initBehavior('phabricator-hovercards'); + Javelin::initBehavior('phui-hovercards'); // Check if the user has view access to the object she was mentioned in if ($context_object diff --git a/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php b/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php index ad452d5f02..40159d1cc2 100644 --- a/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php +++ b/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php @@ -54,8 +54,10 @@ final class PhabricatorPeopleUserPHIDType extends PhabricatorPHIDType { $icon_icon = PhabricatorPeopleIconSet::getIconIcon($icon_key); $subtitle = $profile->getDisplayTitle(); - $handle->setIcon($icon_icon); - $handle->setSubtitle($subtitle); + $handle + ->setIcon($icon_icon) + ->setSubtitle($subtitle) + ->setTokenIcon('fa-user'); } $availability = null; diff --git a/src/applications/people/view/PhabricatorUserLogView.php b/src/applications/people/view/PhabricatorUserLogView.php index 12bcee9d76..c467a9010d 100644 --- a/src/applications/people/view/PhabricatorUserLogView.php +++ b/src/applications/people/view/PhabricatorUserLogView.php @@ -41,13 +41,13 @@ final class PhabricatorUserLogView extends AphrontView { $ip = phutil_tag( 'a', array( - 'href' => $base_uri.'?ip='.$log->getRemoteAddr().'#R', + 'href' => $base_uri.'?ip='.$ip.'#R', ), $ip); $session = phutil_tag( 'a', array( - 'href' => $base_uri.'?sessions='.$log->getSession().'#R', + 'href' => $base_uri.'?sessions='.$ip.'#R', ), $session); } diff --git a/src/applications/phame/controller/PhameHomeController.php b/src/applications/phame/controller/PhameHomeController.php index b9c7fc76d8..cea86735b5 100644 --- a/src/applications/phame/controller/PhameHomeController.php +++ b/src/applications/phame/controller/PhameHomeController.php @@ -55,20 +55,17 @@ final class PhameHomeController extends PhamePostController { ->addAction($create_button); } - $actions = $this->renderActions($viewer); - $action_button = id(new PHUIButtonView()) + $view_all = id(new PHUIButtonView()) ->setTag('a') - ->setText(pht('Actions')) - ->setHref('#') - ->setIcon('fa-bars') - ->addClass('phui-mobile-menu') - ->setDropdownMenu($actions); + ->setText(pht('View All')) + ->setHref($this->getApplicationURI('post/')) + ->setIcon('fa-list-ul'); $title = pht('Recent Posts'); $header = id(new PHUIHeaderView()) ->setHeader($title) - ->addActionLink($action_button); + ->addActionLink($view_all); $crumbs = $this->buildApplicationCrumbs(); $crumbs->setBorder(true); @@ -108,39 +105,21 @@ final class PhameHomeController extends PhamePostController { $blog_list, $draft_list, )) - ->setDisplay(PHUITwoColumnView::DISPLAY_LEFT) - ->addClass('phame-home-view'); + ->addClass('phame-home-container'); + + $phame_home = phutil_tag_div('phame-home-view', $phame_view); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $phame_view, + $phame_home, )); } - private function renderActions($viewer) { - $actions = id(new PhabricatorActionListView()) - ->setUser($viewer); - - $actions->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-pencil') - ->setHref($this->getApplicationURI('post/query/draft/')) - ->setName(pht('My Drafts'))); - - $actions->addAction( - id(new PhabricatorActionView()) - ->setIcon('fa-pencil-square-o') - ->setHref($this->getApplicationURI('post/')) - ->setName(pht('All Posts'))); - - return $actions; - } - private function renderBlogs($viewer, $blogs) {} protected function buildApplicationCrumbs() { diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php index 68c9f22082..537bfbb713 100644 --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -101,9 +101,18 @@ final class PhamePostViewController $subtitle = pht('Written by %s on %s.', $author, $date); } + $user_icon = $blogger_profile->getIcon(); + $user_icon = PhabricatorPeopleIconSet::getIconIcon($user_icon); + $user_icon = id(new PHUIIconView())->setIcon($user_icon); + $about = id(new PhameDescriptionView()) ->setTitle($subtitle) - ->setDescription($blogger_profile->getTitle()) + ->setDescription( + array( + $user_icon, + ' ', + $blogger_profile->getTitle(), + )) ->setImage($blogger->getProfileImageURI()) ->setImageHref('/p/'.$blogger->getUsername()); diff --git a/src/applications/phid/PhabricatorObjectHandle.php b/src/applications/phid/PhabricatorObjectHandle.php index e49094e311..2b626f2e5d 100644 --- a/src/applications/phid/PhabricatorObjectHandle.php +++ b/src/applications/phid/PhabricatorObjectHandle.php @@ -28,6 +28,7 @@ final class PhabricatorObjectHandle private $objectName; private $policyFiltered; private $subtitle; + private $tokenIcon; public function setIcon($icon) { $this->icon = $icon; @@ -86,6 +87,19 @@ final class PhabricatorObjectHandle return null; } + public function setTokenIcon($icon) { + $this->tokenIcon = $icon; + return $this; + } + + public function getTokenIcon() { + if ($this->tokenIcon !== null) { + return $this->tokenIcon; + } + + return $this->getIcon(); + } + public function getTypeIcon() { if ($this->getPHIDType()) { return $this->getPHIDType()->getTypeIcon(); @@ -262,7 +276,7 @@ final class PhabricatorObjectHandle } public function renderHovercardLink($name = null) { - Javelin::initBehavior('phabricator-hovercards'); + Javelin::initBehavior('phui-hovercards'); $attributes = array( 'sigil' => 'hovercard', diff --git a/src/applications/phid/view/PHUIHandleTagListView.php b/src/applications/phid/view/PHUIHandleTagListView.php index 73cbef2350..0bbfc4d249 100644 --- a/src/applications/phid/view/PHUIHandleTagListView.php +++ b/src/applications/phid/view/PHUIHandleTagListView.php @@ -9,7 +9,7 @@ final class PHUIHandleTagListView extends AphrontTagView { private $slim; private $showHovercards; - public function setHandles(array $handles) { + public function setHandles($handles) { $this->handles = $handles; return $this; } diff --git a/src/applications/policy/controller/PhabricatorPolicyEditController.php b/src/applications/policy/controller/PhabricatorPolicyEditController.php index be380aa0b1..3dd8924bb6 100644 --- a/src/applications/policy/controller/PhabricatorPolicyEditController.php +++ b/src/applications/policy/controller/PhabricatorPolicyEditController.php @@ -6,7 +6,6 @@ final class PhabricatorPolicyEditController public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); - $object_phid = $request->getURIData('objectPHID'); if ($object_phid) { $object = id(new PhabricatorObjectQuery()) @@ -32,6 +31,17 @@ final class PhabricatorPolicyEditController } } + $phid = $request->getURIData('phid'); + switch ($phid) { + case AphrontFormPolicyControl::getSelectProjectKey(): + return $this->handleProjectRequest($request); + case AphrontFormPolicyControl::getSelectCustomKey(): + $phid = null; + break; + default: + break; + } + $action_options = array( PhabricatorPolicy::ACTION_ALLOW => pht('Allow'), PhabricatorPolicy::ACTION_DENY => pht('Deny'), @@ -55,7 +65,6 @@ final class PhabricatorPolicyEditController 'value' => null, ); - $phid = $request->getURIData('phid'); if ($phid) { $policies = id(new PhabricatorPolicyQuery()) ->setViewer($viewer) @@ -253,4 +262,79 @@ final class PhabricatorPolicyEditController return id(new AphrontDialogResponse())->setDialog($dialog); } + private function handleProjectRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); + + $errors = array(); + $e_project = true; + + if ($request->isFormPost()) { + $project_phids = $request->getArr('projectPHIDs'); + $project_phid = head($project_phids); + + $project = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($project_phid)) + ->executeOne(); + + if ($project) { + // Save this project as one of the user's most recently used projects, + // so we'll show it by default in future menus. + + $pref_key = PhabricatorUserPreferences::PREFERENCE_FAVORITE_POLICIES; + + $preferences = $viewer->loadPreferences(); + $favorites = $preferences->getPreference($pref_key); + if (!is_array($favorites)) { + $favorites = array(); + } + + // Add this, or move it to the end of the list. + unset($favorites[$project_phid]); + $favorites[$project_phid] = true; + + $preferences->setPreference($pref_key, $favorites); + $preferences->save(); + + $data = array( + 'phid' => $project->getPHID(), + 'info' => array( + 'name' => $project->getName(), + 'full' => $project->getName(), + 'icon' => $project->getDisplayIconIcon(), + ), + ); + + return id(new AphrontAjaxResponse())->setContent($data); + } else { + $errors[] = pht('You must choose a project.'); + $e_project = pht('Required'); + } + } + + $project_datasource = id(new PhabricatorProjectDatasource()) + ->setParameters( + array( + 'policy' => 1, + )); + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setLabel(pht('Members Of')) + ->setName('projectPHIDs') + ->setLimit(1) + ->setError($e_project) + ->setDatasource($project_datasource)); + + return $this->newDialog() + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->setErrors($errors) + ->setTitle(pht('Select Project')) + ->appendForm($form) + ->addSubmitButton(pht('Done')) + ->addCancelButton('#'); + } + } diff --git a/src/applications/policy/query/PhabricatorPolicyQuery.php b/src/applications/policy/query/PhabricatorPolicyQuery.php index 7ad2630503..df1d6fb0b8 100644 --- a/src/applications/policy/query/PhabricatorPolicyQuery.php +++ b/src/applications/policy/query/PhabricatorPolicyQuery.php @@ -195,10 +195,48 @@ final class PhabricatorPolicyQuery $viewer = $this->getViewer(); if ($viewer->getPHID()) { - $projects = id(new PhabricatorProjectQuery()) - ->setViewer($viewer) - ->withMemberPHIDs(array($viewer->getPHID())) - ->execute(); + $pref_key = PhabricatorUserPreferences::PREFERENCE_FAVORITE_POLICIES; + + $favorite_limit = 10; + $default_limit = 5; + + // If possible, show the user's 10 most recently used projects. + $preferences = $viewer->loadPreferences(); + $favorites = $preferences->getPreference($pref_key); + if (!is_array($favorites)) { + $favorites = array(); + } + $favorite_phids = array_keys($favorites); + $favorite_phids = array_slice($favorite_phids, -$favorite_limit); + + if ($favorite_phids) { + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withPHIDs($favorite_phids) + ->withIsMilestone(false) + ->setLimit($favorite_limit) + ->execute(); + $projects = mpull($projects, null, 'getPHID'); + } else { + $projects = array(); + } + + // If we didn't find enough favorites, add some default projects. These + // are just arbitrary projects that the viewer is a member of, but may + // be useful on smaller installs and for new users until they can use + // the control enough time to establish useful favorites. + if (count($projects) < $default_limit) { + $default_projects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withMemberPHIDs(array($viewer->getPHID())) + ->withIsMilestone(false) + ->setLimit($default_limit) + ->execute(); + $default_projects = mpull($default_projects, null, 'getPHID'); + $projects = $projects + $default_projects; + $projects = array_slice($projects, 0, $default_limit); + } + foreach ($projects as $project) { $phids[] = $project->getPHID(); } diff --git a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php index 3bd38353f6..ee90afcb3b 100644 --- a/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php +++ b/src/applications/project/__tests__/PhabricatorProjectCoreTestCase.php @@ -808,6 +808,318 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { pht('Engineering + Scan')); } + public function testTagAncestryConflicts() { + $user = $this->createUser(); + $user->save(); + + $stonework = $this->createProject($user); + $stonework_masonry = $this->createProject($user, $stonework); + $stonework_sculpting = $this->createProject($user, $stonework); + + $task = $this->newTask($user, array()); + $this->assertEqual(array(), $this->getTaskProjects($task)); + + $this->addProjectTags($user, $task, array($stonework->getPHID())); + $this->assertEqual( + array( + $stonework->getPHID(), + ), + $this->getTaskProjects($task)); + + // Adding a descendant should remove the parent. + $this->addProjectTags($user, $task, array($stonework_masonry->getPHID())); + $this->assertEqual( + array( + $stonework_masonry->getPHID(), + ), + $this->getTaskProjects($task)); + + // Adding an ancestor should remove the descendant. + $this->addProjectTags($user, $task, array($stonework->getPHID())); + $this->assertEqual( + array( + $stonework->getPHID(), + ), + $this->getTaskProjects($task)); + + // Adding two tags in the same hierarchy which are not mutual ancestors + // should remove the ancestor but otherwise work fine. + $this->addProjectTags( + $user, + $task, + array( + $stonework_masonry->getPHID(), + $stonework_sculpting->getPHID(), + )); + + $expect = array( + $stonework_masonry->getPHID(), + $stonework_sculpting->getPHID(), + ); + sort($expect); + + $this->assertEqual($expect, $this->getTaskProjects($task)); + } + + public function testTagMilestoneConflicts() { + $user = $this->createUser(); + $user->save(); + + $stonework = $this->createProject($user); + $stonework_1 = $this->createProject($user, $stonework, true); + $stonework_2 = $this->createProject($user, $stonework, true); + + $task = $this->newTask($user, array()); + $this->assertEqual(array(), $this->getTaskProjects($task)); + + $this->addProjectTags($user, $task, array($stonework->getPHID())); + $this->assertEqual( + array( + $stonework->getPHID(), + ), + $this->getTaskProjects($task)); + + // Adding a milesone should remove the parent. + $this->addProjectTags($user, $task, array($stonework_1->getPHID())); + $this->assertEqual( + array( + $stonework_1->getPHID(), + ), + $this->getTaskProjects($task)); + + // Adding the parent should remove the milestone. + $this->addProjectTags($user, $task, array($stonework->getPHID())); + $this->assertEqual( + array( + $stonework->getPHID(), + ), + $this->getTaskProjects($task)); + + // First, add one milestone. + $this->addProjectTags($user, $task, array($stonework_1->getPHID())); + // Now, adding a second milestone should remove the first milestone. + $this->addProjectTags($user, $task, array($stonework_2->getPHID())); + $this->assertEqual( + array( + $stonework_2->getPHID(), + ), + $this->getTaskProjects($task)); + } + + public function testBoardMoves() { + $user = $this->createUser(); + $user->save(); + + $board = $this->createProject($user); + + $backlog = $this->addColumn($user, $board, 0); + $column = $this->addColumn($user, $board, 1); + + // New tasks should appear in the backlog. + $task1 = $this->newTask($user, array($board)); + $expect = array( + $backlog->getPHID(), + ); + $this->assertColumns($expect, $user, $board, $task1); + + // Moving a task should move it to the destination column. + $this->moveToColumn($user, $board, $task1, $backlog, $column); + $expect = array( + $column->getPHID(), + ); + $this->assertColumns($expect, $user, $board, $task1); + + // Same thing again, with a new task. + $task2 = $this->newTask($user, array($board)); + $expect = array( + $backlog->getPHID(), + ); + $this->assertColumns($expect, $user, $board, $task2); + + // Move it, too. + $this->moveToColumn($user, $board, $task2, $backlog, $column); + $expect = array( + $column->getPHID(), + ); + $this->assertColumns($expect, $user, $board, $task2); + + // Now the stuff should be in the column, in order, with the more recently + // moved task on top. + $expect = array( + $task2->getPHID(), + $task1->getPHID(), + ); + $this->assertTasksInColumn($expect, $user, $board, $column); + + // Move the second task after the first task. + $options = array( + 'afterPHID' => $task1->getPHID(), + ); + $this->moveToColumn($user, $board, $task2, $column, $column, $options); + $expect = array( + $task1->getPHID(), + $task2->getPHID(), + ); + $this->assertTasksInColumn($expect, $user, $board, $column); + + // Move the second task before the first task. + $options = array( + 'beforePHID' => $task1->getPHID(), + ); + $this->moveToColumn($user, $board, $task2, $column, $column, $options); + $expect = array( + $task2->getPHID(), + $task1->getPHID(), + ); + $this->assertTasksInColumn($expect, $user, $board, $column); + } + + public function testMilestoneMoves() { + $user = $this->createUser(); + $user->save(); + + $board = $this->createProject($user); + + $backlog = $this->addColumn($user, $board, 0); + + // Create a task into the backlog. + $task = $this->newTask($user, array($board)); + $expect = array( + $backlog->getPHID(), + ); + $this->assertColumns($expect, $user, $board, $task); + + $milestone = $this->createProject($user, $board, true); + + $this->addProjectTags($user, $task, array($milestone->getPHID())); + + // We just want the side effect of looking at the board: creation of the + // milestone column. + $this->loadColumns($user, $board, $task); + + $column = id(new PhabricatorProjectColumnQuery()) + ->setViewer($user) + ->withProjectPHIDs(array($board->getPHID())) + ->withProxyPHIDs(array($milestone->getPHID())) + ->executeOne(); + + $this->assertTrue((bool)$column); + + // Moving the task to the milestone should have moved it to the milestone + // column. + $expect = array( + $column->getPHID(), + ); + $this->assertColumns($expect, $user, $board, $task); + } + + private function moveToColumn( + PhabricatorUser $viewer, + PhabricatorProject $board, + ManiphestTask $task, + PhabricatorProjectColumn $src, + PhabricatorProjectColumn $dst, + $options = null) { + + $xactions = array(); + + if (!$options) { + $options = array(); + } + + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(ManiphestTransaction::TYPE_PROJECT_COLUMN) + ->setOldValue( + array( + 'projectPHID' => $board->getPHID(), + 'columnPHIDs' => array($src->getPHID()), + )) + ->setNewValue( + array( + 'projectPHID' => $board->getPHID(), + 'columnPHIDs' => array($dst->getPHID()), + ) + $options); + + $editor = id(new ManiphestTransactionEditor()) + ->setActor($viewer) + ->setContentSource(PhabricatorContentSource::newConsoleSource()) + ->setContinueOnNoEffect(true) + ->applyTransactions($task, $xactions); + } + + private function assertColumns( + array $expect, + PhabricatorUser $viewer, + PhabricatorProject $board, + ManiphestTask $task) { + $column_phids = $this->loadColumns($viewer, $board, $task); + $this->assertEqual($expect, $column_phids); + } + + private function loadColumns( + PhabricatorUser $viewer, + PhabricatorProject $board, + ManiphestTask $task) { + $engine = id(new PhabricatorBoardLayoutEngine()) + ->setViewer($viewer) + ->setBoardPHIDs(array($board->getPHID())) + ->setObjectPHIDs( + array( + $task->getPHID(), + )) + ->executeLayout(); + + $columns = $engine->getObjectColumns($board->getPHID(), $task->getPHID()); + $column_phids = mpull($columns, 'getPHID'); + $column_phids = array_values($column_phids); + + return $column_phids; + } + + private function assertTasksInColumn( + array $expect, + PhabricatorUser $viewer, + PhabricatorProject $board, + PhabricatorProjectColumn $column) { + + $engine = id(new PhabricatorBoardLayoutEngine()) + ->setViewer($viewer) + ->setBoardPHIDs(array($board->getPHID())) + ->setObjectPHIDs($expect) + ->executeLayout(); + + $object_phids = $engine->getColumnObjectPHIDs( + $board->getPHID(), + $column->getPHID()); + $object_phids = array_values($object_phids); + + $this->assertEqual($expect, $object_phids); + } + + private function addColumn( + PhabricatorUser $viewer, + PhabricatorProject $project, + $sequence) { + + $project->setHasWorkboard(1)->save(); + + return PhabricatorProjectColumn::initializeNewColumn($viewer) + ->setSequence(0) + ->setProperty('isDefault', ($sequence == 0)) + ->setProjectPHID($project->getPHID()) + ->save(); + } + + private function getTaskProjects(ManiphestTask $task) { + $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $task->getPHID(), + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); + + sort($project_phids); + + return $project_phids; + } + private function attemptProjectEdit( PhabricatorProject $proj, PhabricatorUser $user, @@ -827,6 +1139,30 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { } + private function addProjectTags( + PhabricatorUser $viewer, + ManiphestTask $task, + array $phids) { + + $xactions = array(); + + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue( + 'edge:type', + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) + ->setNewValue( + array( + '+' => array_fuse($phids), + )); + + $editor = id(new ManiphestTransactionEditor()) + ->setActor($viewer) + ->setContentSource(PhabricatorContentSource::newConsoleSource()) + ->setContinueOnNoEffect(true) + ->applyTransactions($task, $xactions); + } + private function newTask( PhabricatorUser $viewer, array $projects, @@ -945,6 +1281,16 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase { $this->applyTransactions($project, $user, $xactions); + // Force these values immediately; they are normally updated by the + // index engine. + if ($parent) { + if ($is_milestone) { + $parent->setHasMilestones(1)->save(); + } else { + $parent->setHasSubprojects(1)->save(); + } + } + return $project; } diff --git a/src/applications/project/application/PhabricatorProjectApplication.php b/src/applications/project/application/PhabricatorProjectApplication.php index 59904cb06b..5f3cb9e090 100644 --- a/src/applications/project/application/PhabricatorProjectApplication.php +++ b/src/applications/project/application/PhabricatorProjectApplication.php @@ -91,6 +91,8 @@ final class PhabricatorProjectApplication extends PhabricatorApplication { => 'PhabricatorProjectWatchController', 'silence/(?P[1-9]\d*)/' => 'PhabricatorProjectSilenceController', + 'warning/(?P[1-9]\d*)/' + => 'PhabricatorProjectSubprojectWarningController', ), '/tag/' => array( '(?P[^/]+)/' => 'PhabricatorProjectViewController', diff --git a/src/applications/project/controller/PhabricatorProjectBoardReorderController.php b/src/applications/project/controller/PhabricatorProjectBoardReorderController.php index 425c27b5f0..011bbd069a 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardReorderController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardReorderController.php @@ -97,9 +97,20 @@ final class PhabricatorProjectBoardReorderController ->setFlush(true); foreach ($columns as $column) { + // Don't allow milestone columns to be reordered. + $proxy = $column->getProxy(); + if ($proxy && $proxy->isMilestone()) { + continue; + } + + // At least for now, don't show subproject column. + if ($proxy) { + continue; + } + $item = id(new PHUIObjectItemView()) ->setHeader($column->getDisplayName()) - ->addIcon('none', $column->getDisplayType()); + ->addIcon($column->getHeaderIcon(), $column->getDisplayType()); if ($column->isHidden()) { $item->setDisabled(true); diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index 4d83ca6fa3..d0580a95ad 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -28,10 +28,103 @@ final class PhabricatorProjectBoardViewController $project = $this->getProject(); $this->readRequestState(); - $columns = $this->loadColumns($project); - // TODO: Expand the checks here if we add the ability - // to hide the Backlog column + $board_uri = $this->getApplicationURI('board/'.$project->getID().'/'); + + $search_engine = id(new ManiphestTaskSearchEngine()) + ->setViewer($viewer) + ->setBaseURI($board_uri) + ->setIsBoardView(true); + + if ($request->isFormPost() && !$request->getBool('initialize')) { + $saved = $search_engine->buildSavedQueryFromRequest($request); + $search_engine->saveQuery($saved); + $filter_form = id(new AphrontFormView()) + ->setUser($viewer); + $search_engine->buildSearchForm($filter_form, $saved); + if ($search_engine->getErrors()) { + return $this->newDialog() + ->setWidth(AphrontDialogView::WIDTH_FULL) + ->setTitle(pht('Advanced Filter')) + ->appendChild($filter_form->buildLayoutView()) + ->setErrors($search_engine->getErrors()) + ->setSubmitURI($board_uri) + ->addSubmitButton(pht('Apply Filter')) + ->addCancelButton($board_uri); + } + return id(new AphrontRedirectResponse())->setURI( + $this->getURIWithState( + $search_engine->getQueryResultsPageURI($saved->getQueryKey()))); + } + + $query_key = $request->getURIData('queryKey'); + if (!$query_key) { + $query_key = 'open'; + } + $this->queryKey = $query_key; + + $custom_query = null; + if ($search_engine->isBuiltinQuery($query_key)) { + $saved = $search_engine->buildSavedQueryFromBuiltin($query_key); + } else { + $saved = id(new PhabricatorSavedQueryQuery()) + ->setViewer($viewer) + ->withQueryKeys(array($query_key)) + ->executeOne(); + + if (!$saved) { + return new Aphront404Response(); + } + + $custom_query = $saved; + } + + if ($request->getURIData('filter')) { + $filter_form = id(new AphrontFormView()) + ->setUser($viewer); + $search_engine->buildSearchForm($filter_form, $saved); + + return $this->newDialog() + ->setWidth(AphrontDialogView::WIDTH_FULL) + ->setTitle(pht('Advanced Filter')) + ->appendChild($filter_form->buildLayoutView()) + ->setSubmitURI($board_uri) + ->addSubmitButton(pht('Apply Filter')) + ->addCancelButton($board_uri); + } + + $task_query = $search_engine->buildQueryFromSavedQuery($saved); + + $select_phids = array($project->getPHID()); + if ($project->getHasSubprojects() || $project->getHasMilestones()) { + $descendants = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withAncestorProjectPHIDs($select_phids) + ->execute(); + foreach ($descendants as $descendant) { + $select_phids[] = $descendant->getPHID(); + } + } + + $tasks = $task_query + ->withEdgeLogicPHIDs( + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + PhabricatorQueryConstraint::OPERATOR_ANCESTOR, + array($select_phids)) + ->setOrder(ManiphestTaskQuery::ORDER_PRIORITY) + ->setViewer($viewer) + ->execute(); + $tasks = mpull($tasks, null, 'getPHID'); + + $board_phid = $project->getPHID(); + + $layout_engine = id(new PhabricatorBoardLayoutEngine()) + ->setViewer($viewer) + ->setBoardPHIDs(array($board_phid)) + ->setObjectPHIDs(array_keys($tasks)) + ->executeLayout(); + + $columns = $layout_engine->getColumns($board_phid); if (!$columns) { $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, @@ -56,132 +149,14 @@ final class PhabricatorProjectBoardViewController return $this->newPage() ->setTitle( array( + $project->getDisplayName(), pht('Workboard'), - $project->getName(), )) ->setNavigation($nav) ->setCrumbs($crumbs) ->appendChild($content); } - $board_uri = $this->getApplicationURI('board/'.$project->getID().'/'); - - $engine = id(new ManiphestTaskSearchEngine()) - ->setViewer($viewer) - ->setBaseURI($board_uri) - ->setIsBoardView(true); - - if ($request->isFormPost()) { - $saved = $engine->buildSavedQueryFromRequest($request); - $engine->saveQuery($saved); - $filter_form = id(new AphrontFormView()) - ->setUser($viewer); - $engine->buildSearchForm($filter_form, $saved); - if ($engine->getErrors()) { - return $this->newDialog() - ->setWidth(AphrontDialogView::WIDTH_FULL) - ->setTitle(pht('Advanced Filter')) - ->appendChild($filter_form->buildLayoutView()) - ->setErrors($engine->getErrors()) - ->setSubmitURI($board_uri) - ->addSubmitButton(pht('Apply Filter')) - ->addCancelButton($board_uri); - } - return id(new AphrontRedirectResponse())->setURI( - $this->getURIWithState( - $engine->getQueryResultsPageURI($saved->getQueryKey()))); - } - - $query_key = $request->getURIData('queryKey'); - if (!$query_key) { - $query_key = 'open'; - } - $this->queryKey = $query_key; - - $custom_query = null; - if ($engine->isBuiltinQuery($query_key)) { - $saved = $engine->buildSavedQueryFromBuiltin($query_key); - } else { - $saved = id(new PhabricatorSavedQueryQuery()) - ->setViewer($viewer) - ->withQueryKeys(array($query_key)) - ->executeOne(); - - if (!$saved) { - return new Aphront404Response(); - } - - $custom_query = $saved; - } - - if ($request->getURIData('filter')) { - $filter_form = id(new AphrontFormView()) - ->setUser($viewer); - $engine->buildSearchForm($filter_form, $saved); - - return $this->newDialog() - ->setWidth(AphrontDialogView::WIDTH_FULL) - ->setTitle(pht('Advanced Filter')) - ->appendChild($filter_form->buildLayoutView()) - ->setSubmitURI($board_uri) - ->addSubmitButton(pht('Apply Filter')) - ->addCancelButton($board_uri); - } - - $task_query = $engine->buildQueryFromSavedQuery($saved); - - $tasks = $task_query - ->withEdgeLogicPHIDs( - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, - PhabricatorQueryConstraint::OPERATOR_AND, - array($project->getPHID())) - ->setOrder(ManiphestTaskQuery::ORDER_PRIORITY) - ->setViewer($viewer) - ->execute(); - $tasks = mpull($tasks, null, 'getPHID'); - - if ($tasks) { - $positions = id(new PhabricatorProjectColumnPositionQuery()) - ->setViewer($viewer) - ->withObjectPHIDs(mpull($tasks, 'getPHID')) - ->withColumns($columns) - ->execute(); - $positions = mpull($positions, null, 'getObjectPHID'); - } else { - $positions = array(); - } - - $task_map = array(); - foreach ($tasks as $task) { - $task_phid = $task->getPHID(); - if (empty($positions[$task_phid])) { - // This shouldn't normally be possible because we create positions on - // demand, but we might have raced as an object was removed from the - // board. Just drop the task if we don't have a position for it. - continue; - } - - $position = $positions[$task_phid]; - $task_map[$position->getColumnPHID()][] = $task_phid; - } - - // If we're showing the board in "natural" order, sort columns by their - // column positions. - if ($this->sortKey == PhabricatorProjectColumn::ORDER_NATURAL) { - foreach ($task_map as $column_phid => $task_phids) { - $order = array(); - foreach ($task_phids as $task_phid) { - if (isset($positions[$task_phid])) { - $order[$task_phid] = $positions[$task_phid]->getOrderingKey(); - } else { - $order[$task_phid] = 0; - } - } - asort($order); - $task_map[$column_phid] = array_keys($order); - } - } - $task_can_edit_map = id(new PhabricatorPolicyFilter()) ->setViewer($viewer) ->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT)) @@ -198,7 +173,10 @@ final class PhabricatorProjectBoardViewController return new Aphront404Response(); } - $batch_task_phids = idx($task_map, $batch_column->getPHID(), array()); + $batch_task_phids = $layout_engine->getColumnObjectPHIDs( + $board_phid, + $batch_column->getPHID()); + foreach ($batch_task_phids as $key => $batch_task_phid) { if (empty($task_can_edit_map[$batch_task_phid])) { unset($batch_task_phids[$key]); @@ -250,10 +228,46 @@ final class PhabricatorProjectBoardViewController $this->handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); + $all_project_phids = array(); + foreach ($tasks as $task) { + foreach ($task->getProjectPHIDs() as $project_phid) { + $all_project_phids[$project_phid] = $project_phid; + } + } + + foreach ($select_phids as $phid) { + unset($all_project_phids[$phid]); + } + + $all_handles = $viewer->loadHandles($all_project_phids); + $all_handles = iterator_to_array($all_handles); + foreach ($columns as $column) { - $task_phids = idx($task_map, $column->getPHID(), array()); + if (!$this->showHidden) { + if ($column->isHidden()) { + continue; + } + } + + $proxy = $column->getProxy(); + if ($proxy && !$proxy->isMilestone()) { + // TODO: For now, don't show subproject columns because we can't + // handle tasks with multiple positions yet. + continue; + } + + $task_phids = $layout_engine->getColumnObjectPHIDs( + $board_phid, + $column->getPHID()); + $column_tasks = array_select_keys($tasks, $task_phids); + // If we aren't using "natural" order, reorder the column by the original + // query order. + if ($this->sortKey != PhabricatorProjectColumn::ORDER_NATURAL) { + $column_tasks = array_select_keys($column_tasks, array_keys($tasks)); + } + $panel = id(new PHUIWorkpanelView()) ->setHeader($column->getDisplayName()) ->setSubHeader($column->getDisplayType()) @@ -264,6 +278,11 @@ final class PhabricatorProjectBoardViewController $panel->setHeaderIcon($header_icon); } + $display_class = $column->getDisplayClass(); + if ($display_class) { + $panel->addClass($display_class); + } + if ($column->isHidden()) { $panel->addClass('project-panel-hidden'); } @@ -288,6 +307,7 @@ final class PhabricatorProjectBoardViewController ->setFlush(true) ->setAllowEmptyList(true) ->addSigil('project-column') + ->setItemClass('phui-workcard') ->setMetadata( array( 'columnPHID' => $column->getPHID(), @@ -302,8 +322,12 @@ final class PhabricatorProjectBoardViewController $owner = $this->handles[$task->getOwnerPHID()]; } $can_edit = idx($task_can_edit_map, $task->getPHID(), false); + + $handles = array_select_keys($all_handles, $task->getProjectPHIDs()); + $cards->addItem(id(new ProjectBoardTaskCard()) ->setViewer($viewer) + ->setProjectHandles($handles) ->setTask($task) ->setOwner($owner) ->setCanEdit($can_edit) @@ -320,7 +344,7 @@ final class PhabricatorProjectBoardViewController $filter_menu = $this->buildFilterMenu( $viewer, $custom_query, - $engine, + $search_engine, $query_key); $manage_menu = $this->buildManageMenu($project, $this->showHidden); @@ -347,7 +371,11 @@ final class PhabricatorProjectBoardViewController $crumbs->addAction($manage_menu); return $this->newPage() - ->setTitle(pht('%s Board', $project->getName())) + ->setTitle( + array( + $project->getDisplayName(), + pht('Workboard'), + )) ->setPageObjectPHIDs(array($project->getPHID())) ->setShowFooter(false) ->setNavigation($nav) @@ -381,25 +409,6 @@ final class PhabricatorProjectBoardViewController $this->sortKey = $sort_key; } - private function loadColumns(PhabricatorProject $project) { - $viewer = $this->getViewer(); - - $column_query = id(new PhabricatorProjectColumnQuery()) - ->setViewer($viewer) - ->withProjectPHIDs(array($project->getPHID())); - - if (!$this->showHidden) { - $column_query->withStatuses( - array(PhabricatorProjectColumn::STATUS_ACTIVE)); - } - - $columns = $column_query->execute(); - $columns = mpull($columns, null, 'getSequence'); - ksort($columns); - - return $columns; - } - private function buildSortMenu( PhabricatorUser $viewer, $sort_key) { @@ -616,6 +625,12 @@ final class PhabricatorProjectBoardViewController $column_items = array(); + if ($column->getProxyPHID()) { + $default_phid = $column->getProxyPHID(); + } else { + $default_phid = $column->getProjectPHID(); + } + $column_items[] = id(new PhabricatorActionView()) ->setIcon('fa-plus') ->setName(pht('Create Task...')) @@ -624,6 +639,7 @@ final class PhabricatorProjectBoardViewController ->setMetadata( array( 'columnPHID' => $column->getPHID(), + 'projectPHID' => $default_phid, )); $batch_edit_uri = $request->getRequestURI(); @@ -772,6 +788,10 @@ final class PhabricatorProjectBoardViewController } } + // TODO: Tailor this UI if the project is already a parent project. We + // should not offer options for creating a parent project workboard, since + // they can't have their own columns. + $new_selector = id(new AphrontFormRadioButtonControl()) ->setLabel(pht('Columns')) ->setName('initialize-type') @@ -795,6 +815,7 @@ final class PhabricatorProjectBoardViewController $form = id(new AphrontFormView()) ->setUser($viewer) + ->addHiddenInput('initialize', 1) ->appendRemarkupInstructions( pht('The workboard for this project has not been created yet.')) ->appendControl($new_selector) diff --git a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php index ca25c089fb..e008c832d9 100644 --- a/src/applications/project/controller/PhabricatorProjectColumnDetailController.php +++ b/src/applications/project/controller/PhabricatorProjectColumnDetailController.php @@ -22,6 +22,8 @@ final class PhabricatorProjectColumnDetailController } $this->setProject($project); + $project_id = $project->getID(); + $column = id(new PhabricatorProjectColumnQuery()) ->setViewer($viewer) ->withIDs(array($id)) @@ -45,6 +47,10 @@ final class PhabricatorProjectColumnDetailController $actions = $this->buildActionView($column); $properties = $this->buildPropertyView($column, $actions); + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Workboard'), "/project/board/{$project_id}/"); + $crumbs->addTextCrumb(pht('Column: %s', $title)); + $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($properties); @@ -54,6 +60,7 @@ final class PhabricatorProjectColumnDetailController return $this->newPage() ->setTitle($title) ->setNavigation($nav) + ->setCrumbs($crumbs) ->appendChild( array( $box, diff --git a/src/applications/project/controller/PhabricatorProjectColumnEditController.php b/src/applications/project/controller/PhabricatorProjectColumnEditController.php index 77f90b56cb..5ebc721c69 100644 --- a/src/applications/project/controller/PhabricatorProjectColumnEditController.php +++ b/src/applications/project/controller/PhabricatorProjectColumnEditController.php @@ -81,10 +81,12 @@ final class PhabricatorProjectColumnEditController $xactions = array(); - $type_name = PhabricatorProjectColumnTransaction::TYPE_NAME; - $xactions[] = id(new PhabricatorProjectColumnTransaction()) - ->setTransactionType($type_name) - ->setNewValue($v_name); + if (!$column->getProxy()) { + $type_name = PhabricatorProjectColumnTransaction::TYPE_NAME; + $xactions[] = id(new PhabricatorProjectColumnTransaction()) + ->setTransactionType($type_name) + ->setNewValue($v_name); + } $type_limit = PhabricatorProjectColumnTransaction::TYPE_LIMIT; $xactions[] = id(new PhabricatorProjectColumnTransaction()) @@ -105,26 +107,26 @@ final class PhabricatorProjectColumnEditController } } - $form = new AphrontFormView(); - $form - ->setUser($request->getUser()) - ->appendChild( + $form = id(new AphrontFormView()) + ->setUser($request->getUser()); + + if (!$column->getProxy()) { + $form->appendChild( id(new AphrontFormTextControl()) ->setValue($v_name) ->setLabel(pht('Name')) ->setName('name') - ->setError($e_name) - ->setCaption( - pht('This will be displayed as the header of the column.'))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setValue($v_limit) - ->setLabel(pht('Point Limit')) - ->setName('limit') - ->setError($e_limit) - ->setCaption( - pht('Maximum number of points of tasks allowed in the column.'))); + ->setError($e_name)); + } + $form->appendChild( + id(new AphrontFormTextControl()) + ->setValue($v_limit) + ->setLabel(pht('Point Limit')) + ->setName('limit') + ->setError($e_limit) + ->setCaption( + pht('Maximum number of points of tasks allowed in the column.'))); if ($is_new) { $title = pht('Create Column'); diff --git a/src/applications/project/controller/PhabricatorProjectEditController.php b/src/applications/project/controller/PhabricatorProjectEditController.php index d87910462c..5091135bec 100644 --- a/src/applications/project/controller/PhabricatorProjectEditController.php +++ b/src/applications/project/controller/PhabricatorProjectEditController.php @@ -42,6 +42,7 @@ final class PhabricatorProjectEditController if ($parent_id) { $query = id(new PhabricatorProjectQuery()) ->setViewer($viewer) + ->needImages(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -58,7 +59,7 @@ final class PhabricatorProjectEditController if ($is_milestone) { if (!$parent->supportsMilestones()) { - $cancel_uri = "/project/milestones/{$parent_id}/"; + $cancel_uri = "/project/subprojects/{$parent_id}/"; return $this->newDialog() ->setTitle(pht('No Milestones')) ->appendParagraph( @@ -91,20 +92,13 @@ final class PhabricatorProjectEditController $engine = $this->getEngine(); if ($engine) { $parent = $engine->getParentProject(); - if ($parent) { - $id = $parent->getID(); + $milestone = $engine->getMilestoneProject(); + if ($parent || $milestone) { + $id = nonempty($parent, $milestone)->getID(); $crumbs->addTextCrumb( pht('Subprojects'), $this->getApplicationURI("subprojects/{$id}/")); } - - $milestone = $engine->getMilestoneProject(); - if ($milestone) { - $id = $milestone->getID(); - $crumbs->addTextCrumb( - pht('Milestones'), - $this->getApplicationURI("milestones/{$id}/")); - } } return $crumbs; diff --git a/src/applications/project/controller/PhabricatorProjectManageController.php b/src/applications/project/controller/PhabricatorProjectManageController.php index 2721265493..87420d1aa3 100644 --- a/src/applications/project/controller/PhabricatorProjectManageController.php +++ b/src/applications/project/controller/PhabricatorProjectManageController.php @@ -51,7 +51,11 @@ final class PhabricatorProjectManageController return $this->newPage() ->setNavigation($nav) ->setCrumbs($crumbs) - ->setTitle($project->getName()) + ->setTitle( + array( + $project->getDisplayName(), + pht('Manage'), + )) ->appendChild( array( $object_box, diff --git a/src/applications/project/controller/PhabricatorProjectMembersViewController.php b/src/applications/project/controller/PhabricatorProjectMembersViewController.php index efe2106a26..88bc56be24 100644 --- a/src/applications/project/controller/PhabricatorProjectMembersViewController.php +++ b/src/applications/project/controller/PhabricatorProjectMembersViewController.php @@ -48,7 +48,7 @@ final class PhabricatorProjectMembersViewController return $this->newPage() ->setNavigation($nav) ->setCrumbs($crumbs) - ->setTitle(array($project->getName(), $title)) + ->setTitle(array($project->getDisplayName(), $title)) ->appendChild( array( $object_box, diff --git a/src/applications/project/controller/PhabricatorProjectMilestonesController.php b/src/applications/project/controller/PhabricatorProjectMilestonesController.php deleted file mode 100644 index ebbe5bc2e3..0000000000 --- a/src/applications/project/controller/PhabricatorProjectMilestonesController.php +++ /dev/null @@ -1,92 +0,0 @@ -getViewer(); - - $response = $this->loadProject(); - if ($response) { - return $response; - } - - $project = $this->getProject(); - $id = $project->getID(); - - $can_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $project, - PhabricatorPolicyCapability::CAN_EDIT); - - $has_support = $project->supportsMilestones(); - if ($has_support) { - $milestones = id(new PhabricatorProjectQuery()) - ->setViewer($viewer) - ->withParentProjectPHIDs(array($project->getPHID())) - ->needImages(true) - ->withIsMilestone(true) - ->setOrder('newest') - ->execute(); - } else { - $milestones = array(); - } - - $can_create = $can_edit && $has_support; - - if ($project->getHasMilestones()) { - $button_text = pht('Create Next Milestone'); - } else { - $button_text = pht('Add Milestones'); - } - - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Milestones')) - ->addActionLink( - id(new PHUIButtonView()) - ->setTag('a') - ->setHref("/project/edit/?milestone={$id}") - ->setIcon('fa-plus') - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create) - ->setText($button_text)); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header); - - if (!$has_support) { - $no_support = pht( - 'This project is a milestone. Milestones can not have their own '. - 'milestones.'); - - $info_view = id(new PHUIInfoView()) - ->setErrors(array($no_support)) - ->setSeverity(PHUIInfoView::SEVERITY_WARNING); - - $box->setInfoView($info_view); - } - - $box->setObjectList( - id(new PhabricatorProjectListView()) - ->setUser($viewer) - ->setProjects($milestones) - ->renderList()); - - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorProject::PANEL_MILESTONES); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Milestones')); - - return $this->newPage() - ->setNavigation($nav) - ->setCrumbs($crumbs) - ->setTitle(array($project->getName(), pht('Milestones'))) - ->appendChild($box); - } - -} diff --git a/src/applications/project/controller/PhabricatorProjectMoveController.php b/src/applications/project/controller/PhabricatorProjectMoveController.php index 1b2429917c..7cbbf3d0ae 100644 --- a/src/applications/project/controller/PhabricatorProjectMoveController.php +++ b/src/applications/project/controller/PhabricatorProjectMoveController.php @@ -26,9 +26,12 @@ final class PhabricatorProjectMoveController return new Aphront404Response(); } - $object = id(new PhabricatorObjectQuery()) + $board_phid = $project->getPHID(); + + $object = id(new ManiphestTaskQuery()) ->setViewer($viewer) ->withPHIDs(array($object_phid)) + ->needProjectPHIDs(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -53,11 +56,14 @@ final class PhabricatorProjectMoveController return new Aphront404Response(); } - $positions = id(new PhabricatorProjectColumnPositionQuery()) + $engine = id(new PhabricatorBoardLayoutEngine()) ->setViewer($viewer) - ->withColumns($columns) - ->withObjectPHIDs(array($object_phid)) - ->execute(); + ->setBoardPHIDs(array($board_phid)) + ->setObjectPHIDs(array($object_phid)) + ->executeLayout(); + + $columns = $engine->getObjectColumns($board_phid, $object_phid); + $old_column_phids = mpull($columns, 'getPHID'); $xactions = array(); @@ -79,7 +85,7 @@ final class PhabricatorProjectMoveController ) + $order_params) ->setOldValue( array( - 'columnPHIDs' => mpull($positions, 'getColumnPHID'), + 'columnPHIDs' => $old_column_phids, 'projectPHID' => $column->getProjectPHID(), )); @@ -95,6 +101,7 @@ final class PhabricatorProjectMoveController $tasks = id(new ManiphestTaskQuery()) ->setViewer($viewer) ->withPHIDs($task_phids) + ->needProjectPHIDs(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -132,7 +139,33 @@ final class PhabricatorProjectMoveController ->setTransactionType(ManiphestTransaction::TYPE_SUBPRIORITY) ->setNewValue($sub); } - } + } + + $proxy = $column->getProxy(); + if ($proxy) { + // We're moving the task into a subproject or milestone column, so add + // the subproject or milestone. + $add_projects = array($proxy->getPHID()); + } else if ($project->getHasSubprojects() || $project->getHasMilestones()) { + // We're moving the task into the "Backlog" column on the parent project, + // so add the parent explicitly. This gets rid of any subproject or + // milestone tags. + $add_projects = array($project->getPHID()); + } else { + $add_projects = array(); + } + + if ($add_projects) { + $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; + + $xactions[] = id(new ManiphestTransaction()) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue('edge:type', $project_type) + ->setNewValue( + array( + '+' => array_fuse($add_projects), + )); + } $editor = id(new ManiphestTransactionEditor()) ->setActor($viewer) @@ -149,15 +182,49 @@ final class PhabricatorProjectMoveController ->withPHIDs(array($object->getOwnerPHID())) ->executeOne(); } + + // Reload the object so it reflects edits which have been applied. + $object = id(new ManiphestTaskQuery()) + ->setViewer($viewer) + ->withPHIDs(array($object_phid)) + ->needProjectPHIDs(true) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + + $except_phids = array($board_phid); + if ($project->getHasSubprojects() || $project->getHasMilestones()) { + $descendants = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withAncestorProjectPHIDs($except_phids) + ->execute(); + foreach ($descendants as $descendant) { + $except_phids[] = $descendant->getPHID(); + } + } + + $except_phids = array_fuse($except_phids); + $handle_phids = array_fuse($object->getProjectPHIDs()); + $handle_phids = array_diff_key($handle_phids, $except_phids); + + $project_handles = $viewer->loadHandles($handle_phids); + $project_handles = iterator_to_array($project_handles); + $card = id(new ProjectBoardTaskCard()) ->setViewer($viewer) ->setTask($object) ->setOwner($owner) ->setCanEdit(true) + ->setProjectHandles($project_handles) ->getItem(); + $card->addClass('phui-workcard'); + return id(new AphrontAjaxResponse())->setContent( array('task' => $card)); - } + } } diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index c03b82b05d..4e670475ce 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -17,9 +17,16 @@ final class PhabricatorProjectProfileController $project = $this->getProject(); $id = $project->getID(); $picture = $project->getProfileImageURI(); + $icon = $project->getDisplayIconIcon(); + $icon_name = $project->getDisplayIconName(); + $tag = id(new PHUITagView()) + ->setIcon($icon) + ->setName($icon_name) + ->addClass('project-view-header-tag') + ->setType(PHUITagView::TYPE_SHADE); $header = id(new PHUIHeaderView()) - ->setHeader($project->getName()) + ->setHeader(array($project->getDisplayName(), $tag)) ->setUser($viewer) ->setPolicyObject($project) ->setImage($picture) @@ -45,6 +52,9 @@ final class PhabricatorProjectProfileController $watch_action = $this->renderWatchAction($project); $header->addActionLink($watch_action); + $milestone_list = $this->buildMilestoneList($project); + $subproject_list = $this->buildSubprojectList($project); + $member_list = id(new PhabricatorProjectMemberListView()) ->setUser($viewer) ->setProject($project) @@ -82,6 +92,8 @@ final class PhabricatorProjectProfileController )) ->setSideColumn( array( + $milestone_list, + $subproject_list, $member_list, $watcher_list, )); @@ -103,7 +115,7 @@ final class PhabricatorProjectProfileController return $this->newPage() ->setNavigation($nav) ->setCrumbs($crumbs) - ->setTitle($project->getName()) + ->setTitle($project->getDisplayName()) ->setPageObjectPHIDs(array($project->getPHID())) ->appendChild( array( @@ -125,7 +137,7 @@ final class PhabricatorProjectProfileController PhabricatorCustomField::ROLE_VIEW); $field_list->appendFieldsToPropertyList($project, $viewer, $view); - if ($view->isEmpty()) { + if (!$view->hasAnyProperties()) { return null; } @@ -176,5 +188,90 @@ final class PhabricatorProjectProfileController ->setHref($watch_href); } + private function buildMilestoneList(PhabricatorProject $project) { + if (!$project->getHasMilestones()) { + return null; + } + + $viewer = $this->getViewer(); + $id = $project->getID(); + + $milestones = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withParentProjectPHIDs(array($project->getPHID())) + ->needImages(true) + ->withIsMilestone(true) + ->setOrder('newest') + ->execute(); + if (!$milestones) { + return null; + } + + $milestone_list = id(new PhabricatorProjectListView()) + ->setUser($viewer) + ->setProjects($milestones) + ->renderList(); + + $view_all = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon( + id(new PHUIIconView()) + ->setIcon('fa-list-ul')) + ->setText(pht('View All')) + ->setHref("/project/subprojects/{$id}/"); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Milestones')) + ->addActionLink($view_all); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIBoxView::GREY) + ->setObjectList($milestone_list); + } + + private function buildSubprojectList(PhabricatorProject $project) { + if (!$project->getHasSubprojects()) { + return null; + } + + $viewer = $this->getViewer(); + $id = $project->getID(); + + $limit = 25; + + $subprojects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withParentProjectPHIDs(array($project->getPHID())) + ->needImages(true) + ->withIsMilestone(false) + ->setLimit($limit) + ->execute(); + if (!$subprojects) { + return null; + } + + $subproject_list = id(new PhabricatorProjectListView()) + ->setUser($viewer) + ->setProjects($subprojects) + ->renderList(); + + $view_all = id(new PHUIButtonView()) + ->setTag('a') + ->setIcon( + id(new PHUIIconView()) + ->setIcon('fa-list-ul')) + ->setText(pht('View All')) + ->setHref("/project/subprojects/{$id}/"); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Subprojects')) + ->addActionLink($view_all); + + return id(new PHUIObjectBoxView()) + ->setHeader($header) + ->setBackground(PHUIBoxView::GREY) + ->setObjectList($subproject_list); + } } diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectWarningController.php b/src/applications/project/controller/PhabricatorProjectSubprojectWarningController.php new file mode 100644 index 0000000000..d9dd401101 --- /dev/null +++ b/src/applications/project/controller/PhabricatorProjectSubprojectWarningController.php @@ -0,0 +1,51 @@ +getViewer(); + + $response = $this->loadProject(); + if ($response) { + return $response; + } + + $project = $this->getProject(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $project, + PhabricatorPolicyCapability::CAN_EDIT); + + if (!$can_edit) { + return new Aphront404Response(); + } + + $id = $project->getID(); + $cancel_uri = "/project/subprojects/{$id}/"; + $done_uri = "/project/edit/?parent={$id}"; + + if ($request->isFormPost()) { + return id(new AphrontRedirectResponse()) + ->setURI($done_uri); + } + + $doc_href = PhabricatorEnv::getDoclink('Projects User Guide'); + + $conversion_help = pht( + "Creating a project's first subproject **moves all ". + "members** to become members of the subproject instead". + "\n\n". + "See [[ %s | Projects User Guide ]] in the documentation for details. ". + "This process can not be undone.", + $doc_href); + + return $this->newDialog() + ->setTitle(pht('Convert to Parent Project')) + ->appendChild(new PHUIRemarkupView($viewer, $conversion_help)) + ->addCancelButton($cancel_uri) + ->addSubmitButton(pht('Convert Project')); + } + +} diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php index a88bf7d07c..afafed77b1 100644 --- a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php +++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php @@ -23,9 +23,10 @@ final class PhabricatorProjectSubprojectsController $project, PhabricatorPolicyCapability::CAN_EDIT); - $has_support = $project->supportsSubprojects(); + $allows_subprojects = $project->supportsSubprojects(); + $allows_milestones = $project->supportsMilestones(); - if ($has_support) { + if ($allows_subprojects) { $subprojects = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withParentProjectPHIDs(array($project->getPHID())) @@ -36,44 +37,57 @@ final class PhabricatorProjectSubprojectsController $subprojects = array(); } - $can_create = $can_edit && $has_support; - - if ($project->getHasSubprojects()) { - $button_text = pht('Create Subproject'); + if ($allows_milestones) { + $milestones = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withParentProjectPHIDs(array($project->getPHID())) + ->needImages(true) + ->withIsMilestone(true) + ->setOrder('newest') + ->execute(); } else { - $button_text = pht('Add Subprojects'); + $milestones = array(); } - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Subprojects')) - ->addActionLink( - id(new PHUIButtonView()) - ->setTag('a') - ->setHref("/project/edit/?parent={$id}") - ->setIcon('fa-plus') - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create) - ->setText($button_text)); - - $box = id(new PHUIObjectBoxView()) - ->setHeader($header); - - if (!$has_support) { - $no_support = pht( - 'This project is a milestone. Milestones can not have subprojects.'); - - $info_view = id(new PHUIInfoView()) - ->setErrors(array($no_support)) - ->setSeverity(PHUIInfoView::SEVERITY_WARNING); - - $box->setInfoView($info_view); + if ($milestones) { + $milestone_list = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Milestones')) + ->setObjectList( + id(new PhabricatorProjectListView()) + ->setUser($viewer) + ->setProjects($milestones) + ->renderList()); + } else { + $milestone_list = null; } - $box->setObjectList( - id(new PhabricatorProjectListView()) - ->setUser($viewer) - ->setProjects($subprojects) - ->renderList()); + if ($subprojects) { + $subproject_list = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Subprojects')) + ->setObjectList( + id(new PhabricatorProjectListView()) + ->setUser($viewer) + ->setProjects($subprojects) + ->renderList()); + } else { + $subproject_list = null; + } + + $property_list = $this->buildPropertyList( + $project, + $milestones, + $subprojects); + + $action_list = $this->buildActionList( + $project, + $milestones, + $subprojects); + + $property_list->setActionList($action_list); + + $header_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Subprojects and Milestones')) + ->addPropertyList($property_list); $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorProject::PANEL_SUBPROJECTS); @@ -85,7 +99,151 @@ final class PhabricatorProjectSubprojectsController ->setNavigation($nav) ->setCrumbs($crumbs) ->setTitle(array($project->getName(), pht('Subprojects'))) - ->appendChild($box); + ->appendChild( + array( + $header_box, + $milestone_list, + $subproject_list, + )); } + private function buildPropertyList( + PhabricatorProject $project, + array $milestones, + array $subprojects) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $view->addProperty( + pht('Prototype'), + $this->renderStatus( + 'fa-exclamation-triangle red', + pht('Warning'), + pht('Subprojects and milestones are only partially implemented.'))); + + if (!$project->supportsMilestones()) { + $milestone_status = $this->renderStatus( + 'fa-times grey', + pht('Already Milestone'), + pht( + 'This project is already a milestone, and milestones may not '. + 'have their own milestones.')); + } else { + if (!$milestones) { + $milestone_status = $this->renderStatus( + 'fa-check grey', + pht('None Created'), + pht( + 'You can create milestones for this project.')); + } else { + $milestone_status = $this->renderStatus( + 'fa-check green', + pht('Has Milestones'), + pht('This project has milestones.')); + } + } + + $view->addProperty(pht('Milestones'), $milestone_status); + + if (!$project->supportsSubprojects()) { + $subproject_status = $this->renderStatus( + 'fa-times grey', + pht('Milestone'), + pht( + 'This project is a milestone, and milestones may not have '. + 'subprojects.')); + } else { + if (!$subprojects) { + $subproject_status = $this->renderStatus( + 'fa-check grey', + pht('None Created'), + pht('You can create subprojects for this project.')); + } else { + $subproject_status = $this->renderStatus( + 'fa-check green', + pht('Has Subprojects'), + pht( + 'This project has subprojects.')); + } + } + + $view->addProperty(pht('Subprojects'), $subproject_status); + + return $view; + } + + private function buildActionList( + PhabricatorProject $project, + array $milestones, + array $subprojects) { + $viewer = $this->getViewer(); + $id = $project->getID(); + + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $project, + PhabricatorPolicyCapability::CAN_EDIT); + + $allows_milestones = $project->supportsMilestones(); + $allows_subprojects = $project->supportsSubprojects(); + + $view = id(new PhabricatorActionListView()) + ->setUser($viewer); + + if ($allows_milestones && $milestones) { + $milestone_text = pht('Create Next Milestone'); + } else { + $milestone_text = pht('Create Milestone'); + } + + $can_milestone = ($can_edit && $allows_milestones); + $milestone_href = "/project/edit/?milestone={$id}"; + + $view->addAction( + id(new PhabricatorActionView()) + ->setName($milestone_text) + ->setIcon('fa-plus') + ->setHref($milestone_href) + ->setDisabled(!$can_milestone) + ->setWorkflow(!$can_milestone)); + + $can_subproject = ($can_edit && $allows_subprojects); + + // If we're offering to create the first subproject, we're going to warn + // the user about the effects before moving forward. + if ($can_subproject && !$subprojects) { + $subproject_href = "/project/warning/{$id}/"; + $subproject_disabled = false; + $subproject_workflow = true; + } else { + $subproject_href = "/project/edit/?parent={$id}"; + $subproject_disabled = !$can_subproject; + $subproject_workflow = !$can_subproject; + } + + $view->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Create Subproject')) + ->setIcon('fa-plus') + ->setHref($subproject_href) + ->setDisabled($subproject_disabled) + ->setWorkflow($subproject_workflow)); + + return $view; + } + + private function renderStatus($icon, $target, $note) { + $item = id(new PHUIStatusItemView()) + ->setIcon($icon) + ->setTarget(phutil_tag('strong', array(), $target)) + ->setNote($note); + + return id(new PHUIStatusListView()) + ->addItem($item); + } + + + } diff --git a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php index 0a03f55d7c..867bf7e9fe 100644 --- a/src/applications/project/editor/PhabricatorProjectTransactionEditor.php +++ b/src/applications/project/editor/PhabricatorProjectTransactionEditor.php @@ -219,7 +219,12 @@ final class PhabricatorProjectTransactionEditor foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { - case PhabricatorProjectTransaction::TYPE_MEMBERS: + case PhabricatorTransactions::TYPE_EDGE: + $type = $xaction->getMetadataValue('edge:type'); + if ($type != PhabricatorProjectProjectHasMemberEdgeType::EDGECONST) { + break; + } + if ($is_parent) { $errors[] = new PhabricatorApplicationTransactionValidationError( $xaction->getTransactionType(), @@ -792,27 +797,6 @@ final class PhabricatorProjectTransactionEditor $results = parent::expandTransactions($object, $xactions); - // Automatically add the author as a member when they create a project - // if they're using the web interface. - - $content_source = $this->getContentSource(); - $source_web = PhabricatorContentSource::SOURCE_WEB; - $is_web = ($content_source->getSource() === $source_web); - - if ($this->getIsNewObject() && $is_web) { - if ($actor_phid) { - $type_member = PhabricatorProjectProjectHasMemberEdgeType::EDGECONST; - - $results[] = id(new PhabricatorProjectTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $type_member) - ->setNewValue( - array( - '+' => array($actor_phid => $actor_phid), - )); - } - } - $is_milestone = $object->isMilestone(); foreach ($xactions as $xaction) { switch ($xaction->getTransactionType()) { diff --git a/src/applications/project/engine/PhabricatorBoardLayoutEngine.php b/src/applications/project/engine/PhabricatorBoardLayoutEngine.php new file mode 100644 index 0000000000..4415e2fdf9 --- /dev/null +++ b/src/applications/project/engine/PhabricatorBoardLayoutEngine.php @@ -0,0 +1,582 @@ +viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function setBoardPHIDs(array $board_phids) { + $this->boardPHIDs = array_fuse($board_phids); + return $this; + } + + public function getBoardPHIDs() { + return $this->boardPHIDs; + } + + public function setObjectPHIDs(array $object_phids) { + $this->objectPHIDs = array_fuse($object_phids); + return $this; + } + + public function getObjectPHIDs() { + return $this->objectPHIDs; + } + + public function executeLayout() { + $viewer = $this->getViewer(); + + $boards = $this->loadBoards(); + if (!$boards) { + return $this; + } + + $columns = $this->loadColumns($boards); + $positions = $this->loadPositions($boards); + + foreach ($boards as $board_phid => $board) { + $board_columns = idx($columns, $board_phid); + + // Don't layout boards with no columns. These boards need to be formally + // created first. + if (!$columns) { + continue; + } + + $board_positions = idx($positions, $board_phid, array()); + + $this->layoutBoard($board, $board_columns, $board_positions); + } + + return $this; + } + + public function getColumns($board_phid) { + $columns = idx($this->boardLayout, $board_phid, array()); + return array_select_keys($this->columnMap, array_keys($columns)); + } + + public function getColumnObjectPHIDs($board_phid, $column_phid) { + $columns = idx($this->boardLayout, $board_phid, array()); + $positions = idx($columns, $column_phid, array()); + return mpull($positions, 'getObjectPHID'); + } + + public function getObjectColumns($board_phid, $object_phid) { + $board_map = idx($this->objectColumnMap, $board_phid, array()); + + $column_phids = idx($board_map, $object_phid); + if (!$column_phids) { + return array(); + } + + return array_select_keys($this->columnMap, $column_phids); + } + + public function queueRemovePosition( + $board_phid, + $column_phid, + $object_phid) { + + $board_layout = idx($this->boardLayout, $board_phid, array()); + $positions = idx($board_layout, $column_phid, array()); + $position = idx($positions, $object_phid); + + if ($position) { + $this->remQueue[] = $position; + + // If this position hasn't been saved yet, get it out of the add queue. + if (!$position->getID()) { + foreach ($this->addQueue as $key => $add_position) { + if ($add_position === $position) { + unset($this->addQueue[$key]); + } + } + } + } + + unset($this->boardLayout[$board_phid][$column_phid][$object_phid]); + + return $this; + } + + public function queueAddPositionBefore( + $board_phid, + $column_phid, + $object_phid, + $before_phid) { + + return $this->queueAddPositionRelative( + $board_phid, + $column_phid, + $object_phid, + $before_phid, + true); + } + + public function queueAddPositionAfter( + $board_phid, + $column_phid, + $object_phid, + $after_phid) { + + return $this->queueAddPositionRelative( + $board_phid, + $column_phid, + $object_phid, + $after_phid, + false); + } + + public function queueAddPosition( + $board_phid, + $column_phid, + $object_phid) { + return $this->queueAddPositionRelative( + $board_phid, + $column_phid, + $object_phid, + null, + true); + } + + private function queueAddPositionRelative( + $board_phid, + $column_phid, + $object_phid, + $relative_phid, + $is_before) { + + $board_layout = idx($this->boardLayout, $board_phid, array()); + $positions = idx($board_layout, $column_phid, array()); + + // Check if the object is already in the column, and remove it if it is. + $object_position = idx($positions, $object_phid); + unset($positions[$object_phid]); + + if (!$object_position) { + $object_position = id(new PhabricatorProjectColumnPosition()) + ->setBoardPHID($board_phid) + ->setColumnPHID($column_phid) + ->setObjectPHID($object_phid); + } + + $found = false; + if (!$positions) { + $object_position->setSequence(0); + } else { + foreach ($positions as $position) { + if (!$found) { + if ($relative_phid === null) { + $is_match = true; + } else { + $position_phid = $position->getObjectPHID(); + $is_match = ($relative_phid == $position_phid); + } + + if ($is_match) { + $found = true; + + $sequence = $position->getSequence(); + + if (!$is_before) { + $sequence++; + } + + $object_position->setSequence($sequence++); + + if (!$is_before) { + // If we're inserting after this position, continue the loop so + // we don't update it. + continue; + } + } + } + + if ($found) { + $position->setSequence($sequence++); + $this->addQueue[] = $position; + } + } + } + + if ($relative_phid && !$found) { + throw new Exception( + pht( + 'Unable to find object "%s" in column "%s" on board "%s".', + $relative_phid, + $column_phid, + $board_phid)); + } + + $this->addQueue[] = $object_position; + + $positions[$object_phid] = $object_position; + $positions = msort($positions, 'getOrderingKey'); + + $this->boardLayout[$board_phid][$column_phid] = $positions; + + return $this; + } + + public function applyPositionUpdates() { + foreach ($this->remQueue as $position) { + if ($position->getID()) { + $position->delete(); + } + } + $this->remQueue = array(); + + $adds = array(); + $updates = array(); + + foreach ($this->addQueue as $position) { + $id = $position->getID(); + if ($id) { + $updates[$id] = $position; + } else { + $adds[] = $position; + } + } + $this->addQueue = array(); + + $table = new PhabricatorProjectColumnPosition(); + $conn_w = $table->establishConnection('w'); + + $pairs = array(); + foreach ($updates as $id => $position) { + // This is ugly because MySQL gets upset with us if it is configured + // strictly and we attempt inserts which can't work. We'll never actually + // do these inserts since they'll always collide (triggering the ON + // DUPLICATE KEY logic), so we just provide dummy values in order to get + // there. + + $pairs[] = qsprintf( + $conn_w, + '(%d, %d, "", "", "")', + $id, + $position->getSequence()); + } + + if ($pairs) { + queryfx( + $conn_w, + 'INSERT INTO %T (id, sequence, boardPHID, columnPHID, objectPHID) + VALUES %Q ON DUPLICATE KEY UPDATE sequence = VALUES(sequence)', + $table->getTableName(), + implode(', ', $pairs)); + } + + foreach ($adds as $position) { + $position->save(); + } + + return $this; + } + + private function loadBoards() { + $viewer = $this->getViewer(); + $board_phids = $this->getBoardPHIDs(); + + $boards = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withPHIDs($board_phids) + ->execute(); + $boards = mpull($boards, null, 'getPHID'); + + foreach ($boards as $key => $board) { + if (!$board->getHasWorkboard()) { + unset($boards[$key]); + } + } + + return $boards; + } + + private function loadColumns(array $boards) { + $viewer = $this->getViewer(); + + $columns = id(new PhabricatorProjectColumnQuery()) + ->setViewer($viewer) + ->withProjectPHIDs(array_keys($boards)) + ->execute(); + $columns = msort($columns, 'getOrderingKey'); + $columns = mpull($columns, null, 'getPHID'); + + $need_children = array(); + foreach ($boards as $phid => $board) { + if ($board->getHasMilestones() || $board->getHasSubprojects()) { + $need_children[] = $phid; + } + } + + if ($need_children) { + $children = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withParentProjectPHIDs($need_children) + ->execute(); + $children = mpull($children, null, 'getPHID'); + $children = mgroup($children, 'getParentProjectPHID'); + } else { + $children = array(); + } + + $columns = mgroup($columns, 'getProjectPHID'); + foreach ($boards as $board_phid => $board) { + $board_columns = idx($columns, $board_phid, array()); + + // If the project has milestones, create any missing columns. + if ($board->getHasMilestones() || $board->getHasSubprojects()) { + $child_projects = idx($children, $board_phid, array()); + + $next_sequence = last($board_columns)->getSequence() + 1; + $proxy_columns = mpull($board_columns, null, 'getProxyPHID'); + foreach ($child_projects as $child_phid => $child) { + if (isset($proxy_columns[$child_phid])) { + continue; + } + + $new_column = PhabricatorProjectColumn::initializeNewColumn($viewer) + ->attachProject($board) + ->attachProxy($child) + ->setSequence($next_sequence++) + ->setProjectPHID($board_phid) + ->setProxyPHID($child_phid); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $new_column->save(); + unset($unguarded); + + $board_columns[$new_column->getPHID()] = $new_column; + } + } + + $board_columns = msort($board_columns, 'getOrderingKey'); + + $columns[$board_phid] = $board_columns; + } + + foreach ($columns as $board_phid => $board_columns) { + foreach ($board_columns as $board_column) { + $column_phid = $board_column->getPHID(); + $this->columnMap[$column_phid] = $board_column; + } + } + + return $columns; + } + + private function loadPositions(array $boards) { + $viewer = $this->getViewer(); + + $object_phids = $this->getObjectPHIDs(); + if (!$object_phids) { + return array(); + } + + $positions = id(new PhabricatorProjectColumnPositionQuery()) + ->setViewer($viewer) + ->withBoardPHIDs(array_keys($boards)) + ->withObjectPHIDs($object_phids) + ->execute(); + $positions = msort($positions, 'getOrderingKey'); + $positions = mgroup($positions, 'getBoardPHID'); + + return $positions; + } + + private function layoutBoard( + $board, + array $columns, + array $positions) { + + $viewer = $this->getViewer(); + + $board_phid = $board->getPHID(); + $position_groups = mgroup($positions, 'getObjectPHID'); + + $layout = array(); + foreach ($columns as $column) { + $column_phid = $column->getPHID(); + $layout[$column_phid] = array(); + + if ($column->isDefaultColumn()) { + $default_phid = $column_phid; + } + } + + // Find all the columns which are proxies for other objects. + $proxy_map = array(); + foreach ($columns as $column) { + $proxy_phid = $column->getProxyPHID(); + if ($proxy_phid) { + $proxy_map[$proxy_phid] = $column->getPHID(); + } + } + + $object_phids = $this->getObjectPHIDs(); + + // If we have proxies, we need to force cards into the correct proxy + // columns. + if ($proxy_map) { + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs($object_phids) + ->withEdgeTypes( + array( + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, + )); + $edge_query->execute(); + + $project_phids = $edge_query->getDestinationPHIDs(); + $project_phids = array_fuse($project_phids); + } else { + $project_phids = array(); + } + + if ($project_phids) { + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withPHIDs($project_phids) + ->execute(); + $projects = mpull($projects, null, 'getPHID'); + } else { + $projects = array(); + } + + // Build a map from every project that any task is tagged with to the + // ancestor project which has a column on this board, if one exists. + $ancestor_map = array(); + foreach ($projects as $phid => $project) { + if (isset($proxy_map[$phid])) { + $ancestor_map[$phid] = $proxy_map[$phid]; + } else { + $seen = array($phid); + foreach ($project->getAncestorProjects() as $ancestor) { + $ancestor_phid = $ancestor->getPHID(); + $seen[] = $ancestor_phid; + if (isset($proxy_map[$ancestor_phid])) { + foreach ($seen as $project_phid) { + $ancestor_map[$project_phid] = $proxy_map[$ancestor_phid]; + } + } + } + } + } + + foreach ($object_phids as $object_phid) { + $positions = idx($position_groups, $object_phid, array()); + + // First, check for objects that have corresponding proxy columns. We're + // going to overwrite normal column positions if a tag belongs to a proxy + // column, since you can't be in normal columns if you're in proxy + // columns. + $proxy_hits = array(); + if ($proxy_map) { + $object_project_phids = $edge_query->getDestinationPHIDs( + array( + $object_phid, + )); + + foreach ($object_project_phids as $project_phid) { + if (isset($ancestor_map[$project_phid])) { + $proxy_hits[] = $ancestor_map[$project_phid]; + } + } + } + + if ($proxy_hits) { + // TODO: For now, only one column hit is permissible. + $proxy_hits = array_slice($proxy_hits, 0, 1); + + $proxy_hits = array_fuse($proxy_hits); + + // Check the object positions: we hope to find a position in each + // column the object should be part of. We're going to drop any + // invalid positions and create new positions where positions are + // missing. + foreach ($positions as $key => $position) { + $column_phid = $position->getColumnPHID(); + if (isset($proxy_hits[$column_phid])) { + // Valid column, mark the position as found. + unset($proxy_hits[$column_phid]); + } else { + // Invalid column, ignore the position. + unset($positions[$key]); + } + } + + // Create new positions for anything we haven't found. + foreach ($proxy_hits as $proxy_hit) { + $new_position = id(new PhabricatorProjectColumnPosition()) + ->setBoardPHID($board_phid) + ->setColumnPHID($proxy_hit) + ->setObjectPHID($object_phid) + ->setSequence(0); + + $this->addQueue[] = $new_position; + + $positions[] = $new_position; + } + } else { + // Ignore any positions in columns which no longer exist. We don't + // actively destory them because the rest of the code ignores them and + // there's no real need to destroy the data. + foreach ($positions as $key => $position) { + $column_phid = $position->getColumnPHID(); + if (empty($columns[$column_phid])) { + unset($positions[$key]); + } + } + + // If the object has no position, put it on the default column. + if (!$positions) { + $new_position = id(new PhabricatorProjectColumnPosition()) + ->setBoardPHID($board_phid) + ->setColumnPHID($default_phid) + ->setObjectPHID($object_phid) + ->setSequence(0); + + $this->addQueue[] = $new_position; + + $positions = array( + $new_position, + ); + } + } + + foreach ($positions as $position) { + $column_phid = $position->getColumnPHID(); + $layout[$column_phid][$object_phid] = $position; + } + } + + foreach ($layout as $column_phid => $map) { + $map = msort($map, 'getOrderingKey'); + $layout[$column_phid] = $map; + + foreach ($map as $object_phid => $position) { + $this->objectColumnMap[$board_phid][$object_phid][] = $column_phid; + } + } + + $this->boardLayout[$board_phid] = $layout; + } + +} diff --git a/src/applications/project/engine/PhabricatorProjectEditEngine.php b/src/applications/project/engine/PhabricatorProjectEditEngine.php index 95416989df..54144cbbb5 100644 --- a/src/applications/project/engine/PhabricatorProjectEditEngine.php +++ b/src/applications/project/engine/PhabricatorProjectEditEngine.php @@ -176,7 +176,7 @@ final class PhabricatorProjectEditEngine $milestone_phid = null; } - return array( + $fields = array( id(new PhabricatorHandlesEditField()) ->setKey('parent') ->setLabel(pht('Parent')) @@ -243,6 +243,49 @@ final class PhabricatorProjectEditEngine ->setConduitTypeDescription(pht('New list of slugs.')) ->setValue($slugs), ); + + $can_edit_members = (!$milestone) && + (!$object->isMilestone()) && + (!$object->getHasSubprojects()); + + if ($can_edit_members) { + + // Show this on the web UI when creating a project, but not when editing + // one. It is always available via Conduit. + $conduit_only = !$this->getIsCreate(); + + $members_field = id(new PhabricatorUsersEditField()) + ->setKey('members') + ->setAliases(array('memberPHIDs')) + ->setLabel(pht('Initial Members')) + ->setIsConduitOnly($conduit_only) + ->setUseEdgeTransactions(true) + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) + ->setMetadataValue( + 'edge:type', + PhabricatorProjectProjectHasMemberEdgeType::EDGECONST) + ->setDescription(pht('Initial project members.')) + ->setConduitDescription(pht('Set project members.')) + ->setConduitTypeDescription(pht('New list of members.')) + ->setValue(array()); + + $members_field->setViewer($this->getViewer()); + + $edit_add = $members_field->getConduitEditType('members.add') + ->setConduitDescription(pht('Add members.')); + + $edit_set = $members_field->getConduitEditType('members.set') + ->setConduitDescription( + pht('Set members, overwriting the current value.')); + + $edit_rem = $members_field->getConduitEditType('members.remove') + ->setConduitDescription(pht('Remove members.')); + + $fields[] = $members_field; + } + + return $fields; + } } diff --git a/src/applications/project/engine/PhabricatorProjectProfilePanelEngine.php b/src/applications/project/engine/PhabricatorProjectProfilePanelEngine.php index 612022d380..58b9b0b1ab 100644 --- a/src/applications/project/engine/PhabricatorProjectProfilePanelEngine.php +++ b/src/applications/project/engine/PhabricatorProjectProfilePanelEngine.php @@ -28,6 +28,10 @@ final class PhabricatorProjectProfilePanelEngine ->setBuiltinKey(PhabricatorProject::PANEL_MEMBERS) ->setPanelKey(PhabricatorProjectMembersProfilePanel::PANELKEY); + $panels[] = $this->newPanel() + ->setBuiltinKey(PhabricatorProject::PANEL_SUBPROJECTS) + ->setPanelKey(PhabricatorProjectSubprojectsProfilePanel::PANELKEY); + $panels[] = $this->newPanel() ->setBuiltinKey(PhabricatorProject::PANEL_MANAGE) ->setPanelKey(PhabricatorProjectManageProfilePanel::PANELKEY); diff --git a/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php b/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php index e1460381ed..567f5b749e 100644 --- a/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php +++ b/src/applications/project/engineextension/PhabricatorProjectsMembershipIndexEngineExtension.php @@ -61,6 +61,15 @@ final class PhabricatorProjectsMembershipIndexEngineExtension $conn_w = $project->establishConnection('w'); + $any_milestone = queryfx_one( + $conn_w, + 'SELECT id FROM %T + WHERE parentProjectPHID = %s AND milestoneNumber IS NOT NULL + LIMIT 1', + $project->getTableName(), + $project_phid); + $has_milestones = (bool)$any_milestone; + $project->openTransaction(); // Delete any existing materialized member edges. @@ -92,6 +101,14 @@ final class PhabricatorProjectsMembershipIndexEngineExtension (int)$has_subprojects, $project->getID()); + // Update the hasMilestones flag. + queryfx( + $conn_w, + 'UPDATE %T SET hasMilestones = %d WHERE id = %d', + $project->getTableName(), + (int)$has_milestones, + $project->getID()); + $project->saveTransaction(); } diff --git a/src/applications/project/events/PhabricatorProjectUIEventListener.php b/src/applications/project/events/PhabricatorProjectUIEventListener.php index 55a4a2c4a0..afbc0aab4e 100644 --- a/src/applications/project/events/PhabricatorProjectUIEventListener.php +++ b/src/applications/project/events/PhabricatorProjectUIEventListener.php @@ -49,49 +49,42 @@ final class PhabricatorProjectUIEventListener $annotations = array(); if ($handles && $can_appear_on_boards) { + $engine = id(new PhabricatorBoardLayoutEngine()) + ->setViewer($user) + ->setBoardPHIDs($project_phids) + ->setObjectPHIDs(array($object->getPHID())) + ->executeLayout(); // TDOO: Generalize this UI and move it out of Maniphest. - require_celerity_resource('maniphest-task-summary-css'); - $positions_query = id(new PhabricatorProjectColumnPositionQuery()) - ->setViewer($user) - ->withBoardPHIDs($project_phids) - ->withObjectPHIDs(array($object->getPHID())) - ->needColumns(true); - - // This is important because positions will be created "on demand" - // based on the set of columns. If we don't specify it, positions - // won't be created. - $columns = id(new PhabricatorProjectColumnQuery()) - ->setViewer($user) - ->withProjectPHIDs($project_phids) - ->execute(); - if ($columns) { - $positions_query->withColumns($columns); - } - $positions = $positions_query->execute(); - $positions = mpull($positions, null, 'getBoardPHID'); - foreach ($project_phids as $project_phid) { $handle = $handles[$project_phid]; - $position = idx($positions, $project_phid); - if ($position) { - $column = $position->getColumn(); + $columns = $engine->getObjectColumns( + $project_phid, + $object->getPHID()); + + $annotation = array(); + foreach ($columns as $column) { + $project_id = $column->getProject()->getID(); $column_name = pht('(%s)', $column->getDisplayName()); $column_link = phutil_tag( 'a', array( - 'href' => $handle->getURI().'board/', + 'href' => "/project/board/{$project_id}/", 'class' => 'maniphest-board-link', ), $column_name); + $annotation[] = $column_link; + } + + if ($annotation) { $annotations[$project_phid] = array( ' ', - $column_link, + phutil_implode_html(', ', $annotation), ); } } diff --git a/src/applications/project/events/ProjectHovercardEngineExtension.php b/src/applications/project/events/ProjectHovercardEngineExtension.php new file mode 100644 index 0000000000..deef9a30f8 --- /dev/null +++ b/src/applications/project/events/ProjectHovercardEngineExtension.php @@ -0,0 +1,55 @@ +getViewer(); + $phids = mpull($objects, 'getPHID'); + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withPHIDs($phids) + ->needImages(true) + ->execute(); + $projects = mpull($projects, null, 'getPHID'); + + return array( + 'projects' => $projects, + ); + } + + public function renderHovercard( + PHUIHovercardView $hovercard, + PhabricatorObjectHandle $handle, + $object, + $data) { + $viewer = $this->getViewer(); + + $project = idx($data['projects'], $object->getPHID()); + if (!$project) { + return; + } + + $project_card = id(new PhabricatorProjectCardView()) + ->setProject($project) + ->setViewer($viewer); + + $hovercard->appendChild($project_card); + } + +} diff --git a/src/applications/project/interface/PhabricatorColumnProxyInterface.php b/src/applications/project/interface/PhabricatorColumnProxyInterface.php new file mode 100644 index 0000000000..4e3c882d35 --- /dev/null +++ b/src/applications/project/interface/PhabricatorColumnProxyInterface.php @@ -0,0 +1,7 @@ + $handle) { $project = $objects[$phid]; - $name = $project->getName(); + $name = $project->getDisplayName(); $id = $project->getID(); $slug = $project->getPrimarySlug(); diff --git a/src/applications/project/policyrule/PhabricatorProjectsPolicyRule.php b/src/applications/project/policyrule/PhabricatorProjectsPolicyRule.php index 1782b62a9d..3977b542c1 100644 --- a/src/applications/project/policyrule/PhabricatorProjectsPolicyRule.php +++ b/src/applications/project/policyrule/PhabricatorProjectsPolicyRule.php @@ -48,7 +48,13 @@ final class PhabricatorProjectsPolicyRule } public function getValueControlTemplate() { - return $this->getDatasourceTemplate(new PhabricatorProjectDatasource()); + $datasource = id(new PhabricatorProjectDatasource()) + ->setParameters( + array( + 'policy' => 1, + )); + + return $this->getDatasourceTemplate($datasource); } public function getRuleOrder() { diff --git a/src/applications/project/profilepanel/PhabricatorProjectSubprojectsProfilePanel.php b/src/applications/project/profilepanel/PhabricatorProjectSubprojectsProfilePanel.php new file mode 100644 index 0000000000..fad6378d69 --- /dev/null +++ b/src/applications/project/profilepanel/PhabricatorProjectSubprojectsProfilePanel.php @@ -0,0 +1,63 @@ +getPanelProperty('name'); + + if (strlen($name)) { + return $name; + } + + return $this->getDefaultName(); + } + + public function buildEditEngineFields( + PhabricatorProfilePanelConfiguration $config) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setPlaceholder($this->getDefaultName()) + ->setValue($config->getPanelProperty('name')), + ); + } + + protected function newNavigationMenuItems( + PhabricatorProfilePanelConfiguration $config) { + + $project = $config->getProfileObject(); + + $has_children = ($project->getHasSubprojects()) || + ($project->getHasMilestones()); + + $id = $project->getID(); + + $name = $this->getDisplayName($config); + $icon = 'fa-sitemap'; + $href = "/project/subprojects/{$id}/"; + + $item = $this->newItem() + ->setHref($href) + ->setName($name) + ->setDisabled(!$has_children) + ->setIcon($icon); + + return array( + $item, + ); + } + +} diff --git a/src/applications/project/query/PhabricatorProjectColumnPositionQuery.php b/src/applications/project/query/PhabricatorProjectColumnPositionQuery.php index 3348b4054d..438c558e6e 100644 --- a/src/applications/project/query/PhabricatorProjectColumnPositionQuery.php +++ b/src/applications/project/query/PhabricatorProjectColumnPositionQuery.php @@ -6,10 +6,7 @@ final class PhabricatorProjectColumnPositionQuery private $ids; private $boardPHIDs; private $objectPHIDs; - private $columns; - - private $needColumns; - private $skipImplicitCreate; + private $columnPHIDs; public function withIDs(array $ids) { $this->ids = $ids; @@ -26,277 +23,51 @@ final class PhabricatorProjectColumnPositionQuery return $this; } - /** - * Find objects in specific columns. - * - * NOTE: Using this method activates logic which constructs virtual - * column positions for objects not in any column, if you pass a default - * column. Normally these results are not returned. - * - * @param list Columns to look for objects in. - * @return this - */ - public function withColumns(array $columns) { - assert_instances_of($columns, 'PhabricatorProjectColumn'); - $this->columns = $columns; + public function withColumnPHIDs(array $column_phids) { + $this->columnPHIDs = $column_phids; return $this; } - public function needColumns($need_columns) { - $this->needColumns = true; - return $this; - } - - - /** - * Skip implicit creation of column positions which are implied but do not - * yet exist. - * - * This is primarily useful internally. - * - * @param bool True to skip implicit creation of column positions. - * @return this - */ - public function setSkipImplicitCreate($skip) { - $this->skipImplicitCreate = $skip; - return $this; - } - - // NOTE: For now, boards are always attached to projects. However, they might - // not be in the future. This generalization just anticipates a future where - // we let other types of objects (like users) have boards, or let boards - // contain other types of objects. - - private function newPositionObject() { + public function newResultObject() { return new PhabricatorProjectColumnPosition(); } - private function newColumnQuery() { - return new PhabricatorProjectColumnQuery(); - } - - private function getBoardMembershipEdgeTypes() { - return array( - PhabricatorProjectProjectHasObjectEdgeType::EDGECONST, - ); - } - - private function getBoardMembershipPHIDTypes() { - return array( - ManiphestTaskPHIDType::TYPECONST, - ); - } - protected function loadPage() { - $table = $this->newPositionObject(); - $conn_r = $table->establishConnection('r'); - - // We're going to find results by combining two queries: one query finds - // objects on a board column, while the other query finds objects not on - // any board column and virtually puts them on the default column. - - $unions = array(); - - // First, find all the stuff that's actually on a column. - - $unions[] = qsprintf( - $conn_r, - 'SELECT * FROM %T %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r)); - - // If we have a default column, find all the stuff that's not in any - // column and put it in the default column. - - $must_type_filter = false; - if ($this->columns && !$this->skipImplicitCreate) { - $default_map = array(); - foreach ($this->columns as $column) { - if ($column->isDefaultColumn()) { - $default_map[$column->getProjectPHID()] = $column->getPHID(); - } - } - - if ($default_map) { - $where = array(); - - // Find the edges attached to the boards we have default columns for. - - $where[] = qsprintf( - $conn_r, - 'e.src IN (%Ls)', - array_keys($default_map)); - - // Find only edges which describe a board relationship. - - $where[] = qsprintf( - $conn_r, - 'e.type IN (%Ld)', - $this->getBoardMembershipEdgeTypes()); - - if ($this->boardPHIDs !== null) { - // This should normally be redundant, but construct it anyway if - // the caller has told us to. - $where[] = qsprintf( - $conn_r, - 'e.src IN (%Ls)', - $this->boardPHIDs); - } - - if ($this->objectPHIDs !== null) { - $where[] = qsprintf( - $conn_r, - 'e.dst IN (%Ls)', - $this->objectPHIDs); - } - - $where[] = qsprintf( - $conn_r, - 'p.id IS NULL'); - - $where = $this->formatWhereClause($where); - - $unions[] = qsprintf( - $conn_r, - 'SELECT NULL id, e.src boardPHID, NULL columnPHID, e.dst objectPHID, - 0 sequence - FROM %T e LEFT JOIN %T p - ON e.src = p.boardPHID AND e.dst = p.objectPHID - %Q', - PhabricatorEdgeConfig::TABLE_NAME_EDGE, - $table->getTableName(), - $where); - - $must_type_filter = true; - } - } - - $data = queryfx_all( - $conn_r, - '%Q %Q %Q', - implode(' UNION ALL ', $unions), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - // If we've picked up objects not in any column, we need to filter out any - // matched objects which have the wrong edge type. - if ($must_type_filter) { - $allowed_types = array_fuse($this->getBoardMembershipPHIDTypes()); - foreach ($data as $id => $row) { - if ($row['columnPHID'] === null) { - $object_phid = $row['objectPHID']; - if (empty($allowed_types[phid_get_type($object_phid)])) { - unset($data[$id]); - } - } - } - } - - $positions = $table->loadAllFromArray($data); - - // Find the implied positions which don't exist yet. If there are any, - // we're going to create them. - $create = array(); - foreach ($positions as $position) { - if ($position->getColumnPHID() === null) { - $column_phid = idx($default_map, $position->getBoardPHID()); - $position->setColumnPHID($column_phid); - - $create[] = $position; - } - } - - if ($create) { - // If we're adding several objects to a column, insert the column - // position objects in object ID order. This means that newly added - // objects float to the top, and when a group of newly added objects - // float up at the same time, the most recently created ones end up - // highest in the list. - - $objects = id(new PhabricatorObjectQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withPHIDs(mpull($create, 'getObjectPHID')) - ->execute(); - $objects = mpull($objects, null, 'getPHID'); - $objects = msort($objects, 'getID'); - - $create = mgroup($create, 'getObjectPHID'); - $create = array_select_keys($create, array_keys($objects)) + $create; - - $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); - - foreach ($create as $object_phid => $create_positions) { - foreach ($create_positions as $create_position) { - $create_position->save(); - } - } - - unset($unguarded); - } - - return $positions; + return $this->loadStandardPage($this->newResultObject()); } - protected function willFilterPage(array $page) { - - if ($this->needColumns) { - $column_phids = mpull($page, 'getColumnPHID'); - $columns = $this->newColumnQuery() - ->setParentQuery($this) - ->setViewer($this->getViewer()) - ->withPHIDs($column_phids) - ->execute(); - $columns = mpull($columns, null, 'getPHID'); - - foreach ($page as $key => $position) { - $column = idx($columns, $position->getColumnPHID()); - if (!$column) { - unset($page[$key]); - continue; - } - - $position->attachColumn($column); - } - } - - return $page; - } - - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = array(); if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } if ($this->boardPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'boardPHID IN (%Ls)', $this->boardPHIDs); } if ($this->objectPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'objectPHID IN (%Ls)', $this->objectPHIDs); } - if ($this->columns !== null) { + if ($this->columnPHIDs !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'columnPHID IN (%Ls)', - mpull($this->columns, 'getPHID')); + $this->columnPHIDs); } - // NOTE: Explicitly not building the paging clause here, since it won't - // work with the UNION. - - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/project/query/PhabricatorProjectColumnQuery.php b/src/applications/project/query/PhabricatorProjectColumnQuery.php index 2fe901cec7..41ea770137 100644 --- a/src/applications/project/query/PhabricatorProjectColumnQuery.php +++ b/src/applications/project/query/PhabricatorProjectColumnQuery.php @@ -6,6 +6,7 @@ final class PhabricatorProjectColumnQuery private $ids; private $phids; private $projectPHIDs; + private $proxyPHIDs; private $statuses; public function withIDs(array $ids) { @@ -23,6 +24,11 @@ final class PhabricatorProjectColumnQuery return $this; } + public function withProxyPHIDs(array $proxy_phids) { + $this->proxyPHIDs = $proxy_phids; + return $this; + } + public function withStatuses(array $status) { $this->statuses = $status; return $this; @@ -60,6 +66,55 @@ final class PhabricatorProjectColumnQuery $column->attachProject($project); } + $proxy_phids = array_filter(mpull($page, 'getProjectPHID')); + + return $page; + } + + protected function didFilterPage(array $page) { + $proxy_phids = array(); + foreach ($page as $column) { + $proxy_phid = $column->getProxyPHID(); + if ($proxy_phid !== null) { + $proxy_phids[$proxy_phid] = $proxy_phid; + } + } + + if ($proxy_phids) { + $proxies = id(new PhabricatorObjectQuery()) + ->setParentQuery($this) + ->setViewer($this->getViewer()) + ->withPHIDs($proxy_phids) + ->execute(); + $proxies = mpull($proxies, null, 'getPHID'); + } else { + $proxies = array(); + } + + foreach ($page as $key => $column) { + $proxy_phid = $column->getProxyPHID(); + + if ($proxy_phid !== null) { + $proxy = idx($proxies, $proxy_phid); + + // Only attach valid proxies, so we don't end up getting surprsied if + // an install somehow gets junk into their database. + if (!($proxy instanceof PhabricatorColumnProxyInterface)) { + $proxy = null; + } + + if (!$proxy) { + $this->didRejectResult($column); + unset($page[$key]); + continue; + } + } else { + $proxy = null; + } + + $column->attachProxy($proxy); + } + return $page; } @@ -87,6 +142,13 @@ final class PhabricatorProjectColumnQuery $this->projectPHIDs); } + if ($this->proxyPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'proxyPHID IN (%Ls)', + $this->proxyPHIDs); + } + if ($this->statuses !== null) { $where[] = qsprintf( $conn, diff --git a/src/applications/project/remarkup/ProjectRemarkupRule.php b/src/applications/project/remarkup/ProjectRemarkupRule.php index 43253e5b67..70d6b8eda3 100644 --- a/src/applications/project/remarkup/ProjectRemarkupRule.php +++ b/src/applications/project/remarkup/ProjectRemarkupRule.php @@ -16,7 +16,9 @@ final class ProjectRemarkupRule extends PhabricatorObjectRemarkupRule { return '#'.$id; } - return $handle->renderTag(); + $tag = $handle->renderTag(); + $tag->setPHID($handle->getPHID()); + return $tag; } protected function getObjectIDPattern() { diff --git a/src/applications/project/storage/PhabricatorProject.php b/src/applications/project/storage/PhabricatorProject.php index 6cf116688e..093b368ef7 100644 --- a/src/applications/project/storage/PhabricatorProject.php +++ b/src/applications/project/storage/PhabricatorProject.php @@ -9,7 +9,8 @@ final class PhabricatorProject extends PhabricatorProjectDAO PhabricatorCustomFieldInterface, PhabricatorDestructibleInterface, PhabricatorFulltextInterface, - PhabricatorConduitResultInterface { + PhabricatorConduitResultInterface, + PhabricatorColumnProxyInterface { protected $name; protected $status = PhabricatorProjectStatus::STATUS_ACTIVE; @@ -378,7 +379,7 @@ final class PhabricatorProject extends PhabricatorProjectDAO $this->getPHID()); $all_strings = ipull($slugs, 'slug'); - $all_strings[] = $this->getName(); + $all_strings[] = $this->getDisplayName(); $all_strings = implode(' ', $all_strings); $tokens = PhabricatorTypeaheadDatasource::tokenizeString($all_strings); @@ -489,6 +490,20 @@ final class PhabricatorProject extends PhabricatorProjectDAO return $number; } + public function getDisplayName() { + $name = $this->getName(); + + // If this is a milestone, show it as "Parent > Sprint 99". + if ($this->isMilestone()) { + $name = pht( + '%s (%s)', + $this->getParentProject()->getName(), + $name); + } + + return $name; + } + public function getDisplayIconKey() { if ($this->isMilestone()) { $key = PhabricatorProjectIconSet::getMilestoneIconKey(); @@ -663,4 +678,25 @@ final class PhabricatorProject extends PhabricatorProjectDAO ); } + +/* -( PhabricatorColumnProxyInterface )------------------------------------ */ + + + public function getProxyColumnName() { + return $this->getName(); + } + + public function getProxyColumnIcon() { + return $this->getDisplayIconIcon(); + } + + public function getProxyColumnClass() { + if ($this->isMilestone()) { + return 'phui-workboard-column-milestone'; + } + + return null; + } + + } diff --git a/src/applications/project/storage/PhabricatorProjectColumn.php b/src/applications/project/storage/PhabricatorProjectColumn.php index ca124fec96..0ab6ec89c0 100644 --- a/src/applications/project/storage/PhabricatorProjectColumn.php +++ b/src/applications/project/storage/PhabricatorProjectColumn.php @@ -17,15 +17,18 @@ final class PhabricatorProjectColumn protected $name; protected $status; protected $projectPHID; + protected $proxyPHID; protected $sequence; protected $properties = array(); private $project = self::ATTACHABLE; + private $proxy = self::ATTACHABLE; public static function initializeNewColumn(PhabricatorUser $user) { return id(new PhabricatorProjectColumn()) ->setName('') - ->setStatus(self::STATUS_ACTIVE); + ->setStatus(self::STATUS_ACTIVE) + ->attachProxy(null); } protected function getConfiguration() { @@ -38,6 +41,7 @@ final class PhabricatorProjectColumn 'name' => 'text255', 'status' => 'uint32', 'sequence' => 'uint32', + 'proxyPHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'key_status' => array( @@ -46,6 +50,10 @@ final class PhabricatorProjectColumn 'key_sequence' => array( 'columns' => array('projectPHID', 'sequence'), ), + 'key_proxy' => array( + 'columns' => array('projectPHID', 'proxyPHID'), + 'unique' => true, + ), ), ) + parent::getConfiguration(); } @@ -64,6 +72,15 @@ final class PhabricatorProjectColumn return $this->assertAttached($this->project); } + public function attachProxy($proxy) { + $this->proxy = $proxy; + return $this; + } + + public function getProxy() { + return $this->assertAttached($this->proxy); + } + public function isDefaultColumn() { return (bool)$this->getProperty('isDefault'); } @@ -73,6 +90,11 @@ final class PhabricatorProjectColumn } public function getDisplayName() { + $proxy = $this->getProxy(); + if ($proxy) { + return $proxy->getProxyColumnName(); + } + $name = $this->getName(); if (strlen($name)) { return $name; @@ -96,11 +118,23 @@ final class PhabricatorProjectColumn return null; } + public function getDisplayClass() { + $proxy = $this->getProxy(); + if ($proxy) { + return $proxy->getProxyColumnClass(); + } + + return null; + } + public function getHeaderIcon() { - $icon = null; + $proxy = $this->getProxy(); + if ($proxy) { + return $proxy->getProxyColumnIcon(); + } if ($this->isHidden()) { - $icon = 'fa-eye-slash'; + return 'fa-eye-slash'; } return null; @@ -124,6 +158,25 @@ final class PhabricatorProjectColumn return $this; } + public function getOrderingKey() { + $proxy = $this->getProxy(); + + // Normal columns and subproject columns go first, in a user-controlled + // order. + + // All the milestone columns go last, in their sequential order. + + if (!$proxy || !$proxy->isMilestone()) { + $group = 'A'; + $sequence = $this->getSequence(); + } else { + $group = 'B'; + $sequence = $proxy->getMilestoneNumber(); + } + + return sprintf('%s%012d', $group, $sequence); + } + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/project/storage/PhabricatorProjectColumnPosition.php b/src/applications/project/storage/PhabricatorProjectColumnPosition.php index 7abcd7e372..c59676f8e4 100644 --- a/src/applications/project/storage/PhabricatorProjectColumnPosition.php +++ b/src/applications/project/storage/PhabricatorProjectColumnPosition.php @@ -41,6 +41,10 @@ final class PhabricatorProjectColumnPosition extends PhabricatorProjectDAO } public function getOrderingKey() { + if (!$this->getID() && !$this->getSequence()) { + return 0; + } + // Low sequence numbers go above high sequence numbers. // High position IDs go above low position IDs. // Broadly, this makes newly added stuff float to the top. diff --git a/src/applications/project/typeahead/PhabricatorProjectDatasource.php b/src/applications/project/typeahead/PhabricatorProjectDatasource.php index 9a18006e5c..33fb8df0e9 100644 --- a/src/applications/project/typeahead/PhabricatorProjectDatasource.php +++ b/src/applications/project/typeahead/PhabricatorProjectDatasource.php @@ -32,6 +32,12 @@ final class PhabricatorProjectDatasource $query->withNameTokens($tokens); } + // If this is for policy selection, prevent users from using milestones. + $for_policy = $this->getParameter('policy'); + if ($for_policy) { + $query->withIsMilestone(false); + } + $projs = $this->executeQuery($query); $projs = mpull($projs, null, 'getPHID'); @@ -58,13 +64,13 @@ final class PhabricatorProjectDatasource } $all_strings = mpull($proj->getSlugs(), 'getSlug'); - $all_strings[] = $proj->getName(); + $all_strings[] = $proj->getDisplayName(); $all_strings = implode(' ', $all_strings); $proj_result = id(new PhabricatorTypeaheadResult()) ->setName($all_strings) - ->setDisplayName($proj->getName()) - ->setDisplayType(pht('Project')) + ->setDisplayName($proj->getDisplayName()) + ->setDisplayType($proj->getDisplayIconName()) ->setURI($proj->getURI()) ->setPHID($proj->getPHID()) ->setIcon($proj->getDisplayIconIcon()) diff --git a/src/applications/project/view/PhabricatorProjectCardView.php b/src/applications/project/view/PhabricatorProjectCardView.php new file mode 100644 index 0000000000..f82ee8c99e --- /dev/null +++ b/src/applications/project/view/PhabricatorProjectCardView.php @@ -0,0 +1,84 @@ +project = $project; + return $this; + } + + public function setViewer(PhabricatorUser $viewer) { + $this->viewer = $viewer; + return $this; + } + + public function setTag($tag) { + $this->tag = $tag; + return $this; + } + + protected function getTagName() { + if ($this->tag) { + return $this->tag; + } + return 'div'; + } + + protected function getTagAttributes() { + $classes = array(); + $classes[] = 'project-card-view'; + + $color = $this->project->getColor(); + $classes[] = 'project-card-'.$color; + + return array( + 'class' => implode($classes, ' '), + ); + } + + protected function getTagContent() { + + $project = $this->project; + $viewer = $this->viewer; + require_celerity_resource('project-card-view-css'); + + $icon = $project->getDisplayIconIcon(); + $icon_name = $project->getDisplayIconName(); + $tag = id(new PHUITagView()) + ->setIcon($icon) + ->setName($icon_name) + ->addClass('project-view-header-tag') + ->setType(PHUITagView::TYPE_SHADE); + + $header = id(new PHUIHeaderView()) + ->setHeader(array($project->getDisplayName(), $tag)) + ->setUser($viewer) + ->setPolicyObject($project) + ->setImage($project->getProfileImageURI()); + + if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) { + $header->setStatus('fa-check', 'bluegrey', pht('Active')); + } else { + $header->setStatus('fa-ban', 'red', pht('Archived')); + } + + $description = null; + + $card = phutil_tag( + 'div', + array( + 'class' => 'project-card-inner', + ), + array( + $header, + $description, + )); + + return $card; + } + +} diff --git a/src/applications/project/view/PhabricatorProjectUserListView.php b/src/applications/project/view/PhabricatorProjectUserListView.php index 715e28f944..e7f6631bfb 100644 --- a/src/applications/project/view/PhabricatorProjectUserListView.php +++ b/src/applications/project/view/PhabricatorProjectUserListView.php @@ -86,7 +86,7 @@ abstract class PhabricatorProjectUserListView extends AphrontView { ->setImageURI($handle->getImageURI()); $icon = id(new PHUIIconView()) - ->setIcon($handle->getIcon().' lightbluetext'); + ->setIcon($handle->getIcon()); $subtitle = $handle->getSubtitle(); diff --git a/src/applications/project/view/ProjectBoardTaskCard.php b/src/applications/project/view/ProjectBoardTaskCard.php index 4fc26988d7..2c69e0f37b 100644 --- a/src/applications/project/view/ProjectBoardTaskCard.php +++ b/src/applications/project/view/ProjectBoardTaskCard.php @@ -3,6 +3,7 @@ final class ProjectBoardTaskCard extends Phobject { private $viewer; + private $projectHandles; private $task; private $owner; private $canEdit; @@ -15,6 +16,15 @@ final class ProjectBoardTaskCard extends Phobject { return $this->viewer; } + public function setProjectHandles(array $handles) { + $this->projectHandles = $handles; + return $this; + } + + public function getProjectHandles() { + return $this->projectHandles; + } + public function setTask(ManiphestTask $task) { $this->task = $task; return $this; @@ -44,13 +54,14 @@ final class ProjectBoardTaskCard extends Phobject { $task = $this->getTask(); $owner = $this->getOwner(); $can_edit = $this->getCanEdit(); + $viewer = $this->getViewer(); $color_map = ManiphestTaskPriority::getColorMap(); $bar_color = idx($color_map, $task->getPriority(), 'grey'); $card = id(new PHUIObjectItemView()) ->setObject($task) - ->setUser($this->getViewer()) + ->setUser($viewer) ->setObjectName('T'.$task->getID()) ->setHeader($task->getTitle()) ->setGrippable($can_edit) @@ -70,7 +81,23 @@ final class ProjectBoardTaskCard extends Phobject { ->setBarColor($bar_color); if ($owner) { - $card->addAttribute($owner->renderLink()); + $card->addHandleIcon($owner, $owner->getName()); + } + + if ($task->isClosed()) { + $icon = ManiphestTaskStatus::getStatusIcon($task->getStatus()); + $icon = id(new PHUIIconView()) + ->setIcon($icon.' grey'); + $card->addAttribute($icon); + $card->setBarColor('grey'); + } + + $project_handles = $this->getProjectHandles(); + if ($project_handles) { + $tag_list = id(new PHUIHandleTagListView()) + ->setSlim(true) + ->setHandles($project_handles); + $card->addAttribute($tag_list); } return $card; diff --git a/src/applications/repository/storage/PhabricatorRepositoryPullEvent.php b/src/applications/repository/storage/PhabricatorRepositoryPullEvent.php index d17fded9a8..c1227402d7 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryPullEvent.php +++ b/src/applications/repository/storage/PhabricatorRepositoryPullEvent.php @@ -30,7 +30,7 @@ final class PhabricatorRepositoryPullEvent self::CONFIG_COLUMN_SCHEMA => array( 'repositoryPHID' => 'phid?', 'pullerPHID' => 'phid?', - 'remoteAddress' => 'uint32?', + 'remoteAddress' => 'ipaddress?', 'remoteProtocol' => 'text32?', 'resultType' => 'text32', 'resultCode' => 'uint32', diff --git a/src/applications/repository/storage/PhabricatorRepositoryPushEvent.php b/src/applications/repository/storage/PhabricatorRepositoryPushEvent.php index 2455499b88..2bc751ffca 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryPushEvent.php +++ b/src/applications/repository/storage/PhabricatorRepositoryPushEvent.php @@ -29,7 +29,7 @@ final class PhabricatorRepositoryPushEvent self::CONFIG_AUX_PHID => true, self::CONFIG_TIMESTAMPS => false, self::CONFIG_COLUMN_SCHEMA => array( - 'remoteAddress' => 'uint32?', + 'remoteAddress' => 'ipaddress?', 'remoteProtocol' => 'text32?', 'rejectCode' => 'uint32', 'rejectDetails' => 'text64?', diff --git a/src/applications/search/controller/PhabricatorSearchHovercardController.php b/src/applications/search/controller/PhabricatorSearchHovercardController.php index 3f1e197996..2fecf80dfd 100644 --- a/src/applications/search/controller/PhabricatorSearchHovercardController.php +++ b/src/applications/search/controller/PhabricatorSearchHovercardController.php @@ -56,7 +56,7 @@ final class PhabricatorSearchHovercardController $handle = $handles[$phid]; $object = idx($objects, $phid); - $hovercard = id(new PhabricatorHovercardView()) + $hovercard = id(new PHUIHovercardView()) ->setUser($viewer) ->setObjectHandle($handle); diff --git a/src/applications/search/engine/PhabricatorProfilePanelEngine.php b/src/applications/search/engine/PhabricatorProfilePanelEngine.php index 09307cd340..a3907feac8 100644 --- a/src/applications/search/engine/PhabricatorProfilePanelEngine.php +++ b/src/applications/search/engine/PhabricatorProfilePanelEngine.php @@ -663,6 +663,7 @@ abstract class PhabricatorProfilePanelEngine extends Phobject { $header = id(new PHUIHeaderView()) ->setHeader(pht('Profile Menu Items')) + ->setSubHeader(pht('Drag tabs to reorder menu')) ->addActionLink($action_button); $box = id(new PHUIObjectBoxView()) @@ -907,7 +908,7 @@ abstract class PhabricatorProfilePanelEngine extends Phobject { $is_target = (($builtin_key !== null) && ($builtin_key === $key)) || - (($id !== null) && ($id === (int)$key)); + (($id !== null) && ((int)$id === (int)$key)); if ($is_target) { if (!$panel->isDefault()) { diff --git a/src/applications/search/engineextension/PhabricatorHovercardEngineExtension.php b/src/applications/search/engineextension/PhabricatorHovercardEngineExtension.php index 79eb9b60a4..060441c985 100644 --- a/src/applications/search/engineextension/PhabricatorHovercardEngineExtension.php +++ b/src/applications/search/engineextension/PhabricatorHovercardEngineExtension.php @@ -32,7 +32,7 @@ abstract class PhabricatorHovercardEngineExtension extends Phobject { } abstract public function renderHovercard( - PhabricatorHovercardView $hovercard, + PHUIHovercardView $hovercard, PhabricatorObjectHandle $handle, $object, $data); diff --git a/src/applications/search/profilepanel/PhabricatorMotivatorProfilePanel.php b/src/applications/search/profilepanel/PhabricatorMotivatorProfilePanel.php index 3537520903..34c767e99c 100644 --- a/src/applications/search/profilepanel/PhabricatorMotivatorProfilePanel.php +++ b/src/applications/search/profilepanel/PhabricatorMotivatorProfilePanel.php @@ -131,6 +131,15 @@ final class PhabricatorMotivatorProfilePanel pht( 'The Japanese word for cat is "kome", which is also the word for '. 'rice. Japanese cats love to eat rice, so the two are synonymous.'), + pht('Cats have five pointy ends.'), + pht('cat -A can find mice hiding in files.'), + pht('A cat\'s visual, olfactory, and auditory senses, '. + 'Contribute to their hunting skills and natural defenses.'), + pht( + 'Cats with high self-esteem seek out high perches '. + 'to launch their attacks. Watch out!'), + pht('Cats prefer vanilla ice cream.'), + pht('Taco cat spelled backwards is taco cat.'), ); } diff --git a/src/applications/settings/storage/PhabricatorUserPreferences.php b/src/applications/settings/storage/PhabricatorUserPreferences.php index 53c080ec88..271fd1afb4 100644 --- a/src/applications/settings/storage/PhabricatorUserPreferences.php +++ b/src/applications/settings/storage/PhabricatorUserPreferences.php @@ -42,6 +42,7 @@ final class PhabricatorUserPreferences extends PhabricatorUserDAO { const PREFERENCE_DESKTOP_NOTIFICATIONS = 'desktop-notifications'; const PREFERENCE_PROFILE_MENU_COLLAPSED = 'profile-menu.collapsed'; + const PREFERENCE_FAVORITE_POLICIES = 'policy.favorites'; // These are in an unusual order for historic reasons. const MAILTAG_PREFERENCE_NOTIFY = 0; diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 7fa83b3fb0..d6f70cac39 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -400,7 +400,15 @@ abstract class PhabricatorApplicationTransactionEditor return $space_phid; } case PhabricatorTransactions::TYPE_EDGE: - return $this->getEdgeTransactionNewValue($xaction); + $new_value = $this->getEdgeTransactionNewValue($xaction); + + $edge_type = $xaction->getMetadataValue('edge:type'); + $type_project = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; + if ($edge_type == $type_project) { + $new_value = $this->applyProjectConflictRules($new_value); + } + + return $new_value; case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getCustomFieldForTransaction($object, $xaction); return $field->getNewValueFromApplicationTransactions($xaction); @@ -3346,4 +3354,127 @@ abstract class PhabricatorApplicationTransactionEditor return $state; } + + /** + * Remove conflicts from a list of projects. + * + * Objects aren't allowed to be tagged with multiple milestones in the same + * group, nor projects such that one tag is the ancestor of any other tag. + * If the list of PHIDs include mutually exclusive projects, remove the + * conflicting projects. + * + * @param list List of project PHIDs. + * @return list List with conflicts removed. + */ + private function applyProjectConflictRules(array $phids) { + if (!$phids) { + return array(); + } + + // Overall, the last project in the list wins in cases of conflict (so when + // you add something, the thing you just added sticks and removes older + // values). + + // Beyond that, there are two basic cases: + + // Milestones: An object can't be in "A > Sprint 3" and "A > Sprint 4". + // If multiple projects are milestones of the same parent, we only keep the + // last one. + + // Ancestor: You can't be in "A" and "A > B". If "A > B" comes later + // in the list, we remove "A" and keep "A > B". If "A" comes later, we + // remove "A > B" and keep "A". + + // Note that it's OK to be in "A > B" and "A > C". There's only a conflict + // if one project is an ancestor of another. It's OK to have something + // tagged with multiple projects which share a common ancestor, so long as + // they are not mutual ancestors. + + $viewer = PhabricatorUser::getOmnipotentUser(); + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array_keys($phids)) + ->execute(); + $projects = mpull($projects, null, 'getPHID'); + + // We're going to build a map from each project with milestones to the last + // milestone in the list. This last milestone is the milestone we'll keep. + $milestone_map = array(); + + // We're going to build a set of the projects which have no descendants + // later in the list. This allows us to apply both ancestor rules. + $ancestor_map = array(); + + foreach ($phids as $phid => $ignored) { + $project = idx($projects, $phid); + if (!$project) { + continue; + } + + // This is the last milestone we've seen, so set it as the selection for + // the project's parent. This might be setting a new value or overwriting + // an earlier value. + if ($project->isMilestone()) { + $parent_phid = $project->getParentProjectPHID(); + $milestone_map[$parent_phid] = $phid; + } + + // Since this is the last item in the list we've examined so far, add it + // to the set of projects with no later descendants. + $ancestor_map[$phid] = $phid; + + // Remove any ancestors from the set, since this is a later descendant. + foreach ($project->getAncestorProjects() as $ancestor) { + $ancestor_phid = $ancestor->getPHID(); + unset($ancestor_map[$ancestor_phid]); + } + } + + // Now that we've built the maps, we can throw away all the projects which + // have conflicts. + foreach ($phids as $phid => $ignored) { + $project = idx($projects, $phid); + + if (!$project) { + // If a PHID is invalid, we just leave it as-is. We could clean it up, + // but leaving it untouched is less likely to cause collateral damage. + continue; + } + + // If this was a milestone, check if it was the last milestone from its + // group in the list. If not, remove it from the list. + if ($project->isMilestone()) { + $parent_phid = $project->getParentProjectPHID(); + if ($milestone_map[$parent_phid] !== $phid) { + unset($phids[$phid]); + continue; + } + } + + // If a later project in the list is a subproject of this one, it will + // have removed ancestors from the map. If this project does not point + // at itself in the ancestor map, it should be discarded in favor of a + // subproject that comes later. + if (idx($ancestor_map, $phid) !== $phid) { + unset($phids[$phid]); + continue; + } + + // If a later project in the list is an ancestor of this one, it will + // have added itself to the map. If any ancestor of this project points + // at itself in the map, this project should be dicarded in favor of + // that later ancestor. + foreach ($project->getAncestorProjects() as $ancestor) { + $ancestor_phid = $ancestor->getPHID(); + if (isset($ancestor_map[$ancestor_phid])) { + unset($phids[$phid]); + continue 2; + } + } + } + + return $phids; + } + } diff --git a/src/applications/typeahead/view/PhabricatorTypeaheadTokenView.php b/src/applications/typeahead/view/PhabricatorTypeaheadTokenView.php index 2dacd84022..56867d8278 100644 --- a/src/applications/typeahead/view/PhabricatorTypeaheadTokenView.php +++ b/src/applications/typeahead/view/PhabricatorTypeaheadTokenView.php @@ -32,7 +32,7 @@ final class PhabricatorTypeaheadTokenView $token = id(new PhabricatorTypeaheadTokenView()) ->setKey($handle->getPHID()) ->setValue($handle->getFullName()) - ->setIcon($handle->getIcon()); + ->setIcon($handle->getTokenIcon()); if ($handle->isDisabled() || $handle->getStatus() == PhabricatorObjectHandle::STATUS_CLOSED) { diff --git a/src/applications/uiexample/examples/PhabricatorHovercardUIExample.php b/src/applications/uiexample/examples/PHUIHovercardUIExample.php similarity index 87% rename from src/applications/uiexample/examples/PhabricatorHovercardUIExample.php rename to src/applications/uiexample/examples/PHUIHovercardUIExample.php index 3b3fc323d8..8673441d09 100644 --- a/src/applications/uiexample/examples/PhabricatorHovercardUIExample.php +++ b/src/applications/uiexample/examples/PHUIHovercardUIExample.php @@ -1,6 +1,6 @@ createPanel(pht('Differential Hovercard')); - $panel->appendChild(id(new PhabricatorHovercardView()) + $panel->appendChild(id(new PHUIHovercardView()) ->setObjectHandle($diff_handle) ->addField(pht('Author'), $user->getUsername()) ->addField(pht('Updated'), phabricator_datetime(time(), $user)) @@ -41,7 +41,7 @@ final class PhabricatorHovercardUIExample extends PhabricatorUIExample { ->setType(PHUITagView::TYPE_STATE) ->setName(pht('Closed, Resolved')); $panel = $this->createPanel(pht('Maniphest Hovercard')); - $panel->appendChild(id(new PhabricatorHovercardView()) + $panel->appendChild(id(new PHUIHovercardView()) ->setObjectHandle($task_handle) ->setUser($user) ->addField(pht('Assigned to'), $user->getUsername()) @@ -66,7 +66,7 @@ final class PhabricatorHovercardUIExample extends PhabricatorUIExample { $user_handle->setImageURI( celerity_get_resource_uri('/rsrc/image/people/washington.png')); $panel = $this->createPanel(pht('Whatevery Hovercard')); - $panel->appendChild(id(new PhabricatorHovercardView()) + $panel->appendChild(id(new PHUIHovercardView()) ->setObjectHandle($user_handle) ->addField(pht('Status'), pht('Available')) ->addField(pht('Member since'), '30. February 1750') diff --git a/src/docs/user/userguide/diffusion_hosting.diviner b/src/docs/user/userguide/diffusion_hosting.diviner index ecf7e1c3e3..6427be92e5 100644 --- a/src/docs/user/userguide/diffusion_hosting.diviner +++ b/src/docs/user/userguide/diffusion_hosting.diviner @@ -127,8 +127,13 @@ If you plan to use authenticated HTTP, you need to set use only anonymous HTTP, you can leave this setting disabled. If you plan to use authenticated HTTP, you'll also need to configure a VCS -password in {nav Settings > VCS Password}. This is a different password than -your main Phabricator password primarily for security reasons. +password in {nav Settings > VCS Password}. + +Your VCS password must be a different password than your main Phabricator +password because VCS passwords are very easy to accidentally disclose. They are +often stored in plaintext in world-readable files, observable in `ps` output, +and present in command output and logs. We strongly encourage you to use SSH +instead of HTTP to authenticate access to repositories. Otherwise, if you've configured system accounts above, you're all set. No additional server configuration is required to make HTTP work. diff --git a/src/docs/user/userguide/projects.diviner b/src/docs/user/userguide/projects.diviner index f563d527f2..6f29e3586c 100644 --- a/src/docs/user/userguide/projects.diviner +++ b/src/docs/user/userguide/projects.diviner @@ -135,6 +135,142 @@ members or won't have a workboard, you can hide these items to streamline the menu. +Subprojects and Milestones +========================== + +IMPORTANT: This feature is only partially implemented. + +After creating a project, you can use the +{nav icon="sitemap", name="Subprojects"} menu item to add subprojects or +milestones. + +**Subprojects** are projects that are contained inside the main project. You +can use them to break large or complex groups, tags, lists, or undertakings +apart into smaller pieces. + +**Milestones** are a special kind of subproject for organizing tasks into +blocks of work. You can use them to implement sprints, iterations, milestones, +versions, etc. + +Subprojects and milestones have some additional special behaviors and rules, +particularly around policies and membership. See below for details. + +This is a brief summary of the major differences between normal projects, +subprojects, parent projects, and milestones. + +| | Normal | Parent | Subproject | Milestone | +|---|---|---|---|---| +| //Members// | Yes | Union of Subprojects | Yes | Same as Parent | +| //Policies// | Yes | Yes | Affected by Parent | Same as Parent | +| //Hashtags// | Yes | Yes | Yes | Special | + + +Subprojects +=========== + +Subprojects are full-power projects that are contained inside some parent +project. You can use them to divide a large or complex project into smaller +parts. + +Subprojects have normal members and normal policies, but note that the policies +of the parent project affect the policies of the subproject (see "Parent +Projects", below). + +Subprojects can have their own subprojects, milestones, or both. If a +subproject has its own subprojects, it is both a subproject and a parent +project. Thus, the parent project rules apply to it, and are stronger than the +subproject rules. + +Subprojects can have normal workboards. + +The maximum subproject depth is 16. This limit is intended to grossly exceed +the depth necessary in normal usage. + +Objects may not be tagged with multiple projects that are ancestors or +descendants of one another. For example, a task may not be tagged with both +{nav Stonework} and {nav Stonework > Masonry}. + +When a project tag is added that is the ancestor or descendant of one or more +existing tags, the old tags are replaced. For example, adding +{nav Stonework > Masonry} to a task tagged with {nav Stonework} will replace +{nav Stonework} with the newer, more specific tag. + +This restriction does not apply to projects which share some common ancestor +but are not themselves mutual ancestors. For example, a task may be tagged +with both {nav Stonework > Masonry} and {nav Stonework > Sculpting}. + +This restriction //does// apply when the descendant is a milestone. For +example, a task may not be tagged with both {nav Stonework} and +{nav Stonework > Iteration II}. + + +Milestones +========== + +Milestones are simple subprojects for tracking sprints, iterations, versions, +or other similar blocks of work. Milestones make it easier to create and manage +a large number of similar subprojects (for example: {nav Sprint 1}, +{nav Sprint 2}, {nav Sprint 3}, etc). + +Milestones can not have direct members or policies. Instead, the membership +and policies of a milestones are always the same as the milestone's parent +project. This makes large numbers of milestones more manageable when changes +occur. + +Milestones can not have subprojects, and can not have their own milestones. + +By default, Milestones do not have their own hashtags. + +Milestones can have normal workboards. + +Objects may not be tagged with two different milestones of the same parent +project. For example, a task may not be tagged with both {nav Stonework > +Iteration III} and {nav Stonework > Iteration V}. + +When a milestone tag is added to an object which already has a tag from the +same series of milestones, the old tag is removed. For example, adding the +{nav Stonework > Iteration V} tag to a task which already has the +{nav Stonework > Iteration III} tag will remove the {nav Iteration III} tag. + +This restriction does not apply to milestones which are not part of the same +series. For example, a task may be tagged with both +{nav Stonework > Iteration V} and {nav Heraldry > Iteration IX}. + + +Parent Projects +=============== + +When you add the first subproject to an existing project, it is converted into +a **parent project**. Parent projects have some special rules. + +**No Direct Members**: Parent projects can not have members of their own. +Instead, all of the users who are members of any subproject count as members +of the parent project. By joining (or leaving) a subproject, a user is +implicitly added to (or removed from) all ancestors of that project. + +Consequently, when you add the first subproject to an existing project, all of +the project's current members are moved to become members of the subproject +instead. Implicitly, they will remain members of the parent project because the +parent project is an ancestor of the new subproject. + +You can edit the project afterward to change or remove members if you want to +split membership apart in a more granular way across multiple new subprojects. + +**Searching**: When you search for a parent project, results for any subproject +are returned. For example, if you search for {nav Engineering}, your query will +match results in {nav Engineering} itself, but also subprojects like +{nav Engineering > Warp Drive} and {nav Engineering > Shield Batteries}. + +**Policy Effects**: To view a subproject or milestone, you must be able to +view the parent project. As a result, the parent project's view policy now +affects child projects. If you restrict the visibility of the parent, you also +restrict the visibility of the children. + +In contrast, permission to edit a parent project grants permission to edit +any subproject. If a user can {nav Root Project}, they can also edit +{nav Root Project > Child} and {nav Root Project > Child > Sprint 3}. + + Policies In Depth ================= diff --git a/src/view/form/control/AphrontFormPolicyControl.php b/src/view/form/control/AphrontFormPolicyControl.php index 71087bfe07..6448b2b8b2 100644 --- a/src/view/form/control/AphrontFormPolicyControl.php +++ b/src/view/form/control/AphrontFormPolicyControl.php @@ -103,6 +103,24 @@ final class AphrontFormPolicyControl extends AphrontFormControl { protected function getOptions() { $capability = $this->capability; $policies = $this->policies; + $viewer = $this->getUser(); + + // Check if we're missing the policy for the current control value. This + // is unusual, but can occur if the user is submitting a form and selected + // an unusual project as a policy but the change has not been saved yet. + $policy_map = mpull($policies, null, 'getPHID'); + $value = $this->getValue(); + if ($value && empty($policy_map[$value])) { + $handle = id(new PhabricatorHandleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($value)) + ->executeOne(); + if ($handle->isComplete()) { + $policies[] = PhabricatorPolicy::newFromPolicyAndHandle( + $value, + $handle); + } + } // Exclude object policies which don't make sense here. This primarily // filters object policies associated from template capabilities (like @@ -143,12 +161,32 @@ final class AphrontFormPolicyControl extends AphrontFormControl { 'name' => $policy_short_name, 'full' => $policy->getName(), 'icon' => $policy->getIcon(), + 'sort' => phutil_utf8_strtolower($policy->getName()), ); } + $type_project = PhabricatorPolicyType::TYPE_PROJECT; + + // Make sure we have a "Projects" group before we adjust it. + if (empty($options[$type_project])) { + $options[$type_project] = array(); + } + + $options[$type_project] = isort($options[$type_project], 'sort'); + + $placeholder = id(new PhabricatorPolicy()) + ->setName(pht('Other Project...')) + ->setIcon('fa-search'); + + $options[$type_project][$this->getSelectProjectKey()] = array( + 'name' => $placeholder->getName(), + 'full' => $placeholder->getName(), + 'icon' => $placeholder->getIcon(), + ); + // If we were passed several custom policy options, throw away the ones // which aren't the value for this capability. For example, an object might - // have a custom view pollicy and a custom edit policy. When we render + // have a custom view policy and a custom edit policy. When we render // the selector for "Can View", we don't want to show the "Can Edit" // custom policy -- if we did, the menu would look like this: // @@ -172,7 +210,7 @@ final class AphrontFormPolicyControl extends AphrontFormControl { if (empty($options[$type_custom])) { $placeholder = new PhabricatorPolicy(); $placeholder->setName(pht('Custom Policy...')); - $options[$type_custom][$this->getCustomPolicyPlaceholder()] = array( + $options[$type_custom][$this->getSelectCustomKey()] = array( 'name' => $placeholder->getName(), 'full' => $placeholder->getName(), 'icon' => $placeholder->getIcon(), @@ -266,12 +304,12 @@ final class AphrontFormPolicyControl extends AphrontFormControl { 'options' => $flat_options, 'groups' => array_keys($options), 'order' => $order, - 'icons' => $icons, 'labels' => $labels, 'value' => $this->getValue(), 'capability' => $this->capability, 'editURI' => '/policy/edit/'.$context_path, - 'customPlaceholder' => $this->getCustomPolicyPlaceholder(), + 'customKey' => $this->getSelectCustomKey(), + 'projectKey' => $this->getSelectProjectKey(), 'disabled' => $this->getDisabled(), )); @@ -322,8 +360,12 @@ final class AphrontFormPolicyControl extends AphrontFormControl { )); } - private function getCustomPolicyPlaceholder() { - return 'custom:placeholder'; + public static function getSelectCustomKey() { + return 'select:custom'; + } + + public static function getSelectProjectKey() { + return 'select:project'; } private function buildSpacesControl() { diff --git a/src/view/phui/PHUIFeedStoryView.php b/src/view/phui/PHUIFeedStoryView.php index 40b7c76290..bacd266089 100644 --- a/src/view/phui/PHUIFeedStoryView.php +++ b/src/view/phui/PHUIFeedStoryView.php @@ -157,7 +157,7 @@ final class PHUIFeedStoryView extends AphrontView { public function render() { require_celerity_resource('phui-feed-story-css'); - Javelin::initBehavior('phabricator-hovercards'); + Javelin::initBehavior('phui-hovercards'); $body = null; $foot = null; diff --git a/src/view/widget/hovercard/PhabricatorHovercardView.php b/src/view/phui/PHUIHovercardView.php similarity index 76% rename from src/view/widget/hovercard/PhabricatorHovercardView.php rename to src/view/phui/PHUIHovercardView.php index 0da3aa9a0a..e5c89bb8fd 100644 --- a/src/view/widget/hovercard/PhabricatorHovercardView.php +++ b/src/view/phui/PHUIHovercardView.php @@ -4,7 +4,7 @@ * The default one-for-all hovercard. We may derive from this one to create * more specialized ones. */ -final class PhabricatorHovercardView extends AphrontView { +final class PHUIHovercardView extends AphrontTagView { /** * @var PhabricatorObjectHandle @@ -70,7 +70,16 @@ final class PhabricatorHovercardView extends AphrontView { return $this; } - public function render() { + protected function getTagAttributes() { + $classes = array(); + $classes[] = 'phui-hovercard-wrapper'; + + return array( + 'class' => implode(' ', $classes), + ); + } + + protected function getTagContent() { if (!$this->handle) { throw new PhutilInvalidStateException('setObjectHandle'); } @@ -78,16 +87,19 @@ final class PhabricatorHovercardView extends AphrontView { $viewer = $this->getUser(); $handle = $this->handle; - require_celerity_resource('phabricator-hovercard-view-css'); + require_celerity_resource('phui-hovercard-view-css'); + + // If we're a fully custom Hovercard, skip the common UI + $children = $this->renderChildren(); + if ($children) { + return $children; + } $title = array( id(new PHUISpacesNamespaceContextView()) ->setUser($viewer) ->setObject($this->getObject()), - pht( - '%s: %s', - $handle->getTypeName(), - $this->title ? $this->title : $handle->getName()), + $this->title ? $this->title : $handle->getName(), ); $header = new PHUIHeaderView(); @@ -107,7 +119,7 @@ final class PhabricatorHovercardView extends AphrontView { $body_title = $handle->getFullName(); } - $body[] = phutil_tag_div('phabricator-hovercard-body-header', $body_title); + $body[] = phutil_tag_div('phui-hovercard-body-header', $body_title); foreach ($this->fields as $field) { $item = array( @@ -115,7 +127,7 @@ final class PhabricatorHovercardView extends AphrontView { ': ', phutil_tag('span', array(), $field['value']), ); - $body[] = phutil_tag_div('phabricator-hovercard-body-item', $item); + $body[] = phutil_tag_div('phui-hovercard-body-item', $item); } if ($this->badges) { @@ -125,7 +137,7 @@ final class PhabricatorHovercardView extends AphrontView { $body[] = phutil_tag( 'div', array( - 'class' => 'phabricator-hovercard-body-item hovercard-badges', + 'class' => 'phui-hovercard-body-item hovercard-badges', ), $badges); } @@ -136,7 +148,7 @@ final class PhabricatorHovercardView extends AphrontView { $body = phutil_tag( 'div', array( - 'class' => 'phabricator-hovercard-body-image', + 'class' => 'phui-hovercard-body-image', ), phutil_tag( 'div', @@ -149,7 +161,7 @@ final class PhabricatorHovercardView extends AphrontView { phutil_tag( 'div', array( - 'class' => 'phabricator-hovercard-body-details', + 'class' => 'phui-hovercard-body-details', ), $body)); } @@ -178,18 +190,18 @@ final class PhabricatorHovercardView extends AphrontView { $tail = null; if ($buttons) { - $tail = phutil_tag_div('phabricator-hovercard-tail', $buttons); + $tail = phutil_tag_div('phui-hovercard-tail', $buttons); } $hovercard = phutil_tag_div( - 'phabricator-hovercard-container', + 'phui-hovercard-container grouped', array( - phutil_tag_div('phabricator-hovercard-head', $header), - phutil_tag_div('phabricator-hovercard-body grouped', $body), + phutil_tag_div('phui-hovercard-head', $header), + phutil_tag_div('phui-hovercard-body grouped', $body), $tail, )); - return phutil_tag_div('phabricator-hovercard-wrapper', $hovercard); + return $hovercard; } } diff --git a/src/view/phui/PHUIObjectItemListView.php b/src/view/phui/PHUIObjectItemListView.php index bc62d8e5bc..c21ea558a2 100644 --- a/src/view/phui/PHUIObjectItemListView.php +++ b/src/view/phui/PHUIObjectItemListView.php @@ -9,6 +9,7 @@ final class PHUIObjectItemListView extends AphrontTagView { private $flush; private $allowEmptyList; private $states; + private $itemClass = 'phui-object-item-standard'; public function setAllowEmptyList($allow_empty_list) { $this->allowEmptyList = $allow_empty_list; @@ -49,6 +50,11 @@ final class PHUIObjectItemListView extends AphrontTagView { return $this; } + public function setItemClass($item_class) { + $this->itemClass = $item_class; + return $this; + } + protected function getTagName() { return 'ul'; } @@ -89,6 +95,11 @@ final class PHUIObjectItemListView extends AphrontTagView { $item->setUser($viewer); } } + + foreach ($this->items as $item) { + $item->addClass($this->itemClass); + } + $items = $this->items; } else if ($this->allowEmptyList) { $items = null; diff --git a/src/view/phui/PHUIObjectItemView.php b/src/view/phui/PHUIObjectItemView.php index b3c4368c22..c7a3d074b5 100644 --- a/src/view/phui/PHUIObjectItemView.php +++ b/src/view/phui/PHUIObjectItemView.php @@ -383,17 +383,24 @@ final class PHUIObjectItemView extends AphrontTagView { ), $this->header); - $header = javelin_tag( + // Wrap the header content in a with the "slippery" sigil. This + // prevents us from beginning a drag if you click the text (like "T123"), + // but not if you click the white space after the header. + $header = phutil_tag( 'div', array( 'class' => 'phui-object-item-name', - 'sigil' => 'slippery', ), - array( - $this->headIcons, - $header_name, - $header_link, - )); + javelin_tag( + 'span', + array( + 'sigil' => 'slippery', + ), + array( + $this->headIcons, + $header_name, + $header_link, + ))); $icons = array(); if ($this->icons) { @@ -452,14 +459,15 @@ final class PHUIObjectItemView extends AphrontTagView { $icon_list); } + $handle_bar = null; if ($this->handleIcons) { $handle_bar = array(); foreach ($this->handleIcons as $handleicon) { $handle_bar[] = $this->renderHandleIcon($handleicon['icon'], $handleicon['label']); } - $icons[] = phutil_tag( - 'div', + $handle_bar = phutil_tag( + 'li', array( 'class' => 'phui-object-item-handle-icons', ), @@ -504,7 +512,7 @@ final class PHUIObjectItemView extends AphrontTagView { } $attrs = null; - if ($this->attributes) { + if ($this->attributes || $handle_bar) { $attrs = array(); $spacer = phutil_tag( 'span', @@ -531,7 +539,10 @@ final class PHUIObjectItemView extends AphrontTagView { array( 'class' => 'phui-object-item-attributes', ), - $attrs); + array( + $handle_bar, + $attrs, + )); } $status = null; @@ -750,7 +761,7 @@ final class PHUIObjectItemView extends AphrontTagView { if (strlen($label)) { $options['sigil'] = 'has-tooltip'; - $options['meta'] = array('tip' => $label); + $options['meta'] = array('tip' => $label, 'align' => 'E'); } return javelin_tag('span', $options, ''); diff --git a/src/view/phui/PHUIPropertyListView.php b/src/view/phui/PHUIPropertyListView.php index 0d60ed25b0..336c494a3c 100644 --- a/src/view/phui/PHUIPropertyListView.php +++ b/src/view/phui/PHUIPropertyListView.php @@ -115,14 +115,14 @@ final class PHUIPropertyListView extends AphrontView { $this->invokedWillRenderEvent = true; } - public function isEmpty() { + public function hasAnyProperties() { $this->invokeWillRenderEvent(); if ($this->parts) { - return false; + return true; } - return true; + return false; } public function render() { diff --git a/src/view/phui/PHUITagView.php b/src/view/phui/PHUITagView.php index 8b6e6aa8d6..ae80fc1731 100644 --- a/src/view/phui/PHUITagView.php +++ b/src/view/phui/PHUITagView.php @@ -122,7 +122,7 @@ final class PHUITagView extends AphrontTagView { } if ($this->phid) { - Javelin::initBehavior('phabricator-hovercards'); + Javelin::initBehavior('phui-hovercards'); $attributes = array( 'href' => $this->href, diff --git a/src/view/phui/PHUIWorkpanelView.php b/src/view/phui/PHUIWorkpanelView.php index 96978de952..50b2e12161 100644 --- a/src/view/phui/PHUIWorkpanelView.php +++ b/src/view/phui/PHUIWorkpanelView.php @@ -10,8 +10,8 @@ final class PHUIWorkpanelView extends AphrontTagView { private $headerTag; private $headerIcon; - public function setHeaderIcon(PHUIIconView $header_icon) { - $this->headerIcon = $header_icon; + public function setHeaderIcon($icon) { + $this->headerIcon = $icon; return $this; } diff --git a/support/lint/browser.jshintrc b/support/lint/browser.jshintrc index 9bf5d3fc6a..b88c931eee 100644 --- a/support/lint/browser.jshintrc +++ b/support/lint/browser.jshintrc @@ -17,8 +17,9 @@ "globals": { "JX": false, - "Raphael": false, + "d3": false, "__DEV__": false }, + "browser": true } diff --git a/webroot/rsrc/css/application/base/standard-page-view.css b/webroot/rsrc/css/application/base/standard-page-view.css index 830554db3e..ca69443d07 100644 --- a/webroot/rsrc/css/application/base/standard-page-view.css +++ b/webroot/rsrc/css/application/base/standard-page-view.css @@ -19,14 +19,14 @@ .phabricator-standard-page-footer { text-align: right; - margin: 32px 16px 16px; + margin: 44px 16px 16px; padding: 12px 0; - border-top: 1px solid rgba(71, 87, 120, 0.20); + border-top: 1px solid rgba(55,55,55,.1); color: {$greytext}; } .device .phabricator-standard-page-footer { - margin: 4px 8px; + margin: 24px 8px 16px; } !print .phabricator-standard-page-footer { diff --git a/webroot/rsrc/css/application/phame/phame.css b/webroot/rsrc/css/application/phame/phame.css index be161c1ede..3d151426d6 100644 --- a/webroot/rsrc/css/application/phame/phame.css +++ b/webroot/rsrc/css/application/phame/phame.css @@ -43,24 +43,34 @@ background-color: #fff; } -.device .phame-home-view .phui-side-column { - background-color: transparent; +.phame-home-view { + background-color: #fff; + border-bottom: 1px solid rgba(55,55,55,.1); +} + +.phame-home-view .phame-home-container { + max-width: 980px; + margin: 0 auto; +} + +.phame-home-view .phui-document-container { + border: none; } .phame-blog-list { - margin: 24px 16px 16px 16px; + margin: 96px 16px 16px 16px; +} + +.phame-blog-list + .phame-blog-list { + margin-top: 24px; } .device .phame-blog-list { - padding: 0; - background-color: {$bluebackground}; - margin: 0; - border-radius: 0; - border-bottom: 1px solid {$thinblueborder}; + margin: 16px; } -.phame-blog-list-item:last-child { - margin-bottom: 0; +.device-phone .phame-blog-list { + margin: 16px 8px; } .phame-blog-list-header { diff --git a/webroot/rsrc/css/application/project/project-card-view.css b/webroot/rsrc/css/application/project/project-card-view.css new file mode 100644 index 0000000000..475f7c76f2 --- /dev/null +++ b/webroot/rsrc/css/application/project/project-card-view.css @@ -0,0 +1,150 @@ +/** + * @provides project-card-view-css + */ + +.project-card-view { + margin: 0 12px 16px 0; + text-align: left; + background: #fff; + border: 1px solid {$lightblueborder}; + border-radius: 3px; + box-shadow: {$dropshadow}; + width: 380px; +} + +.project-card-view .phui-header-shell { + margin: 0; + padding: 12px 12px 16px 12px; + border: none; + border-radius: 3px; +} + +.project-card-view .phui-header-shell .phui-header-image { + border: 3px solid #fff; + border-radius: 3px; + background-color: #fff; +} + +.project-card-view .phui-header-shell .phui-header-header { + font-size: 18px; + font-family: 'Aleo', {$fontfamily}; + width: 290px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + display: block; +} + +.project-card-view .phui-header-shell .phui-header-col1 { + vertical-align: top; + width: 64px; +} + +.project-card-view .phui-header-subheader { + font-size: {$normalfontsize}; + margin-top: 12px; +} + +.project-card-view .phui-header-header .phui-tag-view { + display: block; + font-weight: normal; + color: {$bluetext}; + font-size: {$normalfontsize}; + font-family: {$fontfamily}; + margin-top: 8px; +} + +.project-card-view .phui-header-header .phui-tag-view .phui-tag-core { + padding: 0; +} + +.project-card-view .phui-header-header .phui-tag-view .phui-icon-view { + margin-left: 0; + color: {$bluetext}; +} + + +/* Colors */ + +.project-card-view.project-card-red { + border-color: {$sh-redborder}; +} + +.project-card-view.project-card-red .phui-header-shell { + background: linear-gradient(to bottom, + {$sh-redbackground} 42px, #fff 42px); +} + +.project-card-view.project-card-orange { + border-color: {$sh-orangeborder}; +} + +.project-card-view.project-card-orange .phui-header-shell { + background: linear-gradient(to bottom, + {$sh-orangebackground} 42px, #fff 42px); +} + +.project-card-view.project-card-yellow { + border-color: {$sh-yellowborder}; +} + +.project-card-view.project-card-yellow .phui-header-shell { + background: linear-gradient(to bottom, + {$sh-yellowbackground} 42px, #fff 42px); +} + +.project-card-view.project-card-green { + border-color: {$sh-greenborder}; +} + +.project-card-view.project-card-green .phui-header-shell { + background: linear-gradient(to bottom, + {$sh-greenbackground} 42px, #fff 42px); +} + +.project-card-view.project-card-blue { + border-color: {$sh-blueborder}; +} + +.project-card-view.project-card-blue .phui-header-shell { + background: linear-gradient(to bottom, + {$sh-bluebackground} 42px, #fff 42px); +} + +.project-card-view.project-card-indigo { + border-color: {$sh-indigoborder}; +} + +.project-card-view.project-card-indigo .phui-header-shell { + background: linear-gradient(to bottom, + {$sh-indigobackground} 42px, #fff 42px); +} + +.project-card-view.project-card-violet { + border-color: {$sh-violetborder}; +} + +.project-card-view.project-card-violet .phui-header-shell { + background: linear-gradient(to bottom, + {$sh-violetbackground} 42px, #fff 42px); +} + +.project-card-view.project-card-pink { + border-color: {$sh-pinkborder}; +} + +.project-card-view.project-card-pink .phui-header-shell { + background: linear-gradient(to bottom, + {$sh-pinkbackground} 42px, #fff 42px); +} + +.project-card-view.project-card-grey, +.project-card-view.project-card-checkered { + border-color: {$sh-greyborder}; +} + +.project-card-view.project-card-grey .phui-header-shell, +.project-card-view.project-card-checkered .phui-header-shell { + background: linear-gradient(to bottom, + {$sh-greybackground} 42px, #fff 42px); +} diff --git a/webroot/rsrc/css/application/project/project-view.css b/webroot/rsrc/css/application/project/project-view.css index 453942ecc4..afbeff3950 100644 --- a/webroot/rsrc/css/application/project/project-view.css +++ b/webroot/rsrc/css/application/project/project-view.css @@ -7,6 +7,23 @@ padding-bottom: 64px; } +.project-view-header-tag { + margin-left: 8px; + font-size: {$normalfontsize}; + color: {$bluetext}; + font-family: {$fontfamily}; + font-weight: normal; +} + +.device-phone .project-view-header-tag { + display: block; + margin-left: -4px; +} + +.project-view-header-tag .phui-icon-view { + color: {$bluetext}; +} + .phui-box.phui-box-grey.project-view-properties { margin: 0 16px 0 16px; padding: 4px 12px; @@ -31,13 +48,18 @@ padding: 8px; } -.project-view-feed .phui-header-shell { +.project-view-feed .phui-object-box .phui-header-shell { padding: 8px 4px; } .project-view-feed .phui-header-header { font-size: {$biggerfontsize}; - margin-left: 4px; + margin-left: 8px; +} + +.device-desktop .project-view-feed .phui-feed-story, +.device-tablet .project-view-feed .phui-feed-story { + padding-left: 22px; } .project-view-home .phui-box-grey { @@ -60,6 +82,10 @@ width: 364px; } +.project-view-home .phui-box-grey .phui-object-item-attribute .phui-icon-view { + color: {$lightgreytext}; +} + .profile-no-badges { padding: 24px 0; } diff --git a/webroot/rsrc/css/core/z-index.css b/webroot/rsrc/css/core/z-index.css index 3025b5dc5c..2d4c050d0c 100644 --- a/webroot/rsrc/css/core/z-index.css +++ b/webroot/rsrc/css/core/z-index.css @@ -81,10 +81,6 @@ div.phui-calendar-day-event { z-index: 5; } -.drag-dragging { - z-index: 5; -} - .phui-calendar-date-number { z-index: 5; } @@ -114,6 +110,10 @@ div.phui-calendar-day-event { z-index: 9; } +.drag-frame { + z-index: 10; +} + .jx-mask { z-index: 10; } diff --git a/webroot/rsrc/css/phui/phui-chart.css b/webroot/rsrc/css/phui/phui-chart.css new file mode 100644 index 0000000000..be401b1fe3 --- /dev/null +++ b/webroot/rsrc/css/phui/phui-chart.css @@ -0,0 +1,54 @@ +/** + * @provides phui-chart-css + */ + +.chart .axis line, +.chart .axis path { + fill: none; + stroke: {$blueborder}; + shape-rendering: crispEdges; +} + +.chart .axis text { + fill: {$darkgreytext}; +} + +.chart .outer, +.chart .inner { + shape-rendering: crispEdges; +} + +.chart .outer { + fill: none; + stroke: none; +} + +.chart .inner { + fill: {$lightbluebackground}; + stroke: {$lightblueborder}; +} + +.chart .line { + fill: none; + stroke: {$blue}; + stroke-width: 2px; +} + +.chart .point { + fill: {$lightblue}; + stroke: {$blue}; + stroke-width: 1px; +} + +.chart-tooltip { + position: absolute; + text-align: center; + width: 120px; + height: 16px; + overflow: hidden; + padding: 2px; + background: {$lightbluebackground}; + border: 1px solid {$blueborder}; + border-radius: 8px; + pointer-events: none; +} diff --git a/webroot/rsrc/css/phui/phui-feed-story.css b/webroot/rsrc/css/phui/phui-feed-story.css index 9162d68328..cc0bf279fd 100644 --- a/webroot/rsrc/css/phui/phui-feed-story.css +++ b/webroot/rsrc/css/phui/phui-feed-story.css @@ -24,7 +24,6 @@ padding: 12px 4px; overflow: hidden; color: {$greytext}; - line-height: 16px; word-break: break-word; } @@ -35,6 +34,7 @@ .phui-feed-story-body { margin: 4px 4px 8px; + padding-bottom: 8px; color: {$darkgreytext}; word-break: break-word; max-height: 300px; @@ -43,7 +43,7 @@ .phui-feed-story-foot { font-size: {$smallerfontsize}; - padding: 8px 4px 12px; + padding: 0 4px 12px; } .phui-feed-story-foot, diff --git a/webroot/rsrc/css/layout/phabricator-hovercard-view.css b/webroot/rsrc/css/phui/phui-hovercard.css similarity index 58% rename from webroot/rsrc/css/layout/phabricator-hovercard-view.css rename to webroot/rsrc/css/phui/phui-hovercard.css index 23b3956763..6431c01cdf 100644 --- a/webroot/rsrc/css/layout/phabricator-hovercard-view.css +++ b/webroot/rsrc/css/phui/phui-hovercard.css @@ -1,53 +1,50 @@ /** - * @provides phabricator-hovercard-view-css + * @provides phui-hovercard-view-css */ .jx-hovercard-container { position: absolute; } -.phabricator-hovercard-wrapper { - float: left; +.phui-hovercard-wrapper { width: 400px; } -.device-phone .phabricator-hovercard-wrapper { - float: left; +.device-phone .phui-hovercard-wrapper { width: 300px; } -.phabricator-hovercard-container { - float: left; +.phui-hovercard-container { width: 100%; box-shadow: {$dropshadow}; - border: 1px solid {$blueborder}; + border: 1px solid {$lightblueborder}; border-radius: 3px; background-color: #fff; } -.phabricator-hovercard-head .phui-header-shell { +.phui-hovercard-head .phui-header-shell { padding: 6px 8px 6px 12px; background-color: {$bluebackground}; border-top-left-radius: 3px; border-top-right-radius: 3px; } -.phabricator-hovercard-head .phui-header-header { - font-size: 14px; +.phui-hovercard-head .phui-header-header { + font-size: {$biggerfontsize}; } -.phabricator-hovercard-head .phui-tag-type-state { +.phui-hovercard-head .phui-tag-type-state { color: {$darkbluetext}; text-shadow: none; font-weight: normal; } -.phabricator-hovercard-tags { +.phui-hovercard-tags { float: right; white-space: normal; } -.phabricator-hovercard-body { +.phui-hovercard-body { padding: 12px; color: {$darkgreytext}; border-bottom-right-radius: 3px; @@ -55,26 +52,26 @@ position: relative; } -.phabricator-hovercard-body-item { +.phui-hovercard-body-item { margin: 4px 0 0 0; } -.phabricator-hovercard-body-header { +.phui-hovercard-body-header { font-size: 14px; padding-bottom: 4px; color: {$darkgreytext}; line-height: 18px; } -.phabricator-hovercard-body .phabricator-hovercard-body-image { +.phui-hovercard-body .phui-hovercard-body-image { width: 58px; } -.phabricator-hovercard-body .phabricator-hovercard-body-details { +.phui-hovercard-body .phui-hovercard-body-details { margin-left: 58px; } -.phabricator-hovercard-body .profile-header-picture-frame { +.phui-hovercard-body .profile-header-picture-frame { float: left; width: 50px; height: 50px; @@ -91,7 +88,7 @@ float: left; } -.phabricator-hovercard-tail { +.phui-hovercard-tail { width: 396px; float: left; padding: 2px; @@ -100,7 +97,7 @@ border-bottom-right-radius: 3px; } -.phabricator-hovercard-tail button, -.phabricator-hovercard-tail a.button { +.phui-hovercard-tail button, +.phui-hovercard-tail a.button { margin: 3px; } diff --git a/webroot/rsrc/css/phui/phui-object-item-list-view.css b/webroot/rsrc/css/phui/phui-object-item-list-view.css index b1e718cc6a..551144673b 100644 --- a/webroot/rsrc/css/phui/phui-object-item-list-view.css +++ b/webroot/rsrc/css/phui/phui-object-item-list-view.css @@ -297,11 +297,13 @@ ul.phui-object-item-list-view { .phui-object-item-attributes { padding: 0 8px 6px; line-height: 18px; + min-height: 21px; } .phui-object-item-attribute { - display: inline; + display: inline-block; color: {$greytext}; + vertical-align: middle; } .phui-object-item-attribute-spacer { @@ -324,10 +326,6 @@ ul.phui-object-item-list-view { margin: 0 0 4px; } -.phui-object-item-with-handle-icons .phui-object-item-icons { - padding-bottom: 30px; -} - .phui-object-item-icons { padding: 0 4px 0 0; } @@ -376,10 +374,6 @@ ul.phui-object-item-icons { attributes. */ -.phui-workboard-view .phui-object-item { - border-left-width: 4px; -} - .phui-object-item { border-left-width: 0; } @@ -537,20 +531,17 @@ ul.phui-object-item-list-view .phui-object-item-selected */ .phui-object-item-handle-icons { - height: 28px; - margin-right: 10px; bottom: 0; - right: 0; - text-align: right; + right: 4px; position: absolute; } .phui-object-item-handle-icon { - margin: 1px; - width: 28px; - height: 28px; + width: 24px; + height: 24px; display: inline-block; - background-size: 28px 28px; + background-size: 100%; + border-radius: 3px; background-repeat: no-repeat; } @@ -598,15 +589,36 @@ ul.phui-object-item-list-view .phui-object-item-selected } .drag-dragging { - position: relative; - background: {$sh-yellowbackground}; - opacity: 0.9; + opacity: 0.25; } .drag-sending { opacity: 0.5; } +.drag-clone, +.drag-frame { + /* This allows mousewheel events to pass through the clone and frame while + they are being dragged. Without this, the mousewheel does not work during + a drag operation. */ + pointer-events: none; +} + +.drag-frame { + position: fixed; + overflow: hidden; + left: 0; + right: 0; + top: 0; + bottom: 0; +} + +.drag-clone { + position: absolute; + list-style: none; +} + + /* - State --------------------------------------------------------------------- Provides a list of object status or states, success or fail, etc @@ -666,6 +678,12 @@ ul.phui-object-item-list-view .phui-object-item-selected border-bottom: 1px solid {$thinblueborder}; } +.drag-clone.phui-object-item-standard .phui-object-item-frame { + border: none; + opacity: 0.8; + background: {$sh-bluebackground}; +} + .phui-object-box .phui-object-item-list-header { font-size: {$normalfontsize}; color: {$darkbluetext}; diff --git a/webroot/rsrc/css/phui/phui-tag-view.css b/webroot/rsrc/css/phui/phui-tag-view.css index 4396fe8fe3..c6bb18ed0a 100644 --- a/webroot/rsrc/css/phui/phui-tag-view.css +++ b/webroot/rsrc/css/phui/phui-tag-view.css @@ -7,6 +7,7 @@ text-decoration: none; position: relative; -webkit-font-smoothing: antialiased; + white-space: nowrap; } a.phui-tag-view:hover { @@ -149,7 +150,7 @@ a.phui-tag-view:hover } .phui-object-item .phabricator-handle-tag-list-item { - display: inline; + display: inline-block; margin: 0 4px 2px 0; } diff --git a/webroot/rsrc/css/phui/workboards/phui-workcard.css b/webroot/rsrc/css/phui/workboards/phui-workcard.css index 95930b0da1..d0c427da57 100644 --- a/webroot/rsrc/css/phui/workboards/phui-workcard.css +++ b/webroot/rsrc/css/phui/workboards/phui-workcard.css @@ -2,26 +2,30 @@ * @provides phui-workcard-view-css */ -.phui-workboard-view .phui-object-item { +.phui-workcard.phui-object-item { background-color: #fff; border-radius: 3px; - margin-bottom: 6px; + margin-bottom: 8px; + border-left-width: 4px; + box-sizing: border-box; } -.phui-workboard-view .phui-object-item-name { +.phui-workcard .phui-object-item-name { padding-bottom: 4px; } -.phui-workboard-view .phui-object-item-content { +.phui-workcard .phui-object-item-content { margin-top: 0; } -.phui-workboard-view .phui-object-item-frame { +.phui-workcard .phui-object-item-frame { border-top-right-radius: 3px; border-bottom-right-radius: 3px; + border-color: {$thinblueborder}; + border-bottom-color: {$lightblueborder}; } -.phui-workboard-view .phui-object-item .phui-object-item-objname { +.phui-workcard.phui-object-item .phui-object-item-objname { -webkit-touch-callout: text; -webkit-user-select: text; -khtml-user-select: text; @@ -30,97 +34,116 @@ user-select: text; } -.phui-workboard-view .phui-object-item-link { +.phui-workcard .phui-object-item-link { white-space: normal; font-weight: normal; color: #000; margin-left: 2px; } -.device-desktop .phui-workboard-view .phui-object-item-with-1-actions - .phui-object-item-content-box { - margin-right: 0; - overflow: hidden; +.phui-object-item-disabled.phui-workcard { + background-color: rgba(255,255,255,.67); } -.phui-workboard-view .phui-object-item-objname { +.phui-object-item-disabled.phui-workcard .phui-object-item-link { + color: {$greytext}; +} + +.device-desktop .phui-workcard .phui-object-item-with-1-actions + .phui-object-item-content-box { + margin-right: 0; + overflow: hidden; +} + +.phui-workcard .phui-object-item-objname { vertical-align: top; } -.phui-workpanel-view .phui-object-item-grippable .phui-object-item-frame { +.phui-workcard.phui-object-item-grippable .phui-object-item-frame { padding-left: 0; } -.phui-workpanel-view .phui-object-item-grip { +.phui-workcard .phui-object-item-grip { display: none; } -.device-desktop .phui-workpanel-view .phui-list-item-icon { +.device-desktop .phui-workcard .phui-list-item-icon { display: none; } -.phui-workpanel-view .phui-object-item .phui-list-item-href { - height: 26px; +.phui-workcard.phui-object-item .phui-list-item-href { + height: 24px; + width: 24px; } -.device-desktop .phui-workpanel-view .phui-object-item:hover +.device-desktop .phui-workcard.phui-object-item:hover .phui-list-item-href { background: #fff; opacity: .7; } -.device-desktop .phui-workpanel-view .phui-object-item +.device-desktop .phui-workcard.phui-object-item .phui-list-item-href:hover { background: {$sh-bluebackground}; opacity: 1; } -.phui-workpanel-view .phui-object-item:hover .phui-list-item-icon { +.phui-workcard.phui-object-item:hover .phui-list-item-icon { display: block; } +.phui-workcard .phui-object-item-attributes { + margin-right: 12px; +} +.phui-workpanel-view .drag-ghost { + margin-bottom: 8px; +} /* - Draggable Colors --------------------------------------------------------*/ -.phui-workboard-view .phui-object-item.drag-dragging { +.phui-workcard.phui-object-item.drag-clone { box-shadow: {$dropshadow}; background-color: {$sh-greybackground}; } -.phui-workboard-view .drag-dragging.phui-object-item-bar-color-red { +.phui-workcard.phui-object-item.drag-clone .phui-list-item-href { + display: none; +} + +.phui-workcard.drag-clone.phui-object-item-bar-color-red { background-color: {$sh-redbackground}; } -.phui-workboard-view .drag-dragging.phui-object-item-bar-color-orange { +.phui-workcard.drag-clone.phui-object-item-bar-color-orange { background-color: {$sh-orangebackground}; } -.phui-workboard-view .drag-dragging.phui-object-item-bar-color-yellow { +.phui-workcard.drag-clone.phui-object-item-bar-color-yellow { background-color: {$sh-yellowbackground}; } -.phui-workboard-view .drag-dragging.phui-object-item-bar-color-green { +.phui-workcard.drag-clone.phui-object-item-bar-color-green { background-color: {$sh-greenbackground}; } -.phui-workboard-view .drag-dragging.phui-object-item-bar-color-blue { +.phui-workcard.drag-clone.phui-object-item-bar-color-blue { background-color: {$sh-bluebackground}; } -.phui-workboard-view .drag-dragging.phui-object-item-bar-color-indigo { +.phui-workcard.drag-clone.phui-object-item-bar-color-indigo { background-color: {$sh-indigobackground}; } -.phui-workboard-view .drag-dragging.phui-object-item-bar-color-violet { +.phui-workcard.drag-clone.phui-object-item-bar-color-violet { background-color: {$sh-violetbackground}; } -.phui-workboard-view .drag-dragging.phui-object-item-bar-color-pink { +.phui-workcard.drag-clone.phui-object-item-bar-color-pink { background-color: {$sh-pinkbackground}; } -.phui-workboard-view .drag-dragging.phui-object-item-bar-color-sky { +.phui-workcard.drag-clone.phui-object-item-bar-color-sky { background-color: {$sh-bluebackground}; } diff --git a/webroot/rsrc/css/phui/workboards/phui-workpanel.css b/webroot/rsrc/css/phui/workboards/phui-workpanel.css index 978a3c4863..b600577d33 100644 --- a/webroot/rsrc/css/phui/workboards/phui-workpanel.css +++ b/webroot/rsrc/css/phui/workboards/phui-workpanel.css @@ -19,6 +19,7 @@ margin: 0; display: inline-block; color: {$lightgreytext}; + font-size: {$normalfontsize}; } .device .phui-workpanel-view .phui-header-shell { @@ -34,6 +35,14 @@ user-select: none; } +.phui-workpanel-view.phui-workboard-column-milestone .phui-box-grey { + background-color: rgba(234, 230, 247, 0.75); +} + +.phui-workpanel-view .phui-header-col2 .phui-icon-view { + margin-right: 4px; +} + .phui-workpanel-view .phui-workpanel-header-action { float: right; width: 24px; @@ -44,7 +53,7 @@ padding: 8px 8px 4px 8px; } -.device-phone .phui-workpanel-view .phui-workpanel-body { +.device .phui-workpanel-view .phui-workpanel-body { padding: 8px 0; } diff --git a/webroot/rsrc/externals/d3/LICENSE b/webroot/rsrc/externals/d3/LICENSE new file mode 100644 index 0000000000..83013469b9 --- /dev/null +++ b/webroot/rsrc/externals/d3/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2010-2014, Michael Bostock +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* The name Michael Bostock may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/webroot/rsrc/externals/d3/README.md b/webroot/rsrc/externals/d3/README.md new file mode 100644 index 0000000000..eb334e2701 --- /dev/null +++ b/webroot/rsrc/externals/d3/README.md @@ -0,0 +1,9 @@ +# Data-Driven Documents + + + +**D3.js** is a JavaScript library for manipulating documents based on data. **D3** helps you bring data to life using HTML, SVG and CSS. D3’s emphasis on web standards gives you the full capabilities of modern browsers without tying yourself to a proprietary framework, combining powerful visualization components and a data-driven approach to DOM manipulation. + +Want to learn more? [See the wiki.](https://github.com/mbostock/d3/wiki) + +For examples, [see the gallery](https://github.com/mbostock/d3/wiki/Gallery) and [mbostock’s bl.ocks](http://bl.ocks.org/mbostock). diff --git a/webroot/rsrc/externals/d3/d3.min.js b/webroot/rsrc/externals/d3/d3.min.js new file mode 100644 index 0000000000..f878a89d69 --- /dev/null +++ b/webroot/rsrc/externals/d3/d3.min.js @@ -0,0 +1,9 @@ +/** + * @provides d3 + * @do-not-minify + */ +!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function u(n){return!isNaN(n)}function i(n){return{left:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)<0?r=i+1:u=i}return r},right:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)>0?u=i:r=i+1}return r}}}function a(n){return n.length}function o(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function s(n){return(n+="")===xa||n[0]===ba?ba+n:n}function f(n){return(n+="")[0]===ba?n.slice(1):n}function h(n){return s(n)in this._}function g(n){return(n=s(n))in this._&&delete this._[n]}function p(){var n=[];for(var t in this._)n.push(f(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function m(){this._=Object.create(null)}function y(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=_a.length;r>e;++e){var u=_a[e]+t;if(u in n)return u}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,u=-1,i=r.length;++ue;e++)for(var u,i=n[e],a=0,o=i.length;o>a;a++)(u=i[a])&&t(u,a,e);return n}function Z(n){return Sa(n,za),n}function V(n){var t,e;return function(r,u,i){var a,o=n[i].update,l=o.length;for(i!=e&&(e=i,t=0),u>=t&&(t=u+1);!(a=o[t])&&++t0&&(n=n.slice(0,o));var c=La.get(n);return c&&(n=c,l=B),o?t?u:r:t?b:i}function $(n,t){return function(e){var r=oa.event;oa.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{oa.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Ta,u="click"+r,i=oa.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==qa&&(qa="onselectstart"in e?!1:x(e.style,"userSelect")),qa){var a=n(e).style,o=a[qa];a[qa]="none"}return function(n){if(i.on(r,null),qa&&(a[qa]=o),n){var t=function(){i.on(u,null)};i.on(u,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var u=r.createSVGPoint();if(0>Ra){var i=t(n);if(i.scrollX||i.scrollY){r=oa.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var a=r[0][0].getScreenCTM();Ra=!(a.f||a.e),r.remove()}}return Ra?(u.x=e.pageX,u.y=e.pageY):(u.x=e.clientX,u.y=e.clientY),u=u.matrixTransform(n.getScreenCTM().inverse()),[u.x,u.y]}var o=n.getBoundingClientRect();return[e.clientX-o.left-n.clientLeft,e.clientY-o.top-n.clientTop]}function G(){return oa.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?Ua:Math.acos(n)}function tn(n){return n>1?Ha:-1>n?-Ha:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function an(n){return(n=Math.sin(n/2))*n}function on(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?i+(a-i)*n/60:180>n?a:240>n?i+(a-i)*(240-n)/60:i}function u(n){return Math.round(255*r(n))}var i,a;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,a=.5>=e?e*(1+t):e+t-e*t,i=2*e-a,new yn(u(n+120),u(n),u(n-120))}function sn(n,t,e){return this instanceof sn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof sn?new sn(n.h,n.c,n.l):n instanceof hn?pn(n.l,n.a,n.b):pn((n=Sn((n=oa.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new sn(n,t,e)}function fn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Oa)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof sn?fn(n.h,n.c,n.l):Sn((n=yn(n)).r,n.g,n.b):new hn(n,t,e)}function gn(n,t,e){var r=(n+16)/116,u=r+t/500,i=r-e/200;return u=vn(u)*Ka,r=vn(r)*Qa,i=vn(i)*no,new yn(mn(3.2404542*u-1.5371385*r-.4985314*i),mn(-.969266*u+1.8760108*r+.041556*i),mn(.0556434*u-.2040259*r+1.0572252*i))}function pn(n,t,e){return n>0?new sn(Math.atan2(e,t)*Ia,Math.sqrt(t*t+e*e),n):new sn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function mn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function yn(n,t,e){return this instanceof yn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof yn?new yn(n.r,n.g,n.b):_n(""+n,yn,cn):new yn(n,t,e)}function Mn(n){return new yn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,u,i,a=0,o=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(u=r[2].split(","),r[1]){case"hsl":return e(parseFloat(u[0]),parseFloat(u[1])/100,parseFloat(u[2])/100);case"rgb":return t(Nn(u[0]),Nn(u[1]),Nn(u[2]))}return(i=ro.get(n))?t(i.r,i.g,i.b):(null==n||"#"!==n.charAt(0)||isNaN(i=parseInt(n.slice(1),16))||(4===n.length?(a=(3840&i)>>4,a=a>>4|a,o=240&i,o=o>>4|o,l=15&i,l=l<<4|l):7===n.length&&(a=(16711680&i)>>16,o=(65280&i)>>8,l=255&i)),t(a,o,l))}function wn(n,t,e){var r,u,i=Math.min(n/=255,t/=255,e/=255),a=Math.max(n,t,e),o=a-i,l=(a+i)/2;return o?(u=.5>l?o/(a+i):o/(2-a-i),r=n==a?(t-e)/o+(e>t?6:0):t==a?(e-n)/o+2:(n-t)/o+4,r*=60):(r=NaN,u=l>0&&1>l?0:r),new ln(r,u,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/Ka),u=dn((.2126729*n+.7151522*t+.072175*e)/Qa),i=dn((.0193339*n+.119192*t+.9503041*e)/no);return hn(116*u-16,500*(r-u),200*(u-i))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function u(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(i,l)}catch(r){return void a.error.call(i,r)}a.load.call(i,n)}else a.error.call(i,l)}var i={},a=oa.dispatch("beforesend","progress","load","error"),o={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=u:l.onreadystatechange=function(){l.readyState>3&&u()},l.onprogress=function(n){var t=oa.event;oa.event=n;try{a.progress.call(i,l)}finally{oa.event=t}},i.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?o[n]:(null==t?delete o[n]:o[n]=t+"",i)},i.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",i):t},i.responseType=function(n){return arguments.length?(c=n,i):c},i.response=function(n){return e=n,i},["get","post"].forEach(function(n){i[n]=function(){return i.send.apply(i,[n].concat(ca(arguments)))}}),i.send=function(e,r,u){if(2===arguments.length&&"function"==typeof r&&(u=r,r=null),l.open(e,n,!0),null==t||"accept"in o||(o.accept=t+",*/*"),l.setRequestHeader)for(var s in o)l.setRequestHeader(s,o[s]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=u&&i.on("error",u).on("load",function(n){u(null,n)}),a.beforesend.call(i,l),l.send(null==r?null:r),i},i.abort=function(){return l.abort(),i},oa.rebind(i,a,"on"),null==r?i:i.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var u=e+t,i={c:n,t:u,n:null};return io?io.n=i:uo=i,io=i,ao||(oo=clearTimeout(oo),ao=1,lo(Tn)),i}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(oo),oo=setTimeout(Tn,t)),ao=0):(ao=1,lo(Tn))}function Rn(){for(var n=Date.now(),t=uo;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=uo,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jn(n){var t=n.decimal,e=n.thousands,r=n.grouping,u=n.currency,i=r&&e?function(n,t){for(var u=n.length,i=[],a=0,o=r[0],l=0;u>0&&o>0&&(l+o+1>t&&(o=Math.max(1,t-l)),i.push(n.substring(u-=o,u+o)),!((l+=o+1)>t));)o=r[a=(a+1)%r.length];return i.reverse().join(e)}:y;return function(n){var e=so.exec(n),r=e[1]||" ",a=e[2]||">",o=e[3]||"-",l=e[4]||"",c=e[5],s=+e[6],f=e[7],h=e[8],g=e[9],p=1,v="",d="",m=!1,y=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===a)&&(c=r="0",a="="),g){case"n":f=!0,g="g";break;case"%":p=100,d="%",g="f";break;case"p":p=100,d="%",g="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+g.toLowerCase());case"c":y=!1;case"d":m=!0,h=0;break;case"s":p=-1,g="r"}"$"===l&&(v=u[0],d=u[1]),"r"!=g||h||(g="g"),null!=h&&("g"==g?h=Math.max(1,Math.min(21,h)):("e"==g||"f"==g)&&(h=Math.max(0,Math.min(20,h)))),g=fo.get(g)||Fn;var M=c&&f;return function(n){var e=d;if(m&&n%1)return"";var u=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===o?"":o;if(0>p){var l=oa.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=p;n=g(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=y?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&f&&(x=i(x,1/0));var S=v.length+x.length+b.length+(M?0:u.length),k=s>S?new Array(S=s-S+1).join(r):"";return M&&(x=i(k+x,k.length?s-b.length:1/0)),u+=v,n=x+b,("<"===a?u+n+k:">"===a?k+u+n:"^"===a?k.substring(0,S>>=1)+u+n+k.substring(S):u+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=i(e,1);return r-t>t-e?e:r}function u(e){return t(e=n(new go(e-1)),1),e}function i(n,e){return t(n=new go(+n),e),n}function a(n,r,i){var a=u(n),o=[];if(i>1)for(;r>a;)e(a)%i||o.push(new Date(+a)),t(a,1);else for(;r>a;)o.push(new Date(+a)),t(a,1);return o}function o(n,t,e){try{go=Hn;var r=new Hn;return r._=n,a(r,t,e)}finally{go=Date}}n.floor=n,n.round=r,n.ceil=u,n.offset=i,n.range=a;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(u),l.offset=In(i),l.range=o,n}function In(n){return function(t,e){try{go=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{go=Date}}}function Yn(n){function t(n){function t(t){for(var e,u,i,a=[],o=-1,l=0;++oo;){if(r>=c)return-1;if(u=t.charCodeAt(o++),37===u){if(a=t.charAt(o++),i=C[a in vo?t.charAt(o++):a],!i||(r=i(n,e,r))<0)return-1}else if(u!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function s(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var f=n.dateTime,h=n.date,g=n.time,p=n.periods,v=n.days,d=n.shortDays,m=n.months,y=n.shortMonths;t.utc=function(n){function e(n){try{go=Hn;var t=new go;return t._=n,r(t)}finally{go=Date}}var r=t(n);return e.parse=function(n){try{go=Hn;var t=r.parse(n);return t&&t._}finally{go=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=oa.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(m),k=Xn(m),N=Vn(y),E=Xn(y);p.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return y[n.getMonth()]},B:function(n){return m[n.getMonth()]},c:t(f),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+ho.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return p[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(ho.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(ho.mondayOfYear(n),t,2)},x:t(h),X:t(g),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:ot,"%":function(){return"%"}},C={a:r,A:u,b:i,B:a,c:o,d:tt,e:tt,H:rt,I:rt,j:et,L:at,m:nt,M:ut,p:s,S:it,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",u=(r?-n:n)+"",i=u.length;return r+(e>i?new Array(e-i+1).join(t)+u:u)}function Vn(n){return new RegExp("^(?:"+n.map(oa.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function ut(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function it(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function at(n,t,e){mo.lastIndex=0;var r=mo.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function ot(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=Ma(t)/60|0,u=Ma(t)%60;return e+Zn(r,"0",2)+Zn(u,"0",2)}function lt(n,t,e){yo.lastIndex=0;var r=yo.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,o=a*e,l=Math.cos(t),c=Math.sin(t),s=i*c,f=u*l+s*Math.cos(o),h=s*a*Math.sin(o);So.add(Math.atan2(h,f)),r=n,u=l,i=c}var t,e,r,u,i;ko.point=function(a,o){ko.point=n,r=(t=a)*Oa,u=Math.cos(o=(e=o)*Oa/2+Ua/4),i=Math.sin(o)},ko.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function mt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function yt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return Ma(n[0]-t[0])o;++o)u.point((e=n[o])[0],e[1]);return void u.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,i.push(l),a.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,i.push(l),a.push(c)}}),a.sort(t),qt(i),qt(a),i.length){for(var o=0,l=e,c=a.length;c>o;++o)a[o].e=l=!l;for(var s,f,h=i[0];;){for(var g=h,p=!0;g.v;)if((g=g.n)===h)return;s=g.z,u.lineStart();do{if(g.v=g.o.v=!0,g.e){if(p)for(var o=0,c=s.length;c>o;++o)u.point((f=s[o])[0],f[1]);else r(g.x,g.n.x,1,u);g=g.n}else{if(p){s=g.p.z;for(var o=s.length-1;o>=0;--o)u.point((f=s[o])[0],f[1])}else r(g.x,g.p.x,-1,u);g=g.p}g=g.o,s=g.z,p=!p}while(!g.v);u.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,u=n[0];++r0){for(b||(i.polygonStart(),b=!0),i.lineStart();++a1&&2&t&&e.push(e.pop().concat(e.shift())),g.push(e.filter(Dt))}var g,p,v,d=t(i),m=u.invert(r[0],r[1]),y={point:a,lineStart:l,lineEnd:c,polygonStart:function(){y.point=s,y.lineStart=f,y.lineEnd=h,g=[],p=[]},polygonEnd:function(){y.point=a,y.lineStart=l,y.lineEnd=c,g=oa.merge(g);var n=Ot(m,p);g.length?(b||(i.polygonStart(),b=!0),Lt(g,Ut,n,e,i)):n&&(b||(i.polygonStart(),b=!0),i.lineStart(),e(null,null,1,i),i.lineEnd()),b&&(i.polygonEnd(),b=!1),g=p=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},M=Pt(),x=t(M),b=!1;return y}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ut(n,t){return((n=n.x)[0]<0?n[1]-Ha-Da:Ha-n[1])-((t=t.x)[0]<0?t[1]-Ha-Da:Ha-t[1])}function jt(n){var t,e=NaN,r=NaN,u=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(i,a){var o=i>0?Ua:-Ua,l=Ma(i-e);Ma(l-Ua)0?Ha:-Ha),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(o,r),n.point(i,r),t=0):u!==o&&l>=Ua&&(Ma(e-u)Da?Math.atan((Math.sin(t)*(i=Math.cos(r))*Math.sin(e)-Math.sin(r)*(u=Math.cos(t))*Math.sin(n))/(u*i*a)):(t+r)/2}function Ht(n,t,e,r){var u;if(null==n)u=e*Ha,r.point(-Ua,u),r.point(0,u),r.point(Ua,u),r.point(Ua,0),r.point(Ua,-u),r.point(0,-u),r.point(-Ua,-u),r.point(-Ua,0),r.point(-Ua,u);else if(Ma(n[0]-t[0])>Da){var i=n[0]o;++o){var c=t[o],s=c.length;if(s)for(var f=c[0],h=f[0],g=f[1]/2+Ua/4,p=Math.sin(g),v=Math.cos(g),d=1;;){d===s&&(d=0),n=c[d];var m=n[0],y=n[1]/2+Ua/4,M=Math.sin(y),x=Math.cos(y),b=m-h,_=b>=0?1:-1,w=_*b,S=w>Ua,k=p*M;if(So.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),i+=S?b+_*ja:b,S^h>=e^m>=e){var N=yt(dt(f),dt(n));bt(N);var E=yt(u,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(a+=S^b>=0?1:-1)}if(!d++)break;h=m,p=M,v=x,f=n}}return(-Da>i||Da>i&&0>So)^1&a}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>i}function e(n){var e,i,l,c,s;return{lineStart:function(){c=l=!1,s=1},point:function(f,h){var g,p=[f,h],v=t(f,h),d=a?v?0:u(f,h):v?u(f+(0>f?Ua:-Ua),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(g=r(e,p),(wt(e,g)||wt(p,g))&&(p[0]+=Da,p[1]+=Da,v=t(p[0],p[1]))),v!==l)s=0,v?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(o&&e&&a^v){var m;d&i||!(m=r(p,e,!0))||(s=0,a?(n.lineStart(),n.point(m[0][0],m[0][1]),n.point(m[1][0],m[1][1]),n.lineEnd()):(n.point(m[1][0],m[1][1]),n.lineEnd(),n.lineStart(),n.point(m[0][0],m[0][1])))}!v||e&&wt(e,p)||n.point(p[0],p[1]),e=p,l=v,i=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return s|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),u=dt(t),a=[1,0,0],o=yt(r,u),l=mt(o,o),c=o[0],s=l-c*c;if(!s)return!e&&n;var f=i*l/s,h=-i*c/s,g=yt(a,o),p=xt(a,f),v=xt(o,h);Mt(p,v);var d=g,m=mt(p,d),y=mt(d,d),M=m*m-y*(mt(p,p)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-m-x)/y);if(Mt(b,p),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=Ma(E-Ua)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(Ma(b[0]-w)Ua^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-m+x)/y);return Mt(z,p),[b,_t(z)]}}}function u(t,e){var r=a?n:Ua-n,u=0;return-r>t?u|=1:t>r&&(u|=2),-r>e?u|=4:e>r&&(u|=8),u}var i=Math.cos(n),a=i>0,o=Ma(i)>Da,l=ve(n,6*Oa);return Rt(t,e,l,a?[0,-n]:[-Ua,n-Ua])}function Yt(n,t,e,r){return function(u){var i,a=u.a,o=u.b,l=a.x,c=a.y,s=o.x,f=o.y,h=0,g=1,p=s-l,v=f-c;if(i=n-l,p||!(i>0)){if(i/=p,0>p){if(h>i)return;g>i&&(g=i)}else if(p>0){if(i>g)return;i>h&&(h=i)}if(i=e-l,p||!(0>i)){if(i/=p,0>p){if(i>g)return;i>h&&(h=i)}else if(p>0){if(h>i)return;g>i&&(g=i)}if(i=t-c,v||!(i>0)){if(i/=v,0>v){if(h>i)return;g>i&&(g=i)}else if(v>0){if(i>g)return;i>h&&(h=i)}if(i=r-c,v||!(0>i)){if(i/=v,0>v){if(i>g)return;i>h&&(h=i)}else if(v>0){if(h>i)return;g>i&&(g=i)}return h>0&&(u.a={x:l+h*p,y:c+h*v}),1>g&&(u.b={x:l+g*p,y:c+g*v}),u}}}}}}function Zt(n,t,e,r){function u(r,u){return Ma(r[0]-n)0?0:3:Ma(r[0]-e)0?2:1:Ma(r[1]-t)0?1:0:u>0?3:2}function i(n,t){return a(n.x,t.x)}function a(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(o){function l(n){for(var t=0,e=d.length,r=n[1],u=0;e>u;++u)for(var i,a=1,o=d[u],l=o.length,c=o[0];l>a;++a)i=o[a],c[1]<=r?i[1]>r&&Q(c,i,n)>0&&++t:i[1]<=r&&Q(c,i,n)<0&&--t,c=i;return 0!==t}function c(i,o,l,c){var s=0,f=0;if(null==i||(s=u(i,l))!==(f=u(o,l))||a(i,o)<0^l>0){do c.point(0===s||3===s?n:e,s>1?r:t);while((s=(s+l+4)%4)!==f)}else c.point(o[0],o[1])}function s(u,i){return u>=n&&e>=u&&i>=t&&r>=i}function f(n,t){s(n,t)&&o.point(n,t)}function h(){C.point=p,d&&d.push(m=[]),S=!0,w=!1,b=_=NaN}function g(){v&&(p(y,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=f,w&&o.lineEnd()}function p(n,t){n=Math.max(-Fo,Math.min(Fo,n)),t=Math.max(-Fo,Math.min(Fo,t));var e=s(n,t);if(d&&m.push([n,t]),S)y=n,M=t,x=e,S=!1,e&&(o.lineStart(),o.point(n,t));else if(e&&w)o.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(o.lineStart(),o.point(r.a.x,r.a.y)),o.point(r.b.x,r.b.y),e||o.lineEnd(),k=!1):e&&(o.lineStart(),o.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,m,y,M,x,b,_,w,S,k,N=o,E=Pt(),A=Yt(n,t,e,r),C={point:f,lineStart:h,lineEnd:g,polygonStart:function(){o=E,v=[],d=[],k=!0},polygonEnd:function(){o=N,v=oa.merge(v);var t=l([n,r]),e=k&&t,u=v.length;(e||u)&&(o.polygonStart(),e&&(o.lineStart(),c(null,null,1,o),o.lineEnd()),u&&Lt(v,i,t,c,o),o.polygonEnd()),v=d=m=null}};return C}}function Vt(n){var t=0,e=Ua/3,r=oe(n),u=r(t,e);return u.parallels=function(n){return arguments.length?r(t=n[0]*Ua/180,e=n[1]*Ua/180):[t/Ua*180,e/Ua*180]},u}function Xt(n,t){function e(n,t){var e=Math.sqrt(i-2*u*Math.sin(t))/u;return[e*Math.sin(n*=u),a-e*Math.cos(n)]}var r=Math.sin(n),u=(r+Math.sin(t))/2,i=1+r*(2*u-r),a=Math.sqrt(i)/u;return e.invert=function(n,t){var e=a-t;return[Math.atan2(n,e)/u,tn((i-(n*n+e*e)*u*u)/(2*u))]},e}function $t(){function n(n,t){Oo+=u*n-r*t,r=n,u=t}var t,e,r,u;Xo.point=function(i,a){Xo.point=n,t=r=i,e=u=a},Xo.lineEnd=function(){n(t,e)}}function Bt(n,t){Io>n&&(Io=n),n>Zo&&(Zo=n),Yo>t&&(Yo=t),t>Vo&&(Vo=t)}function Wt(){function n(n,t){a.push("M",n,",",t,i)}function t(n,t){a.push("M",n,",",t),o.point=e}function e(n,t){a.push("L",n,",",t)}function r(){o.point=n}function u(){a.push("Z")}var i=Jt(4.5),a=[],o={point:n,lineStart:function(){o.point=t},lineEnd:r,polygonStart:function(){o.lineEnd=u},polygonEnd:function(){o.lineEnd=r,o.point=n},pointRadius:function(n){return i=Jt(n),o},result:function(){if(a.length){var n=a.join("");return a=[],n}}};return o}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Ao+=n,Co+=t,++zo}function Kt(){function n(n,r){var u=n-t,i=r-e,a=Math.sqrt(u*u+i*i);Lo+=a*(t+n)/2,qo+=a*(e+r)/2,To+=a,Gt(t=n,e=r)}var t,e;Bo.point=function(r,u){Bo.point=n,Gt(t=r,e=u)}}function Qt(){Bo.point=Gt}function ne(){function n(n,t){var e=n-r,i=t-u,a=Math.sqrt(e*e+i*i);Lo+=a*(r+n)/2,qo+=a*(u+t)/2,To+=a,a=u*n-r*t,Ro+=a*(r+n),Do+=a*(u+t),Po+=3*a,Gt(r=n,u=t)}var t,e,r,u;Bo.point=function(i,a){Bo.point=n,Gt(t=r=i,e=u=a)},Bo.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+a,e),n.arc(t,e,a,0,ja)}function e(t,e){n.moveTo(t,e),o.point=r}function r(t,e){n.lineTo(t,e)}function u(){o.point=t}function i(){n.closePath()}var a=4.5,o={point:t,lineStart:function(){o.point=e},lineEnd:u,polygonStart:function(){o.lineEnd=i},polygonEnd:function(){o.lineEnd=u,o.point=t},pointRadius:function(n){return a=n,o},result:b};return o}function ee(n){function t(n){return(o?r:e)(n)}function e(t){return ie(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=i,t.lineStart()}function i(e,r){var i=dt([e,r]),a=n(e,r);u(M,x,y,b,_,w,M=a[0],x=a[1],y=e,b=i[0],_=i[1],w=i[2],o,t),t.point(M,x)}function a(){S.point=e,t.lineEnd()}function l(){ +r(),S.point=c,S.lineEnd=s}function c(n,t){i(f=n,h=t),g=M,p=x,v=b,d=_,m=w,S.point=i}function s(){u(M,x,y,b,_,w,g,p,f,v,d,m,o,t),S.lineEnd=a,a()}var f,h,g,p,v,d,m,y,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:a,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function u(t,e,r,o,l,c,s,f,h,g,p,v,d,m){var y=s-t,M=f-e,x=y*y+M*M;if(x>4*i&&d--){var b=o+g,_=l+p,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=Ma(Ma(w)-1)i||Ma((y*z+M*L)/x-.5)>.3||a>o*g+l*p+c*v)&&(u(t,e,r,o,l,c,A,C,N,b/=S,_/=S,w,d,m),m.point(A,C),u(A,C,N,b,_,w,s,f,h,g,p,v,d,m))}}var i=.5,a=Math.cos(30*Oa),o=16;return t.precision=function(n){return arguments.length?(o=(i=n*n)>0&&16,t):Math.sqrt(i)},t}function re(n){var t=ee(function(t,e){return n([t*Ia,e*Ia])});return function(n){return le(t(n))}}function ue(n){this.stream=n}function ie(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function ae(n){return oe(function(){return n})()}function oe(n){function t(n){return n=o(n[0]*Oa,n[1]*Oa),[n[0]*h+l,c-n[1]*h]}function e(n){return n=o.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Ia,n[1]*Ia]}function r(){o=Ct(a=fe(m,M,x),i);var n=i(v,d);return l=g-n[0]*h,c=p+n[1]*h,u()}function u(){return s&&(s.valid=!1,s=null),t}var i,a,o,l,c,s,f=ee(function(n,t){return n=i(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,g=480,p=250,v=0,d=0,m=0,M=0,x=0,b=jo,_=y,w=null,S=null;return t.stream=function(n){return s&&(s.valid=!1),s=le(b(a,f(_(n)))),s.valid=!0,s},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,jo):It((w=+n)*Oa),u()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):y,u()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(v=n[0]%360*Oa,d=n[1]%360*Oa,r()):[v*Ia,d*Ia]},t.rotate=function(n){return arguments.length?(m=n[0]%360*Oa,M=n[1]%360*Oa,x=n.length>2?n[2]%360*Oa:0,r()):[m*Ia,M*Ia,x*Ia]},oa.rebind(t,f,"precision"),function(){return i=n.apply(this,arguments),t.invert=i.invert&&e,r()}}function le(n){return ie(n,function(t,e){n.point(t*Oa,e*Oa)})}function ce(n,t){return[n,t]}function se(n,t){return[n>Ua?n-ja:-Ua>n?n+ja:n,t]}function fe(n,t,e){return n?t||e?Ct(ge(n),pe(t,e)):ge(n):t||e?pe(t,e):se}function he(n){return function(t,e){return t+=n,[t>Ua?t-ja:-Ua>t?t+ja:t,e]}}function ge(n){var t=he(n);return t.invert=he(-n),t}function pe(n,t){function e(n,t){var e=Math.cos(t),o=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),s=c*r+o*u;return[Math.atan2(l*i-s*a,o*r-c*u),tn(s*i+l*a)]}var r=Math.cos(n),u=Math.sin(n),i=Math.cos(t),a=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),o=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),s=c*i-l*a;return[Math.atan2(l*i+c*a,o*r+s*u),tn(s*r-o*u)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(u,i,a,o){var l=a*t;null!=u?(u=de(e,u),i=de(e,i),(a>0?i>u:u>i)&&(u+=a*ja)):(u=n+a*ja,i=n-.5*l);for(var c,s=u;a>0?s>i:i>s;s-=l)o.point((c=_t([e,-r*Math.cos(s),-r*Math.sin(s)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Da)%(2*Math.PI)}function me(n,t,e){var r=oa.range(n,t-Da,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function ye(n,t,e){var r=oa.range(n,t-Da,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var u=Math.cos(t),i=Math.sin(t),a=Math.cos(r),o=Math.sin(r),l=u*Math.cos(n),c=u*Math.sin(n),s=a*Math.cos(e),f=a*Math.sin(e),h=2*Math.asin(Math.sqrt(an(r-t)+u*a*an(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*l+t*s,u=e*c+t*f,a=e*i+t*o;return[Math.atan2(u,r)*Ia,Math.atan2(a,Math.sqrt(r*r+u*u))*Ia]}:function(){return[n*Ia,t*Ia]};return p.distance=h,p}function _e(){function n(n,u){var i=Math.sin(u*=Oa),a=Math.cos(u),o=Ma((n*=Oa)-t),l=Math.cos(o);Wo+=Math.atan2(Math.sqrt((o=a*Math.sin(o))*o+(o=r*i-e*a*l)*o),e*i+r*a*l),t=n,e=i,r=a}var t,e,r;Jo.point=function(u,i){t=u*Oa,e=Math.sin(i*=Oa),r=Math.cos(i),Jo.point=n},Jo.lineEnd=function(){Jo.point=Jo.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),u=Math.cos(e),i=n(r*u);return[i*u*Math.sin(t),i*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),u=t(r),i=Math.sin(u),a=Math.cos(u);return[Math.atan2(n*i,r*a),Math.asin(r&&e*i/r)]},e}function Se(n,t){function e(n,t){a>0?-Ha+Da>t&&(t=-Ha+Da):t>Ha-Da&&(t=Ha-Da);var e=a/Math.pow(u(t),i);return[e*Math.sin(i*n),a-e*Math.cos(i*n)]}var r=Math.cos(n),u=function(n){return Math.tan(Ua/4+n/2)},i=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(u(t)/u(n)),a=r*Math.pow(u(n),i)/i;return i?(e.invert=function(n,t){var e=a-t,r=K(i)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/i,2*Math.atan(Math.pow(a/r,1/i))-Ha]},e):Ne}function ke(n,t){function e(n,t){var e=i-t;return[e*Math.sin(u*n),i-e*Math.cos(u*n)]}var r=Math.cos(n),u=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),i=r/u+n;return Ma(u)u;u++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[u])<=0;)--r;e[r++]=u}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var u=n[0],i=e[0],a=t[0]-u,o=r[0]-i,l=n[1],c=e[1],s=t[1]-l,f=r[1]-c,h=(o*(l-c)-f*(u-i))/(f*a-o*s);return[u+h*a,l+h*s]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function Ue(n){var t=ll.pop()||new Pe;return t.site=n,t}function je(n){Be(n),il.remove(n),ll.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,u={x:e,y:r},i=n.P,a=n.N,o=[n];je(n);for(var l=i;l.circle&&Ma(e-l.circle.x)s;++s)c=o[s],l=o[s-1],nr(c.edge,l.site,c.site,u);l=o[0],c=o[f-1],c.edge=Ke(l.site,c.site,null,u),$e(l),$e(c)}function He(n){for(var t,e,r,u,i=n.x,a=n.y,o=il._;o;)if(r=Oe(o,a)-i,r>Da)o=o.L;else{if(u=i-Ie(o,a),!(u>Da)){r>-Da?(t=o.P,e=o):u>-Da?(t=o,e=o.N):t=e=o;break}if(!o.R){t=o;break}o=o.R}var l=Ue(n);if(il.insert(t,l),t||e){if(t===e)return Be(t),e=Ue(t.site),il.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,s=c.x,f=c.y,h=n.x-s,g=n.y-f,p=e.site,v=p.x-s,d=p.y-f,m=2*(h*d-g*v),y=h*h+g*g,M=v*v+d*d,x={x:(d*y-g*M)/m+s,y:(h*M-v*y)/m+f};nr(e.edge,c,p,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,p,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,u=e.y,i=u-t;if(!i)return r;var a=n.P;if(!a)return-(1/0);e=a.site;var o=e.x,l=e.y,c=l-t;if(!c)return o;var s=o-r,f=1/i-1/c,h=s/c;return f?(-h+Math.sqrt(h*h-2*f*(s*s/(-2*c)-l+c/2+u-i/2)))/f+r:(r+o)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,u,i,a,o,l,c,s,f=n[0][0],h=n[1][0],g=n[0][1],p=n[1][1],v=ul,d=v.length;d--;)if(i=v[d],i&&i.prepare())for(o=i.edges,l=o.length,a=0;l>a;)s=o[a].end(),r=s.x,u=s.y,c=o[++a%l].start(),t=c.x,e=c.y,(Ma(r-t)>Da||Ma(u-e)>Da)&&(o.splice(a,0,new tr(Qe(i.site,s,Ma(r-f)Da?{x:f,y:Ma(t-f)Da?{x:Ma(e-p)Da?{x:h,y:Ma(t-h)Da?{x:Ma(e-g)=-Pa)){var g=l*l+c*c,p=s*s+f*f,v=(f*g-c*p)/h,d=(l*p-s*g)/h,f=d+o,m=cl.pop()||new Xe;m.arc=n,m.site=u,m.x=v+a,m.y=f+Math.sqrt(v*v+d*d),m.cy=f,n.circle=m;for(var y=null,M=ol._;M;)if(m.yd||d>=o)return;if(h>p){if(i){if(i.y>=c)return}else i={x:d,y:l};e={x:d,y:c}}else{if(i){if(i.yr||r>1)if(h>p){if(i){if(i.y>=c)return}else i={x:(l-u)/r,y:l};e={x:(c-u)/r,y:c}}else{if(i){if(i.yg){if(i){if(i.x>=o)return}else i={x:a,y:r*a+u};e={x:o,y:r*o+u}}else{if(i){if(i.xi||f>a||r>h||u>g)){if(p=n.point){var p,v=t-n.x,d=e-n.y,m=v*v+d*d;if(l>m){var y=Math.sqrt(l=m);r=t-y,u=e-y,i=t+y,a=e+y,o=p}}for(var M=n.nodes,x=.5*(s+h),b=.5*(f+g),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,s,f,x,b);break;case 1:c(n,x,f,h,b);break;case 2:c(n,s,b,x,g);break;case 3:c(n,x,b,h,g)}}}(n,r,u,i,a),o}function vr(n,t){n=oa.rgb(n),t=oa.rgb(t);var e=n.r,r=n.g,u=n.b,i=t.r-e,a=t.g-r,o=t.b-u;return function(n){return"#"+bn(Math.round(e+i*n))+bn(Math.round(r+a*n))+bn(Math.round(u+o*n))}}function dr(n,t){var e,r={},u={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):u[e]=n[e];for(e in t)e in n||(u[e]=t[e]);return function(n){for(e in r)u[e]=r[e](n);return u}}function mr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function yr(n,t){var e,r,u,i=fl.lastIndex=hl.lastIndex=0,a=-1,o=[],l=[];for(n+="",t+="";(e=fl.exec(n))&&(r=hl.exec(t));)(u=r.index)>i&&(u=t.slice(i,u),o[a]?o[a]+=u:o[++a]=u),(e=e[0])===(r=r[0])?o[a]?o[a]+=r:o[++a]=r:(o[++a]=null,l.push({i:a,x:mr(e,r)})),i=hl.lastIndex;return ir;++r)o[(e=l[r]).i]=e.x(n);return o.join("")})}function Mr(n,t){for(var e,r=oa.interpolators.length;--r>=0&&!(e=oa.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],u=[],i=n.length,a=t.length,o=Math.min(n.length,t.length);for(e=0;o>e;++e)r.push(Mr(n[e],t[e]));for(;i>e;++e)u[e]=n[e];for(;a>e;++e)u[e]=t[e];return function(n){for(e=0;o>e;++e)u[e]=r[e](n);return u}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Ha)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/ja*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*ja/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=oa.hcl(n),t=oa.hcl(t);var e=n.h,r=n.c,u=n.l,i=t.h-e,a=t.c-r,o=t.l-u;return isNaN(a)&&(a=0,r=isNaN(r)?t.c:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return fn(e+i*n,r+a*n,u+o*n)+""}}function Dr(n,t){n=oa.hsl(n),t=oa.hsl(t);var e=n.h,r=n.s,u=n.l,i=t.h-e,a=t.s-r,o=t.l-u;return isNaN(a)&&(a=0,r=isNaN(r)?t.s:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return cn(e+i*n,r+a*n,u+o*n)+""}}function Pr(n,t){n=oa.lab(n),t=oa.lab(t);var e=n.l,r=n.a,u=n.b,i=t.l-e,a=t.a-r,o=t.b-u;return function(n){return gn(e+i*n,r+a*n,u+o*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function jr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),u=Fr(t,e),i=Hr(Or(e,t,-u))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:mr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:mr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var u=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:u-4,x:mr(n[0],t[0])},{i:u-2,x:mr(n[1],t[1])})}else(1!==t[0]||1!==t[1])&&e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=oa.transform(n),t=oa.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,u=-1,i=r.length;++u=0;)e.push(u[r])}function au(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(i=n.children)&&(u=i.length))for(var u,i,a=-1;++ae;++e)(t=n[e][1])>u&&(r=e,u=t);return r}function mu(n){return n.reduce(yu,0)}function yu(n,t){return n+t[1]}function Mu(n,t){return xu(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xu(n,t){for(var e=-1,r=+n[0],u=(n[1]-r)/t,i=[];++e<=t;)i[e]=u*e+r;return i}function bu(n){return[oa.min(n),oa.max(n)]}function _u(n,t){return n.value-t.value}function wu(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Su(n,t){n._pack_next=t,t._pack_prev=n}function ku(n,t){var e=t.x-n.x,r=t.y-n.y,u=n.r+t.r;return.999*u*u>e*e+r*r}function Nu(n){function t(n){s=Math.min(n.x-n.r,s),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(c=e.length)){var e,r,u,i,a,o,l,c,s=1/0,f=-(1/0),h=1/0,g=-(1/0);if(e.forEach(Eu),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(u=e[1],u.x=u.r,u.y=0,t(u),c>2))for(i=e[2],zu(r,u,i),t(i),wu(r,i),r._pack_prev=i,wu(i,u),u=r._pack_next,a=3;c>a;a++){zu(r,u,i=e[a]);var p=0,v=1,d=1;for(o=u._pack_next;o!==u;o=o._pack_next,v++)if(ku(o,i)){p=1;break}if(1==p)for(l=r._pack_prev;l!==o._pack_prev&&!ku(l,i);l=l._pack_prev,d++);p?(d>v||v==d&&u.ra;a++)i=e[a],i.x-=m,i.y-=y,M=Math.max(M,i.r+Math.sqrt(i.x*i.x+i.y*i.y));n.r=M,e.forEach(Au)}}function Eu(n){n._pack_next=n._pack_prev=n}function Au(n){delete n._pack_next,delete n._pack_prev}function Cu(n,t,e,r){var u=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,u)for(var i=-1,a=u.length;++i=0;)t=u[i],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pu(n,t,e){return n.a.parent===t.parent?n.a:e}function Uu(n){return 1+oa.max(n,function(n){return n.y})}function ju(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fu(n){var t=n.children;return t&&t.length?Fu(t[0]):n}function Hu(n){var t,e=n.children;return e&&(t=e.length)?Hu(e[t-1]):n}function Ou(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Iu(n,t){var e=n.x+t[3],r=n.y+t[0],u=n.dx-t[1]-t[3],i=n.dy-t[0]-t[2];return 0>u&&(e+=u/2,u=0),0>i&&(r+=i/2,i=0),{x:e,y:r,dx:u,dy:i}}function Yu(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zu(n){return n.rangeExtent?n.rangeExtent():Yu(n.range())}function Vu(n,t,e,r){var u=e(n[0],n[1]),i=r(t[0],t[1]);return function(n){return i(u(n))}}function Xu(n,t){var e,r=0,u=n.length-1,i=n[r],a=n[u];return i>a&&(e=r,r=u,u=e,e=i,i=a,a=e),n[r]=t.floor(i),n[u]=t.ceil(a),n}function $u(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:wl}function Bu(n,t,e,r){var u=[],i=[],a=0,o=Math.min(n.length,t.length)-1;for(n[o]2?Bu:Vu,l=r?Wr:Br;return a=u(n,t,l,e),o=u(t,n,l,Mr),i}function i(n){return a(n)}var a,o;return i.invert=function(n){return o(n)},i.domain=function(t){return arguments.length?(n=t.map(Number),u()):n},i.range=function(n){return arguments.length?(t=n,u()):t},i.rangeRound=function(n){return i.range(n).interpolate(Ur)},i.clamp=function(n){return arguments.length?(r=n,u()):r},i.interpolate=function(n){return arguments.length?(e=n,u()):e},i.ticks=function(t){return Qu(n,t)},i.tickFormat=function(t,e){return ni(n,t,e)},i.nice=function(t){return Gu(n,t),u()},i.copy=function(){return Wu(n,t,e,r)},u()}function Ju(n,t){return oa.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gu(n,t){return Xu(n,$u(Ku(n,t)[2])),Xu(n,$u(Ku(n,t)[2])),n}function Ku(n,t){null==t&&(t=10);var e=Yu(n),r=e[1]-e[0],u=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),i=t/r*u;return.15>=i?u*=10:.35>=i?u*=5:.75>=i&&(u*=2),e[0]=Math.ceil(e[0]/u)*u,e[1]=Math.floor(e[1]/u)*u+.5*u,e[2]=u,e}function Qu(n,t){return oa.range.apply(oa,Ku(n,t))}function ni(n,t,e){var r=Ku(n,t);if(e){var u=so.exec(e);if(u.shift(),"s"===u[8]){var i=oa.formatPrefix(Math.max(Ma(r[0]),Ma(r[1])));return u[7]||(u[7]="."+ti(i.scale(r[2]))),u[8]="f",e=oa.format(u.join("")),function(n){return e(i.scale(n))+i.symbol}}u[7]||(u[7]="."+ei(u[8],r)),e=u.join("")}else e=",."+ti(r[2])+"f";return oa.format(e)}function ti(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function ei(n,t){var e=ti(t[2]);return n in Sl?Math.abs(e-ti(Math.max(Ma(t[0]),Ma(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ri(n,t,e,r){function u(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function i(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function a(t){return n(u(t))}return a.invert=function(t){return i(n.invert(t))},a.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(u)),a):r},a.base=function(e){return arguments.length?(t=+e,n.domain(r.map(u)),a):t},a.nice=function(){var t=Xu(r.map(u),e?Math:Nl);return n.domain(t),r=t.map(i),a},a.ticks=function(){var n=Yu(r),a=[],o=n[0],l=n[1],c=Math.floor(u(o)),s=Math.ceil(u(l)),f=t%1?2:t;if(isFinite(s-c)){if(e){for(;s>c;c++)for(var h=1;f>h;h++)a.push(i(c)*h);a.push(i(c))}else for(a.push(i(c));c++0;h--)a.push(i(c)*h);for(c=0;a[c]l;s--);a=a.slice(c,s)}return a},a.tickFormat=function(n,e){if(!arguments.length)return kl;arguments.length<2?e=kl:"function"!=typeof e&&(e=oa.format(e));var r=Math.max(1,t*n/a.ticks().length);return function(n){var a=n/i(Math.round(u(n)));return t-.5>a*t&&(a*=t),r>=a?e(n):""}},a.copy=function(){return ri(n.copy(),t,e,r)},Ju(a,n)}function ui(n,t,e){function r(t){return n(u(t))}var u=ii(t),i=ii(1/t);return r.invert=function(t){return i(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(u)),r):e},r.ticks=function(n){return Qu(e,n)},r.tickFormat=function(n,t){return ni(e,n,t)},r.nice=function(n){return r.domain(Gu(e,n))},r.exponent=function(a){return arguments.length?(u=ii(t=a),i=ii(1/t),n.domain(e.map(u)),r):t},r.copy=function(){return ui(n.copy(),t,e)},Ju(r,n)}function ii(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ai(n,t){function e(e){return i[((u.get(e)||("range"===t.t?u.set(e,n.push(e)):NaN))-1)%i.length]}function r(t,e){return oa.range(n.length).map(function(n){return t+e*n})}var u,i,a;return e.domain=function(r){if(!arguments.length)return n;n=[],u=new c;for(var i,a=-1,o=r.length;++ae?[NaN,NaN]:[e>0?o[e-1]:n[0],et?NaN:t/i+n,[t,t+1/i]},r.copy=function(){return li(n,t,e)},u()}function ci(n,t){function e(e){return e>=e?t[oa.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return ci(n,t)},e}function si(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qu(n,t)},t.tickFormat=function(t,e){return ni(n,t,e)},t.copy=function(){return si(n)},t}function fi(){return 0}function hi(n){return n.innerRadius}function gi(n){return n.outerRadius}function pi(n){return n.startAngle}function vi(n){return n.endAngle}function di(n){return n&&n.padAngle}function mi(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function yi(n,t,e,r,u){var i=n[0]-t[0],a=n[1]-t[1],o=(u?r:-r)/Math.sqrt(i*i+a*a),l=o*a,c=-o*i,s=n[0]+l,f=n[1]+c,h=t[0]+l,g=t[1]+c,p=(s+h)/2,v=(f+g)/2,d=h-s,m=g-f,y=d*d+m*m,M=e-r,x=s*g-h*f,b=(0>m?-1:1)*Math.sqrt(Math.max(0,M*M*y-x*x)),_=(x*m-d*b)/y,w=(-x*d-m*b)/y,S=(x*m+d*b)/y,k=(-x*d+m*b)/y,N=_-p,E=w-v,A=S-p,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mi(n){function t(t){function a(){c.push("M",i(n(s),o))}for(var l,c=[],s=[],f=-1,h=t.length,g=En(e),p=En(r);++f1?n.join("L"):n+"Z"}function bi(n){return n.join("L")+"Z"}function _i(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1&&u.push("H",r[0]),u.join("")}function wi(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1){o=t[1],i=n[l],l++,r+="C"+(u[0]+a[0])+","+(u[1]+a[1])+","+(i[0]-o[0])+","+(i[1]-o[1])+","+i[0]+","+i[1];for(var c=2;c9&&(u=3*t/Math.sqrt(u),a[o]=u*e,a[o+1]=u*r));for(o=-1;++o<=l;)u=(n[Math.min(l,o+1)][0]-n[Math.max(0,o-1)][0])/(6*(1+a[o]*a[o])),i.push([u||0,a[o]*u||0]);return i}function Fi(n){return n.length<3?xi(n):n[0]+Ai(n,ji(n))}function Hi(n){for(var t,e,r,u=-1,i=n.length;++u=t?a(n-t):void(s.c=a)}function a(e){var u=p.active,i=p[u];i&&(i.timer.c=null,i.timer.t=NaN,--p.count,delete p[u],i.event&&i.event.interrupt.call(n,n.__data__,i.index));for(var a in p)if(r>+a){var c=p[a];c.timer.c=null,c.timer.t=NaN,--p.count,delete p[a]}s.c=o,qn(function(){return s.c&&o(e||1)&&(s.c=null,s.t=NaN),1},0,l),p.active=r,v.event&&v.event.start.call(n,n.__data__,t),g=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&g.push(r)}),h=v.ease,f=v.duration}function o(u){for(var i=u/f,a=h(i),o=g.length;o>0;)g[--o].call(n,a);return i>=1?(v.event&&v.event.end.call(n,n.__data__,t),--p.count?delete p[r]:delete n[e],1):void 0}var l,s,f,h,g,p=n[e]||(n[e]={active:0,count:0}),v=p[r];v||(l=u.time,s=qn(i,0,l),v=p[r]={tween:new c,time:l,timer:s,delay:u.delay,duration:u.duration,ease:u.ease,index:t},u=null,++p.count)}function na(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function ta(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function ea(n){return n.toISOString()}function ra(n,t,e){function r(t){return n(t)}function u(n,e){var r=n[1]-n[0],u=r/e,i=oa.bisect(Gl,u);return i==Gl.length?[t.year,Ku(n.map(function(n){return n/31536e6}),e)[2]]:i?t[u/Gl[i-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=ua(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=ua(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yu(r.domain()),i=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return i&&(n=i[0],t=i[1]),n.range(e[0],ua(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ra(n.copy(),t,e)},Ju(r,n)}function ua(n){return new Date(n)}function ia(n){return JSON.parse(n.responseText)}function aa(n){var t=sa.createRange();return t.selectNode(sa.body),t.createContextualFragment(n.responseText)}var oa={version:"3.5.14"},la=[].slice,ca=function(n){return la.call(n)},sa=this.document;if(sa)try{ca(sa.documentElement.childNodes)[0].nodeType}catch(fa){ca=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),sa)try{sa.createElement("DIV").style.setProperty("opacity",0,"")}catch(ha){var ga=this.Element.prototype,pa=ga.setAttribute,va=ga.setAttributeNS,da=this.CSSStyleDeclaration.prototype,ma=da.setProperty;ga.setAttribute=function(n,t){pa.call(this,n,t+"")},ga.setAttributeNS=function(n,t,e){va.call(this,n,t,e+"")},da.setProperty=function(n,t,e){ma.call(this,n,t+"",e)}}oa.ascending=e,oa.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},oa.min=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ur&&(e=r)}else{for(;++u=r){e=r;break}for(;++ur&&(e=r)}return e},oa.max=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ue&&(e=r)}else{for(;++u=r){e=r;break}for(;++ue&&(e=r)}return e},oa.extent=function(n,t){var e,r,u,i=-1,a=n.length;if(1===arguments.length){for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}else{for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}return[e,u]},oa.sum=function(n,t){var e,r=0,i=n.length,a=-1;if(1===arguments.length)for(;++a1?l/(s-1):void 0},oa.deviation=function(){var n=oa.variance.apply(this,arguments);return n?Math.sqrt(n):n};var ya=i(e);oa.bisectLeft=ya.left,oa.bisect=oa.bisectRight=ya.right,oa.bisector=function(n){return i(1===n.length?function(t,r){return e(n(t),r)}:n)},oa.shuffle=function(n,t,e){(i=arguments.length)<3&&(e=n.length,2>i&&(t=0));for(var r,u,i=e-t;i;)u=Math.random()*i--|0,r=n[i+t],n[i+t]=n[u+t],n[u+t]=r;return n},oa.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},oa.pairs=function(n){for(var t,e=0,r=n.length-1,u=n[0],i=new Array(0>r?0:r);r>e;)i[e]=[t=u,u=n[++e]];return i},oa.zip=function(){if(!(r=arguments.length))return[];for(var n=-1,t=oa.min(arguments,a),e=new Array(t);++n=0;)for(r=n[u],t=r.length;--t>=0;)e[--a]=r[t];return e};var Ma=Math.abs;oa.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,u=[],i=o(Ma(e)),a=-1;if(n*=i,t*=i,e*=i,0>e)for(;(r=n+e*++a)>t;)u.push(r/i);else for(;(r=n+e*++a)=i.length)return r?r.call(u,a):e?a.sort(e):a;for(var l,s,f,h,g=-1,p=a.length,v=i[o++],d=new c;++g=i.length)return n;var r=[],u=a[e++];return n.forEach(function(n,u){r.push({key:n,values:t(u,e)})}),u?r.sort(function(n,t){return u(n.key,t.key)}):r}var e,r,u={},i=[],a=[];return u.map=function(t,e){return n(e,t,0)},u.entries=function(e){return t(n(oa.map,e,0),0)},u.key=function(n){return i.push(n),u},u.sortKeys=function(n){return a[i.length-1]=n,u},u.sortValues=function(n){return e=n,u},u.rollup=function(n){return r=n,u},u},oa.set=function(n){var t=new m;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(m,{has:h,add:function(n){return this._[s(n+="")]=!0,n},remove:g,values:p,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,f(t))}}),oa.behavior={},oa.rebind=function(n,t){for(var e,r=1,u=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},oa.event=null,oa.requote=function(n){return n.replace(wa,"\\$&")};var wa=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,Sa={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},ka=function(n,t){return t.querySelector(n)},Na=function(n,t){return t.querySelectorAll(n)},Ea=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ea=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(ka=function(n,t){return Sizzle(n,t)[0]||null},Na=Sizzle,Ea=Sizzle.matchesSelector),oa.selection=function(){return oa.select(sa.documentElement)};var Aa=oa.selection.prototype=[];Aa.select=function(n){var t,e,r,u,i=[];n=A(n);for(var a=-1,o=this.length;++a=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Ca.hasOwnProperty(e)?{space:Ca[e],local:n}:n}},Aa.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=oa.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Aa.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,u=-1;if(t=e.classList){for(;++uu){if("string"!=typeof n){2>u&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>u){var i=this.node();return t(i).getComputedStyle(i,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Aa.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},Aa.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Aa.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Aa.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Aa.insert=function(n,t){return n=j(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Aa.remove=function(){return this.each(F)},Aa.data=function(n,t){function e(n,e){var r,u,i,a=n.length,f=e.length,h=Math.min(a,f),g=new Array(f),p=new Array(f),v=new Array(a);if(t){var d,m=new c,y=new Array(a);for(r=-1;++rr;++r)p[r]=H(e[r]);for(;a>r;++r)v[r]=n[r]}p.update=g,p.parentNode=g.parentNode=v.parentNode=n.parentNode,o.push(p),l.push(g),s.push(v)}var r,u,i=-1,a=this.length;if(!arguments.length){for(n=new Array(a=(r=this[0]).length);++ii;i++){u.push(t=[]),t.parentNode=(e=this[i]).parentNode;for(var o=0,l=e.length;l>o;o++)(r=e[o])&&n.call(r,r.__data__,o,i)&&t.push(r)}return E(u)},Aa.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[u])&&(i&&i!==e.nextSibling&&i.parentNode.insertBefore(e,i),i=e);return this},Aa.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,u=e.length;u>r;r++){var i=e[r];if(i)return i}return null},Aa.size=function(){var n=0;return Y(this,function(){++n}),n};var za=[];oa.selection.enter=Z,oa.selection.enter.prototype=za,za.append=Aa.append,za.empty=Aa.empty,za.node=Aa.node,za.call=Aa.call,za.size=Aa.size,za.select=function(n){for(var t,e,r,u,i,a=[],o=-1,l=this.length;++or){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var La=oa.map({mouseenter:"mouseover",mouseleave:"mouseout"});sa&&La.forEach(function(n){"on"+n in sa&&La.remove(n)});var qa,Ta=0;oa.mouse=function(n){return J(n,k())};var Ra=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;oa.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,u=0,i=t.length;i>u;++u)if((r=t[u]).identifier===e)return J(n,r)},oa.behavior.drag=function(){function n(){this.on("mousedown.drag",i).on("touchstart.drag",a)}function e(n,t,e,i,a){return function(){function o(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],p|=n|e,M=r,g({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(m.on(i+d,null).on(a+d,null),y(p),g({type:"dragend"}))}var c,s=this,f=oa.event.target,h=s.parentNode,g=r.of(s,arguments),p=0,v=n(),d=".drag"+(null==v?"":"-"+v),m=oa.select(e(f)).on(i+d,o).on(a+d,l),y=W(f),M=t(h,v);u?(c=u.apply(s,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],g({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),u=null,i=e(b,oa.mouse,t,"mousemove","mouseup"),a=e(G,oa.touch,y,"touchmove","touchend");return n.origin=function(t){return arguments.length?(u=t,n):u},oa.rebind(n,r,"on")},oa.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?ca(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Da=1e-6,Pa=Da*Da,Ua=Math.PI,ja=2*Ua,Fa=ja-Da,Ha=Ua/2,Oa=Ua/180,Ia=180/Ua,Ya=Math.SQRT2,Za=2,Va=4;oa.interpolateZoom=function(n,t){var e,r,u=n[0],i=n[1],a=n[2],o=t[0],l=t[1],c=t[2],s=o-u,f=l-i,h=s*s+f*f;if(Pa>h)r=Math.log(c/a)/Ya,e=function(n){return[u+n*s,i+n*f,a*Math.exp(Ya*n*r)]};else{var g=Math.sqrt(h),p=(c*c-a*a+Va*h)/(2*a*Za*g),v=(c*c-a*a-Va*h)/(2*c*Za*g),d=Math.log(Math.sqrt(p*p+1)-p),m=Math.log(Math.sqrt(v*v+1)-v);r=(m-d)/Ya,e=function(n){var t=n*r,e=rn(d),o=a/(Za*g)*(e*un(Ya*t+d)-en(d));return[u+o*s,i+o*f,a*e/rn(Ya*t+d)]}}return e.duration=1e3*r,e},oa.behavior.zoom=function(){function n(n){n.on(L,f).on($a+".zoom",g).on("dblclick.zoom",p).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function u(n){k.k=Math.max(A[0],Math.min(A[1],n))}function i(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function a(t,e,r,a){t.__chart__={x:k.x,y:k.y,k:k.k},u(Math.pow(2,a)),i(d=e,r),t=oa.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function o(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){o(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function s(n){--z||(n({type:"zoomend"}),d=null)}function f(){function n(){o=1,i(oa.mouse(u),h),c(a)}function r(){f.on(q,null).on(T,null),g(o),s(a)}var u=this,a=D.of(u,arguments),o=0,f=oa.select(t(u)).on(q,n).on(T,r),h=e(oa.mouse(u)),g=W(u);Ol.call(u),l(a)}function h(){function n(){var n=oa.touches(p);return g=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=oa.event.target;oa.select(t).on(x,r).on(b,o),_.push(t);for(var e=oa.event.changedTouches,u=0,i=e.length;i>u;++u)d[e[u].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var s=l[0];a(p,s,d[s.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var s=l[0],f=l[1],h=s[0]-f[0],g=s[1]-f[1];m=h*h+g*g}}function r(){var n,t,e,r,a=oa.touches(p);Ol.call(p);for(var o=0,l=a.length;l>o;++o,r=null)if(e=a[o],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var s=(s=e[0]-n[0])*s+(s=e[1]-n[1])*s,f=m&&Math.sqrt(s/m);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],u(f*g)}M=null,i(n,t),c(v)}function o(){if(oa.event.touches.length){for(var t=oa.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var u in d)return void n()}oa.selectAll(_).on(y,null),w.on(L,f).on(R,h),N(),s(v)}var g,p=this,v=D.of(p,arguments),d={},m=0,y=".zoom-"+oa.event.changedTouches[0].identifier,x="touchmove"+y,b="touchend"+y,_=[],w=oa.select(p),N=W(p);t(),l(v),w.on(L,null).on(R,t)}function g(){var n=D.of(this,arguments);y?clearTimeout(y):(Ol.call(this),v=e(d=m||oa.mouse(this)),l(n)),y=setTimeout(function(){y=null,s(n)},50),S(),u(Math.pow(2,.002*Xa())*k.k),i(d,v),c(n)}function p(){var n=oa.mouse(this),t=Math.log(k.k)/Math.LN2;a(this,n,e(n),oa.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,m,y,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Ba,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return $a||($a="onwheel"in sa?(Xa=function(){return-oa.event.deltaY*(oa.event.deltaMode?120:1)},"wheel"):"onmousewheel"in sa?(Xa=function(){return oa.event.wheelDelta},"mousewheel"):(Xa=function(){return-oa.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Fl?oa.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],u=d?d[0]:e/2,i=d?d[1]:r/2,a=oa.interpolateZoom([(u-k.x)/k.k,(i-k.y)/k.k,e/k.k],[(u-t.x)/t.k,(i-t.y)/t.k,e/t.k]);return function(t){var r=a(t),o=e/r[2];this.__chart__=k={x:u-r[0]*o,y:i-r[1]*o,k:o},c(n)}}).each("interrupt.zoom",function(){s(n)}).each("end.zoom",function(){s(n)}):(this.__chart__=k,l(n),c(n),s(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},o(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},u(+t),o(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Ba:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(m=t&&[+t[0],+t[1]],n):m},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},oa.rebind(n,D,"on")};var Xa,$a,Ba=[0,1/0];oa.color=on,on.prototype.toString=function(){return this.rgb()+""},oa.hsl=ln;var Wa=ln.prototype=new on;Wa.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Wa.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Wa.rgb=function(){return cn(this.h,this.s,this.l)},oa.hcl=sn;var Ja=sn.prototype=new on;Ja.brighter=function(n){return new sn(this.h,this.c,Math.min(100,this.l+Ga*(arguments.length?n:1)))},Ja.darker=function(n){return new sn(this.h,this.c,Math.max(0,this.l-Ga*(arguments.length?n:1)))},Ja.rgb=function(){return fn(this.h,this.c,this.l).rgb()},oa.lab=hn;var Ga=18,Ka=.95047,Qa=1,no=1.08883,to=hn.prototype=new on;to.brighter=function(n){return new hn(Math.min(100,this.l+Ga*(arguments.length?n:1)),this.a,this.b)},to.darker=function(n){return new hn(Math.max(0,this.l-Ga*(arguments.length?n:1)),this.a,this.b)},to.rgb=function(){return gn(this.l,this.a,this.b)},oa.rgb=yn;var eo=yn.prototype=new on;eo.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,u=30;return t||e||r?(t&&u>t&&(t=u),e&&u>e&&(e=u),r&&u>r&&(r=u),new yn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new yn(u,u,u)},eo.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new yn(n*this.r,n*this.g,n*this.b)},eo.hsl=function(){return wn(this.r,this.g,this.b)},eo.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var ro=oa.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});ro.forEach(function(n,t){ro.set(n,Mn(t))}),oa.functor=En,oa.xhr=An(y),oa.dsv=function(n,t){function e(n,e,i){arguments.length<3&&(i=e,e=null);var a=Cn(n,t,null==e?r:u(e),i);return a.row=function(n){return arguments.length?a.response(null==(e=n)?r:u(n)):e},a}function r(n){return e.parse(n.responseText)}function u(n){return function(t){return e.parse(t.responseText,n)}}function i(t){return t.map(a).join(n)}function a(n){return o.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var o=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var u=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(u(n),e)}:u})},e.parseRows=function(n,t){function e(){if(s>=c)return a;if(u)return u=!1,i;var t=s;if(34===n.charCodeAt(t)){for(var e=t;e++s;){var r=n.charCodeAt(s++),o=1;if(10===r)u=!0;else if(13===r)u=!0,10===n.charCodeAt(s)&&(++s,++o);else if(r!==l)continue;return n.slice(t,s-o)}return n.slice(t)}for(var r,u,i={},a={},o=[],c=n.length,s=0,f=0;(r=e())!==a;){for(var h=[];r!==i&&r!==a;)h.push(r),r=e();t&&null==(h=t(h,f++))||o.push(h)}return o},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new m,u=[];return t.forEach(function(n){for(var t in n)r.has(t)||u.push(r.add(t))}),[u.map(a).join(n)].concat(t.map(function(t){return u.map(function(n){return a(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(i).join("\n")},e},oa.csv=oa.dsv(",","text/csv"),oa.tsv=oa.dsv(" ","text/tab-separated-values");var uo,io,ao,oo,lo=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};oa.timer=function(){qn.apply(this,arguments)},oa.timer.flush=function(){Rn(),Dn()},oa.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var co=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Un);oa.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=oa.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),co[8+e/3]};var so=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,fo=oa.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=oa.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),ho=oa.time={},go=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){po.setUTCDate.apply(this._,arguments)},setDay:function(){po.setUTCDay.apply(this._,arguments)},setFullYear:function(){po.setUTCFullYear.apply(this._,arguments)},setHours:function(){po.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){po.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){po.setUTCMinutes.apply(this._,arguments)},setMonth:function(){po.setUTCMonth.apply(this._,arguments)},setSeconds:function(){po.setUTCSeconds.apply(this._,arguments)},setTime:function(){po.setTime.apply(this._,arguments)}};var po=Date.prototype;ho.year=On(function(n){return n=ho.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ho.years=ho.year.range,ho.years.utc=ho.year.utc.range,ho.day=On(function(n){var t=new go(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ho.days=ho.day.range,ho.days.utc=ho.day.utc.range,ho.dayOfYear=function(n){var t=ho.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ho[n]=On(function(n){return(n=ho.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ho.year(n).getDay();return Math.floor((ho.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ho[n+"s"]=e.range,ho[n+"s"].utc=e.utc.range,ho[n+"OfYear"]=function(n){var e=ho.year(n).getDay();return Math.floor((ho.dayOfYear(n)+(e+t)%7)/7)}}),ho.week=ho.sunday,ho.weeks=ho.sunday.range,ho.weeks.utc=ho.sunday.utc.range,ho.weekOfYear=ho.sundayOfYear;var vo={"-":"",_:" ",0:"0"},mo=/^\s*\d+/,yo=/^%/;oa.locale=function(n){return{numberFormat:jn(n),timeFormat:Yn(n)}};var Mo=oa.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], +shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});oa.format=Mo.numberFormat,oa.geo={},st.prototype={s:0,t:0,add:function(n){ft(n,this.t,xo),ft(xo.s,this.s,this),this.s?this.t+=xo.t:this.s=xo.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var xo=new st;oa.geo.stream=function(n,t){n&&bo.hasOwnProperty(n.type)?bo[n.type](n,t):ht(n,t)};var bo={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,u=e.length;++rn?4*Ua+n:n,ko.lineStart=ko.lineEnd=ko.point=b}};oa.geo.bounds=function(){function n(n,t){M.push(x=[s=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=dt([t*Oa,e*Oa]);if(m){var u=yt(m,r),i=[u[1],-u[0],0],a=yt(i,u);bt(a),a=_t(a);var l=t-p,c=l>0?1:-1,v=a[0]*Ia*c,d=Ma(l)>180;if(d^(v>c*p&&c*t>v)){var y=a[1]*Ia;y>g&&(g=y)}else if(v=(v+360)%360-180,d^(v>c*p&&c*t>v)){var y=-a[1]*Ia;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?o(s,t)>o(s,h)&&(h=t):o(t,h)>o(s,h)&&(s=t):h>=s?(s>t&&(s=t),t>h&&(h=t)):t>p?o(s,t)>o(s,h)&&(h=t):o(t,h)>o(s,h)&&(s=t)}else n(t,e);m=r,p=t}function e(){b.point=t}function r(){x[0]=s,x[1]=h,b.point=n,m=null}function u(n,e){if(m){var r=n-p;y+=Ma(r)>180?r+(r>0?360:-360):r}else v=n,d=e;ko.point(n,e),t(n,e)}function i(){ko.lineStart()}function a(){u(v,d),ko.lineEnd(),Ma(y)>Da&&(s=-(h=180)),x[0]=s,x[1]=h,m=null}function o(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nSo?(s=-(h=180),f=-(g=90)):y>Da?g=90:-Da>y&&(f=-90),x[0]=s,x[1]=h}};return function(n){g=h=-(s=f=1/0),M=[],oa.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,u=M[0],i=[u];t>r;++r)e=M[r],c(e[0],u)||c(e[1],u)?(o(u[0],e[1])>o(u[0],u[1])&&(u[1]=e[1]),o(e[0],u[1])>o(u[0],u[1])&&(u[0]=e[0])):i.push(u=e);for(var a,e,p=-(1/0),t=i.length-1,r=0,u=i[t];t>=r;u=e,++r)e=i[r],(a=o(u[1],e[0]))>p&&(p=a,s=e[0],h=u[1])}return M=x=null,s===1/0||f===1/0?[[NaN,NaN],[NaN,NaN]]:[[s,f],[h,g]]}}(),oa.geo.centroid=function(n){No=Eo=Ao=Co=zo=Lo=qo=To=Ro=Do=Po=0,oa.geo.stream(n,Uo);var t=Ro,e=Do,r=Po,u=t*t+e*e+r*r;return Pa>u&&(t=Lo,e=qo,r=To,Da>Eo&&(t=Ao,e=Co,r=zo),u=t*t+e*e+r*r,Pa>u)?[NaN,NaN]:[Math.atan2(e,t)*Ia,tn(r/Math.sqrt(u))*Ia]};var No,Eo,Ao,Co,zo,Lo,qo,To,Ro,Do,Po,Uo={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){Uo.lineStart=At},polygonEnd:function(){Uo.lineStart=Nt}},jo=Rt(zt,jt,Ht,[-Ua,-Ua/2]),Fo=1e9;oa.geo.clipExtent=function(){var n,t,e,r,u,i,a={stream:function(n){return u&&(u.valid=!1),u=i(n),u.valid=!0,u},extent:function(o){return arguments.length?(i=Zt(n=+o[0][0],t=+o[0][1],e=+o[1][0],r=+o[1][1]),u&&(u.valid=!1,u=null),a):[[n,t],[e,r]]}};return a.extent([[0,0],[960,500]])},(oa.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,oa.geo.albers=function(){return oa.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},oa.geo.albersUsa=function(){function n(n){var i=n[0],a=n[1];return t=null,e(i,a),t||(r(i,a),t)||u(i,a),t}var t,e,r,u,i=oa.geo.albers(),a=oa.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),o=oa.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=i.scale(),e=i.translate(),r=(n[0]-e[0])/t,u=(n[1]-e[1])/t;return(u>=.12&&.234>u&&r>=-.425&&-.214>r?a:u>=.166&&.234>u&&r>=-.214&&-.115>r?o:i).invert(n)},n.stream=function(n){var t=i.stream(n),e=a.stream(n),r=o.stream(n);return{point:function(n,u){t.point(n,u),e.point(n,u),r.point(n,u)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(i.precision(t),a.precision(t),o.precision(t),n):i.precision()},n.scale=function(t){return arguments.length?(i.scale(t),a.scale(.35*t),o.scale(t),n.translate(i.translate())):i.scale()},n.translate=function(t){if(!arguments.length)return i.translate();var c=i.scale(),s=+t[0],f=+t[1];return e=i.translate(t).clipExtent([[s-.455*c,f-.238*c],[s+.455*c,f+.238*c]]).stream(l).point,r=a.translate([s-.307*c,f+.201*c]).clipExtent([[s-.425*c+Da,f+.12*c+Da],[s-.214*c-Da,f+.234*c-Da]]).stream(l).point,u=o.translate([s-.205*c,f+.212*c]).clipExtent([[s-.214*c+Da,f+.166*c+Da],[s-.115*c-Da,f+.234*c-Da]]).stream(l).point,n},n.scale(1070)};var Ho,Oo,Io,Yo,Zo,Vo,Xo={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Oo=0,Xo.lineStart=$t},polygonEnd:function(){Xo.lineStart=Xo.lineEnd=Xo.point=b,Ho+=Ma(Oo/2)}},$o={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Bo={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Bo.lineStart=ne},polygonEnd:function(){Bo.point=Gt,Bo.lineStart=Kt,Bo.lineEnd=Qt}};oa.geo.path=function(){function n(n){return n&&("function"==typeof o&&i.pointRadius(+o.apply(this,arguments)),a&&a.valid||(a=u(i)),oa.geo.stream(n,a)),i.result()}function t(){return a=null,n}var e,r,u,i,a,o=4.5;return n.area=function(n){return Ho=0,oa.geo.stream(n,u(Xo)),Ho},n.centroid=function(n){return Ao=Co=zo=Lo=qo=To=Ro=Do=Po=0,oa.geo.stream(n,u(Bo)),Po?[Ro/Po,Do/Po]:To?[Lo/To,qo/To]:zo?[Ao/zo,Co/zo]:[NaN,NaN]},n.bounds=function(n){return Zo=Vo=-(Io=Yo=1/0),oa.geo.stream(n,u($o)),[[Io,Yo],[Zo,Vo]]},n.projection=function(n){return arguments.length?(u=(e=n)?n.stream||re(n):y,t()):e},n.context=function(n){return arguments.length?(i=null==(r=n)?new Wt:new te(n),"function"!=typeof o&&i.pointRadius(o),t()):r},n.pointRadius=function(t){return arguments.length?(o="function"==typeof t?t:(i.pointRadius(+t),+t),n):o},n.projection(oa.geo.albersUsa()).context(null)},oa.geo.transform=function(n){return{stream:function(t){var e=new ue(t);for(var r in n)e[r]=n[r];return e}}},ue.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},oa.geo.projection=ae,oa.geo.projectionMutator=oe,(oa.geo.equirectangular=function(){return ae(ce)}).raw=ce.invert=ce,oa.geo.rotation=function(n){function t(t){return t=n(t[0]*Oa,t[1]*Oa),t[0]*=Ia,t[1]*=Ia,t}return n=fe(n[0]%360*Oa,n[1]*Oa,n.length>2?n[2]*Oa:0),t.invert=function(t){return t=n.invert(t[0]*Oa,t[1]*Oa),t[0]*=Ia,t[1]*=Ia,t},t},se.invert=ce,oa.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=fe(-n[0]*Oa,-n[1]*Oa,0).invert,u=[];return e(null,null,1,{point:function(n,e){u.push(n=t(n,e)),n[0]*=Ia,n[1]*=Ia}}),{type:"Polygon",coordinates:[u]}}var t,e,r=[0,0],u=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Oa,u*Oa),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Oa,(u=+r)*Oa),n):u},n.angle(90)},oa.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Oa,u=n[1]*Oa,i=t[1]*Oa,a=Math.sin(r),o=Math.cos(r),l=Math.sin(u),c=Math.cos(u),s=Math.sin(i),f=Math.cos(i);return Math.atan2(Math.sqrt((e=f*a)*e+(e=c*s-l*f*o)*e),l*s+c*f*o)},oa.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return oa.range(Math.ceil(i/d)*d,u,d).map(h).concat(oa.range(Math.ceil(c/m)*m,l,m).map(g)).concat(oa.range(Math.ceil(r/p)*p,e,p).filter(function(n){return Ma(n%d)>Da}).map(s)).concat(oa.range(Math.ceil(o/v)*v,a,v).filter(function(n){return Ma(n%m)>Da}).map(f))}var e,r,u,i,a,o,l,c,s,f,h,g,p=10,v=p,d=90,m=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(i).concat(g(l).slice(1),h(u).reverse().slice(1),g(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(i=+t[0][0],u=+t[1][0],c=+t[0][1],l=+t[1][1],i>u&&(t=i,i=u,u=t),c>l&&(t=c,c=l,l=t),n.precision(y)):[[i,c],[u,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],o=+t[0][1],a=+t[1][1],r>e&&(t=r,r=e,e=t),o>a&&(t=o,o=a,a=t),n.precision(y)):[[r,o],[e,a]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],m=+t[1],n):[d,m]},n.minorStep=function(t){return arguments.length?(p=+t[0],v=+t[1],n):[p,v]},n.precision=function(t){return arguments.length?(y=+t,s=me(o,a,90),f=ye(r,e,y),h=me(c,l,90),g=ye(i,u,y),n):y},n.majorExtent([[-180,-90+Da],[180,90-Da]]).minorExtent([[-180,-80-Da],[180,80+Da]])},oa.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||u.apply(this,arguments)]}}var t,e,r=Me,u=xe;return n.distance=function(){return oa.geo.distance(t||r.apply(this,arguments),e||u.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(u=t,e="function"==typeof t?null:t,n):u},n.precision=function(){return arguments.length?n:0},n},oa.geo.interpolate=function(n,t){return be(n[0]*Oa,n[1]*Oa,t[0]*Oa,t[1]*Oa)},oa.geo.length=function(n){return Wo=0,oa.geo.stream(n,Jo),Wo};var Wo,Jo={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Go=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(oa.geo.azimuthalEqualArea=function(){return ae(Go)}).raw=Go;var Ko=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},y);(oa.geo.azimuthalEquidistant=function(){return ae(Ko)}).raw=Ko,(oa.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(oa.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var Qo=we(function(n){return 1/n},Math.atan);(oa.geo.gnomonic=function(){return ae(Qo)}).raw=Qo,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Ha]},(oa.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var nl=we(function(){return 1},Math.asin);(oa.geo.orthographic=function(){return ae(nl)}).raw=nl;var tl=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(oa.geo.stereographic=function(){return ae(tl)}).raw=tl,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Ha]},(oa.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,oa.geom={},oa.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,u=En(e),i=En(r),a=n.length,o=[],l=[];for(t=0;a>t;t++)o.push([+u.call(this,n[t],t),+i.call(this,n[t],t),t]);for(o.sort(qe),t=0;a>t;t++)l.push([o[t][0],-o[t][1]]);var c=Le(o),s=Le(l),f=s[0]===c[0],h=s[s.length-1]===c[c.length-1],g=[];for(t=c.length-1;t>=0;--t)g.push(n[o[c[t]][2]]);for(t=+f;t=r&&c.x<=i&&c.y>=u&&c.y<=a?[[r,a],[i,a],[i,u],[r,u]]:[];s.point=n[o]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(i(n,t)/Da)*Da,y:Math.round(a(n,t)/Da)*Da,i:t}})}var r=Ce,u=ze,i=r,a=u,o=sl;return n?t(n):(t.links=function(n){return or(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return or(e(n)).cells.forEach(function(e,r){for(var u,i,a=e.site,o=e.edges.sort(Ve),l=-1,c=o.length,s=o[c-1].edge,f=s.l===a?s.r:s.l;++l=c,h=r>=s,g=h<<1|f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=hr()),f?u=c:o=c,h?a=s:l=s,i(n,t,e,r,u,a,o,l)}var s,f,h,g,p,v,d,m,y,M=En(o),x=En(l);if(null!=t)v=t,d=e,m=r,y=u;else if(m=y=-(v=d=1/0),f=[],h=[],p=n.length,a)for(g=0;p>g;++g)s=n[g],s.xm&&(m=s.x),s.y>y&&(y=s.y),f.push(s.x),h.push(s.y);else for(g=0;p>g;++g){var b=+M(s=n[g],g),_=+x(s,g);v>b&&(v=b),d>_&&(d=_),b>m&&(m=b),_>y&&(y=_),f.push(b),h.push(_)}var w=m-v,S=y-d;w>S?y=d+w:m=v+S;var k=hr();if(k.add=function(n){i(k,n,+M(n,++g),+x(n,g),v,d,m,y)},k.visit=function(n){gr(n,k,v,d,m,y)},k.find=function(n){return pr(k,n[0],n[1],v,d,m,y)},g=-1,null==t){for(;++g=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=pl.get(e)||gl,r=vl.get(r)||y,br(r(e.apply(null,la.call(arguments,1))))},oa.interpolateHcl=Rr,oa.interpolateHsl=Dr,oa.interpolateLab=Pr,oa.interpolateRound=Ur,oa.transform=function(n){var t=sa.createElementNS(oa.ns.prefix.svg,"g");return(oa.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new jr(e?e.matrix:dl)})(n)},jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var dl={a:1,b:0,c:0,d:1,e:0,f:0};oa.interpolateTransform=$r,oa.layout={},oa.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++eo*o/m){if(v>l){var c=t.charge/l;n.px-=i*c,n.py-=a*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=i*c,n.py-=a*c}}return!t.charge}}function t(n){n.px=oa.event.x,n.py=oa.event.y,l.resume()}var e,r,u,i,a,o,l={},c=oa.dispatch("start","tick","end"),s=[1,1],f=.9,h=ml,g=yl,p=-30,v=Ml,d=.1,m=.64,M=[],x=[];return l.tick=function(){if((u*=.99)<.005)return e=null,c.end({type:"end",alpha:u=0}),!0;var t,r,l,h,g,v,m,y,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,g=l.target,y=g.x-h.x,b=g.y-h.y,(v=y*y+b*b)&&(v=u*a[r]*((v=Math.sqrt(v))-i[r])/v,y*=v,b*=v,g.x-=y*(m=h.weight+g.weight?h.weight/(h.weight+g.weight):.5),g.y-=b*m,h.x+=y*(m=1-m),h.y+=b*m);if((m=u*d)&&(y=s[0]/2,b=s[1]/2,r=-1,m))for(;++r<_;)l=M[r],l.x+=(y-l.x)*m,l.y+=(b-l.y)*m;if(p)for(ru(t=oa.geom.quadtree(M),u,o),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*f,l.y-=(l.py-(l.py=l.y))*f);c.tick({type:"tick",alpha:u})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(s=n,l):s},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.friction=function(n){return arguments.length?(f=+n,l):f},l.charge=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(m=n*n,l):Math.sqrt(m)},l.alpha=function(n){return arguments.length?(n=+n,u?n>0?u=n:(e.c=null,e.t=NaN,e=null,c.end({type:"end",alpha:u=0})):n>0&&(c.start({type:"start",alpha:u=n}),e=qn(l.tick)),l):u},l.start=function(){function n(n,r){if(!e){for(e=new Array(u),l=0;u>l;++l)e[l]=[];for(l=0;c>l;++l){var i=x[l];e[i.source.index].push(i.target),e[i.target.index].push(i.source)}}for(var a,o=e[t],l=-1,s=o.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;u>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",f)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(i=[],"function"==typeof h)for(t=0;c>t;++t)i[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)i[t]=h;if(a=[],"function"==typeof g)for(t=0;c>t;++t)a[t]=+g.call(this,x[t],t);else for(t=0;c>t;++t)a[t]=g;if(o=[],"function"==typeof p)for(t=0;u>t;++t)o[t]=+p.call(this,M[t],t);else for(t=0;u>t;++t)o[t]=p;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=oa.behavior.drag().origin(y).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",nu)),arguments.length?void this.on("mouseover.force",tu).on("mouseout.force",eu).call(r):r},oa.rebind(l,c,"on")};var ml=20,yl=1,Ml=1/0;oa.layout.hierarchy=function(){function n(u){var i,a=[u],o=[];for(u.depth=0;null!=(i=a.pop());)if(o.push(i),(c=e.call(n,i,i.depth))&&(l=c.length)){for(var l,c,s;--l>=0;)a.push(s=c[l]),s.parent=i,s.depth=i.depth+1;r&&(i.value=0),i.children=c}else r&&(i.value=+r.call(n,i,i.depth)||0),delete i.children;return au(u,function(n){var e,u;t&&(e=n.children)&&e.sort(t),r&&(u=n.parent)&&(u.value+=n.value)}),o}var t=cu,e=ou,r=lu;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(iu(t,function(n){n.children&&(n.value=0)}),au(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},oa.layout.partition=function(){function n(t,e,r,u){var i=t.children;if(t.x=e,t.y=t.depth*u,t.dx=r,t.dy=u,i&&(a=i.length)){var a,o,l,c=-1;for(r=t.value?r/t.value:0;++cf?-1:1),p=oa.sum(c),v=p?(f-l*g)/p:0,d=oa.range(l),m=[];return null!=e&&d.sort(e===xl?function(n,t){return c[t]-c[n]}:function(n,t){return e(a[n],a[t])}),d.forEach(function(n){m[n]={data:a[n],value:o=c[n],startAngle:s,endAngle:s+=o*v+g,padAngle:h}}),m}var t=Number,e=xl,r=0,u=ja,i=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(u=t,n):u},n.padAngle=function(t){return arguments.length?(i=t,n):i},n};var xl={};oa.layout.stack=function(){function n(o,l){if(!(h=o.length))return o;var c=o.map(function(e,r){return t.call(n,e,r)}),s=c.map(function(t){return t.map(function(t,e){return[i.call(n,t,e),a.call(n,t,e)]})}),f=e.call(n,s,l);c=oa.permute(c,f),s=oa.permute(s,f);var h,g,p,v,d=r.call(n,s,l),m=c[0].length;for(p=0;m>p;++p)for(u.call(n,c[0][p],v=d[p],s[0][p][1]),g=1;h>g;++g)u.call(n,c[g][p],v+=s[g-1][p][1],s[g][p][1]);return o}var t=y,e=pu,r=vu,u=gu,i=fu,a=hu;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:bl.get(t)||pu,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:_l.get(t)||vu,n):r},n.x=function(t){return arguments.length?(i=t,n):i},n.y=function(t){return arguments.length?(a=t,n):a},n.out=function(t){return arguments.length?(u=t,n):u},n};var bl=oa.map({"inside-out":function(n){var t,e,r=n.length,u=n.map(du),i=n.map(mu),a=oa.range(r).sort(function(n,t){return u[n]-u[t]}),o=0,l=0,c=[],s=[];for(t=0;r>t;++t)e=a[t],l>o?(o+=i[e],c.push(e)):(l+=i[e],s.push(e));return s.reverse().concat(c)},reverse:function(n){return oa.range(n.length).reverse()},"default":pu}),_l=oa.map({silhouette:function(n){var t,e,r,u=n.length,i=n[0].length,a=[],o=0,l=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];r>o&&(o=r),a.push(r)}for(e=0;i>e;++e)l[e]=(o-a[e])/2;return l},wiggle:function(n){var t,e,r,u,i,a,o,l,c,s=n.length,f=n[0],h=f.length,g=[];for(g[0]=l=c=0,e=1;h>e;++e){for(t=0,u=0;s>t;++t)u+=n[t][e][1];for(t=0,i=0,o=f[e][0]-f[e-1][0];s>t;++t){for(r=0,a=(n[t][e][1]-n[t][e-1][1])/(2*o);t>r;++r)a+=(n[r][e][1]-n[r][e-1][1])/o;i+=a*n[t][e][1]}g[e]=l-=u?i/u*o:0,c>l&&(c=l)}for(e=0;h>e;++e)g[e]-=c;return g},expand:function(n){var t,e,r,u=n.length,i=n[0].length,a=1/u,o=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];if(r)for(t=0;u>t;t++)n[t][e][1]/=r;else for(t=0;u>t;t++)n[t][e][1]=a}for(e=0;i>e;++e)o[e]=0;return o},zero:vu});oa.layout.histogram=function(){function n(n,i){for(var a,o,l=[],c=n.map(e,this),s=r.call(this,c,i),f=u.call(this,s,c,i),i=-1,h=c.length,g=f.length-1,p=t?1:1/h;++i0)for(i=-1;++i=s[0]&&o<=s[1]&&(a=l[oa.bisect(f,o,1,g)-1],a.y+=p,a.push(n[i]));return l}var t=!0,e=Number,r=bu,u=Mu;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(u="number"==typeof t?function(n){return xu(n,t)}:En(t),n):u},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},oa.layout.pack=function(){function n(n,i){var a=e.call(this,n,i),o=a[0],l=u[0],c=u[1],s=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(o.x=o.y=0,au(o,function(n){n.r=+s(n.value)}),au(o,Nu),r){var f=r*(t?1:Math.max(2*o.r/l,2*o.r/c))/2;au(o,function(n){n.r+=f}),au(o,Nu),au(o,function(n){n.r-=f})}return Cu(o,l/2,c/2,t?1:1/Math.max(2*o.r/l,2*o.r/c)),a}var t,e=oa.layout.hierarchy().sort(_u),r=0,u=[1,1];return n.size=function(t){return arguments.length?(u=t,n):u},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},uu(n,e)},oa.layout.tree=function(){function n(n,u){var s=a.call(this,n,u),f=s[0],h=t(f);if(au(h,e),h.parent.m=-h.z,iu(h,r),c)iu(f,i);else{var g=f,p=f,v=f;iu(f,function(n){n.xp.x&&(p=n),n.depth>v.depth&&(v=n)});var d=o(g,p)/2-g.x,m=l[0]/(p.x+o(p,g)/2+d),y=l[1]/(v.depth||1);iu(f,function(n){n.x=(n.x+d)*m,n.y=n.depth*y})}return s}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var u,i=t.children,a=0,o=i.length;o>a;++a)r.push((i[a]=u={_:i[a],parent:t,children:(u=i[a].children)&&u.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:a}).a=u);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Du(n);var i=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+o(n._,r._),n.m=n.z-i):n.z=i}else r&&(n.z=r.z+o(n._,r._));n.parent.A=u(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function u(n,t,e){if(t){for(var r,u=n,i=n,a=t,l=u.parent.children[0],c=u.m,s=i.m,f=a.m,h=l.m;a=Tu(a),u=qu(u),a&&u;)l=qu(l),i=Tu(i),i.a=n,r=a.z+f-u.z-c+o(a._,u._),r>0&&(Ru(Pu(a,n,e),n,r),c+=r,s+=r),f+=a.m,c+=u.m,h+=l.m,s+=i.m;a&&!Tu(i)&&(i.t=a,i.m+=f-s),u&&!qu(l)&&(l.t=u,l.m+=c-h,e=n)}return e}function i(n){n.x*=l[0],n.y=n.depth*l[1]}var a=oa.layout.hierarchy().sort(null).value(null),o=Lu,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(o=t,n):o},n.size=function(t){return arguments.length?(c=null==(l=t)?i:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:i,n):c?l:null},uu(n,a)},oa.layout.cluster=function(){function n(n,i){var a,o=t.call(this,n,i),l=o[0],c=0;au(l,function(n){var t=n.children;t&&t.length?(n.x=ju(t),n.y=Uu(t)):(n.x=a?c+=e(n,a):0,n.y=0,a=n)});var s=Fu(l),f=Hu(l),h=s.x-e(s,f)/2,g=f.x+e(f,s)/2;return au(l,u?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),o}var t=oa.layout.hierarchy().sort(null).value(null),e=Lu,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},uu(n,t)},oa.layout.treemap=function(){function n(n,t){for(var e,r,u=-1,i=n.length;++ut?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var i=e.children;if(i&&i.length){var a,o,l,c=f(e),s=[],h=i.slice(),p=1/0,v="slice"===g?c.dx:"dice"===g?c.dy:"slice-dice"===g?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),s.area=0;(l=h.length)>0;)s.push(a=h[l-1]),s.area+=a.area,"squarify"!==g||(o=r(s,v))<=p?(h.pop(),p=o):(s.area-=s.pop().area,u(s,v,c,!1),v=Math.min(c.dx,c.dy),s.length=s.area=0,p=1/0);s.length&&(u(s,v,c,!0),s.length=s.area=0),i.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var i,a=f(t),o=r.slice(),l=[];for(n(o,a.dx*a.dy/t.value),l.area=0;i=o.pop();)l.push(i),l.area+=i.area,null!=i.z&&(u(l,i.z?a.dx:a.dy,a,!o.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,u=0,i=1/0,a=-1,o=n.length;++ae&&(i=e),e>u&&(u=e));return r*=r,t*=t,r?Math.max(t*u*p/r,r/(t*i*p)):1/0}function u(n,t,e,r){var u,i=-1,a=n.length,o=e.x,c=e.y,s=t?l(n.area/t):0; +if(t==e.dx){for((r||s>e.dy)&&(s=e.dy);++ie.dx)&&(s=e.dx);++ie&&(t=1),1>e&&(n=0),function(){var e,r,u;do e=2*Math.random()-1,r=2*Math.random()-1,u=e*e+r*r;while(!u||u>1);return n+t*e*Math.sqrt(-2*Math.log(u)/u)}},logNormal:function(){var n=oa.random.normal.apply(oa,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=oa.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},oa.scale={};var wl={floor:y,ceil:y};oa.scale.linear=function(){return Wu([0,1],[0,1],Mr,!1)};var Sl={s:1,g:1,p:1,r:1,e:1};oa.scale.log=function(){return ri(oa.scale.linear().domain([0,1]),10,!0,[1,10])};var kl=oa.format(".0e"),Nl={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};oa.scale.pow=function(){return ui(oa.scale.linear(),1,[0,1])},oa.scale.sqrt=function(){return oa.scale.pow().exponent(.5)},oa.scale.ordinal=function(){return ai([],{t:"range",a:[[]]})},oa.scale.category10=function(){return oa.scale.ordinal().range(El)},oa.scale.category20=function(){return oa.scale.ordinal().range(Al)},oa.scale.category20b=function(){return oa.scale.ordinal().range(Cl)},oa.scale.category20c=function(){return oa.scale.ordinal().range(zl)};var El=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Al=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),Cl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),zl=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);oa.scale.quantile=function(){return oi([],[])},oa.scale.quantize=function(){return li(0,1,[0,1])},oa.scale.threshold=function(){return ci([.5],[0,1])},oa.scale.identity=function(){return si([0,1])},oa.svg={},oa.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),s=a.apply(this,arguments)-Ha,f=o.apply(this,arguments)-Ha,h=Math.abs(f-s),g=s>f?0:1;if(n>c&&(p=c,c=n,n=p),h>=Fa)return t(c,g)+(n?t(n,1-g):"")+"Z";var p,v,d,m,y,M,x,b,_,w,S,k,N=0,E=0,A=[];if((m=(+l.apply(this,arguments)||0)/2)&&(d=i===Ll?Math.sqrt(n*n+c*c):+i.apply(this,arguments),g||(E*=-1),c&&(E=tn(d/c*Math.sin(m))),n&&(N=tn(d/n*Math.sin(m)))),c){y=c*Math.cos(s+E),M=c*Math.sin(s+E),x=c*Math.cos(f-E),b=c*Math.sin(f-E);var C=Math.abs(f-s-2*E)<=Ua?0:1;if(E&&mi(y,M,x,b)===g^C){var z=(s+f)/2;y=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else y=M=0;if(n){_=n*Math.cos(f-N),w=n*Math.sin(f-N),S=n*Math.cos(s+N),k=n*Math.sin(s+N);var L=Math.abs(s-f+2*N)<=Ua?0:1;if(N&&mi(_,w,S,k)===1-g^L){var q=(s+f)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Da&&(p=Math.min(Math.abs(c-n)/2,+u.apply(this,arguments)))>.001){v=c>n^g?0:1;var T=p,R=p;if(Ua>h){var D=null==S?[_,w]:null==x?[y,M]:Re([y,M],[S,k],[x,b],[_,w]),P=y-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(p,(n-O)/(H-1)),T=Math.min(p,(c-O)/(H+1))}if(null!=x){var I=yi(null==S?[_,w]:[S,k],[y,M],c,T,g),Y=yi([x,b],[_,w],c,T,g);p===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-g^mi(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",g," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",y,",",M);if(null!=S){var Z=yi([y,M],[S,k],n,-R,g),V=yi([_,w],null==x?[y,M]:[x,b],n,-R,g);p===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",g^mi(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-g," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",y,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",g," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-g," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hi,r=gi,u=fi,i=Ll,a=pi,o=vi,l=di;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(u=En(t),n):u},n.padRadius=function(t){return arguments.length?(i=t==Ll?Ll:En(t),n):i},n.startAngle=function(t){return arguments.length?(a=En(t),n):a},n.endAngle=function(t){return arguments.length?(o=En(t),n):o},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+a.apply(this,arguments)+ +o.apply(this,arguments))/2-Ha;return[Math.cos(t)*n,Math.sin(t)*n]},n};var Ll="auto";oa.svg.line=function(){return Mi(y)};var ql=oa.map({linear:xi,"linear-closed":bi,step:_i,"step-before":wi,"step-after":Si,basis:zi,"basis-open":Li,"basis-closed":qi,bundle:Ti,cardinal:Ei,"cardinal-open":ki,"cardinal-closed":Ni,monotone:Fi});ql.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Tl=[0,2/3,1/3,0],Rl=[0,1/3,2/3,0],Dl=[0,1/6,2/3,1/6];oa.svg.line.radial=function(){var n=Mi(Hi);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wi.reverse=Si,Si.reverse=wi,oa.svg.area=function(){return Oi(y)},oa.svg.area.radial=function(){var n=Oi(Hi);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},oa.svg.chord=function(){function n(n,o){var l=t(this,i,n,o),c=t(this,a,n,o);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?u(l.r,l.p1,l.r,l.p0):u(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+u(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var u=t.call(n,e,r),i=o.call(n,u,r),a=l.call(n,u,r)-Ha,s=c.call(n,u,r)-Ha;return{r:i,a0:a,a1:s,p0:[i*Math.cos(a),i*Math.sin(a)],p1:[i*Math.cos(s),i*Math.sin(s)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Ua)+",1 "+t}function u(n,t,e,r){return"Q 0,0 "+r}var i=Me,a=xe,o=Ii,l=pi,c=vi;return n.radius=function(t){return arguments.length?(o=En(t),n):o},n.source=function(t){return arguments.length?(i=En(t),n):i},n.target=function(t){return arguments.length?(a=En(t),n):a},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},oa.svg.diagonal=function(){function n(n,u){var i=t.call(this,n,u),a=e.call(this,n,u),o=(i.y+a.y)/2,l=[i,{x:i.x,y:o},{x:a.x,y:o},a];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yi;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},oa.svg.diagonal.radial=function(){var n=oa.svg.diagonal(),t=Yi,e=n.projection;return n.projection=function(n){return arguments.length?e(Zi(t=n)):t},n},oa.svg.symbol=function(){function n(n,r){return(Pl.get(t.call(this,n,r))||$i)(e.call(this,n,r))}var t=Xi,e=Vi;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Pl=oa.map({circle:$i,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*jl)),e=t*jl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/Ul),e=t*Ul/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/Ul),e=t*Ul/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});oa.svg.symbolTypes=Pl.keys();var Ul=Math.sqrt(3),jl=Math.tan(30*Oa);Aa.transition=function(n){for(var t,e,r=Fl||++Yl,u=Ki(n),i=[],a=Hl||{time:Date.now(),ease:Nr,delay:0,duration:250},o=-1,l=this.length;++oi;i++){u.push(t=[]);for(var e=this[i],o=0,l=e.length;l>o;o++)(r=e[o])&&n.call(r,r.__data__,o,i)&&t.push(r)}return Wi(u,this.namespace,this.id)},Il.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(u){u[r][e].tween.set(n,t)})},Il.attr=function(n,t){function e(){this.removeAttribute(o)}function r(){this.removeAttributeNS(o.space,o.local)}function u(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(o);return e!==n&&(t=a(e,n),function(n){this.setAttribute(o,t(n))})})}function i(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(o.space,o.local);return e!==n&&(t=a(e,n),function(n){this.setAttributeNS(o.space,o.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var a="transform"==n?$r:Mr,o=oa.ns.qualify(n);return Ji(this,"attr."+n,t,o.local?i:u)},Il.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(u));return r&&function(n){this.setAttribute(u,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(u.space,u.local));return r&&function(n){this.setAttributeNS(u.space,u.local,r(n))}}var u=oa.ns.qualify(n);return this.tween("attr."+n,u.local?r:e)},Il.style=function(n,e,r){function u(){this.style.removeProperty(n)}function i(e){return null==e?u:(e+="",function(){var u,i=t(this).getComputedStyle(this,null).getPropertyValue(n);return i!==e&&(u=Mr(i,e),function(t){this.style.setProperty(n,u(t),r)})})}var a=arguments.length;if(3>a){if("string"!=typeof n){2>a&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ji(this,"style."+n,e,i)},Il.styleTween=function(n,e,r){function u(u,i){var a=e.call(this,u,i,t(this).getComputedStyle(this,null).getPropertyValue(n));return a&&function(t){this.style.setProperty(n,a(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,u)},Il.text=function(n){return Ji(this,"text",n,Gi)},Il.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Il.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=oa.ease.apply(oa,arguments)),Y(this,function(r){r[e][t].ease=n}))},Il.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,u,i){r[e][t].delay=+n.call(r,r.__data__,u,i)}:(n=+n,function(r){r[e][t].delay=n}))},Il.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,u,i){r[e][t].duration=Math.max(1,n.call(r,r.__data__,u,i))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Il.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var u=Hl,i=Fl;try{Fl=e,Y(this,function(t,u,i){Hl=t[r][e],n.call(t,t.__data__,u,i)})}finally{Hl=u,Fl=i}}else Y(this,function(u){var i=u[r][e];(i.event||(i.event=oa.dispatch("start","end","interrupt"))).on(n,t)});return this},Il.transition=function(){for(var n,t,e,r,u=this.id,i=++Yl,a=this.namespace,o=[],l=0,c=this.length;c>l;l++){o.push(n=[]);for(var t=this[l],s=0,f=t.length;f>s;s++)(e=t[s])&&(r=e[a][u],Qi(e,s,a,i,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wi(o,a,i)},oa.svg.axis=function(){function n(n){n.each(function(){var n,c=oa.select(this),s=this.__chart__||e,f=this.__chart__=e.copy(),h=null==l?f.ticks?f.ticks.apply(f,o):f.domain():l,g=null==t?f.tickFormat?f.tickFormat.apply(f,o):y:t,p=c.selectAll(".tick").data(h,f),v=p.enter().insert("g",".domain").attr("class","tick").style("opacity",Da),d=oa.transition(p.exit()).style("opacity",Da).remove(),m=oa.transition(p.order()).style("opacity",1),M=Math.max(u,0)+a,x=Zu(f),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),oa.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=m.select("line"),C=p.select("text").text(g),z=v.select("text"),L=m.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=na,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*i+"V0H"+x[1]+"V"+q*i)):(n=ta,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*i+","+x[0]+"H0V"+x[1]+"H"+q*i)),E.attr(N,q*u),z.attr(k,q*M),A.attr(S,0).attr(N,q*u),L.attr(w,0).attr(k,q*M),f.rangeBand){var T=f,R=T.rangeBand()/2;s=f=function(n){return T(n)+R}}else s.rangeBand?s=f:d.call(n,f,s);v.call(n,s,f),m.call(n,f,f)})}var t,e=oa.scale.linear(),r=Zl,u=6,i=6,a=3,o=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Vl?t+"":Zl,n):r},n.ticks=function(){return arguments.length?(o=ca(arguments),n):o},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(u=+t,i=+arguments[e-1],n):u},n.innerTickSize=function(t){return arguments.length?(u=+t,n):u},n.outerTickSize=function(t){return arguments.length?(i=+t,n):i},n.tickPadding=function(t){return arguments.length?(a=+t,n):a},n.tickSubdivide=function(){return arguments.length&&n},n};var Zl="bottom",Vl={top:1,right:1,bottom:1,left:1};oa.svg.brush=function(){function n(t){t.each(function(){var t=oa.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",i).on("touchstart.brush",i),a=t.selectAll(".background").data([0]);a.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var o=t.selectAll(".resize").data(v,y);o.exit().remove(),o.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return Xl[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),o.style("display",n.empty()?"none":null);var l,f=oa.transition(t),h=oa.transition(a);c&&(l=Zu(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(f)),s&&(l=Zu(s),h.attr("y",l[0]).attr("height",l[1]-l[0]),u(f)),e(f)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+f[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",f[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",f[1]-f[0])}function u(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function i(){function i(){32==oa.event.keyCode&&(C||(M=null,L[0]-=f[1],L[1]-=h[1],C=2),S())}function v(){32==oa.event.keyCode&&2==C&&(L[0]+=f[1],L[1]+=h[1],C=0,S())}function d(){var n=oa.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(oa.event.altKey?(M||(M=[(f[0]+f[1])/2,(h[0]+h[1])/2]),L[0]=f[+(n[0]s?(u=r,r=s):u=s),v[0]!=r||v[1]!=u?(e?o=null:a=null,v[0]=r,v[1]=u,!0):void 0}function y(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),oa.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=oa.select(oa.event.target),w=l.of(b,arguments),k=oa.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&s,C=_.classed("extent"),z=W(b),L=oa.mouse(b),q=oa.select(t(b)).on("keydown.brush",i).on("keyup.brush",v);if(oa.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",y):q.on("mousemove.brush",d).on("mouseup.brush",y),k.interrupt().selectAll("*").interrupt(),C)L[0]=f[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[f[1-T]-L[0],h[1-R]-L[1]],L[0]=f[T],L[1]=h[R]}else oa.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),oa.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var a,o,l=N(n,"brushstart","brush","brushend"),c=null,s=null,f=[0,0],h=[0,0],g=!0,p=!0,v=$l[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:f,y:h,i:a,j:o},e=this.__chart__||t;this.__chart__=t,Fl?oa.select(this).transition().each("start.brush",function(){a=e.i,o=e.j,f=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(f,t.x),r=xr(h,t.y);return a=o=null,function(u){f=t.x=e(u),h=t.y=r(u),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){a=t.i,o=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=$l[!c<<1|!s],n):c},n.y=function(t){return arguments.length?(s=t,v=$l[!c<<1|!s],n):s},n.clamp=function(t){return arguments.length?(c&&s?(g=!!t[0],p=!!t[1]):c?g=!!t:s&&(p=!!t),n):c&&s?[g,p]:c?g:s?p:null},n.extent=function(t){var e,r,u,i,l;return arguments.length?(c&&(e=t[0],r=t[1],s&&(e=e[0],r=r[0]),a=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),(e!=f[0]||r!=f[1])&&(f=[e,r])),s&&(u=t[0],i=t[1],c&&(u=u[1],i=i[1]),o=[u,i],s.invert&&(u=s(u),i=s(i)),u>i&&(l=u,u=i,i=l),(u!=h[0]||i!=h[1])&&(h=[u,i])),n):(c&&(a?(e=a[0],r=a[1]):(e=f[0],r=f[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),s&&(o?(u=o[0],i=o[1]):(u=h[0],i=h[1],s.invert&&(u=s.invert(u),i=s.invert(i)),u>i&&(l=u,u=i,i=l))),c&&s?[[e,u],[r,i]]:c?[e,r]:s&&[u,i])},n.clear=function(){return n.empty()||(f=[0,0],h=[0,0],a=o=null),n},n.empty=function(){return!!c&&f[0]==f[1]||!!s&&h[0]==h[1]},oa.rebind(n,l,"on")};var Xl={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},$l=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Bl=ho.format=Mo.timeFormat,Wl=Bl.utc,Jl=Wl("%Y-%m-%dT%H:%M:%S.%LZ");Bl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?ea:Jl,ea.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},ea.toString=Jl.toString,ho.second=On(function(n){return new go(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ho.seconds=ho.second.range,ho.seconds.utc=ho.second.utc.range,ho.minute=On(function(n){return new go(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ho.minutes=ho.minute.range,ho.minutes.utc=ho.minute.utc.range,ho.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new go(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ho.hours=ho.hour.range,ho.hours.utc=ho.hour.utc.range,ho.month=On(function(n){return n=ho.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ho.months=ho.month.range,ho.months.utc=ho.month.utc.range;var Gl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Kl=[[ho.second,1],[ho.second,5],[ho.second,15],[ho.second,30],[ho.minute,1],[ho.minute,5],[ho.minute,15],[ho.minute,30],[ho.hour,1],[ho.hour,3],[ho.hour,6],[ho.hour,12],[ho.day,1],[ho.day,2],[ho.week,1],[ho.month,1],[ho.month,3],[ho.year,1]],Ql=Bl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),nc={range:function(n,t,e){return oa.range(Math.ceil(n/e)*e,+t,e).map(ua)},floor:y,ceil:y};Kl.year=ho.year,ho.scale=function(){return ra(oa.scale.linear(),Kl,Ql)};var tc=Kl.map(function(n){return[n[0].utc,n[1]]}),ec=Wl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);tc.year=ho.year.utc,ho.scale.utc=function(){return ra(oa.scale.linear(),tc,ec)},oa.text=An(function(n){return n.responseText}),oa.json=function(n,t){return Cn(n,"application/json",ia,t)},oa.html=function(n,t){return Cn(n,"text/html",aa,t)},oa.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=oa,define(oa)):"object"==typeof module&&module.exports?module.exports=oa:this.d3=oa}(); \ No newline at end of file diff --git a/webroot/rsrc/externals/raphael/g.raphael.js b/webroot/rsrc/externals/raphael/g.raphael.js deleted file mode 100644 index 70936409ad..0000000000 --- a/webroot/rsrc/externals/raphael/g.raphael.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @provides raphael-g - * @do-not-minify - * @nolint - */ -/*! - * g.Raphael 0.5 - Charting library, based on Raphaël - * - * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com) - * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. - */ -Raphael.el.popup=function(d,k,h,g){var c=this.paper||this[0].paper,f,j,b,e,a;if(!c){return}switch(this.type){case"text":case"circle":case"ellipse":b=true;break;default:b=false}d=d==null?"up":d;k=k||5;f=this.getBBox();h=typeof h=="number"?h:(b?f.x+f.width/2:f.x);g=typeof g=="number"?g:(b?f.y+f.height/2:f.y);e=Math.max(f.width/2-k,0);a=Math.max(f.height/2-k,0);this.translate(h-f.x-(b?f.width/2:0),g-f.y-(b?f.height/2:0));f=this.getBBox();var i={up:["M",h,g,"l",-k,-k,-e,0,"a",k,k,0,0,1,-k,-k,"l",0,-f.height,"a",k,k,0,0,1,k,-k,"l",k*2+e*2,0,"a",k,k,0,0,1,k,k,"l",0,f.height,"a",k,k,0,0,1,-k,k,"l",-e,0,"z"].join(","),down:["M",h,g,"l",k,k,e,0,"a",k,k,0,0,1,k,k,"l",0,f.height,"a",k,k,0,0,1,-k,k,"l",-(k*2+e*2),0,"a",k,k,0,0,1,-k,-k,"l",0,-f.height,"a",k,k,0,0,1,k,-k,"l",e,0,"z"].join(","),left:["M",h,g,"l",-k,k,0,a,"a",k,k,0,0,1,-k,k,"l",-f.width,0,"a",k,k,0,0,1,-k,-k,"l",0,-(k*2+a*2),"a",k,k,0,0,1,k,-k,"l",f.width,0,"a",k,k,0,0,1,k,k,"l",0,a,"z"].join(","),right:["M",h,g,"l",k,-k,0,-a,"a",k,k,0,0,1,k,-k,"l",f.width,0,"a",k,k,0,0,1,k,k,"l",0,k*2+a*2,"a",k,k,0,0,1,-k,k,"l",-f.width,0,"a",k,k,0,0,1,-k,-k,"l",0,-a,"z"].join(",")};j={up:{x:-!b*(f.width/2),y:-k*2-(b?f.height/2:f.height)},down:{x:-!b*(f.width/2),y:k*2+(b?f.height/2:f.height)},left:{x:-k*2-(b?f.width/2:f.width),y:-!b*(f.height/2)},right:{x:k*2+(b?f.width/2:f.width),y:-!b*(f.height/2)}}[d];this.translate(j.x,j.y);return c.path(i[d]).attr({fill:"#000",stroke:"none"}).insertBefore(this.node?this:this[0])};Raphael.el.tag=function(f,b,l,k){var i=3,e=this.paper||this[0].paper;if(!e){return}var c=e.path().attr({fill:"#000",stroke:"#000"}),j=this.getBBox(),m,h,a,g;switch(this.type){case"text":case"circle":case"ellipse":a=true;break;default:a=false}f=f||0;l=typeof l=="number"?l:(a?j.x+j.width/2:j.x);k=typeof k=="number"?k:(a?j.y+j.height/2:j.y);b=b==null?5:b;h=0.5522*b;if(j.height>=b*2){c.attr({path:["M",l,k+b,"a",b,b,0,1,1,0,-b*2,b,b,0,1,1,0,b*2,"m",0,-b*2-i,"a",b+i,b+i,0,1,0,0,(b+i)*2,"L",l+b+i,k+j.height/2+i,"l",j.width+2*i,0,0,-j.height-2*i,-j.width-2*i,0,"L",l,k-b-i].join(",")})}else{m=Math.sqrt(Math.pow(b+i,2)-Math.pow(j.height/2+i,2));c.attr({path:["M",l,k+b,"c",-h,0,-b,h-b,-b,-b,0,-h,b-h,-b,b,-b,h,0,b,b-h,b,b,0,h,h-b,b,-b,b,"M",l+m,k-j.height/2-i,"a",b+i,b+i,0,1,0,0,j.height+2*i,"l",b+i-m+j.width+2*i,0,0,-j.height-2*i,"L",l+m,k-j.height/2-i].join(",")})}f=360-f;c.rotate(f,l,k);if(this.attrs){this.attr(this.attrs.x?"x":"cx",l+b+i+(!a?this.type=="text"?j.width:0:j.width/2)).attr("y",a?k:k-j.height/2);this.rotate(f,l,k);f>90&&f<270&&this.attr(this.attrs.x?"x":"cx",l-b-i-(!a?j.width:j.width/2)).rotate(180,l,k)}else{if(f>90&&f<270){this.translate(l-j.x-j.width-b-i,k-j.y-j.height/2);this.rotate(f-180,j.x+j.width+b+i,j.y+j.height/2)}else{this.translate(l-j.x+b+i,k-j.y-j.height/2);this.rotate(f,j.x-b-i,j.y+j.height/2)}}return c.insertBefore(this.node?this:this[0])};Raphael.el.drop=function(d,g,f){var e=this.getBBox(),c=this.paper||this[0].paper,a,j,b,i,h;if(!c){return}switch(this.type){case"text":case"circle":case"ellipse":a=true;break;default:a=false}d=d||0;g=typeof g=="number"?g:(a?e.x+e.width/2:e.x);f=typeof f=="number"?f:(a?e.y+e.height/2:e.y);j=Math.max(e.width,e.height)+Math.min(e.width,e.height);b=c.path(["M",g,f,"l",j,0,"A",j*0.4,j*0.4,0,1,0,g+j*0.7,f-j*0.7,"z"]).attr({fill:"#000",stroke:"none"}).rotate(22.5-d,g,f);d=(d+90)*Math.PI/180;i=(g+j*Math.sin(d))-(a?0:e.width/2);h=(f+j*Math.cos(d))-(a?0:e.height/2);this.attrs?this.attr(this.attrs.x?"x":"cx",i).attr(this.attrs.y?"y":"cy",h):this.translate(i-e.x,h-e.y);return b.insertBefore(this.node?this:this[0])};Raphael.el.flag=function(e,k,j){var g=3,c=this.paper||this[0].paper;if(!c){return}var b=c.path().attr({fill:"#000",stroke:"#000"}),i=this.getBBox(),f=i.height/2,a;switch(this.type){case"text":case"circle":case"ellipse":a=true;break;default:a=false}e=e||0;k=typeof k=="number"?k:(a?i.x+i.width/2:i.x);j=typeof j=="number"?j:(a?i.y+i.height/2:i.y);b.attr({path:["M",k,j,"l",f+g,-f-g,i.width+2*g,0,0,i.height+2*g,-i.width-2*g,0,"z"].join(",")});e=360-e;b.rotate(e,k,j);if(this.attrs){this.attr(this.attrs.x?"x":"cx",k+f+g+(!a?this.type=="text"?i.width:0:i.width/2)).attr("y",a?j:j-i.height/2);this.rotate(e,k,j);e>90&&e<270&&this.attr(this.attrs.x?"x":"cx",k-f-g-(!a?i.width:i.width/2)).rotate(180,k,j)}else{if(e>90&&e<270){this.translate(k-i.x-i.width-f-g,j-i.y-i.height/2);this.rotate(e-180,i.x+i.width+f+g,i.y+i.height/2)}else{this.translate(k-i.x+f+g,j-i.y-i.height/2);this.rotate(e,i.x-f-g,i.y+i.height/2)}}return b.insertBefore(this.node?this:this[0])};Raphael.el.label=function(){var c=this.getBBox(),b=this.paper||this[0].paper,a=Math.min(20,c.width+10,c.height+10)/2;if(!b){return}return b.rect(c.x-a/2,c.y-a/2,c.width+a,c.height+a,a).attr({stroke:"none",fill:"#000"}).insertBefore(this.node?this:this[0])};Raphael.el.blob=function(z,j,i){var g=this.getBBox(),B=Math.PI/180,n=this.paper||this[0].paper,r,A,q;if(!n){return}switch(this.type){case"text":case"circle":case"ellipse":A=true;break;default:A=false}r=n.path().attr({fill:"#000",stroke:"none"});z=(+z+1?z:45)+90;q=Math.min(g.height,g.width);j=typeof j=="number"?j:(A?g.x+g.width/2:g.x);i=typeof i=="number"?i:(A?g.y+g.height/2:g.y);var m=Math.max(g.width+q,q*25/12),t=Math.max(g.height+q,q*25/12),u=j+q*Math.sin((z-22.5)*B),b=i+q*Math.cos((z-22.5)*B),v=j+q*Math.sin((z+22.5)*B),d=i+q*Math.cos((z+22.5)*B),o=(v-u)/2,l=(d-b)/2,f=m/2,e=t/2,s=-Math.sqrt(Math.abs(f*f*e*e-f*f*l*l-e*e*o*o)/(f*f*l*l+e*e*o*o)),c=s*f*l/e+(v+u)/2,a=s*-e*o/f+(d+b)/2;r.attr({x:c,y:a,path:["M",j,i,"L",v,d,"A",f,e,0,1,1,u,b,"z"].join(",")});this.translate(c-g.x-g.width/2,a-g.y-g.height/2);return r.insertBefore(this.node?this:this[0])};Raphael.fn.label=function(a,d,b){var c=this.set();b=this.text(a,d,b).attr(Raphael.g.txtattr);return c.push(b.label(),b)};Raphael.fn.popup=function(a,f,d,b,c){var e=this.set();d=this.text(a,f,d).attr(Raphael.g.txtattr);return e.push(d.popup(b,c),d)};Raphael.fn.tag=function(a,f,d,c,b){var e=this.set();d=this.text(a,f,d).attr(Raphael.g.txtattr);return e.push(d.tag(c,b),d)};Raphael.fn.flag=function(a,e,c,b){var d=this.set();c=this.text(a,e,c).attr(Raphael.g.txtattr);return d.push(c.flag(b),c)};Raphael.fn.drop=function(a,e,c,b){var d=this.set();c=this.text(a,e,c).attr(Raphael.g.txtattr);return d.push(c.drop(b),c)};Raphael.fn.blob=function(a,e,c,b){var d=this.set();c=this.text(a,e,c).attr(Raphael.g.txtattr);return d.push(c.blob(b),c)};Raphael.el.lighter=function(b){b=b||2;var a=[this.attrs.fill,this.attrs.stroke];this.fs=this.fs||[a[0],a[1]];a[0]=Raphael.rgb2hsb(Raphael.getRGB(a[0]).hex);a[1]=Raphael.rgb2hsb(Raphael.getRGB(a[1]).hex);a[0].b=Math.min(a[0].b*b,1);a[0].s=a[0].s/b;a[1].b=Math.min(a[1].b*b,1);a[1].s=a[1].s/b;this.attr({fill:"hsb("+[a[0].h,a[0].s,a[0].b]+")",stroke:"hsb("+[a[1].h,a[1].s,a[1].b]+")"});return this};Raphael.el.darker=function(b){b=b||2;var a=[this.attrs.fill,this.attrs.stroke];this.fs=this.fs||[a[0],a[1]];a[0]=Raphael.rgb2hsb(Raphael.getRGB(a[0]).hex);a[1]=Raphael.rgb2hsb(Raphael.getRGB(a[1]).hex);a[0].s=Math.min(a[0].s*b,1);a[0].b=a[0].b/b;a[1].s=Math.min(a[1].s*b,1);a[1].b=a[1].b/b;this.attr({fill:"hsb("+[a[0].h,a[0].s,a[0].b]+")",stroke:"hsb("+[a[1].h,a[1].s,a[1].b]+")"});return this};Raphael.el.resetBrightness=function(){if(this.fs){this.attr({fill:this.fs[0],stroke:this.fs[1]});delete this.fs}return this};(function(){var c=["lighter","darker","resetBrightness"],a=["popup","tag","flag","label","drop","blob"];for(var b in a){(function(d){Raphael.st[d]=function(){return Raphael.el[d].apply(this,arguments)}})(a[b])}for(var b in c){(function(d){Raphael.st[d]=function(){for(var e=0;e0?0:0.5))*Math.pow(10,b))/Math.pow(10,b);return{from:e,to:l,power:b}},axis:function(p,o,k,D,e,G,g,J,h,a,q){a=a==null?2:a;h=h||"t";G=G||10;q=arguments[arguments.length-1];var C=h=="|"||h==" "?["M",p+0.5,o,"l",0,0.001]:g==1||g==3?["M",p+0.5,o,"l",0,-k]:["M",p,o+0.5,"l",k,0],s=this.snapEnds(D,e,G),H=s.from,z=s.to,F=s.power,E=0,w={font:"11px 'Fontin Sans', Fontin-Sans, sans-serif"},v=q.set(),I;I=(z-H)/G;var n=H,m=F>0?F:0;r=k/G;if(+g==1||+g==3){var b=o,u=(g-1?1:-1)*(a+3+!!(g-1));while(b>=o-k){h!="-"&&h!=" "&&(C=C.concat(["M",p-(h=="+"||h=="|"?a:!(g-1)*a*2),b+0.5,"l",a*2+1,0]));v.push(q.text(p+u,b,(J&&J[E++])||(Math.round(n)==n?n:+n.toFixed(m))).attr(w).attr({"text-anchor":g-1?"start":"end"}));n+=I;b-=r}if(Math.round(b+r-(o-k))){h!="-"&&h!=" "&&(C=C.concat(["M",p-(h=="+"||h=="|"?a:!(g-1)*a*2),o-k+0.5,"l",a*2+1,0]));v.push(q.text(p+u,o-k,(J&&J[E])||(Math.round(n)==n?n:+n.toFixed(m))).attr(w).attr({"text-anchor":g-1?"start":"end"}))}}else{n=H;m=(F>0)*F;u=(g?-1:1)*(a+9+!g);var c=p,r=k/G,A=0,B=0;while(c<=p+k){h!="-"&&h!=" "&&(C=C.concat(["M",c+0.5,o-(h=="+"?a:!!g*a*2),"l",0,a*2+1]));v.push(A=q.text(c,o+u,(J&&J[E++])||(Math.round(n)==n?n:+n.toFixed(m))).attr(w));var l=A.getBBox();if(B>=l.x-5){v.pop(v.length-1).remove()}else{B=l.x+l.width}n+=I;c+=r}if(Math.round(c-r-p-k)){h!="-"&&h!=" "&&(C=C.concat(["M",p+k+0.5,o-(h=="+"?a:!!g*a*2),"l",0,a*2+1]));v.push(q.text(p+k,o+u,(J&&J[E])||(Math.round(n)==n?n:+n.toFixed(m))).attr(w))}}var K=q.path(C);K.text=v;K.all=q.set([K,v]);K.remove=function(){this.text.remove();this.constructor.prototype.remove.call(this)};return K},labelise:function(a,c,b){if(a){return(a+"").replace(/(##+(?:\.#+)?)|(%%+(?:\.%+)?)/g,function(d,f,e){if(f){return(+c).toFixed(f.replace(/^#+\.?/g,"").length)}if(e){return(c*100/b).toFixed(e.replace(/^%+\.?/g,"").length)+"%"}})}else{return(+c).toFixed(0)}}}; \ No newline at end of file diff --git a/webroot/rsrc/externals/raphael/g.raphael.line.js b/webroot/rsrc/externals/raphael/g.raphael.line.js deleted file mode 100644 index fe8892e190..0000000000 --- a/webroot/rsrc/externals/raphael/g.raphael.line.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @provides raphael-g-line - * @do-not-minify - * @nolint - */ -/*! - * g.Raphael 0.5 - Charting library, based on Raphaël - * - * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com) - * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. - */ -(function(){function a(g,n){var f=g.length/n,h=0,e=f,m=0,i=[];while(he-2*q){z[ac]=a(z[ac],e-2*q);B=e-2*q}if(A[ac]&&A[ac].length>e-2*q){A[ac]=a(A[ac],e-2*q)}}var W=Array.prototype.concat.apply([],A),U=Array.prototype.concat.apply([],z),u=s.snapEnds(Math.min.apply(Math,W),Math.max.apply(Math,W),A[0].length-1),E=u.from,o=u.to,N=s.snapEnds(Math.min.apply(Math,U),Math.max.apply(Math,U),z[0].length-1),C=N.from,n=N.to,Z=(e-q*2)/((o-E)||1),V=(h-q*2)/((n-C)||1);var G=f.set();if(J.axis){var m=(J.axis+"").split(/[,\s]+/);+m[0]&&G.push(s.axis(P+q,O+q,e-2*q,E,o,J.axisxstep||Math.floor((e-2*q)/20),2,f));+m[1]&&G.push(s.axis(P+e-q,O+h-q,h-2*q,C,n,J.axisystep||Math.floor((h-2*q)/20),3,f));+m[2]&&G.push(s.axis(P+q,O+h-q,e-2*q,E,o,J.axisxstep||Math.floor((e-2*q)/20),0,f));+m[3]&&G.push(s.axis(P+q,O+h-q,h-2*q,C,n,J.axisystep||Math.floor((h-2*q)/20),1,f))}var M=f.set(),aa=f.set(),r;for(ac=0,L=z.length;acf*b.top){e=b.percents[y],p=b.percents[y-1]||0,t=t/b.top*(e-p),o=b.percents[y+1],j=b.anim[e];break}f&&d.attr(b.anim[b.percents[y]])}if(!!j){if(!k){for(var A in j)if(j[g](A))if(U[g](A)||d.paper.customAttributes[g](A)){u[A]=d.attr(A),u[A]==null&&(u[A]=T[A]),v[A]=j[A];switch(U[A]){case C:w[A]=(v[A]-u[A])/t;break;case"colour":u[A]=a.getRGB(u[A]);var B=a.getRGB(v[A]);w[A]={r:(B.r-u[A].r)/t,g:(B.g-u[A].g)/t,b:(B.b-u[A].b)/t};break;case"path":var D=bG(u[A],v[A]),E=D[1];u[A]=D[0],w[A]=[];for(y=0,z=u[A].length;yd)return d;while(cf?c=e:d=e,e=(d-c)/2+c}return e}function n(a,b){var c=o(a,b);return((l*c+k)*c+j)*c}function m(a){return((i*a+h)*a+g)*a}var g=3*b,h=3*(d-b)-g,i=1-g-h,j=3*c,k=3*(e-c)-j,l=1-j-k;return n(a,1/(200*f))}function cd(){return this.x+q+this.y+q+this.width+" × "+this.height}function cc(){return this.x+q+this.y}function bQ(a,b,c,d,e,f){a!=null?(this.a=+a,this.b=+b,this.c=+c,this.d=+d,this.e=+e,this.f=+f):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}function bw(a){var b=[];for(var c=0,d=a.length;d-2>c;c+=2){var e=[{x:+a[c],y:+a[c+1]},{x:+a[c],y:+a[c+1]},{x:+a[c+2],y:+a[c+3]},{x:+a[c+4],y:+a[c+5]}];d-4==c?(e[0]={x:+a[c-2],y:+a[c-1]},e[3]=e[2]):c&&(e[0]={x:+a[c-2],y:+a[c-1]}),b.push(["C",(-e[0].x+6*e[1].x+e[2].x)/6,(-e[0].y+6*e[1].y+e[2].y)/6,(e[1].x+6*e[2].x-e[3].x)/6,(e[1].y+6*e[2].y-e[3].y)/6,e[2].x,e[2].y])}return b}function bv(){return this.hex}function bt(a,b,c){function d(){var e=Array.prototype.slice.call(arguments,0),f=e.join("␀"),h=d.cache=d.cache||{},i=d.count=d.count||[];if(h[g](f)){bs(i,f);return c?c(h[f]):h[f]}i.length>=1e3&&delete h[i.shift()],i.push(f),h[f]=a[m](b,e);return c?c(h[f]):h[f]}return d}function bs(a,b){for(var c=0,d=a.length;c',bk=bj.firstChild,bk.style.behavior="url(#default#VML)";if(!bk||typeof bk.adj!="object")return a.type=p;bj=null}a.svg=!(a.vml=a.type=="VML"),a._Paper=j,a.fn=k=j.prototype=a.prototype,a._id=0,a._oid=0,a.is=function(a,b){b=v.call(b);if(b=="finite")return!M[g](+a);if(b=="array")return a instanceof Array;return b=="null"&&a===null||b==typeof a&&a!==null||b=="object"&&a===Object(a)||b=="array"&&Array.isArray&&Array.isArray(a)||H.call(a).slice(8,-1).toLowerCase()==b},a.angle=function(b,c,d,e,f,g){if(f==null){var h=b-d,i=c-e;if(!h&&!i)return 0;return(180+w.atan2(-i,-h)*180/B+360)%360}return a.angle(b,c,f,g)-a.angle(d,e,f,g)},a.rad=function(a){return a%360*B/180},a.deg=function(a){return a*180/B%360},a.snapTo=function(b,c,d){d=a.is(d,"finite")?d:10;if(a.is(b,E)){var e=b.length;while(e--)if(z(b[e]-c)<=d)return b[e]}else{b=+b;var f=c%b;if(fb-d)return c-f+b}return c};var bl=a.createUUID=function(a,b){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(a,b).toUpperCase()}}(/[xy]/g,function(a){var b=w.random()*16|0,c=a=="x"?b:b&3|8;return c.toString(16)});a.setWindow=function(b){eve("setWindow",a,h.win,b),h.win=b,h.doc=h.win.document,a._engine.initWin&&a._engine.initWin(h.win)};var bm=function(b){if(a.vml){var c=/^\s+|\s+$/g,d;try{var e=new ActiveXObject("htmlfile");e.write(""),e.close(),d=e.body}catch(f){d=createPopup().document.body}var g=d.createTextRange();bm=bt(function(a){try{d.style.color=r(a).replace(c,p);var b=g.queryCommandValue("ForeColor");b=(b&255)<<16|b&65280|(b&16711680)>>>16;return"#"+("000000"+b.toString(16)).slice(-6)}catch(e){return"none"}})}else{var i=h.doc.createElement("i");i.title="Raphaël Colour Picker",i.style.display="none",h.doc.body.appendChild(i),bm=bt(function(a){i.style.color=a;return h.doc.defaultView.getComputedStyle(i,p).getPropertyValue("color")})}return bm(b)},bn=function(){return"hsb("+[this.h,this.s,this.b]+")"},bo=function(){return"hsl("+[this.h,this.s,this.l]+")"},bp=function(){return this.hex},bq=function(b,c,d){c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b&&(d=b.b,c=b.g,b=b.r);if(c==null&&a.is(b,D)){var e=a.getRGB(b);b=e.r,c=e.g,d=e.b}if(b>1||c>1||d>1)b/=255,c/=255,d/=255;return[b,c,d]},br=function(b,c,d,e){b*=255,c*=255,d*=255;var f={r:b,g:c,b:d,hex:a.rgb(b,c,d),toString:bp};a.is(e,"finite")&&(f.opacity=e);return f};a.color=function(b){var c;a.is(b,"object")&&"h"in b&&"s"in b&&"b"in b?(c=a.hsb2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):a.is(b,"object")&&"h"in b&&"s"in b&&"l"in b?(c=a.hsl2rgb(b),b.r=c.r,b.g=c.g,b.b=c.b,b.hex=c.hex):(a.is(b,"string")&&(b=a.getRGB(b)),a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b?(c=a.rgb2hsl(b),b.h=c.h,b.s=c.s,b.l=c.l,c=a.rgb2hsb(b),b.v=c.b):(b={hex:"none"},b.r=b.g=b.b=b.h=b.s=b.v=b.l=-1)),b.toString=bp;return b},a.hsb2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,a=a.h,d=a.o),a*=360;var e,f,g,h,i;a=a%360/60,i=c*b,h=i*(1-z(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return br(e,f,g,d)},a.hsl2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h);if(a>1||b>1||c>1)a/=360,b/=100,c/=100;a*=360;var e,f,g,h,i;a=a%360/60,i=2*b*(c<.5?c:1-c),h=i*(1-z(a%2-1)),e=f=g=c-i/2,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a];return br(e,f,g,d)},a.rgb2hsb=function(a,b,c){c=bq(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;f=x(a,b,c),g=f-y(a,b,c),d=g==0?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=g==0?0:g/f;return{h:d,s:e,b:f,toString:bn}},a.rgb2hsl=function(a,b,c){c=bq(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;g=x(a,b,c),h=y(a,b,c),i=g-h,d=i==0?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=i==0?0:f<.5?i/(2*f):i/(2-2*f);return{h:d,s:e,l:f,toString:bo}},a._path2string=function(){return this.join(",").replace(X,"$1")};var bu=a._preload=function(a,b){var c=h.doc.createElement("img");c.style.cssText="position:absolute;left:-9999em;top:-9999em",c.onload=function(){b.call(this),this.onload=null,h.doc.body.removeChild(this)},c.onerror=function(){h.doc.body.removeChild(this)},h.doc.body.appendChild(c),c.src=a};a.getRGB=bt(function(b){if(!b||!!((b=r(b)).indexOf("-")+1))return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bv};if(b=="none")return{r:-1,g:-1,b:-1,hex:"none",toString:bv};!W[g](b.toLowerCase().substring(0,2))&&b.charAt()!="#"&&(b=bm(b));var c,d,e,f,h,i,j,k=b.match(L);if(k){k[2]&&(f=R(k[2].substring(5),16),e=R(k[2].substring(3,5),16),d=R(k[2].substring(1,3),16)),k[3]&&(f=R((i=k[3].charAt(3))+i,16),e=R((i=k[3].charAt(2))+i,16),d=R((i=k[3].charAt(1))+i,16)),k[4]&&(j=k[4][s](V),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),k[1].toLowerCase().slice(0,4)=="rgba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100));if(k[5]){j=k[5][s](V),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsba"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsb2rgb(d,e,f,h)}if(k[6]){j=k[6][s](V),d=Q(j[0]),j[0].slice(-1)=="%"&&(d*=2.55),e=Q(j[1]),j[1].slice(-1)=="%"&&(e*=2.55),f=Q(j[2]),j[2].slice(-1)=="%"&&(f*=2.55),(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360),k[1].toLowerCase().slice(0,4)=="hsla"&&(h=Q(j[3])),j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsl2rgb(d,e,f,h)}k={r:d,g:e,b:f,toString:bv},k.hex="#"+(16777216|f|e<<8|d<<16).toString(16).slice(1),a.is(h,"finite")&&(k.opacity=h);return k}return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:bv}},a),a.hsb=bt(function(b,c,d){return a.hsb2rgb(b,c,d).hex}),a.hsl=bt(function(b,c,d){return a.hsl2rgb(b,c,d).hex}),a.rgb=bt(function(a,b,c){return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)}),a.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||.75},c=this.hsb2rgb(b.h,b.s,b.b);b.h+=.075,b.h>1&&(b.h=0,b.s-=.2,b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b}));return c.hex},a.getColor.reset=function(){delete this.start},a.parsePathString=bt(function(b){if(!b)return null;var c={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},d=[];a.is(b,E)&&a.is(b[0],E)&&(d=by(b)),d.length||r(b).replace(Y,function(a,b,e){var f=[],g=b.toLowerCase();e.replace($,function(a,b){b&&f.push(+b)}),g=="m"&&f.length>2&&(d.push([b][n](f.splice(0,2))),g="l",b=b=="m"?"l":"L");if(g=="r")d.push([b][n](f));else while(f.length>=c[g]){d.push([b][n](f.splice(0,c[g])));if(!c[g])break}}),d.toString=a._path2string;return d}),a.parseTransformString=bt(function(b){if(!b)return null;var c={r:3,s:4,t:2,m:6},d=[];a.is(b,E)&&a.is(b[0],E)&&(d=by(b)),d.length||r(b).replace(Z,function(a,b,c){var e=[],f=v.call(b);c.replace($,function(a,b){b&&e.push(+b)}),d.push([b][n](e))}),d.toString=a._path2string;return d}),a.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=A(j,3),l=A(j,2),m=i*i,n=m*i,o=k*a+l*3*i*c+j*3*i*i*e+n*g,p=k*b+l*3*i*d+j*3*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,x=j*e+i*g,y=j*f+i*h,z=90-w.atan2(q-s,r-t)*180/B;(q>s||r1&&(v=w.sqrt(v),c=v*c,d=v*d);var x=c*c,y=d*d,A=(f==g?-1:1)*w.sqrt(z((x*y-x*u*u-y*t*t)/(x*u*u+y*t*t))),C=A*c*u/d+(a+h)/2,D=A*-d*t/c+(b+i)/2,E=w.asin(((b-D)/d).toFixed(9)),F=w.asin(((i-D)/d).toFixed(9));E=aF&&(E=E-B*2),!g&&F>E&&(F=F-B*2)}else E=j[0],F=j[1],C=j[2],D=j[3];var G=F-E;if(z(G)>k){var H=F,I=h,J=i;F=E+k*(g&&F>E?1:-1),h=C+c*w.cos(F),i=D+d*w.sin(F),m=bD(h,i,c,d,e,0,g,I,J,[F,H,C,D])}G=F-E;var K=w.cos(E),L=w.sin(E),M=w.cos(F),N=w.sin(F),O=w.tan(G/4),P=4/3*c*O,Q=4/3*d*O,R=[a,b],S=[a+P*L,b-Q*K],T=[h+P*N,i-Q*M],U=[h,i];S[0]=2*R[0]-S[0],S[1]=2*R[1]-S[1];if(j)return[S,T,U][n](m);m=[S,T,U][n](m).join()[s](",");var V=[];for(var W=0,X=m.length;W"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bE(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bE(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y)),i=f-2*d+b-(h-2*f+d),j=2*(d-b)-2*(f-d),k=b-d,l=(-j+w.sqrt(j*j-4*i*k))/2/i,n=(-j-w.sqrt(j*j-4*i*k))/2/i,z(l)>"1e12"&&(l=.5),z(n)>"1e12"&&(n=.5),l>0&&l<1&&(q=bE(a,b,c,d,e,f,g,h,l),p.push(q.x),o.push(q.y)),n>0&&n<1&&(q=bE(a,b,c,d,e,f,g,h,n),p.push(q.x),o.push(q.y));return{min:{x:y[m](0,p),y:y[m](0,o)},max:{x:x[m](0,p),y:x[m](0,o)}}}),bG=a._path2curve=bt(function(a,b){var c=bA(a),d=b&&bA(b),e={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g=function(a,b){var c,d;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null);switch(a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"][n](bD[m](0,[b.x,b.y][n](a.slice(1))));break;case"S":c=b.x+(b.x-(b.bx||b.x)),d=b.y+(b.y-(b.by||b.y)),a=["C",c,d][n](a.slice(1));break;case"T":b.qx=b.x+(b.x-(b.qx||b.x)),b.qy=b.y+(b.y-(b.qy||b.y)),a=["C"][n](bC(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"][n](bC(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][n](bB(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][n](bB(b.x,b.y,a[1],b.y));break;case"V":a=["C"][n](bB(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][n](bB(b.x,b.y,b.X,b.Y))}return a},h=function(a,b){if(a[b].length>7){a[b].shift();var e=a[b];while(e.length)a.splice(b++,0,["C"][n](e.splice(0,6)));a.splice(b,1),k=x(c.length,d&&d.length||0)}},i=function(a,b,e,f,g){a&&b&&a[g][0]=="M"&&b[g][0]!="M"&&(b.splice(g,0,["M",f.x,f.y]),e.bx=0,e.by=0,e.x=a[g][1],e.y=a[g][2],k=x(c.length,d&&d.length||0))};for(var j=0,k=x(c.length,d&&d.length||0);j=j)return p;o=p}if(j==null)return k},cg=function(b,c){return function(d,e,f){d=bG(d);var g,h,i,j,k="",l={},m,n=0;for(var o=0,p=d.length;oe){if(c&&!l.start){m=cf(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),k+=["C"+m.start.x,m.start.y,m.m.x,m.m.y,m.x,m.y];if(f)return k;l.start=k,k=["M"+m.x,m.y+"C"+m.n.x,m.n.y,m.end.x,m.end.y,i[5],i[6]].join(),n+=j,g=+i[5],h=+i[6];continue}if(!b&&!c){m=cf(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);return{x:m.x,y:m.y,alpha:m.alpha}}}n+=j,g=+i[5],h=+i[6]}k+=i.shift()+i}l.end=k,m=b?n:c?l:a.findDotsAtSegment(g,h,i[0],i[1],i[2],i[3],i[4],i[5],1),m.alpha&&(m={x:m.x,y:m.y,alpha:m.alpha});return m}},ch=cg(1),ci=cg(),cj=cg(0,1);a.getTotalLength=ch,a.getPointAtLength=ci,a.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return cj(a,b).end;var d=cj(a,c,1);return b?cj(d,b).end:d},b$.getTotalLength=function(){if(this.type=="path"){if(this.node.getTotalLength)return this.node.getTotalLength();return ch(this.attrs.path)}},b$.getPointAtLength=function(a){if(this.type=="path")return ci(this.attrs.path,a)},b$.getSubpath=function(b,c){if(this.type=="path")return a.getSubpath(this.attrs.path,b,c)};var ck=a.easing_formulas={linear:function(a){return a},"<":function(a){return A(a,1.7)},">":function(a){return A(a,.48)},"<>":function(a){var b=.48-a/1.04,c=w.sqrt(.1734+b*b),d=c-b,e=A(z(d),1/3)*(d<0?-1:1),f=-c-b,g=A(z(f),1/3)*(f<0?-1:1),h=e+g+.5;return(1-h)*3*h*h+h*h*h},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a=a-1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){if(a==!!a)return a;return A(2,-10*a)*w.sin((a-.075)*2*B/.3)+1},bounce:function(a){var b=7.5625,c=2.75,d;a<1/c?d=b*a*a:a<2/c?(a-=1.5/c,d=b*a*a+.75):a<2.5/c?(a-=2.25/c,d=b*a*a+.9375):(a-=2.625/c,d=b*a*a+.984375);return d}};ck.easeIn=ck["ease-in"]=ck["<"],ck.easeOut=ck["ease-out"]=ck[">"],ck.easeInOut=ck["ease-in-out"]=ck["<>"],ck["back-in"]=ck.backIn,ck["back-out"]=ck.backOut;var cl=[],cm=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){setTimeout(a,16)},cn=function(){var b=+(new Date),c=0;for(;c1&&!d.next){for(s in k)k[g](s)&&(r[s]=d.totalOrigin[s]);d.el.attr(r),cr(d.anim,d.el,d.anim.percents[0],null,d.totalOrigin,d.repeat-1)}d.next&&!d.stop&&cr(d.anim,d.el,d.next,null,d.totalOrigin,d.repeat)}}a.svg&&m&&m.paper&&m.paper.safari(),cl.length&&cm(cn)},co=function(a){return a>255?255:a<0?0:a};b$.animateWith=function(b,c,d,e,f,g){var h=d?a.animation(d,e,f,g):c,i=b.status(c);return this.animate(h).status(h,i*c.ms/h.ms)},b$.onAnimation=function(a){a?eve.on("anim.frame."+this.id,a):eve.unbind("anim.frame."+this.id);return this},cq.prototype.delay=function(a){var b=new cq(this.anim,this.ms);b.times=this.times,b.del=+a||0;return b},cq.prototype.repeat=function(a){var b=new cq(this.anim,this.ms);b.del=this.del,b.times=w.floor(x(a,0))||1;return b},a.animation=function(b,c,d,e){if(b instanceof cq)return b;if(a.is(d,"function")||!d)e=e||d||null,d=null;b=Object(b),c=+c||0;var f={},h,i;for(i in b)b[g](i)&&Q(i)!=i&&Q(i)+"%"!=i&&(h=!0,f[i]=b[i]);if(!h)return new cq(b,c);d&&(f.easing=d),e&&(f.callback=e);return new cq({100:f},c)},b$.animate=function(b,c,d,e){var f=this;if(f.removed){e&&e.call(f);return f}var g=b instanceof cq?b:a.animation(b,c,d,e);cr(g,f,g.percents[0],null,f.attr());return f},b$.setTime=function(a,b){a&&b!=null&&this.status(a,y(b,a.ms)/a.ms);return this},b$.status=function(a,b){var c=[],d=0,e,f;if(b!=null){cr(a,this,-1,y(b,1));return this}e=cl.length;for(;d.5)*2-1;i(m-.5,2)+i(n-.5,2)>.25&&(n=f.sqrt(.25-i(m-.5,2))*e+.5)&&n!=.5&&(n=n.toFixed(5)-1e-5*e)}return l}),e=e.split(/\s*\-\s*/);if(j=="linear"){var t=e.shift();t=-d(t);if(isNaN(t))return null;var u=[0,0,f.cos(a.rad(t)),f.sin(a.rad(t))],v=1/(g(h(u[2]),h(u[3]))||1);u[2]*=v,u[3]*=v,u[2]<0&&(u[0]=-u[2],u[2]=0),u[3]<0&&(u[1]=-u[3],u[3]=0)}var w=a._parseDots(e);if(!w)return null;k=k.replace(/[\(\)\s,\xb0#]/g,"_"),b.gradient&&k!=b.gradient.id&&(p.defs.removeChild(b.gradient),delete b.gradient);if(!b.gradient){s=q(j+"Gradient",{id:k}),b.gradient=s,q(s,j=="radial"?{fx:m,fy:n}:{x1:u[0],y1:u[1],x2:u[2],y2:u[3],gradientTransform:b.matrix.invert()}),p.defs.appendChild(s);for(var x=0,y=w.length;x1?G.opacity/100:G.opacity});case"stroke":G=a.getRGB(p),i.setAttribute(o,G.hex),o=="stroke"&&G[b]("opacity")&&q(i,{"stroke-opacity":G.opacity>1?G.opacity/100:G.opacity}),o=="stroke"&&d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"gradient":(d.type=="circle"||d.type=="ellipse"||c(p).charAt()!="r")&&r(d,p);break;case"opacity":k.gradient&&!k[b]("stroke-opacity")&&q(i,{"stroke-opacity":p>1?p/100:p});case"fill-opacity":if(k.gradient){H=a._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l)),H&&(I=H.getElementsByTagName("stop"),q(I[I.length-1],{"stop-opacity":p}));break};default:o=="font-size"&&(p=e(p,10)+"px");var J=o.replace(/(\-.)/g,function(a){return a.substring(1).toUpperCase()});i.style[J]=p,d._.dirty=1,i.setAttribute(o,p)}}y(d,f),i.style.visibility=m},x=1.2,y=function(d,f){if(d.type=="text"&&!!(f[b]("text")||f[b]("font")||f[b]("font-size")||f[b]("x")||f[b]("y"))){var g=d.attrs,h=d.node,i=h.firstChild?e(a._g.doc.defaultView.getComputedStyle(h.firstChild,l).getPropertyValue("font-size"),10):10;if(f[b]("text")){g.text=f.text;while(h.firstChild)h.removeChild(h.firstChild);var j=c(f.text).split("\n"),k=[],m;for(var n=0,o=j.length;n"));var $=X.getBoundingClientRect();t.W=m.w=($.right-$.left)/Y,t.H=m.h=($.bottom-$.top)/Y,t.X=m.x,t.Y=m.y+t.H/2,("x"in i||"y"in i)&&(t.path.v=a.format("m{0},{1}l{2},{1}",f(m.x*u),f(m.y*u),f(m.x*u)+1));var _=["x","y","text","font","font-family","font-weight","font-style","font-size"];for(var ba=0,bb=_.length;ba.25&&(c=e.sqrt(.25-i(b-.5,2))*((c>.5)*2-1)+.5),m=b+n+c);return o}),f=f.split(/\s*\-\s*/);if(l=="linear"){var p=f.shift();p=-d(p);if(isNaN(p))return null}var q=a._parseDots(f);if(!q)return null;b=b.shape||b.node;if(q.length){b.removeChild(g),g.on=!0,g.method="none",g.color=q[0].color,g.color2=q[q.length-1].color;var r=[];for(var s=0,t=q.length;s')}}catch(c){F=function(a){return b.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},a._engine.initWin(a._g.win),a._engine.create=function(){var b=a._getContainer.apply(0,arguments),c=b.container,d=b.height,e,f=b.width,g=b.x,h=b.y;if(!c)throw new Error("VML container not found.");var i=new a._Paper,j=i.canvas=a._g.doc.createElement("div"),k=j.style;g=g||0,h=h||0,f=f||512,d=d||342,i.width=f,i.height=d,f==+f&&(f+="px"),d==+d&&(d+="px"),i.coordsize=u*1e3+n+u*1e3,i.coordorigin="0 0",i.span=a._g.doc.createElement("span"),i.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",j.appendChild(i.span),k.cssText=a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",f,d),c==1?(a._g.doc.body.appendChild(j),k.left=g+"px",k.top=h+"px",k.position="absolute"):c.firstChild?c.insertBefore(j,c.firstChild):c.appendChild(j),i.renderfix=function(){};return i},a.prototype.clear=function(){a.eve("clear",this),this.canvas.innerHTML=o,this.span=a._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},a.prototype.remove=function(){a.eve("remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var b in this)this[b]=typeof this[b]=="function"?a._removedFactory(b):null;return!0};var G=a.st;for(var H in E)E[b](H)&&!G[b](H)&&(G[H]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(H))}(window.Raphael) \ No newline at end of file diff --git a/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js b/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js index f04456bf2a..fbbf411be8 100644 --- a/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js +++ b/webroot/rsrc/js/application/dashboard/behavior-dashboard-move-panels.js @@ -85,7 +85,8 @@ JX.behavior('dashboard-move-panels', function(config) { for (ii = 0; ii < cols.length; ii++) { col = cols[ii]; var list = new JX.DraggableList(itemSigil, col) - .setFindItemsHandler(JX.bind(null, finditems, col)); + .setFindItemsHandler(JX.bind(null, finditems, col)) + .setCanDragX(true); list.listen('didSend', JX.bind(list, onupdate, col)); list.listen('didReceive', JX.bind(list, onupdate, col)); diff --git a/webroot/rsrc/js/application/maniphest/behavior-line-chart.js b/webroot/rsrc/js/application/maniphest/behavior-line-chart.js index 6e66b19d2c..2f63657c56 100644 --- a/webroot/rsrc/js/application/maniphest/behavior-line-chart.js +++ b/webroot/rsrc/js/application/maniphest/behavior-line-chart.js @@ -3,86 +3,121 @@ * @requires javelin-behavior * javelin-dom * javelin-vector + * phui-chart-css */ JX.behavior('line-chart', function(config) { + function fn(n) { + return n + '(' + JX.$A(arguments).slice(1).join(', ') + ')'; + } + var h = JX.$(config.hardpoint); - var p = JX.$V(h); var d = JX.Vector.getDim(h); - var mx = 60; - var my = 30; - var r = new Raphael(h, d.x, d.y); + var padding = { + top: 24, + left: 48, + bottom: 48, + right: 32 + }; - var l = r.linechart( - mx, my, - d.x - (2 * mx), d.y - (2 * my), - config.x, - config.y, - { - nostroke: false, - axis: '0 0 1 1', - shade: true, - gutter: 1, - colors: config.colors || ['#2980b9'] + var size = { + frameWidth: d.x, + frameHeight: d.y, + }; + + size.width = size.frameWidth - padding.left - padding.right; + size.height = size.frameHeight - padding.top - padding.bottom; + + var x = d3.time.scale() + .range([0, size.width]); + + var y = d3.scale.linear() + .range([size.height, 0]); + + var xAxis = d3.svg.axis() + .scale(x) + .orient('bottom'); + + var yAxis = d3.svg.axis() + .scale(y) + .orient('left'); + + var svg = d3.select('#' + config.hardpoint).append('svg') + .attr('width', size.frameWidth) + .attr('height', size.frameHeight) + .attr('class', 'chart'); + + var g = svg.append('g') + .attr('transform', fn('translate', padding.left, padding.top)); + + g.append('rect') + .attr('class', 'inner') + .attr('width', size.width) + .attr('height', size.height); + + var line = d3.svg.line() + .x(function(d) { return x(d.date); }) + .y(function(d) { return y(d.count); }); + + var data = []; + for (var ii = 0; ii < config.x[0].length; ii++) { + data.push( + { + date: new Date(config.x[0][ii] * 1000), + count: +config.y[0][ii] + }); + } + + x.domain(d3.extent(data, function(d) { return d.date; })); + + var yex = d3.extent(data, function(d) { return d.count; }); + yex[0] = 0; + yex[1] = yex[1] * 1.05; + y.domain(yex); + + g.append('path') + .datum(data) + .attr('class', 'line') + .attr('d', line); + + g.append('g') + .attr('class', 'x axis') + .attr('transform', fn('translate', 0, size.height)) + .call(xAxis); + + g.append('g') + .attr('class', 'y axis') + .attr('transform', fn('translate', 0, 0)) + .call(yAxis); + + var div = d3.select('body') + .append('div') + .attr('class', 'chart-tooltip') + .style('opacity', 0); + + g.selectAll('dot') + .data(data) + .enter() + .append('circle') + .attr('class', 'point') + .attr('r', 3) + .attr('cx', function(d) { return x(d.date); }) + .attr('cy', function(d) { return y(d.count); }) + .on('mouseover', function(d) { + var d_y = d.date.getFullYear(); + var d_m = d.date.getMonth(); + var d_d = d.date.getDate(); + + div + .html(d_y + '-' + d_m + '-' + d_d + ': ' + d.count) + .style('opacity', 0.9) + .style('left', (d3.event.pageX - 60) + 'px') + .style('top', (d3.event.pageY - 38) + 'px'); + }) + .on('mouseout', function() { + div.style('opacity', 0); }); - function format(value, type) { - switch (type) { - case 'epoch': - return new Date(parseInt(value, 10) * 1000).toLocaleDateString(); - case 'int': - return parseInt(value, 10); - default: - return value; - } - } - - // Format the X axis. - - var n = 2; - var ii = 0; - var text = l.axis[0].text.items; - for (var k in text) { - if (ii++ % n) { - text[k].attr({text: ''}); - } else { - var cur = text[k].attr('text'); - var str = format(cur, config.xformat); - text[k].attr({text: str}); - } - } - - // Show values on hover. - - l.hoverColumn(function() { - this.tags = r.set(); - for (var yy = 0; yy < config.y.length; yy++) { - var yvalue = 0; - for (var ii = 0; ii < config.x[0].length; ii++) { - if (config.x[0][ii] > this.axis) { - break; - } - yvalue = format(config.y[yy][ii], config.yformat); - } - - var xvalue = format(this.axis, config.xformat); - - var tag = r.tag( - this.x, - this.y[yy], - [xvalue, yvalue].join('\n'), - 180, - 24); - tag - .insertBefore(this) - .attr([{fill : '#fff'}, {fill: '#000'}]); - - this.tags.push(tag); - } - }, function() { - this.tags && this.tags.remove(); - }); - }); diff --git a/webroot/rsrc/js/application/policy/behavior-policy-control.js b/webroot/rsrc/js/application/policy/behavior-policy-control.js index 9696a09dc8..044b909352 100644 --- a/webroot/rsrc/js/application/policy/behavior-policy-control.js +++ b/webroot/rsrc/js/application/policy/behavior-policy-control.js @@ -7,6 +7,7 @@ * phuix-action-list-view * phuix-action-view * javelin-workflow + * phuix-icon-view * @javelin */ JX.behavior('policy-control', function(config) { @@ -57,6 +58,21 @@ JX.behavior('policy-control', function(config) { .start(); }, phid); + } else if (phid == config.projectKey) { + onselect = JX.bind(null, function(phid) { + var uri = get_custom_uri(phid, config.capability); + + new JX.Workflow(uri) + .setHandler(function(response) { + if (!response.phid) { + return; + } + + add_policy(phid, response.phid, response.info); + select_policy(response.phid); + }) + .start(); + }, phid); } else { onselect = JX.bind(null, select_policy, phid); } @@ -101,20 +117,22 @@ JX.behavior('policy-control', function(config) { name = JX.$N('span', {title: option.full}, name); } - return [JX.$H(config.icons[option.icon]), name]; + return [render_icon(option.icon), name]; }; + var render_icon = function(icon) { + return new JX.PHUIXIconView() + .setIcon(icon) + .getNode(); + }; /** * Get the workflow URI to create or edit a policy with a given PHID. */ var get_custom_uri = function(phid, capability) { - var uri = config.editURI; - if (phid != config.customPlaceholder) { - uri += phid + '/'; - } - uri += '?capability=' + capability; - return uri; + return JX.$U(config.editURI + phid + '/') + .setQueryParam('capability', capability) + .toString(); }; @@ -123,16 +141,28 @@ JX.behavior('policy-control', function(config) { * policies after the user edits them. */ var replace_policy = function(old_phid, new_phid, info) { + return add_policy(old_phid, new_phid, info, true); + }; + + + /** + * Add a new policy above an existing one, optionally replacing it. + */ + var add_policy = function(old_phid, new_phid, info, replace) { + if (config.options[new_phid]) { + return; + } + config.options[new_phid] = info; + for (var k in config.order) { for (var ii = 0; ii < config.order[k].length; ii++) { if (config.order[k][ii] == old_phid) { - config.order[k][ii] = new_phid; + config.order[k].splice(ii, (replace ? 1 : 0), new_phid); return; } } } }; - }); diff --git a/webroot/rsrc/js/application/projects/behavior-project-boards.js b/webroot/rsrc/js/application/projects/behavior-project-boards.js index 04e99fde28..8d9b61cc8c 100644 --- a/webroot/rsrc/js/application/projects/behavior-project-boards.js +++ b/webroot/rsrc/js/application/projects/behavior-project-boards.js @@ -231,7 +231,9 @@ JX.behavior('project-boards', function(config, statics) { 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])) + .setOuterContainer(JX.$(config.boardID)) + .setCanDragX(true); list.listen('didSend', JX.bind(list, onupdate, cols[ii])); list.listen('didReceive', JX.bind(list, onupdate, cols[ii])); @@ -278,13 +280,16 @@ JX.behavior('project-boards', function(config, statics) { // close the dropdown, but don't want to follow the link. e.prevent(); - var column_phid = e.getNodeData('column-add-task').columnPHID; + var column_data = e.getNodeData('column-add-task'); + var column_phid = column_data.columnPHID; + var request_data = { responseType: 'card', columnPHID: column_phid, - projects: statics.projectPHID, + projects: column_data.projectPHID, order: statics.order }; + var cols = getcolumns(); var ii; var column; diff --git a/webroot/rsrc/js/core/DraggableList.js b/webroot/rsrc/js/core/DraggableList.js index 1f8f50c7aa..efe137e7ab 100644 --- a/webroot/rsrc/js/core/DraggableList.js +++ b/webroot/rsrc/js/core/DraggableList.js @@ -23,6 +23,7 @@ JX.install('DraggableList', { JX.Stratcom.listen('mousemove', null, JX.bind(this, this._onmove)); JX.Stratcom.listen('scroll', null, JX.bind(this, this._onmove)); JX.Stratcom.listen('mouseup', null, JX.bind(this, this._ondrop)); + JX.Stratcom.listen('keypress', null, JX.bind(this, this._onkey)); }, events : [ @@ -37,23 +38,29 @@ JX.install('DraggableList', { 'didReceive'], properties : { - findItemsHandler : null + findItemsHandler: null, + canDragX: false, + outerContainer: null }, members : { _root : null, _dragging : null, _locked : 0, - _origin : null, - _originScroll : null, _target : null, _targets : null, - _dimensions : null, _ghostHandler : null, _ghostNode : null, _group : null, - _lastMousePosition: null, - _lastAdjust: null, + _cursorPosition: null, + _cursorOrigin: null, + _cursorScroll: null, + _frame: null, + _clone: null, + _offset: null, + _autoscroll: null, + _autoscroller: null, + _autotimer: null, getRootNode : function() { return this._root; @@ -98,10 +105,6 @@ JX.install('DraggableList', { return this; }, - _canDragX : function() { - return this._hasGroup(); - }, - _hasGroup : function() { return (this._group.length > 1); }, @@ -132,7 +135,17 @@ JX.install('DraggableList', { } } - return handler(); + var items = handler(); + + // Make sure the clone element is never included as a target. + for (var ii = 0; ii < items.length; ii++) { + if (items[ii] === this._clone) { + items.splice(ii, 1); + break; + } + } + + return items; }, _ondrag : function(e) { @@ -168,25 +181,73 @@ JX.install('DraggableList', { e.kill(); - this._dragging = e.getNode(this._sigil); - this._origin = JX.$V(e); - this._originScroll = JX.Vector.getAggregateScrollForNode(this._dragging); - this._dimensions = JX.$V(this._dragging); + var drag = e.getNode(this._sigil); + + this._autoscroll = {}; + this._autoscroller = setInterval(JX.bind(this, this._onautoscroll), 10); + this._autotimer = null; for (var ii = 0; ii < this._group.length; ii++) { this._group[ii]._clearTarget(); } - if (!this.invoke('didBeginDrag', this._dragging).getPrevented()) { - // Set the height of all the ghosts in the group. In the normal case, - // this just sets this list's ghost height. - for (var jj = 0; jj < this._group.length; jj++) { - var ghost = this._group[jj].getGhostNode(); - ghost.style.height = JX.Vector.getDim(this._dragging).y + 'px'; - } + var pos = JX.$V(drag); + var dim = JX.Vector.getDim(drag); - JX.DOM.alterClass(this._dragging, 'drag-dragging', true); + // Create and adjust the ghost nodes. + for (var jj = 0; jj < this._group.length; jj++) { + var ghost = this._group[jj].getGhostNode(); + ghost.style.height = dim.y + 'px'; } + + // Here's what's going on: we're cloning the thing that's being dragged. + // This is the "clone", stored in "this._clone". We're going to leave the + // original where it is in the document, and put the clone at top-level + // so it can be freely dragged around the whole document, even if it's + // inside a container with overflow hidden. + + // Because the clone has been moved up, CSS classes which rely on some + // parent selector won't work. Draggable objects need to pick up all of + // their CSS properties without relying on container classes. This isn't + // great, but leaving them where they are in the document creates a large + // number of positioning problems with scrollable, absolute, relative, + // or overflow hidden containers. + + // Note that we don't actually want to let the user drag it outside the + // document. One problem is that doing so lets the user drag objects + // infinitely far to the right by dragging them to the edge so the + // document extends, scrolling the document, dragging them to the edge + // of the new larger document, scrolling the document, and so on forever. + + // To prevent this, we're putting a "frame" (stored in "this._frame") at + // top level, then putting the clone inside the frame. The frame has the + // same size as the entire viewport, and overflow hidden, so dragging the + // item outside the document just cuts it off. + + // Create the clone for dragging. + var clone = drag.cloneNode(true); + + pos.setPos(clone); + dim.setDim(clone); + + JX.DOM.alterClass(drag, 'drag-dragging', true); + JX.DOM.alterClass(clone, 'drag-clone', true); + + var frame = JX.$N('div', {className: 'drag-frame'}); + frame.appendChild(clone); + + document.body.appendChild(frame); + + this._dragging = drag; + this._clone = clone; + this._frame = frame; + + var cursor = JX.$V(e); + this._offset = new JX.Vector(pos.x - cursor.x, pos.y - cursor.y); + + JX.Tooltip.lock(); + + this.invoke('didBeginDrag', this._dragging); }, _getTargets : function() { @@ -197,18 +258,6 @@ JX.install('DraggableList', { var item = items[ii]; var ipos = JX.$V(item); - if (item == this._dragging) { - // If the item we're measuring is also the item we're dragging, - // we need to measure its position as though it was still in the - // list, not its current position in the document (which is - // under the cursor). To do this, adjust the measured position by - // removing the offsets we added to put the item underneath the - // cursor. - if (this._lastAdjust) { - ipos.x -= this._lastAdjust.x; - ipos.y -= this._lastAdjust.y; - } - } targets.push({ item: items[ii], @@ -290,6 +339,11 @@ JX.install('DraggableList', { } this._target = false; + + // Clear the target position cache, since adding or removing ghosts + // changes element positions. + this._dirtyTargetCache(); + return this; }, @@ -298,9 +352,6 @@ JX.install('DraggableList', { var targets = this._getTargets(); var dragging = this._dragging; - var adjust_h = JX.Vector.getDim(ghost).y; - var adjust_y = JX.$V(ghost).y; - // Find the node we're dragging the object underneath. This is the first // node in the list that's above the cursor. If that node is the node // we're dragging or its predecessor, don't select a target, because the @@ -314,34 +365,23 @@ JX.install('DraggableList', { var cur_target = null; var trigger; for (var ii = 0; ii < targets.length; ii++) { - - // If the drop target indicator is above the target, we need to adjust - // the target's trigger height down accordingly. This makes dragging - // items down the list smoother, because the target doesn't jump to the - // next item while the cursor is over it. - trigger = targets[ii].y; - if (adjust_y <= trigger) { - trigger += adjust_h; - } // If the cursor is above this target, we aren't dropping underneath it. - if (trigger >= p.y) { continue; } // Don't choose the dragged row or its predecessor as targets. - cur_target = targets[ii].item; if (!dragging) { // If the item on the cursor isn't from this list, it can't be // dropped onto itself or its predecessor in this list. } else { - if (cur_target == dragging) { + if (cur_target === dragging) { cur_target = false; } - if (targets[ii - 1] && targets[ii - 1].item == dragging) { + if (targets[ii - 1] && (targets[ii - 1].item === dragging)) { cur_target = false; } } @@ -368,14 +408,16 @@ JX.install('DraggableList', { // reuse the known position. if (e.getType() == 'mousemove') { - this._lastMousePosition = JX.$V(e); + this._cursorPosition = JX.$V(e); + this._cursorOrigin = JX.$V(e); + this._cursorScroll = JX.Vector.getScroll(); } if (!this._dragging) { return; } - if (!this._lastMousePosition) { + if (!this._cursorPosition) { return; } @@ -383,9 +425,17 @@ JX.install('DraggableList', { // If this is a scroll event, the positions of drag targets may have // changed. this._dirtyTargetCache(); + + // Correct the cursor position to account for scrolling. + var s = JX.Vector.getScroll(); + this._cursorPosition = new JX.$V( + this._cursorOrigin.x - (this._cursorScroll.x - s.x), + this._cursorOrigin.y - (this._cursorScroll.y - s.y)); } - var p = JX.$V(this._lastMousePosition.x, this._lastMousePosition.y); + this._updateAutoscroll(this._cursorPosition); + + var p = JX.$V(this._cursorPosition.x, this._cursorPosition.y); var group = this._group; var target_list = this._getTargetList(p); @@ -409,86 +459,203 @@ JX.install('DraggableList', { } } - // If the drop target indicator is above the cursor in the document, - // adjust the cursor position for the change in node document position. - // Do this before choosing a new target to avoid a flash of nonsense. + var f = JX.$V(this._frame); + p.x -= f.x; + p.y -= f.y; - var scroll = JX.Vector.getAggregateScrollForNode(this._dragging); + p.y += this._offset.y; + this._clone.style.top = p.y + 'px'; - var origin = { - x: this._origin.x + (this._originScroll.x - scroll.x), - y: this._origin.y + (this._originScroll.y - scroll.y) - }; - - var adjust_h = 0; - var adjust_y = 0; - if (this._target !== false) { - var ghost = this.getGhostNode(); - adjust_h = JX.Vector.getDim(ghost).y; - adjust_y = JX.$V(ghost).y; - - if (adjust_y <= origin.y) { - p.y -= adjust_h; - } + if (this.getCanDragX()) { + p.x += this._offset.x; + this._clone.style.left = p.x + 'px'; } - if (this._canDragX()) { - p.x -= origin.x; - } else { - p.x = 0; - } - - p.y -= origin.y; - this._lastAdjust = new JX.Vector(p.x, p.y); - p.setPos(this._dragging); - e.kill(); }, + _updateAutoscroll: function(p) { + var container = this._dragging.parentNode; + var autoscroll = {}; + + var outer = this.getOuterContainer(); + + var cpos; + var cdim; + + while (container) { + if (outer && (container == outer)) { + break; + } + + try { + cpos = JX.Vector.getPos(container); + cdim = JX.Vector.getDim(container); + if (container == document.body) { + cdim = JX.Vector.getViewport(); + cpos.x += container.scrollLeft; + cpos.y += container.scrollTop; + } + } catch (ignored) { + break; + } + + var fuzz = 64; + + if (p.y <= cpos.y + fuzz) { + autoscroll.up = container; + } + + if (p.y >= cpos.y + cdim.y - fuzz) { + autoscroll.down = container; + } + + if (p.x <= cpos.x + fuzz) { + autoscroll.left = container; + } + + if (p.x >= cpos.x + cdim.x - fuzz) { + autoscroll.right = container; + } + + if (container == document.body) { + break; + } + + container = container.parentNode; + } + + this._autoscroll = autoscroll; + }, + + _onkey: function(e) { + // Cancel any current drag if the user presses escape. + if (this._dragging && (e.getSpecialKey() == 'esc')) { + e.kill(); + this._drop(null); + return; + } + }, + _ondrop : function(e) { + if (this._dragging) { + e.kill(); + } + + var p = JX.$V(e); + this._drop(p); + }, + + _drop: function(cursor) { if (!this._dragging) { return; } - var p = JX.$V(e); - var dragging = this._dragging; this._dragging = null; + clearInterval(this._autoscroller); + this._autoscroller = null; + + JX.DOM.remove(this._frame); + this._frame = null; + this._clone = null; var target = false; var ghost = false; - var target_list = this._getTargetList(p); - if (target_list) { - target = target_list._target; - ghost = target_list.getGhostNode(); + if (cursor) { + var target_list = this._getTargetList(cursor); + if (target_list) { + target = target_list._target; + ghost = target_list.getGhostNode(); + } } JX.$V(0, 0).setPos(dragging); - if (target !== false) { + if (target === false) { + this.invoke('didCancelDrag', dragging); + } else { JX.DOM.remove(dragging); JX.DOM.replace(ghost, dragging); this.invoke('didSend', dragging, target_list); target_list.invoke('didReceive', dragging, this); target_list.invoke('didDrop', dragging, target, this); - } else { - this.invoke('didCancelDrag', dragging); } var group = this._group; for (var ii = 0; ii < group.length; ii++) { JX.DOM.alterClass(group[ii].getRootNode(), 'drag-target-list', false); group[ii]._clearTarget(); - group[ii]._dirtyTargetCache(); - group[ii]._lastAdjust = null; } - if (!this.invoke('didEndDrag', dragging).getPrevented()) { - JX.DOM.alterClass(dragging, 'drag-dragging', false); + JX.DOM.alterClass(dragging, 'drag-dragging', false); + JX.Tooltip.unlock(); + + this.invoke('didEndDrag', dragging); + }, + + _onautoscroll: function() { + var u = this._autoscroll.up; + var d = this._autoscroll.down; + var l = this._autoscroll.left; + var r = this._autoscroll.right; + + var now = +new Date(); + + if (!this._autotimer) { + this._autotimer = now; + return; } - e.kill(); + var delta = now - this._autotimer; + this._autotimer = now; + + var amount = 12 * (delta / 10); + + if (u && (u != d)) { + this._tryScroll(this._dragging, u, 'scrollTop', amount); + } + + if (d && (d != u)) { + this._tryScroll(this._dragging, d, 'scrollTop', -amount); + } + + if (l && (l != r)) { + this._tryScroll(this._dragging, l, 'scrollLeft', amount); + } + + if (r && (r != l)) { + this._tryScroll(this._dragging, r, 'scrollLeft', -amount); + } + }, + + /** + * Walk up the tree from a node to some parent, trying to scroll every + * container. Stop when we find a container which we're able to scroll. + */ + _tryScroll: function(from, to, property, amount) { + var value; + + var container = from.parentNode; + while (container) { + // Read the current scroll value. + value = container[property]; + + // Try to scroll. + container[property] -= amount; + + // If we scrolled it, we're all done. + if (container[property] != value) { + break; + } + + if (container == to) { + break; + } + + container = container.parentNode; + } }, lock : function() { diff --git a/webroot/rsrc/js/core/Hovercard.js b/webroot/rsrc/js/core/Hovercard.js index 26774d4f25..65e1455252 100644 --- a/webroot/rsrc/js/core/Hovercard.js +++ b/webroot/rsrc/js/core/Hovercard.js @@ -4,7 +4,7 @@ * javelin-vector * javelin-request * javelin-uri - * @provides phabricator-hovercard + * @provides phui-hovercard * @javelin */ diff --git a/webroot/rsrc/js/core/ToolTip.js b/webroot/rsrc/js/core/ToolTip.js index add181c7e5..90fff2bcbe 100644 --- a/webroot/rsrc/js/core/ToolTip.js +++ b/webroot/rsrc/js/core/ToolTip.js @@ -9,10 +9,17 @@ JX.install('Tooltip', { - statics : { - _node : null, + statics: { + _node: null, + _lock: 0, show : function(root, scale, align, content) { + var self = JX.Tooltip; + + if (self._lock) { + return; + } + if (__DEV__) { switch (align) { case 'N': @@ -45,27 +52,28 @@ JX.install('Tooltip', { node.style.maxWidth = scale + 'px'; JX.Tooltip.hide(); - this._node = node; + self._node = node; // Append the tip to the document, but offscreen, so we can measure it. node.style.left = '-10000px'; document.body.appendChild(node); // Jump through some hoops trying to auto-position the tooltip - var pos = this._getSmartPosition(align, root, node); + var pos = self._getSmartPosition(align, root, node); pos.setPos(node); }, _getSmartPosition: function (align, root, node) { - var pos = JX.Tooltip._proposePosition(align, root, node); + var self = JX.Tooltip; + var pos = self._proposePosition(align, root, node); // If toolip is offscreen, try to be clever if (!JX.Tooltip.isOnScreen(pos, node)) { - align = JX.Tooltip._getImprovedOrientation(pos, node); - pos = JX.Tooltip._proposePosition(align, root, node); + align = self._getImprovedOrientation(pos, node); + pos = self._proposePosition(align, root, node); } - JX.Tooltip._setAnchor(align); + self._setAnchor(align); return pos; }, @@ -167,6 +175,17 @@ JX.install('Tooltip', { JX.DOM.remove(this._node); this._node = null; } + }, + + lock: function() { + var self = JX.Tooltip; + self.hide(); + self._lock++; + }, + + unlock: function() { + var self = JX.Tooltip; + self._lock--; } } }); diff --git a/webroot/rsrc/js/core/behavior-hovercard.js b/webroot/rsrc/js/core/behavior-hovercard.js index 26e62ad02a..8a1c610752 100644 --- a/webroot/rsrc/js/core/behavior-hovercard.js +++ b/webroot/rsrc/js/core/behavior-hovercard.js @@ -1,14 +1,14 @@ /** - * @provides javelin-behavior-phabricator-hovercards + * @provides javelin-behavior-phui-hovercards * @requires javelin-behavior * javelin-behavior-device * javelin-stratcom * javelin-vector - * phabricator-hovercard + * phui-hovercard * @javelin */ -JX.behavior('phabricator-hovercards', function() { +JX.behavior('phui-hovercards', function() { // We listen for mousemove instead of mouseover to handle the case when user // scrolls with keyboard. We don't want to display hovercard if node gets diff --git a/webroot/rsrc/js/core/behavior-search-typeahead.js b/webroot/rsrc/js/core/behavior-search-typeahead.js index f58cb813d0..a981f3861e 100644 --- a/webroot/rsrc/js/core/behavior-search-typeahead.js +++ b/webroot/rsrc/js/core/behavior-search-typeahead.js @@ -8,6 +8,7 @@ * javelin-util * javelin-stratcom * phabricator-prefab + * phuix-icon-view */ JX.behavior('phabricator-search-typeahead', function(config) { @@ -25,12 +26,22 @@ JX.behavior('phabricator-search-typeahead', function(config) { attr.style = {backgroundImage: 'url('+object.imageURI+')'}; } + var icon = null; + if (object.icon) { + icon = new JX.PHUIXIconView() + .setIcon(object.icon) + .setColor('lightgreytext') + .getNode(); + icon = [icon, ' ']; + } + var render = JX.$N( 'span', attr, [ JX.$N('span', {className: object.sprite}), JX.$N('span', {className: 'result-name'}, object.displayName), + icon, JX.$N('span', {className: 'result-type'}, object.type) ]);