1 Commits

4 changed files with 117 additions and 66 deletions

View File

@@ -155,7 +155,7 @@
background: transparent !important;
color: #555 !important;
width: auto;
max-width: 70%;
max-width: 20%;
border-bottom: 1px dashed #ccc !important;
outline: none;
}
@@ -175,10 +175,10 @@
<div class="actions-bar">
<button type="button" class="btn-back" onclick="window.location.href= '/'">← Retour </button>
<button type="button" class="btn-cancel" onclick="handleCancel(event)">✖ Tout annuler</button>
<button type="button" class="btn-cat" onclick="addCategory()">+ Catégorie</button>
<button type="button" onclick="sendData()" class="btn-save" style="position:static; transform:none;">💾
Enregistrer tout</button>
<button type="button" class="btn-cancel" onclick="handleCancel(event)">✖ Tout annuler</button>
</div>
</form>
@@ -187,24 +187,32 @@
var t = document.getElementById('table-' + cat);
var r = t.insertRow(-1);
var id = Date.now();
r.innerHTML = '<td><input type="file" id="f_' + id + '" style="display:none" onchange="uImg(this)">' +
'<button type="button" class="link-mod" onclick="document.getElementById(\'f_' + id + '\').click()">Ajouter</button>' +
'<input type="hidden" name="new|' + cat + '|' + id + '|img" value=""></td>' +
r.innerHTML = '<td>' +
'<img src="" class="img-p" onerror="this.src=\'https://placehold.co/60?text=?\'" style="display:block; margin-bottom:5px;">' +
'<input type="file" accept=".jpg,.png" style="font-size:10px; width:70px" onchange="uImg(this)">' +
'<input type="hidden" name="new|' + cat + '|' + id + '|img" value="">' +
'</td>' +
'<td><input type="text" name="new|' + cat + '|' + id + '|name" placeholder="Nom..." required></td>' +
'<td><input type="number" name="new|' + cat + '|' + id + '|min" value="0" min="0" required></td>' +
'<td><input type="number" name="new|' + cat + '|' + id + '|max" value="0" min="0" required></td>' +
'<td><button type="button" class="btn-del" onclick="var l=this.parentNode.parentNode; l.parentNode.removeChild(l);">×</button></td>';
}
function uImg(el) {
var f = el.files[0];
function uImg(element) {
var f = element.files[0];
if (!f) return;
var row = el.parentNode.parentNode;
var id = f.name.split('.').slice(0, -1).join('.');
row.querySelector('input[type="hidden"]').value = id;
var fd = new FormData();
fd.append('images', f);
fetch('/upload_images', { method: 'POST', body: fd }).then(function () { alert('Image envoyée : ' + id); });
var td = element.parentNode;
var imgTag = td.querySelector('.img-p');
var hiddenInput = td.querySelector('input[type="hidden"]');
var reader = new FileReader();
reader.onload = function(e) {
imgTag.src = e.target.result;
hiddenInput.value = e.target.result;
};
reader.readAsDataURL(f);
}
function handleCancel(e) {
@@ -229,6 +237,12 @@
container.innerHTML =
'<h2>' +
'<input type="text" class="input-h2" name="cat|' + tempId + '|name" value="' + name + '">' +
'<div style="display:inline-block; vertical-align:middle; margin: 0 15px;">' +
'<small>Fond:</small><br><input type="color" name="cat|' + tempId + '|bgColor" value="#0049AF" style="width:30px; height:25px; border:none; cursor:pointer;">' +
'</div>' +
'<div style="display:inline-block; vertical-align:middle; margin-right:15px;">' +
'<small>Texte:</small><br><input type="color" name="cat|' + tempId + '|textColor" value="#FFFFFF" style="width:30px; height:25px; border:none; cursor:pointer;">' +
'</div>' +
' <button type="button" class="btn-del" onclick="if(confirm(\'Supprimer cette catégorie ?\')) this.closest(\'.card\').remove()">×</button>' +
'</h2>' +
'<table id="table-' + tempId + '">' +
@@ -253,12 +267,17 @@
// Browse showed categories
const catOrder = [];
document.querySelectorAll('.card').forEach((card, catIdx) => {
const catInput = card.querySelector('input[name^="cat|"]');
let techId = card.querySelector('input[name^="cat|"]')?.name.split('|')[1] || "c" + catIdx; // c0, c1 but avoid globals
catOrder.push(techId);
const catInput = card.querySelector('input[name$="|name"]');
const bgInput = card.querySelector('input[name$="|bgColor"]');
const textInput = card.querySelector('input[name$="|textColor"]');
if (!catInput) return;
const techId = "c" + catIdx; // c0, c1...
catOrder.push(techId);
data.items["cat|" + techId + "|name"] = catInput.value;
data.items["cat|" + techId + "|bgColor"] = bgInput ? bgInput.value : "#0049AF";
data.items["cat|" + techId + "|textColor"] = textInput ? textInput.value : "#FFFFFF";
// Browse each line
card.querySelectorAll('table tr').forEach((row, itemIdx) => {

View File

@@ -31,4 +31,8 @@ public class Category {
public String getBgColor() { return bgColor; }
public String getTextColor() { return textColor; }
public List<Item> getItems() { return items != null ? items : new ArrayList<>(); }
// Setters
public void setBgColor(String bgColor) { this.bgColor = bgColor; }
public void setTextColor(String textColor) { this.textColor = textColor; }
}

View File

@@ -13,15 +13,12 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Scanner;
import java.util.Iterator;
/**
* Create a web server to remote management of app assets.
@@ -110,26 +107,31 @@ public class ControlServer extends NanoHTTPD {
* Read from asset and return html file
*/
private String fillEditor() {
// Get data for assets
// Get data from assets
DataLoader.loadData();
// Create an html string to fill the template
StringBuilder html = new StringBuilder();
// Save categories order
List<String> orderList = new ArrayList<>();
// Globals
String globalId = "global";
Category globalCat = new Category("global", DataLoader.getGlobalItems());
html.append(renderCategorySection(globalId, globalCat));
orderList.add(globalId);
// Browser categories
int index = 0;
for (Category cat : DataLoader.getCategories()) {
// cat_0, cat_1...
String techId = "cat_" + index;
// Browse map : 'id' for categories name, 'items' for items list
for (Map.Entry<String, List<Item>> section : DataLoader.getAllSections().entrySet()) {
String technicalId = "cat_" + index;
String displayName = section.getKey();
List<Item> items = section.getValue();
html.append(renderCategorySection(techId, cat));
orderList.add(techId);
orderList.add(technicalId);
// For each category, render a HTML card
html.append(renderCategorySection(technicalId, displayName, items));
index++;
}
String orderField = "<input type='hidden' name='cat_order_list' value='" +
android.text.TextUtils.join(",", orderList) + "'>";
@@ -139,7 +141,9 @@ public class ControlServer extends NanoHTTPD {
/**
* Generate HTML section for each category
*/
private String renderCategorySection(String techId, String displayName, List<Item> items) {
private String renderCategorySection(String techId, Category cat) {
String displayName = cat.getName();
List<Item> items = cat.getItems();
StringBuilder sb = new StringBuilder();
sb.append("<div class='card'>");
@@ -147,19 +151,28 @@ public class ControlServer extends NanoHTTPD {
if ("global".equals(displayName)) {
sb.append("Articles globaux");
// Hidden input to mark cat for server
sb.append("<input type='hidden' name='cat|").append(displayName).append("|name' value='global'>");
sb.append("<input type='hidden' name='cat|global|name' value='global'>");
techId = "global";
} else {
// Copy H2 theme
sb.append("<input type='text' name='cat|").append(techId).append("|name' ")
.append("value=\"").append(displayName).append("\" class='input-h2'>");
// Nom
sb.append("<input type='text' name='cat|").append(techId).append("|name' value=\"").append(displayName).append("\" class='input-h2'>");
// Delete category button
// Couleur de Fond (on utilise cat.getBgColor())
sb.append("<div style='display:inline-block; vertical-align:middle; margin-right:15px;'>");
sb.append("<div style='font-weight: bold; font-size: 0.7em; padding-bottom: 5px;'>Fond</div>");
sb.append("<input type='color' name='cat|").append(techId).append("|bgColor' value='").append(cat.getBgColor()).append("' style='width:30px; height:25px; border:none; cursor:pointer;'>");
sb.append("</div>");
// Couleur de Texte (on utilise cat.getTextColor())
sb.append("<div style='display:inline-block; vertical-align:middle; margin-right:15px;'>");
sb.append("<div style='font-weight: bold; font-size: 0.7em; padding-bottom: 5px;'>Texte</div>");
sb.append("<input type='color' name='cat|").append(techId).append("|textColor' value='").append(cat.getTextColor()).append("' style='width:30px; height:25px; border:none; cursor:pointer;'>");
sb.append("</div>");
// Bouton supprimer
sb.append(" <button type='button' class='btn-del' style='vertical-align: middle; margin-left: 10px;' ")
.append("onclick=\"if(confirm('Supprimer cette catégorie et tous ses articles?')) this.closest('.card').remove()\">×</button>");
.append("onclick=\"if(confirm('Supprimer cette catégorie?')) this.closest('.card').remove()\">×</button>");
}
sb.append("</h2>");
// Build table
@@ -236,18 +249,15 @@ public class ControlServer extends NanoHTTPD {
// Get categories order
String[] orderedIds = fullJson.getString("cat_order_list").split(",");
// LinkedHashMap to keep order
Map<String, List<Item>> finalData = new LinkedHashMap<>();
List<Category> finalData = new ArrayList<>();
// For each category
for (String techId : orderedIds) {
// Create awaited label
String catNameKey = "cat|" + techId + "|name";
// Does it exist
if (!allFields.has(catNameKey)) continue;
// Get data
String realCatName = allFields.getString(catNameKey);
String name = allFields.getString(catNameKey);
List<Item> itemsInCategory = new ArrayList<>();
// Browse and create items
@@ -257,17 +267,38 @@ public class ControlServer extends NanoHTTPD {
// On vérifie si l'item suivant existe (via son champ name)
if (!allFields.has(itemBase + "name")) break;
// Manage image
String imgVal = allFields.optString(itemBase + "img", "");
if (imgVal.startsWith("data:image")) {
try {
String newImgName = "img_" + System.currentTimeMillis() + "_" + i;
String base64Data = imgVal.split(",")[1];
byte[] decoded = android.util.Base64.decode(base64Data, android.util.Base64.DEFAULT);
File imageFile = new File(imagesDir, newImgName + ".jpg");
try (FileOutputStream fos = new FileOutputStream(imageFile)) {
fos.write(decoded);
}
imgVal = newImgName; // Replace base64 with name of created jpg file
} catch (Exception e) { Log.e("ControlServer", "Img Error", e); }
}
itemsInCategory.add(new Item(
// Mandatory
allFields.getString(itemBase + "name"),
// Not mandatory
allFields.optString(itemBase + "img", ""),
imgVal,
parseSafely(allFields.optString(itemBase + "min", "0")),
parseSafely(allFields.optString(itemBase + "max", "0"))
));
i++;
}
finalData.put(realCatName, itemsInCategory);
// Create category with items and colors
Category cat = new Category(name, itemsInCategory);
cat.setBgColor(allFields.optString("cat|" + techId + "|bgColor", "#0049AF"));
cat.setTextColor(allFields.optString("cat|" + techId + "|textColor", "#FFFFFF"));
finalData.add(cat);
}
DataLoader.saveData(finalData);

View File

@@ -102,6 +102,9 @@ public class DataLoader {
public static List<Category> getCategories() {
return cachedCategories;
}
public static List<Item> getGlobalItems() {
return cachedGlobals;
}
/**
* Internal class for GSON
@@ -114,36 +117,30 @@ public class DataLoader {
/**
* Write JSON from online editor data
*/
public static void saveData(Map<String, List<Item>> sections) throws Exception {
File dir = new File(Environment.getExternalStorageDirectory(), EXTERNAL_DIR);
File jsonFile = new File(dir, PIECES_FILE);
// To respect original format, we use the same format as GSON
List<Item> globalList = sections.get("global");
if (globalList == null) {
globalList = new ArrayList<>();
}
public static void saveData(List<Category> categoriesList) throws Exception {
CategoriesWrapper wrapper = new CategoriesWrapper();
wrapper.globalItems = globalList;
wrapper.categories = new ArrayList<>();
wrapper.globalItems = new ArrayList<>();
// Fill each category
for (Map.Entry<String, List<Item>> entry : sections.entrySet()) {
if (!"global".equals(entry.getKey())) {
wrapper.categories.add(new Category(entry.getKey(), entry.getValue()));
// Browse category
for (Category cat : categoriesList) {
if ("global".equals(cat.getName())) {
wrapper.globalItems = cat.getItems();
} else {
wrapper.categories.add(cat);
}
}
// Convert to pretty JSON, human readable
// Create JSON
Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
String jsonString = gson.toJson(wrapper);
// Write to disk
try (FileOutputStream fos = new FileOutputStream(jsonFile);
OutputStreamWriter writer = new OutputStreamWriter(fos, "UTF-8")) {
File dir = new File(Environment.getExternalStorageDirectory(), Config.EXTERNAL_DIR_NAME);
File jsonFile = new File(dir, Config.INPUT_JSON_NAME);
// Write JSON
try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(jsonFile), "UTF-8")) {
writer.write(jsonString);
writer.flush();
}
// Update app cache