From ebc36b75b87b9752a12c25d41a3619faa7d2715e Mon Sep 17 00:00:00 2001 From: Roan Kattouw Date: Fri, 7 Jul 2017 11:15:29 -0700 Subject: [PATCH] RCFilters: Basic implementation of live updates Adds a live updates button that refreshes the changes list every 3 seconds. For now this is pretty dumb in that it re-requests the entire list every time; the next step would be to make it only load new changes using the &from= query parameter. Bug: T167743 Change-Id: Ic2ddea840e5c46f42b32ae4fff91138cacc28ec0 --- includes/DefaultSettings.php | 5 ++ includes/specials/SpecialRecentchanges.php | 7 ++- languages/i18n/en.json | 1 + languages/i18n/qqq.json | 1 + resources/Resources.php | 4 ++ .../mw.rcfilters.dm.ChangesListViewModel.js | 1 + .../mw.rcfilters.Controller.js | 48 +++++++++++++++++-- .../mw.rcfilters.ui.FilterWrapperWidget.less | 4 ++ ...w.rcfilters.ui.LiveUpdateButtonWidget.less | 37 ++++++++++++++ .../ui/mw.rcfilters.ui.FilterWrapperWidget.js | 15 +++++- .../mw.rcfilters.ui.LiveUpdateButtonWidget.js | 42 ++++++++++++++++ 11 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.LiveUpdateButtonWidget.less create mode 100644 resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.LiveUpdateButtonWidget.js diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 1459ab65a9..0548d8bacf 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -6778,6 +6778,11 @@ $wgStructuredChangeFiltersEnableSaving = true; */ $wgStructuredChangeFiltersEnableExperimentalViews = false; +/** + * Whether to allow users to use the experimental live update feature in the new RecentChanges UI + */ +$wgStructuredChangeFiltersEnableLiveUpdate = false; + /** * Use new page patrolling to check new pages on Special:Newpages */ diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php index d856d4b20e..bec87c592a 100644 --- a/includes/specials/SpecialRecentchanges.php +++ b/includes/specials/SpecialRecentchanges.php @@ -139,7 +139,8 @@ class SpecialRecentChanges extends ChangesListSpecialPage { */ public function execute( $subpage ) { global $wgStructuredChangeFiltersEnableSaving, - $wgStructuredChangeFiltersEnableExperimentalViews; + $wgStructuredChangeFiltersEnableExperimentalViews, + $wgStructuredChangeFiltersEnableLiveUpdate; // Backwards-compatibility: redirect to new feed URLs $feedFormat = $this->getRequest()->getVal( 'feed' ); @@ -189,6 +190,10 @@ class SpecialRecentChanges extends ChangesListSpecialPage { 'wgStructuredChangeFiltersEnableExperimentalViews', $wgStructuredChangeFiltersEnableExperimentalViews ); + $out->addJsConfigVars( + 'wgStructuredChangeFiltersEnableLiveUpdate', + $wgStructuredChangeFiltersEnableLiveUpdate + ); $out->addJsConfigVars( 'wgRCFiltersChangeTags', $this->buildChangeTagList() diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 7d107d9b63..9447de6622 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -1446,6 +1446,7 @@ "rcfilters-view-namespaces-tooltip": "Filter results by namespace", "rcfilters-view-tags-tooltip": "Filter results using edit tags", "rcfilters-view-return-to-default-tooltip": "Return to main filter menu", + "rcfilters-liveupdates-button": "Live updates", "rcnotefrom": "Below {{PLURAL:$5|is the change|are the changes}} since $3, $4 (up to $1 shown).", "rclistfromreset": "Reset date selection", "rclistfrom": "Show new changes starting from $2, $3", diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index 4d854d909a..7c995f03de 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -1636,6 +1636,7 @@ "rcfilters-view-namespaces-tooltip": "Tooltip for the button that loads the namespace view in [[Special:RecentChanges]]", "rcfilters-view-tags-tooltip": "Tooltip for the button that loads the tags view in [[Special:RecentChanges]]", "rcfilters-view-return-to-default-tooltip": "Tooltip for the button that returns to the default filter view in [[Special:RecentChanges]]", + "rcfilters-liveupdates-button": "Label for the button to enable or disable live updates on [[Special:RecentChanges]]", "rcnotefrom": "This message is displayed at [[Special:RecentChanges]] when viewing recentchanges from some specific time.\n\nThe corresponding message is {{msg-mw|Rclistfrom}}.\n\nParameters:\n* $1 - the maximum number of changes that are displayed\n* $2 - (Optional) a date and time\n* $3 - a date\n* $4 - a time\n* $5 - Number of changes are displayed, for use with PLURAL", "rclistfromreset": "Used on [[Special:RecentChanges]] to reset a selection of a certain date range.", "rclistfrom": "Used on [[Special:RecentChanges]]. Parameters:\n* $1 - (Currently not use) date and time. The date and the time adds to the rclistfrom description.\n* $2 - time. The time adds to the rclistfrom link description (with split of date and time).\n* $3 - date. The date adds to the rclistfrom link description (with split of date and time).\n\nThe corresponding message is {{msg-mw|Rcnotefrom}}.", diff --git a/resources/Resources.php b/resources/Resources.php index 12f482f865..01ac67946c 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1789,6 +1789,7 @@ return [ 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FormWrapperWidget.js', 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterItemHighlightButton.js', 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.HighlightColorPickerWidget.js', + 'resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.LiveUpdateButtonWidget.js', 'resources/src/mediawiki.rcfilters/mw.rcfilters.HighlightColors.js', 'resources/src/mediawiki.rcfilters/mw.rcfilters.init.js', ], @@ -1812,6 +1813,7 @@ return [ 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.SavedLinksListWidget.less', 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.SavedLinksListItemWidget.less', 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.SaveFiltersPopupButtonWidget.less', + 'resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.LiveUpdateButtonWidget.less', ], 'skinStyles' => [ 'monobook' => [ @@ -1859,6 +1861,7 @@ return [ 'rcfilters-view-namespaces-tooltip', 'rcfilters-view-tags-tooltip', 'rcfilters-view-return-to-default-tooltip', + 'rcfilters-liveupdates-button', 'blanknamespace', 'namespaces', 'invert', @@ -1877,6 +1880,7 @@ return [ 'oojs-ui.styles.icons-interactions', 'oojs-ui.styles.icons-content', 'oojs-ui.styles.icons-layout', + 'oojs-ui.styles.icons-media', ], ], 'mediawiki.special' => [ diff --git a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ChangesListViewModel.js b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ChangesListViewModel.js index d6ce734caf..c839a13fbe 100644 --- a/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ChangesListViewModel.js +++ b/resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.ChangesListViewModel.js @@ -28,6 +28,7 @@ /** * @event update * @param {jQuery|string} changesListContent + * @param {jQuery} $fieldset * * The list of change is now up to date */ diff --git a/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js b/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js index 27387c9243..58585668df 100644 --- a/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js +++ b/resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js @@ -271,6 +271,42 @@ this._trackHighlight( 'clear', filterName ); }; + /** + * Enable or disable live updates. + * @param {boolean} enable True to enable, false to disable + */ + mw.rcfilters.Controller.prototype.toggleLiveUpdate = function ( enable ) { + if ( enable && !this.liveUpdateTimeout ) { + this._scheduleLiveUpdate(); + } else if ( !enable && this.liveUpdateTimeout ) { + clearTimeout( this.liveUpdateTimeout ); + this.liveUpdateTimeout = null; + } + }; + + /** + * Set a timeout for the next live update. + * @private + */ + mw.rcfilters.Controller.prototype._scheduleLiveUpdate = function () { + this.liveUpdateTimeout = setTimeout( this._doLiveUpdate.bind( this ), 3000 ); + }; + + /** + * Perform a live update. + * @private + */ + mw.rcfilters.Controller.prototype._doLiveUpdate = function () { + var controller = this; + this.updateChangesList( {}, true ) + .always( function () { + if ( controller.liveUpdateTimeout ) { + // Live update was not disabled in the meantime + controller._scheduleLiveUpdate(); + } + } ); + }; + /** * Save the current model state as a saved query * @@ -555,11 +591,15 @@ * Update the list of changes and notify the model * * @param {Object} [params] Extra parameters to add to the API call + * @param {boolean} [isLiveUpdate] Don't update the URL or invalidate the changes list + * @return {jQuery.Promise} Promise that is resolved when the update is complete */ - mw.rcfilters.Controller.prototype.updateChangesList = function ( params ) { - this._updateURL( params ); - this.changesListModel.invalidate(); - this._fetchChangesList() + mw.rcfilters.Controller.prototype.updateChangesList = function ( params, isLiveUpdate ) { + if ( !isLiveUpdate ) { + this._updateURL( params ); + this.changesListModel.invalidate(); + } + return this._fetchChangesList() .then( // Success function ( pieces ) { diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterWrapperWidget.less b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterWrapperWidget.less index 1a29459f48..5aa866de65 100644 --- a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterWrapperWidget.less +++ b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.FilterWrapperWidget.less @@ -7,4 +7,8 @@ &-viewToggleButtons { margin-top: 1em; } + + &-bottom { + margin-top: 1em; + } } diff --git a/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.LiveUpdateButtonWidget.less b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.LiveUpdateButtonWidget.less new file mode 100644 index 0000000000..63ea264844 --- /dev/null +++ b/resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.LiveUpdateButtonWidget.less @@ -0,0 +1,37 @@ +.mw-rcfilters-ui-liveUpdateButtonWidget { + &.oo-ui-toggleWidget-on { + position: relative; + overflow: hidden; + &:after { + content: ''; + mix-blend-mode: screen; + position: absolute; + width: 1.875em; + height: 1.875em; + top: 1.875em / 4; + left: 0.46875em; + background: rgba( 51, 102, 204, 0.5 ); + border-radius: 100%; + transform-origin: 50% 50%; + opacity: 0; + animation: ripple 1.2s ease-out infinite; + animation-delay: 1s; + } + } +} + +@keyframes ripple { + 0%, + 35% { + transform: scale( 0 ); + opacity: 1; + } + 50% { + transform: scale( 1.5 ); + opacity: 0.8; + } + 100% { + opacity: 0; + transform: scale( 4 ); + } +} diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js index ebef62fb53..a748063461 100644 --- a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterWrapperWidget.js @@ -14,6 +14,7 @@ * @cfg {jQuery} [$overlay] A jQuery object serving as overlay for popups */ mw.rcfilters.ui.FilterWrapperWidget = function MwRcfiltersUiFilterWrapperWidget( controller, model, savedQueriesModel, config ) { + var $bottom; config = config || {}; // Parent @@ -33,6 +34,10 @@ { $overlay: this.$overlay } ); + this.liveUpdateButton = new mw.rcfilters.ui.LiveUpdateButtonWidget( + this.controller + ); + // Initialize this.$element .addClass( 'mw-rcfilters-ui-filterWrapperWidget' ); @@ -50,8 +55,16 @@ } + $bottom = $( '
' ) + .addClass( 'mw-rcfilters-ui-filterWrapperWidget-bottom' ); + + if ( mw.config.get( 'wgStructuredChangeFiltersEnableLiveUpdate' ) ) { + $bottom.append( this.liveUpdateButton.$element ); + } + this.$element.append( - this.filterTagWidget.$element + this.filterTagWidget.$element, + $bottom ); }; diff --git a/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.LiveUpdateButtonWidget.js b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.LiveUpdateButtonWidget.js new file mode 100644 index 0000000000..8bab981a7b --- /dev/null +++ b/resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.LiveUpdateButtonWidget.js @@ -0,0 +1,42 @@ +( function ( mw ) { + /** + * Widget for toggling live updates + * + * @extends OO.ui.ToggleButtonWidget + * + * @constructor + * @param {mw.rcfilters.Controller} controller + * @param {Object} config Configuration object + */ + mw.rcfilters.ui.LiveUpdateButtonWidget = function MwRcfiltersUiLiveUpdateButtonWidget( controller, config ) { + config = config || {}; + + // Parent + mw.rcfilters.ui.LiveUpdateButtonWidget.parent.call( this, $.extend( { + icon: 'play', + label: mw.message( 'rcfilters-liveupdates-button' ).text() + } ), config ); + + this.controller = controller; + + // Events + this.connect( this, { change: 'onChange' } ); + + this.$element.addClass( 'mw-rcfilters-ui-liveUpdateButtonWidget' ); + }; + + /* Initialization */ + + OO.inheritClass( mw.rcfilters.ui.LiveUpdateButtonWidget, OO.ui.ToggleButtonWidget ); + + /* Methods */ + + /** + * Respond to the button being toggled. + * @param {boolean} enable Whether the button is now pressed/enabled + */ + mw.rcfilters.ui.LiveUpdateButtonWidget.prototype.onChange = function ( enable ) { + this.controller.toggleLiveUpdate( enable ); + }; + +}( mediaWiki ) ); -- 2.20.1