Skip to content

Commit 4824ccb

Browse files
committed
Changes to be committed:
deleted: .gitignore new file: Assets/src/com/container.js new file: Assets/src/com/widget-add.js new file: Assets/src/com/widget-content.js new file: Assets/src/com/widget-menu.js new file: Assets/src/com/widget.js new file: Assets/src/index.js new file: Assets/src/scss/main.scss
1 parent b569cae commit 4824ccb

8 files changed

Lines changed: 821 additions & 1 deletion

File tree

.gitignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

Assets/src/com/container.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import * as dialog from "@il4mb/merapipanel/dialog";
2+
import React, { useEffect, useRef, useState, createContext, useContext } from "react";
3+
4+
const ContainerContext = createContext({});
5+
6+
export const useContainer = () => {
7+
return useContext(ContainerContext);
8+
}
9+
10+
export const Container = ({ children }) => {
11+
12+
const [isEdit, setEdit] = useState(false);
13+
const containerRef = useRef(null);
14+
const [contents, setContents] = useState([]);
15+
const [openMenu, setOpenMenu] = useState(false);
16+
const [isChanged, setChanged] = useState(false);
17+
18+
19+
useEffect(() => {
20+
const editToggle = document.getElementById('edit-widget-button');
21+
if (editToggle) {
22+
const handleClick = () => {
23+
24+
if (isEdit && isChanged) {
25+
__.dialog.confirm('<h4><i class="fa-solid fa-triangle-exclamation"></i> Unsaved changes</h4>', 'You have unsaved changes.<br>Are you sure you want to discard them?')
26+
.then((result) => {
27+
if (result) {
28+
setEdit(prevIsEdit => !prevIsEdit); // Toggle isEdit
29+
}
30+
})
31+
} else {
32+
setEdit(prevIsEdit => !prevIsEdit); // Toggle isEdit
33+
}
34+
35+
};
36+
editToggle.addEventListener('click', handleClick);
37+
38+
return () => {
39+
editToggle.removeEventListener('click', handleClick); // Cleanup listener
40+
};
41+
}
42+
}, [isChanged, isEdit]);
43+
44+
useEffect(() => {
45+
if (containerRef.current) {
46+
if (isEdit) {
47+
containerRef.current.classList.add('widget-editing');
48+
document.getElementById('edit-widget-button')?.classList.add('active');
49+
} else {
50+
containerRef.current.classList.remove('widget-editing');
51+
document.getElementById('edit-widget-button')?.classList.remove('active');
52+
}
53+
}
54+
}, [isEdit]);
55+
56+
57+
const store = {
58+
isChanged,
59+
setChanged,
60+
isEdit,
61+
setEdit,
62+
containerRef,
63+
contents: contents,
64+
addContent: (content) => {
65+
setContents([...contents, content]);
66+
},
67+
setContents,
68+
openMenu,
69+
setOpenMenu
70+
};
71+
72+
73+
return (
74+
<ContainerContext.Provider value={store}>
75+
<div ref={containerRef} className="widget-container">
76+
{children}
77+
</div>
78+
</ContainerContext.Provider>
79+
)
80+
};

Assets/src/com/widget-add.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React, { useEffect, useRef } from "react";
2+
import { useContainer } from "./container";
3+
4+
export const WidgetAdd = ({ }) => {
5+
6+
const { isEdit, setOpenMenu } = useContainer();
7+
const ref = useRef(null);
8+
9+
const handleClick = () => {
10+
setOpenMenu(current => !current);
11+
}
12+
13+
return (
14+
<>
15+
{isEdit &&
16+
<div ref={ref} className="widget-add" onClick={handleClick}>
17+
<i className="fa-solid fa-plus fa-x2"></i>
18+
<span className="ms-1">Add Widget</span>
19+
</div>}
20+
</>
21+
)
22+
}

