SHA256
UI: добавить журнал загрузок Arweave
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
||||
client.version=1.4.2
|
||||
client.version=1.4.3
|
||||
server.version=1.4.0
|
||||
|
||||
@@ -61,6 +61,8 @@ UI поддерживает три сценария:
|
||||
- выбрать файл из журнала файлов, загруженных в текущей браузерной сессии.
|
||||
|
||||
Журнал хранится только в `sessionStorage` текущего браузера и не является частью блокчейна.
|
||||
В настройках клиента есть отдельный экран журнала загрузок Arweave: пользователь может заранее загрузить файл в Arweave без создания сообщения, увидеть плитку с именем, размером, датой, `txId` и статусом доступности через gateway, а затем выбрать этот файл из истории при создании сообщения.
|
||||
Если история очищена, уже созданные сообщения не меняются: в блокчейне остаются attach-блоки с `txId`.
|
||||
|
||||
## Отображение
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# История изменений документации блокчейна
|
||||
|
||||
## 2026-07-31 13:12:24 +0400
|
||||
- Базовый коммит-ориентир: `c530627`.
|
||||
- Для UI-вложений добавлен отдельный экран журнала загрузок Arweave в настройках: загрузка файла без создания сообщения, плитки истории с `txId`, размером, датой и статусом доступности через gateway.
|
||||
- Уточнено, что журнал хранится только в `sessionStorage` текущего браузера и не влияет на уже записанные attach-блоки в блокчейне.
|
||||
|
||||
## 2026-07-31 12:47:41 +0400
|
||||
- Базовый коммит-ориентир: `f961947`.
|
||||
- Для `SHiNE:attach v=1` уточнено клиентское отображение вложений: горизонтальная карусель над текстом сообщения, счётчик позиции, просмотр image/video в модальном окне и скачивание файлов.
|
||||
|
||||
@@ -59,6 +59,7 @@ import * as settingsView from './pages/settings-view.js';
|
||||
import * as accessServersView from './pages/access-servers-view.js';
|
||||
import * as developerSettingsView from './pages/developer-settings-view.js';
|
||||
import * as serverSettingsView from './pages/server-settings-view.js?v=202606161240';
|
||||
import * as arweaveUploadsView from './pages/arweave-uploads-view.js';
|
||||
import * as remoteAddBlockSessionView from './pages/remote-addblock-session-view.js?v=202606281300';
|
||||
import * as deviceView from './pages/device-view.js?v=202606131435';
|
||||
import * as connectDeviceView from './pages/connect-device-view.js?v=202606142055';
|
||||
@@ -120,6 +121,7 @@ const routes = {
|
||||
'access-servers-view': accessServersView,
|
||||
'developer-settings-view': developerSettingsView,
|
||||
'server-settings-view': serverSettingsView,
|
||||
'arweave-uploads-view': arweaveUploadsView,
|
||||
'remote-addblock-session-view': remoteAddBlockSessionView,
|
||||
'device-view': deviceView,
|
||||
'connect-device-view': connectDeviceView,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
buildArweaveDataUrl,
|
||||
getArweaveUploadPrice,
|
||||
loadArweaveFileMetadata,
|
||||
sha256HexFromArrayBuffer,
|
||||
@@ -10,8 +11,9 @@ import { escapeHtml, formatBytes, normalizeAttachment } from '../services/attach
|
||||
|
||||
const HISTORY_KEY = 'shine-ui-arweave-attachment-history-v1';
|
||||
const MAX_HISTORY_ITEMS = 100;
|
||||
const RECENT_UPLOAD_MS = 20 * 60 * 1000;
|
||||
|
||||
function readHistory(login) {
|
||||
export function readArweaveAttachmentHistory(login) {
|
||||
try {
|
||||
const raw = sessionStorage.getItem(HISTORY_KEY);
|
||||
const parsed = raw ? JSON.parse(raw) : {};
|
||||
@@ -23,7 +25,7 @@ function readHistory(login) {
|
||||
}
|
||||
}
|
||||
|
||||
function saveHistory(login, rows) {
|
||||
export function saveArweaveAttachmentHistory(login, rows) {
|
||||
try {
|
||||
const key = String(login || '').trim().toLowerCase();
|
||||
const raw = sessionStorage.getItem(HISTORY_KEY);
|
||||
@@ -35,20 +37,40 @@ function saveHistory(login, rows) {
|
||||
}
|
||||
}
|
||||
|
||||
function addToHistory(login, attachment) {
|
||||
export function clearArweaveAttachmentHistory(login) {
|
||||
saveArweaveAttachmentHistory(login, []);
|
||||
}
|
||||
|
||||
export function addArweaveAttachmentToHistory(login, attachment) {
|
||||
const item = normalizeAttachment(attachment);
|
||||
const rows = readHistory(login).filter((row) => row.ar !== item.ar);
|
||||
const rows = readArweaveAttachmentHistory(login).filter((row) => row.ar !== item.ar);
|
||||
rows.unshift(item);
|
||||
saveHistory(login, rows);
|
||||
saveArweaveAttachmentHistory(login, rows);
|
||||
return item;
|
||||
}
|
||||
|
||||
function formatDateTime(ms) {
|
||||
export function formatArweaveHistoryDateTime(ms) {
|
||||
const value = Number(ms || 0);
|
||||
if (!Number.isFinite(value) || value <= 0) return '—';
|
||||
return new Date(value).toLocaleString('ru-RU');
|
||||
}
|
||||
|
||||
export async function getArweaveAttachmentAvailability({ gateway, attachment } = {}) {
|
||||
const item = normalizeAttachment(attachment);
|
||||
const url = buildArweaveDataUrl({ gateway, txId: item.ar });
|
||||
try {
|
||||
const response = await fetch(url, { method: 'HEAD', cache: 'no-store' });
|
||||
if (response.ok) return { status: 'available', label: '✓ Доступен', url };
|
||||
} catch {
|
||||
// gateway может временно не отвечать или блокировать HEAD; для UI это всё равно недоступно.
|
||||
}
|
||||
const ageMs = Date.now() - Number(item.uploadedAtMs || 0);
|
||||
if (Number.isFinite(ageMs) && ageMs >= 0 && ageMs < RECENT_UPLOAD_MS) {
|
||||
return { status: 'pending', label: '… Ещё обновляется', url };
|
||||
}
|
||||
return { status: 'unavailable', label: '✕ Недоступен', url };
|
||||
}
|
||||
|
||||
function setText(node, text) {
|
||||
if (node) node.textContent = String(text || '');
|
||||
}
|
||||
@@ -64,6 +86,7 @@ export function openArweaveAttachmentManager({
|
||||
storagePwd,
|
||||
gateway,
|
||||
onSelect,
|
||||
selectedTxIds = [],
|
||||
} = {}) {
|
||||
const cleanLogin = String(login || '').trim();
|
||||
const cleanStoragePwd = String(storagePwd || '').trim();
|
||||
@@ -82,6 +105,7 @@ export function openArweaveAttachmentManager({
|
||||
let priceInfo = null;
|
||||
let balanceInfo = null;
|
||||
let autoOpenedFileDialog = false;
|
||||
const selectedTxIdSet = new Set((Array.isArray(selectedTxIds) ? selectedTxIds : []).map((item) => String(item || '').trim()).filter(Boolean));
|
||||
|
||||
function selectedWallet() {
|
||||
return wallets.find((item) => String(item.id) === String(selectedWalletId)) || wallets[0] || null;
|
||||
@@ -95,7 +119,7 @@ export function openArweaveAttachmentManager({
|
||||
}
|
||||
|
||||
function finish(resolve, attachment) {
|
||||
const item = addToHistory(cleanLogin, attachment);
|
||||
const item = addArweaveAttachmentToHistory(cleanLogin, attachment);
|
||||
if (typeof onSelect === 'function') onSelect(item);
|
||||
close(resolve, item);
|
||||
}
|
||||
@@ -131,13 +155,14 @@ export function openArweaveAttachmentManager({
|
||||
<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>
|
||||
<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="secondary-btn" type="button" data-action="history">История загрузок</button>
|
||||
<button class="primary-btn" type="button" data-action="upload" disabled>Загрузить</button>
|
||||
</div>
|
||||
<button class="secondary-btn" type="button" data-action="cancel">Отмена</button>
|
||||
@@ -313,29 +338,27 @@ export function openArweaveAttachmentManager({
|
||||
};
|
||||
|
||||
const showHistory = () => {
|
||||
const rows = readHistory(cleanLogin);
|
||||
const rows = readArweaveAttachmentHistory(cleanLogin).slice().reverse();
|
||||
root.innerHTML = `
|
||||
<div class="modal" data-ar-attach-modal="true">
|
||||
<div class="modal-card stack ar-attachment-manager-card ar-attachment-manager-card--wide">
|
||||
<h3 class="modal-title">Журнал загруженных файлов</h3>
|
||||
<p class="meta-muted">Журнал хранится только в этой браузерной сессии.</p>
|
||||
<div class="ar-attachment-history-table-wrap">
|
||||
<table class="ar-attachment-history-table">
|
||||
<thead>
|
||||
<tr><th>Файл</th><th>txId</th><th>Размер</th><th>Дата</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${rows.length ? rows.map((item, index) => `
|
||||
<tr>
|
||||
<td>${escapeHtml(item.name)}</td>
|
||||
<td class="mono-cell">${escapeHtml(item.ar)}</td>
|
||||
<td>${escapeHtml(formatBytes(item.size))}</td>
|
||||
<td>${escapeHtml(formatDateTime(item.uploadedAtMs))}</td>
|
||||
<td><button class="ghost-btn" type="button" data-pick="${index}">Выбрать</button></td>
|
||||
</tr>
|
||||
`).join('') : '<tr><td colspan="5">В этой сессии ещё нет загруженных файлов.</td></tr>'}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="ar-attachment-history-tiles" data-history-scroll="true">
|
||||
${rows.length ? rows.map((item, index) => {
|
||||
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>
|
||||
<span class="ar-attachment-status ar-attachment-status--pending" data-status="${index}">Проверяем...</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>
|
||||
`;
|
||||
}).join('') : '<div class="card meta-muted">В этой сессии ещё нет загруженных файлов.</div>'}
|
||||
</div>
|
||||
<div class="form-actions-grid">
|
||||
<button class="secondary-btn" type="button" data-action="back">Назад</button>
|
||||
@@ -344,6 +367,10 @@ export function openArweaveAttachmentManager({
|
||||
</div>
|
||||
`;
|
||||
bindBackdrop();
|
||||
const scrollEl = root.querySelector('[data-history-scroll="true"]');
|
||||
window.setTimeout(() => {
|
||||
if (scrollEl) scrollEl.scrollTop = scrollEl.scrollHeight;
|
||||
}, 0);
|
||||
root.querySelector('[data-action="back"]')?.addEventListener('click', showUpload);
|
||||
root.querySelectorAll('[data-pick]').forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
@@ -352,6 +379,20 @@ export function openArweaveAttachmentManager({
|
||||
if (item) finish(resolve, item);
|
||||
});
|
||||
});
|
||||
rows.forEach((item, index) => {
|
||||
const statusEl = root.querySelector(`[data-status="${index}"]`);
|
||||
void getArweaveAttachmentAvailability({ gateway: cleanGateway, attachment: item })
|
||||
.then((result) => {
|
||||
if (!statusEl) return;
|
||||
statusEl.className = `ar-attachment-status ar-attachment-status--${result.status}`;
|
||||
statusEl.textContent = result.label;
|
||||
})
|
||||
.catch(() => {
|
||||
if (!statusEl) return;
|
||||
statusEl.className = 'ar-attachment-status ar-attachment-status--unavailable';
|
||||
statusEl.textContent = 'Недоступен';
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const showAddWallet = () => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -31,6 +31,7 @@ const PRETTY_PATHS = new Map([
|
||||
['settings-view', 'settings'],
|
||||
['access-servers-view', 'settings/access-servers'],
|
||||
['server-settings-view', 'settings/servers'],
|
||||
['arweave-uploads-view', 'settings/arweave-uploads'],
|
||||
['developer-settings-view', 'settings/developer'],
|
||||
['trusted-device-login-settings-view', 'settings/device-login'],
|
||||
['language-view', 'settings/language'],
|
||||
@@ -261,6 +262,7 @@ export function getRoute() {
|
||||
const sub = decodePart(segments[1] || '').toLowerCase();
|
||||
if (sub === 'access-servers') return { pageId: 'access-servers-view', params: {} };
|
||||
if (sub === 'servers') return { pageId: 'server-settings-view', params: {} };
|
||||
if (sub === 'arweave-uploads') return { pageId: 'arweave-uploads-view', params: {} };
|
||||
if (sub === 'developer') return { pageId: 'developer-settings-view', params: {} };
|
||||
if (sub === 'device-login') return { pageId: 'trusted-device-login-settings-view', params: {} };
|
||||
if (sub === 'language') return { pageId: 'language-view', params: {} };
|
||||
@@ -373,6 +375,7 @@ export function resolveToolbarActive(pageId) {
|
||||
pageId === 'access-servers-view' ||
|
||||
pageId === 'developer-settings-view' ||
|
||||
pageId === 'server-settings-view' ||
|
||||
pageId === 'arweave-uploads-view' ||
|
||||
pageId === 'remote-addblock-session-view' ||
|
||||
pageId === 'device-view' ||
|
||||
pageId === 'connect-device-view' ||
|
||||
|
||||
@@ -2498,6 +2498,93 @@ textarea.input {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.ar-attachment-history-tiles {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
max-height: 58vh;
|
||||
overflow: auto;
|
||||
padding-right: 0.1rem;
|
||||
}
|
||||
|
||||
.ar-attachment-history-tile {
|
||||
background: linear-gradient(135deg, rgba(248, 250, 252, 0.96), rgba(236, 253, 245, 0.96));
|
||||
border: 1px solid rgba(20, 184, 166, 0.22);
|
||||
border-radius: 1rem;
|
||||
color: #0f172a;
|
||||
display: grid;
|
||||
gap: 0.45rem;
|
||||
padding: 0.9rem;
|
||||
}
|
||||
|
||||
.ar-attachment-history-tile.is-selected {
|
||||
border-color: rgba(34, 197, 94, 0.55);
|
||||
box-shadow: inset 0 0 0 1px rgba(34, 197, 94, 0.18);
|
||||
}
|
||||
|
||||
.ar-attachment-history-tile--page {
|
||||
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.ar-attachment-history-tile-head {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
gap: 0.65rem;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ar-attachment-history-tile-head strong {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ar-attachment-history-txid {
|
||||
background: rgba(15, 23, 42, 0.06);
|
||||
border-radius: 0.65rem;
|
||||
padding: 0.45rem 0.55rem;
|
||||
}
|
||||
|
||||
.ar-attachment-status {
|
||||
border-radius: 999px;
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.74rem;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
padding: 0.35rem 0.55rem;
|
||||
}
|
||||
|
||||
.ar-attachment-status--available {
|
||||
background: rgba(220, 252, 231, 0.96);
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.ar-attachment-status--pending {
|
||||
background: rgba(254, 249, 195, 0.96);
|
||||
color: #854d0e;
|
||||
}
|
||||
|
||||
.ar-attachment-status--unavailable {
|
||||
background: rgba(254, 226, 226, 0.96);
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
.arweave-uploads-screen {
|
||||
min-height: calc(100vh - 2rem);
|
||||
}
|
||||
|
||||
.arweave-uploads-toolbar {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
.arweave-uploads-list {
|
||||
max-height: calc(100vh - 18rem);
|
||||
min-height: 18rem;
|
||||
}
|
||||
|
||||
.mono-cell {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
overflow-wrap: anywhere;
|
||||
|
||||
Reference in New Issue
Block a user