pref: Use virtual scroll load models
This commit is contained in:
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -35,6 +35,7 @@
|
|||||||
"inplace",
|
"inplace",
|
||||||
"contentcontainer",
|
"contentcontainer",
|
||||||
"itemlist",
|
"itemlist",
|
||||||
|
"virtualscroller"
|
||||||
],
|
],
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
</ResponseSelect>
|
</ResponseSelect>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ResponseScrollArea class="-mx-5 h-full">
|
<ResponseScroll class="-mx-5 h-full">
|
||||||
<div class="px-5">
|
<div class="px-5">
|
||||||
<ModelContent
|
<ModelContent
|
||||||
v-for="{ item } in data"
|
v-for="{ item } in data"
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ResponseScrollArea>
|
</ResponseScroll>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogResizer :min-width="390"></DialogResizer>
|
<DialogResizer :min-width="390"></DialogResizer>
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
import DialogResizer from 'components/DialogResizer.vue'
|
import DialogResizer from 'components/DialogResizer.vue'
|
||||||
import ResponseInput from 'components/ResponseInput.vue'
|
import ResponseInput from 'components/ResponseInput.vue'
|
||||||
import ResponseSelect from 'components/ResponseSelect.vue'
|
import ResponseSelect from 'components/ResponseSelect.vue'
|
||||||
import ResponseScrollArea from 'components/ResponseScrollArea.vue'
|
import ResponseScroll from 'components/ResponseScroll.vue'
|
||||||
import ModelContent from 'components/ModelContent.vue'
|
import ModelContent from 'components/ModelContent.vue'
|
||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button'
|
||||||
import Dialog from 'primevue/dialog'
|
import Dialog from 'primevue/dialog'
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ResponseScrollArea>
|
<ResponseScroll>
|
||||||
<div class="w-full px-4">
|
<div class="w-full px-4">
|
||||||
<ul class="m-0 flex list-none flex-col gap-4 p-0">
|
<ul class="m-0 flex list-none flex-col gap-4 p-0">
|
||||||
<li
|
<li
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul> -->
|
</ul> -->
|
||||||
</ResponseScrollArea>
|
</ResponseScroll>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogResizer :min-width="390" :min-height="390"></DialogResizer>
|
<DialogResizer :min-width="390" :min-height="390"></DialogResizer>
|
||||||
@@ -155,7 +155,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import DialogCreateTask from 'components/DialogCreateTask.vue'
|
import DialogCreateTask from 'components/DialogCreateTask.vue'
|
||||||
import DialogResizer from 'components/DialogResizer.vue'
|
import DialogResizer from 'components/DialogResizer.vue'
|
||||||
import ResponseScrollArea from 'components/ResponseScrollArea.vue'
|
import ResponseScroll from 'components/ResponseScroll.vue'
|
||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button'
|
||||||
import Dialog from 'primevue/dialog'
|
import Dialog from 'primevue/dialog'
|
||||||
import { useDownload } from 'hooks/download'
|
import { useDownload } from 'hooks/download'
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
minimizeIcon="pi pi-arrow-down-left-and-arrow-up-right-to-center"
|
minimizeIcon="pi pi-arrow-down-left-and-arrow-up-right-to-center"
|
||||||
:pt:mask:class="['group', { open }]"
|
:pt:mask:class="['group', { open }]"
|
||||||
pt:root:class="max-h-full group-[:not(.open)]:!hidden"
|
pt:root:class="max-h-full group-[:not(.open)]:!hidden"
|
||||||
pt:content:class="px-0"
|
pt:content:class="px-0 flex-1"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex flex-1 items-center justify-between pr-2">
|
<div class="flex flex-1 items-center justify-between pr-2">
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
['--card-width']: `${cardWidth}px`,
|
['--card-width']: `${cardWidth}px`,
|
||||||
['--gutter']: `${gutter}px`,
|
['--gutter']: `${gutter}px`,
|
||||||
}"
|
}"
|
||||||
|
v-resize="onContainerResize"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
:class="[
|
:class="[
|
||||||
@@ -72,34 +73,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ResponseScrollArea class="h-full">
|
<ResponseScroll
|
||||||
<div
|
:items="list"
|
||||||
:class="[
|
:itemSize="cardWidth / aspect + gutter"
|
||||||
'-mt-8 grid grid-cols-1 justify-center gap-8 px-8',
|
:row-key="(item) => item.map(genModelKey).join(',')"
|
||||||
'@lg/content:grid-cols-[repeat(auto-fit,var(--card-width))]',
|
class="h-full flex-1"
|
||||||
'@lg/content:gap-[var(--gutter)]',
|
>
|
||||||
'@lg/content:-mt-[var(--gutter)]',
|
<template #item="{ item }">
|
||||||
'@lg/content:px-4',
|
<div
|
||||||
]"
|
:class="[
|
||||||
>
|
'grid grid-cols-1 justify-center gap-8 px-8',
|
||||||
<div class="col-span-full"></div>
|
'@lg/content:grid-cols-[repeat(auto-fit,var(--card-width))]',
|
||||||
<div v-for="model in list" v-show="model.visible" :key="model.id">
|
'@lg/content:gap-[var(--gutter)]',
|
||||||
|
'@lg/content:px-4',
|
||||||
|
]"
|
||||||
|
>
|
||||||
<DialogModelCard
|
<DialogModelCard
|
||||||
:key="`${model.type}:${model.pathIndex}:${model.fullname}`"
|
v-for="model in item"
|
||||||
|
:key="genModelKey(model)"
|
||||||
:model="model"
|
:model="model"
|
||||||
></DialogModelCard>
|
></DialogModelCard>
|
||||||
|
<div class="col-span-full"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
|
|
||||||
<div v-show="noneDisplayModel" class="flex justify-center pt-20">
|
<template #empty>
|
||||||
<div class="select-none text-lg font-bold">No models found</div>
|
<div class="flex flex-col items-center gap-4 pt-20 opacity-70">
|
||||||
</div>
|
<i class="pi pi-box text-4xl"></i>
|
||||||
</ResponseScrollArea>
|
<div class="select-none text-lg font-bold">No models found</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ResponseScroll>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogResizer
|
<DialogResizer
|
||||||
:min-width="cardWidth * 2 + gutter + 42"
|
:min-width="cardWidth * 2 + gutter + 42"
|
||||||
:min-height="cardWidth * aspect * 0.5 + 162"
|
:min-height="(cardWidth / aspect) * 0.5 + 162"
|
||||||
></DialogResizer>
|
></DialogResizer>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
@@ -112,13 +121,15 @@ import DialogResizer from 'components/DialogResizer.vue'
|
|||||||
import DialogModelCard from 'components/DialogModelCard.vue'
|
import DialogModelCard from 'components/DialogModelCard.vue'
|
||||||
import ResponseInput from 'components/ResponseInput.vue'
|
import ResponseInput from 'components/ResponseInput.vue'
|
||||||
import ResponseSelect from 'components/ResponseSelect.vue'
|
import ResponseSelect from 'components/ResponseSelect.vue'
|
||||||
import ResponseScrollArea from 'components/ResponseScrollArea.vue'
|
import ResponseScroll from 'components/ResponseScroll.vue'
|
||||||
import Dialog from 'primevue/dialog'
|
import Dialog from 'primevue/dialog'
|
||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { useToast } from 'hooks/toast'
|
import { useToast } from 'hooks/toast'
|
||||||
import { useDownload } from 'hooks/download'
|
import { useDownload } from 'hooks/download'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { chunk } from 'lodash'
|
||||||
|
import { defineResizeCallback } from 'hooks/resize'
|
||||||
|
|
||||||
const { isMobile, cardWidth, gutter, aspect, refreshSetting } = useConfig()
|
const { isMobile, cardWidth, gutter, aspect, refreshSetting } = useConfig()
|
||||||
|
|
||||||
@@ -173,8 +184,11 @@ const sortOrderOptions = ref(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const colSpan = ref(1)
|
||||||
|
const colSpanWidth = ref(cardWidth)
|
||||||
|
|
||||||
const list = computed(() => {
|
const list = computed(() => {
|
||||||
const filterList = data.value.map((model) => {
|
const filterList = data.value.filter((model) => {
|
||||||
const showAllModel = currentType.value === 'all'
|
const showAllModel = currentType.value === 'all'
|
||||||
|
|
||||||
const matchType = showAllModel || model.type === currentType.value
|
const matchType = showAllModel || model.type === currentType.value
|
||||||
@@ -182,9 +196,7 @@ const list = computed(() => {
|
|||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(searchContent.value?.toLowerCase() || '')
|
.includes(searchContent.value?.toLowerCase() || '')
|
||||||
|
|
||||||
model.visible = matchType && matchName
|
return matchType && matchName
|
||||||
|
|
||||||
return model
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let sortStrategy = (a: Model, b: Model) => 0
|
let sortStrategy = (a: Model, b: Model) => 0
|
||||||
@@ -205,11 +217,9 @@ const list = computed(() => {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return filterList.sort(sortStrategy)
|
const sortedList = filterList.sort(sortStrategy)
|
||||||
})
|
|
||||||
|
|
||||||
const noneDisplayModel = computed(() => {
|
return chunk(sortedList, colSpan.value)
|
||||||
return !list.value.some((model) => model.visible)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const refreshModels = async () => {
|
const refreshModels = async () => {
|
||||||
@@ -220,4 +230,19 @@ const refreshModels = async () => {
|
|||||||
life: 2000,
|
life: 2000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onContainerResize = defineResizeCallback((entries) => {
|
||||||
|
const entry = entries[0]
|
||||||
|
if (isMobile.value) {
|
||||||
|
colSpan.value = 1
|
||||||
|
} else {
|
||||||
|
const containerWidth = entry.contentRect.width
|
||||||
|
colSpan.value = Math.floor((containerWidth - gutter) / (cardWidth + gutter))
|
||||||
|
colSpanWidth.value = colSpan.value * (cardWidth + gutter) - gutter
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const genModelKey = (model: BaseModel) => {
|
||||||
|
return `${model.type}:${model.pathIndex}:${model.fullname}`
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
pt:content:class="px-0"
|
pt:content:class="px-0"
|
||||||
@after-hide="handleCancel"
|
@after-hide="handleCancel"
|
||||||
>
|
>
|
||||||
<ResponseScrollArea class="h-full">
|
<ResponseScroll class="h-full">
|
||||||
<div class="px-8">
|
<div class="px-8">
|
||||||
<ModelContent
|
<ModelContent
|
||||||
v-model:editable="editable"
|
v-model:editable="editable"
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</ModelContent>
|
</ModelContent>
|
||||||
</div>
|
</div>
|
||||||
</ResponseScrollArea>
|
</ResponseScroll>
|
||||||
<DialogResizer :min-width="390"></DialogResizer>
|
<DialogResizer :min-width="390"></DialogResizer>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
@@ -59,7 +59,7 @@ import Button from 'primevue/button'
|
|||||||
import Dialog from 'primevue/dialog'
|
import Dialog from 'primevue/dialog'
|
||||||
import ModelContent from 'components/ModelContent.vue'
|
import ModelContent from 'components/ModelContent.vue'
|
||||||
import DialogResizer from 'components/DialogResizer.vue'
|
import DialogResizer from 'components/DialogResizer.vue'
|
||||||
import ResponseScrollArea from 'components/ResponseScrollArea.vue'
|
import ResponseScroll from 'components/ResponseScroll.vue'
|
||||||
import { useConfig } from 'hooks/config'
|
import { useConfig } from 'hooks/config'
|
||||||
import { computed, ref, watchEffect } from 'vue'
|
import { computed, ref, watchEffect } from 'vue'
|
||||||
import { useModelNodeAction, useModels } from 'hooks/model'
|
import { useModelNodeAction, useModels } from 'hooks/model'
|
||||||
|
|||||||
@@ -4,12 +4,30 @@
|
|||||||
ref="viewport"
|
ref="viewport"
|
||||||
data-scroll-viewport
|
data-scroll-viewport
|
||||||
class="h-full w-full overflow-auto scrollbar-none"
|
class="h-full w-full overflow-auto scrollbar-none"
|
||||||
|
:style="{ contain: items ? 'strict' : undefined }"
|
||||||
@scroll="onContentScroll"
|
@scroll="onContentScroll"
|
||||||
v-resize="onContainerResize"
|
v-resize="onContainerResize"
|
||||||
>
|
>
|
||||||
<div data-scroll-content style="min-width: 100%">
|
<div data-scroll-content class="relative min-w-full">
|
||||||
<slot name="default"></slot>
|
<slot name="default">
|
||||||
|
<div
|
||||||
|
v-for="(item, index) in loadedItems"
|
||||||
|
:key="genRowKey(item, index)"
|
||||||
|
:style="{ height: `${itemSize}px` }"
|
||||||
|
>
|
||||||
|
<slot name="item" :item="item"></slot>
|
||||||
|
</div>
|
||||||
|
<slot v-if="loadedItems.length === 0" name="empty">
|
||||||
|
<div class="absolute w-full py-20 text-center">No Data</div>
|
||||||
|
</slot>
|
||||||
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
data-scroll-space
|
||||||
|
class="pointer-events-none absolute left-0 top-0 h-px w-px"
|
||||||
|
:style="spaceStyle"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -41,12 +59,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts" generic="T">
|
||||||
import { nextTick, onUnmounted, ref } from 'vue'
|
import { nextTick, onUnmounted, ref, watch } from 'vue'
|
||||||
import { clamp, throttle } from 'lodash'
|
import { clamp, throttle } from 'lodash'
|
||||||
|
|
||||||
interface ScrollAreaProps {
|
interface ScrollAreaProps {
|
||||||
|
items?: T[][]
|
||||||
|
itemSize?: number
|
||||||
scrollbar?: boolean
|
scrollbar?: boolean
|
||||||
|
rowKey?: string | ((item: T[]) => string)
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<ScrollAreaProps>(), {
|
const props = withDefaults(defineProps<ScrollAreaProps>(), {
|
||||||
@@ -105,25 +126,91 @@ const scrollbars = ref<Record<ScrollbarDirection, Scrollbar>>({
|
|||||||
|
|
||||||
const isDragging = ref(false)
|
const isDragging = ref(false)
|
||||||
|
|
||||||
const onContainerResize: ResizeObserverCallback = throttle((entries) => {
|
const spaceStyle = ref({})
|
||||||
emit('resize', entries)
|
const loadedItems = ref<T[][]>([])
|
||||||
if (isDragging.value) return
|
|
||||||
|
|
||||||
const entry = entries[0]
|
const genRowKey = (item: any | any[], index: number) => {
|
||||||
const container = entry.target as HTMLElement
|
if (typeof props.rowKey === 'function') {
|
||||||
const content = container.querySelector('[data-scroll-content]')!
|
return props.rowKey(item)
|
||||||
|
}
|
||||||
|
return item[props.rowKey ?? 'key'] ?? index
|
||||||
|
}
|
||||||
|
|
||||||
|
const setSpacerSize = () => {
|
||||||
|
const items = props.items
|
||||||
|
if (items) {
|
||||||
|
const itemSize = props.itemSize ?? 0
|
||||||
|
spaceStyle.value = { height: `${itemSize * items.length}px` }
|
||||||
|
} else {
|
||||||
|
spaceStyle.value = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getContainerContent = (raw?: boolean): HTMLElement => {
|
||||||
|
const container = viewport.value as HTMLElement
|
||||||
|
|
||||||
|
if (props.items && !raw) {
|
||||||
|
return container.querySelector('[data-scroll-space]')!
|
||||||
|
}
|
||||||
|
return container.querySelector('[data-scroll-content]')!
|
||||||
|
}
|
||||||
|
|
||||||
|
const init = () => {
|
||||||
|
const container = viewport.value as HTMLElement
|
||||||
|
container.scrollTop = 0
|
||||||
|
getContainerContent().style.transform = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateLoadItems = () => {
|
||||||
|
let visibleItems: any[] = []
|
||||||
|
|
||||||
|
if (props.items) {
|
||||||
|
const container = viewport.value as HTMLElement
|
||||||
|
const content = getContainerContent(true)
|
||||||
|
|
||||||
|
const resolveVisibleItems = (items: any[], attr: ScrollbarAttribute) => {
|
||||||
|
const containerSize = container[attr.clientSize]
|
||||||
|
const itemSize = props.itemSize!
|
||||||
|
const viewCount = Math.ceil(containerSize / itemSize)
|
||||||
|
|
||||||
|
let start = Math.floor(container[attr.scrollOffset] / itemSize)
|
||||||
|
const offset = start * itemSize
|
||||||
|
|
||||||
|
let end = start + viewCount
|
||||||
|
end = Math.min(end + viewCount, items.length)
|
||||||
|
|
||||||
|
content.style.transform = `translateY(${offset}px)`
|
||||||
|
return items.slice(start, end)
|
||||||
|
}
|
||||||
|
|
||||||
|
visibleItems = resolveVisibleItems(props.items, scrollbarAttrs.vertical)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedItems.value = visibleItems
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateScrollThumbSize = () => {
|
||||||
|
const container = viewport.value as HTMLElement
|
||||||
|
const content = getContainerContent()
|
||||||
|
|
||||||
const resolveScrollbarSize = (item: Scrollbar, attr: ScrollbarAttribute) => {
|
const resolveScrollbarSize = (item: Scrollbar, attr: ScrollbarAttribute) => {
|
||||||
const containerSize: number = container[attr.clientSize]
|
const containerSize: number = container[attr.clientSize]
|
||||||
const contentSize: number = content[attr.clientSize]
|
const contentSize: number = content[attr.clientSize]
|
||||||
item.visible = props.scrollbar && contentSize > containerSize
|
item.visible = props.scrollbar && contentSize > containerSize
|
||||||
item.size = Math.pow(containerSize, 2) / contentSize
|
item.size = Math.max(Math.pow(containerSize, 2) / contentSize, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
resolveScrollbarSize(scrollbars.value.horizontal, scrollbarAttrs.horizontal)
|
resolveScrollbarSize(scrollbars.value.horizontal, scrollbarAttrs.horizontal)
|
||||||
resolveScrollbarSize(scrollbars.value.vertical, scrollbarAttrs.vertical)
|
resolveScrollbarSize(scrollbars.value.vertical, scrollbarAttrs.vertical)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onContainerResize: ResizeObserverCallback = throttle((entries) => {
|
||||||
|
emit('resize', entries)
|
||||||
|
if (isDragging.value) return
|
||||||
|
|
||||||
|
calculateScrollThumbSize()
|
||||||
})
|
})
|
||||||
|
|
||||||
const onContentScroll = throttle((event: Event) => {
|
const onContentScroll = throttle((event: Event) => {
|
||||||
@@ -131,7 +218,7 @@ const onContentScroll = throttle((event: Event) => {
|
|||||||
if (isDragging.value) return
|
if (isDragging.value) return
|
||||||
|
|
||||||
const container = event.target as HTMLDivElement
|
const container = event.target as HTMLDivElement
|
||||||
const content = container.querySelector('[data-scroll-content]')!
|
const content = getContainerContent()
|
||||||
|
|
||||||
const resolveOffset = (item: Scrollbar, attr: ScrollbarAttribute) => {
|
const resolveOffset = (item: Scrollbar, attr: ScrollbarAttribute) => {
|
||||||
const containerSize = container[attr.clientSize]
|
const containerSize = container[attr.clientSize]
|
||||||
@@ -145,6 +232,8 @@ const onContentScroll = throttle((event: Event) => {
|
|||||||
|
|
||||||
resolveOffset(scrollbars.value.horizontal, scrollbarAttrs.horizontal)
|
resolveOffset(scrollbars.value.horizontal, scrollbarAttrs.horizontal)
|
||||||
resolveOffset(scrollbars.value.vertical, scrollbarAttrs.vertical)
|
resolveOffset(scrollbars.value.vertical, scrollbarAttrs.vertical)
|
||||||
|
|
||||||
|
calculateLoadItems()
|
||||||
})
|
})
|
||||||
|
|
||||||
const viewport = ref<HTMLElement>()
|
const viewport = ref<HTMLElement>()
|
||||||
@@ -154,7 +243,7 @@ const prevDraggingEvent = ref<MouseEvent>()
|
|||||||
const moveThumb = throttle((event: MouseEvent) => {
|
const moveThumb = throttle((event: MouseEvent) => {
|
||||||
if (isDragging.value) {
|
if (isDragging.value) {
|
||||||
const container = viewport.value!
|
const container = viewport.value!
|
||||||
const content = container.querySelector('[data-scroll-content]')!
|
const content = getContainerContent()
|
||||||
|
|
||||||
const resolveOffset = (item: Scrollbar, attr: ScrollbarAttribute) => {
|
const resolveOffset = (item: Scrollbar, attr: ScrollbarAttribute) => {
|
||||||
const containerSize = container[attr.clientSize]
|
const containerSize = container[attr.clientSize]
|
||||||
@@ -180,6 +269,8 @@ const moveThumb = throttle((event: MouseEvent) => {
|
|||||||
scrollbarAttrs[scrollDirection],
|
scrollbarAttrs[scrollDirection],
|
||||||
)
|
)
|
||||||
prevDraggingEvent.value = event
|
prevDraggingEvent.value = event
|
||||||
|
|
||||||
|
calculateLoadItems()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -204,6 +295,16 @@ const startDragThumb = (event: MouseEvent) => {
|
|||||||
document.body.style.cursor = 'default'
|
document.body.style.cursor = 'default'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.items,
|
||||||
|
() => {
|
||||||
|
init()
|
||||||
|
setSpacerSize()
|
||||||
|
calculateScrollThumbSize()
|
||||||
|
calculateLoadItems()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
stopMoveThumb()
|
stopMoveThumb()
|
||||||
})
|
})
|
||||||
@@ -8,7 +8,6 @@ import { app } from 'scripts/comfyAPI'
|
|||||||
import { bytesToSize, formatDate, previewUrlToFile } from 'utils/common'
|
import { bytesToSize, formatDate, previewUrlToFile } from 'utils/common'
|
||||||
import { ModelGrid } from 'utils/legacy'
|
import { ModelGrid } from 'utils/legacy'
|
||||||
import { resolveModelType } from 'utils/model'
|
import { resolveModelType } from 'utils/model'
|
||||||
// import {}
|
|
||||||
import {
|
import {
|
||||||
computed,
|
computed,
|
||||||
inject,
|
inject,
|
||||||
@@ -22,10 +21,7 @@ import {
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
export const useModels = defineStore('models', () => {
|
export const useModels = defineStore('models', () => {
|
||||||
const { data, refresh } = useRequest<(Model & { visible?: boolean })[]>(
|
const { data, refresh } = useRequest<Model[]>('/models', { defaultValue: [] })
|
||||||
'/models',
|
|
||||||
{ defaultValue: [] },
|
|
||||||
)
|
|
||||||
const { toast, confirm } = useToast()
|
const { toast, confirm } = useToast()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const loading = useLoading()
|
const loading = useLoading()
|
||||||
|
|||||||
Reference in New Issue
Block a user