300 lines
14 KiB
JavaScript
300 lines
14 KiB
JavaScript
"use strict";
|
|
|
|
const JudgePole = (function(){
|
|
|
|
const JudgePole = function(){
|
|
|
|
const self = this,
|
|
screen = {x : 0, y : 0};
|
|
let cells = 40,
|
|
thread_interval = null,
|
|
fps = 12;
|
|
|
|
this.item_self = document;
|
|
|
|
this.settings = new SettingsManager(self);
|
|
this.i18n = new I18NManager(self);
|
|
this.db = new DatabaseManager(self);
|
|
this.session = new SessionManager(self);
|
|
this.views = {};
|
|
this.content_area = null;
|
|
|
|
const constructor = () => {
|
|
|
|
const position = self.settings.get("position", null, "body");
|
|
let target, preload = setInterval(() => {
|
|
if(target = document.querySelector(position)){
|
|
clearInterval(preload);
|
|
|
|
self.item_self = target.appendChild(document.createElement("div"));
|
|
self.item_self.className = self.item_self.id = self.settings.get("class_name", null, "judge-pole");
|
|
self.item_self.setAttribute("data-cells", cells = self.settings.get("cells", null, cells));
|
|
|
|
thread_interval = setInterval(thread, 1000 / fps);
|
|
|
|
build();
|
|
|
|
};
|
|
}, fps = self.settings.get(["frames_per_second", "fps"], null, fps));
|
|
|
|
Common.SCSS(SCSS);
|
|
|
|
};
|
|
|
|
const thread = () => {
|
|
if(screen.x != self.item_self.offsetWidth || screen.y != self.item_self.offsetHeight){
|
|
screen.x = self.item_self.offsetWidth;
|
|
screen.y = self.item_self.offsetHeight;
|
|
|
|
self.item_self.style.fontSize = ((screen.x < screen.y ? screen.x : screen.y) / cells) + "px";
|
|
|
|
};
|
|
};
|
|
|
|
const build = () => {
|
|
self.views.users = new UsersView(self);
|
|
self.views.projects = new ProjectsView(self);
|
|
self.views.votes = new VotesView(self);
|
|
|
|
self.render_header();
|
|
|
|
self.content_area = self.html(["main", { "class" : "jp-content-area" }])[0];
|
|
self.item_self.appendChild(self.content_area);
|
|
|
|
switch_view("votes");
|
|
};
|
|
|
|
// 1. Declarar render_header expuesto en 'this' / 'self'
|
|
this.render_header = () => {
|
|
const existing_header = self.item_self.querySelector("header.jp-header");
|
|
if(existing_header) existing_header.remove();
|
|
|
|
const is_admin = self.session.is_admin();
|
|
const auth_required = self.session.is_auth_required();
|
|
|
|
const nav_buttons = [
|
|
["button", { "class" : "jp-btn" + (self.current_view === "users" ? " is-active" : ""), "data-i18n" : "nav_users", "onclick" : (btn) => switch_view("users", btn) }, self.i18n.get("nav_users")],
|
|
["button", { "class" : "jp-btn" + (self.current_view === "projects" ? " is-active" : ""), "data-i18n" : "nav_projects", "onclick" : (btn) => switch_view("projects", btn) }, self.i18n.get("nav_projects")],
|
|
["button", { "class" : "jp-btn" + (self.current_view === "votes" ? " is-active" : ""), "data-i18n" : "nav_votes", "onclick" : (btn) => switch_view("votes", btn) }, self.i18n.get("nav_votes")]
|
|
];
|
|
|
|
// Botón de Base64 sólo para administradores
|
|
if(is_admin){
|
|
nav_buttons.push(["button", { "class" : "jp-btn jp-btn-secondary", "data-i18n" : "nav_export", "onclick" : () => show_export_modal() }, self.i18n.get("nav_export")]);
|
|
}
|
|
|
|
// Botón de Login / Logout si la base de datos requiere autenticación
|
|
if(auth_required){
|
|
if(is_admin){
|
|
nav_buttons.push(["button", {
|
|
"type" : "button",
|
|
"class" : "jp-btn-danger",
|
|
"data-i18n" : "nav_logout",
|
|
"style" : "margin-left:0.5em;",
|
|
"onclick" : () => {
|
|
self.session.logout();
|
|
self.render_header();
|
|
self.views[self.current_view].go();
|
|
}
|
|
}, self.i18n.get("nav_logout")]);
|
|
} else {
|
|
nav_buttons.push(["button", {
|
|
"type" : "button",
|
|
"class" : "jp-btn-primary",
|
|
"data-i18n" : "nav_login",
|
|
"style" : "margin-left:0.5em;",
|
|
"onclick" : () => show_login_modal()
|
|
}, self.i18n.get("nav_login")]);
|
|
}
|
|
}
|
|
|
|
const current_lang = self.i18n.get_current_language();
|
|
const lang_options = self.i18n.get_languages().map(lang_key => {
|
|
const label_key = "lang_" + lang_key;
|
|
return ["option", {
|
|
"value" : lang_key,
|
|
"data-i18n" : label_key,
|
|
...(lang_key === current_lang ? { "selected" : "selected" } : {})
|
|
}, self.i18n.get(label_key, null, lang_key)];
|
|
});
|
|
|
|
const lang_select = self.html(["select", {
|
|
"class" : "jp-lang-select",
|
|
"onchange" : (select) => {
|
|
self.i18n.set_language(select.value);
|
|
if(self.current_view && self.views[self.current_view]){
|
|
self.views[self.current_view].go();
|
|
}
|
|
}
|
|
}, lang_options])[0];
|
|
|
|
const header = self.html(["header", { "class" : "jp-header" }, [
|
|
["h1", { "class" : "jp-title", "data-i18n" : "judge_pole" }, self.i18n.get("judge_pole")],
|
|
["div", { "style" : "display:flex; align-items:center; gap:0.6em;" }, [
|
|
["nav", { "class" : "jp-nav" }, nav_buttons],
|
|
lang_select
|
|
]]
|
|
]])[0];
|
|
|
|
self.item_self.insertBefore(header, self.content_area || null);
|
|
};
|
|
|
|
const show_login_modal = () => {
|
|
const close_modal = () => {
|
|
if(modal_overlay && modal_overlay.parentNode) modal_overlay.remove();
|
|
};
|
|
|
|
const modal_overlay = self.html(["div", {
|
|
"class" : "jp-modal-overlay",
|
|
"onclick" : (overlay, e) => { if(e.target === overlay) close_modal(); }
|
|
}, [
|
|
["div", { "class" : "jp-modal-window", "style" : "max-width:24em;" }, [
|
|
["div", { "class" : "jp-modal-header" }, [
|
|
["h3", { "data-i18n" : "login_modal_title" }, self.i18n.get("login_modal_title")],
|
|
["button", { "type" : "button", "class" : "jp-btn-danger", "onclick" : () => close_modal() }, "✕"]
|
|
]],
|
|
["div", { "class" : "jp-modal-body" }, [
|
|
["form", {
|
|
"onsubmit" : (f, e) => {
|
|
e.preventDefault();
|
|
const nick = f.querySelector("[name='nick']").value;
|
|
const pass = f.querySelector("[name='password']").value;
|
|
if(self.session.login(nick, pass)){
|
|
close_modal();
|
|
self.render_header();
|
|
self.views[self.current_view].go();
|
|
} else {
|
|
alert(self.i18n.get("login_error"));
|
|
}
|
|
}
|
|
}, [
|
|
["div", { "class" : "jp-field" }, [
|
|
["label", { "data-i18n" : "login_nick" }, self.i18n.get("login_nick")],
|
|
["input", { "type" : "text", "name" : "nick", "required" : "required" }]
|
|
]],
|
|
["div", { "class" : "jp-field" }, [
|
|
["label", { "data-i18n" : "login_pass" }, self.i18n.get("login_pass")],
|
|
["input", { "type" : "password", "name" : "password", "required" : "required" }]
|
|
]],
|
|
["div", { "style" : "display:flex; justify-content:flex-end; gap:0.5em; margin-top:1em;" }, [
|
|
["button", { "type" : "button", "class" : "jp-btn", "onclick" : () => close_modal() }, "Cancelar"],
|
|
["button", { "type" : "submit", "class" : "jp-btn-primary", "data-i18n" : "login_btn" }, self.i18n.get("login_btn")]
|
|
]]
|
|
]]
|
|
]]
|
|
]]
|
|
]])[0];
|
|
|
|
self.item_self.appendChild(modal_overlay);
|
|
};
|
|
|
|
const switch_view = (view_name, active_btn) => {
|
|
self.current_view = view_name;
|
|
self.item_self.querySelectorAll(".jp-nav .jp-btn").forEach(b => b.classList.remove("is-active"));
|
|
if(active_btn) active_btn.classList.add("is-active");
|
|
|
|
if(self.views[view_name])
|
|
self.views[view_name].go();
|
|
};
|
|
|
|
const show_export_modal = () => {
|
|
const b64 = self.db.export_base64();
|
|
|
|
const close_modal = () => {
|
|
if(modal_overlay && modal_overlay.parentNode)
|
|
modal_overlay.remove();
|
|
};
|
|
|
|
const modal_overlay = self.html(["div", {
|
|
"class" : "jp-modal-overlay",
|
|
"onclick" : (overlay, event) => {
|
|
// Cerrar sólo si se hace clic sobre el fondo oscuro exterior
|
|
if(event.target === overlay) close_modal();
|
|
}
|
|
}, [
|
|
["div", { "class" : "jp-modal-window", "style" : "max-width:32em;" }, [
|
|
["div", { "class" : "jp-modal-header" }, [
|
|
["h3", {"data-i18n" : "db_modal_title"}, self.i18n.get("db_modal_title")],
|
|
["button", { "type" : "button", "class" : "jp-btn-danger", "onclick" : () => close_modal() }, "✕"]
|
|
]],
|
|
["div", { "class" : "jp-modal-body", "style" : "display:flex; flex-direction:column; gap:0.6em;" }, [
|
|
["p", { "class" : "jp-desc", "style" : "margin:0;" }, "Copia esta constante para sobreescribir /Public/data/database.ecma.js:"],
|
|
["textarea", {
|
|
"style" : "width:100%; height:8em; box-sizing:border-box; font-family:monospace; font-size:0.75em; background:#0d1117; color:#58a6ff; border:0.05em solid #30363d; padding:0.5em; border-radius:0.2em; resize:none;",
|
|
"readonly" : "readonly"
|
|
}, `const database = "${b64}";`],
|
|
["div", { "style" : "display:flex; justify-content:flex-end; gap:0.5em; margin-top:0.5em;" }, [
|
|
["button", { "type" : "button", "class" : "jp-btn-primary", "data-i18n" : "db_copy_btn", "onclick" : () => {
|
|
navigator.clipboard.writeText(`const database = "${b64}";`);
|
|
alert(self.i18n.get("db_copied"));
|
|
}}, self.i18n.get("db_copy_btn")],
|
|
["button", { "type" : "button", "class" : "jp-btn-secondary", "data-i18n" : "db_import_btn", "onclick" : () => {
|
|
const input = prompt("Pega el Base64:");
|
|
if(input && self.db.import_base64(input)){
|
|
alert(self.i18n.get("db_imported"));
|
|
self.views.users.go();
|
|
close_modal();
|
|
}
|
|
}}, self.i18n.get("db_import_btn")],
|
|
["button", { "type" : "button", "class" : "jp-btn", "onclick" : () => close_modal() }, "Cerrar"]
|
|
]]
|
|
]]
|
|
]]
|
|
]])[0];
|
|
|
|
self.item_self.appendChild(modal_overlay);
|
|
};
|
|
|
|
/**
|
|
* @param {...(HTMLElement|string|Array.<any|null>)} inputs
|
|
* @returns {Array.<HTMLElement>}
|
|
* @access public
|
|
* @static
|
|
*/
|
|
this.html = (...inputs) => {
|
|
const fragment = new DocumentFragment(),
|
|
master = (
|
|
inputs[0] && (inputs[0].tagName || inputs[0].nodeName) ? inputs[0] :
|
|
typeof inputs[0] == "string" ? document.querySelector(inputs[0]) :
|
|
null),
|
|
items = [];
|
|
|
|
(master ? inputs.slice(1) : inputs).forEach(subinputs => {
|
|
if(subinputs && (subinputs.tagName || subinputs.nodeName))
|
|
fragment.appendChild(subinputs);
|
|
else if(typeof subinputs == "string")
|
|
fragment.appendChild(document.createTextNode(subinputs));
|
|
else if(subinputs instanceof Array){
|
|
const [tag, attributes, children] = subinputs.concat([null, null]).slice(0, 3),
|
|
item = document.createElement(tag);
|
|
|
|
attributes && attributes.constructor == Object && Object.entries(attributes).forEach(([key, value]) => {
|
|
if(/^on[_\-]?/i.test(key) && typeof value == "function")
|
|
item.addEventListener(key.replace(/^on[_\-]?/i, "").replace(/[^a-z0-9]+/gi, "").toLowerCase(), event => {
|
|
value(item, event);
|
|
});
|
|
else if(value !== null)
|
|
item.setAttribute(key.replace(/[^a-z0-9]+/gi, "-"), value);
|
|
});
|
|
|
|
if(children instanceof Array)
|
|
self.html(...children).forEach(child => item.appendChild(child));
|
|
else if(typeof children == "string")
|
|
item.innerHTML += children;
|
|
|
|
fragment.appendChild(item);
|
|
};
|
|
});
|
|
|
|
items.push(...fragment.childNodes);
|
|
master && master.appendChild(fragment);
|
|
|
|
return items;
|
|
};
|
|
|
|
constructor();
|
|
|
|
};
|
|
|
|
return JudgePole;
|
|
})(); |