(function() {
/**
* @typedef {Object} Help4.widget.tourlist.Data.Params
* @property {Help4.widget.tourlist.Widget} widget - the owner tour widget
*/
/**
* @typedef {Object} Help4.widget.tourlist.Catalogues
* @property {string[]} pub - public catalogue projects for end-users
* @property {string[]} head - non-public catalogue projects for authors
*/
/**
* Data handler for tourlist widget.
* @augments Help4.jscore.DataBase
* @property {Help4.widget.tourlist.Catalogues} catalogues
* @property {Help4.widget.tourlist.Widget} __widget
*/
Help4.widget.tourlist.Data = class extends Help4.jscore.DataBase {
/**
* @override
* @param {Help4.widget.tourlist.Data.Params} params
*/
constructor(params) {
/** @type {Help4.widget.tourlist.CatalogueTypeExtension} */
const T = Help4.jscore.DataBase.TYPES;
super(params, {
params: {
widget: {type: T.instance, mandatory: true, private: true, readonly: true}
},
data: {
catalogues: {type: T.widgetTourCatalogue}
}
});
}
/**
* get tours from help catalogue
* @param {Help4.widget.Widget} widget
* @param {function(Help4.widget.help.CatalogueProject): boolean} filter
* @returns {Help4.widget.tourlist.Catalogues}
*/
static getTours(widget, filter) {
const context = widget.getContext();
const {
configuration,
widget: {help: {data: {
/** @type {Help4.widget.help.Catalogues} */ catalogues
}}}
} = /** @type {Help4.widget.help.Data} */ context;
const {
pub: {/** @type {Help4.widget.help.CatalogueProject[]} */ projects: pp},
head: {/** @type {Help4.widget.help.CatalogueProject[]} */ projects: hp}
} = catalogues;
let pub = /** @type {Help4.widget.help.CatalogueProject[]} */ pp.filter(filter);
let head = /** @type {Help4.widget.help.CatalogueProject[]} */ hp.filter(filter);
if (configuration.core.mixedLanguages) {
// sort lists by language priority
pub = _sortToursByLanguage(pub, configuration)
head = _sortToursByLanguage(head, configuration)
} else {
// accept only one language
pub = _filterToursByLanguage(pub, configuration);
head = _filterToursByLanguage(head, configuration);
}
const pubList = /** @type {string[]} */ _removeDuplicates(pub)
.map(({id}) => id);
const headList = /** @type {string[]} */ _removeDuplicates(head)
.map(({id}) => id);
return {pub: pubList, head: headList};
}
/** updates tour list based on catalogue from help widget */
updateCatalogue() {
const {Data} = Help4.widget.tourlist;
const {/** @type {Help4.widget.tourlist.Widget} */ __widget} = this;
/**
* will get all tours from help catalogue in case they<br>
* - are not extended (EXT for tour completely overrides; therefore ignore extended tours)<br>
* - not filtered out by extension<br>
* - not whatsnew
* @param {Help4.widget.help.CatalogueProject} project
* @returns {boolean}
*/
const getTours = ({contextType: ct, _ext, _ignore, _whatsnew})=>
ct === 'TOUR' && _ext === undefined && !_ignore && !_whatsnew;
const {pub, head} = Data.getTours(__widget, getTours);
this.catalogues = {pub, head};
}
/**
* @param {string} projectId
* @param {Help4.widget.help.CatalogueKeys} catalogueKey
* @returns {?Help4.widget.help.CatalogueProject}
*/
getCatalogueProject(projectId, catalogueKey) {
// do not check whether project exists within this.catalogues as this.catalogues does not include
// - RO projects extended by RW
// - RW projects ignored through EXT
// allow to get those projects as well based on projectId
const {/** @type {Help4.widget.tourlist.Widget} */ __widget} = this;
const {/** @type {Help4.widget.help.Data} */ data} = __widget.getContext().widget.help;
return data.getCatalogueProject(projectId, catalogueKey);
}
}
/**
* @memberof Help4.widget.tourlist.Data
* @private
* @param {Help4.widget.help.CatalogueProject[]} list
* @param {Help4.typedef.SystemConfiguration} config
* @returns {Help4.widget.help.CatalogueProject[]}
*/
function _filterToursByLanguage(list, config) {
// get language lists for RO (UACP, SEN) and RW (SEN)
// could be different keys, therefore handle separately
const {roLangList, rwLangList} = _getLangLists(list, config);
// search for best language in both RO and RW individually
/** @type {Help4.widget.help.CatalogueProject[]} */ let ro = [];
/** @type {Help4.widget.help.CatalogueProject[]} */ let rw = [];
// run through language list; select best language only
const length = Math.max(roLangList.length, rwLangList.length);
for (let index = 0; index < length; index++) {
/** @type {?string} */ const roLang = roLangList[index];
/** @type {?string} */ const rwLang = rwLangList[index];
if (!ro.length) ro = list.filter(({_catalogueType: ct, language: l}) => ct !== 'SEN2' && l === roLang);
if (!rw.length) rw = list.filter(({_catalogueType: ct, language: l}) => ct === 'SEN2' && l === rwLang);
}
return [...ro, ...rw];
}
/**
* @memberof Help4.widget.tourlist.Data
* @private
* @param {Help4.widget.help.CatalogueProject[]} list
* @param {Help4.typedef.SystemConfiguration} config
* @returns {Help4.widget.help.CatalogueProject[]}
*/
function _sortToursByLanguage(list, config) {
// get language lists for RO (UACP, SEN) and RW (SEN)
// could be different keys, therefore handle separately
const {roLangList, rwLangList} = _getLangLists(list, config);
/** @type {Help4.widget.help.CatalogueProject[]} */ let sorted = [];
// run through language list; select best language only
const length = Math.max(roLangList.length, rwLangList.length);
for (let index = 0; index < length; index++) {
/** @type {?string} */ const roLang = roLangList[index];
/** @type {?string} */ const rwLang = rwLangList[index];
// filter current priority language
/** @type {Help4.widget.help.CatalogueProject[]} */
const filtered = list.filter(({language: l, _catalogueType: ct}) => ct === 'SEN2' ? l === rwLang : l === roLang);
// add to list; as we loop by priority this will order the list by priority
sorted.push(...filtered);
}
return sorted;
}
/**
* @memberof Help4.widget.tourlist.Data
* @private
* @param {Help4.widget.help.CatalogueProject[]} list
* @param {Help4.typedef.SystemConfiguration} config
* @returns {{roLangList: string[], rwLangList: string[]}}
*/
function _getLangLists(list, config) {
const {companionCore} = Help4.widget;
/** @type {Help4.widget.help.CatalogueProject} */ const roTour = list.find(({_catalogueType: ct}) => ct === 'UACP' || ct === 'SEN');
/** @type {string[]} */ const roLangList = roTour ? companionCore[roTour._dataType].getLanguages(config) : [];
/** @type {string[]} */ const rwLangList = companionCore.SEN.getLanguages(config);
return {roLangList, rwLangList};
}
/**
* remove duplicate tours; list needs to be in language priority order!
* @memberof Help4.widget.tourlist.Data
* @private
* @param {Help4.widget.help.CatalogueProject[]} list
* @returns {Help4.widget.help.CatalogueProject[]}
*/
function _removeDuplicates(list) {
if (list.length < 2) return list; // empty or single tour; nothing to do
/**
* @param {Help4.widget.help.CatalogueProject[]} list
* @param {number} start
* @param {Function} searchFn
* @returns {{duplicate: Help4.widget.help.CatalogueProject, index: number}|null}
*/
const find = (list, start, searchFn) => {
for (let index = ++start, duplicate; duplicate = list[index]; index++) {
if (searchFn(list[index])) return {duplicate: list[index], index};
}
return null;
}
// do NOT cache list.length; will otherwise become an infinite loop!
for (let i = 0; i < list.length; i++) {
const {cloneSrc, language} = /** @type {Help4.widget.help.CatalogueProject} */ list[i];
if (!cloneSrc) continue; // duplicates to be identified using cloneSrc
const {duplicate, index} =
find(list, i, ({id}) => id === cloneSrc) || // 1st: identify the tour with id === cloneSrc
find(list, i, ({cloneSrc: cs}) => cs === cloneSrc) || // 2nd: identify other tours sharing the same cloneSrc
{};
if (duplicate && duplicate.language !== language) { // duplicates do only exist in different languages
// remove the duplicate from lower priority languages
// the higher the index the lower the priority (data sorted in language priority)
// find(...) works forwards, beginning from i + 1
// -> every finding is behind and therefore LOWER PRIORITY than list[i]
list.splice(index, 1);
i--;
}
}
return list;
}
})();