Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import requests
import time
import os
import qrcode
from PIL import Image
import io
from werkzeug.utils import secure_filename

# Creating a Flask application instance
Expand All @@ -32,7 +35,6 @@ def proxy_image():
response = requests.get(image_url, stream=True)
return Response(response.content, content_type=response.headers['Content-Type'])


# Route to Favicon
@app.route('/favicon.ico')
def favicon():
Expand Down Expand Up @@ -228,6 +230,37 @@ def item(id):
else:
return jsonify({'error': 'Invalid action'}), 400

@app.route('/api/items/<id>/qr-code', methods=['GET'])
def qr_code(id):
item = db.get_item(id)
value = item['name']
if item['article_number']:
value = item['article_number']

qr = qrcode.QRCode(
version=5,
error_correction=qrcode.constants.ERROR_CORRECT_H,
box_size=10,
border=4
)
qr.add_data(value)
qr.make(fit=True)
qr_img = qr.make_image(fill='black', back_color='white').convert('RGBA')

logo = Image.open('./favicon.png')

qr_width, qr_height = qr_img.size
logo_size = qr_width // 4
logo = logo.resize((logo_size, logo_size), Image.LANCZOS)

pos = ((qr_width - logo_size) // 2, (qr_height - logo_size) // 2)
qr_img.paste(logo, pos, mask=logo)

img_io = io.BytesIO()
qr_img.save(img_io, format='PNG')
img_io.seek(0)

return Response(img_io, mimetype='image/png')

def send_request(target_ip, data, timeout=0.2):
url = f"http://{target_ip}/json/state"
Expand Down
27 changes: 19 additions & 8 deletions db.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def create_combined_db():
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
article_number TEXT,
link TEXT,
image TEXT,
position TEXT,
Expand Down Expand Up @@ -52,7 +53,8 @@ def create_combined_db():
timeout INTEGER DEFAULT 5,
lightMode TEXT DEFAULT 'light',
colors TEXT DEFAULT '[#00ff00, #00ff00]',
language TEXT DEFAULT 'en'
language TEXT DEFAULT 'en',
search_locates BOOLEAN NOT NULL CHECK (search_locates IN (0, 1)) DEFAULT 0
)
''')

Expand All @@ -71,6 +73,15 @@ def create_combined_db():
if 'language' not in columns:
cursor.execute("ALTER TABLE settings ADD COLUMN language TEXT DEFAULT 'en'")
conn_combined.commit()
if 'search_locates' not in columns:
cursor.execute("ALTER TABLE settings ADD COLUMN search_locates BOOLEAN NOT NULL CHECK (search_locates IN (0, 1)) DEFAULT 0")
conn_combined.commit()

cursor.execute("PRAGMA table_info(items)")
columns = [column[1] for column in cursor.fetchall()]
if 'article_number' not in columns:
cursor.execute("ALTER TABLE items ADD COLUMN article_number TEXT NULL")
conn_combined.commit()

return conn_combined

Expand All @@ -86,8 +97,8 @@ def read_items():
def write_item(item):
conn = create_combined_db()
cursor = conn.cursor()
cursor.execute('INSERT INTO items (name, link, image, position, quantity, ip, tags) VALUES (?, ?, ?, ?, ?, ?, ?)',
[item['name'], item['link'], item['image'], item['position'], item['quantity'], item['ip'],
cursor.execute('INSERT INTO items (name, article_number, link, image, position, quantity, ip, tags) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
[item['name'], item['article_number'], item['link'], item['image'], item['position'], item['quantity'], item['ip'],
item['tags']])
lastId = cursor.lastrowid
conn.commit()
Expand Down Expand Up @@ -116,8 +127,8 @@ def update_item(id, data):
try:

conn.execute(
'UPDATE items SET name = ?, link = ?, image = ?, position = ?, quantity = ?, ip = ?, tags = ? WHERE id = ?',
[data['name'], data['link'], data['image'], data['position'], data['quantity'], data['ip'], data['tags'],
'UPDATE items SET name = ?, article_number = ?, link = ?, image = ?, position = ?, quantity = ?, ip = ?, tags = ? WHERE id = ?',
[data['name'], data['article_number'], data['link'], data['image'], data['position'], data['quantity'], data['ip'], data['tags'],
id])
conn.commit()
except sqlite3.Error as e:
Expand Down Expand Up @@ -336,9 +347,9 @@ def update_settings(settings):
cursor = conn.cursor()
cursor.execute('DELETE FROM settings') # Clear existing settings
cursor.execute('''
INSERT INTO settings (brightness, timeout, lightMode, colors, language)
VALUES (?, ?, ?, ?, ?)
''', [settings['brightness'], settings['timeout'], settings['lightMode'], settings['colors'], settings['language']])
INSERT INTO settings (brightness, timeout, lightMode, colors, language, search_locates)
VALUES (?, ?, ?, ?, ?, ?)
''', [settings['brightness'], settings['timeout'], settings['lightMode'], settings['colors'], settings['language'], settings['search_locates']])
conn.commit()
except sqlite3.Error as e:
print(f"SQLite error while updating settings: {e}")
Expand Down
Binary file modified requirements.txt
Binary file not shown.
40 changes: 37 additions & 3 deletions static/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ async function addItem(event) {
// Gather item information from the form
const name = document.getElementById("item_name").value;
const link = document.getElementById("item_url").value || "";
const article_number = document.getElementById("item_article_number").value || "";
const image = document.getElementById("item_image").value.replace(window.location.href, "");
let position = localStorage.getItem('led_positions');
let quantity = document.getElementById("item_quantity").value;
Expand All @@ -53,6 +54,7 @@ async function addItem(event) {
const item = {
name,
link,
article_number,
image,
position,
quantity,
Expand Down Expand Up @@ -196,6 +198,7 @@ function createItem(item) {
// Set dataset attributes to store item information
col.dataset.id = item.id;
col.dataset.name = item.name;
col.dataset.item_article_number = item.article_number;
col.dataset.quantity = parseInt(item.quantity, 10); // Store as numbers
col.dataset.ip = item.ip;
if (!Array.isArray(item.position)) {
Expand Down Expand Up @@ -238,6 +241,7 @@ function createItem(item) {
<li><a id="copy_item_${item.id}" class="dropdown-item copy-btn" href="#">Copy Item</a></li>
<li><a id="delete_item_${item.id}" class="dropdown-item delete-btn" href="#">Delete</a></li>
<li><a id="crop_image_${item.id}" class="dropdown-item image-edit-btn" href="#">Crop Image</a></li>
<li><a id="view_qrcode_${item.id}" class="dropdown-item qr-code-btn" href="#">View QR-Code</a></li>
</ul>
</div>
</div>
Expand Down Expand Up @@ -381,15 +385,25 @@ function createItem(item) {
}
});

col.querySelector('.qr-code-btn').addEventListener('click', () => {
const qrCodeModal = document.getElementById('qrCodeModal');
const qrCodeImage = document.getElementById('qr-code-image');
const qrCodeUrl = `/api/items/${item.id}/qr-code`;
const downloadQrCodeButton = document.getElementById('download-qr-code-btn');
qrCodeImage.src = qrCodeUrl;
downloadQrCodeButton.setAttribute('data-download-url', qrCodeUrl);
downloadQrCodeButton.setAttribute('data-item-id', item.id);


$(qrCodeModal).modal("show");
});

col.querySelector('.edit-btn').addEventListener('click', () => {
// Set flag for editing, remove local storage, and show the item modal
isEditingItem = true;
removeLocalStorage();
$("#item-modal").modal("show");
document.getElementById("item_name").value = item.name;
document.getElementById("item_article_number").value = item.article_number;
document.getElementById("item_url").value = item.link;
document.getElementById("item_image").value = item.image;
document.getElementById("item_quantity").value = item.quantity;
Expand Down Expand Up @@ -492,6 +506,7 @@ function initialiseTooltips() {
function resetModal() {
document.getElementById("item_name").value = "";
document.getElementById("item_url").value = "";
document.getElementById("item_article_number").value = "";
document.getElementById("item_image").value = "";
document.getElementById("item_quantity").value = "";
document.getElementById("item_image_upload").value = "";
Expand Down Expand Up @@ -541,14 +556,26 @@ document.getElementById("search").addEventListener("input", function (e){
const searchText = e.target.value.toLowerCase();
const items = Array.from(itemsContainer.children);

let foundItems = [];

Array.from(items).forEach((item) => {
const itemName = item.dataset["name"];
if (itemName.toLowerCase().indexOf(searchText) !== -1) {
const itemArticleNumber = item.dataset["item_article_number"];
if (itemName.toLowerCase().indexOf(searchText) !== -1 || (itemArticleNumber && itemArticleNumber.toLowerCase().indexOf(searchText) !== -1)) {
item.style.display = "flex";
foundItems.push(item.dataset);
} else {
item.style.display = "none";
}
});

if (foundItems.length == 1 && e.target.getAttribute('data-search-locates') == 1) {
fetch(`/api/items/${foundItems[0].id}`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ action: "locate" }),
}).catch((error) => console.error(error));
}
});


Expand Down Expand Up @@ -612,7 +639,14 @@ const handleEmptyFields = (formType) => {
return false;
};


document.getElementById('download-qr-code-btn').addEventListener('click', function (e) {
const link = document.createElement('a');
link.href = e.target.getAttribute('data-download-url');
link.download = 'qrcode' + e.target.getAttribute('data-item-id') + '.png';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});

loadItems();
window.addEventListener('resize', function() {
Expand Down
16 changes: 15 additions & 1 deletion static/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,18 @@ function addSettings(event) {
if (language === undefined){
language = "en"
}
const settings = {brightness, timeout, lightMode, colors, language};
const search_locates = document.getElementById('search_locates').checked ? 1 : 0;
const settings = { brightness, timeout, lightMode, colors, language, search_locates };
// Save the settings in the database using fetch
fetch("/api/settings", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify(settings),
})
.then((response) => response.json())
.then(() => {
updateSearchInput(settings.search_locates);
})
.catch((error) => console.error(error));
}

Expand All @@ -72,10 +76,20 @@ function loadSettings() {
lightMode = settings.lightMode;
language = settings.language;
loadAvailableLanguages();

document.getElementById('search_locates').checked = settings.search_locates;
updateSearchInput(settings.search_locates);
})
.catch((error) => console.error(error));
}

document.getElementById('search_locates').addEventListener('change', (e) => {
addSettings(e);
});
function updateSearchInput(value) {
document.getElementById('search').setAttribute('data-search-locates', value);
}

window.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('[data-bs-theme-value]')
.forEach(toggle => {
Expand Down
43 changes: 42 additions & 1 deletion templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,20 @@ <h6 id="inventur_label">Inventur</h6>
</div>
</div>
<hr>
<div class="row">
<h6 id="search">Search</h6>
<div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="search_locates" value="1">
<label class="form-check-label" for="search_locates" id="search_locates_label">Search Locates</label>
</div>
<div class="alert alert-info" role="alert" id="search_locates_info">
If on, and only one item is found the search triggers automatic a locate request.
So you can see the item directly in your storage.
</div>
</div>
</div>
<hr>
<div class="col-4">
<div class="form-floating mb-3">
<select class="form-select" id="language-selector">
Expand Down Expand Up @@ -246,6 +260,11 @@ <h1 class="modal-title fs-5" id="item-modal-label">Add Item</h1>
<input type="text" class="form-control" id="item_name" placeholder="Name" required>
<label id="item_name_label" for="item_name">Item name</label>
</div>
<div class="form-floating mb-3">
<input type="text" class="form-control" id="item_article_number"
placeholder="Article Number">
<label id="item_article_number_label" for="item_article_number">Item Article Number</label>
</div>
<div class="form-floating mb-3">
<input type="text" class="form-control" id="item_url" placeholder="URL">
<label id="item_url_label" for="item_url">Item URL</label>
Expand Down Expand Up @@ -440,7 +459,29 @@ <h5 class="modal-title" id="cropImageModalLabel">Crop Image</h5>
</div>
</div>


<!-- QR-Code Modal -->
<div class="modal fade" id="qrCodeModal" tabindex="-1" aria-labelledby="qrCodeModallabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="qrCodeModalLabel">QR Code</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" style="max-height: 40rem; overflow: hidden; padding: 0;">
<div class="img-container"
style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; overflow: hidden;">
<img id="qr-code-image" src="" alt="QR Code"
style="max-width: 100%; max-height: 100%; object-fit: contain;">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="closeQrCode"
data-bs-dismiss="modal">Close</button>
<button id="download-qr-code-btn" class="btn btn-primary">Download Image</button>
</div>
</div>
</div>
</div>

<div class="modal fade" id="esp-modal" tabindex="-1" aria-labelledby="esp-modal-label" aria-hidden="true">
<div class="modal-dialog">
Expand Down