From 8706000af9a4a6bccfa1997e8b5e381016986a52 Mon Sep 17 00:00:00 2001
From: VIAUD Nathan <nathan.viaud@inria.fr>
Date: Mon, 5 May 2025 10:19:54 +0200
Subject: [PATCH] feat: disable certificate option

---
 i18n/en/translation.json                      |  2 ++
 i18n/fr/translation.json                      |  2 ++
 package-lock.json                             |  8 ++---
 package.json                                  |  2 +-
 .../forms/components/GenericField.vue         | 30 +++++++++-------
 .../forms/components/inputs/GenericInput.vue  |  3 +-
 src/shared/classes/epoc-v1.ts                 |  3 ++
 src/shared/data/forms/nodeForm.data.ts        | 15 +++++++-
 src/shared/interfaces/form/input.interface.ts |  1 +
 src/shared/services/graph.service.ts          |  1 +
 src/shared/services/graph/node.service.ts     | 35 +++++++++++++------
 11 files changed, 72 insertions(+), 30 deletions(-)

diff --git a/i18n/en/translation.json b/i18n/en/translation.json
index 0826bad7..48fb544f 100644
--- a/i18n/en/translation.json
+++ b/i18n/en/translation.json
@@ -286,6 +286,8 @@
                 },
                 "biography": "Short author biography"
             },
+            "certificateOptions": "Certificate options",
+            "certificateDisabled": "Disable certificate",
             "certificateBadge": "Number of badges required for certification",
             "certificateScore": "Score required for certification",
             "certificateScoreHint": "Not considered if the number of badges required for certification is greater than 0",
diff --git a/i18n/fr/translation.json b/i18n/fr/translation.json
index b1724101..b8feeb75 100644
--- a/i18n/fr/translation.json
+++ b/i18n/fr/translation.json
@@ -286,6 +286,8 @@
                 },
                 "biography": "Courte biographie de l'auteur"
             },
+            "certificateOptions": "Options de l'attestation",
+            "certificateDisabled": "Désactiver l'attestation",
             "certificateBadge": "Nombre de badge pour obtenir l'attestation",
             "certificateScore": "Score pour obtenir l'attestation",
             "certificateScoreHint": "N'est pas pris en compte si le nombre de badge pour obtenir l'attestation est supérieur à 0",
