251 lines
5.6 KiB
Svelte
251 lines
5.6 KiB
Svelte
<script>
|
||
import { t } from './i18n'
|
||
|
||
export let events = []
|
||
export let loading = false
|
||
|
||
export let onAccept = (ev) => {}
|
||
export let onDismiss = (ev) => {}
|
||
export let onAttach = (ev) => {}
|
||
export let onRefresh = () => {}
|
||
export let formatTime = (iso) => ''
|
||
|
||
function eventIcon(type) {
|
||
if (type === 'page_visit') return '🌐'
|
||
if (type === 'note_capture') return '📝'
|
||
if (type === 'screenshot') return '📸'
|
||
return '•'
|
||
}
|
||
|
||
function truncate(s, n) {
|
||
if (!s) return ''
|
||
return s.length > n ? s.substring(0, n) + '...' : s
|
||
}
|
||
</script>
|
||
|
||
<div class="browser-events">
|
||
<div class="browser-header">
|
||
<h3>{t('browserEvents', 'События браузера')}</h3>
|
||
<button class="refresh-btn" on:click={onRefresh} disabled={loading}>
|
||
{loading ? '⏳' : '🔄'}
|
||
</button>
|
||
</div>
|
||
|
||
{#if events.length === 0}
|
||
<div class="empty-state">
|
||
{#if loading}
|
||
<p class="loading">{t('loading', 'Загрузка...')}</p>
|
||
{:else}
|
||
<p class="empty-text">{t('noBrowserEvents', 'Нет событий браузера')}</p>
|
||
{/if}
|
||
</div>
|
||
{:else}
|
||
<div class="event-list">
|
||
{#each events as ev}
|
||
<div class="event-item" class:pending={ev.status === 'pending'}>
|
||
<div class="event-icon">{eventIcon(ev.type)}</div>
|
||
<div class="event-body">
|
||
<div class="event-domain">{ev.domain || '?'}</div>
|
||
<div class="event-title" title={ev.title}>{truncate(ev.title || ev.url, 80)}</div>
|
||
<div class="event-meta">
|
||
{#if ev.active_seconds > 0}
|
||
<span class="duration">{ev.active_seconds}с</span>
|
||
{/if}
|
||
{#if ev.ts_start}
|
||
<span class="time">{formatTime(ev.ts_start)}</span>
|
||
{/if}
|
||
{#if ev.status !== 'pending'}
|
||
<span class="status-badge status-{ev.status}">{ev.status}</span>
|
||
{/if}
|
||
</div>
|
||
{#if ev.selected_text}
|
||
<div class="event-text">"{truncate(ev.selected_text, 120)}"</div>
|
||
{/if}
|
||
</div>
|
||
{#if ev.status === 'pending'}
|
||
<div class="event-actions">
|
||
<button class="btn-accept" title="Принять как worklog"
|
||
on:click={() => onAccept(ev)}>✓</button>
|
||
<button class="btn-attach" title="Прикрепить к делу"
|
||
on:click={() => onAttach(ev)}>📎</button>
|
||
<button class="btn-dismiss" title="Удалить"
|
||
on:click={() => onDismiss(ev)}>✕</button>
|
||
</div>
|
||
{/if}
|
||
</div>
|
||
{/each}
|
||
</div>
|
||
{/if}
|
||
</div>
|
||
|
||
<style>
|
||
.browser-events {
|
||
padding: 8px 0;
|
||
}
|
||
|
||
.browser-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 8px;
|
||
padding: 0 4px;
|
||
}
|
||
|
||
.browser-header h3 {
|
||
font-size: 12px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
color: var(--text-dim, #8892b0);
|
||
margin: 0;
|
||
}
|
||
|
||
.refresh-btn {
|
||
background: var(--surface2, #1e2d50);
|
||
border: none;
|
||
border-radius: 4px;
|
||
padding: 4px 8px;
|
||
cursor: pointer;
|
||
font-size: 12px;
|
||
transition: background 0.2s;
|
||
}
|
||
|
||
.refresh-btn:hover {
|
||
background: var(--surface, #16213e);
|
||
}
|
||
|
||
.empty-state {
|
||
padding: 24px;
|
||
text-align: center;
|
||
}
|
||
|
||
.empty-text, .loading {
|
||
color: var(--text-dim, #8892b0);
|
||
font-style: italic;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.event-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.event-item {
|
||
display: flex;
|
||
gap: 8px;
|
||
padding: 6px 8px;
|
||
background: var(--surface, #16213e);
|
||
border-radius: 6px;
|
||
transition: background 0.15s;
|
||
}
|
||
|
||
.event-item.pending {
|
||
border-left: 2px solid var(--accent, #0f9b8e);
|
||
}
|
||
|
||
.event-icon {
|
||
font-size: 16px;
|
||
line-height: 1.4;
|
||
min-width: 20px;
|
||
text-align: center;
|
||
}
|
||
|
||
.event-body {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.event-domain {
|
||
font-size: 11px;
|
||
color: var(--accent, #0f9b8e);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.event-title {
|
||
font-size: 12px;
|
||
color: var(--text, #e0e0e0);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.event-meta {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.duration, .time {
|
||
font-size: 10px;
|
||
color: var(--text-dim, #8892b0);
|
||
}
|
||
|
||
.status-badge {
|
||
font-size: 9px;
|
||
padding: 1px 4px;
|
||
border-radius: 3px;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.status-accepted {
|
||
background: rgba(46, 204, 113, 0.2);
|
||
color: #2ecc71;
|
||
}
|
||
|
||
.status-dismissed {
|
||
background: rgba(231, 76, 60, 0.2);
|
||
color: #e74c3c;
|
||
}
|
||
|
||
.status-attached {
|
||
background: rgba(52, 152, 219, 0.2);
|
||
color: #3498db;
|
||
}
|
||
|
||
.event-text {
|
||
font-size: 11px;
|
||
color: var(--text-dim, #8892b0);
|
||
font-style: italic;
|
||
margin-top: 2px;
|
||
padding: 2px 6px;
|
||
background: rgba(255,255,255,0.03);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.event-actions {
|
||
display: flex;
|
||
gap: 2px;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.event-actions button {
|
||
background: none;
|
||
border: 1px solid var(--surface2, #1e2d50);
|
||
border-radius: 4px;
|
||
width: 24px;
|
||
height: 24px;
|
||
cursor: pointer;
|
||
font-size: 11px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0;
|
||
transition: all 0.15s;
|
||
}
|
||
|
||
.btn-accept:hover {
|
||
background: rgba(46, 204, 113, 0.2);
|
||
border-color: #2ecc71;
|
||
}
|
||
|
||
.btn-attach:hover {
|
||
background: rgba(52, 152, 219, 0.2);
|
||
border-color: #3498db;
|
||
}
|
||
|
||
.btn-dismiss:hover {
|
||
background: rgba(231, 76, 60, 0.2);
|
||
border-color: #e74c3c;
|
||
}
|
||
</style>
|