Adds new JCB package engine. Fix issue with loading the Component Builder Wiki. Adds advanced version update notice to the Component Builder Dashboard. Completely refactors the class that builds the Component Dashboard. #1134. Adds Initialize, Reset, and Push functionality to the Repository entities. Completely refactors the SQL teaks and SQL dump classes. Changes J4 fields to allow NULL. Fix a bug in Dynamic Get JavaScript that causes table columns to not load.
413 lines
11 KiB
JavaScript
413 lines
11 KiB
JavaScript
/**
|
|
* @package Joomla.Component.Builder
|
|
*
|
|
* @created 30th April, 2015
|
|
* @author Llewellyn van der Merwe <https://dev.vdm.io>
|
|
* @git Joomla Component Builder <https://git.vdm.dev/joomla/Component-Builder>
|
|
* @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.
|
|
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
|
*/
|
|
|
|
/* JS Document */
|
|
|
|
const memoryinitialization = [];
|
|
|
|
function setSessionMemory(key, values, merge = true) {
|
|
if (merge) {
|
|
values = mergeSessionMemory(key, values);
|
|
} else {
|
|
values = JSON.stringify(values);
|
|
}
|
|
|
|
if (typeof Storage !== "undefined") {
|
|
sessionStorage.setItem(key, values);
|
|
} else {
|
|
memoryinitialization[key] = values;
|
|
}
|
|
}
|
|
|
|
function mergeSessionMemory(key, values) {
|
|
const oldValues = getSessionMemory(key);
|
|
|
|
if (oldValues) {
|
|
values = { ...oldValues, ...values };
|
|
}
|
|
|
|
return JSON.stringify(values);
|
|
}
|
|
|
|
function getSessionMemory(key, defaultValue = null) {
|
|
if (typeof Storage !== "undefined") {
|
|
const localValue = sessionStorage.getItem(key);
|
|
|
|
if (isJsonString(localValue)) {
|
|
defaultValue = JSON.parse(localValue);
|
|
}
|
|
} else if (typeof memoryinitialization[key] !== "undefined") {
|
|
const localValue = memoryinitialization[key];
|
|
|
|
if (isJsonString(localValue)) {
|
|
defaultValue = JSON.parse(localValue);
|
|
}
|
|
}
|
|
|
|
return defaultValue;
|
|
}
|
|
|
|
function isJsonString(str) {
|
|
try {
|
|
JSON.parse(str);
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function getArrayFormat(items) {
|
|
// Check if items is an object and not an array
|
|
if (typeof items === 'object' && !Array.isArray(items)) {
|
|
return Object.values(items);
|
|
}
|
|
return items;
|
|
}
|
|
class InitializationManager {
|
|
#repoArea = document.getElementById('select-repo-area');
|
|
#powersArea = document.getElementById('select-powers-area');
|
|
#initButton = document.getElementById('init-selected-powers');
|
|
#backButton = document.getElementById('back-to-select-repo');
|
|
#loadingDiv = window.loadingDiv || null;
|
|
#buildTable = typeof buildPowerSelectionTable === 'function' ? buildPowerSelectionTable : null;
|
|
#drawTable = typeof drawPowerSelectionTable === 'function' ? drawPowerSelectionTable : null;
|
|
|
|
currentRepo = null;
|
|
currentArea = null;
|
|
|
|
constructor() {
|
|
this._bindRepoButtons();
|
|
this._bindInitSelectedPowers();
|
|
this._updateInitButtonState();
|
|
}
|
|
|
|
/** Getter for selected items using global window reference. */
|
|
get selectedItems() {
|
|
if (!Array.isArray(window.selectedPowerItems)) {
|
|
window.selectedPowerItems = [];
|
|
}
|
|
return window.selectedPowerItems;
|
|
}
|
|
|
|
/** Setter for selected items with sync to window and button state update. */
|
|
set selectedItems(items) {
|
|
window.selectedPowerItems = items;
|
|
this._updateInitButtonState();
|
|
}
|
|
|
|
/** Add items to selection if not already selected. */
|
|
addSelectedItems(data) {
|
|
if (!data || typeof data.length !== 'number') return;
|
|
|
|
const updated = [...this.selectedItems];
|
|
|
|
for (let i = 0; i < data.length; i++) {
|
|
const item = data[i];
|
|
if (!updated.some(existing => this.#isSameItem(existing, item))) {
|
|
updated.push(item);
|
|
}
|
|
}
|
|
|
|
this.selectedItems = updated;
|
|
}
|
|
|
|
/** Remove items from selection based on identity comparison. */
|
|
removeSelectedItems(data) {
|
|
if (!data || typeof data.length !== 'number') return;
|
|
|
|
const updated = this.selectedItems.filter(existing => {
|
|
for (let i = 0; i < data.length; i++) {
|
|
if (this.#isSameItem(existing, data[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
|
|
this.selectedItems = updated;
|
|
}
|
|
|
|
/** Check if two items are the same using GUID. */
|
|
#isSameItem(a, b) {
|
|
return a?.guid && b?.guid && a.guid === b.guid;
|
|
}
|
|
|
|
/** Enable/disable the init button based on selection state. */
|
|
_updateInitButtonState() {
|
|
if (this.#initButton) {
|
|
this.#initButton.disabled = this.selectedItems.length === 0;
|
|
}
|
|
}
|
|
|
|
_bindRepoButtons() {
|
|
document.querySelectorAll('.select-repo-to-initialize').forEach(button => {
|
|
button.addEventListener('click', (e) => this._handleRepoClick(e));
|
|
});
|
|
}
|
|
|
|
_bindInitSelectedPowers() {
|
|
if (this.#initButton) {
|
|
this.#initButton.addEventListener('click', () => this._handleInitSelectedPowers());
|
|
}
|
|
if (this.#backButton) {
|
|
this.#backButton.addEventListener('click', () => this._handleBackToRepos());
|
|
}
|
|
}
|
|
|
|
_getInitFunctionName(area) {
|
|
const powers = [
|
|
'Joomla.Fieldtype',
|
|
'Joomla.Power',
|
|
'Repository',
|
|
'Power'
|
|
];
|
|
|
|
return powers.includes(area) ? 'initSelectedPowers' : 'initSelectedPackages';
|
|
}
|
|
|
|
async _handleRepoClick(event) {
|
|
const button = event.currentTarget;
|
|
const repo = button?.dataset?.repo;
|
|
const area = button?.dataset?.area;
|
|
|
|
if (!repo || !area) {
|
|
this._notify(Joomla.Text._("COM_COMPONENTBUILDER_MISSING_REPOSITORY_OR_AREA_DATA"), "danger");
|
|
return;
|
|
}
|
|
|
|
this._showLoading();
|
|
clearPowerSelectionTable();
|
|
|
|
const url = `${UrlAjax}getRepoIndex&repo=${encodeURIComponent(repo)}&area=${encodeURIComponent(area)}`;
|
|
|
|
try {
|
|
const response = await fetch(url);
|
|
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.index && this.#buildTable) {
|
|
const repoData = data.index[0];
|
|
const {
|
|
path = 'joomla/super-powers',
|
|
read_branch = 'master',
|
|
target,
|
|
base = 'https://git.vdm.dev'
|
|
} = repoData;
|
|
|
|
const repo_base = (target === 'github') ? 'https://github.com' : base;
|
|
const repo_path = (target === 'github') ? 'tree' : 'src/branch';
|
|
|
|
window.targetPowerRepoUrl = `${repo_base}/${path}/${repo_path}/${read_branch}/`;
|
|
|
|
this.#buildTable(repoData.index);
|
|
|
|
setTimeout(() => {
|
|
this._transitionTo(this.#repoArea, this.#powersArea);
|
|
this._hideLoading();
|
|
}, 500);
|
|
|
|
this.currentRepo = repoData.guid;
|
|
this.currentArea = area;
|
|
} else {
|
|
this._notify(data.message || Joomla.Text._("COM_COMPONENTBUILDER_FAILED_TO_RETRIEVE_REPOSITORY_INDEX"), "danger");
|
|
this._hideLoading();
|
|
}
|
|
} catch (error) {
|
|
console.error("Fetch error:", error);
|
|
this._hideLoading();
|
|
this._notify(Joomla.Text._("COM_COMPONENTBUILDER_NETWORK_OR_SERVER_ERROR_OCCURRED_WHILE_FETCHING_INDEX"), "danger");
|
|
}
|
|
}
|
|
|
|
_handleBackToRepos() {
|
|
this._transitionTo(this.#powersArea, this.#repoArea);
|
|
}
|
|
|
|
async _handleInitSelectedPowers() {
|
|
if (!Array.isArray(this.selectedItems) || this.selectedItems.length === 0) {
|
|
this._notify(Joomla.Text._("COM_COMPONENTBUILDER_NO_ITEMS_SELECTED"), "warning");
|
|
return;
|
|
}
|
|
|
|
this._showLoading();
|
|
|
|
const area = this.currentArea || 'error';
|
|
const repo = this.currentRepo || 'error';
|
|
|
|
const func = this._getInitFunctionName(area);
|
|
|
|
try {
|
|
// Convert selected items to form data
|
|
const formData = new FormData();
|
|
|
|
// Assuming Joomla expects `selected[]` for multiple values
|
|
for (const item of this.selectedItems) {
|
|
formData.append('selected[]', item.guid); // Only sending GUIDs
|
|
}
|
|
formData.append('area', area);
|
|
formData.append('repo', repo);
|
|
|
|
const response = await fetch(`${UrlAjax}${func}`, {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
|
|
const data = await response.json();
|
|
|
|
if (!data.success) {
|
|
this._notify(data.message || Joomla.Text._("COM_COMPONENTBUILDER_FAILED_TO_INITIALIZE_SELECTED_POWERS"), "danger");
|
|
} else {
|
|
this._handleResultLog(data.result_log || {});
|
|
}
|
|
|
|
this._hideLoading();
|
|
this._transitionTo(this.#powersArea, this.#repoArea);
|
|
} catch (error) {
|
|
console.error("Submission error:", error);
|
|
this._notify(Joomla.Text._("COM_COMPONENTBUILDER_ERROR_OCCURRED_WHILE_INITIALIZING_POWERS"), "danger");
|
|
|
|
this._hideLoading();
|
|
this._transitionTo(this.#powersArea, this.#repoArea);
|
|
}
|
|
}
|
|
|
|
_handleResultLog(resultLog) {
|
|
const localGuids = this._normalizeGuids(resultLog.local);
|
|
const notFoundGuids = this._normalizeGuids(resultLog.not_found);
|
|
const addedGuids = this._normalizeGuids(resultLog.added);
|
|
|
|
if (localGuids.length > 0) {
|
|
this._notify(
|
|
this._generateResultMessage(localGuids, 'local'),
|
|
'info'
|
|
);
|
|
}
|
|
|
|
if (notFoundGuids.length > 0) {
|
|
this._notify(
|
|
this._generateResultMessage(notFoundGuids, 'not_found'),
|
|
'info'
|
|
);
|
|
}
|
|
|
|
if (addedGuids.length > 0) {
|
|
this._notify(
|
|
this._generateResultMessage(addedGuids, 'added'),
|
|
'success'
|
|
);
|
|
}
|
|
}
|
|
|
|
_normalizeGuids(value) {
|
|
if (!value) return [];
|
|
|
|
if (Array.isArray(value)) {
|
|
return value;
|
|
}
|
|
|
|
if (typeof value === 'object') {
|
|
return Object.keys(value);
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
_generateResultMessage(guids, type) {
|
|
const messages = {
|
|
local: Joomla.Text._('COM_COMPONENTBUILDER_THESE_ITEMS_WERE_ALREADY_PRESENT_LOCALLY_AND_WERE_NOT_INITIALIZED'),
|
|
not_found: Joomla.Text._('COM_COMPONENTBUILDER_THESE_ITEMS_COULD_NOT_BE_FOUND_IN_THE_REMOTE_REPOSITORY_AND_WERE_NOT_INITIALIZED'),
|
|
added: Joomla.Text._('COM_COMPONENTBUILDER_THESE_ITEMS_WERE_SUCCESSFULLY_INITIALIZED')
|
|
};
|
|
|
|
const names = [];
|
|
|
|
for (const guid of guids) {
|
|
const item = this.selectedItems.find(i => i.guid === guid);
|
|
if (item?.name) {
|
|
names.push(item.name);
|
|
}
|
|
}
|
|
|
|
if (names.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
return `${messages[type]}\n<br>- ${names.join('\n<br>- ')}`;
|
|
}
|
|
|
|
_transitionTo(hideEl, showEl) {
|
|
if (hideEl && showEl) {
|
|
UIkit.util.ready(() => {
|
|
UIkit.util.removeClass(hideEl, 'uk-animation-slide-top-small');
|
|
UIkit.util.removeClass(showEl, 'uk-animation-slide-bottom-small');
|
|
UIkit.util.addClass(hideEl, 'uk-animation-slide-top-small');
|
|
|
|
setTimeout(() => {
|
|
hideEl.style.display = 'none';
|
|
showEl.style.display = '';
|
|
UIkit.util.addClass(showEl, 'uk-animation-slide-bottom-small');
|
|
if (this.#drawTable) this.#drawTable();
|
|
}, 300);
|
|
});
|
|
}
|
|
}
|
|
|
|
_showLoading() {
|
|
if (this.#loadingDiv) this.#loadingDiv.style.display = 'block';
|
|
}
|
|
|
|
_hideLoading() {
|
|
if (this.#loadingDiv) this.#loadingDiv.style.display = 'none';
|
|
}
|
|
|
|
_notify(message, type = 'info') {
|
|
const alertTypes = {
|
|
primary: 'alert-primary',
|
|
info: 'alert-info',
|
|
success: 'alert-success',
|
|
warning: 'alert-warning',
|
|
danger: 'alert-danger',
|
|
};
|
|
|
|
const alertClass = alertTypes[type] || alertTypes.primary;
|
|
|
|
let container = document.getElementById('alert-container');
|
|
if (!container) {
|
|
container = document.createElement('div');
|
|
container.id = 'alert-container';
|
|
container.className = 'position-fixed top-0 start-50 translate-middle-x p-3';
|
|
container.style.zIndex = '1060';
|
|
document.body.appendChild(container);
|
|
}
|
|
|
|
const alert = document.createElement('div');
|
|
alert.className = `alert ${alertClass} alert-dismissible fade show`;
|
|
alert.setAttribute('role', 'alert');
|
|
alert.innerHTML = `
|
|
${message}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
`;
|
|
|
|
container.appendChild(alert);
|
|
|
|
setTimeout(() => {
|
|
alert.classList.remove('show');
|
|
alert.classList.add('hide');
|
|
alert.addEventListener('transitionend', () => {
|
|
alert.remove();
|
|
});
|
|
}, 5000);
|
|
}
|
|
} |