|
|
class CustomFileCard extends HTMLElement { |
|
|
static get observedAttributes() { |
|
|
return ['status']; |
|
|
} |
|
|
|
|
|
constructor() { |
|
|
super(); |
|
|
this.attachShadow({ mode: 'open' }); |
|
|
} |
|
|
|
|
|
connectedCallback() { |
|
|
this.render(); |
|
|
} |
|
|
|
|
|
attributeChangedCallback(name, oldValue, newValue) { |
|
|
if (name === 'status') { |
|
|
this.render(); |
|
|
} |
|
|
} |
|
|
|
|
|
render() { |
|
|
const name = this.getAttribute('name') || 'Untitled'; |
|
|
const status = this.getAttribute('status') || 'pending'; |
|
|
|
|
|
let statusText, statusColor, icon; |
|
|
|
|
|
switch(status) { |
|
|
case 'pending': |
|
|
statusText = 'Analyzing...'; |
|
|
statusColor = 'bg-yellow-500'; |
|
|
icon = 'clock'; |
|
|
break; |
|
|
case 'safe': |
|
|
statusText = 'Secure'; |
|
|
statusColor = 'bg-green-500'; |
|
|
icon = 'check-circle'; |
|
|
break; |
|
|
case 'vulnerable': |
|
|
statusText = 'Vulnerable'; |
|
|
statusColor = 'bg-red-500'; |
|
|
icon = 'alert-triangle'; |
|
|
break; |
|
|
case 'error': |
|
|
statusText = 'Error'; |
|
|
statusColor = 'bg-gray-500'; |
|
|
icon = 'x-circle'; |
|
|
break; |
|
|
default: |
|
|
statusText = 'Unknown'; |
|
|
statusColor = 'bg-gray-500'; |
|
|
icon = 'help-circle'; |
|
|
} |
|
|
|
|
|
this.shadowRoot.innerHTML = ` |
|
|
<style> |
|
|
.card { |
|
|
background-color: white; |
|
|
border-radius: 0.75rem; |
|
|
overflow: hidden; |
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
.card:hover { |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); |
|
|
} |
|
|
.card-header { |
|
|
padding: 1.5rem; |
|
|
border-bottom: 1px solid #e5e7eb; |
|
|
} |
|
|
.card-title { |
|
|
font-size: 1.25rem; |
|
|
font-weight: 600; |
|
|
color: #1f2937; |
|
|
margin-bottom: 0.5rem; |
|
|
white-space: nowrap; |
|
|
overflow: hidden; |
|
|
text-overflow: ellipsis; |
|
|
} |
|
|
.card-body { |
|
|
padding: 1.5rem; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
align-items: center; |
|
|
} |
|
|
.status { |
|
|
display: inline-flex; |
|
|
align-items: center; |
|
|
padding: 0.25rem 0.75rem; |
|
|
border-radius: 9999px; |
|
|
font-size: 0.875rem; |
|
|
font-weight: 500; |
|
|
color: white; |
|
|
} |
|
|
.icon { |
|
|
width: 3rem; |
|
|
height: 3rem; |
|
|
margin-bottom: 1rem; |
|
|
} |
|
|
.progress-bar { |
|
|
width: 100%; |
|
|
height: 0.5rem; |
|
|
background-color: #e5e7eb; |
|
|
border-radius: 0.25rem; |
|
|
overflow: hidden; |
|
|
margin-top: 1rem; |
|
|
} |
|
|
.progress { |
|
|
height: 100%; |
|
|
background-color: #3b82f6; |
|
|
width: ${status === 'pending' ? '50%' : '100%'}; |
|
|
animation: ${status === 'pending' ? 'pulse 2s infinite' : 'none'}; |
|
|
} |
|
|
@keyframes pulse { |
|
|
0% { opacity: 0.6; } |
|
|
50% { opacity: 1; } |
|
|
100% { opacity: 0.6; } |
|
|
} |
|
|
</style> |
|
|
|
|
|
<div class="card"> |
|
|
<div class="card-header"> |
|
|
<h3 class="card-title">${name}</h3> |
|
|
<div class="status ${statusColor}"> |
|
|
<i data-feather="${icon}" class="mr-1" width="14" height="14"></i> |
|
|
${statusText} |
|
|
</div> |
|
|
</div> |
|
|
<div class="card-body"> |
|
|
<i data-feather="${icon === 'clock' ? 'code' : icon}" class="icon" width="48" height="48"></i> |
|
|
<div class="progress-bar"> |
|
|
<div class="progress"></div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
|
|
|
if (this.shadowRoot.querySelector('[data-feather]')) { |
|
|
feather.replace(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
customElements.define('custom-file-card', CustomFileCard); |