Feat resize model card (#104)

* feat: Use setting definition card size

* refactor: Optimize computed value of the list items

- Add useContainerResize hooks
- Remove v-resize
- Change cols to computed

* refactor(ModelCard): Optimize style

- Control the display of button or name in difference sizes
- Add name tooltip when hiding name

* feat: Add i18n

* pref: optimize style code structure

* feat: add quick resize card size

* feat: add custom size tooltip

* feat: optimize card tool button display judgment
This commit is contained in:
Hayden
2025-02-03 16:40:33 +08:00
committed by GitHub
parent faf4c15865
commit 6a77554932
5 changed files with 151 additions and 16 deletions

View File

@@ -8,7 +8,7 @@
:style="$content_lg(contentStyle)"
>
<div ref="toolbarContainer" class="col-span-full">
<div class="flex flex-col gap-4" :style="$toolbar_2xl(toolbarStyle)">
<div :class="['flex gap-4', $toolbar_2xl('flex-row', 'flex-col')]">
<ResponseInput
v-model="searchContent"
:placeholder="$t('searchModels')"
@@ -26,6 +26,10 @@
v-model="sortOrder"
:items="sortOrderOptions"
></ResponseSelect>
<ResponseSelect
v-model="currentCardSize"
:items="cardSizeOptions"
></ResponseSelect>
</div>
</div>
</div>
@@ -72,7 +76,7 @@ import { genModelKey } from 'utils/model'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
const { isMobile, cardWidth, gutter, aspect } = useConfig()
const { isMobile, gutter, cardSize } = useConfig()
const { data, folders } = useModels()
const { t } = useI18n()
@@ -126,14 +130,14 @@ const sortOrderOptions = ref(
)
const itemSize = computed(() => {
let itemWidth = cardWidth
let itemHeight = cardSize.value.height
let itemGutter = gutter
if (isMobile.value) {
const baseSize = 16
itemWidth = window.innerWidth - baseSize * 2 * 2
itemHeight = window.innerWidth - baseSize * 2 * 2
itemGutter = baseSize * 2
}
return itemWidth / aspect + itemGutter
return itemHeight + itemGutter
})
const { width } = useElementSize(contentContainer)
@@ -142,9 +146,9 @@ const cols = computed(() => {
if (isMobile.value) {
return 1
}
const containerWidth = width.value
return Math.floor((containerWidth - gutter) / (cardWidth + gutter))
const itemWidth = cardSize.value.width
return Math.floor((containerWidth - gutter) / (itemWidth + gutter))
})
const list = computed(() => {
@@ -187,13 +191,56 @@ const list = computed(() => {
})
const contentStyle = computed(() => ({
gridTemplateColumns: `repeat(auto-fit, ${cardWidth}px)`,
gridTemplateColumns: `repeat(auto-fit, ${cardSize.value.width}px)`,
gap: `${gutter}px`,
paddingLeft: `1rem`,
paddingRight: `1rem`,
}))
const toolbarStyle = computed(() => ({
flexDirection: 'row',
}))
const currentCardSize = computed({
get: () => {
const options = cardSizeOptions.value.map((item) => item.value)
const current = [cardSize.value.width, cardSize.value.height].join('x')
if (options.includes(current)) {
return current
}
return 'custom'
},
set: (val) => {
if (val === 'custom') {
app.ui?.settings.show(t('size.customTip'))
return
}
const [width, height] = val.split('x')
app.ui?.settings.setSettingValue(
'ModelManager.UI.CardWidth',
parseInt(width),
)
app.ui?.settings.setSettingValue(
'ModelManager.UI.CardHeight',
parseInt(height),
)
},
})
const cardSizeOptions = computed(() => {
const defineOptions = {
extraLarge: '240x320',
large: '180x240',
medium: '120x160',
small: '80x120',
custom: 'custom',
}
return Object.entries(defineOptions).map(([key, value]) => {
return {
label: t(`size.${key}`),
value,
command() {
currentCardSize.value = value
},
}
})
})
</script>

View File

@@ -1,6 +1,8 @@
<template>
<div
class="group/card relative w-full cursor-pointer select-none preview-aspect"
class="group/card relative cursor-pointer select-none"
:style="{ width: `${cardSize.width}px`, height: `${cardSize.height}px` }"
v-tooltip.top="{ value: model.basename, disabled: showModelName }"
@click.stop="openDetailDialog"
>
<div class="h-full overflow-hidden rounded-lg">
@@ -18,7 +20,7 @@
<div class="pointer-events-none absolute left-0 top-0 h-full w-full p-4">
<div class="relative h-full w-full text-white">
<div class="absolute bottom-0 left-0">
<div v-show="showModelName" class="absolute bottom-0 left-0">
<div class="drop-shadow-[0px_2px_2px_rgba(0,0,0,0.75)]">
<div
:class="[
@@ -33,13 +35,19 @@
<div class="absolute left-0 top-0 w-full">
<div class="flex flex-row items-start justify-between">
<div class="flex items-center rounded-full bg-black/30 px-3 py-2">
<div
v-show="showModelType"
class="flex items-center rounded-full bg-black/30 px-3 py-2"
>
<div :class="['font-bold', $lg('text-xs')]">
{{ model.type }}
</div>
</div>
<div class="opacity-0 duration-300 group-hover/card:opacity-100">
<div
v-show="showToolButton"
class="opacity-0 duration-300 group-hover/card:opacity-100"
>
<div class="flex flex-col gap-4 *:pointer-events-auto">
<Button
icon="pi pi-plus"
@@ -71,6 +79,7 @@
<script setup lang="ts">
import DialogModelDetail from 'components/DialogModelDetail.vue'
import { useConfig } from 'hooks/config'
import { useContainerQueries } from 'hooks/container'
import { useDialog } from 'hooks/dialog'
import { useModelNodeAction } from 'hooks/model'
@@ -85,6 +94,8 @@ interface Props {
const props = defineProps<Props>()
const { cardSize } = useConfig()
const dialog = useDialog()
const openDetailDialog = () => {
@@ -105,6 +116,18 @@ const preview = computed(() =>
: props.model.preview,
)
const showToolButton = computed(() => {
return cardSize.value.width >= 180 && cardSize.value.height >= 240
})
const showModelName = computed(() => {
return cardSize.value.width >= 160 && cardSize.value.height >= 120
})
const showModelType = computed(() => {
return cardSize.value.width >= 120
})
const { addModelNode, dragToAddModelNode, copyModelNode, loadPreviewWorkflow } =
useModelNodeAction(props.model)

View File

@@ -21,9 +21,19 @@ export const useConfig = defineStore('config', (store) => {
window.removeEventListener('resize', checkDeviceType)
})
const cardSize = ref({
width:
app.ui?.settings.getSettingValue<number>('ModelManager.UI.CardWidth') ??
240,
height:
app.ui?.settings.getSettingValue<number>('ModelManager.UI.CardHeight') ??
310,
})
const config = {
isMobile,
gutter: 16,
cardSize,
cardWidth: 240,
aspect: 7 / 9,
}
@@ -99,6 +109,39 @@ function useAddConfigSettings(store: import('hooks/store').StoreProvider) {
defaultValue: undefined,
})
// UI settings
app.ui?.settings.addSetting({
id: 'ModelManager.UI.CardWidth',
category: [t('modelManager'), t('setting.ui'), 'CardWidth'],
name: t('setting.cardWidth'),
type: 'slider',
defaultValue: 240,
attrs: {
min: 80,
max: 320,
step: 10,
},
onChange(value) {
store.config.cardSize.value.width = value
},
})
app.ui?.settings.addSetting({
id: 'ModelManager.UI.CardHeight',
category: [t('modelManager'), t('setting.ui'), 'CardHeight'],
name: t('setting.cardHeight'),
type: 'slider',
defaultValue: 320,
attrs: {
min: 80,
max: 320,
step: 10,
},
onChange(value) {
store.config.cardSize.value.height = value
},
})
// Scan information
app.ui?.settings.addSetting({
id: 'ModelManager.ScanFiles.Full',

View File

@@ -31,6 +31,14 @@ const messages = {
created: 'Latest created',
modified: 'Latest modified',
},
size: {
extraLarge: 'Extra Large Icons',
large: 'Large Icons',
medium: 'Medium Icons',
small: 'Small Icons',
custom: 'Custom Size',
customTip: 'Set in `Settings > Model Manager > UI`',
},
info: {
type: 'Model Type',
pathIndex: 'Directory',
@@ -41,11 +49,14 @@ const messages = {
},
setting: {
apiKey: 'API Key',
cardHeight: 'Card Height',
cardWidth: 'Card Width',
scan: 'Scan',
scanMissing: 'Download missing information or preview',
scanAll: "Override all models' information and preview",
includeHiddenFiles: 'Include hidden files(start with .)',
excludeScanTypes: 'Exclude scan types (separate with commas)',
ui: 'UI',
},
},
zh: {
@@ -77,6 +88,14 @@ const messages = {
created: '最新创建',
modified: '最新修改',
},
size: {
extraLarge: '超大图标',
large: '大图标',
medium: '中等图标',
small: '小图标',
custom: '自定义尺寸',
customTip: '在 `设置 > 模型管理器 > 外观` 中设置',
},
info: {
type: '类型',
pathIndex: '目录',
@@ -87,11 +106,14 @@ const messages = {
},
setting: {
apiKey: '密钥',
cardHeight: '卡片高度',
cardWidth: '卡片宽度',
scan: '扫描',
scanMissing: '下载缺失的信息或预览图片',
scanAll: '覆盖所有模型信息和预览图片',
includeHiddenFiles: '包含隐藏文件(以 . 开头的文件或文件夹)',
excludeScanTypes: '排除扫描类型(使用英文逗号隔开)',
ui: '外观',
},
},
}

View File

@@ -155,7 +155,7 @@ declare namespace ComfyAPI {
deprecated?: boolean
}
class ComfySettingsDialog {
class ComfySettingsDialog extends dialog.ComfyDialog {
addSetting: (params: SettingParams) => { value: any }
getSettingValue: <T>(id: string, defaultValue?: T) => T
setSettingValue: <T>(id: string, value: T) => void