You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
338 lines
7.3 KiB
338 lines
7.3 KiB
<script lang="ts"> |
|
interface Props { |
|
open?: boolean; |
|
results?: { |
|
success: string[]; |
|
failed: Array<{ relay: string; error: string }>; |
|
} | null; |
|
} |
|
|
|
let { open = $bindable(false), results = $bindable(null) }: Props = $props(); |
|
|
|
let autoCloseTimeout: ReturnType<typeof setTimeout> | null = null; |
|
|
|
$effect(() => { |
|
// Clear any existing timeout when modal state changes |
|
if (autoCloseTimeout) { |
|
clearTimeout(autoCloseTimeout); |
|
autoCloseTimeout = null; |
|
} |
|
|
|
if (open && results) { |
|
// Auto-close after 30 seconds |
|
autoCloseTimeout = setTimeout(() => { |
|
open = false; |
|
autoCloseTimeout = null; |
|
}, 30000); |
|
} |
|
|
|
return () => { |
|
if (autoCloseTimeout) { |
|
clearTimeout(autoCloseTimeout); |
|
autoCloseTimeout = null; |
|
} |
|
}; |
|
}); |
|
|
|
function close() { |
|
open = false; |
|
if (autoCloseTimeout) { |
|
clearTimeout(autoCloseTimeout); |
|
autoCloseTimeout = null; |
|
} |
|
} |
|
|
|
/** |
|
* Convert URLs in text to clickable links |
|
*/ |
|
function linkify(text: string): Array<string | { type: 'link'; url: string; text: string }> { |
|
const urlRegex = /(https?:\/\/[^\s]+)/g; |
|
const parts: Array<string | { type: 'link'; url: string; text: string }> = []; |
|
let lastIndex = 0; |
|
let match; |
|
|
|
while ((match = urlRegex.exec(text)) !== null) { |
|
// Add text before the URL |
|
if (match.index > lastIndex) { |
|
parts.push(text.substring(lastIndex, match.index)); |
|
} |
|
// Add the URL as a link object |
|
parts.push({ |
|
type: 'link', |
|
url: match[0], |
|
text: match[0] |
|
}); |
|
lastIndex = match.index + match[0].length; |
|
} |
|
|
|
// Add remaining text |
|
if (lastIndex < text.length) { |
|
parts.push(text.substring(lastIndex)); |
|
} |
|
|
|
return parts.length > 0 ? parts : [text]; |
|
} |
|
</script> |
|
|
|
{#if open && results} |
|
<div |
|
class="modal-overlay" |
|
onclick={close} |
|
onkeydown={(e) => e.key === 'Escape' && close()} |
|
role="dialog" |
|
aria-modal="true" |
|
tabindex="-1" |
|
> |
|
<div class="modal-content"> |
|
<div class="modal-header"> |
|
<h2>Publication Status</h2> |
|
<button onclick={close} class="close-button">×</button> |
|
</div> |
|
|
|
<div class="modal-body"> |
|
{#if results.success.length > 0} |
|
<div class="success-section"> |
|
<h3>Success ({results.success.length})</h3> |
|
<ul> |
|
{#each results.success as relay} |
|
<li>{relay}</li> |
|
{/each} |
|
</ul> |
|
</div> |
|
{/if} |
|
|
|
{#if results.failed.length > 0} |
|
<div class="failed-section"> |
|
<h3>Failed ({results.failed.length})</h3> |
|
<ul> |
|
{#each results.failed as { relay, error }} |
|
<li> |
|
<strong>{relay}:</strong> |
|
<span class="error-message"> |
|
{#each linkify(error) as part} |
|
{#if typeof part === 'object' && part.type === 'link'} |
|
<a href={part.url} target="_blank" rel="noopener noreferrer" class="error-link">{part.text}</a> |
|
{:else} |
|
{part} |
|
{/if} |
|
{/each} |
|
</span> |
|
</li> |
|
{/each} |
|
</ul> |
|
</div> |
|
{/if} |
|
</div> |
|
|
|
<div class="modal-footer"> |
|
<button onclick={close}>Close</button> |
|
</div> |
|
</div> |
|
</div> |
|
{/if} |
|
|
|
<style> |
|
.modal-overlay { |
|
position: fixed; |
|
top: 0; |
|
left: 0; |
|
right: 0; |
|
bottom: 0; |
|
background: rgba(15, 23, 42, 0.4); |
|
backdrop-filter: blur(4px); |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
z-index: 1000; |
|
} |
|
|
|
.modal-content { |
|
background: #f8fafc; |
|
border: 1px solid #cbd5e1; |
|
border-radius: 8px; |
|
max-width: 600px; |
|
width: 90%; |
|
max-height: 80vh; |
|
overflow: auto; |
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
:global(.dark) .modal-content { |
|
background: #1e293b; |
|
border-color: #475569; |
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); |
|
} |
|
|
|
.modal-header { |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
padding: 1rem; |
|
border-bottom: 1px solid #cbd5e1; |
|
} |
|
|
|
:global(.dark) .modal-header { |
|
border-bottom-color: #475569; |
|
} |
|
|
|
.modal-header h2 { |
|
margin: 0; |
|
font-size: 1.25rem; |
|
font-weight: 600; |
|
color: var(--fog-text, #1f2937); |
|
} |
|
|
|
:global(.dark) .modal-header h2 { |
|
color: var(--fog-dark-text, #f1f5f9); |
|
} |
|
|
|
.close-button { |
|
background: none; |
|
border: none; |
|
font-size: 1.5rem; |
|
cursor: pointer; |
|
padding: 0; |
|
width: 2rem; |
|
height: 2rem; |
|
color: var(--fog-text, #64748b); |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
border-radius: 0.25rem; |
|
transition: background-color 0.2s, color 0.2s; |
|
} |
|
|
|
.close-button:hover { |
|
background: var(--fog-highlight, #e2e8f0); |
|
color: var(--fog-text, #1e2937); |
|
} |
|
|
|
:global(.dark) .close-button { |
|
color: var(--fog-dark-text, #94a3b8); |
|
} |
|
|
|
:global(.dark) .close-button:hover { |
|
background: var(--fog-dark-highlight, #334155); |
|
color: var(--fog-dark-text, #f1f5f9); |
|
} |
|
|
|
.modal-body { |
|
padding: 1rem; |
|
color: var(--fog-text, #1f2937); |
|
} |
|
|
|
:global(.dark) .modal-body { |
|
color: var(--fog-dark-text, #f1f5f9); |
|
} |
|
|
|
.success-section, |
|
.failed-section { |
|
margin-bottom: 1rem; |
|
} |
|
|
|
.success-section h3 { |
|
color: #64748b; |
|
margin: 0 0 0.5rem 0; |
|
font-size: 1rem; |
|
font-weight: 600; |
|
} |
|
|
|
:global(.dark) .success-section h3 { |
|
color: #94a3b8; |
|
} |
|
|
|
.failed-section h3 { |
|
color: #dc2626; |
|
margin: 0 0 0.5rem 0; |
|
font-size: 1rem; |
|
font-weight: 600; |
|
} |
|
|
|
:global(.dark) .failed-section h3 { |
|
color: #f87171; |
|
} |
|
|
|
.modal-body ul { |
|
list-style: none; |
|
padding: 0; |
|
margin: 0.5rem 0; |
|
} |
|
|
|
.modal-body li { |
|
padding: 0.25rem 0; |
|
color: var(--fog-text, #1f2937); |
|
} |
|
|
|
:global(.dark) .modal-body li { |
|
color: var(--fog-dark-text, #f1f5f9); |
|
} |
|
|
|
.modal-body li strong { |
|
color: var(--fog-text, #1f2937); |
|
} |
|
|
|
:global(.dark) .modal-body li strong { |
|
color: var(--fog-dark-text, #f1f5f9); |
|
} |
|
|
|
.error-message { |
|
word-break: break-word; |
|
color: var(--fog-text, #1f2937); |
|
} |
|
|
|
:global(.dark) .error-message { |
|
color: var(--fog-dark-text, #f1f5f9); |
|
} |
|
|
|
.error-link { |
|
color: #3b82f6; |
|
text-decoration: underline; |
|
cursor: pointer; |
|
} |
|
|
|
.error-link:hover { |
|
color: #2563eb; |
|
} |
|
|
|
:global(.dark) .error-link { |
|
color: #60a5fa; |
|
} |
|
|
|
:global(.dark) .error-link:hover { |
|
color: #93c5fd; |
|
} |
|
|
|
.modal-footer { |
|
padding: 1rem; |
|
border-top: 1px solid #cbd5e1; |
|
text-align: right; |
|
} |
|
|
|
:global(.dark) .modal-footer { |
|
border-top-color: #475569; |
|
} |
|
|
|
.modal-footer button { |
|
padding: 0.5rem 1rem; |
|
background: var(--fog-accent, #64748b); |
|
color: #ffffff; /* White text on accent background for good contrast */ |
|
border: none; |
|
border-radius: 4px; |
|
cursor: pointer; |
|
transition: background-color 0.2s; |
|
font-weight: 500; |
|
} |
|
|
|
.modal-footer button:hover { |
|
background: var(--fog-accent, #475569); |
|
} |
|
|
|
:global(.dark) .modal-footer button { |
|
background: var(--fog-dark-accent, #64748b); |
|
color: #ffffff; /* White text on dark accent for good contrast */ |
|
} |
|
|
|
:global(.dark) .modal-footer button:hover { |
|
background: var(--fog-dark-accent, #94a3b8); |
|
} |
|
</style>
|
|
|