feat(models): align component model editing with type selection~
This commit is contained in:
@@ -5,13 +5,15 @@
|
||||
:depth="0"
|
||||
:component-types="availableComponentTypes"
|
||||
:piece-types="availablePieceTypes"
|
||||
:lock-type="lockRootType"
|
||||
:locked-type-label="displayedRootTypeLabel"
|
||||
is-root
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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 {
|
||||
defaultStructure,
|
||||
@@ -28,11 +30,62 @@ const props = defineProps({
|
||||
type: Object,
|
||||
default: () => defaultStructure(),
|
||||
},
|
||||
rootTypeId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
rootTypeLabel: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
lockRootType: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update: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 hydrated = hydrateStructureForEditor(value)
|
||||
@@ -40,6 +93,7 @@ const syncFromProps = (value: any) => {
|
||||
localStructure.pieces = hydrated.pieces
|
||||
localStructure.subComponents = hydrated.subComponents
|
||||
lastEmitted = JSON.stringify(cloneStructure(value))
|
||||
syncRootType()
|
||||
}
|
||||
|
||||
watch(
|
||||
@@ -50,7 +104,20 @@ watch(
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
let lastEmitted = JSON.stringify(cloneStructure(props.modelValue))
|
||||
watch(
|
||||
() => [props.rootTypeId, props.rootTypeLabel, props.lockRootType],
|
||||
() => {
|
||||
syncRootType()
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(
|
||||
availableComponentTypes,
|
||||
() => {
|
||||
syncRootType()
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
localStructure,
|
||||
@@ -65,12 +132,6 @@ watch(
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const { pieceTypes, loadPieceTypes } = usePieceTypes()
|
||||
const { componentTypes, loadComponentTypes } = useComponentTypes()
|
||||
|
||||
const availablePieceTypes = computed(() => pieceTypes.value ?? [])
|
||||
const availableComponentTypes = computed(() => componentTypes.value ?? [])
|
||||
|
||||
onMounted(async () => {
|
||||
const loaders: Promise<any>[] = []
|
||||
if (!availablePieceTypes.value.length) {
|
||||
@@ -82,5 +143,6 @@ onMounted(async () => {
|
||||
if (loaders.length) {
|
||||
await Promise.allSettled(loaders)
|
||||
}
|
||||
syncRootType()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -8,25 +8,32 @@
|
||||
{{ isRoot ? 'Famille de composant racine' : 'Famille de composant' }}
|
||||
</span>
|
||||
</label>
|
||||
<select
|
||||
v-model="node.typeComposantId"
|
||||
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"
|
||||
<template v-if="!lockType">
|
||||
<select
|
||||
v-model="node.typeComposantId"
|
||||
class="select select-bordered select-sm w-full"
|
||||
@change="handleComponentTypeSelect(node)"
|
||||
>
|
||||
{{ 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>
|
||||
<option value="">
|
||||
Sélectionner une famille de composant
|
||||
</option>
|
||||
<option
|
||||
v-for="type in componentTypes"
|
||||
: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>
|
||||
<button
|
||||
v-if="!isRoot"
|
||||
@@ -202,11 +209,15 @@ const props = withDefaults(defineProps<{
|
||||
componentTypes?: ModelTypeOption[]
|
||||
pieceTypes?: ModelTypeOption[]
|
||||
isRoot?: boolean
|
||||
lockType?: boolean
|
||||
lockedTypeLabel?: string
|
||||
}>(), {
|
||||
depth: 0,
|
||||
componentTypes: () => [],
|
||||
pieceTypes: () => [],
|
||||
isRoot: false,
|
||||
lockType: false,
|
||||
lockedTypeLabel: '',
|
||||
})
|
||||
|
||||
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 lockedTypeDisplay = computed(() => {
|
||||
if (props.lockedTypeLabel) {
|
||||
return props.lockedTypeLabel
|
||||
}
|
||||
return getComponentTypeLabel(props.node?.typeComposantId) || 'Famille non définie'
|
||||
})
|
||||
|
||||
const formatModelTypeOption = (type: ModelTypeOption | undefined | null) => {
|
||||
if (!type) return ''
|
||||
@@ -274,6 +291,15 @@ const syncComponentType = (component: any) => {
|
||||
if (!component) {
|
||||
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'
|
||||
? component.typeComposantId
|
||||
: ''
|
||||
@@ -430,4 +456,18 @@ watch(
|
||||
},
|
||||
{ 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>
|
||||
|
||||
@@ -180,7 +180,12 @@
|
||||
<div class="divider my-0">
|
||||
Structure
|
||||
</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">
|
||||
Aperçu : {{ formatStructurePreview(form.data.structure) }}
|
||||
</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 () => {
|
||||
if (selectedType.value === 'all') {
|
||||
await loadComponentModels()
|
||||
|
||||
Reference in New Issue
Block a user