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:
@@ -8,7 +8,7 @@
|
|||||||
:style="$content_lg(contentStyle)"
|
:style="$content_lg(contentStyle)"
|
||||||
>
|
>
|
||||||
<div ref="toolbarContainer" class="col-span-full">
|
<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
|
<ResponseInput
|
||||||
v-model="searchContent"
|
v-model="searchContent"
|
||||||
:placeholder="$t('searchModels')"
|
:placeholder="$t('searchModels')"
|
||||||
@@ -26,6 +26,10 @@
|
|||||||
v-model="sortOrder"
|
v-model="sortOrder"
|
||||||
:items="sortOrderOptions"
|
:items="sortOrderOptions"
|
||||||
></ResponseSelect>
|
></ResponseSelect>
|
||||||
|
<ResponseSelect
|
||||||
|
v-model="currentCardSize"
|
||||||
|
:items="cardSizeOptions"
|
||||||
|
></ResponseSelect>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,7 +76,7 @@ import { genModelKey } from 'utils/model'
|
|||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
const { isMobile, cardWidth, gutter, aspect } = useConfig()
|
const { isMobile, gutter, cardSize } = useConfig()
|
||||||
|
|
||||||
const { data, folders } = useModels()
|
const { data, folders } = useModels()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -126,14 +130,14 @@ const sortOrderOptions = ref(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const itemSize = computed(() => {
|
const itemSize = computed(() => {
|
||||||
let itemWidth = cardWidth
|
let itemHeight = cardSize.value.height
|
||||||
let itemGutter = gutter
|
let itemGutter = gutter
|
||||||
if (isMobile.value) {
|
if (isMobile.value) {
|
||||||
const baseSize = 16
|
const baseSize = 16
|
||||||
itemWidth = window.innerWidth - baseSize * 2 * 2
|
itemHeight = window.innerWidth - baseSize * 2 * 2
|
||||||
itemGutter = baseSize * 2
|
itemGutter = baseSize * 2
|
||||||
}
|
}
|
||||||
return itemWidth / aspect + itemGutter
|
return itemHeight + itemGutter
|
||||||
})
|
})
|
||||||
|
|
||||||
const { width } = useElementSize(contentContainer)
|
const { width } = useElementSize(contentContainer)
|
||||||
@@ -142,9 +146,9 @@ const cols = computed(() => {
|
|||||||
if (isMobile.value) {
|
if (isMobile.value) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
const containerWidth = width.value
|
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(() => {
|
const list = computed(() => {
|
||||||
@@ -187,13 +191,56 @@ const list = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const contentStyle = computed(() => ({
|
const contentStyle = computed(() => ({
|
||||||
gridTemplateColumns: `repeat(auto-fit, ${cardWidth}px)`,
|
gridTemplateColumns: `repeat(auto-fit, ${cardSize.value.width}px)`,
|
||||||
gap: `${gutter}px`,
|
gap: `${gutter}px`,
|
||||||
paddingLeft: `1rem`,
|
paddingLeft: `1rem`,
|
||||||
paddingRight: `1rem`,
|
paddingRight: `1rem`,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const toolbarStyle = computed(() => ({
|
const currentCardSize = computed({
|
||||||
flexDirection: 'row',
|
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>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<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"
|
@click.stop="openDetailDialog"
|
||||||
>
|
>
|
||||||
<div class="h-full overflow-hidden rounded-lg">
|
<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="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="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="drop-shadow-[0px_2px_2px_rgba(0,0,0,0.75)]">
|
||||||
<div
|
<div
|
||||||
:class="[
|
:class="[
|
||||||
@@ -33,13 +35,19 @@
|
|||||||
|
|
||||||
<div class="absolute left-0 top-0 w-full">
|
<div class="absolute left-0 top-0 w-full">
|
||||||
<div class="flex flex-row items-start justify-between">
|
<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')]">
|
<div :class="['font-bold', $lg('text-xs')]">
|
||||||
{{ model.type }}
|
{{ model.type }}
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="flex flex-col gap-4 *:pointer-events-auto">
|
||||||
<Button
|
<Button
|
||||||
icon="pi pi-plus"
|
icon="pi pi-plus"
|
||||||
@@ -71,6 +79,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import DialogModelDetail from 'components/DialogModelDetail.vue'
|
import DialogModelDetail from 'components/DialogModelDetail.vue'
|
||||||
|
import { useConfig } from 'hooks/config'
|
||||||
import { useContainerQueries } from 'hooks/container'
|
import { useContainerQueries } from 'hooks/container'
|
||||||
import { useDialog } from 'hooks/dialog'
|
import { useDialog } from 'hooks/dialog'
|
||||||
import { useModelNodeAction } from 'hooks/model'
|
import { useModelNodeAction } from 'hooks/model'
|
||||||
@@ -85,6 +94,8 @@ interface Props {
|
|||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const { cardSize } = useConfig()
|
||||||
|
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
|
|
||||||
const openDetailDialog = () => {
|
const openDetailDialog = () => {
|
||||||
@@ -105,6 +116,18 @@ const preview = computed(() =>
|
|||||||
: props.model.preview,
|
: 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 } =
|
const { addModelNode, dragToAddModelNode, copyModelNode, loadPreviewWorkflow } =
|
||||||
useModelNodeAction(props.model)
|
useModelNodeAction(props.model)
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,19 @@ export const useConfig = defineStore('config', (store) => {
|
|||||||
window.removeEventListener('resize', checkDeviceType)
|
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 = {
|
const config = {
|
||||||
isMobile,
|
isMobile,
|
||||||
gutter: 16,
|
gutter: 16,
|
||||||
|
cardSize,
|
||||||
cardWidth: 240,
|
cardWidth: 240,
|
||||||
aspect: 7 / 9,
|
aspect: 7 / 9,
|
||||||
}
|
}
|
||||||
@@ -99,6 +109,39 @@ function useAddConfigSettings(store: import('hooks/store').StoreProvider) {
|
|||||||
defaultValue: undefined,
|
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
|
// Scan information
|
||||||
app.ui?.settings.addSetting({
|
app.ui?.settings.addSetting({
|
||||||
id: 'ModelManager.ScanFiles.Full',
|
id: 'ModelManager.ScanFiles.Full',
|
||||||
|
|||||||
22
src/i18n.ts
22
src/i18n.ts
@@ -31,6 +31,14 @@ const messages = {
|
|||||||
created: 'Latest created',
|
created: 'Latest created',
|
||||||
modified: 'Latest modified',
|
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: {
|
info: {
|
||||||
type: 'Model Type',
|
type: 'Model Type',
|
||||||
pathIndex: 'Directory',
|
pathIndex: 'Directory',
|
||||||
@@ -41,11 +49,14 @@ const messages = {
|
|||||||
},
|
},
|
||||||
setting: {
|
setting: {
|
||||||
apiKey: 'API Key',
|
apiKey: 'API Key',
|
||||||
|
cardHeight: 'Card Height',
|
||||||
|
cardWidth: 'Card Width',
|
||||||
scan: 'Scan',
|
scan: 'Scan',
|
||||||
scanMissing: 'Download missing information or preview',
|
scanMissing: 'Download missing information or preview',
|
||||||
scanAll: "Override all models' information and preview",
|
scanAll: "Override all models' information and preview",
|
||||||
includeHiddenFiles: 'Include hidden files(start with .)',
|
includeHiddenFiles: 'Include hidden files(start with .)',
|
||||||
excludeScanTypes: 'Exclude scan types (separate with commas)',
|
excludeScanTypes: 'Exclude scan types (separate with commas)',
|
||||||
|
ui: 'UI',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
@@ -77,6 +88,14 @@ const messages = {
|
|||||||
created: '最新创建',
|
created: '最新创建',
|
||||||
modified: '最新修改',
|
modified: '最新修改',
|
||||||
},
|
},
|
||||||
|
size: {
|
||||||
|
extraLarge: '超大图标',
|
||||||
|
large: '大图标',
|
||||||
|
medium: '中等图标',
|
||||||
|
small: '小图标',
|
||||||
|
custom: '自定义尺寸',
|
||||||
|
customTip: '在 `设置 > 模型管理器 > 外观` 中设置',
|
||||||
|
},
|
||||||
info: {
|
info: {
|
||||||
type: '类型',
|
type: '类型',
|
||||||
pathIndex: '目录',
|
pathIndex: '目录',
|
||||||
@@ -87,11 +106,14 @@ const messages = {
|
|||||||
},
|
},
|
||||||
setting: {
|
setting: {
|
||||||
apiKey: '密钥',
|
apiKey: '密钥',
|
||||||
|
cardHeight: '卡片高度',
|
||||||
|
cardWidth: '卡片宽度',
|
||||||
scan: '扫描',
|
scan: '扫描',
|
||||||
scanMissing: '下载缺失的信息或预览图片',
|
scanMissing: '下载缺失的信息或预览图片',
|
||||||
scanAll: '覆盖所有模型信息和预览图片',
|
scanAll: '覆盖所有模型信息和预览图片',
|
||||||
includeHiddenFiles: '包含隐藏文件(以 . 开头的文件或文件夹)',
|
includeHiddenFiles: '包含隐藏文件(以 . 开头的文件或文件夹)',
|
||||||
excludeScanTypes: '排除扫描类型(使用英文逗号隔开)',
|
excludeScanTypes: '排除扫描类型(使用英文逗号隔开)',
|
||||||
|
ui: '外观',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/types/global.d.ts
vendored
2
src/types/global.d.ts
vendored
@@ -155,7 +155,7 @@ declare namespace ComfyAPI {
|
|||||||
deprecated?: boolean
|
deprecated?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
class ComfySettingsDialog {
|
class ComfySettingsDialog extends dialog.ComfyDialog {
|
||||||
addSetting: (params: SettingParams) => { value: any }
|
addSetting: (params: SettingParams) => { value: any }
|
||||||
getSettingValue: <T>(id: string, defaultValue?: T) => T
|
getSettingValue: <T>(id: string, defaultValue?: T) => T
|
||||||
setSettingValue: <T>(id: string, value: T) => void
|
setSettingValue: <T>(id: string, value: T) => void
|
||||||
|
|||||||
Reference in New Issue
Block a user