agper-agen/assets/js/molybdenum.js
2025-06-01 10:41:52 +07:00

988 lines
34 KiB
JavaScript
Executable File

/*!
* molybdenum v1.1.0
* Copyright 2025 PT Nusa Angkasa Siber
* Released under the MIT License
*/
class molybdenum{
version = '1.1.0';
_alerts = [];
_confirms = [];
_loads = [];
_dialogs = [];
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}></${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 = `
<style>
:host {
display: inline;
-webkit-user-select: none;
user-select: none;
cursor: pointer;
font-family: 'Source Sans 3';
color: var(--primary-accent);
margin-inline-start: .5ch;
margin-inline-end: .5ch;
white-space: nowrap;
text-decoration: none;
filter: brightness(1);
transition: filter .35s ease, text-shadow .35s ease;
}
:host([disabled]) {
color: var(--secondary-foreground) !important;
pointer-events: none;
}
:host(:hover){
filter: brightness(1.25);
text-shadow: 0 0 .25em var(--semi-primary);
}
:host(:active){
filter: brightness(1.25);
text-shadow: 0 0 .75em var(--semi-primary);
}
:host([type="blend"]), :host([type="peek"])
{
color: inherit;
}
:host([type="blend"]:hover)
{
filter: brightness(4);
text-shadow: none;
}
:host([type="blend"]:active)
{
filter: brightness(8);
text-shadow: none;
}
:host([type="negative"])
{
color: var(--negative-accent);
}
:host([type="negative"]:hover)
{
filter: brightness(1.25);
text-shadow: 0 0 .35em var(--semi-negative);
}
:host([type="negative"]:active)
{
filter: brightness(1.25);
text-shadow: 0 0 .65em var(--semi-negative);
}
:host([type="peek"])
{
text-shadow: none;
position: absolute !important;
padding-top: .25em;
transform: translatex(-3.5ch);
}
</style>
<slot></slot>
`;
}
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"];
constructor() {
super();
this.attachShadow({ mode: "open" }); // Attach Shadow DOM
this.shadowRoot.innerHTML = `
<style>
:host
{
display: inline-flex;
width: 25ch;
height: 1.7em;
border: 1px solid var(--secondary-foreground);
user-select: none;
cursor: pointer;
text-align: center;
justify-content: center;
align-items: center;
color: var(--secondary-foreground);
}
div
{
font-size: .8em;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: calc(100%);
padding-inline-start: .75ch;
padding-inline-end: .75ch;
}
</style>
`;
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 = "Click to select files.";
}
}
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 = "Click to select files.";
}
else if (this._input.files.length == 1)
{
this._label.innerText = `${this._input.files[0].name} selected.`;
}
else
{
this._label.innerText = "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;
}
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"];
constructor()
{
super();
this.attachShadow({mode: "open"});
this.shadowRoot.innerHTML = `
<style>
:host
{
display: block;
padding: .5em .25em .5em .75em;
margin: .25em;
margin-top: 1em;
border-top: 1px solid var(--secondary-foreground);
}
:host([type="box"])
{
border: 1px solid var(--secondary-foreground);
}
:host([collapsed]:not([type="box"]))
{
padding: .35em;
}
:host([type="box"][collapsed])
{
padding: .32em;
}
group-label
{
display: inline-block;
background-color: var(--primary-background);
position: absolute;
top: -.85em;
left: .5ch;
padding-left: .25ch;
padding-right: .25ch;
}
:host([collapsed]) group-content
{
display: none;
}
m-inline
{
max-width: 1ch;
width: 1ch;
overflow: hidden;
font-family: 'Material Symbols Outlined';
font-weight: normal;
font-style: normal;
font-size: 1.25em;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
padding-bottom: .1em;
vertical-align: middle;
}
a-button
{
margin: 0;
}
</style>
<group-label><a-button><span></span> <m-inline></m-inline></a-button></group-label>
<group-content><slot></slot></group-content>
`;
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.shadowRoot).children("group-label").children("a-button").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");
}
}
}
get collapsed() {
return this.hasAttribute("collapsed");
}
set collapsed(value) {
if (value) {
this.setAttribute("collapsed", "");
} else {
this.removeAttribute("collapsed");
}
}
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 = `
<style>
:host
{
display: block;
}
</style>
<div></div>
`;
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 = `
<style>
:host
{
display: block;
}
div
{
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
<div><slot></slot></div>
`;
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 = `
<style>
:host
{
display: inline;
cursor: pointer;
}
</style>
<slot></slot>
`;
}
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);
}
})();