mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-28 19:47:49 +08:00
fix: match agent bubble arrow style
This commit is contained in:
@@ -118,6 +118,11 @@ interface FabBubbleLayout {
|
|||||||
y: number
|
y: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FabBubbleArrowSource {
|
||||||
|
kind: AgentAssistantBubbleKind
|
||||||
|
variant: AgentAssistantBubbleVariant
|
||||||
|
}
|
||||||
|
|
||||||
const fabRootRef = ref<HTMLElement | null>(null)
|
const fabRootRef = ref<HTMLElement | null>(null)
|
||||||
const fabBubbleRef = ref<HTMLElement | null>(null)
|
const fabBubbleRef = ref<HTMLElement | null>(null)
|
||||||
const fabDocked = ref(false)
|
const fabDocked = ref(false)
|
||||||
@@ -150,6 +155,10 @@ const fabBubblePositionStyle = ref({
|
|||||||
'--agent-assistant-bubbles-y': '0px',
|
'--agent-assistant-bubbles-y': '0px',
|
||||||
})
|
})
|
||||||
const fabBubblePositioned = ref(false)
|
const fabBubblePositioned = ref(false)
|
||||||
|
const fabBubbleArrowSource = ref<FabBubbleArrowSource>({
|
||||||
|
kind: 'custom',
|
||||||
|
variant: 'default',
|
||||||
|
})
|
||||||
const fabPressed = ref(false)
|
const fabPressed = ref(false)
|
||||||
const fabBubbles = ref<AgentAssistantEntryBubble[]>([])
|
const fabBubbles = ref<AgentAssistantEntryBubble[]>([])
|
||||||
const fabDragging = ref(false)
|
const fabDragging = ref(false)
|
||||||
@@ -171,6 +180,11 @@ const fabBubbleTimers = new Map<string, number>()
|
|||||||
|
|
||||||
const hasFabBubbles = computed(() => fabBubbles.value.length > 0)
|
const hasFabBubbles = computed(() => fabBubbles.value.length > 0)
|
||||||
const hasKeepOpenFabBubbles = computed(() => fabBubbles.value.some(item => item.keepOpen))
|
const hasKeepOpenFabBubbles = computed(() => fabBubbles.value.some(item => item.keepOpen))
|
||||||
|
const fabBubbleClassList = computed(() => [
|
||||||
|
`agent-assistant-fab__bubbles--${fabBubblePlacement.value}`,
|
||||||
|
`agent-assistant-fab__bubbles--arrow-${fabBubbleArrowSource.value.kind}`,
|
||||||
|
`agent-assistant-fab__bubbles--arrow-${fabBubbleArrowSource.value.variant}`,
|
||||||
|
])
|
||||||
|
|
||||||
function createBubbleId(prefix = 'bubble') {
|
function createBubbleId(prefix = 'bubble') {
|
||||||
return `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
return `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
||||||
@@ -426,11 +440,69 @@ function calculateFabBubbleLayout(): FabBubbleLayout | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFallbackFabBubbleArrowSource(placement: FabBubblePlacement): FabBubbleArrowSource {
|
||||||
|
const fallbackBubble =
|
||||||
|
placement === 'bottom' || placement === 'right'
|
||||||
|
? fabBubbles.value[0]
|
||||||
|
: fabBubbles.value.at(-1) || fabBubbles.value[0]
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: fallbackBubble?.kind || 'custom',
|
||||||
|
variant: fallbackBubble?.variant || 'default',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBubbleDistanceToArrow(rect: DOMRect, arrowX: number, arrowY: number, placement: FabBubblePlacement) {
|
||||||
|
if (placement === 'left' || placement === 'right') {
|
||||||
|
if (arrowY >= rect.top && arrowY <= rect.bottom) return 0
|
||||||
|
|
||||||
|
return Math.min(Math.abs(arrowY - rect.top), Math.abs(arrowY - rect.bottom))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arrowX >= rect.left && arrowX <= rect.right) return 0
|
||||||
|
|
||||||
|
return Math.min(Math.abs(arrowX - rect.left), Math.abs(arrowX - rect.right))
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncFabBubbleArrowSource(layout: FabBubbleLayout, rootRect: DOMRect) {
|
||||||
|
const bubbleElements = Array.from(
|
||||||
|
fabBubbleRef.value?.querySelectorAll<HTMLElement>('.agent-assistant-fab__bubble') || [],
|
||||||
|
)
|
||||||
|
if (!bubbleElements.length) {
|
||||||
|
fabBubbleArrowSource.value = getFallbackFabBubbleArrowSource(layout.placement)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrowClientX = rootRect.left + layout.x + layout.arrowX
|
||||||
|
const arrowClientY = rootRect.top + layout.y + layout.arrowY
|
||||||
|
let matchedBubbleId = ''
|
||||||
|
let matchedDistance = Number.POSITIVE_INFINITY
|
||||||
|
|
||||||
|
for (const element of bubbleElements) {
|
||||||
|
const rect = element.getBoundingClientRect()
|
||||||
|
const distance = getBubbleDistanceToArrow(rect, arrowClientX, arrowClientY, layout.placement)
|
||||||
|
|
||||||
|
if (distance < matchedDistance) {
|
||||||
|
matchedDistance = distance
|
||||||
|
matchedBubbleId = element.dataset.bubbleId || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchedBubble = fabBubbles.value.find(item => item.id === matchedBubbleId)
|
||||||
|
fabBubbleArrowSource.value = matchedBubble
|
||||||
|
? {
|
||||||
|
kind: matchedBubble.kind,
|
||||||
|
variant: matchedBubble.variant,
|
||||||
|
}
|
||||||
|
: getFallbackFabBubbleArrowSource(layout.placement)
|
||||||
|
}
|
||||||
|
|
||||||
function syncFabBubblePosition() {
|
function syncFabBubblePosition() {
|
||||||
if (!hasFabBubbles.value || !props.active) return
|
if (!hasFabBubbles.value || !props.active) return
|
||||||
|
|
||||||
|
const rootRect = getFabRootElement()?.getBoundingClientRect()
|
||||||
const layout = calculateFabBubbleLayout()
|
const layout = calculateFabBubbleLayout()
|
||||||
if (!layout) return
|
if (!layout || !rootRect) return
|
||||||
|
|
||||||
fabBubblePlacement.value = layout.placement
|
fabBubblePlacement.value = layout.placement
|
||||||
fabBubblePositionStyle.value = {
|
fabBubblePositionStyle.value = {
|
||||||
@@ -439,6 +511,7 @@ function syncFabBubblePosition() {
|
|||||||
'--agent-assistant-bubbles-x': `${Math.round(layout.x)}px`,
|
'--agent-assistant-bubbles-x': `${Math.round(layout.x)}px`,
|
||||||
'--agent-assistant-bubbles-y': `${Math.round(layout.y)}px`,
|
'--agent-assistant-bubbles-y': `${Math.round(layout.y)}px`,
|
||||||
}
|
}
|
||||||
|
syncFabBubbleArrowSource(layout, rootRect)
|
||||||
fabBubblePositioned.value = true
|
fabBubblePositioned.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1060,7 +1133,7 @@ defineExpose({
|
|||||||
v-if="hasFabBubbles"
|
v-if="hasFabBubbles"
|
||||||
ref="fabBubbleRef"
|
ref="fabBubbleRef"
|
||||||
class="agent-assistant-fab__bubbles"
|
class="agent-assistant-fab__bubbles"
|
||||||
:class="`agent-assistant-fab__bubbles--${fabBubblePlacement}`"
|
:class="fabBubbleClassList"
|
||||||
:style="fabBubblePositionStyle"
|
:style="fabBubblePositionStyle"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
>
|
>
|
||||||
@@ -1070,6 +1143,7 @@ defineExpose({
|
|||||||
:key="bubble.id"
|
:key="bubble.id"
|
||||||
class="agent-assistant-fab__bubble"
|
class="agent-assistant-fab__bubble"
|
||||||
:class="[`agent-assistant-fab__bubble--${bubble.kind}`, `agent-assistant-fab__bubble--${bubble.variant}`]"
|
:class="[`agent-assistant-fab__bubble--${bubble.kind}`, `agent-assistant-fab__bubble--${bubble.variant}`]"
|
||||||
|
:data-bubble-id="bubble.id"
|
||||||
role="status"
|
role="status"
|
||||||
>
|
>
|
||||||
<strong v-if="bubble.title" class="agent-assistant-fab__bubble-title">
|
<strong v-if="bubble.title" class="agent-assistant-fab__bubble-title">
|
||||||
@@ -1323,8 +1397,12 @@ defineExpose({
|
|||||||
.agent-assistant-fab__bubbles::before {
|
.agent-assistant-fab__bubbles::before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
border: 1px solid rgba(var(--v-theme-on-surface), 0.08);
|
--agent-assistant-bubble-arrow-accent-rgb: var(--v-theme-primary);
|
||||||
background: var(--agent-assistant-bubble-bg);
|
--agent-assistant-bubble-arrow-border: rgba(var(--v-theme-on-surface), 0.08);
|
||||||
|
--agent-assistant-bubble-arrow-bg: var(--agent-assistant-bubble-bg);
|
||||||
|
|
||||||
|
border: 1px solid var(--agent-assistant-bubble-arrow-border);
|
||||||
|
background: var(--agent-assistant-bubble-arrow-bg);
|
||||||
block-size: 0.62rem;
|
block-size: 0.62rem;
|
||||||
border-block-start: 0;
|
border-block-start: 0;
|
||||||
border-inline-start: 0;
|
border-inline-start: 0;
|
||||||
@@ -1335,6 +1413,42 @@ defineExpose({
|
|||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agent-assistant-fab__bubbles--arrow-notification::before {
|
||||||
|
--agent-assistant-bubble-arrow-border: rgba(var(--v-theme-primary), 0.22);
|
||||||
|
--agent-assistant-bubble-arrow-bg: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(var(--v-theme-primary), 0.1),
|
||||||
|
transparent 48%
|
||||||
|
),
|
||||||
|
rgba(var(--v-theme-surface), 0.94);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-assistant-fab__bubbles--arrow-success::before {
|
||||||
|
--agent-assistant-bubble-arrow-accent-rgb: var(--v-theme-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-assistant-fab__bubbles--arrow-error::before {
|
||||||
|
--agent-assistant-bubble-arrow-accent-rgb: var(--v-theme-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-assistant-fab__bubbles--arrow-warning::before {
|
||||||
|
--agent-assistant-bubble-arrow-accent-rgb: 245, 158, 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-assistant-fab__bubbles--arrow-info::before {
|
||||||
|
--agent-assistant-bubble-arrow-accent-rgb: 14, 165, 233;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-assistant-fab__bubbles--arrow-toast::before {
|
||||||
|
--agent-assistant-bubble-arrow-border: rgba(var(--agent-assistant-bubble-arrow-accent-rgb), 0.3);
|
||||||
|
--agent-assistant-bubble-arrow-bg: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(var(--agent-assistant-bubble-arrow-accent-rgb), 0.12),
|
||||||
|
transparent 54%
|
||||||
|
),
|
||||||
|
rgba(var(--v-theme-surface), 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
.agent-assistant-fab__bubbles--top::before {
|
.agent-assistant-fab__bubbles--top::before {
|
||||||
margin-block-start: -0.31rem;
|
margin-block-start: -0.31rem;
|
||||||
margin-inline-start: -0.31rem;
|
margin-inline-start: -0.31rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user