/*! * molybdenum v1.0.250619.1428 * Copyright 2025 PT Nusa Angkasa Siber * Released under the MIT License */ class molybdenum{ version = '1.0.250619.1428'; _alerts = []; _confirms = []; _loads = []; _dialogs = []; lastDialog() { if(this._dialogs.length > 0) { return this._dialogs.at(-1); } return null; } lastLoadScreenId() { if(this._loads.length > 0) { return this._loads.at(-1).id; } return null; } newElement(name = "div", options = {}){ const ele = document.createElement(name); options.style ? ele.style = options.style : null; options.className ? ele.className = options.className : null; typeof options.append === 'string' ? ele.append(options.append) : options.append instanceof Element ? ele.append(options.append) : null; typeof options.href === 'string' && ele.nodeName === "A" ? ele.setAttribute("href",options.href) : null; return ele; } customElement(name = "div", options = {}) { const par = document.createElement("div"); par.innerHTML=`<${name}>`; const ele = par.firstChild; options.style ? ele.style = options.style : null; options.className ? ele.className = options.className : null; typeof options.append === 'string' ? ele.append(options.append) : options.append instanceof Element ? ele.append(options.append) : null; typeof options.href === 'string' && ele.nodeName === "A" ? ele.setAttribute("href",options.href) : null; return ele; } loadScreen = class { static clear() { $('load-screen').remove(); moly._loads = []; } static show(message = "", style = "sonar", target = "body") { if (target != "body" && ($(target).length != 1 || $(target).prevObject)) { console.error("Target Invalid: " + JSON.stringify($(target))); return -1; } let sid = Date.now() + Math.random().toString(16).slice(2); let ls = moly.newElement("load-screen"); let lm = moly.newElement("load-message"); lm.append(message); if (style == "dots") { let p = moly.newElement("chat-load") ls.append(p); p.append(moly.newElement("span")); p.append(moly.newElement("span")); p.append(moly.newElement("span")); } else if (style == "bar") { ls.append(moly.newElement("progress-loop")); } else { ls.append(moly.newElement("sonar-ping")); } ls.append(lm); moly._loads.push({id: sid, screen: ls}); $(target).append(ls); return sid; } static update(id=0, message = "") { let i = moly._loads.findIndex(e=> e.id == id) if(i >= 0 && message.length > 0) { $(moly._loads[i].screen).children("load-message").html(message); } } static close(id=0) { let i = moly._loads.findIndex(e=> e.id == id) if(i >= 0) { moly._loads[i].screen.remove(); moly._loads.splice(i,1); } } } dialog = class { static clear() { $('dialog-box').remove(); moly._dialogs = []; } static async show({ title = "Dialog Box", content = "", fetching = false, data = {} } = {}) { let dialog = { id: Date.now() + Math.random().toString(16).slice(2), screen: moly.newElement("dialog-screen"), box: moly.newElement("dialog-box"), titlet: moly.newElement("span"), content: moly.newElement("div"), beforeResolve: [], resolve: undefined, data: data } moly._dialogs.push(dialog); dialog.screen.append(dialog.box) let titlebar = moly.newElement("dialog-title"); let closebtn = moly.newElement("a-button"); let closeicn = moly.newElement("m-icon"); closebtn.append(closeicn) closeicn.append("close"); closebtn.setAttribute("type","negative"); dialog.box.append(titlebar); $(dialog.box).css("width",data.width); $(dialog.box).css("height",data.height); titlebar.append(dialog.titlet); $(dialog.titlet).html(title); titlebar.append(closebtn); dialog.box.append(dialog.content); dialog.content.setAttribute("id",`db${dialog.id}`); dialog.content.style = "overflow-y: auto;" $('body').append(dialog.screen); setTimeout(()=>{ $(dialog.box).css("scale",1); },5); let loadid = moly.loadScreen.show("Memuat Konten...","sonar",`#db${dialog.id}`); $(dialog.content).html(fetching ? await fetch(content).then(r => r.text()) : content); moly.loadScreen.close(loadid); return new Promise((resolve,reject)=>{ dialog.resolve = function(out) { moly.dialog.close(dialog.id); resolve(out); } $(closebtn).click(()=>{ moly.dialog.close(dialog.id); resolve(false); }); }); } static close(id="") { let i = moly._dialogs.findIndex(e => e.id == id); //this.screens.findIndex(e=> e.id == id) if(i >= 0) { $(moly._dialogs[i].box).css("scale",0); setTimeout(() => { $.each(moly._dialogs[i].beforeResolve, (i,v)=> { typeof v == "function" ? v() : false; }); moly._dialogs[i].screen.remove(); moly._dialogs.splice(i,1); }, 250); } } static resolve(id = 0, data) { let i = moly._dialogs.findIndex(e => e.id == id); //this.screens.findIndex(e=> e.id == id) if(i >= 0) { moly._dialogs[i].resolve(data); } } } alert = class { static show(title = "Perhatian", messageHTML = "", timeOut = 0) { let alert = { id: Date.now() + Math.random().toString(16).slice(2), screen: moly.newElement("alert-screen"), box: moly.newElement("alert-box"), title: moly.newElement("alert-title"), message: moly.newElement("alert-text"), handle: moly.newElement("alert-handle") } alert.screen.append(alert.box); alert.box.append(alert.title); alert.box.append(alert.message); alert.box.append(alert.handle); alert.title.innerText = title; alert.message.innerHTML = messageHTML; alert.message.setAttribute("title", alert.message.innerText); if (timeOut >0 ) {alert.handle.innerText = `Tunggu ${timeOut} detik.`;} else {alert.handle.innerText = "Tutup";} moly._alerts.push(alert); $('body').append(alert.screen); setTimeout(() => { $(alert.box).css("scale",1); }, 5); setTimeout(() => { if ( timeOut > 0) { let ms = timeOut * 1000; let intv = setInterval(()=>{ ms -= 1000; if (ms <= 0) { moly.alert.close(alert.id); clearInterval(intv); return; } $(moly._alerts.findLast(()=>true).screen).find("alert-handle")[0].innerText = `Tunggu ${ms/1000} detik`; },1000); } else { $(alert.handle).click(()=> { moly.alert.close(alert.id); }) } }, 250); } static close(id = "") { let ix = moly._alerts.findIndex(e=> e.id == id) if (ix >= 0) { $(moly._alerts[ix].box).css("scale",0); setTimeout(()=>{ moly._alerts[ix].screen.remove(); moly._alerts.splice(ix,1); }, 250); } } } confirm = class { static show(title = "Anda Yakin?", messageHTML = "", yesLabel = "Yes", noLabel = "No", positiveYes = null, positiveNo = null) { let confirm = { id: Date.now() + Math.random().toString(16).slice(2), screen: moly.newElement("confirm-screen"), box: moly.newElement("confirm-box"), title: moly.newElement("confirm-title"), message: moly.newElement("confirm-text"), handle: moly.newElement("confirm-handle"), resolve: undefined } let yes = moly.newElement("a-button"); let no = moly.newElement("a-button"); confirm.screen.append(confirm.box); confirm.box.append(confirm.title); confirm.box.append(confirm.message); confirm.box.append(confirm.handle); confirm.title.innerText = title; confirm.message.innerHTML = messageHTML; confirm.message.setAttribute("title", confirm.message.innerText); confirm.handle.append(yes); confirm.handle.append(no); yes.innerText = yesLabel; no.innerText = noLabel; if (positiveYes == true) { yes.setAttribute("type","positive"); } else if (positiveYes == false) { yes.setAttribute("type","negative"); } else { yes.setAttribute("type","blend"); } if (positiveNo == true) { no.setAttribute("type","positive"); } else if (positiveNo == false) { no.setAttribute("type","negative"); } else { no.setAttribute("type","blend"); } moly._confirms.push(confirm); $('body').append(confirm.screen); setTimeout(() => { $(confirm.box).css("scale",1); }, 5); return new Promise((resolve,reject)=>{ confirm.resolve = function(out) { moly.confirm.close(confirm.id); resolve(out); } $(no).click(()=>{ moly.confirm.close(confirm.id); resolve(false); }); $(yes).click(()=>{ moly.confirm.close(confirm.id); resolve(true); }); }); } static close(id ="") { let i = moly._confirms.findIndex(e => e.id == id); //this.screens.findIndex(e=> e.id == id) if(i >= 0) { $(moly._confirms[i].box).css("scale",0); setTimeout(() => { moly._confirms[i].screen.remove(); moly._confirms.splice(i,1); }, 250); } } } file = class { static async crc32(file){ let crc32Table = new Uint32Array(256).map((t, c) => { for (let k = 0; k < 8; k++) { c = c & 1 ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1); } return c >>> 0; }); const arrayBuffer = await file.arrayBuffer(); const buf = new Uint8Array(arrayBuffer); let crc = 0 ^ (-1); for (let i = 0; i < buf.length; i++) { crc = (crc >>> 8) ^ crc32Table[(crc ^ buf[i]) & 0xFF]; } return ((crc ^ (-1)) >>> 0).toString(16).padStart(8, '0').toUpperCase(); }; static async serialise(file = new File([],"")){ let hash = await this.crc32(file); const reader = new FileReader(); return new Promise((resolve, reject) => { reader.onload = function(event) { const base64Data = event.target.result.split(',')[1]; const fileData = { name: file.name, type: file.type, data: base64Data, crc32: hash }; resolve(fileData); }; reader.onerror = reject; reader.readAsDataURL(file); }); }; static deserialise(fileData){ const byteCharacters = atob(fileData.data); const byteNumbers = new Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); const blob = new Blob([byteArray], { type: fileData.type }); const file = new File([blob], fileData.name, { type: fileData.type }); return file; } } AButton = class extends HTMLElement { static observedAttributes = ["disabled"]; constructor() { super(); this.attachShadow({ mode: "open" }); // Attach Shadow DOM this.shadowRoot.innerHTML = ` `; } get disabled() { return this.hasAttribute("disabled"); } set disabled(value) { if (value) { this.setAttribute("disabled", ""); } else { this.removeAttribute("disabled"); } } } FileInput = class extends HTMLElement { static observedAttributes = ["disabled", "accept", "multiple", "label"]; constructor() { super(); this.attachShadow({ mode: "open" }); // Attach Shadow DOM this.shadowRoot.innerHTML = ` `; this._input = document.createElement("input"); this._input.type = "file"; this._input.setAttribute("hidden",""); this._handleChange = this._handleChange.bind(this); this._handleClick = this._handleClick.bind(this); this._label = document.createElement("div"); } connectedCallback() { this._input.addEventListener("change", this._handleChange); this.shadowRoot.appendChild(this._input); this.shadowRoot.appendChild(this._label); this._label.innerText = "Click to select a file."; this.addEventListener("click", this._handleClick); if (this.getAttribute("accept")) { this._input.setAttribute("accept", this.accept); this.accept = this.getAttribute("accept"); } if (this.hasAttribute("multiple")) { this._input.setAttribute("multiple", ""); this.multiple = this.hasAttribute("multiple"); this._label.innerText = this.hasAttribute("label") ? this.getAttribute("label") : "Click to select files."; } else { this._input.removeAttribute("multiple"); this.multiple = this.hasAttribute("multiple"); this._label.innerText = this.hasAttribute("label") ? this.getAttribute("label") : "Click to select a file."; } } disconnectedCallback() { this._input.removeEventListener("change", this._handleChange); } _handleChange(event) { if (this.hasAttribute("multiple") && this._input.files.length == 1) { this._label.innerText = `${this._input.files[0].name} selected.`; } else if (this.hasAttribute("multiple") && this._input.files.length > 1) { this._label.innerText = `${this._input.files.length} files selected.` } else if (this.hasAttribute("multiple")) { this._label.innerText = this.hasAttribute("label") ? this.getAttribute("label") : "Click to select files."; } else if (this._input.files.length == 1) { this._label.innerText = `${this._input.files[0].name} selected.`; } else { this._label.innerText = this.hasAttribute("label") ? this.getAttribute("label") : "Click to select a file."; } this.dispatchEvent(new CustomEvent("change", {bubble: true, composed: true, detail: this.files})); } _handleClick(event) { this._input.click(); } get disabled() { return this.hasAttribute("disabled"); } set disabled(value) { if (value) { this.setAttribute("disabled", ""); } else { this.removeAttribute("disabled"); } } get files() { return this._input.files; } set accept(value) { if (value) { this._input.accept = value; this.setAttribute("accept", value); // Keep the attribute in sync } else { this._input.removeAttribute("accept"); this.removeAttribute("accept"); } } get accept() { return this._input.accept; } get label() { return this.getAttribute("label"); } set label(value) { if (value) { this._label.innerText = this.getAttribute("label"); } else { this._label.innerText = this.hasAttribute("multiple") ? "Click to select files." : "Click to select a file."; } } set multiple(value) { if (value) { this._input.setAttribute("multiple", ""); this.setAttribute("multiple", ""); this._label.innerText = "Click to select files."; } else { this._input.removeAttribute("multiple"); this.removeAttribute("multiple"); this._label.innerText = "Click to select a file."; } this._input.value = ""; } get multiple() { return this._input.hasAttribute("multiple"); } reset() { this._input.value = ""; if (this.hasAttribute("multiple")) { this._label.innerText = "Click to select files."; } else { this._label.innerText = "Click to select a file."; } return this._input.files.length; } } Group = class extends HTMLElement { static observedAttributes = ["label", "type", "collapsed", "disabled"]; constructor() { super(); this.attachShadow({mode: "open"}); this.shadowRoot.innerHTML = ` `; this._labelButton = $(this.shadowRoot).children("group-label").children("a-button"); this._labelText = $(this.shadowRoot).children("group-label").children("a-button").children("span"); this._labelIcon = $(this.shadowRoot).children("group-label").children("a-button").children("m-inline"); } connectedCallback() { if (this.label && this.label.length > 0) { $(this.shadowRoot).children("group-label").children("a-button").children("span").text(this.label); } else { $(this.shadowRoot).children("group-label").children("a-button").children("span").text("Group Label"); } if (this.collapsed) { $(this.shadowRoot).children("group-label").children("a-button").children("m-inline").text("expand_more"); } else { $(this.shadowRoot).children("group-label").children("a-button").children("m-inline").text("expand_less"); } this._labelButton.click(()=> { this.collapsed = !this.collapsed; }); } disconnectedCallback() { $(this.shadowRoot).children("group-label").children("a-button").off("click"); } attributeChangedCallback(name, oldValue, newValue) { if (name === "label") { if (this.label && this.label.length > 0) { this._labelText.text(this.label); } else { this._labelText.text("Group Label"); } } else if (name === "collapsed") { if (this.collapsed) { this._labelIcon.text("expand_more"); } else { this._labelIcon.text("expand_less"); } } else if (name === "disabled") { if (this.disabled) { this._labelIcon.text("expand_more"); this._labelButton.prop("disabled",true); } else { this._labelIcon.text("expand_less"); this._labelButton.prop("disabled",false); } } } get collapsed() { return this.hasAttribute("collapsed"); } set collapsed(value) { if (value) { this.setAttribute("collapsed", ""); } else { this.removeAttribute("collapsed"); } } get disabled() { return this.hasAttribute("disabled"); } set disabled(value) { if (value) { this.setAttribute("disabled", ""); } else { this.removeAttribute("disabled"); } } get label() { return this.getAttribute("label"); } set label(value) { this.setAttribute("label", value.toString()); } get type() { return this.getAttribute("type"); } set type(value) { this.setAttribute("type", value.toString()); } } VSpacer = class extends HTMLElement { static observedAttributes = ["lines"]; constructor() { super(); this.attachShadow({mode: "open"}); this.shadowRoot.innerHTML = `
`; this._div = $(this.shadowRoot).children("div"); } connectedCallback(){ this._applyHeight(); } attributeChangedCallback(name, oldValue, newValue) { if (name === "lines") { this._applyHeight(); } } _applyHeight() { const height = (!this.lines || isNaN(this.lines)) ? 1 : this.lines this._div.css("height",`${height}lh`); } get lines() { return Number(this.getAttribute("lines")); } set lines(value) { if (isNaN(value)) { this.setAttribute("lines", 1); } else { this.setAttribute("lines", Number(value)); } } } MultiLineEllipsis = class extends HTMLElement { static observedAttributes = ["lines"]; constructor() { super(); this.attachShadow({mode: "open"}); this.shadowRoot.innerHTML = `
`; this._div = $(this.shadowRoot).children("div"); this._slot = $(this.shadowRoot).find("slot"); } connectedCallback(){ this._applyLineClamp(); this.title = this.innerText; this._slot.on("slotchange", ()=>{this.title = this.innerText;}); } attributeChangedCallback(name, oldValue, newValue) { if (name === "lines") { this._applyLineClamp(); } } _applyLineClamp() { const lines = (!this.lines || isNaN(this.lines)) ? "1" : this.lines.toString(); this._div.css({ "line-clamp": lines, "-webkit-line-clamp": lines }); } get lines() { return Number(this.getAttribute("lines")); } set lines(value) { if (isNaN(value)) { this.setAttribute("lines", 1); } else { this.setAttribute("lines", Number(value)); } } } MidlineEllipsis = class extends HTMLElement { static observedAttributes = ["chars"]; constructor() { super(); this.attachShadow({mode: "open"}); this.shadowRoot.innerHTML = ` `; } connectedCallback(){ this.title = this.innerText; this._chars = Number(this.getAttribute("chars")); this._chars > 5 ? 1==1 : this.chars = 5; this._originalNodes = this.cloneNode(true); this._applyEllipsis(); this.addEventListener("click",()=>{ moly.alert.show("Konten Lengkap",this._originalNodes.innerHTML); }) } disconnectedCallback() { this.removeEventListener("click"); } _applyEllipsis() { let sourceNodes = this._originalNodes.cloneNode(true).childNodes; let skip = ["a-button","button", "img"]; let fhalftl = Math.ceil((this.chars-3)/2); let lhalftl = Math.floor((this.chars-3)/2); let fhalfl = 0; let lhalfl = 0; let fsearch = true; let fpart = moly.newElement("span"); let lpart = moly.newElement("span"); let title = ""; $.each(sourceNodes,(i,v)=>{ if (skip.includes(v.nodeName.toLowerCase())) return; title += v.textContent; if(fsearch && v.textContent.length > 0 ) { if (fhalfl + v.textContent.length < fhalftl) { fpart.append(v.cloneNode(true)); } else { let remaining = fhalftl - fhalfl ; let snode = v.cloneNode(true); snode.textContent = snode.textContent.substring(0,remaining); if(snode.textContent.length>0) fpart.append(snode); fpart.append("..."); fsearch = false; } fhalfl += v.textContent.length; } }); this.title = title.replace(/\s+/g, ' ').trim(); for (let i = sourceNodes.length - 1; i >= 0; i--) { let v = sourceNodes[i]; if (skip.includes(v.nodeName.toLowerCase())) continue; if(v.textContent.length > 0 ) { if (lhalfl + v.textContent.length < lhalftl) { lpart.prepend(v.cloneNode(true)); } else { let remaining = lhalftl - lhalfl; let snode = v.cloneNode(true); snode.textContent = snode.textContent.substring(snode.textContent.length-remaining); if(snode.textContent.length>0) lpart.prepend(snode); break; } lhalfl += v.textContent.length; } } fpart.innerHTML = fpart.innerHTML.trim(); lpart.innerHTML = lpart.innerHTML.trim() $(this).empty().append(...fpart.childNodes,...lpart.childNodes); } attributeChangedCallback(name, oldValue, newValue) { if (name === "chars") { if (this._originalNodes) this._applyEllipsis(); } } get chars() { let retval; this._chars = Number(this.getAttribute("chars")); this._chars > 5 ? retval = this._chars : retval = 5; return retval; } set chars(value) { if (isNaN(value)) { this._chars = 5 this.setAttribute("chars", 5); } else { this._chars = Number(value); this.setAttribute("chars", this._chars > 5 ? this.chars : 5); } } } } (async ()=>{ let time = 0 while (typeof window.jQuery === "undefined" && time < 10000) { await new Promise(resolve => setTimeout(resolve, 50)); time +=50; } if (window.jQuery) { window.moly = new molybdenum(); customElements.define("a-button",moly.AButton); customElements.define("file-input",moly.FileInput); customElements.define("group-el",moly.Group); customElements.define("v-spacer",moly.VSpacer); customElements.define("multi-ellipsis",moly.MultiLineEllipsis); customElements.define("midline-ellipsis",moly.MidlineEllipsis); } })();