User
Difference between revisions of "Seb35/bluell.js"
< User:Seb35
(ajout d’un premier élément vide également pour les ComboBoxInputWidget afin que l’utilisateur ait un moyen de désélectionner facilement (même si sur ce type de widget c’est déjà possible avec Ctrl+A Suppr, mais pas forcément intuitif)) |
(revert de mon dernier changement : sur les ComboBoxInputWidget, le menu ne peut plus être ouvert quand le texte correspond à une option, rendant la sélection d’une option vide impossible ; j’abandonne pour ce cas, ce comportement était juste un nice-to-have mais trop compliqué ici) Tag: Undo |
||
Line 143: | Line 143: | ||
// We dedupe because the 'speaker' (only) has duplicate (ordered) values | // We dedupe because the 'speaker' (only) has duplicate (ordered) values | ||
const v = values.results.bindings; | const v = values.results.bindings; | ||
− | var emptyItem = [ {} ]; | + | list[type] = dedupe( v, function( x ) { return x[type].value; } ); |
− | + | if( widgetType === 'DropdownWidget' ) { | |
− | + | var emptyItem = [ {} ]; | |
− | + | emptyItem[0][type] = { value: '' }; | |
+ | emptyItem[0][type+'Label'] = { value: ' ' }; | ||
+ | list[type] = emptyItem.concat( list[type] ); | ||
+ | } | ||
// Save the mapping Qxx → index to update the list from a list of Qxx (see doQuery) | // Save the mapping Qxx → index to update the list from a list of Qxx (see doQuery) | ||
Line 173: | Line 176: | ||
menu: { | menu: { | ||
filterFromInput: true, | filterFromInput: true, | ||
− | items: list[type].map( function( x | + | items: list[type].map( function( x ) { |
return new OO.ui.MenuOptionWidget( { | return new OO.ui.MenuOptionWidget( { | ||
− | data: widgetType === 'ComboBoxInputWidget' | + | data: widgetType === 'ComboBoxInputWidget' ? x[ type + 'Label' ].value + " (" + x[type].value.substr( 31 ) + ")" : x[type].value.substr( 31 ), |
label: x[ type + 'Label' ].value | label: x[ type + 'Label' ].value | ||
} ); | } ); | ||
Line 181: | Line 184: | ||
} | } | ||
} ); | } ); | ||
− | oouiSelectors[type].getMenu().items[0].toggle( false ); | + | if( widgetType === 'DropdownWidget' ) { |
+ | oouiSelectors[type].getMenu().items[0].toggle( false ); | ||
+ | } | ||
if( values.length === 1 ) { | if( values.length === 1 ) { |
Revision as of 11:28, 11 September 2021
// Development in final phase of the interface for displaying contributions
$( function (){
if( mw.config.get( 'wgPageName' ) !== 'User:Nicolas_NALLET' ) {
return;
}
const userLanguage = mw.config.get( 'wgUserLanguage' );
const messages = {
'msg-no-results': {
en: 'No results.',
de: 'Keine Ergebnisse.',
fr: 'Pas de résultat.',
//qqq: 'Message when there are no results.',
},
'button-gosearch': {
en: 'Search',
de: 'Suchen',
fr: 'Rechercher',
//qqq: 'Button where the user clicks to search.',
},
'button-resetsearch': {
en: 'Reset',
de: 'Löschen',
fr: 'Effacer',
//qqq: 'Button where the user clicks to reset the form.',
},
'placeholder-speaker': {
en: '👤 Speaker',
fr: '👤 Locuteur',
//qqq: 'Placeholder for the field Speaker.',
},
'placeholder-gender': {
en: '♀️ ♂️ Speaker\'s gender',
fr: '♀️ ♂️ Genre du locuteur',
//qqq: 'Placeholder for the field Gender.',
},
'placeholder-language': {
en: '🏳️ Language',
fr: '🏳️ Langue',
//qqq: 'Placeholder for the field Language.',
},
'placeholder-proficiency': {
en: '🥇 Level of proficiency',
fr: '🥇 Niveau de compétence',
//qqq: 'Placeholder for the field Proficiency.',
},
};
mw.loader.using( ['oojs', 'oojs-ui', 'mediawiki.api', 'ext.recordWizard.wikibase'], function () {
oouiSelectors = {
'speaker': null,
'gender': null,
'language': null,
'proficiency': null,
};
var sparqlGlobal = {
'speaker': 'SELECT ?speaker ?speakerLabel ?gender ?language ?proficiency WHERE { ?speaker prop:P2 entity:Q3 . OPTIONAL { ?speaker prop:P8 ?gender } . OPTIONAL { ?speaker llp:P4 ?statement . ?statement llv:P4 ?language . OPTIONAL { ?statement llq:P16 ?proficiency } } . SERVICE wikibase:label { bd:serviceParam wikibase:language "' + userLanguage + ',fr,en" } } ORDER BY ?speakerLabel',
'gender': 'SELECT DISTINCT ?gender ?genderLabel WHERE { ?gender prop:P2 entity:Q7 . SERVICE wikibase:label { bd:serviceParam wikibase:language "' + userLanguage + ',fr,en" } } ORDER BY ?gender',
'language': 'SELECT DISTINCT ?language ?languageLabel WHERE { ?language prop:P2 entity:Q4 . SERVICE wikibase:label { bd:serviceParam wikibase:language "' + userLanguage + ',fr,en" } } ORDER BY ?languageLabel',
'proficiency': 'SELECT DISTINCT ?proficiency ?proficiencyLabel WHERE { ?proficiency prop:P2 entity:Q5 . SERVICE wikibase:label { bd:serviceParam wikibase:language "' + userLanguage + ',fr,en" } } ORDER BY ?proficiency',
};
const htmlElements = {
'speaker': '#filteruser',
'gender': '#filtergender',
'language': '#filterlanguage',
'proficiency': '#filterlevelofproficiency',
};
const unknown = {};
var request = {
'speaker': '',
'gender': '',
'language': '',
'proficiency': '',
}, lastRequest = {
'speaker': '',
'gender': '',
'language': '',
'proficiency': '',
}, speakersCriteria = [];
const list = {
'speaker': null,
'gender': null,
'language': null,
'proficiency': null,
}, mapping = {
'speaker': null,
'gender': null,
'language': null,
'proficiency': null,
};
// Helper function to obtain the Qvalue of some selector
function getQValue( type ) {
var rawValue;
if( type === 'speaker' || type === 'language' ) {
rawValue = oouiSelectors[type].getValue();
} else {
rawValue = oouiSelectors[type].getMenu().findSelectedItem() ? oouiSelectors[type].getMenu().findSelectedItem().getData() : '';
}
rawValue = rawValue.trim();
if( !rawValue ) {
return '';
} else if( /^Q[0-9]+$/.test( rawValue ) ) {
return rawValue;
} else if( /\((Q[0-9]+)\)$/.test( rawValue ) ) {
return rawValue.replace( /.*\((Q[0-9]+)\)$/, '$1' );
} else {
return '';
}
}
// Helper function to dedupe a sorted list
function dedupe( arr, fn ) {
if( fn ) {
return arr.filter( function( x, i, a ) {
return !i || fn( x ) !== fn( a[i-1] );
} );
}
return arr.filter( function( x, i, a ) {
return !i || x !== a[i-1];
} );
}
// Returns the list of items of a given type for a restricted list of Qids
function getListSelectorFromQids( type, qids ) {
const indexes = qids.map( function( x ) {
return mapping[type][x];
} ).sort();
return indexes.map( function( x ) {
return list[type][x];
} );
}
// Initialise a selector and keep the list in memory the result for later reuse
function updateSelector( type ) {
return function( values ) {
const widgetType = type === 'speaker' || type === 'language' ? 'ComboBoxInputWidget' : 'DropdownWidget';
if( !list[type] ) {
// Save the list in a global variable to quickly filter then
// We dedupe because the 'speaker' (only) has duplicate (ordered) values
const v = values.results.bindings;
list[type] = dedupe( v, function( x ) { return x[type].value; } );
if( widgetType === 'DropdownWidget' ) {
var emptyItem = [ {} ];
emptyItem[0][type] = { value: '' };
emptyItem[0][type+'Label'] = { value: ' ' };
list[type] = emptyItem.concat( list[type] );
}
// Save the mapping Qxx → index to update the list from a list of Qxx (see doQuery)
console.log( type, list[type] );
mapping[type] = list[type].reduce( function( o, x, i ) {
o[ x[type].value.substr( 31 ) ] = i;
return o;
}, {} );
// Save the global matrix: speaker x gender x language x proficiency
if( type === 'speaker' ) {
speakersCriteria = v.map( function( x ) {
return [
x.speaker.value.substr( 31 ),
!x.gender || x.gender.type === 'bnode' ? unknown : x.gender.value.substr( 31 ),
!x.language || x.language.type === 'bnode' ? unknown : x.language.value.substr( 31 ),
!x.proficiency || x.proficiency.type === 'bnode' ? unknown : x.proficiency.value.substr( 31 ),
];
} );
}
}
// Create the OOUI selector
oouiSelectors[type] = new OO.ui[widgetType]( {
placeholder: messages['placeholder-'+type][userLanguage] ? messages['placeholder-'+type][userLanguage] : messages['placeholder-'+type].en,
menu: {
filterFromInput: true,
items: list[type].map( function( x ) {
return new OO.ui.MenuOptionWidget( {
data: widgetType === 'ComboBoxInputWidget' ? x[ type + 'Label' ].value + " (" + x[type].value.substr( 31 ) + ")" : x[type].value.substr( 31 ),
label: x[ type + 'Label' ].value
} );
} )
}
} );
if( widgetType === 'DropdownWidget' ) {
oouiSelectors[type].getMenu().items[0].toggle( false );
}
if( values.length === 1 ) {
if( widgetType === 'ComboBoxInputWidget' ) {
oouiSelectors[type].setValue( v[0][ type + 'Label' ].value + " (" + v[0][type].value.substr( 31 ) + ")" );
} else {
oouiSelectors[type].getMenu().selectItemByData( v[0][ type + 'Label' ].value + " (" + v[0][type].value.substr( 31 ) + ")" );
}
}
$( htmlElements[type] ).html('').append(
oouiSelectors[type].$element
);
//if( widgetType === 'ComboBoxInputWidget' ) {
// oouiSelectors[type].on( 'change', onChange );
//} else {
if( widgetType === 'DropdownWidget' ) {
oouiSelectors[type].getMenu().on( 'select', function( item ) {
if( item.getData() === '' ) {
item.toggle( false );
} else {
oouiSelectors[type].getMenu().items[0].toggle( true );
}
} );
}
};
}
function initSelectors() {
$.getJSON(
'https://lingualibre.org/bigdata/namespace/wdq/sparql',
{
query: sparqlGlobal['speaker']
}
).done( updateSelector( 'speaker' ) );
$.getJSON(
'https://lingualibre.org/bigdata/namespace/wdq/sparql',
{
query: sparqlGlobal['gender']
}
).done( updateSelector( 'gender' ) );
$.getJSON(
'https://lingualibre.org/bigdata/namespace/wdq/sparql',
{
query: sparqlGlobal['language']
}
).done( updateSelector( 'language' ) );
$.getJSON(
'https://lingualibre.org/bigdata/namespace/wdq/sparql',
{
query: sparqlGlobal['proficiency']
}
).done( updateSelector( 'proficiency' ) );
}
function reset() {
lastRequest = {
speaker: '',
gender: '',
language: '',
proficiency: '',
};
updateSelector( 'speaker' )( list.speaker );
updateSelector( 'gender' )( list.gender );
updateSelector( 'language' )( list.language );
updateSelector( 'proficiency' )( list.proficiency );
$( '#audioresults' ).html( '' );
}
function reduceSpeakers( index, value, arr ) {
return arr.filter( function( x ) {
return x[index] === value;
} );
}
// Add button 'Search'
$( '#gosearch' ).html('').append(
( new OO.ui.ButtonInputWidget( {
label: messages['button-gosearch'][userLanguage] ? messages['button-gosearch'][userLanguage] : messages['button-gosearch'].en
} ) ).on( 'click', doQuery ).$element
);
// Add button 'Reset'
$( '#resetsearch' ).html('').append(
( new OO.ui.ButtonInputWidget( {
label: messages['button-resetsearch'][userLanguage] ? messages['button-resetsearch'][userLanguage] : messages['button-resetsearch'].en
} ) ).on( 'click', reset ).$element
);
// Display results
function createAudioBoxesForSearch( data ) {
if ( data.results === undefined || data.results.bindings === undefined ) {
displayError( 'error: no result from SPARQL' );
return;
}
if ( data.results.bindings.length < 1 ) {
$( '#audioresults' ).html( messages['msg-no-results'][userLanguage] ? messages['msg-no-results'][userLanguage] : messages['msg-no-results'].en );
return;
}
var length = data.results.bindings.length;
console.log( data.results.bindings );
function displayAudioBoxes( index ) {
$( '#audioresults' ).html( '' );
console.log( 'index', index );
for( var i = 0; i < 10 && i+index*10 < length; i++ ) {
console.log( 'i effectif', i+index*10 );
var box = $( '<div class="audiobox"> <div class="ab-playbutton"><i></i></div> <div> <div class="ab-title">...</div> <div class="ab-metadata">...</div> </div> </div>' );
var audiobox = new AudioBox( data.results.bindings[ i+index*10 ].record.value.substr( 31 ), box );
$( '#audioresults' ).append( box );
}
function applyFn( i ) {
return function() {
console.log( i );
displayAudioBoxes( i );
return false;
}
}
for( var i = 0; i < Math.ceil( length/10 ); i++ ) {
var link = $( '<a href="#">' + (i+1) + '</a>' ).on( 'click', applyFn( i ) );
$( '#audioresults' ).append( link ).append( ' ' );
}
}
displayAudioBoxes( 0 );
}
// Do SPARQL request from filters
function doQuery() {
request = {
speaker: getQValue( 'speaker' ),
gender: getQValue( 'gender' ),
language: getQValue( 'language' ),
proficiency: getQValue( 'proficiency' ),
};
if( JSON.stringify( request ) === JSON.stringify( lastRequest ) ) {
return;
}
lastRequest = request;
console.log( request );
// When all fields are empty, do not query
if( !request.speaker && !request.gender && !request.language && !request.proficiency ) {
return;
}
var potentialSpeakers = speakersCriteria;
var query = "SELECT ?record WHERE { ?record prop:P2 entity:Q2 ; prop:P4 ?language ; prop:P5 ?speaker . ";
if( request.language ) {
query += "?record prop:P4 entity:" + request.language + " . ";
potentialSpeakers = reduceSpeakers( 2, request.language, potentialSpeakers );
}
if( request.speaker ) {
query += "?record prop:P5 entity:" + request.speaker + " . ";
potentialSpeakers = reduceSpeakers( 0, request.speaker, potentialSpeakers );
}
if( request.gender ) {
potentialSpeakers = reduceSpeakers( 1, request.gender, potentialSpeakers );
}
if( request.proficiency ) {
potentialSpeakers = reduceSpeakers( 3, request.proficiency, potentialSpeakers );
query += "?speaker llp:P4 [ llv:P4 ?language ; llq:P16 entity:" + request.proficiency + " ] . ";
}
if( potentialSpeakers.length < speakersCriteria.length ) {
var listSpeakers = dedupe( potentialSpeakers.map( function( x ) {
return 'entity:' + x[0];
} ) ).join();
query += 'FILTER( ?speaker IN (' + listSpeakers + ') )';
}
query += "} LIMIT 100";
// Voir https://commons.wikimedia.org/wiki/Category:Throbbers - il faut que le fond soit transparent
$( '#audioresults' ).html( '' ).append( '<img src="https://upload.wikimedia.org/wikipedia/commons/7/7a/Balls.gif" width="50" />' );
console.log( potentialSpeakers );
console.log( listSpeakers );
// Execute the request
var result = $.getJSON(
'https://lingualibre.org/bigdata/namespace/wdq/sparql',
{
query: query,
//Accept: 'application/sparql-results+json'
}
);
// Update the selectors with the restricted values
// This could have been done a bit earlier, but we launched the SPARQL request before
if( request.speaker ) {
updateSelector( 'speaker' )( getListSelectorFromQids( 'speaker', potentialSpeakers.map( function( x ) { return x[0]; } ) ) );
}
if( request.gender ) {
updateSelector( 'gender' )( getListSelectorFromQids( 'gender', potentialSpeakers.map( function( x ) { return x[1]; } ) ) );
}
if( request.language ) {
updateSelector( 'language' )( getListSelectorFromQids( 'language', potentialSpeakers.map( function( x ) { return x[2]; } ) ) );
}
if( request.proficiency ) {
updateSelector( 'proficiency' )( getListSelectorFromQids( 'proficiency', potentialSpeakers.map( function( x ) { return x[3]; } ) ) );
}
result.then( createAudioBoxesForSearch, displayError );
}
initSelectors();
} );
} );