@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Improve live Harbormaster log follow behaviors

Summary:
Depends on D19166. Ref T13088. When the user scrolls away from a followed log, break the focus lock.

Let users stop following a live log.

Show when lines are added more clearly.

Don't refresh quite as quickly give users a better shot at clicking the stop button.

These behaviors can probably be refined but are at least more plausible and less actively user-hostile than the first version of this behavior was.

Test Plan: Used `write-log --rate` to write a large log slowly. Clicked "Follow Log", followed for a bit. Scrolled away, still got live updates but no more scroll lock. Clicked stop, no more updates.

Subscribers: PHID-OPKG-gm6ozazyms6q6i22gyam

Maniphest Tasks: T13088

Differential Revision: https://secure.phabricator.com/D19167

+93 -18
+7 -7
resources/celerity/map.php
··· 78 78 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 79 79 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', 80 80 'rsrc/css/application/flag/flag.css' => 'bba8f811', 81 - 'rsrc/css/application/harbormaster/harbormaster.css' => 'cd73d427', 81 + 'rsrc/css/application/harbormaster/harbormaster.css' => '730a4a3c', 82 82 'rsrc/css/application/herald/herald-test.css' => 'a52e323e', 83 83 'rsrc/css/application/herald/herald.css' => 'cd8d0134', 84 84 'rsrc/css/application/maniphest/report.css' => '9b9580b7', ··· 416 416 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef', 417 417 'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab', 418 418 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 419 - 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => 'ab1173d1', 419 + 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909', 420 420 'rsrc/js/application/herald/HeraldRuleEditor.js' => 'dca75c0e', 421 421 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 422 422 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', ··· 579 579 'font-fontawesome' => 'e838e088', 580 580 'font-lato' => 'c7ccd872', 581 581 'global-drag-and-drop-css' => 'b556a948', 582 - 'harbormaster-css' => 'cd73d427', 582 + 'harbormaster-css' => '730a4a3c', 583 583 'herald-css' => 'cd8d0134', 584 584 'herald-rule-editor' => 'dca75c0e', 585 585 'herald-test-css' => 'a52e323e', ··· 636 636 'javelin-behavior-event-all-day' => 'b41537c9', 637 637 'javelin-behavior-fancy-datepicker' => 'ecf4e799', 638 638 'javelin-behavior-global-drag-and-drop' => '960f6a39', 639 - 'javelin-behavior-harbormaster-log' => 'ab1173d1', 639 + 'javelin-behavior-harbormaster-log' => '191b4909', 640 640 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 641 641 'javelin-behavior-high-security-warning' => 'a464fe03', 642 642 'javelin-behavior-history-install' => '7ee2b591', ··· 1021 1021 ), 1022 1022 '185bbd53' => array( 1023 1023 'javelin-install', 1024 + ), 1025 + '191b4909' => array( 1026 + 'javelin-behavior', 1024 1027 ), 1025 1028 '1ad0a787' => array( 1026 1029 'javelin-install', ··· 1764 1767 'javelin-dom', 1765 1768 'javelin-util', 1766 1769 'phabricator-prefab', 1767 - ), 1768 - 'ab1173d1' => array( 1769 - 'javelin-behavior', 1770 1770 ), 1771 1771 'ab2f381b' => array( 1772 1772 'javelin-request',
+23 -3
src/applications/harbormaster/controller/HarbormasterBuildLogRenderController.php
··· 720 720 721 721 private function renderLiveRow($log_size) { 722 722 $icon_down = id(new PHUIIconView()) 723 - ->setIcon('fa-chevron-down'); 723 + ->setIcon('fa-angle-double-down'); 724 + 725 + $icon_pause = id(new PHUIIconView()) 726 + ->setIcon('fa-pause'); 724 727 725 728 $follow = javelin_tag( 726 729 'a', 727 730 array( 728 731 'sigil' => 'harbormaster-log-expand harbormaster-log-live', 732 + 'class' => 'harbormaster-log-follow-start', 729 733 'meta' => array( 730 734 'headOffset' => $log_size, 731 735 'head' => 0, ··· 737 741 $icon_down, 738 742 ' ', 739 743 pht('Follow Log'), 744 + )); 745 + 746 + $stop_following = javelin_tag( 747 + 'a', 748 + array( 749 + 'sigil' => 'harbormaster-log-expand', 750 + 'class' => 'harbormaster-log-follow-stop', 751 + 'meta' => array( 752 + 'stop' => true, 753 + ), 754 + ), 755 + array( 756 + $icon_pause, 740 757 ' ', 741 - $icon_down, 758 + pht('Stop Following Log'), 742 759 )); 743 760 744 761 $expand_cells = array( ··· 747 764 array( 748 765 'class' => 'harbormaster-log-follow', 749 766 ), 750 - $follow), 767 + array( 768 + $follow, 769 + $stop_following, 770 + )), 751 771 ); 752 772 753 773 return $this->renderActionTable($expand_cells);
+28
webroot/rsrc/css/application/harbormaster/harbormaster.css
··· 139 139 .harbormaster-log-expand-down .phui-icon-view { 140 140 margin: 0 4px 0 0; 141 141 } 142 + 143 + 144 + .harbormaster-log-following .harbormaster-log-table 145 + .harbormaster-log-follow-start { 146 + display: none; 147 + } 148 + 149 + .harbormaster-log-table .harbormaster-log-follow-stop { 150 + display: none; 151 + } 152 + 153 + .harbormaster-log-following .harbormaster-log-table 154 + .harbormaster-log-follow-stop { 155 + display: block; 156 + } 157 + 158 + .harbormaster-log-appear > td { 159 + animation: harbormaster-fade-in 1s linear; 160 + } 161 + 162 + @keyframes harbormaster-fade-in { 163 + 0% { 164 + opacity: 0.5; 165 + } 166 + 100% { 167 + opacity: 1.0; 168 + } 169 + }
+35 -8
webroot/rsrc/js/application/harbormaster/behavior-harbormaster-log.js
··· 5 5 6 6 JX.behavior('harbormaster-log', function(config) { 7 7 var contentNode = JX.$(config.contentNodeID); 8 + 8 9 var following = false; 10 + var autoscroll = false; 9 11 10 12 JX.DOM.listen(contentNode, 'click', 'harbormaster-log-expand', function(e) { 11 13 if (!e.isNormalClick()) { ··· 14 16 15 17 e.kill(); 16 18 17 - expand(e.getTarget()); 19 + expand(e.getTarget(), true); 18 20 }); 19 21 20 - function expand(node) { 22 + function expand(node, is_action) { 21 23 var row = JX.DOM.findAbove(node, 'tr'); 22 24 row = JX.DOM.findAbove(row, 'tr'); 23 25 24 26 var data = JX.Stratcom.getData(node); 25 27 28 + if (data.stop) { 29 + following = false; 30 + autoscroll = false; 31 + JX.DOM.alterClass(contentNode, 'harbormaster-log-following', false); 32 + return; 33 + } 34 + 26 35 var uri = new JX.URI(config.renderURI) 27 36 .addQueryParams(data); 28 37 29 - if (data.live) { 38 + if (data.live && is_action) { 30 39 following = true; 40 + autoscroll = true; 41 + JX.DOM.alterClass(contentNode, 'harbormaster-log-following', true); 31 42 } 32 43 33 44 var request = new JX.Request(uri, function(r) { ··· 46 57 if (data.live) { 47 58 // If this was a live follow, scroll the new data into view. This is 48 59 // probably intensely annoying in practice but seems cool for now. 49 - var last_row = rows[rows.length - 1]; 50 - var tail_pos = JX.$V(last_row).y + JX.Vector.getDim(last_row).y; 51 - var view_y = JX.Vector.getViewport().y; 52 - JX.DOM.scrollToPosition(null, (tail_pos - view_y) + 32); 60 + if (autoscroll) { 61 + var last_row = rows[rows.length - 1]; 62 + var tail_pos = JX.$V(last_row).y + JX.Vector.getDim(last_row).y; 63 + var view_y = JX.Vector.getViewport().y; 64 + JX.DOM.scrollToPosition(null, (tail_pos - view_y) + 32); 65 + 66 + // This will fire a scroll event, but we want to keep autoscroll 67 + // enabled until we see an explicit scroll event by the user. 68 + setTimeout(function() { autoscroll = true; }, 0); 69 + } 70 + 71 + setTimeout(follow, 2000); 53 72 54 - setTimeout(follow, 500); 73 + for (var ii = 1; ii < (rows.length - 1); ii++) { 74 + JX.DOM.alterClass(rows[ii], 'harbormaster-log-appear', true); 75 + } 55 76 } 56 77 }); 57 78 58 79 request.send(); 59 80 } 81 + 82 + // If the user explicitly scrolls while following a log, keep live updating 83 + // it but stop following it with the scrollbar. 84 + JX.Stratcom.listen('scroll', null, function() { 85 + autoscroll = false; 86 + }); 60 87 61 88 function follow() { 62 89 if (!following) {