Limited width toggle

* Introduce a generalized feature toggle system that uses user
preferences or localStorage for anons (right now the latter is out
of scope but will be explored in a follow up)
* Feature flagged to VisualEnhancementNext for now, given the dependency
on icon size

Bug: T319449
Change-Id: I7343a3f38b720411d5ef5f3414f25f475b0bb84a
This commit is contained in:
Jon Robson 2022-10-21 11:14:54 -07:00 committed by Jdlrobson
parent 685a3171bd
commit 0eb8811e7a
11 changed files with 150 additions and 4 deletions

View file

@ -29,10 +29,10 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 25,
functions: 29,
lines: 32,
statements: 32
branches: 26,
functions: 32,
lines: 34,
statements: 34
}
},

View file

@ -4,3 +4,4 @@ var mockMediaWiki = require( '@wikimedia/mw-node-qunit/src/mockMediaWiki.js' );
global.mw = mockMediaWiki();
global.$ = require('jquery');
global.mw.util.showPortlet = function() {};
global.mw.Api.prototype.saveOption = function() {};

View file

@ -0,0 +1,58 @@
/** @interface MwApi */
var /** @type {MwApi} */api,
debounce = require( /** @type {string} */ ( 'mediawiki.util' ) ).debounce;
/**
* Saves preference to user preferences and/or localStorage.
*
* @param {string} feature
* @param {boolean} enabled
*/
function save( feature, enabled ) {
var featuresJSON,
// @ts-ignore
features = mw.storage.get( 'VectorFeatures' ) || '{}';
try {
featuresJSON = JSON.parse( features );
} catch ( e ) {
featuresJSON = {};
}
featuresJSON[ feature ] = enabled;
// @ts-ignore
mw.storage.set( 'VectorFeatures', JSON.stringify( featuresJSON ) );
if ( !mw.user.isAnon() ) {
debounce( function () {
api = api || new mw.Api();
api.saveOption( 'vector-' + feature, enabled ? 1 : 0 );
}, 500 )();
}
}
/**
* @param {string} name
* @throws {Error} if unknown feature toggled.
*/
function toggle( name ) {
var featureClassEnabled = 'vector-feature-' + name + '-enabled',
classList = document.body.classList,
featureClassDisabled = 'vector-feature-' + name + '-disabled';
if ( classList.contains( featureClassDisabled ) ) {
classList.remove( featureClassDisabled );
classList.add( featureClassEnabled );
save( name, true );
} else if ( classList.contains( featureClassEnabled ) ) {
classList.add( featureClassDisabled );
classList.remove( featureClassEnabled );
save( name, false );
} else {
throw new Error( 'Attempt to toggle unknown feature: ' + name );
}
}
module.exports = {
toggle: toggle
};

View file

@ -0,0 +1 @@
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg"><g fill="#000" fill-rule="evenodd"><path d="M11.5 8.5v-5H13V7h3.5v1.5h-5ZM11.5 11.5v5H13V13h3.5v-1.5h-5ZM8.5 8.5v-5H7V7H3.5v1.5h5ZM8.5 11.5v5H7V13H3.5v-1.5h5Z"/></g></svg>

After

Width:  |  Height:  |  Size: 234 B

View file

@ -0,0 +1 @@
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg"><g fill="#000" fill-rule="evenodd"><path d="M16.5 3.5v5H15V5h-3.5V3.5h5ZM16.5 16.5v-5H15V15h-3.5v1.5h5ZM3.5 3.5v5H5V5h3.5V3.5h-5ZM3.5 16.5v-5H5V15h3.5v1.5h-5Z"/></g></svg>

After

Width:  |  Height:  |  Size: 234 B

View file

@ -0,0 +1,15 @@
var features = require( './features.js' );
/**
* adds a toggle button
*/
function init() {
var toggle = document.createElement( 'div' );
toggle.classList.add( 'mw-ui-icon', 'mw-ui-icon-element', 'mw-ui-button', 'vector-limited-width-toggle' );
document.body.appendChild( toggle );
toggle.addEventListener( 'click', function () {
features.toggle( 'limited-width' );
} );
}
module.exports = init;

View file

@ -0,0 +1,25 @@
.vector-limited-width-toggle {
display: none;
}
// Note on certain pages the control will have no effect e.g. Special:RecentChanges
// Defining this at 1600px was a product decision so do not change it
// (more context at https://phabricator.wikimedia.org/T319449#8346630)
@media ( min-width: 1600px ) {
.vector-feature-visual-enhancement-next-enabled .vector-limited-width-toggle {
display: block;
position: fixed;
bottom: 8px;
right: 8px;
&:before {
background-image: url( images/fullscreen.svg );
}
}
.vector-feature-visual-enhancement-next-enabled.vector-feature-limited-width-content-disable {
.vector-limited-width-toggle:before {
background-image: url( images/fullscreen-close.svg );
}
}
}

View file

@ -3,6 +3,7 @@ var languageButton = require( './languageButton.js' ),
initSearchLoader = require( './searchLoader.js' ).initSearchLoader,
dropdownMenus = require( './dropdownMenus.js' ).dropdownMenus,
sidebarPersistence = require( './sidebarPersistence.js' ),
limitedWidthToggle = require( './limitedWidthToggle.js' ),
watchstar = require( './watchstar.js' ),
// @ts-ignore
menuTabs = require( './menuTabs.js' ),
@ -81,6 +82,7 @@ function main( window ) {
// menuTabs should follow `dropdownMenus` as that can move menu items from a
// tab menu to a dropdown.
menuTabs();
limitedWidthToggle();
addNamespacesGadgetSupport();
if ( document.body.classList.contains( 'vector-feature-visual-enhancement-next-enabled' ) ) {
watchstar();

View file

@ -381,6 +381,7 @@
"mediawiki.page.ready",
"mediawiki.page.watch.ajax",
"mediawiki.util",
"mediawiki.storage",
"mediawiki.experiments"
],
"messages": [
@ -391,6 +392,9 @@
]
},
"skins.vector.js": {
"styles": [
"resources/skins.vector.js/limitedWidthToggle.less"
],
"packageFiles": [
"resources/skins.vector.js/skin.js",
{
@ -403,6 +407,8 @@
"resources/skins.vector.js/sidebarPersistence.js",
"resources/skins.vector.js/languageButton.js",
"resources/skins.vector.js/echo.js",
"resources/skins.vector.js/limitedWidthToggle.js",
"resources/skins.vector.js/features.js",
"resources/skins.vector.js/searchLoader.js",
"resources/skins.vector.js/menuTabs.js"
],

View file

@ -0,0 +1,3 @@
module.exports = {
debounce: ( /** @type {Function} */fn ) => fn
};

View file

@ -0,0 +1,34 @@
const features = require( '../../../resources/skins.vector.js/features.js' );
describe( 'features', () => {
beforeEach( () => {
document.body.setAttribute( 'class', 'vector-feature-foo-disabled vector-feature-bar-enabled hello' );
} );
test( 'toggle', () => {
features.toggle( 'foo' );
features.toggle( 'bar' );
expect(
document.body.classList.contains( 'vector-feature-foo-enabled' )
).toBe( true );
expect(
document.body.classList.contains( 'vector-feature-foo-disabled' )
).toBe( false );
expect(
document.body.classList.contains( 'vector-feature-bar-disabled' )
).toBe( true );
expect(
document.body.classList.contains( 'vector-feature-bar-enabled' )
).toBe( false );
expect(
document.body.classList.contains( 'hello' )
).toBe( true );
} );
test( 'toggle unknown feature', () => {
expect( () => {
features.toggle( 'unknown' );
} ).toThrow();
} );
} );