UI: добавить журнал загрузок Arweave

This commit is contained in:
AidarKC
2026-07-31 13:13:58 +04:00
parent c530627078
commit 98eba54621
11 changed files with 310 additions and 27 deletions
+133
View File
@@ -0,0 +1,133 @@
import { renderHeader } from '../components/header.js';
import {
clearArweaveAttachmentHistory,
formatArweaveHistoryDateTime,
getArweaveAttachmentAvailability,
openArweaveAttachmentManager,
readArweaveAttachmentHistory,
} from '../components/arweave-attachment-manager.js';
import { formatBytes } from '../services/attachment-format.js';
import { toUserMessage } from '../services/ui-error-texts.js';
import { state } from '../state.js';
export const pageMeta = { id: 'arweave-uploads-view', title: 'Загрузки в Arweave' };
function setStatusBadge(node, status, label) {
if (!node) return;
node.className = `ar-attachment-status ar-attachment-status--${status || 'pending'}`;
node.textContent = label || 'Проверяем...';
}
function renderTile(item, index) {
const tile = document.createElement('article');
tile.className = 'ar-attachment-history-tile ar-attachment-history-tile--page';
const head = document.createElement('div');
head.className = 'ar-attachment-history-tile-head';
const name = document.createElement('strong');
name.textContent = item.name;
const status = document.createElement('span');
status.className = 'ar-attachment-status ar-attachment-status--pending';
status.dataset.statusIndex = String(index);
status.textContent = 'Проверяем...';
const meta = document.createElement('div');
meta.className = 'meta-muted';
meta.textContent = `${formatBytes(item.size)} · ${formatArweaveHistoryDateTime(item.uploadedAtMs)}`;
const txId = document.createElement('div');
txId.className = 'mono-cell ar-attachment-history-txid';
txId.textContent = item.ar;
head.append(name, status);
tile.append(head, meta, txId);
return tile;
}
export function render({ navigate }) {
const screen = document.createElement('section');
screen.className = 'stack arweave-uploads-screen';
screen.append(
renderHeader({
title: 'Загрузки в Arweave',
leftAction: { label: '←', onClick: () => navigate('settings-view') },
}),
);
const intro = document.createElement('div');
intro.className = 'card stack';
intro.innerHTML = `
<p class="field-label">Журнал файлов этой сессии</p>
<p class="meta-muted">Здесь можно заранее загрузить файл в Arweave. Он попадёт только в журнал, а в сообщение его потом можно добавить из истории загрузок.</p>
`;
const controls = document.createElement('div');
controls.className = 'card arweave-uploads-toolbar';
controls.innerHTML = `
<button class="primary-btn" type="button" data-action="upload">Добавить файл</button>
<button class="secondary-btn" type="button" data-action="clear">Очистить историю</button>
`;
const statusLine = document.createElement('p');
statusLine.className = 'meta-muted inline-error';
const list = document.createElement('div');
list.className = 'ar-attachment-history-tiles arweave-uploads-list';
function renderHistory() {
const rows = readArweaveAttachmentHistory(state.session.login).slice().reverse();
list.innerHTML = '';
statusLine.textContent = rows.length ? `Файлов в журнале: ${rows.length}` : 'Журнал пока пуст.';
if (!rows.length) {
const empty = document.createElement('div');
empty.className = 'card meta-muted';
empty.textContent = 'Загруженные в этой сессии файлы появятся здесь.';
list.append(empty);
return;
}
rows.forEach((item, index) => {
list.append(renderTile(item, index));
const statusEl = list.querySelector(`[data-status-index="${index}"]`);
void getArweaveAttachmentAvailability({
gateway: state.entrySettings.arweaveServer,
attachment: item,
})
.then((result) => setStatusBadge(statusEl, result.status, result.label))
.catch(() => setStatusBadge(statusEl, 'unavailable', 'Недоступен'));
});
window.setTimeout(() => {
list.scrollTop = list.scrollHeight;
}, 0);
}
controls.querySelector('[data-action="upload"]')?.addEventListener('click', async () => {
statusLine.textContent = '';
try {
await openArweaveAttachmentManager({
login: state.session.login,
storagePwd: state.session.storagePwdInMemory,
gateway: state.entrySettings.arweaveServer,
});
renderHistory();
} catch (error) {
statusLine.textContent = toUserMessage(error, 'Не удалось загрузить файл.');
}
});
controls.querySelector('[data-action="clear"]')?.addEventListener('click', () => {
const confirmed = window.confirm('Очистить журнал загруженных файлов этой сессии?');
if (!confirmed) return;
clearArweaveAttachmentHistory(state.session.login);
renderHistory();
});
screen.append(intro, controls, statusLine, list);
renderHistory();
return screen;
}
+1
View File
@@ -426,6 +426,7 @@ function openReplyModal({ onSubmit, navigate }) {
login: state.session.login,
storagePwd: state.session.storagePwdInMemory,
gateway: state.entrySettings.arweaveServer,
selectedTxIds: attachments.map((attachment) => attachment.ar),
});
if (!item) return;
attachments.push(item);
+2
View File
@@ -401,6 +401,7 @@ function openReplyModal({ onSubmit, navigate }) {
login: state.session.login,
storagePwd: state.session.storagePwdInMemory,
gateway: state.entrySettings.arweaveServer,
selectedTxIds: attachments.map((attachment) => attachment.ar),
});
if (!item) return;
attachments.push(item);
@@ -559,6 +560,7 @@ function openAddMessageModal({ channelName, onSubmit, navigate }) {
login: state.session.login,
storagePwd: state.session.storagePwdInMemory,
gateway: state.entrySettings.arweaveServer,
selectedTxIds: attachments.map((attachment) => attachment.ar),
});
if (!item) return;
attachments.push(item);
+7
View File
@@ -54,6 +54,12 @@ export function render({ navigate }) {
<span class="meta-muted" style="display:block; margin-top:0.2rem;">Solana, SHiNE и Arweave для публичных данных</span>
</span>
</button>
<button class="text-btn" type="button" id="settings-arweave-uploads">
<span style="display:block; text-align:left;">
<strong>Загрузки в Arweave</strong>
<span class="meta-muted" style="display:block; margin-top:0.2rem;">Заранее загрузить файл и выбрать его потом из истории</span>
</span>
</button>
<button class="text-btn" type="button" id="settings-language">Язык / Language</button>
<button class="text-btn" type="button" id="settings-signout">Завершить текущий сеанс</button>
`;
@@ -62,6 +68,7 @@ export function render({ navigate }) {
card.querySelector('#settings-remote-addblock').addEventListener('click', () => navigate('remote-addblock-session-view'));
card.querySelector('#settings-access-servers').addEventListener('click', () => navigate('access-servers-view'));
card.querySelector('#settings-blockchain-servers').addEventListener('click', () => navigate('server-settings-view'));
card.querySelector('#settings-arweave-uploads').addEventListener('click', () => navigate('arweave-uploads-view'));
card.querySelector('#settings-language').addEventListener('click', () => navigate('language-view'));
const signOutBtn = card.querySelector('#settings-signout');