dzura/www/js/ui.js
2025-05-20 12:59:44 -07:00

530 lines
18 KiB
JavaScript

const UI = {
Row:class{
constructor(element, css_class) {
this.element = element;
this.css_class = css_class;
}
},
text(value) {
return document.createTextNode(value);
},
link(text, callback=null) {
let link = document.createElement("a");
link.innerText = text;
if(callback !== null) { link.addEventListener("click", callback); }
return link;
},
button(text, callback=null, select=false) {
let button = document.createElement("button");
button.innerText = text;
if(select) { button.setAttribute("class", "selected"); }
if(callback !== null) { button.addEventListener("click", callback); }
return button;
},
button_piece(piece, callback=null, select=false) {
let button = document.createElement("button");
let moves = document.createElement("canvas");
button.appendChild(moves);
setTimeout(UI.draw_play_icons, 10, moves, [piece]);
if(select) { button.setAttribute("class", "selected"); }
if(callback !== null) { button.addEventListener("click", callback); }
return button;
},
submit(text) {
let button = document.createElement("input");
button.setAttribute("type", "submit");
button.value = text;
return button;
},
slider(id, callback) {
let input = document.createElement("input");
if(id !== null) { input.setAttribute("id", id); }
input.setAttribute("type", "range");
input.setAttribute("value", "0");
input.setAttribute("min", "0");
input.setAttribute("max", "0");
if(callback !== null) { input.addEventListener("input", callback); }
return input;
},
checkbox(id, callback) {
let input = document.createElement("input");
if(id !== null) { input.setAttribute("id", id); }
input.setAttribute("type", "checkbox");
if(callback !== null) { input.addEventListener("change", callback); }
return input;
},
textbox(id, placeholder) {
let input = document.createElement("input");
input.setAttribute("type", "text");
if(id !== null) { input.setAttribute("id", id); }
input.setAttribute("placeholder", placeholder);
return input;
},
password(id) {
let input = document.createElement("input");
input.setAttribute("type", "password");
input.setAttribute("placeholder", "◌◌◌◌◌◌");
if(id !== null) { input.setAttribute("id", id); }
return input;
},
label(name, id) {
let label = document.createElement("label");
label.setAttribute("for", id);
label.innerText = name;
return label;
},
span(children, attr_class) {
let span = document.createElement("span");
if(attr_class !== undefined) { span.setAttribute("class", attr_class); }
for(child of children) { span.appendChild(child); }
return span;
},
div(children, attr_class) {
let div = document.createElement("div");
if(attr_class !== undefined) { div.setAttribute("class", attr_class); }
for(child of children) { div.appendChild(child); }
return div;
},
maincontent(element) {
let box = document.createElement("div");
box.setAttribute("class", "box");
box.appendChild(element);
MAIN.appendChild(box);
},
table_content(header, rows) {
let tbody = document.createElement("tbody");
if(header !== null) {
let row = document.createElement("tr");
for(head of header) {
let cell = document.createElement("th");
cell.innerText = head;
row.appendChild(cell);
}
tbody.appendChild(row);
}
for(row of rows) {
let tr = document.createElement("tr");
for(node of row) {
let cell = document.createElement("td");
if(Array.isArray(node)) { for(item of node) { cell.appendChild(item); } }
else { cell.appendChild(node); }
tr.appendChild(cell);
}
tbody.appendChild(tr);
}
return tbody;
},
table(header, rows) {
let table = document.createElement("table");
table.appendChild(this.table_content(header, rows));
return table;
},
mainnav(left_children, right_children, features={}) {
let header = document.createElement("nav");
let left = document.createElement("section");
if(sessionStorage.getItem("auth") === null) {
if(features.auth === true) {
left.appendChild(UI.button(LANG("register"), () => { SCENE.load(SCENES.Register); }));
left.appendChild(UI.button(LANG("login"), () => { SCENE.load(SCENES.Authenticate); }));
}
} else {
if(features.session === true) {
let button_challenge = UI.button(LANG("requests"), () => { SCENE.load(SCENES.Requests); });
button_challenge.setAttribute("id", "button-requests");
left.appendChild(UI.button(LANG("users"), () => { SCENE.load(SCENES.Users); }));
left.appendChild(button_challenge);
}
}
for(child of left_children) { left.appendChild(child); }
let right = document.createElement("section");
for(child of right_children) { right.appendChild(child); }
header.appendChild(left);
header.appendChild(right);
MAIN.appendChild(header);
},
nav(top, bottom) {
let section = document.createElement("section");
for(node of top) { section.appendChild(node); }
MENU.appendChild(section);
section = document.createElement("section");
for(node of bottom) { section.appendChild(node); }
MENU.appendChild(section);
},
mainmenu(page) {
if(SOCKET !== null) {
let top = [ ];
let bottom = [ ];
top.push(UI.button(LANG("browse"), () => { SCENE.load(SCENES.Browse); }, page == "browse"));
if(sessionStorage.getItem("auth") !== null) {
let button_resume = UI.button(LANG("resume"), () => { SCENE.load(SCENES.Continue); }, page == "continue");
button_resume.setAttribute("id", "button-resume");
top.push(button_resume);
//top.push(UI.button("Join", () => { SCENE.load(SCENES.Join); }, page == "join"));
}
top.push(UI.button(LANG("live"), () => { SCENE.load(SCENES.Live); }, page == "live"));
top.push(UI.button(LANG("history"), () => { SCENE.load(SCENES.History); }, page == "history"));
top.push(UI.button(LANG("practice"), () => { SCENE.load(SCENES.GamePractice); }, page == "practice"));
top.push(UI.button(LANG("guide"), () => { SCENE.load(SCENES.Guide); }, page == "guide"));
top.push(UI.button(LANG("notice"), () => { SCENE.load(SCENES.Notice); }, page == "notice"));
top.push(UI.button(LANG("about"), () => { SCENE.load(SCENES.About); }, page == "about"));
if(CONTEXT.Auth !== null) {
bottom.push(UI.button(LANG("account"), () => { SCENE.load(SCENES.Account); }));
}
bottom.push(UI.button(LANG("extras"), () => { SCENE.load(SCENES.Extras) }, page == "extras"));
UI.nav(top, bottom);
}
},
mainmenu_account(handle, page) {
let buttons_top = [ ];
let buttons_bottom = [ ];
// Top Buttons
buttons_top.push(UI.button("Profile", () => { SCENE.load(SCENES.Profile, { handle:CONTEXT.Auth.handle }) }, page == "profile"));
// Bottom Buttons
if(CONTEXT.Auth !== null && (handle === null || handle == CONTEXT.Auth.handle)) {
buttons_bottom.push(UI.button("Account", () => { SCENE.load(SCENES.Account) }, page == "account"));
buttons_bottom.push(UI.button("Invitations", () => { SCENE.load(SCENES.Invitations) }, page == "invitations"));
}
buttons_bottom.push(UI.button(LANG("back"), () => { SCENE.load(SCENES.Browse); }));
UI.nav(buttons_top, buttons_bottom);
},
mainmenu_guide(page, game_buttons=false) {
let buttons_top = [ ];
let buttons_bottom = [ ];
// Top Buttons
buttons_top.push(UI.button("Pieces", () => { SCENE.load(SCENES.Guide); }, page == "guide_pieces"));
buttons_top.push(UI.button("Drops", () => { SCENE.load(SCENES.Guide, "drop"); }, page == "guide_drop"));
buttons_top.push(UI.button("Promotion", () => { SCENE.load(SCENES.Guide, "promote"); }, page == "guide_promote"));
buttons_top.push(UI.button("Check", () => { SCENE.load(SCENES.Guide, "check"); }, page == "guide_check"));
buttons_top.push(UI.button("Checkmate", () => { SCENE.load(SCENES.Guide, "checkmate"); }, page == "guide_checkmate"));
buttons_top.push(UI.button("Freeplay", () => { SCENE.load(SCENES.Guide, "freeplay"); }, page == "guide_freeplay"));
// Bottom Buttons
if(game_buttons) {
buttons_bottom.push(UI.button(LANG("undo"), () => { INTERFACE.undo(); }));
buttons_bottom.push(UI.button(LANG("reset"), () => { INTERFACE.reset(); }));
}
buttons_bottom.push(UI.button(LANG("back"), () => { SCENE.load(SCENES.Browse); }));
UI.nav(buttons_top, buttons_bottom);
},
session_table(records) {
let rows = [ ];
for(let r = 0; r < records.length; ++r) {
let record = records[r];
let buttons = [ ];
let join_callback = function() {
SCENE.load(SCENES.Game, {
token:this.token,
mode:INTERFACE.Mode.Player,
});
};
join_callback = join_callback.bind({
token: record.token,
});
let spectate_callback = function() {
SCENE.load(SCENES.Game, {
token:this.token,
mode:INTERFACE.Mode.Review,
});
};
spectate_callback = spectate_callback.bind({
token: record.token,
});
if(record.player != 0) {
let button_resume = UI.button(LANG("resume"), join_callback);
if(record.is_turn) {
button_resume.setAttribute("class", "highlight");
}
buttons.push(button_resume);
buttons.push(UI.button(LANG("review"), spectate_callback));
} else {
buttons.push(UI.button(LANG("view"), spectate_callback));
}
let callback_profile_dawn = function() { SCENE.load(SCENES.Profile, { handle: this.handle }); }
callback_profile_dawn = callback_profile_dawn.bind({ handle: record.dawn });
let callback_profile_dusk = function() { SCENE.load(SCENES.Profile, { handle: this.handle }); }
callback_profile_dusk = callback_profile_dusk.bind({ handle: record.dusk });
let dawn = UI.link(record.dawn, callback_profile_dawn);
let dusk = UI.link(record.dusk, callback_profile_dusk);
let moves = document.createElement("canvas");
setTimeout(UI.draw_play_icons, 10, moves, record.moves);
rows.push([
dawn,
dusk,
UI.text(records[r].turn),
moves,
UI.text(records[r].viewers),
buttons,
]);
}
let tbody = UI.table_content(
[ LANG("dawn"), LANG("dusk"), LANG("turns"), "", LANG("viewers"), "" ],
rows,
);
return tbody;
},
session_table_resume(records) {
let rows = [ ];
for(let r = 0; r < records.length; ++r) {
let buttons = [ ];
let join_callback = function() {
SCENE_FORWARD = SCENE;
SCENE.load(SCENES.Game, {
token:this.token,
mode:INTERFACE.Mode.Player,
});
};
join_callback = join_callback.bind({token: records[r].token});
let spectate_callback = function() {
SCENE_FORWARD = SCENE;
SCENE.load(SCENES.Game, {
token:this.token,
mode:INTERFACE.Mode.Review,
});
};
spectate_callback = spectate_callback.bind({token: records[r].token});
let button_resume = UI.button(LANG("resume"), join_callback);
if(records[r].is_turn) {
button_resume.setAttribute("class", "highlight");
}
buttons.push(button_resume);
buttons.push(UI.button(LANG("review"), spectate_callback));
let handle = records[r].dawn;
if(records[r].player == 1) { handle = records[r].dusk; }
let callback_profile = function() { SCENE.load(SCENES.Profile, { handle: this.handle }); }
callback_profile = callback_profile.bind({ handle: handle });
let link_handle = UI.link(handle, callback_profile);
rows.push([
link_handle,
UI.text(records[r].turn),
UI.text(records[r].viewers),
buttons,
]);
}
let tbody = UI.table_content(
[ LANG("handle"), LANG("turns"), LANG("viewers"), "" ],
rows,
);
return tbody;
},
session_table_history(records) {
let rows = [ ];
for(let r = 0; r < records.length; ++r) {
let record = records[r];
let buttons = [ ];
let view_callback = function() {
SCENE_FORWARD = SCENE;
SCENE.load(SCENES.Game, {
token:this.token,
mode:INTERFACE.Mode.Review,
});
MESSAGE_SESSION_VIEW(this.token, false);
};
view_callback = view_callback.bind({token: record.token});
buttons.push(UI.button(LANG("review"), view_callback));
let callback_profile_dawn = function() { SCENE.load(SCENES.Profile, { handle: this.handle }); }
callback_profile_dawn = callback_profile_dawn.bind({ handle: record.dawn });
let callback_profile_dusk = function() { SCENE.load(SCENES.Profile, { handle: this.handle }); }
callback_profile_dusk = callback_profile_dusk.bind({ handle: record.dusk });
let dawn = UI.link(record.dawn, callback_profile_dawn);
let dusk = UI.link(record.dusk, callback_profile_dusk);
switch(record.is_complete) {
case 1: dawn = UI.span([dawn], "c_dawn bold"); break;
case 2: dusk = UI.span([dusk], "c_dusk bold"); break;
}
let moves = document.createElement("canvas");
setTimeout(UI.draw_play_icons, 10, moves, record.moves);
rows.push([
dawn,
dusk,
UI.text(record.turn),
moves,
buttons,
]);
}
let tbody = UI.table_content(
[ LANG("dawn"), LANG("dusk"), LANG("turns"), "", "" ],
rows,
);
return tbody;
},
invite_table(records) {
let rows = [ ];
for(let r = 0; r < records.length; ++r) {
let record = records[r];
rows.push([
UI.span([UI.text(record)], "monospace"),
UI.text(""),
]);
}
let tbody = UI.table_content(
[ LANG("invitation"), "" ],
rows,
);
return tbody;
},
page_indicator(first, last, total) {
let ind = document.getElementById("indicator-page");
UI.clear(ind);
ind.appendChild(UI.text(LANG_PAGEOF(first, last, total)));
},
clear(dom) {
while(dom.lastChild !== null) { dom.removeChild(dom.lastChild); }
},
rebuild() {
this.clear(document.body);
MENU = document.createElement("nav");
let title = document.createElement("header");
title.innerText = "Dzura";
MENU.appendChild(title);
MAIN = document.createElement("main");
document.body.appendChild(MENU);
document.body.appendChild(MAIN);
},
update_status() {
let b_requests = document.getElementById("button-requests");
let b_resume = document.getElementById("button-resume");
if(b_requests !== null) {
let text = LANG("requests");
if(STATUS.challenge > 0) {
if(STATUS.challenge < 10) {
text += " " + String.fromCharCode("❶".charCodeAt(0) + (STATUS.challenge - 1));
} else {
text += " ❾";
}
} else {
text += " ⓿";
}
b_requests.innerText = text;
}
if(b_resume !== null) {
let text = LANG("resume");
if(STATUS.resume > 0) {
if(STATUS.resume < 10) {
text += " " + String.fromCharCode("❶".charCodeAt(0) + (STATUS.resume - 1));
} else {
text += " ❾";
}
} else {
text += " ⓿";
}
b_resume.innerText = text;
}
},
draw_play_icons(canvas, moves) {
canvas.width = canvas.clientWidth * (window.devicePixelRatio || 1);
canvas.height = canvas.clientHeight * (window.devicePixelRatio || 1);
let ctx = canvas.getContext("2d");
let size = canvas.height / 2;
for(let i = 0; i < moves.length; ++i) {
let piece = moves[i] & 0x7;
let promoted = (moves[i] & 0x8) >> 3;
let player = (moves[i] & 0x10) >> 4;
let color = null;
if(player == 0) { color = INTERFACE.Color.Dawn; } else { color = INTERFACE.Color.Dusk; }
let piece_name = GAME.Const.Piece[piece].name;
if(promoted) {
GAME_ASSET.Image.Promote.draw(ctx, canvas.height, [size * (1 + (2 * i)), size], INTERFACE.Color.Promote);
}
GAME_ASSET.Image[piece_name].draw(ctx, canvas.height, [size * (1 + (2 * i)), size], color);
}
},
};