From: Vincent Vanwaelscappel Date: Wed, 26 Jul 2023 18:25:17 +0000 (+0200) Subject: wip #6180 @4 X-Git-Url: http://git.cubedesigners.com/?a=commitdiff_plain;h=2a2842844a16bfd1563c12a30f712775ec680bd7;p=fluidbook-toolbox.git wip #6180 @4 --- diff --git a/app/Elearning/QuizCompiler.php b/app/Elearning/QuizCompiler.php new file mode 100644 index 000000000..ecf067ba5 --- /dev/null +++ b/app/Elearning/QuizCompiler.php @@ -0,0 +1,106 @@ +quiz = Quiz::withoutGlobalScopes()->find($quiz); + $this->compilePath = protected_path('quiz/compile/' . $this->quiz->id); + $this->dest = $dest; + $this->forceScorm = $forceScorm; + } + + public function handle() + { + $this->copyFilesFromResources(); + // Add data related to the current quiz in the "to compile" files + $this->writeStyles(); + $this->writeData($data); + // Run the compiler + $this->runWebpack(); + + $vdir = new VirtualDirectory($this->dest); + + $html = view('quizv2.index', ['quiz' => $this->quiz, 'data' => $data])->render(); + + $vdir->file_put_contents('index.html', $html); + + + if ($this->forceScorm || $this->getAttribute('scorm')) { + $scormVersion = $this->quiz->getAttribute('scorm_version') ?: Version::SCORM_1_2; + $manifest = new Manifest($this->quiz->getAttribute('title'), $scormVersion, $this->quiz->getAttribute('client') ?: backpack_user()->getCompanyNameAttribute(), $this->quiz->getAttribute('project') ?: 'Quiz'); + $vdir->file_put_contents('imsmanifest.xml', $manifest); + } + $vdir->sync(true); + } + + protected function writeStyles() + { + + $this->writeSass(); + } + + protected function writeData(&$data) + { + $data = $this->quiz->getData(); + $json = json_encode($data, JSON_THROW_ON_ERROR); + $dataSource = $this->compilePath . '/js/quiz.data.js'; + file_put_contents($dataSource, str_replace('this.data = data;', 'this.data = ' . $json . ';', file_get_contents($dataSource))); + } + + protected function writeSass() + { + $sass = ''; + foreach ($this->sassVariables as $key => $value) { + $sass .= '$' . $key . ': ' . $value . "\n"; + } + file_put_contents($this->compilePath . '/style/002-item-variables.sass', $sass); + } + + + protected function copyFilesFromResources() + { + // Copy base files from resources + $compile = new VirtualDirectory($this->compilePath); + $compile->copyDirectory(resource_path('quizv2/js'), 'js'); + $compile->copyDirectory(resource_path('quizv2/style'), 'style'); + $mix = 'const mix = require("laravel-mix"); +mix.setPublicPath("dist").js("js/quiz.js", "js") + .sass("style/style.sass", "css").options({processCssUrls: false}).version(); +'; + $compile->file_put_contents('webpack.mix.js', $mix); + $compile->sync(true); + } + + protected function runWebpack() + { + $cli = new Npx(); + $cli->cd($this->compilePath); + $cli->setModule('cross-env process.env.local=1 mix --production'); + $cli->execute(); + + if (!file_exists($this->compilePath . '/dist/quiz.js')) { + $cli->dd(); + } + } +} diff --git a/app/Models/Quiz.php b/app/Models/Quiz.php index c518fe94d..40939d4e6 100644 --- a/app/Models/Quiz.php +++ b/app/Models/Quiz.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Elearning\QuizCompiler; use App\Fields\SCORMVersion; use App\Http\Controllers\Admin\Operations\ChangeownerOperation; use App\Http\Controllers\Admin\Operations\Quiz\DownloadOperation; @@ -21,9 +22,11 @@ use Cubist\Backpack\Magic\Fields\SelectFromArray; use Cubist\Backpack\Magic\Fields\Text; use Cubist\Backpack\Magic\Fields\Textarea; use Cubist\Scorm\Manifest; +use Cubist\Util\CommandLine\Npx; use Cubist\Util\Files\VirtualDirectory; use Spatie\Image\Manipulations; use Spatie\MediaLibrary\MediaCollections\Models\Media; +use Cubist\Scorm\Version; // __('!! e-Learning') class Quiz extends ToolboxModel @@ -238,8 +241,6 @@ class Quiz extends ToolboxModel ]); - - $this->addField('section_qa', FormBigSection::class, __('Question & réponses')); $this->addField([ 'name' => 'questions', @@ -249,8 +250,8 @@ class Quiz extends ToolboxModel 'add_label' => __('Nouvelle question'), ]); - $this->addField('section_advanced',FormSuperSection::class,__('Paramètres avancés')); - $this->addField('section_advanced_',FormSection::class); + $this->addField('section_advanced', FormSuperSection::class, __('Paramètres avancés')); + $this->addField('section_advanced_', FormSection::class); $this->addField(['name' => 'logattempts', 'label' => __('Activer l\'enregistrement des tentatives'), 'hint' => __('Les tentatives seront enregistrées sur le serveur de la Toolbox'), @@ -307,43 +308,8 @@ class Quiz extends ToolboxModel */ public function compile($dest, $forceScorm = false, $user = null) { - $wdir = resource_path('quiz'); - $vdir = new VirtualDirectory($dest); - - - $data = $this->getData(); - $json = json_encode($data, JSON_THROW_ON_ERROR); - $vdir->file_put_contents('data.js', 'var DATA=' . $json . ';'); - $vdir->copy($wdir . '/index.html', 'index.html'); - $vdir->copyDirectory($wdir . '/dist/css', 'css'); - $vdir->copyDirectory($wdir . '/dist/js', 'js'); - $vdir->copyDirectory($wdir . '/fonts', 'fonts'); - $vdir->copyDirectory($wdir . '/assets', 'assets'); - - // Copy assets - $assets = ['banner' => 'banner.jpg', 'logo' => 'logo.png']; - foreach ($assets as $asset => $filename) { - $coll = $this->getAttribute($asset); - if ($coll) { - $media = $this->getFirstMedia($coll); - - if ($media) { - $conversionName = $asset . '_compile'; - - $path = $media->getPath($conversionName); - if (file_exists($path)) { - $vdir->copy($path, 'assets/' . $filename); - } - } - } - } - - if ($forceScorm || $this->getAttribute('scorm')) { - $scormVersion = $this->getAttribute('scorm_version') ?: '1.2'; - $manifest = new Manifest($this->getAttribute('title'), $scormVersion, $this->getAttribute('client') ?: backpack_user()->getCompanyNameAttribute(), $this->getAttribute('project') ?: 'Quiz'); - $vdir->file_put_contents('imsmanifest.xml', $manifest); - } - $vdir->sync(true); + $job = new QuizCompiler($this->id, $dest, $forceScorm); + $job->handle(); } public function getData() diff --git a/package.json b/package.json index 2c3d43708..4370ad872 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "scripts": { - "all": "npm run elearningmedia;npm run elearningpackage;npm run quiz;npm run linkeditor", + "all": "npm run elearningmedia;npm run elearningpackage;npm run quiz;npm run quizv2;npm run linkeditor", "elearningmedia-dev": "cross-env process.env.section=elearningmedia mix", "elearningmedia-watch": "cross-env process.env.section=elearningmedia mix watch", "elearningmedia": "cross-env process.env.section=elearningmedia mix --production", diff --git a/resources/quizv2/js/quiz.data.js b/resources/quizv2/js/quiz.data.js new file mode 100644 index 000000000..89bf278e7 --- /dev/null +++ b/resources/quizv2/js/quiz.data.js @@ -0,0 +1,7 @@ +function QuizData(quiz) { + this.quiz = quiz; + // Data will be populated at compile time + this.data = data; +} + +module.exports = QuizData; diff --git a/resources/quizv2/js/quiz.js b/resources/quizv2/js/quiz.js new file mode 100644 index 000000000..0e8cca3dd --- /dev/null +++ b/resources/quizv2/js/quiz.js @@ -0,0 +1,166 @@ +import $ from "jquery"; +import gsap from "gsap"; +import {CubeSCORM} from '/application/resources/scorm/scorm'; + +import QuizData from "./quiz.data"; + +window.cubeSCORM = new CubeSCORM(); +window.$ = window.jQuery = $; + +function Quiz() { +} + +Quiz.prototype = { + init: function () { + this.data = new QuizData(this); + }, +} + +(function (global) { + $(function () { + window.quiz = new Quiz(); + window.quiz.init(); + }); +})(typeof window === 'undefined' ? this : window); + + +(function (global) { + 'use strict'; + if (!global.console) { + global.console = {}; + } + var con = global.console; + var prop, method; + var dummy = function () { + }; + var properties = ['memory']; + var methods = ('assert,clear,count,debug,dir,dirxml,error,exception,group,' + 'groupCollapsed,groupEnd,info,log,markTimeline,profile,profiles,profileEnd,' + 'show,table,time,timeEnd,timeline,timelineEnd,timeStamp,trace,warn').split(','); + while (prop = properties.pop()) if (!con[prop]) con[prop] = {}; + while (method = methods.pop()) if (typeof con[method] !== 'function') con[method] = dummy; + // Using `this` for web workers & supports Browserify / Webpack. +})(typeof window === 'undefined' ? this : window); + +/*! sprintf-js v1.1.2 | Copyright (c) 2007-present, Alexandru Mărășteanu | BSD-3-Clause */ +!function () { + "use strict"; + var g = { + not_string: /[^s]/, + not_bool: /[^t]/, + not_type: /[^T]/, + not_primitive: /[^v]/, + number: /[diefg]/, + numeric_arg: /[bcdiefguxX]/, + json: /[j]/, + not_json: /[^j]/, + text: /^[^\x25]+/, + modulo: /^\x25{2}/, + placeholder: /^\x25(?:([1-9]\d*)\$|\(([^)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijostTuvxX])/, + key: /^([a-z_][a-z_\d]*)/i, + key_access: /^\.([a-z_][a-z_\d]*)/i, + index_access: /^\[(\d+)\]/, + sign: /^[+-]/ + }; + + function y(e) { + return function (e, t) { + var r, n, i, s, a, o, p, c, l, u = 1, f = e.length, d = ""; + for (n = 0; n < f; n++) if ("string" == typeof e[n]) d += e[n]; else if ("object" == typeof e[n]) { + if ((s = e[n]).keys) for (r = t[u], i = 0; i < s.keys.length; i++) { + if (null == r) throw new Error(y('[sprintf] Cannot access property "%s" of undefined value "%s"', s.keys[i], s.keys[i - 1])); + r = r[s.keys[i]] + } else r = s.param_no ? t[s.param_no] : t[u++]; + if (g.not_type.test(s.type) && g.not_primitive.test(s.type) && r instanceof Function && (r = r()), g.numeric_arg.test(s.type) && "number" != typeof r && isNaN(r)) throw new TypeError(y("[sprintf] expecting number but found %T", r)); + switch (g.number.test(s.type) && (c = 0 <= r), s.type) { + case"b": + r = parseInt(r, 10).toString(2); + break; + case"c": + r = String.fromCharCode(parseInt(r, 10)); + break; + case"d": + case"i": + r = parseInt(r, 10); + break; + case"j": + r = JSON.stringify(r, null, s.width ? parseInt(s.width) : 0); + break; + case"e": + r = s.precision ? parseFloat(r).toExponential(s.precision) : parseFloat(r).toExponential(); + break; + case"f": + r = s.precision ? parseFloat(r).toFixed(s.precision) : parseFloat(r); + break; + case"g": + r = s.precision ? String(Number(r.toPrecision(s.precision))) : parseFloat(r); + break; + case"o": + r = (parseInt(r, 10) >>> 0).toString(8); + break; + case"s": + r = String(r), r = s.precision ? r.substring(0, s.precision) : r; + break; + case"t": + r = String(!!r), r = s.precision ? r.substring(0, s.precision) : r; + break; + case"T": + r = Object.prototype.toString.call(r).slice(8, -1).toLowerCase(), r = s.precision ? r.substring(0, s.precision) : r; + break; + case"u": + r = parseInt(r, 10) >>> 0; + break; + case"v": + r = r.valueOf(), r = s.precision ? r.substring(0, s.precision) : r; + break; + case"x": + r = (parseInt(r, 10) >>> 0).toString(16); + break; + case"X": + r = (parseInt(r, 10) >>> 0).toString(16).toUpperCase() + } + g.json.test(s.type) ? d += r : (!g.number.test(s.type) || c && !s.sign ? l = "" : (l = c ? "+" : "-", r = r.toString().replace(g.sign, "")), o = s.pad_char ? "0" === s.pad_char ? "0" : s.pad_char.charAt(1) : " ", p = s.width - (l + r).length, a = s.width && 0 < p ? o.repeat(p) : "", d += s.align ? l + r + a : "0" === o ? l + a + r : a + l + r) + } + return d + }(function (e) { + if (p[e]) return p[e]; + var t, r = e, n = [], i = 0; + for (; r;) { + if (null !== (t = g.text.exec(r))) n.push(t[0]); else if (null !== (t = g.modulo.exec(r))) n.push("%"); else { + if (null === (t = g.placeholder.exec(r))) throw new SyntaxError("[sprintf] unexpected placeholder"); + if (t[2]) { + i |= 1; + var s = [], a = t[2], o = []; + if (null === (o = g.key.exec(a))) throw new SyntaxError("[sprintf] failed to parse named argument key"); + for (s.push(o[1]); "" !== (a = a.substring(o[0].length));) if (null !== (o = g.key_access.exec(a))) s.push(o[1]); else { + if (null === (o = g.index_access.exec(a))) throw new SyntaxError("[sprintf] failed to parse named argument key"); + s.push(o[1]) + } + t[2] = s + } else i |= 2; + if (3 === i) throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported"); + n.push({ + placeholder: t[0], + param_no: t[1], + keys: t[2], + sign: t[3], + pad_char: t[4], + align: t[5], + width: t[6], + precision: t[7], + type: t[8] + }) + } + r = r.substring(t[0].length) + } + return p[e] = n + }(e), arguments) + } + + function e(e, t) { + return y.apply(null, [e].concat(t || [])) + } + + var p = Object.create(null); + "undefined" != typeof exports && (exports.sprintf = y, exports.vsprintf = e), "undefined" != typeof window && (window.sprintf = y, window.vsprintf = e, "function" == typeof define && define.amd && define(function () { + return {sprintf: y, vsprintf: e} + })) +}(); diff --git a/resources/quizv2/style/001-global-variables.sass b/resources/quizv2/style/001-global-variables.sass new file mode 100644 index 000000000..e69de29bb diff --git a/resources/quizv2/style/002-item-variables.sass b/resources/quizv2/style/002-item-variables.sass new file mode 100644 index 000000000..9e5f8fc13 --- /dev/null +++ b/resources/quizv2/style/002-item-variables.sass @@ -0,0 +1 @@ +// Do not edit this file, quiz variables will be populated at compile time diff --git a/resources/quizv2/style/003-reset.sass b/resources/quizv2/style/003-reset.sass new file mode 100644 index 000000000..45897f9ce --- /dev/null +++ b/resources/quizv2/style/003-reset.sass @@ -0,0 +1,51 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) */ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video, main + margin: 0 + padding: 0 + border: 0 + font-size: 100% + font: inherit + vertical-align: baseline + box-sizing: border-box + + +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section, main + display: block + + +body + line-height: 1 + + +ol, ul + list-style: none + + +blockquote, q + quotes: none + +blockquote:before, blockquote:after, +q:before, q:after + content: '' + content: none + +table + border-collapse: collapse + border-spacing: 0 diff --git a/resources/quizv2/style/style.sass b/resources/quizv2/style/style.sass new file mode 100644 index 000000000..87b33b202 --- /dev/null +++ b/resources/quizv2/style/style.sass @@ -0,0 +1,3 @@ +@import 001-global-variables +@import 002-item-variables +@import 003-reset diff --git a/resources/views/quizv2/icons.blade.php b/resources/views/quizv2/icons.blade.php new file mode 100644 index 000000000..2f8b0fe15 --- /dev/null +++ b/resources/views/quizv2/icons.blade.php @@ -0,0 +1,47 @@ +{{-- + +Downloaded from https://toolbox.fluidbook.com/tool-sprite/9/download +Edit here : https://toolbox.fluidbook.com/tool-sprite/9/edit + +--}} + diff --git a/resources/views/quizv2/index.blade.php b/resources/views/quizv2/index.blade.php new file mode 100644 index 000000000..1c0f20431 --- /dev/null +++ b/resources/views/quizv2/index.blade.php @@ -0,0 +1,19 @@ + + + + + + + + + + + + +@include("quizv2.icons") +
+
+ + + + diff --git a/webpack.mix.js b/webpack.mix.js index 9b8411d80..5fb3f8cae 100644 --- a/webpack.mix.js +++ b/webpack.mix.js @@ -3,3 +3,6 @@ const mix = require('laravel-mix'); if (process.env.section) { require(`${__dirname}/resources/${process.env.section}/webpack.mix.js`); } +if(process.env.local){ + require(`${process.env.custom}/webpack.mix.js`); +}