17-04-2026
Сделал красивыми кнопки для изменения параметров на вкладке профиль
This commit is contained in:
parent
c7bf8051b9
commit
9b188d56e9
@ -30,28 +30,11 @@ function genderLabel(value) {
|
|||||||
return 'Не указан';
|
return 'Не указан';
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseGenderChoice(value) {
|
const GENDER_OPTIONS = Object.freeze([
|
||||||
const normalized = String(value || '').trim().toLowerCase();
|
{ value: PROFILE_GENDER_MALE, label: 'Мужской' },
|
||||||
if (!normalized) return '';
|
{ value: PROFILE_GENDER_FEMALE, label: 'Женский' },
|
||||||
if (normalized === '1' || normalized === 'м' || normalized === 'муж' || normalized === 'мужской' || normalized === PROFILE_GENDER_MALE) {
|
{ value: PROFILE_GENDER_UNKNOWN, label: 'Не указан' },
|
||||||
return PROFILE_GENDER_MALE;
|
]);
|
||||||
}
|
|
||||||
if (normalized === '2' || normalized === 'ж' || normalized === 'жен' || normalized === 'женский' || normalized === PROFILE_GENDER_FEMALE) {
|
|
||||||
return PROFILE_GENDER_FEMALE;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
normalized === '3' ||
|
|
||||||
normalized === 'н' ||
|
|
||||||
normalized === 'не указан' ||
|
|
||||||
normalized === 'неуказан' ||
|
|
||||||
normalized === 'не указано' ||
|
|
||||||
normalized === 'неизвестно' ||
|
|
||||||
normalized === PROFILE_GENDER_UNKNOWN
|
|
||||||
) {
|
|
||||||
return PROFILE_GENDER_UNKNOWN;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
return String(text || '')
|
return String(text || '')
|
||||||
@ -104,27 +87,70 @@ export function render({ navigate }) {
|
|||||||
status.className = 'status-line';
|
status.className = 'status-line';
|
||||||
status.textContent = 'Загрузка параметров...';
|
status.textContent = 'Загрузка параметров...';
|
||||||
|
|
||||||
const genderWrap = document.createElement('div');
|
|
||||||
genderWrap.className = 'card row profile-param-item';
|
|
||||||
genderWrap.innerHTML = `
|
|
||||||
<div class="profile-param-value"><b>Пол</b>: <span data-gender-value>Не указан</span></div>
|
|
||||||
<button class="ghost-btn" type="button" data-edit-gender="true">Выбрать</button>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const listWrap = document.createElement('div');
|
const listWrap = document.createElement('div');
|
||||||
listWrap.className = 'stack profile-param-list';
|
listWrap.className = 'stack profile-param-list';
|
||||||
|
|
||||||
const reloadBtn = topRow.querySelector('[data-reload="true"]');
|
const reloadBtn = topRow.querySelector('[data-reload="true"]');
|
||||||
const officialBtn = badgesRow.querySelector('[data-toggle="official"]');
|
const officialBtn = badgesRow.querySelector('[data-toggle="official"]');
|
||||||
const shineBtn = badgesRow.querySelector('[data-toggle="shine"]');
|
const shineBtn = badgesRow.querySelector('[data-toggle="shine"]');
|
||||||
const genderValueEl = genderWrap.querySelector('[data-gender-value]');
|
|
||||||
const genderBtn = genderWrap.querySelector('[data-edit-gender="true"]');
|
|
||||||
|
|
||||||
let currentFields = [];
|
let currentFields = [];
|
||||||
let currentToggles = [];
|
let currentToggles = [];
|
||||||
let currentGender = PROFILE_GENDER_UNKNOWN;
|
let currentGender = PROFILE_GENDER_UNKNOWN;
|
||||||
const identityEl = topRow.querySelector('[data-profile-identity="true"]');
|
const identityEl = topRow.querySelector('[data-profile-identity="true"]');
|
||||||
|
|
||||||
|
function openGenderPickerModal(initialGender) {
|
||||||
|
const root = document.getElementById('modal-root');
|
||||||
|
if (!root) return Promise.resolve(null);
|
||||||
|
root.innerHTML = '';
|
||||||
|
|
||||||
|
const selected = GENDER_OPTIONS.some((item) => item.value === initialGender)
|
||||||
|
? initialGender
|
||||||
|
: PROFILE_GENDER_UNKNOWN;
|
||||||
|
|
||||||
|
root.innerHTML = `
|
||||||
|
<div class="modal" id="profile-gender-modal">
|
||||||
|
<div class="modal-card stack">
|
||||||
|
<h3 class="modal-title">Выбор пола</h3>
|
||||||
|
<p class="meta-muted">Выберите значение профиля из списка:</p>
|
||||||
|
<select class="input profile-gender-select" id="profile-gender-select">
|
||||||
|
${GENDER_OPTIONS.map((item) => (
|
||||||
|
`<option value="${item.value}" ${item.value === selected ? 'selected' : ''}>${item.label}</option>`
|
||||||
|
)).join('')}
|
||||||
|
</select>
|
||||||
|
<div class="form-actions-grid">
|
||||||
|
<button class="secondary-btn" id="profile-gender-cancel" type="button">Отмена</button>
|
||||||
|
<button class="primary-btn" id="profile-gender-save" type="button">Сохранить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const modal = root.querySelector('#profile-gender-modal');
|
||||||
|
const selectEl = root.querySelector('#profile-gender-select');
|
||||||
|
const saveEl = root.querySelector('#profile-gender-save');
|
||||||
|
const cancelEl = root.querySelector('#profile-gender-cancel');
|
||||||
|
if (!(modal instanceof HTMLElement) || !(selectEl instanceof HTMLSelectElement)) {
|
||||||
|
root.innerHTML = '';
|
||||||
|
resolve(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = (value = null) => {
|
||||||
|
root.innerHTML = '';
|
||||||
|
resolve(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
modal.addEventListener('click', (event) => {
|
||||||
|
if (event.target === modal) close(null);
|
||||||
|
});
|
||||||
|
cancelEl?.addEventListener('click', () => close(null));
|
||||||
|
saveEl?.addEventListener('click', () => close(selectEl.value || PROFILE_GENDER_UNKNOWN));
|
||||||
|
window.setTimeout(() => selectEl.focus(), 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function syncIdentity() {
|
function syncIdentity() {
|
||||||
if (!identityEl) return;
|
if (!identityEl) return;
|
||||||
const firstName = currentFields.find((field) => field.key === 'first_name')?.value || '';
|
const firstName = currentFields.find((field) => field.key === 'first_name')?.value || '';
|
||||||
@ -159,10 +185,69 @@ export function render({ navigate }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateGenderUi() {
|
function updateGenderUi() {
|
||||||
|
const genderValueEl = listWrap.querySelector('[data-gender-value]');
|
||||||
if (!genderValueEl) return;
|
if (!genderValueEl) return;
|
||||||
genderValueEl.textContent = genderLabel(currentGender);
|
genderValueEl.textContent = genderLabel(currentGender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openFieldEditModal({ label, value, placeholder = '' }) {
|
||||||
|
const root = document.getElementById('modal-root');
|
||||||
|
if (!root) return Promise.resolve(null);
|
||||||
|
root.innerHTML = `
|
||||||
|
<div class="modal" id="profile-field-edit-modal">
|
||||||
|
<div class="modal-card stack">
|
||||||
|
<h3 class="modal-title">Изменить: ${escapeHtml(label)}</h3>
|
||||||
|
<input
|
||||||
|
id="profile-field-edit-input"
|
||||||
|
class="input"
|
||||||
|
type="text"
|
||||||
|
maxlength="300"
|
||||||
|
placeholder="${escapeHtml(placeholder || `Введите ${label.toLowerCase()}`)}"
|
||||||
|
value="${escapeHtml(String(value || ''))}"
|
||||||
|
/>
|
||||||
|
<div class="form-actions-grid">
|
||||||
|
<button class="secondary-btn" id="profile-field-edit-cancel" type="button">Отмена</button>
|
||||||
|
<button class="primary-btn" id="profile-field-edit-save" type="button">Сохранить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const modal = root.querySelector('#profile-field-edit-modal');
|
||||||
|
const inputEl = root.querySelector('#profile-field-edit-input');
|
||||||
|
const saveEl = root.querySelector('#profile-field-edit-save');
|
||||||
|
const cancelEl = root.querySelector('#profile-field-edit-cancel');
|
||||||
|
if (!(modal instanceof HTMLElement) || !(inputEl instanceof HTMLInputElement)) {
|
||||||
|
root.innerHTML = '';
|
||||||
|
resolve(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = (nextValue = null) => {
|
||||||
|
root.innerHTML = '';
|
||||||
|
resolve(nextValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
modal.addEventListener('click', (event) => {
|
||||||
|
if (event.target === modal) close(null);
|
||||||
|
});
|
||||||
|
cancelEl?.addEventListener('click', () => close(null));
|
||||||
|
saveEl?.addEventListener('click', () => close(inputEl.value));
|
||||||
|
inputEl.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault();
|
||||||
|
close(inputEl.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.setTimeout(() => {
|
||||||
|
inputEl.focus();
|
||||||
|
inputEl.selectionStart = inputEl.value.length;
|
||||||
|
inputEl.selectionEnd = inputEl.value.length;
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function renderFields(fields) {
|
function renderFields(fields) {
|
||||||
listWrap.innerHTML = '';
|
listWrap.innerHTML = '';
|
||||||
fields.forEach((field) => {
|
fields.forEach((field) => {
|
||||||
@ -172,10 +257,20 @@ export function render({ navigate }) {
|
|||||||
const isNameField = field.key === 'first_name' || field.key === 'last_name';
|
const isNameField = field.key === 'first_name' || field.key === 'last_name';
|
||||||
const valueClass = isNameField ? 'profile-param-value profile-param-value-small' : 'profile-param-value';
|
const valueClass = isNameField ? 'profile-param-value profile-param-value-small' : 'profile-param-value';
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<div class="${valueClass}"><b>${field.label}</b>: ${value}</div>
|
<div class="${valueClass}"><b>${field.label}</b>: ${escapeHtml(value)}</div>
|
||||||
<button class="ghost-btn" type="button" data-edit-field="${field.key}">Изменить</button>
|
<button class="ghost-btn" type="button" data-edit-field="${field.key}">Изменить</button>
|
||||||
`;
|
`;
|
||||||
listWrap.append(row);
|
listWrap.append(row);
|
||||||
|
|
||||||
|
if (field.key === 'last_name') {
|
||||||
|
const genderRow = document.createElement('div');
|
||||||
|
genderRow.className = 'card profile-param-item row';
|
||||||
|
genderRow.innerHTML = `
|
||||||
|
<div class="profile-param-value"><b>Пол</b>: <span data-gender-value>${escapeHtml(genderLabel(currentGender))}</span></div>
|
||||||
|
<button class="ghost-btn" type="button" data-edit-gender="true">Изменить</button>
|
||||||
|
`;
|
||||||
|
listWrap.append(genderRow);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,7 +280,10 @@ export function render({ navigate }) {
|
|||||||
reloadBtn.disabled = true;
|
reloadBtn.disabled = true;
|
||||||
officialBtn.disabled = true;
|
officialBtn.disabled = true;
|
||||||
shineBtn.disabled = true;
|
shineBtn.disabled = true;
|
||||||
genderBtn.disabled = true;
|
const genderActionBtn = listWrap.querySelector('[data-edit-gender="true"]');
|
||||||
|
if (genderActionBtn instanceof HTMLButtonElement) {
|
||||||
|
genderActionBtn.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const snapshot = await loadProfileSnapshot(login);
|
const snapshot = await loadProfileSnapshot(login);
|
||||||
@ -208,7 +306,10 @@ export function render({ navigate }) {
|
|||||||
reloadBtn.disabled = false;
|
reloadBtn.disabled = false;
|
||||||
officialBtn.disabled = false;
|
officialBtn.disabled = false;
|
||||||
shineBtn.disabled = false;
|
shineBtn.disabled = false;
|
||||||
genderBtn.disabled = false;
|
const genderActionBtnAfter = listWrap.querySelector('[data-edit-gender="true"]');
|
||||||
|
if (genderActionBtnAfter instanceof HTMLButtonElement) {
|
||||||
|
genderActionBtnAfter.disabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,14 +341,13 @@ export function render({ navigate }) {
|
|||||||
const field = currentFields.find((item) => item.key === fieldKey);
|
const field = currentFields.find((item) => item.key === fieldKey);
|
||||||
if (!field) return;
|
if (!field) return;
|
||||||
|
|
||||||
const entered = window.prompt(`Введите новое значение для «${field.label}»:`, field.value || '');
|
const entered = await openFieldEditModal({
|
||||||
|
label: field.label,
|
||||||
|
value: field.value || '',
|
||||||
|
placeholder: field.placeholder || '',
|
||||||
|
});
|
||||||
if (entered === null) return;
|
if (entered === null) return;
|
||||||
|
|
||||||
const confirmed = window.confirm(
|
|
||||||
`Записать новое значение параметра «${field.label}» в блокчейн?`,
|
|
||||||
);
|
|
||||||
if (!confirmed) return;
|
|
||||||
|
|
||||||
status.className = 'status-line';
|
status.className = 'status-line';
|
||||||
status.textContent = 'Сохранение в блокчейн...';
|
status.textContent = 'Сохранение в блокчейн...';
|
||||||
|
|
||||||
@ -262,21 +362,8 @@ export function render({ navigate }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function onGenderClick() {
|
async function onGenderClick() {
|
||||||
const entered = window.prompt(
|
const nextGender = await openGenderPickerModal(currentGender);
|
||||||
'Выберите пол:\n1 — Мужской\n2 — Женский\n3 — Не указан\nМожно ввести номер или значение (male/female/unknown).',
|
if (!nextGender) return;
|
||||||
currentGender,
|
|
||||||
);
|
|
||||||
if (entered === null) return;
|
|
||||||
const nextGender = parseGenderChoice(entered);
|
|
||||||
if (!nextGender) {
|
|
||||||
window.alert('Некорректный выбор пола. Доступно: male, female, unknown.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmed = window.confirm(
|
|
||||||
`Установить пол: «${genderLabel(nextGender)}»?\nБудет создана запись в блокчейне.`,
|
|
||||||
);
|
|
||||||
if (!confirmed) return;
|
|
||||||
|
|
||||||
status.className = 'status-line';
|
status.className = 'status-line';
|
||||||
status.textContent = 'Сохранение в блокчейн...';
|
status.textContent = 'Сохранение в блокчейн...';
|
||||||
@ -294,6 +381,10 @@ export function render({ navigate }) {
|
|||||||
listWrap.addEventListener('click', (event) => {
|
listWrap.addEventListener('click', (event) => {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
if (!(target instanceof HTMLElement)) return;
|
if (!(target instanceof HTMLElement)) return;
|
||||||
|
if (target.dataset.editGender === 'true') {
|
||||||
|
onGenderClick();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const fieldKey = target.dataset.editField;
|
const fieldKey = target.dataset.editField;
|
||||||
if (!fieldKey) return;
|
if (!fieldKey) return;
|
||||||
onEditFieldClick(fieldKey);
|
onEditFieldClick(fieldKey);
|
||||||
@ -302,9 +393,8 @@ export function render({ navigate }) {
|
|||||||
reloadBtn.addEventListener('click', refreshProfileSnapshot);
|
reloadBtn.addEventListener('click', refreshProfileSnapshot);
|
||||||
officialBtn.addEventListener('click', () => onToggleClick('official'));
|
officialBtn.addEventListener('click', () => onToggleClick('official'));
|
||||||
shineBtn.addEventListener('click', () => onToggleClick('shine'));
|
shineBtn.addEventListener('click', () => onToggleClick('shine'));
|
||||||
genderBtn.addEventListener('click', onGenderClick);
|
|
||||||
|
|
||||||
card.append(topRow, badgesRow, status, genderWrap, listWrap);
|
card.append(topRow, badgesRow, status, listWrap);
|
||||||
screen.append(card);
|
screen.append(card);
|
||||||
|
|
||||||
refreshProfileSnapshot();
|
refreshProfileSnapshot();
|
||||||
|
|||||||
@ -253,6 +253,22 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.profile-gender-select {
|
||||||
|
min-height: 46px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(157, 185, 238, 0.35);
|
||||||
|
background:
|
||||||
|
linear-gradient(150deg, rgba(23, 43, 79, 0.9), rgba(10, 22, 44, 0.92));
|
||||||
|
color: #eef4ff;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-gender-select:focus {
|
||||||
|
border-color: rgba(120, 211, 255, 0.9);
|
||||||
|
box-shadow: 0 0 0 3px rgba(65, 174, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
.profile-param-time {
|
.profile-param-time {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
@ -1468,7 +1484,8 @@ textarea.input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.thread-node-level {
|
.thread-node-level {
|
||||||
margin-left: calc(min(var(--depth, 0), 4) * 12px);
|
--depth: 0;
|
||||||
|
margin-left: calc(var(--depth) * 12px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread-block {
|
.thread-block {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user