Assets/src/com/widget-content.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import React, { useEffect, useState, useRef } from "react";
2+
import { useContainer } from "./container";
3+
import { WidgetAdd } from "./widget-add";
4+
import { Widget } from "./widget";
5+
6+
7+
8+
const SaveButton = () => {
9+
10+
const { contents, setChanged } = useContainer();
11+
const { endpoint_save, endpoint_load } = window.widgetPayload;
12+
13+
const saveHandle = () => {
14+
15+
if (!endpoint_save) {
16+
return console.error('endpoint_save not found');
17+
}
18+
if (Array.isArray(contents)) {
19+
const contentJson = JSON.stringify(contents.map((item) => item.props));
20+
__.http.post(endpoint_save, { data: contentJson })
21+
.then((data) => {
22+
setChanged(false);
23+
__.toast("Widget Saved", 5, 'text-success');
24+
})
25+
.catch((error) => {
26+
__.toast(error.message || "Something went wrong", 5, 'text-danger');
27+
});
28+
}
29+
}
30+
31+
return (
32+
<div className="save-button" onClick={saveHandle}>
33+
<i className="fa-solid fa-floppy-disk"></i>
34+
</div>
35+
)
36+
}
37+
38+
39+
export const WidgetContent = ({ children }) => {
40+
41+
const { endpoint_save, endpoint_load, endpoint_fetch } = window.widgetPayload;
42+
const { isEdit, contents, setContents, addContent } = useContainer();
43+
const ref = useRef(null);
44+
45+
useEffect(() => {
46+
__.http.get(endpoint_fetch)
47+
.then((response) => {
48+
const tempContents = [];
49+
(response.data || []).forEach((item) => {
50+
tempContents.push(<Widget
51+
id={item.id}
52+
name={item.name}
53+
title={item.title}
54+
description={item.description || ''}
55+
option={item.option || { width: 200, height: 100 }}
56+
icon={item.icon || ''} />);
57+
});
58+
setContents(tempContents);
59+
})
60+
}, [])
61+
62+
63+
64+
65+
return (
66+
<div ref={ref} className="widget-content">
67+
{contents.map((widget, index) => {
68+
return (
69+
<React.Fragment key={index}>
70+
{widget}
71+
</React.Fragment>
72+
)
73+
})}
74+
<WidgetAdd />
75+
{isEdit && <SaveButton />}
76+
</div>
77+
)
78+
}

Assets/src/com/widget-menu.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import React, { useEffect, useRef, useState } from "react";
2+
import { useContainer } from "./container";
3+
import { Widget } from "./widget";
4+
5+
6+
7+
8+
export const MenuGroup = ({ name, items = [], isOpen = false }) => {
9+
const [open, setOpen] = useState(isOpen);
10+
11+
return (
12+
<div className={`menu-group${open ? ' open' : ''}`}>
13+
<div className="menu-group-name" onClick={() => setOpen(!open)}>{name}</div>
14+
<div className="menu-group-items">
15+
{items.map((item, index) => {
16+
return (
17+
<MenuItems key={index} name={item.name} title={item.title} description={item.description} icon={item.icon} option={item.option} />
18+
)
19+
})}
20+
</div>
21+
</div>
22+
)
23+
}
24+
25+
26+
27+
28+
29+
30+
export const MenuItems = ({ name, title, icon = `<i class="fa-regular fa-face-smile"></i>`, description, option }) => {
31+
32+
const { setOpenMenu, addContent, setChanged } = useContainer();
33+
34+
const handleClick = () => {
35+
36+
console.log(name, title, icon, description, option)
37+
setOpenMenu(false);
38+
setChanged(true);
39+
addContent(<Widget
40+
id={(new Date().getTime()).toString(36)}
41+
name={name}
42+
title={title}
43+
description={description}
44+
option={Object.assign({ width: 200, height: 100 }, option || {})}
45+
focus={true} />);
46+
}
47+
48+
49+
50+
if (icon.startsWith("<svg")) {
51+
const viewBox = icon.match(/viewBox="([^"]+)"/)[1];
52+
icon = icon.replace(/\<svg.*\>/, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"22\" height=\"22\" fill=\"currentColor\" viewBox=\"" + viewBox + "\">");
53+
}
54+
55+
56+
return (
57+
<div className="menu-item" data-name={name} onClick={handleClick}>
58+
<div className="menu-item-icon" dangerouslySetInnerHTML={{ __html: icon }}></div>
59+
<div className="menu-item-title">{title}</div>
60+
<div className="menu-item-description">{description}</div>
61+
</div>
62+
)
63+
}
64+
65+
66+
67+
68+
export const WidgetMenu = ({ children }) => {
69+
70+
const { isEdit, openMenu, setOpenMenu } = useContainer();
71+
const ref = useRef(null);
72+
const [items, setItems] = useState([]);
73+
74+
useEffect(() => {
75+
if (!isEdit) {
76+
setOpenMenu(false);
77+
} else {
78+
fetchMenu();
79+
}
80+
}, [isEdit]);
81+
82+
83+
const fetchMenu = () => {
84+
85+
const { endpoint_edit, endpoint_save, endpoint_load } = window.widgetPayload;
86+
87+
fetch(endpoint_edit).then((response) => response.json()).then((response) => {
88+
const stack = {};
89+
if (response.data && Array.isArray(response.data)) {
90+
response.data.forEach((item) => {
91+
if (!stack[(item.category || 'default').toLowerCase()]) {
92+
stack[(item.category || 'default').toLowerCase()] = [];
93+
}
94+
stack[(item.category || 'default').toLowerCase()].push(item);
95+
})
96+
}
97+
setItems(stack);
98+
});
99+
}
100+
101+
102+
103+
return (
104+
<div ref={ref} className={`widget-menu${openMenu ? ' open' : ''}`}>
105+
{Object.keys(items).map((key, index) => {
106+
return (
107+
108+
<MenuGroup key={index} name={key} items={items[key]} isOpen={index === 0} />
109+
)
110+
})}
111+
</div>
112+
)
113+
}

0 commit comments

Comments
 (0)