Compare commits
4 Commits
main
...
webui_grai
| Author | SHA1 | Date | |
|---|---|---|---|
| 31e57da117 | |||
| 2baa43db20 | |||
| 3e2c481f6f | |||
| 1cb9ff63e7 |
@@ -3,6 +3,7 @@ from flask import Flask, redirect, abort, render_template, request
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
import psutil
|
||||
from d2warehouse.item import Item, Quality, lookup_basetype
|
||||
@@ -14,6 +15,7 @@ import re
|
||||
|
||||
from d2warehouse.stash import StashFullError
|
||||
|
||||
|
||||
STASH_FILES = {
|
||||
"softcore": "SharedStashSoftCoreV2.d2i",
|
||||
"hardcore": "SharedStashHardCoreV2.d2i",
|
||||
@@ -92,22 +94,26 @@ def storage_count(item: Item, stash: str) -> int | str:
|
||||
db = get_stash_db(stash)
|
||||
if item.is_simple:
|
||||
return db.execute(
|
||||
"SELECT COUNT(id) FROM item WHERE code = ? AND deleted IS NULL",
|
||||
"SELECT COUNT(id) FROM item "
|
||||
"WHERE code = ? AND deleted IS NULL AND socketed_into IS NULL",
|
||||
(item.code,),
|
||||
).fetchone()[0]
|
||||
elif item.quality == Quality.UNIQUE:
|
||||
return db.execute(
|
||||
"SELECT COUNT(id) FROM item INNER JOIN item_extra ON id = item_id WHERE code = ? AND unique_id = ? AND deleted IS NULL",
|
||||
"SELECT COUNT(id) FROM item INNER JOIN item_extra ON id = item_id "
|
||||
"WHERE code = ? AND unique_id = ? AND deleted IS NULL AND socketed_into IS NULL",
|
||||
(item.code, item.unique_id),
|
||||
).fetchone()[0]
|
||||
elif item.quality == Quality.SET:
|
||||
return db.execute(
|
||||
"SELECT COUNT(id) FROM item INNER JOIN item_extra ON id = item_id WHERE code = ? AND set_id = ? AND deleted IS NULL",
|
||||
"SELECT COUNT(id) FROM item INNER JOIN item_extra ON id = item_id "
|
||||
"WHERE code = ? AND set_id = ? AND deleted IS NULL AND socketed_into IS NULL",
|
||||
(item.code, item.set_id),
|
||||
).fetchone()[0]
|
||||
elif item.is_runeword:
|
||||
return db.execute(
|
||||
"SELECT COUNT(id) FROM item INNER JOIN item_extra ON id = item_id WHERE code = ? AND runeword_id = ? AND deleted IS NULL",
|
||||
"SELECT COUNT(id) FROM item INNER JOIN item_extra ON id = item_id "
|
||||
"WHERE code = ? AND runeword_id = ? AND deleted IS NULL and socketed_into IS NULL",
|
||||
(item.code, item.runeword_id),
|
||||
).fetchone()[0]
|
||||
else:
|
||||
@@ -217,7 +223,9 @@ def list_storage(stash_name: str):
|
||||
|
||||
db = get_stash_db(stash_name)
|
||||
items = {}
|
||||
rows = db.execute("SELECT id FROM item WHERE deleted IS NULL").fetchall()
|
||||
rows = db.execute(
|
||||
"SELECT id FROM item WHERE deleted IS NULL and socketed_into IS NULL"
|
||||
).fetchall()
|
||||
for row in rows:
|
||||
items[row["id"]] = Item.load_from_db(row["id"], db=db)
|
||||
|
||||
@@ -238,16 +246,21 @@ def list_storage_category(stash_name: str, category: str):
|
||||
|
||||
if category == "uniques":
|
||||
q = db.execute(
|
||||
"SELECT id FROM item INNER JOIN item_extra ON id = item_id WHERE deleted IS NULL AND quality = ?",
|
||||
"SELECT id FROM item INNER JOIN item_extra ON id = item_id "
|
||||
"WHERE deleted IS NULL AND socketed_into IS NULL AND quality = ?",
|
||||
(int(Quality.UNIQUE),),
|
||||
)
|
||||
elif category == "sets":
|
||||
q = db.execute(
|
||||
"SELECT id FROM item INNER JOIN item_extra ON id = item_id WHERE deleted IS NULL AND quality = ?",
|
||||
"SELECT id FROM item INNER JOIN item_extra ON id = item_id "
|
||||
"WHERE deleted IS NULL AND socketed_into IS NULL AND quality = ?",
|
||||
(int(Quality.SET),),
|
||||
)
|
||||
elif category == "misc":
|
||||
q = db.execute("SELECT id FROM item WHERE deleted IS NULL AND is_simple = TRUE")
|
||||
q = db.execute(
|
||||
"SELECT id FROM item "
|
||||
"WHERE deleted IS NULL AND socketed_into IS NULL AND is_simple = TRUE"
|
||||
)
|
||||
else:
|
||||
return "Unexpected category", 400
|
||||
|
||||
@@ -260,6 +273,7 @@ def list_storage_category(stash_name: str, category: str):
|
||||
"list_storage.html",
|
||||
stash_name=stash_name,
|
||||
storage_items=items,
|
||||
category=category,
|
||||
storage_count=lambda x: storage_count(x, stash_name),
|
||||
)
|
||||
|
||||
@@ -325,14 +339,16 @@ def storage_currency_counts(item_codes: list[str], stash_name: str) -> dict:
|
||||
for code in item_codes:
|
||||
currencies[code] = {
|
||||
"count": db.execute(
|
||||
"SELECT COUNT(id) FROM item WHERE code = ? AND deleted IS NULL", (code,)
|
||||
"SELECT COUNT(id) FROM item "
|
||||
"WHERE code = ? AND deleted IS NULL AND socketed_into IS NULL",
|
||||
(code,),
|
||||
).fetchone()[0],
|
||||
"name": lookup_basetype(code)["name"],
|
||||
}
|
||||
return currencies
|
||||
|
||||
|
||||
@app.route("/storage/<stash_name>/currency")
|
||||
@app.route("/currency/<stash_name>")
|
||||
def storage_currency(stash_name: str):
|
||||
if stash_name not in DB_FILES:
|
||||
abort(404)
|
||||
@@ -343,3 +359,86 @@ def storage_currency(stash_name: str):
|
||||
return render_template(
|
||||
"currency.html", runes=runes, gems=gems, keys=keys, essences=essences
|
||||
)
|
||||
|
||||
|
||||
def load_uniques_data():
|
||||
"""Return a sorted dictionary of unique item ids indexed by names."""
|
||||
uniques_path = Path(__file__).resolve().parent.parent / "data/uniques.json"
|
||||
with uniques_path.open() as f:
|
||||
data = json.load(f)
|
||||
name_to_id = {v["name"]: int(k) for k, v in data.items()}
|
||||
del name_to_id["Amulet of the Viper"]
|
||||
del name_to_id["Staff of Kings"]
|
||||
del name_to_id["Horadric Staff"]
|
||||
del name_to_id["Khalim's Flail"]
|
||||
del name_to_id["Khalim's Will"]
|
||||
del name_to_id["Hell Forge Hammer"]
|
||||
return {name: name_to_id[name] for name in sorted(name_to_id.keys())}
|
||||
|
||||
|
||||
def load_sets_data():
|
||||
"""Return a sorted dictionary of unique item ids indexed by names."""
|
||||
uniques_path = Path(__file__).resolve().parent.parent / "data/sets.json"
|
||||
with uniques_path.open() as f:
|
||||
data = json.load(f)
|
||||
name_to_id = {v["name"]: int(k) for k, v in data.items()}
|
||||
return {name: name_to_id[name] for name in sorted(name_to_id.keys())}
|
||||
|
||||
|
||||
all_uniques = load_uniques_data()
|
||||
all_sets = load_sets_data()
|
||||
|
||||
|
||||
def get_found_uniques(db):
|
||||
rows = db.execute(
|
||||
"SELECT DISTINCT unique_id "
|
||||
"FROM item INNER JOIN item_extra ON id = item_id "
|
||||
"WHERE quality = 7 AND deleted IS NULL AND socketed_into IS NULL",
|
||||
).fetchall()
|
||||
return {row["unique_id"]: True for row in rows}
|
||||
|
||||
|
||||
def get_found_sets(db):
|
||||
rows = db.execute(
|
||||
"SELECT DISTINCT set_id "
|
||||
"FROM item INNER JOIN item_extra ON id = item_id "
|
||||
"WHERE quality = 5 AND deleted IS NULL AND socketed_into IS NULL",
|
||||
).fetchall()
|
||||
return {row["set_id"]: True for row in rows}
|
||||
|
||||
|
||||
@app.route("/grail/<stash_name>")
|
||||
def storage_grail(stash_name: str):
|
||||
if stash_name not in DB_FILES:
|
||||
abort(404)
|
||||
db = get_stash_db(stash_name)
|
||||
|
||||
# Unique progress
|
||||
found = get_found_uniques(db)
|
||||
items = {name: found.get(id, False) for name, id in all_uniques.items()}
|
||||
count = len(found)
|
||||
total = len(all_uniques)
|
||||
uniques = {
|
||||
"list": items,
|
||||
"count": count,
|
||||
"total": total,
|
||||
"progress": count / total * 100,
|
||||
}
|
||||
|
||||
# Set progress
|
||||
found = get_found_sets(db)
|
||||
items = {name: found.get(id, False) for name, id in all_sets.items()}
|
||||
count = len(found)
|
||||
total = len(all_sets)
|
||||
sets = {
|
||||
"list": items,
|
||||
"count": count,
|
||||
"total": total,
|
||||
"progress": count / total * 100,
|
||||
}
|
||||
|
||||
return render_template(
|
||||
"grail.html",
|
||||
uniques=uniques,
|
||||
sets=sets,
|
||||
)
|
||||
|
||||
@@ -11,3 +11,10 @@ function toggleSelectAll(tabIndex) {
|
||||
cb.checked = !allSelected;
|
||||
});
|
||||
}
|
||||
|
||||
function toggleCollected() {
|
||||
const collected = document.querySelectorAll('.collected');
|
||||
collected.forEach(item => {
|
||||
item.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,6 +5,25 @@ body {
|
||||
color: rgb(240, 240, 240);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: block;
|
||||
padding: 1rem 1.5rem;
|
||||
text-decoration: none;
|
||||
color: rgb(240, 240, 240);
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.stash-tab {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
@@ -104,3 +123,28 @@ input[type="checkbox"]:checked + label {
|
||||
background-color: #444;
|
||||
color: rgb(240, 240, 240);
|
||||
}
|
||||
|
||||
ul.grail {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem 2rem;
|
||||
}
|
||||
|
||||
ul.grail li {
|
||||
flex: 0 0 15rem;
|
||||
padding: 0.5rem;
|
||||
width: 15rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.collected {
|
||||
background-color: #343;
|
||||
}
|
||||
.uncollected {
|
||||
background-color: #433;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,19 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Shared Stash</title>
|
||||
<title>Currency</title>
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
<head>
|
||||
<body>
|
||||
{% include "menu.html" %}
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}">All</a></li>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}/uniques">Uniques</a></li>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}/sets">Sets</a></li>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}/currency">Currency</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="currencies">
|
||||
<div>
|
||||
<table>
|
||||
|
||||
28
d2warehouse/app/templates/grail.html
Normal file
28
d2warehouse/app/templates/grail.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Grail</title>
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
<script src="/static/helpers.js"></script>
|
||||
<head>
|
||||
<body>
|
||||
{% include "menu.html" %}
|
||||
<button type="button" onclick="toggleCollected()">Toggle collected</button>
|
||||
<h1>Unique Items</h1>
|
||||
Progress: {{uniques.count}}/{{uniques.total}} ({{uniques.progress | round(1)}}%)
|
||||
<ul class="grail">
|
||||
{% for name,collected in uniques.list.items() %}
|
||||
<li class="{{ 'collected' if collected else 'uncollected' }}">{{name}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<h1>Set Items</h1>
|
||||
Progress: {{sets.count}}/{{sets.total}} ({{sets.progress | round(1)}}%)
|
||||
<ul class="grail">
|
||||
{% for name,collected in sets.list.items() %}
|
||||
<li class="{{ 'collected' if collected else 'uncollected' }}">{{name}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<script src="/static/helpers.js"></script>
|
||||
<head>
|
||||
<body>
|
||||
{% include "menu.html" %}
|
||||
<form action="/stash/{{stash_name}}/store" method="POST">
|
||||
{% for tab in stash.tabs %}
|
||||
{% set tabloop = loop %}
|
||||
|
||||
@@ -2,13 +2,23 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Storage</title>
|
||||
<title>Storage - {{category or 'all'}}</title>
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
<head>
|
||||
<body>
|
||||
{% include "menu.html" %}
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}">All</a></li>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}/uniques">Uniques</a></li>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}/sets">Sets</a></li>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}/misc">Misc</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<form action="/storage/{{stash_name}}/take" method="POST">
|
||||
<div>
|
||||
<!-- TODO: Include item.html -->
|
||||
There are {{ storage_items | length }} items.
|
||||
{% for db_id, item in storage_items.items() %}
|
||||
<div class="storage-item-entry">
|
||||
<input type="checkbox" name="item_{{db_id}}" id="item_{{db_id}}" value="take" />
|
||||
|
||||
8
d2warehouse/app/templates/menu.html
Normal file
8
d2warehouse/app/templates/menu.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/stash/{{stash_name or 'softcore'}}">Stash</a></li>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}">Storage</a></li>
|
||||
<li><a href="/grail/{{stash_name or 'softcore'}}">Grail</a></li>
|
||||
<li><a href="/currency/{{stash_name or 'softcore'}}">Currency</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
Reference in New Issue
Block a user