SHA256
UI: новый общий стиль, тёмная/светлая тема, настраиваемая палитра
- styles/main.css: роли цветов (по умолчанию «Индиго»), шкала отступов/скруглений/шрифтов, цвета отношений для обеих тем; жёсткие цвета в стилях заменены на роли. - Палитра: пресеты и личные правки, «Оформление» Авто/День/Ночь, долгое нажатие — редактор цветов с экспортом/импортом. - Каналы: пузыри постов автора, плашки дней, строка «Написать в канал…», «О канале», создание канала с адресом из названия; лента открывается на свежих постах. - Чаты: плоский список, чипы-фильтры, пузыри, плашки дней; нижняя панель скрыта в переписке. - Корневые разделы — единая шапка; профиль и чужой профиль — общая карточка; настройки, кошелёк, сеансы — меню-списки. - Нижняя панель: иконки без подписей, бейджи на иконках. - confirmDialog вместо window.confirm/alert; на телефоне диалоги — шторки снизу. - docs/UI-Design/ISSUES-for-dev.md — найденные ошибки сервера/UI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { confirmDialog } from '../components/confirm-dialog.js';
|
||||
import { createTopBar } from '../components/topbar.js';
|
||||
import { addAppLogEntry, authService, closeSavedProfile, state } from '../state.js';
|
||||
import { isDeveloperToolsEnabled } from '../services/feature-settings.js';
|
||||
import { getThemeMode, setThemeMode } from '../services/theme-service.js';
|
||||
import { openPaletteEditor } from '../components/palette-editor.js';
|
||||
|
||||
export const pageMeta = { id: 'settings-view', title: 'Настройки' };
|
||||
|
||||
@@ -40,7 +42,7 @@ export function render({navigate, chrome}) {
|
||||
const logoButton = document.createElement('button');
|
||||
logoButton.type = 'button';
|
||||
logoButton.className = 'settings-shine-logo-button';
|
||||
logoButton.setAttribute('aria-label', 'Логотип SHiNE');
|
||||
logoButton.setAttribute('aria-label', 'Логотип Сияния');
|
||||
logoButton.title = 'SHiNE';
|
||||
logoButton.innerHTML = '<img class="settings-shine-logo" src="/img/shine-logo-transparent-final_big.png" alt="SHiNE" />';
|
||||
|
||||
@@ -56,44 +58,64 @@ export function render({navigate, chrome}) {
|
||||
});
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card stack';
|
||||
card.className = 'stack settings-sections';
|
||||
const row = (id, label, hint) => `<button class="nav-row" type="button" id="${id}"><span class="nav-row__label">${label}</span>${hint ? `<span class="nav-row__hint">${hint}</span>` : ''}</button>`;
|
||||
card.innerHTML = `
|
||||
<div class="stack">
|
||||
<label for="settings-theme"><strong>Оформление</strong></label>
|
||||
<span class="meta-muted">Тема меняет только цвета и не перезагружает экран.</span>
|
||||
<select class="select" id="settings-theme">
|
||||
<option value="system">Как на устройстве</option>
|
||||
<option value="light">Дневное</option>
|
||||
<option value="dark">Ночное</option>
|
||||
</select>
|
||||
<div class="card stack settings-theme-card">
|
||||
<strong id="settings-theme-label">Оформление</strong>
|
||||
<div class="tabs tabs--auto" id="settings-theme" role="radiogroup" aria-labelledby="settings-theme-label">
|
||||
<button type="button" class="tab-btn" role="radio" data-mode="system">Авто</button>
|
||||
<button type="button" class="tab-btn" role="radio" data-mode="light">День</button>
|
||||
<button type="button" class="tab-btn" role="radio" data-mode="dark">Ночь</button>
|
||||
</div>
|
||||
<span class="meta-muted">«Авто» — как на устройстве.</span>
|
||||
</div>
|
||||
<div class="nav-list">
|
||||
${row('settings-device', 'Устройства', 'Сеансы, подключение устройств, ключи')}
|
||||
${row('settings-access-servers', 'Сервер доступа', 'Личная переписка, звонки и зашифрованные данные')}
|
||||
${row('settings-blockchain-servers', 'Серверы блокчейнов', 'Solana, Сияние и Arweave для публичных данных')}
|
||||
${row('settings-arweave-uploads', 'Файлы в блокчейне', 'Заранее загрузить файл и выбрать его потом')}
|
||||
${row('settings-remote-addblock', 'Публикация через домашний сервер', 'Когда ключа публикаций нет на этом устройстве')}
|
||||
${row('settings-language', 'Язык / Language', 'Русский')}
|
||||
</div>
|
||||
<div class="nav-list">
|
||||
<button class="nav-row nav-row--danger" type="button" id="settings-signout"><span class="nav-row__label">Выйти из аккаунта на этом устройстве</span></button>
|
||||
</div>
|
||||
<button class="shine-btn shine-btn--settings" type="button" id="settings-device">Устройства</button>
|
||||
<button class="shine-btn shine-btn--settings" type="button" id="settings-remote-addblock">Remote AddBlock через homeserver</button>
|
||||
<button class="shine-btn shine-btn--settings" type="button" id="settings-access-servers">
|
||||
<span style="display:block; text-align:left;">
|
||||
<strong>Сервер доступа</strong>
|
||||
<span class="meta-muted" style="display:block; margin-top:0.2rem;">Личная переписка, звонки и зашифрованные данные</span>
|
||||
</span>
|
||||
</button>
|
||||
<button class="shine-btn shine-btn--settings" type="button" id="settings-blockchain-servers">
|
||||
<span style="display:block; text-align:left;">
|
||||
<strong>Серверы блокчейнов</strong>
|
||||
<span class="meta-muted" style="display:block; margin-top:0.2rem;">Solana, SHiNE и Arweave для публичных данных</span>
|
||||
</span>
|
||||
</button>
|
||||
<button class="shine-btn shine-btn--settings" type="button" id="settings-arweave-uploads">
|
||||
<span style="display:block; text-align:left;">
|
||||
<strong>Загрузить файлы в блокчейн</strong>
|
||||
<span class="meta-muted" style="display:block; margin-top:0.2rem;">Заранее загрузить файл и выбрать его потом из истории</span>
|
||||
</span>
|
||||
</button>
|
||||
<button class="shine-btn shine-btn--settings" type="button" id="settings-language">Язык / Language</button>
|
||||
<button class="shine-btn shine-btn--settings" type="button" id="settings-signout">Завершить текущий сеанс</button>
|
||||
`;
|
||||
|
||||
const themeSelect = card.querySelector('#settings-theme');
|
||||
themeSelect.value = getThemeMode();
|
||||
themeSelect.addEventListener('change', () => setThemeMode(themeSelect.value));
|
||||
const themeSwitch = card.querySelector('#settings-theme');
|
||||
const syncThemeSwitch = () => {
|
||||
const mode = getThemeMode();
|
||||
themeSwitch.querySelectorAll('[data-mode]').forEach((btn) => {
|
||||
btn.setAttribute('aria-checked', String(btn.dataset.mode === mode));
|
||||
});
|
||||
};
|
||||
syncThemeSwitch();
|
||||
|
||||
const LONG_PRESS_MS = 600;
|
||||
let longPressTimer = 0;
|
||||
let longPressFired = false;
|
||||
const cancelLongPress = () => { window.clearTimeout(longPressTimer); longPressTimer = 0; };
|
||||
themeSwitch.addEventListener('pointerdown', (event) => {
|
||||
const mode = event.target.closest('[data-mode]')?.dataset.mode;
|
||||
longPressFired = false;
|
||||
if (mode !== 'light' && mode !== 'dark') return;
|
||||
longPressTimer = window.setTimeout(() => {
|
||||
longPressFired = true;
|
||||
setThemeMode(mode);
|
||||
syncThemeSwitch();
|
||||
openPaletteEditor({ theme: mode });
|
||||
}, LONG_PRESS_MS);
|
||||
});
|
||||
['pointerup', 'pointerleave', 'pointercancel'].forEach((type) => themeSwitch.addEventListener(type, cancelLongPress));
|
||||
// На телефоне долгое нажатие иначе вызывает системное меню выделения.
|
||||
themeSwitch.addEventListener('contextmenu', (event) => event.preventDefault());
|
||||
themeSwitch.addEventListener('click', (event) => {
|
||||
const mode = event.target.closest('[data-mode]')?.dataset.mode;
|
||||
if (!mode || longPressFired) return;
|
||||
setThemeMode(mode);
|
||||
syncThemeSwitch();
|
||||
});
|
||||
|
||||
card.querySelector('#settings-device').addEventListener('click', () => navigate('device-view'));
|
||||
card.querySelector('#settings-remote-addblock').addEventListener('click', () => navigate('remote-addblock-session-view'));
|
||||
@@ -107,9 +129,12 @@ export function render({navigate, chrome}) {
|
||||
|
||||
const signOutBtn = card.querySelector('#settings-signout');
|
||||
signOutBtn.addEventListener('click', async () => {
|
||||
const confirmed = window.confirm(
|
||||
'Завершить текущий профиль на этом устройстве? Если есть другие сохранённые профили, приложение переключится на следующий.'
|
||||
);
|
||||
const confirmed = await confirmDialog({
|
||||
title: 'Выйти из аккаунта?',
|
||||
text: 'Вы выйдете на этом устройстве. Если сохранены другие профили, приложение переключится на следующий.',
|
||||
confirmLabel: 'Выйти',
|
||||
danger: true,
|
||||
});
|
||||
if (!confirmed) return;
|
||||
|
||||
signOutBtn.disabled = true;
|
||||
@@ -184,6 +209,7 @@ export function render({navigate, chrome}) {
|
||||
|
||||
screen.cleanup = () => {
|
||||
isDisposed = true;
|
||||
cancelLongPress();
|
||||
};
|
||||
return screen;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user