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

This commit is contained in:
AidarKC
2026-07-31 13:50:17 +04:00
parent 98eba54621
commit cd67176b41
9 changed files with 219 additions and 67 deletions
@@ -13,13 +13,23 @@ const HISTORY_KEY = 'shine-ui-arweave-attachment-history-v1';
const MAX_HISTORY_ITEMS = 100;
const RECENT_UPLOAD_MS = 20 * 60 * 1000;
function normalizeHistoryItem(input = {}) {
const item = normalizeAttachment(input);
const placedInShineAtMs = Number(input.placedInShineAtMs || 0) || 0;
return {
...item,
pendingPlacement: input.pendingPlacement === true && !placedInShineAtMs,
placedInShineAtMs,
};
}
export function readArweaveAttachmentHistory(login) {
try {
const raw = sessionStorage.getItem(HISTORY_KEY);
const parsed = raw ? JSON.parse(raw) : {};
const key = String(login || '').trim().toLowerCase();
const rows = Array.isArray(parsed?.[key]) ? parsed[key] : [];
return rows.map((item) => normalizeAttachment(item)).filter(Boolean);
return rows.map((item) => normalizeHistoryItem(item)).filter(Boolean);
} catch {
return [];
}
@@ -30,7 +40,9 @@ export function saveArweaveAttachmentHistory(login, rows) {
const key = String(login || '').trim().toLowerCase();
const raw = sessionStorage.getItem(HISTORY_KEY);
const parsed = raw ? JSON.parse(raw) : {};
parsed[key] = (Array.isArray(rows) ? rows : []).slice(0, MAX_HISTORY_ITEMS);
parsed[key] = (Array.isArray(rows) ? rows : [])
.map((item) => normalizeHistoryItem(item))
.slice(0, MAX_HISTORY_ITEMS);
sessionStorage.setItem(HISTORY_KEY, JSON.stringify(parsed));
} catch {
// ignore sessionStorage errors
@@ -41,12 +53,52 @@ export function clearArweaveAttachmentHistory(login) {
saveArweaveAttachmentHistory(login, []);
}
export function addArweaveAttachmentToHistory(login, attachment) {
const item = normalizeAttachment(attachment);
const rows = readArweaveAttachmentHistory(login).filter((row) => row.ar !== item.ar);
rows.unshift(item);
saveArweaveAttachmentHistory(login, rows);
return item;
export function addArweaveAttachmentToHistory(login, attachment, { pendingPlacement = undefined, markPlaced = false } = {}) {
const item = normalizeHistoryItem(attachment);
const rows = readArweaveAttachmentHistory(login);
const existing = rows.find((row) => row.ar === item.ar) || {};
const placedInShineAtMs = markPlaced
? Date.now()
: Number(existing.placedInShineAtMs || item.placedInShineAtMs || 0) || 0;
const nextPendingPlacement = pendingPlacement === undefined
? (existing.pendingPlacement === true && !placedInShineAtMs)
: (!!pendingPlacement && !placedInShineAtMs);
const nextItem = {
...item,
pendingPlacement: nextPendingPlacement,
placedInShineAtMs,
};
const nextRows = rows.filter((row) => row.ar !== item.ar);
nextRows.unshift(nextItem);
saveArweaveAttachmentHistory(login, nextRows);
return nextItem;
}
export function markArweaveAttachmentPlaced(login, attachment) {
const item = normalizeHistoryItem(attachment);
const rows = readArweaveAttachmentHistory(login);
const existing = rows.find((row) => row.ar === item.ar) || {};
const nextItem = {
...item,
uploadedAtMs: item.uploadedAtMs || existing.uploadedAtMs || Date.now(),
pendingPlacement: false,
placedInShineAtMs: Number(existing.placedInShineAtMs || 0) || Date.now(),
};
const nextRows = rows.filter((row) => row.ar !== item.ar);
nextRows.unshift(nextItem);
saveArweaveAttachmentHistory(login, nextRows);
return nextItem;
}
export function formatArweaveHistoryTime(ms) {
const value = Number(ms || 0);
if (!Number.isFinite(value) || value <= 0) return '—';
const diffMs = Date.now() - value;
if (diffMs >= 0 && diffMs < RECENT_UPLOAD_MS) {
const minutes = Math.max(1, Math.floor(diffMs / 60000));
return `${minutes} мин назад`;
}
return new Date(value).toLocaleString('ru-RU');
}
export function formatArweaveHistoryDateTime(ms) {
@@ -87,6 +139,7 @@ export function openArweaveAttachmentManager({
gateway,
onSelect,
selectedTxIds = [],
historyOnly = false,
} = {}) {
const cleanLogin = String(login || '').trim();
const cleanStoragePwd = String(storagePwd || '').trim();
@@ -118,9 +171,12 @@ export function openArweaveAttachmentManager({
resolve(result);
}
function finish(resolve, attachment) {
const item = addArweaveAttachmentToHistory(cleanLogin, attachment);
if (typeof onSelect === 'function') onSelect(item);
function finish(resolve, attachment, { pendingPlacement = undefined } = {}) {
const item = addArweaveAttachmentToHistory(cleanLogin, attachment, {
pendingPlacement,
markPlaced: false,
});
if (!pendingPlacement && typeof onSelect === 'function') onSelect(item);
close(resolve, item);
}
@@ -151,19 +207,19 @@ export function openArweaveAttachmentManager({
root.innerHTML = `
<div class="modal" data-ar-attach-modal="true">
<div class="modal-card stack ar-attachment-manager-card">
<h3 class="modal-title">Добавить вложение</h3>
<h3 class="modal-title">${historyOnly ? 'Загрузить файл в блокчейн' : 'Добавить вложение'}</h3>
<label class="meta-muted" for="ar-attach-wallet">Кошелёк оплаты Arweave</label>
<select class="input" id="ar-attach-wallet"></select>
<button class="ghost-btn" type="button" data-action="add-wallet">Добавить кошелёк</button>
<p class="meta-muted">Для надёжности лучше сначала загрузить файл в Arweave, дождаться доступности в журнале, а потом добавить его из истории загрузок.</p>
${historyOnly ? '' : '<p class="meta-muted">Для надёжности лучше сначала загрузить файл в Arweave, дождаться доступности в журнале, а потом добавить его из истории загрузок.</p>'}
<label class="meta-muted" for="ar-attach-file">Файл для загрузки</label>
<input class="input" id="ar-attach-file" type="file" />
<div class="ar-attachment-meta" data-meta="true"></div>
<p class="meta-muted inline-error" data-error="true"></p>
<div class="form-actions-grid">
<button class="secondary-btn" type="button" data-action="existing">Ввести txId</button>
<button class="secondary-btn" type="button" data-action="history">История загрузок</button>
<button class="primary-btn" type="button" data-action="upload" disabled>Загрузить</button>
${historyOnly ? '' : '<button class="secondary-btn" type="button" data-action="existing">Ввести txId</button>'}
${historyOnly ? '' : '<button class="secondary-btn" type="button" data-action="history">История загрузок</button>'}
<button class="primary-btn" type="button" data-action="upload" disabled>${historyOnly ? 'Загрузить в журнал' : 'Загрузить'}</button>
</div>
<button class="secondary-btn" type="button" data-action="cancel">Отмена</button>
</div>
@@ -261,7 +317,7 @@ export function openArweaveAttachmentManager({
sha256: selectedSha256,
ar: uploaded.id,
uploadedAtMs: Date.now(),
});
}, { pendingPlacement: historyOnly });
} catch (error) {
uploadBtn.disabled = false;
setText(errorEl, error?.message || 'Не удалось загрузить файл в Arweave.');
@@ -349,11 +405,13 @@ export function openArweaveAttachmentManager({
const isSelected = selectedTxIdSet.has(item.ar);
return `
<article class="ar-attachment-history-tile${isSelected ? ' is-selected' : ''}" data-history-tile="${index}">
<div class="ar-attachment-history-tile-head">
<strong>${escapeHtml(item.name)}</strong>
<strong class="ar-attachment-history-name">${escapeHtml(item.name)}</strong>
<div class="ar-attachment-history-meta-row">
<span>${escapeHtml(formatBytes(item.size))}</span>
<span>${escapeHtml(formatArweaveHistoryTime(item.uploadedAtMs))}</span>
<span class="ar-attachment-status ar-attachment-status--pending" data-status="${index}">Проверяем...</span>
${item.pendingPlacement ? '<span class="ar-attachment-placement-flag">Не добавлен в SHiNE</span>' : ''}
</div>
<div class="meta-muted">${escapeHtml(formatBytes(item.size))} · ${escapeHtml(formatArweaveHistoryDateTime(item.uploadedAtMs))}</div>
<div class="mono-cell ar-attachment-history-txid">${escapeHtml(item.ar)}</div>
<button class="${isSelected ? 'secondary-btn' : 'ghost-btn'}" type="button" data-pick="${index}" ${isSelected ? 'disabled' : ''}>${isSelected ? 'Уже добавлен ✓' : 'Выбрать'}</button>
</article>