From 40d1ade537f6a2850e58d2706de75acd46c3909e Mon Sep 17 00:00:00 2001 From: Benoit Rospars <benoit.rospars@inria.fr> Date: Mon, 13 Mar 2023 13:21:10 +0100 Subject: [PATCH] Issue #109 : ePoc v1 metadata and chapter --- electron/components/file.js | 8 + electron/components/ipc.js | 6 +- electron/electron.js | 2 +- src/features/forms/FormPanel.vue | 2 +- .../forms/components/GenericField.vue | 4 +- src/shared/classes/epoc-v1.ts | 64 +++++++ src/shared/data/form.data.ts | 62 +++++++ src/shared/services/editor.service.ts | 2 - src/shared/services/project.service.ts | 161 +++++++----------- 9 files changed, 202 insertions(+), 109 deletions(-) create mode 100644 src/shared/classes/epoc-v1.ts diff --git a/electron/components/file.js b/electron/components/file.js index 8df05420..071e2511 100644 --- a/electron/components/file.js +++ b/electron/components/file.js @@ -184,6 +184,13 @@ const writeProjectData = async function (workdir, data) { fs.writeFileSync(path.join(workdir, 'project.json'), data); }; +/** + * Write the epoc data to the content.json file in workdir + */ +const writeEpocData = async function (workdir, data) { + fs.writeFileSync(path.join(workdir, 'content.json'), data); +}; + /** * Copy the imported file to the workdir * @param {string} workdir @@ -238,6 +245,7 @@ module.exports = { saveAsEpocProject, exportProject, writeProjectData, + writeEpocData, readProjectData, copyFileToWorkdir, cleanAllWorkdir diff --git a/electron/components/ipc.js b/electron/components/ipc.js index ff03ff9e..a169d761 100644 --- a/electron/components/ipc.js +++ b/electron/components/ipc.js @@ -2,7 +2,7 @@ const { ipcMain } = require('electron'); const path = require('path'); const store = require('./store'); const { runPreview } = require('./preview'); -const { getRecentFiles, pickEpocProject, openEpocProject, newEpocProject, saveEpocProject, exportProject, writeProjectData, readProjectData, copyFileToWorkdir } = require('./file'); +const { getRecentFiles, pickEpocProject, openEpocProject, newEpocProject, saveEpocProject, exportProject, writeProjectData, writeEpocData, readProjectData, copyFileToWorkdir } = require('./file'); /** * Setup ipc listeners that are received from renderer process @@ -66,6 +66,10 @@ const setupIpcListener = function (targetWindow) { await writeProjectData(store.state.currentProject.workdir, data); }); + ipcMain.on('writeEpocData', async (event, data) => { + await writeEpocData(store.state.currentProject.workdir, data); + }); + ipcMain.on('importFile', async (event, filepath) => { sendToFrontend(targetWindow, 'fileImported', await copyFileToWorkdir(store.state.currentProject.workdir, filepath)); }); diff --git a/electron/electron.js b/electron/electron.js index 842e200f..e8245e97 100644 --- a/electron/electron.js +++ b/electron/electron.js @@ -60,7 +60,7 @@ app.whenReady().then(() => { }; session.defaultSession.webRequest.onBeforeRequest(filter, (details, callback) => { - if (details.url.indexOf('assets/') !== -1) { + if (mainWindow.webContents.id === details.webContents.id && details.url.indexOf('assets/') !== -1) { const filepath = details.url.split('assets/')[1]; return callback({ redirectURL: `assets://assets/${filepath}` }); diff --git a/src/features/forms/FormPanel.vue b/src/features/forms/FormPanel.vue index a7d5892a..a90fce00 100644 --- a/src/features/forms/FormPanel.vue +++ b/src/features/forms/FormPanel.vue @@ -11,7 +11,7 @@ function actionOnForm(action: string) { switch (action) { case 'delete': editorStore.deleteElement(editorStore.openedNodeId); - projectService.saveProjectData(); + projectService.writeProjectData(); break; } } diff --git a/src/features/forms/components/GenericField.vue b/src/features/forms/components/GenericField.vue index 5dacb231..67f40316 100644 --- a/src/features/forms/components/GenericField.vue +++ b/src/features/forms/components/GenericField.vue @@ -20,7 +20,7 @@ const node = editorStore.openedParentId ? findNode(editorStore.openedParentId) : function onInput(value, id) { node.data.formValues[id] = value; - projectService.saveProjectData(); + projectService.writeProjectData(); } function onRepeatInput(value, id) { @@ -52,7 +52,7 @@ function onRepeatInput(value, id) { node.data.formValues[id][value.index][value.id] = value.value; } } - projectService.saveProjectData(); + projectService.writeProjectData(); } </script> diff --git a/src/shared/classes/epoc-v1.ts b/src/shared/classes/epoc-v1.ts new file mode 100644 index 00000000..dcdd623a --- /dev/null +++ b/src/shared/classes/epoc-v1.ts @@ -0,0 +1,64 @@ +import { Assessment, Chapter, Content, Epoc, html, Parameters, uid } from '@epoc/epoc-specs/dist/v1'; +import { Question } from '@epoc/epoc-specs/dist/v1/question'; +import { Author } from '@epoc/epoc-specs/dist/v1/author'; + +export class EpocV1 implements Epoc { + id: string; + title: string; + image: string; + objectives: string[]; + summary: html; + teaser: string; + thumbnail: string; + version: string; + certificateScore: number; + download: string; + lastModif: string; + chaptersCount: number; + assessmentsCount: number; + authors:Author[]; + parameters: Parameters; + chapters: Record<uid, Chapter>; + contents: Record<uid, Content>; + questions: Record<uid, Question>; + + constructor( + id: string, title: string, image: string, objectives: string[], summary: html, teaser: string, + thumbnail: string, version: string, certificateScore: number, authors: Author[], + chapterParameter: string + ) { + this.id = id; + this.title = title; + this.image = image; + this.objectives = objectives; + this.summary = summary; + this.teaser = teaser; + this.thumbnail = thumbnail; + this.version = version; + this.certificateScore = certificateScore; + this.authors = authors; + this.parameters = { + chapterParameter + }; + this.chapters = {}; + this.contents = {}; + this.questions = {}; + this.chaptersCount = 0; + this.assessmentsCount = 0; + this.download = ''; + } + + addChapter(id: uid, chapter: Chapter) { + this.chapters[id] = chapter; + } + + addContent(id, chapterId, content) { + this.questions[id] = content; + this.chapters[chapterId].contents.push(id); + } + + addDuestion(id, contentId, question) { + this.questions[id] = question; + (this.contents[contentId] as Assessment).questions.push(id); + } +} \ No newline at end of file diff --git a/src/shared/data/form.data.ts b/src/shared/data/form.data.ts index 14d76d74..b2989228 100644 --- a/src/shared/data/form.data.ts +++ b/src/shared/data/form.data.ts @@ -191,6 +191,68 @@ export const epocForm: Form = { } ] }, + { + name: 'Auteurs', + inputs: [ + { + id: 'authors', + label: 'Auteur', + type: 'repeat', + value: '', + inputs: [ + { + id: 'name', + type: 'text', + label: 'Nom', + placeholder: 'Préom nom', + value: '' + }, + { + id: 'image', + type: 'file', + label: 'Image', + placeholder: 'Ajouter une image', + value: '', + accept: '.png,.jpg,.jpeg,.gif,.bmp,.svg,.webp' + }, + { + id: 'title', + type: 'text', + label: 'Titre', + placeholder: 'Chercheuse à l\'Inria', + value: '', + }, + { + id: 'description', + type: 'ql-editor', + label: '', + placeholder: 'Description', + value: '' + } + ] + } + ] + }, + { + name: 'Objectifs', + inputs: [ + { + id: 'objectives', + label: 'Objectif', + type: 'repeat', + value: '', + inputs: [ + { + id: '', + type: 'textarea', + label: '', + placeholder: 'Saisissez un objectif ...', + value: '' + } + ] + } + ] + }, { name: 'Paramètres :', inputs : [ diff --git a/src/shared/services/editor.service.ts b/src/shared/services/editor.service.ts index 4ef4bc51..cfe519bf 100644 --- a/src/shared/services/editor.service.ts +++ b/src/shared/services/editor.service.ts @@ -2,7 +2,6 @@ import { ApiInterface } from '@/src/shared/interfaces/api.interface'; import { router } from '@/src/router'; import { useEditorStore, useProjectStore } from '@/src/shared/stores'; import { ePocProject } from '@/src/shared/interfaces'; -import { projectService } from './project.service'; import { createToaster } from '@meforma/vue-toaster'; const toaster = createToaster({ @@ -139,7 +138,6 @@ function openEpocProject(project): void { function saveEpocProject(): void { editorStore.saving = true; api.send('saveEpocProject'); - projectService.createContentJSON(); } function runPreview(): void { diff --git a/src/shared/services/project.service.ts b/src/shared/services/project.service.ts index bf92b07d..907c0f31 100644 --- a/src/shared/services/project.service.ts +++ b/src/shared/services/project.service.ts @@ -1,16 +1,19 @@ import { ApiInterface } from '@/src/shared/interfaces/api.interface'; import { useProjectStore } from '../stores'; -import { getConnectedEdges, useVueFlow } from '@vue-flow/core'; +import { getConnectedEdges, GraphNode, useVueFlow } from '@vue-flow/core'; +import { EpocV1 } from '@/src/shared/classes/epoc-v1'; declare const api: ApiInterface; const { toObject, onNodesChange, nodes, edges, findNode } = useVueFlow({ id: 'main' }); const projectStore = useProjectStore(); -function saveProjectData(): void { +function writeProjectData(): void { debounceFunction(500, () => { const data = JSON.stringify(toObject()); + const content = JSON.stringify(createContentJSON()); api.send('writeProjectData', data); + api.send('writeEpocData', content); }); } @@ -32,116 +35,70 @@ const debounceFunction = function (delay, cb) { }; onNodesChange(() => { - saveProjectData(); + writeProjectData(); }); -function createContentJSON() { - // const content = {}; - // content['chapters'] = {}; - // content['contents'] = {}; - // const startingNode = nodes.value.filter((node) => { return node.type === 'epoc' || node.type === 'chapter';}); - // startingNode.forEach((node) => { - // if(node.type === 'epoc') { - // const inputs = node.data.form.fields[0].inputs; - // content['lastModif'] = new Date().toISOString(); - // content['title'] = inputs[0].value; - // content['image']= inputs[2].value; - // content['thumbnail'] = inputs[3].value; - // content['teaser'] = inputs[4].value; - // content['summary'] = inputs[5].value; - // content['id'] = inputs[6].value; - // content['edition'] = inputs[7].value; - // content['parameters'] = { - // 'chapterParameters': inputs[1].value - // }; - - // //TODO - // content['author'] = {}; - // content['certificateScore'] = 0; - // content['plugins'] = []; - // } - // if(node.type === 'chapter') { - // const chapter = {}; - // const titleInputs = node.data.form.fields[0].inputs; - // chapter['title'] = titleInputs[0].value; - // const objectiveInputs = node.data.form.fields[1].inputs; - // const objectives = []; - // for(const objective of objectiveInputs) { - // objectives.push(objective.inputs[0].value); - // } - // chapter['objective'] = objectives; - - // chapter['contents'] = []; - - // let nextEdge = getNextEdge(node); - // while (nextEdge) { - // const nextNode = findNode(nextEdge.target); - // chapter['contents'].push(nextNode.data.contentId); - - // const type = identifyTemplate(nextNode); - // const element = {}; - // if(type === 'video') { - // const titleInput = nextNode.data.form.fields[0].inputs[0].value; - // const summaryInput = nextNode.data.elements[0].form.fields[0].inputs[1].value; - - // element['type'] = 'video'; - // element['title'] = titleInput; - // element['summary'] = summaryInput; - - // //TODO - // element['source'] = ''; - // element['subtitles'] = []; - // element['transcript'] = ''; - // element['poster'] = ''; - - // } else if(type === 'text') { - // const titleInput = nextNode.data.form.fields[0].inputs[0].value; - // const htmlInput = nextNode.data.elements[0].form.fields[0].inputs[0].value; - - // element['type'] = 'html'; - // element['title'] = titleInput; - // element['html'] = htmlInput; - - // } else { - // const question = {}; - // const element = nextNode.data.elements[0]; - // question['type'] = element.action.type; - - // const fields = element.form.fields; - - // question['score'] = fields[0].inputs[1].value; - // question['statement'] = fields[0].inputs[0].value; - // question[''] - - // console.log('question:',question); - // console.log('node:', nextNode); - // } - // content['contents'][nextNode.data.contentId] = element; - - // nextEdge = getNextEdge(nextNode); - // } - // content['chapters'][node.data.contentId] = chapter; - // } - // }); - // console.log('content:', JSON.stringify(content)); +function createContentJSON() : EpocV1 { + + const epocNode = nodes.value.find((node) => { return node.type === 'epoc'; }); + const chapterNodes = nodes.value.filter((node) => { return node.type === 'chapter'; }); + + const ePocValues = epocNode.data.formValues; + + const epoc = new EpocV1( + ePocValues.id || 'E000XX', + ePocValues.title || 'Title', + ePocValues.image || '', + ePocValues.objectives || [], + ePocValues.summary || '', + ePocValues.teaser || '', + ePocValues.thumbnail || '', + ePocValues.version || new Date().getFullYear(), + ePocValues.certificateScore || 0, + ePocValues.authors || {}, + ePocValues.chapterParameter + ); + + console.log(epoc); + + chapterNodes.forEach(chapter => { + console.log(chapter.data); + const chapterValues = chapter.data.formValues; + epoc.addChapter(chapter.data.contentId, { + title: chapterValues.title || '', + image: chapterValues.image || '', + objectives: chapterValues.objectives || [], + contents: [] + }); + let nextNode = getNextNode(chapter); + while (nextNode) { + console.log(nextNode.data); + const chapterValues = chapter.data.formValues; + epoc.addChapter(chapter.data.contentId, { + title: chapterValues.title || '', + image: chapterValues.image || '', + objectives: chapterValues.objectives || [], + contents: [] + }); + nextNode = getNextNode(nextNode); + } + }); + + + return epoc; } -function getNextEdge(node) { +function getNextNode(node) { const edge = getConnectedEdges([node], edges.value).filter((edge) => edge.source === node.id)[0]; - return edge ? { target: edge.target, source: edge.source } : null; + return edge ? getNodeById(edge.target) : null; } -function identifyTemplate(node) { - if(node.data.type !== 'template'){ - return null; - } else { - return node.data.elements[0].action.type; - } +function getNodeById(id) : GraphNode { + return nodes.value.find((node) => { return node.id === id; }); } export const projectService = { importFile, - saveProjectData, - createContentJSON, + writeProjectData }; \ No newline at end of file -- GitLab