Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
bfbe2885dc |
46
.gitea/workflows/build.yaml
Normal file
46
.gitea/workflows/build.yaml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
name: Build and push image
|
||||||
|
|
||||||
|
on:
|
||||||
|
# schedule:
|
||||||
|
# - cron: '59 4 * * 0'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Check out repository code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
-
|
||||||
|
run: |
|
||||||
|
echo "The job was automatically triggered by a ${{ gitea.event_name }} event."
|
||||||
|
echo "The ${{ gitea.repository }} repository has been cloned to the runner."
|
||||||
|
-
|
||||||
|
name: Set up QEMU
|
||||||
|
uses: https://github.com/docker/setup-qemu-action@v3
|
||||||
|
with:
|
||||||
|
platforms: 'arm64,amd64'
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: https://github.com/docker/setup-buildx-action@v3
|
||||||
|
-
|
||||||
|
name: Login to Registry
|
||||||
|
uses: https://github.com/docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ secrets.PACKAGE_URL }}
|
||||||
|
username: ${{ gitea.actor }}
|
||||||
|
password: ${{ secrets.PACKAGE_TOKEN }}
|
||||||
|
-
|
||||||
|
name: Build and push
|
||||||
|
uses: https://github.com/docker/build-push-action@v6
|
||||||
|
env:
|
||||||
|
ACTIONS_RUNTIME_TOKEN: ''
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
tags: ${{ secrets.PACKAGE_URL }}/${{ gitea.repository }}:latest
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
41
README.md
41
README.md
@ -4,8 +4,6 @@ A web application for organizing and tracking LEGO sets, parts, and minifigures.
|
|||||||
|
|
||||||
> **Screenshots at the end of the readme!**
|
> **Screenshots at the end of the readme!**
|
||||||
|
|
||||||
<a href="https://www.buymeacoffee.com/frederikb" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="41" width="174"></a>
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Track multiple LEGO sets with their parts and minifigures
|
- Track multiple LEGO sets with their parts and minifigures
|
||||||
@ -35,15 +33,9 @@ mkdir static/{sets,instructions,parts,minifigs}
|
|||||||
```
|
```
|
||||||
REBRICKABLE_API_KEY=your_api_key_here
|
REBRICKABLE_API_KEY=your_api_key_here
|
||||||
DOMAIN_NAME=https://your.domain.com
|
DOMAIN_NAME=https://your.domain.com
|
||||||
LINKS=True
|
|
||||||
RANDOM=True
|
|
||||||
```
|
```
|
||||||
|
|
||||||
- If using locally, set `DOMAIN_NAME` to `http://localhost:3333`.
|
If using locally, set `DOMAIN_NAME` to `http://localhost:3333`.
|
||||||
|
|
||||||
- `LINKS`: Set to `True` in order for set numbers to be links to Rebrickable on the front page.
|
|
||||||
|
|
||||||
- `RANDOM`: Set to `True` in order for the sets to shuffle on the frontpage each load.
|
|
||||||
|
|
||||||
3. Deploy with Docker Compose:
|
3. Deploy with Docker Compose:
|
||||||
```bash
|
```bash
|
||||||
@ -64,7 +56,15 @@ mkdir -p static/{sets,instructions,parts,minifigs}
|
|||||||
touch app.db
|
touch app.db
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Create Docker Compose file:
|
2. Create a `.env` file with your configuration:
|
||||||
|
```
|
||||||
|
REBRICKABLE_API_KEY=your_api_key_here
|
||||||
|
DOMAIN_NAME=https://your.domain.com
|
||||||
|
```
|
||||||
|
|
||||||
|
If using locally, set `DOMAIN_NAME` to `http://localhost:3333`.
|
||||||
|
|
||||||
|
3. Create Docker Compose file:
|
||||||
```bash
|
```bash
|
||||||
services:
|
services:
|
||||||
bricktracker:
|
bricktracker:
|
||||||
@ -74,21 +74,14 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "3333:3333"
|
- "3333:3333"
|
||||||
volumes:
|
volumes:
|
||||||
|
- ./.env:/app/.env
|
||||||
- ./static/parts:/app/static/parts
|
- ./static/parts:/app/static/parts
|
||||||
- ./static/instructions:/app/static/instructions
|
- ./static/instructions:/app/static/instructions
|
||||||
- ./static/sets:/app/static/sets
|
- ./static/sets:/app/static/sets
|
||||||
- ./static/minifigs:/app/static/minifigs
|
|
||||||
- ./app.db:/app/app.db
|
- ./app.db:/app/app.db
|
||||||
environment:
|
|
||||||
- REBRICKABLE_API_KEY=your_api_key_here
|
|
||||||
- DOMAIN_NAME=https://your.domain.com
|
|
||||||
- LINKS=True #optional, enables set numbers to be Rebrickable links on the front page
|
|
||||||
- RANDOM=False #optional, set to True if you want your front page to be shuffled on load
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If using locally, set `DOMAIN_NAME` to `http://localhost:3333`.
|
4. Deploy with Docker Compose:
|
||||||
|
|
||||||
3. Deploy with Docker Compose:
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
@ -123,8 +116,6 @@ Instructions can be added to the `static/instructions` folder. Instructions **mu
|
|||||||
|
|
||||||
Instructions are not automatically downloaded!
|
Instructions are not automatically downloaded!
|
||||||
|
|
||||||
Instructions can be uploaded from the webinterface (desktop-only) using the `Upload` button in the navbar.
|
|
||||||
|
|
||||||
## Docker Configuration
|
## Docker Configuration
|
||||||
|
|
||||||
The application uses two main configuration files:
|
The application uses two main configuration files:
|
||||||
@ -220,12 +211,6 @@ Sets are added from rebrickable using your own API key. Set numbers are checked
|
|||||||
|
|
||||||
### Wishlist
|
### Wishlist
|
||||||
|
|
||||||
![](https://xbackbone.baerentsen.space/LaMU8/qUYeHAKU93.png/raw)
|
![](https://xbackbone.baerentsen.space/LaMU8/hACAbArO44.png/raw)
|
||||||
|
|
||||||
Sets are added from rebrickable and again checked against sets.csv. If you can't add a brand new set, consider updating your data from the [`/config` page](https://xbackbone.baerentsen.space/LaMU8/lErImaCE12.png/raw). Press the delete button to remove a set. Known Issue: If multiple sets of the same number is added, they will all be deleted.
|
Sets are added from rebrickable and again checked against sets.csv. If you can't add a brand new set, consider updating your data from the [`/config` page](https://xbackbone.baerentsen.space/LaMU8/lErImaCE12.png/raw). Press the delete button to remove a set. Known Issue: If multiple sets of the same number is added, they will all be deleted.
|
||||||
|
|
||||||
Wishlist uses *unofficial* retirement data.
|
|
||||||
|
|
||||||
Each set number, links to bricklink for pricecheck.
|
|
||||||
|
|
||||||
|
|
||||||
|
168
app.py
168
app.py
@ -12,14 +12,9 @@ import rebrick #rebrickable api
|
|||||||
import requests # request img from web
|
import requests # request img from web
|
||||||
import shutil # save img locally
|
import shutil # save img locally
|
||||||
import eventlet
|
import eventlet
|
||||||
from collections import defaultdict
|
|
||||||
import plotly.express as px
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
from downloadRB import download_and_unzip,get_nil_images,get_retired_sets
|
from downloadRB import download_and_unzip,get_nil_images,get_retired_sets
|
||||||
from db import initialize_database,get_rows,delete_tables
|
from db import initialize_database,get_rows,delete_tables
|
||||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||||
from werkzeug.utils import secure_filename
|
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
@ -27,21 +22,8 @@ app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_
|
|||||||
socketio = SocketIO(app,cors_allowed_origins=os.getenv("DOMAIN_NAME"))
|
socketio = SocketIO(app,cors_allowed_origins=os.getenv("DOMAIN_NAME"))
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
if os.getenv("RANDOM") == 'True':
|
|
||||||
RANDOM = True
|
|
||||||
else:
|
|
||||||
RANDOM = False
|
|
||||||
|
|
||||||
if os.getenv("LINKS"):
|
|
||||||
LINKS = os.getenv("LINKS")
|
|
||||||
else:
|
|
||||||
LINKS = False
|
|
||||||
|
|
||||||
DIRECTORY = os.path.join(os.getcwd(), 'static', 'instructions')
|
DIRECTORY = os.path.join(os.getcwd(), 'static', 'instructions')
|
||||||
|
|
||||||
UPLOAD_FOLDER = DIRECTORY
|
|
||||||
ALLOWED_EXTENSIONS = {'pdf'}
|
|
||||||
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
|
||||||
|
|
||||||
@app.route('/favicon.ico')
|
@app.route('/favicon.ico')
|
||||||
|
|
||||||
@ -93,49 +75,6 @@ def hyphen_split(a):
|
|||||||
return a.split("-")[0]
|
return a.split("-")[0]
|
||||||
return "-".join(a.split("-", 2)[:2])
|
return "-".join(a.split("-", 2)[:2])
|
||||||
|
|
||||||
def allowed_file(filename):
|
|
||||||
return '.' in filename and \
|
|
||||||
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
|
||||||
|
|
||||||
@app.route('/upload',methods=['GET','POST'])
|
|
||||||
def uploadInst():
|
|
||||||
if request.method == 'POST':
|
|
||||||
# check if the post request has the file part
|
|
||||||
if 'file' not in request.files:
|
|
||||||
flash('No file part')
|
|
||||||
return redirect(request.url)
|
|
||||||
file = request.files['file']
|
|
||||||
# If the user does not select a file, the browser submits an
|
|
||||||
# empty file without a filename.
|
|
||||||
if file.filename == '':
|
|
||||||
flash('No selected file')
|
|
||||||
return redirect(request.url)
|
|
||||||
if file and allowed_file(file.filename):
|
|
||||||
filename = secure_filename(file.filename)
|
|
||||||
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
|
|
||||||
return redirect('/')
|
|
||||||
return '''
|
|
||||||
<!doctype html>
|
|
||||||
<title>Upload instructions</title>
|
|
||||||
<h1>Upload instructions</h1>
|
|
||||||
<p>Files must be named like:</p>
|
|
||||||
<code><set number>-<version>-<part>.pdf</code>
|
|
||||||
<ul>
|
|
||||||
<li><code>7595-1.pdf</code> for set 7595</li>
|
|
||||||
<li><code>71039-2.pdf</code> for Moon Knight in <code>Collectible Minifigures: Marvel Series 2</code></li>
|
|
||||||
<li><code>71039-13.pdf</code> for the whole set <code>Collectible Minifigures: Marvel Series 2</code></li>
|
|
||||||
<li><code>10294-1-1.pdf</code> for the 1st pdf in the 10294 set
|
|
||||||
<li><code>10294-1-2.pdf</code> for the 2nd pdf in the 10294 set
|
|
||||||
<li><code>10294-1-3.pdf</code> for the 3rd pdf in the 10294 set
|
|
||||||
<li><code>10937-1-0.pdf</code> for the comic that comes with set 10937.
|
|
||||||
<li><code>10937-1-1.pdf</code> for the 1st pdf in the 10937 set
|
|
||||||
</ul>
|
|
||||||
<form method=post enctype=multipart/form-data>
|
|
||||||
<input type=file name=file>
|
|
||||||
<input type=submit value=Upload>
|
|
||||||
</form>
|
|
||||||
'''
|
|
||||||
|
|
||||||
@app.route('/delete/<tmp>',methods=['POST', 'GET'])
|
@app.route('/delete/<tmp>',methods=['POST', 'GET'])
|
||||||
def delete(tmp):
|
def delete(tmp):
|
||||||
|
|
||||||
@ -497,9 +436,7 @@ def config():
|
|||||||
def missing():
|
def missing():
|
||||||
conn = sqlite3.connect('app.db')
|
conn = sqlite3.connect('app.db')
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
#cursor.execute("SELECT part_num, color_id, element_id, part_img_url_id, SUM(quantity) AS total_quantity, GROUP_CONCAT(set_num, ', ') AS set_number FROM missing GROUP BY part_num, color_id, element_id;")
|
cursor.execute('SELECT part_num, color_id, element_id, part_img_url_id, SUM(quantity) AS total_quantity FROM missing GROUP BY part_num, color_id, element_id;')
|
||||||
|
|
||||||
cursor.execute("SELECT part_num, color_id, element_id, part_img_url_id, SUM(quantity) AS total_quantity, GROUP_CONCAT(set_num || ',' || u_id, ',') AS set_number FROM missing GROUP BY part_num, color_id, element_id, part_img_url_id ORDER BY part_num;")
|
|
||||||
|
|
||||||
results = cursor.fetchall()
|
results = cursor.fetchall()
|
||||||
missing_list = [list(i) for i in results]
|
missing_list = [list(i) for i in results]
|
||||||
@ -521,7 +458,7 @@ def missing():
|
|||||||
def parts():
|
def parts():
|
||||||
conn = sqlite3.connect('app.db')
|
conn = sqlite3.connect('app.db')
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('SELECT id, part_num, color_id, color_name, element_id, part_img_url_id, SUM(quantity) AS total_quantity, name FROM inventory GROUP BY part_num, part_img_url_id, color_id, color_name, element_id, name;')
|
cursor.execute('SELECT id, part_num, color_id, color_name, element_id, part_img_url_id, SUM(quantity) AS total_quantity, name FROM inventory GROUP BY id, part_num, part_img_url_id, color_id, color_name, element_id, name;')
|
||||||
|
|
||||||
results = cursor.fetchall()
|
results = cursor.fetchall()
|
||||||
missing_list = [list(i) for i in results]
|
missing_list = [list(i) for i in results]
|
||||||
@ -674,8 +611,7 @@ def index():
|
|||||||
|
|
||||||
results = cursor.fetchall()
|
results = cursor.fetchall()
|
||||||
set_list = [list(i) for i in results]
|
set_list = [list(i) for i in results]
|
||||||
if RANDOM:
|
|
||||||
random.shuffle(set_list)
|
|
||||||
cursor.execute('SELECT DISTINCT u_id from missing;')
|
cursor.execute('SELECT DISTINCT u_id from missing;')
|
||||||
results = cursor.fetchall()
|
results = cursor.fetchall()
|
||||||
missing_list = [list(i)[0] for i in results]
|
missing_list = [list(i)[0] for i in results]
|
||||||
@ -697,11 +633,11 @@ def index():
|
|||||||
|
|
||||||
files = [f for f in os.listdir(DIRECTORY) if f.endswith('.pdf')]
|
files = [f for f in os.listdir(DIRECTORY) if f.endswith('.pdf')]
|
||||||
#files = [re.match(r'^([\w]+-[\w]+)', f).group() for f in os.listdir(DIRECTORY) if f.endswith('.pdf')]
|
#files = [re.match(r'^([\w]+-[\w]+)', f).group() for f in os.listdir(DIRECTORY) if f.endswith('.pdf')]
|
||||||
files.sort()
|
print(files.sort())
|
||||||
|
|
||||||
return render_template('index.html',set_list=set_list,themes_list=theme_file,missing_list=missing_list,files=files,minifigs=minifigs,links=LINKS)
|
return render_template('index.html',set_list=set_list,themes_list=theme_file,missing_list=missing_list,files=files,minifigs=minifigs)
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'post':
|
||||||
set_num = request.form.get('set_num')
|
set_num = request.form.get('set_num')
|
||||||
u_id = request.form.get('u_id')
|
u_id = request.form.get('u_id')
|
||||||
minif = request.form.get('minif')
|
minif = request.form.get('minif')
|
||||||
@ -1089,97 +1025,5 @@ def save_number(tmp):
|
|||||||
|
|
||||||
return Response(status=204)
|
return Response(status=204)
|
||||||
|
|
||||||
@app.route('/dashboard')
|
|
||||||
def dashboard():
|
|
||||||
|
|
||||||
# Connect to the SQLite database
|
|
||||||
conn = sqlite3.connect('app.db')
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
# Execute the query
|
|
||||||
cursor.execute("SELECT year, set_num, theme_id FROM sets")
|
|
||||||
rows = cursor.fetchall()
|
|
||||||
|
|
||||||
# Initialize defaultdict to count occurrences
|
|
||||||
theme_counts = defaultdict(int)
|
|
||||||
year_counts = defaultdict(int)
|
|
||||||
|
|
||||||
# Count unique occurrences (removing duplicates)
|
|
||||||
seen = set() # To track unique combinations
|
|
||||||
for year, set_num, theme_id in rows:
|
|
||||||
# Create a unique identifier for each entry
|
|
||||||
entry_id = f"{year}-{set_num}-{theme_id}"
|
|
||||||
if entry_id not in seen:
|
|
||||||
theme_counts[theme_id] += 1
|
|
||||||
year_counts[year] += 1
|
|
||||||
seen.add(entry_id)
|
|
||||||
|
|
||||||
# Convert to regular dictionaries and sort
|
|
||||||
sets_by_theme = dict(sorted(
|
|
||||||
{k: v for k, v in theme_counts.items() if v > 1}.items()
|
|
||||||
))
|
|
||||||
|
|
||||||
sets_by_year = dict(sorted(
|
|
||||||
{k: v for k, v in year_counts.items() if v > 0}.items()
|
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
# Graphs using Plotly
|
|
||||||
fig_sets_by_theme = px.bar(
|
|
||||||
x=list(sets_by_theme.keys()),
|
|
||||||
y=list(sets_by_theme.values()),
|
|
||||||
labels={'x': 'Theme ID', 'y': 'Number of Sets'},
|
|
||||||
title='Number of Sets by Theme'
|
|
||||||
)
|
|
||||||
fig_sets_by_year = px.line(
|
|
||||||
x=list(sets_by_year.keys()),
|
|
||||||
y=list(sets_by_year.values()),
|
|
||||||
labels={'x': 'Year', 'y': 'Number of Sets'},
|
|
||||||
title='Number of Sets Released Per Year'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
most_frequent_parts = {
|
|
||||||
"Brick 1 x 1": 866,
|
|
||||||
"Plate 1 x 1": 782,
|
|
||||||
"Plate 1 x 2": 633,
|
|
||||||
"Plate Round 1 x 1 with Solid Stud": 409,
|
|
||||||
"Tile 1 x 2 with Groove": 382,
|
|
||||||
}
|
|
||||||
minifigs_by_set = {"10217-1": 12, "7595-1": 8, "10297-1": 7, "21338-1": 4, "4865-1": 4}
|
|
||||||
missing_parts_by_set = {"10297-1": 4, "10280-1": 1, "21301-1": 1, "21338-1": 1, "7595-1": 1}
|
|
||||||
|
|
||||||
|
|
||||||
fig_parts = px.bar(
|
|
||||||
x=list(most_frequent_parts.keys()),
|
|
||||||
y=list(most_frequent_parts.values()),
|
|
||||||
labels={'x': 'Part Name', 'y': 'Quantity'},
|
|
||||||
title='Most Frequent Parts'
|
|
||||||
)
|
|
||||||
fig_minifigs = px.bar(
|
|
||||||
x=list(minifigs_by_set.keys()),
|
|
||||||
y=list(minifigs_by_set.values()),
|
|
||||||
labels={'x': 'Set Number', 'y': 'Number of Minifigures'},
|
|
||||||
title='Minifigures by Set'
|
|
||||||
)
|
|
||||||
fig_missing_parts = px.bar(
|
|
||||||
x=list(missing_parts_by_set.keys()),
|
|
||||||
y=list(missing_parts_by_set.values()),
|
|
||||||
labels={'x': 'Set Number', 'y': 'Missing Parts Count'},
|
|
||||||
title='Missing Parts by Set'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Convert graphs to HTML
|
|
||||||
graphs = {
|
|
||||||
"sets_by_theme": fig_sets_by_theme.to_html(full_html=False),
|
|
||||||
"sets_by_year": fig_sets_by_year.to_html(full_html=False),
|
|
||||||
"parts": fig_parts.to_html(full_html=False),
|
|
||||||
"minifigs": fig_minifigs.to_html(full_html=False),
|
|
||||||
"missing_parts": fig_missing_parts.to_html(full_html=False),
|
|
||||||
}
|
|
||||||
|
|
||||||
return render_template("dashboard.html", graphs=graphs)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
socketio.run(app.run(host='0.0.0.0', debug=True, port=3333))
|
socketio.run(app.run(host='0.0.0.0', debug=True, port=3333))
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
flask
|
flask
|
||||||
flask_socketio
|
flask_socketio
|
||||||
pathlib
|
pathlib
|
||||||
plotly
|
|
||||||
pandas
|
|
||||||
numpy
|
numpy
|
||||||
rebrick
|
rebrick
|
||||||
requests
|
requests
|
||||||
|
@ -14,12 +14,6 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
|
|
||||||
.title-image {
|
|
||||||
object-fit: cover;
|
|
||||||
width: 100%;
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
table.sortable th:not(.sorttable_sorted):not(.sorttable_sorted_reverse):not(.sorttable_nosort):after {
|
table.sortable th:not(.sorttable_sorted):not(.sorttable_sorted_reverse):not(.sorttable_nosort):after {
|
||||||
content: " \25B4\25BE"
|
content: " \25B4\25BE"
|
||||||
}
|
}
|
||||||
@ -34,7 +28,6 @@ table.sortable tbody tr:nth-child(2n+1) td {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%; /* Ensure the table takes full width of its container */
|
width: 100%; /* Ensure the table takes full width of its container */
|
||||||
|
|
||||||
@ -253,8 +246,8 @@ background-color: white;
|
|||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<center>
|
<center>
|
||||||
<h1 class="title is-2 mt-4">{{ tmp }} - {{ title }}</h1>
|
<h1 class="title">{{ tmp }} - {{ title }}</h1>
|
||||||
<img class="lightbox-trigger title-image mb-5" id="cover" style='height: 150px; width: auto; object-fit: contain' src="/static/sets/{{ tmp }}.jpg" alt="{{ tmp }} - {{ title }}">
|
<img class="lightbox-trigger" id="cover" style='height: 150px; width: auto; object-fit: contain' src="/static/sets/{{ tmp }}.jpg" alt="{{ tmp }} - {{ title }}">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
{% block scripts %}{% endblock %}
|
{% block scripts %}{% endblock %}
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>LEGO Dashboard</title>
|
|
||||||
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 20px;
|
|
||||||
background-color: #f4f4f4;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
text-align: center;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.chart-container {
|
|
||||||
margin-bottom: 50px;
|
|
||||||
background-color: #fff;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.chart-container h2 {
|
|
||||||
text-align: center;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>LEGO Dashboard</h1>
|
|
||||||
|
|
||||||
<div class="chart-container">
|
|
||||||
<h2>Sets by Theme</h2>
|
|
||||||
{{ graphs['sets_by_theme']|safe }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="chart-container">
|
|
||||||
<h2>Sets Released Per Year</h2>
|
|
||||||
{{ graphs['sets_by_year']|safe }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="chart-container">
|
|
||||||
<h2>Most Frequent Parts</h2>
|
|
||||||
{{ graphs['parts']|safe }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="chart-container">
|
|
||||||
<h2>Minifigures by Set</h2>
|
|
||||||
{{ graphs['minifigs']|safe }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="chart-container">
|
|
||||||
<h2>Missing Parts by Set</h2>
|
|
||||||
{{ graphs['missing_parts']|safe }}
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -12,11 +12,6 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
.my-input::placeholder {
|
|
||||||
color: var(--bulma-grey-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@media only screen and (max-width: 480px) {
|
@media only screen and (max-width: 480px) {
|
||||||
/* horizontal scrollbar for tables if mobile screen */
|
/* horizontal scrollbar for tables if mobile screen */
|
||||||
.hidden-mobile {
|
.hidden-mobile {
|
||||||
@ -134,10 +129,6 @@ display: none !important;
|
|||||||
<a class="navbar-item hidden-mobile" href="/config">
|
<a class="navbar-item hidden-mobile" href="/config">
|
||||||
Config
|
Config
|
||||||
</a>
|
</a>
|
||||||
<span></span>
|
|
||||||
<a class="navbar-item hidden-mobile" href="/upload">
|
|
||||||
Upload
|
|
||||||
</a>
|
|
||||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navMenu">
|
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navMenu">
|
||||||
<span aria-hidden="true"></span>
|
<span aria-hidden="true"></span>
|
||||||
<span aria-hidden="true"></span>
|
<span aria-hidden="true"></span>
|
||||||
@ -168,9 +159,6 @@ display: none !important;
|
|||||||
<a class="navbar-item" id="toggleButton4">
|
<a class="navbar-item" id="toggleButton4">
|
||||||
Sets with missing pieces
|
Sets with missing pieces
|
||||||
</a>
|
</a>
|
||||||
<a class="navbar-item" id="toggleButton5">
|
|
||||||
Missing instructions
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-item has-dropdown is-hoverable">
|
<div class="navbar-item has-dropdown is-hoverable">
|
||||||
@ -208,8 +196,10 @@ display: none !important;
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<input class="input my-input" type="text" id="searchInput" onkeyup="searchFunction()" placeholder="Search title, set number, theme or parts...">
|
<input class="input"type="text" id="searchInput" onkeyup="searchFunction()" placeholder="Search title or set number...">
|
||||||
<!-- <center hidden="true">
|
<!-- <center hidden="true">
|
||||||
<button class="button button-outline" onclick="dynamicSort('set_id')">Sort by ID</button>
|
<button class="button button-outline" onclick="dynamicSort('set_id')">Sort by ID</button>
|
||||||
<button class="button button-outline" onclick="dynamicSort('set_year')">Sort by Year</button>
|
<button class="button button-outline" onclick="dynamicSort('set_year')">Sort by Year</button>
|
||||||
@ -228,20 +218,17 @@ display: none !important;
|
|||||||
<!-- Add more buttons for other text values if needed -->
|
<!-- Add more buttons for other text values if needed -->
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-container" id="gridContainer">
|
<div class="grid-container" id="gridContainer">
|
||||||
|
|
||||||
{% for i in set_list %}
|
{% for i in set_list %}
|
||||||
<div class="grid-item">
|
<div class="grid-item">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="columns" style="">
|
<div class="columns" style="">
|
||||||
<div class="column is-full" style="text-align: left;">
|
<div class="column is-full" style="text-align: left;">
|
||||||
<p class="is-size-5 searchTitle">
|
<p class="is-size-5 searchTitle">
|
||||||
{% if links == 'True' %}
|
<span style="font-weight: bold;" class="set_id">{{ i[0] }}</span> <span style="font-weight: bold;" class="set_name">{{ i[1] }}</span><br>
|
||||||
<a style="font-weight: bold;" href="https://rebrickable.com/sets/{{ i[0] }}" target="_blank" class="has-text-dark set_id">{{ i[0] }}</a> <span style="font-weight: bold;" class="has-text-dark set_name">{{ i[1] }}</span><br>
|
|
||||||
{% else %}
|
|
||||||
<span style="font-weight: bold;" class="has-text-dark set_id">{{ i[0] }}</span> <span style="font-weight: bold;" class="has-text-dark set_name">{{ i[1] }}</span><br>
|
|
||||||
{% endif %}
|
|
||||||
<a class="is-size-7 set_theme" style="color: #363636;">{{ i[3] }}</a> <a class="is-size-7" style="color: #363636;"> (<span class='set_year'>{{ i[2] }}</span>)</a>
|
<a class="is-size-7 set_theme" style="color: #363636;">{{ i[3] }}</a> <a class="is-size-7" style="color: #363636;"> (<span class='set_year'>{{ i[2] }}</span>)</a>
|
||||||
<span></span>
|
<span></span>
|
||||||
<a class="is-size-5" style="color: #363636;float:right;"><b>Parts:</b> <span class='set_parts'>{{ i[4] }}</span></a>
|
<a class="is-size-5" style="color: #363636;float:right;"><b>Parts:</b> {{ i[4] }}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<!--<div class="column" style="text-align: left;">
|
<!--<div class="column" style="text-align: left;">
|
||||||
@ -323,6 +310,7 @@ display: none !important;
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% set ns = namespace(found=false) %}
|
{% set ns = namespace(found=false) %}
|
||||||
|
|
||||||
{% for file in files %}
|
{% for file in files %}
|
||||||
{% if ns.found is sameas false and file.startswith(i[0]) %}
|
{% if ns.found is sameas false and file.startswith(i[0]) %}
|
||||||
|
|
||||||
@ -330,7 +318,7 @@ display: none !important;
|
|||||||
<div class="dropdown-trigger">
|
<div class="dropdown-trigger">
|
||||||
<button class="is-size-6" aria-haspopup="true" aria-controls="dropdown-menu3">
|
<button class="is-size-6" aria-haspopup="true" aria-controls="dropdown-menu3">
|
||||||
<span>
|
<span>
|
||||||
<a id="inst-link" class="is-size-6" style="color: #363636;">Inst.</a>
|
<a class="is-size-6" style="color: #363636;">Inst.</a>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -339,7 +327,7 @@ display: none !important;
|
|||||||
<!-- <a class="js-modal-trigger is-size-6" style="color: #363636;" data-id="{{ i[0] }}" data-target="modal-inst">Inst.</a> -->
|
<!-- <a class="js-modal-trigger is-size-6" style="color: #363636;" data-id="{{ i[0] }}" data-target="modal-inst">Inst.</a> -->
|
||||||
{% for x in files %}
|
{% for x in files %}
|
||||||
{% if x.startswith(i[0]) %}
|
{% if x.startswith(i[0]) %}
|
||||||
<a href="/files/{{ x }}" target="_blank" class="dropdown-item">{{ x }}</a>
|
<a href="/files/{{ x }}" class="dropdown-item">{{ x }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
@ -354,6 +342,9 @@ display: none !important;
|
|||||||
<a class="is-size-6" style="color: #363636;" href="/{{ i[0] }}/{{ i[11] }}">Inv.</a>
|
<a class="is-size-6" style="color: #363636;" href="/{{ i[0] }}/{{ i[11] }}">Inv.</a>
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -721,36 +712,6 @@ display: none !important;
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
const toggleButton = document.getElementById('toggleButton5');
|
|
||||||
let isHidden = true; // Initially, only show checked grid items
|
|
||||||
|
|
||||||
// Initialize visibility based on isHidden
|
|
||||||
updateVisibility();
|
|
||||||
|
|
||||||
toggleButton.addEventListener('click', function() {
|
|
||||||
// Toggle visibility and update grid items
|
|
||||||
isHidden = !isHidden;
|
|
||||||
updateVisibility();
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateVisibility() {
|
|
||||||
// Get all grid items
|
|
||||||
const gridItems = document.querySelectorAll('.grid-item');
|
|
||||||
|
|
||||||
// Iterate over each grid item
|
|
||||||
gridItems.forEach(function(item) {
|
|
||||||
const hasInst = item.querySelector('#inst-link');
|
|
||||||
if (isHidden || !hasInst ) {
|
|
||||||
// Show the grid item if it's hidden or the checkbox is checked
|
|
||||||
item.style.display = 'block';
|
|
||||||
} else {
|
|
||||||
// Hide the grid item if the checkbox is not checked
|
|
||||||
item.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function customSort(a, b) {
|
function customSort(a, b) {
|
||||||
// Function to remove leading articles
|
// Function to remove leading articles
|
||||||
|
@ -25,17 +25,6 @@ table.sortable tbody tr:nth-child(2n+1) td {
|
|||||||
background: #ecf0f1;
|
background: #ecf0f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
|
||||||
width: 100%; /* Ensure the table takes full width of its container */
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@media only screen and (max-width: 480px) {
|
@media only screen and (max-width: 480px) {
|
||||||
/* horizontal scrollbar for tables if mobile screen */
|
/* horizontal scrollbar for tables if mobile screen */
|
||||||
@ -84,9 +73,6 @@ td {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fixed-width {
|
|
||||||
max-width:100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Modal Styles */
|
/* Modal Styles */
|
||||||
@ -178,46 +164,36 @@ td {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="container">
|
|
||||||
<center>
|
<center>
|
||||||
<h1 class="title is-2 mt-4">Missing Pieces</h1>
|
<div class="center-table" >
|
||||||
<div style="overflow-x:auto;border-radius: 10px;border: 1px #ccc solid; box-shadow:0 0.5em 1em -0.125em hsla(221deg,14%,4%,0.1),0 0px 0 1px hsla(221deg,14%,4%,0.02);" >
|
<table id="data" class="table sortable tablemobile">
|
||||||
<table id="data" class="table sortable tablemobile">
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:65px;" class="fixed-width sorttable_nosort"></th>
|
<th class="sorttable_nosort"></th>
|
||||||
<th class="fixed-width" >Part Num</th>
|
<th >Part Num</th>
|
||||||
<th class="fixed-width" >Color</th>
|
<th >Color</th>
|
||||||
<th class="fixed-width" >Element ID</th>
|
<th >Element ID</th>
|
||||||
<th class="fixed-width" >Qty</th>
|
<th >Qty</th>
|
||||||
<th class="fixed-width sorttable_nosort">Sets</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for brick in missing_list %}
|
{% for brick in missing_list %}
|
||||||
<tr>
|
<tr>
|
||||||
{% if brick[4] == 'nil' %}
|
{% if brick[4] == 'nil' %}
|
||||||
<td style="background-color: #ffffff;"><img src="{{ '/static/none.jpg' }}" class="lightbox-trigger" alt="{{ brick[3] }}" style="height: 50px; width: 50px;margin:0;padding: 0;" loading="lazy"></td>
|
<td><img src="{{ '/static/none.jpg' }}" class="lightbox-trigger" alt="{{ brick[3] }}" style="height: 50px; width: 50px;margin:0;padding: 0;" loading="lazy"></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td style="background-color: #ffffff;"><img src="{{ '/static/parts/' + brick[3] + '.jpg' }}" alt="{{ brick[3] }}" class="lightbox-trigger" style="height: 50px; width: 50px;margin:0;padding: 0;" loading="lazy"></td>
|
<td><img src="{{ '/static/parts/' + brick[3] + '.jpg' }}" alt="{{ brick[3] }}" class="lightbox-trigger" style="height: 50px; width: 50px;margin:0;padding: 0;" loading="lazy"></td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<td><a target="_blank" href="https://www.bricklink.com/v2/catalog/catalogitem.page?P={{ brick[0] }}">{{ brick[0] }}</a></td>
|
<td><a target="_blank" href="https://www.bricklink.com/v2/catalog/catalogitem.page?P={{ brick[0] }}">{{ brick[0] }}</a></td>
|
||||||
<td>{{ brick[1] }}</td>
|
<td>{{ brick[1] }}</td>
|
||||||
<td><a target="_blank" href="https://www.rebrickable.com/elements/{{ brick[2] }}">{{ brick[2] }}</a></td>
|
<td><a target="_blank" href="https://www.rebrickable.com/elements/{{ brick[2] }}">{{ brick[2] }}</a></td>
|
||||||
<td>{{ brick[4] }}</td>
|
<td>{{ brick[4] }}</td>
|
||||||
<td>
|
|
||||||
{% set set_numbers = brick[5].split(',') %}
|
|
||||||
{% for i in range(0, set_numbers|length, 2) %}
|
|
||||||
<a href="{{ set_numbers[i] }}/{{ set_numbers[i+1] }}">{{ set_numbers[i] }}</a>{% if i != set_numbers|length - 2 %},{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</center>
|
</center>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div id="lightbox-modal">
|
<div id="lightbox-modal">
|
||||||
|
@ -24,23 +24,6 @@ table.sortable tbody tr:nth-child(2n+1) td {
|
|||||||
background: #ecf0f1;
|
background: #ecf0f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.my-input::placeholder {
|
|
||||||
color: var(--bulma-grey-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.name-class {
|
|
||||||
max-width: 300px;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-container {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@media only screen and (max-width: 480px) {
|
@media only screen and (max-width: 480px) {
|
||||||
/* horizontal scrollbar for tables if mobile screen */
|
/* horizontal scrollbar for tables if mobile screen */
|
||||||
img {
|
img {
|
||||||
@ -183,21 +166,15 @@ table.sortable tbody tr:nth-child(2n+1) td {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="container">
|
|
||||||
<center>
|
<center>
|
||||||
<h1 class="title is-2 mt-4">Parts</h1>
|
|
||||||
<div class="search-container">
|
|
||||||
<input class="input my-input" type="text" id="searchInput" onkeyup="searchFunction()" placeholder="Search number, color, name or element id...">
|
|
||||||
</div>
|
|
||||||
<div class="center-table" >
|
<div class="center-table" >
|
||||||
<div style="overflow-x:auto;border-radius: 10px;border: 1px #ccc solid; box-shadow:0 0.5em 1em -0.125em hsla(221deg,14%,4%,0.1),0 0px 0 1px hsla(221deg,14%,4%,0.02);" >
|
<table id="data" class="table tablemobile sortable">
|
||||||
<table id="data" class="table tablemobile sortable" style="widht:100%;height:100%;table-layout: fixed;">
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:65px;" class="sorttable_nosort"></th>
|
<th class="sorttable_nosort"></th>
|
||||||
<th >Part Num</th>
|
<th >Part Num</th>
|
||||||
<th >Color</th>
|
<th >Color</th>
|
||||||
<th class="name-class">Name</th>
|
<th class="name-class" >Name</th>
|
||||||
<th >element_id</th>
|
<th >element_id</th>
|
||||||
<th >total_quantity</th>
|
<th >total_quantity</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -219,10 +196,8 @@ table.sortable tbody tr:nth-child(2n+1) td {
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</center>
|
</center>
|
||||||
</div>
|
|
||||||
<div id="lightbox-modal">
|
<div id="lightbox-modal">
|
||||||
<div class="lightbox-wrapper">
|
<div class="lightbox-wrapper">
|
||||||
<span class="close">×</span>
|
<span class="close">×</span>
|
||||||
@ -274,37 +249,6 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function searchFunction() {
|
|
||||||
// Get input element and filter value
|
|
||||||
var input = document.getElementById('searchInput');
|
|
||||||
var filter = input.value.toUpperCase();
|
|
||||||
|
|
||||||
// Get the table and its rows
|
|
||||||
var table = document.getElementById('data');
|
|
||||||
var rows = table.getElementsByTagName('tr');
|
|
||||||
|
|
||||||
// Loop through all rows (skip the header row)
|
|
||||||
for (var i = 1; i < rows.length; i++) {
|
|
||||||
var cells = rows[i].getElementsByTagName('td');
|
|
||||||
var rowMatches = false;
|
|
||||||
|
|
||||||
// Loop through each cell in the row
|
|
||||||
for (var j = 0; j < cells.length; j++) {
|
|
||||||
var cellContent = cells[j].textContent || cells[j].innerText;
|
|
||||||
if (cellContent.toUpperCase().indexOf(filter) > -1) {
|
|
||||||
rowMatches = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show or hide the row based on the match
|
|
||||||
rows[i].style.display = rowMatches ? '' : 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div style="overflow-x:auto;border-radius: 10px;border: 1px #ccc solid; box-shadow:0 0.5em 1em -0.125em hsla(221deg,14%,4%,0.1),0 0px 0 1px hsla(221deg,14%,4%,0.02);" >
|
<div style="overflow-x:auto;">
|
||||||
<table id="data" class="table tablemobile sortable">
|
<table id="data" class="table tablemobile sortable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="sorttable_nosort" style="width:65px;">Image</th>
|
<th class="sorttable_nosort" style="width:65px;">Image</th>
|
||||||
<th class="hidden-mobile name-class">Name</th>
|
<th class="hidden-mobile name-class">Name</th>
|
||||||
<th class="hidden-mobile">Color</th>
|
<th class="hidden-mobile">Color</th>
|
||||||
<th style="text-align:center">Qty</th>
|
<th >Qty</th>
|
||||||
<th class="sorttable_nosort" style="text-align:right;">Missing</th>
|
<th class="sorttable_nosort" style="text-align:center;">Missing</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -58,12 +58,11 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="minifigs" style="margin: 2rem 0; padding: 2rem; border-bottom: 2px solid #eee;"></div>
|
|
||||||
|
|
||||||
{% if minifig_list | length > 0 %}
|
{% if minifig_list | length > 0 %}
|
||||||
<h1 class="title is-2 ">Minifigs</h1>
|
|
||||||
|
<h1 id="minifigs" class="title">Minifigs</h1>
|
||||||
{% for fig in minifig_list %}
|
{% for fig in minifig_list %}
|
||||||
<h2 class="subtitle is-4 mt-4 ">{{ fig[2] }} ({{ fig[0] }})</h2>
|
<h2 class="subtitle">{{ fig[2] }} ({{ fig[0] }})</h2>
|
||||||
|
|
||||||
<div style="display: flex; justify-content: center;">
|
<div style="display: flex; justify-content: center;">
|
||||||
<div style="display: flex; align-items: center;">
|
<div style="display: flex; align-items: center;">
|
||||||
@ -76,15 +75,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="center-table" style="overflow-x:auto;border-radius: 10px;border: 1px #ccc solid; box-shadow:0 0.5em 1em -0.125em hsla(221deg,14%,4%,0.1),0 0px 0 1px hsla(221deg,14%,4%,0.02);" >
|
<div class="center-table" >
|
||||||
<table id="data" class="table tablemobile sortable">
|
<table id="data" class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="fixed-width sorttable_nosort"></th>
|
<th class="fixed-width"></th>
|
||||||
<th style="text-align:left;margin:0px;" class="fixed-width hidden-mobile name-class">Name</th>
|
<th style="text-align:left;margin:0px;" class="fixed-width hidden-mobile name-class">Name</th>
|
||||||
<th class="fixed-width hidden-mobile">Color</th>
|
<th class="fixed-width hidden-mobile">Color</th>
|
||||||
<th class="fixed-width" style="text-align: center;">Qty</th>
|
<th class="fixed-width" style="text-align: center;">Qty</th>
|
||||||
<th class="fixed-width sorttable_nosort" style="text-align: right;">Missing</th>
|
<th class="fixed-width" style="text-align: center;">Missing</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -102,7 +101,7 @@
|
|||||||
<td style="text-align:left;margin:0px;" class="hidden-mobile name-class">{{ part[3] }}</td>
|
<td style="text-align:left;margin:0px;" class="hidden-mobile name-class">{{ part[3] }}</td>
|
||||||
<td class="hidden-mobile">{{ part[7] }}</td>
|
<td class="hidden-mobile">{{ part[7] }}</td>
|
||||||
<td style="text-align: center;">{{ part[8] * fig[3] }}</td>
|
<td style="text-align: center;">{{ part[8] * fig[3] }}</td>
|
||||||
<td class="centered-cell" style="text-align:right;">
|
<td class="centered-cell">
|
||||||
<div class="inputContainer">
|
<div class="inputContainer">
|
||||||
{% set ns = namespace(count='') %}
|
{% set ns = namespace(count='') %}
|
||||||
<form id="number-form">
|
<form id="number-form">
|
||||||
@ -142,7 +141,7 @@
|
|||||||
<div id="lightbox-modal">
|
<div id="lightbox-modal">
|
||||||
<div class="lightbox-wrapper">
|
<div class="lightbox-wrapper">
|
||||||
<span class="close">×</span>
|
<span class="close">×</span>
|
||||||
<img style="background-color: white;" class="lightbox-content" id="lightbox-image">
|
<img class="lightbox-content" id="lightbox-image">
|
||||||
<div class="text-container" id="lightbox-text"></div>
|
<div class="text-container" id="lightbox-text"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Wishlist</title>
|
<title>Withlist</title>
|
||||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, initial-scale=1.0"> <!-- CSS Reset -->
|
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, initial-scale=1.0"> <!-- CSS Reset -->
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
@ -242,7 +242,7 @@ background-color: white;
|
|||||||
{% for sets in wishlist %}
|
{% for sets in wishlist %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="{{ '/static/sets/' + sets[0] + '.jpg' }}" class="lightbox-trigger" style="height: 50px; width: auto;"></td>
|
<td><img src="{{ '/static/sets/' + sets[0] + '.jpg' }}" class="lightbox-trigger" style="height: 50px; width: auto;"></td>
|
||||||
<td style="text-align:center;margin:0px;" ><a class="has-text-dark" href="https://www.bricklink.com/v2/catalog/catalogitem.page?S={{ sets[0] }}" target="_blank">{{ sets[0] }}</a></td>
|
<td style="text-align:center;margin:0px;">{{ sets[0] }}</td>
|
||||||
<td>{{ sets[1] }}</td>
|
<td>{{ sets[1] }}</td>
|
||||||
<td style="text-align:center;" class="hidden-mobile">{{ sets[2] }}</td>
|
<td style="text-align:center;" class="hidden-mobile">{{ sets[2] }}</td>
|
||||||
<td style="text-align:center;" class="hidden-mobile">{{ sets[4] }}</td>
|
<td style="text-align:center;" class="hidden-mobile">{{ sets[4] }}</td>
|
||||||
|
Loading…
Reference in New Issue
Block a user