SHA256
- 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>
156 lines
6.4 KiB
JavaScript
156 lines
6.4 KiB
JavaScript
import { confirmDialog } from '../components/confirm-dialog.js';
|
||
import { createTopBar } from '../components/topbar.js';
|
||
import {
|
||
closeAllSavedProfiles,
|
||
closeSavedProfile,
|
||
getSavedProfiles,
|
||
prepareAddProfileLogin,
|
||
switchToSavedProfile,
|
||
} from '../state.js';
|
||
|
||
export const pageMeta = { id: 'profiles-view', title: 'Профили' };
|
||
|
||
function reloadTo(path) {
|
||
const clean = String(path || '/profile').trim() || '/profile';
|
||
window.location.assign(clean.startsWith('/') ? clean : `/${clean}`);
|
||
}
|
||
|
||
export function render({navigate, chrome}) {
|
||
const screen = document.createElement('section');
|
||
screen.className = 'stack profiles-screen';
|
||
|
||
chrome?.setTopbar(createTopBar({
|
||
title: 'Профили',
|
||
back: { label: '←', onClick: () => navigate('profile-view') },
|
||
}));
|
||
|
||
const intro = document.createElement('div');
|
||
intro.className = 'meta-muted profiles-summary';
|
||
|
||
const list = document.createElement('div');
|
||
list.className = 'stack profiles-list';
|
||
|
||
const status = document.createElement('div');
|
||
status.className = 'status-line';
|
||
status.hidden = true;
|
||
|
||
const actions = document.createElement('div');
|
||
actions.className = 'stack profiles-actions';
|
||
|
||
const addButton = document.createElement('button');
|
||
addButton.type = 'button';
|
||
addButton.className = 'secondary-btn';
|
||
addButton.textContent = 'Добавить профиль';
|
||
addButton.addEventListener('click', async () => {
|
||
addButton.disabled = true;
|
||
status.hidden = false;
|
||
status.className = 'status-line';
|
||
status.textContent = 'Подготавливаем вход в новый профиль…';
|
||
try {
|
||
await prepareAddProfileLogin();
|
||
navigate('login-view');
|
||
} catch (error) {
|
||
status.className = 'status-line is-unavailable';
|
||
status.textContent = `Не удалось начать добавление профиля: ${error?.message || 'unknown'}`;
|
||
addButton.disabled = false;
|
||
}
|
||
});
|
||
|
||
const closeAllButton = document.createElement('button');
|
||
closeAllButton.type = 'button';
|
||
closeAllButton.className = 'destructive-btn profiles-close-all';
|
||
closeAllButton.textContent = 'Выйти из всех профилей';
|
||
closeAllButton.addEventListener('click', async () => {
|
||
const profiles = getSavedProfiles();
|
||
if (!profiles.length) return;
|
||
const confirmed = await confirmDialog({ title: 'Выйти из всех профилей?', text: 'Все профили на этом устройстве будут закрыты, откроется экран входа.', confirmLabel: 'Выйти', danger: true });
|
||
if (!confirmed) return;
|
||
closeAllButton.disabled = true;
|
||
status.hidden = false;
|
||
status.textContent = 'Закрываем профили…';
|
||
try {
|
||
await closeAllSavedProfiles();
|
||
reloadTo('/start');
|
||
} catch (error) {
|
||
status.className = 'status-line is-unavailable';
|
||
status.textContent = `Не удалось закрыть профили: ${error?.message || 'unknown'}`;
|
||
closeAllButton.disabled = false;
|
||
}
|
||
});
|
||
|
||
actions.append(addButton, closeAllButton);
|
||
screen.append(intro, list, status, actions);
|
||
|
||
const renderList = () => {
|
||
const profiles = getSavedProfiles();
|
||
intro.textContent = profiles.length ? '' : 'На устройстве нет сохранённых профилей.';
|
||
intro.hidden = profiles.length > 0;
|
||
closeAllButton.disabled = profiles.length === 0;
|
||
list.innerHTML = '';
|
||
|
||
profiles.forEach((profile) => {
|
||
const row = document.createElement('div');
|
||
row.className = `card profiles-row${profile.isActive ? ' is-active' : ''}`;
|
||
|
||
const select = document.createElement('button');
|
||
select.type = 'button';
|
||
select.className = 'profiles-select';
|
||
select.innerHTML = `<span class="profiles-login">${profile.login}</span>${profile.isActive ? '<span class="profiles-active-badge">Сейчас открыт</span>' : ''}`;
|
||
select.disabled = profile.isActive;
|
||
select.addEventListener('click', async () => {
|
||
if (profile.isActive) return;
|
||
const confirmed = await confirmDialog({ title: 'Переключить профиль?', text: `Откроется профиль «${profile.login}».`, confirmLabel: 'Переключить' });
|
||
if (!confirmed) return;
|
||
status.hidden = false;
|
||
status.className = 'status-line';
|
||
status.textContent = `Подключаем профиль ${profile.login}…`;
|
||
try {
|
||
await switchToSavedProfile(profile.login);
|
||
reloadTo('/profile');
|
||
} catch (error) {
|
||
status.className = 'status-line is-unavailable';
|
||
status.textContent = `Не удалось переключить профиль: ${error?.message || 'unknown'}`;
|
||
}
|
||
});
|
||
|
||
const close = document.createElement('button');
|
||
close.type = 'button';
|
||
close.className = 'profiles-close';
|
||
close.setAttribute('aria-label', `Выйти из профиля ${profile.login}`);
|
||
close.title = 'Выйти из профиля';
|
||
close.textContent = '×';
|
||
close.addEventListener('click', async () => {
|
||
const others = profiles.filter((item) => item.login.toLowerCase() !== profile.login.toLowerCase());
|
||
const message = profile.isActive
|
||
? (others.length
|
||
? 'Приложение переключится на следующий сохранённый профиль.'
|
||
: 'После выхода откроется экран входа.')
|
||
: 'Профиль будет закрыт на этом устройстве.';
|
||
if (!await confirmDialog({ title: `Выйти из профиля «${profile.login}»?`, text: message, confirmLabel: 'Выйти', danger: true })) return;
|
||
|
||
status.hidden = false;
|
||
status.className = 'status-line';
|
||
status.textContent = `Закрываем профиль ${profile.login}…`;
|
||
try {
|
||
const result = await closeSavedProfile(profile.login);
|
||
if (profile.isActive) {
|
||
reloadTo(result.nextProfile ? '/profile' : '/start');
|
||
return;
|
||
}
|
||
status.hidden = true;
|
||
renderList();
|
||
} catch (error) {
|
||
status.className = 'status-line is-unavailable';
|
||
status.textContent = `Не удалось закрыть профиль: ${error?.message || 'unknown'}`;
|
||
}
|
||
});
|
||
|
||
row.append(select, close);
|
||
list.append(row);
|
||
});
|
||
};
|
||
|
||
renderList();
|
||
return screen;
|
||
}
|