523 lines
16 KiB
JavaScript
523 lines
16 KiB
JavaScript
class Dropdown {
|
|
constructor(prefix = "plugins") {
|
|
this.prefix = prefix;
|
|
this.container = document.querySelector("main");
|
|
this.initDropdown();
|
|
}
|
|
|
|
initDropdown() {
|
|
this.container.addEventListener("click", (e) => {
|
|
//SELECT BTN LOGIC
|
|
try {
|
|
if (
|
|
e.target
|
|
.closest("button")
|
|
.hasAttribute(`data-${this.prefix}-setting-select`) &&
|
|
!e.target.closest("button").hasAttribute(`disabled`)
|
|
) {
|
|
const btnName = e.target
|
|
.closest("button")
|
|
.getAttribute(`data-${this.prefix}-setting-select`);
|
|
if (this.lastDrop !== btnName) {
|
|
this.lastDrop = btnName;
|
|
this.closeAllDrop();
|
|
}
|
|
|
|
this.toggleSelectBtn(e);
|
|
}
|
|
} catch (err) {}
|
|
//SELECT DROPDOWN BTN LOGIC
|
|
try {
|
|
if (
|
|
e.target
|
|
.closest("button")
|
|
.hasAttribute(`data-${this.prefix}-setting-select-dropdown-btn`)
|
|
) {
|
|
const btn = e.target.closest("button");
|
|
const btnValue = btn.getAttribute("value");
|
|
const btnSetting = btn.getAttribute(
|
|
`data-${this.prefix}-setting-select-dropdown-btn`
|
|
);
|
|
//stop if same value to avoid new fetching
|
|
const isSameVal = this.isSameValue(btnSetting, btnValue);
|
|
if (isSameVal) return this.hideDropdown(btnSetting);
|
|
//else, add new value to custom
|
|
this.setSelectNewValue(btnSetting, btnValue);
|
|
//close dropdown and change style
|
|
this.hideDropdown(btnSetting);
|
|
this.changeDropBtnStyle(btnSetting, btn);
|
|
//show / hide filter
|
|
if (btnSetting === "instances") {
|
|
this.hideFilterOnLocal(btn.getAttribute("data-_type"));
|
|
}
|
|
}
|
|
} catch (err) {}
|
|
});
|
|
}
|
|
|
|
closeAllDrop() {
|
|
const drops = document.querySelectorAll(
|
|
`[data-${this.prefix}-setting-select-dropdown]`
|
|
);
|
|
drops.forEach((drop) => {
|
|
drop.classList.add("hidden");
|
|
drop.classList.remove("flex");
|
|
document
|
|
.querySelector(
|
|
`svg[data-${this.prefix}-setting-select="${drop.getAttribute(
|
|
`data-${this.prefix}-setting-select-dropdown`
|
|
)}"]`
|
|
)
|
|
.classList.remove("rotate-180");
|
|
});
|
|
}
|
|
|
|
isSameValue(btnSetting, value) {
|
|
const selectCustom = document.querySelector(
|
|
`[data-${this.prefix}-setting-select-text="${btnSetting}"]`
|
|
);
|
|
const currVal = selectCustom.textContent;
|
|
return currVal === value ? true : false;
|
|
}
|
|
|
|
setSelectNewValue(btnSetting, value) {
|
|
const selectCustom = document.querySelector(
|
|
`[data-${this.prefix}-setting-select="${btnSetting}"]`
|
|
);
|
|
selectCustom.querySelector(
|
|
`[data-${this.prefix}-setting-select-text]`
|
|
).textContent = value;
|
|
}
|
|
|
|
hideDropdown(btnSetting) {
|
|
//hide dropdown
|
|
const dropdownEl = document.querySelector(
|
|
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`
|
|
);
|
|
dropdownEl.classList.add("hidden");
|
|
dropdownEl.classList.remove("flex");
|
|
//svg effect
|
|
const dropdownChevron = document.querySelector(
|
|
`svg[data-${this.prefix}-setting-select="${btnSetting}"]`
|
|
);
|
|
dropdownChevron.classList.remove("rotate-180");
|
|
}
|
|
|
|
changeDropBtnStyle(btnSetting, selectedBtn) {
|
|
const dropdownEl = document.querySelector(
|
|
`[data-${this.prefix}-setting-select-dropdown="${btnSetting}"]`
|
|
);
|
|
//reset dropdown btns
|
|
const btnEls = dropdownEl.querySelectorAll("button");
|
|
|
|
btnEls.forEach((btn) => {
|
|
btn.classList.remove(
|
|
"dark:bg-primary",
|
|
"bg-primary",
|
|
"bg-primary",
|
|
"text-gray-300",
|
|
"text-gray-300"
|
|
);
|
|
btn.classList.add("bg-white", "dark:bg-slate-700", "text-gray-700");
|
|
});
|
|
//highlight clicked btn
|
|
selectedBtn.classList.remove(
|
|
"bg-white",
|
|
"dark:bg-slate-700",
|
|
"text-gray-700"
|
|
);
|
|
selectedBtn.classList.add("dark:bg-primary", "bg-primary", "text-gray-300");
|
|
}
|
|
|
|
toggleSelectBtn(e) {
|
|
const attribut = e.target
|
|
.closest("button")
|
|
.getAttribute(`data-${this.prefix}-setting-select`);
|
|
//toggle dropdown
|
|
const dropdownEl = document.querySelector(
|
|
`[data-${this.prefix}-setting-select-dropdown="${attribut}"]`
|
|
);
|
|
const dropdownChevron = document.querySelector(
|
|
`svg[data-${this.prefix}-setting-select="${attribut}"]`
|
|
);
|
|
dropdownEl.classList.toggle("hidden");
|
|
dropdownEl.classList.toggle("flex");
|
|
dropdownChevron.classList.toggle("rotate-180");
|
|
}
|
|
|
|
//hide date filter on local
|
|
hideFilterOnLocal(type) {
|
|
if (type === "local") {
|
|
this.hideInp(`input#from-date`);
|
|
this.hideInp(`input#to-date`);
|
|
}
|
|
|
|
if (type !== "local") {
|
|
this.showInp(`input#from-date`);
|
|
this.showInp(`input#to-date`);
|
|
}
|
|
}
|
|
|
|
showInp(selector) {
|
|
document.querySelector(selector).closest("div").classList.add("flex");
|
|
document.querySelector(selector).closest("div").classList.remove("hidden");
|
|
}
|
|
|
|
hideInp(selector) {
|
|
document.querySelector(selector).closest("div").classList.add("hidden");
|
|
document.querySelector(selector).closest("div").classList.remove("flex");
|
|
}
|
|
}
|
|
|
|
class Filter {
|
|
constructor(prefix = "plugins") {
|
|
this.prefix = prefix;
|
|
this.container = document.querySelector(`[data-${this.prefix}-filter]`);
|
|
this.keyInp = document.querySelector("input#keyword");
|
|
this.lastType = "all";
|
|
this.initHandler();
|
|
}
|
|
|
|
initHandler() {
|
|
//TYPE HANDLER
|
|
this.container.addEventListener("click", (e) => {
|
|
try {
|
|
if (
|
|
e.target
|
|
.closest("button")
|
|
.getAttribute(`data-${this.prefix}-setting-select-dropdown-btn`) ===
|
|
"types"
|
|
) {
|
|
const btn = e.target.closest("button");
|
|
const btnValue = btn.getAttribute("value");
|
|
|
|
this.lastType = btnValue;
|
|
//run filter
|
|
this.filter();
|
|
}
|
|
} catch (err) {}
|
|
});
|
|
//KEYWORD HANDLER
|
|
this.keyInp.addEventListener("input", (e) => {
|
|
this.filter();
|
|
});
|
|
}
|
|
|
|
filter() {
|
|
const logs = document.querySelector(`[data-${this.prefix}-list]`).children;
|
|
if (logs.length === 0) return;
|
|
//reset
|
|
for (let i = 0; i < logs.length; i++) {
|
|
const el = logs[i];
|
|
el.classList.remove("hidden");
|
|
}
|
|
//filter type
|
|
this.setFilterType(logs);
|
|
this.setFilterKeyword(logs);
|
|
}
|
|
|
|
setFilterType(logs) {
|
|
if (this.lastType === "all") return;
|
|
for (let i = 0; i < logs.length; i++) {
|
|
const el = logs[i];
|
|
const type = el.getAttribute(`data-${this.prefix}-external`).trim();
|
|
if (type !== this.lastType) el.classList.add("hidden");
|
|
}
|
|
}
|
|
|
|
setFilterKeyword(logs) {
|
|
const keyword = this.keyInp.value.trim().toLowerCase();
|
|
if (!keyword) return;
|
|
for (let i = 0; i < logs.length; i++) {
|
|
const el = logs[i];
|
|
const content = el
|
|
.querySelector(`[data-${this.prefix}-content]`)
|
|
.textContent.trim()
|
|
.toLowerCase();
|
|
|
|
if (!content.includes(keyword)) el.classList.add("hidden");
|
|
}
|
|
}
|
|
}
|
|
|
|
class Upload {
|
|
constructor() {
|
|
this.container = document.querySelector("[data-plugins-upload]");
|
|
this.form = document.querySelector("#dropzone-form");
|
|
this.dropZoneElement = document.querySelector(".drop-zone");
|
|
this.fileInput = document.querySelector(".file-input");
|
|
this.progressArea = document.querySelector(".progress-area");
|
|
this.uploadedArea = document.querySelector(".uploaded-area");
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
//form click launch input file
|
|
this.form.addEventListener("click", () => {
|
|
this.fileInput.click();
|
|
});
|
|
//dropzone logic
|
|
this.dropZoneElement.addEventListener("dragover", (e) => {
|
|
e.preventDefault();
|
|
this.dragInStyle();
|
|
});
|
|
|
|
["dragleave", "dragend"].forEach((type) => {
|
|
this.dropZoneElement.addEventListener(type, (e) => {
|
|
this.dragOutStyle();
|
|
});
|
|
});
|
|
|
|
this.dropZoneElement.addEventListener("drop", (e) => {
|
|
e.preventDefault();
|
|
this.fileInput.files = e.dataTransfer.files;
|
|
this.fileInput.dispatchEvent(new Event("change"));
|
|
this.dragOutStyle();
|
|
});
|
|
//when added file, set upload logic
|
|
this.fileInput.addEventListener("change", () => {
|
|
this.dragOutStyle();
|
|
const timeout = 500;
|
|
for (let i = 0; i < this.fileInput.files.length; i++) {
|
|
setTimeout(() => this.uploadFile(this.fileInput.files[i]), timeout * i);
|
|
}
|
|
});
|
|
|
|
//close fail/success log
|
|
this.container.addEventListener("click", (e) => {
|
|
try {
|
|
if (
|
|
e.target.closest("button").hasAttribute("data-upload-message-delete")
|
|
) {
|
|
const message = e.target.closest("div[data-upload-message]");
|
|
message.remove();
|
|
}
|
|
} catch (err) {}
|
|
});
|
|
}
|
|
|
|
dragOutStyle() {
|
|
this.dropZoneElement.classList.remove(
|
|
"border-solid",
|
|
"bg-gray-100",
|
|
"dark:bg-slate-700/50"
|
|
);
|
|
this.dropZoneElement.classList.add("border-dashed");
|
|
}
|
|
|
|
dragInStyle() {
|
|
this.dropZoneElement.classList.add(
|
|
"border-solid",
|
|
"bg-gray-100",
|
|
"dark:bg-slate-700/50"
|
|
);
|
|
this.dropZoneElement.classList.remove("border-dashed");
|
|
}
|
|
|
|
uploadFile(file) {
|
|
let name = file.name;
|
|
if (name.length >= 12) {
|
|
let splitName = name.split(".");
|
|
name = splitName[0].substring(0, 13) + "... ." + splitName[1];
|
|
}
|
|
|
|
let xhr = new XMLHttpRequest();
|
|
xhr.open("POST", "plugins/upload");
|
|
let fileSize;
|
|
|
|
xhr.upload.addEventListener("progress", ({ loaded, total }) => {
|
|
let fileTotal = Math.floor(total / 1000);
|
|
|
|
fileTotal < 1024
|
|
? (fileSize = fileTotal + " KB")
|
|
: (fileSize = (loaded / (1024 * 1024)).toFixed(2) + " MB");
|
|
|
|
const progressHTML = this.fileLoad(name, fileSize);
|
|
|
|
this.uploadedArea.classList.add("onprogress");
|
|
this.progressArea.innerHTML = progressHTML;
|
|
});
|
|
|
|
xhr.addEventListener("readystatechange", () => {
|
|
if (xhr.readyState === XMLHttpRequest.DONE) {
|
|
this.progressArea.innerHTML = "";
|
|
|
|
if (xhr.status == 201) {
|
|
this.uploadedArea.insertAdjacentHTML(
|
|
"afterbegin",
|
|
this.fileSuccess(name, fileSize)
|
|
);
|
|
this.allowReload();
|
|
} else {
|
|
this.uploadedArea.insertAdjacentHTML(
|
|
"afterbegin",
|
|
this.fileFail(name, fileSize)
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
let data = new FormData();
|
|
data.set("file", file);
|
|
data.set("csrf_token", document.querySelector("#csrf_token").value);
|
|
xhr.send(data);
|
|
}
|
|
|
|
allowReload() {
|
|
const reloadBtn = document.querySelector("[data-plugin-reload-btn]");
|
|
reloadBtn.removeAttribute("disabled");
|
|
}
|
|
|
|
fileLoad(name, fileSize) {
|
|
const str = `<div class="mt-2 rounded p-2 w-full bg-gray-100 dark:bg-gray-800">
|
|
<div class="flex items-center justify-between">
|
|
<svg class="fill-sky-500 stroke-sky-500 h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
|
</svg>
|
|
<span class="text-sm text-slate-700 dark:text-gray-300 mr-4">${name} </span>
|
|
<span class="text-sm text-slate-700 dark:text-gray-300">${fileSize}</span>
|
|
|
|
<svg class=" fill-gray-600 dark:fill-gray-300 dark:opacity-80 h-3 w-3 " viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
|
<circle cx="50" cy="50" r="50"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
return str;
|
|
}
|
|
|
|
fileSuccess(name, fileSize) {
|
|
const str = `<div data-upload-message class="mt-2 rounded p-2 w-full bg-gray-100 dark:bg-gray-800">
|
|
<div class="flex items-center justify-between">
|
|
<svg
|
|
class="fill-green-500 h-5 w-5"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 512 512"
|
|
>
|
|
<path
|
|
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"
|
|
/>
|
|
</svg>
|
|
<span class="text-sm text-slate-700 dark:text-gray-300 mr-4">${name} </span>
|
|
<span class="text-sm text-slate-700 dark:text-gray-300">${fileSize}</span>
|
|
<button type="button" data-upload-message-delete>
|
|
<svg class="cursor-pointer fill-gray-600 dark:fill-gray-300 dark:opacity-80 h-4 w-4 " xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
|
|
<path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
let cleanHTML = DOMPurify.sanitize(str);
|
|
return cleanHTML;
|
|
}
|
|
|
|
fileFail(name, fileSize) {
|
|
const str = `<div data-upload-message class="mt-2 rounded p-2 w-full bg-gray-100 dark:bg-gray-800">
|
|
<div class="flex items-center justify-between">
|
|
<svg
|
|
class="fill-red-500 h-5 w-5 mr-4"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
viewBox="0 0 512 512"
|
|
>
|
|
<path
|
|
d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"
|
|
/>
|
|
</svg>
|
|
<span class="text-sm text-slate-700 dark:text-gray-300 mr-4">${name} </span>
|
|
<span class="text-sm text-slate-700 dark:text-gray-300">${fileSize}</span>
|
|
<button type="button" data-upload-message-delete>
|
|
|
|
<svg class="cursor-pointer fill-gray-600 dark:fill-gray-300 dark:opacity-80 h-4 w-4 " xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
|
|
<path d="M310.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L160 210.7 54.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L114.7 256 9.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 301.3 265.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L205.3 256 310.6 150.6z"></path>
|
|
</svg>
|
|
</button>
|
|
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
let cleanHTML = DOMPurify.sanitize(str);
|
|
return cleanHTML;
|
|
}
|
|
}
|
|
|
|
class Modal {
|
|
constructor(prefix = "plugins") {
|
|
this.prefix = prefix;
|
|
this.container = document.querySelector(`[data-${this.prefix}-list]`);
|
|
this.modal = document.querySelector(`[data-${this.prefix}-modal]`);
|
|
this.modalNameInp = this.modal.querySelector("input#name");
|
|
this.modalExtInp = this.modal.querySelector("input#external");
|
|
|
|
this.modalTitle = this.modal.querySelector(
|
|
`[data-${this.prefix}-modal-title]`
|
|
);
|
|
this.modalTxt = this.modal.querySelector(
|
|
`[data-${this.prefix}-modal-text]`
|
|
);
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.container.addEventListener("click", (e) => {
|
|
//DELETE HANDLER
|
|
try {
|
|
if (
|
|
e.target
|
|
.closest("button")
|
|
.getAttribute(`data-${this.prefix}-action`) === "delete"
|
|
) {
|
|
const btnEl = e.target.closest("button");
|
|
this.setModal(btnEl);
|
|
this.showModal();
|
|
}
|
|
} catch (err) {}
|
|
});
|
|
|
|
this.modal.addEventListener("click", (e) => {
|
|
//CLOSE MODAL HANDLER
|
|
try {
|
|
if (
|
|
e.target
|
|
.closest("button")
|
|
.hasAttribute(`data-${this.prefix}-modal-close`)
|
|
) {
|
|
this.hideModal();
|
|
}
|
|
} catch (err) {}
|
|
});
|
|
}
|
|
|
|
setModal(el) {
|
|
//name
|
|
const elName = el.getAttribute("name");
|
|
this.modalNameInp.value = elName;
|
|
this.modalTitle.textContent = `DELETE ${elName}`;
|
|
this.modalTxt.textContent = `Are you sure you want to delete ${elName} ?`;
|
|
//external
|
|
const isExternal = el
|
|
.closest("[data-plugins-external]")
|
|
.getAttribute("data-plugins-external")
|
|
.trim()
|
|
.includes("external")
|
|
? "True"
|
|
: "False";
|
|
this.modalExtInp.value = isExternal;
|
|
}
|
|
|
|
showModal() {
|
|
this.modal.classList.add("flex");
|
|
this.modal.classList.remove("hidden");
|
|
}
|
|
|
|
hideModal() {
|
|
this.modal.classList.add("hidden");
|
|
this.modal.classList.remove("flex");
|
|
}
|
|
}
|
|
|
|
const setDropdown = new Dropdown("plugins");
|
|
const setFilter = new Filter("plugins");
|
|
const setUpload = new Upload();
|
|
const setModal = new Modal("plugins");
|