@underpostnet/underpost
Version:
647 lines (620 loc) • 25.3 kB
JavaScript
import { getId, isValidDate, newInstance } from './CommonJs.js';
import { LoadingAnimation } from '../core/LoadingAnimation.js';
import { Validator } from '../core/Validator.js';
import { Input } from '../core/Input.js';
import { Responsive } from '../core/Responsive.js';
import { append, getDataFromInputFile, getQueryParams, htmls, prepend, s } from './VanillaJs.js';
import { BtnIcon } from './BtnIcon.js';
import { Translate } from './Translate.js';
import { DropDown } from './DropDown.js';
import { dynamicCol, renderCssAttr } from './Css.js';
import { EventsUI } from './EventsUI.js';
import { ToggleSwitch } from './ToggleSwitch.js';
import { Modal } from './Modal.js';
import { RouterEvents } from './Router.js';
import { RichText } from './RichText.js';
import { loggerFactory } from './Logger.js';
import { Badge } from './Badge.js';
import { Content } from './Content.js';
const logger = loggerFactory(import.meta);
const Panel = {
Tokens: {},
Render: async function (
options = {
idPanel: '',
parentIdModal: '',
scrollClassContainer: '',
htmlFormHeader: async () => '',
formData: [],
data: [],
originData: () => [],
filesData: () => [],
onClick: () => {},
},
) {
const idPanel = options?.idPanel ? options.idPanel : getId(this.Tokens, `${idPanel}-`);
if (options.formData)
options.formData = options.formData.map((formObj) => {
formObj.id = `${idPanel}-${formObj.id}`;
return formObj;
});
const { scrollClassContainer, formData, data, heightTopBar, heightBottomBar } = options;
const titleObj = formData.find((f) => f.panel && f.panel.type === 'title');
const titleKey = titleObj ? titleObj.model : '';
const subTitleObj = formData.find((f) => f.panel && f.panel.type === 'subtitle');
const subTitleKey = subTitleObj ? subTitleObj.model : '';
const fileNameInputExtDefaultContent = html` <div class="abs center">
<i style="font-size: 25px" class="fa-solid fa-cloud"></i>
</div>`;
const openPanelForm = () => {
s(`.${idPanel}-form-body`).classList.remove('hide');
s(`.btn-${idPanel}-add`).classList.add('hide');
// s(`.${scrollClassContainer}`).style.overflow = 'hidden';
if (options.customButtons) {
let customBtnIndex = -1;
for (const dataBtn of options.customButtons) {
customBtnIndex++;
const customBtnIndexFn = customBtnIndex;
const btnSelector = `btn-${idPanel}-custom${customBtnIndexFn}`;
s(`.${btnSelector}`).classList.add('hide');
}
}
setTimeout(() => {
s(`.${idPanel}-form-body`).style.opacity = 1;
});
};
const renderPanel = async (payload) => {
const obj = newInstance(payload);
if ('_id' in obj) obj.id = obj._id;
const { id } = obj;
setTimeout(async () => {
if (!s(`.${idPanel}`)) return;
LoadingAnimation.spinner.play(`.${idPanel}-img-spinner-${id}`, 'dual-ring');
if (options && options.callBackPanelRender)
await options.callBackPanelRender({
data: obj,
fileRender: async (options = { file: '', style: {}, class: '' }) => {
await Content.RenderFile({ container: `.${idPanel}-cell-col-a-${id}`, ...options });
s(`.${idPanel}-img-spinner-${id}`).classList.add('hide');
},
htmlRender: async ({ render }) => {
htmls(`.${idPanel}-cell-col-a-${id}`, render);
},
});
EventsUI.onClick(
`.${idPanel}-btn-delete-${id}`,
async (e) => {
logger.warn('delete', obj);
const { status } = await options.on.remove({ e, data: obj });
if (status === 'error') return;
if (s(`.${idPanel}-${id}`)) s(`.${idPanel}-${id}`).remove();
},
{ context: 'modal' },
);
EventsUI.onClick(`.${idPanel}-btn-edit-${id}`, async () => {
logger.warn('edit', obj);
if (obj._id) Panel.Tokens[idPanel].editId = obj._id;
else if (obj.id) Panel.Tokens[idPanel].editId = obj.id;
s(`.btn-${idPanel}-label-edit`).classList.remove('hide');
s(`.btn-${idPanel}-label-add`).classList.add('hide');
openPanelForm();
// s(`.btn-${idPanel}-add`).click();
s(`.${scrollClassContainer}`).scrollTop = 0;
Input.setValues(
formData,
obj,
options.originData().find((d) => d._id === obj._id || d.id === obj.id),
options.filesData().find((d) => d._id === obj._id || d.id === obj.id),
);
});
s(`.a-${payload._id}`).onclick = async (e) => {
e.preventDefault();
};
s(`.container-${idPanel}-${id}`).onclick = async (e) => {
e.preventDefault();
if (options.onClick) await options.onClick({ payload });
};
});
if (s(`.${idPanel}-${id}`)) s(`.${idPanel}-${id}`).remove();
return html` <div class="in box-shadow ${idPanel} ${idPanel}-${id}">
<div class="fl ${idPanel}-tools session-fl-log-in ${obj.tools ? '' : 'hide'}">
${await BtnIcon.Render({
class: `in flr main-btn-menu action-bar-box ${idPanel}-btn-tool ${idPanel}-btn-edit-${id}`,
label: html`<div class="abs center"><i class="fas fa-edit"></i></div>`,
tooltipHtml: await Badge.Render({
id: `tooltip-${idPanel}-${id}`,
text: `${Translate.Render(`edit`)}`,
classList: '',
style: { top: `-22px`, left: '-5px' },
}),
})}
${await BtnIcon.Render({
class: `in flr main-btn-menu action-bar-box ${idPanel}-btn-tool ${idPanel}-btn-delete-${id}`,
label: html`<div class="abs center"><i class="fas fa-trash"></i></div>`,
tooltipHtml: await Badge.Render({
id: `tooltip-${idPanel}-${id}`,
text: `${Translate.Render(`delete`)}`,
classList: '',
style: { top: `-22px`, left: '-13px' },
}),
})}
</div>
<div class="in container-${idPanel}-${id}">
<div class="in ${idPanel}-head">
<div class="in ${idPanel}-title">
${options.titleIcon}
<a href="?cid=${payload._id}" class="a-title-${idPanel} a-${payload._id}">
${titleKey ? obj[titleKey] : ''}</a
>
</div>
<div class="in ${idPanel}-subtitle">
${subTitleKey ? obj[subTitleKey] : ''} <span class="tag-render-${id}"></span>
</div>
<!-- <div class="in ${idPanel}-tags"></div> -->
</div>
<div class="fl">
<div class="in fll ${idPanel}-cell ${idPanel}-cell-col-a ${idPanel}-cell-col-a-${id}">
<div class="abs center ${idPanel}-img-spinner-${id}"></div>
</div>
<div class="in fll ${idPanel}-cell ${idPanel}-cell-col-b">
${Object.keys(obj)
.map((infoKey) => {
if (infoKey === 'id') return html``;
const formObjData = formData.find((f) => f.model === infoKey);
const valueIcon = formObjData?.panel?.icon?.value ? formObjData.panel.icon.value : '';
const keyIcon = formObjData?.panel?.icon?.key ? formObjData.panel.icon.key : '';
if (formObjData && ['datetime-local'].includes(formObjData.inputType) && isValidDate(obj[infoKey])) {
obj[infoKey] = `${obj[infoKey]}`.replace('T', ' ').replace('.000Z', '');
}
if (formData.find((f) => f.model === infoKey && f.panel && f.panel.type === 'tags')) {
setTimeout(async () => {
let tagRender = html``;
for (const tag of obj[infoKey]) {
tagRender += await Badge.Render({
text: tag,
style: { color: 'white' },
classList: 'inl',
style: { margin: '3px', background: `#a2a2a2` },
});
}
if (s(`.tag-render-${id}`)) htmls(`.tag-render-${id}`, tagRender);
});
return html``;
}
{
const formDataObj = formData.find((f) => f.model === infoKey && f.panel && f.panel.type === 'list');
if (obj[infoKey] && obj[infoKey].length > 0 && formDataObj)
return html`<div class="in ${idPanel}-row">
<span class="${idPanel}-row-key capitalize ${formObjData.label?.disabled ? 'hide' : ''}">
${keyIcon} ${Translate.Render(infoKey)}:</span
>
<span class="${idPanel}-row-value"
>${valueIcon} ${obj[infoKey].map((k) => Translate.Render(k)).join(', ')}</span
>
</div> `;
}
{
const formDataObj = formData.find(
(f) => f.model === infoKey && f.panel && f.panel.type === 'info-row-pin',
);
if (obj[infoKey] && formDataObj)
return html`<div class="in ${idPanel}-row">
<span class="${idPanel}-row-pin-key capitalize ${formObjData.label?.disabled ? 'hide' : ''}">
${keyIcon}
${formDataObj.translateCode
? Translate.Render(formDataObj.translateCode)
: Translate.Render(infoKey)}:</span
>
<span class="${idPanel}-row-pin-value">${valueIcon} ${obj[infoKey]}</span>
</div> `;
}
{
const formDataObj = formData.find(
(f) => f.model === infoKey && f.panel && f.panel.type === 'info-row',
);
if (obj[infoKey] && formDataObj)
return html`<div class="in ${idPanel}-row">
<span class="${idPanel}-row-key capitalize ${formObjData.label?.disabled ? 'hide' : ''}">
${keyIcon}
${formDataObj.translateCode
? Translate.Render(formDataObj.translateCode)
: Translate.Render(infoKey)}:</span
>
<span class="${idPanel}-row-value"> ${valueIcon} ${obj[infoKey]}</span>
</div> `;
}
return html``;
})
.join('')}
</div>
</div>
</div>
</div>`;
};
let render = '';
let renderForm = html` <div class="in modal" style="top: 0px; z-index: 1; padding-bottom: 5px">
${await BtnIcon.Render({
class: `inl section-mp btn-custom btn-${idPanel}-close`,
label: html`<i class="fa-solid fa-xmark"></i> ${Translate.Render('close')}`,
type: 'button',
})}
</div>
${options?.htmlFormHeader ? await options.htmlFormHeader() : ''}`;
for (const modelData of formData) {
if (modelData.disableRender) continue;
switch (modelData.inputType) {
case 'dropdown-checkbox': {
renderForm += html`<div class="in section-mp">
${await DropDown.Render({
id: `${modelData.id}`,
label: html`${Translate.Render(modelData.model)}`,
type: 'checkbox',
value: modelData.dropdown.options[0],
resetOption: true,
containerClass: `${idPanel}-dropdown-checkbox`,
data: modelData.dropdown.options.map((dKey) => {
return {
value: dKey,
data: dKey,
checked: false,
display: html`${Translate.Render(dKey)}`,
onClick: function () {
logger.info('DropDown onClick', this.checked);
},
};
}),
})}
</div>`;
break;
}
case 'dropdown':
renderForm += html` <div class="in section-mp">
${await DropDown.Render({
id: `${modelData.id}`,
label: html`${Translate.Render(modelData.model)}`,
containerClass: `${idPanel}-dropdown`,
// type: 'checkbox',
value: modelData.dropdown.options[0].replaceAll(' ', '-').toLowerCase(),
data: modelData.dropdown.options.map((dKey) => {
const key = dKey.replaceAll(' ', '-').toLowerCase();
return {
value: key,
data: dKey,
// checked: true,
display: html`${Translate.Render(dKey)}`,
onClick: function () {},
};
}),
})}
</div>`;
break;
case 'md': {
renderForm += html`<div class="in section-mp">
${await RichText.Render({ id: modelData.id, parentIdModal: options.parentIdModal })}
</div>`;
break;
}
case 'checkbox-on-off':
{
setTimeout(() => {
s(`.toggle-form-container-${modelData.id}`).onclick = () => {
ToggleSwitch.Tokens[`${modelData.id}`].click();
};
});
renderForm += html`<div
class="in section-mp toggle-form-container toggle-form-container-${modelData.id} hover"
style="height: 82px;"
>
<div class="fl">
<div class="in fll" style="width: 70%">
<div class="in">
${modelData.panel && modelData.panel.icon ? modelData.panel.icon : ''}
${Translate.Render(modelData.model)}
</div>
</div>
<div class="in fll" style="width: 30%">
${await ToggleSwitch.Render({
id: `${modelData.id}`,
containerClass: 'inl',
disabledOnClick: true,
checked: false,
on: {
unchecked: () => {},
checked: () => {},
},
})}
</div>
</div>
</div>`;
}
break;
case 'file':
setTimeout(() => {
s(`.${modelData.id}`).fileNameInputExtDefaultContent = fileNameInputExtDefaultContent;
s(`.${modelData.id}`).onchange = async (e) => {
if (!Object.keys(e.target.files).length) return;
s(`.${modelData.id}`).inputFiles = e.target.files;
let htmlFileRender = '';
for (const fileKey of Object.keys(e.target.files)) {
const file = e.target.files[fileKey];
htmlFileRender += html`${await Content.RenderFile({
url: URL.createObjectURL(file),
file: {
mimetype: file.type,
name: file.name,
data: {
data: await getDataFromInputFile(file),
},
},
aHrefOptions: {
disable: true,
},
raw: true,
})}
<div class="in" style="overflow: hidden">${file.name}</div>`;
}
htmls(`.file-name-render-${modelData.id}`, htmlFileRender);
};
});
renderForm += `${await Input.Render({
inputClass: 'hide',
id: `${modelData.id}`,
type: modelData.inputType,
multiple: true,
// autocomplete: 'new-password',
label: html`<i class="fa-solid fa-file-arrow-up"></i> ${Translate.Render('select')}
${Translate.Render('file')}`,
containerClass: 'in section-mp width-mini-box input-container',
placeholder: true,
extension: () =>
html`<div class="file-name-render-${modelData.id}" style="min-height: 50px">
${fileNameInputExtDefaultContent}
</div>`,
// disabled: true,
// disabledEye: true,
})}`;
break;
default:
renderForm += `${await Input.Render({
id: `${modelData.id}`,
type: modelData.inputType,
// autocomplete: 'new-password',
label: html`<i class="fa-solid fa-pen-to-square"></i> ${Translate.Render(modelData.model)}`,
containerClass: 'in section-mp width-mini-box input-container',
placeholder: true,
// disabled: true,
// disabledEye: true,
})}`;
break;
}
}
let renderFormBtn = html`
${await BtnIcon.Render({
class: `inl section-mp btn-custom btn-${idPanel}-submit`,
label: html`<span class="btn-${idPanel}-label-add"><i class="fas fa-plus"></i> ${Translate.Render('add')}</span
><span class="btn-${idPanel}-label-edit hide"><i class="fas fa-edit"></i> ${Translate.Render('edit')}</span>`,
type: 'submit',
})}
${await BtnIcon.Render({
class: `inl section-mp btn-custom btn-${idPanel}-clean`,
label: html`<i class="fa-solid fa-broom"></i> ${Translate.Render('clear')}`,
type: 'button',
})}
`;
setTimeout(async () => {
const validators = await Validator.instance(formData);
s(`.${idPanel}-form`).onsubmit = (e) => {
e.preventDefault();
s(`.btn-${idPanel}-submit`).click();
};
EventsUI.onClick(`.btn-${idPanel}-submit`, async (e) => {
e.preventDefault();
const { errorMessage } = await validators();
if (errorMessage) return;
const obj = Input.getValues(formData);
obj.id = `${data.length}`;
let documents;
if (options && options.on && options.on.add) {
const { status, data } = await options.on.add({ data: obj, editId: Panel.Tokens[idPanel].editId });
if (status === 'error') return;
documents = data;
}
s(`.btn-${idPanel}-clean`).click();
if (Panel.Tokens[idPanel].editId && s(`.${idPanel}-${Panel.Tokens[idPanel].editId}`))
s(`.${idPanel}-${Panel.Tokens[idPanel].editId}`).remove();
if (Array.isArray(documents)) {
htmls(`.${idPanel}-render`, '');
for (const doc of documents) {
append(`.${idPanel}-render`, await renderPanel(doc));
}
} else htmls(`.${idPanel}-render`, await renderPanel({ ...obj, ...documents }));
Input.cleanValues(formData);
s(`.btn-${idPanel}-close`).click();
s(`.${scrollClassContainer}`).scrollTop = 0;
if (s(`.${scrollClassContainer}`)) s(`.${scrollClassContainer}`).style.overflow = 'auto';
});
s(`.btn-${idPanel}-clean`).onclick = () => {
Input.cleanValues(formData);
};
s(`.btn-${idPanel}-close`).onclick = (e) => {
e.preventDefault();
s(`.${idPanel}-form-body`).style.opacity = 0;
s(`.btn-${idPanel}-add`).classList.remove('hide');
s(`.${scrollClassContainer}`).style.overflow = 'auto';
if (options.customButtons) {
let customBtnIndex = -1;
for (const dataBtn of options.customButtons) {
customBtnIndex++;
const customBtnIndexFn = customBtnIndex;
const btnSelector = `btn-${idPanel}-custom${customBtnIndexFn}`;
s(`.${btnSelector}`).classList.remove('hide');
}
}
setTimeout(() => {
s(`.${idPanel}-form-body`).classList.add('hide');
});
};
s(`.btn-${idPanel}-add`).onclick = (e) => {
e.preventDefault();
// s(`.btn-${idPanel}-clean`).click();
Panel.Tokens[idPanel].editId = undefined;
s(`.btn-${idPanel}-label-add`).classList.remove('hide');
s(`.btn-${idPanel}-label-edit`).classList.add('hide');
s(`.${scrollClassContainer}`).scrollTop = 0;
openPanelForm();
};
if (s(`.${scrollClassContainer}`)) s(`.${scrollClassContainer}`).style.overflow = 'auto';
});
if (data.length > 0) for (const obj of data) render += await renderPanel(obj);
else
render += html`<div class="in" style="min-height: 200px">
<div class="abs center"><i class="fas fa-exclamation-circle"></i> ${Translate.Render(`no-result-found`)}</div>
</div>`;
this.Tokens[idPanel] = { idPanel, scrollClassContainer, formData, data, titleKey, subTitleKey, renderPanel };
let customButtonsRender = '';
if (options && options.customButtons) {
let customBtnIndex = -1;
for (const dataBtn of options.customButtons) {
customBtnIndex++;
const customBtnIndexFn = customBtnIndex;
const btnSelector = `btn-${idPanel}-custom${customBtnIndexFn}`;
if (dataBtn.onClick)
setTimeout(() => {
s(`.${btnSelector}`).onclick = () => dataBtn.onClick();
});
customButtonsRender += ` ${await BtnIcon.Render({
class: `inl section-mp btn-custom ${btnSelector}`,
label: dataBtn.label,
type: 'button',
})}`;
}
}
return html`
<style>
.${scrollClassContainer} {
scroll-behavior: smooth;
}
.${idPanel}-form-container {
padding-bottom: 20px;
top: 0px;
z-index: 1;
overflow: auto;
}
.${idPanel}-form {
max-width: 900px;
}
.${idPanel}-cell {
min-height: 200px;
}
.${idPanel}-container {
}
.${idPanel} {
margin: 10px;
transition: 0.3s;
border-radius: 10px;
background: #f6f6f6;
color: black;
padding: 10px;
cursor: pointer;
min-height: 400px;
}
.${idPanel}:hover {
background: #ffffff;
}
.${idPanel}-head {
/* background: white; */
margin-bottom: 10px;
}
.img-${idPanel} {
width: 100%;
}
.${idPanel}-title {
color: rgba(109, 104, 255, 1);
font-size: 24px;
padding: 5px;
}
.a-title-${idPanel} {
color: rgba(109, 104, 255, 1);
}
.a-title-${idPanel}:hover {
color: #e89f4c;
}
.${idPanel}-row {
padding: 5px;
margin: 5px;
font-size: 16px;
}
.${idPanel}-subtitle {
font-size: 17px;
margin-left: 20px;
top: -7px;
}
.${idPanel}-tags {
font-size: 17px;
margin-left: 10px;
top: -7px;
}
.${idPanel}-row-key {
}
.${idPanel}-row-value {
}
.${idPanel}-row-pin-key {
}
.${idPanel}-row-pin-value {
font-size: 20px;
color: rgb(19 190 84);
}
.${idPanel}-form-header {
}
.${idPanel}-form-body {
transition: 0.3s;
}
.btn-${idPanel}-add {
padding: 10px;
font-size: 20px;
}
.${idPanel}-dropdown {
min-height: 100px;
}
.${idPanel}-btn-tool {
background: none ;
color: #c4c4c4 ;
}
.${idPanel}-btn-tool:hover {
color: #000000 ;
font-size: 17px ;
}
</style>
<div class="${idPanel}-container">
<div class="in modal ${idPanel}-form-container ${options.formContainerClass ? options.formContainerClass : ''}">
<div class="in ${idPanel}-form-header">
${await BtnIcon.Render({
class: `inl section-mp btn-custom btn-${idPanel}-add ${
options?.role?.add ? (!options.role.add() ? 'hide' : '') : ''
}`,
label: html`<i class="fas fa-plus"></i> ${Translate.Render('add')}`,
type: 'button',
})}
<!-- pagination component -->
${customButtonsRender}
</div>
<div class="in ${idPanel}-form-body hide" style="opacity: 0">
<form class="in ${idPanel}-form">
<div class="fl">${renderForm}</div>
<div class="in">${renderFormBtn}</div>
<br /><br />
</form>
</div>
</div>
${dynamicCol({
id: `${idPanel}-cell`,
containerSelector: `${idPanel}-render`,
limit: 500,
type: 'a-50-b-50',
})}
<div class="in ${idPanel}-render">${render}</div>
</div>
`;
},
};
export { Panel };