diff --git a/admin-frontend/src/components/create-article-form.js b/admin-frontend/src/components/create-article-form.js index efda81086b643a19155a715e9d8f27fc91324b5f..733e663166a05ba9363c331dbacecf8d4637b923 100644 --- a/admin-frontend/src/components/create-article-form.js +++ b/admin-frontend/src/components/create-article-form.js @@ -1,7 +1,6 @@ "use strict"; const Article = require("../article"); -const { images_url } = require("../constants"); const { fetch_post_article, fetch_article, fetch_update_article } = require("../xhr"); class CreateArticleForm { @@ -23,6 +22,7 @@ class CreateArticleForm { const metadata = Object.assign(this.state.output.metadata, { [field]: e.target.value, }); + this.state.output.metadata = metadata; } @@ -166,22 +166,26 @@ class CreateArticleForm { { tag: "img", style_rules: { minWidth: "100%", minHeight: "100%" }, - src: img ? `${images_url}/${img}` : "", + src: img || "", } ], }, { - tag: "input", - type: "text", - placeholder: "image file name", - value: img, - oninput: e => { + tag: "select", + contents: [ + { tag: "option", value: "", contents: "choose", disabled: true, selected: !this.state.output.images[i] } + ].concat((this.params.files_index.images || []).map(url => { + return { + tag: "option", + value: url, + selected: this.state.output.images[i] && this.state.output.images[i] === url, + contents: url.split("/").reverse()[0], + } + })), + onchange: e => { this.state.output.images[i] = e.target.value; - } - }, - { - tag: "button", contents: "OK", - onclick: this.refresh_images.bind(this) + this.refresh_images(); + }, }, { tag: "button", contents: "DEL", @@ -234,7 +238,7 @@ class CreateArticleForm { return { tag: "img", style_rules: { height: "100px", width: "auto" }, - src: `${images_url}/${img}` + src: img } }) }, diff --git a/admin-frontend/src/components/manage-files-form.js b/admin-frontend/src/components/manage-files-form.js new file mode 100644 index 0000000000000000000000000000000000000000..0358cbdf2df3547afa9fa030e76b3793c231f21c --- /dev/null +++ b/admin-frontend/src/components/manage-files-form.js @@ -0,0 +1,54 @@ +"use strict"; + +const { fetch_post_file } = require("../xhr"); + +class ManageFilesForm { + constructor(params) { + this.params = params; + } + + render_index_view() { + return { + tag: "div", + contents: Object.entries(this.params.files_index).map(entry => { + const [category, urls] = entry; + return { + tag: "div", + contents: [ + { + tag: "h3", + contents: category + } + ].concat(urls.map(url => { + return { tag: "div", contents: url } + })) + } + }) + } + + } + + render() { + return { + tag: "div", + contents: [ + this.render_index_view(), + { + tag: "form", + style_rules: { border: "1px solid black" }, + enctype: "multipart/form-data", + onsubmit: function (e) { + e.preventDefault(); + fetch_post_file(e.target).then(res => console.log(res)).catch(err => console.log(err)); + }, + contents: [ + { tag: "input", name: "file", type: "file", multiple: true }, + { tag: "input", type: "submit" } + ] + } + ] + } + } +} + +module.exports = ManageFilesForm; \ No newline at end of file diff --git a/admin-frontend/src/components/root.js b/admin-frontend/src/components/root.js index d07a98cc3abcadebe21d05c0df51fc38d3a000d9..d14afadd7c3a64c59ca1716010000d49f0efeba0 100644 --- a/admin-frontend/src/components/root.js +++ b/admin-frontend/src/components/root.js @@ -1,13 +1,39 @@ -const { fetch_post_file } = require("../xhr"); +const { fetch_post_file, fetch_static_files_index } = require("../xhr"); const CreateArticleForm = require("./create-article-form"); +const ManageFilesForm = require("./manage-files-form"); const UpdateArticleForm = require("./update-article-form"); class RootComponent { constructor() { this.state = { - selected_tab: "" + selected_tab: "", + loading_index: true, + static_files_index: {} }; + + this.fetch_files_index(); + } + + build_index_object(files_urls) { + return files_urls.reduce((o, url) => { + const split_url = url.split("/"); + const cat = split_url[(split_url[2] === "uploads") ? 3 : 2]; + o[cat] = o[cat] ? [...o[cat], url] : [url]; + return o; + }, {}); + } + + fetch_files_index() { + fetch_static_files_index() + .then(files => + this.state.static_files_index = this.build_index_object(files) + ) + .catch(err => console.log(err)) + .finally(() => { + this.state.loading_index = false; + obj2htm.renderCycle(); + }); } handle_nav_click(e) { @@ -16,11 +42,14 @@ class RootComponent { } render_state() { + const files_index = this.state.static_files_index; switch (this.state.selected_tab) { case "create": - return new CreateArticleForm().render(); + return new CreateArticleForm({ files_index }).render(); case "update": - return new UpdateArticleForm().render(); + return new UpdateArticleForm({ files_index }).render(); + case "files": + return new ManageFilesForm({ files_index }).render() default: return undefined; } @@ -31,19 +60,6 @@ class RootComponent { tag: "main", contents: [ { tag: "h1", contents: "Kuadrado admin panel" }, - { - tag: "form", - style_rules: { border: "1px solid black" }, - enctype: "multipart/form-data", - onsubmit: function (e) { - e.preventDefault(); - fetch_post_file(e.target).then(res => console.log(res)).catch(err => console.log(err)); - }, - contents: [ - { tag: "input", name: "file", type: "file", multiple: true }, - { tag: "input", type: "submit" } - ] - }, { tag: "nav", contents: [ @@ -57,6 +73,11 @@ class RootComponent { class: this.state.selected_tab === "update" ? "selected" : "", onclick: this.handle_nav_click.bind(this), }, + { + tag: "span", contents: "Manage files", tab_name: "files", + class: this.state.selected_tab === "files" ? "selected" : "", + onclick: this.handle_nav_click.bind(this), + }, ], }, this.render_state(), diff --git a/admin-frontend/src/components/update-article-form.js b/admin-frontend/src/components/update-article-form.js index fb79156892f71fd878a439581271f775034c67a5..047fdbd84bd67dad33ff6cf41fc644c78b182970 100644 --- a/admin-frontend/src/components/update-article-form.js +++ b/admin-frontend/src/components/update-article-form.js @@ -5,7 +5,8 @@ const ArticleList = require("./articles-list"); const CreateArticleForm = require("./create-article-form"); class UpdateArticleForm { - constructor() { + constructor(params) { + this.params = params; this.state = { search_article_title: "", article_to_update: {}, @@ -63,7 +64,8 @@ class UpdateArticleForm { on_article_sent: () => { this.reset(); this.articles_list.fetch_list(); - } + }, + ...this.params }).render()] : [] } diff --git a/admin-frontend/src/xhr.js b/admin-frontend/src/xhr.js index 4255cff70527fd3c46e43c5178131b5cec67b38e..4c360f060046fd4344a1dc8c1001d45503351555 100644 --- a/admin-frontend/src/xhr.js +++ b/admin-frontend/src/xhr.js @@ -115,11 +115,23 @@ function fetch_post_file(form) { } else { resolve((await res.json())); } - }) + }).catch(err => reject(err)) }) } +function fetch_static_files_index() { + return new Promise((resolve, reject) => { + fetch("/static-files-index").then(async res => { + if (res.status >= 400 && res.status < 600) { + reject((await res.text())) + } else { + resolve((await res.json())); + } + }).catch(err => reject(err)) + }) +} + module.exports = { fetch_article, @@ -129,4 +141,5 @@ module.exports = { fetch_delete_article, fetch_all_articles, fetch_post_file, + fetch_static_files_index, } \ No newline at end of file diff --git a/mongo/scripts/migration.js b/mongo/scripts/migration.js index 3db6e73551411471d9e72bae3e3865205c9becf9..5948c3c82cc46f6d14ba6bd9772dfe28ff9527eb 100644 --- a/mongo/scripts/migration.js +++ b/mongo/scripts/migration.js @@ -7,12 +7,7 @@ db.auth(adminname, adminpwd); articles = db.getCollection("articles"); -articles.update({}, - { - $set: { - "metadata": { "description": "" }, - "with_static_view": false - } - }, - { upsert: false, multi: true } -); \ No newline at end of file +articles.find().forEach(art => { + art.images = art.images.map(img => "/assets/images/" + img); + articles.save(art); +}); \ No newline at end of file diff --git a/src/core/static_files.rs b/src/core/static_files.rs index 561d400ce0da779f123b8f02c061312bd3e3abe7..e8057dc4666bc65af40d4b3442b8a8932b1811f9 100644 --- a/src/core/static_files.rs +++ b/src/core/static_files.rs @@ -39,7 +39,7 @@ impl StaticFilesIndex { fn _push_path(path: &Path, files: &mut Vec<String>, strip_from: &Path) { let push_path = path.strip_prefix(strip_from).unwrap(); - files.push(push_path.to_str().unwrap().to_owned()); + files.push(format!("/{}", push_path.to_str().unwrap().to_owned())); } pub fn rebuild(&mut self, env: &Env) { @@ -54,6 +54,10 @@ impl StaticFilesIndex { let strip_from = StaticFilesIndex::get_public_dir(env); StaticFilesIndex::_push_path(path, &mut self.0, &strip_from); } + + pub fn get_index(&self) -> Vec<String> { + self.0.clone() + } } #[derive(Debug, PartialEq)] diff --git a/src/main.rs b/src/main.rs index 6439d511f3d86874239cc0931331bde66cf37a6d..07ac8b4e0c1822f397e213fc8a5d4a24cccd0a2e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -95,6 +95,7 @@ async fn main() -> std::io::Result<()> { .service(get_article) .service(get_all_articles) .service(post_files) + .service(get_static_files_index) ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // STANDARD FILES /////////////////////////////////////////////////////////////////////////////////////////// .service(resource("/favicon.ico").route(get().to(favicon))) diff --git a/src/service/static_files.rs b/src/service/static_files.rs index c3af8f01937bcf5628cf1332a8bc762ee235e765..96850a27510f6d77f8d0231faa44d8b60c496d47 100644 --- a/src/service/static_files.rs +++ b/src/service/static_files.rs @@ -1,6 +1,6 @@ use crate::{core::static_files::*, middleware::AuthenticatedAdminMiddleware, AppState}; use actix_multipart::Multipart; -use actix_web::{post, web::Data, HttpRequest, HttpResponse, Responder}; +use actix_web::{get, post, web::Data, HttpRequest, HttpResponse, Responder}; use futures::StreamExt; use std::{ fs::{remove_file, File}, @@ -106,6 +106,18 @@ pub async fn post_files( HttpResponse::Ok().json(uploaded_filepathes) } +#[get("/static-files-index")] +async fn get_static_files_index( + static_files_index: Data<std::sync::Mutex<StaticFilesIndex>>, +) -> impl Responder { + HttpResponse::Ok().json( + static_files_index + .lock() + .expect("Couldn't lock files index") + .get_index(), + ) +} + // EXAMPLE FROM ACTIX REPO (using threadpool) // use futures::TryStreamExt; diff --git a/website/package-lock.json b/website/package-lock.json index 3f08ae9befb54c6a17edd4637420a954939e7a56..e11bd851006c2450f3d7308fa60f7b4e95f3fbf9 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -1,12 +1,12 @@ { "name": "kuadrado-website", - "version": "1.0.4", + "version": "1.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "kuadrado-website", - "version": "1.0.4", + "version": "1.1.0", "license": "MIT", "dependencies": { "ks-cheap-translator": "^0.1.0", diff --git a/website/src/article-vew-components/game-article.js b/website/src/article-vew-components/game-article.js index 94cf298421435785b2af3da1ddce0f98f702a169..d611a779b45dec1c902c0c32b07cc268f31205ff 100644 --- a/website/src/article-vew-components/game-article.js +++ b/website/src/article-vew-components/game-article.js @@ -4,7 +4,6 @@ const ImageCarousel = require("../generic-components/image-carousel"); const { getArticleBody } = require("../lib/article-utils"); const { fetch_json_or_error_text } = require("../lib/fetch"); const { MentaloEngine } = require("mentalo-engine"); -const { images_url, data_url } = require("../../constants"); class GameArticle { @@ -41,16 +40,16 @@ class GameArticle { tag: "button", class: "play-button", contents: "â–¶ï¸ " + t("Jouer"), - onclick: this.handle_click_play.bind(this, button_data.filename, button_data.engine) + onclick: this.handle_click_play.bind(this, button_data.fileurl, button_data.engine) }; } - load_and_run_mentalo_game(filename, button_element) { + load_and_run_mentalo_game(fileurl, button_element) { const button_text = button_element.innerHTML; button_element.innerHTML = "Loading ..."; button_element.style.pointerEvents = "none"; - fetch_json_or_error_text(`${data_url}/${filename}`) + fetch_json_or_error_text(fileurl) .then(game_data => { const container = document.createElement("div"); container.style.position = "fixed"; @@ -88,10 +87,10 @@ class GameArticle { }); } - handle_click_play(filename, engine, e) { + handle_click_play(fileurl, engine, e) { switch (engine) { case "mentalo": - this.load_and_run_mentalo_game(filename, e.target); + this.load_and_run_mentalo_game(fileurl, e.target); break; default: console.log("Error, unkown engine") @@ -116,7 +115,7 @@ class GameArticle { tag: "div", id: "article-banner", style_rules: { - backgroundImage: `url(${images_url}/${images[0]})`, + backgroundImage: `url(${images[0]})`, }, contents: [ { @@ -158,7 +157,7 @@ class GameArticle { }, ], }, - trad_ready && new ImageCarousel({ images: images.map(img => `${images_url}/${img}`) }).render(), + trad_ready && new ImageCarousel({ images }).render(), this.details.length > 0 && { tag: "div", class: "article-details", diff --git a/website/src/article-vew-components/software-article.js b/website/src/article-vew-components/software-article.js index 0170cba1e127fed4a4ad5d3b82b8f83dd28e9895..38b3d257f35d7f24dfc3b484053c98a0798a95aa 100644 --- a/website/src/article-vew-components/software-article.js +++ b/website/src/article-vew-components/software-article.js @@ -1,7 +1,6 @@ "use strict"; const ImageCarousel = require("../generic-components/image-carousel"); -const { images_url } = require("../../constants"); const { getArticleBody } = require("../lib/article-utils"); @@ -35,7 +34,7 @@ class SoftwareArticle { contents: [ { tag: "img", - src: `${images_url}/${logo}` + src: logo }, ] }, @@ -76,7 +75,7 @@ class SoftwareArticle { tag: "div", class: "article-more", contents: [ - trad_ready && screens.length > 0 && new ImageCarousel({ images: screens.map(img => `${images_url}/${img}`) }).render(), + trad_ready && screens.length > 0 && new ImageCarousel({ images: screens }).render(), details.length > 0 && { tag: "div", class: "article-details", diff --git a/website/src/home-page-components/theme-card.js b/website/src/home-page-components/theme-card.js index 87f6a2efd1667e05b9dea2a9eb00f01eb94a1b98..75dba51f5a219aa2ace0b96b48e075296b4d81b0 100644 --- a/website/src/home-page-components/theme-card.js +++ b/website/src/home-page-components/theme-card.js @@ -1,7 +1,5 @@ "use strict"; -const { images_url } = require("../../constants"); - class ThemeCard { constructor(props) { this.props = props; @@ -16,7 +14,12 @@ class ThemeCard { { tag: "div", class: "card-img", - contents: [{ tag: "img", alt: `thematic image ${this.props.img.replace(/\.[A-Za-z]+/, "")}`, src: `${images_url}/${this.props.img}` }], + contents: [ + { + tag: "img", + alt: `thematic image ${this.props.img.split("/").reverse()[0].replace(/\.[A-Za-z]+/, "")}`, + src: this.props.img + }], }, { tag: "div", diff --git a/website/src/homepage.js b/website/src/homepage.js index 3aff6801fd4b7b81c90ddbe84413f1e26531c9d8..b6dc91087a6ff045c52150f1c668d36d1f7e1dd1 100644 --- a/website/src/homepage.js +++ b/website/src/homepage.js @@ -59,20 +59,20 @@ class HomePage extends WebPage { contents: [ { title: t("Jeux"), - img: "game_controller.svg", + img: images_url + "/game_controller.svg", href: "/games/", description: t("games-description"), }, { title: t("Pédagogie"), - img: "brain.svg", + img: images_url + "/brain.svg", href: "/education/", description: t("education-description"), }, { title: "Software", - img: "meca_proc.svg", + img: images_url + "/meca_proc.svg", href: "/software-development/", description: t("software-description"), }, diff --git a/website/src/pages/education/components/edu-article.js b/website/src/pages/education/components/edu-article.js index ab035167a8d5f31ec639d9b53731ba04cb2a4df3..9814af40a414a4ad641eb4d870c4716e3526c58e 100644 --- a/website/src/pages/education/components/edu-article.js +++ b/website/src/pages/education/components/edu-article.js @@ -1,6 +1,5 @@ "use strict"; -const { images_url } = require("../../../../constants"); const ImageCarousel = require("../../../generic-components/image-carousel"); const { getArticleBody } = require("../../../lib/article-utils"); @@ -27,7 +26,7 @@ class EduArticle { tag: "div", class: "edu-art-image", contents: [ { - tag: "img", src: `${images_url}/${images[0]}` + tag: "img", src: images[0] } ] }, @@ -45,7 +44,7 @@ class EduArticle { }, images.length > 1 && { tag: "div", class: "edu-art-carousel", contents: [ - new ImageCarousel({ images: images.map(img => `${images_url}/${img}`) }).render() + new ImageCarousel({ images: images }).render() ] }, details.length > 0 && { diff --git a/website/src/pages/games/components/game-thumb.js b/website/src/pages/games/components/game-thumb.js index 1ce8674e4e6327847ebdb27c094bee717b656b67..42cc451d54902eb5e97f39b36377e1e9935e4cc6 100644 --- a/website/src/pages/games/components/game-thumb.js +++ b/website/src/pages/games/components/game-thumb.js @@ -1,6 +1,5 @@ "use strict"; -const { images_url } = require("../../../../../admin-frontend/src/constants"); class GameThumb { constructor(props) { @@ -21,7 +20,7 @@ class GameThumb { target: "_blank", class: "game-thumb", style_rules: { - backgroundImage: `url(${images_url}/${images[0]})`, + backgroundImage: `url(${images[0]})`, }, contents: [ { diff --git a/website/src/pages/software-development/components/software-thumb.js b/website/src/pages/software-development/components/software-thumb.js index 04fbb425bdfec6cbed7976d4a38d1213fad3344b..ad0390f416413355d489fd2fca2a94fb73c38118 100644 --- a/website/src/pages/software-development/components/software-thumb.js +++ b/website/src/pages/software-development/components/software-thumb.js @@ -1,6 +1,5 @@ "use strict"; -const { images_url } = require("../../../../../admin-frontend/src/constants"); class SoftwareThumb { constructor(props) { @@ -26,7 +25,7 @@ class SoftwareThumb { class: "software-image", contents: [ { - tag: "img", src: `${images_url}/${images[0]}` + tag: "img", src: `${images[0]}` } ] },