diff --git a/public/articles/games/fantom_quest/fantom_quest.json b/public/articles/games/fantom_quest/fantom_quest.json deleted file mode 100755 index 5a36a8776e917f7ffc23efd0dc1179e0cf1aaca4..0000000000000000000000000000000000000000 --- a/public/articles/games/fantom_quest/fantom_quest.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "title": "Fantom Quest", - "subtitle": "Le labyrinthe de la quête d'identité", - "tags": ["jeu web", "pixelart", "javascript", "labyrinthe"], - "body": "<file>fantom_quest.txt", - "image_banner": "fantom_quest_intro.png", - "images": [ - "screen_fantom_quest_1.jpg", - "screen_fantom_quest_2.jpg", - "screen_fantom_quest_3.jpg", - "screen_fantom_quest_4.jpg", - "screen_fantom_quest_5.jpg" - ], - "team_subarticles": [ - { - "title": "Lucie Ventadour", - "subtitle": "Artiste 2D, illustratice, artiste peintre, enseignante arts plastiques", - "body": "<file>lucie_ventadour.txt", - "images": ["lucipix.png"] - }, - { - "title": "Pierre Jarriges", - "subtitle": "Auteur BD, dessinateur, compositeur, développeur.", - "body": "<file>pijar.txt", - "images": ["pijarpix.png"] - } - ] -} diff --git a/public/articles/games/fantom_quest/fantom_quest.txt b/public/articles/games/fantom_quest/fantom_quest.txt deleted file mode 100755 index a65a64cff65661e95229aeec494304111dc6a1d0..0000000000000000000000000000000000000000 --- a/public/articles/games/fantom_quest/fantom_quest.txt +++ /dev/null @@ -1,11 +0,0 @@ -Un petit fantôme en quête d'identité va devoir gravir chaque étage d'un donjon afin de rencontrer la sorcière qui lui révélera sa vraie nature. - -Ce petit jeu de quête minimaliste en forme de labyrinthe sera jouable directement sur navigateur ou à télécharger sur pc. -<s>Sortie prévue : Mars 2021 (si on y arrive !)</s> -Ça arrive, ça arrive. - -- Concept original et graphismes : Lucie Ventadour - -- Programmation Javascript et bande son: Pierre Jarriges - -- <a href="https://gitlab.com/kuadrado-software/fantom-quest" target="_blank">Dépôt du code source</a> diff --git a/public/articles/games/fantom_quest/images/lucipix.png b/public/articles/games/fantom_quest/images/lucipix.png deleted file mode 100755 index e8add7830567023d7d884bd74c0879ca8859eff8..0000000000000000000000000000000000000000 Binary files a/public/articles/games/fantom_quest/images/lucipix.png and /dev/null differ diff --git a/public/articles/games/fantom_quest/images/pijarpix.png b/public/articles/games/fantom_quest/images/pijarpix.png deleted file mode 100755 index e32f91b8528a1d3c06af2aa88d51320504a7dee3..0000000000000000000000000000000000000000 Binary files a/public/articles/games/fantom_quest/images/pijarpix.png and /dev/null differ diff --git a/public/articles/games/fantom_quest/lucie_ventadour.txt b/public/articles/games/fantom_quest/lucie_ventadour.txt deleted file mode 100755 index fbe251f37a26b39097daadaea7164116945c223f..0000000000000000000000000000000000000000 --- a/public/articles/games/fantom_quest/lucie_ventadour.txt +++ /dev/null @@ -1 +0,0 @@ -<a href="https://www.lucieventadour.com/" target="_blank">Site web</a> | <a href="http://lucipix.canalblog.com/" target="_blank">Blog pix</a> \ No newline at end of file diff --git a/public/articles/games/fantom_quest/pijar.txt b/public/articles/games/fantom_quest/pijar.txt deleted file mode 100755 index 09caaea7310a7810f36c1ce2821e35285c71e091..0000000000000000000000000000000000000000 --- a/public/articles/games/fantom_quest/pijar.txt +++ /dev/null @@ -1 +0,0 @@ -<a href="https://pierrejarriges-dessins.blogspot.com" target="_blank">Blog Dessin</a> | <a href="https://soundcloud.com/abstractobject" target="_blank">Soundcloud</a> | <a href="https://gitlab.com/peter_rabbit" target="_blank">Gitlab</a> | <a href=" https://github.com/codnpix" target="_blank">Github</a> \ No newline at end of file diff --git a/public/articles/games/index.json b/public/articles/games/index.json deleted file mode 100755 index a9d294c4f48cce33b6436c5970e02b71723453d4..0000000000000000000000000000000000000000 --- a/public/articles/games/index.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "articles": ["fantom_quest"] -} diff --git a/public/articles/news/game-studio-club-pioneers/game-studio-club-pioneers.json b/public/articles/news/game-studio-club-pioneers/game-studio-club-pioneers.json deleted file mode 100644 index 6ed7bbf15a898535fd512dc913af48e9d3908345..0000000000000000000000000000000000000000 --- a/public/articles/news/game-studio-club-pioneers/game-studio-club-pioneers.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "title": "Le Game Studio Club est lancé !", - "date": "2021/03/27", - "subtitle": "", - "body": "<file>game-studio-club-pioneers.txt", - "images": [ - "gsc_3.jpg", - "gsc_1.jpg", - "gsc_2.jpg" - ] -} \ No newline at end of file diff --git a/public/articles/news/game-studio-club-pioneers/game-studio-club-pioneers.txt b/public/articles/news/game-studio-club-pioneers/game-studio-club-pioneers.txt deleted file mode 100644 index 83fa427c2a07082ddb1a05d413fc33487ea5563c..0000000000000000000000000000000000000000 --- a/public/articles/news/game-studio-club-pioneers/game-studio-club-pioneers.txt +++ /dev/null @@ -1,4 +0,0 @@ -Quelques jeunes pionniers se lancent courageusement dans le code Python et l'animation 2D ! - -<b><blue>Envie de nous rejoindre ?</blue></b> -Toutes les infos <a href="https://kuadrado-software.fr/education/#game-studio-club" target="_blank">sur la page Game Studio Club</a> \ No newline at end of file diff --git a/public/articles/news/game-studio-club-pioneers/images/gsc_1.jpg b/public/articles/news/game-studio-club-pioneers/images/gsc_1.jpg deleted file mode 100644 index 16c30edb49af888166b3a4253fdf827fbc086fda..0000000000000000000000000000000000000000 Binary files a/public/articles/news/game-studio-club-pioneers/images/gsc_1.jpg and /dev/null differ diff --git a/public/articles/news/game-studio-club-pioneers/images/gsc_2.jpg b/public/articles/news/game-studio-club-pioneers/images/gsc_2.jpg deleted file mode 100644 index 12c3c59d1525d933773a525459cf540ea88426d7..0000000000000000000000000000000000000000 Binary files a/public/articles/news/game-studio-club-pioneers/images/gsc_2.jpg and /dev/null differ diff --git a/public/articles/news/game-studio-club-pioneers/images/gsc_3.jpg b/public/articles/news/game-studio-club-pioneers/images/gsc_3.jpg deleted file mode 100644 index 547eb7d52bf36a7763572af6753c5eb30df48a46..0000000000000000000000000000000000000000 Binary files a/public/articles/news/game-studio-club-pioneers/images/gsc_3.jpg and /dev/null differ diff --git a/public/articles/news/go-game-studio-club/go-game-studio-club.json b/public/articles/news/go-game-studio-club/go-game-studio-club.json deleted file mode 100644 index 746a36a0b32e40cda1ae6bf5a151fa80b4d865da..0000000000000000000000000000000000000000 --- a/public/articles/news/go-game-studio-club/go-game-studio-club.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "title": "Le Game Studio Club démarrera le 1er Mars !", - "date": "2021/02/10", - "subtitle": "Apprends à créer un jeu de A à Z !", - "body": "<file>go-game-studio-club.txt", - "images": ["platformer_pixelart_banner.png"] -} diff --git a/public/articles/news/go-game-studio-club/go-game-studio-club.txt b/public/articles/news/go-game-studio-club/go-game-studio-club.txt deleted file mode 100644 index a21fd8c92d84afbeccfa4f0bd04ad3cbe6640755..0000000000000000000000000000000000000000 --- a/public/articles/news/go-game-studio-club/go-game-studio-club.txt +++ /dev/null @@ -1,11 +0,0 @@ -Le <b><a href="https://kuadrado-software.fr/education/#game-studio-club" target="_blank">Game Studio Club</a></b> ouvrira ses portes à compter du 1er Mars dans nos locaux à Vernoux en Vivarais ! - -<b><blue>Qu'est-ce qu'on y fera ?</blue></b> - -Des <b>jeux vidéos</b> bien sûr, non pas pour y jouer mais bien pour les <b>fabriquer</b> ! -Créer un jeu c'est l'occasion de croiser ensemble une multitude de disciplines, comme l'animation 2D, l'écriture, le sons, la programmation, les maths, et d'autres choses encore. -S'inscrire au Game Studio Club c'est pouvoir être accompagné en douceur sur l'apprentissage de toutes ces choses et acquerrir l'autonomie pour créer un jeu vidéo entièrement. - -Retrouvez toutes les infos sur la page <b><a href="https://kuadrado-software.fr/education/#game-studio-club" target="_blank">Game Studio Club</a></b> ! - -À très bientôt ! diff --git a/public/articles/news/go-game-studio-club/images/platformer_pixelart_banner.png b/public/articles/news/go-game-studio-club/images/platformer_pixelart_banner.png deleted file mode 100644 index 87eae460c230abd732129ac6b2e40340ea679133..0000000000000000000000000000000000000000 Binary files a/public/articles/news/go-game-studio-club/images/platformer_pixelart_banner.png and /dev/null differ diff --git a/public/articles/news/index.json b/public/articles/news/index.json deleted file mode 100755 index 89d317d927d2b73d5c0417fd3d4b966246181f43..0000000000000000000000000000000000000000 --- a/public/articles/news/index.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "articles": ["installation-kuadrado", "go-game-studio-club", "rdb_27_02_2021", "game-studio-club-pioneers"] -} diff --git a/public/articles/news/installation-kuadrado/images/atelier_bureau.jpg b/public/articles/news/installation-kuadrado/images/atelier_bureau.jpg deleted file mode 100755 index 76976fbb035c92ec756f5b6789c997125ae23821..0000000000000000000000000000000000000000 Binary files a/public/articles/news/installation-kuadrado/images/atelier_bureau.jpg and /dev/null differ diff --git a/public/articles/news/installation-kuadrado/images/local_entree.jpg b/public/articles/news/installation-kuadrado/images/local_entree.jpg deleted file mode 100755 index fc605a67176a957bea3a4242568c6fe007eb8db1..0000000000000000000000000000000000000000 Binary files a/public/articles/news/installation-kuadrado/images/local_entree.jpg and /dev/null differ diff --git a/public/articles/news/installation-kuadrado/installation-kuadrado.json b/public/articles/news/installation-kuadrado/installation-kuadrado.json deleted file mode 100755 index e7d1f06a46d70c47a574bb8ae74c973bca89369b..0000000000000000000000000000000000000000 --- a/public/articles/news/installation-kuadrado/installation-kuadrado.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "title": "Kuadrado s'installe à Vernoux en Vivarais (07)", - "date": "2021/01/24", - "subtitle": "De l'art et du code !", - "body": "<file>installation-kuadrado.txt", - "images": [ - "local_entree.jpg", - "atelier_bureau.jpg" - ] -} diff --git a/public/articles/news/installation-kuadrado/installation-kuadrado.txt b/public/articles/news/installation-kuadrado/installation-kuadrado.txt deleted file mode 100755 index aa32460c2f25a0d92d9721aa3c76ad66bf5dc677..0000000000000000000000000000000000000000 --- a/public/articles/news/installation-kuadrado/installation-kuadrado.txt +++ /dev/null @@ -1,4 +0,0 @@ -Depuis septembre 2020, Kuadrado Software investit ses nouveaux locaux à Vernoux en Vivarais pour y développer ses activités : -Création de jeu vidéo, ateliers pédagogiques, et développement web et logiciel. - -À très bientôt ! <img src="/assets/images/mini_pix/pc_pix.png"/> <img src="/assets/images/mini_pix/heart_pix.gif"/> \ No newline at end of file diff --git a/public/articles/news/rdb_27_02_2021/rdb_27_02_2021.json b/public/articles/news/rdb_27_02_2021/rdb_27_02_2021.json deleted file mode 100644 index 56e7323460b16ccdf00c28820463a0147a8f56f7..0000000000000000000000000000000000000000 --- a/public/articles/news/rdb_27_02_2021/rdb_27_02_2021.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "title": "On en parle à la radio !", - "date": "2021/03/01", - "subtitle": "Podcast RDB.fm - Avatars et Coquecigrues - 27/02/2021", - "body": "<file>rdb_27_02_2021.txt" -} diff --git a/public/articles/news/rdb_27_02_2021/rdb_27_02_2021.txt b/public/articles/news/rdb_27_02_2021/rdb_27_02_2021.txt deleted file mode 100644 index 987e064debb38113dcd0ee1472d569caad071449..0000000000000000000000000000000000000000 --- a/public/articles/news/rdb_27_02_2021/rdb_27_02_2021.txt +++ /dev/null @@ -1,5 +0,0 @@ -Samedi dernier on parlait de <b><blue>Kuadrado Software</blue></b> et de l'<b><blue>École Numérique Ardéchoise</blue></b> avec Jean-Bernard Huet et Pierre Jarriges sur <b><blue>Radio des Boutières</blue></b> ! -2 heures d'émssion et d'échanges autour du numérique, le code, la formation et la pédagogie, le web, le game dev et l'artisanat logiciel. -Merci à Nicolas et Frank de RDB.fm pour l'émission ! - -<iframe scrolling="no" id="hearthis_at_track_5680217" width="100%" height="150" src="https://app.hearthis.at/embed/5680217/transparent_black/?hcolor=&color=&style=2&block_size=2&block_space=1&background=1&waveform=0&cover=0&autoplay=0&css=" frameborder="0" allowtransparency allow="autoplay"><p>Listen to <a href="https://hearthis.at/radiodesboutieres/2021-02-27-avatars-et-coquecigrues-29/" target="_blank">2021-02-27 Avatars et Coquecigrues # 29</a> <span>by</span><a href="https://hearthis.at/radiodesboutieres/" target="_blank" >Radio des Boutières (RDBFM)</a> <span>on</span> <a href="https://hearthis.at/" target="_blank">hearthis.at</a></p></iframe> \ No newline at end of file diff --git a/public/articles/software/index.json b/public/articles/software/index.json deleted file mode 100755 index cac11b23b91fed1f84290688c8159f814d8f270a..0000000000000000000000000000000000000000 --- a/public/articles/software/index.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "articles": [ - "mentalo", - "object-to-html-renderer", - "make_frames" - ] -} \ No newline at end of file diff --git a/public/articles/software/make_frames/images/screen_make_frames.png b/public/articles/software/make_frames/images/screen_make_frames.png deleted file mode 100644 index 1cad3ad3fa68becd61855f7055cfacfc231ef401..0000000000000000000000000000000000000000 Binary files a/public/articles/software/make_frames/images/screen_make_frames.png and /dev/null differ diff --git a/public/articles/software/make_frames/make_frames.json b/public/articles/software/make_frames/make_frames.json deleted file mode 100644 index 8042256640b056f288dcd552cea9907f4ac10692..0000000000000000000000000000000000000000 --- a/public/articles/software/make_frames/make_frames.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "title": "Make Frames", - "date": "2021/02/21", - "subtitle": "Outil en ligne de commande de manipulation d'animation", - "body": "<file>make_frames.txt", - "details": [ - { - "label": "Stack", - "value": "Rust" - }, - { - "label": "License", - "value": "GNU-GPL v3" - }, - { - "label": "Source", - "value": "<a href='https://gitlab.com/kuadrado-software/make_frames' target='_blank'>gitlab.com/kuadrado-software/make_frames</a>" - } - ], - "images": [ - "screen_make_frames.png" - ], - "releases": [ - { - "version": "0.1.1", - "platform": "Debian/Ubuntu - amd64", - "download": "make_frames_0.1.1_amd64.deb" - } - ] -} \ No newline at end of file diff --git a/public/articles/software/make_frames/make_frames.txt b/public/articles/software/make_frames/make_frames.txt deleted file mode 100644 index 90fa245106e02d862e9a4443857d1d9f9ff2679c..0000000000000000000000000000000000000000 --- a/public/articles/software/make_frames/make_frames.txt +++ /dev/null @@ -1,3 +0,0 @@ -Make Frames est un utilitaire en ligne de commande pour Linux écrit en Rust destiné à faciliter le montage des animations sous forme de frise de frames. - -Le programme prend en entrée soit une animation au format GIF, soit un dossier de frames séparées au format PNG, et sort un fichier PNG unique avec toutes les frames montées en ligne. \ No newline at end of file diff --git a/public/articles/software/make_frames/release/make_frames_0.1.1_amd64.deb b/public/articles/software/make_frames/release/make_frames_0.1.1_amd64.deb deleted file mode 100644 index 6026931e4cfeedca14c43845c33b33f8115ce167..0000000000000000000000000000000000000000 Binary files a/public/articles/software/make_frames/release/make_frames_0.1.1_amd64.deb and /dev/null differ diff --git a/public/articles/software/mentalo/images/mental-eau.png b/public/articles/software/mentalo/images/mental-eau.png deleted file mode 100644 index ed6c2eeced7fbb226d7c30e1a526b7c0b09c6d93..0000000000000000000000000000000000000000 Binary files a/public/articles/software/mentalo/images/mental-eau.png and /dev/null differ diff --git a/public/articles/software/mentalo/mentalo.json b/public/articles/software/mentalo/mentalo.json deleted file mode 100755 index 7032d7ceab1e2c77419fda8c28a5a6da8332cc15..0000000000000000000000000000000000000000 --- a/public/articles/software/mentalo/mentalo.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "title": "Mentalo", - "date": "2021/07/18", - "subtitle": "Une application pour développer la logique en créant des jeux.", - "body": "<file>mentalo.txt", - "details": [ - { - "label": "Stack", - "value": "Javascript, Rust, MongoDb" - }, - { - "label": "License", - "value": "GNU GPL v3 / LGPL" - }, - { - "label": "Source App", - "value": "<a href='https://gitlab.com/kuadrado-software/mentalo-app' target='_blank'>gitlab.com/kuadrado-software/mentalo-app</a>" - }, - { - "label": "Source API", - "value": "<a href='https://gitlab.com/kuadrado-software/mentalo_api' target='_blank'>gitlab.com/kuadrado-software/mentalo_api</a>" - }, - { - "label": "Source Engine", - "value": "<a href='https://gitlab.com/kuadrado-software/mentalo-engine' target='_blank'>gitlab.com/kuadrado-software/mentalo-engine</a>" - }, - { - "label": "Source applet dessin", - "value": "<a href='https://gitlab.com/kuadrado-software/mentalo-drawing-tool' target='_blank'>gitlab.com/kuadrado-software/mentalo-drawing-tool</a>" - } - ], - "images": [ - "mental-eau.png" - ] -} \ No newline at end of file diff --git a/public/articles/software/mentalo/mentalo.txt b/public/articles/software/mentalo/mentalo.txt deleted file mode 100755 index 703729b735086c43ee78b90d40ea3be4cf104d30..0000000000000000000000000000000000000000 --- a/public/articles/software/mentalo/mentalo.txt +++ /dev/null @@ -1,2 +0,0 @@ -Mentalo est une application éducative qui permet de construire un raisonnement logique à travers la création de jeux simples basés sur des écrans statiques et des choix texte. -Accessible sur <strong><a href='https://mentalo-app.com/' target='_blank'>mentalo-app.com</a></strong> \ No newline at end of file diff --git a/public/articles/software/object-to-html-renderer/images/obj2htm-logo.png b/public/articles/software/object-to-html-renderer/images/obj2htm-logo.png deleted file mode 100644 index 8efda75f697267280b833f5aeb6b1a36b96cee67..0000000000000000000000000000000000000000 Binary files a/public/articles/software/object-to-html-renderer/images/obj2htm-logo.png and /dev/null differ diff --git a/public/articles/software/object-to-html-renderer/object-to-html-renderer.json b/public/articles/software/object-to-html-renderer/object-to-html-renderer.json deleted file mode 100755 index 25246b528dfcf507ff7ef33abd3eca5b1db4b29b..0000000000000000000000000000000000000000 --- a/public/articles/software/object-to-html-renderer/object-to-html-renderer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "title": "Object to HTML Renderer", - "date": "2021/07/18", - "subtitle": "Un moteur de rendu web dynamique", - "body": "<file>object-to-html-renderer.txt", - "details": [ - { - "label": "Stack", - "value": "Javascript, NodeJs" - }, - { - "label": "License", - "value": "LGPL-3.0" - }, - { - "label": "Source", - "value": "<a href='https://gitlab.com/kuadrado-software/object-to-html-renderer' target='_blank'>gitlab.com/kuadrado-software/object-to-html-renderer</a>" - } - ], - "images": [ - "obj-to-html-logo.png" - ] -} \ No newline at end of file diff --git a/public/articles/software/object-to-html-renderer/object-to-html-renderer.txt b/public/articles/software/object-to-html-renderer/object-to-html-renderer.txt deleted file mode 100755 index 34837d1ece16927b50fb23bb5ae75fc4aee71bd6..0000000000000000000000000000000000000000 --- a/public/articles/software/object-to-html-renderer/object-to-html-renderer.txt +++ /dev/null @@ -1 +0,0 @@ -Une librairie minimaliste et sans aucune dépendances pour faire du rendu web html dynamique avec du Javascript. \ No newline at end of file diff --git a/public/articles/games/fantom_quest/images/fantom_quest_intro.gif b/public/assets/images/fantom_quest_intro.gif similarity index 100% rename from public/articles/games/fantom_quest/images/fantom_quest_intro.gif rename to public/assets/images/fantom_quest_intro.gif diff --git a/public/articles/games/fantom_quest/images/fantom_quest_intro.png b/public/assets/images/fantom_quest_intro.png similarity index 100% rename from public/articles/games/fantom_quest/images/fantom_quest_intro.png rename to public/assets/images/fantom_quest_intro.png diff --git a/public/articles/games/fantom_quest/images/screen_fantom_quest_1.jpg b/public/assets/images/screen_fantom_quest_1.jpg similarity index 100% rename from public/articles/games/fantom_quest/images/screen_fantom_quest_1.jpg rename to public/assets/images/screen_fantom_quest_1.jpg diff --git a/public/articles/games/fantom_quest/images/screen_fantom_quest_2.jpg b/public/assets/images/screen_fantom_quest_2.jpg similarity index 100% rename from public/articles/games/fantom_quest/images/screen_fantom_quest_2.jpg rename to public/assets/images/screen_fantom_quest_2.jpg diff --git a/public/articles/games/fantom_quest/images/screen_fantom_quest_3.jpg b/public/assets/images/screen_fantom_quest_3.jpg similarity index 100% rename from public/articles/games/fantom_quest/images/screen_fantom_quest_3.jpg rename to public/assets/images/screen_fantom_quest_3.jpg diff --git a/public/articles/games/fantom_quest/images/screen_fantom_quest_4.jpg b/public/assets/images/screen_fantom_quest_4.jpg similarity index 100% rename from public/articles/games/fantom_quest/images/screen_fantom_quest_4.jpg rename to public/assets/images/screen_fantom_quest_4.jpg diff --git a/public/articles/games/fantom_quest/images/screen_fantom_quest_5.jpg b/public/assets/images/screen_fantom_quest_5.jpg similarity index 100% rename from public/articles/games/fantom_quest/images/screen_fantom_quest_5.jpg rename to public/assets/images/screen_fantom_quest_5.jpg diff --git a/public/education/education.js b/public/education/education.js index 9353ccf4d71b304483b2d3cb0210a70d0170a48f..2a3db470f49d7f33ed00ec85355e529908bd3f28 100644 --- a/public/education/education.js +++ b/public/education/education.js @@ -1,45 +1,99 @@ (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){ -function getServerUrl() { - return `${location.origin}${location.origin.charAt(location.origin.length - 1) !== "/" ? "/" : "" - }`; -} - module.exports = { - getServerUrl, build: { - protected_dirs: ["assets", "style", "articles"], + protected_dirs: ["assets", "style", "views", "standard"], default_meta_keys: ["title", "description", "image", "open_graph", "json_ld"], }, }; },{}],2:[function(require,module,exports){ -const { getServerUrl } = require("./config"); - module.exports = { - images_url: `${getServerUrl()}assets/images/`, - articles_url: `${getServerUrl()}articles/`, + images_url: `/assets/images/`, }; -},{"./config":1}],3:[function(require,module,exports){ +},{}],3:[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; }, - objectToHtml: function objectToHtml(obj) { - const { tag } = obj; - const node = document.createElement(tag); - const excludeKeys = ["tag", "contents", "style_rules", "state"]; + 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 => { - if (attr === "class") { - node.classList.add(...obj[attr].split(" ").filter(s => s !== "")); - } else { - node[attr] = obj[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") { @@ -53,6 +107,9 @@ module.exports = { node.innerHTML = el; break; case "object": + if (xmlns !== undefined) { + el = Object.assign(el, { xmlns }) + } node.appendChild(objectToHtml(el)); break; } @@ -67,23 +124,78 @@ module.exports = { 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 () { - this.subRender(this.renderCycleRoot.render(), document.getElementsByTagName("main")[0], { - mode: "replace", - }); + 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" }) { - const insert = this.objectToHtml(object); + let outputNode = null; + + const get_insert = () => { + outputNode = this.objectToHtml(object); + return outputNode; + }; + switch (options.mode) { case "append": - htmlNode.appendChild(insert); + htmlNode.appendChild(get_insert()); break; case "override": htmlNode.innerHTML = ""; - htmlNode.appendChild(insert); + htmlNode.appendChild(get_insert()); break; case "insert-before": - htmlNode.insertBefore(insert, htmlNode.childNodes[options.insertIndex]); + htmlNode.insertBefore(get_insert(), htmlNode.childNodes[options.insertIndex]); break; case "adjacent": /** @@ -94,18 +206,28 @@ module.exports = { * beforebegin * beforeend */ - htmlNode.insertAdjacentHTML(options.insertLocation, insert); + htmlNode.insertAdjacentHTML(options.insertLocation, get_insert()); break; case "replace": - htmlNode.parentNode.replaceChild(insert, htmlNode); + 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); }, }; - },{}],4:[function(require,module,exports){ "use strict"; @@ -432,13 +554,14 @@ runPage(EducationPage); },{"../../run-page":7,"./education":5}],7:[function(require,module,exports){ "use strict"; -const objectHtmlRenderer = require("object-to-html-renderer") +const renderer = require("object-to-html-renderer") const Template = require("./template/template"); module.exports = function runPage(PageComponent) { const template = new Template({ page: new PageComponent() }); - objectHtmlRenderer.setRenderCycleRoot(template); - objectHtmlRenderer.renderCycle(); + renderer.register("obj2htm") + obj2htm.setRenderCycleRoot(template); + obj2htm.renderCycle(); }; },{"./template/template":9,"object-to-html-renderer":3}],8:[function(require,module,exports){ diff --git a/public/games/games.js b/public/games/games.js index a7c9ab989fe6b43c48504ebdf68b827ccd21b86d..ceef0f01136166cff52a598d74d4ee2e1f60ff0c 100644 --- a/public/games/games.js +++ b/public/games/games.js @@ -1,45 +1,103 @@ (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){ -function getServerUrl() { - return `${location.origin}${location.origin.charAt(location.origin.length - 1) !== "/" ? "/" : "" - }`; +module.exports = { + images_url: "/assets/images" } - +},{}],2:[function(require,module,exports){ module.exports = { - getServerUrl, build: { - protected_dirs: ["assets", "style", "articles"], + protected_dirs: ["assets", "style", "views", "standard"], default_meta_keys: ["title", "description", "image", "open_graph", "json_ld"], }, }; -},{}],2:[function(require,module,exports){ -const { getServerUrl } = require("./config"); - +},{}],3:[function(require,module,exports){ module.exports = { - images_url: `${getServerUrl()}assets/images/`, - articles_url: `${getServerUrl()}articles/`, + images_url: `/assets/images/`, }; -},{"./config":1}],3:[function(require,module,exports){ +},{}],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; }, - objectToHtml: function objectToHtml(obj) { - const { tag } = obj; - const node = document.createElement(tag); - const excludeKeys = ["tag", "contents", "style_rules", "state"]; + 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 => { - if (attr === "class") { - node.classList.add(...obj[attr].split(" ").filter(s => s !== "")); - } else { - node[attr] = obj[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") { @@ -53,6 +111,9 @@ module.exports = { node.innerHTML = el; break; case "object": + if (xmlns !== undefined) { + el = Object.assign(el, { xmlns }) + } node.appendChild(objectToHtml(el)); break; } @@ -67,23 +128,78 @@ module.exports = { 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 () { - this.subRender(this.renderCycleRoot.render(), document.getElementsByTagName("main")[0], { - mode: "replace", - }); + 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" }) { - const insert = this.objectToHtml(object); + let outputNode = null; + + const get_insert = () => { + outputNode = this.objectToHtml(object); + return outputNode; + }; + switch (options.mode) { case "append": - htmlNode.appendChild(insert); + htmlNode.appendChild(get_insert()); break; case "override": htmlNode.innerHTML = ""; - htmlNode.appendChild(insert); + htmlNode.appendChild(get_insert()); break; case "insert-before": - htmlNode.insertBefore(insert, htmlNode.childNodes[options.insertIndex]); + htmlNode.insertBefore(get_insert(), htmlNode.childNodes[options.insertIndex]); break; case "adjacent": /** @@ -94,23 +210,31 @@ module.exports = { * beforebegin * beforeend */ - htmlNode.insertAdjacentHTML(options.insertLocation, insert); + htmlNode.insertAdjacentHTML(options.insertLocation, get_insert()); break; case "replace": - htmlNode.parentNode.replaceChild(insert, htmlNode); + 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); }, }; - -},{}],4:[function(require,module,exports){ +},{}],5:[function(require,module,exports){ "use strict"; -const objectHtmlRenderer = require("object-to-html-renderer") - class ImageCarousel { constructor(props) { this.props = props; @@ -138,7 +262,7 @@ class ImageCarousel { } refreshImage() { - objectHtmlRenderer.subRender(this.render(), document.getElementById(this.id), { + obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace", }); } @@ -176,10 +300,10 @@ class ImageCarousel { module.exports = ImageCarousel; -},{"object-to-html-renderer":3}],5:[function(require,module,exports){ +},{}],6:[function(require,module,exports){ "use strict"; -const { fetchjson, fetchtext } = require("./fetch"); +const { fetch_json_or_error_text } = require("./fetch"); function getArticleBody(text) { return text.replaceAll("\n", "<br/>"); @@ -189,57 +313,17 @@ function getArticleDate(date) { return `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`; } -function loadArticles(dir_url) { - return new Promise((resolve, reject) => { - fetchjson(`${dir_url}/index.json`) - .then(json => { - Promise.all( - json.articles.map(async artDir => { - const artPath = `${artDir}/${artDir}.json`; - const art = await fetchjson(`${dir_url}/${artPath}`); - const tmpSplit = artPath.split("/"); - tmpSplit.pop(); - const absArtPath = `${dir_url}/${tmpSplit.join("/")}`; - return Object.assign(art, { path: absArtPath }); - }) - ) - .then(articles => { - populateArticles(articles) - .then(completeArticles => resolve(completeArticles)) - .catch(e => reject(e)); - }) - .catch(e => reject(e)); - }) - .catch(e => console.log(e)); - }); -} - -function populateArticles(articles) { - return new Promise((resolve, reject) => { - Promise.all( - articles.map(async article => { - if (article.body.indexOf("<file>") !== -1) { - const txtPath = article.body.replace("<file>", ""); - const textValue = await fetchtext(`${article.path}/${txtPath}`); - article.body = textValue; - article.date = article.date ? new Date(article.date) : undefined; - } - return article; - }) - ) - .then(completeArticles => resolve(completeArticles.sort((a, b) => b.date - a.date))) - .catch(e => reject(e)); - }); +function loadArticles(category) { + return fetch_json_or_error_text(`/articles/${category}`) } module.exports = { loadArticles, getArticleBody, getArticleDate, - populateArticles, }; -},{"./fetch":6}],6:[function(require,module,exports){ +},{"./fetch":7}],7:[function(require,module,exports){ "use strict"; function fetchjson(url) { @@ -260,12 +344,25 @@ function fetchtext(url) { }); } +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, }; -},{}],7:[function(require,module,exports){ +},{}],8:[function(require,module,exports){ "use strict"; class WebPage { @@ -275,60 +372,13 @@ class WebPage { } module.exports = WebPage; -},{}],8:[function(require,module,exports){ +},{}],9:[function(require,module,exports){ "use strict"; +const { images_url } = require("../../../../../admin-frontend/src/constants"); const ImageCarousel = require("../../../generic-components/image-carousel"); const { getArticleBody } = require("../../../lib/article-utils"); -class TeamMember { - constructor(props) { - this.props = props; - } - - render() { - const { title, subtitle, body, images, path } = this.props; - return { - tag: "div", - class: "team-member", - typeof: "Person", - property: "author", - contents: [ - { - tag: "div", - class: "team-member-img", - contents: [ - { - tag: "img", - alt: `ìmage team member ${title}`, - src: images.map(im => `${path}/images/${im}`)[0], - property: "image", - }, - ], - }, - { - tag: "h3", - class: "team-member-title", - contents: title, - property: "name", - }, - { - tag: "strong", - class: "team-member-subtitle", - contents: subtitle, - property: "jobTitle", - }, - { - tag: "p", - class: "team-member-body", - contents: getArticleBody(body), - property: "description", - }, - ], - }; - } -} - class GameArticle { constructor(props) { this.props = props; @@ -337,13 +387,10 @@ class GameArticle { render() { const { title, - tags, - body, subtitle, + body, images, - path, - team_subarticles, - image_banner, + details, } = this.props; return { tag: "article", @@ -361,16 +408,9 @@ class GameArticle { tag: "div", class: "game-banner", contents: [ - { tag: "img", class: "pixelated", src: `${path}/images/${image_banner}` }, + { tag: "img", class: "pixelated", src: `${images_url}/${images[0]}` }, ], }, - { - tag: "div", - class: "game-tags", - contents: tags.map(tag => { - return { tag: "span", contents: tag, property: "about" }; - }), - }, { tag: "h3", class: "game-subtitle", @@ -383,21 +423,31 @@ class GameArticle { property: "description", contents: getArticleBody(body), }, - new ImageCarousel({ images: images.map(img => `${path}/images/${img}`) }).render(), - { + new ImageCarousel({ images: images.map(img => `${images_url}/${img}`) }).render(), + details.length > 0 && { tag: "div", - class: "game-team", + class: "article-details", contents: [ { tag: "h2", - contents: "L'équipe", + contents: "Details", }, { - tag: "div", - class: "team-members", - contents: team_subarticles.map(tsa => - new TeamMember({ ...tsa }).render() - ), + tag: "ul", + class: "details-list", + contents: details.map(detail => { + return { + tag: "li", + class: "detail", + contents: [ + { tag: "label", contents: detail.label }, + { + tag: "div", + contents: detail.value + }, + ], + }; + }), }, ], }, @@ -408,12 +458,10 @@ class GameArticle { module.exports = GameArticle; -},{"../../../generic-components/image-carousel":4,"../../../lib/article-utils":5}],9:[function(require,module,exports){ +},{"../../../../../admin-frontend/src/constants":1,"../../../generic-components/image-carousel":5,"../../../lib/article-utils":6}],10:[function(require,module,exports){ "use strict"; -const { articles_url } = require("../../../../constants"); -const { loadArticles, populateArticles } = require("../../../lib/article-utils"); -const objectHtmlRenderer = require("object-to-html-renderer") +const { loadArticles } = require("../../../lib/article-utils"); const GameArticle = require("./game-article"); class GameArticles { @@ -427,21 +475,10 @@ class GameArticles { } loadArticles() { - loadArticles(`${articles_url}games`) + loadArticles("games") .then(articles => { - Promise.all( - articles.map(async a => { - if (a.team_subarticles) { - a.team_subarticles = await populateArticles( - a.team_subarticles.map(sa => Object.assign(sa, { path: a.path })) - ); - } - return a; - }) - ).then(completeArticles => { - this.state.articles = completeArticles; - this.refresh(); - }); + this.state.articles = articles; + this.refresh(); }) .catch(e => console.log(e)); } @@ -455,7 +492,7 @@ class GameArticles { } refresh() { - objectHtmlRenderer.subRender(this.render(), document.getElementById(this.id), { + obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace", }); } @@ -476,7 +513,7 @@ class GameArticles { module.exports = GameArticles; -},{"../../../../constants":2,"../../../lib/article-utils":5,"./game-article":8,"object-to-html-renderer":3}],10:[function(require,module,exports){ +},{"../../../lib/article-utils":6,"./game-article":9}],11:[function(require,module,exports){ "use strict"; const { images_url } = require("../../../constants"); @@ -526,7 +563,7 @@ class GamesPage extends WebPage { module.exports = GamesPage; -},{"../../../constants":2,"../../lib/web-page":7,"./components/game-articles":9}],11:[function(require,module,exports){ +},{"../../../constants":3,"../../lib/web-page":8,"./components/game-articles":10}],12:[function(require,module,exports){ "use strict"; "use strict"; @@ -534,19 +571,20 @@ const runPage = require("../../run-page"); const GamesPage = require("./games"); runPage(GamesPage); -},{"../../run-page":12,"./games":10}],12:[function(require,module,exports){ +},{"../../run-page":13,"./games":11}],13:[function(require,module,exports){ "use strict"; -const objectHtmlRenderer = require("object-to-html-renderer") +const renderer = require("object-to-html-renderer") const Template = require("./template/template"); module.exports = function runPage(PageComponent) { const template = new Template({ page: new PageComponent() }); - objectHtmlRenderer.setRenderCycleRoot(template); - objectHtmlRenderer.renderCycle(); + renderer.register("obj2htm") + obj2htm.setRenderCycleRoot(template); + obj2htm.renderCycle(); }; -},{"./template/template":14,"object-to-html-renderer":3}],13:[function(require,module,exports){ +},{"./template/template":15,"object-to-html-renderer":4}],14:[function(require,module,exports){ "use strict"; const { images_url } = require("../../../constants"); @@ -656,7 +694,7 @@ class NavBar { module.exports = NavBar; -},{"../../../constants":2}],14:[function(require,module,exports){ +},{"../../../constants":3}],15:[function(require,module,exports){ "use strict"; const { in_construction } = require("../../config"); @@ -765,4 +803,4 @@ class Template { module.exports = Template; -},{"../../config":1,"../../constants":2,"./components/navbar":13}]},{},[11]); +},{"../../config":2,"../../constants":3,"./components/navbar":14}]},{},[12]); diff --git a/public/main.js b/public/main.js index 314ad6705028b05a2de0346de576cc758a46a0e2..49aa5f00bf4dcea42590e8ba125749f873a37bff 100644 --- a/public/main.js +++ b/public/main.js @@ -1,45 +1,99 @@ (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){ -function getServerUrl() { - return `${location.origin}${location.origin.charAt(location.origin.length - 1) !== "/" ? "/" : "" - }`; -} - module.exports = { - getServerUrl, build: { - protected_dirs: ["assets", "style", "articles"], + protected_dirs: ["assets", "style", "views", "standard"], default_meta_keys: ["title", "description", "image", "open_graph", "json_ld"], }, }; },{}],2:[function(require,module,exports){ -const { getServerUrl } = require("./config"); - module.exports = { - images_url: `${getServerUrl()}assets/images/`, - articles_url: `${getServerUrl()}articles/`, + images_url: `/assets/images/`, }; -},{"./config":1}],3:[function(require,module,exports){ +},{}],3:[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; }, - objectToHtml: function objectToHtml(obj) { - const { tag } = obj; - const node = document.createElement(tag); - const excludeKeys = ["tag", "contents", "style_rules", "state"]; + 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 => { - if (attr === "class") { - node.classList.add(...obj[attr].split(" ").filter(s => s !== "")); - } else { - node[attr] = obj[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") { @@ -53,6 +107,9 @@ module.exports = { node.innerHTML = el; break; case "object": + if (xmlns !== undefined) { + el = Object.assign(el, { xmlns }) + } node.appendChild(objectToHtml(el)); break; } @@ -67,23 +124,78 @@ module.exports = { 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 () { - this.subRender(this.renderCycleRoot.render(), document.getElementsByTagName("main")[0], { - mode: "replace", - }); + 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" }) { - const insert = this.objectToHtml(object); + let outputNode = null; + + const get_insert = () => { + outputNode = this.objectToHtml(object); + return outputNode; + }; + switch (options.mode) { case "append": - htmlNode.appendChild(insert); + htmlNode.appendChild(get_insert()); break; case "override": htmlNode.innerHTML = ""; - htmlNode.appendChild(insert); + htmlNode.appendChild(get_insert()); break; case "insert-before": - htmlNode.insertBefore(insert, htmlNode.childNodes[options.insertIndex]); + htmlNode.insertBefore(get_insert(), htmlNode.childNodes[options.insertIndex]); break; case "adjacent": /** @@ -94,23 +206,31 @@ module.exports = { * beforebegin * beforeend */ - htmlNode.insertAdjacentHTML(options.insertLocation, insert); + htmlNode.insertAdjacentHTML(options.insertLocation, get_insert()); break; case "replace": - htmlNode.parentNode.replaceChild(insert, htmlNode); + 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); }, }; - },{}],4:[function(require,module,exports){ "use strict"; -const objectHtmlRenderer = require("object-to-html-renderer") - class ImageCarousel { constructor(props) { this.props = props; @@ -138,7 +258,7 @@ class ImageCarousel { } refreshImage() { - objectHtmlRenderer.subRender(this.render(), document.getElementById(this.id), { + obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace", }); } @@ -176,7 +296,7 @@ class ImageCarousel { module.exports = ImageCarousel; -},{"object-to-html-renderer":3}],5:[function(require,module,exports){ +},{}],5:[function(require,module,exports){ "use strict"; class KuadradoValues { @@ -239,7 +359,6 @@ module.exports = KuadradoValues; "use strict"; const { articles_url } = require("../../constants"); -const objectHtmlRenderer = require("object-to-html-renderer"); const ImageCarousel = require("../generic-components/image-carousel"); const { loadArticles, getArticleDate, getArticleBody } = require("../lib/article-utils"); @@ -263,7 +382,7 @@ class NewsArticles { } refresh() { - objectHtmlRenderer.subRender(this.render(), document.getElementById(this.id), { + obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace", }); } @@ -319,9 +438,9 @@ class NewsArticles { ], }, articleData.images && - new ImageCarousel({ - images: articleData.images.map(img => `${articleData.path}/images/${img}`), - }).render(), + new ImageCarousel({ + images: articleData.images.map(img => `${articleData.path}/images/${img}`), + }).render(), ], }; } @@ -348,8 +467,8 @@ class NewsArticles { ? showArticleIndex - 1 : 0 : showArticleIndex + 1 <= articles.length - 1 - ? showArticleIndex + 1 - : articles.length - 1; + ? showArticleIndex + 1 + : articles.length - 1; this.state.showArticleIndex = showArticleIndex; this.refresh(); } @@ -365,26 +484,26 @@ class NewsArticles { contents: articles.length > 0 ? [ - this.renderArticle(articles[showArticleIndex]), - { - tag: "div", - class: "prev-next-buttons", - contents: [ - { - tag: "button", - class: `prev-btn ${!showPrev ? "disabled" : "active"}`, - contents: "Précédent", - onclick: this.handleChangeArticle.bind(this, "prev"), - }, - { - tag: "button", - class: `next-btn ${!showNext ? "disabled" : "active"}`, - contents: "Suivant", - onclick: this.handleChangeArticle.bind(this, "next"), - }, - ], - }, - ] + this.renderArticle(articles[showArticleIndex]), + { + tag: "div", + class: "prev-next-buttons", + contents: [ + { + tag: "button", + class: `prev-btn ${!showPrev ? "disabled" : "active"}`, + contents: "Précédent", + onclick: this.handleChangeArticle.bind(this, "prev"), + }, + { + tag: "button", + class: `next-btn ${!showNext ? "disabled" : "active"}`, + contents: "Suivant", + onclick: this.handleChangeArticle.bind(this, "next"), + }, + ], + }, + ] : [this.renderArticlePlaceholder()], }; } @@ -392,7 +511,7 @@ class NewsArticles { module.exports = NewsArticles; -},{"../../constants":2,"../generic-components/image-carousel":4,"../lib/article-utils":10,"object-to-html-renderer":3}],7:[function(require,module,exports){ +},{"../../constants":2,"../generic-components/image-carousel":4,"../lib/article-utils":10}],7:[function(require,module,exports){ "use strict"; const { images_url } = require("../../constants"); @@ -642,7 +761,7 @@ module.exports = HomePage; },{"../constants":2,"./home-page-components/kuadrado-values":5,"./home-page-components/news-articles":6,"./home-page-components/theme-card":7,"./home-page-components/whoami":8,"./lib/web-page":12}],10:[function(require,module,exports){ "use strict"; -const { fetchjson, fetchtext } = require("./fetch"); +const { fetch_json_or_error_text } = require("./fetch"); function getArticleBody(text) { return text.replaceAll("\n", "<br/>"); @@ -652,54 +771,14 @@ function getArticleDate(date) { return `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`; } -function loadArticles(dir_url) { - return new Promise((resolve, reject) => { - fetchjson(`${dir_url}/index.json`) - .then(json => { - Promise.all( - json.articles.map(async artDir => { - const artPath = `${artDir}/${artDir}.json`; - const art = await fetchjson(`${dir_url}/${artPath}`); - const tmpSplit = artPath.split("/"); - tmpSplit.pop(); - const absArtPath = `${dir_url}/${tmpSplit.join("/")}`; - return Object.assign(art, { path: absArtPath }); - }) - ) - .then(articles => { - populateArticles(articles) - .then(completeArticles => resolve(completeArticles)) - .catch(e => reject(e)); - }) - .catch(e => reject(e)); - }) - .catch(e => console.log(e)); - }); -} - -function populateArticles(articles) { - return new Promise((resolve, reject) => { - Promise.all( - articles.map(async article => { - if (article.body.indexOf("<file>") !== -1) { - const txtPath = article.body.replace("<file>", ""); - const textValue = await fetchtext(`${article.path}/${txtPath}`); - article.body = textValue; - article.date = article.date ? new Date(article.date) : undefined; - } - return article; - }) - ) - .then(completeArticles => resolve(completeArticles.sort((a, b) => b.date - a.date))) - .catch(e => reject(e)); - }); +function loadArticles(category) { + return fetch_json_or_error_text(`/articles/${category}`) } module.exports = { loadArticles, getArticleBody, getArticleDate, - populateArticles, }; },{"./fetch":11}],11:[function(require,module,exports){ @@ -723,9 +802,22 @@ function fetchtext(url) { }); } +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, }; },{}],12:[function(require,module,exports){ @@ -749,13 +841,14 @@ runPage(HomePage); },{"./homepage":9,"./run-page":14}],14:[function(require,module,exports){ "use strict"; -const objectHtmlRenderer = require("object-to-html-renderer") +const renderer = require("object-to-html-renderer") const Template = require("./template/template"); module.exports = function runPage(PageComponent) { const template = new Template({ page: new PageComponent() }); - objectHtmlRenderer.setRenderCycleRoot(template); - objectHtmlRenderer.renderCycle(); + renderer.register("obj2htm") + obj2htm.setRenderCycleRoot(template); + obj2htm.renderCycle(); }; },{"./template/template":16,"object-to-html-renderer":3}],15:[function(require,module,exports){ diff --git a/public/software-development/software-development.js b/public/software-development/software-development.js index 06cc0cf53a8e4a7a3c4d3e6a5319f987a1b639c9..21d30045447fa4bd8e31a7aaac84838820c939ae 100644 --- a/public/software-development/software-development.js +++ b/public/software-development/software-development.js @@ -1,45 +1,103 @@ (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){ -function getServerUrl() { - return `${location.origin}${location.origin.charAt(location.origin.length - 1) !== "/" ? "/" : "" - }`; +module.exports = { + images_url: "/assets/images" } - +},{}],2:[function(require,module,exports){ module.exports = { - getServerUrl, build: { - protected_dirs: ["assets", "style", "articles"], + protected_dirs: ["assets", "style", "views", "standard"], default_meta_keys: ["title", "description", "image", "open_graph", "json_ld"], }, }; -},{}],2:[function(require,module,exports){ -const { getServerUrl } = require("./config"); - +},{}],3:[function(require,module,exports){ module.exports = { - images_url: `${getServerUrl()}assets/images/`, - articles_url: `${getServerUrl()}articles/`, + images_url: `/assets/images/`, }; -},{"./config":1}],3:[function(require,module,exports){ +},{}],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; }, - objectToHtml: function objectToHtml(obj) { - const { tag } = obj; - const node = document.createElement(tag); - const excludeKeys = ["tag", "contents", "style_rules", "state"]; + 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 => { - if (attr === "class") { - node.classList.add(...obj[attr].split(" ").filter(s => s !== "")); - } else { - node[attr] = obj[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") { @@ -53,6 +111,9 @@ module.exports = { node.innerHTML = el; break; case "object": + if (xmlns !== undefined) { + el = Object.assign(el, { xmlns }) + } node.appendChild(objectToHtml(el)); break; } @@ -67,23 +128,78 @@ module.exports = { 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 () { - this.subRender(this.renderCycleRoot.render(), document.getElementsByTagName("main")[0], { - mode: "replace", - }); + 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" }) { - const insert = this.objectToHtml(object); + let outputNode = null; + + const get_insert = () => { + outputNode = this.objectToHtml(object); + return outputNode; + }; + switch (options.mode) { case "append": - htmlNode.appendChild(insert); + htmlNode.appendChild(get_insert()); break; case "override": htmlNode.innerHTML = ""; - htmlNode.appendChild(insert); + htmlNode.appendChild(get_insert()); break; case "insert-before": - htmlNode.insertBefore(insert, htmlNode.childNodes[options.insertIndex]); + htmlNode.insertBefore(get_insert(), htmlNode.childNodes[options.insertIndex]); break; case "adjacent": /** @@ -94,22 +210,32 @@ module.exports = { * beforebegin * beforeend */ - htmlNode.insertAdjacentHTML(options.insertLocation, insert); + htmlNode.insertAdjacentHTML(options.insertLocation, get_insert()); break; case "replace": - htmlNode.parentNode.replaceChild(insert, htmlNode); + 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); }, }; - -},{}],4:[function(require,module,exports){ +},{}],5:[function(require,module,exports){ "use strict"; -const { fetchjson, fetchtext } = require("./fetch"); +const { fetch_json_or_error_text } = require("./fetch"); function getArticleBody(text) { return text.replaceAll("\n", "<br/>"); @@ -119,57 +245,17 @@ function getArticleDate(date) { return `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`; } -function loadArticles(dir_url) { - return new Promise((resolve, reject) => { - fetchjson(`${dir_url}/index.json`) - .then(json => { - Promise.all( - json.articles.map(async artDir => { - const artPath = `${artDir}/${artDir}.json`; - const art = await fetchjson(`${dir_url}/${artPath}`); - const tmpSplit = artPath.split("/"); - tmpSplit.pop(); - const absArtPath = `${dir_url}/${tmpSplit.join("/")}`; - return Object.assign(art, { path: absArtPath }); - }) - ) - .then(articles => { - populateArticles(articles) - .then(completeArticles => resolve(completeArticles)) - .catch(e => reject(e)); - }) - .catch(e => reject(e)); - }) - .catch(e => console.log(e)); - }); -} - -function populateArticles(articles) { - return new Promise((resolve, reject) => { - Promise.all( - articles.map(async article => { - if (article.body.indexOf("<file>") !== -1) { - const txtPath = article.body.replace("<file>", ""); - const textValue = await fetchtext(`${article.path}/${txtPath}`); - article.body = textValue; - article.date = article.date ? new Date(article.date) : undefined; - } - return article; - }) - ) - .then(completeArticles => resolve(completeArticles.sort((a, b) => b.date - a.date))) - .catch(e => reject(e)); - }); +function loadArticles(category) { + return fetch_json_or_error_text(`/articles/${category}`) } module.exports = { loadArticles, getArticleBody, getArticleDate, - populateArticles, }; -},{"./fetch":5}],5:[function(require,module,exports){ +},{"./fetch":6}],6:[function(require,module,exports){ "use strict"; function fetchjson(url) { @@ -190,12 +276,25 @@ function fetchtext(url) { }); } +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, }; -},{}],6:[function(require,module,exports){ +},{}],7:[function(require,module,exports){ "use strict"; class WebPage { @@ -205,12 +304,11 @@ class WebPage { } module.exports = WebPage; -},{}],7:[function(require,module,exports){ +},{}],8:[function(require,module,exports){ "use strict"; -const { articles_url } = require("../../../../constants"); -const { loadArticles, getArticleBody, getArticleDate } = require("../../../lib/article-utils"); -const objectHtmlRenderer = require("object-to-html-renderer") +const { images_url } = require("../../../../../admin-frontend/src/constants"); +const { getArticleBody } = require("../../../lib/article-utils"); class SoftwareArticle { constructor(props) { @@ -218,7 +316,7 @@ class SoftwareArticle { } render() { - const { title, body, subtitle, images, path, details = [], releases } = this.props; + const { title, body, subtitle, images, details = [] } = this.props; return { tag: "article", @@ -236,7 +334,7 @@ class SoftwareArticle { tag: "div", class: "software-image", contents: [ { - tag: "img", src: `${path}/images/${images[0]}` + tag: "img", src: `${images_url}/${images[0]}` } ] }, @@ -252,9 +350,9 @@ class SoftwareArticle { contents: getArticleBody(body), property: "description", }, - { + details.length > 0 && { tag: "div", - class: "software-technical", + class: "article-details", contents: [ { tag: "h2", @@ -262,7 +360,7 @@ class SoftwareArticle { }, { tag: "ul", - class: "technical-details", + class: "details-list", contents: details.map(detail => { return { tag: "li", @@ -277,52 +375,6 @@ class SoftwareArticle { }; }), }, - releases && { - tag: "h2", - contents: "Releases", - }, - releases && { - tag: "ul", - class: "releases", - contents: [ - { - tag: "li", - class: "detail", - contents: [ - { - tag: "label", - class: "label", - contents: "Plateforme", - }, - { - tag: "label", - class: "label", - contents: "Téléchargement", - }, - ], - }, - ].concat( - releases.map(rel => { - return { - tag: "li", - class: "release detail", - contents: [ - { - tag: "label", - contents: rel.platform, - }, - { - tag: "a", - download: rel.download, - href: `${path}/release/${rel.download}`, - contents: rel.download, - property: "url", - }, - ], - }; - }) - ), - }, ], }, ], @@ -330,6 +382,13 @@ class SoftwareArticle { } } +module.exports = SoftwareArticle; +},{"../../../../../admin-frontend/src/constants":1,"../../../lib/article-utils":5}],9:[function(require,module,exports){ +"use strict"; + +const { loadArticles } = require("../../../lib/article-utils"); +const SoftwareArticle = require("./software-article"); + class SoftwareArticles { constructor(props) { this.props = props; @@ -341,13 +400,11 @@ class SoftwareArticles { } loadArticles() { - loadArticles(`${articles_url}software`) - .then(articles => { - this.state.articles = articles; - this.refresh(); - this.fixScroll(); - }) - .catch(e => console.log(e)); + loadArticles("software").then(articles => { + this.state.articles = articles; + this.refresh(); + this.fixScroll(); + }).catch(e => console.log(e)) } renderPlaceholder() { @@ -363,7 +420,7 @@ class SoftwareArticles { } refresh() { - objectHtmlRenderer.subRender(this.render(), document.getElementById(this.id), { + obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace", }); } @@ -394,7 +451,7 @@ class SoftwareArticles { module.exports = SoftwareArticles; -},{"../../../../constants":2,"../../../lib/article-utils":4,"object-to-html-renderer":3}],8:[function(require,module,exports){ +},{"../../../lib/article-utils":5,"./software-article":8}],10:[function(require,module,exports){ "use strict"; const { images_url } = require("../../../constants"); @@ -443,7 +500,7 @@ class SoftwareDevelopment extends WebPage { module.exports = SoftwareDevelopment; -},{"../../../constants":2,"../../lib/web-page":6,"./components/software-articles":7}],9:[function(require,module,exports){ +},{"../../../constants":3,"../../lib/web-page":7,"./components/software-articles":9}],11:[function(require,module,exports){ "use strict"; "use strict"; @@ -451,19 +508,20 @@ const runPage = require("../../run-page"); const SoftwareDevelopment = require("./software-development"); runPage(SoftwareDevelopment); -},{"../../run-page":10,"./software-development":8}],10:[function(require,module,exports){ +},{"../../run-page":12,"./software-development":10}],12:[function(require,module,exports){ "use strict"; -const objectHtmlRenderer = require("object-to-html-renderer") +const renderer = require("object-to-html-renderer") const Template = require("./template/template"); module.exports = function runPage(PageComponent) { const template = new Template({ page: new PageComponent() }); - objectHtmlRenderer.setRenderCycleRoot(template); - objectHtmlRenderer.renderCycle(); + renderer.register("obj2htm") + obj2htm.setRenderCycleRoot(template); + obj2htm.renderCycle(); }; -},{"./template/template":12,"object-to-html-renderer":3}],11:[function(require,module,exports){ +},{"./template/template":14,"object-to-html-renderer":4}],13:[function(require,module,exports){ "use strict"; const { images_url } = require("../../../constants"); @@ -573,7 +631,7 @@ class NavBar { module.exports = NavBar; -},{"../../../constants":2}],12:[function(require,module,exports){ +},{"../../../constants":3}],14:[function(require,module,exports){ "use strict"; const { in_construction } = require("../../config"); @@ -682,4 +740,4 @@ class Template { module.exports = Template; -},{"../../config":1,"../../constants":2,"./components/navbar":11}]},{},[9]); +},{"../../config":2,"../../constants":3,"./components/navbar":13}]},{},[11]); diff --git a/public/style/style.css b/public/style/style.css index 0a9f9e41827bd5955f356fd37e6503b50c7c7f1f..f916d2eefdbb54e6b50f2a55d76fdb66e33af4d4 100644 --- a/public/style/style.css +++ b/public/style/style.css @@ -375,6 +375,53 @@ 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; + 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 ul { + display: flex; + flex-wrap: wrap; + gap: 10px; +} +main #page-container .article-details .detail { + display: grid; + grid-template-columns: 1fr auto; + font-size: 12px; + border-bottom: 1px solid #d4d9dd; + padding: 5px 0; +} +main #page-container .article-details .detail label { + font-weight: bold; + color: #6b7880; +} +main #page-container .article-details .detail .label { + color: #aabbc8; +} +main #page-container .article-details .detail ul { + display: flex; + flex-wrap: wrap; + gap: 10px; +} main #page-container #home-page { display: flex; flex-direction: column; @@ -1017,48 +1064,6 @@ main #page-container #games-page .game-articles article.game-article .image-caro grid-row: 3/span 4; height: 100%; } -main #page-container #games-page .game-articles article.game-article .game-team { - grid-column: 1/span 2; -} -main #page-container #games-page .game-articles article.game-article .game-team h2 { - color: #6b7880; - padding: 10px 20px; - font-style: italic; - font-size: 20px; - margin: 0; -} -main #page-container #games-page .game-articles article.game-article .game-team .team-members { - display: flex; - flex-direction: column; -} -main #page-container #games-page .game-articles article.game-article .game-team .team-members .team-member { - display: grid; - grid-template-columns: 80px 1fr; - grid-template-rows: auto auto 1fr; - margin: 10px; -} -main #page-container #games-page .game-articles article.game-article .game-team .team-members .team-member .team-member-img { - grid-row: 1/span 3; - overflow: hidden; -} -main #page-container #games-page .game-articles article.game-article .game-team .team-members .team-member .team-member-img img { - width: 100%; - padding: 0 10px 0 0; -} -main #page-container #games-page .game-articles article.game-article .game-team .team-members .team-member .team-member-title { - margin: 0 0 5px; - color: #6b7880; -} -main #page-container #games-page .game-articles article.game-article .game-team .team-members .team-member .team-member-subtitle { - margin: 0 10px; - font-size: 14px; - color: #96a5ae; - font-style: italic; -} -main #page-container #games-page .game-articles article.game-article .game-team .team-members .team-member .team-member-body { - margin: 10px 10px 20px 20px; - text-align: justify; -} main #page-container #games-page .game-articles article.placeholder { height: 400px; } @@ -1088,18 +1093,6 @@ main #page-container #games-page .game-articles article.placeholder * { height: 400px; margin: 0 -20px; } - main #page-container #games-page .game-articles article.game-article .game-team { - grid-column: 1; - } - main #page-container #games-page .game-articles article.game-article .game-team .team-members .team-member { - grid-template-columns: 70px 1fr; - } - main #page-container #games-page .game-articles article.game-article .game-team .team-members .team-member .team-member-img { - grid-row: 1/span 2; - } - main #page-container #games-page .game-articles article.game-article .game-team .team-members .team-member .team-member-body { - grid-column: 1/span 2; - } } main #page-container #software-page .software-articles { margin: 20px auto 50px; @@ -1144,56 +1137,6 @@ main #page-container #software-page .software-articles article.software-article max-width: 100%; max-height: 400px; } -main #page-container #software-page .software-articles article.software-article .software-technical { - grid-column: 1/span 2; -} -main #page-container #software-page .software-articles article.software-article .software-technical h2 { - color: #6b7880; - margin: 0 10px; - padding: 10px 0 0; - font-size: 16px; -} -main #page-container #software-page .software-articles article.software-article .software-technical ul.technical-details { - margin: 10px; -} -main #page-container #software-page .software-articles article.software-article .software-technical ul.technical-details .detail { - display: grid; - grid-template-columns: 1fr auto; - font-size: 12px; - border-bottom: 1px solid #d4d9dd; - padding: 5px 0; -} -main #page-container #software-page .software-articles article.software-article .software-technical ul.technical-details .detail label { - font-weight: bold; - color: #6b7880; -} -main #page-container #software-page .software-articles article.software-article .software-technical ul.technical-details .detail ul { - display: flex; - flex-wrap: wrap; - gap: 10px; -} -main #page-container #software-page .software-articles article.software-article .software-technical ul.releases { - margin: 10px; -} -main #page-container #software-page .software-articles article.software-article .software-technical .detail { - display: grid; - grid-template-columns: 1fr auto; - font-size: 12px; - border-bottom: 1px solid #d4d9dd; - padding: 5px 0; -} -main #page-container #software-page .software-articles article.software-article .software-technical .detail label { - font-weight: bold; - color: #6b7880; -} -main #page-container #software-page .software-articles article.software-article .software-technical .detail .label { - color: #aabbc8; -} -main #page-container #software-page .software-articles article.software-article .software-technical .detail ul { - display: flex; - flex-wrap: wrap; - gap: 10px; -} @media screen and (max-width: 900px) { main #page-container #software-page .software-articles article.software-article .software-title { display: flex; diff --git a/update-prod.sh b/update-prod.sh new file mode 100755 index 0000000000000000000000000000000000000000..6fbee9015a165f5bbd48359e8662741369856360 --- /dev/null +++ b/update-prod.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +git checkout . +git pull origin master + +cd admin-frontend +npm install +npm run build + +cd ../website +npm install +npm run build + +cd .. +docker-compose up --build diff --git a/website/config.js b/website/config.js index 17760e7c9b4d2fc31a156bfc72a559efb9d56b74..864f2cb4454ddfaf32023227a992e6a0e451e089 100644 --- a/website/config.js +++ b/website/config.js @@ -1,12 +1,6 @@ -function getServerUrl() { - return `${location.origin}${location.origin.charAt(location.origin.length - 1) !== "/" ? "/" : "" - }`; -} - module.exports = { - getServerUrl, build: { - protected_dirs: ["assets", "style", "articles"], + protected_dirs: ["assets", "style", "views", "standard"], default_meta_keys: ["title", "description", "image", "open_graph", "json_ld"], }, }; diff --git a/website/constants.js b/website/constants.js index 32116ec9721a5244b316f336e800b67494801def..0efcb1efcf832763c34102a6b709ab926ef1e3fc 100644 --- a/website/constants.js +++ b/website/constants.js @@ -1,6 +1,3 @@ -const { getServerUrl } = require("./config"); - module.exports = { - images_url: `${getServerUrl()}assets/images/`, - articles_url: `${getServerUrl()}articles/`, + images_url: `/assets/images/`, }; diff --git a/website/package-lock.json b/website/package-lock.json index 0ea1b6034a7c3ba8351c96ff3a00b06c5e722c8d..fec3b10e40f7bae51edfd11003fbb4f5f4f6a70a 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.3", "license": "MIT", "dependencies": { - "object-to-html-renderer": "^1.0.0" + "object-to-html-renderer": "^1.1.1" }, "devDependencies": { "sass": "^1.32.0", @@ -1283,9 +1283,9 @@ } }, "node_modules/object-to-html-renderer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/object-to-html-renderer/-/object-to-html-renderer-1.0.0.tgz", - "integrity": "sha512-ZB+jYQ0cjH+I4YWx6UcifaOP26ZY/KEdIzYZc9h96L/hGWbBx/aSBSnR/rn30+Y/0SaR/JNfLZiBhKorJNJ7Rg==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object-to-html-renderer/-/object-to-html-renderer-1.1.3.tgz", + "integrity": "sha512-OWZd0lRBOQylycJEuFf9CfeYEOsylU5CUf44yFWN6JEE3MpVts1nSwLCIQpUCcASwHJ0qa33DpI3eNLwcXiDWA==" }, "node_modules/object.assign": { "version": "4.1.2", @@ -3027,9 +3027,9 @@ "dev": true }, "object-to-html-renderer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/object-to-html-renderer/-/object-to-html-renderer-1.0.0.tgz", - "integrity": "sha512-ZB+jYQ0cjH+I4YWx6UcifaOP26ZY/KEdIzYZc9h96L/hGWbBx/aSBSnR/rn30+Y/0SaR/JNfLZiBhKorJNJ7Rg==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object-to-html-renderer/-/object-to-html-renderer-1.1.3.tgz", + "integrity": "sha512-OWZd0lRBOQylycJEuFf9CfeYEOsylU5CUf44yFWN6JEE3MpVts1nSwLCIQpUCcASwHJ0qa33DpI3eNLwcXiDWA==" }, "object.assign": { "version": "4.1.2", diff --git a/website/package.json b/website/package.json index be165d06826295c9be44ecb2ffbde4fecb44e04e..23ed5167c7ad1eaa43cd6e0530f8f3ae84a9a049 100644 --- a/website/package.json +++ b/website/package.json @@ -14,7 +14,7 @@ "license": "MIT", "homepage": "https://gitlab.com/peter_rabbit/kuadrado-website#readme", "dependencies": { - "object-to-html-renderer": "^1.0.0" + "object-to-html-renderer": "^1.1.1" }, "devDependencies": { "sass": "^1.32.0", diff --git a/website/src/generic-components/image-carousel.js b/website/src/generic-components/image-carousel.js index e3e03521715d000da891e561b1d823740fa380d3..8c850560d52e9ebf7be4f00d6ecb07c6fd698b14 100644 --- a/website/src/generic-components/image-carousel.js +++ b/website/src/generic-components/image-carousel.js @@ -1,7 +1,5 @@ "use strict"; -const objectHtmlRenderer = require("object-to-html-renderer") - class ImageCarousel { constructor(props) { this.props = props; @@ -29,7 +27,7 @@ class ImageCarousel { } refreshImage() { - objectHtmlRenderer.subRender(this.render(), document.getElementById(this.id), { + obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace", }); } diff --git a/website/src/home-page-components/news-articles.js b/website/src/home-page-components/news-articles.js index 69ee7acd0241457a01931e1eaa78f605b4c1313d..c2065da1a137ce103363dc38388bc20ab664705a 100644 --- a/website/src/home-page-components/news-articles.js +++ b/website/src/home-page-components/news-articles.js @@ -1,7 +1,6 @@ "use strict"; const { articles_url } = require("../../constants"); -const objectHtmlRenderer = require("object-to-html-renderer"); const ImageCarousel = require("../generic-components/image-carousel"); const { loadArticles, getArticleDate, getArticleBody } = require("../lib/article-utils"); @@ -25,7 +24,7 @@ class NewsArticles { } refresh() { - objectHtmlRenderer.subRender(this.render(), document.getElementById(this.id), { + obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace", }); } @@ -81,9 +80,9 @@ class NewsArticles { ], }, articleData.images && - new ImageCarousel({ - images: articleData.images.map(img => `${articleData.path}/images/${img}`), - }).render(), + new ImageCarousel({ + images: articleData.images.map(img => `${articleData.path}/images/${img}`), + }).render(), ], }; } @@ -110,8 +109,8 @@ class NewsArticles { ? showArticleIndex - 1 : 0 : showArticleIndex + 1 <= articles.length - 1 - ? showArticleIndex + 1 - : articles.length - 1; + ? showArticleIndex + 1 + : articles.length - 1; this.state.showArticleIndex = showArticleIndex; this.refresh(); } @@ -127,26 +126,26 @@ class NewsArticles { contents: articles.length > 0 ? [ - this.renderArticle(articles[showArticleIndex]), - { - tag: "div", - class: "prev-next-buttons", - contents: [ - { - tag: "button", - class: `prev-btn ${!showPrev ? "disabled" : "active"}`, - contents: "Précédent", - onclick: this.handleChangeArticle.bind(this, "prev"), - }, - { - tag: "button", - class: `next-btn ${!showNext ? "disabled" : "active"}`, - contents: "Suivant", - onclick: this.handleChangeArticle.bind(this, "next"), - }, - ], - }, - ] + this.renderArticle(articles[showArticleIndex]), + { + tag: "div", + class: "prev-next-buttons", + contents: [ + { + tag: "button", + class: `prev-btn ${!showPrev ? "disabled" : "active"}`, + contents: "Précédent", + onclick: this.handleChangeArticle.bind(this, "prev"), + }, + { + tag: "button", + class: `next-btn ${!showNext ? "disabled" : "active"}`, + contents: "Suivant", + onclick: this.handleChangeArticle.bind(this, "next"), + }, + ], + }, + ] : [this.renderArticlePlaceholder()], }; } diff --git a/website/src/lib/article-utils.js b/website/src/lib/article-utils.js index 2d182cab9eaecdf620cb1a751e44462f9b3d6233..3e00a88258938157bad464a9949e59a624e674b6 100644 --- a/website/src/lib/article-utils.js +++ b/website/src/lib/article-utils.js @@ -1,6 +1,6 @@ "use strict"; -const { fetchjson, fetchtext } = require("./fetch"); +const { fetch_json_or_error_text } = require("./fetch"); function getArticleBody(text) { return text.replaceAll("\n", "<br/>"); @@ -10,52 +10,12 @@ function getArticleDate(date) { return `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`; } -function loadArticles(dir_url) { - return new Promise((resolve, reject) => { - fetchjson(`${dir_url}/index.json`) - .then(json => { - Promise.all( - json.articles.map(async artDir => { - const artPath = `${artDir}/${artDir}.json`; - const art = await fetchjson(`${dir_url}/${artPath}`); - const tmpSplit = artPath.split("/"); - tmpSplit.pop(); - const absArtPath = `${dir_url}/${tmpSplit.join("/")}`; - return Object.assign(art, { path: absArtPath }); - }) - ) - .then(articles => { - populateArticles(articles) - .then(completeArticles => resolve(completeArticles)) - .catch(e => reject(e)); - }) - .catch(e => reject(e)); - }) - .catch(e => console.log(e)); - }); -} - -function populateArticles(articles) { - return new Promise((resolve, reject) => { - Promise.all( - articles.map(async article => { - if (article.body.indexOf("<file>") !== -1) { - const txtPath = article.body.replace("<file>", ""); - const textValue = await fetchtext(`${article.path}/${txtPath}`); - article.body = textValue; - article.date = article.date ? new Date(article.date) : undefined; - } - return article; - }) - ) - .then(completeArticles => resolve(completeArticles.sort((a, b) => b.date - a.date))) - .catch(e => reject(e)); - }); +function loadArticles(category) { + return fetch_json_or_error_text(`/articles/${category}`) } module.exports = { loadArticles, getArticleBody, getArticleDate, - populateArticles, }; diff --git a/website/src/lib/fetch.js b/website/src/lib/fetch.js index b734de11918a09b30ee454d33860512d741117fa..9f98377afd2d0a7936f8440c708ea618e0bdacd0 100644 --- a/website/src/lib/fetch.js +++ b/website/src/lib/fetch.js @@ -18,7 +18,20 @@ function fetchtext(url) { }); } +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, }; diff --git a/website/src/pages/games/components/game-article.js b/website/src/pages/games/components/game-article.js index 18d663f5fa4c71d18ad106e284fd7e3635bab746..294365d70ee4356e3f2de114abcad2f13aa21203 100644 --- a/website/src/pages/games/components/game-article.js +++ b/website/src/pages/games/components/game-article.js @@ -1,56 +1,9 @@ "use strict"; +const { images_url } = require("../../../../../admin-frontend/src/constants"); const ImageCarousel = require("../../../generic-components/image-carousel"); const { getArticleBody } = require("../../../lib/article-utils"); -class TeamMember { - constructor(props) { - this.props = props; - } - - render() { - const { title, subtitle, body, images, path } = this.props; - return { - tag: "div", - class: "team-member", - typeof: "Person", - property: "author", - contents: [ - { - tag: "div", - class: "team-member-img", - contents: [ - { - tag: "img", - alt: `ìmage team member ${title}`, - src: images.map(im => `${path}/images/${im}`)[0], - property: "image", - }, - ], - }, - { - tag: "h3", - class: "team-member-title", - contents: title, - property: "name", - }, - { - tag: "strong", - class: "team-member-subtitle", - contents: subtitle, - property: "jobTitle", - }, - { - tag: "p", - class: "team-member-body", - contents: getArticleBody(body), - property: "description", - }, - ], - }; - } -} - class GameArticle { constructor(props) { this.props = props; @@ -59,13 +12,10 @@ class GameArticle { render() { const { title, - tags, - body, subtitle, + body, images, - path, - team_subarticles, - image_banner, + details, } = this.props; return { tag: "article", @@ -83,16 +33,9 @@ class GameArticle { tag: "div", class: "game-banner", contents: [ - { tag: "img", class: "pixelated", src: `${path}/images/${image_banner}` }, + { tag: "img", class: "pixelated", src: `${images_url}/${images[0]}` }, ], }, - { - tag: "div", - class: "game-tags", - contents: tags.map(tag => { - return { tag: "span", contents: tag, property: "about" }; - }), - }, { tag: "h3", class: "game-subtitle", @@ -105,21 +48,31 @@ class GameArticle { property: "description", contents: getArticleBody(body), }, - new ImageCarousel({ images: images.map(img => `${path}/images/${img}`) }).render(), - { + new ImageCarousel({ images: images.map(img => `${images_url}/${img}`) }).render(), + details.length > 0 && { tag: "div", - class: "game-team", + class: "article-details", contents: [ { tag: "h2", - contents: "L'équipe", + contents: "Details", }, { - tag: "div", - class: "team-members", - contents: team_subarticles.map(tsa => - new TeamMember({ ...tsa }).render() - ), + tag: "ul", + class: "details-list", + contents: details.map(detail => { + return { + tag: "li", + class: "detail", + contents: [ + { tag: "label", contents: detail.label }, + { + tag: "div", + contents: detail.value + }, + ], + }; + }), }, ], }, diff --git a/website/src/pages/games/components/game-articles.js b/website/src/pages/games/components/game-articles.js index 0f377e9f9f76e5be279cc0f274b314573bbb0081..f50bc19951d94cdcdac784d1ad18a6c156c8b683 100644 --- a/website/src/pages/games/components/game-articles.js +++ b/website/src/pages/games/components/game-articles.js @@ -1,8 +1,6 @@ "use strict"; -const { articles_url } = require("../../../../constants"); -const { loadArticles, populateArticles } = require("../../../lib/article-utils"); -const objectHtmlRenderer = require("object-to-html-renderer") +const { loadArticles } = require("../../../lib/article-utils"); const GameArticle = require("./game-article"); class GameArticles { @@ -16,21 +14,10 @@ class GameArticles { } loadArticles() { - loadArticles(`${articles_url}games`) + loadArticles("games") .then(articles => { - Promise.all( - articles.map(async a => { - if (a.team_subarticles) { - a.team_subarticles = await populateArticles( - a.team_subarticles.map(sa => Object.assign(sa, { path: a.path })) - ); - } - return a; - }) - ).then(completeArticles => { - this.state.articles = completeArticles; - this.refresh(); - }); + this.state.articles = articles; + this.refresh(); }) .catch(e => console.log(e)); } @@ -44,7 +31,7 @@ class GameArticles { } refresh() { - objectHtmlRenderer.subRender(this.render(), document.getElementById(this.id), { + obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace", }); } diff --git a/website/src/pages/games/games.scss b/website/src/pages/games/games.scss index cdabdccf819801eb9a802366e6586f8197759902..9082c1cadda4c9e07db222e883862b3ea2733221 100644 --- a/website/src/pages/games/games.scss +++ b/website/src/pages/games/games.scss @@ -59,48 +59,6 @@ grid-row: 3 / span 4; height: 100%; } - .game-team { - grid-column: 1 / span 2; - h2 { - color: $medium_grey; - padding: 10px 20px; - font-style: italic; - font-size: 20px; - margin: 0; - } - .team-members { - display: flex; - flex-direction: column; - .team-member { - display: grid; - grid-template-columns: 80px 1fr; - grid-template-rows: auto auto 1fr; - margin: 10px; - .team-member-img { - grid-row: 1 / span 3; - overflow: hidden; - img { - width: 100%; - padding: 0 10px 0 0; - } - } - .team-member-title { - margin: 0 0 5px; - color: $medium_grey; - } - .team-member-subtitle { - margin: 0 10px; - font-size: 14px; - color: $light_1; - font-style: italic; - } - .team-member-body { - margin: 10px 10px 20px 20px; - text-align: justify; - } - } - } - } } &.placeholder { * { @@ -130,20 +88,6 @@ height: 400px; margin: 0 -20px; } - .game-team { - grid-column: 1; - .team-members { - .team-member { - grid-template-columns: 70px 1fr; - .team-member-img { - grid-row: 1 / span 2; - } - .team-member-body { - grid-column: 1 / span 2; - } - } - } - } } } } diff --git a/website/src/pages/software-development/components/software-article.js b/website/src/pages/software-development/components/software-article.js new file mode 100644 index 0000000000000000000000000000000000000000..ae5ad4f689ec7accf942fd31aa7314428c8228b5 --- /dev/null +++ b/website/src/pages/software-development/components/software-article.js @@ -0,0 +1,78 @@ +"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", + contents: detail.value + }, + ], + }; + }), + }, + ], + }, + ], + }; + } +} + +module.exports = SoftwareArticle; \ No newline at end of file diff --git a/website/src/pages/software-development/components/software-articles.js b/website/src/pages/software-development/components/software-articles.js index 5be340e2f580bb12593b63ceb0581393f8c79d5d..acc5199a8c7041e90027bc13b1af4903db82d8cd 100644 --- a/website/src/pages/software-development/components/software-articles.js +++ b/website/src/pages/software-development/components/software-articles.js @@ -1,126 +1,7 @@ "use strict"; -const { articles_url } = require("../../../../constants"); -const { loadArticles, getArticleBody, getArticleDate } = require("../../../lib/article-utils"); -const objectHtmlRenderer = require("object-to-html-renderer") - -class SoftwareArticle { - constructor(props) { - this.props = props; - } - - render() { - const { title, body, subtitle, images, path, details = [], releases } = 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: `${path}/images/${images[0]}` - } - ] - }, - { - tag: "h3", - class: "software-subtitle", - contents: subtitle, - property: "alternativeHeadline", - }, - { - tag: "div", - class: "software-description", - contents: getArticleBody(body), - property: "description", - }, - { - tag: "div", - class: "software-technical", - contents: [ - { - tag: "h2", - contents: "Details", - }, - { - tag: "ul", - class: "technical-details", - contents: details.map(detail => { - return { - tag: "li", - class: "detail", - contents: [ - { tag: "label", contents: detail.label }, - { - tag: "div", - contents: detail.value - }, - ], - }; - }), - }, - releases && { - tag: "h2", - contents: "Releases", - }, - releases && { - tag: "ul", - class: "releases", - contents: [ - { - tag: "li", - class: "detail", - contents: [ - { - tag: "label", - class: "label", - contents: "Plateforme", - }, - { - tag: "label", - class: "label", - contents: "Téléchargement", - }, - ], - }, - ].concat( - releases.map(rel => { - return { - tag: "li", - class: "release detail", - contents: [ - { - tag: "label", - contents: rel.platform, - }, - { - tag: "a", - download: rel.download, - href: `${path}/release/${rel.download}`, - contents: rel.download, - property: "url", - }, - ], - }; - }) - ), - }, - ], - }, - ], - }; - } -} +const { loadArticles } = require("../../../lib/article-utils"); +const SoftwareArticle = require("./software-article"); class SoftwareArticles { constructor(props) { @@ -133,13 +14,11 @@ class SoftwareArticles { } loadArticles() { - loadArticles(`${articles_url}software`) - .then(articles => { - this.state.articles = articles; - this.refresh(); - this.fixScroll(); - }) - .catch(e => console.log(e)); + loadArticles("software").then(articles => { + this.state.articles = articles; + this.refresh(); + this.fixScroll(); + }).catch(e => console.log(e)) } renderPlaceholder() { @@ -155,7 +34,7 @@ class SoftwareArticles { } refresh() { - objectHtmlRenderer.subRender(this.render(), document.getElementById(this.id), { + obj2htm.subRender(this.render(), document.getElementById(this.id), { mode: "replace", }); } diff --git a/website/src/pages/software-development/software-development.scss b/website/src/pages/software-development/software-development.scss index c6f66707f8c865554a51448ed027a5b72fc50ff6..5bb087a404f73952c74c52f5de90ac79b30ae23a 100644 --- a/website/src/pages/software-development/software-development.scss +++ b/website/src/pages/software-development/software-development.scss @@ -1,132 +1,80 @@ #software-page { - .software-articles { - margin: 20px auto 50px; - article.software-article { - display: grid; - grid-template-columns: auto 1fr; - margin: 0 0 50px; - gap: 10px 30px; + .software-articles { + margin: 20px auto 50px; + article.software-article { + display: grid; + grid-template-columns: auto 1fr; + margin: 0 0 50px; + gap: 10px 30px; - .software-title { - grid-column: 2; - color: $light_2; - margin: 0; - padding: 10px; - } + .software-title { + grid-column: 2; + color: $light_2; + margin: 0; + padding: 10px; + } - .software-subtitle { - grid-column: 2; - margin: 10px; - color: $medium_grey; - } + .software-subtitle { + grid-column: 2; + margin: 10px; + color: $medium_grey; + } - .software-description { - grid-column: 2; - text-align: justify; - margin: 10px; - } + .software-description { + grid-column: 2; + text-align: justify; + margin: 10px; + } - .software-image { - padding: 20px; - background-color: black; - grid-column: 1; - grid-row: 1 / span 3; - @include flex-center; - width: 200px; - height: 200px; - overflow: hidden; - border-radius: 100%; - img { - max-width: 100%; - max-height: 400px; - } - } + .software-image { + padding: 20px; + background-color: black; + grid-column: 1; + grid-row: 1 / span 3; + @include flex-center; + width: 200px; + height: 200px; + overflow: hidden; + border-radius: 100%; + img { + max-width: 100%; + max-height: 400px; + } + } - .software-technical { - grid-column: 1 / span 2; - h2 { - color: $medium_grey; - margin: 0 10px; - padding: 10px 0 0; - font-size: 16px; - } - ul.technical-details { - margin: 10px; - .detail { - display: grid; - grid-template-columns: 1fr auto; - font-size: 12px; - border-bottom: 1px solid $light_0; - - padding: 5px 0; - label { - font-weight: bold; - color: $medium_grey; - } - ul { - display: flex; - flex-wrap: wrap; - gap: 10px; - } - } - } - ul.releases { - margin: 10px; - } - .detail { - display: grid; - grid-template-columns: 1fr auto; - font-size: 12px; - border-bottom: 1px solid $light_0; - - padding: 5px 0; - label { - font-weight: bold; - color: $medium_grey; - } - .label { - color: $light_2; - } - ul { - display: flex; - flex-wrap: wrap; - gap: 10px; - } - } - } - @media screen and (max-width: $screen_l) { - .software-title { - display: flex; - align-items: center; - } - .software-subtitle, - .software-description { - grid-column: 1 / span 2; - } - .software-image { - width: 100px; - height: 100px; - grid-row: 1; - } - } - } - article.placeholder { - display: flex; - flex-direction: column; - gap: 10px; - margin: 30px; - * { - background-color: $light_0; - } - .title { - height: 60px; - } - .body { - height: 400px; - } - .details { - height: 200px; - } - } - } + @media screen and (max-width: $screen_l) { + .software-title { + display: flex; + align-items: center; + } + .software-subtitle, + .software-description { + grid-column: 1 / span 2; + } + .software-image { + width: 100px; + height: 100px; + grid-row: 1; + } + } + } + article.placeholder { + display: flex; + flex-direction: column; + gap: 10px; + margin: 30px; + * { + background-color: $light_0; + } + .title { + height: 60px; + } + .body { + height: 400px; + } + .details { + height: 200px; + } + } + } } diff --git a/website/src/run-page.js b/website/src/run-page.js index d0b0af0a321c180c763502ce5cefa5e750a66485..62a540225157626043a625169f25d76963901a66 100644 --- a/website/src/run-page.js +++ b/website/src/run-page.js @@ -1,10 +1,11 @@ "use strict"; -const objectHtmlRenderer = require("object-to-html-renderer") +const renderer = require("object-to-html-renderer") const Template = require("./template/template"); module.exports = function runPage(PageComponent) { const template = new Template({ page: new PageComponent() }); - objectHtmlRenderer.setRenderCycleRoot(template); - objectHtmlRenderer.renderCycle(); + renderer.register("obj2htm") + obj2htm.setRenderCycleRoot(template); + obj2htm.renderCycle(); }; diff --git a/website/src/style.scss b/website/src/style.scss index 5095b552e321162c058c0d998b7c33edb913ccc3..4d735cadfe926d92f8b9580d6683eeef7f960ff1 100644 --- a/website/src/style.scss +++ b/website/src/style.scss @@ -1,415 +1,465 @@ @import "./theme.scss"; body { - * { - box-sizing: border-box; - color: $dark_1; - line-height: 1.3em; - } - font-family: Arial, Helvetica, sans-serif; - margin: 0; - ul { - margin: 0; - padding: 0; - list-style-type: none; - } - a { - color: $blue_2; - text-decoration: none; - &:hover { - color: $blue_3; - } - } + * { + box-sizing: border-box; + color: $dark_1; + line-height: 1.3em; + } + font-family: Arial, Helvetica, sans-serif; + margin: 0; + ul { + margin: 0; + padding: 0; + list-style-type: none; + } + a { + color: $blue_2; + text-decoration: none; + &:hover { + color: $blue_3; + } + } - blue { - color: $blue_2; - } - red { - color: $red_1; - } - green { - color: $kaki; - } - emoji { - font-style: initial; - font-size: 25px; - } + blue { + color: $blue_2; + } + red { + color: $red_1; + } + green { + color: $kaki; + } + emoji { + font-style: initial; + font-size: 25px; + } - .bg-blue { - background-color: $blue_2; - color: white; - } + .bg-blue { + background-color: $blue_2; + color: white; + } - .bg-dark { - background-color: $dark_2; - color: $light_2; - } + .bg-dark { + background-color: $dark_2; + color: $light_2; + } - #seo-title { - visibility: hidden; - } + #seo-title { + visibility: hidden; + } - img.pixelated { - image-rendering: pixelated; - image-rendering: -moz-crisp-edges; - image-rendering: crisp-edges; - } + 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; + display: flex; + flex-direction: column; + align-items: center; + min-height: 100vh; - .warning-banner { - background: url("/assets/images/wallpaper_warning.svg"); - width: 100%; - height: 40px; - padding: 20px 10%; - @include flex-center; - strong { - font-size: 18px; - color: $blue_1; - } - } + .warning-banner { + background: url("/assets/images/wallpaper_warning.svg"); + width: 100%; + height: 40px; + padding: 20px 10%; + @include flex-center; + strong { + font-size: 18px; + color: $blue_1; + } + } - .image-carousel { - overflow: hidden; - @include flex-center; - background-color: black; - position: relative; - img { - position: absolute; - max-width: 100%; - max-height: 400px; - } - .carousel-bullets { - position: absolute; - bottom: 0; - padding: 20px; - display: flex; - gap: 10px; - .bullet { - cursor: pointer; - width: 8px; - height: 8px; - background-color: $medium_grey; - border-radius: 100%; - box-shadow: 0 0 3px black; - &.active { - background-color: $light_0; - } - } - } - @media screen and (max-width: $screen_l) { - img { - max-height: 100%; - } - .carousel-bullets { - gap: 30px; - .bullet { - width: 12px; - height: 12px; - } - } - } - } + .image-carousel { + overflow: hidden; + @include flex-center; + background-color: black; + position: relative; + img { + position: absolute; + max-width: 100%; + max-height: 400px; + } + .carousel-bullets { + position: absolute; + bottom: 0; + padding: 20px; + display: flex; + gap: 10px; + .bullet { + cursor: pointer; + width: 8px; + height: 8px; + background-color: $medium_grey; + border-radius: 100%; + box-shadow: 0 0 3px black; + &.active { + background-color: $light_0; + } + } + } + @media screen and (max-width: $screen_l) { + img { + max-height: 100%; + } + .carousel-bullets { + gap: 30px; + .bullet { + width: 12px; + height: 12px; + } + } + } + } - header { - width: 100%; - background-color: white; - position: sticky; - position: -webkit-sticky; - top: 0; - z-index: 10; - nav { - display: flex; - align-items: center; - height: $navbar_height; - .home { - margin: 0 10px; - a { - display: flex; - align-items: center; - gap: 10px; - img { - height: 40px; - width: auto; - } - img.logo-text { - width: 120px; - height: auto; - } - } - } - ul { - display: flex; - padding: 0; - margin: 0; - list-style-type: none; - height: 100%; - li { - position: relative; - a { - display: flex; - align-items: center; - height: 100%; - padding: 10px 20px; - color: $light_1; - font-weight: 800; - text-decoration: none; - } + header { + width: 100%; + background-color: white; + position: sticky; + position: -webkit-sticky; + top: 0; + z-index: 10; + nav { + display: flex; + align-items: center; + height: $navbar_height; + .home { + margin: 0 10px; + a { + display: flex; + align-items: center; + gap: 10px; + img { + height: 40px; + width: auto; + } + img.logo-text { + width: 120px; + height: auto; + } + } + } + ul { + display: flex; + padding: 0; + margin: 0; + list-style-type: none; + height: 100%; + li { + position: relative; + a { + display: flex; + align-items: center; + height: 100%; + padding: 10px 20px; + color: $light_1; + font-weight: 800; + text-decoration: none; + } - .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; - } - &.active { - a { - color: $dark_2; - border-bottom: 3px solid; - } - } - &:hover { - a { - color: $dark_2; - } + .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; + } + &.active { + a { + color: $dark_2; + border-bottom: 3px solid; + } + } + &:hover { + a { + color: $dark_2; + } - .submenu { - visibility: unset; - max-height: 1000px; - a { - color: $light_1; - border: none; - } - li { - &:hover { - a { - color: $dark_2; - } - } - } - } - } - } - } - .burger { - display: none; - } - @media screen and (max-width: $screen_s) { - justify-content: space-between; - .burger { - @include flex-center-col; - font-weight: bold; - border: 1px solid; - margin: 0 20px; - cursor: pointer; - border-radius: 100%; - width: 35px; - height: 35px; - color: $dark_3; - font-size: 25px; - } - ul { - display: none; - &.responsive-show { - display: flex; - flex-direction: column; - position: absolute; - right: 0; - max-width: 100vw; - min-width: 50vw; - top: $navbar_height; - background-color: white; - box-shadow: 0 4px 6px 2px #0000000a; - height: unset; - li { - &.active { - a { - border: none; - } - } - .submenu { - display: flex; - visibility: visible; - position: relative; - height: unset; - max-height: unset; - transition: max-height 0.6s; - top: unset; - left: unset; - li { - a { - font-weight: 400; - font-size: 14px; - color: $light_1; - } - } - margin-left: 20px; - } - } - } - } - } - } - } - #page-container { - width: 100%; - flex: 1; - .page-header { - background-image: url("/assets/images/wallpaper_binary.png"); - padding: 50px 0; - h1 { - padding: 15px 40px 0; - font-size: 25px; - color: $blue_2; - margin: 0 auto; - } - p { - color: $blue_3; - * { - color: $blue_3; - } - font-style: italic; - padding: 15px 40px 15px 100px; - margin: 0 auto; - font-size: 18px; - } - .big-logo { - @include flex-center; - gap: 20px; - padding: 20px; - img { - width: 200px; - max-width: 100%; - &.logo-text { - width: 300px; - max-width: 100%; - } - } - } - .logo { - padding-left: 30px; - @include flex-center; - img { - width: 100%; - } - } - @media screen and (max-width: $screen_s) { - h1 { - padding: 15px 20px 0; - } - p { - padding: 20px 20px 30px 40px; - text-align: justify; - } - .big-logo { - flex-direction: column; - } - } - &.logo-left { - .grid-wrapper { - h1 { - width: 100%; - } - display: grid; - grid-template-columns: 120px 1fr; - grid-template-rows: auto 1fr; - .logo { - grid-column: 1; - grid-row: 1; - width: 100%; - } - p { - margin: 0; - grid-column: 1 / span 2; - } - } - @media screen and (max-width: $screen_m) { - .grid-wrapper { - h1 { - padding: 0 20px; - } - .logo { - padding: 0 20px; - } - } - } - } - } - .page-philo { - background-image: url("/assets/images/wallpaper_binary.png"); - padding: 120px 30px; - p { - width: 100%; - max-width: 600px; - font-size: 18px; - color: $light_2; - * { - color: $light_2; - } - text-align: center; - font-style: italic; - font-weight: bold; - } - } - .page-contents-center { - @include page-contents-center; - } - h2.page-section-title { - color: $blue_2; - padding: 20px 0 10px; - @include page-contents-center; - } + .submenu { + visibility: unset; + max-height: 1000px; + a { + color: $light_1; + border: none; + } + li { + &:hover { + a { + color: $dark_2; + } + } + } + } + } + } + } + .burger { + display: none; + } + @media screen and (max-width: $screen_s) { + justify-content: space-between; + .burger { + @include flex-center-col; + font-weight: bold; + border: 1px solid; + margin: 0 20px; + cursor: pointer; + border-radius: 100%; + width: 35px; + height: 35px; + color: $dark_3; + font-size: 25px; + } + ul { + display: none; + &.responsive-show { + display: flex; + flex-direction: column; + position: absolute; + right: 0; + max-width: 100vw; + min-width: 50vw; + top: $navbar_height; + background-color: white; + box-shadow: 0 4px 6px 2px #0000000a; + height: unset; + li { + &.active { + a { + border: none; + } + } + .submenu { + display: flex; + visibility: visible; + position: relative; + height: unset; + max-height: unset; + transition: max-height 0.6s; + top: unset; + left: unset; + li { + a { + font-weight: 400; + font-size: 14px; + color: $light_1; + } + } + margin-left: 20px; + } + } + } + } + } + } + } + #page-container { + width: 100%; + flex: 1; + .page-header { + background-image: url("/assets/images/wallpaper_binary.png"); + padding: 50px 0; + h1 { + padding: 15px 40px 0; + font-size: 25px; + color: $blue_2; + margin: 0 auto; + } + p { + color: $blue_3; + * { + color: $blue_3; + } + font-style: italic; + padding: 15px 40px 15px 100px; + margin: 0 auto; + font-size: 18px; + } + .big-logo { + @include flex-center; + gap: 20px; + padding: 20px; + img { + width: 200px; + max-width: 100%; + &.logo-text { + width: 300px; + max-width: 100%; + } + } + } + .logo { + padding-left: 30px; + @include flex-center; + img { + width: 100%; + } + } + @media screen and (max-width: $screen_s) { + h1 { + padding: 15px 20px 0; + } + p { + padding: 20px 20px 30px 40px; + text-align: justify; + } + .big-logo { + flex-direction: column; + } + } + &.logo-left { + .grid-wrapper { + h1 { + width: 100%; + } + display: grid; + grid-template-columns: 120px 1fr; + grid-template-rows: auto 1fr; + .logo { + grid-column: 1; + grid-row: 1; + width: 100%; + } + p { + margin: 0; + grid-column: 1 / span 2; + } + } + @media screen and (max-width: $screen_m) { + .grid-wrapper { + h1 { + padding: 0 20px; + } + .logo { + padding: 0 20px; + } + } + } + } + } + .page-philo { + background-image: url("/assets/images/wallpaper_binary.png"); + padding: 120px 30px; + p { + width: 100%; + max-width: 600px; + font-size: 18px; + color: $light_2; + * { + color: $light_2; + } + text-align: center; + font-style: italic; + font-weight: bold; + } + } + .page-contents-center { + @include page-contents-center; + } + h2.page-section-title { + color: $blue_2; + padding: 20px 0 10px; + @include page-contents-center; + } - @import "./homepage.scss"; - @import "./pages/education/education.scss"; - @import "./pages/games/games.scss"; - @import "./pages/software-development/software-development.scss"; - } - footer { - @include flex-center-col; - width: 100%; - background-image: url("/assets/images/wallpaper_binary.png"); - padding: 40px 20px; - gap: 20px; - font-size: 12px; - span { - color: $light_1; - text-align: center; - } - .logo { - @include flex-center; - gap: 10px; - img { - width: 35px; - &.text-logo { - width: 100px; - } - } - } - .social { - @include flex-center; - gap: 20px; - a { - background-color: $dark_3; - @include flex-center; - width: 25px; - height: 25px; - font-weight: bold; - font-size: 16px; - border-radius: 100%; - } - } - } + .article-details { + grid-column: 1 / span 2; + h2 { + color: $medium_grey; + margin: 0 10px; + padding: 10px 0 0; + font-size: 16px; + } + ul.details-list { + margin: 10px; + .detail { + display: grid; + grid-template-columns: 1fr auto; + font-size: 12px; + border-bottom: 1px solid $light_0; + + padding: 5px 0; + label { + font-weight: bold; + color: $medium_grey; + } + ul { + display: flex; + flex-wrap: wrap; + gap: 10px; + } + } + } + .detail { + display: grid; + grid-template-columns: 1fr auto; + font-size: 12px; + border-bottom: 1px solid $light_0; + + padding: 5px 0; + label { + font-weight: bold; + color: $medium_grey; + } + .label { + color: $light_2; + } + ul { + display: flex; + flex-wrap: wrap; + gap: 10px; + } + } + } + + @import "./homepage.scss"; + @import "./pages/education/education.scss"; + @import "./pages/games/games.scss"; + @import "./pages/software-development/software-development.scss"; + } + footer { + @include flex-center-col; + width: 100%; + background-image: url("/assets/images/wallpaper_binary.png"); + padding: 40px 20px; + gap: 20px; + font-size: 12px; + span { + color: $light_1; + text-align: center; + } + .logo { + @include flex-center; + gap: 10px; + img { + width: 35px; + &.text-logo { + width: 100px; + } + } + } + .social { + @include flex-center; + gap: 20px; + a { + background-color: $dark_3; + @include flex-center; + width: 25px; + height: 25px; + font-weight: bold; + font-size: 16px; + border-radius: 100%; + } + } + } }