SHA256
145 lines
5.8 KiB
JavaScript
145 lines
5.8 KiB
JavaScript
import { createTopBar } from '../components/topbar.js';
|
|
import { authService, state } from '../state.js';
|
|
|
|
export const pageMeta = { id: 'blockchain-archive-view', title: 'Архив блокчейна' };
|
|
|
|
function text(value) {
|
|
return String(value == null ? '' : value).trim();
|
|
}
|
|
|
|
function buildViewerUrl(location, blockchainName, channelName, messageNumber) {
|
|
const url = new URL('/Blockchain-Viewer.html', window.location.origin);
|
|
url.searchParams.set('tx', text(location.arweaveTxId));
|
|
url.searchParams.set('offset', String(location.chunkOffset));
|
|
url.searchParams.set('size', String(location.chunkSize));
|
|
url.searchParams.set('blockchain', blockchainName);
|
|
const channel = text(channelName);
|
|
if (channel) url.searchParams.set('channel', channel);
|
|
const message = text(messageNumber);
|
|
if (message) url.searchParams.set('message', message);
|
|
return url.toString();
|
|
}
|
|
|
|
async function copyText(value) {
|
|
if (navigator.clipboard?.writeText) {
|
|
await navigator.clipboard.writeText(value);
|
|
return;
|
|
}
|
|
const area = document.createElement('textarea');
|
|
area.value = value;
|
|
document.body.appendChild(area);
|
|
area.select();
|
|
document.execCommand('copy');
|
|
area.remove();
|
|
}
|
|
|
|
export function render({ navigate, chrome }) {
|
|
const screen = document.createElement('section');
|
|
screen.className = 'stack';
|
|
chrome?.setTopbar(createTopBar({
|
|
title: 'Архив блокчейна',
|
|
back: { label: '←', onClick: () => navigate('settings-view') },
|
|
}));
|
|
|
|
const card = document.createElement('div');
|
|
card.className = 'card stack';
|
|
card.innerHTML = `
|
|
<div class="stack" style="gap:.35rem;">
|
|
<strong>Последний архивный блок</strong>
|
|
<div id="archive-status" class="meta-muted">Загружаю...</div>
|
|
</div>
|
|
<label class="field">
|
|
<span>Блокчейн</span>
|
|
<input id="archive-blockchain" type="text" readonly>
|
|
</label>
|
|
<label class="field">
|
|
<span>Канал (необязательно)</span>
|
|
<input id="archive-channel" type="text" placeholder="Название канала">
|
|
</label>
|
|
<label class="field">
|
|
<span>Номер сообщения / блока (необязательно)</span>
|
|
<input id="archive-message" type="number" min="0" step="1" placeholder="Например, 125">
|
|
</label>
|
|
<label class="field">
|
|
<span>Ссылка</span>
|
|
<textarea id="archive-link" rows="5" readonly style="resize:vertical;"></textarea>
|
|
</label>
|
|
<div style="display:flex; gap:.6rem; flex-wrap:wrap;">
|
|
<button id="archive-copy" class="shine-btn" type="button" disabled>Копировать ссылку</button>
|
|
<button id="archive-open" class="shine-btn" type="button" disabled>Открыть Viewer</button>
|
|
</div>
|
|
<details>
|
|
<summary>Технические данные</summary>
|
|
<pre id="archive-tech" style="white-space:pre-wrap; word-break:break-all;"></pre>
|
|
</details>
|
|
`;
|
|
screen.appendChild(card);
|
|
|
|
const status = card.querySelector('#archive-status');
|
|
const blockchainInput = card.querySelector('#archive-blockchain');
|
|
const channelInput = card.querySelector('#archive-channel');
|
|
const messageInput = card.querySelector('#archive-message');
|
|
const linkInput = card.querySelector('#archive-link');
|
|
const copyButton = card.querySelector('#archive-copy');
|
|
const openButton = card.querySelector('#archive-open');
|
|
const tech = card.querySelector('#archive-tech');
|
|
|
|
let location = null;
|
|
let blockchainName = '';
|
|
|
|
function refreshLink() {
|
|
if (!location || !blockchainName) {
|
|
linkInput.value = '';
|
|
copyButton.disabled = true;
|
|
openButton.disabled = true;
|
|
return;
|
|
}
|
|
linkInput.value = buildViewerUrl(location, blockchainName, channelInput.value, messageInput.value);
|
|
copyButton.disabled = false;
|
|
openButton.disabled = false;
|
|
}
|
|
|
|
channelInput.addEventListener('input', refreshLink);
|
|
messageInput.addEventListener('input', refreshLink);
|
|
copyButton.addEventListener('click', async () => {
|
|
if (!linkInput.value) return;
|
|
await copyText(linkInput.value);
|
|
status.textContent = 'Ссылка скопирована.';
|
|
});
|
|
openButton.addEventListener('click', () => {
|
|
if (linkInput.value) window.open(linkInput.value, '_blank', 'noopener,noreferrer');
|
|
});
|
|
|
|
void (async () => {
|
|
try {
|
|
const login = text(state.session.login);
|
|
if (!login) throw new Error('Нет активного пользователя');
|
|
const user = await authService.getUser(login);
|
|
blockchainName = text(user.blockchainName);
|
|
if (!blockchainName) throw new Error('У пользователя не найден blockchainName');
|
|
blockchainInput.value = blockchainName;
|
|
location = await authService.getArchiveBlockchainLocation(blockchainName);
|
|
if (!text(location.arweaveTxId) || !Number.isFinite(Number(location.chunkOffset)) || !Number.isFinite(Number(location.chunkSize))) {
|
|
throw new Error('Сервер вернул неполную архивную ссылку');
|
|
}
|
|
status.textContent = `Известна архивная история до блока ${location.sourceLastBlockNumber ?? '—'}.`;
|
|
tech.textContent = [
|
|
`publisher: ${text(location.publisherLogin) || '—'}`,
|
|
`archive big block: ${location.bigBlockNumber ?? '—'}`,
|
|
`Arweave TX: ${text(location.arweaveTxId)}`,
|
|
`archive hash: ${text(location.archiveHash) || '—'}`,
|
|
`chunk offset: ${location.chunkOffset}`,
|
|
`chunk size: ${location.chunkSize}`,
|
|
`source last block: ${location.sourceLastBlockNumber ?? '—'}`,
|
|
].join('\n');
|
|
refreshLink();
|
|
} catch (error) {
|
|
status.textContent = `Архивная ссылка пока недоступна: ${error?.message || error}`;
|
|
tech.textContent = '';
|
|
refreshLink();
|
|
}
|
|
})();
|
|
|
|
return screen;
|
|
}
|