Files
justice/www/html/Game/public/js/display-name.js
T
2026-05-27 08:02:22 +00:00

251 lines
10 KiB
JavaScript

(function (global) {
'use strict';
var DISPLAY_NAME_KEY = 'roomLobbyDisplayName';
var PLAYER_NAME_KEY = 'playerName';
var MAX_LEN = 24;
function sanitizeDisplayName(raw) {
var s = String(raw == null ? '' : raw).trim();
if (!s) return '';
try {
s = s.normalize('NFKC');
} catch (e) { /* ignore */ }
s = s.replace(/[\x00-\x1f\x7f]/g, '');
if (s.length > MAX_LEN) s = s.slice(0, MAX_LEN);
return s;
}
function readDisplayName() {
try {
return (
localStorage.getItem(DISPLAY_NAME_KEY) ||
localStorage.getItem(PLAYER_NAME_KEY) ||
'MONE'
).trim() || 'MONE';
} catch (e) {
return 'MONE';
}
}
function syncDisplayNameDom(displayName) {
var text = String(displayName || '').trim();
if (!text) return;
var upper = text.toUpperCase();
var ids = [
'lobby-profile-name',
'room-lobby-profile-overlay-name',
'room-lobby-profile-name'
];
ids.forEach(function (id) {
var el = document.getElementById(id);
if (el) el.textContent = upper;
});
try {
document.querySelectorAll('[data-jd-display-name]').forEach(function (el) {
el.textContent = upper;
});
} catch (e2) { /* ignore */ }
}
function replaceNickQueryParam(displayName) {
try {
var u = new URL(global.location.href);
if (!u.searchParams.has('nick')) return;
u.searchParams.set('nick', displayName);
global.history.replaceState({}, '', u.pathname + u.search + u.hash);
} catch (e) { /* ignore */ }
}
/**
* @param {string} nextName
* @param {{ socket?: object, silent?: boolean }} [options]
* @returns {{ ok: boolean, displayName?: string, error?: string }}
*/
function applyDisplayNameChange(nextName, options) {
var safe = sanitizeDisplayName(nextName);
if (!safe) return { ok: false, error: 'empty' };
var opts = options || {};
try {
localStorage.setItem(DISPLAY_NAME_KEY, safe);
localStorage.setItem(PLAYER_NAME_KEY, safe);
} catch (e) { /* ignore */ }
syncDisplayNameDom(safe);
replaceNickQueryParam(safe);
try {
global.dispatchEvent(new CustomEvent('jd-display-name-changed', {
detail: { displayName: safe }
}));
} catch (e2) { /* ignore */ }
var sock = opts.socket || global.__jdGameSocket || null;
if (sock && typeof sock.emit === 'function' && sock.connected) {
sock.emit('update-display-name', { nickname: safe }, function (res) {
if (res && res.ok && res.nickname) {
try {
localStorage.setItem(DISPLAY_NAME_KEY, res.nickname);
localStorage.setItem(PLAYER_NAME_KEY, res.nickname);
} catch (e3) { /* ignore */ }
syncDisplayNameDom(res.nickname);
}
});
}
return { ok: true, displayName: safe };
}
var editorReady = false;
function ensureNameEditorDom() {
if (editorReady && document.getElementById('jd-display-name-editor-overlay')) return;
if (!document.getElementById('jd-display-name-editor-style')) {
var st = document.createElement('style');
st.id = 'jd-display-name-editor-style';
st.textContent = '' +
'.jd-dne-overlay{position:fixed;inset:0;z-index:10080;display:flex;align-items:center;justify-content:center;padding:16px;}' +
'.jd-dne-overlay.is-hidden{display:none!important;}' +
'.jd-dne-backdrop{position:absolute;inset:0;background:rgba(0,0,0,.72);}' +
'.jd-dne-dialog{position:relative;z-index:1;width:min(420px,calc(100vw - 32px));padding:22px 20px 18px;border-radius:14px;' +
'background:linear-gradient(180deg,#0d2848 0%,#061528 100%);border:2px solid rgba(56,232,255,.85);box-shadow:0 0 28px rgba(56,232,255,.35);color:#eaf8ff;font-family:Kanit,Segoe UI,sans-serif;}' +
'.jd-dne-title{margin:0 0 14px;font-size:1.25rem;font-weight:700;text-align:center;letter-spacing:.02em;}' +
'.jd-dne-input{display:block;width:100%;box-sizing:border-box;padding:12px 14px;border-radius:10px;border:2px solid rgba(56,232,255,.55);' +
'background:rgba(0,20,40,.85);color:#fff;font-size:1.05rem;font-family:inherit;outline:none;}' +
'.jd-dne-input:focus{border-color:#5ce9ff;box-shadow:0 0 0 3px rgba(92,233,255,.25);}' +
'.jd-dne-hint{margin:8px 0 0;font-size:.82rem;opacity:.75;text-align:center;}' +
'.jd-dne-error{margin:8px 0 0;font-size:.85rem;color:#ff8a8a;text-align:center;min-height:1.1em;}' +
'.jd-dne-actions{display:flex;gap:10px;margin-top:18px;justify-content:center;}' +
'.jd-dne-btn{min-width:110px;padding:10px 16px;border-radius:10px;border:2px solid transparent;font-size:1rem;font-weight:600;font-family:inherit;cursor:pointer;}' +
'.jd-dne-btn--cancel{background:rgba(255,255,255,.08);border-color:rgba(255,255,255,.25);color:#e2e8f0;}' +
'.jd-dne-btn--confirm{background:linear-gradient(180deg,#2ee6ff,#0ea5c9);border-color:#7df4ff;color:#042033;}';
document.head.appendChild(st);
}
if (!document.getElementById('jd-display-name-editor-overlay')) {
var wrap = document.createElement('div');
wrap.id = 'jd-display-name-editor-overlay';
wrap.className = 'jd-dne-overlay is-hidden';
wrap.setAttribute('aria-hidden', 'true');
wrap.innerHTML = '' +
'<div class="jd-dne-backdrop" id="jd-dne-backdrop" aria-hidden="true"></div>' +
'<div class="jd-dne-dialog" role="dialog" aria-modal="true" aria-labelledby="jd-dne-title">' +
' <h2 class="jd-dne-title" id="jd-dne-title">แก้ไขชื่อผู้เล่น</h2>' +
' <input type="text" id="jd-dne-input" class="jd-dne-input" maxlength="24" autocomplete="nickname" aria-label="ชื่อผู้เล่น">' +
' <p class="jd-dne-hint">สูงสุด 24 ตัวอักษร — ชื่อนี้จะแสดงในเกม</p>' +
' <p class="jd-dne-error" id="jd-dne-error" role="alert"></p>' +
' <div class="jd-dne-actions">' +
' <button type="button" class="jd-dne-btn jd-dne-btn--cancel" id="jd-dne-cancel">ยกเลิก</button>' +
' <button type="button" class="jd-dne-btn jd-dne-btn--confirm" id="jd-dne-confirm">ยืนยัน</button>' +
' </div>' +
'</div>';
document.body.appendChild(wrap);
}
editorReady = true;
}
/**
* เปิดกล่องแก้ชื่อ (ใช้แทน window.prompt — ทำงานใน Cursor browser / mobile)
* @param {{ initialValue?: string, title?: string }} [options]
* @returns {Promise<string|null>} ชื่อใหม่ หรือ null ถ้ายกเลิก
*/
function openDisplayNameEditor(options) {
ensureNameEditorDom();
var opts = options || {};
var overlay = document.getElementById('jd-display-name-editor-overlay');
var input = document.getElementById('jd-dne-input');
var errEl = document.getElementById('jd-dne-error');
var btnOk = document.getElementById('jd-dne-confirm');
var btnCancel = document.getElementById('jd-dne-cancel');
var backdrop = document.getElementById('jd-dne-backdrop');
var titleEl = document.getElementById('jd-dne-title');
if (!overlay || !input || !btnOk || !btnCancel) {
return Promise.resolve(null);
}
return new Promise(function (resolve) {
var settled = false;
function finish(value) {
if (settled) return;
settled = true;
overlay.classList.add('is-hidden');
overlay.setAttribute('aria-hidden', 'true');
document.removeEventListener('keydown', onDocKey, true);
resolve(value);
}
function onDocKey(e) {
if (overlay.classList.contains('is-hidden')) return;
if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
finish(null);
}
}
function submit() {
if (errEl) errEl.textContent = '';
var safe = sanitizeDisplayName(input.value);
if (!safe) {
if (errEl) errEl.textContent = 'กรุณากรอกชื่อผู้เล่น';
input.focus();
return;
}
finish(safe);
}
if (titleEl && opts.title) titleEl.textContent = opts.title;
input.value = opts.initialValue != null ? String(opts.initialValue) : readDisplayName();
if (errEl) errEl.textContent = '';
overlay.classList.remove('is-hidden');
overlay.setAttribute('aria-hidden', 'false');
document.addEventListener('keydown', onDocKey, true);
btnOk.onclick = function (e) {
e.preventDefault();
submit();
};
btnCancel.onclick = function (e) {
e.preventDefault();
finish(null);
};
backdrop.onclick = function () {
finish(null);
};
input.onkeydown = function (e) {
if (e.key === 'Enter') {
e.preventDefault();
submit();
}
};
requestAnimationFrame(function () {
input.focus();
input.select();
});
});
}
/**
* เปิด editor แล้ว apply ชื่อ (socket sync ถ้ามี)
* @param {{ socket?: object }} [options]
* @returns {Promise<{ ok: boolean, displayName?: string, cancelled?: boolean }>}
*/
function promptAndApplyDisplayName(options) {
var opts = options || {};
return openDisplayNameEditor({ initialValue: readDisplayName() }).then(function (draft) {
if (draft == null) return { ok: false, cancelled: true };
return applyDisplayNameChange(draft, opts);
});
}
global.jdDisplayName = {
key: DISPLAY_NAME_KEY,
read: readDisplayName,
sanitize: sanitizeDisplayName,
syncDom: syncDisplayNameDom,
apply: applyDisplayNameChange,
openEditor: openDisplayNameEditor,
promptAndApply: promptAndApplyDisplayName
};
global.jdApplyDisplayNameChange = applyDisplayNameChange;
global.jdReadDisplayName = readDisplayName;
global.jdOpenDisplayNameEditor = openDisplayNameEditor;
global.jdPromptAndApplyDisplayName = promptAndApplyDisplayName;
})(typeof window !== 'undefined' ? window : this);