UI вложений и черновики форматов каналов

This commit is contained in:
AidarKC
2026-08-06 12:39:33 +04:00
parent 75b75fd223
commit c4f2c7be63
5 changed files with 541 additions and 39 deletions
@@ -309,6 +309,7 @@ export function openArweaveAttachmentManager({
let selectedPreviewEnabled = false;
let selectedPreviewFile = null;
let selectedPreviewSha256 = '';
let selectedPreviewPriceInfo = null;
let priceInfo = null;
let balanceInfo = null;
let autoOpenedFileDialog = false;
@@ -391,41 +392,48 @@ export function openArweaveAttachmentManager({
}
};
const renderTurboMeta = (metaEl) => {
if (!metaEl) return;
const renderTurboMeta = (mainMetaEl, previewMetaEl) => {
if (!mainMetaEl || !previewMetaEl) return;
const turboAddress = String(balanceInfo?.address || priceInfo?.address || '').trim();
const turboCredits = balanceInfo ? formatTurboCredits(balanceInfo.effectiveCredits, 6) : '—';
const solBalance = balanceInfo ? `${escapeHtml(String(balanceInfo.solBalance ?? 0))} SOL` : '—';
const uploadPrice = priceInfo ? `${formatTurboCredits(priceInfo.credits, 6)} credits` : '—';
const fileSize = selectedFile ? formatBytes(selectedFile.size) : '—';
const previewSize = selectedPreviewEnabled && selectedPreviewFile ? formatBytes(selectedPreviewFile.size) : '';
const freeHint = selectedFile
? (combinedUploadSize() <= getFreeTurboUploadBytesLimit() ? 'Да, бесплатно через Turbo' : 'Нет, нужен Turbo balance')
: `До ${formatBytes(getFreeTurboUploadBytesLimit())} бесплатно`;
const freeBadge = priceInfo?.isFree
? ' <span style="color:#15803d;">(пока файлы до 100 KB бесплатно)</span>'
: '';
metaEl.innerHTML = `
mainMetaEl.innerHTML = `
<div>Turbo-адрес: ${escapeHtml(shortAddress(turboAddress || '—'))}</div>
<div>Turbo balance: ${escapeHtml(turboCredits)}</div>
<div>SOL на этом ключе: ${solBalance}</div>
<div>Размер файла: ${escapeHtml(fileSize)}</div>
${previewSize ? `<div>Размер превью: ${escapeHtml(previewSize)}</div>` : ''}
<div>SHA-256: ${escapeHtml(selectedSha256 || '—')}</div>
${selectedPreviewEnabled && selectedPreviewSha256 ? `<div>SHA-256 превью: ${escapeHtml(selectedPreviewSha256)}</div>` : ''}
<div>Цена Turbo: ${escapeHtml(uploadPrice)}${freeBadge}</div>
<div>До ${escapeHtml(formatBytes(getFreeTurboUploadBytesLimit()))} бесплатно: ${escapeHtml(freeHint)}</div>
`;
previewMetaEl.innerHTML = '';
if (selectedPreviewEnabled && selectedPreviewFile) {
previewMetaEl.innerHTML = `
<div><strong>Файл превью</strong></div>
<div>Имя: ${escapeHtml(selectedPreviewFile.name)}</div>
<div>Размер превью: ${escapeHtml(formatBytes(selectedPreviewFile.size))}</div>
<div>SHA-256 превью: ${escapeHtml(selectedPreviewSha256 || '—')}</div>
<div>Цена превью: ${escapeHtml(selectedPreviewPriceInfo ? `${formatTurboCredits(selectedPreviewPriceInfo.credits, 6)} credits` : '—')}</div>
`;
}
};
const refreshTurboBalance = async (metaEl, errorEl) => {
const refreshTurboBalance = async (mainMetaEl, previewMetaEl, errorEl) => {
try {
balanceInfo = await getTurboBalanceForStoredSolanaKey({
login: cleanLogin,
storagePwd: cleanStoragePwd,
keySource: selectedTurboKeySource,
});
renderTurboMeta(metaEl);
renderTurboMeta(mainMetaEl, previewMetaEl);
if (errorEl && String(errorEl.textContent || '').includes('Turbo balance')) {
setText(errorEl, '');
}
@@ -434,7 +442,7 @@ export function openArweaveAttachmentManager({
}
};
const promptTurboTopUp = async (metaEl, errorEl) => {
const promptTurboTopUp = async (mainMetaEl, previewMetaEl, errorEl) => {
const value = window.prompt('Сколько SOL перевести в Turbo?', '0.01');
if (value == null) return;
setText(errorEl, 'Пополняем Turbo...');
@@ -445,7 +453,7 @@ export function openArweaveAttachmentManager({
keySource: selectedTurboKeySource,
amountSol: value,
});
await refreshTurboBalance(metaEl, errorEl);
await refreshTurboBalance(mainMetaEl, previewMetaEl, errorEl);
setText(errorEl, `Turbo пополнен. Tx: ${result.txId || 'отправлено'}`);
} catch (error) {
setText(errorEl, error?.message || 'Не удалось пополнить Turbo.');
@@ -456,6 +464,7 @@ export function openArweaveAttachmentManager({
selectedPreviewEnabled = false;
selectedPreviewFile = null;
selectedPreviewSha256 = '';
selectedPreviewPriceInfo = null;
};
const isPreviewEligibleForCurrentFile = () => (
@@ -468,30 +477,36 @@ export function openArweaveAttachmentManager({
return Number(selectedFile?.size || 0) + Number(selectedPreviewEnabled ? (selectedPreviewFile?.size || 0) : 0);
};
const renderArweaveMeta = (metaEl) => {
if (!metaEl) return;
metaEl.innerHTML = `
const renderArweaveMeta = (mainMetaEl, previewMetaEl) => {
if (!mainMetaEl || !previewMetaEl) return;
mainMetaEl.innerHTML = `
${isAvatarMode ? '' : `<div>Имя: ${escapeHtml(selectedFile?.name || 'file')}</div>`}
<div>Размер: ${escapeHtml(formatBytes(selectedFile?.size || 0))}</div>
<div>SHA-256: ${escapeHtml(selectedSha256 || '—')}</div>
${selectedPreviewEnabled && selectedPreviewFile
? `<div>Превью: ${escapeHtml(selectedPreviewFile.name)} · ${escapeHtml(formatBytes(selectedPreviewFile.size))}</div>`
: ''}
${selectedPreviewEnabled && selectedPreviewSha256
? `<div>SHA-256 превью: ${escapeHtml(selectedPreviewSha256)}</div>`
: ''}
<div>Цена: ${escapeHtml(Number(priceInfo?.ar || 0).toLocaleString('ru-RU', { maximumFractionDigits: 6 }))} AR</div>
<div>Баланс кошелька: ${escapeHtml(Number(balanceInfo?.ar || 0).toLocaleString('ru-RU', { maximumFractionDigits: 6 }))} AR</div>
`;
previewMetaEl.innerHTML = '';
if (selectedPreviewEnabled && selectedPreviewFile) {
previewMetaEl.innerHTML = `
<div><strong>Файл превью</strong></div>
<div>Имя: ${escapeHtml(selectedPreviewFile.name)}</div>
<div>Размер превью: ${escapeHtml(formatBytes(selectedPreviewFile.size))}</div>
<div>SHA-256 превью: ${escapeHtml(selectedPreviewSha256 || '—')}</div>
<div>Цена превью: ${escapeHtml(selectedPreviewPriceInfo ? `${Number(selectedPreviewPriceInfo.ar || 0).toLocaleString('ru-RU', { maximumFractionDigits: 6 })} AR` : '—')}</div>
`;
}
};
const recalculateArweaveState = async (metaEl, errorEl, uploadBtn) => {
const recalculateArweaveState = async (mainMetaEl, previewMetaEl, errorEl, uploadBtn) => {
uploadBtn.disabled = true;
priceInfo = null;
balanceInfo = null;
selectedPreviewPriceInfo = null;
setText(errorEl, '');
if (!selectedFile || !selectedSha256) {
setText(metaEl, '');
setText(mainMetaEl, '');
setText(previewMetaEl, '');
return;
}
const wallet = selectedWallet();
@@ -500,6 +515,7 @@ export function openArweaveAttachmentManager({
let totalWinston = BigInt(mainPrice.winston);
if (selectedPreviewEnabled && selectedPreviewFile) {
const previewPrice = await getArweaveUploadPrice({ gateway: cleanGateway, byteLength: selectedPreviewFile.size });
selectedPreviewPriceInfo = previewPrice;
totalWinston += BigInt(previewPrice.winston);
}
balanceInfo = await getArweaveBalance({ gateway: cleanGateway, address: wallet.address });
@@ -507,7 +523,7 @@ export function openArweaveAttachmentManager({
ar: Number(totalWinston) / 1e12,
winston: totalWinston.toString(),
};
renderArweaveMeta(metaEl);
renderArweaveMeta(mainMetaEl, previewMetaEl);
const hasFunds = BigInt(balanceInfo.winston) >= totalWinston;
if (!hasFunds) {
setText(errorEl, 'Недостаточно AR на выбранном кошельке.');
@@ -516,13 +532,14 @@ export function openArweaveAttachmentManager({
uploadBtn.disabled = false;
};
const refreshTurboStateForCurrentFile = async (metaEl, errorEl, uploadBtn) => {
const refreshTurboStateForCurrentFile = async (mainMetaEl, previewMetaEl, errorEl, uploadBtn) => {
uploadBtn.disabled = true;
priceInfo = null;
balanceInfo = null;
selectedPreviewPriceInfo = null;
setText(errorEl, '');
if (!selectedFile || !selectedSha256) {
renderTurboMeta(metaEl);
renderTurboMeta(mainMetaEl, previewMetaEl);
return;
}
try {
@@ -540,6 +557,7 @@ export function openArweaveAttachmentManager({
keySource: selectedTurboKeySource,
byteLength: selectedPreviewFile.size,
});
selectedPreviewPriceInfo = previewPrice;
totalWinc += BigInt(previewPrice.winc);
}
priceInfo = {
@@ -554,7 +572,7 @@ export function openArweaveAttachmentManager({
storagePwd: cleanStoragePwd,
keySource: selectedTurboKeySource,
});
renderTurboMeta(metaEl);
renderTurboMeta(mainMetaEl, previewMetaEl);
if (!priceInfo.isFree && BigInt(balanceInfo.effectiveWinc) < BigInt(priceInfo.winc)) {
setText(errorEl, 'Turbo balance недостаточен. Нажмите «Пополнить Turbo».');
return;
@@ -591,11 +609,12 @@ export function openArweaveAttachmentManager({
`}
<label class="meta-muted" for="ar-attach-file">Файл для загрузки</label>
<input class="input" id="ar-attach-file" type="file" ${isAvatarMode ? 'accept="image/*"' : ''} />
<div class="ar-attachment-meta" data-meta-main="true"></div>
<label class="meta-muted" data-preview-option="true" hidden>
<input type="checkbox" data-preview-toggle="true" />
Добавить превью
</label>
<div class="ar-attachment-meta" data-meta="true"></div>
<div class="ar-attachment-meta" data-meta-preview="true"></div>
<p class="meta-muted inline-error" data-error="true"></p>
<div class="form-actions-grid">
${turboMode ? '<button class="secondary-btn" type="button" data-action="topup">Пополнить Turbo</button>' : ''}
@@ -609,7 +628,8 @@ export function openArweaveAttachmentManager({
const walletEl = root.querySelector('#ar-attach-wallet');
const turboKeyEl = root.querySelector('#turbo-key-source');
const fileEl = root.querySelector('#ar-attach-file');
const metaEl = root.querySelector('[data-meta="true"]');
const mainMetaEl = root.querySelector('[data-meta-main="true"]');
const previewMetaEl = root.querySelector('[data-meta-preview="true"]');
const errorEl = root.querySelector('[data-error="true"]');
const uploadBtn = root.querySelector('[data-action="upload"]');
const previewOptionEl = root.querySelector('[data-preview-option="true"]');
@@ -648,10 +668,10 @@ export function openArweaveAttachmentManager({
turboKeyEl.addEventListener('change', async () => {
selectedTurboKeySource = String(turboKeyEl.value || 'client');
writeLastTurboKeySource(cleanLogin, selectedTurboKeySource);
await refreshTurboStateForCurrentFile(metaEl, errorEl, uploadBtn);
await refreshTurboStateForCurrentFile(mainMetaEl, previewMetaEl, errorEl, uploadBtn);
});
}
await refreshTurboStateForCurrentFile(metaEl, errorEl, uploadBtn);
await refreshTurboStateForCurrentFile(mainMetaEl, previewMetaEl, errorEl, uploadBtn);
} else {
await loadWallets(errorEl);
if (walletEl) {
@@ -667,16 +687,17 @@ export function openArweaveAttachmentManager({
priceInfo = null;
balanceInfo = null;
if (fileEl) fileEl.value = '';
setText(metaEl, '');
setText(mainMetaEl, '');
setText(previewMetaEl, '');
});
}
}
root.querySelector('[data-action="cancel"]')?.addEventListener('click', () => close(resolve, null));
root.querySelector('[data-action="topup"]')?.addEventListener('click', async () => {
await promptTurboTopUp(metaEl, errorEl);
await promptTurboTopUp(mainMetaEl, previewMetaEl, errorEl);
if (turboMode && selectedFile && selectedSha256) {
await refreshTurboStateForCurrentFile(metaEl, errorEl, uploadBtn);
await refreshTurboStateForCurrentFile(mainMetaEl, previewMetaEl, errorEl, uploadBtn);
}
});
if (!turboMode) {
@@ -691,7 +712,8 @@ export function openArweaveAttachmentManager({
balanceInfo = null;
uploadBtn.disabled = true;
setText(errorEl, '');
setText(metaEl, '');
setText(mainMetaEl, '');
setText(previewMetaEl, '');
if (previewToggleEl instanceof HTMLInputElement) previewToggleEl.checked = false;
if (previewOptionEl) previewOptionEl.hidden = true;
if (!selectedFile) return;
@@ -709,9 +731,9 @@ export function openArweaveAttachmentManager({
previewOptionEl.hidden = false;
}
if (turboMode) {
await refreshTurboStateForCurrentFile(metaEl, errorEl, uploadBtn);
await refreshTurboStateForCurrentFile(mainMetaEl, previewMetaEl, errorEl, uploadBtn);
} else {
await recalculateArweaveState(metaEl, errorEl, uploadBtn);
await recalculateArweaveState(mainMetaEl, previewMetaEl, errorEl, uploadBtn);
}
} catch (error) {
setText(errorEl, error?.message || 'Не удалось подготовить файл.');
@@ -735,9 +757,9 @@ export function openArweaveAttachmentManager({
selectedPreviewSha256 = await sha256HexFromArrayBuffer(await selectedPreviewFile.arrayBuffer());
}
if (turboMode) {
await refreshTurboStateForCurrentFile(metaEl, errorEl, uploadBtn);
await refreshTurboStateForCurrentFile(mainMetaEl, previewMetaEl, errorEl, uploadBtn);
} else {
await recalculateArweaveState(metaEl, errorEl, uploadBtn);
await recalculateArweaveState(mainMetaEl, previewMetaEl, errorEl, uploadBtn);
}
} catch (error) {
resetPreviewState();