mirror of
https://github.com/lucasroyerdev/stock-pignon.git
synced 2026-05-10 11:02:26 +00:00
Compare commits
1 Commits
bae5186029
...
v0.6.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 0dbe47ad3b |
@@ -10,8 +10,8 @@ android {
|
||||
applicationId "com.stock.pignon"
|
||||
minSdkVersion 17
|
||||
targetSdkVersion 36
|
||||
versionCode 7
|
||||
versionName "0.5.2"
|
||||
versionCode 8
|
||||
versionName "0.6.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user