all repos — artbound-python @ 0edae2b70652990a729ba21b58d5eb12a11f7496

A client-server reimplementation of the administration panel for ArtBound.

finishing touches
BiRabittoh birabittoh@tilde.team
Sun, 06 Aug 2023 17:02:33 +0200
commit

0edae2b70652990a729ba21b58d5eb12a11f7496

parent

3d6c695d27ba8a782bd99f2410cea108f6bc3941

M artbound_python/cache.pyartbound_python/cache.py

@@ -47,11 +47,14 @@ }

class DB(): def update_database(self): + prev_length = len(self.db) self.db = [ handle_row(x) for x in get_rows() ] - if len(self.db) == 0: + new_length = len(self.db) + if new_length == 0: print("No fanarts!") exit(1) self.last_updated = datetime.now() + return new_length - prev_length def __init__(self): self.db = []
M artbound_python/static/script.jsartbound_python/static/script.js

@@ -1,5 +1,4 @@

-const BS_COL_WIDTH = 4, - MAX_WIDTH = MAX_HEIGHT = 1000, +const MAX_WIDTH = MAX_HEIGHT = 1000, WATERMARK_SRC = "/static/res/wm.png", WATERMARK_WIDTH = 325, WATERMARK_HEIGHT = 98,

@@ -11,7 +10,7 @@ IG_MAX_HEIGHT = 988,

WATERMARK = new Image(), IG_TEMPLATE = new Image(), - date = new Date(), + last_updated_link = document.getElementById("last-updated-link"), month_input = document.getElementById("month_input"), month_div = document.getElementById("month_div"), get_button = document.getElementById("get_button"),

@@ -26,25 +25,19 @@ canvas_ig = document.getElementById("instagram-canvas"),

fanart_template = document.getElementById("fanart-template").innerHTML; let fanarts = new Array(), - watermark_invert = ''; + new_entries = 0; WATERMARK.src = WATERMARK_SRC; IG_TEMPLATE.src = IG_TEMPLATE_SRC; -month_input.value = `${date.getFullYear()}-${("0" + (date.getMonth() + 1)).slice(-2)}`; +month_input.addEventListener("keyup", (e) => { if (e.key === "Enter") getArtworks(); }); function addCanvasEvents(element, ctx) { function abc(e) { if (element.enabled == 0) return; - if (e.button == 0) { - if (e.type == "mousedown") { - element.clicked = true; - console.log(element); - } - element.watermark.opacity = opacity_range.value; - addWatermark(e, element, ctx); - } + if (e.button != 0) return; + element.watermark.opacity = opacity_range.value; + addWatermark(e, element, ctx); } - if (element.clicked) { element.canvas.addEventListener('mousedown', abc); return;

@@ -55,7 +48,7 @@ if (!element.clicked && element.enabled)

setBaseImage(element.image, element.canvas, ctx); }); element.canvas.addEventListener('mousedown', (e) => { - abc(e); + element.clicked = true; element.canvas.removeEventListener('mousemove', abc); element.canvas.addEventListener('mousedown', abc); });

@@ -68,31 +61,25 @@ return div.firstChild;

} function getNewCardHtml(element) { - const id = element.id, index = element.index, name = element.name, content = element.content, filename = `${('0' + element.index).slice(-2)} - ${element.name}.png`, disabled = element.enabled == 0 ? " entry-disabled" : ""; - const html_string = Mustache.render("{{={| |}=}}" + fanart_template, { id, index, name, content, filename, disabled }); element.div = createElementFromHTML(html_string); element.canvas = element.div.getElementsByTagName("canvas")[0]; - const ctx = element.canvas.getContext("2d"); - element.image = new Image(); element.image.addEventListener("load", () => { const wm_info = element.watermark; setBaseImage(element.image, element.canvas, ctx); - if (element.clicked) { + if (element.clicked) drawWatermark(wm_info, ctx); - } addCanvasEvents(element, ctx); }); element.image.src = element.content; - return element.div; }

@@ -110,7 +97,6 @@ fanart.index = i;

