feat(models): align component model editing with type selection~

This commit is contained in:
Matthieu
2025-09-30 17:32:00 +02:00
parent 84bc99d8ec
commit 25d2aa1bcc
3 changed files with 149 additions and 27 deletions

View File

@@ -5,13 +5,15 @@
:depth="0" :depth="0"
:component-types="availableComponentTypes" :component-types="availableComponentTypes"
:piece-types="availablePieceTypes" :piece-types="availablePieceTypes"
:lock-type="lockRootType"
:locked-type-label="displayedRootTypeLabel"
is-root is-root
/> />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, watch, computed, onMounted } from 'vue' import { reactive, watch, computed, onMounted, ref } from 'vue'
import StructureNodeEditor from '~/components/StructureNodeEditor.vue' import StructureNodeEditor from '~/components/StructureNodeEditor.vue'
import { import {
defaultStructure, defaultStructure,
@@ -28,11 +30,62 @@ const props = defineProps({
type: Object, type: Object,
default: () => defaultStructure(), default: () => defaultStructure(),
}, },
rootTypeId: {
type: String,
default: '',
},
rootTypeLabel: {
type: String,
default: '',
},
lockRootType: {
type: Boolean,
default: false,
},
}) })
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
const localStructure = reactive(hydrateStructureForEditor(props.modelValue)) const localStructure = reactive(hydrateStructureForEditor(props.modelValue))
const previousLockedLabel = ref(props.rootTypeLabel || '')
const { pieceTypes, loadPieceTypes } = usePieceTypes()
const { componentTypes, loadComponentTypes } = useComponentTypes()
const availablePieceTypes = computed(() => pieceTypes.value ?? [])
const availableComponentTypes = computed(() => componentTypes.value ?? [])
const fallbackRootTypeLabel = computed(() => {
if (!props.rootTypeId) {
return ''
}
const match = availableComponentTypes.value.find((type) => type?.id === props.rootTypeId)
return match?.name || ''
})
const displayedRootTypeLabel = computed(() => props.rootTypeLabel || fallbackRootTypeLabel.value)
const syncRootType = () => {
if (!props.lockRootType) {
previousLockedLabel.value = props.rootTypeLabel || ''
return
}
const newTypeId = props.rootTypeId || ''
const newLabel = displayedRootTypeLabel.value
localStructure.typeComposantId = newTypeId
localStructure.typeComposantLabel = newLabel
const previousLabel = previousLockedLabel.value
if (!localStructure.name || localStructure.name === previousLabel || localStructure.name === '') {
localStructure.name = newLabel || localStructure.name
}
previousLockedLabel.value = newLabel
}
let lastEmitted = JSON.stringify(cloneStructure(props.modelValue))
const syncFromProps = (value: any) => { const syncFromProps = (value: any) => {
const hydrated = hydrateStructureForEditor(value) const hydrated = hydrateStructureForEditor(value)
@@ -40,6 +93,7 @@ const syncFromProps = (value: any) => {
localStructure.pieces = hydrated.pieces localStructure.pieces = hydrated.pieces
localStructure.subComponents = hydrated.subComponents localStructure.subComponents = hydrated.subComponents
lastEmitted = JSON.stringify(cloneStructure(value)) lastEmitted = JSON.stringify(cloneStructure(value))
syncRootType()
} }
watch( watch(
@@ -50,7 +104,20 @@ watch(
{ deep: true } { deep: true }
) )
let lastEmitted = JSON.stringify(cloneStructure(props.modelValue)) watch(
() => [props.rootTypeId, props.rootTypeLabel, props.lockRootType],
() => {
syncRootType()
},
{ immediate: true }
)
watch(
availableComponentTypes,
() => {
syncRootType()
}
)
watch( watch(
localStructure, localStructure,
@@ -65,12 +132,6 @@ watch(
{ deep: true } { deep: true }
) )
const { pieceTypes, loadPieceTypes } = usePieceTypes()
const { componentTypes, loadComponentTypes } = useComponentTypes()
const availablePieceTypes = computed(() => pieceTypes.value ?? [])
const availableComponentTypes = computed(() => componentTypes.value ?? [])
onMounted(async () => { onMounted(async () => {
const loaders: Promise<any>[] = [] const loaders: Promise<any>[] = []
if (!availablePieceTypes.value.length) { if (!availablePieceTypes.value.length) {
@@ -82,5 +143,6 @@ onMounted(async () => {
if (loaders.length) { if (loaders.length) {
await Promise.allSettled(loaders) await Promise.allSettled(loaders)
} }
syncRootType()
}) })
</script> </script>

View File

@@ -8,25 +8,32 @@
{{ isRoot ? 'Famille de composant racine' : 'Famille de composant' }} {{ isRoot ? 'Famille de composant racine' : 'Famille de composant' }}
</span> </span>
</label> </label>
<select <template v-if="!lockType">
v-model="node.typeComposantId" <select
class="select select-bordered select-sm w-full" v-model="node.typeComposantId"
@change="handleComponentTypeSelect(node)" class="select select-bordered select-sm w-full"
> @change="handleComponentTypeSelect(node)"
<option value="">
Sélectionner une famille de composant
</option>
<option
v-for="type in componentTypes"
:key="type.id"
:value="type.id"
> >
{{ formatComponentTypeOption(type) }} <option value="">
</option> Sélectionner une famille de composant
</select> </option>
<p class="text-[11px] text-gray-500"> <option
{{ node.typeComposantId ? `Sélection : ${getComponentTypeLabel(node.typeComposantId) || 'Inconnue'}` : 'Aucune famille sélectionnée' }} v-for="type in componentTypes"
</p> :key="type.id"
:value="type.id"
>
{{ formatComponentTypeOption(type) }}
</option>
</select>
<p class="text-[11px] text-gray-500">
{{ node.typeComposantId ? `Sélection : ${getComponentTypeLabel(node.typeComposantId) || 'Inconnue'}` : 'Aucune famille sélectionnée' }}
</p>
</template>
<template v-else>
<div class="input input-bordered input-sm bg-base-200 flex items-center">
{{ lockedTypeDisplay }}
</div>
</template>
</div> </div>
<button <button
v-if="!isRoot" v-if="!isRoot"
@@ -202,11 +209,15 @@ const props = withDefaults(defineProps<{
componentTypes?: ModelTypeOption[] componentTypes?: ModelTypeOption[]
pieceTypes?: ModelTypeOption[] pieceTypes?: ModelTypeOption[]
isRoot?: boolean isRoot?: boolean
lockType?: boolean
lockedTypeLabel?: string
}>(), { }>(), {
depth: 0, depth: 0,
componentTypes: () => [], componentTypes: () => [],
pieceTypes: () => [], pieceTypes: () => [],
isRoot: false, isRoot: false,
lockType: false,
lockedTypeLabel: '',
}) })
const emit = defineEmits(['remove']) const emit = defineEmits(['remove'])
@@ -222,6 +233,12 @@ const containerClass = computed(() => {
}) })
const headingClass = computed(() => (props.isRoot ? 'text-sm font-semibold' : 'text-xs font-semibold')) const headingClass = computed(() => (props.isRoot ? 'text-sm font-semibold' : 'text-xs font-semibold'))
const lockedTypeDisplay = computed(() => {
if (props.lockedTypeLabel) {
return props.lockedTypeLabel
}
return getComponentTypeLabel(props.node?.typeComposantId) || 'Famille non définie'
})
const formatModelTypeOption = (type: ModelTypeOption | undefined | null) => { const formatModelTypeOption = (type: ModelTypeOption | undefined | null) => {
if (!type) return '' if (!type) return ''
@@ -274,6 +291,15 @@ const syncComponentType = (component: any) => {
if (!component) { if (!component) {
return return
} }
if (props.lockType && props.isRoot) {
if (props.lockedTypeLabel) {
component.typeComposantLabel = props.lockedTypeLabel
if (!component.name || component.name === component.typeComposantLabel) {
component.name = props.lockedTypeLabel
}
}
return
}
const id = typeof component.typeComposantId === 'string' const id = typeof component.typeComposantId === 'string'
? component.typeComposantId ? component.typeComposantId
: '' : ''
@@ -430,4 +456,18 @@ watch(
}, },
{ deep: true } { deep: true }
) )
watch(
() => [props.lockedTypeLabel, props.lockType],
() => {
if (props.lockType && props.isRoot) {
const label = props.lockedTypeLabel || lockedTypeDisplay.value
props.node.typeComposantLabel = label
if (label && (!props.node.name || props.node.name === lockedTypeDisplay.value)) {
props.node.name = label
}
}
},
{ immediate: true }
)
</script> </script>

View File

@@ -180,7 +180,12 @@
<div class="divider my-0"> <div class="divider my-0">
Structure Structure
</div> </div>
<ComponentModelStructureEditor v-model="form.data.structure" /> <ComponentModelStructureEditor
v-model="form.data.structure"
:root-type-id="form.data.typeComposantId"
:root-type-label="selectedComponentTypeLabel"
:lock-root-type="!!form.data.typeComposantId"
/>
<div class="rounded-lg border border-base-200 bg-base-200/60 p-3 text-xs text-gray-500"> <div class="rounded-lg border border-base-200 bg-base-200/60 p-3 text-xs text-gray-500">
Aperçu : {{ formatStructurePreview(form.data.structure) }} Aperçu : {{ formatStructurePreview(form.data.structure) }}
</div> </div>
@@ -292,6 +297,21 @@ const filteredModels = computed(() => {
}) })
}) })
const selectedComponentType = computed(() => {
if (!form.data.typeComposantId) {
return null
}
return componentTypes.value.find((type) => type.id === form.data.typeComposantId) || null
})
const selectedComponentTypeLabel = computed(() => {
const type = selectedComponentType.value
if (!type) {
return ''
}
return type.code ? `${type.name} (${type.code})` : type.name
})
const refreshModels = async () => { const refreshModels = async () => {
if (selectedType.value === 'all') { if (selectedType.value === 'all') {
await loadComponentModels() await loadComponentModels()