feat(models): align component model editing with type selection~
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user