mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-23 02:28:44 +02:00
Allow dashboard to restore state (either saved state or from backend). Restore old state on edition cancel
This commit is contained in:
@@ -39,7 +39,7 @@ class IboGridSlot extends HTMLElement {
|
|||||||
return oSlot;
|
return oSlot;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serialize() {
|
Serialize(bIncludeHtml = false) {
|
||||||
const oDashlet = this.oDashlet;
|
const oDashlet = this.oDashlet;
|
||||||
|
|
||||||
const aSlotData = {
|
const aSlotData = {
|
||||||
@@ -49,7 +49,7 @@ class IboGridSlot extends HTMLElement {
|
|||||||
height: this.iHeight
|
height: this.iHeight
|
||||||
};
|
};
|
||||||
|
|
||||||
const aDashletData = oDashlet ? oDashlet.Serialize() : {};
|
const aDashletData = oDashlet ? oDashlet.Serialize(bIncludeHtml) : {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...aSlotData,
|
...aSlotData,
|
||||||
|
|||||||
@@ -143,11 +143,16 @@ class IboGrid extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Serialize() {
|
|
||||||
|
ClearGrid() {
|
||||||
|
this.oGrid.removeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
Serialize(bIncludeHtml = false) {
|
||||||
const aSlots = this.getSlots();
|
const aSlots = this.getSlots();
|
||||||
|
|
||||||
return aSlots.reduce((aAccumulator, oSlot) => {
|
return aSlots.reduce((aAccumulator, oSlot) => {
|
||||||
aAccumulator[oSlot.oDashlet.sDashletId] = oSlot.Serialize();
|
aAccumulator[oSlot.oDashlet.sDashletId] = oSlot.Serialize(bIncludeHtml);
|
||||||
return aAccumulator;
|
return aAccumulator;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,10 @@ class IboDashboard extends HTMLElement {
|
|||||||
|
|
||||||
this.querySelector('[data-role="ibo-button"][name="cancel"]')?.addEventListener('click', (e) => {
|
this.querySelector('[data-role="ibo-button"][name="cancel"]')?.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// TODO 3.3: Implement cancel functionality (reload last saved state)
|
if (this.aLastSavedState) {
|
||||||
this.SetEditMode(false)
|
this.Load(this.aLastSavedState);
|
||||||
|
}
|
||||||
|
this.SetEditMode(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO 3.3 Add event listener to dashboard toggler to get custom/default dashboard switching
|
// TODO 3.3 Add event listener to dashboard toggler to get custom/default dashboard switching
|
||||||
@@ -84,7 +86,7 @@ class IboDashboard extends HTMLElement {
|
|||||||
if(this.bEditMode){
|
if(this.bEditMode){
|
||||||
// TODO 3.3 If we are in default dashboard display, change to custom to allow editing
|
// TODO 3.3 If we are in default dashboard display, change to custom to allow editing
|
||||||
// TODO 3.3 Get the custom dashboard and load it, show a tooltip on the dashboard toggler to explain that we switched to custom mode
|
// TODO 3.3 Get the custom dashboard and load it, show a tooltip on the dashboard toggler to explain that we switched to custom mode
|
||||||
this.aLastSavedState = this.Serialize();
|
this.aLastSavedState = this.Serialize(true);
|
||||||
this.setAttribute("data-edit-mode", "edit");
|
this.setAttribute("data-edit-mode", "edit");
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
@@ -250,11 +252,21 @@ class IboDashboard extends HTMLElement {
|
|||||||
this.oGrid.RemoveDashlet(sDashletId);
|
this.oGrid.RemoveDashlet(sDashletId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Serialize() {
|
RefreshFromBackend(bCustomDashboard = false) {
|
||||||
|
const sLoadDashboardUrl = GetAbsoluteUrlAppRoot() + `/pages/UI.php?route=dashboard.load&id=${this.sId}&custom=${bCustomDashboard ? 'true' : 'false'}`;
|
||||||
|
fetch(sLoadDashboardUrl)
|
||||||
|
.then(async oResponse => {
|
||||||
|
const oDashletData = await oResponse.json();
|
||||||
|
this.Load(oDashletData);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Serialize(bIncludeHtml = false) {
|
||||||
const sDashboardTitle = this.querySelector('.ibo-dashboard--form--inputs input[name="dashboard_title"]').value;
|
const sDashboardTitle = this.querySelector('.ibo-dashboard--form--inputs input[name="dashboard_title"]').value;
|
||||||
const sDashboardRefreshRate = this.querySelector('.ibo-dashboard--form--inputs select[name="refresh_interval"]').value;
|
const sDashboardRefreshRate = this.querySelector('.ibo-dashboard--form--inputs select[name="refresh_interval"]').value;
|
||||||
|
|
||||||
const aSerializedGrid = this.oGrid.Serialize();
|
const aSerializedGrid = this.oGrid.Serialize(bIncludeHtml);
|
||||||
return {
|
return {
|
||||||
schema_version: this.schemaVersion,
|
schema_version: this.schemaVersion,
|
||||||
id: this.sId,
|
id: this.sId,
|
||||||
@@ -275,8 +287,8 @@ class IboDashboard extends HTMLElement {
|
|||||||
const res = await data.json();
|
const res = await data.json();
|
||||||
if(res.status === 'ok') {
|
if(res.status === 'ok') {
|
||||||
CombodoToast.OpenToast(res.message, 'success');
|
CombodoToast.OpenToast(res.message, 'success');
|
||||||
|
this.aLastSavedState = this.Serialize(true);
|
||||||
this.SetEditMode(false);
|
this.SetEditMode(false);
|
||||||
this.aLastSavedState = aPayload;
|
|
||||||
} else {
|
} else {
|
||||||
CombodoToast.OpenToast(res.message, 'error');
|
CombodoToast.OpenToast(res.message, 'error');
|
||||||
}
|
}
|
||||||
@@ -284,105 +296,70 @@ class IboDashboard extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async Load(aSaveState) {
|
Load(aSaveState) {
|
||||||
// TODO 3.3: Implement loading dashboard state either from server or local payload
|
try {
|
||||||
// aSaveState expected shape (example):
|
// Validate schema version
|
||||||
// { schema_version: 1, id: "...", title: "...", refresh_rate: "...", dashlets: [ { x, y, w, h, id, type, formData, content? }, ... ] }
|
if (aSaveState.schema_version !== this.schemaVersion) {
|
||||||
|
CombodoToast.OpenToast('Somehow, we got an incompatible dashboard schema version.', 'error');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!aSaveState || typeof aSaveState !== 'object') {
|
// Update dashboard data
|
||||||
this.DisplayError('Invalid dashboard save state payload');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aSaveState.schema_version !== this.schemaVersion) {
|
|
||||||
this.DisplayError('Load schema version mismatch');
|
|
||||||
}
|
|
||||||
|
|
||||||
// set basic props
|
|
||||||
if (aSaveState.id) {
|
|
||||||
this.sId = aSaveState.id;
|
this.sId = aSaveState.id;
|
||||||
// keep element id attribute in sync
|
this.sTitle = aSaveState.title || "";
|
||||||
this.setAttribute('id', this.sId);
|
this.iRefreshRate = parseInt(aSaveState.refresh, 10) || 0;
|
||||||
}
|
|
||||||
if (typeof aSaveState.title !== 'undefined') {
|
|
||||||
this.sTitle = aSaveState.title;
|
|
||||||
const titleInput = this.querySelector('.ibo-dashboard--form--inputs input[name="dashboard_title"]');
|
|
||||||
if (titleInput) titleInput.value = this.sTitle;
|
|
||||||
}
|
|
||||||
if (typeof aSaveState.refresh_rate !== 'undefined') {
|
|
||||||
this.iRefreshRate = Number(aSaveState.refresh_rate) || 0;
|
|
||||||
const sel = this.querySelector('.ibo-dashboard--form--inputs select[name="refresh_interval"]');
|
|
||||||
if (sel) sel.value = aSaveState.refresh_rate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- clear existing grid --
|
// Update form inputs if they exist
|
||||||
this.SetupGrid();
|
const oTitleInput = this.querySelector('.ibo-dashboard--form--inputs input[name="dashboard_title"]');
|
||||||
if (this.oGrid) {
|
if (oTitleInput) {
|
||||||
this.oGrid.Clear();
|
oTitleInput.value = this.sTitle;
|
||||||
}
|
|
||||||
|
|
||||||
// -- iterate dashlets and re-create them --
|
|
||||||
const aDashlets = Array.isArray(aSaveState.dashlets) ? aSaveState.dashlets : [];
|
|
||||||
for (const slotPayload of aDashlets) {
|
|
||||||
// ensure minimal shape
|
|
||||||
const x = Number(slotPayload.x) || undefined;
|
|
||||||
const y = Number(slotPayload.y) || undefined;
|
|
||||||
const w = Number(slotPayload.w) || undefined;
|
|
||||||
const h = Number(slotPayload.h) || undefined;
|
|
||||||
|
|
||||||
// If the payload includes the full HTML markup for the dashlet, use it.
|
|
||||||
// Otherwise, attempt to fetch markup from server based on dashlet type.
|
|
||||||
let sDashletHtml = null;
|
|
||||||
if (slotPayload.content) {
|
|
||||||
sDashletHtml = slotPayload.content;
|
|
||||||
} else if (slotPayload.type) {
|
|
||||||
try {
|
|
||||||
// Use the same endpoint you use for new dashlet creation as a fallback.
|
|
||||||
// Your server may provide an endpoint that accepts type and returns pre-rendered HTML.
|
|
||||||
const sUrl = GetAbsoluteUrlAppRoot() + '/pages/UI.php?route=dashboard.new_dashlet&dashlet_class=' + encodeURIComponent(slotPayload.type);
|
|
||||||
const res = await fetch(sUrl);
|
|
||||||
if (res.ok) {
|
|
||||||
sDashletHtml = await res.text();
|
|
||||||
} else {
|
|
||||||
console.warn('Failed to fetch dashlet HTML for type', slotPayload.type, res.status);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Error fetching dashlet HTML for type', slotPayload.type, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if still missing markup, use a placeholder
|
const oRefreshSelect = this.querySelector('.ibo-dashboard--form--inputs select[name="refresh_interval"]');
|
||||||
if (!sDashletHtml) {
|
if (oRefreshSelect) {
|
||||||
sDashletHtml = `<div class="ibo-dashlet--placeholder">Missing dashlet content for type: ${slotPayload.type || 'unknown'}</div>`;
|
oRefreshSelect.value = aSaveState.refresh;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add dashlet at requested position/size
|
// Clear existing grid
|
||||||
const aOptions = {};
|
this.ClearGrid();
|
||||||
if (typeof x !== 'undefined') aOptions.x = x;
|
|
||||||
if (typeof y !== 'undefined') aOptions.y = y;
|
|
||||||
if (typeof w !== 'undefined') aOptions.w = w;
|
|
||||||
if (typeof h !== 'undefined') aOptions.h = h;
|
|
||||||
|
|
||||||
const oSlot = this.oGrid.AddDashlet(sDashletHtml, aOptions);
|
// Load dashlets
|
||||||
|
const aDashletSlots = aSaveState.pos_dashlets || {};
|
||||||
|
|
||||||
// If payload contains dashlet metadata (id/type/formData) — apply it
|
for (const [sDashletId, aDashletData] of Object.entries(aDashletSlots)) {
|
||||||
if (slotPayload.id || slotPayload.type) {
|
const iPosX = aDashletData.position_x;
|
||||||
const dashletElem = oSlot.oDashlet || oSlot.querySelector('ibo-dashlet');
|
const iPosY = aDashletData.position_y;
|
||||||
if (dashletElem) {
|
const iWidth = aDashletData.width;
|
||||||
if (slotPayload.id) dashletElem.sDashletId = slotPayload.id;
|
const iHeight = aDashletData.height;
|
||||||
if (slotPayload.type) dashletElem.type = slotPayload.type;
|
const aDashlet = aDashletData.dashlet;
|
||||||
if (slotPayload.formData) {
|
|
||||||
dashletElem.ApplyFormData(slotPayload.formData);
|
// We store the dashlet component in the HTML, not only the rendered dashlet
|
||||||
}
|
const sDashetHtml = aDashlet.html;
|
||||||
}
|
|
||||||
|
// Add dashlet to grid with its position and size
|
||||||
|
this.oGrid.AddDashlet(sDashetHtml, {
|
||||||
|
x: iPosX,
|
||||||
|
y: iPosY,
|
||||||
|
w: iWidth,
|
||||||
|
h: iHeight,
|
||||||
|
autoPosition: false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update last saved state
|
||||||
|
this.aLastSavedState = aSaveState;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading dashboard state:', error);
|
||||||
|
CombodoToast.OpenToast('Error loading dashboard state', 'error');
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Save loaded state as last saved so cancel can restore it
|
ClearGrid() {
|
||||||
this.aLastSavedState = aSaveState;
|
this.oGrid.ClearGrid();
|
||||||
|
|
||||||
// Exit edit-mode: loading a previous state implies not being in edit mode by default
|
|
||||||
this.SetEditMode(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayError(sMessage, sSeverity = 'error') {
|
DisplayError(sMessage, sSeverity = 'error') {
|
||||||
|
|||||||
@@ -49,14 +49,18 @@ class IboDashlet extends HTMLElement {
|
|||||||
return oDashlet;
|
return oDashlet;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serialize() {
|
Serialize(bIncludeHtml = false) {
|
||||||
// TODO 3.3 Should we use getters ?
|
// TODO 3.3 Should we use getters ?
|
||||||
const aDashletData = {
|
let aDashletData = {
|
||||||
id: this.sDashletId,
|
id: this.sDashletId,
|
||||||
type: this.sType,
|
type: this.sType,
|
||||||
properties: JSON.parse(this.formData),
|
properties: JSON.parse(this.formData),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if(bIncludeHtml) {
|
||||||
|
aDashletData.html = this.outerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
return aDashletData;
|
return aDashletData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user