Newer
Older
(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`,
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/**
* 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";
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;
},
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
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;
},
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
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.
*/
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;
};
break;
case "override":
htmlNode.innerHTML = "";
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());
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);
"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);
constructor() {
super({ id: "home-page" });
}
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",
},
{
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: [
{
img: "game_controller.svg",
href: "/games/",
description:
},
{
title: "Software",
img: "meca_proc.svg",
href: "/software-development/",
},
].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){
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()
},{"../../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){
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);
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
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,
},
].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)
}
})
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
}),
};
}
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",
},
],
},
{
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: 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]);