Files
PixelandClaude Opus 4.8 d6bc883520 Merge origin/main (+5: финал регистрации, автовход, порядок в deploy)
- register-view: принят вариант агента для видимости пароля — поле видно
  всегда, слова «12 слов» автоматически склеиваются в поле (проверено:
  оба режима, значение сохраняется при переключении).
- Остальное авто-слито (deploy-схема server2, AGENTS, entry-settings, state).
- VERSION: client 1.2.326, server 1.2.297.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 16:02:51 +03:00

208 lines
7.0 KiB
JavaScript

import { renderHeader } from '../components/header.js';
import {
authorizeLocalDemoSession,
isLocalDemoAvailable,
saveEntrySettings,
state,
} from '../state.js';
import { checkServerAvailabilityByKey, resolveAndCheckShineServerLogin } from '../services/server-health-service.js';
export const pageMeta = { id: 'entry-settings-view', title: 'Настройки входа', showAppChrome: false };
const SERVER_FIELDS = [
{ key: 'solanaServer', label: 'Адрес Solana сервера' },
{ key: 'shineServerLogin', label: 'Логин сервера Сияние' },
{ key: 'arweaveServer', label: 'Адрес сервера Arweave' },
];
export function render({ navigate }) {
const screen = document.createElement('section');
screen.className = 'stack';
const draft = {
language: state.entrySettings.language,
solanaServer: state.entrySettings.solanaServer,
shineServerLogin: state.entrySettings.shineServerLogin,
shineServerHttp: state.entrySettings.shineServerHttp,
arweaveServer: state.entrySettings.arweaveServer,
statuses: { ...state.entrySettings.statuses },
};
const timers = new Map();
const body = document.createElement('div');
body.className = 'card stack';
const languageLabel = document.createElement('label');
languageLabel.className = 'stack';
languageLabel.innerHTML = `<span class="field-label">Язык</span>`;
const languageSelect = document.createElement('select');
languageSelect.className = 'select';
languageSelect.innerHTML = `
<option value="ru">Русский</option>
<option value="en">English</option>
`;
languageSelect.value = draft.language;
languageSelect.addEventListener('change', () => {
draft.language = languageSelect.value;
});
languageLabel.append(languageSelect);
body.append(languageLabel);
SERVER_FIELDS.forEach((field) => {
const block = document.createElement('div');
block.className = 'stack';
const title = document.createElement('label');
title.className = 'field-label';
title.textContent = field.label;
const input = document.createElement('input');
input.className = 'input';
input.type = 'text';
input.value = draft[field.key];
const controls = document.createElement('div');
controls.className = 'row wrap-row';
const checkButton = document.createElement('button');
checkButton.className = 'ghost-btn server-check-btn';
checkButton.type = 'button';
checkButton.textContent = 'Проверить';
const status = document.createElement('span');
status.className = 'status-line';
const applyStatus = (value, exactAddress = '') => {
draft.statuses[field.key] = value;
checkButton.classList.remove('is-available', 'is-unavailable');
status.classList.remove('is-available', 'is-unavailable');
if (value === 'available') {
status.textContent = field.key === 'shineServerLogin' && exactAddress
? `Доступен: ${exactAddress}`
: 'Доступен';
checkButton.classList.add('is-available');
status.classList.add('is-available');
} else if (value === 'unavailable') {
status.textContent = 'Недоступен';
checkButton.classList.add('is-unavailable');
status.classList.add('is-unavailable');
} else {
status.textContent = field.key === 'shineServerLogin' && draft.shineServerHttp
? `Текущий адрес: ${draft.shineServerHttp}`
: 'Статус не проверен';
}
};
const runCheck = async () => {
draft[field.key] = input.value.trim();
checkButton.disabled = true;
checkButton.textContent = 'Проверка...';
try {
if (field.key === 'shineServerLogin') {
const resolved = await resolveAndCheckShineServerLogin(input.value, draft.solanaServer);
draft.shineServerHttp = resolved.httpBase;
draft.shineServer = resolved.wsUrl;
applyStatus(resolved.status, resolved.httpBase);
} else {
const next = await checkServerAvailabilityByKey(field.key, input.value, {
solanaEndpoint: draft.solanaServer,
});
applyStatus(next);
}
} finally {
checkButton.disabled = false;
checkButton.textContent = 'Проверить';
}
};
applyStatus(draft.statuses[field.key]);
checkButton.addEventListener('click', runCheck);
input.addEventListener('input', () => {
draft[field.key] = input.value;
applyStatus('idle');
window.clearTimeout(timers.get(field.key));
timers.set(field.key, window.setTimeout(() => {
void runCheck();
}, 3000));
});
input.addEventListener('blur', () => {
void runCheck();
});
input.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
event.preventDefault();
void runCheck();
}
});
controls.append(checkButton, status);
block.append(title, input, controls);
body.append(block);
});
const actions = document.createElement('div');
actions.className = 'auth-footer-actions';
if (isLocalDemoAvailable()) {
const localDemoButton = document.createElement('button');
localDemoButton.className = 'ghost-btn preauth-local-demo-btn';
localDemoButton.type = 'button';
localDemoButton.textContent = 'Открыть локальный тестовый режим';
localDemoButton.addEventListener('click', () => {
if (!authorizeLocalDemoSession()) return;
navigate('messages-list');
});
actions.append(localDemoButton);
}
const serverUiButton = document.createElement('button');
serverUiButton.className = 'ghost-btn';
serverUiButton.type = 'button';
serverUiButton.textContent = 'Настроить свой сервер';
serverUiButton.addEventListener('click', () => {
const url = new URL('server-ui.html', window.location.href);
window.open(url.toString(), '_blank', 'noopener');
});
const cancelButton = document.createElement('button');
cancelButton.className = 'ghost-btn';
cancelButton.type = 'button';
cancelButton.textContent = 'Отмена';
cancelButton.addEventListener('click', () => navigate('start-view'));
const saveButton = document.createElement('button');
saveButton.className = 'primary-btn';
saveButton.type = 'button';
saveButton.textContent = 'Сохранить';
saveButton.addEventListener('click', async () => {
try {
await saveEntrySettings(draft);
navigate('start-view');
} catch (error) {
window.alert(error?.message || 'Не удалось сохранить настройки входа.');
}
});
actions.append(serverUiButton, cancelButton, saveButton);
screen.append(
renderHeader({
title: 'Настройки входа',
leftAction: { label: '←', onClick: () => navigate('start-view') },
}),
body,
actions,
);
screen.cleanup = () => {
timers.forEach((timerId) => window.clearTimeout(timerId));
};
return screen;
}