From 157a5c31f59a4d67145b89ea617938a9ddcb4735 Mon Sep 17 00:00:00 2001 From: NathanViaud <79544144+NathanViaud@users.noreply.github.com> Date: Fri, 13 Jan 2023 16:35:45 +0100 Subject: [PATCH] Issue #9 #69 #70 --- README.md | 3 + src/components/ContentButton.vue | 14 +- src/features/ePocFlow/ePocFlow.vue | 71 ++++++- .../ePocFlow/nodes/AddChapterNode.vue | 24 +++ src/features/ePocFlow/nodes/ChapterNode.vue | 47 +++++ src/features/ePocFlow/nodes/ePocNode.vue | 34 ++++ src/features/forms/FormPanel.vue | 1 + .../forms/components/inputs/CheckBoxInput.vue | 62 ++++++ .../forms/components/inputs/GenericInput.vue | 13 ++ .../forms/components/inputs/TextAreaInput.vue | 2 + .../forms/components/inputs/TextInput.vue | 2 +- .../inputs/question/ReponseCard.vue | 83 ++++++++ src/features/sideBar/SideBarV0.vue | 3 +- .../sideBar/components/ModelMenuV0.vue | 5 +- src/global.scss | 30 ++- src/shared/interfaces/input.interface.ts | 4 + .../interfaces/nodeElement.interface.ts | 2 +- src/shared/stores/editorStore.ts | 180 +++++++++++++++++- 18 files changed, 557 insertions(+), 23 deletions(-) create mode 100644 src/features/ePocFlow/nodes/AddChapterNode.vue create mode 100644 src/features/ePocFlow/nodes/ChapterNode.vue create mode 100644 src/features/ePocFlow/nodes/ePocNode.vue create mode 100644 src/features/forms/components/inputs/CheckBoxInput.vue create mode 100644 src/features/forms/components/inputs/question/ReponseCard.vue diff --git a/README.md b/README.md index 6a51f0ab..09658de7 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ npm install # run in dev mode +npm run electron:dev + +#electron build npm run app:build # vite dev mode diff --git a/src/components/ContentButton.vue b/src/components/ContentButton.vue index 7c05be9d..7618d846 100644 --- a/src/components/ContentButton.vue +++ b/src/components/ContentButton.vue @@ -5,6 +5,7 @@ defineProps<{ classList: object; isActive: boolean; isDraggable: boolean; + subtitle?: string; }>(); const emit = defineEmits<{ @@ -17,14 +18,23 @@ function click(event) { </script> <template> - <button + <div + class="btn btn-content" + :class="[classList, { active: isActive }, { 'draggable': isDraggable }]" + :draggable="isDraggable" + @click="click($event)" + > + <i :class="icon" /> + <span v-if="subtitle" class="subtitle">{{ subtitle }}</span> + </div> + <!-- <button class="btn btn-content" :draggable="isDraggable" :class="[classList, { active: isActive }, { 'draggable': isDraggable }]" @click="click($event)" > <i :class="icon" /> - </button> + </button> --> </template> <style scoped lang="scss"> diff --git a/src/features/ePocFlow/ePocFlow.vue b/src/features/ePocFlow/ePocFlow.vue index 9bcd6f81..75a3af02 100644 --- a/src/features/ePocFlow/ePocFlow.vue +++ b/src/features/ePocFlow/ePocFlow.vue @@ -1,22 +1,57 @@ <script setup lang="ts"> -import { VueFlow, useVueFlow } from '@vue-flow/core'; +import { VueFlow, useVueFlow, Panel, PanelPosition } from '@vue-flow/core'; import { markRaw, nextTick, watch } from 'vue'; import ContentNode from './nodes/ContentNode.vue'; import CustomConnectContent from './edges/CustomConnectContent.vue'; import { SideAction, NodeElement } from '../../shared/interfaces'; import { useEditorStore } from '../../shared/stores'; +import ChapterNode from './nodes/ChapterNode.vue'; +import ePocNode from './nodes/ePocNode.vue'; +import AddChapterNode from './nodes/AddChapterNode.vue'; -const { nodes, addNodes, addEdges, onConnect, vueFlowRef, project, findNode } = useVueFlow(); +const { nodes, addNodes, addEdges, onConnect, vueFlowRef, project, findNode, setNodes, setEdges } = useVueFlow(); onConnect((params) => addEdges([{...params, updatable: true, style: { stroke: '#384257', strokeWidth: 2.5 }}])); const editorStore = useEditorStore(); const nodeTypes = { - content: markRaw(ContentNode) + content: markRaw(ContentNode), + chapter: markRaw(ChapterNode), + epoc: markRaw(ePocNode), + add: markRaw(AddChapterNode), +}; + +const epoc = { + id: 1, + type: 'epoc', + position: { x: 0, y: 0 }, + draggable: false, +}; + +const chapter = { + id: 3, + type: 'chapter', + position: { x: 0, y: 200 }, + draggable: false, +}; + +const add = { + id: 2, + type: 'add', + position: { x: 33, y: 325 }, + draggable: false +}; + +const mainEdge = { + id: 'mainEdge', + source: '1', + target: '2', + style: { stroke: '#CDD3E0', strokeWidth: 2.5 } }; -const elements = []; + +const elements = [epoc, chapter, add, mainEdge]; //? Use this to detect interesctions(for creating screen); // onNodeDrag(({ intersections }) => { @@ -36,12 +71,12 @@ const onDrop = (event) => { }); const data = event.dataTransfer.getData('sideAction'); - - console.log(data); + const isScreen = event.dataTransfer.getData('isScreen'); const actions = JSON.parse(data); - if(actions.length > 1) { + // not sure if this is better than transfer the isScreen in all the case and use it as a boolean here + if(isScreen === 'true') { addNode(position, actions); } else { if(!addToExistingScreen(actions)) { @@ -56,7 +91,7 @@ function addNode(position, actions: SideAction[]) { let elements: NodeElement[] = []; - const id= (nodes.value.length + 1).toString(); + const id = (nodes.value.length + 1).toString(); actions.forEach((action) => { elements.push({ @@ -114,6 +149,13 @@ function addToExistingScreen(action : SideAction):boolean { } return false; } + +//Temporary function +function onDelete() { + setNodes([epoc, chapter, add]); + setEdges([mainEdge]); +} + </script> <template> @@ -129,10 +171,21 @@ function addToExistingScreen(action : SideAction):boolean { @dragover.prevent @dragenter.prevent > - <MiniMap /> + <Panel :position="PanelPosition.TopRight" class="save-restore-controls"> + <button style="background-color: #ff0000; padding: 1rem; border-radius: 8px; border: none; cursor: pointer; font-size: 1.2rem;" @click="onDelete">Delete all</button> + </Panel> <template #node-custom="{ id, data }"> <ContentNode :id="id" :data="data" /> </template> + <template #node-chapter="{ id, data }"> + <ChapterNode :id="id" :data="data" /> + </template> + <template #node-epoc="{ id, data }"> + <ePocNode :id="id" :data="data" /> + </template> + <template #node-add="{ id, data }"> + <ePocNode :id="id" :data="data" /> + </template> <template #connection-line="{ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition }"> <CustomConnectContent :source-x="sourceX" diff --git a/src/features/ePocFlow/nodes/AddChapterNode.vue b/src/features/ePocFlow/nodes/AddChapterNode.vue new file mode 100644 index 00000000..8e29ece9 --- /dev/null +++ b/src/features/ePocFlow/nodes/AddChapterNode.vue @@ -0,0 +1,24 @@ +<template> + <div class="add-chapter"> + <button class="add-btn"><i class="icon-plus"></i></button> + </div> +</template> + +<style scoped lang="scss"> +.add-btn { + border-radius: 50%; + height: 30px; + width: 30px; + background-color: var(--content); + border: 1px solid var(--border); + font-size: .7rem; + cursor: pointer; + transition: box-shadow .1s ease-in-out; + &:hover { + box-shadow: 0 1px 5px var(--shadow-outer); + } + &:active { + opacity: .7; + } +} +</style> \ No newline at end of file diff --git a/src/features/ePocFlow/nodes/ChapterNode.vue b/src/features/ePocFlow/nodes/ChapterNode.vue new file mode 100644 index 00000000..099b17e6 --- /dev/null +++ b/src/features/ePocFlow/nodes/ChapterNode.vue @@ -0,0 +1,47 @@ +<script setup lang="ts"> +import ContentButton from '../../../components/ContentButton.vue'; +import { NodeElement } from '../../../shared/interfaces'; +import { useEditorStore } from '../../../shared/stores'; +import { Handle } from '@vue-flow/core'; + +const editorStore = useEditorStore(); + +const props = defineProps<{ + id: string; + data: { + type: object; + required: true; + } +}>(); + +const element: NodeElement = { id: props.id, action: { icon: 'icon-chapitre', type: 'chapter'}, form: editorStore.getForm('chapter') }; + +function openForm(element: NodeElement) { + editorStore.openFormPanel(element); +} + +</script> + +<template> + <div> + <ContentButton + :icon="element.action.icon" + :is-active="editorStore.formPanel.openedElement ? editorStore.formPanel.openedElement.id === element.id : false" + :is-draggable="false" + :class-list="{ 'btn-content-blue' : false, 'clickable': true, 'btn-content-node': true, 'btn-content-large': true }" + subtitle="Chapitre 1" + @click="openForm(element)" + /> + </div> + <Handle type="source" position="right" /> +</template> + +<style scoped lang="scss"> +.vue-flow__handle { + width: 12px; + height: 12px; + &-right { + right: -6px; + } +} +</style> \ No newline at end of file diff --git a/src/features/ePocFlow/nodes/ePocNode.vue b/src/features/ePocFlow/nodes/ePocNode.vue new file mode 100644 index 00000000..9cc58976 --- /dev/null +++ b/src/features/ePocFlow/nodes/ePocNode.vue @@ -0,0 +1,34 @@ +<script setup lang="ts"> +import ContentButton from '../../../components/ContentButton.vue'; +import { NodeElement } from '../../../shared/interfaces'; +import { useEditorStore } from '../../../shared/stores'; + +const editorStore = useEditorStore(); +const props = defineProps<{ + id: string; + data: { + type: object; + required: true; + } +}>(); + +const element: NodeElement = { id: props.id, action: { icon: 'icon-epoc', type: 'epoc'}, form: editorStore.getForm('epoc') }; + +function openForm(element: NodeElement) { + editorStore.openFormPanel(element); +} + +</script> + +<template> + <div> + <ContentButton + :icon="element.action.icon" + :is-active="editorStore.formPanel.openedElement ? editorStore.formPanel.openedElement.id === element.id : false" + :is-draggable="false" + :class-list="{ 'btn-content-blue' : false, 'clickable': true, 'btn-content-node': true, 'btn-content-large': true }" + subtitle="ePoc" + @click="openForm(element)" + /> + </div> +</template> \ No newline at end of file diff --git a/src/features/forms/FormPanel.vue b/src/features/forms/FormPanel.vue index e3f1b1b7..5323e2df 100644 --- a/src/features/forms/FormPanel.vue +++ b/src/features/forms/FormPanel.vue @@ -38,6 +38,7 @@ function actionOnForm(action: string) { :label="input.label" :placeholder="input.placeholder" :accept="input.accept" + :question="input.question" :input-value="input.value" @input="input.value = $event" /> diff --git a/src/features/forms/components/inputs/CheckBoxInput.vue b/src/features/forms/components/inputs/CheckBoxInput.vue new file mode 100644 index 00000000..27626b32 --- /dev/null +++ b/src/features/forms/components/inputs/CheckBoxInput.vue @@ -0,0 +1,62 @@ +<script setup lang="ts"> +import { ref } from 'vue'; + +const inputValue = ref(false); +const label = ref('C\'est une bonne réponse'); + +const emit = defineEmits<{ + (e: 'input', value: string): void; +}>(); + +</script> + +<template> + <div class="checkbox"> + <input + :id="label" + class="checkbox-input" + type="checkbox" + :value="inputValue" + @input="emit('input', ($event.target as HTMLInputElement).value)" + > + <label :for="label">{{ label }}</label> + </div> +</template> + +<style scoped lang="scss"> +.checkbox { + display: flex; + margin: 0 1rem 1rem 1rem; + input[type="checkbox"] { + appearance: none; + margin: 0; + margin-right: .5rem; + font: inherit; + width: 20px; + height: 20px; + border: 1px solid var(--border); + border-radius: 4px; + transform: translateY(-0.075em); + background-color: var(--item-background); + + cursor: pointer; + + display: grid; + place-content: center; + + &:checked::before { + transform: scale(1); + } + + &::before { + content: ''; + width: 14px; + height: 14px; + transform: scale(0); + transition: .1s transform ease-in-out; + border-radius: 4px; + background-color: var(--editor-blue); + } + } +} +</style> \ No newline at end of file diff --git a/src/features/forms/components/inputs/GenericInput.vue b/src/features/forms/components/inputs/GenericInput.vue index 574a5db6..2c5f3afb 100644 --- a/src/features/forms/components/inputs/GenericInput.vue +++ b/src/features/forms/components/inputs/GenericInput.vue @@ -3,6 +3,7 @@ import TextInput from './TextInput.vue'; import TextAreaInput from './TextAreaInput.vue'; import FileInput from './FileInput.vue'; import AddInput from './AddInput.vue'; +import ResponseCard from './question/ReponseCard.vue'; defineProps<{ type: string; @@ -11,9 +12,14 @@ defineProps<{ placeholder?: string; accept?: string; icon?: string; + question?: { + isLast?: boolean; + pos: number; + } }>(); const emit = defineEmits<{ + //! This will surely be replaced by input event (e: 'update:modelValue', value: string): void; (e: 'input', value: string): void; }>(); @@ -46,4 +52,11 @@ const emit = defineEmits<{ :placeholder="placeholder" :label="label" /> + <ResponseCard + v-if="type === 'response'" + :pos="question.pos" + :is-last="question.isLast" + :input-value="inputValue" + @input="emit('input', $event)" + /> </template> \ No newline at end of file diff --git a/src/features/forms/components/inputs/TextAreaInput.vue b/src/features/forms/components/inputs/TextAreaInput.vue index d632d85a..e23676dc 100644 --- a/src/features/forms/components/inputs/TextAreaInput.vue +++ b/src/features/forms/components/inputs/TextAreaInput.vue @@ -4,6 +4,7 @@ defineProps<{ label: string; placeholder: string; inputValue: string; + insideCard?: boolean; }>(); const emit = defineEmits<{ @@ -17,6 +18,7 @@ const emit = defineEmits<{ <textarea :id="label" class="input input-textarea" + :class="{ 'input-card' : insideCard }" :placeholder="placeholder" :value="inputValue" @input="emit('input', ($event.target as HTMLInputElement).value)" diff --git a/src/features/forms/components/inputs/TextInput.vue b/src/features/forms/components/inputs/TextInput.vue index d9359cc1..89366692 100644 --- a/src/features/forms/components/inputs/TextInput.vue +++ b/src/features/forms/components/inputs/TextInput.vue @@ -13,7 +13,7 @@ const emit = defineEmits<{ </script> <template> - <label class="input-label" :for="label">{{ label }}</label> + <label v-if="label !== ''" class="input-label" :for="label">{{ label }}</label> <input :id="label" class="input" diff --git a/src/features/forms/components/inputs/question/ReponseCard.vue b/src/features/forms/components/inputs/question/ReponseCard.vue new file mode 100644 index 00000000..24d09ac5 --- /dev/null +++ b/src/features/forms/components/inputs/question/ReponseCard.vue @@ -0,0 +1,83 @@ + +<script setup lang="ts"> +import TextAreaInput from '../TextAreaInput.vue'; +import CheckBoxInput from '../CheckBoxInput.vue'; + +defineProps<{ + inputValue: string; + pos: number; + isLast: boolean; +}>(); + +const emit = defineEmits<{ + (e: 'input', value: string): void; +}>(); + +</script> +<template> + <div class="card"> + <div class="card-header"> + <h3>Réponse {{ pos }}</h3> + <div class="card-header-icon"> + <i class="icon-supprimer"></i> + <hr class="vertical-separator"> + <i v-if="!isLast" class="icon-bas"></i> + <i v-if="pos !== 1" class="icon-haut"></i> + <hr class="vertical-separator"> + <i class="icon-glisser"></i> + </div> + </div> + <div class="card-content"> + <TextAreaInput + label="" + :inside-card="true" + placeholder="Saisissez une réponse..." + :input-value="inputValue" + @input="emit('input', $event)" + /> + </div> + <CheckBoxInput /> + </div> +</template> + +<style scoped lang="scss"> +.card { + border: 1px solid var(--border); + width: 25rem; + border-radius: 4px; + margin-bottom: 1rem; + &-header { + padding: 0 .7rem; + border-bottom: 1px solid var(--border); + display: flex; + flex-direction: row; + + h3 { + font-weight: bold; + font-size: 1rem; + margin: .7rem 0; + flex-grow: 1; + } + &-icon { + display: flex; + flex-direction: row; + width: fit-content; + text-align: end; + margin: auto; + hr { + margin-right: .5rem; + } + i { + color: var(--editor-grayblue); + margin: auto; + &:not(:last-child) { + margin-right: .5rem; + } + } + } + } + &-content { + padding: .7rem; + } +} +</style> \ No newline at end of file diff --git a/src/features/sideBar/SideBarV0.vue b/src/features/sideBar/SideBarV0.vue index 1a0219a8..704f8382 100644 --- a/src/features/sideBar/SideBarV0.vue +++ b/src/features/sideBar/SideBarV0.vue @@ -1,10 +1,11 @@ <script setup lang="ts"> import ModelMenuV0 from './components/ModelMenuV0.vue'; -function startDragModel({event, sideAction}) { +function startDragModel({event, sideAction, isScreen }) { event.dataTransfer.dropEffect = 'move'; event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData('sideAction', JSON.stringify(sideAction)); + event.dataTransfer.setData('isScreen', isScreen); } </script> diff --git a/src/features/sideBar/components/ModelMenuV0.vue b/src/features/sideBar/components/ModelMenuV0.vue index 51d6e2e3..834f73cb 100644 --- a/src/features/sideBar/components/ModelMenuV0.vue +++ b/src/features/sideBar/components/ModelMenuV0.vue @@ -8,8 +8,7 @@ import SideActionButtonV0 from './SideActionButtonV0.vue'; const editorStore = useEditorStore(); const emit = defineEmits<{ - (e: 'dragStart', { event , sideAction }): void; - (e: 'dragStartQuestion', { event, sideAction }): void; + (e: 'dragStart', { event , sideAction, isScreen }): void; }>(); const rightCol: Ref<Screen[]> = ref([]); @@ -27,7 +26,7 @@ editorStore.questions.forEach((sideAction: SideAction, index) => { }); function dragStart(event, screen) { - emit('dragStart',{ event: event, sideAction: screen.actions}); + emit('dragStart',{ event: event, sideAction: screen.actions, isScreen: true }); } const displayScreen = ref(false); diff --git a/src/global.scss b/src/global.scss index 462d285e..77070ad2 100644 --- a/src/global.scss +++ b/src/global.scss @@ -68,10 +68,24 @@ body, box-shadow: 0 1px 8px 0 var(--shadow); transition: all 0.2s ease-in-out; cursor: inherit; + display: flex; + flex-direction: column; i { margin: auto; font-size: 1.5rem; } + &-large { + width: calc(60px + 2rem); + height: calc(60px + 2rem); + margin: 0; + span { + margin-top: 0; + margin: .5rem auto auto auto; + } + i { + margin-bottom: 0; + } + } &-node { &:hover { box-shadow: 0 2px 5px 0 var(--shadow-outer); @@ -223,8 +237,19 @@ hr { font-size: 1.1rem; color: var(--text); outline: none; + + &::placeholder { + color: var(--editor-grayblue); + } - width: 24rem; + &:not(.input-card) { + width: 24rem; + } + + &-card { + width: calc(100% - 1.4rem); + margin: 0; + } &-file { font-size: 1.1rem; @@ -238,6 +263,9 @@ hr { &-textarea { resize: none; min-height: 10rem; + &.input-card { + min-height: 5rem; + } } &:focus { outline: 1px solid var(--editor-blue); diff --git a/src/shared/interfaces/input.interface.ts b/src/shared/interfaces/input.interface.ts index cff9f399..a5cd64fd 100644 --- a/src/shared/interfaces/input.interface.ts +++ b/src/shared/interfaces/input.interface.ts @@ -6,4 +6,8 @@ export interface Input { placeholder?: string; accept?: string; icon?: string; + question?: { + pos: number; + isLast?: boolean; + } } \ No newline at end of file diff --git a/src/shared/interfaces/nodeElement.interface.ts b/src/shared/interfaces/nodeElement.interface.ts index 6e4e4092..4b9a9316 100644 --- a/src/shared/interfaces/nodeElement.interface.ts +++ b/src/shared/interfaces/nodeElement.interface.ts @@ -4,5 +4,5 @@ export interface NodeElement { id: string; action: SideAction form: Form; - parentId: string; + parentId?: string; } \ No newline at end of file diff --git a/src/shared/stores/editorStore.ts b/src/shared/stores/editorStore.ts index d2bdda93..7ffcb87e 100644 --- a/src/shared/stores/editorStore.ts +++ b/src/shared/stores/editorStore.ts @@ -2,7 +2,6 @@ import { defineStore } from 'pinia'; import { fetchRecentProjects } from '../services'; import { SideAction, Screen, ePocProject, NodeElement, Form } from '../interfaces'; import { toRaw } from 'vue'; -import { NodeChange, applyNodeChanges, useVueFlow } from '@vue-flow/core'; interface EditorState { recentProjects: ePocProject[]; @@ -86,10 +85,14 @@ export const useEditorStore = defineStore('editor', { return structuredClone(toRaw(this.forms.find(form => form.type === type))); }, deleteCurrentElement() { - const { nodes } = useVueFlow(); - const node = nodes[this.formPanel.openedElement.id - 1]; - const changes: NodeChange[] = [{ id: node.id, type: 'remove' }]; - applyNodeChanges(changes, nodes.value); + // const { applyNodeChanges } = useVueFlow(); + // applyNodeChanges([ + // { + // id: this.formPanel.openedElement.parentId, + // type: 'remove', + // } + // ]); + console.log(this.formPanel.openedElement.parentId); } } }); @@ -267,5 +270,172 @@ const forms: Form[] = [ action: 'delete' } ] + }, + { + type: 'qcm', + name: 'QCM', + icon: 'icon-qcm', + inputs: [ + { + type: 'text', + label: 'Titre', + value: '', + placeholder: 'Saisissez...' + }, + { + type: 'textarea', + label: 'Enoncé', + value: '', + placeholder: 'Saisissez...' + }, + { + type: 'textarea', + label: '', + value: '', + placeholder: 'Saisissez l\'intitulé de la question...' + }, + { + type: 'response', + label: '', + value: '', + placeholder: 'Saisissez une réponse...', + question: { + pos: 1, + } + }, + { + type: 'response', + label: '', + value: '', + placeholder: 'Saisissez une réponse...', + question: { + pos: 2, + isLast: true, + } + }, + { + type: 'add', + label: '', + value: '', + placeholder: 'Ajouter une autre réponse' + }, + { + type: 'textarea', + label: '', + value: '', + placeholder: 'Saisissez une explication...' + }, + ], + buttons: [ + { + label: 'Supprimer', + icon: 'icon-supprimer', + action: 'delete' + }, + { + label: 'Copier le lien', + icon: 'icon-copie', + action: 'copy' + }, + ] + }, + { + type: 'chapter', + name: 'Chapitre', + icon: 'icon-chapitre', + inputs: [ + { + type: 'text', + label: 'Titre', + value: '', + placeholder: 'Saisissez...' + }, + { + type: 'response', + label: '', + value: '', + placeholder: 'Saisissez un objectif...', + question: { + pos: 1, + isLast: false + } + }, + { + type: 'response', + label: '', + value: '', + placeholder: 'Saisissez un objectif...', + question: { + pos: 2, + isLast: true + } + }, + { + type: 'add', + label: '', + value: '', + placeholder: 'Ajouter un autre objectif' + } + ], + buttons: [ + { + label: 'Supprimer', + icon: 'icon-supprimer', + action: 'delete' + }, + { + label: 'Copier le lien', + icon: 'icon-copie', + action: 'copy' + }, + ] + }, + { + type: 'epoc', + name: 'Paramètre de l\'ePoc', + icon: 'icon-epoc', + inputs :[ + { + type: 'text', + label: 'Titre', + value: '', + placeholder: 'Saisissez...' + }, + { + type: 'file', + label: 'Image de couverture', + value: '', + accept: 'image/*' + }, + { + type: 'add', + label: 'Vignette', + value: '', + placeholder: 'Ajouter une vignette' + }, + { + type: 'add', + label: 'Teaser', + value: '', + placeholder: 'Ajouter un teaser' + }, + { + type: 'textarea', + label: 'Présentation', + value: '', + placeholder: 'Saisissez une présentation de l\'ePoc...' + }, + { + type: 'text', + label: 'ID de l\'ePoc', + value: 'id234567890', + + }, + { + type: 'text', + label: 'Version', + value: '1.0', + } + ] } ]; \ No newline at end of file -- GitLab