diff --git a/package-lock.json b/package-lock.json
index b6dcfcb3..01fce500 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,7 @@
             "license": "CeCILL-B",
             "dependencies": {
                 "@electron/notarize": "^1.2.2",
-                "@epoc/epoc-types": "^2.0.0-beta.20",
+                "@epoc/epoc-types": "^2.0.0-beta.21",
                 "@meforma/vue-toaster": "^1.3.0",
                 "@tinymce/tinymce-vue": "^6.1.0",
                 "@vue-flow/core": "^1.41.2",
@@ -765,9 +765,9 @@
             }
         },
         "node_modules/@epoc/epoc-types": {
-            "version": "2.0.0-beta.20",
-            "resolved": "https://registry.npmjs.org/@epoc/epoc-types/-/epoc-types-2.0.0-beta.20.tgz",
-            "integrity": "sha512-6t5Q2o2IMbUNdFQdI/01ZhJSj+GDkrkikfnpfJHAQHjprwhug9sRyEQBby1Og2KW4FAC4tV8+GsB7xSp94d4yw==",
+            "version": "2.0.0-beta.21",
+            "resolved": "https://registry.npmjs.org/@epoc/epoc-types/-/epoc-types-2.0.0-beta.21.tgz",
+            "integrity": "sha512-+DvcWmA6YlI69ctkz3i+AZjaK+IAGPL517P5M5hgdk3sXn7jmhRBbGew9RCyv69nuGpWR1AfZv3zU9L6oe8avA==",
             "license": "CeCILL-B"
         },
         "node_modules/@esbuild/aix-ppc64": {
diff --git a/package.json b/package.json
index 6787a76b..d2f65841 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,7 @@
     },
     "dependencies": {
         "@electron/notarize": "^1.2.2",
-        "@epoc/epoc-types": "^2.0.0-beta.20",
+        "@epoc/epoc-types": "^2.0.0-beta.21",
         "@meforma/vue-toaster": "^1.3.0",
         "@tinymce/tinymce-vue": "^6.1.0",
         "@vue-flow/core": "^1.41.2",
diff --git a/src/features/forms/components/GenericField.vue b/src/features/forms/components/GenericField.vue
index fd16181c..f348d8de 100644
--- a/src/features/forms/components/GenericField.vue
+++ b/src/features/forms/components/GenericField.vue
@@ -11,6 +11,7 @@ import {
     RepeatMoveEvent,
     RepeatRemoveEvent,
 } from '@/src/shared/interfaces/form/repeatInput.interface';
+import { getEpocNodeData } from '@/src/shared/services';
 
 const editorStore = useEditorStore();
 
@@ -25,9 +26,10 @@ const currentNode = editorStore.getCurrentGraphNode;
 
 const getInputValue = (input: Input) => {
     if (editorStore.openedBadgeId) return getBadgeInputValue(input);
-    const formValues = editorStore.openedNodeId
-        ? currentNode.data.elements.find((e: NodeElement) => e.id === editorStore.openedElementId)?.formValues ?? {}
-        : currentNode.data.formValues;
+    const formValues =
+        editorStore.openedNodeId ?
+            (currentNode.data.elements.find((e: NodeElement) => e.id === editorStore.openedElementId)?.formValues ?? {})
+        :   currentNode.data.formValues;
 
     return formValues[input.id] ?? input.value;
 };
@@ -40,9 +42,10 @@ const getBadgeInputValue = (input: Input) => {
 function onInput(value: string, id: string) {
     if (editorStore.openedBadgeId) return onBadgeInput(value, id);
 
-    const element = editorStore.openedNodeId
-        ? currentNode.data.elements.find((e: NodeElement) => e.id === editorStore.openedElementId)
-        : currentNode.data;
+    const element =
+        editorStore.openedNodeId ?
+            currentNode.data.elements.find((e: NodeElement) => e.id === editorStore.openedElementId)
+        :   currentNode.data;
 
     element.formValues[id] = value;
 }
@@ -59,9 +62,10 @@ function onBadgeInput(value: string, id: string) {
 function onRepeatInput(value: RepeatInputEvent, id: string) {
     const state = getCurrentState(true);
 
-    const element = editorStore.openedNodeId
-        ? currentNode.data.elements.find((e: NodeElement) => e.id === editorStore.openedElementId)
-        : null;
+    const element =
+        editorStore.openedNodeId ?
+            currentNode.data.elements.find((e: NodeElement) => e.id === editorStore.openedElementId)
+        :   null;
 
     switch (value.type) {
         case 'add': {
@@ -133,9 +137,10 @@ function handleChangeRepeatInput(element: NodeElement | null, value: RepeatChang
 // Repeat Input end
 
 function onCheck(value: boolean, id: string) {
-    const element = editorStore.openedNodeId
-        ? currentNode.data.elements.find((e: NodeElement) => e.id === editorStore.openedElementId)
-        : currentNode.data;
+    const element =
+        editorStore.openedNodeId ?
+            currentNode.data.elements.find((e: NodeElement) => e.id === editorStore.openedElementId)
+        :   currentNode.data;
 
     element.formValues[id] = value;
 }
@@ -156,6 +161,7 @@ function onSaveGivenState(state: string) {
         :input="input"
         :field-index="fieldIndex"
         :input-value="getInputValue(input)"
+        :hidden="input.hide ? getEpocNodeData()[input.hide] : false"
         @input="onInput($event, input.id)"
         @check="onCheck($event, input.id)"
         @repeat-input="onRepeatInput($event, input.id)"
diff --git a/src/features/forms/components/inputs/GenericInput.vue b/src/features/forms/components/inputs/GenericInput.vue
index efedb486..d7c25c08 100644
--- a/src/features/forms/components/inputs/GenericInput.vue
+++ b/src/features/forms/components/inputs/GenericInput.vue
@@ -25,6 +25,7 @@ const props = defineProps<{
     fieldIndex?: number;
     collapsible?: boolean;
     collapsibleLabel?: string;
+    hidden?: boolean;
 }>();
 
 const emit = defineEmits<{
@@ -63,7 +64,7 @@ function handleCollapse() {
 </script>
 
 <template>
-    <div class="input-group">
+    <div v-if="!hidden" class="input-group">
         <div
             v-if="input.label && showLabel(input.type)"
             class="input-label"
diff --git a/src/shared/classes/epoc-v1.ts b/src/shared/classes/epoc-v1.ts
index 67eb7075..dbf18367 100644
--- a/src/shared/classes/epoc-v1.ts
+++ b/src/shared/classes/epoc-v1.ts
@@ -15,6 +15,7 @@ export class EpocV1 implements Epoc {
     thumbnail: string;
     version: string;
     edition: string;
+    certificateDisabled?: boolean;
     certificateScore: number;
     download: string;
     lastModif: string;
@@ -42,6 +43,7 @@ export class EpocV1 implements Epoc {
         teaser: string,
         thumbnail: string,
         edition: string,
+        certificateDisabled: boolean,
         certificateScore: number,
         certificateBadgeCount: number,
         authors: Author[],
@@ -61,6 +63,7 @@ export class EpocV1 implements Epoc {
         this.teaser = teaser;
         this.thumbnail = thumbnail;
         this.edition = edition;
+        this.certificateDisabled = certificateDisabled;
         this.certificateScore = certificateScore;
         this.certificateBadgeCount = certificateBadgeCount;
         this.authors = authors;
diff --git a/src/shared/data/forms/nodeForm.data.ts b/src/shared/data/forms/nodeForm.data.ts
index 5bd405cd..38d5849b 100644
--- a/src/shared/data/forms/nodeForm.data.ts
+++ b/src/shared/data/forms/nodeForm.data.ts
@@ -278,13 +278,14 @@ export const epocForm: ComputedRef<Form> = computed(() => ({
             ],
         },
         {
-            name: i18n.global.t('settings.title'),
+            name: i18n.global.t('forms.node.certificateOptions'),
             inputs: [
                 {
                     id: 'certificateBadgeCount',
                     type: 'score',
                     label: i18n.global.t('forms.node.certificateBadge'),
                     value: 1,
+                    hide: 'certificateDisabled',
                 },
                 {
                     id: 'certificateScore',
@@ -292,7 +293,19 @@ export const epocForm: ComputedRef<Form> = computed(() => ({
                     label: i18n.global.t('forms.node.certificateScore'),
                     value: 10,
                     hint: i18n.global.t('forms.node.certificateScoreHint'),
+                    hide: 'certificateDisabled',
+                },
+                {
+                    id: 'certificateDisabled',
+                    type: 'checkbox',
+                    label: i18n.global.t('forms.node.certificateDisabled'),
+                    value: false,
                 },
+            ],
+        },
+        {
+            name: i18n.global.t('settings.title'),
+            inputs: [
                 {
                     id: 'chapterDuration',
                     type: 'score',
diff --git a/src/shared/interfaces/form/input.interface.ts b/src/shared/interfaces/form/input.interface.ts
index a1880035..16812900 100644
--- a/src/shared/interfaces/form/input.interface.ts
+++ b/src/shared/interfaces/form/input.interface.ts
@@ -29,4 +29,5 @@ export interface Input {
     hint?: string;
     collapsible?: boolean;
     collapsibleLabel?: string;
+    hide?: string;
 }
diff --git a/src/shared/services/graph.service.ts b/src/shared/services/graph.service.ts
index 3f316bb1..2e03a20f 100644
--- a/src/shared/services/graph.service.ts
+++ b/src/shared/services/graph.service.ts
@@ -85,6 +85,7 @@ function createContentJSON(): EpocV1 {
         ePocValues.teaser || '',
         ePocValues.thumbnail || '',
         ePocValues.edition || new Date().getFullYear(),
+        ePocValues.certificateDisabled || false,
         ePocValues.certificateScore || 10,
         ePocValues.certificateBadgeCount || 1,
         ePocValues.authors || {},
diff --git a/src/shared/services/graph/node.service.ts b/src/shared/services/graph/node.service.ts
index 0bc47112..4baadc5d 100644
--- a/src/shared/services/graph/node.service.ts
+++ b/src/shared/services/graph/node.service.ts
@@ -28,11 +28,15 @@ export function setEpocNodeData(epoc: EpocV1) {
     epocNode.data.formValues.version = epoc.version;
     epocNode.data.formValues.certificateScore = epoc.certificateScore;
     epocNode.data.formValues.authors = Object.values(epoc.authors);
-    epocNode.data.formValues.chapterParameter = epoc.parameters?.chapterParameter;
     epocNode.data.formValues.plugins = epoc.plugins;
     epocNode.data.formValues.badges = epoc.badges;
 }
 
+export function getEpocNodeData() {
+    const epocNode = findNode('1');
+    return epocNode.data.formValues;
+}
+
 export function addPage(position: { x: number; y: number }, actions: SideAction[], noAlign?: boolean): string {
     const id = generateId();
 
@@ -41,7 +45,10 @@ export function addPage(position: { x: number; y: number }, actions: SideAction[
     const isQuestion = questionTypes.includes(type);
     const isCondition = type === 'condition';
 
-    const finalType = isQuestion ? 'question' : isCondition ? 'condition' : 'element';
+    const finalType =
+        isQuestion ? 'question'
+        : isCondition ? 'condition'
+        : 'element';
 
     const newPageNode: Node = {
         id,
@@ -297,7 +304,6 @@ export function duplicatePage(pageId?: string): void {
     closeFormPanel();
 }
 
-
 export function transformActivityToPage(): void {
     const pageNode = findNode(editorStore.openedElementId);
     if (pageNode.data.elements.length > 1) return;
@@ -355,11 +361,12 @@ export function confirmDelete(): void {
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 export function isFormButtonDisabled(isDisabledFunction: (node: any) => boolean): boolean {
     const isChild = Boolean(editorStore.openedNodeId);
-    const nodeData = isChild
-        ? findNode(editorStore.openedNodeId).data.elements.find(
-            (e: NodeElement) => e.id === editorStore.openedElementId,
-        )
-        : findNode(editorStore.openedElementId).data;
+    const nodeData =
+        isChild ?
+            findNode(editorStore.openedNodeId).data.elements.find(
+                (e: NodeElement) => e.id === editorStore.openedElementId,
+            )
+        :   findNode(editorStore.openedElementId).data;
     return isDisabledFunction(nodeData);
 }
 
@@ -388,8 +395,14 @@ export function swapEdges(node1: Node, node2: Node, edges1: Edge[], edges2: Edge
     }
 
     for (const edge of [...edges1, ...edges2]) {
-        const source = edge.source === node1.id ? node2.id : edge.source === node2.id ? node1.id : edge.source;
-        const target = edge.target === node1.id ? node2.id : edge.target === node2.id ? node1.id : edge.target;
+        const source =
+            edge.source === node1.id ? node2.id
+            : edge.source === node2.id ? node1.id
+            : edge.source;
+        const target =
+            edge.target === node1.id ? node2.id
+            : edge.target === node2.id ? node1.id
+            : edge.target;
         createEdge(source, target);
     }
 }
@@ -423,7 +436,7 @@ export function swapNodeWithPrevious(nodeId: string): void {
     const node = findNode(nodeId);
     const previousNode = graphService.getPreviousNode(node);
 
-    if(!previousNode || previousNode.type === 'chapter') return;
+    if (!previousNode || previousNode.type === 'chapter') return;
 
     const tempPosition = node.position;
     node.position = previousNode.position;
-- 
GitLab