content_div.appendChild(getNewCardHtml(fanart)); } } - for (fanart of fanarts) { if (!fanart.enabled) { fanart.index = 0;

@@ -151,13 +137,6 @@ fanarts.map(x => x.enabled = toggle)

updateFanartList() } -function debugFn() { - fanarts = JSON.parse('[{"id":"1dE8L7w2DuOfQSJwf5oRjAeJ-VZfy5o-6","date":["03","08","2022"],"name":"Saro","content":"/static/res/wm.png","enabled":1,"index":1,"div":{}},{"id":"1ZO2poqaylmh7_FkEjRthozVXFpcP1Edx","date":["18","08","2022"],"name":"suchetto","content":"https://drive.google.com/uc?id=1ZO2poqaylmh7_FkEjRthozVXFpcP1Edx","enabled":1,"index":2,"div":{}},{"id":"1jkpZzqnQUdXv7QiW1khuwnSsdnjudBt-","date":["18","08","2022"],"name":"suca","content":"https://drive.google.com/uc?id=1clZbi1QzJQo_c7PGWx-nmLgfPhXqHdZR","enabled":1,"index":3,"div":{}}]'); - controls_div.hidden = false; - updateOpacity(); - updateFanartList(); -} - function clickCoordsToCanvas(clickX, clickY, c) { const rect = c.getBoundingClientRect(); const x = (clickX - rect.left) * c.width / c.clientWidth;

@@ -189,29 +168,29 @@ }

function setBaseImage(img, c, ctx) { const f = getFactor(img.width, img.height, MAX_WIDTH, MAX_HEIGHT); - const new_width = c.width = Math.ceil(img.width * f); const new_height = c.height = Math.ceil(img.height * f) - ctx.imageSmoothingEnabled = (f < 1); ctx.drawImage(img, 0, 0, new_width, new_height); } function moveUpDown(id, amount) { - const pos = fanarts.indexOf(fanarts.find(element => element.id == id)); + const entry = fanarts.find(element => element.id == id); + if (!entry) return; + if (!entry.enabled) return; + const pos = fanarts.indexOf(entry); const new_pos = pos + amount; - if (new_pos <= -1 || new_pos >= fanarts.length) { - return; - } + if (new_pos <= -1 || new_pos >= fanarts.length) return; [fanarts[pos], fanarts[new_pos]] = [fanarts[new_pos], fanarts[pos]]; - updateFanartList(); } function toggleInvert(id, button) { const entry = fanarts.find(element => element.id == id); + if (!entry) return; + if (!entry.enabled) return; entry.watermark.invert = entry.watermark.invert == '' ? 'invert(1)' : ''; button.innerText = entry.watermark.invert ? "⚫" : "⚪"; const ctx = entry.canvas.getContext('2d');

@@ -225,20 +204,18 @@ return response.json();

} function saveEntry(id) { - entry = getFanart(id); + const entry = getFanart(id); if (!entry) return; + if (!entry.enabled) return; saveCanvas(entry.canvas, entry.canvas.getAttribute("data-filename")); } function saveCanvasIG(my_canvas) { const f = getFactor(my_canvas.width, my_canvas.height, IG_MAX_WIDTH, IG_MAX_HEIGHT); - const width = Math.ceil(my_canvas.width * f); const height = Math.ceil(my_canvas.height * f); - const offset_x = Math.round((IG_MAX_WIDTH - width) / 2); const offset_y = Math.round((IG_MAX_HEIGHT - height) / 2); - destCtx = canvas_ig.getContext('2d'); destCtx.drawImage(IG_TEMPLATE, 0, 0); destCtx.drawImage(my_canvas, IG_MIN_OFFSET_X + offset_x, IG_MIN_OFFSET_Y + offset_y, width, height);

@@ -248,6 +225,7 @@

function saveEntryIG(id) { entry = getFanart(id); if (!entry) return; + if (!entry.enabled) return; saveCanvasIG(entry.canvas); }

@@ -255,7 +233,6 @@ function getArtworks() {

get_button.disabled = true; get_button.innerText = "…" postData("/", { month: month_input.value }).then((data) => { - //console.log(data); fanarts = fanarts.concat(data); controls_div.hidden = false; updateOpacity();

@@ -266,7 +243,6 @@

function saveAll() { const response = confirm("Vuoi davvero scaricare tutte le fanart?"); if (response == false) return; - fanarts.forEach((fanart) => { if (fanart.enabled) saveCanvas(fanart.canvas, fanart.canvas.getAttribute("data-filename"));

@@ -276,9 +252,17 @@

function saveAllIG() { const response = confirm("Vuoi davvero scaricare tutte le storie per le fanart?"); if (response == false) return; - fanarts.forEach((fanart) => { if (fanart.enabled) saveCanvasIG(fanart.canvas); }); } + +function updateDb() { + postData("/update").then((data) => { + last_updated_link.innerText = data.timestamp; + if (data.new == 0) return; + new_entries += data.new; + last_updated_link.innerText += ` (+${new_entries})`; + }); +}
M artbound_python/static/style.cssartbound_python/static/style.css

@@ -57,10 +57,6 @@ .card-controls {

flex-direction: row-reverse; } -.card-text { - text-decoration: underline !important; -} - body { background-color: var(--background0); color: var(--text);
M artbound_python/templates/index.htmlartbound_python/templates/index.html

@@ -5,7 +5,7 @@ <head>

<meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content="Un modo semplice e veloce per gestire le fanart di ArtBound."> - <meta name="author" content="Bi-Rabittoh"> + <meta name="author" content="BiRabittoh"> <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>✏️</text></svg>"> <title>ArtBound Panel</title>

@@ -25,22 +25,35 @@ class="feather feather-activity">

<polygon points="14 2 18 6 7 17 3 17 3 13 14 2"></polygon> <line x1="3" y1="22" x2="21" y2="22"></line> </svg> - ArtBound Panel 1.5 + ArtBound Panel</a> </h1> - <a href="/update" class="lead">{{ last_updated }}</a> + <svg fill="white" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" + width="15" height="15" viewBox="0 0 420.827 420.827" + xml:space="preserve"> + <g><g><path d="M210.29,0C156,0,104.43,20.693,65.077,58.269C25.859,95.715,2.794,146.022,0.134,199.921 + c-0.135,2.734,0.857,5.404,2.744,7.388c1.889,1.983,4.507,3.105,7.244,3.105h45.211c5.275,0,9.644-4.098,9.979-9.362 + c4.871-76.214,68.553-135.914,144.979-135.914c80.105,0,145.275,65.171,145.275,145.276c0,80.105-65.17,145.276-145.275,145.276 + c-18.109,0-35.772-3.287-52.501-9.771l17.366-15.425c2.686-2.354,3.912-5.964,3.217-9.468c-0.696-3.506-3.209-6.371-6.592-7.521 + l-113-32.552c-3.387-1.149-7.122-0.407-9.81,1.948c-2.686,2.354-3.913,5.963-3.218,9.467L69.71,403.157 + c0.696,3.505,3.209,6.372,6.591,7.521c3.383,1.147,7.122,0.408,9.81-1.946l18.599-16.298 + c31.946,18.574,68.456,28.394,105.581,28.394c116.021,0,210.414-94.392,210.414-210.414C420.705,94.391,326.312,0,210.29,0z"/> + <path d="M195.112,237.9h118.5c2.757,0,5-2.242,5-5v-30c0-2.757-2.243-5-5-5h-83.5v-91c0-2.757-2.243-5-5-5h-30 + c-2.757,0-5,2.243-5,5v126C190.112,235.658,192.355,237.9,195.112,237.9z"/></g></g></svg> + <a id="last-updated-link" href="#" class="lead" onclick="updateDb();"><?xml version="1.0" encoding="iso-8859-1"?> + {{ last_updated }}</a> <div id="month_div"> - <label for="month_input" style="margin-right: 10px;">Scegli il mese: </label><input id="month_input" - type="month" value="2022-08"> - <a href="#" class="btn my-2 btn-go" onclick="handleAuthClick()" id="authorize_button" hidden>Vai</a> - <button class="btn btn-secondary my-2" onclick="getArtworks()" id="get_button">Ottieni</button> + <label for="month_input" style="margin-right: 10px;">Scegli il mese: </label> + <input id="month_input" type="month" value="{{ current_month }}"> + <a href="#" class="btn my-2 btn-go" onclick="handleAuthClick();" id="authorize_button" hidden>Vai</a> + <button class="btn btn-secondary my-2" onclick="getArtworks();" id="get_button">Ottieni</button> </div> <div id="controls" hidden> - <a href="#" class="btn btn-secondary my-2" onclick="selectAllNone(1)" id="selectall_button">✅</a> - <a href="#" class="btn btn-secondary my-2" onclick="selectAllNone(0)" id="selectnone_button">❎</a> - <input type="range" class="form-range" id="opacity_range" min="0" max="1" step="0.01" oninput="updateOpacity()" value="0.4"> + <a href="#" class="btn btn-secondary my-2" onclick="selectAllNone(1);" id="selectall_button">✅</a> + <a href="#" class="btn btn-secondary my-2" onclick="selectAllNone(0);" id="selectnone_button">❎</a> + <input type="range" class="form-range" id="opacity_range" min="0" max="1" step="0.01" oninput="updateOpacity();" value="0.4"> <label for="opacity_range" class="form-label" id="opacity_label"></label> - <a href="#" class="btn btn-secondary my-2" onclick="saveAll()" id="saveall_button">💾</a> - <a href="#" class="btn btn-secondary my-2" onclick="saveAllIG()" id="saveallig_button">📷</a> + <a href="#" class="btn btn-secondary my-2" onclick="saveAll();" id="saveall_button">💾</a> + <a href="#" class="btn btn-secondary my-2" onclick="saveAllIG();" id="saveallig_button">📷</a> </div> </div> </section>

@@ -71,8 +84,7 @@ </div>

</main> <footer> <div class="container"> - <p>&copy; Earthbound Café, realizzato da <a href="mailto:andronacomarco@gmail.com">Marco Andronaco</a> - (BiRabittoh).</p> + <p>&copy; Earthbound Café, realizzato da <a href="mailto:andronacomarco@gmail.com">Marco Andronaco</a> (BiRabittoh).</p> </div> </footer> <template id="fanart-template">

@@ -87,7 +99,7 @@ <a class="card-text" title="Clicca per copiare." onclick="navigator.clipboard.writeText(this.innerText);">{| filename |}</a>

<div class="d-flex justify-content-between align-items-center card-controls"> <div class="btn-group"> <button class="btn btn-sm btn-outline-secondary" onclick="moveUpDown('{| id |}', -1);">⬅️</button> - <button class="btn btn-sm btn-outline-secondary" onclick="toggleEntry('{| id |}');">*️⃣</button> + <button class="btn btn-sm btn-outline-secondary" onclick="toggleEntry('{| id |}');">♻️</button> <button class="btn btn-sm btn-outline-secondary" onclick="toggleInvert('{| id |}', this);">⚪</button> <button class="btn btn-sm btn-outline-secondary" onclick="saveEntry('{| id |}');">💾</button> <button class="btn btn-sm btn-outline-secondary" onclick="saveEntryIG('{| id |}');">📷</button>
M artbound_python/views.pyartbound_python/views.py

@@ -1,14 +1,15 @@

import json +from datetime import datetime from flask import request, redirect, render_template from artbound_python import app -from artbound_python.cache import last_updated, DB, clear_cache +from artbound_python.cache import DB, clear_cache database = DB() @app.route('/', methods=['GET', 'POST']) def route_index(): if request.method == 'GET': - return render_template("index.html", last_updated=database.get_last_updated()) + return render_template("index.html", last_updated=database.get_last_updated(), current_month=datetime.today().strftime("%Y-%m")) if (request.headers.get('Content-Type') != 'application/json'): return 'Content-Type not supported. Please use "application/json".'

@@ -17,10 +18,13 @@ month = request.json["month"]

fanarts = database.get_fanarts(month) return json.dumps(fanarts) -@app.route('/update') +@app.route('/update', methods=['POST']) def route_update(): - database.update_database() - return redirect("/") + new_entries = database.update_database() + return json.dumps({ + "timestamp": database.get_last_updated(), + "new": new_entries + }) @app.route('/clear') def route_clear():