Skip to content

Commit

Permalink
Modified dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
droberts-ctrlo committed Dec 11, 2024
1 parent 4799fc9 commit ca1332c
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 173 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// We're using the 'client' side of React, not server, nor native.
'use client'

import { Component } from 'component'
Expand All @@ -14,30 +15,22 @@ import "./react/polyfills/classlist";

import React from "react";
import {createRoot} from "react-dom/client";
import App from "./react/app";
import App from "./react/App";
import ApiClient from "./react/api";
import { fromJson } from 'util/common';
import {Layout} from "react-grid-layout";
import {DashboardDefinition} from "./react/interfaces/interfaces";

class DashboardComponent extends Component {
constructor(element) {
constructor(element: HTMLElement) {
super(element)
this.el = $(this.element)

this.gridConfig = {
cols: 2,
margin: [32, 32],
containerPadding: [0, 10],
rowHeight: 80,
};

this.initDashboard()
}

initDashboard() {
this.element.className = "";
const widgetsEls = Array.prototype.slice.call(document.querySelectorAll("#ld-app > div"));
const widgets = widgetsEls.map(el => ({
const widgets = widgetsEls.map((el:HTMLElement) => ({
html: el.innerHTML,
config: fromJson(el.getAttribute("data-grid")),
}));
Expand All @@ -47,14 +40,14 @@ class DashboardComponent extends Component {
<App
api={api}
currentDashboard={fromJson(this.element.getAttribute("data-dashboard"))}
dashboards={fromJson(this.element.getAttribute("data-dashboards"))}
dashboards={fromJson(this.element.getAttribute("data-dashboards")) as DashboardDefinition[]}
hideMenu={this.element.getAttribute("data-hide-menu") === "true"}
noDownload={this.element.getAttribute("data-no-download") === "true"}
readOnly={this.element.getAttribute("data-read-only") === "true"}
widgetTypes={fromJson(this.element.getAttribute("data-widget-types"))}
widgetTypes={fromJson(this.element.getAttribute("data-widget-types")) as string[]}
widgets={widgets}
key={this.element.getAttribute("data-dashboard")}
dashboardId={this.element.getAttribute("data-dashboard-id")} />
dashboardId={parseInt(this.element.getAttribute("data-dashboard-id"))} />
);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
'use client';

import React, { useEffect, useRef } from "react";
import React, {useEffect, useRef} from "react";
import serialize from "form-serialize";

import Modal from "react-modal";
import RGL, { WidthProvider } from "react-grid-layout";
import RGL from "react-grid-layout";

import Header from "./Header";
import Footer from "./Footer";
import { sidebarObservable } from '../../../sidebar/lib/sidebarObservable';
import { AppState, Widget } from "./interfaces/interfaces";
import { compare } from "util/common";
import {sidebarObservable} from '../../../sidebar/lib/sidebarObservable';
import {AppState, WidgetData} from "./interfaces/interfaces";
import {compare} from "util/common";
import Dashboard from "./Dashboard";

declare global {
Expand All @@ -21,28 +21,6 @@ declare global {
}
}

const ReactGridLayout = WidthProvider(RGL);

const modalStyle: Modal.Styles = {
content: {
minWidth: "350px",
maxWidth: "80vw",
maxHeight: "90vh",
top: "50%",
left: "50%",
right: "auto",
bottom: "auto",
marginRight: "-50%",
transform: "translate(-50%, -50%)",
msTransform: "translate(-50%, -50%)",
padding: 0
},
overlay: {
zIndex: 1030,
background: "rgba(0, 0, 0, .15)"
}
};

export default function App(state: AppState) {
const formRef = useRef<HTMLDivElement>();
const api = state.api;
Expand All @@ -56,7 +34,7 @@ export default function App(state: AppState) {

Modal.setAppElement("#ld-app");

const [widgets, setWidgets] = React.useState<Widget[]>(state.widgets);
const [widgets, setWidgets] = React.useState<WidgetData[]>(state.widgets);
const [loadingEditHtml, setLoadingEditHtml] = React.useState(false);
const [editError] = React.useState<string>("");
const [editHtml, setEditHtml] = React.useState<string>("");
Expand All @@ -66,9 +44,42 @@ export default function App(state: AppState) {
const [loading, setLoading] = React.useState(false);
// eslint-disable-next-line

useEffect(() => {
sidebarObservable.addSubscriber(()=> {
window.dispatchEvent(new Event('resize'));
});
initializeGlobeComponents();
}, [])

useEffect(() => {
window.requestAnimationFrame(overWriteSubmitEventListener);

if (modalOpen && !loadingEditHtml && formRef && formRef.current) {
initializeSummernoteComponent();
}

if (!modalOpen && !loadingEditHtml) {
initializeGlobeComponents();
}
}, [modalOpen, loadingEditHtml, formRef]);

const overWriteSubmitEventListener = () => {
const formContainer = document.getElementById("ld-form-container");
if (!formContainer) return

const form = formContainer.querySelector("form");
if (!form) return

form.addEventListener("submit", this.saveActiveWidget);
const submitButton = document.createElement("input");
submitButton.setAttribute("type", "submit");
submitButton.setAttribute("style", "visibility: hidden");
form.appendChild(submitButton);
}

const initializeGlobeComponents = () => {
const arrGlobe = document.querySelectorAll(".globe");
import('../../../globe/lib/component').then(({ default: GlobeComponent }) => {
import('../../../globe/lib/component').then(({default: GlobeComponent}) => {
arrGlobe.forEach((globe) => {
new GlobeComponent(globe)
});
Expand All @@ -79,19 +90,10 @@ export default function App(state: AppState) {
const summernoteEl = formRef.current.querySelector('.summernote');
if (summernoteEl) {
import(/* WebpackChunkName: "summernote" */ "../../../summernote/lib/component")
.then(({ default: SummerNoteComponent }) => new SummerNoteComponent(summernoteEl));
.then(({default: SummerNoteComponent}) => new SummerNoteComponent(summernoteEl));
}
};

const handleSideBarChange = () => {
window.dispatchEvent(new Event('resize'));
};

useEffect(() => {
sidebarObservable.addSubscriber({ handleSideBarChange });
initializeGlobeComponents();
}, [])

useEffect(() => {
if (modalOpen && !loadingEditHtml && formRef.current) {
initializeSummernoteComponent();
Expand All @@ -100,14 +102,9 @@ export default function App(state: AppState) {
}
}, [modalOpen, loadingEditHtml]);

useEffect(() => {
console.log("Widgets", widgets);
}, [widgets]);

const updateWidgetHtml = async (id:string) => {
console.log("Updating widget", id);
const updateWidgetHtml = async (id: string) => {
const newHtml = await api.getWidgetHtml(id);
const newWidgets = widgets.map((widget:Widget) => {
const newWidgets = widgets.map((widget: WidgetData) => {
if (widget.config.i === id) {
return {
config: widget.config,
Expand All @@ -119,8 +116,7 @@ export default function App(state: AppState) {
setWidgets(newWidgets);
};

const fetchEditForm = async (id:string) => {
console.log("Fetching edit form", id);
const fetchEditForm = async (id: string) => {
const editFormHtml = await api.getEditForm(id);
if (editFormHtml.is_error) {
setLoadingEditHtml(false);
Expand All @@ -130,59 +126,61 @@ export default function App(state: AppState) {
setEditHtml(editFormHtml.content);
};

const onEditClick = (id:string) => (event:React.MouseEvent) => {
console.log("Edit click", id);
const onEditClick = (id: string) => (event: React.MouseEvent) => {
event.preventDefault();
showEditForm(id);
};

const showEditForm = (id:string) => {
console.log("Showing edit form", id);
const showEditForm = (id: string) => {
setModalOpen(true);
setLoadingEditHtml(true);
setActiveItem(id);
// noinspection JSIgnoredPromiseFromCall
fetchEditForm(id);
};

const closeModal = () => setModalOpen(false);

const deleteActiveWidget = () => {
console.log("Deleting widget", activeItem);
// eslint-disable-next-line no-alert
if (!window.confirm("Deleting a widget is permanent! Are you sure?"))
return

setWidgets(widgets.filter(item => item.config.i !== activeItem));
setModalOpen(false);
// noinspection JSIgnoredPromiseFromCall
api.deleteWidget(activeItem);
}

const saveActiveWidget = async (event) => {
console.log("Saving widget", activeItem);
const saveActiveWidget = async (event: React.MouseEvent) => {
event.preventDefault();
if (!formRef) {
console.error("No form ref was found!");
return;
}
if (!formRef.current) {
console.error("No form ref current was found!");
return;
}
const formEl = formRef.current.querySelector("form");
if (!formEl) {
// eslint-disable-next-line no-console
console.error("No form element was found!");
return;
}

const form = serialize(formEl, { hash: true });
const form = serialize(formEl, {hash: true});
const result = await api.saveWidget(formEl.getAttribute("action"), form);
if (result.is_error) {
// eslint-disable-next-line no-console
console.error(result.message);
return;
}

updateWidgetHtml(activeItem);
await updateWidgetHtml(activeItem);
closeModal();
}

const isGridConflict = (x: number, y: number, w: number, h: number): boolean => {
console.log("Checking grid conflict", x, y, w, h);
const ulc = { x, y };
const drc = { x: x + w, y: y + h };
const ulc = {x, y};
const drc = {x: x + w, y: y + h};

return widgets.some((widget: any) => {
if (ulc.x >= (widget.x + widget.h) || widget.x >= drc.x) {
Expand All @@ -193,7 +191,6 @@ export default function App(state: AppState) {
}

const firstAvailableSpot = (w: number, h: number) => {
console.log("Finding first available spot", w, h);
let x = 0;
let y = 0;
while (isGridConflict(x, y, w, h)) {
Expand All @@ -205,11 +202,10 @@ export default function App(state: AppState) {
}
if (y > 200) break;
}
return { x, y };
return {x, y};
}

const addWidget = async (type: string) => {
console.log("Adding widget", type);
setLoading(true);
const result = await api.createWidget(type)
if (result.error) {
Expand All @@ -219,8 +215,8 @@ export default function App(state: AppState) {
}

const id = result.message;
const { x, y } = firstAvailableSpot(1, 1);
const widgetLayout: Widget = {
const {x, y} = firstAvailableSpot(1, 1);
const widgetLayout: WidgetData = {
config: {
i: id,
x,
Expand All @@ -232,28 +228,28 @@ export default function App(state: AppState) {
};

const newLayout = widgets.concat(widgetLayout);
console.log("New Layout", newLayout);
setWidgets(newLayout);
setLoading(false);
updateWidgetHtml(id);
await api.saveLayout(state.dashboardId, newLayout);
await updateWidgetHtml(id);
await api.saveLayout(state.dashboardId, newLayout.map(widget => widget.config));
showEditForm(id);
}

const onLayoutChange = (layout:RGL.Layout[]) => {
console.log("Layout change", layout);
if (shouldSaveLayout(widgets.map(widget=>widget.config), layout)) {
api.saveLayout(state.dashboardId, layout);
const onLayoutChange = (layout: RGL.Layout[]) => {
if (shouldSaveLayout(widgets.map(widget => widget.config), layout)) {
api.saveLayout(state.dashboardId, layout).then(() => {
setWidgets(widgets.map((widget, index) => {
return {
config: layout[index],
html: widget.html,
}
}))
})
}
setWidgets(widgets.map((widget, index) => {
return {
config: layout[index],
html: widget.html,
}
}));
};

const shouldSaveLayout = (prevLayout: RGL.Layout[], newLayout: RGL.Layout[]): boolean => {
console.log("Checking if layout should be saved", prevLayout, newLayout);
if (prevLayout.length !== newLayout.length) {
return true
}
Expand All @@ -272,7 +268,7 @@ export default function App(state: AppState) {
dashboards={state.dashboards}
hMargin={config.containerPadding[0]}
includeH1={true}
loading={loading} />)}
loading={loading}/>)}
<Dashboard
widgets={widgets}
config={config}
Expand All @@ -285,13 +281,15 @@ export default function App(state: AppState) {
saveActiveWidget={saveActiveWidget}
readOnly={state.readOnly}
layoutChange={onLayoutChange}
/>
onEditClick={onEditClick}
formRef={formRef}
/>
{!state.hideMenu && (<Footer
addWidget={addWidget}
widgetTypes={state.widgetTypes}
currentDashboard={state.currentDashboard} // I'm going to modify this so it's dynamic to switch I think?
noDownload={state.noDownload}
readOnly={state.readOnly}
addWidget={addWidget}
widgetTypes={state.widgetTypes}
currentDashboard={state.currentDashboard} // I'm going to modify this so it's dynamic to switch I think?
noDownload={state.noDownload}
readOnly={state.readOnly}
/>
)}
</div>
Expand Down
Loading

0 comments on commit ca1332c

Please sign in to comment.