MediaWiki:Gadget-CargoHelperCore.js

From Angelina Jordan Wiki
Revision as of 10:18, 16 November 2025 by Most2dot0 (talk | contribs) (CargoHelper core functionality for delayed on-demand loading only when requested by "Add video" click)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* Gadget-CargoHelperCore.js
   Heavy logic: dialog creation, Cargo queries, validation,
   insertion, form logic.
   Only loaded on demand by Gadget-CargoHelperLoader.js.
*/
( function () {
'use strict';

mw.loader.using([
    'oojs-ui-core',
    'oojs-ui-windows',
    'oojs-ui-widgets',
    'mediawiki.util',
    'mediawiki.api'
]).then(function () {

    // ------------------------------------------------------------
    // EXPORT ENTRYPOINT
    // ------------------------------------------------------------
    window.CargoHelper = window.CargoHelper || {};
    window.CargoHelper.openDialog = function () {

            // ----------------- Helpers -----------------

		    function luaCompatibleSanitize(title) {
		        title = String(title || '');
		        title = title.replace(/&[#\w]+;/g, '_');
		        title = title.replace(/[^\w_.-]/g, '_'); // keep alnum, underscore, hyphen, dot
		        return title;
		    }
		
		    function logError(tag, err) {
		        if (window.console && console.error) console.error(tag, err);
		    }
		
		    // fetch performances for this page (to list existing perfs)
			function fetchPerformancesForPage() {
			    // Decode page name like decode() does
			    var page = mw.config.get('wgPageName') || '';
			    page = page.replace(/_/g, ' '); // convert underscores to spaces
			    page = $('<textarea/>').html(page).text(); // decode HTML entities just in case
			
			    var api = new mw.Api();
			    return api.get({
			        action: 'cargoquery',
			        tables: 'Performances',
			        fields: 'perfID,event,date,contextstr',
			        where: 'song="' + page.replace(/"/g, '\\"') + '"',
			        order_by: 'perfID',
			        limit: 5000
			    }).then(function (data) {
			        if (!data.cargoquery) return [];
			        return data.cargoquery.map(r => r.title || {}).map(t => ({
			            perfID: t.perfID || '',
			            event: t.event || '',
			            date: t.date || '',
			            contextstr: t.contextstr || ''
			        }));
			    }).catch(function (err) { logError('fetchPerformancesForPage failed:', err); return []; });
			}
		
		    // fetch ALL distinct contextstr (complete strings) from Performances table
		    function fetchAllContexts() {
		        var api = new mw.Api();
		        // Cargo doesn't have DISTINCT keyword; grouping produces distinct values.
		        return api.get({
		            action: 'cargoquery',
		            tables: 'Performances',
		            fields: 'contextstr',
		            group_by: 'contextstr',
		            order_by: 'contextstr',
		            limit: 5000
		        }).then(function (data) {
		            if (!data.cargoquery) return [];
		            var arr = data.cargoquery.map(function (r) { return (r.title && r.title.contextstr) ? r.title.contextstr : ''; })
		                .filter(function (s) { return !!s; });
		            // preserve query order, already unique via group_by
		            return arr;
		        }).catch(function (err) { logError('fetchAllContexts failed:', err); return []; });
		    }
		
		    // compute next local numeric id
		    function computeNextLocalId(perfs, page) {
		        var base = luaCompatibleSanitize(page) + '_';
		        var max = 0;
		        perfs.forEach(function (p) {
		            var id = p.perfID || '';
		            if (id.indexOf(base) !== 0) return;
		            var tail = id.slice(base.length);
		            var m = tail.match(/^(\d+)/);
		            if (!m) return;
		            var n = parseInt(m[1], 10);
		            if (!isNaN(n) && n > max) max = n;
		        });
		        return String(max + 1);
		    }
		
		    // Build performance template invocation (literal wikitext)
		    function buildPerformanceTemplate(params) {
		        var lines = ['{{Performance'];
		        if (params.song) lines.push('|song=' + params.song);
		        if (params.event) lines.push('|event=' + params.event);
		        if (params.context) lines.push('|context=' + params.context);
		        if (params.date) lines.push('|date=' + params.date);
		        if (params.type) lines.push('|type=' + params.type);
		        if (params.pos) lines.push('|pos=' + params.pos);
		        if (params.withField) lines.push('|with=' + params.withField);
		        if (params.comment) lines.push('|comment=' + params.comment);
		        if (params.id) lines.push('|id=' + params.id);
		        lines.push('}}');
		        return lines.join('\n');
		    }
		
		    // Build video template invocation (literal wikitext)
		    function buildVideoTemplate(params) {
		        var lines = ['{{Video'];
		        if (params.pid) lines.push('|pid=' + params.pid);
		        if (params.url) lines.push('|url=' + params.url);
		        if (params.duration) lines.push('|duration=' + params.duration);
		        if (params.quality) lines.push('|quality=' + params.quality);
		        if (params.comment) lines.push('|comment=' + params.comment);
		        lines.push('}}');
		        return lines.join('\n');
		    }
		
		    // ----------------- Validation -----------------
		    function validIsoLeadingDate(s) {
		        // Accepts "YYYY", "YYYY-MM", "YYYY-MM-DD" at the start, optionally followed by space + freeform.
		        // Examples accepted: "2020", "2020-05", "2020-05-12", "2020-05-12 extra notes"
		        if (!s) return false;
		        var m = s.match(/^(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?(?:\s.*)?$/);
		        if (!m) return false;
		        // If month exists, must be 01-12; if day exists, must be 01-31 (basic check).
		        if (m[2]) {
		            var mm = parseInt(m[2], 10);
		            if (mm < 1 || mm > 12) return false;
		        }
		        if (m[3]) {
		            var dd = parseInt(m[3], 10);
		            if (dd < 1 || dd > 31) return false;
		        }
		        return true;
		    }
		    function validUrl(s) {
		        if (!s) return false;
		        // Accept absolute http(s) or protocol-relative //.
		        return /^(https?:\/\/|\/\/).+/i.test(s.trim());
		    }
		
		    // ----------------- OOUI dialog -----------------
		
		function openDialog(existingPerfs, allContexts) {
		    // ------------------- Widgets -------------------
		    var modeSelect = new OO.ui.DropdownInputWidget({
		        options: [
		            { data: 'existing', label: 'Use existing performance' },
		            { data: 'new', label: 'Create new performance' }
		        ],
		        value: existingPerfs.length ? 'existing' : 'new'
		    });
		
		    var existingPerfDropdown = null;
		    if (existingPerfs.length) {
		        existingPerfDropdown = new OO.ui.DropdownInputWidget({
		            options: existingPerfs.map(function(p) {
		                var lastUnd = (p.perfID || '').lastIndexOf('_');
		                var localId = (lastUnd >= 0) ? p.perfID.slice(lastUnd + 1) : p.perfID;
		                return { data: p.perfID, label: localId + ' — ' + (p.event || '') + ' (' + (p.date || '') + ')' };
		            }),
		            placeholder: 'Choose existing performance'
		        });
		    }
		
		    var eventInput = new OO.ui.TextInputWidget();
		    var dateInput = new OO.ui.TextInputWidget();
		    var contextCombo = new OO.ui.ComboBoxInputWidget({
		        options: allContexts.map(c => ({ data: c, label: c })),
		        placeholder: 'Type or pick a full context string'
		    });
		    var originalContextOptions = allContexts.map(c => ({ data: c, label: c }));
		
		    // Dynamic filter for context combo
		    contextCombo.on('input', function(value) {
		        var valLower = value.toLowerCase();
		        contextCombo.clearOptions();
		        originalContextOptions.forEach(function(opt) {
		            if (opt.data.toLowerCase().includes(valLower)) {
		                contextCombo.addOption({ data: opt.data, label: opt.label });
		            }
		        });
		    });
		
		    var typeInput = new OO.ui.DropdownInputWidget({
		        options: [
		            { data: '', label: '—' },
		            { data: 'recording', label: 'recording' },
		            { data: 'track', label: 'track' },
		            { data: 'music-video', label: 'music-video' },
		            { data: 'live', label: 'live' },
		            { data: 'rehearsal', label: 'rehearsal' },
		            { data: 'soundcheck', label: 'soundcheck' },
		            { data: 'sing-along', label: 'sing-along' },
		            { data: 'lip-sync', label: 'lip-sync' }
		        ]
		    });
		    var posInput = new OO.ui.TextInputWidget();
		    var withInput = new OO.ui.TextInputWidget({ multiline: true });
		    var commentPInput = new OO.ui.TextInputWidget({ multiline: true });
		
		    var urlInput = new OO.ui.TextInputWidget();
		    var hr = new OO.ui.LabelWidget( { label: $( '<hr>' ).attr('style', 'color: gray' ) });  // horizontal line separator
		    var durationInput = new OO.ui.DropdownInputWidget({
		        options: [
		            { data: '', label: '—' },
		            { data: 'full', label: 'full' },
		            { data: 'short', label: 'short' },
		            { data: 'fragment', label: 'fragment' }
		        ]
		    });
		    var qualityInput = new OO.ui.DropdownInputWidget({
		        options: [
		            { data: '', label: '—' },
		            { data: 'best', label: 'best' },
		            { data: 'good', label: 'good' },
		            { data: 'acceptable', label: 'acceptable' },
		            { data: 'poor', label: 'poor' }
		        ]
		    });
		    var commentVInput = new OO.ui.TextInputWidget({ multiline: true });
		
		    // ------------------- Layout -------------------
		    var form = new OO.ui.FieldsetLayout({ label: 'Add Video / Performance' });
		    form.addItems([ new OO.ui.FieldLayout(modeSelect, { label: 'Mode' }) ]);
		    if (existingPerfDropdown) form.addItems([ new OO.ui.FieldLayout(existingPerfDropdown, { label: 'Existing performance', align: 'top' }) ]);
		
		    form.addItems([
		        new OO.ui.FieldLayout(eventInput, { label: 'Event' }),
		        new OO.ui.FieldLayout(dateInput, { label: 'Date' }),
		        new OO.ui.FieldLayout(contextCombo, { label: 'Context (complete string, # separated)', align: 'top' }),
		        new OO.ui.FieldLayout(typeInput, { label: 'Type' }),
		        new OO.ui.FieldLayout(posInput, { label: 'Position' }),
		        new OO.ui.FieldLayout(withInput, { label: 'With (partners)' }),
		        new OO.ui.FieldLayout(commentPInput, { label: 'Performance comment' }),
		        hr,
		        new OO.ui.FieldLayout(urlInput, { label: 'Video URL' }),
		        new OO.ui.FieldLayout(durationInput, { label: 'Duration' }),
		        new OO.ui.FieldLayout(qualityInput, { label: 'Quality' }),
		        new OO.ui.FieldLayout(commentVInput, { label: 'Video comment' })
		    ]);
		
		    var submitButton = new OO.ui.ButtonWidget({ label: 'Insert', flags: ['primary'] });
		    var cancelButton = new OO.ui.ButtonWidget({ label: 'Cancel' });
		    form.$element.append(submitButton.$element).append(cancelButton.$element);
		
		    var panel = new OO.ui.PanelLayout({ expanded: true, padded: true, content: [ form ] });
		
		    // ------------------- Dialog -------------------
		    function AddVideoDialog(config) {
		        AddVideoDialog.super.call(this, config);
		    }
		    OO.inheritClass(AddVideoDialog, OO.ui.ProcessDialog);
		    AddVideoDialog.static.name = 'AddVideoDialog';
		    AddVideoDialog.static.title = 'Add Video / Performance';
		    AddVideoDialog.prototype.initialize = function () {
		        AddVideoDialog.super.prototype.initialize.call(this);
		        this.$body.append(panel.$element);
		        this.$element.css({ width: '900px', 'max-width': '95%', left: '50%', transform: 'translateX(-50%)' });
		    };
		
		    var windowManager = new OO.ui.WindowManager();
		    $(document.body).append(windowManager.$element);
		    var dlg = new AddVideoDialog();
		    windowManager.addWindows([ dlg ]);
		    windowManager.openWindow(dlg);
		
		    // ------------------- Mode logic -------------------
		    // Enable/disable fields based on mode
		    function updateFields() {
		        var isExisting = modeSelect.getValue() === 'existing';
		
		        if (existingPerfDropdown) {
		            existingPerfDropdown.setDisabled(!isExisting);
		        }
		
		        if (isExisting) {
		            // Existing mode → grey fields, populate values
		            eventInput.setDisabled(true);
		            dateInput.setDisabled(true);
		            contextCombo.setDisabled(true);
		            typeInput.setDisabled(true);
		            posInput.setDisabled(true);
		            withInput.setDisabled(true);
		            commentPInput.setDisabled(true);
		
		            var selected = existingPerfDropdown ? existingPerfDropdown.getValue() : null;
		            var perf = existingPerfs.find(p => p.perfID === selected) || {};
		
		            eventInput.setValue(perf.event || '');
		            dateInput.setValue(perf.date || '');
		            contextCombo.setValue(perf.contextstr || '');
		            typeInput.setValue(perf.type || '');
		            posInput.setValue(perf.pos || '');
		            withInput.setValue(perf.with || '');
		            commentPInput.setValue(perf.comment || '');
		
		        } else {
		            // New mode → clear and enable fields
		            eventInput.setDisabled(false);
		            dateInput.setDisabled(false);
		            contextCombo.setDisabled(false);
		            typeInput.setDisabled(false);
		            posInput.setDisabled(false);
		            withInput.setDisabled(false);
		            commentPInput.setDisabled(false);
		
		            // Clear only performance-related fields
		            eventInput.setValue('');
		            dateInput.setValue('');
		            contextCombo.setValue('');
		            typeInput.setValue('');
		            posInput.setValue('');
		            withInput.setValue('');
		            commentPInput.setValue('');
		        }
		    }
		    modeSelect.on('change', updateFields);
		    if (existingPerfDropdown) existingPerfDropdown.on('change', updateFields);
		    updateFields();
		
		    // ------------------- Helper functions -------------------
		    function buildPerformanceTemplate(params) {
		        var fields = [];
		        if (params.song) fields.push("song=" + params.song);
		        if (params.event) fields.push("event=" + params.event);
		        if (params.context) fields.push("context=" + params.context);
		        if (params.date) fields.push("date=" + params.date);
		        if (params.type) fields.push("type=" + params.type);
		        if (params.pos) fields.push("pos=" + params.pos);
		        if (params.withField) fields.push("with=" + params.withField);
		        if (params.comment) fields.push("comment=" + params.comment);
		        if (params.id) fields.push("id=" + params.id);
		        return "{{Performance|" + fields.join('|') + "}}";
		    }
		
		    function buildVideoTemplate(params) {
		        var fields = [];
		        if (params.pid) fields.push("pid=" + params.pid);
		        if (params.url) fields.push("url=" + params.url);
		        if (params.duration) fields.push("duration=" + params.duration);
		        if (params.quality) fields.push("quality=" + params.quality);
		        if (params.comment) fields.push("comment=" + params.comment);
		        return "{{Video|" + fields.join('|') + "}}";
		    }
		
		    function insertAtCursor(textarea, newText) {
		        var start = textarea.selectionStart;
		        var end = textarea.selectionEnd;
		        var original = textarea.value;
		        textarea.value = original.slice(0, start) + newText + original.slice(end);
		        textarea.selectionStart = textarea.selectionEnd = start + newText.length;
		        textarea.scrollTop = textarea.scrollHeight;
		    }
		
			function formatForInsertion(perfBlock, videoBlock, textarea) {
			    var start = textarea.selectionStart;
			    var textBefore = textarea.value.slice(0, start);
			    var atLineStart = textBefore.endsWith('\n') || textBefore === '';
			
			    if (atLineStart) {
			        if (perfBlock) {
			            // only add bullets if perfBlock exists
			            return "* " + perfBlock + "\n*: " + videoBlock;
			        } else {
			            // only video block, no bullet for perf
			            return videoBlock;
			        }
			    } else {
			        // no bullets if not at line start
			        return (perfBlock || '') + videoBlock;
			    }
			}
		
		    // ------------------- Submit (with improved validation) -------------------
		    submitButton.on('click', function () {
		        try {
		            // Clear previous inline error styles
		            function clearInvalid(widget) {
		                if (widget && widget.$input) {
		                    widget.$input.css('outline', '').css('box-shadow', '');
		                } else if (widget && widget.$element) {
		                    widget.$element.css('outline', '').css('box-shadow', '');
		                }
		            }
		            [ eventInput, dateInput, contextCombo, urlInput, posInput, withInput, commentPInput, commentVInput ].forEach(clearInvalid);
		
		            // helper to mark invalid and attach handler to clear when edited
		            function markInvalid(widget) {
		                if (!widget) return;
		                if (widget.$input) {
		                    widget.$input.css('outline', '2px solid #d33').css('box-shadow', '0 0 0 2px rgba(211,51,51,0.08)');
		                    widget.$input.one('input', function () { widget.$input.css('outline', '').css('box-shadow', ''); });
		                } else if (widget.$element) {
		                    widget.$element.css('outline', '2px solid #d33').css('box-shadow', '0 0 0 2px rgba(211,51,51,0.08)');
		                    widget.$element.one('input', function () { widget.$element.css('outline', '').css('box-shadow', ''); });
		                }
		            }
		
		            // Validation helpers (reuse existing validators)
		            function isValidIsoLeadingDate(s) {
		                if (!s) return false;
		                var m = s.match(/^(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?(?:\s.*)?$/);
		                if (!m) return false;
		                if (m[2]) {
		                    var mm = parseInt(m[2], 10);
		                    if (mm < 1 || mm > 12) return false;
		                }
		                if (m[3]) {
		                    var dd = parseInt(m[3], 10);
		                    if (dd < 1 || dd > 31) return false;
		                }
		                return true;
		            }
		            function isValidUrl(s) {
		                if (!s) return false;
		                return /^(https?:\/\/|\/\/).+/i.test(s.trim());
		            }
		
		            // gather values
		            var page = mw.config.get('wgPageName');
		            var mode = modeSelect.getValue();
		            var pidLocal;
		
		            // determine pid
		            if (mode === 'existing') {
		                if (!existingPerfDropdown) { alert('No existing performance to choose.'); return; }
		                var fullPerfID = existingPerfDropdown.getValue();
		                if (!fullPerfID) { alert('Please select an existing performance.'); return; }
		                var lastUnd = fullPerfID.lastIndexOf('_');
		                pidLocal = (lastUnd >= 0) ? fullPerfID.slice(lastUnd + 1) : fullPerfID;
		            } else {
		                pidLocal = computeNextLocalId(existingPerfs || [], page);
		            }
		
		            // run field validation and collect errors
		            var errors = [];
		
		            // URL is always required
		            var urlVal = urlInput.getValue() || '';
		            if (!isValidUrl(urlVal)) {
		                errors.push('Video URL is required and must start with "http://", "https://" or "//".');
		                markInvalid(urlInput);
		            }
		
		            // If new performance, date is required and must be valid ISO-leading
		            var perfDate = dateInput.getValue() || '';
		            if (mode === 'new') {
		                if (!perfDate) {
		                    errors.push('Date is required when creating a new performance.');
		                    markInvalid(dateInput);
		                } else if (!isValidIsoLeadingDate(perfDate)) {
		                    errors.push('Date must start with an ISO date (YYYY, YYYY-MM or YYYY-MM-DD).');
		                    markInvalid(dateInput);
		                }
		            }
		
		            // optionally check that context is present when creating new perf
		            var ctxVal = contextCombo.getValue() || '';
		            if (mode === 'new' && !ctxVal) {
		                errors.push('Context is required when creating a new performance.');
		                markInvalid(contextCombo);
		            }
		
		            // If errors, show OOUI dialog and focus first invalid
		            if (errors.length) {
		                // Message dialog
		                function ValidationDialog(config) { ValidationDialog.super.call(this, config); }
		                OO.inheritClass(ValidationDialog, OO.ui.MessageDialog);
		                ValidationDialog.static.name = 'ValidationDialog';
		                ValidationDialog.static.title = 'Validation error';
		                ValidationDialog.static.actions = [
		                    { action: 'close', label: 'Close', flags: ['primary'] }
		                ];
		
		                var wm = new OO.ui.WindowManager();
		                $(document.body).append(wm.$element);
		                var dlgVal = new ValidationDialog();
		                wm.addWindows([dlgVal]);
		                wm.openWindow(dlgVal, {
		                    message: errors.join('\n')
		                });
		
		                // focus first invalid input after the dialog opens
		                var toFocus = null;
		                if (!isValidUrl(urlVal)) toFocus = urlInput;
		                else if (mode === 'new' && (!perfDate || !isValidIsoLeadingDate(perfDate))) toFocus = dateInput;
		                else if (mode === 'new' && !ctxVal) toFocus = contextCombo;
		
		                if (toFocus) {
		                    // small timeout to wait until dialog is visible
		                    setTimeout(function () {
		                        if (toFocus.focus) toFocus.focus();
		                        else if (toFocus.$input) toFocus.$input.focus();
		                    }, 50);
		                }
		                return;
		            }
		
		            // Build single-line templates
		            var perfBlock = '';
		            if (mode === 'new') {
		                // single-line performance template
		                var perfFields = [];
		                if (page) perfFields.push('song=' + page);
		                if (eventInput.getValue()) perfFields.push('event=' + eventInput.getValue());
		                if (contextCombo.getValue()) perfFields.push('context=' + contextCombo.getValue());
		                if (dateInput.getValue()) perfFields.push('date=' + dateInput.getValue());
		                if (typeInput.getValue()) perfFields.push('type=' + typeInput.getValue());
		                if (posInput.getValue()) perfFields.push('pos=' + posInput.getValue());
		                if (withInput.getValue()) perfFields.push('with=' + withInput.getValue());
		                if (commentPInput.getValue()) perfFields.push('comment=' + commentPInput.getValue());
		                if (pidLocal) perfFields.push('id=' + pidLocal);
		                perfBlock = '{{Performance|' + perfFields.join('|') + '}}';
		            }
		
		            var videoFields = [];
		            if (pidLocal) videoFields.push('pid=' + pidLocal);
		            if (urlInput.getValue()) videoFields.push('url=' + urlInput.getValue());
		            if (durationInput.getValue()) videoFields.push('duration=' + durationInput.getValue());
		            if (qualityInput.getValue()) videoFields.push('quality=' + qualityInput.getValue());
		            if (commentVInput.getValue()) videoFields.push('comment=' + commentVInput.getValue());
		            var videoBlock = '{{Video|' + videoFields.join('|') + '}}';
		
		            // Compose combined text according to beginning-of-line rules
		            var combinedForPreload = (mode === 'new' ? '* ' + perfBlock + '\n*: ' : '') + videoBlock;
		
		            // Insert at cursor if editing, otherwise redirect with single preload param
		            var $textarea = $('#wpTextbox1');
		            if ($textarea.length) {
		                var insertText;
		                // test if cursor at line start
		                var ta = $textarea[0];
		                var start = ta.selectionStart;
		                var textBefore = ta.value.slice(0, start);
		                var atLineStart = textBefore.endsWith('\n') || textBefore === '';
		                if (atLineStart) {
		                    insertText = (mode === 'new' ? '* ' + perfBlock + '\n*: ' : '') + videoBlock;
		                } else {
		                    insertText = (mode === 'new' ? perfBlock : '') + videoBlock;
		                }
		                insertAtCursor(ta, insertText);
		                windowManager.closeWindow(dlg);
		            } else {
		                var url = mw.util.getUrl(page, {
		                    action: 'edit',
		                    section: 'new',
		                    preload: 'Template:CargoAddVideoPreload',
		                    'preloadparams[]': [ combinedForPreload ]
		                });
		                window.location.href = url;
		            }
		
		        } catch (err) {
		            logError('Submit handler failed:', err);
		            alert('Error while preparing insertion (see console).');
		        }
		    });
		
		    // ------------------- Cancel -------------------
		    cancelButton.on('click', function() {
		        windowManager.closeWindow(dlg);
		    });
		}
		
		    // ----------------- Wiring / entry -----------------
		
		    // Keep a cached copy of perfs for id computation
		    var existingPerfsArray = [];
		
		    function onAddClick() {
		        // fetch both page-specific performances (for existing selection) and all contexts (global)
		        var p1 = fetchPerformancesForPage();
		        var p2 = fetchAllContexts();
		        Promise.all([p1, p2]).then(function (res) {
		            existingPerfsArray = res[0] || [];
		            var allContexts = res[1] || [];
		            openDialog(existingPerfsArray, allContexts);
		        }).catch(function (err) {
		            logError('Failed to fetch data for dialog:', err);
		            alert('Failed to load data (see console).');
		        });
		    }
		
        //---------------------------------------------------------
        // 1) Run initial Cargo loads + open the dialog
        //---------------------------------------------------------

        Promise.all([
            fetchPerformancesForPage(),
            fetchAllContexts()
        ]).then(function (res) {
            var existingPerfsArray = res[0] || [];
            var allContexts = res[1] || [];
            openDialog(existingPerfsArray, allContexts);
        }).catch(function (err) {
            console.error('CargoHelperCore: failed to load data: ', err);
            alert('Failed to load data (see console).');
        });

    }; // end openDialog

}); // mw.loader.using
}());