[New Feature] sub directories support (#135)

* feat: add close all dialog

* feat: add new ui toggle setting

* feat: add tree display ui

* feat: add search and sort

* feat: change model data structure

* pref: Optimize model data structure

* feat: set sub folder by choose
This commit is contained in:
Hayden
2025-02-18 16:03:07 +08:00
committed by GitHub
parent 37be9a0b0d
commit 86c11e5343
20 changed files with 1123 additions and 216 deletions

View File

@@ -7,13 +7,56 @@
</template>
</ResponseSelect>
<ResponseSelect class="w-full" v-model="pathIndex" :items="pathOptions">
</ResponseSelect>
<div class="flex gap-2 overflow-hidden">
<div class="flex-1 overflow-hidden rounded bg-gray-500/30">
<div class="flex h-full items-center justify-end">
<span class="overflow-hidden text-ellipsis whitespace-nowrap px-2">
{{ renderedModelFolder }}
</span>
</div>
</div>
<Button icon="pi pi-folder" @click="handleSelectFolder"></Button>
<Dialog
v-model:visible="folderSelectVisible"
:header="$t('folder')"
:auto-z-index="false"
:pt:mask:style="{ zIndex }"
:pt:root:style="{ height: '50vh', maxWidth: '50vw' }"
pt:content:class="flex-1"
>
<div class="flex h-full flex-col overflow-hidden">
<div class="flex-1 overflow-hidden">
<ResponseScroll>
<Tree
class="h-full"
v-model:selection-keys="modelFolder"
:value="pathOptions"
selectionMode="single"
:pt:nodeLabel:class="'text-ellipsis overflow-hidden'"
></Tree>
</ResponseScroll>
</div>
<div class="flex justify-end gap-2">
<Button
:label="$t('cancel')"
severity="secondary"
@click="handleCancelSelectFolder"
></Button>
<Button
:label="$t('select')"
@click="handleConfirmSelectFolder"
></Button>
</div>
</div>
</Dialog>
</div>
<ResponseInput
v-model.trim="basename"
v-model.trim.valid="basename"
class="-mr-2 text-right"
update-trigger="blur"
:validate="validateBasename"
>
<template #suffix>
<span class="text-base opacity-60">
@@ -48,14 +91,30 @@
<script setup lang="ts">
import ResponseInput from 'components/ResponseInput.vue'
import ResponseScroll from 'components/ResponseScroll.vue'
import ResponseSelect from 'components/ResponseSelect.vue'
import { useModelBaseInfo } from 'hooks/model'
import { computed } from 'vue'
import { useDialog } from 'hooks/dialog'
import { useModelBaseInfo, useModelFolder } from 'hooks/model'
import { useToast } from 'hooks/toast'
import Button from 'primevue/button'
import { usePrimeVue } from 'primevue/config'
import Dialog from 'primevue/dialog'
import Tree from 'primevue/tree'
import { computed, ref } from 'vue'
const editable = defineModel<boolean>('editable')
const { baseInfo, pathIndex, basename, extension, type, modelFolders } =
useModelBaseInfo()
const { toast } = useToast()
const {
baseInfo,
pathIndex,
subFolder,
basename,
extension,
type,
modelFolders,
} = useModelBaseInfo()
const typeOptions = computed(() => {
return Object.keys(modelFolders.value).map((curr) => {
@@ -70,25 +129,104 @@ const typeOptions = computed(() => {
})
})
const pathOptions = computed(() => {
return (modelFolders.value[type.value] ?? []).map((folder, index) => {
return {
value: index,
label: folder,
command: () => {
pathIndex.value = index
},
}
})
})
const information = computed(() => {
return Object.values(baseInfo.value).filter((row) => {
if (editable.value) {
const hiddenKeys = ['fullname', 'pathIndex']
const hiddenKeys = ['basename', 'pathIndex']
return !hiddenKeys.includes(row.key)
}
return true
})
})
const validateBasename = (val: string | undefined) => {
if (!val) {
toast.add({
severity: 'error',
detail: 'basename is required',
life: 3000,
})
return false
}
const invalidChart = /[\\/:*?"<>|]/
if (invalidChart.test(val)) {
toast.add({
severity: 'error',
detail: 'basename is invalid, \\/:*?"<>|',
life: 3000,
})
return false
}
return true
}
const folderSelectVisible = ref(false)
const { stack } = useDialog()
const { config } = usePrimeVue()
const zIndex = computed(() => {
const baseZIndex = config.zIndex?.modal ?? 1100
return baseZIndex + stack.value.length + 1
})
const handleSelectFolder = () => {
if (!type.value) {
toast.add({
severity: 'error',
summary: 'Error',
detail: 'Please select model type first',
life: 5000,
})
return
}
folderSelectVisible.value = true
}
const { pathOptions } = useModelFolder({ type })
const selectedModelFolder = ref<string>()
const modelFolder = computed({
get: () => {
const folderPath = baseInfo.value.pathIndex.display
const selectedKey = selectedModelFolder.value ?? folderPath
return { [selectedKey]: true }
},
set: (val) => {
const folderPath = Object.keys(val)[0]
selectedModelFolder.value = folderPath
},
})
const renderedModelFolder = computed(() => {
return baseInfo.value.pathIndex.display
})
const handleCancelSelectFolder = () => {
selectedModelFolder.value = undefined
folderSelectVisible.value = false
}
const handleConfirmSelectFolder = () => {
const folderPath = Object.keys(modelFolder.value)[0]
const folders = modelFolders.value[type.value]
pathIndex.value = folders.findIndex((item) => folderPath.includes(item))
if (pathIndex.value < 0) {
toast.add({
severity: 'error',
detail: 'Folder not found',
life: 3000,
})
return
}
const prefixPath = folders[pathIndex.value]
subFolder.value = folderPath.replace(prefixPath, '')
if (subFolder.value.startsWith('/')) {
subFolder.value = subFolder.value.replace('/', '')
}
selectedModelFolder.value = undefined
folderSelectVisible.value = false
}
</script>