mirror of
https://github.com/open-webui/open-webui.git
synced 2026-09-13 23:14:11 +00:00
refac
This commit is contained in:
parent
08578557de
commit
5bb321de06
1 changed files with 29 additions and 112 deletions
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { marked } from 'marked';
|
||||
import Fuse from 'fuse.js';
|
||||
import { autoUpdate, computePosition, flip, offset, shift, size } from '@floating-ui/dom';
|
||||
|
||||
import dayjs from '$lib/dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
|
|
@ -87,104 +88,41 @@
|
|||
let show = false;
|
||||
let triggerElement: HTMLElement | null = null;
|
||||
let contentElement: HTMLElement | null = null;
|
||||
let panelElement: HTMLElement | null = null;
|
||||
let dropdownPosition = { top: 0, left: 0, maxHeight: undefined as number | undefined };
|
||||
let positionFrame: number | undefined;
|
||||
let settleTimers: number[] = [];
|
||||
|
||||
const portal = (node: HTMLElement) => {
|
||||
document.body.appendChild(node);
|
||||
const panel = node.firstElementChild as HTMLElement;
|
||||
const cleanup = autoUpdate(triggerElement!, node, () => {
|
||||
// Let flip measure the full list after content or viewport changes.
|
||||
panel.style.maxHeight = '';
|
||||
computePosition(triggerElement!, node, {
|
||||
strategy: 'fixed',
|
||||
placement: `${placement === 'auto' ? 'bottom' : placement}-${align}`,
|
||||
middleware: [
|
||||
offset(2),
|
||||
placement === 'auto' && flip({ padding: 8, crossAxis: false }),
|
||||
shift({ padding: 8 }),
|
||||
size({
|
||||
padding: 8,
|
||||
apply({ availableHeight, availableWidth }) {
|
||||
panel.style.maxHeight = `${Math.max(0, availableHeight)}px`;
|
||||
panel.style.maxWidth = `${Math.max(0, availableWidth)}px`;
|
||||
}
|
||||
})
|
||||
]
|
||||
}).then(({ x, y }) => {
|
||||
node.style.left = `${x}px`;
|
||||
node.style.top = `${y}px`;
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
cleanup();
|
||||
node.remove();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const measureContent = () => {
|
||||
if (!contentElement) return { width: 0, height: 0 };
|
||||
|
||||
const previousMaxHeight = panelElement?.style.maxHeight;
|
||||
if (panelElement) panelElement.style.maxHeight = '';
|
||||
const rect = contentElement.getBoundingClientRect();
|
||||
if (panelElement && previousMaxHeight !== undefined) {
|
||||
panelElement.style.maxHeight = previousMaxHeight;
|
||||
}
|
||||
|
||||
return { width: rect.width, height: rect.height };
|
||||
};
|
||||
|
||||
const visualViewportRect = () => {
|
||||
const viewport = window.visualViewport;
|
||||
return {
|
||||
left: viewport?.offsetLeft ?? 0,
|
||||
top: viewport?.offsetTop ?? 0,
|
||||
width: viewport?.width ?? window.innerWidth,
|
||||
height: viewport?.height ?? window.innerHeight
|
||||
};
|
||||
};
|
||||
|
||||
const updatePosition = () => {
|
||||
if (!show || !triggerElement) return;
|
||||
const rect = triggerElement.getBoundingClientRect();
|
||||
const { width: contentWidth, height: contentHeight } = measureContent();
|
||||
const viewport = visualViewportRect();
|
||||
const viewportRight = viewport.left + viewport.width;
|
||||
const viewportBottom = viewport.top + viewport.height;
|
||||
const pad = 8;
|
||||
const gap = 2;
|
||||
const spaceBelow = viewportBottom - rect.bottom - gap - pad;
|
||||
const spaceAbove = rect.top - viewport.top - gap - pad;
|
||||
const preferredLeft = align === 'end' && contentWidth ? rect.right - contentWidth : rect.left;
|
||||
const maxLeft = contentWidth ? viewportRight - contentWidth - pad : preferredLeft;
|
||||
const resolvedPlacement =
|
||||
placement === 'auto'
|
||||
? contentHeight && spaceBelow < contentHeight && spaceAbove > spaceBelow
|
||||
? 'top'
|
||||
: 'bottom'
|
||||
: placement;
|
||||
const availableHeight = resolvedPlacement === 'top' ? spaceAbove : spaceBelow;
|
||||
const constrainedHeight =
|
||||
contentHeight && availableHeight >= 0
|
||||
? Math.min(contentHeight, availableHeight)
|
||||
: contentHeight;
|
||||
const top =
|
||||
resolvedPlacement === 'top' && contentHeight
|
||||
? rect.top - constrainedHeight - gap
|
||||
: rect.bottom + gap;
|
||||
|
||||
dropdownPosition = {
|
||||
top: Math.max(viewport.top + pad, Math.min(top, viewportBottom - pad - constrainedHeight)),
|
||||
left: Math.max(viewport.left + pad, Math.min(preferredLeft, maxLeft)),
|
||||
maxHeight:
|
||||
contentHeight && availableHeight >= 0 && contentHeight > availableHeight
|
||||
? Math.max(0, availableHeight)
|
||||
: undefined
|
||||
};
|
||||
};
|
||||
|
||||
const schedulePositionUpdate = () => {
|
||||
if (positionFrame != null) cancelAnimationFrame(positionFrame);
|
||||
positionFrame = requestAnimationFrame(() => {
|
||||
positionFrame = undefined;
|
||||
updatePosition();
|
||||
});
|
||||
};
|
||||
|
||||
const scheduleSettledPositionUpdates = () => {
|
||||
for (const timer of settleTimers) window.clearTimeout(timer);
|
||||
settleTimers = [];
|
||||
schedulePositionUpdate();
|
||||
for (const delay of [50, 150, 300]) {
|
||||
settleTimers.push(window.setTimeout(schedulePositionUpdate, delay));
|
||||
}
|
||||
};
|
||||
|
||||
const handleScroll = (event: Event) => {
|
||||
if (event.target instanceof Node && contentElement?.contains(event.target)) return;
|
||||
schedulePositionUpdate();
|
||||
};
|
||||
|
||||
const focusSearchInput = () => {
|
||||
if (!$mobile) {
|
||||
document.getElementById('model-search-input')?.focus();
|
||||
|
|
@ -206,9 +144,7 @@
|
|||
setProviderDownloadConnections();
|
||||
}
|
||||
resetView();
|
||||
updatePosition();
|
||||
await tick();
|
||||
updatePosition();
|
||||
for (const delay of [0, 50, 150]) {
|
||||
window.setTimeout(focusSearchInput, delay);
|
||||
}
|
||||
|
|
@ -478,7 +414,6 @@
|
|||
await tick();
|
||||
const item = document.querySelector(`[data-arrow-selected="true"]`);
|
||||
item?.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'instant' });
|
||||
schedulePositionUpdate();
|
||||
};
|
||||
|
||||
const setCompareEnabled = (enabled: boolean) => {
|
||||
|
|
@ -824,18 +759,6 @@
|
|||
// Remove duplicates and sort
|
||||
tags = Array.from(new Set(tags)).sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', handleScroll, true);
|
||||
window.visualViewport?.addEventListener('resize', scheduleSettledPositionUpdates);
|
||||
window.visualViewport?.addEventListener('scroll', schedulePositionUpdate);
|
||||
|
||||
return () => {
|
||||
if (positionFrame != null) cancelAnimationFrame(positionFrame);
|
||||
for (const timer of settleTimers) window.clearTimeout(timer);
|
||||
window.removeEventListener('scroll', handleScroll, true);
|
||||
window.visualViewport?.removeEventListener('resize', scheduleSettledPositionUpdates);
|
||||
window.visualViewport?.removeEventListener('scroll', schedulePositionUpdate);
|
||||
};
|
||||
});
|
||||
|
||||
const cancelModelPullHandler = async (model: string) => {
|
||||
|
|
@ -978,11 +901,7 @@
|
|||
}}
|
||||
/>
|
||||
|
||||
<svelte:window
|
||||
on:pointerdown={handlePointerDown}
|
||||
on:keydown={handleKeydown}
|
||||
on:resize={scheduleSettledPositionUpdates}
|
||||
/>
|
||||
<svelte:window on:pointerdown={handlePointerDown} on:keydown={handleKeydown} />
|
||||
|
||||
<div class="relative w-full">
|
||||
<button
|
||||
|
|
@ -1023,13 +942,11 @@
|
|||
<div
|
||||
use:portal
|
||||
bind:this={contentElement}
|
||||
style="position: fixed; z-index: 9999; top: {dropdownPosition.top}px; left: {dropdownPosition.left}px;"
|
||||
style="position: fixed; z-index: 9999; top: 0; left: 0; width: max-content;"
|
||||
>
|
||||
<div
|
||||
bind:this={panelElement}
|
||||
class="z-40 {className ??
|
||||
'w-[20rem]'} max-w-[calc(100vw-1rem)] justify-start rounded-xl border border-gray-100 bg-white p-0.5 shadow-lg outline-hidden dark:border-gray-800 dark:bg-gray-850 dark:text-white flex flex-col overflow-hidden"
|
||||
style={dropdownPosition.maxHeight ? `max-height: ${dropdownPosition.maxHeight}px;` : ''}
|
||||
transition:flyAndScale
|
||||
>
|
||||
<slot>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue