%PDF- %PDF-
| Direktori : /www/varak.net/wiki.varak.net/vendor/oojs/oojs-ui/demos/ |
| Current File : /www/varak.net/wiki.varak.net/vendor/oojs/oojs-ui/demos/demo.js |
/* eslint-disable no-console */
/* globals Prism, javascriptStringify */
/**
* @class
* @extends OO.ui.Element
*
* @constructor
*/
window.Demo = function Demo() {
var demo = this;
// Parent constructor
Demo.parent.call( this );
// Mixin constructors
OO.EventEmitter.call( this );
// Normalization
this.normalizeQuery();
// Properties
this.stylesheetLinks = this.getStylesheetLinks();
this.mode = this.getCurrentMode();
this.$menu = $( '<div>' );
this.pageDropdown = new OO.ui.DropdownWidget( {
menu: {
items: [
new OO.ui.MenuOptionWidget( { data: 'dialogs', label: 'Dialogs' } ),
new OO.ui.MenuOptionWidget( { data: 'icons', label: 'Icons' } ),
new OO.ui.MenuOptionWidget( { data: 'toolbars', label: 'Toolbars' } ),
new OO.ui.MenuOptionWidget( { data: 'widgets', label: 'Widgets' } )
],
// Funny effect... This dropdown is considered to always be "out of viewport"
// due to the getViewportSpacing() override below. Don't let it disappear.
hideWhenOutOfView: false
},
classes: [ 'demo-pageDropdown' ]
} );
this.pageMenu = this.pageDropdown.getMenu();
this.themeSelect = new OO.ui.ButtonSelectWidget();
Object.keys( this.constructor.static.themes ).forEach( function ( theme ) {
demo.themeSelect.addItems( [
new OO.ui.ButtonOptionWidget( {
data: theme,
label: demo.constructor.static.themes[ theme ]
} )
] );
} );
this.directionSelect = new OO.ui.ButtonSelectWidget().addItems( [
new OO.ui.ButtonOptionWidget( { data: 'ltr', label: 'LTR' } ),
new OO.ui.ButtonOptionWidget( { data: 'rtl', label: 'RTL' } )
] );
this.jsPhpSelect = new OO.ui.ButtonGroupWidget().addItems( [
new OO.ui.ButtonWidget( { label: 'JS' } ).setActive( true ),
new OO.ui.ButtonWidget( {
label: 'PHP',
href: 'demos.php' + this.getUrlQuery( this.getCurrentFactorValues() )
} )
] );
this.platformSelect = new OO.ui.ButtonSelectWidget().addItems( [
new OO.ui.ButtonOptionWidget( { data: 'desktop', label: 'Desktop' } ),
new OO.ui.ButtonOptionWidget( { data: 'mobile', label: 'Mobile' } )
] );
this.documentationLink = new OO.ui.ButtonWidget( {
label: 'Docs',
classes: [ 'demo-button-docs' ],
icon: 'journal',
href: '../js/',
flags: [ 'progressive' ]
} );
this.tutorialsLink = new OO.ui.ButtonWidget( {
label: 'Tutorials',
classes: [ 'demo-button-docs' ],
icon: 'book',
href: 'tutorials/index.html',
flags: [ 'progressive' ]
} );
// Events
this.pageMenu.on( 'choose', OO.ui.bind( this.onModeChange, this ) );
this.themeSelect.on( 'choose', OO.ui.bind( this.onModeChange, this ) );
this.directionSelect.on( 'choose', OO.ui.bind( this.onModeChange, this ) );
this.platformSelect.on( 'choose', OO.ui.bind( this.onModeChange, this ) );
// Initialization
this.pageMenu.selectItemByData( this.mode.page );
this.themeSelect.selectItemByData( this.mode.theme );
this.directionSelect.selectItemByData( this.mode.direction );
this.platformSelect.selectItemByData( this.mode.platform );
this.$menu
.addClass( 'demo-menu' )
.attr( 'role', 'navigation' )
.append(
this.pageDropdown.$element,
this.themeSelect.$element,
this.directionSelect.$element,
this.jsPhpSelect.$element,
this.platformSelect.$element,
this.documentationLink.$element,
this.tutorialsLink.$element
);
this.$element
.addClass( 'demo' )
.append( this.$menu );
$( 'html' ).attr( 'dir', this.mode.direction );
$( 'head' ).append( this.stylesheetLinks );
$( 'body' ).addClass( 'oo-ui-theme-' + this.mode.theme );
// eslint-disable-next-line new-cap
OO.ui.theme = new OO.ui[ this.constructor.static.themes[ this.mode.theme ] + 'Theme' ]();
OO.ui.isMobile = function () {
return demo.mode.platform === 'mobile';
};
OO.ui.getViewportSpacing = function () {
return {
// Contents of dialogs are shown on top of the fixed menu
top: demo.mode.page === 'dialogs' ? 0 : demo.$menu.outerHeight(),
right: 0,
bottom: 0,
left: 0
};
};
};
/* Setup */
OO.inheritClass( Demo, OO.ui.Element );
OO.mixinClass( Demo, OO.EventEmitter );
/* Static Properties */
/**
* Available pages.
*
* Populated by each of the page scripts in the `pages` directory.
*
* @static
* @property {Object.<string,Function>} pages List of functions that render a page, keyed by
* symbolic page name
*/
Demo.static.pages = {};
/**
* Available themes.
*
* Map of lowercase name to proper name. Lowercase names are used for linking to the
* correct stylesheet file. Proper names are used to find the theme class.
*
* @static
* @property {Object.<string,string>}
*/
Demo.static.themes = {
wikimediaui: 'WikimediaUI', // Do not change this line or you'll break `grunt add-theme`
apex: 'Apex'
};
/**
* Additional suffixes for which each theme defines image modules.
*
* @static
* @property {Object.<string,string[]>
*/
Demo.static.additionalThemeImagesSuffixes = {
wikimediaui: [
'-icons-movement',
'-icons-content',
'-icons-alerts',
'-icons-interactions',
'-icons-moderation',
'-icons-editing-core',
'-icons-editing-styling',
'-icons-editing-list',
'-icons-editing-advanced',
'-icons-editing-citation',
'-icons-media',
'-icons-location',
'-icons-user',
'-icons-layout',
'-icons-accessibility',
'-icons-wikimedia'
],
apex: [
'-icons-movement',
'-icons-content',
'-icons-alerts',
'-icons-interactions',
'-icons-moderation',
'-icons-editing-core',
'-icons-editing-styling',
'-icons-editing-list',
'-icons-editing-advanced',
'-icons-editing-citation',
'-icons-media',
'-icons-location',
'-icons-user',
'-icons-layout',
'-icons-accessibility',
'-icons-wikimedia'
]
};
/**
* Available text directions.
*
* List of text direction descriptions, each containing a `fileSuffix` property used for linking to
* the correct stylesheet file.
*
* @static
* @property {Object.<string,Object>}
*/
Demo.static.directions = {
ltr: { fileSuffix: '' },
rtl: { fileSuffix: '.rtl' }
};
/**
* Available platforms.
*
* @static
* @property {string[]}
*/
Demo.static.platforms = [ 'desktop', 'mobile' ];
/**
* Default page.
*
* @static
* @property {string}
*/
Demo.static.defaultPage = 'widgets';
/**
* Default page.
*
* Set by one of the page scripts in the `pages` directory.
*
* @static
* @property {string}
*/
Demo.static.defaultTheme = 'wikimediaui';
/**
* Default page.
*
* Set by one of the page scripts in the `pages` directory.
*
* @static
* @property {string}
*/
Demo.static.defaultDirection = 'ltr';
/**
* Default platform.
*
* Set by one of the page scripts in the `pages` directory.
*
* @static
* @property {string}
*/
Demo.static.defaultPlatform = 'desktop';
/* Static Methods */
/**
* Scroll to current fragment identifier. We have to do this manually because of the fixed header.
*/
Demo.static.scrollToFragment = function () {
var elem = document.getElementById( location.hash.slice( 1 ) );
if ( elem ) {
// The additional '10' is just because it looks nicer.
$( window ).scrollTop( $( elem ).offset().top - $( '.demo-menu' ).outerHeight() - 10 );
}
};
/* Methods */
/**
* Load the demo page. Must be called after $element is attached.
*
* @return {jQuery.Promise} Resolved when demo is initialized
*/
Demo.prototype.initialize = function () {
var demo = this,
promises = this.stylesheetLinks.map( function ( el ) {
return $( el ).data( 'load-promise' );
} );
// Helper function to get high resolution profiling data, where available.
function now() {
return ( window.performance && performance.now ) ? performance.now() :
Date.now ? Date.now() : new Date().getTime();
}
return $.when.apply( $, promises )
.done( function () {
var start, end;
start = now();
demo.constructor.static.pages[ demo.mode.page ]( demo );
end = now();
window.console.log( 'Took ' + ( end - start ) + ' ms to build demo page.' );
} )
.fail( function () {
demo.$element.append( $( '<p>' ).text( 'Demo styles failed to load.' ) );
} );
};
/**
* Handle mode change events.
*
* Will load a new page.
*/
Demo.prototype.onModeChange = function () {
var page = this.pageMenu.findSelectedItem().getData(),
theme = this.themeSelect.findSelectedItem().getData(),
direction = this.directionSelect.findSelectedItem().getData(),
platform = this.platformSelect.findSelectedItem().getData();
history.pushState( null, document.title, this.getUrlQuery( [ page, theme, direction, platform ] ) );
$( window ).triggerHandler( 'popstate' );
};
/**
* Get URL query for given factors describing the demo's mode.
*
* @param {string[]} factors Factors, as returned e.g. by #getCurrentFactorValues
* @return {string} URL query part, starting with '?'
*/
Demo.prototype.getUrlQuery = function ( factors ) {
return '?page=' + factors[ 0 ] +
'&theme=' + factors[ 1 ] +
'&direction=' + factors[ 2 ] +
'&platform=' + factors[ 3 ] +
// Preserve current URL 'fragment' part
location.hash;
};
/**
* Get a list of mode factors.
*
* Factors are a mapping between symbolic names used in the URL query and internal information used
* to act on those symbolic names.
*
* Factor lists are in URL order: page, theme, direction, platform. Page contains the symbolic
* page name, others contain file suffixes.
*
* @return {Object[]} List of mode factors, keyed by symbolic name
*/
Demo.prototype.getFactors = function () {
var key,
factors = [ {}, {}, {}, {} ];
for ( key in this.constructor.static.pages ) {
factors[ 0 ][ key ] = key;
}
for ( key in this.constructor.static.themes ) {
factors[ 1 ][ key ] = '-' + key;
}
for ( key in this.constructor.static.directions ) {
factors[ 2 ][ key ] = this.constructor.static.directions[ key ].fileSuffix;
}
this.constructor.static.platforms.forEach( function ( platform ) {
factors[ 3 ][ platform ] = '';
} );
return factors;
};
/**
* Get a list of default factors.
*
* Factor defaults are in URL order: page, theme, direction, platform. Each contains a symbolic
* factor name which should be used as a fallback when the URL query is missing or invalid.
*
* @return {Object[]} List of default factors
*/
Demo.prototype.getDefaultFactorValues = function () {
return [
this.constructor.static.defaultPage,
this.constructor.static.defaultTheme,
this.constructor.static.defaultDirection,
this.constructor.static.defaultPlatform
];
};
/**
* Parse the current URL query into factor values.
*
* @return {string[]} Factor values in URL order: page, theme, direction, platform
*/
Demo.prototype.getCurrentFactorValues = function () {
var i, parts, index,
factors = this.getDefaultFactorValues(),
order = [ 'page', 'theme', 'direction', 'platform' ],
query = location.search.slice( 1 ).split( '&' );
for ( i = 0; i < query.length; i++ ) {
parts = query[ i ].split( '=', 2 );
index = order.indexOf( parts[ 0 ] );
if ( index !== -1 ) {
factors[ index ] = decodeURIComponent( parts[ 1 ] );
}
}
return factors;
};
/**
* Get the current mode.
*
* Generated from parsed URL query values.
*
* @return {Object} List of factor values keyed by factor name
*/
Demo.prototype.getCurrentMode = function () {
var factorValues = this.getCurrentFactorValues();
return {
page: factorValues[ 0 ],
theme: factorValues[ 1 ],
direction: factorValues[ 2 ],
platform: factorValues[ 3 ]
};
};
/**
* Get link elements for the current mode.
*
* @return {HTMLElement[]} List of link elements
*/
Demo.prototype.getStylesheetLinks = function () {
var i, len, links, fragments,
factors = this.getFactors(),
theme = this.getCurrentFactorValues()[ 1 ],
suffixes = this.constructor.static.additionalThemeImagesSuffixes[ theme ] || [],
urls = [];
// Translate modes to filename fragments
fragments = this.getCurrentFactorValues().map( function ( val, index ) {
return factors[ index ][ val ];
} );
// Theme styles
urls.push( 'dist/oojs-ui' + fragments.slice( 1 ).join( '' ) + '.css' );
for ( i = 0, len = suffixes.length; i < len; i++ ) {
urls.push( 'dist/oojs-ui' + fragments[ 1 ] + suffixes[ i ] + fragments[ 2 ] + '.css' );
}
// Demo styles
urls.push( 'styles/demo' + fragments[ 2 ] + '.css' );
// Add link tags
links = urls.map( function ( url ) {
var
link = document.createElement( 'link' ),
$link = $( link ),
deferred = $.Deferred();
$link.data( 'load-promise', deferred.promise() );
$link.on( {
load: deferred.resolve,
error: deferred.reject
} );
link.rel = 'stylesheet';
link.href = url;
return link;
} );
return links;
};
/**
* Normalize the URL query.
*/
Demo.prototype.normalizeQuery = function () {
var i, len, factorValues, match, valid, factorValue,
modes = [],
factors = this.getFactors(),
defaults = this.getDefaultFactorValues();
factorValues = this.getCurrentFactorValues();
for ( i = 0, len = factors.length; i < len; i++ ) {
factorValue = factorValues[ i ];
modes[ i ] = factors[ i ][ factorValue ] !== undefined ? factorValue : defaults[ i ];
}
// Backwards-compatibility with old URLs that used the 'fragment' part to link to demo sections:
// if a fragment is specified and it describes valid factors, turn the URL into the new style.
match = location.hash.match( /^#(\w+)-(\w+)-(\w+)-(\w+)$/ );
if ( match ) {
factorValues = [];
valid = true;
for ( i = 0, len = factors.length; i < len; i++ ) {
factorValue = match[ i + 1 ];
if ( factors[ i ][ factorValue ] !== undefined ) {
factorValues[ i ] = factorValue;
} else {
valid = false;
break;
}
}
if ( valid ) {
location.hash = '';
modes = factorValues;
}
}
// Update query
history.replaceState( null, document.title, this.getUrlQuery( modes ) );
};
/**
* Destroy demo.
*/
Demo.prototype.destroy = function () {
$( 'body' ).removeClass( 'oo-ui-ltr oo-ui-rtl' );
$( 'body' ).removeClass( 'oo-ui-theme-' + this.mode.theme );
$( this.stylesheetLinks ).remove();
this.$element.remove();
this.emit( 'destroy' );
};
/**
* Build a console for interacting with an element.
*
* @param {OO.ui.Layout} item
* @param {string} layout Variable name for layout
* @param {string} widget Variable name for layout's field widget
* @return {jQuery} Console interface element
*/
Demo.prototype.buildConsole = function ( item, layout, widget, showLayoutCode ) {
var $toggle, $log, $label, $input, $submit, $console, $form, $pre, $code,
console = window.console;
function exec( str ) {
var func, ret;
if ( str.indexOf( 'return' ) !== 0 ) {
str = 'return ' + str;
}
try {
// eslint-disable-next-line no-new-func
func = new Function( layout, widget, 'item', str );
ret = { value: func( item, item.fieldWidget, item.fieldWidget ) };
} catch ( error ) {
ret = {
value: undefined,
error: error
};
}
return ret;
}
function submit() {
var val, result, logval;
val = $input.val();
$input.val( '' );
$input[ 0 ].focus();
result = exec( val );
logval = String( result.value );
if ( logval === '' ) {
logval = '""';
}
$log.append(
$( '<div>' )
.addClass( 'demo-console-log-line demo-console-log-line-input' )
.text( val ),
$( '<div>' )
.addClass( 'demo-console-log-line demo-console-log-line-return' )
.text( logval || result.value )
);
if ( result.error ) {
$log.append( $( '<div>' ).addClass( 'demo-console-log-line demo-console-log-line-error' ).text( result.error ) );
}
if ( console && console.log ) {
console.log( '[demo]', result.value );
if ( result.error ) {
if ( console.error ) {
console.error( '[demo]', String( result.error ), result.error );
} else {
console.log( '[demo] Error: ', result.error );
}
}
}
// Scrol to end
$log.prop( 'scrollTop', $log.prop( 'scrollHeight' ) );
}
function getCode( item, toplevel ) {
var config, defaultConfig, url, params, out, i,
items = [],
demoLinks = [],
docLinks = [];
function getConstructorName( item ) {
var isDemoWidget = item.constructor.name.indexOf( 'Demo' ) === 0;
return ( isDemoWidget ? 'Demo.' : 'OO.ui.' ) + item.constructor.name.slice( 4 );
}
// If no item was passed we shouldn't show a code block
if ( item === undefined ) {
return false;
}
config = item.initialConfig;
// Prevent the default config from being part of the code
if ( item instanceof OO.ui.ActionFieldLayout ) {
defaultConfig = ( new item.constructor( new OO.ui.TextInputWidget(), new OO.ui.ButtonWidget() ) ).initialConfig;
} else if ( item instanceof OO.ui.FieldLayout ) {
defaultConfig = ( new item.constructor( new OO.ui.ButtonWidget() ) ).initialConfig;
} else {
defaultConfig = ( new item.constructor() ).initialConfig;
}
Object.keys( defaultConfig ).forEach( function ( key ) {
if ( config[ key ] === defaultConfig[ key ] ) {
delete config[ key ];
} else if (
typeof config[ key ] === 'object' && typeof defaultConfig[ key ] === 'object' &&
OO.compare( config[ key ], defaultConfig[ key ] )
) {
delete config[ key ];
}
} );
config = javascriptStringify( config, function ( obj, indent, stringify ) {
if ( obj instanceof Function ) {
// Get function's source code, with extraneous indentation removed
return obj.toString().replace( /^\t\t\t\t\t\t/gm, '' );
} else if ( obj instanceof jQuery ) {
if ( $.contains( item.$element[ 0 ], obj[ 0 ] ) ) {
// If this element appears inside the generated widget,
// assume this was something like `$label: $( '<p>Text</p>' )`
return '$( ' + javascriptStringify( obj.prop( 'outerHTML' ) ) + ' )';
} else {
// Otherwise assume this was something like `$overlay: $( '#overlay' )`
return '$( ' + javascriptStringify( '#' + obj.attr( 'id' ) ) + ' )';
}
} else if ( obj instanceof OO.ui.HtmlSnippet ) {
return 'new OO.ui.HtmlSnippet( ' + javascriptStringify( obj.toString() ) + ' )';
} else if ( obj instanceof OO.ui.Element ) {
return getCode( obj );
} else {
return stringify( obj );
}
}, '\t' );
// The generated code needs to include different arguments, based on the object type
items.push( item );
if ( item instanceof OO.ui.ActionFieldLayout ) {
params = getCode( item.fieldWidget ) + ', ' + getCode( item.buttonWidget );
items.push( item.fieldWidget );
items.push( item.buttonWidget );
} else if ( item instanceof OO.ui.FieldLayout ) {
params = getCode( item.fieldWidget );
items.push( item.fieldWidget );
} else {
params = '';
}
if ( config !== '{}' ) {
params += ( params ? ', ' : '' ) + config;
}
out = 'new ' + getConstructorName( item ) + '(' +
( params ? ' ' : '' ) + params + ( params ? ' ' : '' ) +
')';
if ( toplevel ) {
for ( i = 0; i < items.length; i++ ) {
item = items[ i ];
// The code generated for Demo widgets cannot be copied and used
if ( item.constructor.name.indexOf( 'Demo' ) === 0 ) {
url =
'https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/demos/classes/' +
item.constructor.name.slice( 4 ) + '.js';
demoLinks.push( url );
} else {
url = 'https://doc.wikimedia.org/oojs-ui/master/js/#!/api/' + getConstructorName( item );
url = '[' + url + '](' + url + ')';
docLinks.push( url );
}
}
}
return (
( docLinks.length ? '// See documentation at: \n// ' : '' ) +
docLinks.join( '\n// ' ) + ( docLinks.length ? '\n' : '' ) +
( demoLinks.length ? '// See source code:\n// ' : '' ) +
demoLinks.join( '\n// ' ) + ( demoLinks.length ? '\n' : '' ) +
out
);
}
$toggle = $( '<span>' )
.addClass( 'demo-console-toggle' )
.attr( 'title', 'Toggle console' )
.on( 'click', function ( e ) {
var code;
e.preventDefault();
$console.toggleClass( 'demo-console-collapsed demo-console-expanded' );
if ( $input.is( ':visible' ) ) {
$input[ 0 ].focus();
if ( console && console.log ) {
window[ layout ] = item;
window[ widget ] = item.fieldWidget;
console.log( '[demo]', 'Globals ' + layout + ', ' + widget + ' have been set' );
console.log( '[demo]', item );
if ( showLayoutCode === true ) {
code = getCode( item, true );
} else {
code = getCode( item.fieldWidget, true );
}
if ( code ) {
$code.text( code );
Prism.highlightElement( $code[ 0 ] );
} else {
$code.remove();
}
}
}
} );
$log = $( '<div>' )
.addClass( 'demo-console-log' );
$label = $( '<label>' )
.addClass( 'demo-console-label' );
$input = $( '<input>' )
.addClass( 'demo-console-input' )
.prop( 'placeholder', '... (predefined: ' + layout + ', ' + widget + ')' );
$submit = $( '<div>' )
.addClass( 'demo-console-submit' )
.text( '↵' )
.on( 'click', submit );
$form = $( '<form>' ).on( 'submit', function ( e ) {
e.preventDefault();
submit();
} );
$code = $( '<code>' ).addClass( 'language-javascript' );
$pre = $( '<pre>' )
.addClass( 'demo-sample-code' )
.append( $code );
$console = $( '<div>' )
.addClass( 'demo-console demo-console-collapsed' )
.append(
$toggle,
$log,
$form.append(
$label.append(
$input
),
$submit
),
$pre
);
return $console;
};
/**
* Build a link to this example.
*
* @param {OO.ui.Layout} item
* @param {OO.ui.FieldsetLayout} parentItem
* @return {jQuery} Link interface element
*/
Demo.prototype.buildLinkExample = function ( item, parentItem ) {
var $linkExample, label, fragment;
if ( item.$label.text() === '' ) {
item = parentItem;
}
fragment = item.elementId;
if ( !fragment ) {
label = item.$label.text();
fragment = label.replace( /[^\w]+/g, '-' ).replace( /^-|-$/g, '' );
item.setElementId( fragment );
}
$linkExample = $( '<a>' )
.addClass( 'demo-link-example' )
.attr( 'title', 'Link to this example' )
.attr( 'href', '#' + fragment )
.on( 'click', function ( e ) {
// We have to handle this manually in order to call .scrollToFragment() even if it's the same
// fragment. Normally, the browser will scroll but not fire a 'hashchange' event in this
// situation, and the scroll position will be off because of our fixed header.
if ( e.which === OO.ui.MouseButtons.LEFT ) {
location.hash = $( this ).attr( 'href' );
Demo.static.scrollToFragment();
e.preventDefault();
}
} );
return $linkExample;
};