From 1a0f020f6920fc36493b1caa9aca0f59def3e0f9 Mon Sep 17 00:00:00 2001 From: Pierre Jarriges <pierre.jarriges@tutanota.com> Date: Fri, 19 Nov 2021 16:44:58 +0100 Subject: [PATCH] ignore css git --- .gitignore | 2 +- Makefile | 4 +- .../src/components/create-article-form.js | 2 +- .../src/components/update-article-form.js | 46 +- public/assets/images/screen_mentalo_app.png | Bin 0 -> 57323 bytes public/assets/translations/en.json | 4 +- public/assets/translations/fr.json | 3 +- public/education/education.js | 881 +--- public/games/games.js | 4505 +---------------- public/main.js | 848 +--- .../software-development.js | 986 +--- public/style/style.css | 987 ---- 12 files changed, 25 insertions(+), 8243 deletions(-) create mode 100644 public/assets/images/screen_mentalo_app.png delete mode 100644 public/style/style.css diff --git a/.gitignore b/.gitignore index 37e1e05..7243b96 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ bundle.js *.map *.css.map -src/**/*.css +*.css node_modules target .env diff --git a/Makefile b/Makefile index 637aa2c..61dd6ea 100644 --- a/Makefile +++ b/Makefile @@ -23,10 +23,10 @@ bash-api: docker exec -it kuadrado_server bash build-website: - npm run --prefix ./website build + npm run --prefix ./website build-prod build-website-debug: - npm run --prefix ./website build debug + npm run --prefix ./website build build-admin: npm run --prefix ./admin-frontend build diff --git a/admin-frontend/src/components/create-article-form.js b/admin-frontend/src/components/create-article-form.js index e7651c0..494687d 100644 --- a/admin-frontend/src/components/create-article-form.js +++ b/admin-frontend/src/components/create-article-form.js @@ -193,7 +193,7 @@ class CreateArticleForm { maxWidth: "800px", }, contents: [ - { tag: "button", contents: "RESET", onclick: this.reset.bind(this) }, + !this.params.data && { tag: "button", contents: "RESET", onclick: this.reset.bind(this) }, { tag: "h2", contents: article.title }, { tag: "h4", contents: article.subtitle }, { tag: "p", contents: article.body.replace(/\n/g, "<br>") }, diff --git a/admin-frontend/src/components/update-article-form.js b/admin-frontend/src/components/update-article-form.js index 67994d3..967c4d0 100644 --- a/admin-frontend/src/components/update-article-form.js +++ b/admin-frontend/src/components/update-article-form.js @@ -9,7 +9,12 @@ class UpdateArticleForm { this.state = { search_article_title: "", article_to_update: {}, - } + }; + + this.articles_list = new ArticleList({ + on_select_article: this.handle_select_article.bind(this), + on_delete_result: this.handle_delete_article.bind(this) + }); } reset() { @@ -39,38 +44,6 @@ class UpdateArticleForm { .catch(err => alert(err)) } - refresh_search_result() { - obj2htm.subRender( - this.render_search_result(), - document.getElementById("update-article-form-search-result"), - { mode: "replace" }, - ); - } - - render_search_result() { - const { search_result } = this.state; - return { - tag: "div", - id: "update-article-form-search-result", - style_rules: { - display: "flex", - gap: "10px", - alignItems: "center" - }, - contents: search_result.title ? [ - { tag: "strong", contents: search_result.title }, - { - tag: "button", contents: "SELECT", - onclick: this.handle_select_result.bind(this) - }, - { - tag: "button", contents: "DELETE", - onclick: this.handle_delete_article.bind(this) - } - ] : [] - } - } - refresh_update_form() { obj2htm.subRender( this.render_update_form(), @@ -88,7 +61,7 @@ class UpdateArticleForm { data: this.state.article_to_update, on_article_sent: () => { this.reset(); - this.refresh_search_result(); + this.articles_list.refresh_list(); } }).render()] : [] @@ -110,10 +83,7 @@ class UpdateArticleForm { maxWidth: "800px", }, contents: [ - new ArticleList({ - on_select_article: this.handle_select_article.bind(this), - on_delete_result: this.handle_delete_article.bind(this) - }).render(), + this.articles_list.render(), { tag: "hr", style_rules: { width: "100%" } }, this.render_update_form(), ] diff --git a/public/assets/images/screen_mentalo_app.png b/public/assets/images/screen_mentalo_app.png new file mode 100644 index 0000000000000000000000000000000000000000..5220d25f95dba8e4d2c5fdb3f45a4cd9fcff4931 GIT binary patch literal 57323 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�Kn-~AwR>N2 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 C7|!wl literal 0 HcmV?d00001 diff --git a/public/assets/translations/en.json b/public/assets/translations/en.json index 7dbe040..ad1ff38 100644 --- a/public/assets/translations/en.json +++ b/public/assets/translations/en.json @@ -29,5 +29,7 @@ "edu-learn-mentalo": "Create a game in a few sessions with the Mentalo application. Handle logical, narrative and artistic concepts with maximum simplicity.", "Me contacter": "Contact me", "Pour s'inscrire ou en savoir plus": "To register or find out more", - "software-page-intro": "R&D, experimental projects, software tools for game development or for the web." + "software-page-intro": "R&D, experimental projects, software tools for game development or for the web.", + "Programme XXXX": "{%date%} program", + "Rien de prévu pour le moment": "Nothing planned for the moment" } \ No newline at end of file diff --git a/public/assets/translations/fr.json b/public/assets/translations/fr.json index fdcaa31..b2aea9c 100644 --- a/public/assets/translations/fr.json +++ b/public/assets/translations/fr.json @@ -12,5 +12,6 @@ "edu-learn-computer": "Perdu avec votre ordinateur ou votre smartphone, les logiciels, internet ? Prenez en main les fondamentaux apprenez pas à pas à utiliser sereinement la technologie.", "edu-learn-gnu": "<b>Passez le cap du libre ! </b><br/>Apprenez à installer Linux, faites vos premiers pas avec les logiciels libres et acquérez une autonomie suffisante pour une utilisation basique.", "edu-learn-mentalo": "Créez un jeu en quelques séances avec l'application Mentalo. Manipulez des concepts logiques, narratifs et artistiques avec le maximum de simplicité.", - "software-page-intro": "R&D, projets expérimentaux, outillage logiciel pour le développement de jeu ou pour le web." + "software-page-intro": "R&D, projets expérimentaux, outillage logiciel pour le développement de jeu ou pour le web.", + "Programme XXXX": "Programme {%date%}" } \ No newline at end of file diff --git a/public/education/education.js b/public/education/education.js index 8207c7e..5669100 100644 --- a/public/education/education.js +++ b/public/education/education.js @@ -1,879 +1,2 @@ -(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ -module.exports = { - build: { - protected_dirs: ["assets", "style", "views", "standard"], - default_meta_keys: ["title", "description", "image", "open_graph", "json_ld"], - }, -}; - -},{}],2:[function(require,module,exports){ -module.exports = { - images_url: `/assets/images`, - data_url: `/assets/data`, - translations_url: "/assets/translations" -}; - -},{}],3:[function(require,module,exports){ -"use strict"; -/** - * A tiny library to handle text translation. - */ - -/** - * init parameter object type - * @typedef TranslatorParam - * @property {String[]} supported_languages - DEFAULT: ["en"] - An array of ISO 639 lowercase language codes - * @property {String} locale ISO 639 lowercase language code - DEFAULT: "en" - * - * @property {Boolean} use_url_locale_fragment - DEFAULT: true - - * If true, the 1st fragment of the window.location url will be parsed as a language code - * Example: - * window.location - * > https://example.com/en/some-page... - * then the /en/ part of the url will be taken in priority and locale will be set to "en". - * window.location - * > https://example.com/some-page... - * No locale fragment will be found so locale will not be set from url fragment. - * window.location - * > https://example.com/some-page/en - * Doesn't work, locale fragment must be the first framgment - * - * @property {String} local_storage_key - DEFAULT: "translator-prefered-language" - * The key used to saved the current locale into local storage - * - * @property {String} translations_url - REQUIRED - the url of the directory containing the static json files for translations - * Translations files are expected to be named with their corresponding locale code. - * Example: - * if supported_languages is set to ["en", "fr", "it"] - * and translations_url is set to "https://example.com/translations/"" - * Then the expected translations files are - * https://example.com/translations/en.json - * https://example.com/translations/fr.json - * https://example.com/translations/it.json - * The json resources must simple key value maps, value being the translated text. - */ - -module.exports = { - locale: "en", // ISO 639 lowercase language code - supported_languages: ["en"], - translations: {}, - translations_url: "", - use_url_locale_fragment: true, - local_storage_key: "translator-prefered-language", - - /** - * Initialize the lib with params - * @param {TranslatorParam} params - * @returns {Promise} - */ - init(params) { - Object.entries(params).forEach(k_v => { - const [key, value] = k_v; - if ([ - "supported_languages", - "use_url_locale_fragment", - "local_storage_key", - "translations_url" - ].includes(key)) { - this[key] = value; - } - }); - - this.translations_url = this.format_translations_url(this.translations_url); - this.supported_languages = this.format_supported_languages(this.supported_languages); - - return new Promise((resolve, reject) => { - const loc = - (() => {// Locale from url priority 1 - if (this.use_url_locale_fragment) { - const first_url_fragment = window.location.pathname.substring(1).split("/")[0]; - const fragment_is_locale = this.supported_languages.includes(first_url_fragment); - return fragment_is_locale ? first_url_fragment : ""; - } else { - return ""; - } - })() - || localStorage.getItem(this.local_storage_key) // Locale from storage priority 2 - || (() => { // locale from navigator priority 3 - const navigator_locale = navigator.language.split("-")[0].toLocaleLowerCase(); - return this.supported_languages.includes(navigator_locale) - ? navigator_locale - : this.supported_languages[0] // Default if navigator locale is not supported - })(); - - fetch(`${this.translations_url}${loc}.json`) - .then(response => response.json()) - .then(response => { - this.locale = loc; - this.translations = response; - resolve(); - }) - .catch(err => { - this.locale = "en"; - reject(err); - }); - }); - }, - - /** - * Return a lowercase string without dash. - * If given locale is en-EN, then "en" will be returned. - * @param {String} locale - * @returns A lowercase string - */ - format_locale(locale) { - return locale.split("-")[0].toLowerCase(); - }, - - /** - * Appends a slash at the end of the given string if missing, and returns the url. - * @param {String} url - * @returns {String} - */ - format_translations_url(url) { - if (url.charAt(url.length - 1) !== "/") { - url += "/" - } - return url; - }, - - /** - * Return the array of language codes formatted as lowsercase language code. - * if ["en-EN", "it-IT"]is given, ["en", "it"] will b returned. - * @param {String[]} languages_codes - * @returns {String[]} - */ - format_supported_languages(languages_codes) { - return languages_codes.map(lc => this.format_locale(lc)); - }, - - /** - * Fetches a new set of translations in case the wanted language changes - * @param {String} locale A lowercase language code - * @returns {Promise} - */ - update_translations(locale) { - locale = this.format_locale(locale); - return new Promise((resolve, reject) => { - fetch(`${this.translations_url}${locale}.json`) - .then(response => response.json()) - .then(response => { - this.translations = response; - this.locale = locale; - - localStorage.setItem(this.local_storage_key, locale); - - const split_path = window.location.pathname.substring(1).split("/"); - const first_url_fragment = split_path[0]; - const fragment_is_locale = this.supported_languages.includes(first_url_fragment); - - if (fragment_is_locale) { - split_path.splice(0, 1, locale); - const updated_path = split_path.join("/"); - window.history.replaceState(null, "", "/" + updated_path); - } - resolve(); - }) - .catch(err => { - reject(err); - }); - }); - }, - - /** - * Tries to get the translation of the source string, or return the string as it. - * @param {String} text The source text to translate - * @param {Object} params Some dynamic element to insert in the text - * Params can be used if the translated text provide placeholder like {%some_word%} - * Example: - * translator.trad("Some trad key", {some_word: "a dynamic parameter"}) - * -> translation for "Some trad key": "A translated text with {%some_word%}" - * -> will be return as : "A translated text with a dynamic parameter" - * @returns {String} - */ - trad: function (text, params = {}) { - text = this.translations[text] || text; - - Object.keys(params).forEach(k => { - text = text.replace(`{%${k}%}`, params[k]); - }); - - return text; - } -}; -},{}],4:[function(require,module,exports){ -"use strict"; - -module.exports = { - register_key: "objectToHtmlRender", - - /** - * Register "this" as a window scope accessible variable named by the given key, or default. - * @param {String} key - */ - register(key) { - const register_key = key || this.register_key; - window[register_key] = this; - }, - - /** - * This must be called before any other method in order to initialize the lib. - * It provides the root of the rendering cycle as a Javascript object. - * @param {Object} renderCycleRoot A JS component with a render method. - */ - setRenderCycleRoot(renderCycleRoot) { - this.renderCycleRoot = renderCycleRoot; - }, - - event_name: "objtohtml-render-cycle", - - /** - * Set a custom event name for the event that is trigger on render cycle. - * Default is "objtohtml-render-cycle". - * @param {String} evt_name - */ - setEventName(evt_name) { - this.event_name = evt_name; - }, - - /** - * This is the core agorithm that read an javascript Object and convert it into an HTML element. - * @param {Object} obj The object representing the html element must be formatted like: - * { - * tag: String // The name of the html tag, Any valid html tag should work. div, section, br, ul, li... - * xmlns: String // This can replace the tag key if the element is an element with a namespace URI, for example an <svg> tag. - * See https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS for more information - * style_rules: Object // a object providing css attributes. The attributes names must be in JS syntax, - * like maxHeight: "500px", backgrouncColor: "#ff2d56", margin: 0, etc. - * contents: Array or String // This reprensents the contents that will be nested in the created html element. - * <div>{contents}</div> - * The contents can be an array of other objects reprenting elements (with tag, contents, etc) - * or it can be a simple string. - * // All other attributes will be parsed as html attributes. They can be anything like onclick, href, onchange, title... - * // or they can also define custom html5 attributes, like data, my_custom_attr or anything. - * } - * @returns {HTMLElement} The output html node. - */ - objectToHtml(obj) { - if (!obj) return document.createElement("span"); // in case of invalid input, don't block the whole process. - const objectToHtml = this.objectToHtml.bind(this); - const { tag, xmlns } = obj; - const node = xmlns !== undefined ? document.createElementNS(xmlns, tag) : document.createElement(tag); - const excludeKeys = ["tag", "contents", "style_rules", "state", "xmlns"]; - - Object.keys(obj) - .filter(attr => !excludeKeys.includes(attr)) - .forEach(attr => { - switch (attr) { - case "class": - node.classList.add(...obj[attr].split(" ").filter(s => s !== "")); - break; - case "on_render": - if (!obj.id) { - node.id = `${btoa(JSON.stringify(obj).slice(0, 127)).replace(/\=/g, '')}${window.performance.now()}`; - } - if (typeof obj.on_render !== "function") { - console.error("The on_render attribute must be a function") - } else { - this.attach_on_render_callback(node, obj.on_render); - } - break; - default: - if (xmlns !== undefined) { - node.setAttributeNS(null, attr, obj[attr]) - } else { - node[attr] = obj[attr]; - } - } - }); - if (obj.contents && typeof obj.contents === "string") { - node.innerHTML = obj.contents; - } else { - obj.contents && - obj.contents.length > 0 && - obj.contents.forEach(el => { - switch (typeof el) { - case "string": - node.innerHTML = el; - break; - case "object": - if (xmlns !== undefined) { - el = Object.assign(el, { xmlns }) - } - node.appendChild(objectToHtml(el)); - break; - } - }); - } - - if (obj.style_rules) { - Object.keys(obj.style_rules).forEach(rule => { - node.style[rule] = obj.style_rules[rule]; - }); - } - - return node; - }, - - on_render_callbacks: [], - - /** - * This is called if the on_render attribute of a component is set. - * @param {HTMLElement} node The created html element - * @param {Function} callback The callback defined in the js component to render - */ - attach_on_render_callback(node, callback) { - const callback_handler = { - callback: e => { - if (e.detail.outputNode === node || e.detail.outputNode.querySelector(`#${node.id}`)) { - callback(node); - const handler_index = this.on_render_callbacks.indexOf((this.on_render_callbacks.find(cb => cb.node === node))); - if (handler_index === -1) { - console.warn("A callback was registered for node with id " + node.id + " but callbacck handler is undefined.") - } else { - window.removeEventListener(this.event_name, this.on_render_callbacks[handler_index].callback) - this.on_render_callbacks.splice(handler_index, 1); - } - } - }, - node, - }; - - const len = this.on_render_callbacks.push(callback_handler); - window.addEventListener(this.event_name, this.on_render_callbacks[len - 1].callback); - }, - - /** - * If a main element exists in the html document, it will be used as rendering root. - * If not, it will be created and inserted. - */ - renderCycle: function () { - const main_elmt = document.getElementsByTagName("main")[0] || (function () { - const created_main = document.createElement("main"); - document.body.appendChild(created_main); - return created_main; - })(); - - this.subRender(this.renderCycleRoot.render(), main_elmt, { mode: "replace" }); - }, - - /** - * This method behaves like the renderCycle() method, but rather that starting the rendering cycle from the root component, - * it can start from any component of the tree. The root component must be given as the first argument, the second argument be - * be a valid html element in the dom and will be used as the insertion target. - * @param {Object} object An object providing a render method returning an object representation of the html to insert - * @param {HTMLElement} htmlNode The htlm element to update - * @param {Object} options can be used the define the insertion mode, default is set to "append" and can be set to "override", - * "insert-before" (must be defined along with an insertIndex key (integer)), - * "adjacent" (must be defined along with an insertLocation key (String)), "replace" or "remove". - * In case of "remove", the first argument "object" is not used and can be set to null, undefined or {}... - */ - subRender(object, htmlNode, options = { mode: "append" }) { - let outputNode = null; - - const get_insert = () => { - outputNode = this.objectToHtml(object); - return outputNode; - }; - - switch (options.mode) { - case "append": - htmlNode.appendChild(get_insert()); - break; - case "override": - htmlNode.innerHTML = ""; - htmlNode.appendChild(get_insert()); - break; - case "insert-before": - htmlNode.insertBefore(get_insert(), htmlNode.childNodes[options.insertIndex]); - break; - case "adjacent": - /** - * options.insertLocation must be one of: - * - * afterbegin - * afterend - * beforebegin - * beforeend - */ - htmlNode.insertAdjacentHTML(options.insertLocation, get_insert()); - break; - case "replace": - htmlNode.parentNode.replaceChild(get_insert(), htmlNode); - break; - case "remove": - htmlNode.remove(); - break; - } - const evt_name = this.event_name; - const event = new CustomEvent(evt_name, { - detail: { - inputObject: object, - outputNode, - insertOptions: options, - targetNode: htmlNode, - } - }); - - window.dispatchEvent(event); - }, -}; -},{}],5:[function(require,module,exports){ -"use strict"; -const translator = require("ks-cheap-translator"); -const { translations_url } = require("../../constants"); - -class WebPage { - constructor(args) { - Object.assign(this, args); - - if (!this.id) { - this.id = "webpage-" + performance.now(); - } - - translator.init({ - translations_url, - supported_languages: ["fr", "en"], - }).then(this.refresh_all.bind(this)); - } - - refresh() { - obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace" }) - } - - refresh_all() { - obj2htm.renderCycle() - } -} - -module.exports = WebPage; -},{"../../constants":2,"ks-cheap-translator":3}],6:[function(require,module,exports){ -"use strict"; - -const { images_url } = require("../../../constants"); -const WebPage = require("../../lib/web-page"); -const translator = require("ks-cheap-translator"); -const t = translator.trad.bind(translator); - -const EDU_THEMES = [ - { - title: "Programmation", - description: "edu-learn-coding", - image: "learning_theme_coding.png", - }, - { - title: "Dessin numérique et animation 2D", - description: "edu-learn-2d", - image: "learning_theme_2d.png", - }, - { - title: "Maths et physique", - description: "edu-learn-math", - image: "learning_theme_math.png", - }, - { - title: "Aide informatique générale", - description: "edu-learn-computer", - image: "learning_theme_pc.png", - }, - { - title: "Stage GNU/Linux", - description: "edu-learn-gnu", - image: "learning_theme_linux.png" - }, - { - title: "Créer un jeu avec Mentalo", - description: "edu-learn-mentalo", - image: "learning_theme_mentalo.png", - } -]; - -class EducationPage extends WebPage { - render() { - return { - tag: "div", - id: "education-page", - typeof: "EducationalOrganization", - contents: [ - { - tag: "div", - class: "page-header logo-left", - contents: [ - { - tag: "div", - class: "page-contents-center grid-wrapper", - contents: [ - { - tag: "div", - class: "logo", - contents: [ - { - tag: "img", - alt: "image brain", - src: `${images_url}/brain.svg`, - }, - ], - }, - { tag: "h1", contents: t("Pédagogie") }, - { - tag: "p", - contents: t("edu-page-intro"), - }, - ], - }, - ], - }, - { - tag: "div", - class: "title-banner", - }, - { - tag: "section", - class: "bg-dark", - contents: [ - { - tag: "div", - class: "page-contents-center", - contents: [ - { - tag: "ul", - class: "edu-themes", - contents: EDU_THEMES.map(theme => { - return { - tag: "li", - class: "edu-theme", - contents: [ - { tag: "img", width: 250, height: 140, class: "pixelated", src: `${images_url}/${theme.image}` }, - { tag: "h3", contents: t(theme.title) }, - { tag: "p", contents: t(theme.description) }, - ] - } - }) - }, - ] - }, - ] - }, - { - tag: "section", - class: "practical-info", - contents: [ - { - tag: "div", - class: "page-contents-center", - contents: [ - { - tag: "div", - class: "info-block", - contents: [ - { tag: "h3", class: "info-title", contents: `${t("Pour s'inscrire ou en savoir plus")} <em>(programme 2022 à définir, plus d'infos bientôt)</em>` }, - { - tag: "ul", - class: "info-body", - contents: [ - { - tag: "li", - contents: [ - { tag: "span", contents: t("Me contacter") }, - { - tag: "a", - href: "mailto:contact@kuadrado-software.fr", - contents: "contact@kuadrado-software.fr", - } - ] - }, - { - tag: "li", - contents: [ - { tag: "span", contents: "" }, - { - tag: "a", - href: "tel:+33475780872", - contents: "04 75 78 08 72", - property: "telephone", - }, - ] - }, - ] - } - ] - } - ] - } - ] - - }, - ], - }; - } -} - -module.exports = EducationPage; - -},{"../../../constants":2,"../../lib/web-page":5,"ks-cheap-translator":3}],7:[function(require,module,exports){ -"use strict"; -const runPage = require("../../run-page"); -const EducationPage = require("./education"); -runPage(EducationPage); - -},{"../../run-page":8,"./education":6}],8:[function(require,module,exports){ -"use strict"; - -const renderer = require("object-to-html-renderer") -const Template = require("./template/template"); - -module.exports = function runPage(PageComponent) { - const template = new Template({ page: new PageComponent() }); - renderer.register("obj2htm") - obj2htm.setRenderCycleRoot(template); - obj2htm.renderCycle(); -}; - -},{"./template/template":10,"object-to-html-renderer":4}],9:[function(require,module,exports){ -"use strict"; - -const { images_url } = require("../../../constants"); -const translator = require("ks-cheap-translator"); -const t = translator.trad.bind(translator); - -const NAV_MENU_ITEMS = [ - { url: "/games/", text: "Jeux" }, - { - url: "/education/", - text: "Pédagogie", - }, - { url: "/software-development/", text: "Software" } -]; - -class NavBar { - constructor() { - this.initEventHandlers(); - } - - handleBurgerClick() { - document.getElementById("nav-menu-list").classList.toggle("responsive-show"); - } - - initEventHandlers() { - window.addEventListener("click", event => { - if ( - event.target.id !== "nav-menu-list" && - !event.target.classList.contains("burger") && - !event.target.parentNode.classList.contains("burger") - ) { - document.getElementById("nav-menu-list").classList.remove("responsive-show"); - } - }); - } - - handle_chang_lang(lang) { - translator.update_translations(lang).then(() => { - obj2htm.renderCycle(); - }).catch(err => console.log(err)); - } - - renderHome() { - return { - tag: "div", - class: "home", - contents: [ - { - tag: "a", - href: "/", - contents: [ - { - tag: "img", - alt: "Logo Kuadrado", - src: `${images_url}/logo_kuadrado.svg`, - }, - { - tag: "img", - alt: "Kuadrado Software", - class: "logo-text", - src: `${images_url}/logo_kuadrado_txt.svg`, - }, - ], - }, - ], - }; - } - - renderMenu(menuItemsArray, isSubmenu = false, parentUrl = "") { - return { - tag: "ul", - id: "nav-menu-list", - class: isSubmenu ? "submenu" : "", - contents: menuItemsArray.map(item => { - const { url, text, submenu } = item; - const href = `${parentUrl}${url}`; - return { - tag: "li", - class: !isSubmenu && window.location.pathname === href ? "active" : "", - contents: [ - { - tag: "a", - href, - contents: t(text), - }, - ].concat(submenu ? [this.renderMenu(submenu, true, url)] : []), - }; - }).concat({ - tag: "li", - class: "lang-flags", - contents: ["fr", "en"].map(lang => { - return { - tag: "img", src: `${images_url}/flag-${lang}.svg`, - class: translator.locale === lang ? "selected" : "", - onclick: this.handle_chang_lang.bind(this, lang) - } - }) - }), - }; - } - - renderResponsiveBurger() { - return { - tag: "div", - class: "burger", - onclick: this.handleBurgerClick.bind(this), - contents: [{ tag: "span", contents: "···" }], - }; - } - - render() { - return { - tag: "nav", - contents: [ - this.renderHome(), - this.renderResponsiveBurger(), - this.renderMenu(NAV_MENU_ITEMS), - ], - }; - } -} - -module.exports = NavBar; - -},{"../../../constants":2,"ks-cheap-translator":3}],10:[function(require,module,exports){ -"use strict"; - -const { in_construction } = require("../../config"); -const { images_url } = require("../../constants"); -const NavBar = require("./components/navbar"); -const translator = require("ks-cheap-translator"); -const t = translator.trad.bind(translator) - -class Template { - constructor(props) { - this.props = props; - } - render() { - return { - tag: "main", - contents: [ - { - tag: "header", - contents: [new NavBar().render()], - }, - in_construction && { - tag: "section", - class: "warning-banner", - contents: [ - { - tag: "strong", - class: "page-contents-center", - contents: t("Site en construction ..."), - }, - ], - }, - { - tag: "section", - id: "page-container", - contents: [this.props.page.render()], - }, - { - tag: "footer", - contents: [ - { - tag: "div", - class: "logo", - contents: [ - { - tag: "img", - alt: `logo Kuadrado`, - src: `${images_url}/logo_kuadrado.svg`, - }, - { - tag: "img", - class: "text-logo", - alt: "Kuadrado Software", - src: `${images_url}/logo_kuadrado_txt.svg`, - }, - ], - }, - { - tag: "span", - contents: "32 rue Simon Vialet, 07240 Vernoux en Vivarais. Ardèche, France", - }, - { - tag: "div", - contents: [ - { tag: "strong", contents: "<blue>Contact : </blue>" }, - { - tag: "a", - href: "mailto:contact@kuadrado-software.fr", - contents: "contact@kuadrado-software.fr", - }, - ], - }, - { - tag: "div", - class: "social", - contents: [ - { - tag: "strong", - contents: `<blue>${t("Sur les réseaux")} : </blue>`, - }, - { - tag: "a", - href: "https://www.linkedin.com/company/kuadrado-software", - target: "_blank", - contents: "in", - title: "Linkedin", - }, - { - tag: "a", - href: "https://mastodon.gamedev.place/@kuadrado_software", - target: "_blank", - contents: "m", - title: "Mastodon", - } - ], - }, - { - tag: "span", - contents: `Copyleft 🄯 ${new Date() - .getFullYear()} Kuadrado Software | - ${t("kuadrado-footer-copyleft")}`, - }, - { - tag: "div", contents: [ - { tag: "span", contents: t("Ce site web est") + " " }, - { - tag: "a", target: "_blank", - style_rules: { fontWeight: "bold" }, - href: "https://gitlab.com/kuadrado-software/kuadrado-website/-/blob/master/README.md", - contents: "OPEN SOURCE" - } - ] - } - ], - }, - ], - }; - } -} - -module.exports = Template; - -},{"../../config":1,"../../constants":2,"./components/navbar":9,"ks-cheap-translator":3}]},{},[7]); +!function s(a,r,o){function i(t,e){if(!r[t]){if(!a[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(c)return c(t,!0);throw(n=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",n}n=r[t]={exports:{}},a[t][0].call(n.exports,function(e){return i(a[t][1][e]||e)},n,n.exports,s,a,r,o)}return r[t].exports}for(var c="function"==typeof require&&require,e=0;e<o.length;e++)i(o[e]);return i}({1:[function(e,t,n){t.exports={build:{protected_dirs:["assets","style","views","standard"],default_meta_keys:["title","description","image","open_graph","json_ld"]}}},{}],2:[function(e,t,n){t.exports={images_url:"/assets/images",data_url:"/assets/data",translations_url:"/assets/translations"}},{}],3:[function(e,t,n){"use strict";t.exports={locale:"en",supported_languages:["en"],translations:{},translations_url:"",use_url_locale_fragment:!0,local_storage_key:"translator-prefered-language",init(e){return Object.entries(e).forEach(e=>{var[t,e]=e;["supported_languages","use_url_locale_fragment","local_storage_key","translations_url"].includes(t)&&(this[t]=e)}),this.translations_url=this.format_translations_url(this.translations_url),this.supported_languages=this.format_supported_languages(this.supported_languages),new Promise((t,n)=>{const s=(()=>{if(this.use_url_locale_fragment){var e=window.location.pathname.substring(1).split("/")[0];return this.supported_languages.includes(e)?e:""}return""})()||localStorage.getItem(this.local_storage_key)||(e=navigator.language.split("-")[0].toLocaleLowerCase(),this.supported_languages.includes(e)?e:this.supported_languages[0]);var e;fetch(`${this.translations_url}${s}.json`).then(e=>e.json()).then(e=>{this.locale=s,this.translations=e,t()}).catch(e=>{this.locale="en",n(e)})})},format_locale(e){return e.split("-")[0].toLowerCase()},format_translations_url(e){return"/"!==e.charAt(e.length-1)&&(e+="/"),e},format_supported_languages(e){return e.map(e=>this.format_locale(e))},update_translations(s){return s=this.format_locale(s),new Promise((n,t)=>{fetch(`${this.translations_url}${s}.json`).then(e=>e.json()).then(e=>{this.translations=e,this.locale=s,localStorage.setItem(this.local_storage_key,s);const t=window.location.pathname.substring(1).split("/");e=t[0];this.supported_languages.includes(e)&&(t.splice(0,1,s),e=t.join("/"),window.history.replaceState(null,"","/"+e)),n()}).catch(e=>{t(e)})})},trad:function(t,n={}){return t=this.translations[t]||t,Object.keys(n).forEach(e=>{t=t.replace(`{%${e}%}`,n[e])}),t}}},{}],4:[function(e,t,n){"use strict";t.exports={register_key:"objectToHtmlRender",register(e){e=e||this.register_key;window[e]=this},setRenderCycleRoot(e){this.renderCycleRoot=e},event_name:"objtohtml-render-cycle",setEventName(e){this.event_name=e},objectToHtml(t){if(!t)return document.createElement("span");const n=this.objectToHtml.bind(this),{tag:e,xmlns:s}=t,a=void 0!==s?document.createElementNS(s,e):document.createElement(e),r=["tag","contents","style_rules","state","xmlns"];return Object.keys(t).filter(e=>!r.includes(e)).forEach(e=>{switch(e){case"class":a.classList.add(...t[e].split(" ").filter(e=>""!==e));break;case"on_render":t.id||(a.id=`${btoa(JSON.stringify(t).slice(0,127)).replace(/\=/g,"")}${window.performance.now()}`),"function"!=typeof t.on_render?console.error("The on_render attribute must be a function"):this.attach_on_render_callback(a,t.on_render);break;default:void 0!==s?a.setAttributeNS(null,e,t[e]):a[e]=t[e]}}),t.contents&&"string"==typeof t.contents?a.innerHTML=t.contents:t.contents&&0<t.contents.length&&t.contents.forEach(e=>{switch(typeof e){case"string":a.innerHTML=e;break;case"object":void 0!==s&&(e=Object.assign(e,{xmlns:s})),a.appendChild(n(e))}}),t.style_rules&&Object.keys(t.style_rules).forEach(e=>{a.style[e]=t.style_rules[e]}),a},on_render_callbacks:[],attach_on_render_callback(t,n){var e={callback:e=>{e.detail.outputNode!==t&&!e.detail.outputNode.querySelector(`#${t.id}`)||(n(t),-1===(e=this.on_render_callbacks.indexOf(this.on_render_callbacks.find(e=>e.node===t)))?console.warn("A callback was registered for node with id "+t.id+" but callbacck handler is undefined."):(window.removeEventListener(this.event_name,this.on_render_callbacks[e].callback),this.on_render_callbacks.splice(e,1)))},node:t},e=this.on_render_callbacks.push(e);window.addEventListener(this.event_name,this.on_render_callbacks[e-1].callback)},renderCycle:function(){var e,e=document.getElementsByTagName("main")[0]||(e=document.createElement("main"),document.body.appendChild(e),e);this.subRender(this.renderCycleRoot.render(),e,{mode:"replace"})},subRender(e,t,n={mode:"append"}){let s=null;var a=()=>(s=this.objectToHtml(e),s);switch(n.mode){case"append":t.appendChild(a());break;case"override":t.innerHTML="",t.appendChild(a());break;case"insert-before":t.insertBefore(a(),t.childNodes[n.insertIndex]);break;case"adjacent":t.insertAdjacentHTML(n.insertLocation,a());break;case"replace":t.parentNode.replaceChild(a(),t);break;case"remove":t.remove()}var r=this.event_name,r=new CustomEvent(r,{detail:{inputObject:e,outputNode:s,insertOptions:n,targetNode:t}});window.dispatchEvent(r)}}},{}],5:[function(e,t,n){"use strict";t.exports=class{constructor(e){this.props=e,this.id=this.props.images.join("").replace(/\s\./g),this.state={showImageIndex:0},this.RUN_INTERVAL=5e3,1<this.props.images.length&&this.run()}run(){this.runningInterval=setInterval(()=>{var{showImageIndex:e}=this.state,{images:t}=this.props;this.state.showImageIndex=e<t.length-1?++e:0,this.refreshImage()},this.RUN_INTERVAL)}setImageIndex(e){clearInterval(this.runningInterval),this.state.showImageIndex=e,this.refreshImage()}refreshImage(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}render(){const{showImageIndex:n}=this.state,{images:e}=this.props;return{tag:"div",id:this.id,class:"image-carousel",contents:[{tag:"img",property:"image",alt:`image carousel ${e[n].replace(/\.[A-Za-z]+/,"")}`,src:e[n]},1<e.length&&{tag:"div",class:"carousel-bullets",contents:e.map((e,t)=>{return{tag:"span",class:`bullet ${n===t?"active":""}`,onclick:this.setImageIndex.bind(this,t)}})}]}}}},{}],6:[function(e,t,n){"use strict";const{fetch_json_or_error_text:s}=e("./fetch");t.exports={loadArticles:function(e,t){return s(`/articles/${e}/${t}`)},getArticleBody:function(e){return e.replaceAll("\n","<br/>")},getArticleDate:function(e){return`${e.getDate()}-${e.getMonth()+1}-${e.getFullYear()}`}}},{"./fetch":7}],7:[function(e,t,n){"use strict";t.exports={fetchjson:function(e){return new Promise((t,n)=>{fetch(e).then(e=>e.json()).then(e=>t(e)).catch(e=>n(e))})},fetchtext:function(e){return new Promise((t,n)=>{fetch(e).then(e=>e.text()).then(e=>t(e)).catch(e=>n(e))})},fetch_json_or_error_text:async function(e,s={}){return new Promise((t,n)=>{fetch(e,s).then(async e=>{400<=e.status&&e.status<600?n(await e.text()):t(await e.json())})})}}},{}],8:[function(e,t,n){"use strict";const s=e("ks-cheap-translator"),{translations_url:a}=e("../../constants");t.exports=class{constructor(e){Object.assign(this,e),this.id||(this.id="webpage-"+performance.now()),s.init({translations_url:a,supported_languages:["fr","en"]}).then(this.refresh_all.bind(this))}refresh(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}refresh_all(){obj2htm.renderCycle()}}},{"../../constants":2,"ks-cheap-translator":3}],9:[function(e,t,n){"use strict";const{images_url:r}=e("../../../../constants"),o=e("../../../generic-components/image-carousel"),{getArticleBody:i}=e("../../../lib/article-utils");t.exports=class{constructor(e){this.props=e}render(){const{title:e,body:t,subtitle:n,images:s,details:a=[]}=this.props;return{tag:"article",class:"edu-article",typeof:"Article",contents:[{tag:"h2",class:"edu-art-title",contents:e,property:"name"},{tag:"div",class:"edu-art-image",contents:[{tag:"img",src:`${r}/${s[0]}`}]},{tag:"h3",class:"edu-art-subtitle",contents:n,property:"alternativeHeadline"},{tag:"div",class:"edu-art-description",contents:i(t),property:"description"},1<s.length&&{tag:"div",class:"edu-art-carousel",contents:[new o({images:s.map(e=>`${r}/${e}`)}).render()]},0<a.length&&{tag:"div",class:"article-details edu-art-details",contents:[{tag:"h2",contents:"Details"},{tag:"ul",class:"details-list",contents:a.map(e=>({tag:"li",class:"detail",contents:[{tag:"label",contents:e.label},{tag:"div",class:"detail-value",contents:e.value}]}))}]}]}}}},{"../../../../constants":2,"../../../generic-components/image-carousel":5,"../../../lib/article-utils":6}],10:[function(e,t,n){"use strict";const{loadArticles:s}=e("../../../lib/article-utils"),a=e("ks-cheap-translator"),r=a.trad.bind(a),o=e("./edu-article");t.exports=class{constructor(e){this.props=e,this.state={articles:[],loaded:!1},this.id="edu-articles-section",this.loadArticles()}loadArticles(){s("education",a.locale).then(e=>{this.state.articles=e}).catch(e=>console.log(e)).finally(()=>{this.state.loaded=!0,this.refresh()})}renderPlaceholder(){return{tag:"article",class:"placeholder",contents:[{tag:"div"},{tag:"div"},{tag:"div"},{tag:"div"}]}}refresh(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}render(){const{articles:e,loaded:t}=this.state;return{tag:"section",class:"edu-articles page-contents-center",id:this.id,contents:t&&0<e.length?e.map(e=>new o({...e}).render()):t&&0===e.length?[{tag:"p",contents:r("Rien de prévu pour le moment")}]:[this.renderPlaceholder()]}}}},{"../../../lib/article-utils":6,"./edu-article":9,"ks-cheap-translator":3}],11:[function(e,t,n){"use strict";const{images_url:s}=e("../../../constants");var a=e("../../lib/web-page");const r=e("ks-cheap-translator"),o=e("./components/edu-articles"),i=r.trad.bind(r),c=[{title:"Programmation",description:"edu-learn-coding",image:"learning_theme_coding.png"},{title:"Dessin numérique et animation 2D",description:"edu-learn-2d",image:"learning_theme_2d.png"},{title:"Maths et physique",description:"edu-learn-math",image:"learning_theme_math.png"},{title:"Aide informatique générale",description:"edu-learn-computer",image:"learning_theme_pc.png"},{title:"Stage GNU/Linux",description:"edu-learn-gnu",image:"learning_theme_linux.png"},{title:"Créer un jeu avec Mentalo",description:"edu-learn-mentalo",image:"learning_theme_mentalo.png"}];class l extends a{render(){return{tag:"div",id:"education-page",typeof:"EducationalOrganization",contents:[{tag:"div",class:"page-header logo-left",contents:[{tag:"div",class:"page-contents-center grid-wrapper",contents:[{tag:"div",class:"logo",contents:[{tag:"img",alt:"image brain",src:`${s}/brain.svg`}]},{tag:"h1",contents:i("Pédagogie")},{tag:"p",contents:i("edu-page-intro")}]}]},{tag:"div",class:"title-banner"},{tag:"section",contents:[{tag:"div",class:"page-contents-center",contents:[{tag:"ul",class:"edu-themes",contents:c.map(e=>({tag:"li",class:"edu-theme",contents:[{tag:"img",width:250,height:140,class:"pixelated",src:`${s}/${e.image}`},{tag:"h3",contents:i(e.title)},{tag:"p",contents:i(e.description)}]}))}]}]},{tag:"h2",class:"edu-section-title page-contents-center",contents:i("Programme XXXX",{date:"2022"})},(new o).render()]}}}t.exports=l},{"../../../constants":2,"../../lib/web-page":8,"./components/edu-articles":10,"ks-cheap-translator":3}],12:[function(e,t,n){"use strict";const s=e("../../run-page");e=e("./education");s(e)},{"../../run-page":13,"./education":11}],13:[function(e,t,n){"use strict";const s=e("object-to-html-renderer"),a=e("./template/template");t.exports=function(e){e=new a({page:new e});s.register("obj2htm"),obj2htm.setRenderCycleRoot(e),obj2htm.renderCycle()}},{"./template/template":15,"object-to-html-renderer":4}],14:[function(e,t,n){"use strict";const{images_url:s}=e("../../../constants"),o=e("ks-cheap-translator"),i=o.trad.bind(o),a=[{url:"/games/",text:"Jeux"},{url:"/education/",text:"Pédagogie"},{url:"/software-development/",text:"Software"}];t.exports=class{constructor(){this.initEventHandlers()}handleBurgerClick(){document.getElementById("nav-menu-list").classList.toggle("responsive-show")}initEventHandlers(){window.addEventListener("click",e=>{"nav-menu-list"===e.target.id||e.target.classList.contains("burger")||e.target.parentNode.classList.contains("burger")||document.getElementById("nav-menu-list").classList.remove("responsive-show")})}handle_chang_lang(e){o.update_translations(e).then(()=>{obj2htm.renderCycle()}).catch(e=>console.log(e))}renderHome(){return{tag:"div",class:"home",contents:[{tag:"a",href:"/",contents:[{tag:"img",alt:"Logo Kuadrado",src:`${s}/logo_kuadrado.svg`},{tag:"img",alt:"Kuadrado Software",class:"logo-text",src:`${s}/logo_kuadrado_txt.svg`}]}]}}renderMenu(e,a=!1,r=""){return{tag:"ul",id:"nav-menu-list",class:a?"submenu":"",contents:e.map(e=>{var{url:t,text:n,submenu:e}=e;const s=`${r}${t}`;return{tag:"li",class:a||window.location.pathname!==s?"":"active",contents:[{tag:"a",href:s,contents:i(n)}].concat(e?[this.renderMenu(e,!0,t)]:[])}}).concat({tag:"li",class:"lang-flags",contents:["fr","en"].map(e=>({tag:"img",src:`${s}/flag-${e}.svg`,class:o.locale===e?"selected":"",onclick:this.handle_chang_lang.bind(this,e)}))})}}renderResponsiveBurger(){return{tag:"div",class:"burger",onclick:this.handleBurgerClick.bind(this),contents:[{tag:"span",contents:"···"}]}}render(){return{tag:"nav",contents:[this.renderHome(),this.renderResponsiveBurger(),this.renderMenu(a)]}}}},{"../../../constants":2,"ks-cheap-translator":3}],15:[function(e,t,n){"use strict";const{in_construction:s}=e("../../config"),{images_url:a}=e("../../constants"),r=e("./components/navbar"),o=e("ks-cheap-translator"),i=o.trad.bind(o);t.exports=class{constructor(e){this.props=e}render(){return{tag:"main",contents:[{tag:"header",contents:[(new r).render()]},s&&{tag:"section",class:"warning-banner",contents:[{tag:"strong",class:"page-contents-center",contents:i("Site en construction ...")}]},{tag:"section",id:"page-container",contents:[this.props.page.render()]},{tag:"footer",contents:[{tag:"div",class:"logo",contents:[{tag:"img",alt:"logo Kuadrado",src:`${a}/logo_kuadrado.svg`},{tag:"img",class:"text-logo",alt:"Kuadrado Software",src:`${a}/logo_kuadrado_txt.svg`}]},{tag:"span",contents:"32 rue Simon Vialet, 07240 Vernoux en Vivarais. Ardèche, France"},{tag:"div",contents:[{tag:"strong",contents:"<blue>Contact : </blue>"},{tag:"a",href:"mailto:contact@kuadrado-software.fr",contents:"contact@kuadrado-software.fr"}]},{tag:"div",class:"social",contents:[{tag:"strong",contents:`<blue>${i("Sur les réseaux")} : </blue>`},{tag:"a",href:"https://www.linkedin.com/company/kuadrado-software",target:"_blank",contents:"in",title:"Linkedin"},{tag:"a",href:"https://mastodon.gamedev.place/@kuadrado_software",target:"_blank",contents:"m",title:"Mastodon"}]},{tag:"span",contents:`Copyleft 🄯 ${(new Date).getFullYear()} Kuadrado Software | + ${i("kuadrado-footer-copyleft")}`},{tag:"div",contents:[{tag:"span",contents:i("Ce site web est")+" "},{tag:"a",target:"_blank",style_rules:{fontWeight:"bold"},href:"https://gitlab.com/kuadrado-software/kuadrado-website/-/blob/master/README.md",contents:"OPEN SOURCE"}]}]}]}}}},{"../../config":1,"../../constants":2,"./components/navbar":14,"ks-cheap-translator":3}]},{},[12]); \ No newline at end of file diff --git a/public/games/games.js b/public/games/games.js index 45e5397..d497e27 100644 --- a/public/games/games.js +++ b/public/games/games.js @@ -1,4503 +1,2 @@ -(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ -module.exports = { - images_url: "/assets/images" -} -},{}],2:[function(require,module,exports){ -module.exports = { - build: { - protected_dirs: ["assets", "style", "views", "standard"], - default_meta_keys: ["title", "description", "image", "open_graph", "json_ld"], - }, -}; - -},{}],3:[function(require,module,exports){ -module.exports = { - images_url: `/assets/images`, - data_url: `/assets/data`, - translations_url: "/assets/translations" -}; - -},{}],4:[function(require,module,exports){ -"use strict"; -/** - * A tiny library to handle text translation. - */ - -/** - * init parameter object type - * @typedef TranslatorParam - * @property {String[]} supported_languages - DEFAULT: ["en"] - An array of ISO 639 lowercase language codes - * @property {String} locale ISO 639 lowercase language code - DEFAULT: "en" - * - * @property {Boolean} use_url_locale_fragment - DEFAULT: true - - * If true, the 1st fragment of the window.location url will be parsed as a language code - * Example: - * window.location - * > https://example.com/en/some-page... - * then the /en/ part of the url will be taken in priority and locale will be set to "en". - * window.location - * > https://example.com/some-page... - * No locale fragment will be found so locale will not be set from url fragment. - * window.location - * > https://example.com/some-page/en - * Doesn't work, locale fragment must be the first framgment - * - * @property {String} local_storage_key - DEFAULT: "translator-prefered-language" - * The key used to saved the current locale into local storage - * - * @property {String} translations_url - REQUIRED - the url of the directory containing the static json files for translations - * Translations files are expected to be named with their corresponding locale code. - * Example: - * if supported_languages is set to ["en", "fr", "it"] - * and translations_url is set to "https://example.com/translations/"" - * Then the expected translations files are - * https://example.com/translations/en.json - * https://example.com/translations/fr.json - * https://example.com/translations/it.json - * The json resources must simple key value maps, value being the translated text. - */ - -module.exports = { - locale: "en", // ISO 639 lowercase language code - supported_languages: ["en"], - translations: {}, - translations_url: "", - use_url_locale_fragment: true, - local_storage_key: "translator-prefered-language", - - /** - * Initialize the lib with params - * @param {TranslatorParam} params - * @returns {Promise} - */ - init(params) { - Object.entries(params).forEach(k_v => { - const [key, value] = k_v; - if ([ - "supported_languages", - "use_url_locale_fragment", - "local_storage_key", - "translations_url" - ].includes(key)) { - this[key] = value; - } - }); - - this.translations_url = this.format_translations_url(this.translations_url); - this.supported_languages = this.format_supported_languages(this.supported_languages); - - return new Promise((resolve, reject) => { - const loc = - (() => {// Locale from url priority 1 - if (this.use_url_locale_fragment) { - const first_url_fragment = window.location.pathname.substring(1).split("/")[0]; - const fragment_is_locale = this.supported_languages.includes(first_url_fragment); - return fragment_is_locale ? first_url_fragment : ""; - } else { - return ""; - } - })() - || localStorage.getItem(this.local_storage_key) // Locale from storage priority 2 - || (() => { // locale from navigator priority 3 - const navigator_locale = navigator.language.split("-")[0].toLocaleLowerCase(); - return this.supported_languages.includes(navigator_locale) - ? navigator_locale - : this.supported_languages[0] // Default if navigator locale is not supported - })(); - - fetch(`${this.translations_url}${loc}.json`) - .then(response => response.json()) - .then(response => { - this.locale = loc; - this.translations = response; - resolve(); - }) - .catch(err => { - this.locale = "en"; - reject(err); - }); - }); - }, - - /** - * Return a lowercase string without dash. - * If given locale is en-EN, then "en" will be returned. - * @param {String} locale - * @returns A lowercase string - */ - format_locale(locale) { - return locale.split("-")[0].toLowerCase(); - }, - - /** - * Appends a slash at the end of the given string if missing, and returns the url. - * @param {String} url - * @returns {String} - */ - format_translations_url(url) { - if (url.charAt(url.length - 1) !== "/") { - url += "/" - } - return url; - }, - - /** - * Return the array of language codes formatted as lowsercase language code. - * if ["en-EN", "it-IT"]is given, ["en", "it"] will b returned. - * @param {String[]} languages_codes - * @returns {String[]} - */ - format_supported_languages(languages_codes) { - return languages_codes.map(lc => this.format_locale(lc)); - }, - - /** - * Fetches a new set of translations in case the wanted language changes - * @param {String} locale A lowercase language code - * @returns {Promise} - */ - update_translations(locale) { - locale = this.format_locale(locale); - return new Promise((resolve, reject) => { - fetch(`${this.translations_url}${locale}.json`) - .then(response => response.json()) - .then(response => { - this.translations = response; - this.locale = locale; - - localStorage.setItem(this.local_storage_key, locale); - - const split_path = window.location.pathname.substring(1).split("/"); - const first_url_fragment = split_path[0]; - const fragment_is_locale = this.supported_languages.includes(first_url_fragment); - - if (fragment_is_locale) { - split_path.splice(0, 1, locale); - const updated_path = split_path.join("/"); - window.history.replaceState(null, "", "/" + updated_path); - } - resolve(); - }) - .catch(err => { - reject(err); - }); - }); - }, - - /** - * Tries to get the translation of the source string, or return the string as it. - * @param {String} text The source text to translate - * @param {Object} params Some dynamic element to insert in the text - * Params can be used if the translated text provide placeholder like {%some_word%} - * Example: - * translator.trad("Some trad key", {some_word: "a dynamic parameter"}) - * -> translation for "Some trad key": "A translated text with {%some_word%}" - * -> will be return as : "A translated text with a dynamic parameter" - * @returns {String} - */ - trad: function (text, params = {}) { - text = this.translations[text] || text; - - Object.keys(params).forEach(k => { - text = text.replace(`{%${k}%}`, params[k]); - }); - - return text; - } -}; -},{}],5:[function(require,module,exports){ -"use strict"; - -const MtlAnimation = require("./model/animation"); -const MentaloEngine = require("./mentalo-engine"); -const MtlScene = require("./model/scene"); -const MtlGame = require("./model/game"); -const MtlSoundTrack = require("./model/sound-track"); -const SCENE_TYPES = require("./model/scene-types"); -const MtlChoice = require("./model/choice"); -const MtlGameObject = require("./model/game-object"); -const FrameRateController = require("./lib/frame-rate-controller"); -const font_tools = require("./lib/font-tools"); -const color_tools = require("./lib/color-tools"); -const shape_tools = require("./lib/shape-tools"); -module.exports = { - MentaloEngine, - MtlAnimation, - MtlScene, - MtlGame, - MtlSoundTrack, - SCENE_TYPES, - MtlChoice, - MtlGameObject, - FrameRateController, - font_tools, - color_tools, - shape_tools, -}; -},{"./lib/color-tools":6,"./lib/font-tools":7,"./lib/frame-rate-controller":8,"./lib/shape-tools":9,"./mentalo-engine":11,"./model/animation":12,"./model/choice":13,"./model/game":15,"./model/game-object":14,"./model/scene":19,"./model/scene-types":18,"./model/sound-track":20}],6:[function(require,module,exports){ -"use strict"; -/** - * Helpers to work with color values - */ - -/** - * Get a RGB color value in String from either "rgb(x,x,x)" or #xxxxxx - * @param {String} color A RGB color value as String in the form: #ffffff or rgb(x,x,x) - * @returns {Uint8[]} An array of 3 values from 0 to 256 for [RED, GREEN, BLUE] - */ -function color_str_to_rgb_array(color) { - color = color.toLowerCase(0); - if (color.includes("rgb")) { - return color.replaceAll(/[a-z\(\)w]/g, "") - .split(",") - .slice(0, 3) - .map(n => parseInt(n)); - } else { - return color.replace("#", "") - .match(/.{0,2}/g) - .slice(0, 3) - .map(hex => parseInt(hex, 16)); - } -} - -/** - * @param {String} color A color value (#xxxxxx or rbg(x,x,x)) - * @returns The average between each tone of the given color. - */ -function get_average_rgb_color_tone(color) { - return color_str_to_rgb_array(color).reduce((tot, n) => tot + n / 3, 0); -} - -/** - * Gets an array of 3 8bits integers for red green and blue and and return a color value as an hexadecimal string #xxxxxx - * @param {Uint8[]} rgb - * @returns {String} The hexacdecimal rgb value of the color including the hash character: #xxxxxx - */ -function rgb_array_to_hex(rgb) { - return `#${rgb.slice(0, 3).map(n => { - const hex = n.toString(16); - return hex.length < 2 ? '0' + hex : hex; - }).join('')}`; -} - -/** - * Gets an array of 4 8bits integers for red green blue and transparency channel and return a color value as an hexadecimal string #xxxxxx - * @param {Uint8[]} rgb - * @returns {String} The hexacdecimal rgba value of the color including the hash character: #xxxxxxxx - */ -function rgba_array_to_hex(rgba) { - rgba = rgba.length === 4 ? rgba : rgba.concat([255]) - return `#${rgba.slice(0, 4).map(n => { - const hex = n.toString(16); - return hex.length < 2 ? '0' + hex : hex; - }).join('')}`; -} - -/** - * Gets a background color as argument and return a calculated optimal foreground color. - * For example if the background is dark the function will return a lighten version of the background. - * @param {String} base_color A RGB color value in hexadecimal form: #ffffff - * @returns - */ -function get_optimal_visible_foreground_color(base_color) { - // create a 2D 1px canvas context - const tmp_canvas = document.createElement("canvas"); - tmp_canvas.width = 1; - tmp_canvas.height = 1; - const ctx = tmp_canvas.getContext("2d"); - - // fill it with the given color - ctx.fillStyle = base_color; - ctx.fillRect(0, 0, 1, 1); - - // Get either a semi-transparent black or semi-transparent white regarding the base color is above or below an avg of 127 - const superpo_color = get_average_rgb_color_tone(base_color) > 127 ? "#0000003f" : "#ffffff3f"; - - // Fill the pixel with the semi-transparent black or white on top of the base color - ctx.fillStyle = superpo_color; - ctx.fillRect(0, 0, 1, 1); - - // Return the flatten result of the superposition - return rgb_array_to_hex(Array.from(ctx.getImageData(0, 0, 1, 1).data)); -} - -/** - * @param {Uint8[]} col1 A RGBA color value as an array of 4 0 to 256 integers - * @param {Uint8[]} col2 A RGBA color value as an array of 4 0 to 256 integers - * @returns {Boolean} true if the 2 colors are equals - */ -function same_rgba(col1, col2) { - return col1[0] === col2[0] - && col1[1] === col2[1] - && col1[2] === col2[2] - && col1[3] === col2[3]; -} - -module.exports = { - get_average_rgb_color_tone, - get_optimal_visible_foreground_color, - color_str_to_rgb_array, - rgb_array_to_hex, - rgba_array_to_hex, - same_rgba, -}; -},{}],7:[function(require,module,exports){ -"use strict"; -/** - * Helpers to works with default web fonts - */ - - -const FONT_FAMILIES = [ - { - category: "Sans serif", values: [ - { value: "sans-serif", text: "sans-serif" }, - { text: "Arial", value: "Arial, sans-serif" }, - { text: "Helvetica", value: "Helvetica, sans-serif" }, - { text: "Verdana", value: "Verdana, sans-serif" }, - { text: "Trebuchet MS", value: "Trebuchet MS, sans-serif" }, - { text: "Noto Sans", value: "Noto Sans, sans-serif" }, - { text: "Gill Sans", value: "Gill Sans, sans-serif" }, - { text: "Avantgarde", value: "Avantgarde, TeX Gyre Adventor, URW Gothic L, sans-serif" }, - { text: "Optima", value: "Optima, sans-serif" }, - { text: "Arial Narrow", value: "Arial Narrow, sans-serif" } - ], - }, - { - category: "Serif", values: [ - { text: "serif", value: "serif" }, - { text: "Times", value: "Times, Times New Roman, serif" }, - { text: "Didot", value: "Didot, serif" }, - { text: "Georgia", value: "Georgia, serif" }, - { text: "Palatino", value: "Palatino, URW Palladio L, serif" }, - { text: "Bookman", value: "Bookman, URW Bookman L, serif" }, - { text: "New Century Schoolbook", value: "New Century Schoolbook, TeX Gyre Schola, serif" }, - { text: "American Typewriter", value: "American Typewriter, serif" } - ], - }, - { - category: "Monospace", values: [ - { text: "monospace", value: "monospace" }, - { text: "Andale Mono", value: "Andale Mono, monospace" }, - { text: "Courrier New", value: "Courier New, monospace" }, - { text: "Courrier", value: "Courier, monospace" }, - { text: "FreeMono", value: "FreeMono, monospace" }, - { text: "OCR A Std", value: "OCR A Std, monospace" }, - { text: "DejaVu Sans Mono", value: "DejaVu Sans Mono, monospace" }, - ], - }, - { - category: "Cursive", values: [ - { text: "cursive", value: "cursive" }, - { text: "Comic Sans MS", value: "Comic Sans MS, Comic Sans, cursive" }, - { text: "Apple Chancery", value: "Apple Chancery, cursive" }, - { text: "Bradley Hand", value: "Bradley Hand, cursive" }, - { text: "Brush Script MT", value: "Brush Script MT, Brush Script Std, cursive" }, - { text: "Snell Roundhand", value: "Snell Roundhand, cursive" }, - { text: "URW Chancery L", value: "URW Chancery L, cursive" }, - ], - } -]; - -const TEXT_ALIGN_OPTIONS = [ - { value: "left", text: "Left" }, // TODO trad or icon - { value: "right", text: "Right" }, - { value: "center", text: "Center" }, -]; - -const FONT_STYLE_OPTIONS = [ - { value: "normal", text: "Normal" }, - { value: "italic", text: "Italic" }, // TODO trad or icon -]; - -const FONT_WEIGHT_OPTIONS = [ - { value: "1", text: "Thin" }, - { value: "normal", text: "Normal" }, - { value: "900", text: "Bold" }, -]; - -/** - * Gets an object description of a font and returns it formatted as a string that can be given to a 2D drawing context. - * Example : ctx.font = get_canvas_font({font_family:"monospace", font_size: 32}) - * @param {Object} font_data An object description of the font settings, with font_size, font_family, font_style, font_weight.s - * @returns {String} - */ -function get_canvas_font(font_data = {}) { - const { font_size = 20, font_family = "sans-serif", font_style = "normal", font_weight = "normal" } = font_data; - const font_variant = "normal"; - return `${font_style} ${font_variant} ${font_weight} ${font_size.toFixed()}px ${font_family}`; -} - -/** - * @returns {Object} A map of all defined constants for font families, text align options, font styles and font weights. - */ -function get_font_options() { - return { - font_families: FONT_FAMILIES, - text_align_options: TEXT_ALIGN_OPTIONS, - font_style_options: FONT_STYLE_OPTIONS, - font_weight_options: FONT_WEIGHT_OPTIONS, - }; -} - -/** - * An approximation of the average width and height of a character for a 2D drawing context with a given font configuration. - * @param {CanvasRenderingContext2D} ctx - * @returns {Object} An object with {width, height, text_line_height} entries - */ -function get_canvas_char_size(ctx, font_settings) { - ctx.save(); - ctx.font = get_canvas_font(font_settings); - - const str = `Lorem ipsum dolor Sit amet, Consectetur adipiscing elit`; - const width = ctx.measureText(str).width / str.length; - - // Scale width by an approximative 1.1 factor to calculate the height, - // for example for letters like j q p etc that have a part bellow the text base line - const height = ctx.measureText("M").width * 1.1; - const text_line_height = height + height / 4; - - ctx.restore(); - - return { - width, - height, - text_line_height, - interline_height: height / 4 - } -} - -module.exports = { - get_canvas_font, - get_font_options, - get_canvas_char_size, - FONT_FAMILIES, - TEXT_ALIGN_OPTIONS, - FONT_STYLE_OPTIONS, - FONT_WEIGHT_OPTIONS -}; -},{}],8:[function(require,module,exports){ -"use strict"; - -/** - * A little helper class to control the rendering frame rate - */ -class FrameRateController { - /** - * @param {Integer} fps The wanted frame rate in frame per second - */ - constructor(fps) { - this.tframe = performance.now(); - this.interval = 1000 / fps; // convert in milliseconds - this.initial = true; - } - - /** - * @returns {Boolean} true if the defined interval is elapsed. - */ - nextFrameReady() { - if (this.initial) { - this.initial = false; - return true; - } - - const now = performance.now(); - const elapsed = now - this.tframe; - const ready = elapsed > this.interval; - - if (ready) { - this.tframe = now - (elapsed % this.interval); - } - - return ready; - } -} - -module.exports = FrameRateController; -},{}],9:[function(require,module,exports){ -"use strict"; - -/** - * Draws a rectangle on a canvas 2D context with support of corner rounding and border options. - * @param {CanvasRenderingContext2D} ctx - * @param {Integer} x - * @param {Integer} y - * @param {Integer} width - * @param {Integer} height - * @param {Object} options - */ -function draw_rect(ctx, x, y, width, height, options = { - rounded_corners_radius: 0, - border: { width: 0, color: "rgba(0,0,0,0)" }, - fill_color: "black", -}) { - const { rounded_corners_radius = 0, border = { width: 0, color: "rgba(0,0,0,0)" }, fill_color = "black", fill_image } = options; - const smallest_axis = Math.min(width, height); - const radius = rounded_corners_radius > smallest_axis / 2 ? smallest_axis / 2 : rounded_corners_radius; - - ctx.save(); - - ctx.beginPath(); - ctx.arc(x + radius, y + radius, radius, Math.PI, 3 * Math.PI / 2); - ctx.lineTo(x + width - radius, y); - ctx.arc(x + width - radius, y + radius, radius, 3 * Math.PI / 2, 0); - ctx.lineTo(x + width, y + height - radius); - ctx.arc(x + width - radius, y + height - radius, radius, 0, Math.PI / 2); - ctx.lineTo(x + radius, y + height); - ctx.arc(x + radius, y + height - radius, radius, Math.PI / 2, Math.PI); - ctx.closePath(); - - ctx.clip(); - - if (fill_image) { - ctx.drawImage( - fill_image.src, - 0, 0, - fill_image.src.width, fill_image.src.height, - fill_image.dx, fill_image.dy, - fill_image.dw, fill_image.dh, - ) - } else { - ctx.fillStyle = fill_color; - ctx.fillRect(x, y, width, height); - } - - if (border.width > 0) { - ctx.strokeStyle = border.color; - ctx.lineWidth = border.width; - ctx.stroke(); - } - - ctx.restore(); -} - -module.exports = { - draw_rect, -} -},{}],10:[function(require,module,exports){ -"use strict"; - -const { get_canvas_font, get_canvas_char_size } = require("./font-tools"); - -/** - * A utility function to display text inside of a box coordinates - * @param {CanvasRenderingContext2D} ctx - * @param {String[]} lines - * @param {Object} bounds A bounding box object like {left, right, top, bottom, width, height} - * @param {Object} settings An object with styling settings like {padding, font_color, background_color, ...} - * @param {Object} a_state_ref Any object having a persistent life in the calling instance - * @param {Boolean} streamed Wether the text should be displayed as a progressive text streaming or all at once - * @returns {Boolean} true if the text is fully displayed - */ -function draw_text_in_bounds(ctx, lines, bounds, settings, a_state_ref, streamed) { - ctx.save(); - const char_size = get_canvas_char_size(ctx, settings); - const line_height = char_size.text_line_height; - ctx.font = get_canvas_font(settings); - ctx.fillStyle = settings.font_color; - ctx.textAlign = settings.text_align; - ctx.textBaseline = "top"; - - const get_text_position = () => { - const { padding = 0 } = settings; - const { left, top, width } = bounds; - switch (ctx.textAlign) { - case "left": - return { - x: left + padding, - y: top + padding, - } - case "right": - return { - x: left + width - padding, - y: top + padding, - } - case "center": - return { - x: left + width / 2, - y: top + padding, - } - default: - return { - x: left + padding, - y: top + padding, - } - } - }; - - const text_pos = get_text_position(); - - a_state_ref.stream = a_state_ref.stream || { - line_chars: 0, - line_index: 0, - complete: false, - }; - - const output_lines = streamed ? lines.map((line, i) => { - const stream_state = a_state_ref.stream; - if (i < stream_state.line_index || stream_state.complete) { - return line; - } else if (i > stream_state.line_index) { - return ""; - } else { - const slice = line.slice(0, ++stream_state.line_chars); - if (slice.length === line.length) { - stream_state.complete = lines.length - 1 === stream_state.line_index; - stream_state.line_index = stream_state.complete - ? stream_state.line_index - : stream_state.line_index + 1; - stream_state.line_chars = 0; - } - return slice; - } - }) : (() => { - a_state_ref.stream.complete = true; - return lines; - })(); - - output_lines.forEach(line => { - ctx.fillText(line, text_pos.x, text_pos.y); - text_pos.y += line_height; - }); - - ctx.restore(); - - return a_state_ref.stream.complete; -} - -module.exports = { - draw_text_in_bounds, -} -},{"./font-tools":7}],11:[function(require,module,exports){ -"use strict"; - -const MtlGame = require("./model/game"); -const SCENE_TYPES = require("./model/scene-types"); -const MtlRender = require("./render/render"); -const { supported_locales, get_translated } = require("./translation"); - -/** - * The encapsulating class of all the components of the Mentalo game engine. - * This is the class that should be used from outside to run a game. - */ -class MentaloEngine { - /** - * Initialize A MtlGame instance, populates it with the gama_data given as param. - * Initializes the DOM element to contain the rendering of the engine. - * Defines the listeners for Escape key and initialize a MtlRender instance. - * @param {Object} params - * Parameter params.game_data is required and should be the result of the exportation format of the Mentalo editor app. - */ - constructor(params) { - this.game = new MtlGame(); - this.game.load_data(params.game_data); - - this.game.on_load_resources(() => this.loading = false); - - this.use_locale = params.use_locale && supported_locales.includes(params.use_locale) - ? params.use_locale - : "en"; - - /** - * Allow to exit game by typing Escape or q - * @param {Event} e - */ - this.escape_listener = e => { - const k = e.key.toLowerCase(); - (k === "escape" || k === "q") && this.quit(); - }; - - window.addEventListener("keydown", this.escape_listener); - - this.on_quit_game = function () { - if (this.used_default_container) { - this.container.remove(); - } - window.removeEventListener("keydown", this.escape_listener); - params.on_quit_game && params.on_quit_game(); - }; - - this.used_default_container = false; - - this.container = params.container || (() => { - const el = document.createElement("div"); - el.style.display = "flex"; - el.style.justifyContent = "center"; - el.style.alignItems = "center"; - el.style.position = "absolute"; - el.style.top = 0; - el.style.bottom = 0; - el.style.left = 0; - el.style.right = 0; - el.style.zIndex = 1000; - el.style.overflow = "hidden"; - document.body.appendChild(el); - this.used_default_container = true - return el; - })(); - - this.render = new MtlRender({ - container: this.container, - fullscreen: params.fullscreen, - frame_rate: params.frame_rate, - get_game_settings: this.get_game_settings.bind(this), - get_game_scenes: this.get_game_scenes.bind(this), - get_scene: this.get_scene.bind(this), - get_scene_index: this.get_scene_index.bind(this), - get_inventory: this.get_inventory.bind(this), - on_game_object_click: this.on_game_object_click.bind(this), - on_drop_inventory_object: this.on_drop_inventory_object.bind(this), - on_choice_click: this.on_choice_click.bind(this), - }); - - this.loading = true; - this.will_quit = false; - } - - /** - * Returns a fragment of the game instance ui_options, which are the styling settings of the game interface. - * Can be the settings for text_boxes, choices_panel, inventory, etc. - * @param {String} key The key of the wanted interface settings. - * @returns {Object} The fragment of this.game.game_ui_options for the given key. - */ - get_game_settings(key) { - return this.game.game_ui_options[key]; - } - - /** - * @returns {MtlScene[]} Returns an array of the scenes of the game. - */ - get_game_scenes() { - return this.game.scenes; - } - - /** - * @returns {MtlScene} The scene that is currently displayed - */ - get_scene() { - return this.game.get_scene(); - } - - /** - * @returns {Integer} The index of the scene that is currently displayed - */ - get_scene_index() { - return this.game.scenes.indexOf(this.get_scene()); - } - - /** - * Returns the game objects currently saved in the inventory. - * @returns {MtlGameObject[]} - */ - get_inventory() { - return this.game.state.inventory; - } - - /** - * Sets the flag this.will_quit to true. - * This flag will be parsed in the rendering loop and the engine will then effectively exit the game calling __exit(). - */ - quit() { - this.will_quit = true; - } - - /** - * Clean the render instance, stops the rendering loop and calls the on_quit_game callback. - * This is called when the rendering loop reads the this.will_quit flag as true. - */ - __quit() { - window.cancelAnimationFrame(window.mentalo_engine_animation_id); - this.render.exit_fullscreen(); - this.render.clear_event_listeners(); - this.game.get_soundtrack().stop(); - this.on_quit_game(); - } - - - /** - * Initialize the rendering loop and the MtlRender instance. - * Also checks that the first loaded scene has an image to load. - * If it doesn't the game will exit immediately in order to avoid having a black screen. - * ( If the first scene doesn't have an image to load, the game.on_load_resource callback will - * never be called and the engine will remain in loading state. ) - */ - init() { - window.requestAnimationFrame = window.requestAnimationFrame - || window.mozRequestAnimationFrame - || window.webkitRequestAnimationFrame - || window.msRequestAnimationFrame; - - window.cancelAnimationFrame = window.cancelAnimationFrame - || window.mozCancelAnimationFrame; - - if (window.mentalo_engine_animation_id) { - window.cancelAnimationFrame(window.mentalo_engine_animation_id); - } - - this.render.init(); - - if (this.game.scenes.find(scene => scene.animation.empty)) { - alert(get_translated("Some scenes have an empty image, game cannot be loaded", this.use_locale)); - this.quit(); - } - } - - /** - * Clear the event listeners and rendering element related to the current scene from the MtlRender instance, - * And sets the game current scene state to a new index. - * @param {Integer} index - */ - go_to_scene(index) { - this.render.reset_user_error_popup(); - this.render.clear_event_listeners(); - this.game.get_soundtrack().stop(); - this.game.go_to_scene(index); - this.render.clear_children(); - this.render.reset_text_box_visibility(); - } - - /** - * Event listener for doubleclick on a game object. - * Adds the object to inventory. - * @param {MtlGameObject} obj - */ - on_game_object_click(obj) { - this.game.inventory_has_empty_slot() && this.game.add_object_to_inventory(obj); - } - - /** - * All callback called from the inventory render component when an object image is clicked in the inventory. - * Removes the object from inventory. - * @param {MtlGameObject} obj - */ - on_drop_inventory_object(obj) { - this.game.remove_object_from_inventory(obj); - } - - /** - * Callback called from the choices_panel render component. - * Handles the click on a choice. - * @param {MtlChoice} choice - * @returns - */ - on_choice_click(choice) { - const { destination_scene_index, use_objects } = choice; - if (destination_scene_index === -1) { // -1 is default index when the choice destination scene is not set. - this.render.set_user_error_popup({ - text: get_translated("Destination scene has not been set.", this.use_locale), - }); - return; - } - - if (destination_scene_index === -2) { // -2 is the index used to indicate that destination scene is just the end of program. - this.quit() - return; - } - - // Handle the case where the clicked choice requires some objects to be presents in the inventory. - if (use_objects.value) { - const inventory = this.get_inventory(); - const objs = []; - for (const it of use_objects.items) { - const ob = Array.from(inventory).find(o => o.name === it.name); - if (ob) { - objs.push(ob); - } else { - this.render.set_user_error_popup({ - text: use_objects.missing_object_message, stream_text: true, - }); - return; - } - } - - objs.forEach(o => use_objects.items - .find(it => o.name === it.name) - .consume_object && this.game.consume_game_object(o)); - } - - this.go_to_scene(destination_scene_index); - } - - /** - * Callback called when a scene of type "Cinematic" reaches end. - */ - on_cinematic_end() { - const scene = this.game.get_scene(); - if (scene.end_cinematic_options.quit) { - this.quit(); - } else if (scene.end_cinematic_options.destination_scene_index === -1) { - this.render.set_user_error_popup({ - text: get_translated("Next scene has not been set.", this.use_locale), - on_close: this.quit.bind(this), - }); - } else { - this.go_to_scene(scene.end_cinematic_options.destination_scene_index); - } - } - - /** - * The rendering loop. - * Handles the different states of the engine (quit, loading or runnning normally) - * @returns The id of the registered window.requestAnimationFrame - */ - run_game() { - if (this.will_quit) { - this.__quit(); - return; - } else if (this.loading) { - this.render.draw_loading(); - } else { - if (this.get_scene()._type === SCENE_TYPES.CINEMATIC) { - this.game.update_cinematic_timeout(); - if (this.game.is_cinematic_ended()) { - this.on_cinematic_end(); - } - } - - const sound_track = this.game.get_soundtrack(); - if (sound_track.loaded && !sound_track.is_playing()) { - sound_track.play(); - } - - this.render.draw_game(); - } - - window.mentalo_engine_animation_id = requestAnimationFrame(this.run_game.bind(this)); - } -} - -module.exports = MentaloEngine; -},{"./model/game":15,"./model/scene-types":18,"./render/render":23,"./translation":35}],12:[function(require,module,exports){ -"use strict"; -const Loadable = require("./loadable"); - -/** - * The data type used for a MtlScene animation - */ -class MtlAnimation extends Loadable { - /** - * Initialize an empty instance of Image HtmlElement, - * and registers the onload callback to update the instance with the loaded image actual dimensions. - */ - constructor() { - super(new Image(), "image", "load"); - this.image.onload = () => this.update_dimensions(); - - this.name = ""; - this.dimensions = { - width: 0, - height: 0, - }; - this.frame_nb = 1; - this.frame = 0; - this.speed = 1; - this.play_once = false; - this.initialized = false; - this.finished = false; - } - - /** - * Copies the dimensions of the loaded image at the root level of the instance. - * Deletes the canvas dimensions precalculations created by the SceneAnimation component - */ - update_dimensions() { - this.dimensions = { - width: this.image.width / this.frame_nb, - height: this.image.height, - }; - - if (this.canvas_precalc) { - delete this.canvas_precalc; - } - } - - /** - * Populates the instance with loaded litteral data. - * @param {Object} data - */ - init(data) { - this.empty = data.src === ""; - this.name = data.name; - this.image.src = data.src; - this.frame_nb = data.frame_nb; - this.frame = 0; - this.speed = data.speed; - this.play_once = data.play_once; - this.initialized = true; - } - - /** - * Increments the framcount argument if the animation next frame is ready to be rendered. - * @param {Integer} framecount - */ - update_frame(framecount) { - this.finished = this.play_once && this.frame === this.frame_nb - 1; - if (this.frame_nb > 1 && framecount % this.speed === 0 && !this.finished) { - this.frame = this.frame + 1 <= this.frame_nb - 1 ? this.frame + 1 : 0; - } - } - - /** - * Reset the frame state. - */ - reset_frame() { - this.finished = false; - this.frame = 0; - } -} - -module.exports = MtlAnimation; -},{"./loadable":17}],13:[function(require,module,exports){ -"use strict"; -/** - * The data type used for the choices of a MtlScene - */ -class MtlChoice { - /** - * Initializes the instance with given data or default empty data. - * @param {Object} data - */ - constructor(data = { - text: "", - destination_scene_index: -1, - use_objects: { - value: false, - items: [], - missing_object_message: "", - } - }) { - this.load_data(data); - } - - /** - * Populates the instance with litteral description data - * @param {Object} data - */ - load_data(data) { - this.text = data.text; - this.destination_scene_index = data.destination_scene_index; - this.use_objects = data.use_objects; - } -} - -module.exports = MtlChoice; -},{}],14:[function(require,module,exports){ -"use strict"; -const Loadable = require("./loadable"); - -/** - * The data type used for the game_objects field of a MtlScene - */ -class MtlGameObject extends Loadable { - /** - * Initializes the instance with an empty image and zero position. - */ - constructor() { - super(new Image(), "image", "load"); - this.name = ""; - this.position = { x: 0, y: 0 }; - this.state = {}; - } - - /** - * Populates the instance with a litteral description data object - * @param {Object} data - */ - load_data(data) { - this.image.src = data.image; - this.name = data.name; - this.position = data.position; - } - - /** - * @returns {Object} A {width, height} object for the dimensions of the image. - */ - get_dimensions() { - return { - width: this.image.width, - height: this.image.height, - } - } - - /** - * Returns the bounding box coordinates of the object image as a {top, right, bottom, left} object. - * @returns {Object} - */ - get_bounds() { - const dims = this.get_dimensions(); - return { - top: this.position.y, - right: this.position.x + dims.width, - bottom: this.position.y + dims.height, - left: this.position.x, - } - } -} - -module.exports = MtlGameObject; -},{"./loadable":17}],15:[function(require,module,exports){ -"use strict"; - -const MtlScene = require("./scene"); -const SCENE_TYPES = require("./scene-types"); - -/** - * The data type to encapsulate a loaded game. - */ -class MtlGame { - /** - * Initialize the instance with default data. - */ - constructor() { - this.name = ""; - this.scenes = [new MtlScene()]; - - // Game interface styling preferences - this.game_ui_options = { - // The overall look of the innterface - general: { - background_color: "#000000", - animation_canvas_dimensions: { - width: 600, - ratio: "4:3", - height: function () { - const split_ratio = this.ratio.split(":").map(n => parseInt(n)); - const factor = split_ratio[1] / split_ratio[0]; - return Number((this.width * factor).toFixed()); - }, - }, - }, - // The text box displayed on top of a scene image if a message is defined - text_boxes: { - background_color: "#222222", - font_size: 20, - font_style: "normal", - font_weight: "normal", - font_family: "Arial, sans-serif", - font_color: "#ffffff", - text_align: "left", - padding: 20, - margin: 20, - border_width: 0, - rounded_corners_radius: 0, - }, - // The panel containing the choices buttons below the scene image. - choices_panel: { - background_color: "#222222", - font_size: 20, - font_family: "sans", - font_color: "#ffffff", - font_style: "normal", - text_align: "left", - font_weight: "normal", - container_padding: 20, - choice_padding: 10, - active_choice_background_color: "rgba(255,255,255,.4)", - active_choice_border_width: 0, - active_choice_rounded_corners_radius: 0, - }, - // the inventory grid drawn at the right of the scene image - inventory: { - background_color: "#222222", - columns: 1, - rows: 4, - gap: 10, - padding: 20, - slot_rounded_corner_radius: 0, - slot_border_width: 2, - }, - }; - - this.starting_scene_index = 0; - - // The state of the running game - this.state = { - scene: this.starting_scene_index, - inventory: new Set(), - cinematic_timeout: { - inc: 0, - last_update_time: -1, - timeout: false, - }, - }; - - // This state will be incremented for each when it finishes to load its image and sound resources. - // The loaded_scenes value must be equal to the scenes number for the game to consider that all resources are loaded. - this.load_state = { - loaded_scenes: 0, - }; - } - - /** - * Returns the scene that is currently displayed - * @returns {MtlScene} - */ - get_scene() { - return this.scenes[this.state.scene]; - } - - /** - * @returns {MtlSoundTrack} the soundtrack that is set for the current scene - */ - get_soundtrack() { - return this.get_scene().sound_track; - } - - /** - * Updates the state with a new scene index. - * @param {Integer} index The index of the wanted scene - */ - go_to_scene(index) { - const current_scene = this.get_scene(); - current_scene.animation.reset_frame(); - if (current_scene._type == SCENE_TYPES.CINEMATIC) { - this.state.cinematic_timeout = { - inc: 0, - last_update_time: -1, - timeout: false, - }; - } - this.state.scene = index; - } - - /** - * This is called if the current scene is of type Cinematic. - * UIncrements the cinematic_timeout counter and update the - * cinematic_timeout.timeout state to true if the cinematic - * has been running as much or more time than what is defined in scene.cinematic_duration. - */ - update_cinematic_timeout() { - if (this.state.cinematic_timeout.timeout) return; - - const t = new Date().getTime(); - - if (this.state.cinematic_timeout.last_update_time === -1) { - this.state.cinematic_timeout.last_update_time = t; - } - - this.state.cinematic_timeout.inc = t - this.state.cinematic_timeout.last_update_time; - this.state.cinematic_timeout.timeout = this.get_scene().cinematic_duration * 1000 <= this.state.cinematic_timeout.inc; - } - - /** - * Returns a state wether the running cinematic is ended or not - * @returns {Boolean} - */ - is_cinematic_ended() { - const scene = this.get_scene(); - if (scene.cinematic_duration === 0) return scene.animation.finished; - return this.state.cinematic_timeout.timeout; - } - - /** - * Takes a game object as parameter and references it in the inventory state. - * @param {MtlGameObject} obj - */ - add_object_to_inventory(obj) { - this.state.inventory.add(obj); - } - - /** - * @returns {Boolean} true if inventory has an empty slot. - */ - inventory_has_empty_slot() { - const { columns, rows } = this.game_ui_options.inventory; - return this.state.inventory.size < columns * rows; - } - - /** - * Take a game object as argument and removes its reference from the inventory state. - * @param {MtlGameObject} obj - */ - remove_object_from_inventory(obj) { - this.state.inventory.delete(obj); - } - - /** - * Takes a game object as argument, removes the reference from inventory 7 - * and delete the object from the scene it's defined into. - * @param {MtlGameObject} obj - */ - consume_game_object(obj) { - this.remove_object_from_inventory(obj); - for (const s of this.scenes) { - const found_obj = s.game_objects.find(o => o === obj); - if (found_obj) { - s.game_objects.splice(s.game_objects.indexOf(found_obj), 1); - break; - } - } - } - - /** - * Returns true if the number of scenes having finished to load their loadable resources is equal to the total number of scenes. - * @returns {Boolean} - */ - all_resources_loaded() { - return this.load_state.loadable_elements === this.load_state.loaded_elements; - } - - /** - * Returns a concatenation of all game objects of all scenes. - * @returns {MtlGameObject[]} - */ - get_game_objects() { - return this.scenes.reduce((acc, scene) => acc.concat(scene.game_objects), []); - } - - /** - * Populates the instance with a litteral game desriptor. - * The expected data format is identical to what's exported from the Mentalo app editor, - * or what's returned from the Mentalo API database. - * @param {Object} data - */ - load_data(data) { - this.name = data.name; - - this.starting_scene_index = data.starting_scene_index || 0; - this.state.scene = this.starting_scene_index; - - this.scenes = data.scenes.map(scene => { - const scene_instance = new MtlScene(); - scene_instance.load_data(scene); - scene_instance.on_load_group_complete(() => { - this.load_state.loaded_scenes++; - if (this.load_state.loaded_scenes === this.scenes.length) { - this.on_load_resources_callback && this.on_load_resources_callback(); - } - }) - return scene_instance; - }); - - this.game_ui_options = Object.assign(data.game_ui_options, { - general: { - background_color: data.game_ui_options.general.background_color, - animation_canvas_dimensions: Object.assign(data.game_ui_options.general.animation_canvas_dimensions, { - height: function () { - const split_ratio = this.ratio.split(":").map(n => parseInt(n)); - const factor = split_ratio[1] / split_ratio[0]; - return Number((this.width * factor).toFixed()); - }, - }), - } - }); - } - - /** - * Sets the callback to call when all resources of all scenes are fully loaded. - * @param {Function} callback - */ - on_load_resources(callback) { - this.on_load_resources_callback = callback; - } -} - -module.exports = MtlGame; -},{"./scene":19,"./scene-types":18}],16:[function(require,module,exports){ -"use strict"; - -/** - * A structure to manage a group of object that extends from the ./Loadable class. - * For each handled loadable, increments a loadable_elements state, and once the loadable has finished - * to load whatever it needs to load, the loaded_elements state is incremented. - */ -class LoadableGroup { - constructor() { - this.loadable_elements = 0; - this.loaded_elements = 0; - } - - /** - * Attaches a on_load callback to the Loadable object given as argument. - * Increments the loadable_elements state. - * @param {Loadable} object Any object that extends the Loadable class - */ - add_loadable(object) { - this.loadable_elements++; - /** - * The callback that will be call by the Loadable object when it will have finished to load what it needs to load. - */ - object.on_load(() => { - this.loaded_elements++; - if (this.loadable_elements === this.loaded_elements && this.on_load_group_complete_custom_callback) { - this.on_load_group_complete_custom_callback(); - } - }); - } - - /** - * Defines a custom callback to call when all handled loadable objects have finished loading. - * @param {Function} callback - */ - on_load_group_complete(callback) { - this.on_load_group_complete_custom_callback = callback; - } -} - -module.exports = LoadableGroup; -},{}],17:[function(require,module,exports){ -"use strict"; - -/** - * An abstract type that must be inherited by any object that have a resource to load. - * Can be an image or a sound. - */ -class Loadable { - /** - * Initialization of the actual loadable element. - * @param {HTMLElement} loadable_element Image() or Audio() for example - * @param {String} field_name the name of the field that references the loadable element in the instance. - * @param {String} event_name the name of the event that is dispatched by the loadable - * html element when it has loaded a resource ("load" or "loadeddata") - */ - constructor(loadable_element, field_name, event_name) { - this.loaded = false; - this.init_loadable_element(loadable_element, field_name, event_name); - } - - /** - * Initializes event listeners for the registered loadable_element. - * @param {HTMLElement} loadable_element - * @param {String} field_name the name of the field that references the element - * @param {String} event_name the event to use for resource loading (images uses onload, audio uses onloadeddata ...) - */ - init_loadable_element(loadable_element, field_name, event_name) { - this[field_name] = loadable_element; - - this.load_listener = () => { - this.loaded = true; - this.on_load_callback(); - this.clear(); - }; - - this[field_name].addEventListener(event_name, this.load_listener); - - this.clear = () => { - this[field_name].removeEventListener(event_name, this.load_listener); - } - } - - /** - * Sets a callback to call when the loadable_element has finished to load its resource. - * @param {Function} callback - */ - on_load(callback) { - this.on_load_custom_callback = callback; - } - - /** - * The callback that's called when the loadable element as finished loading its resource - */ - on_load_callback() { - this.loaded = true; - this.on_load_custom_callback && this.on_load_custom_callback(); - } -} - -module.exports = Loadable; -},{}],18:[function(require,module,exports){ -/** - * An enum like object to describe the 2 possible type that can have a MtlScene - */ - -module.exports = { - PLAYABLE: "Playable", - CINEMATIC: "Cinematic", -}; -},{}],19:[function(require,module,exports){ -"use strict"; - -const SCENE_TYPES = require("./scene-types"); -const MtlAnimation = require("./animation"); -const MtlSoundTrack = require("./sound-track"); -const MtlChoice = require("./choice"); -const MtlGameObject = require("./game-object"); -const LoadableGroup = require("./loadable-group"); - -/** - * The type used for the scenes of a MtlGame. - * Extends the LoadableGroup class because it manages multiple objects of the class Loadable. - */ -class MtlScene extends LoadableGroup { - /** - * Initializes the instance with default empty data. - */ - constructor() { - super(); - const default_data = { - name: "", - _type: SCENE_TYPES.PLAYABLE, - animation: new MtlAnimation(), - sound_track: new MtlSoundTrack(), - choices: [], - text_box: "", - game_objects: [], - cinematic_duration: 0, - end_cinematic_options: { - destination_scene_index: -1, - quit: false, - } - }; - - this.name = default_data.name; - this._type = default_data._type; - this.animation = default_data.animation; - this.sound_track = default_data.sound_track; - - this.choices = default_data.choices; - this.text_box = default_data.text_box; - this.game_objects = default_data.game_objects; - - this.cinematic_duration = default_data.cinematic_duration || 0; - this.end_cinematic_options = default_data.end_cinematic_options; - } - - /** - * Populates the instance from a litteral descriptor. - * Creates and populates the instances of Animation, Soundtrack and GameObjects - * @param {Object} data - */ - load_data(data) { - this.name = data.name; - this._type = data._type; - - const animation = new MtlAnimation(); - animation.init(data.animation); - this.animation.clear(); - this.animation = animation; - this.animation.image.src && this.add_loadable(this.animation); - - const sound_track = new MtlSoundTrack(); - sound_track.init(data.sound_track); - this.sound_track.clear(); - this.sound_track = sound_track; - this.sound_track.src && this.add_loadable(this.sound_track); - - this.choices = data.choices.map(c => new MtlChoice(c)); - this.text_box = data.text_box; - this.cinematic_duration = data.cinematic_duration || 0; - - this.game_objects = data.game_objects.map(gob => { - const inst = new MtlGameObject(); - inst.load_data(gob); - this.add_loadable(inst); - return inst; - }); - - this.end_cinematic_options = data.end_cinematic_options; - } -} - -module.exports = MtlScene; -},{"./animation":12,"./choice":13,"./game-object":14,"./loadable-group":16,"./scene-types":18,"./sound-track":20}],20:[function(require,module,exports){ -"use strict"; - -const Loadable = require("./loadable"); - -/** - * The data type used to represent and manage the soundtrack of a scene - */ -class MtlSoundTrack extends Loadable { - constructor() { - super(new Audio(), "audio", "loadeddata"); - this.name = ""; - this.initialized = false; - } - - /** - * Reset the instance to empty values. - */ - reset() { - this.init_loadable_element(new Audio(), "audio", "loadeddata"); - this.name = ""; - this.initialized = false; - } - - /** - * Populates the instance from a litteral descriptor - * @param {Object} data - */ - init(data) { - this.empty = data.src === ""; - this.audio.src = data.src; - this.name = data.name; - this.audio.loop = data._loop; - this.initialized = true; - } - - /** - * Plays the audio resource. - */ - play() { - this.audio.play(); - } - - /** - * Stops the audio player. - */ - stop() { - this.audio.pause(); - this.audio.currentTime = 0; - } - - /** - * Returns the state of the inner audio element - * @returns {Boolean} - */ - is_playing() { - return !this.audio.paused - } -} - -module.exports = MtlSoundTrack; -},{"./loadable":17}],21:[function(require,module,exports){ -"use strict"; - -const ChoicesPanelMetrics = require("./choices-panel-metrics"); - -class CanvasDimensions { - constructor(params) { - this.params = params; - - // The original dimensions of the image part of the canvas - this.image = { width: 0, height: 0 }; - - // The scaled dimensions of the rendering canvas - this.width = 0; - this.height = 0; - - // Scaled dimensions of the inventory zone - this.inventory = { width: 0, height: 0 }; - - // Scaled dimensions of the choices zone - this.choices_panel = { width: 0, height: 0 }; - - // The scale factor that will have been calculated - this.scale_factor = 1; - - this.init_dimensions(); - } - - /** - * Calculate the dimensions from game settings - */ - init_dimensions() { - const { get_game_settings } = this.params; - - const general_settings = get_game_settings("general"); - - const image_h = general_settings.animation_canvas_dimensions.height(); - const image_w = general_settings.animation_canvas_dimensions.width; - const inventory_w = this.get_inventory_base_width(); - const base_w = image_w + inventory_w; - - this.choices_panel_metrics = this.get_choices_panel_metrics(); - - const max_playground_width = window.screen.width - 200; - const max_playground_height = window.screen.height - 200; - - const base_h = image_h + this.choices_panel_metrics.get_total_height(); - const playground_ratio = base_w / base_h; - - const screen_ratio = window.screen.width / window.screen.height; - - // Calculate the width of the canvas to the maximum possible scale - if (screen_ratio > playground_ratio) { - // Screen is more panoramic than game canvas so we scale canvas to the maximum height - const scaled_w = max_playground_height * playground_ratio - this.width = scaled_w <= max_playground_width ? scaled_w : max_playground_width; - } else { - // Image is more panoramic so its scaled to the maximum width - const scaled_h = max_playground_width * (base_h / base_w) - this.height = scaled_h <= max_playground_height ? scaled_h : max_playground_height; - this.width = this.height * playground_ratio; - } - - this.scale_factor = this.width / base_w; - - this.choices_panel_metrics = this.choices_panel_metrics.to_scaled(this.scale_factor); - - this.image = { - width: image_w * this.scale_factor, - height: image_h * this.scale_factor, - }; - - this.inventory = { - width: inventory_w * this.scale_factor, - height: this.image.height, - }; - - this.choices_panel = { - width: this.width, - height: this.choices_panel_metrics.get_total_height(), - }; - - this.height = this.image.height + this.choices_panel.height; - } - - /** - * The width of the inventory panel - * @returns {Integer} - */ - get_inventory_base_width() { - const { get_game_settings } = this.params; - const image_h = get_game_settings("general").animation_canvas_dimensions.height(); - const inventory_style = get_game_settings("inventory"); - const gap = inventory_style.gap; - const h = image_h - (2 * inventory_style.padding); - const gap_h = (inventory_style.rows - 1) * gap; - const gap_w = (inventory_style.columns - 1) * gap - const slot_side = (h - gap_h) / inventory_style.rows; - return (inventory_style.columns * slot_side) - + (2 * inventory_style.padding) - + gap_w; - } - - /** - * The metrics in pixels for each row of choices in the choices panel - * @returns {ChoicesPanelMetrics} - */ - get_choices_panel_metrics() { - const { get_game_settings, get_game_scenes } = this.params; - const choices_panel_settings = get_game_settings("choices_panel"); - const general_settings = get_game_settings("general"); - - const container_padding = choices_panel_settings.container_padding; - const container_width = general_settings.animation_canvas_dimensions.width - + this.get_inventory_base_width() - - (2 * container_padding); - - const scenes_choices = get_game_scenes().map(s => { return { choices: s.choices } }); - - return new ChoicesPanelMetrics({ - settings: choices_panel_settings, - container_width, - scenes_choices, - }); - } -} - -module.exports = CanvasDimensions; -},{"./choices-panel-metrics":22}],22:[function(require,module,exports){ -"use strict"; - -const { get_canvas_char_size } = require("../lib/font-tools"); - -/** - * A class to facilitate the handling of the chocies panel outer and inner dimensions - */ -class ChoicesPanelMetrics { - constructor(params) { - this.params = params; - const { scale_factor = 1 } = this.params; - this.container_width = this.params.container_width * scale_factor; - this.settings = this.get_scaled_settings(scale_factor); - this.scenes_formatted_choices = this.get_scenes_formatted_choices(); - this.container_padding_height = 2 * this.settings.container_padding; - this.rows = this.get_rows_metrics(); - } - - /** - * @returns {object} the choices panel settings object given in the instance parameters - * scaled by the scale_factor parameter - */ - get_scaled_settings(scale_factor) { - const { settings } = this.params; - return Object.assign({ ...settings }, { - font_size: settings.font_size * scale_factor, - active_choice_border_width: settings.active_choice_border_width * scale_factor, - active_choice_rounded_corners_radius: settings.active_choice_rounded_corners_radius * scale_factor, - choice_padding: settings.choice_padding * scale_factor, - container_padding: settings.container_padding * scale_factor, - }); - } - - /** - * @param {Number} row_nb Optional - 1 or 2 if we don't want to get the number of choice rows at its max - * @param {Object[]} scene_choices Optional - The choices that we want to parse, default is all choices. - * @returns {Object[]} An array of 2 objects like {text_height, padding_height} for each choices row - */ - get_rows_metrics(row_nb, scene_choices) { // params will be default if undefined - const { settings } = this; - const max_lines_per_row = this.get_choices_max_lines_per_row(row_nb, scene_choices); - - const char_size = get_canvas_char_size(window.mentalo_drawing_context, settings); - const text_line_h = char_size.text_line_height; - const interline_h = char_size.interline_height; - - const choice_padding_height = 2 * settings.choice_padding; - - const height_text_row_1 = max_lines_per_row[0] * text_line_h - interline_h; - const height_text_row_2 = max_lines_per_row[1] > 0 // If some scene have more than 2 choices, there will be displayed on 2 rows. - ? max_lines_per_row[1] * text_line_h - interline_h - : 0; - - return [ - { - text_height: height_text_row_1, - padding_height: choice_padding_height - }, - { - text_height: height_text_row_2, - padding_height: height_text_row_2 > 0 ? choice_padding_height : 0 - } - ]; - } - - /** - * The maximum number of text lines that each row of choice can have - * @returns {Integer[]} An array with 2 items. 1st is for the upper choices row, second is for the lower one. - */ - get_choices_max_lines_per_row(row_nb, scenes_choices) { - const choices_max_row_nb = row_nb || this.get_max_choices_row_per_scene(); - const formatted_choices = scenes_choices || this.scenes_formatted_choices; - - // get the largest number of text lines per choice row - const max_lines_per_row = [0, 0]; - Array.from({ length: choices_max_row_nb }).forEach((_row, i) => { - const slice_index = i === 0 ? [0, 2] : [2, 4]; - const largest = formatted_choices - .map(s_choices => Math.max(...s_choices - .slice(...slice_index) - .map(c => c.text_lines.length) - )) - .reduce((res, nb) => Math.max(res, nb), 0); - max_lines_per_row[i] = largest; - }); - - return max_lines_per_row; - } - - /** - * @returns {Integer} The maximum number of rows that the choices_panel can have in the loaded game. - */ - get_max_choices_row_per_scene(scenes_slice_index) { - const scenes_choices = scenes_slice_index - ? this.params.scenes_choices.slice(scenes_slice_index, scenes_slice_index + 1) - : this.params.scenes_choices; - - let choices_max_row_nb = 1; - if (Math.max(...scenes_choices.map(s => s.choices.length)) > 2) { - choices_max_row_nb = 2 - } - - return choices_max_row_nb; - } - - /** - * Parses the raw text of each scene choices and returns them with an additional - * text_lines field with the text split into lines ready to be rendered in canvas. - * @typedef FormattedChoice - * @property {String[]} text_lines - * @property {...MtlChoice} - the other MtlChoice fields - * - * @returns {FormattedChoice[]} - */ - get_scenes_formatted_choices() { - const { scenes_choices } = this.params; - const { settings, container_width } = this; - - const char_size = get_canvas_char_size(window.mentalo_drawing_context, settings); - - const { choice_padding } = settings; - const error_offset = .9; - const choice_max_width = ((container_width / 2) - (2 * choice_padding)) * error_offset; - const max_chars_per_row = choice_max_width / char_size.width; - - return scenes_choices.map(s => s.choices.map(c => { - const words = c.text.split(" "); - const lines = [""]; - let line_i = 0; - - words.forEach(w => { - if ((lines[line_i] + w).length >= max_chars_per_row) { - line_i++; - lines.push(""); - } - lines[line_i] = `${lines[line_i]}${lines[line_i] === "" ? "" : " "}${w}`; - }); - return Object.assign({ ...c }, { text_lines: lines }); - })); - } - - /** - * The total calculated height of the choices panel - * @returns {Number} - */ - get_total_height(rows) { - rows = rows || this.rows; - return rows.reduce( - (total, row) => total + row.text_height + row.padding_height, 0 - ) + this.container_padding_height; - } - - /** - * A copy of this instance with values scaled by the given factor - * @param {Number} scale_factor the scaling factor - * @returns {ChoicesPanelMetrics} - */ - to_scaled(scale_factor) { - return new ChoicesPanelMetrics({ ...this.params, scale_factor }); - } - - /** - * The choices panel bounding zone has first been calculated to the maximum, - * but certain scenes may need less space to display their choices. - * This return the only necessary height for a given scene - * @param {MtlChoice[]} scene_choices - * @returns {Number} A bounding box {top, left, right, bottom, width, height} - */ - get_minimum_panel_height_for_scene(scene_index) { - const scene_choices = this.scenes_formatted_choices.slice(scene_index, scene_index + 1); - const choices_row_nb = scene_choices[0].length > 2 ? 2 : 1; - const rows = this.get_rows_metrics(choices_row_nb, scene_choices); - return this.get_total_height(rows); - } - - /** - * Get the metrics for a given row of choices - * @param {Integer} scene_index - * @param {Number} scale_fator - */ - get_one_choices_row_metrics(scene_index, row_index) { - const scene_choices = this.scenes_formatted_choices.slice(scene_index, scene_index + 1); - const rows = this.get_rows_metrics(row_index + 1, scene_choices); - const row = rows[row_index]; - return Object.assign(row, { - text_height: row.text_height, - padding_height: row.padding_height, - }); - } -} - -module.exports = ChoicesPanelMetrics; -},{"../lib/font-tools":7}],23:[function(require,module,exports){ -"use strict"; - -const FrameRateController = require("../lib/frame-rate-controller"); -const { get_optimal_visible_foreground_color } = require("../lib/color-tools"); -const { get_canvas_char_size } = require("../lib/font-tools"); -const SCENE_TYPES = require("../model/scene-types"); -const ChoiceCpt = require("./ui-components/choice-cpt"); -const ChoicesPanelCpt = require("./ui-components/choices-panel-cpt"); -const ClosingIconCpt = require("./ui-components/closing-icon-cpt"); -const GameObjectCpt = require("./ui-components/game-object-cpt"); -const InventoryCpt = require("./ui-components/inventory-cpt"); -const InventoryObjectCpt = require("./ui-components/inventory-object-cpt"); -const InventorySlotCpt = require("./ui-components/inventory-slot-cpt"); -const SceneAnimationCpt = require("./ui-components/scene-animation-cpt"); -const TextBoxCpt = require("./ui-components/text-box-cpt"); -const UserErrorPopup = require("./ui-components/user-error-popup"); -const CanvasDimensions = require("./canvas-dimensions"); - -/** - * The class that handles all interactions with the canvas 2D drawing context to draw the game. - */ -class MtlRender { - constructor(params) { - this.params = params; - const frame_rate = this.params.frame_rate || 30; - this.fps_controller = new FrameRateController(frame_rate); - - this.params.container.style.backgroundColor = this.params.get_game_settings("general").background_color; - this.canvas = document.createElement("canvas"); - this.canvas.style.backgroundColor = "black"; - - window.mentalo_drawing_context = this.canvas.getContext("2d"); - - this.canvas_dimensions = {}; - this.canvas_zones = {}; - this.event_listeners = { - game_objects: [], - inventory: [], - text_box: [], - }; - - this.state = { - user_error_popup: { is_set: false, text: "", on_close: function () { } } - }; - - this.loading_frame = 0; - } - - /** - * Initializes the rendering of the game, creates the components, the canvas zones and requests full screen. - */ - init() { - if (this.params.fullscreen) { - this.set_full_screen(); - } - - this.set_canvas_dimensions(); - this.create_canvas_zones(); - - this.params.container.appendChild(this.canvas); - - const ctx = window.mentalo_drawing_context; - // Obsolete properties for browsers compatibility - ctx.mozImageSmoothingEnabled = false; - ctx.webkitImageSmoothingEnabled = false; - ctx.msImageSmoothingEnabled = false; - ctx.imageSmoothingEnabled = false; - - this.create_components(); - } - - /** - * Removes the event listeners attached to the components. - */ - clear_event_listeners() { - Object.values(this.components).forEach(cpt => { - cpt.clear_event_listeners(); - }); - } - - /** - * Removes the elements rendered as children of the rendering components - * (Example a TextBoxCpt is a child the scene_animation component) - * @param {Object} options can defined a set of constructor names to exclude from clean up - */ - clear_children(options = { exclude: [] }) { - Object.values(this.components).forEach(cpt => { - cpt.clear_children(options); - }); - } - - /** - * Reset the scene text box visibility state to true. - * The text box visibility state is controlled by the scene_animation component and not directly by the text box component. - */ - reset_text_box_visibility() { - this.components.scene_animation.set_text_box_visibility(true) - } - - /** - * Tries to enable the window fullscreen mode. - */ - set_full_screen() { - const body = document.body; - body.requestFullScreen = body.requestFullScreen - || body.webkitRequestFullScreen - || body.msRequestFullscreen - || body.mozRequestFullScreen; - try { - body.requestFullScreen(); - } catch (err) { - console.error(err.message) - } - } - - /** - * Exits the window fullscreen mode - */ - exit_fullscreen() { - if (document.fullscreenElement) { - document.exitFullscreen(); - } - } - - /** - * Updates the error popup state with new values given as argument. - * @param {Object} params Must have a text<String> entry and a on_close<Function> entry. - */ - set_user_error_popup(params) { - this.state.user_error_popup = { - is_set: true, - text: params.text, - stream_text: !!params.stream_text, - on_close: function () { - params.on_close && params.on_close(); - }, - }; - this.clear_event_listeners(); - // Clears children excluding everything but errorpopup - this.clear_children({ exclude: ["TextBoxCpt", "InventoryCpt", "ChoicesPanelCpt", "GameObjectCpt"] }); - } - - /** - * Unsets error popup - */ - clear_user_error_popup() { - this.reset_user_error_popup(); - this.clear_event_listeners(); - this.clear_children({ exclude: ["TextBoxCpt", "InventoryCpt", "ChoicesPanelCpt", "GameObjectCpt"] }); - } - - /** - * Sets error popup to empty values - */ - reset_user_error_popup() { - this.state.user_error_popup = { - is_set: false, - text: "", - on_close: function () { } - }; - } - - /** - * Callback called when error popup is closed - */ - on_close_user_error_popup() { - this.state.user_error_popup.on_close(); - this.clear_user_error_popup(); - } - - /** - * Parses game data and calculates optimal dimensions for the canvas. - */ - set_canvas_dimensions() { - const { get_game_settings, get_game_scenes } = this.params; - - this.canvas_dimensions = new CanvasDimensions({ - get_game_settings, - get_game_scenes, - }); - - this.canvas.width = this.canvas_dimensions.width; - this.canvas.height = this.canvas_dimensions.height; - } - - /** - * @returns {Object} The choices_panel settings scaled to display canvas size - */ - get_scaled_choices_panel_settings() { - return this.canvas_dimensions.choices_panel_metrics.settings; - } - - /** - * @returns {Object} The inventory settings scaled to display canvas size - */ - get_scaled_inventory_settings() { - const { scale_factor } = this.canvas_dimensions; - const { get_game_settings } = this.params; - - const base_settings = get_game_settings("inventory"); - - return Object.assign({ ...base_settings }, { - slot_rounded_corner_radius: base_settings.slot_rounded_corner_radius * scale_factor, - slot_border_width: base_settings.slot_border_width * scale_factor, - gap: base_settings.gap * scale_factor, - padding: base_settings.padding * scale_factor, - }); - } - - /** - * @returns {Object} The text boxes settings scaled to display canvas size - */ - get_scaled_text_boxes_settings() { - const { scale_factor } = this.canvas_dimensions; - const { get_game_settings } = this.params; - const text_box_settings = get_game_settings("text_boxes"); - - return Object.assign({ ...text_box_settings }, { - font_size: text_box_settings.font_size * scale_factor, - padding: text_box_settings.padding * scale_factor, - margin: text_box_settings.margin * scale_factor, - rounded_corners_radius: text_box_settings.rounded_corners_radius * scale_factor, - border_width: text_box_settings.border_width * scale_factor, - }) - } - - /** - * Precalculates the bounding boxes for each canvas zone. - * set_canvas_dimensions() must have been called before this. - */ - create_canvas_zones() { - const metrics = this.canvas_dimensions; - const inventory_settings = this.get_scaled_inventory_settings(); - const choices_panel_settings = this.get_scaled_choices_panel_settings(); - - this.canvas_zones = { - root: { - left: 0, - top: 0, - right: metrics.width, - bottom: metrics.height, - width: metrics.width, - height: metrics.height, - padding: 0, - }, - scene_animation: { - left: 0, - top: 0, - right: metrics.image.width, - bottom: metrics.image.height, - width: metrics.image.width, - height: metrics.image.height, - clear_color: "black", - padding: 0, - }, - inventory: { - left: metrics.image.width, - top: 0, - right: metrics.width, - bottom: metrics.inventory.height, - width: metrics.width - metrics.image.width, - height: metrics.inventory.height, - clear_color: inventory_settings.background_color, - padding: inventory_settings.padding, - }, - choices_panel: { - left: 0, - top: metrics.image.height, - right: metrics.width, - bottom: metrics.height, - width: metrics.width, - height: metrics.height - metrics.image.height, - clear_color: choices_panel_settings.background_color, - padding: choices_panel_settings.container_padding - }, - }; - } - - /** - * @returns {Boolean} true if scene is of type Playable - */ - scene_is_not_cinematic() { - return this.params.get_scene()._type === SCENE_TYPES.PLAYABLE; - } - - /** - * Creates the tree of all the components to render. All component extends the UiComponent class. - * The root component instances (Scene animation, Inventory, Choices panel) will only be created once, - * but children components are functions of their parents so they are recreated when get_children is called on a parent component. - * For example A TextBoxCpt is a children a SceneAnimationCpt, so the text box component will be recreated each time - * scene_animation.get_children() is called. This allow to display those components as functions of dynamical states. - */ - create_components() { - const { get_game_settings, get_inventory, get_scene, get_game_scenes, get_scene_index } = this.params; - const { scale_factor } = this.canvas_dimensions; - const scene_is_not_cinematic = this.scene_is_not_cinematic.bind(this); - this.components = { - scene_animation: new SceneAnimationCpt({ - bounding_zone: this.canvas_zones.scene_animation, - get_animation: () => get_scene().animation, - next_frame_ready: () => this.fps_controller.nextFrameReady(), - get_children: () => { - const scene = get_scene(); - return scene.game_objects.map(o => { - const parent_zone = this.canvas_zones.scene_animation; - const obj_pos = { - x: (o.position.x * scale_factor) + parent_zone.left, - y: (o.position.y * scale_factor) + parent_zone.top, - }; - - const obj_dim = { - w: o.image.width * scale_factor, - h: o.image.height * scale_factor, - }; - - const obj_bounds = { - left: obj_pos.x, - right: obj_pos.x + obj_dim.w, - top: obj_pos.y, - bottom: obj_pos.y + obj_dim.h, - width: obj_dim.w, - height: obj_dim.h, - }; - - const game_objects_cpt_params = { - bounding_zone: obj_bounds, - position: obj_pos, - dimensions: obj_dim, - image: o.image, - is_in_inventory: () => get_inventory().has(o), - }; - - const obj_cpt = new GameObjectCpt(game_objects_cpt_params); - - obj_cpt.add_event_listener({ - event_type: "mousemove", - listener: e => { - if (!get_inventory().has(o)) { - const cursor_is_over_obj = e.offsetX >= obj_bounds.left - && e.offsetX <= obj_bounds.right - && e.offsetY >= obj_bounds.top - && e.offsetY <= obj_bounds.bottom; - obj_cpt.state.draw_border = cursor_is_over_obj; - } - } - }); - - obj_cpt.add_event_listener({ - event_type: "click", - listener: e => { - if (!get_inventory().has(o) - && e.offsetX >= obj_bounds.left - && e.offsetX <= obj_bounds.right - && e.offsetY >= obj_bounds.top - && e.offsetY <= obj_bounds.bottom) { - this.params.on_game_object_click(o); - this.clear_event_listeners(); - this.clear_children({ exclude: "TextBoxCpt" }); - } - } - }); - - return obj_cpt; - }) - .concat(scene.text_box ? [ - (() => { - const text_box_settings = this.get_scaled_text_boxes_settings(); - - // calculation of the minimum bound for textbox, without content - const text_box_bounds = (() => { - const parent_zone = this.canvas_zones.scene_animation; - const padding = text_box_settings.padding; - const margin = text_box_settings.margin; - const left = parent_zone.left + margin; - const right = parent_zone.right - margin; - const bottom = parent_zone.bottom - margin; - const top = bottom - (2 * padding); - return { - left, - right, - top, - bottom, - width: right - left, - height: bottom - top, - } - })(); - - const closing_icon_radius = 10 * scale_factor; - const closing_icon_center = { - x: text_box_bounds.right - (closing_icon_radius / 2), - y: text_box_bounds.top + (closing_icon_radius / 2), - }; - - const closing_icon_params = { - color: text_box_settings.font_color, - radius: closing_icon_radius, - center: closing_icon_center, - background_color: text_box_settings.background_color, - line_width: Math.floor(2 * scale_factor), - bounding_zone: { - left: closing_icon_center.x - closing_icon_radius, - right: closing_icon_center.x + closing_icon_radius, - top: closing_icon_center.y - closing_icon_radius, - bottom: closing_icon_center.y + closing_icon_radius, - width: 2 * closing_icon_radius, - height: 2 * closing_icon_radius, - }, - }; - - const text_box = new TextBoxCpt({ - text: get_scene().text_box, - settings: text_box_settings, - bounding_zone: text_box_bounds, - get_visibility_state: () => this.components.scene_animation.state.text_box_visible, - get_children: () => [new ClosingIconCpt(closing_icon_params)], - }); - - const close_text_box_icon = text_box.children[0]; - text_box.children[0].add_event_listener({ - event_type: "click", - listener: e => { - const bounds = close_text_box_icon.params.bounding_zone; - const click_over_icon = e.offsetX >= bounds.left - && e.offsetX <= bounds.right - && e.offsetY >= bounds.top - && e.offsetY <= bounds.bottom; - - const { scene_animation } = this.components; - - if (click_over_icon && scene_animation.state.text_box_visible) { - scene_animation.set_text_box_visibility(false); - } - } - }); - return text_box; - })(), - ] : []) - .concat(this.state.user_error_popup.is_set ? [ - (() => { - const { text, stream_text } = this.state.user_error_popup; - const use_settings = this.get_scaled_text_boxes_settings(); - const parent_bounds = this.canvas_zones.scene_animation - const char_size = get_canvas_char_size(window.mentalo_drawing_context, use_settings); - - const popup_width = parent_bounds.width / 2; - const padding = char_size.width * 2; - const max_chars_per_row = (popup_width - (2 * padding)) / char_size.width; - const text_lines = [""]; - - let line_i = 0; - text.split(" ").forEach(word => { - if (`${text_lines[line_i]}${word} `.length > max_chars_per_row) { - line_i++; - text_lines.push(""); - } - text_lines[line_i] += `${word} `; - }); - - const line_h = char_size.text_line_height; - const popup_height = (text_lines.length * line_h) + (2 * padding) - char_size.interline_height; - - const background_color = use_settings.background_color; - const text_color = use_settings.font_color; - - const popup_bounds = { - left: parent_bounds.left + (parent_bounds.width / 2) - (popup_width / 2), - top: parent_bounds.top + (parent_bounds.height / 2) - (popup_height / 2), - right: parent_bounds.left + (parent_bounds.width / 2) + (popup_width / 2), - bottom: parent_bounds.top + (parent_bounds.height / 2) - (popup_height / 2) + popup_height, - width: popup_width, - height: popup_height, - clear_color: background_color, - }; - - const popup_params = { - settings: use_settings, - bounding_zone: popup_bounds, - modal_bounds: this.canvas_zones.scene_animation, - clear_modal_color: "#0004", - text_lines, - padding, - stream_text, - get_children: () => { - const center = { - x: popup_bounds.right - 5, - y: popup_bounds.top + 5, - }; - - const radius = 15; - - const closing_icon_bounds = { - left: center.x - radius, - right: center.x + radius, - top: center.y - radius, - bottom: center.y + radius, - width: radius * 2, - height: radius * 2, - }; - - const closing_icon_params = { - color: text_color, - radius, - center, - background_color, - line_width: 2, - bounding_zone: closing_icon_bounds, - }; - - const closing_icon = new ClosingIconCpt(closing_icon_params); - - closing_icon.add_event_listener({ - event_type: "click", - listener: e => { - if (e.offsetX >= closing_icon_bounds.left - && e.offsetX <= closing_icon_bounds.right - && e.offsetY >= closing_icon_bounds.top - && e.offsetY <= closing_icon_bounds.bottom) { - this.on_close_user_error_popup(); - } - } - }); - - return [closing_icon]; - } - }; - const popup = new UserErrorPopup(popup_params); - return popup; - })() - ] : []); - }, - }), - inventory: new InventoryCpt({ - bounding_zone: this.canvas_zones.inventory, - get_inventory, - is_visible: scene_is_not_cinematic, - invisible_clear_color: get_game_settings("general").background_color, - get_children: () => { - const settings = this.get_scaled_inventory_settings(); - - const slots_zone = { - left: this.canvas_zones.inventory.left + settings.padding, - top: this.canvas_zones.inventory.top + settings.padding, - right: this.canvas_zones.inventory.right - settings.padding, - bottom: this.canvas_zones.inventory.bottom - settings.padding, - width: this.canvas_zones.inventory.width - (2 * settings.padding), - height: this.canvas_zones.inventory.height - (2 * settings.padding) - }; - - const gap = settings.gap; - const h = slots_zone.height; - const gap_h = (settings.rows - 1) * gap; - const slot_side = (h - gap_h) / settings.rows; - const slots_total_w = (slot_side * settings.columns) + (gap * (settings.columns - 1)); - const center_slot_x_offset = (slots_zone.width - (slots_total_w)) / 2; - - const slots = []; - let slot_i = 0; - Array.from({ length: settings.rows }).forEach((_row, i) => { - const top = slots_zone.top + (i * (slot_side + gap)); - Array.from({ length: settings.columns }).forEach((_col, j) => { - const left = slots_zone.left + (j * (slot_side + gap)) + center_slot_x_offset; - - const slot_bounds = { - left, - top, - right: left + slot_side, - bottom: top + slot_side, - width: slot_side, - height: slot_side - }; - - const stroke_color = get_optimal_visible_foreground_color(settings.background_color); - - const slot_params = { - bounding_zone: slot_bounds, - stroke_color, - is_visible: scene_is_not_cinematic, - settings, - get_children: () => { - const inventory_object = new InventoryObjectCpt({ - slot_index: slot_i, - is_visible: scene_is_not_cinematic, - settings, - stroke_color, - get_game_object: index => { - const obj = Array.from(get_inventory())[index]; - if (obj) { - const obj_dim = obj.get_dimensions(); - const obj_ratio = obj_dim.width / obj_dim.height; - const scaled_dim = { width: 0, height: 0 }; - const pos = { x: 0, y: 0 }; - if (obj_ratio > 1) { - // object image landscape oriented - scaled_dim.width = slot_side; - scaled_dim.height = slot_side * (obj_dim.height / obj_dim.width); - pos.x = slot_bounds.left; - pos.y = slot_bounds.top + ((slot_side / 2) - (scaled_dim.height / 2)); - } else if (obj_ratio < 1) { - // portrait oriented - scaled_dim.height = slot_side; - scaled_dim.width = slot_side * obj_ratio; - pos.y = slot_bounds.top; - pos.x = slot_bounds.left + ((slot_side / 2) - (scaled_dim.width / 2)); - } else { - // square - scaled_dim.width = slot_side; - scaled_dim.height = slot_side; - pos.x = slot_bounds.left; - pos.y = slot_bounds.top; - } - return { - ref: obj, - position: pos, - dimensions: scaled_dim, - }; - } else { - return undefined; - } - }, - bounding_zone: slot_bounds, - get_children: () => [ - new ClosingIconCpt({ - is_visible: scene_is_not_cinematic, - color: "#bf5e43", // Some kind of red - center: { - x: slot_bounds.left + (slot_side / 2), - y: slot_bounds.top + (slot_side / 2) - }, - radius: slot_side / 4, - line_width: slot_side / 20 > 2 ? slot_side / 20 : 2, - bounding_zone: { ...slot_bounds, clear_color: "rgba(0,0,0,0.2)" }, - }), - ] - }); - - inventory_object.add_event_listener({ - event_type: "mousemove", - listener: e => { - const obj = inventory_object.params.get_game_object(inventory_object.params.slot_index); - const bounds = inventory_object.params.bounding_zone; - - inventory_object.state.draw_inventory_close_icon = inventory_object.params.is_visible() - && !!obj - && get_scene().game_objects.includes(obj.ref) - && ( - e.offsetX >= bounds.left - && e.offsetX <= bounds.right - && e.offsetY >= bounds.top - && e.offsetY <= bounds.bottom - ); - } - }); - - inventory_object.children[0].add_event_listener({ - event_type: "click", - listener: e => { - if (!inventory_object.params.is_visible()) return false; - const obj = inventory_object.params.get_game_object(inventory_object.params.slot_index); - const bounds = inventory_object.params.bounding_zone; - const mouse_over_slot = e.offsetX >= bounds.left - && e.offsetX <= bounds.right - && e.offsetY >= bounds.top - && e.offsetY <= bounds.bottom; - if (!!obj && mouse_over_slot && get_scene().game_objects.includes(obj.ref)) { - this.params.on_drop_inventory_object(obj.ref); - this.clear_event_listeners(); - this.clear_children({ exclude: "TextBoxCpt" }); - } - } - }); - - return [inventory_object]; - }, - }; - - const slot = new InventorySlotCpt(slot_params); - slots.push(slot); - slot_i++; - }); - }); - return slots; - }, - }), - choices_panel: new ChoicesPanelCpt({ - bounding_zone: this.canvas_zones.choices_panel, - minimum_bounding_zone: () => { - const initial_zone = this.canvas_zones.choices_panel; - const minium_height = this.canvas_dimensions.choices_panel_metrics - .get_minimum_panel_height_for_scene( - get_scene_index() - ); - const maximum_height = this.canvas_dimensions.choices_panel_metrics - .get_total_height(); - const dif = maximum_height - minium_height; - - return Object.assign({ ...initial_zone }, { - bottom: initial_zone.bottom - dif, - height: initial_zone.height - dif, - }); - }, - is_visible: scene_is_not_cinematic, - invisible_clear_color: get_game_settings("general").background_color, - get_children: () => Array.from({ - length: this.canvas_dimensions.choices_panel_metrics - .get_max_choices_row_per_scene(get_scene_index()) * 2 - } - ).map((_, i) => { - const choices_settings = this.get_scaled_choices_panel_settings(); - const writable_parent_zone = { // The writable zone of the choices panel. = The choices_panel bounding box excluding the padding. - left: this.canvas_zones.choices_panel.left + choices_settings.container_padding, - right: this.canvas_zones.choices_panel.right - choices_settings.container_padding, - top: this.canvas_zones.choices_panel.top + choices_settings.container_padding, - bottom: this.canvas_zones.choices_panel.bottom - choices_settings.container_padding, - width: 0, - height: 0, - }; - - writable_parent_zone.width = writable_parent_zone.right - writable_parent_zone.left; - writable_parent_zone.height = writable_parent_zone.bottom - writable_parent_zone.top; - - const choice_width = writable_parent_zone.width / 2; - - const { choices_panel_metrics } = this.canvas_dimensions; - const choices_height_per_row = choices_panel_metrics.rows.map(row => row.text_height + row.padding_height); - - const choice_bounds = { - left: writable_parent_zone.left + ((i % 2) * choice_width), // if i % 2 choice is on the right side of the panel - top: writable_parent_zone.top + ((i > 1 ? 1 : 0) * choices_height_per_row[0]), // If index is > 1, choice is on the lower row - right: writable_parent_zone.left + ((i % 2) * choice_width) + choice_width, - bottom: writable_parent_zone.top + ((i > 1 ? 1 : 0) * choices_height_per_row[1]) + choices_height_per_row[i > 1 ? 1 : 0], - width: choice_width, - height: choices_height_per_row[i > 1 ? 1 : 0], - }; - - const choice_cpt = new ChoiceCpt({ - is_visible: scene_is_not_cinematic, - minimum_bounding_zone: () => { - if (!choice_cpt.minimum_bounding_zone) { - const row_index = i > 1 ? 1 : 0; - const metric = this.canvas_dimensions.choices_panel_metrics.get_one_choices_row_metrics( - get_scene_index(), row_index, - ); - - const min_height = metric.text_height + metric.padding_height; - - const dif_upper_row = (() => { - const choices_cpt = this.components.choices_panel.children; - const cpt_index = choices_cpt.indexOf(choice_cpt); - return cpt_index >= 2 - ? choice_bounds.top - choices_cpt[0].params.minimum_bounding_zone().bottom - : 0; - })(); - - choice_cpt.minimum_bounding_zone = Object.assign({ ...choice_bounds }, { - height: min_height, - top: choice_bounds.top - dif_upper_row, - bottom: choice_bounds.top - dif_upper_row + min_height, - }); - } - return choice_cpt.minimum_bounding_zone; - }, - get_formatted_choice: () => { - const scene_choices = this.canvas_dimensions.choices_panel_metrics.scenes_formatted_choices[ - get_game_scenes().indexOf(get_scene()) - ]; - return scene_choices.length - 1 >= i ? scene_choices[i] : undefined; - }, - settings: choices_settings, - }); - - choice_cpt.add_event_listener({ - event_type: "mousemove", - listener: e => { - choice_cpt.state.active = - choice_cpt.params.is_visible() - && !!choice_cpt.params.get_formatted_choice() - && choice_cpt.is_hover(e); - } - }); - - choice_cpt.add_event_listener({ - event_type: "click", - listener: () => { - if (choice_cpt.state.active) { - const choice = choice_cpt.params.get_formatted_choice(); - const success = this.params.on_choice_click(choice); - if (success) { - this.clear_event_listeners(); - this.clear_children(); - } - } - } - }); - - return choice_cpt; - }) - }), - }; - } - - /** - * Draw a loading state on the black screen while some game resources are loading. - * This shouldn't show up a lot except if some resources are really big... - */ - draw_loading() { - const ctx = window.mentalo_drawing_context; - ctx.save(); - ctx.font = '25px monospace'; - ctx.fillStyle = "black"; - ctx.fillRect(0, 0, window.innerWidth, window.innerHeight); - ctx.fillStyle = "white"; - ctx.fillText("Loading", 50, window.innerHeight / 2); - const dots = Array.from({ length: ++this.loading_frame }).map(() => '.').join(''); - ctx.font = '8px monospace'; - ctx.fillText(dots, 50, window.innerHeight / 2 + 20); - ctx.restore(); - } - - /** - * Executes the draw method of each registered component. - */ - draw_game() { - Object.values(this.components).forEach(cpt => cpt.draw()); - } -} - -module.exports = MtlRender; -},{"../lib/color-tools":6,"../lib/font-tools":7,"../lib/frame-rate-controller":8,"../model/scene-types":18,"./canvas-dimensions":21,"./ui-components/choice-cpt":24,"./ui-components/choices-panel-cpt":25,"./ui-components/closing-icon-cpt":26,"./ui-components/game-object-cpt":27,"./ui-components/inventory-cpt":28,"./ui-components/inventory-object-cpt":29,"./ui-components/inventory-slot-cpt":30,"./ui-components/scene-animation-cpt":31,"./ui-components/text-box-cpt":32,"./ui-components/user-error-popup":34}],24:[function(require,module,exports){ -"use strict"; - -const { draw_text_in_bounds } = require("../../lib/text-tools"); -const { draw_rect } = require("../../lib/shape-tools"); -const MtlUiComponent = require("./ui-component"); - -/** - * The rendering component for a scene choice. - */ -class ChoiceCpt extends MtlUiComponent { - constructor(params) { - super(params); - this.set_str_id("ChoiceCpt") - } - - /** - * Draw the choice on the registered 2D drawing context - */ - draw() { - super.draw(); - const { - get_formatted_choice, - settings, - } = this.params; - - const bounding_zone = this.params.minimum_bounding_zone(); - - const writable_zone = { - left: bounding_zone.left + settings.choice_padding, - right: bounding_zone.right - settings.choice_padding, - top: bounding_zone.top + settings.choice_padding, - bottom: bounding_zone.bottom - settings.choice_padding, - width: bounding_zone.width - (2 * settings.choice_padding), - height: bounding_zone.height - (2 * settings.choice_padding), - }; - - const choice = get_formatted_choice(); - - const ctx = window.mentalo_drawing_context; - - if (choice) { - if (this.state.active) { - const { left, top, width, height } = bounding_zone; - - draw_rect(ctx, left, top, width, height, { - fill_color: settings.active_choice_background_color, - rounded_corners_radius: settings.active_choice_rounded_corners_radius, - border: { - width: settings.active_choice_border_width, - color: settings.font_color, - } - }); - } - - draw_text_in_bounds(ctx, choice.text_lines, writable_zone, settings, {}, false); - } - } - - /** - * @param {Event} e - * @returns {Boolean} true if the mouse event is inside the choice bounding box - */ - is_hover(e) { - const bounds = this.params.minimum_bounding_zone(); - return e.offsetX >= bounds.left - && e.offsetX <= bounds.right - && e.offsetY >= bounds.top - && e.offsetY <= bounds.bottom; - } -} - -module.exports = ChoiceCpt; -},{"../../lib/shape-tools":9,"../../lib/text-tools":10,"./ui-component":33}],25:[function(require,module,exports){ -"use strict"; - -const MtlUiComponent = require("./ui-component"); - -/** - * The rendering component for the game choices panel - */ -class ChoicesPanelCpt extends MtlUiComponent { - constructor(params) { - super(params); - this.set_str_id("ChoicesPanelCpt") - } - - /** - * Draw the choices panel and the choices as children components on the registered 2D drawing context - */ - draw() { - super.draw(); - if (this.params.is_visible()) { - this.clear_bounding_zone({ clear_color: this.params.invisible_clear_color }); - this.clear_bounding_zone({ use_bounding_zone: this.params.minimum_bounding_zone() }) - this.draw_children(); - } else { - this.clear_bounding_zone({ clear_color: this.params.invisible_clear_color }) - } - } -} - -module.exports = ChoicesPanelCpt; -},{"./ui-component":33}],26:[function(require,module,exports){ -"use strict"; - -const MtlUiComponent = require("./ui-component"); - -/** - * A rendering component that displays a closing icon (a cross inside a circle) - */ -class ClosingIconCpt extends MtlUiComponent { - constructor(params) { - super(params); - this.set_str_id("ClosingIconCpt") - } - - /** - * Uses standard vectorial drawing methods to draw a circle and a cross at - * given coordinates on the registered 2D drawing context. - */ - draw() { - super.draw(); - this.clear_bounding_zone(); - const { color, center, radius = 5, line_width = 2, background_color = "rgba(0,0,0,0)" } = this.params; - const ctx = window.mentalo_drawing_context; - const padding = radius / 1.5; - - const cross_coords = { - left: center.x - radius + padding, - right: center.x + radius - padding, - top: center.y - radius + padding, - bottom: center.y + radius - padding, - }; - - ctx.fillStyle = background_color; - ctx.beginPath(); - ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI); - ctx.fill(); - - ctx.strokeStyle = color; - ctx.lineWidth = line_width; - ctx.beginPath(); - ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI); - ctx.stroke(); - - ctx.beginPath(); - ctx.moveTo(cross_coords.left, cross_coords.top); - ctx.lineTo(cross_coords.right, cross_coords.bottom); - ctx.moveTo(cross_coords.left, cross_coords.bottom) - ctx.lineTo(cross_coords.right, cross_coords.top); - ctx.stroke(); - } -} - -module.exports = ClosingIconCpt; -},{"./ui-component":33}],27:[function(require,module,exports){ -"use strict"; - -const MtlUiComponent = require("./ui-component"); - -/** - * A component that draws a GameObject - */ -class GameObjectCpt extends MtlUiComponent { - constructor(params) { - super(params); - this.set_str_id("GameObjectCpt") - } - - /** - * Draws the image of the object on the canvas. - */ - draw() { - super.draw(); - const { position, dimensions, image, is_in_inventory } = this.params; - if (is_in_inventory()) { - return; - } - - const ctx = window.mentalo_drawing_context; - - ctx.drawImage( - image, - 0, 0, - image.width, image.height, - position.x, position.y, - dimensions.w, dimensions.h, - ); - - if (this.state.draw_border) { - ctx.lineWidth = 1; - ctx.strokeStyle = "rgba(180, 180, 180, 0.5)"; - ctx.strokeRect(position.x - 5, position.y - 5, dimensions.w + 10, dimensions.h + 10); - } - } -} - -module.exports = GameObjectCpt; -},{"./ui-component":33}],28:[function(require,module,exports){ -"use strict"; - -const MtlUiComponent = require("./ui-component"); - -/** - * The component that draws the inventory panel - */ -class InventoryCpt extends MtlUiComponent { - constructor(params) { - super(params); - this.set_str_id("InventoryCpt") - } - - /** - * Draws the panel on the canvas, T - * the grid slots and the game object images are draws as children components (InventorySlotCpt and InventoryObjectCpt) - */ - draw() { - super.draw(); - if (this.params.is_visible()) { - this.clear_bounding_zone(); - this.draw_children(); - } else { - this.clear_bounding_zone({ clear_color: this.params.invisible_clear_color }) - } - } -} - -module.exports = InventoryCpt; -},{"./ui-component":33}],29:[function(require,module,exports){ -"use strict"; - -const { draw_rect } = require("../../lib/shape-tools"); -const MtlUiComponent = require("./ui-component"); - -/** - * The component to display a game object image inside a grid slot of the inventory panel. - */ -class InventoryObjectCpt extends MtlUiComponent { - constructor(params) { - super(params); - this.set_str_id("InventoryObjectCpt") - } - - /** - * If a game object exists for the slot bound to this instance, its image will be drawn cropped inside a bounding rectangle. - * When a game object is hovered in the inventory, the draw_inventory_closing_icon is updated and a red cross will be drawn - * on top of the object image. If the cross get's clicked the object is removed from inventory. - */ - draw() { - super.draw(); - const { get_game_object, slot_index, settings, bounding_zone, stroke_color } = this.params; - const game_object = get_game_object(slot_index); - if (game_object) { - const ctx = window.mentalo_drawing_context; - - const image = game_object.ref.image; - draw_rect(ctx, bounding_zone.left, bounding_zone.top, bounding_zone.width, bounding_zone.height, { - fill_image: { - src: image, - dw: game_object.dimensions.width, - dh: game_object.dimensions.height, - dx: game_object.position.x, - dy: game_object.position.y, - }, - rounded_corners_radius: settings.slot_rounded_corner_radius, - border: { - width: settings.slot_border_width, - color: stroke_color, - }, - }); - - if (this.state.draw_inventory_close_icon) { - this.draw_children(); // drop object icon - } - } - } -} - -module.exports = InventoryObjectCpt; -},{"../../lib/shape-tools":9,"./ui-component":33}],30:[function(require,module,exports){ -"use strict"; - -const { draw_rect } = require("../../lib/shape-tools"); -const MtlUiComponent = require("./ui-component"); - -/** - * The component to draw a grid slot in the inventory panel. - */ -class InventorySlotCpt extends MtlUiComponent { - constructor(params) { - super(params); - this.set_str_id("InventorySlotCpt"); - } - - /** - * Draws a rectangle as the grid slot. - * Calls draw_children to draw the image of the game object stored in that slot if there is one. - */ - draw() { - super.draw(); - const { bounding_zone, stroke_color, settings } = this.params; - const { left, top, width, height } = bounding_zone; - const ctx = window.mentalo_drawing_context; - - draw_rect(ctx, left, top, width, height, { - fill_color: "rgba(0,0,0,0)", - rounded_corners_radius: settings.slot_rounded_corner_radius, - border: { - width: settings.slot_border_width, - color: stroke_color, - } - }); - - this.draw_children(); - } -} - -module.exports = InventorySlotCpt; -},{"../../lib/shape-tools":9,"./ui-component":33}],31:[function(require,module,exports){ -"use strict"; - -const MtlUiComponent = require("./ui-component"); - -/** - * The component that draws the scene animation. - */ -class SceneAnimationCpt extends MtlUiComponent { - constructor(params) { - super(params); - this.set_str_id("SceneAnimationCpt"); - this.framecount = 0; - // This state controls the visibility of the text box child component. - this.state.text_box_visible = true; - } - - /** - * Sets the text_box_visibility state. - * @param {Boolean} value - */ - set_text_box_visibility(value) { - this.state.text_box_visible = value; - } - - /** - * Increments the animation frame count - */ - update_framecount() { - this.framecount = this.framecount + 1 <= Number.MAX_SAFE_INTEGER ? this.framecount + 1 : 0; - } - - /** - * Draw the scene animation on the canvas at the frame given by the animation state - * and draw each child component like text box, game objects and error popup. - */ - draw() { - super.draw(); - const { bounding_zone, get_animation, next_frame_ready } = this.params; - if (next_frame_ready()) { - this.clear_bounding_zone(); - const ctx = window.mentalo_drawing_context; - - const animation = get_animation(); - animation.update_frame(this.framecount); - const dim = animation.dimensions; - const w = dim.width; - const h = dim.height; - const offsetX = animation.frame * w; - - const cw = bounding_zone.width; - const ch = bounding_zone.height; - - // center the image - if (!animation.canvas_precalc) { - animation.canvas_precalc = { - dx: bounding_zone.left, - dy: bounding_zone.top, - dw: cw, - dh: ch, - }; - - if (w / h > cw / ch) { - // image is more panoramic than canvas - animation.canvas_precalc.dw = cw; - animation.canvas_precalc.dh = cw * (h / w); - // center vertically - animation.canvas_precalc.dy = bounding_zone.top + ((ch - animation.canvas_precalc.dh) / 2); - } else if (cw / ch > w / h) { - // canvas is more panoramic - animation.canvas_precalc.dh = ch; - animation.canvas_precalc.dw = ch * (w / h); - // center horizontally - animation.canvas_precalc.dx = bounding_zone.left + ((cw - animation.canvas_precalc.dw) / 2); - } else { - // same ratio - animation.canvas_precalc.dh = ch; - animation.canvas_precalc.dw = cw; - } - } - - const { dx, dy, dw, dh } = animation.canvas_precalc; - ctx.drawImage( - animation.image, - offsetX, 0, - w, h, - dx, dy, - dw, dh - ); - - this.draw_children(); - this.update_framecount(); - } - } -} - -module.exports = SceneAnimationCpt; -},{"./ui-component":33}],32:[function(require,module,exports){ -"use strict"; - -const { draw_text_in_bounds } = require("../../lib/text-tools"); -const { get_canvas_char_size } = require("../../lib/font-tools"); -const { draw_rect } = require("../../lib/shape-tools"); -const MtlUiComponent = require("./ui-component"); - -/** - * The component that handles the displaying of a scene text boxe. - */ -class TextBoxCpt extends MtlUiComponent { - constructor(params) { - super(params); - this.set_str_id("TextBoxCpt"); - this.state.text = {}; - } - - /** - * Initialize precalculations for the text box, text dimensions, split lines, etc. - */ - init() { - const { text, settings, bounding_zone } = this.params; - - const char_size = get_canvas_char_size(window.mentalo_drawing_context, settings); - - const line_height = char_size.text_line_height; - const error_offset = 3 * char_size.width; - const chars_per_line = (bounding_zone.width - error_offset) / char_size.width; - const output_lines = []; - const lines = text.split("\n"); - - lines.forEach(line => { - const res = [""]; - let target_i = 0; - for (const word of line.split(" ")) { - if ((res[target_i] + word + " ").length > chars_per_line) { - res.push(""); - target_i++; - } - res[target_i] += word + " "; - } - - res.forEach(l => output_lines.push(l)); - }); - - const text_height = output_lines.length * line_height - char_size.interline_height; - - const updated_bounds = Object.assign({ ...bounding_zone }, { - height: bounding_zone.height + text_height, - top: bounding_zone.top - text_height, - }); - - this.state.text = { - lines: output_lines, - line_height, - bounding_zone: updated_bounds, - }; - - // Update closing icon position - const closing_icon = this.children[0]; - let closing_icon_bounds = closing_icon.params.bounding_zone; - closing_icon_bounds = Object.assign(closing_icon_bounds, { - top: closing_icon_bounds.top - text_height, - bottom: closing_icon_bounds.bottom - text_height, - }); - - closing_icon.params.center.y -= text_height; - - this.state.initialized = true; - } - - /** - * Draw the text box and the text inside of it. - * The text is not drawn at once, the characters are drawn one by one at each frame - * and a local text.stream state is updated until text is complete. - */ - draw() { - if (!this.params.get_visibility_state()) { - return; - } - - super.draw(); - - const { settings } = this.params; - - if (!this.state.initialized) { - this.init(); - } - - const ctx = window.mentalo_drawing_context; - const { lines, bounding_zone } = this.state.text; - - const box_pos = { x: bounding_zone.left, y: bounding_zone.top }; - const box_width = bounding_zone.width; - const box_height = bounding_zone.height; - - draw_rect(ctx, box_pos.x, box_pos.y, box_width, box_height, { - fill_color: settings.background_color, - rounded_corners_radius: settings.rounded_corners_radius, - border: { - width: settings.border_width, - color: settings.font_color, - }, - }); - - const complete = draw_text_in_bounds(ctx, lines, bounding_zone, settings, this.state.text, true); - - if (complete) { // draw closing_icon if text is entirely displayed - this.draw_children(); - } - } -} - -module.exports = TextBoxCpt; -},{"../../lib/font-tools":7,"../../lib/shape-tools":9,"../../lib/text-tools":10,"./ui-component":33}],33:[function(require,module,exports){ -"use strict"; - -const { draw_rect } = require("../../lib/shape-tools"); - -// const { draw_rect } = require("../lib/shape-tools"); - -/** - * A generic class that must be extended by all components registered in MtlRender.components. - * It provides helping methods to handle event listeners and children components. - */ -class MtlUiComponent { - constructor(params) { - this.set_str_id(); - this.params = params; - this.params.event_listeners = this.params.event_listeners || []; - this.params.get_children = this.params.get_children || (() => []); - this.params.is_visible = this.params.is_visible || (() => true); - this.children = []; - this.children_set = false; - this.set_children(); - this.state = { - event_listeners_initialized: false, - }; - - this.init_event_listeners(); - } - - /** - * Provide a identifier for the object. Default is contructor name. - * Should be called from child class to set a custom id. - * @param {String} str - */ - set_str_id(str = this.constructor.name) { - this.str_id = str; - } - - /** - * Draws a black rectangle on all the surface of the bounding zone defined for this component - * @param {Object} options can provide a clear_color<String> rgba value - */ - clear_bounding_zone(options = {}) { - let { bounding_zone } = this.params; - const { use_bounding_zone } = options; - bounding_zone = use_bounding_zone || bounding_zone; - const { left, top, width, height } = bounding_zone; - draw_rect( - window.mentalo_drawing_context, - left, top, width, height, - { - fill_color: options.clear_color || bounding_zone.clear_color || "rgba(0,0,0,0)" - } - ); - } - - /** - * Removes the event listeners attached to this component and to children components. - * @param {Object} options can pass a recursive flag wether children must be cleared or not. Default is true. - */ - clear_event_listeners(options = { recursive: true }) { - this.params.event_listeners.forEach(ref => { - window.removeEventListener(ref.event_type, ref.listener); - }); - - if (options.recursive) { - this.children.forEach(child => { - child.clear_event_listeners(); - }); - } - - this.state.event_listeners_initialized = false; - } - - /** - * Removes children components from this component. - * Options can define some component to exlude identifying them by their str_id. - * The excluded component will be kept as persistent components. - * @param {Object} options - */ - clear_children(options) { - const { exclude } = options; - const persistent_children = []; - - this.children.forEach(child => { - if (exclude.includes(child.str_id)) { - persistent_children.push(child); - } - }); - - this.children = persistent_children; - this.children_set = false; - } - - /** - * Register an array of children components in this component. - * This is called when chlidren_set is false. Which happend at initialization and when clear_children is called. - * Doing this allows saving the get_children() calculation at each frame. - * This children components are concatenated recursively with their own children components. - */ - set_children() { - this.children = this.children.concat( - this.params.get_children() - .filter(child => { - // If this child has been kept as a persistent component, keep it as it. - const keep_previous_child = this.children.find(ch => ch.str_id === child.str_id); - return !keep_previous_child; - }) - ); - - this.children_set = true; - } - - /** - * Attaches a event listener to this component - * @param {Object} obj A descriptor of the event listener like : - * { - * event_type: "click", - * listener: e => { - * ... something to do on click that component - * } - * } - */ - add_event_listener(obj) { - const len = this.params.event_listeners.push(obj); - const ref = this.params.event_listeners[len - 1]; - window.addEventListener(ref.event_type, ref.listener); - } - - /** - * Attaches the event listeners given as parameter to the component - */ - init_event_listeners() { - const { event_listeners } = this.params; - event_listeners.forEach(obj => this.add_event_listener(obj)); - this.state.event_listeners_initialized = true - } - - /** - * Call the draw method of the children component - */ - draw_children() { - if (!this.children_set) { - this.set_children(); - } - this.children.forEach(c => c.draw()); - } - - /** - * Initializes the event listeners if it's not already done. - * This must be called by the inherited call draw method. - */ - draw() { - if (!this.state.event_listeners_initialized) { - this.init_event_listeners(); - } - } -} - -module.exports = MtlUiComponent; -},{"../../lib/shape-tools":9}],34:[function(require,module,exports){ -"use strict"; - -const { draw_text_in_bounds } = require("../../lib/text-tools"); -const { draw_rect } = require("../../lib/shape-tools"); -const MtlUiComponent = require("./ui-component"); - -/** - * A component to display an error message in a popup - */ -class UserErrorPopup extends MtlUiComponent { - constructor(params) { - super(params); - this.state = { - text: {}, - }; - - this.set_str_id("UserErrorPopup"); - } - - /** - * Draw the popup to the canvas. - * Draws the box, then draw the text lines. - * draw_children is for the closing_icon. - */ - draw() { - super.draw(); - const { text_lines, bounding_zone, padding, settings, modal_bounds, clear_modal_color } = this.params; - this.clear_bounding_zone({ use_bounding_zone: modal_bounds, clear_color: clear_modal_color }); - - const ctx = window.mentalo_drawing_context; - const { left, top, width, height } = bounding_zone; - - draw_rect(ctx, left, top, width, height, { - fill_color: settings.background_color, - rounded_corners_radius: settings.rounded_corners_radius, - border: { - width: settings.border_width, - color: settings.font_color, - }, - }); - - const complete = draw_text_in_bounds( - ctx, - text_lines, - bounding_zone, - Object.assign({ ...settings }, padding), - this.state.text, - !!this.params.stream_text - ); - - if (complete) { - this.draw_children(); - } - } -} - -module.exports = UserErrorPopup; -},{"../../lib/shape-tools":9,"../../lib/text-tools":10,"./ui-component":33}],35:[function(require,module,exports){ -const supported_locales = ["en", "fr", "es"]; - -/** - * Translations for the default error messages. - */ - -const translations = { - "Some scenes have an empty image, game cannot be loaded": { - fr: "Certaines scènes ont une image vide, le jeu ne peut pas être chargé", - es: "Algunas escenas tienen una imagen vacÃa, el juego no se puede cargar" - }, - "Destination scene has not been set.": { - fr: "La scène de destination n'a pas été définie.", - es: "La escena de destino no ha sido definida.", - }, - "Next scene has not been set.": { - fr: "La scène suivante n'a pas été définie", - es: "La siguiente escena no ha sido definida.", - }, -}; - -function get_translated(str, locale) { - return translations[str] && locale !== "en" && supported_locales.includes(locale) - ? translations[str][locale] - : str; -} - -module.exports = { - get_translated, - supported_locales, -}; -},{}],36:[function(require,module,exports){ -"use strict"; - -module.exports = { - register_key: "objectToHtmlRender", - - /** - * Register "this" as a window scope accessible variable named by the given key, or default. - * @param {String} key - */ - register(key) { - const register_key = key || this.register_key; - window[register_key] = this; - }, - - /** - * This must be called before any other method in order to initialize the lib. - * It provides the root of the rendering cycle as a Javascript object. - * @param {Object} renderCycleRoot A JS component with a render method. - */ - setRenderCycleRoot(renderCycleRoot) { - this.renderCycleRoot = renderCycleRoot; - }, - - event_name: "objtohtml-render-cycle", - - /** - * Set a custom event name for the event that is trigger on render cycle. - * Default is "objtohtml-render-cycle". - * @param {String} evt_name - */ - setEventName(evt_name) { - this.event_name = evt_name; - }, - - /** - * This is the core agorithm that read an javascript Object and convert it into an HTML element. - * @param {Object} obj The object representing the html element must be formatted like: - * { - * tag: String // The name of the html tag, Any valid html tag should work. div, section, br, ul, li... - * xmlns: String // This can replace the tag key if the element is an element with a namespace URI, for example an <svg> tag. - * See https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS for more information - * style_rules: Object // a object providing css attributes. The attributes names must be in JS syntax, - * like maxHeight: "500px", backgrouncColor: "#ff2d56", margin: 0, etc. - * contents: Array or String // This reprensents the contents that will be nested in the created html element. - * <div>{contents}</div> - * The contents can be an array of other objects reprenting elements (with tag, contents, etc) - * or it can be a simple string. - * // All other attributes will be parsed as html attributes. They can be anything like onclick, href, onchange, title... - * // or they can also define custom html5 attributes, like data, my_custom_attr or anything. - * } - * @returns {HTMLElement} The output html node. - */ - objectToHtml(obj) { - if (!obj) return document.createElement("span"); // in case of invalid input, don't block the whole process. - const objectToHtml = this.objectToHtml.bind(this); - const { tag, xmlns } = obj; - const node = xmlns !== undefined ? document.createElementNS(xmlns, tag) : document.createElement(tag); - const excludeKeys = ["tag", "contents", "style_rules", "state", "xmlns"]; - - Object.keys(obj) - .filter(attr => !excludeKeys.includes(attr)) - .forEach(attr => { - switch (attr) { - case "class": - node.classList.add(...obj[attr].split(" ").filter(s => s !== "")); - break; - case "on_render": - if (!obj.id) { - node.id = `${btoa(JSON.stringify(obj).slice(0, 127)).replace(/\=/g, '')}${window.performance.now()}`; - } - if (typeof obj.on_render !== "function") { - console.error("The on_render attribute must be a function") - } else { - this.attach_on_render_callback(node, obj.on_render); - } - break; - default: - if (xmlns !== undefined) { - node.setAttributeNS(null, attr, obj[attr]) - } else { - node[attr] = obj[attr]; - } - } - }); - if (obj.contents && typeof obj.contents === "string") { - node.innerHTML = obj.contents; - } else { - obj.contents && - obj.contents.length > 0 && - obj.contents.forEach(el => { - switch (typeof el) { - case "string": - node.innerHTML = el; - break; - case "object": - if (xmlns !== undefined) { - el = Object.assign(el, { xmlns }) - } - node.appendChild(objectToHtml(el)); - break; - } - }); - } - - if (obj.style_rules) { - Object.keys(obj.style_rules).forEach(rule => { - node.style[rule] = obj.style_rules[rule]; - }); - } - - return node; - }, - - on_render_callbacks: [], - - /** - * This is called if the on_render attribute of a component is set. - * @param {HTMLElement} node The created html element - * @param {Function} callback The callback defined in the js component to render - */ - attach_on_render_callback(node, callback) { - const callback_handler = { - callback: e => { - if (e.detail.outputNode === node || e.detail.outputNode.querySelector(`#${node.id}`)) { - callback(node); - const handler_index = this.on_render_callbacks.indexOf((this.on_render_callbacks.find(cb => cb.node === node))); - if (handler_index === -1) { - console.warn("A callback was registered for node with id " + node.id + " but callbacck handler is undefined.") - } else { - window.removeEventListener(this.event_name, this.on_render_callbacks[handler_index].callback) - this.on_render_callbacks.splice(handler_index, 1); - } - } - }, - node, - }; - - const len = this.on_render_callbacks.push(callback_handler); - window.addEventListener(this.event_name, this.on_render_callbacks[len - 1].callback); - }, - - /** - * If a main element exists in the html document, it will be used as rendering root. - * If not, it will be created and inserted. - */ - renderCycle: function () { - const main_elmt = document.getElementsByTagName("main")[0] || (function () { - const created_main = document.createElement("main"); - document.body.appendChild(created_main); - return created_main; - })(); - - this.subRender(this.renderCycleRoot.render(), main_elmt, { mode: "replace" }); - }, - - /** - * This method behaves like the renderCycle() method, but rather that starting the rendering cycle from the root component, - * it can start from any component of the tree. The root component must be given as the first argument, the second argument be - * be a valid html element in the dom and will be used as the insertion target. - * @param {Object} object An object providing a render method returning an object representation of the html to insert - * @param {HTMLElement} htmlNode The htlm element to update - * @param {Object} options can be used the define the insertion mode, default is set to "append" and can be set to "override", - * "insert-before" (must be defined along with an insertIndex key (integer)), - * "adjacent" (must be defined along with an insertLocation key (String)), "replace" or "remove". - * In case of "remove", the first argument "object" is not used and can be set to null, undefined or {}... - */ - subRender(object, htmlNode, options = { mode: "append" }) { - let outputNode = null; - - const get_insert = () => { - outputNode = this.objectToHtml(object); - return outputNode; - }; - - switch (options.mode) { - case "append": - htmlNode.appendChild(get_insert()); - break; - case "override": - htmlNode.innerHTML = ""; - htmlNode.appendChild(get_insert()); - break; - case "insert-before": - htmlNode.insertBefore(get_insert(), htmlNode.childNodes[options.insertIndex]); - break; - case "adjacent": - /** - * options.insertLocation must be one of: - * - * afterbegin - * afterend - * beforebegin - * beforeend - */ - htmlNode.insertAdjacentHTML(options.insertLocation, get_insert()); - break; - case "replace": - htmlNode.parentNode.replaceChild(get_insert(), htmlNode); - break; - case "remove": - htmlNode.remove(); - break; - } - const evt_name = this.event_name; - const event = new CustomEvent(evt_name, { - detail: { - inputObject: object, - outputNode, - insertOptions: options, - targetNode: htmlNode, - } - }); - - window.dispatchEvent(event); - }, -}; -},{}],37:[function(require,module,exports){ -"use strict"; - -class ImageCarousel { - constructor(props) { - this.props = props; - this.id = this.props.images.join("").replace(/\s\./g); - this.state = { - showImageIndex: 0, - }; - this.RUN_INTERVAL = 5000; - this.props.images.length > 1 && this.run(); - } - - run() { - this.runningInterval = setInterval(() => { - let { showImageIndex } = this.state; - const { images } = this.props; - this.state.showImageIndex = showImageIndex < images.length - 1 ? ++showImageIndex : 0; - this.refreshImage(); - }, this.RUN_INTERVAL); - } - - setImageIndex(i) { - clearInterval(this.runningInterval); - this.state.showImageIndex = i; - this.refreshImage(); - } - - refreshImage() { - obj2htm.subRender(this.render(), document.getElementById(this.id), { - mode: "replace", - }); - } - - render() { - const { showImageIndex } = this.state; - const { images } = this.props; - return { - tag: "div", - id: this.id, - class: "image-carousel", - contents: [ - { - tag: "img", - property: "image", - alt: `image carousel ${images[showImageIndex].replace(/\.[A-Za-z]+/, "")}`, - src: images[showImageIndex], - }, - images.length > 1 && { - tag: "div", - class: "carousel-bullets", - contents: images.map((_, i) => { - const active = showImageIndex === i; - return { - tag: "span", - class: `bullet ${active ? "active" : ""}`, - onclick: this.setImageIndex.bind(this, i), - }; - }), - }, - ], - }; - } -} - -module.exports = ImageCarousel; - -},{}],38:[function(require,module,exports){ -"use strict"; - -const { fetch_json_or_error_text } = require("./fetch"); - -function getArticleBody(text) { - return text.replaceAll("\n", "<br/>"); -} - -function getArticleDate(date) { - return `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`; -} - -function loadArticles(category, locale) { - return fetch_json_or_error_text(`/articles/${category}/${locale}`); -} - -module.exports = { - loadArticles, - getArticleBody, - getArticleDate, -}; - -},{"./fetch":39}],39:[function(require,module,exports){ -"use strict"; - -function fetchjson(url) { - return new Promise((resolve, reject) => { - fetch(url) - .then(r => r.json()) - .then(r => resolve(r)) - .catch(e => reject(e)); - }); -} - -function fetchtext(url) { - return new Promise((resolve, reject) => { - fetch(url) - .then(r => r.text()) - .then(r => resolve(r)) - .catch(e => reject(e)); - }); -} - -async function fetch_json_or_error_text(url, options = {}) { - return new Promise((resolve, reject) => { - fetch(url, options).then(async res => { - if (res.status >= 400 && res.status < 600) { - reject(await res.text()); - } else { - resolve(await res.json()); - } - }) - }) -} - -module.exports = { - fetchjson, - fetchtext, - fetch_json_or_error_text, -}; - -},{}],40:[function(require,module,exports){ -"use strict"; -const translator = require("ks-cheap-translator"); -const { translations_url } = require("../../constants"); - -class WebPage { - constructor(args) { - Object.assign(this, args); - - if (!this.id) { - this.id = "webpage-" + performance.now(); - } - - translator.init({ - translations_url, - supported_languages: ["fr", "en"], - }).then(this.refresh_all.bind(this)); - } - - refresh() { - obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace" }) - } - - refresh_all() { - obj2htm.renderCycle() - } -} - -module.exports = WebPage; -},{"../../constants":3,"ks-cheap-translator":4}],41:[function(require,module,exports){ -"use strict"; - -const { images_url } = require("../../../../../admin-frontend/src/constants"); -const { data_url } = require("../../../../constants"); -const ImageCarousel = require("../../../generic-components/image-carousel"); -const { getArticleBody } = require("../../../lib/article-utils"); -const { fetch_json_or_error_text } = require("../../../lib/fetch"); -const { MentaloEngine } = require("mentalo-engine"); -const translator = require("ks-cheap-translator"); -const t = translator.trad.bind(translator); - -class GameArticle { - constructor(props) { - this.props = props; - this.parse_body(); - } - - parse_body() { - let body = getArticleBody(this.props.body); - const play_btn_regex = /\[PLAY_BUTTON\s\{.+\}\]/g; - const found_play_buttons = body.match(play_btn_regex); - if (found_play_buttons) { - this.build_play_button(JSON.parse(found_play_buttons[0].replace(/[\[\]PLAY_BUTTON\s]/g, ""))); - body = body.replace(play_btn_regex, ""); - } - this.body = body; - } - - build_play_button(button_data) { - this.render_play_button = { - tag: "button", - class: "play-button", - contents: t("Jouer"), - onclick: this.handle_click_play.bind(this, button_data.filename, button_data.engine) - }; - } - - load_and_run_mentalo_game(filename, button_element) { - const button_text = button_element.innerHTML; - button_element.innerHTML = "Loading ..."; - button_element.style.pointerEvents = "none"; - - fetch_json_or_error_text(`${data_url}/${filename}`) - .then(game_data => { - const container = document.createElement("div"); - container.style.position = "fixed"; - container.style.top = 0; - container.style.left = 0; - container.style.right = 0; - container.style.bottom = 0; - container.style.zIndex = 10; - container.style.display = "flex"; - container.style.justifyContent = "center"; - container.style.alignItems = "center"; - - container.id = "kuadrado-tmp-game-player-container"; - document.body.appendChild(container); - document.body.style.overflow = "hidden"; - - const engine = new MentaloEngine({ - game_data, - fullscreen: true, - frame_rate: 30, - container, - on_quit_game: () => { - container.remove(); - document.body.style.overflow = "visible"; - } - }); - - engine.init(); - engine.run_game(); - }) - .catch(err => console.log(err)) - .finally(() => { - button_element.innerHTML = button_text; - button_element.style.pointerEvents = "unset"; - }); - } - - handle_click_play(filename, engine, e) { - switch (engine) { - case "mentalo": - this.load_and_run_mentalo_game(filename, e.target); - break; - default: - console.log("Error, unkown engine") - return; - } - } - - render() { - const { - title, - subtitle, - images, - details, - } = this.props; - - return { - tag: "article", - typeof: "VideoGame", - additionalType: "Article", - class: "game-article", - contents: [ - { - tag: "h2", - property: "name", - class: "game-title", - contents: title, - }, - { - tag: "div", - class: "game-banner", - contents: [ - { tag: "img", class: "pixelated", src: `${images_url}/${images[0]}` }, - ], - }, - { - tag: "h3", - class: "game-subtitle", - contents: subtitle, - property: "alternativeHeadline", - }, - { - tag: "div", - class: "game-description", - property: "description", - contents: [{ tag: "p", style_rules: { margin: 0 }, contents: this.body }] - .concat(this.render_play_button - ? [this.render_play_button] - : []), - }, - new ImageCarousel({ images: images.map(img => `${images_url}/${img}`) }).render(), - details.length > 0 && { - tag: "div", - class: "article-details", - contents: [ - { - tag: "h2", - contents: "Details", - }, - { - tag: "ul", - class: "details-list", - contents: details.map(detail => { - return { - tag: "li", - class: "detail", - contents: [ - { tag: "label", contents: detail.label }, - { - tag: "div", - class: "detail-value", - contents: detail.value - }, - ], - }; - }), - }, - ], - }, - ], - }; - } -} - -module.exports = GameArticle; - -},{"../../../../../admin-frontend/src/constants":1,"../../../../constants":3,"../../../generic-components/image-carousel":37,"../../../lib/article-utils":38,"../../../lib/fetch":39,"ks-cheap-translator":4,"mentalo-engine":5}],42:[function(require,module,exports){ -"use strict"; - -const { loadArticles } = require("../../../lib/article-utils"); -const GameArticle = require("./game-article"); -const translator = require("ks-cheap-translator"); - -class GameArticles { - constructor(props) { - this.props = props; - this.state = { - articles: [], - }; - this.id = "game-articles-section"; - this.loadArticles(); - } - - loadArticles() { - loadArticles("games", translator.locale) - .then(articles => { - this.state.articles = articles; - this.refresh(); - }) - .catch(e => console.log(e)); - } - - renderPlaceholder() { - return { - tag: "article", - class: "placeholder", - contents: [{ tag: "div" }, { tag: "div" }], - }; - } - - refresh() { - obj2htm.subRender(this.render(), document.getElementById(this.id), { - mode: "replace", - }); - } - - render() { - const { articles } = this.state; - return { - tag: "section", - class: "game-articles page-contents-center", - id: this.id, - contents: - articles.length > 0 - ? articles.map(article => new GameArticle({ ...article }).render()) - : [this.renderPlaceholder()], - }; - } -} - -module.exports = GameArticles; - -},{"../../../lib/article-utils":38,"./game-article":41,"ks-cheap-translator":4}],43:[function(require,module,exports){ -"use strict"; - -const { images_url } = require("../../../constants"); -const WebPage = require("../../lib/web-page"); -const GameArticles = require("./components/game-articles"); -const translator = require("ks-cheap-translator"); -const t = translator.trad.bind(translator); - -class GamesPage extends WebPage { - render() { - return { - tag: "div", - id: "games-page", - contents: [ - { - tag: "div", - class: "page-header logo-left", - contents: [ - { - tag: "div", - class: "page-contents-center grid-wrapper", - contents: [ - { - tag: "div", - class: "logo", - contents: [ - { - tag: "img", - alt: "image game controller", - src: `${images_url}/game_controller.svg`, - }, - ], - }, - { tag: "h1", contents: t("Jeux") }, - { - tag: "p", - contents: t("games-page-intro"), - }, - ], - }, - ], - }, - new GameArticles().render(), - ], - }; - } -} - -module.exports = GamesPage; - -},{"../../../constants":3,"../../lib/web-page":40,"./components/game-articles":42,"ks-cheap-translator":4}],44:[function(require,module,exports){ -"use strict"; - -"use strict"; -const runPage = require("../../run-page"); -const GamesPage = require("./games"); -runPage(GamesPage); - -},{"../../run-page":45,"./games":43}],45:[function(require,module,exports){ -"use strict"; - -const renderer = require("object-to-html-renderer") -const Template = require("./template/template"); - -module.exports = function runPage(PageComponent) { - const template = new Template({ page: new PageComponent() }); - renderer.register("obj2htm") - obj2htm.setRenderCycleRoot(template); - obj2htm.renderCycle(); -}; - -},{"./template/template":47,"object-to-html-renderer":36}],46:[function(require,module,exports){ -"use strict"; - -const { images_url } = require("../../../constants"); -const translator = require("ks-cheap-translator"); -const t = translator.trad.bind(translator); - -const NAV_MENU_ITEMS = [ - { url: "/games/", text: "Jeux" }, - { - url: "/education/", - text: "Pédagogie", - }, - { url: "/software-development/", text: "Software" } -]; - -class NavBar { - constructor() { - this.initEventHandlers(); - } - - handleBurgerClick() { - document.getElementById("nav-menu-list").classList.toggle("responsive-show"); - } - - initEventHandlers() { - window.addEventListener("click", event => { - if ( - event.target.id !== "nav-menu-list" && - !event.target.classList.contains("burger") && - !event.target.parentNode.classList.contains("burger") - ) { - document.getElementById("nav-menu-list").classList.remove("responsive-show"); - } - }); - } - - handle_chang_lang(lang) { - translator.update_translations(lang).then(() => { - obj2htm.renderCycle(); - }).catch(err => console.log(err)); - } - - renderHome() { - return { - tag: "div", - class: "home", - contents: [ - { - tag: "a", - href: "/", - contents: [ - { - tag: "img", - alt: "Logo Kuadrado", - src: `${images_url}/logo_kuadrado.svg`, - }, - { - tag: "img", - alt: "Kuadrado Software", - class: "logo-text", - src: `${images_url}/logo_kuadrado_txt.svg`, - }, - ], - }, - ], - }; - } - - renderMenu(menuItemsArray, isSubmenu = false, parentUrl = "") { - return { - tag: "ul", - id: "nav-menu-list", - class: isSubmenu ? "submenu" : "", - contents: menuItemsArray.map(item => { - const { url, text, submenu } = item; - const href = `${parentUrl}${url}`; - return { - tag: "li", - class: !isSubmenu && window.location.pathname === href ? "active" : "", - contents: [ - { - tag: "a", - href, - contents: t(text), - }, - ].concat(submenu ? [this.renderMenu(submenu, true, url)] : []), - }; - }).concat({ - tag: "li", - class: "lang-flags", - contents: ["fr", "en"].map(lang => { - return { - tag: "img", src: `${images_url}/flag-${lang}.svg`, - class: translator.locale === lang ? "selected" : "", - onclick: this.handle_chang_lang.bind(this, lang) - } - }) - }), - }; - } - - renderResponsiveBurger() { - return { - tag: "div", - class: "burger", - onclick: this.handleBurgerClick.bind(this), - contents: [{ tag: "span", contents: "···" }], - }; - } - - render() { - return { - tag: "nav", - contents: [ - this.renderHome(), - this.renderResponsiveBurger(), - this.renderMenu(NAV_MENU_ITEMS), - ], - }; - } -} - -module.exports = NavBar; - -},{"../../../constants":3,"ks-cheap-translator":4}],47:[function(require,module,exports){ -"use strict"; - -const { in_construction } = require("../../config"); -const { images_url } = require("../../constants"); -const NavBar = require("./components/navbar"); -const translator = require("ks-cheap-translator"); -const t = translator.trad.bind(translator) - -class Template { - constructor(props) { - this.props = props; - } - render() { - return { - tag: "main", - contents: [ - { - tag: "header", - contents: [new NavBar().render()], - }, - in_construction && { - tag: "section", - class: "warning-banner", - contents: [ - { - tag: "strong", - class: "page-contents-center", - contents: t("Site en construction ..."), - }, - ], - }, - { - tag: "section", - id: "page-container", - contents: [this.props.page.render()], - }, - { - tag: "footer", - contents: [ - { - tag: "div", - class: "logo", - contents: [ - { - tag: "img", - alt: `logo Kuadrado`, - src: `${images_url}/logo_kuadrado.svg`, - }, - { - tag: "img", - class: "text-logo", - alt: "Kuadrado Software", - src: `${images_url}/logo_kuadrado_txt.svg`, - }, - ], - }, - { - tag: "span", - contents: "32 rue Simon Vialet, 07240 Vernoux en Vivarais. Ardèche, France", - }, - { - tag: "div", - contents: [ - { tag: "strong", contents: "<blue>Contact : </blue>" }, - { - tag: "a", - href: "mailto:contact@kuadrado-software.fr", - contents: "contact@kuadrado-software.fr", - }, - ], - }, - { - tag: "div", - class: "social", - contents: [ - { - tag: "strong", - contents: `<blue>${t("Sur les réseaux")} : </blue>`, - }, - { - tag: "a", - href: "https://www.linkedin.com/company/kuadrado-software", - target: "_blank", - contents: "in", - title: "Linkedin", - }, - { - tag: "a", - href: "https://mastodon.gamedev.place/@kuadrado_software", - target: "_blank", - contents: "m", - title: "Mastodon", - } - ], - }, - { - tag: "span", - contents: `Copyleft 🄯 ${new Date() - .getFullYear()} Kuadrado Software | - ${t("kuadrado-footer-copyleft")}`, - }, - { - tag: "div", contents: [ - { tag: "span", contents: t("Ce site web est") + " " }, - { - tag: "a", target: "_blank", - style_rules: { fontWeight: "bold" }, - href: "https://gitlab.com/kuadrado-software/kuadrado-website/-/blob/master/README.md", - contents: "OPEN SOURCE" - } - ] - } - ], - }, - ], - }; - } -} - -module.exports = Template; - -},{"../../config":2,"../../constants":3,"./components/navbar":46,"ks-cheap-translator":4}]},{},[44]); +!function s(i,o,a){function r(t,e){if(!o[t]){if(!i[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(c)return c(t,!0);throw(n=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",n}n=o[t]={exports:{}},i[t][0].call(n.exports,function(e){return r(i[t][1][e]||e)},n,n.exports,s,i,o,a)}return o[t].exports}for(var c="function"==typeof require&&require,e=0;e<a.length;e++)r(a[e]);return r}({1:[function(e,t,n){t.exports={images_url:"/assets/images"}},{}],2:[function(e,t,n){t.exports={build:{protected_dirs:["assets","style","views","standard"],default_meta_keys:["title","description","image","open_graph","json_ld"]}}},{}],3:[function(e,t,n){t.exports={images_url:"/assets/images",data_url:"/assets/data",translations_url:"/assets/translations"}},{}],4:[function(e,t,n){"use strict";t.exports={locale:"en",supported_languages:["en"],translations:{},translations_url:"",use_url_locale_fragment:!0,local_storage_key:"translator-prefered-language",init(e){return Object.entries(e).forEach(e=>{var[t,e]=e;["supported_languages","use_url_locale_fragment","local_storage_key","translations_url"].includes(t)&&(this[t]=e)}),this.translations_url=this.format_translations_url(this.translations_url),this.supported_languages=this.format_supported_languages(this.supported_languages),new Promise((t,n)=>{const s=(()=>{if(this.use_url_locale_fragment){var e=window.location.pathname.substring(1).split("/")[0];return this.supported_languages.includes(e)?e:""}return""})()||localStorage.getItem(this.local_storage_key)||(e=navigator.language.split("-")[0].toLocaleLowerCase(),this.supported_languages.includes(e)?e:this.supported_languages[0]);var e;fetch(`${this.translations_url}${s}.json`).then(e=>e.json()).then(e=>{this.locale=s,this.translations=e,t()}).catch(e=>{this.locale="en",n(e)})})},format_locale(e){return e.split("-")[0].toLowerCase()},format_translations_url(e){return"/"!==e.charAt(e.length-1)&&(e+="/"),e},format_supported_languages(e){return e.map(e=>this.format_locale(e))},update_translations(s){return s=this.format_locale(s),new Promise((n,t)=>{fetch(`${this.translations_url}${s}.json`).then(e=>e.json()).then(e=>{this.translations=e,this.locale=s,localStorage.setItem(this.local_storage_key,s);const t=window.location.pathname.substring(1).split("/");e=t[0];this.supported_languages.includes(e)&&(t.splice(0,1,s),e=t.join("/"),window.history.replaceState(null,"","/"+e)),n()}).catch(e=>{t(e)})})},trad:function(t,n={}){return t=this.translations[t]||t,Object.keys(n).forEach(e=>{t=t.replace(`{%${e}%}`,n[e])}),t}}},{}],5:[function(e,t,n){"use strict";var s=e("./model/animation"),i=e("./mentalo-engine"),o=e("./model/scene"),a=e("./model/game"),r=e("./model/sound-track"),c=e("./model/scene-types"),l=e("./model/choice"),_=e("./model/game-object"),h=e("./lib/frame-rate-controller"),d=e("./lib/font-tools"),m=e("./lib/color-tools"),e=e("./lib/shape-tools");t.exports={MentaloEngine:i,MtlAnimation:s,MtlScene:o,MtlGame:a,MtlSoundTrack:r,SCENE_TYPES:c,MtlChoice:l,MtlGameObject:_,FrameRateController:h,font_tools:d,color_tools:m,shape_tools:e}},{"./lib/color-tools":6,"./lib/font-tools":7,"./lib/frame-rate-controller":8,"./lib/shape-tools":9,"./mentalo-engine":11,"./model/animation":12,"./model/choice":13,"./model/game":15,"./model/game-object":14,"./model/scene":19,"./model/scene-types":18,"./model/sound-track":20}],6:[function(e,t,n){"use strict";function s(e){return(e=e.toLowerCase(0)).includes("rgb")?e.replaceAll(/[a-z\(\)w]/g,"").split(",").slice(0,3).map(e=>parseInt(e)):e.replace("#","").match(/.{0,2}/g).slice(0,3).map(e=>parseInt(e,16))}function i(e){return s(e).reduce((e,t)=>e+t/3,0)}function o(e){return`#${e.slice(0,3).map(e=>{e=e.toString(16);return e.length<2?"0"+e:e}).join("")}`}t.exports={get_average_rgb_color_tone:i,get_optimal_visible_foreground_color:function(e){const t=document.createElement("canvas");t.width=1,t.height=1;const n=t.getContext("2d");return n.fillStyle=e,n.fillRect(0,0,1,1),e=127<i(e)?"#0000003f":"#ffffff3f",n.fillStyle=e,n.fillRect(0,0,1,1),o(Array.from(n.getImageData(0,0,1,1).data))},color_str_to_rgb_array:s,rgb_array_to_hex:o,rgba_array_to_hex:function(e){return`#${(e=4===e.length?e:e.concat([255])).slice(0,4).map(e=>{e=e.toString(16);return e.length<2?"0"+e:e}).join("")}`},same_rgba:function(e,t){return e[0]===t[0]&&e[1]===t[1]&&e[2]===t[2]&&e[3]===t[3]}}},{}],7:[function(e,t,n){"use strict";const s=[{category:"Sans serif",values:[{value:"sans-serif",text:"sans-serif"},{text:"Arial",value:"Arial, sans-serif"},{text:"Helvetica",value:"Helvetica, sans-serif"},{text:"Verdana",value:"Verdana, sans-serif"},{text:"Trebuchet MS",value:"Trebuchet MS, sans-serif"},{text:"Noto Sans",value:"Noto Sans, sans-serif"},{text:"Gill Sans",value:"Gill Sans, sans-serif"},{text:"Avantgarde",value:"Avantgarde, TeX Gyre Adventor, URW Gothic L, sans-serif"},{text:"Optima",value:"Optima, sans-serif"},{text:"Arial Narrow",value:"Arial Narrow, sans-serif"}]},{category:"Serif",values:[{text:"serif",value:"serif"},{text:"Times",value:"Times, Times New Roman, serif"},{text:"Didot",value:"Didot, serif"},{text:"Georgia",value:"Georgia, serif"},{text:"Palatino",value:"Palatino, URW Palladio L, serif"},{text:"Bookman",value:"Bookman, URW Bookman L, serif"},{text:"New Century Schoolbook",value:"New Century Schoolbook, TeX Gyre Schola, serif"},{text:"American Typewriter",value:"American Typewriter, serif"}]},{category:"Monospace",values:[{text:"monospace",value:"monospace"},{text:"Andale Mono",value:"Andale Mono, monospace"},{text:"Courrier New",value:"Courier New, monospace"},{text:"Courrier",value:"Courier, monospace"},{text:"FreeMono",value:"FreeMono, monospace"},{text:"OCR A Std",value:"OCR A Std, monospace"},{text:"DejaVu Sans Mono",value:"DejaVu Sans Mono, monospace"}]},{category:"Cursive",values:[{text:"cursive",value:"cursive"},{text:"Comic Sans MS",value:"Comic Sans MS, Comic Sans, cursive"},{text:"Apple Chancery",value:"Apple Chancery, cursive"},{text:"Bradley Hand",value:"Bradley Hand, cursive"},{text:"Brush Script MT",value:"Brush Script MT, Brush Script Std, cursive"},{text:"Snell Roundhand",value:"Snell Roundhand, cursive"},{text:"URW Chancery L",value:"URW Chancery L, cursive"}]}],i=[{value:"left",text:"Left"},{value:"right",text:"Right"},{value:"center",text:"Center"}],o=[{value:"normal",text:"Normal"},{value:"italic",text:"Italic"}],a=[{value:"1",text:"Thin"},{value:"normal",text:"Normal"},{value:"900",text:"Bold"}];function r(e={}){const{font_size:t=20,font_family:n="sans-serif",font_style:s="normal",font_weight:i="normal"}=e;return`${s} normal ${i} ${t.toFixed()}px ${n}`}t.exports={get_canvas_font:r,get_font_options:function(){return{font_families:s,text_align_options:i,font_style_options:o,font_weight_options:a}},get_canvas_char_size:function(e,t){e.save(),e.font=r(t);var n="Lorem ipsum dolor Sit amet, Consectetur adipiscing elit",s=e.measureText(n).width/n.length,n=(t=1.1*e.measureText("M").width)+t/4;return e.restore(),{width:s,height:t,text_line_height:n,interline_height:t/4}},FONT_FAMILIES:s,TEXT_ALIGN_OPTIONS:i,FONT_STYLE_OPTIONS:o,FONT_WEIGHT_OPTIONS:a}},{}],8:[function(e,t,n){"use strict";t.exports=class{constructor(e){this.tframe=performance.now(),this.interval=1e3/e,this.initial=!0}nextFrameReady(){if(this.initial)return!(this.initial=!1);var e=performance.now(),t=e-this.tframe,n=t>this.interval;return n&&(this.tframe=e-t%this.interval),n}}},{}],9:[function(e,t,n){"use strict";t.exports={draw_rect:function(e,t,n,s,i,o={rounded_corners_radius:0,border:{width:0,color:"rgba(0,0,0,0)"},fill_color:"black"}){var{rounded_corners_radius:a=0,border:r={width:0,color:"rgba(0,0,0,0)"},fill_color:c="black",fill_image:l}=o,a=(o=Math.min(s,i))/2<a?o/2:a;e.save(),e.beginPath(),e.arc(t+a,n+a,a,Math.PI,3*Math.PI/2),e.lineTo(t+s-a,n),e.arc(t+s-a,n+a,a,3*Math.PI/2,0),e.lineTo(t+s,n+i-a),e.arc(t+s-a,n+i-a,a,0,Math.PI/2),e.lineTo(t+a,n+i),e.arc(t+a,n+i-a,a,Math.PI/2,Math.PI),e.closePath(),e.clip(),l?e.drawImage(l.src,0,0,l.src.width,l.src.height,l.dx,l.dy,l.dw,l.dh):(e.fillStyle=c,e.fillRect(t,n,s,i)),0<r.width&&(e.strokeStyle=r.color,e.lineWidth=r.width,e.stroke()),e.restore()}}},{}],10:[function(e,t,n){"use strict";const{get_canvas_font:l,get_canvas_char_size:_}=e("./font-tools");t.exports={draw_text_in_bounds:function(i,s,o,a,r,e){i.save();const t=_(i,a).text_line_height;i.font=l(a),i.fillStyle=a.font_color,i.textAlign=a.text_align,i.textBaseline="top";const n=(()=>{var{padding:e=0}=a,{left:t,top:n,width:s}=o;switch(i.textAlign){case"left":return{x:t+e,y:n+e};case"right":return{x:t+s-e,y:n+e};case"center":return{x:t+s/2,y:n+e};default:return{x:t+e,y:n+e}}})();r.stream=r.stream||{line_chars:0,line_index:0,complete:!1};const c=e?s.map((e,t)=>{const n=r.stream;if(t<n.line_index||n.complete)return e;if(t>n.line_index)return"";t=e.slice(0,++n.line_chars);return t.length===e.length&&(n.complete=s.length-1===n.line_index,n.line_index=n.complete?n.line_index:n.line_index+1,n.line_chars=0),t}):(r.stream.complete=!0,s);return c.forEach(e=>{i.fillText(e,n.x,n.y),n.y+=t}),i.restore(),r.stream.complete}}},{"./font-tools":7}],11:[function(e,t,n){"use strict";const s=e("./model/game"),i=e("./model/scene-types"),o=e("./render/render"),{supported_locales:a,get_translated:r}=e("./translation");t.exports=class{constructor(e){this.game=new s,this.game.load_data(e.game_data),this.game.on_load_resources(()=>this.loading=!1),this.use_locale=e.use_locale&&a.includes(e.use_locale)?e.use_locale:"en",this.escape_listener=e=>{e=e.key.toLowerCase();"escape"!==e&&"q"!==e||this.quit()},window.addEventListener("keydown",this.escape_listener),this.on_quit_game=function(){this.used_default_container&&this.container.remove(),window.removeEventListener("keydown",this.escape_listener),e.on_quit_game&&e.on_quit_game()},this.used_default_container=!1,this.container=e.container||(()=>{const e=document.createElement("div");return e.style.display="flex",e.style.justifyContent="center",e.style.alignItems="center",e.style.position="absolute",e.style.top=0,e.style.bottom=0,e.style.left=0,e.style.right=0,e.style.zIndex=1e3,e.style.overflow="hidden",document.body.appendChild(e),this.used_default_container=!0,e})(),this.render=new o({container:this.container,fullscreen:e.fullscreen,frame_rate:e.frame_rate,get_game_settings:this.get_game_settings.bind(this),get_game_scenes:this.get_game_scenes.bind(this),get_scene:this.get_scene.bind(this),get_scene_index:this.get_scene_index.bind(this),get_inventory:this.get_inventory.bind(this),on_game_object_click:this.on_game_object_click.bind(this),on_drop_inventory_object:this.on_drop_inventory_object.bind(this),on_choice_click:this.on_choice_click.bind(this)}),this.loading=!0,this.will_quit=!1}get_game_settings(e){return this.game.game_ui_options[e]}get_game_scenes(){return this.game.scenes}get_scene(){return this.game.get_scene()}get_scene_index(){return this.game.scenes.indexOf(this.get_scene())}get_inventory(){return this.game.state.inventory}quit(){this.will_quit=!0}__quit(){window.cancelAnimationFrame(window.mentalo_engine_animation_id),this.render.exit_fullscreen(),this.render.clear_event_listeners(),this.game.get_soundtrack().stop(),this.on_quit_game()}init(){window.requestAnimationFrame=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame,window.cancelAnimationFrame=window.cancelAnimationFrame||window.mozCancelAnimationFrame,window.mentalo_engine_animation_id&&window.cancelAnimationFrame(window.mentalo_engine_animation_id),this.render.init(),this.game.scenes.find(e=>e.animation.empty)&&(alert(r("Some scenes have an empty image, game cannot be loaded",this.use_locale)),this.quit())}go_to_scene(e){this.render.reset_user_error_popup(),this.render.clear_event_listeners(),this.game.get_soundtrack().stop(),this.game.go_to_scene(e),this.render.clear_children(),this.render.reset_text_box_visibility()}on_game_object_click(e){this.game.inventory_has_empty_slot()&&this.game.add_object_to_inventory(e)}on_drop_inventory_object(e){this.game.remove_object_from_inventory(e)}on_choice_click(e){const{destination_scene_index:t,use_objects:n}=e;if(-1!==t)if(-2!==t){if(n.value){var s=this.get_inventory();const o=[];for(const a of n.items){var i=Array.from(s).find(e=>e.name===a.name);if(!i)return void this.render.set_user_error_popup({text:n.missing_object_message,stream_text:!0});o.push(i)}o.forEach(t=>n.items.find(e=>t.name===e.name).consume_object&&this.game.consume_game_object(t))}this.go_to_scene(t)}else this.quit();else this.render.set_user_error_popup({text:r("Destination scene has not been set.",this.use_locale)})}on_cinematic_end(){var e=this.game.get_scene();e.end_cinematic_options.quit?this.quit():-1===e.end_cinematic_options.destination_scene_index?this.render.set_user_error_popup({text:r("Next scene has not been set.",this.use_locale),on_close:this.quit.bind(this)}):this.go_to_scene(e.end_cinematic_options.destination_scene_index)}run_game(){if(this.will_quit)this.__quit();else{if(this.loading)this.render.draw_loading();else{this.get_scene()._type===i.CINEMATIC&&(this.game.update_cinematic_timeout(),this.game.is_cinematic_ended()&&this.on_cinematic_end());const e=this.game.get_soundtrack();e.loaded&&!e.is_playing()&&e.play(),this.render.draw_game()}window.mentalo_engine_animation_id=requestAnimationFrame(this.run_game.bind(this))}}}},{"./model/game":15,"./model/scene-types":18,"./render/render":23,"./translation":35}],12:[function(e,t,n){"use strict";class s extends e("./loadable"){constructor(){super(new Image,"image","load"),this.image.onload=()=>this.update_dimensions(),this.name="",this.dimensions={width:0,height:0},this.frame_nb=1,this.frame=0,this.speed=1,this.play_once=!1,this.initialized=!1,this.finished=!1}update_dimensions(){this.dimensions={width:this.image.width/this.frame_nb,height:this.image.height},this.canvas_precalc&&delete this.canvas_precalc}init(e){this.empty=""===e.src,this.name=e.name,this.image.src=e.src,this.frame_nb=e.frame_nb,this.frame=0,this.speed=e.speed,this.play_once=e.play_once,this.initialized=!0}update_frame(e){this.finished=this.play_once&&this.frame===this.frame_nb-1,1<this.frame_nb&&e%this.speed==0&&!this.finished&&(this.frame=this.frame+1<=this.frame_nb-1?this.frame+1:0)}reset_frame(){this.finished=!1,this.frame=0}}t.exports=s},{"./loadable":17}],13:[function(e,t,n){"use strict";t.exports=class{constructor(e={text:"",destination_scene_index:-1,use_objects:{value:!1,items:[],missing_object_message:""}}){this.load_data(e)}load_data(e){this.text=e.text,this.destination_scene_index=e.destination_scene_index,this.use_objects=e.use_objects}}},{}],14:[function(e,t,n){"use strict";class s extends e("./loadable"){constructor(){super(new Image,"image","load"),this.name="",this.position={x:0,y:0},this.state={}}load_data(e){this.image.src=e.image,this.name=e.name,this.position=e.position}get_dimensions(){return{width:this.image.width,height:this.image.height}}get_bounds(){var e=this.get_dimensions();return{top:this.position.y,right:this.position.x+e.width,bottom:this.position.y+e.height,left:this.position.x}}}t.exports=s},{"./loadable":17}],15:[function(e,t,n){"use strict";const s=e("./scene"),i=e("./scene-types");t.exports=class{constructor(){this.name="",this.scenes=[new s],this.game_ui_options={general:{background_color:"#000000",animation_canvas_dimensions:{width:600,ratio:"4:3",height:function(){var e=this.ratio.split(":").map(e=>parseInt(e)),e=e[1]/e[0];return Number((this.width*e).toFixed())}}},text_boxes:{background_color:"#222222",font_size:20,font_style:"normal",font_weight:"normal",font_family:"Arial, sans-serif",font_color:"#ffffff",text_align:"left",padding:20,margin:20,border_width:0,rounded_corners_radius:0},choices_panel:{background_color:"#222222",font_size:20,font_family:"sans",font_color:"#ffffff",font_style:"normal",text_align:"left",font_weight:"normal",container_padding:20,choice_padding:10,active_choice_background_color:"rgba(255,255,255,.4)",active_choice_border_width:0,active_choice_rounded_corners_radius:0},inventory:{background_color:"#222222",columns:1,rows:4,gap:10,padding:20,slot_rounded_corner_radius:0,slot_border_width:2}},this.starting_scene_index=0,this.state={scene:this.starting_scene_index,inventory:new Set,cinematic_timeout:{inc:0,last_update_time:-1,timeout:!1}},this.load_state={loaded_scenes:0}}get_scene(){return this.scenes[this.state.scene]}get_soundtrack(){return this.get_scene().sound_track}go_to_scene(e){const t=this.get_scene();t.animation.reset_frame(),t._type==i.CINEMATIC&&(this.state.cinematic_timeout={inc:0,last_update_time:-1,timeout:!1}),this.state.scene=e}update_cinematic_timeout(){var e;this.state.cinematic_timeout.timeout||(e=(new Date).getTime(),-1===this.state.cinematic_timeout.last_update_time&&(this.state.cinematic_timeout.last_update_time=e),this.state.cinematic_timeout.inc=e-this.state.cinematic_timeout.last_update_time,this.state.cinematic_timeout.timeout=1e3*this.get_scene().cinematic_duration<=this.state.cinematic_timeout.inc)}is_cinematic_ended(){var e=this.get_scene();return 0===e.cinematic_duration?e.animation.finished:this.state.cinematic_timeout.timeout}add_object_to_inventory(e){this.state.inventory.add(e)}inventory_has_empty_slot(){var{columns:e,rows:t}=this.game_ui_options.inventory;return this.state.inventory.size<e*t}remove_object_from_inventory(e){this.state.inventory.delete(e)}consume_game_object(t){this.remove_object_from_inventory(t);for(const n of this.scenes){var e=n.game_objects.find(e=>e===t);if(e){n.game_objects.splice(n.game_objects.indexOf(e),1);break}}}all_resources_loaded(){return this.load_state.loadable_elements===this.load_state.loaded_elements}get_game_objects(){return this.scenes.reduce((e,t)=>e.concat(t.game_objects),[])}load_data(e){this.name=e.name,this.starting_scene_index=e.starting_scene_index||0,this.state.scene=this.starting_scene_index,this.scenes=e.scenes.map(e=>{const t=new s;return t.load_data(e),t.on_load_group_complete(()=>{this.load_state.loaded_scenes++,this.load_state.loaded_scenes===this.scenes.length&&this.on_load_resources_callback&&this.on_load_resources_callback()}),t}),this.game_ui_options=Object.assign(e.game_ui_options,{general:{background_color:e.game_ui_options.general.background_color,animation_canvas_dimensions:Object.assign(e.game_ui_options.general.animation_canvas_dimensions,{height:function(){var e=this.ratio.split(":").map(e=>parseInt(e)),e=e[1]/e[0];return Number((this.width*e).toFixed())}})}})}on_load_resources(e){this.on_load_resources_callback=e}}},{"./scene":19,"./scene-types":18}],16:[function(e,t,n){"use strict";t.exports=class{constructor(){this.loadable_elements=0,this.loaded_elements=0}add_loadable(e){this.loadable_elements++,e.on_load(()=>{this.loaded_elements++,this.loadable_elements===this.loaded_elements&&this.on_load_group_complete_custom_callback&&this.on_load_group_complete_custom_callback()})}on_load_group_complete(e){this.on_load_group_complete_custom_callback=e}}},{}],17:[function(e,t,n){"use strict";t.exports=class{constructor(e,t,n){this.loaded=!1,this.init_loadable_element(e,t,n)}init_loadable_element(e,t,n){this[t]=e,this.load_listener=()=>{this.loaded=!0,this.on_load_callback(),this.clear()},this[t].addEventListener(n,this.load_listener),this.clear=()=>{this[t].removeEventListener(n,this.load_listener)}}on_load(e){this.on_load_custom_callback=e}on_load_callback(){this.loaded=!0,this.on_load_custom_callback&&this.on_load_custom_callback()}}},{}],18:[function(e,t,n){t.exports={PLAYABLE:"Playable",CINEMATIC:"Cinematic"}},{}],19:[function(e,t,n){"use strict";const s=e("./scene-types"),i=e("./animation"),o=e("./sound-track"),a=e("./choice"),r=e("./game-object");class c extends e("./loadable-group"){constructor(){super();var e={name:"",_type:s.PLAYABLE,animation:new i,sound_track:new o,choices:[],text_box:"",game_objects:[],cinematic_duration:0,end_cinematic_options:{destination_scene_index:-1,quit:!1}};this.name=e.name,this._type=e._type,this.animation=e.animation,this.sound_track=e.sound_track,this.choices=e.choices,this.text_box=e.text_box,this.game_objects=e.game_objects,this.cinematic_duration=e.cinematic_duration||0,this.end_cinematic_options=e.end_cinematic_options}load_data(e){this.name=e.name,this._type=e._type;const t=new i;t.init(e.animation),this.animation.clear(),this.animation=t,this.animation.image.src&&this.add_loadable(this.animation);const n=new o;n.init(e.sound_track),this.sound_track.clear(),this.sound_track=n,this.sound_track.src&&this.add_loadable(this.sound_track),this.choices=e.choices.map(e=>new a(e)),this.text_box=e.text_box,this.cinematic_duration=e.cinematic_duration||0,this.game_objects=e.game_objects.map(e=>{const t=new r;return t.load_data(e),this.add_loadable(t),t}),this.end_cinematic_options=e.end_cinematic_options}}t.exports=c},{"./animation":12,"./choice":13,"./game-object":14,"./loadable-group":16,"./scene-types":18,"./sound-track":20}],20:[function(e,t,n){"use strict";class s extends e("./loadable"){constructor(){super(new Audio,"audio","loadeddata"),this.name="",this.initialized=!1}reset(){this.init_loadable_element(new Audio,"audio","loadeddata"),this.name="",this.initialized=!1}init(e){this.empty=""===e.src,this.audio.src=e.src,this.name=e.name,this.audio.loop=e._loop,this.initialized=!0}play(){this.audio.play()}stop(){this.audio.pause(),this.audio.currentTime=0}is_playing(){return!this.audio.paused}}t.exports=s},{"./loadable":17}],21:[function(e,t,n){"use strict";const o=e("./choices-panel-metrics");t.exports=class{constructor(e){this.params=e,this.image={width:0,height:0},this.width=0,this.height=0,this.inventory={width:0,height:0},this.choices_panel={width:0,height:0},this.scale_factor=1,this.init_dimensions()}init_dimensions(){const{get_game_settings:e}=this.params,t=e("general");var n=t.animation_canvas_dimensions.height(),s=t.animation_canvas_dimensions.width,i=this.get_inventory_base_width(),o=s+i;this.choices_panel_metrics=this.get_choices_panel_metrics();var a,r=window.screen.width-200,c=window.screen.height-200,l=n+this.choices_panel_metrics.get_total_height(),_=o/l;_<window.screen.width/window.screen.height?this.width=(a=c*_)<=r?a:r:(this.height=(r=l/o*r)<=c?r:c,this.width=this.height*_),this.scale_factor=this.width/o,this.choices_panel_metrics=this.choices_panel_metrics.to_scaled(this.scale_factor),this.image={width:s*this.scale_factor,height:n*this.scale_factor},this.inventory={width:i*this.scale_factor,height:this.image.height},this.choices_panel={width:this.width,height:this.choices_panel_metrics.get_total_height()},this.height=this.image.height+this.choices_panel.height}get_inventory_base_width(){const{get_game_settings:e}=this.params;var t=e("general").animation_canvas_dimensions.height(),n=e("inventory"),s=n.gap,i=t-2*n.padding,t=(n.rows-1)*s,s=(n.columns-1)*s,t=(i-t)/n.rows;return n.columns*t+2*n.padding+s}get_choices_panel_metrics(){const{get_game_settings:e,get_game_scenes:t}=this.params;var n=e("choices_panel"),s=e("general"),i=n.container_padding,s=s.animation_canvas_dimensions.width+this.get_inventory_base_width()-2*i,i=t().map(e=>({choices:e.choices}));return new o({settings:n,container_width:s,scenes_choices:i})}}},{"./choices-panel-metrics":22}],22:[function(e,t,n){"use strict";const{get_canvas_char_size:o}=e("../lib/font-tools");t.exports=class t{constructor(e){this.params=e;var{scale_factor:e=1}=this.params;this.container_width=this.params.container_width*e,this.settings=this.get_scaled_settings(e),this.scenes_formatted_choices=this.get_scenes_formatted_choices(),this.container_padding_height=2*this.settings.container_padding,this.rows=this.get_rows_metrics()}get_scaled_settings(e){var{settings:t}=this.params;return Object.assign({...t},{font_size:t.font_size*e,active_choice_border_width:t.active_choice_border_width*e,active_choice_rounded_corners_radius:t.active_choice_rounded_corners_radius*e,choice_padding:t.choice_padding*e,container_padding:t.container_padding*e})}get_rows_metrics(e,t){var{settings:n}=this,s=this.get_choices_max_lines_per_row(e,t),e=(i=o(window.mentalo_drawing_context,n)).text_line_height,t=i.interline_height,i=2*n.choice_padding,n=s[0]*e-t,t=0<s[1]?s[1]*e-t:0;return[{text_height:n,padding_height:i},{text_height:t,padding_height:0<t?i:0}]}get_choices_max_lines_per_row(e,t){e=e||this.get_max_choices_row_per_scene();const i=t||this.scenes_formatted_choices,o=[0,0];return Array.from({length:e}).forEach((e,t)=>{const n=0===t?[0,2]:[2,4];var s=i.map(e=>Math.max(...e.slice(...n).map(e=>e.text_lines.length))).reduce((e,t)=>Math.max(e,t),0);o[t]=s}),o}get_max_choices_row_per_scene(e){const t=e?this.params.scenes_choices.slice(e,e+1):this.params.scenes_choices;let n=1;return 2<Math.max(...t.map(e=>e.choices.length))&&(n=2),n}get_scenes_formatted_choices(){const{scenes_choices:e}=this.params;var{settings:t,container_width:n}=this,{choice_padding:s}=t;const i=.9*(n/2-2*s)/o(window.mentalo_drawing_context,t).width;return e.map(e=>e.choices.map(e=>{const t=e.text.split(" "),n=[""];let s=0;return t.forEach(e=>{(n[s]+e).length>=i&&(s++,n.push("")),n[s]=`${n[s]}${""===n[s]?"":" "}${e}`}),Object.assign({...e},{text_lines:n})}))}get_total_height(e){return(e=e||this.rows).reduce((e,t)=>e+t.text_height+t.padding_height,0)+this.container_padding_height}to_scaled(e){return new t({...this.params,scale_factor:e})}get_minimum_panel_height_for_scene(e){var e=2<(t=this.scenes_formatted_choices.slice(e,e+1))[0].length?2:1,t=this.get_rows_metrics(e,t);return this.get_total_height(t)}get_one_choices_row_metrics(e,t){return e=this.scenes_formatted_choices.slice(e,e+1),t=this.get_rows_metrics(t+1,e)[t],Object.assign(t,{text_height:t.text_height,padding_height:t.padding_height})}}},{"../lib/font-tools":7}],23:[function(e,t,n){"use strict";const s=e("../lib/frame-rate-controller"),{get_optimal_visible_foreground_color:u}=e("../lib/color-tools"),{get_canvas_char_size:p}=e("../lib/font-tools"),i=e("../model/scene-types"),f=e("./ui-components/choice-cpt"),o=e("./ui-components/choices-panel-cpt"),b=e("./ui-components/closing-icon-cpt"),a=e("./ui-components/game-object-cpt"),r=e("./ui-components/inventory-cpt"),v=e("./ui-components/inventory-object-cpt"),w=e("./ui-components/inventory-slot-cpt"),c=e("./ui-components/scene-animation-cpt"),x=e("./ui-components/text-box-cpt"),y=e("./ui-components/user-error-popup"),l=e("./canvas-dimensions");t.exports=class{constructor(e){this.params=e;e=this.params.frame_rate||30;this.fps_controller=new s(e),this.params.container.style.backgroundColor=this.params.get_game_settings("general").background_color,this.canvas=document.createElement("canvas"),this.canvas.style.backgroundColor="black",window.mentalo_drawing_context=this.canvas.getContext("2d"),this.canvas_dimensions={},this.canvas_zones={},this.event_listeners={game_objects:[],inventory:[],text_box:[]},this.state={user_error_popup:{is_set:!1,text:"",on_close:function(){}}},this.loading_frame=0}init(){this.params.fullscreen&&this.set_full_screen(),this.set_canvas_dimensions(),this.create_canvas_zones(),this.params.container.appendChild(this.canvas);const e=window.mentalo_drawing_context;e.mozImageSmoothingEnabled=!1,e.webkitImageSmoothingEnabled=!1,e.msImageSmoothingEnabled=!1,e.imageSmoothingEnabled=!1,this.create_components()}clear_event_listeners(){Object.values(this.components).forEach(e=>{e.clear_event_listeners()})}clear_children(t={exclude:[]}){Object.values(this.components).forEach(e=>{e.clear_children(t)})}reset_text_box_visibility(){this.components.scene_animation.set_text_box_visibility(!0)}set_full_screen(){const e=document.body;e.requestFullScreen=e.requestFullScreen||e.webkitRequestFullScreen||e.msRequestFullscreen||e.mozRequestFullScreen;try{e.requestFullScreen()}catch(e){console.error(e.message)}}exit_fullscreen(){document.fullscreenElement&&document.exitFullscreen()}set_user_error_popup(e){this.state.user_error_popup={is_set:!0,text:e.text,stream_text:!!e.stream_text,on_close:function(){e.on_close&&e.on_close()}},this.clear_event_listeners(),this.clear_children({exclude:["TextBoxCpt","InventoryCpt","ChoicesPanelCpt","GameObjectCpt"]})}clear_user_error_popup(){this.reset_user_error_popup(),this.clear_event_listeners(),this.clear_children({exclude:["TextBoxCpt","InventoryCpt","ChoicesPanelCpt","GameObjectCpt"]})}reset_user_error_popup(){this.state.user_error_popup={is_set:!1,text:"",on_close:function(){}}}on_close_user_error_popup(){this.state.user_error_popup.on_close(),this.clear_user_error_popup()}set_canvas_dimensions(){var{get_game_settings:e,get_game_scenes:t}=this.params;this.canvas_dimensions=new l({get_game_settings:e,get_game_scenes:t}),this.canvas.width=this.canvas_dimensions.width,this.canvas.height=this.canvas_dimensions.height}get_scaled_choices_panel_settings(){return this.canvas_dimensions.choices_panel_metrics.settings}get_scaled_inventory_settings(){var{scale_factor:e}=this.canvas_dimensions;const{get_game_settings:t}=this.params;var n=t("inventory");return Object.assign({...n},{slot_rounded_corner_radius:n.slot_rounded_corner_radius*e,slot_border_width:n.slot_border_width*e,gap:n.gap*e,padding:n.padding*e})}get_scaled_text_boxes_settings(){var{scale_factor:e}=this.canvas_dimensions;const{get_game_settings:t}=this.params;var n=t("text_boxes");return Object.assign({...n},{font_size:n.font_size*e,padding:n.padding*e,margin:n.margin*e,rounded_corners_radius:n.rounded_corners_radius*e,border_width:n.border_width*e})}create_canvas_zones(){var e=this.canvas_dimensions,t=this.get_scaled_inventory_settings(),n=this.get_scaled_choices_panel_settings();this.canvas_zones={root:{left:0,top:0,right:e.width,bottom:e.height,width:e.width,height:e.height,padding:0},scene_animation:{left:0,top:0,right:e.image.width,bottom:e.image.height,width:e.image.width,height:e.image.height,clear_color:"black",padding:0},inventory:{left:e.image.width,top:0,right:e.width,bottom:e.inventory.height,width:e.width-e.image.width,height:e.inventory.height,clear_color:t.background_color,padding:t.padding},choices_panel:{left:0,top:e.image.height,right:e.width,bottom:e.height,width:e.width,height:e.height-e.image.height,clear_color:n.background_color,padding:n.container_padding}}}scene_is_not_cinematic(){return this.params.get_scene()._type===i.PLAYABLE}create_components(){const{get_game_settings:e,get_inventory:d,get_scene:m,get_game_scenes:l,get_scene_index:_}=this.params,{scale_factor:h}=this.canvas_dimensions,g=this.scene_is_not_cinematic.bind(this);this.components={scene_animation:new c({bounding_zone:this.canvas_zones.scene_animation,get_animation:()=>m().animation,next_frame_ready:()=>this.fps_controller.nextFrameReady(),get_children:()=>{const e=m();return e.game_objects.map(t=>{var e=this.canvas_zones.scene_animation,n={x:t.position.x*h+e.left,y:t.position.y*h+e.top},e={w:t.image.width*h,h:t.image.height*h};const s={left:n.x,right:n.x+e.w,top:n.y,bottom:n.y+e.h,width:e.w,height:e.h};e={bounding_zone:s,position:n,dimensions:e,image:t.image,is_in_inventory:()=>d().has(t)};const i=new a(e);return i.add_event_listener({event_type:"mousemove",listener:e=>{d().has(t)||(e=e.offsetX>=s.left&&e.offsetX<=s.right&&e.offsetY>=s.top&&e.offsetY<=s.bottom,i.state.draw_border=e)}}),i.add_event_listener({event_type:"click",listener:e=>{!d().has(t)&&e.offsetX>=s.left&&e.offsetX<=s.right&&e.offsetY>=s.top&&e.offsetY<=s.bottom&&(this.params.on_game_object_click(t),this.clear_event_listeners(),this.clear_children({exclude:"TextBoxCpt"}))}}),i}).concat(e.text_box?[(()=>{const e=this.get_scaled_text_boxes_settings();var t,n,s,n=(t=this.canvas_zones.scene_animation,o=e.padding,i=e.margin,n=t.left+i,s=t.right-i,i=t.bottom-i,{left:n,right:s,top:o=i-2*o,bottom:i,width:s-n,height:i-o}),i=10*h,o={x:n.right-i/2,y:n.top+i/2};const a={color:e.font_color,radius:i,center:o,background_color:e.background_color,line_width:Math.floor(2*h),bounding_zone:{left:o.x-i,right:o.x+i,top:o.y-i,bottom:o.y+i,width:2*i,height:2*i}},r=new x({text:m().text_box,settings:e,bounding_zone:n,get_visibility_state:()=>this.components.scene_animation.state.text_box_visible,get_children:()=>[new b(a)]}),c=r.children[0];return r.children[0].add_event_listener({event_type:"click",listener:e=>{var t=c.params.bounding_zone,t=e.offsetX>=t.left&&e.offsetX<=t.right&&e.offsetY>=t.top&&e.offsetY<=t.bottom;const{scene_animation:n}=this.components;t&&n.state.text_box_visible&&n.set_text_box_visibility(!1)}}),r})()]:[]).concat(this.state.user_error_popup.is_set?[(()=>{const{text:e,stream_text:t}=this.state.user_error_popup;var n=this.get_scaled_text_boxes_settings(),s=this.canvas_zones.scene_animation,i=p(window.mentalo_drawing_context,n),o=s.width/2,a=2*i.width;const r=(o-2*a)/i.width,c=[""];let l=0;e.split(" ").forEach(e=>{`${c[l]}${e} `.length>r&&(l++,c.push("")),c[l]+=`${e} `});var _=i.text_line_height,i=c.length*_+2*a-i.interline_height;const h=n.background_color,d=n.font_color,m={left:s.left+s.width/2-o/2,top:s.top+s.height/2-i/2,right:s.left+s.width/2+o/2,bottom:s.top+s.height/2-i/2+i,width:o,height:i,clear_color:h};a={settings:n,bounding_zone:m,modal_bounds:this.canvas_zones.scene_animation,clear_modal_color:"#0004",text_lines:c,padding:a,stream_text:t,get_children:()=>{var e={x:m.right-5,y:m.top+5};const t={left:e.x-15,right:e.x+15,top:e.y-15,bottom:e.y+15,width:30,height:30};e={color:d,radius:15,center:e,background_color:h,line_width:2,bounding_zone:t};const n=new b(e);return n.add_event_listener({event_type:"click",listener:e=>{e.offsetX>=t.left&&e.offsetX<=t.right&&e.offsetY>=t.top&&e.offsetY<=t.bottom&&this.on_close_user_error_popup()}}),[n]}};return new y(a)})()]:[])}}),inventory:new r({bounding_zone:this.canvas_zones.inventory,get_inventory:d,is_visible:g,invisible_clear_color:e("general").background_color,get_children:()=>{const i=this.get_scaled_inventory_settings(),a={left:this.canvas_zones.inventory.left+i.padding,top:this.canvas_zones.inventory.top+i.padding,right:this.canvas_zones.inventory.right-i.padding,bottom:this.canvas_zones.inventory.bottom-i.padding,width:this.canvas_zones.inventory.width-2*i.padding,height:this.canvas_zones.inventory.height-2*i.padding},r=i.gap;const c=(a.height-(i.rows-1)*r)/i.rows;var e=c*i.columns+r*(i.columns-1);const l=(a.width-e)/2,_=[];let h=0;return Array.from({length:i.rows}).forEach((e,t)=>{const s=a.top+t*(c+r);Array.from({length:i.columns}).forEach((e,t)=>{t=a.left+t*(c+r)+l;const o={left:t,top:s,right:t+c,bottom:s+c,width:c,height:c},n=u(i.background_color);t={bounding_zone:o,stroke_color:n,is_visible:g,settings:i,get_children:()=>{const s=new v({slot_index:h,is_visible:g,settings:i,stroke_color:n,get_game_object:e=>{const t=Array.from(d())[e];if(t){var n=t.get_dimensions(),e=n.width/n.height;const s={width:0,height:0},i={x:0,y:0};return 1<e?(s.width=c,s.height=c*(n.height/n.width),i.x=o.left,i.y=o.top+(c/2-s.height/2)):e<1?(s.height=c,s.width=c*e,i.y=o.top,i.x=o.left+(c/2-s.width/2)):(s.width=c,s.height=c,i.x=o.left,i.y=o.top),{ref:t,position:i,dimensions:s}}},bounding_zone:o,get_children:()=>[new b({is_visible:g,color:"#bf5e43",center:{x:o.left+c/2,y:o.top+c/2},radius:c/4,line_width:2<c/20?c/20:2,bounding_zone:{...o,clear_color:"rgba(0,0,0,0.2)"}})]});return s.add_event_listener({event_type:"mousemove",listener:e=>{var t=s.params.get_game_object(s.params.slot_index),n=s.params.bounding_zone;s.state.draw_inventory_close_icon=s.params.is_visible()&&!!t&&m().game_objects.includes(t.ref)&&e.offsetX>=n.left&&e.offsetX<=n.right&&e.offsetY>=n.top&&e.offsetY<=n.bottom}}),s.children[0].add_event_listener({event_type:"click",listener:e=>{if(!s.params.is_visible())return!1;var t=s.params.get_game_object(s.params.slot_index),n=s.params.bounding_zone,n=e.offsetX>=n.left&&e.offsetX<=n.right&&e.offsetY>=n.top&&e.offsetY<=n.bottom;t&&n&&m().game_objects.includes(t.ref)&&(this.params.on_drop_inventory_object(t.ref),this.clear_event_listeners(),this.clear_children({exclude:"TextBoxCpt"}))}}),[s]}},t=new w(t);_.push(t),h++})}),_}}),choices_panel:new o({bounding_zone:this.canvas_zones.choices_panel,minimum_bounding_zone:()=>{var e=this.canvas_zones.choices_panel,t=this.canvas_dimensions.choices_panel_metrics.get_minimum_panel_height_for_scene(_()),t=this.canvas_dimensions.choices_panel_metrics.get_total_height()-t;return Object.assign({...e},{bottom:e.bottom-t,height:e.height-t})},is_visible:g,invisible_clear_color:e("general").background_color,get_children:()=>Array.from({length:2*this.canvas_dimensions.choices_panel_metrics.get_max_choices_row_per_scene(_())}).map((e,n)=>{var t=this.get_scaled_choices_panel_settings();const s={left:this.canvas_zones.choices_panel.left+t.container_padding,right:this.canvas_zones.choices_panel.right-t.container_padding,top:this.canvas_zones.choices_panel.top+t.container_padding,bottom:this.canvas_zones.choices_panel.bottom-t.container_padding,width:0,height:0};s.width=s.right-s.left,s.height=s.bottom-s.top;var i=s.width/2;const{choices_panel_metrics:o}=this.canvas_dimensions;var a=o.rows.map(e=>e.text_height+e.padding_height);const r={left:s.left+n%2*i,top:s.top+(1<n?1:0)*a[0],right:s.left+n%2*i+i,bottom:s.top+(1<n?1:0)*a[1]+a[1<n?1:0],width:i,height:a[1<n?1:0]},c=new f({is_visible:g,minimum_bounding_zone:()=>{var e,t;return c.minimum_bounding_zone||(e=1<n?1:0,e=(t=this.canvas_dimensions.choices_panel_metrics.get_one_choices_row_metrics(_(),e)).text_height+t.padding_height,t=(()=>{const e=this.components.choices_panel.children;return 2<=e.indexOf(c)?r.top-e[0].params.minimum_bounding_zone().bottom:0})(),c.minimum_bounding_zone=Object.assign({...r},{height:e,top:r.top-t,bottom:r.top-t+e})),c.minimum_bounding_zone},get_formatted_choice:()=>{var e=this.canvas_dimensions.choices_panel_metrics.scenes_formatted_choices[l().indexOf(m())];return e.length-1>=n?e[n]:void 0},settings:t});return c.add_event_listener({event_type:"mousemove",listener:e=>{c.state.active=c.params.is_visible()&&!!c.params.get_formatted_choice()&&c.is_hover(e)}}),c.add_event_listener({event_type:"click",listener:()=>{var e;c.state.active&&(e=c.params.get_formatted_choice(),this.params.on_choice_click(e)&&(this.clear_event_listeners(),this.clear_children()))}}),c})})}}draw_loading(){const e=window.mentalo_drawing_context;e.save(),e.font="25px monospace",e.fillStyle="black",e.fillRect(0,0,window.innerWidth,window.innerHeight),e.fillStyle="white",e.fillText("Loading",50,window.innerHeight/2);var t=Array.from({length:++this.loading_frame}).map(()=>".").join("");e.font="8px monospace",e.fillText(t,50,window.innerHeight/2+20),e.restore()}draw_game(){Object.values(this.components).forEach(e=>e.draw())}}},{"../lib/color-tools":6,"../lib/font-tools":7,"../lib/frame-rate-controller":8,"../model/scene-types":18,"./canvas-dimensions":21,"./ui-components/choice-cpt":24,"./ui-components/choices-panel-cpt":25,"./ui-components/closing-icon-cpt":26,"./ui-components/game-object-cpt":27,"./ui-components/inventory-cpt":28,"./ui-components/inventory-object-cpt":29,"./ui-components/inventory-slot-cpt":30,"./ui-components/scene-animation-cpt":31,"./ui-components/text-box-cpt":32,"./ui-components/user-error-popup":34}],24:[function(e,t,n){"use strict";const{draw_text_in_bounds:l}=e("../../lib/text-tools"),{draw_rect:_}=e("../../lib/shape-tools");class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("ChoiceCpt")}draw(){super.draw();const{get_formatted_choice:e,settings:t}=this.params;var n,s,i,o=this.params.minimum_bounding_zone(),a={left:o.left+t.choice_padding,right:o.right-t.choice_padding,top:o.top+t.choice_padding,bottom:o.bottom-t.choice_padding,width:o.width-2*t.choice_padding,height:o.height-2*t.choice_padding},r=e(),c=window.mentalo_drawing_context;r&&(this.state.active&&({left:n,top:s,width:i,height:o}=o,_(c,n,s,i,o,{fill_color:t.active_choice_background_color,rounded_corners_radius:t.active_choice_rounded_corners_radius,border:{width:t.active_choice_border_width,color:t.font_color}})),l(c,r.text_lines,a,t,{},!1))}is_hover(e){var t=this.params.minimum_bounding_zone();return e.offsetX>=t.left&&e.offsetX<=t.right&&e.offsetY>=t.top&&e.offsetY<=t.bottom}}t.exports=s},{"../../lib/shape-tools":9,"../../lib/text-tools":10,"./ui-component":33}],25:[function(e,t,n){"use strict";class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("ChoicesPanelCpt")}draw(){super.draw(),this.params.is_visible()?(this.clear_bounding_zone({clear_color:this.params.invisible_clear_color}),this.clear_bounding_zone({use_bounding_zone:this.params.minimum_bounding_zone()}),this.draw_children()):this.clear_bounding_zone({clear_color:this.params.invisible_clear_color})}}t.exports=s},{"./ui-component":33}],26:[function(e,t,n){"use strict";class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("ClosingIconCpt")}draw(){super.draw(),this.clear_bounding_zone();var{color:e,center:t,radius:n=5,line_width:s=2,background_color:i="rgba(0,0,0,0)"}=this.params;const o=window.mentalo_drawing_context;var a=n/1.5,a={left:t.x-n+a,right:t.x+n-a,top:t.y-n+a,bottom:t.y+n-a};o.fillStyle=i,o.beginPath(),o.arc(t.x,t.y,n,0,2*Math.PI),o.fill(),o.strokeStyle=e,o.lineWidth=s,o.beginPath(),o.arc(t.x,t.y,n,0,2*Math.PI),o.stroke(),o.beginPath(),o.moveTo(a.left,a.top),o.lineTo(a.right,a.bottom),o.moveTo(a.left,a.bottom),o.lineTo(a.right,a.top),o.stroke()}}t.exports=s},{"./ui-component":33}],27:[function(e,t,n){"use strict";class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("GameObjectCpt")}draw(){super.draw();const{position:e,dimensions:t,image:n,is_in_inventory:s}=this.params;if(!s()){const i=window.mentalo_drawing_context;i.drawImage(n,0,0,n.width,n.height,e.x,e.y,t.w,t.h),this.state.draw_border&&(i.lineWidth=1,i.strokeStyle="rgba(180, 180, 180, 0.5)",i.strokeRect(e.x-5,e.y-5,t.w+10,t.h+10))}}}t.exports=s},{"./ui-component":33}],28:[function(e,t,n){"use strict";class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("InventoryCpt")}draw(){super.draw(),this.params.is_visible()?(this.clear_bounding_zone(),this.draw_children()):this.clear_bounding_zone({clear_color:this.params.invisible_clear_color})}}t.exports=s},{"./ui-component":33}],29:[function(e,t,n){"use strict";const{draw_rect:c}=e("../../lib/shape-tools");class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("InventoryObjectCpt")}draw(){super.draw();const{get_game_object:e,slot_index:t,settings:n,bounding_zone:s,stroke_color:i}=this.params;var o,a,r=e(t);r&&(o=window.mentalo_drawing_context,a=r.ref.image,c(o,s.left,s.top,s.width,s.height,{fill_image:{src:a,dw:r.dimensions.width,dh:r.dimensions.height,dx:r.position.x,dy:r.position.y},rounded_corners_radius:n.slot_rounded_corner_radius,border:{width:n.slot_border_width,color:i}}),this.state.draw_inventory_close_icon&&this.draw_children())}}t.exports=s},{"../../lib/shape-tools":9,"./ui-component":33}],30:[function(e,t,n){"use strict";const{draw_rect:r}=e("../../lib/shape-tools");class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("InventorySlotCpt")}draw(){super.draw();var{bounding_zone:e,stroke_color:t,settings:n}=this.params,{left:s,top:i,width:o,height:a}=e,e=window.mentalo_drawing_context;r(e,s,i,o,a,{fill_color:"rgba(0,0,0,0)",rounded_corners_radius:n.slot_rounded_corner_radius,border:{width:n.slot_border_width,color:t}}),this.draw_children()}}t.exports=s},{"../../lib/shape-tools":9,"./ui-component":33}],31:[function(e,t,n){"use strict";class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("SceneAnimationCpt"),this.framecount=0,this.state.text_box_visible=!0}set_text_box_visibility(e){this.state.text_box_visible=e}update_framecount(){this.framecount=this.framecount+1<=Number.MAX_SAFE_INTEGER?this.framecount+1:0}draw(){super.draw();const{bounding_zone:e,get_animation:t,next_frame_ready:n}=this.params;if(n()){this.clear_bounding_zone();const _=window.mentalo_drawing_context,h=t();h.update_frame(this.framecount);var s=h.dimensions,i=s.width,o=s.height,a=h.frame*i,r=e.width,c=e.height;h.canvas_precalc||(h.canvas_precalc={dx:e.left,dy:e.top,dw:r,dh:c},r/c<i/o?(h.canvas_precalc.dw=r,h.canvas_precalc.dh=r*(o/i),h.canvas_precalc.dy=e.top+(c-h.canvas_precalc.dh)/2):i/o<r/c?(h.canvas_precalc.dh=c,h.canvas_precalc.dw=c*(i/o),h.canvas_precalc.dx=e.left+(r-h.canvas_precalc.dw)/2):(h.canvas_precalc.dh=c,h.canvas_precalc.dw=r));var{dx:l,dy:s,dw:c,dh:r}=h.canvas_precalc;_.drawImage(h.image,a,0,i,o,l,s,c,r),this.draw_children(),this.update_framecount()}}}t.exports=s},{"./ui-component":33}],32:[function(e,t,n){"use strict";const{draw_text_in_bounds:r}=e("../../lib/text-tools"),{get_canvas_char_size:_}=e("../../lib/font-tools"),{draw_rect:c}=e("../../lib/shape-tools");class s extends e("./ui-component"){constructor(e){super(e),this.set_str_id("TextBoxCpt"),this.state.text={}}init(){const{text:e,settings:t,bounding_zone:n}=this.params;var s=_(window.mentalo_drawing_context,t),i=s.text_line_height,o=3*s.width;const a=(n.width-o)/s.width,r=[],c=e.split("\n");c.forEach(e=>{const t=[""];let n=0;for(const s of e.split(" "))(t[n]+s+" ").length>a&&(t.push(""),n++),t[n]+=s+" ";t.forEach(e=>r.push(e))});o=r.length*i-s.interline_height,s=Object.assign({...n},{height:n.height+o,top:n.top-o});this.state.text={lines:r,line_height:i,bounding_zone:s};const l=this.children[0];s=l.params.bounding_zone,Object.assign(s,{top:s.top-o,bottom:s.bottom-o});l.params.center.y-=o,this.state.initialized=!0}draw(){var e,t,n,s,i,o,a;this.params.get_visibility_state()&&(super.draw(),{settings:e}=this.params,this.state.initialized||this.init(),t=window.mentalo_drawing_context,{lines:n,bounding_zone:s}=this.state.text,i={x:s.left,y:s.top},o=s.width,a=s.height,c(t,i.x,i.y,o,a,{fill_color:e.background_color,rounded_corners_radius:e.rounded_corners_radius,border:{width:e.border_width,color:e.font_color}}),r(t,n,s,e,this.state.text,!0)&&this.draw_children())}}t.exports=s},{"../../lib/font-tools":7,"../../lib/shape-tools":9,"../../lib/text-tools":10,"./ui-component":33}],33:[function(e,t,n){"use strict";const{draw_rect:a}=e("../../lib/shape-tools");t.exports=class{constructor(e){this.set_str_id(),this.params=e,this.params.event_listeners=this.params.event_listeners||[],this.params.get_children=this.params.get_children||(()=>[]),this.params.is_visible=this.params.is_visible||(()=>!0),this.children=[],this.children_set=!1,this.set_children(),this.state={event_listeners_initialized:!1},this.init_event_listeners()}set_str_id(e=this.constructor.name){this.str_id=e}clear_bounding_zone(e={}){let{bounding_zone:t}=this.params;var{use_bounding_zone:n}=e;t=n||t;var{left:s,top:i,width:o,height:n}=t;a(window.mentalo_drawing_context,s,i,o,n,{fill_color:e.clear_color||t.clear_color||"rgba(0,0,0,0)"})}clear_event_listeners(e={recursive:!0}){this.params.event_listeners.forEach(e=>{window.removeEventListener(e.event_type,e.listener)}),e.recursive&&this.children.forEach(e=>{e.clear_event_listeners()}),this.state.event_listeners_initialized=!1}clear_children(e){const{exclude:t}=e,n=[];this.children.forEach(e=>{t.includes(e.str_id)&&n.push(e)}),this.children=n,this.children_set=!1}set_children(){this.children=this.children.concat(this.params.get_children().filter(t=>{return!this.children.find(e=>e.str_id===t.str_id)})),this.children_set=!0}add_event_listener(e){e=this.params.event_listeners.push(e),e=this.params.event_listeners[e-1];window.addEventListener(e.event_type,e.listener)}init_event_listeners(){const{event_listeners:e}=this.params;e.forEach(e=>this.add_event_listener(e)),this.state.event_listeners_initialized=!0}draw_children(){this.children_set||this.set_children(),this.children.forEach(e=>e.draw())}draw(){this.state.event_listeners_initialized||this.init_event_listeners()}}},{"../../lib/shape-tools":9}],34:[function(e,t,n){"use strict";const{draw_text_in_bounds:l}=e("../../lib/text-tools"),{draw_rect:_}=e("../../lib/shape-tools");class s extends e("./ui-component"){constructor(e){super(e),this.state={text:{}},this.set_str_id("UserErrorPopup")}draw(){super.draw();var{text_lines:e,bounding_zone:t,padding:n,settings:s,modal_bounds:i,clear_modal_color:o}=this.params;this.clear_bounding_zone({use_bounding_zone:i,clear_color:o});var a=window.mentalo_drawing_context,{left:r,top:c,width:i,height:o}=t;_(a,r,c,i,o,{fill_color:s.background_color,rounded_corners_radius:s.rounded_corners_radius,border:{width:s.border_width,color:s.font_color}}),l(a,e,t,Object.assign({...s},n),this.state.text,!!this.params.stream_text)&&this.draw_children()}}t.exports=s},{"../../lib/shape-tools":9,"../../lib/text-tools":10,"./ui-component":33}],35:[function(e,t,n){const s=["en","fr","es"],i={"Some scenes have an empty image, game cannot be loaded":{fr:"Certaines scènes ont une image vide, le jeu ne peut pas être chargé",es:"Algunas escenas tienen una imagen vacÃa, el juego no se puede cargar"},"Destination scene has not been set.":{fr:"La scène de destination n'a pas été définie.",es:"La escena de destino no ha sido definida."},"Next scene has not been set.":{fr:"La scène suivante n'a pas été définie",es:"La siguiente escena no ha sido definida."}};t.exports={get_translated:function(e,t){return i[e]&&"en"!==t&&s.includes(t)?i[e][t]:e},supported_locales:s}},{}],36:[function(e,t,n){"use strict";t.exports={register_key:"objectToHtmlRender",register(e){e=e||this.register_key;window[e]=this},setRenderCycleRoot(e){this.renderCycleRoot=e},event_name:"objtohtml-render-cycle",setEventName(e){this.event_name=e},objectToHtml(t){if(!t)return document.createElement("span");const n=this.objectToHtml.bind(this),{tag:e,xmlns:s}=t,i=void 0!==s?document.createElementNS(s,e):document.createElement(e),o=["tag","contents","style_rules","state","xmlns"];return Object.keys(t).filter(e=>!o.includes(e)).forEach(e=>{switch(e){case"class":i.classList.add(...t[e].split(" ").filter(e=>""!==e));break;case"on_render":t.id||(i.id=`${btoa(JSON.stringify(t).slice(0,127)).replace(/\=/g,"")}${window.performance.now()}`),"function"!=typeof t.on_render?console.error("The on_render attribute must be a function"):this.attach_on_render_callback(i,t.on_render);break;default:void 0!==s?i.setAttributeNS(null,e,t[e]):i[e]=t[e]}}),t.contents&&"string"==typeof t.contents?i.innerHTML=t.contents:t.contents&&0<t.contents.length&&t.contents.forEach(e=>{switch(typeof e){case"string":i.innerHTML=e;break;case"object":void 0!==s&&(e=Object.assign(e,{xmlns:s})),i.appendChild(n(e))}}),t.style_rules&&Object.keys(t.style_rules).forEach(e=>{i.style[e]=t.style_rules[e]}),i},on_render_callbacks:[],attach_on_render_callback(t,n){var e={callback:e=>{e.detail.outputNode!==t&&!e.detail.outputNode.querySelector(`#${t.id}`)||(n(t),-1===(e=this.on_render_callbacks.indexOf(this.on_render_callbacks.find(e=>e.node===t)))?console.warn("A callback was registered for node with id "+t.id+" but callbacck handler is undefined."):(window.removeEventListener(this.event_name,this.on_render_callbacks[e].callback),this.on_render_callbacks.splice(e,1)))},node:t},e=this.on_render_callbacks.push(e);window.addEventListener(this.event_name,this.on_render_callbacks[e-1].callback)},renderCycle:function(){var e,e=document.getElementsByTagName("main")[0]||(e=document.createElement("main"),document.body.appendChild(e),e);this.subRender(this.renderCycleRoot.render(),e,{mode:"replace"})},subRender(e,t,n={mode:"append"}){let s=null;var i=()=>(s=this.objectToHtml(e),s);switch(n.mode){case"append":t.appendChild(i());break;case"override":t.innerHTML="",t.appendChild(i());break;case"insert-before":t.insertBefore(i(),t.childNodes[n.insertIndex]);break;case"adjacent":t.insertAdjacentHTML(n.insertLocation,i());break;case"replace":t.parentNode.replaceChild(i(),t);break;case"remove":t.remove()}var o=this.event_name,o=new CustomEvent(o,{detail:{inputObject:e,outputNode:s,insertOptions:n,targetNode:t}});window.dispatchEvent(o)}}},{}],37:[function(e,t,n){"use strict";t.exports=class{constructor(e){this.props=e,this.id=this.props.images.join("").replace(/\s\./g),this.state={showImageIndex:0},this.RUN_INTERVAL=5e3,1<this.props.images.length&&this.run()}run(){this.runningInterval=setInterval(()=>{var{showImageIndex:e}=this.state,{images:t}=this.props;this.state.showImageIndex=e<t.length-1?++e:0,this.refreshImage()},this.RUN_INTERVAL)}setImageIndex(e){clearInterval(this.runningInterval),this.state.showImageIndex=e,this.refreshImage()}refreshImage(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}render(){const{showImageIndex:n}=this.state,{images:e}=this.props;return{tag:"div",id:this.id,class:"image-carousel",contents:[{tag:"img",property:"image",alt:`image carousel ${e[n].replace(/\.[A-Za-z]+/,"")}`,src:e[n]},1<e.length&&{tag:"div",class:"carousel-bullets",contents:e.map((e,t)=>{return{tag:"span",class:`bullet ${n===t?"active":""}`,onclick:this.setImageIndex.bind(this,t)}})}]}}}},{}],38:[function(e,t,n){"use strict";const{fetch_json_or_error_text:s}=e("./fetch");t.exports={loadArticles:function(e,t){return s(`/articles/${e}/${t}`)},getArticleBody:function(e){return e.replaceAll("\n","<br/>")},getArticleDate:function(e){return`${e.getDate()}-${e.getMonth()+1}-${e.getFullYear()}`}}},{"./fetch":39}],39:[function(e,t,n){"use strict";t.exports={fetchjson:function(e){return new Promise((t,n)=>{fetch(e).then(e=>e.json()).then(e=>t(e)).catch(e=>n(e))})},fetchtext:function(e){return new Promise((t,n)=>{fetch(e).then(e=>e.text()).then(e=>t(e)).catch(e=>n(e))})},fetch_json_or_error_text:async function(e,s={}){return new Promise((t,n)=>{fetch(e,s).then(async e=>{400<=e.status&&e.status<600?n(await e.text()):t(await e.json())})})}}},{}],40:[function(e,t,n){"use strict";const s=e("ks-cheap-translator"),{translations_url:i}=e("../../constants");t.exports=class{constructor(e){Object.assign(this,e),this.id||(this.id="webpage-"+performance.now()),s.init({translations_url:i,supported_languages:["fr","en"]}).then(this.refresh_all.bind(this))}refresh(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}refresh_all(){obj2htm.renderCycle()}}},{"../../constants":3,"ks-cheap-translator":4}],41:[function(e,t,n){"use strict";const{images_url:i}=e("../../../../../admin-frontend/src/constants"),{data_url:s}=e("../../../../constants"),o=e("../../../generic-components/image-carousel"),{getArticleBody:a}=e("../../../lib/article-utils"),{fetch_json_or_error_text:r}=e("../../../lib/fetch"),{MentaloEngine:c}=e("mentalo-engine"),l=e("ks-cheap-translator"),_=l.trad.bind(l);t.exports=class{constructor(e){this.props=e,this.parse_body()}parse_body(){let e=a(this.props.body);var t=/\[PLAY_BUTTON\s\{.+\}\]/g;const n=e.match(t);n&&(this.build_play_button(JSON.parse(n[0].replace(/[\[\]PLAY_BUTTON\s]/g,""))),e=e.replace(t,"")),this.body=e}build_play_button(e){this.render_play_button={tag:"button",class:"play-button",contents:_("Jouer"),onclick:this.handle_click_play.bind(this,e.filename,e.engine)}}load_and_run_mentalo_game(e,t){const n=t.innerHTML;t.innerHTML="Loading ...",t.style.pointerEvents="none",r(`${s}/${e}`).then(e=>{const t=document.createElement("div");t.style.position="fixed",t.style.top=0,t.style.left=0,t.style.right=0,t.style.bottom=0,t.style.zIndex=10,t.style.display="flex",t.style.justifyContent="center",t.style.alignItems="center",t.id="kuadrado-tmp-game-player-container",document.body.appendChild(t),document.body.style.overflow="hidden";const n=new c({game_data:e,fullscreen:!0,frame_rate:30,container:t,on_quit_game:()=>{t.remove(),document.body.style.overflow="visible"}});n.init(),n.run_game()}).catch(e=>console.log(e)).finally(()=>{t.innerHTML=n,t.style.pointerEvents="unset"})}handle_click_play(e,t,n){"mentalo"===t?this.load_and_run_mentalo_game(e,n.target):console.log("Error, unkown engine")}render(){const{title:e,subtitle:t,images:n,details:s}=this.props;return{tag:"article",typeof:"VideoGame",additionalType:"Article",class:"game-article",contents:[{tag:"h2",property:"name",class:"game-title",contents:e},{tag:"div",class:"game-banner",contents:[{tag:"img",class:"pixelated",src:`${i}/${n[0]}`}]},{tag:"h3",class:"game-subtitle",contents:t,property:"alternativeHeadline"},{tag:"div",class:"game-description",property:"description",contents:[{tag:"p",style_rules:{margin:0},contents:this.body}].concat(this.render_play_button?[this.render_play_button]:[])},new o({images:n.map(e=>`${i}/${e}`)}).render(),0<s.length&&{tag:"div",class:"article-details",contents:[{tag:"h2",contents:"Details"},{tag:"ul",class:"details-list",contents:s.map(e=>({tag:"li",class:"detail",contents:[{tag:"label",contents:e.label},{tag:"div",class:"detail-value",contents:e.value}]}))}]}]}}}},{"../../../../../admin-frontend/src/constants":1,"../../../../constants":3,"../../../generic-components/image-carousel":37,"../../../lib/article-utils":38,"../../../lib/fetch":39,"ks-cheap-translator":4,"mentalo-engine":5}],42:[function(e,t,n){"use strict";const{loadArticles:s}=e("../../../lib/article-utils"),i=e("./game-article"),o=e("ks-cheap-translator");t.exports=class{constructor(e){this.props=e,this.state={articles:[]},this.id="game-articles-section",this.loadArticles()}loadArticles(){s("games",o.locale).then(e=>{this.state.articles=e,this.refresh()}).catch(e=>console.log(e))}renderPlaceholder(){return{tag:"article",class:"placeholder",contents:[{tag:"div"},{tag:"div"}]}}refresh(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}render(){const{articles:e}=this.state;return{tag:"section",class:"game-articles page-contents-center",id:this.id,contents:0<e.length?e.map(e=>new i({...e}).render()):[this.renderPlaceholder()]}}}},{"../../../lib/article-utils":38,"./game-article":41,"ks-cheap-translator":4}],43:[function(e,t,n){"use strict";const{images_url:s}=e("../../../constants");var i=e("../../lib/web-page");const o=e("./components/game-articles"),a=e("ks-cheap-translator"),r=a.trad.bind(a);class c extends i{render(){return{tag:"div",id:"games-page",contents:[{tag:"div",class:"page-header logo-left",contents:[{tag:"div",class:"page-contents-center grid-wrapper",contents:[{tag:"div",class:"logo",contents:[{tag:"img",alt:"image game controller",src:`${s}/game_controller.svg`}]},{tag:"h1",contents:r("Jeux")},{tag:"p",contents:r("games-page-intro")}]}]},(new o).render()]}}}t.exports=c},{"../../../constants":3,"../../lib/web-page":40,"./components/game-articles":42,"ks-cheap-translator":4}],44:[function(e,t,n){"use strict";const s=e("../../run-page");e=e("./games");s(e)},{"../../run-page":45,"./games":43}],45:[function(e,t,n){"use strict";const s=e("object-to-html-renderer"),i=e("./template/template");t.exports=function(e){e=new i({page:new e});s.register("obj2htm"),obj2htm.setRenderCycleRoot(e),obj2htm.renderCycle()}},{"./template/template":47,"object-to-html-renderer":36}],46:[function(e,t,n){"use strict";const{images_url:s}=e("../../../constants"),a=e("ks-cheap-translator"),r=a.trad.bind(a),i=[{url:"/games/",text:"Jeux"},{url:"/education/",text:"Pédagogie"},{url:"/software-development/",text:"Software"}];t.exports=class{constructor(){this.initEventHandlers()}handleBurgerClick(){document.getElementById("nav-menu-list").classList.toggle("responsive-show")}initEventHandlers(){window.addEventListener("click",e=>{"nav-menu-list"===e.target.id||e.target.classList.contains("burger")||e.target.parentNode.classList.contains("burger")||document.getElementById("nav-menu-list").classList.remove("responsive-show")})}handle_chang_lang(e){a.update_translations(e).then(()=>{obj2htm.renderCycle()}).catch(e=>console.log(e))}renderHome(){return{tag:"div",class:"home",contents:[{tag:"a",href:"/",contents:[{tag:"img",alt:"Logo Kuadrado",src:`${s}/logo_kuadrado.svg`},{tag:"img",alt:"Kuadrado Software",class:"logo-text",src:`${s}/logo_kuadrado_txt.svg`}]}]}}renderMenu(e,i=!1,o=""){return{tag:"ul",id:"nav-menu-list",class:i?"submenu":"",contents:e.map(e=>{var{url:t,text:n,submenu:e}=e;const s=`${o}${t}`;return{tag:"li",class:i||window.location.pathname!==s?"":"active",contents:[{tag:"a",href:s,contents:r(n)}].concat(e?[this.renderMenu(e,!0,t)]:[])}}).concat({tag:"li",class:"lang-flags",contents:["fr","en"].map(e=>({tag:"img",src:`${s}/flag-${e}.svg`,class:a.locale===e?"selected":"",onclick:this.handle_chang_lang.bind(this,e)}))})}}renderResponsiveBurger(){return{tag:"div",class:"burger",onclick:this.handleBurgerClick.bind(this),contents:[{tag:"span",contents:"···"}]}}render(){return{tag:"nav",contents:[this.renderHome(),this.renderResponsiveBurger(),this.renderMenu(i)]}}}},{"../../../constants":3,"ks-cheap-translator":4}],47:[function(e,t,n){"use strict";const{in_construction:s}=e("../../config"),{images_url:i}=e("../../constants"),o=e("./components/navbar"),a=e("ks-cheap-translator"),r=a.trad.bind(a);t.exports=class{constructor(e){this.props=e}render(){return{tag:"main",contents:[{tag:"header",contents:[(new o).render()]},s&&{tag:"section",class:"warning-banner",contents:[{tag:"strong",class:"page-contents-center",contents:r("Site en construction ...")}]},{tag:"section",id:"page-container",contents:[this.props.page.render()]},{tag:"footer",contents:[{tag:"div",class:"logo",contents:[{tag:"img",alt:"logo Kuadrado",src:`${i}/logo_kuadrado.svg`},{tag:"img",class:"text-logo",alt:"Kuadrado Software",src:`${i}/logo_kuadrado_txt.svg`}]},{tag:"span",contents:"32 rue Simon Vialet, 07240 Vernoux en Vivarais. Ardèche, France"},{tag:"div",contents:[{tag:"strong",contents:"<blue>Contact : </blue>"},{tag:"a",href:"mailto:contact@kuadrado-software.fr",contents:"contact@kuadrado-software.fr"}]},{tag:"div",class:"social",contents:[{tag:"strong",contents:`<blue>${r("Sur les réseaux")} : </blue>`},{tag:"a",href:"https://www.linkedin.com/company/kuadrado-software",target:"_blank",contents:"in",title:"Linkedin"},{tag:"a",href:"https://mastodon.gamedev.place/@kuadrado_software",target:"_blank",contents:"m",title:"Mastodon"}]},{tag:"span",contents:`Copyleft 🄯 ${(new Date).getFullYear()} Kuadrado Software | + ${r("kuadrado-footer-copyleft")}`},{tag:"div",contents:[{tag:"span",contents:r("Ce site web est")+" "},{tag:"a",target:"_blank",style_rules:{fontWeight:"bold"},href:"https://gitlab.com/kuadrado-software/kuadrado-website/-/blob/master/README.md",contents:"OPEN SOURCE"}]}]}]}}}},{"../../config":2,"../../constants":3,"./components/navbar":46,"ks-cheap-translator":4}]},{},[44]); \ No newline at end of file diff --git a/public/main.js b/public/main.js index 1e3f2ac..f94c5f5 100644 --- a/public/main.js +++ b/public/main.js @@ -1,846 +1,2 @@ -(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ -module.exports = { - build: { - protected_dirs: ["assets", "style", "views", "standard"], - default_meta_keys: ["title", "description", "image", "open_graph", "json_ld"], - }, -}; - -},{}],2:[function(require,module,exports){ -module.exports = { - images_url: `/assets/images`, - data_url: `/assets/data`, - translations_url: "/assets/translations" -}; - -},{}],3:[function(require,module,exports){ -"use strict"; -/** - * A tiny library to handle text translation. - */ - -/** - * init parameter object type - * @typedef TranslatorParam - * @property {String[]} supported_languages - DEFAULT: ["en"] - An array of ISO 639 lowercase language codes - * @property {String} locale ISO 639 lowercase language code - DEFAULT: "en" - * - * @property {Boolean} use_url_locale_fragment - DEFAULT: true - - * If true, the 1st fragment of the window.location url will be parsed as a language code - * Example: - * window.location - * > https://example.com/en/some-page... - * then the /en/ part of the url will be taken in priority and locale will be set to "en". - * window.location - * > https://example.com/some-page... - * No locale fragment will be found so locale will not be set from url fragment. - * window.location - * > https://example.com/some-page/en - * Doesn't work, locale fragment must be the first framgment - * - * @property {String} local_storage_key - DEFAULT: "translator-prefered-language" - * The key used to saved the current locale into local storage - * - * @property {String} translations_url - REQUIRED - the url of the directory containing the static json files for translations - * Translations files are expected to be named with their corresponding locale code. - * Example: - * if supported_languages is set to ["en", "fr", "it"] - * and translations_url is set to "https://example.com/translations/"" - * Then the expected translations files are - * https://example.com/translations/en.json - * https://example.com/translations/fr.json - * https://example.com/translations/it.json - * The json resources must simple key value maps, value being the translated text. - */ - -module.exports = { - locale: "en", // ISO 639 lowercase language code - supported_languages: ["en"], - translations: {}, - translations_url: "", - use_url_locale_fragment: true, - local_storage_key: "translator-prefered-language", - - /** - * Initialize the lib with params - * @param {TranslatorParam} params - * @returns {Promise} - */ - init(params) { - Object.entries(params).forEach(k_v => { - const [key, value] = k_v; - if ([ - "supported_languages", - "use_url_locale_fragment", - "local_storage_key", - "translations_url" - ].includes(key)) { - this[key] = value; - } - }); - - this.translations_url = this.format_translations_url(this.translations_url); - this.supported_languages = this.format_supported_languages(this.supported_languages); - - return new Promise((resolve, reject) => { - const loc = - (() => {// Locale from url priority 1 - if (this.use_url_locale_fragment) { - const first_url_fragment = window.location.pathname.substring(1).split("/")[0]; - const fragment_is_locale = this.supported_languages.includes(first_url_fragment); - return fragment_is_locale ? first_url_fragment : ""; - } else { - return ""; - } - })() - || localStorage.getItem(this.local_storage_key) // Locale from storage priority 2 - || (() => { // locale from navigator priority 3 - const navigator_locale = navigator.language.split("-")[0].toLocaleLowerCase(); - return this.supported_languages.includes(navigator_locale) - ? navigator_locale - : this.supported_languages[0] // Default if navigator locale is not supported - })(); - - fetch(`${this.translations_url}${loc}.json`) - .then(response => response.json()) - .then(response => { - this.locale = loc; - this.translations = response; - resolve(); - }) - .catch(err => { - this.locale = "en"; - reject(err); - }); - }); - }, - - /** - * Return a lowercase string without dash. - * If given locale is en-EN, then "en" will be returned. - * @param {String} locale - * @returns A lowercase string - */ - format_locale(locale) { - return locale.split("-")[0].toLowerCase(); - }, - - /** - * Appends a slash at the end of the given string if missing, and returns the url. - * @param {String} url - * @returns {String} - */ - format_translations_url(url) { - if (url.charAt(url.length - 1) !== "/") { - url += "/" - } - return url; - }, - - /** - * Return the array of language codes formatted as lowsercase language code. - * if ["en-EN", "it-IT"]is given, ["en", "it"] will b returned. - * @param {String[]} languages_codes - * @returns {String[]} - */ - format_supported_languages(languages_codes) { - return languages_codes.map(lc => this.format_locale(lc)); - }, - - /** - * Fetches a new set of translations in case the wanted language changes - * @param {String} locale A lowercase language code - * @returns {Promise} - */ - update_translations(locale) { - locale = this.format_locale(locale); - return new Promise((resolve, reject) => { - fetch(`${this.translations_url}${locale}.json`) - .then(response => response.json()) - .then(response => { - this.translations = response; - this.locale = locale; - - localStorage.setItem(this.local_storage_key, locale); - - const split_path = window.location.pathname.substring(1).split("/"); - const first_url_fragment = split_path[0]; - const fragment_is_locale = this.supported_languages.includes(first_url_fragment); - - if (fragment_is_locale) { - split_path.splice(0, 1, locale); - const updated_path = split_path.join("/"); - window.history.replaceState(null, "", "/" + updated_path); - } - resolve(); - }) - .catch(err => { - reject(err); - }); - }); - }, - - /** - * Tries to get the translation of the source string, or return the string as it. - * @param {String} text The source text to translate - * @param {Object} params Some dynamic element to insert in the text - * Params can be used if the translated text provide placeholder like {%some_word%} - * Example: - * translator.trad("Some trad key", {some_word: "a dynamic parameter"}) - * -> translation for "Some trad key": "A translated text with {%some_word%}" - * -> will be return as : "A translated text with a dynamic parameter" - * @returns {String} - */ - trad: function (text, params = {}) { - text = this.translations[text] || text; - - Object.keys(params).forEach(k => { - text = text.replace(`{%${k}%}`, params[k]); - }); - - return text; - } -}; -},{}],4:[function(require,module,exports){ -"use strict"; - -module.exports = { - register_key: "objectToHtmlRender", - - /** - * Register "this" as a window scope accessible variable named by the given key, or default. - * @param {String} key - */ - register(key) { - const register_key = key || this.register_key; - window[register_key] = this; - }, - - /** - * This must be called before any other method in order to initialize the lib. - * It provides the root of the rendering cycle as a Javascript object. - * @param {Object} renderCycleRoot A JS component with a render method. - */ - setRenderCycleRoot(renderCycleRoot) { - this.renderCycleRoot = renderCycleRoot; - }, - - event_name: "objtohtml-render-cycle", - - /** - * Set a custom event name for the event that is trigger on render cycle. - * Default is "objtohtml-render-cycle". - * @param {String} evt_name - */ - setEventName(evt_name) { - this.event_name = evt_name; - }, - - /** - * This is the core agorithm that read an javascript Object and convert it into an HTML element. - * @param {Object} obj The object representing the html element must be formatted like: - * { - * tag: String // The name of the html tag, Any valid html tag should work. div, section, br, ul, li... - * xmlns: String // This can replace the tag key if the element is an element with a namespace URI, for example an <svg> tag. - * See https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS for more information - * style_rules: Object // a object providing css attributes. The attributes names must be in JS syntax, - * like maxHeight: "500px", backgrouncColor: "#ff2d56", margin: 0, etc. - * contents: Array or String // This reprensents the contents that will be nested in the created html element. - * <div>{contents}</div> - * The contents can be an array of other objects reprenting elements (with tag, contents, etc) - * or it can be a simple string. - * // All other attributes will be parsed as html attributes. They can be anything like onclick, href, onchange, title... - * // or they can also define custom html5 attributes, like data, my_custom_attr or anything. - * } - * @returns {HTMLElement} The output html node. - */ - objectToHtml(obj) { - if (!obj) return document.createElement("span"); // in case of invalid input, don't block the whole process. - const objectToHtml = this.objectToHtml.bind(this); - const { tag, xmlns } = obj; - const node = xmlns !== undefined ? document.createElementNS(xmlns, tag) : document.createElement(tag); - const excludeKeys = ["tag", "contents", "style_rules", "state", "xmlns"]; - - Object.keys(obj) - .filter(attr => !excludeKeys.includes(attr)) - .forEach(attr => { - switch (attr) { - case "class": - node.classList.add(...obj[attr].split(" ").filter(s => s !== "")); - break; - case "on_render": - if (!obj.id) { - node.id = `${btoa(JSON.stringify(obj).slice(0, 127)).replace(/\=/g, '')}${window.performance.now()}`; - } - if (typeof obj.on_render !== "function") { - console.error("The on_render attribute must be a function") - } else { - this.attach_on_render_callback(node, obj.on_render); - } - break; - default: - if (xmlns !== undefined) { - node.setAttributeNS(null, attr, obj[attr]) - } else { - node[attr] = obj[attr]; - } - } - }); - if (obj.contents && typeof obj.contents === "string") { - node.innerHTML = obj.contents; - } else { - obj.contents && - obj.contents.length > 0 && - obj.contents.forEach(el => { - switch (typeof el) { - case "string": - node.innerHTML = el; - break; - case "object": - if (xmlns !== undefined) { - el = Object.assign(el, { xmlns }) - } - node.appendChild(objectToHtml(el)); - break; - } - }); - } - - if (obj.style_rules) { - Object.keys(obj.style_rules).forEach(rule => { - node.style[rule] = obj.style_rules[rule]; - }); - } - - return node; - }, - - on_render_callbacks: [], - - /** - * This is called if the on_render attribute of a component is set. - * @param {HTMLElement} node The created html element - * @param {Function} callback The callback defined in the js component to render - */ - attach_on_render_callback(node, callback) { - const callback_handler = { - callback: e => { - if (e.detail.outputNode === node || e.detail.outputNode.querySelector(`#${node.id}`)) { - callback(node); - const handler_index = this.on_render_callbacks.indexOf((this.on_render_callbacks.find(cb => cb.node === node))); - if (handler_index === -1) { - console.warn("A callback was registered for node with id " + node.id + " but callbacck handler is undefined.") - } else { - window.removeEventListener(this.event_name, this.on_render_callbacks[handler_index].callback) - this.on_render_callbacks.splice(handler_index, 1); - } - } - }, - node, - }; - - const len = this.on_render_callbacks.push(callback_handler); - window.addEventListener(this.event_name, this.on_render_callbacks[len - 1].callback); - }, - - /** - * If a main element exists in the html document, it will be used as rendering root. - * If not, it will be created and inserted. - */ - renderCycle: function () { - const main_elmt = document.getElementsByTagName("main")[0] || (function () { - const created_main = document.createElement("main"); - document.body.appendChild(created_main); - return created_main; - })(); - - this.subRender(this.renderCycleRoot.render(), main_elmt, { mode: "replace" }); - }, - - /** - * This method behaves like the renderCycle() method, but rather that starting the rendering cycle from the root component, - * it can start from any component of the tree. The root component must be given as the first argument, the second argument be - * be a valid html element in the dom and will be used as the insertion target. - * @param {Object} object An object providing a render method returning an object representation of the html to insert - * @param {HTMLElement} htmlNode The htlm element to update - * @param {Object} options can be used the define the insertion mode, default is set to "append" and can be set to "override", - * "insert-before" (must be defined along with an insertIndex key (integer)), - * "adjacent" (must be defined along with an insertLocation key (String)), "replace" or "remove". - * In case of "remove", the first argument "object" is not used and can be set to null, undefined or {}... - */ - subRender(object, htmlNode, options = { mode: "append" }) { - let outputNode = null; - - const get_insert = () => { - outputNode = this.objectToHtml(object); - return outputNode; - }; - - switch (options.mode) { - case "append": - htmlNode.appendChild(get_insert()); - break; - case "override": - htmlNode.innerHTML = ""; - htmlNode.appendChild(get_insert()); - break; - case "insert-before": - htmlNode.insertBefore(get_insert(), htmlNode.childNodes[options.insertIndex]); - break; - case "adjacent": - /** - * options.insertLocation must be one of: - * - * afterbegin - * afterend - * beforebegin - * beforeend - */ - htmlNode.insertAdjacentHTML(options.insertLocation, get_insert()); - break; - case "replace": - htmlNode.parentNode.replaceChild(get_insert(), htmlNode); - break; - case "remove": - htmlNode.remove(); - break; - } - const evt_name = this.event_name; - const event = new CustomEvent(evt_name, { - detail: { - inputObject: object, - outputNode, - insertOptions: options, - targetNode: htmlNode, - } - }); - - window.dispatchEvent(event); - }, -}; -},{}],5:[function(require,module,exports){ -"use strict"; - -const { images_url } = require("../../constants"); - -class ThemeCard { - constructor(props) { - this.props = props; - } - - render() { - return { - tag: "a", - class: "theme-card", - href: this.props.href, - contents: [ - { - tag: "div", - class: "card-img", - contents: [{ tag: "img", alt: `thematic image ${this.props.img.replace(/\.[A-Za-z]+/, "")}`, src: `${images_url}/${this.props.img}` }], - }, - { - tag: "div", - class: "card-title", - contents: [{ tag: "h2", class: "section-title", contents: this.props.title }], - }, - { - tag: "div", - class: "card-description", - contents: [{ tag: "p", contents: this.props.description }], - }, - ], - }; - } -} - -module.exports = ThemeCard; - -},{"../../constants":2}],6:[function(require,module,exports){ -"use strict"; - -const { images_url } = require("../constants"); -const ThemeCard = require("./home-page-components/theme-card"); -const WebPage = require("./lib/web-page"); -const translator = require("ks-cheap-translator"); -const t = translator.trad.bind(translator); - -class HomePage extends WebPage { - constructor() { - super({ id: "home-page" }); - } - - render() { - return { - tag: "div", - id: this.id, - contents: [ - { - tag: "div", - class: "page-header", - contents: [ - { - tag: "div", - class: "big-logo page-contents-center", - contents: [ - { - tag: "img", - alt: "logo Kuadrado", - src: `${images_url}/logo_kuadrado.svg`, - }, - { - tag: "img", - class: "logo-text", - alt: "Kuadrado", - src: `${images_url}/logo_kuadrado_txt.svg`, - }, - ], - }, - { tag: "h1", contents: "Kuadrado Software", class: "page-contents-center" }, - { - tag: "p", - class: "page-contents-center", - contents: t("kuadrado-home-description"), - }, - { - tag: "ul", - class: "philo-bubbles", - contents: [t("Simplicité"), t("Légèreté"), t("Écologie")].map(word => { - return { - tag: "li", - contents: [{ tag: "span", contents: word }], - }; - }), - }, - ], - }, - { - tag: "section", - class: "page-contents-center poles", - contents: [ - { - title: t("Jeux"), - img: "game_controller.svg", - href: "/games/", - description: - t("games-description"), - }, - { - title: t("Pédagogie"), - img: "brain.svg", - href: "/education/", - description: t("education-description"), - }, - { - title: "Software", - img: "meca_proc.svg", - href: "/software-development/", - description: t("software-description"), - }, - ].map(cardProps => new ThemeCard(cardProps).render()), - }, - ], - }; - } -} - -module.exports = HomePage; - -},{"../constants":2,"./home-page-components/theme-card":5,"./lib/web-page":7,"ks-cheap-translator":3}],7:[function(require,module,exports){ -"use strict"; -const translator = require("ks-cheap-translator"); -const { translations_url } = require("../../constants"); - -class WebPage { - constructor(args) { - Object.assign(this, args); - - if (!this.id) { - this.id = "webpage-" + performance.now(); - } - - translator.init({ - translations_url, - supported_languages: ["fr", "en"], - }).then(this.refresh_all.bind(this)); - } - - refresh() { - obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace" }) - } - - refresh_all() { - obj2htm.renderCycle() - } -} - -module.exports = WebPage; -},{"../../constants":2,"ks-cheap-translator":3}],8:[function(require,module,exports){ -"use strict"; - -const HomePage = require("./homepage"); -const runPage = require("./run-page"); - -runPage(HomePage); - -},{"./homepage":6,"./run-page":9}],9:[function(require,module,exports){ -"use strict"; - -const renderer = require("object-to-html-renderer") -const Template = require("./template/template"); - -module.exports = function runPage(PageComponent) { - const template = new Template({ page: new PageComponent() }); - renderer.register("obj2htm") - obj2htm.setRenderCycleRoot(template); - obj2htm.renderCycle(); -}; - -},{"./template/template":11,"object-to-html-renderer":4}],10:[function(require,module,exports){ -"use strict"; - -const { images_url } = require("../../../constants"); -const translator = require("ks-cheap-translator"); -const t = translator.trad.bind(translator); - -const NAV_MENU_ITEMS = [ - { url: "/games/", text: "Jeux" }, - { - url: "/education/", - text: "Pédagogie", - }, - { url: "/software-development/", text: "Software" } -]; - -class NavBar { - constructor() { - this.initEventHandlers(); - } - - handleBurgerClick() { - document.getElementById("nav-menu-list").classList.toggle("responsive-show"); - } - - initEventHandlers() { - window.addEventListener("click", event => { - if ( - event.target.id !== "nav-menu-list" && - !event.target.classList.contains("burger") && - !event.target.parentNode.classList.contains("burger") - ) { - document.getElementById("nav-menu-list").classList.remove("responsive-show"); - } - }); - } - - handle_chang_lang(lang) { - translator.update_translations(lang).then(() => { - obj2htm.renderCycle(); - }).catch(err => console.log(err)); - } - - renderHome() { - return { - tag: "div", - class: "home", - contents: [ - { - tag: "a", - href: "/", - contents: [ - { - tag: "img", - alt: "Logo Kuadrado", - src: `${images_url}/logo_kuadrado.svg`, - }, - { - tag: "img", - alt: "Kuadrado Software", - class: "logo-text", - src: `${images_url}/logo_kuadrado_txt.svg`, - }, - ], - }, - ], - }; - } - - renderMenu(menuItemsArray, isSubmenu = false, parentUrl = "") { - return { - tag: "ul", - id: "nav-menu-list", - class: isSubmenu ? "submenu" : "", - contents: menuItemsArray.map(item => { - const { url, text, submenu } = item; - const href = `${parentUrl}${url}`; - return { - tag: "li", - class: !isSubmenu && window.location.pathname === href ? "active" : "", - contents: [ - { - tag: "a", - href, - contents: t(text), - }, - ].concat(submenu ? [this.renderMenu(submenu, true, url)] : []), - }; - }).concat({ - tag: "li", - class: "lang-flags", - contents: ["fr", "en"].map(lang => { - return { - tag: "img", src: `${images_url}/flag-${lang}.svg`, - class: translator.locale === lang ? "selected" : "", - onclick: this.handle_chang_lang.bind(this, lang) - } - }) - }), - }; - } - - renderResponsiveBurger() { - return { - tag: "div", - class: "burger", - onclick: this.handleBurgerClick.bind(this), - contents: [{ tag: "span", contents: "···" }], - }; - } - - render() { - return { - tag: "nav", - contents: [ - this.renderHome(), - this.renderResponsiveBurger(), - this.renderMenu(NAV_MENU_ITEMS), - ], - }; - } -} - -module.exports = NavBar; - -},{"../../../constants":2,"ks-cheap-translator":3}],11:[function(require,module,exports){ -"use strict"; - -const { in_construction } = require("../../config"); -const { images_url } = require("../../constants"); -const NavBar = require("./components/navbar"); -const translator = require("ks-cheap-translator"); -const t = translator.trad.bind(translator) - -class Template { - constructor(props) { - this.props = props; - } - render() { - return { - tag: "main", - contents: [ - { - tag: "header", - contents: [new NavBar().render()], - }, - in_construction && { - tag: "section", - class: "warning-banner", - contents: [ - { - tag: "strong", - class: "page-contents-center", - contents: t("Site en construction ..."), - }, - ], - }, - { - tag: "section", - id: "page-container", - contents: [this.props.page.render()], - }, - { - tag: "footer", - contents: [ - { - tag: "div", - class: "logo", - contents: [ - { - tag: "img", - alt: `logo Kuadrado`, - src: `${images_url}/logo_kuadrado.svg`, - }, - { - tag: "img", - class: "text-logo", - alt: "Kuadrado Software", - src: `${images_url}/logo_kuadrado_txt.svg`, - }, - ], - }, - { - tag: "span", - contents: "32 rue Simon Vialet, 07240 Vernoux en Vivarais. Ardèche, France", - }, - { - tag: "div", - contents: [ - { tag: "strong", contents: "<blue>Contact : </blue>" }, - { - tag: "a", - href: "mailto:contact@kuadrado-software.fr", - contents: "contact@kuadrado-software.fr", - }, - ], - }, - { - tag: "div", - class: "social", - contents: [ - { - tag: "strong", - contents: `<blue>${t("Sur les réseaux")} : </blue>`, - }, - { - tag: "a", - href: "https://www.linkedin.com/company/kuadrado-software", - target: "_blank", - contents: "in", - title: "Linkedin", - }, - { - tag: "a", - href: "https://mastodon.gamedev.place/@kuadrado_software", - target: "_blank", - contents: "m", - title: "Mastodon", - } - ], - }, - { - tag: "span", - contents: `Copyleft 🄯 ${new Date() - .getFullYear()} Kuadrado Software | - ${t("kuadrado-footer-copyleft")}`, - }, - { - tag: "div", contents: [ - { tag: "span", contents: t("Ce site web est") + " " }, - { - tag: "a", target: "_blank", - style_rules: { fontWeight: "bold" }, - href: "https://gitlab.com/kuadrado-software/kuadrado-website/-/blob/master/README.md", - contents: "OPEN SOURCE" - } - ] - } - ], - }, - ], - }; - } -} - -module.exports = Template; - -},{"../../config":1,"../../constants":2,"./components/navbar":10,"ks-cheap-translator":3}]},{},[8]); +!function s(a,r,o){function c(e,t){if(!r[e]){if(!a[e]){var n="function"==typeof require&&require;if(!t&&n)return n(e,!0);if(i)return i(e,!0);throw(n=new Error("Cannot find module '"+e+"'")).code="MODULE_NOT_FOUND",n}n=r[e]={exports:{}},a[e][0].call(n.exports,function(t){return c(a[e][1][t]||t)},n,n.exports,s,a,r,o)}return r[e].exports}for(var i="function"==typeof require&&require,t=0;t<o.length;t++)c(o[t]);return c}({1:[function(t,e,n){e.exports={build:{protected_dirs:["assets","style","views","standard"],default_meta_keys:["title","description","image","open_graph","json_ld"]}}},{}],2:[function(t,e,n){e.exports={images_url:"/assets/images",data_url:"/assets/data",translations_url:"/assets/translations"}},{}],3:[function(t,e,n){"use strict";e.exports={locale:"en",supported_languages:["en"],translations:{},translations_url:"",use_url_locale_fragment:!0,local_storage_key:"translator-prefered-language",init(t){return Object.entries(t).forEach(t=>{var[e,t]=t;["supported_languages","use_url_locale_fragment","local_storage_key","translations_url"].includes(e)&&(this[e]=t)}),this.translations_url=this.format_translations_url(this.translations_url),this.supported_languages=this.format_supported_languages(this.supported_languages),new Promise((e,n)=>{const s=(()=>{if(this.use_url_locale_fragment){var t=window.location.pathname.substring(1).split("/")[0];return this.supported_languages.includes(t)?t:""}return""})()||localStorage.getItem(this.local_storage_key)||(t=navigator.language.split("-")[0].toLocaleLowerCase(),this.supported_languages.includes(t)?t:this.supported_languages[0]);var t;fetch(`${this.translations_url}${s}.json`).then(t=>t.json()).then(t=>{this.locale=s,this.translations=t,e()}).catch(t=>{this.locale="en",n(t)})})},format_locale(t){return t.split("-")[0].toLowerCase()},format_translations_url(t){return"/"!==t.charAt(t.length-1)&&(t+="/"),t},format_supported_languages(t){return t.map(t=>this.format_locale(t))},update_translations(s){return s=this.format_locale(s),new Promise((n,e)=>{fetch(`${this.translations_url}${s}.json`).then(t=>t.json()).then(t=>{this.translations=t,this.locale=s,localStorage.setItem(this.local_storage_key,s);const e=window.location.pathname.substring(1).split("/");t=e[0];this.supported_languages.includes(t)&&(e.splice(0,1,s),t=e.join("/"),window.history.replaceState(null,"","/"+t)),n()}).catch(t=>{e(t)})})},trad:function(e,n={}){return e=this.translations[e]||e,Object.keys(n).forEach(t=>{e=e.replace(`{%${t}%}`,n[t])}),e}}},{}],4:[function(t,e,n){"use strict";e.exports={register_key:"objectToHtmlRender",register(t){t=t||this.register_key;window[t]=this},setRenderCycleRoot(t){this.renderCycleRoot=t},event_name:"objtohtml-render-cycle",setEventName(t){this.event_name=t},objectToHtml(e){if(!e)return document.createElement("span");const n=this.objectToHtml.bind(this),{tag:t,xmlns:s}=e,a=void 0!==s?document.createElementNS(s,t):document.createElement(t),r=["tag","contents","style_rules","state","xmlns"];return Object.keys(e).filter(t=>!r.includes(t)).forEach(t=>{switch(t){case"class":a.classList.add(...e[t].split(" ").filter(t=>""!==t));break;case"on_render":e.id||(a.id=`${btoa(JSON.stringify(e).slice(0,127)).replace(/\=/g,"")}${window.performance.now()}`),"function"!=typeof e.on_render?console.error("The on_render attribute must be a function"):this.attach_on_render_callback(a,e.on_render);break;default:void 0!==s?a.setAttributeNS(null,t,e[t]):a[t]=e[t]}}),e.contents&&"string"==typeof e.contents?a.innerHTML=e.contents:e.contents&&0<e.contents.length&&e.contents.forEach(t=>{switch(typeof t){case"string":a.innerHTML=t;break;case"object":void 0!==s&&(t=Object.assign(t,{xmlns:s})),a.appendChild(n(t))}}),e.style_rules&&Object.keys(e.style_rules).forEach(t=>{a.style[t]=e.style_rules[t]}),a},on_render_callbacks:[],attach_on_render_callback(e,n){var t={callback:t=>{t.detail.outputNode!==e&&!t.detail.outputNode.querySelector(`#${e.id}`)||(n(e),-1===(t=this.on_render_callbacks.indexOf(this.on_render_callbacks.find(t=>t.node===e)))?console.warn("A callback was registered for node with id "+e.id+" but callbacck handler is undefined."):(window.removeEventListener(this.event_name,this.on_render_callbacks[t].callback),this.on_render_callbacks.splice(t,1)))},node:e},t=this.on_render_callbacks.push(t);window.addEventListener(this.event_name,this.on_render_callbacks[t-1].callback)},renderCycle:function(){var t,t=document.getElementsByTagName("main")[0]||(t=document.createElement("main"),document.body.appendChild(t),t);this.subRender(this.renderCycleRoot.render(),t,{mode:"replace"})},subRender(t,e,n={mode:"append"}){let s=null;var a=()=>(s=this.objectToHtml(t),s);switch(n.mode){case"append":e.appendChild(a());break;case"override":e.innerHTML="",e.appendChild(a());break;case"insert-before":e.insertBefore(a(),e.childNodes[n.insertIndex]);break;case"adjacent":e.insertAdjacentHTML(n.insertLocation,a());break;case"replace":e.parentNode.replaceChild(a(),e);break;case"remove":e.remove()}var r=this.event_name,r=new CustomEvent(r,{detail:{inputObject:t,outputNode:s,insertOptions:n,targetNode:e}});window.dispatchEvent(r)}}},{}],5:[function(t,e,n){"use strict";const{images_url:s}=t("../../constants");e.exports=class{constructor(t){this.props=t}render(){return{tag:"a",class:"theme-card",href:this.props.href,contents:[{tag:"div",class:"card-img",contents:[{tag:"img",alt:`thematic image ${this.props.img.replace(/\.[A-Za-z]+/,"")}`,src:`${s}/${this.props.img}`}]},{tag:"div",class:"card-title",contents:[{tag:"h2",class:"section-title",contents:this.props.title}]},{tag:"div",class:"card-description",contents:[{tag:"p",contents:this.props.description}]}]}}}},{"../../constants":2}],6:[function(t,e,n){"use strict";const{images_url:s}=t("../constants"),a=t("./home-page-components/theme-card");var r=t("./lib/web-page");const o=t("ks-cheap-translator"),c=o.trad.bind(o);class i extends r{constructor(){super({id:"home-page"})}render(){return{tag:"div",id:this.id,contents:[{tag:"div",class:"page-header",contents:[{tag:"div",class:"big-logo page-contents-center",contents:[{tag:"img",alt:"logo Kuadrado",src:`${s}/logo_kuadrado.svg`},{tag:"img",class:"logo-text",alt:"Kuadrado",src:`${s}/logo_kuadrado_txt.svg`}]},{tag:"h1",contents:"Kuadrado Software",class:"page-contents-center"},{tag:"p",class:"page-contents-center",contents:c("kuadrado-home-description")},{tag:"ul",class:"philo-bubbles",contents:[c("Simplicité"),c("Légèreté"),c("Écologie")].map(t=>({tag:"li",contents:[{tag:"span",contents:t}]}))}]},{tag:"section",class:"page-contents-center poles",contents:[{title:c("Jeux"),img:"game_controller.svg",href:"/games/",description:c("games-description")},{title:c("Pédagogie"),img:"brain.svg",href:"/education/",description:c("education-description")},{title:"Software",img:"meca_proc.svg",href:"/software-development/",description:c("software-description")}].map(t=>new a(t).render())}]}}}e.exports=i},{"../constants":2,"./home-page-components/theme-card":5,"./lib/web-page":7,"ks-cheap-translator":3}],7:[function(t,e,n){"use strict";const s=t("ks-cheap-translator"),{translations_url:a}=t("../../constants");e.exports=class{constructor(t){Object.assign(this,t),this.id||(this.id="webpage-"+performance.now()),s.init({translations_url:a,supported_languages:["fr","en"]}).then(this.refresh_all.bind(this))}refresh(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}refresh_all(){obj2htm.renderCycle()}}},{"../../constants":2,"ks-cheap-translator":3}],8:[function(t,e,n){"use strict";var s=t("./homepage");const a=t("./run-page");a(s)},{"./homepage":6,"./run-page":9}],9:[function(t,e,n){"use strict";const s=t("object-to-html-renderer"),a=t("./template/template");e.exports=function(t){t=new a({page:new t});s.register("obj2htm"),obj2htm.setRenderCycleRoot(t),obj2htm.renderCycle()}},{"./template/template":11,"object-to-html-renderer":4}],10:[function(t,e,n){"use strict";const{images_url:s}=t("../../../constants"),o=t("ks-cheap-translator"),c=o.trad.bind(o),a=[{url:"/games/",text:"Jeux"},{url:"/education/",text:"Pédagogie"},{url:"/software-development/",text:"Software"}];e.exports=class{constructor(){this.initEventHandlers()}handleBurgerClick(){document.getElementById("nav-menu-list").classList.toggle("responsive-show")}initEventHandlers(){window.addEventListener("click",t=>{"nav-menu-list"===t.target.id||t.target.classList.contains("burger")||t.target.parentNode.classList.contains("burger")||document.getElementById("nav-menu-list").classList.remove("responsive-show")})}handle_chang_lang(t){o.update_translations(t).then(()=>{obj2htm.renderCycle()}).catch(t=>console.log(t))}renderHome(){return{tag:"div",class:"home",contents:[{tag:"a",href:"/",contents:[{tag:"img",alt:"Logo Kuadrado",src:`${s}/logo_kuadrado.svg`},{tag:"img",alt:"Kuadrado Software",class:"logo-text",src:`${s}/logo_kuadrado_txt.svg`}]}]}}renderMenu(t,a=!1,r=""){return{tag:"ul",id:"nav-menu-list",class:a?"submenu":"",contents:t.map(t=>{var{url:e,text:n,submenu:t}=t;const s=`${r}${e}`;return{tag:"li",class:a||window.location.pathname!==s?"":"active",contents:[{tag:"a",href:s,contents:c(n)}].concat(t?[this.renderMenu(t,!0,e)]:[])}}).concat({tag:"li",class:"lang-flags",contents:["fr","en"].map(t=>({tag:"img",src:`${s}/flag-${t}.svg`,class:o.locale===t?"selected":"",onclick:this.handle_chang_lang.bind(this,t)}))})}}renderResponsiveBurger(){return{tag:"div",class:"burger",onclick:this.handleBurgerClick.bind(this),contents:[{tag:"span",contents:"···"}]}}render(){return{tag:"nav",contents:[this.renderHome(),this.renderResponsiveBurger(),this.renderMenu(a)]}}}},{"../../../constants":2,"ks-cheap-translator":3}],11:[function(t,e,n){"use strict";const{in_construction:s}=t("../../config"),{images_url:a}=t("../../constants"),r=t("./components/navbar"),o=t("ks-cheap-translator"),c=o.trad.bind(o);e.exports=class{constructor(t){this.props=t}render(){return{tag:"main",contents:[{tag:"header",contents:[(new r).render()]},s&&{tag:"section",class:"warning-banner",contents:[{tag:"strong",class:"page-contents-center",contents:c("Site en construction ...")}]},{tag:"section",id:"page-container",contents:[this.props.page.render()]},{tag:"footer",contents:[{tag:"div",class:"logo",contents:[{tag:"img",alt:"logo Kuadrado",src:`${a}/logo_kuadrado.svg`},{tag:"img",class:"text-logo",alt:"Kuadrado Software",src:`${a}/logo_kuadrado_txt.svg`}]},{tag:"span",contents:"32 rue Simon Vialet, 07240 Vernoux en Vivarais. Ardèche, France"},{tag:"div",contents:[{tag:"strong",contents:"<blue>Contact : </blue>"},{tag:"a",href:"mailto:contact@kuadrado-software.fr",contents:"contact@kuadrado-software.fr"}]},{tag:"div",class:"social",contents:[{tag:"strong",contents:`<blue>${c("Sur les réseaux")} : </blue>`},{tag:"a",href:"https://www.linkedin.com/company/kuadrado-software",target:"_blank",contents:"in",title:"Linkedin"},{tag:"a",href:"https://mastodon.gamedev.place/@kuadrado_software",target:"_blank",contents:"m",title:"Mastodon"}]},{tag:"span",contents:`Copyleft 🄯 ${(new Date).getFullYear()} Kuadrado Software | + ${c("kuadrado-footer-copyleft")}`},{tag:"div",contents:[{tag:"span",contents:c("Ce site web est")+" "},{tag:"a",target:"_blank",style_rules:{fontWeight:"bold"},href:"https://gitlab.com/kuadrado-software/kuadrado-website/-/blob/master/README.md",contents:"OPEN SOURCE"}]}]}]}}}},{"../../config":1,"../../constants":2,"./components/navbar":10,"ks-cheap-translator":3}]},{},[8]); \ No newline at end of file diff --git a/public/software-development/software-development.js b/public/software-development/software-development.js index c2ed856..5fa85b3 100644 --- a/public/software-development/software-development.js +++ b/public/software-development/software-development.js @@ -1,984 +1,2 @@ -(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ -module.exports = { - images_url: "/assets/images" -} -},{}],2:[function(require,module,exports){ -module.exports = { - build: { - protected_dirs: ["assets", "style", "views", "standard"], - default_meta_keys: ["title", "description", "image", "open_graph", "json_ld"], - }, -}; - -},{}],3:[function(require,module,exports){ -module.exports = { - images_url: `/assets/images`, - data_url: `/assets/data`, - translations_url: "/assets/translations" -}; - -},{}],4:[function(require,module,exports){ -"use strict"; -/** - * A tiny library to handle text translation. - */ - -/** - * init parameter object type - * @typedef TranslatorParam - * @property {String[]} supported_languages - DEFAULT: ["en"] - An array of ISO 639 lowercase language codes - * @property {String} locale ISO 639 lowercase language code - DEFAULT: "en" - * - * @property {Boolean} use_url_locale_fragment - DEFAULT: true - - * If true, the 1st fragment of the window.location url will be parsed as a language code - * Example: - * window.location - * > https://example.com/en/some-page... - * then the /en/ part of the url will be taken in priority and locale will be set to "en". - * window.location - * > https://example.com/some-page... - * No locale fragment will be found so locale will not be set from url fragment. - * window.location - * > https://example.com/some-page/en - * Doesn't work, locale fragment must be the first framgment - * - * @property {String} local_storage_key - DEFAULT: "translator-prefered-language" - * The key used to saved the current locale into local storage - * - * @property {String} translations_url - REQUIRED - the url of the directory containing the static json files for translations - * Translations files are expected to be named with their corresponding locale code. - * Example: - * if supported_languages is set to ["en", "fr", "it"] - * and translations_url is set to "https://example.com/translations/"" - * Then the expected translations files are - * https://example.com/translations/en.json - * https://example.com/translations/fr.json - * https://example.com/translations/it.json - * The json resources must simple key value maps, value being the translated text. - */ - -module.exports = { - locale: "en", // ISO 639 lowercase language code - supported_languages: ["en"], - translations: {}, - translations_url: "", - use_url_locale_fragment: true, - local_storage_key: "translator-prefered-language", - - /** - * Initialize the lib with params - * @param {TranslatorParam} params - * @returns {Promise} - */ - init(params) { - Object.entries(params).forEach(k_v => { - const [key, value] = k_v; - if ([ - "supported_languages", - "use_url_locale_fragment", - "local_storage_key", - "translations_url" - ].includes(key)) { - this[key] = value; - } - }); - - this.translations_url = this.format_translations_url(this.translations_url); - this.supported_languages = this.format_supported_languages(this.supported_languages); - - return new Promise((resolve, reject) => { - const loc = - (() => {// Locale from url priority 1 - if (this.use_url_locale_fragment) { - const first_url_fragment = window.location.pathname.substring(1).split("/")[0]; - const fragment_is_locale = this.supported_languages.includes(first_url_fragment); - return fragment_is_locale ? first_url_fragment : ""; - } else { - return ""; - } - })() - || localStorage.getItem(this.local_storage_key) // Locale from storage priority 2 - || (() => { // locale from navigator priority 3 - const navigator_locale = navigator.language.split("-")[0].toLocaleLowerCase(); - return this.supported_languages.includes(navigator_locale) - ? navigator_locale - : this.supported_languages[0] // Default if navigator locale is not supported - })(); - - fetch(`${this.translations_url}${loc}.json`) - .then(response => response.json()) - .then(response => { - this.locale = loc; - this.translations = response; - resolve(); - }) - .catch(err => { - this.locale = "en"; - reject(err); - }); - }); - }, - - /** - * Return a lowercase string without dash. - * If given locale is en-EN, then "en" will be returned. - * @param {String} locale - * @returns A lowercase string - */ - format_locale(locale) { - return locale.split("-")[0].toLowerCase(); - }, - - /** - * Appends a slash at the end of the given string if missing, and returns the url. - * @param {String} url - * @returns {String} - */ - format_translations_url(url) { - if (url.charAt(url.length - 1) !== "/") { - url += "/" - } - return url; - }, - - /** - * Return the array of language codes formatted as lowsercase language code. - * if ["en-EN", "it-IT"]is given, ["en", "it"] will b returned. - * @param {String[]} languages_codes - * @returns {String[]} - */ - format_supported_languages(languages_codes) { - return languages_codes.map(lc => this.format_locale(lc)); - }, - - /** - * Fetches a new set of translations in case the wanted language changes - * @param {String} locale A lowercase language code - * @returns {Promise} - */ - update_translations(locale) { - locale = this.format_locale(locale); - return new Promise((resolve, reject) => { - fetch(`${this.translations_url}${locale}.json`) - .then(response => response.json()) - .then(response => { - this.translations = response; - this.locale = locale; - - localStorage.setItem(this.local_storage_key, locale); - - const split_path = window.location.pathname.substring(1).split("/"); - const first_url_fragment = split_path[0]; - const fragment_is_locale = this.supported_languages.includes(first_url_fragment); - - if (fragment_is_locale) { - split_path.splice(0, 1, locale); - const updated_path = split_path.join("/"); - window.history.replaceState(null, "", "/" + updated_path); - } - resolve(); - }) - .catch(err => { - reject(err); - }); - }); - }, - - /** - * Tries to get the translation of the source string, or return the string as it. - * @param {String} text The source text to translate - * @param {Object} params Some dynamic element to insert in the text - * Params can be used if the translated text provide placeholder like {%some_word%} - * Example: - * translator.trad("Some trad key", {some_word: "a dynamic parameter"}) - * -> translation for "Some trad key": "A translated text with {%some_word%}" - * -> will be return as : "A translated text with a dynamic parameter" - * @returns {String} - */ - trad: function (text, params = {}) { - text = this.translations[text] || text; - - Object.keys(params).forEach(k => { - text = text.replace(`{%${k}%}`, params[k]); - }); - - return text; - } -}; -},{}],5:[function(require,module,exports){ -"use strict"; - -module.exports = { - register_key: "objectToHtmlRender", - - /** - * Register "this" as a window scope accessible variable named by the given key, or default. - * @param {String} key - */ - register(key) { - const register_key = key || this.register_key; - window[register_key] = this; - }, - - /** - * This must be called before any other method in order to initialize the lib. - * It provides the root of the rendering cycle as a Javascript object. - * @param {Object} renderCycleRoot A JS component with a render method. - */ - setRenderCycleRoot(renderCycleRoot) { - this.renderCycleRoot = renderCycleRoot; - }, - - event_name: "objtohtml-render-cycle", - - /** - * Set a custom event name for the event that is trigger on render cycle. - * Default is "objtohtml-render-cycle". - * @param {String} evt_name - */ - setEventName(evt_name) { - this.event_name = evt_name; - }, - - /** - * This is the core agorithm that read an javascript Object and convert it into an HTML element. - * @param {Object} obj The object representing the html element must be formatted like: - * { - * tag: String // The name of the html tag, Any valid html tag should work. div, section, br, ul, li... - * xmlns: String // This can replace the tag key if the element is an element with a namespace URI, for example an <svg> tag. - * See https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS for more information - * style_rules: Object // a object providing css attributes. The attributes names must be in JS syntax, - * like maxHeight: "500px", backgrouncColor: "#ff2d56", margin: 0, etc. - * contents: Array or String // This reprensents the contents that will be nested in the created html element. - * <div>{contents}</div> - * The contents can be an array of other objects reprenting elements (with tag, contents, etc) - * or it can be a simple string. - * // All other attributes will be parsed as html attributes. They can be anything like onclick, href, onchange, title... - * // or they can also define custom html5 attributes, like data, my_custom_attr or anything. - * } - * @returns {HTMLElement} The output html node. - */ - objectToHtml(obj) { - if (!obj) return document.createElement("span"); // in case of invalid input, don't block the whole process. - const objectToHtml = this.objectToHtml.bind(this); - const { tag, xmlns } = obj; - const node = xmlns !== undefined ? document.createElementNS(xmlns, tag) : document.createElement(tag); - const excludeKeys = ["tag", "contents", "style_rules", "state", "xmlns"]; - - Object.keys(obj) - .filter(attr => !excludeKeys.includes(attr)) - .forEach(attr => { - switch (attr) { - case "class": - node.classList.add(...obj[attr].split(" ").filter(s => s !== "")); - break; - case "on_render": - if (!obj.id) { - node.id = `${btoa(JSON.stringify(obj).slice(0, 127)).replace(/\=/g, '')}${window.performance.now()}`; - } - if (typeof obj.on_render !== "function") { - console.error("The on_render attribute must be a function") - } else { - this.attach_on_render_callback(node, obj.on_render); - } - break; - default: - if (xmlns !== undefined) { - node.setAttributeNS(null, attr, obj[attr]) - } else { - node[attr] = obj[attr]; - } - } - }); - if (obj.contents && typeof obj.contents === "string") { - node.innerHTML = obj.contents; - } else { - obj.contents && - obj.contents.length > 0 && - obj.contents.forEach(el => { - switch (typeof el) { - case "string": - node.innerHTML = el; - break; - case "object": - if (xmlns !== undefined) { - el = Object.assign(el, { xmlns }) - } - node.appendChild(objectToHtml(el)); - break; - } - }); - } - - if (obj.style_rules) { - Object.keys(obj.style_rules).forEach(rule => { - node.style[rule] = obj.style_rules[rule]; - }); - } - - return node; - }, - - on_render_callbacks: [], - - /** - * This is called if the on_render attribute of a component is set. - * @param {HTMLElement} node The created html element - * @param {Function} callback The callback defined in the js component to render - */ - attach_on_render_callback(node, callback) { - const callback_handler = { - callback: e => { - if (e.detail.outputNode === node || e.detail.outputNode.querySelector(`#${node.id}`)) { - callback(node); - const handler_index = this.on_render_callbacks.indexOf((this.on_render_callbacks.find(cb => cb.node === node))); - if (handler_index === -1) { - console.warn("A callback was registered for node with id " + node.id + " but callbacck handler is undefined.") - } else { - window.removeEventListener(this.event_name, this.on_render_callbacks[handler_index].callback) - this.on_render_callbacks.splice(handler_index, 1); - } - } - }, - node, - }; - - const len = this.on_render_callbacks.push(callback_handler); - window.addEventListener(this.event_name, this.on_render_callbacks[len - 1].callback); - }, - - /** - * If a main element exists in the html document, it will be used as rendering root. - * If not, it will be created and inserted. - */ - renderCycle: function () { - const main_elmt = document.getElementsByTagName("main")[0] || (function () { - const created_main = document.createElement("main"); - document.body.appendChild(created_main); - return created_main; - })(); - - this.subRender(this.renderCycleRoot.render(), main_elmt, { mode: "replace" }); - }, - - /** - * This method behaves like the renderCycle() method, but rather that starting the rendering cycle from the root component, - * it can start from any component of the tree. The root component must be given as the first argument, the second argument be - * be a valid html element in the dom and will be used as the insertion target. - * @param {Object} object An object providing a render method returning an object representation of the html to insert - * @param {HTMLElement} htmlNode The htlm element to update - * @param {Object} options can be used the define the insertion mode, default is set to "append" and can be set to "override", - * "insert-before" (must be defined along with an insertIndex key (integer)), - * "adjacent" (must be defined along with an insertLocation key (String)), "replace" or "remove". - * In case of "remove", the first argument "object" is not used and can be set to null, undefined or {}... - */ - subRender(object, htmlNode, options = { mode: "append" }) { - let outputNode = null; - - const get_insert = () => { - outputNode = this.objectToHtml(object); - return outputNode; - }; - - switch (options.mode) { - case "append": - htmlNode.appendChild(get_insert()); - break; - case "override": - htmlNode.innerHTML = ""; - htmlNode.appendChild(get_insert()); - break; - case "insert-before": - htmlNode.insertBefore(get_insert(), htmlNode.childNodes[options.insertIndex]); - break; - case "adjacent": - /** - * options.insertLocation must be one of: - * - * afterbegin - * afterend - * beforebegin - * beforeend - */ - htmlNode.insertAdjacentHTML(options.insertLocation, get_insert()); - break; - case "replace": - htmlNode.parentNode.replaceChild(get_insert(), htmlNode); - break; - case "remove": - htmlNode.remove(); - break; - } - const evt_name = this.event_name; - const event = new CustomEvent(evt_name, { - detail: { - inputObject: object, - outputNode, - insertOptions: options, - targetNode: htmlNode, - } - }); - - window.dispatchEvent(event); - }, -}; -},{}],6:[function(require,module,exports){ -"use strict"; - -const { fetch_json_or_error_text } = require("./fetch"); - -function getArticleBody(text) { - return text.replaceAll("\n", "<br/>"); -} - -function getArticleDate(date) { - return `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`; -} - -function loadArticles(category, locale) { - return fetch_json_or_error_text(`/articles/${category}/${locale}`); -} - -module.exports = { - loadArticles, - getArticleBody, - getArticleDate, -}; - -},{"./fetch":7}],7:[function(require,module,exports){ -"use strict"; - -function fetchjson(url) { - return new Promise((resolve, reject) => { - fetch(url) - .then(r => r.json()) - .then(r => resolve(r)) - .catch(e => reject(e)); - }); -} - -function fetchtext(url) { - return new Promise((resolve, reject) => { - fetch(url) - .then(r => r.text()) - .then(r => resolve(r)) - .catch(e => reject(e)); - }); -} - -async function fetch_json_or_error_text(url, options = {}) { - return new Promise((resolve, reject) => { - fetch(url, options).then(async res => { - if (res.status >= 400 && res.status < 600) { - reject(await res.text()); - } else { - resolve(await res.json()); - } - }) - }) -} - -module.exports = { - fetchjson, - fetchtext, - fetch_json_or_error_text, -}; - -},{}],8:[function(require,module,exports){ -"use strict"; -const translator = require("ks-cheap-translator"); -const { translations_url } = require("../../constants"); - -class WebPage { - constructor(args) { - Object.assign(this, args); - - if (!this.id) { - this.id = "webpage-" + performance.now(); - } - - translator.init({ - translations_url, - supported_languages: ["fr", "en"], - }).then(this.refresh_all.bind(this)); - } - - refresh() { - obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace" }) - } - - refresh_all() { - obj2htm.renderCycle() - } -} - -module.exports = WebPage; -},{"../../constants":3,"ks-cheap-translator":4}],9:[function(require,module,exports){ -"use strict"; - -const { images_url } = require("../../../../../admin-frontend/src/constants"); -const { getArticleBody } = require("../../../lib/article-utils"); - -class SoftwareArticle { - constructor(props) { - this.props = props; - } - - render() { - const { title, body, subtitle, images, details = [] } = this.props; - - return { - tag: "article", - class: "software-article", - typeof: "SoftwareApplication", - additionalType: "Article", - contents: [ - { - tag: "h2", - class: "software-title", - contents: title, - property: "name", - }, - { - tag: "div", class: "software-image", - contents: [ - { - tag: "img", src: `${images_url}/${images[0]}` - } - ] - }, - { - tag: "h3", - class: "software-subtitle", - contents: subtitle, - property: "alternativeHeadline", - }, - { - tag: "div", - class: "software-description", - contents: getArticleBody(body), - property: "description", - }, - details.length > 0 && { - tag: "div", - class: "article-details", - contents: [ - { - tag: "h2", - contents: "Details", - }, - { - tag: "ul", - class: "details-list", - contents: details.map(detail => { - return { - tag: "li", - class: "detail", - contents: [ - { tag: "label", contents: detail.label }, - { - tag: "div", - class: "detail-value", - contents: detail.value - }, - ], - }; - }), - }, - ], - }, - ], - }; - } -} - -module.exports = SoftwareArticle; -},{"../../../../../admin-frontend/src/constants":1,"../../../lib/article-utils":6}],10:[function(require,module,exports){ -"use strict"; - -const { loadArticles } = require("../../../lib/article-utils"); -const SoftwareArticle = require("./software-article"); -const translator = require("ks-cheap-translator"); - -class SoftwareArticles { - constructor(props) { - this.props = props; - this.state = { - articles: [], - }; - this.id = "software-articles-section"; - this.loadArticles(); - } - - loadArticles() { - loadArticles("software", translator.locale).then(articles => { - this.state.articles = articles; - this.refresh(); - this.fixScroll(); - }).catch(e => console.log(e)) - } - - renderPlaceholder() { - return { - tag: "article", - class: "placeholder", - contents: [ - { tag: "div", class: "title" }, - { tag: "div", class: "body" }, - { tag: "div", class: "details" }, - ], - }; - } - - refresh() { - obj2htm.subRender(this.render(), document.getElementById(this.id), { - mode: "replace", - }); - } - - fixScroll() { - if (window.location.href.includes("#")) { - window.scrollTo( - 0, - document.getElementById(window.location.href.match(/#.+/)[0].replace("#", "")) - .offsetTop - ); - } - } - - render() { - const { articles } = this.state; - return { - tag: "section", - class: "software-articles page-contents-center", - id: this.id, - contents: - articles.length > 0 - ? articles.map(article => new SoftwareArticle({ ...article }).render()) - : [this.renderPlaceholder()], - }; - } -} - -module.exports = SoftwareArticles; - -},{"../../../lib/article-utils":6,"./software-article":9,"ks-cheap-translator":4}],11:[function(require,module,exports){ -"use strict"; - -const { images_url } = require("../../../constants"); -const WebPage = require("../../lib/web-page"); -const SoftwareArticles = require("./components/software-articles"); -const translator = require("ks-cheap-translator"); -const t = translator.trad.bind(translator); - -class SoftwareDevelopment extends WebPage { - render() { - return { - tag: "div", - id: "software-page", - contents: [ - { - tag: "div", - class: "page-header logo-left", - contents: [ - { - tag: "div", - class: "page-contents-center grid-wrapper", - contents: [ - { - tag: "div", - class: "logo", - contents: [ - { - tag: "img", - alt: `image mechanic electronic`, - src: `${images_url}/meca_proc.svg`, - }, - ], - }, - { tag: "h1", contents: "Software" }, - { - tag: "p", - contents: t("software-page-intro"), - }, - ], - }, - ], - }, - new SoftwareArticles().render(), - ], - }; - } -} - -module.exports = SoftwareDevelopment; - -},{"../../../constants":3,"../../lib/web-page":8,"./components/software-articles":10,"ks-cheap-translator":4}],12:[function(require,module,exports){ -"use strict"; - -"use strict"; -const runPage = require("../../run-page"); -const SoftwareDevelopment = require("./software-development"); -runPage(SoftwareDevelopment); - -},{"../../run-page":13,"./software-development":11}],13:[function(require,module,exports){ -"use strict"; - -const renderer = require("object-to-html-renderer") -const Template = require("./template/template"); - -module.exports = function runPage(PageComponent) { - const template = new Template({ page: new PageComponent() }); - renderer.register("obj2htm") - obj2htm.setRenderCycleRoot(template); - obj2htm.renderCycle(); -}; - -},{"./template/template":15,"object-to-html-renderer":5}],14:[function(require,module,exports){ -"use strict"; - -const { images_url } = require("../../../constants"); -const translator = require("ks-cheap-translator"); -const t = translator.trad.bind(translator); - -const NAV_MENU_ITEMS = [ - { url: "/games/", text: "Jeux" }, - { - url: "/education/", - text: "Pédagogie", - }, - { url: "/software-development/", text: "Software" } -]; - -class NavBar { - constructor() { - this.initEventHandlers(); - } - - handleBurgerClick() { - document.getElementById("nav-menu-list").classList.toggle("responsive-show"); - } - - initEventHandlers() { - window.addEventListener("click", event => { - if ( - event.target.id !== "nav-menu-list" && - !event.target.classList.contains("burger") && - !event.target.parentNode.classList.contains("burger") - ) { - document.getElementById("nav-menu-list").classList.remove("responsive-show"); - } - }); - } - - handle_chang_lang(lang) { - translator.update_translations(lang).then(() => { - obj2htm.renderCycle(); - }).catch(err => console.log(err)); - } - - renderHome() { - return { - tag: "div", - class: "home", - contents: [ - { - tag: "a", - href: "/", - contents: [ - { - tag: "img", - alt: "Logo Kuadrado", - src: `${images_url}/logo_kuadrado.svg`, - }, - { - tag: "img", - alt: "Kuadrado Software", - class: "logo-text", - src: `${images_url}/logo_kuadrado_txt.svg`, - }, - ], - }, - ], - }; - } - - renderMenu(menuItemsArray, isSubmenu = false, parentUrl = "") { - return { - tag: "ul", - id: "nav-menu-list", - class: isSubmenu ? "submenu" : "", - contents: menuItemsArray.map(item => { - const { url, text, submenu } = item; - const href = `${parentUrl}${url}`; - return { - tag: "li", - class: !isSubmenu && window.location.pathname === href ? "active" : "", - contents: [ - { - tag: "a", - href, - contents: t(text), - }, - ].concat(submenu ? [this.renderMenu(submenu, true, url)] : []), - }; - }).concat({ - tag: "li", - class: "lang-flags", - contents: ["fr", "en"].map(lang => { - return { - tag: "img", src: `${images_url}/flag-${lang}.svg`, - class: translator.locale === lang ? "selected" : "", - onclick: this.handle_chang_lang.bind(this, lang) - } - }) - }), - }; - } - - renderResponsiveBurger() { - return { - tag: "div", - class: "burger", - onclick: this.handleBurgerClick.bind(this), - contents: [{ tag: "span", contents: "···" }], - }; - } - - render() { - return { - tag: "nav", - contents: [ - this.renderHome(), - this.renderResponsiveBurger(), - this.renderMenu(NAV_MENU_ITEMS), - ], - }; - } -} - -module.exports = NavBar; - -},{"../../../constants":3,"ks-cheap-translator":4}],15:[function(require,module,exports){ -"use strict"; - -const { in_construction } = require("../../config"); -const { images_url } = require("../../constants"); -const NavBar = require("./components/navbar"); -const translator = require("ks-cheap-translator"); -const t = translator.trad.bind(translator) - -class Template { - constructor(props) { - this.props = props; - } - render() { - return { - tag: "main", - contents: [ - { - tag: "header", - contents: [new NavBar().render()], - }, - in_construction && { - tag: "section", - class: "warning-banner", - contents: [ - { - tag: "strong", - class: "page-contents-center", - contents: t("Site en construction ..."), - }, - ], - }, - { - tag: "section", - id: "page-container", - contents: [this.props.page.render()], - }, - { - tag: "footer", - contents: [ - { - tag: "div", - class: "logo", - contents: [ - { - tag: "img", - alt: `logo Kuadrado`, - src: `${images_url}/logo_kuadrado.svg`, - }, - { - tag: "img", - class: "text-logo", - alt: "Kuadrado Software", - src: `${images_url}/logo_kuadrado_txt.svg`, - }, - ], - }, - { - tag: "span", - contents: "32 rue Simon Vialet, 07240 Vernoux en Vivarais. Ardèche, France", - }, - { - tag: "div", - contents: [ - { tag: "strong", contents: "<blue>Contact : </blue>" }, - { - tag: "a", - href: "mailto:contact@kuadrado-software.fr", - contents: "contact@kuadrado-software.fr", - }, - ], - }, - { - tag: "div", - class: "social", - contents: [ - { - tag: "strong", - contents: `<blue>${t("Sur les réseaux")} : </blue>`, - }, - { - tag: "a", - href: "https://www.linkedin.com/company/kuadrado-software", - target: "_blank", - contents: "in", - title: "Linkedin", - }, - { - tag: "a", - href: "https://mastodon.gamedev.place/@kuadrado_software", - target: "_blank", - contents: "m", - title: "Mastodon", - } - ], - }, - { - tag: "span", - contents: `Copyleft 🄯 ${new Date() - .getFullYear()} Kuadrado Software | - ${t("kuadrado-footer-copyleft")}`, - }, - { - tag: "div", contents: [ - { tag: "span", contents: t("Ce site web est") + " " }, - { - tag: "a", target: "_blank", - style_rules: { fontWeight: "bold" }, - href: "https://gitlab.com/kuadrado-software/kuadrado-website/-/blob/master/README.md", - contents: "OPEN SOURCE" - } - ] - } - ], - }, - ], - }; - } -} - -module.exports = Template; - -},{"../../config":2,"../../constants":3,"./components/navbar":14,"ks-cheap-translator":4}]},{},[12]); +!function s(a,r,o){function c(e,t){if(!r[e]){if(!a[e]){var n="function"==typeof require&&require;if(!t&&n)return n(e,!0);if(i)return i(e,!0);throw(n=new Error("Cannot find module '"+e+"'")).code="MODULE_NOT_FOUND",n}n=r[e]={exports:{}},a[e][0].call(n.exports,function(t){return c(a[e][1][t]||t)},n,n.exports,s,a,r,o)}return r[e].exports}for(var i="function"==typeof require&&require,t=0;t<o.length;t++)c(o[t]);return c}({1:[function(t,e,n){e.exports={images_url:"/assets/images"}},{}],2:[function(t,e,n){e.exports={build:{protected_dirs:["assets","style","views","standard"],default_meta_keys:["title","description","image","open_graph","json_ld"]}}},{}],3:[function(t,e,n){e.exports={images_url:"/assets/images",data_url:"/assets/data",translations_url:"/assets/translations"}},{}],4:[function(t,e,n){"use strict";e.exports={locale:"en",supported_languages:["en"],translations:{},translations_url:"",use_url_locale_fragment:!0,local_storage_key:"translator-prefered-language",init(t){return Object.entries(t).forEach(t=>{var[e,t]=t;["supported_languages","use_url_locale_fragment","local_storage_key","translations_url"].includes(e)&&(this[e]=t)}),this.translations_url=this.format_translations_url(this.translations_url),this.supported_languages=this.format_supported_languages(this.supported_languages),new Promise((e,n)=>{const s=(()=>{if(this.use_url_locale_fragment){var t=window.location.pathname.substring(1).split("/")[0];return this.supported_languages.includes(t)?t:""}return""})()||localStorage.getItem(this.local_storage_key)||(t=navigator.language.split("-")[0].toLocaleLowerCase(),this.supported_languages.includes(t)?t:this.supported_languages[0]);var t;fetch(`${this.translations_url}${s}.json`).then(t=>t.json()).then(t=>{this.locale=s,this.translations=t,e()}).catch(t=>{this.locale="en",n(t)})})},format_locale(t){return t.split("-")[0].toLowerCase()},format_translations_url(t){return"/"!==t.charAt(t.length-1)&&(t+="/"),t},format_supported_languages(t){return t.map(t=>this.format_locale(t))},update_translations(s){return s=this.format_locale(s),new Promise((n,e)=>{fetch(`${this.translations_url}${s}.json`).then(t=>t.json()).then(t=>{this.translations=t,this.locale=s,localStorage.setItem(this.local_storage_key,s);const e=window.location.pathname.substring(1).split("/");t=e[0];this.supported_languages.includes(t)&&(e.splice(0,1,s),t=e.join("/"),window.history.replaceState(null,"","/"+t)),n()}).catch(t=>{e(t)})})},trad:function(e,n={}){return e=this.translations[e]||e,Object.keys(n).forEach(t=>{e=e.replace(`{%${t}%}`,n[t])}),e}}},{}],5:[function(t,e,n){"use strict";e.exports={register_key:"objectToHtmlRender",register(t){t=t||this.register_key;window[t]=this},setRenderCycleRoot(t){this.renderCycleRoot=t},event_name:"objtohtml-render-cycle",setEventName(t){this.event_name=t},objectToHtml(e){if(!e)return document.createElement("span");const n=this.objectToHtml.bind(this),{tag:t,xmlns:s}=e,a=void 0!==s?document.createElementNS(s,t):document.createElement(t),r=["tag","contents","style_rules","state","xmlns"];return Object.keys(e).filter(t=>!r.includes(t)).forEach(t=>{switch(t){case"class":a.classList.add(...e[t].split(" ").filter(t=>""!==t));break;case"on_render":e.id||(a.id=`${btoa(JSON.stringify(e).slice(0,127)).replace(/\=/g,"")}${window.performance.now()}`),"function"!=typeof e.on_render?console.error("The on_render attribute must be a function"):this.attach_on_render_callback(a,e.on_render);break;default:void 0!==s?a.setAttributeNS(null,t,e[t]):a[t]=e[t]}}),e.contents&&"string"==typeof e.contents?a.innerHTML=e.contents:e.contents&&0<e.contents.length&&e.contents.forEach(t=>{switch(typeof t){case"string":a.innerHTML=t;break;case"object":void 0!==s&&(t=Object.assign(t,{xmlns:s})),a.appendChild(n(t))}}),e.style_rules&&Object.keys(e.style_rules).forEach(t=>{a.style[t]=e.style_rules[t]}),a},on_render_callbacks:[],attach_on_render_callback(e,n){var t={callback:t=>{t.detail.outputNode!==e&&!t.detail.outputNode.querySelector(`#${e.id}`)||(n(e),-1===(t=this.on_render_callbacks.indexOf(this.on_render_callbacks.find(t=>t.node===e)))?console.warn("A callback was registered for node with id "+e.id+" but callbacck handler is undefined."):(window.removeEventListener(this.event_name,this.on_render_callbacks[t].callback),this.on_render_callbacks.splice(t,1)))},node:e},t=this.on_render_callbacks.push(t);window.addEventListener(this.event_name,this.on_render_callbacks[t-1].callback)},renderCycle:function(){var t,t=document.getElementsByTagName("main")[0]||(t=document.createElement("main"),document.body.appendChild(t),t);this.subRender(this.renderCycleRoot.render(),t,{mode:"replace"})},subRender(t,e,n={mode:"append"}){let s=null;var a=()=>(s=this.objectToHtml(t),s);switch(n.mode){case"append":e.appendChild(a());break;case"override":e.innerHTML="",e.appendChild(a());break;case"insert-before":e.insertBefore(a(),e.childNodes[n.insertIndex]);break;case"adjacent":e.insertAdjacentHTML(n.insertLocation,a());break;case"replace":e.parentNode.replaceChild(a(),e);break;case"remove":e.remove()}var r=this.event_name,r=new CustomEvent(r,{detail:{inputObject:t,outputNode:s,insertOptions:n,targetNode:e}});window.dispatchEvent(r)}}},{}],6:[function(t,e,n){"use strict";const{fetch_json_or_error_text:s}=t("./fetch");e.exports={loadArticles:function(t,e){return s(`/articles/${t}/${e}`)},getArticleBody:function(t){return t.replaceAll("\n","<br/>")},getArticleDate:function(t){return`${t.getDate()}-${t.getMonth()+1}-${t.getFullYear()}`}}},{"./fetch":7}],7:[function(t,e,n){"use strict";e.exports={fetchjson:function(t){return new Promise((e,n)=>{fetch(t).then(t=>t.json()).then(t=>e(t)).catch(t=>n(t))})},fetchtext:function(t){return new Promise((e,n)=>{fetch(t).then(t=>t.text()).then(t=>e(t)).catch(t=>n(t))})},fetch_json_or_error_text:async function(t,s={}){return new Promise((e,n)=>{fetch(t,s).then(async t=>{400<=t.status&&t.status<600?n(await t.text()):e(await t.json())})})}}},{}],8:[function(t,e,n){"use strict";const s=t("ks-cheap-translator"),{translations_url:a}=t("../../constants");e.exports=class{constructor(t){Object.assign(this,t),this.id||(this.id="webpage-"+performance.now()),s.init({translations_url:a,supported_languages:["fr","en"]}).then(this.refresh_all.bind(this))}refresh(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}refresh_all(){obj2htm.renderCycle()}}},{"../../constants":3,"ks-cheap-translator":4}],9:[function(t,e,n){"use strict";const{images_url:r}=t("../../../../../admin-frontend/src/constants"),{getArticleBody:o}=t("../../../lib/article-utils");e.exports=class{constructor(t){this.props=t}render(){const{title:t,body:e,subtitle:n,images:s,details:a=[]}=this.props;return{tag:"article",class:"software-article",typeof:"SoftwareApplication",additionalType:"Article",contents:[{tag:"h2",class:"software-title",contents:t,property:"name"},{tag:"div",class:"software-image",contents:[{tag:"img",src:`${r}/${s[0]}`}]},{tag:"h3",class:"software-subtitle",contents:n,property:"alternativeHeadline"},{tag:"div",class:"software-description",contents:o(e),property:"description"},0<a.length&&{tag:"div",class:"article-details",contents:[{tag:"h2",contents:"Details"},{tag:"ul",class:"details-list",contents:a.map(t=>({tag:"li",class:"detail",contents:[{tag:"label",contents:t.label},{tag:"div",class:"detail-value",contents:t.value}]}))}]}]}}}},{"../../../../../admin-frontend/src/constants":1,"../../../lib/article-utils":6}],10:[function(t,e,n){"use strict";const{loadArticles:s}=t("../../../lib/article-utils"),a=t("./software-article"),r=t("ks-cheap-translator");e.exports=class{constructor(t){this.props=t,this.state={articles:[]},this.id="software-articles-section",this.loadArticles()}loadArticles(){s("software",r.locale).then(t=>{this.state.articles=t,this.refresh(),this.fixScroll()}).catch(t=>console.log(t))}renderPlaceholder(){return{tag:"article",class:"placeholder",contents:[{tag:"div",class:"title"},{tag:"div",class:"body"},{tag:"div",class:"details"}]}}refresh(){obj2htm.subRender(this.render(),document.getElementById(this.id),{mode:"replace"})}fixScroll(){window.location.href.includes("#")&&window.scrollTo(0,document.getElementById(window.location.href.match(/#.+/)[0].replace("#","")).offsetTop)}render(){const{articles:t}=this.state;return{tag:"section",class:"software-articles page-contents-center",id:this.id,contents:0<t.length?t.map(t=>new a({...t}).render()):[this.renderPlaceholder()]}}}},{"../../../lib/article-utils":6,"./software-article":9,"ks-cheap-translator":4}],11:[function(t,e,n){"use strict";const{images_url:s}=t("../../../constants");var a=t("../../lib/web-page");const r=t("./components/software-articles"),o=t("ks-cheap-translator"),c=o.trad.bind(o);class i extends a{render(){return{tag:"div",id:"software-page",contents:[{tag:"div",class:"page-header logo-left",contents:[{tag:"div",class:"page-contents-center grid-wrapper",contents:[{tag:"div",class:"logo",contents:[{tag:"img",alt:"image mechanic electronic",src:`${s}/meca_proc.svg`}]},{tag:"h1",contents:"Software"},{tag:"p",contents:c("software-page-intro")}]}]},(new r).render()]}}}e.exports=i},{"../../../constants":3,"../../lib/web-page":8,"./components/software-articles":10,"ks-cheap-translator":4}],12:[function(t,e,n){"use strict";const s=t("../../run-page");t=t("./software-development");s(t)},{"../../run-page":13,"./software-development":11}],13:[function(t,e,n){"use strict";const s=t("object-to-html-renderer"),a=t("./template/template");e.exports=function(t){t=new a({page:new t});s.register("obj2htm"),obj2htm.setRenderCycleRoot(t),obj2htm.renderCycle()}},{"./template/template":15,"object-to-html-renderer":5}],14:[function(t,e,n){"use strict";const{images_url:s}=t("../../../constants"),o=t("ks-cheap-translator"),c=o.trad.bind(o),a=[{url:"/games/",text:"Jeux"},{url:"/education/",text:"Pédagogie"},{url:"/software-development/",text:"Software"}];e.exports=class{constructor(){this.initEventHandlers()}handleBurgerClick(){document.getElementById("nav-menu-list").classList.toggle("responsive-show")}initEventHandlers(){window.addEventListener("click",t=>{"nav-menu-list"===t.target.id||t.target.classList.contains("burger")||t.target.parentNode.classList.contains("burger")||document.getElementById("nav-menu-list").classList.remove("responsive-show")})}handle_chang_lang(t){o.update_translations(t).then(()=>{obj2htm.renderCycle()}).catch(t=>console.log(t))}renderHome(){return{tag:"div",class:"home",contents:[{tag:"a",href:"/",contents:[{tag:"img",alt:"Logo Kuadrado",src:`${s}/logo_kuadrado.svg`},{tag:"img",alt:"Kuadrado Software",class:"logo-text",src:`${s}/logo_kuadrado_txt.svg`}]}]}}renderMenu(t,a=!1,r=""){return{tag:"ul",id:"nav-menu-list",class:a?"submenu":"",contents:t.map(t=>{var{url:e,text:n,submenu:t}=t;const s=`${r}${e}`;return{tag:"li",class:a||window.location.pathname!==s?"":"active",contents:[{tag:"a",href:s,contents:c(n)}].concat(t?[this.renderMenu(t,!0,e)]:[])}}).concat({tag:"li",class:"lang-flags",contents:["fr","en"].map(t=>({tag:"img",src:`${s}/flag-${t}.svg`,class:o.locale===t?"selected":"",onclick:this.handle_chang_lang.bind(this,t)}))})}}renderResponsiveBurger(){return{tag:"div",class:"burger",onclick:this.handleBurgerClick.bind(this),contents:[{tag:"span",contents:"···"}]}}render(){return{tag:"nav",contents:[this.renderHome(),this.renderResponsiveBurger(),this.renderMenu(a)]}}}},{"../../../constants":3,"ks-cheap-translator":4}],15:[function(t,e,n){"use strict";const{in_construction:s}=t("../../config"),{images_url:a}=t("../../constants"),r=t("./components/navbar"),o=t("ks-cheap-translator"),c=o.trad.bind(o);e.exports=class{constructor(t){this.props=t}render(){return{tag:"main",contents:[{tag:"header",contents:[(new r).render()]},s&&{tag:"section",class:"warning-banner",contents:[{tag:"strong",class:"page-contents-center",contents:c("Site en construction ...")}]},{tag:"section",id:"page-container",contents:[this.props.page.render()]},{tag:"footer",contents:[{tag:"div",class:"logo",contents:[{tag:"img",alt:"logo Kuadrado",src:`${a}/logo_kuadrado.svg`},{tag:"img",class:"text-logo",alt:"Kuadrado Software",src:`${a}/logo_kuadrado_txt.svg`}]},{tag:"span",contents:"32 rue Simon Vialet, 07240 Vernoux en Vivarais. Ardèche, France"},{tag:"div",contents:[{tag:"strong",contents:"<blue>Contact : </blue>"},{tag:"a",href:"mailto:contact@kuadrado-software.fr",contents:"contact@kuadrado-software.fr"}]},{tag:"div",class:"social",contents:[{tag:"strong",contents:`<blue>${c("Sur les réseaux")} : </blue>`},{tag:"a",href:"https://www.linkedin.com/company/kuadrado-software",target:"_blank",contents:"in",title:"Linkedin"},{tag:"a",href:"https://mastodon.gamedev.place/@kuadrado_software",target:"_blank",contents:"m",title:"Mastodon"}]},{tag:"span",contents:`Copyleft 🄯 ${(new Date).getFullYear()} Kuadrado Software | + ${c("kuadrado-footer-copyleft")}`},{tag:"div",contents:[{tag:"span",contents:c("Ce site web est")+" "},{tag:"a",target:"_blank",style_rules:{fontWeight:"bold"},href:"https://gitlab.com/kuadrado-software/kuadrado-website/-/blob/master/README.md",contents:"OPEN SOURCE"}]}]}]}}}},{"../../config":2,"../../constants":3,"./components/navbar":14,"ks-cheap-translator":4}]},{},[12]); \ No newline at end of file diff --git a/public/style/style.css b/public/style/style.css deleted file mode 100644 index db7b101..0000000 --- a/public/style/style.css +++ /dev/null @@ -1,987 +0,0 @@ -body { - font-family: Arial, Helvetica, sans-serif; - margin: 0; -} -body * { - box-sizing: border-box; - color: #35393c; - line-height: 1.3em; -} -body ul { - margin: 0; - padding: 0; - list-style-type: none; -} -body a { - color: #4baabb; - text-decoration: none; -} -body a:hover { - color: #72e3f0; -} -body blue { - color: #4baabb; -} -body red { - color: #9c3030; -} -body green { - color: #368736; -} -body emoji { - font-style: initial; - font-size: 25px; -} -body .bg-blue { - background-color: #4baabb; - color: white; -} -body .bg-dark { - background-color: #3c4144; - color: #aabbc8; -} -body #seo-title { - visibility: hidden; -} -body img.pixelated { - image-rendering: pixelated; - image-rendering: -moz-crisp-edges; - image-rendering: crisp-edges; -} - -main { - display: flex; - flex-direction: column; - align-items: center; - min-height: 100vh; -} -main .warning-banner { - background: url("/assets/images/wallpaper_warning.svg"); - width: 100%; - height: 40px; - padding: 20px 10%; - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; -} -main .warning-banner strong { - font-size: 18px; - color: #1c3db2; -} -main .image-carousel { - overflow: hidden; - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; - background-color: black; - position: relative; -} -main .image-carousel img { - position: absolute; - object-fit: contain; - height: 80%; - max-width: 100%; -} -main .image-carousel .carousel-bullets { - position: absolute; - bottom: 0; - padding: 20px; - display: flex; - gap: 10px; -} -main .image-carousel .carousel-bullets .bullet { - cursor: pointer; - width: 8px; - height: 8px; - background-color: #6b7880; - border-radius: 100%; - box-shadow: 0 0 3px black; -} -main .image-carousel .carousel-bullets .bullet.active { - background-color: #d4d9dd; -} -@media screen and (max-width: 900px) { - main .image-carousel .carousel-bullets { - gap: 30px; - } - main .image-carousel .carousel-bullets .bullet { - width: 12px; - height: 12px; - } -} -main header { - width: 100%; - background-color: white; - position: sticky; - position: -webkit-sticky; - top: 0; - z-index: 10; -} -main header nav { - display: flex; - align-items: center; - height: 60px; -} -main header nav .home { - margin: 0 10px; -} -main header nav .home a { - display: flex; - align-items: center; - gap: 10px; -} -main header nav .home a img { - height: 40px; - width: auto; -} -main header nav .home a img.logo-text { - width: 120px; - height: auto; -} -main header nav ul { - display: flex; - padding: 0; - margin: 0; - list-style-type: none; - height: 100%; - flex: 1; -} -main header nav ul li { - position: relative; -} -main header nav ul li a { - display: flex; - align-items: center; - height: 100%; - padding: 10px 20px; - color: #96a5ae; - font-weight: 800; - text-decoration: none; -} -main header nav ul li .submenu { - visibility: hidden; - overflow: hidden; - position: absolute; - height: auto; - max-height: 0; - transition: max-height 0.6s; - top: 100%; - left: 50%; - flex-direction: column; - background-color: white; - white-space: nowrap; -} -main header nav ul li.active a { - color: #3c4144; - border-bottom: 3px solid; -} -main header nav ul li:hover a { - color: #3c4144; -} -main header nav ul li:hover .submenu { - visibility: unset; - max-height: 1000px; -} -main header nav ul li:hover .submenu a { - color: #96a5ae; - border: none; -} -main header nav ul li:hover .submenu li:hover a { - color: #3c4144; -} -main header nav ul li.lang-flags { - display: flex; - align-items: center; - gap: 10px; - margin-left: auto; - padding: 0 20px; -} -main header nav ul li.lang-flags img { - width: 35px; - height: 30px; - cursor: pointer; - opacity: 0.5; -} -main header nav ul li.lang-flags img.selected, main header nav ul li.lang-flags img:hover { - opacity: 1; -} -main header nav .burger { - display: none; -} -@media screen and (max-width: 560px) { - main header nav { - justify-content: space-between; - } - main header nav .burger { - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; - flex-direction: column; - font-weight: bold; - border: 1px solid; - margin: 0 20px; - cursor: pointer; - border-radius: 100%; - width: 35px; - height: 35px; - color: #555d61; - font-size: 25px; - } - main header nav ul { - display: none; - } - main header nav ul.responsive-show { - display: flex; - flex-direction: column; - position: absolute; - right: 0; - max-width: 100vw; - min-width: 50vw; - top: 60px; - background-color: white; - box-shadow: 0 4px 6px 2px #0000000a; - height: unset; - } - main header nav ul.responsive-show li.active a { - border: none; - } - main header nav ul.responsive-show li .submenu { - display: flex; - visibility: visible; - position: relative; - height: unset; - max-height: unset; - transition: max-height 0.6s; - top: unset; - left: unset; - margin-left: 20px; - } - main header nav ul.responsive-show li .submenu li a { - font-weight: 400; - font-size: 14px; - color: #96a5ae; - } - main header nav ul.responsive-show li.lang-flags { - margin-left: unset; - justify-content: space-around; - padding: 20px; - } -} -main #page-container { - width: 100%; - flex: 1; -} -main #page-container .page-header { - background-image: url("/assets/images/wallpaper_binary.png"); - padding: 50px 0; -} -main #page-container .page-header h1 { - padding: 15px 40px 0; - font-size: 25px; - color: #4baabb; - margin: 0 auto; -} -main #page-container .page-header p { - color: #72e3f0; - font-style: italic; - padding: 15px 40px 15px 100px; - margin: 0 auto; - font-size: 18px; -} -main #page-container .page-header p * { - color: #72e3f0; -} -main #page-container .page-header .big-logo { - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; - gap: 20px; - padding: 20px; -} -main #page-container .page-header .big-logo img { - width: 200px; - max-width: 100%; -} -main #page-container .page-header .big-logo img.logo-text { - width: 300px; - max-width: 100%; -} -main #page-container .page-header .logo { - padding-left: 30px; - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; -} -main #page-container .page-header .logo img { - width: 100%; -} -@media screen and (max-width: 560px) { - main #page-container .page-header h1 { - padding: 15px 20px 0; - } - main #page-container .page-header p { - padding: 20px 20px 30px 40px; - text-align: justify; - } - main #page-container .page-header .big-logo { - flex-direction: column; - } -} -main #page-container .page-header.logo-left .grid-wrapper { - display: grid; - grid-template-columns: 120px 1fr; - grid-template-rows: auto 1fr; -} -main #page-container .page-header.logo-left .grid-wrapper h1 { - width: 100%; -} -main #page-container .page-header.logo-left .grid-wrapper .logo { - grid-column: 1; - grid-row: 1; - width: 100%; -} -main #page-container .page-header.logo-left .grid-wrapper p { - margin: 0; - grid-column: 1/span 2; -} -@media screen and (max-width: 780px) { - main #page-container .page-header.logo-left .grid-wrapper h1 { - padding: 0 20px; - } - main #page-container .page-header.logo-left .grid-wrapper .logo { - padding: 0 20px; - } -} -main #page-container .page-philo { - background-image: url("/assets/images/wallpaper_binary.png"); - padding: 120px 30px; -} -main #page-container .page-philo p { - width: 100%; - max-width: 600px; - font-size: 18px; - color: #aabbc8; - text-align: center; - font-style: italic; - font-weight: bold; -} -main #page-container .page-philo p * { - color: #aabbc8; -} -main #page-container .page-contents-center { - width: 1300px; - max-width: 100%; - margin: 0 auto; -} -@media screen and (max-width: 1300px) { - main #page-container .page-contents-center { - padding: 20px 20px 0; - } -} -main #page-container h2.page-section-title { - color: #4baabb; - padding: 20px 0 10px; - width: 1300px; - max-width: 100%; - margin: 0 auto; -} -@media screen and (max-width: 1300px) { - main #page-container h2.page-section-title { - padding: 20px 20px 0; - } -} -main #page-container .article-details { - grid-column: 1/span 2; -} -main #page-container .article-details h2 { - color: #6b7880; - margin: 0 10px; - padding: 10px 0 0; - font-size: 16px; -} -main #page-container .article-details ul.details-list { - margin: 10px; -} -main #page-container .article-details ul.details-list .detail { - display: grid; - grid-template-columns: 1fr auto; - gap: 20px; - font-size: 12px; - border-bottom: 1px solid #d4d9dd; - padding: 5px 0; -} -main #page-container .article-details ul.details-list .detail label { - font-weight: bold; - color: #6b7880; -} -main #page-container .article-details ul.details-list .detail .detail-value { - text-align: right; -} -main #page-container #home-page { - display: flex; - flex-direction: column; -} -main #page-container #home-page .section-title { - padding: 10px; - margin: 0; - color: #aabbc8; -} -main #page-container #home-page .page-header .philo-bubbles { - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; - flex-wrap: wrap; - gap: 40px; - margin: 30px 20px; -} -@media screen and (max-width: 780px) { - main #page-container #home-page .page-header .philo-bubbles { - gap: 20px; - } -} -main #page-container #home-page .page-header .philo-bubbles li { - border-radius: 100%; - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; - background-color: #d4d9dd; - width: 100px; - height: 100px; -} -main #page-container #home-page .page-header .philo-bubbles li * { - color: #6b7880; -} -@media screen and (max-width: 560px) { - main #page-container #home-page .page-header .philo-bubbles li { - width: 75px; - height: 75px; - } - main #page-container #home-page .page-header .philo-bubbles li * { - font-size: 12px; - } -} -main #page-container #home-page .page-header .philo-bubbles li:first-child { - background-color: #6b7880; -} -main #page-container #home-page .page-header .philo-bubbles li:first-child * { - color: white; -} -main #page-container #home-page .page-header .philo-bubbles li:last-child { - background-color: #35393c; -} -main #page-container #home-page .page-header .philo-bubbles li:last-child * { - color: #96a5ae; -} -main #page-container #home-page .poles { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - gap: 30px; - padding: 100px 0; -} -main #page-container #home-page .poles .theme-card { - display: flex; - flex-direction: column; - width: 100%; - cursor: pointer; - transition: transform 0.3s; -} -main #page-container #home-page .poles .theme-card .card-img { - width: 100%; - height: 240px; - overflow: hidden; - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; - flex-direction: column; - position: relative; -} -main #page-container #home-page .poles .theme-card .card-img img { - position: absolute; - max-width: 100%; - height: 100%; - padding: 10px; -} -main #page-container #home-page .poles .theme-card .card-title h2 { - margin: 0; - text-align: center; - padding: 10px 20px; - color: #4baabb; - display: block; - background-color: white; -} -main #page-container #home-page .poles .theme-card .card-description { - flex: 1; - padding: 30px 20px; -} -main #page-container #home-page .poles .theme-card .card-description p { - margin: 0; - color: #4baabb; - text-align: center; -} -main #page-container #home-page .poles .theme-card:hover { - transform: scale(1.03); -} -main #page-container #home-page .kuadrado-values { - background-image: url("/assets/images/wallpaper_binary_light.png"); - padding: 100px 0 120px; -} -main #page-container #home-page .kuadrado-values *:not(a, blue) { - color: #d4d9dd; -} -main #page-container #home-page .kuadrado-values h2 { - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; - margin: 0 auto 60px; - width: 120px; - height: 120px; - background-image: url("/assets/images/wallpaper_binary.png"); - border-radius: 100%; - color: #72e3f0; -} -main #page-container #home-page .kuadrado-values ul.values-list { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 30px; -} -main #page-container #home-page .kuadrado-values ul.values-list li { - background-image: url("/assets/images/wallpaper_binary.png"); - padding: 30px 20px 40px; -} -main #page-container #home-page .kuadrado-values ul.values-list li h3 { - text-align: center; -} -main #page-container #home-page .kuadrado-values ul.values-list li p { - text-align: justify; -} -@media screen and (max-width: 900px) { - main #page-container #home-page .poles { - grid-template-columns: 1fr; - gap: 40px; - } - main #page-container #home-page .poles .theme-card { - transition: transform 0.3s; - } - main #page-container #home-page .poles .theme-card .card-img { - height: 300px; - } - main #page-container #home-page .poles .theme-card .card-img img { - min-width: unset; - height: 100%; - } - main #page-container #home-page .poles .theme-card .card-title h2 { - padding: 5px 20px; - } - main #page-container #home-page .poles .theme-card .card-description { - padding: 20px 30px; - } - main #page-container #home-page .poles .theme-card:hover { - transform: none; - } - main #page-container #home-page .kuadrado-values ul.values-list { - grid-template-columns: 1fr; - } -} -@media screen and (max-width: 1300px) { - main #page-container #home-page .poles { - padding: 20px; - } - main #page-container #home-page .articles-displayer { - padding: 0; - } -} -main #page-container #education-page h3.big { - font-size: 30px; -} -main #page-container #education-page .title-banner { - display: flex; - justify-content: flex-end; - flex-direction: column; - height: 20vw; - min-height: 250px; - background-image: url("/assets/images/popularization_banner.png"); - background-size: cover; - background-repeat: no-repeat; - background-position: center; -} -main #page-container #education-page .title-banner h2 { - color: white; - font-size: 2.5em; - margin: 40px; - text-shadow: 0 0 6px #0003; -} -main #page-container #education-page .special-announcement { - background-color: #ffd000; -} -main #page-container #education-page .special-announcement .page-contents-center { - padding: 0 20px; - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; -} -main #page-container #education-page .special-announcement .page-contents-center p { - color: #555d61; - font-size: 20px; - font-weight: 600; - margin: 0; - padding: 40px 0; -} -main #page-container #education-page .edu-themes { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 50px; - font-family: monospace; - padding: 70px 0; -} -main #page-container #education-page .edu-themes .edu-theme { - display: grid; - grid-template-columns: auto 1fr; -} -main #page-container #education-page .edu-themes .edu-theme * { - border-style: dashed; - border-color: #00ff00; - border-width: 0 0 0 0; -} -main #page-container #education-page .edu-themes .edu-theme h3 { - color: #00ff00; - grid-row: 1; - margin: 0; - padding: 10px; - display: flex; - align-items: center; - border-width: 0 0 0 1px; -} -main #page-container #education-page .edu-themes .edu-theme img { - width: 100%; - grid-row: 1/span 2; - border-width: 1px 0 1px 1px; -} -main #page-container #education-page .edu-themes .edu-theme p { - text-align: justify; - color: #72e3f0; - grid-row: 2; - margin: 0; - padding: 10px 30px 0 10px; - border-width: 1px 1px 1px 0; -} -main #page-container #education-page .edu-themes .edu-theme p * { - color: #72e3f0; -} -main #page-container #education-page .practical-info { - padding: 50px 0; -} -main #page-container #education-page .practical-info .page-contents-center { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 50px; -} -main #page-container #education-page .practical-info .page-contents-center .info-block { - display: grid; - grid-template-rows: auto 1fr; -} -main #page-container #education-page .practical-info .page-contents-center .info-block .info-title { - color: #4baabb; - margin: 0; - border-bottom: 1px dashed #aabbc8; - border-left: 1px dashed #aabbc8; - padding: 10px; -} -main #page-container #education-page .practical-info .page-contents-center .info-block .info-body { - margin: 0; - padding: 20px 10px; - border-right: 1px dashed #aabbc8; - border-bottom: 1px dashed #aabbc8; -} -main #page-container #education-page .practical-info .page-contents-center .info-block ul { - display: flex; - flex-direction: column; - gap: 5px; -} -main #page-container #education-page .practical-info .page-contents-center .info-block ul li:not(.fullwidth) { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 10px; -} -main #page-container #education-page .practical-info .page-contents-center .info-block ul.tabled li span { - padding: 3px 0; -} -main #page-container #education-page .practical-info .page-contents-center .info-block ul.tabled li span:first-child { - font-weight: bold; - color: #6b7880; -} -main #page-container #education-page .practical-info .page-contents-center .info-block ul.tabled li span:last-child { - color: #4baabb; -} -@media screen and (max-width: 1200px) { - main #page-container #education-page .edu-themes { - grid-template-columns: 1fr; - gap: 30px; - padding: 70px 0; - } -} -@media screen and (max-width: 780px) { - main #page-container #education-page .practical-info .page-contents-center { - grid-template-columns: 1fr; - gap: 30px; - } - main #page-container #education-page .practical-info .page-contents-center .info-block .info-title { - border-top: 1px dashed #aabbc8; - } - main #page-container #education-page .practical-info .page-contents-center .info-block .info-body { - border-bottom: none; - } -} -@media screen and (max-width: 560px) { - main #page-container #education-page .edu-themes .edu-theme h3 { - border-width: 0 0 1px 1px; - } - main #page-container #education-page .edu-themes .edu-theme img { - max-width: 150px; - height: auto; - grid-row: 1; - border-width: 1px 0 0 1px; - } - main #page-container #education-page .edu-themes .edu-theme p { - grid-row: 2; - grid-column: 1/span 2; - padding: 20px 10px 30px 10px; - border-width: 0 1px 1px 1px; - } -} -main #page-container #games-page .game-articles article { - display: grid; - grid-template-columns: 0.7fr 1fr; - gap: 30px 50px; - margin: 20px 0; -} -main #page-container #games-page .game-articles article.game-article { - grid-template-rows: repeat(7, auto); - width: 100%; -} -main #page-container #games-page .game-articles article.game-article .game-title { - grid-column: 1/span 2; - margin: 0; - padding: 30px 20px; - color: #aabbc8; - font-size: 35px; - font-style: italic; -} -main #page-container #games-page .game-articles article.game-article .game-banner { - grid-column: 1/span 2; - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; - flex-direction: column; - background-color: black; - overflow: hidden; - height: 300px; -} -main #page-container #games-page .game-articles article.game-article .game-banner img { - width: 100%; -} -main #page-container #games-page .game-articles article.game-article .game-tags { - display: flex; - gap: 10px; - flex-wrap: wrap; - margin: 10px 20px; -} -main #page-container #games-page .game-articles article.game-article .game-tags span { - font-size: 12px; - padding: 4px; - background-color: #d4d9dd; - color: #6b7880; - border-radius: 5px; - font-weight: 600; -} -main #page-container #games-page .game-articles article.game-article .game-subtitle { - grid-column: 1; - margin: 10px 20px; - color: #6b7880; -} -main #page-container #games-page .game-articles article.game-article .game-description { - grid-column: 1; - text-align: justify; - margin: 10px 20px 30px; -} -main #page-container #games-page .game-articles article.game-article .image-carousel { - grid-column: 2; - grid-row: 3/span 4; - height: 400px; -} -main #page-container #games-page .game-articles article.placeholder { - height: 400px; -} -main #page-container #games-page .game-articles article.placeholder * { - background-color: #d4d9dd; -} -@media screen and (max-width: 900px) { - main #page-container #games-page .game-articles article { - grid-template-columns: 1fr; - } - main #page-container #games-page .game-articles article.game-article { - grid-template-rows: repeat(6, auto); - } - main #page-container #games-page .game-articles article.game-article .game-title { - grid-column: 1; - padding: 0; - font-size: 25px; - } - main #page-container #games-page .game-articles article.game-article .game-banner { - grid-column: 1; - margin: 0 -20px; - height: 200px; - } - main #page-container #games-page .game-articles article.game-article .image-carousel { - grid-column: 1; - grid-row: 3; - margin: 0 -20px; - } -} -main #page-container #games-page .game-articles article .play-button { - border: none; - background-color: unset; - font-weight: bold; - font-size: 20px; - cursor: pointer; - color: #4baabb; -} -main #page-container #games-page .game-articles article .play-button:hover { - color: #72e3f0; -} -main #page-container #software-page .software-articles { - margin: 20px auto 50px; -} -main #page-container #software-page .software-articles article.software-article { - display: grid; - grid-template-columns: auto 1fr; - margin: 0 0 50px; - gap: 10px 30px; -} -main #page-container #software-page .software-articles article.software-article .software-title { - grid-column: 2; - color: #aabbc8; - margin: 0; - padding: 10px; -} -main #page-container #software-page .software-articles article.software-article .software-subtitle { - grid-column: 2; - margin: 10px; - color: #6b7880; -} -main #page-container #software-page .software-articles article.software-article .software-description { - grid-column: 2; - text-align: justify; - margin: 10px; -} -main #page-container #software-page .software-articles article.software-article .software-image { - padding: 20px; - background-color: black; - grid-column: 1; - grid-row: 1/span 3; - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; - width: 200px; - height: 200px; - overflow: hidden; - border-radius: 100%; -} -main #page-container #software-page .software-articles article.software-article .software-image img { - max-width: 100%; - max-height: 400px; -} -@media screen and (max-width: 900px) { - main #page-container #software-page .software-articles article.software-article .software-title { - display: flex; - align-items: center; - } - main #page-container #software-page .software-articles article.software-article .software-subtitle, -main #page-container #software-page .software-articles article.software-article .software-description { - grid-column: 1/span 2; - } - main #page-container #software-page .software-articles article.software-article .software-image { - width: 100px; - height: 100px; - grid-row: 1; - } -} -main #page-container #software-page .software-articles article.placeholder { - display: flex; - flex-direction: column; - gap: 10px; - margin: 30px; -} -main #page-container #software-page .software-articles article.placeholder * { - background-color: #d4d9dd; -} -main #page-container #software-page .software-articles article.placeholder .title { - height: 60px; -} -main #page-container #software-page .software-articles article.placeholder .body { - height: 400px; -} -main #page-container #software-page .software-articles article.placeholder .details { - height: 200px; -} -main footer { - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; - flex-direction: column; - width: 100%; - background-image: url("/assets/images/wallpaper_binary.png"); - padding: 40px 20px; - gap: 20px; - font-size: 12px; -} -main footer span { - color: #96a5ae; - text-align: center; -} -main footer .logo { - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; - gap: 10px; -} -main footer .logo img { - width: 35px; -} -main footer .logo img.text-logo { - width: 100px; -} -main footer .social { - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; - gap: 20px; -} -main footer .social a { - background-color: #555d61; - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; - width: 25px; - height: 25px; - font-weight: bold; - font-size: 16px; - border-radius: 100%; -} - -/*# sourceMappingURL=style.css.map */ -- GitLab