diff --git a/frontend/src/components/GraphPanel.vue b/frontend/src/components/GraphPanel.vue index e7ebf16..539ebff 100644 --- a/frontend/src/components/GraphPanel.vue +++ b/frontend/src/components/GraphPanel.vue @@ -1,13 +1,15 @@ @@ -281,31 +764,46 @@ onMounted(() => { .panel-header { position: absolute; top: 0; + left: 0; right: 0; - padding: 16px; + padding: 16px 20px; z-index: 10; - pointer-events: none; /* Let clicks pass through to graph */ + display: flex; + justify-content: space-between; + align-items: center; + background: linear-gradient(to bottom, rgba(255,255,255,0.95), rgba(255,255,255,0)); + pointer-events: none; +} + +.panel-title { + font-size: 14px; + font-weight: 600; + color: #333; + pointer-events: auto; } .header-tools { pointer-events: auto; display: flex; - gap: 8px; + gap: 10px; + align-items: center; } .tool-btn { - width: 32px; height: 32px; + padding: 0 12px; border: 1px solid #E0E0E0; background: #FFF; - border-radius: 4px; + border-radius: 6px; display: flex; align-items: center; justify-content: center; + gap: 6px; cursor: pointer; color: #666; transition: all 0.2s; box-shadow: 0 2px 4px rgba(0,0,0,0.02); + font-size: 13px; } .tool-btn:hover { @@ -314,6 +812,10 @@ onMounted(() => { border-color: #CCC; } +.tool-btn .btn-text { + font-size: 12px; +} + .icon-refresh.spinning { animation: spin 1s linear infinite; } @@ -346,19 +848,34 @@ onMounted(() => { opacity: 0.2; } +/* Entity Types Legend - Bottom Left */ .graph-legend { position: absolute; bottom: 24px; left: 24px; - display: flex; - flex-wrap: wrap; - gap: 12px; - background: rgba(255,255,255,0.9); - padding: 12px; + background: rgba(255,255,255,0.95); + padding: 12px 16px; border-radius: 8px; border: 1px solid #EAEAEA; - box-shadow: 0 4px 12px rgba(0,0,0,0.05); - max-width: 80%; + box-shadow: 0 4px 16px rgba(0,0,0,0.06); + z-index: 10; +} + +.legend-title { + display: block; + font-size: 11px; + font-weight: 600; + color: #E91E63; + margin-bottom: 10px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.legend-items { + display: flex; + flex-wrap: wrap; + gap: 10px 16px; + max-width: 320px; } .legend-item { @@ -366,95 +883,423 @@ onMounted(() => { align-items: center; gap: 6px; font-size: 12px; - color: #444; + color: #555; } .legend-dot { - width: 8px; - height: 8px; + width: 10px; + height: 10px; border-radius: 50%; + flex-shrink: 0; } -/* Detail Panel */ +.legend-label { + white-space: nowrap; +} + +/* Edge Labels Toggle - Top Right */ +.edge-labels-toggle { + position: absolute; + top: 60px; + right: 20px; + display: flex; + align-items: center; + gap: 10px; + background: #FFF; + padding: 8px 14px; + border-radius: 20px; + border: 1px solid #E0E0E0; + box-shadow: 0 2px 8px rgba(0,0,0,0.04); + z-index: 10; +} + +.toggle-switch { + position: relative; + display: inline-block; + width: 40px; + height: 22px; +} + +.toggle-switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #E0E0E0; + border-radius: 22px; + transition: 0.3s; +} + +.slider:before { + position: absolute; + content: ""; + height: 16px; + width: 16px; + left: 3px; + bottom: 3px; + background-color: white; + border-radius: 50%; + transition: 0.3s; +} + +input:checked + .slider { + background-color: #7B2D8E; +} + +input:checked + .slider:before { + transform: translateX(18px); +} + +.toggle-label { + font-size: 12px; + color: #666; +} + +/* Detail Panel - Right Side */ .detail-panel { position: absolute; - top: 16px; - left: 16px; - width: 280px; + top: 60px; + right: 20px; + width: 320px; + max-height: calc(100% - 100px); background: #FFF; border: 1px solid #EAEAEA; - border-radius: 8px; - box-shadow: 0 4px 20px rgba(0,0,0,0.08); + border-radius: 10px; + box-shadow: 0 8px 32px rgba(0,0,0,0.1); overflow: hidden; - font-family: 'JetBrains Mono', monospace; - font-size: 12px; + font-family: system-ui, -apple-system, sans-serif; + font-size: 13px; + z-index: 20; + display: flex; + flex-direction: column; } .detail-panel-header { display: flex; justify-content: space-between; align-items: center; - padding: 10px 14px; + padding: 14px 16px; background: #FAFAFA; border-bottom: 1px solid #EEE; + flex-shrink: 0; } .detail-title { font-weight: 600; - color: #000; + color: #333; + font-size: 14px; +} + +.detail-type-badge { + padding: 4px 10px; + border-radius: 12px; + font-size: 11px; + font-weight: 500; + margin-left: auto; + margin-right: 12px; } .detail-close { background: none; border: none; - font-size: 16px; + font-size: 20px; cursor: pointer; color: #999; + line-height: 1; + padding: 0; + transition: color 0.2s; +} + +.detail-close:hover { + color: #333; } .detail-content { - padding: 14px; + padding: 16px; + overflow-y: auto; + flex: 1; } .detail-row { - margin-bottom: 8px; + margin-bottom: 12px; display: flex; - flex-direction: column; + flex-wrap: wrap; gap: 4px; } .detail-label { - color: #999; - font-size: 10px; - text-transform: uppercase; + color: #888; + font-size: 12px; + font-weight: 500; + min-width: 80px; } .detail-value { color: #333; + flex: 1; + word-break: break-word; } -.detail-badge { - display: inline-block; - padding: 2px 6px; - color: #FFF; - border-radius: 4px; - font-size: 10px; - width: fit-content; +.detail-value.uuid-text { + font-family: 'JetBrains Mono', monospace; + font-size: 11px; + color: #666; +} + +.detail-value.fact-text { + line-height: 1.5; + color: #444; +} + +.detail-section { + margin-top: 16px; + padding-top: 14px; + border-top: 1px solid #F0F0F0; +} + +.section-title { + font-size: 12px; + font-weight: 600; + color: #666; + margin-bottom: 10px; } .properties-list { - margin-top: 8px; - border-top: 1px dashed #EEE; - padding-top: 8px; + display: flex; + flex-direction: column; + gap: 8px; } .property-item { display: flex; - justify-content: space-between; - margin-bottom: 4px; + gap: 8px; } .property-key { + color: #888; + font-weight: 500; + min-width: 90px; +} + +.property-value { + color: #333; + flex: 1; +} + +.summary-text { + line-height: 1.6; + color: #444; + font-size: 12px; +} + +.labels-list { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.label-tag { + display: inline-block; + padding: 4px 12px; + background: #F5F5F5; + border: 1px solid #E0E0E0; + border-radius: 16px; + font-size: 11px; + color: #555; +} + +.episodes-list { + display: flex; + flex-direction: column; + gap: 6px; +} + +.episode-tag { + display: inline-block; + padding: 6px 10px; + background: #F8F8F8; + border: 1px solid #E8E8E8; + border-radius: 6px; + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + color: #666; + word-break: break-all; +} + +/* Edge relation header */ +.edge-relation-header { + background: #F8F8F8; + padding: 12px; + border-radius: 8px; + margin-bottom: 16px; + font-size: 13px; + font-weight: 500; + color: #333; + line-height: 1.5; + word-break: break-word; +} + +/* Building hint */ +.graph-building-hint { + position: absolute; + bottom: 80px; + left: 50%; + transform: translateX(-50%); + background: rgba(0,0,0,0.75); + color: #fff; + padding: 8px 16px; + border-radius: 20px; + font-size: 12px; + display: flex; + align-items: center; + gap: 8px; +} + +.building-dot { + width: 8px; + height: 8px; + background: #4CAF50; + border-radius: 50%; + animation: pulse 1.5s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.5; transform: scale(0.8); } +} + +/* Loading spinner */ +.loading-spinner { + width: 40px; + height: 40px; + border: 3px solid #E0E0E0; + border-top-color: #7B2D8E; + border-radius: 50%; + animation: spin 1s linear infinite; + margin: 0 auto 16px; +} + +/* Self-loop styles */ +.self-loop-header { + display: flex; + align-items: center; + gap: 8px; + background: linear-gradient(135deg, #E8F5E9 0%, #F1F8E9 100%); + border: 1px solid #C8E6C9; +} + +.self-loop-count { + margin-left: auto; + font-size: 11px; + color: #666; + background: rgba(255,255,255,0.8); + padding: 2px 8px; + border-radius: 10px; +} + +.self-loop-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.self-loop-item { + background: #FAFAFA; + border: 1px solid #EAEAEA; + border-radius: 8px; +} + +.self-loop-item-header { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 12px; + background: #F5F5F5; + cursor: pointer; + transition: background 0.2s; +} + +.self-loop-item-header:hover { + background: #EEEEEE; +} + +.self-loop-item.expanded .self-loop-item-header { + background: #E8E8E8; +} + +.self-loop-index { + font-size: 10px; + font-weight: 600; + color: #888; + background: #E0E0E0; + padding: 2px 6px; + border-radius: 4px; +} + +.self-loop-name { + font-size: 12px; + font-weight: 500; + color: #333; + flex: 1; +} + +.self-loop-toggle { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + font-weight: 600; + color: #888; + background: #E0E0E0; + border-radius: 4px; + transition: all 0.2s; +} + +.self-loop-item.expanded .self-loop-toggle { + background: #D0D0D0; color: #666; } + +.self-loop-item-content { + padding: 12px; + border-top: 1px solid #EAEAEA; +} + +.self-loop-item-content .detail-row { + margin-bottom: 8px; +} + +.self-loop-item-content .detail-label { + font-size: 11px; + min-width: 60px; +} + +.self-loop-item-content .detail-value { + font-size: 12px; +} + +.self-loop-episodes { + margin-top: 8px; +} + +.episodes-list.compact { + flex-direction: row; + flex-wrap: wrap; + gap: 4px; +} + +.episode-tag.small { + padding: 3px 6px; + font-size: 9px; +}