Separate bricktracker sets from rebrickable sets (dedup), introduce custom checkboxes

This commit is contained in:
2025-01-24 10:36:24 +01:00
parent 57d9f167a5
commit d063062780
88 changed files with 1560 additions and 748 deletions
+1 -1
View File
@@ -47,7 +47,7 @@
<div class="card mb-3 col-6">
<div class="card-header">
<h5 class="mb-0">
<span class="badge text-bg-secondary fw-normal"><i class="ri-hashtag"></i> <span id="add-card-number"></span></span>
<span class="badge text-bg-secondary fw-normal"><i class="ri-hashtag"></i> <span id="add-card-set"></span></span>
<span id="add-card-name"></span>
</h5>
</div>
+8 -1
View File
@@ -12,7 +12,9 @@
<h5 class="mb-0"><i class="ri-settings-4-line"></i> Administration</h5>
</div>
<div class="accordion accordion-flush" id="admin">
{% if delete_database %}
{% if delete_checkbox %}
{% include 'admin/checkbox/delete.html' %}
{% elif delete_database %}
{% include 'admin/database/delete.html' %}
{% elif drop_database %}
{% include 'admin/database/drop.html' %}
@@ -28,6 +30,7 @@
{% endif %}
{% include 'admin/theme.html' %}
{% include 'admin/retired.html' %}
{% include 'admin/checkbox.html' %}
{% include 'admin/database.html' %}
{% include 'admin/configuration.html' %}
{% endif %}
@@ -38,4 +41,8 @@
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='scripts/changer.js') }}"></script>
{% endblock %}
+67
View File
@@ -0,0 +1,67 @@
{% import 'macro/accordion.html' as accordion %}
{{ accordion.header('Checkboxes', 'checkbox', 'admin', expanded=open_checkbox, icon='checkbox-line', class='p-0') }}
{% if error %}<div class="alert alert-danger m-2" role="alert"><strong>Error:</strong> {{ error }}.</div>{% endif %}
{% if database_error %}<div class="alert alert-danger m-2" role="alert"><strong>Error:</strong> {{ database_error }}.</div>{% endif %}
<ul class="list-group list-group-flush">
{% if brickset_checkboxes | length %}
{% for checkbox in brickset_checkboxes %}
<li class="list-group-item">
<form action="{{ url_for('admin_checkbox.rename', id=checkbox.fields.id) }}" method="post" class="row row-cols-lg-auto g-3 align-items-center">
<div class="col-12 flex-grow-1">
<label class="visually-hidden" for="name-{{ checkbox.fields.id }}">Name</label>
<div class="input-group">
<div class="input-group-text">Name</div>
<input type="text" class="form-control" id="name-{{ checkbox.fields.id }}" name="name" value="{{ checkbox.fields.name }}">
<button type="submit" class="btn btn-primary"><i class="ri-edit-line"></i> Rename</button>
</div>
</div>
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="grid-{{ checkbox.fields.id }}"
data-changer-id="{{ checkbox.fields.id }}" data-changer-prefix="grid" data-changer-url="{{ url_for('admin_checkbox.update_status', id=checkbox.fields.id, name='displayed_on_grid')}}"
{% if checkbox.fields.displayed_on_grid %}checked{% endif %} autocomplete="off">
<label class="form-check-label" for="grid-{{ checkbox.fields.id }}">
Displayed on the Set Grid
<i id="status-grid-{{ checkbox.fields.id }}" class="mb-1"></i>
</label>
</div>
</div>
<div class="col-12">
<a href="{{ url_for('admin_checkbox.delete', id=checkbox.fields.id) }}" class="btn btn-danger" role="button"><i class="ri-delete-bin-2-line"></i> Delete</a>
</div>
</form>
</li>
{% endfor %}
{% else %}
<li class="list-group-item"><i class="ri-error-warning-line"></i> No checkbox found.</li>
{% endif %}
<li class="list-group-item">
<form action="{{ url_for('admin_checkbox.add') }}" method="post" class="row row-cols-lg-auto g-3 align-items-center">
<div class="col-12 flex-grow-1">
<label class="visually-hidden" for="name">Name</label>
<div class="input-group">
<div class="input-group-text">Name</div>
<input type="text" class="form-control" id="name" name="name" value="">
</div>
</div>
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="grid" name="grid" checked>
<label class="form-check-label" for="grid-">
Displayed on the Set Grid
</label>
</div>
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary"><i class="ri-add-circle-line"></i> Add</button>
</div>
</form>
</li>
</ul>
{{ accordion.footer() }}
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", () => {
setup_changers();
});
</script>
+25
View File
@@ -0,0 +1,25 @@
{% import 'macro/accordion.html' as accordion %}
{{ accordion.header('Checkbox danger zone', 'checkbox-danger', 'admin', expanded=true, danger=true, class='text-end') }}
<form action="{{ url_for('admin_checkbox.do_delete', id=checkbox.fields.id) }}" method="post">
{% if error %}<div class="alert alert-danger text-start" role="alert"><strong>Error:</strong> {{ error }}.</div>{% endif %}
<div class="alert alert-danger text-center" role="alert">You are about to <strong>delete a checkbox</strong>. This action is irreversible.</div>
<div class="row row-cols-lg-auto g-3 align-items-center">
<div class="col-12 flex-grow-1">
<div class="input-group">
<div class="input-group-text">Name</div>
<input type="text" class="form-control" value="{{ checkbox.fields.name }}" disabled>
</div>
</div>
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" {% if checkbox.fields.displayed_on_grid %}checked{% endif %} disabled>
<span class="form-check-label">Displayed on the Set Grid</span>
</div>
</div>
</div>
<hr class="border-bottom">
<a class="btn btn-danger" href="{{ url_for('admin.admin', open_checkbox=true) }}" role="button"><i class="ri-arrow-left-long-line"></i> Back to the admin</a>
<button type="submit" class="btn btn-danger"><i class="ri-delete-bin-2-line"></i> Delete <strong>the checkbox</strong></button>
</form>
{{ accordion.footer() }}
+1 -1
View File
@@ -1,6 +1,6 @@
{% extends 'base.html' %}
{% block title %} - Delete a set {{ item.fields.set_num }} ({{ item.fields.u_id }}){% endblock %}
{% block title %} - Delete a set {{ item.fields.set }} ({{ item.fields.id }}){% endblock %}
{% block main %}
<div class="container">
+2 -2
View File
@@ -8,9 +8,9 @@
</div>
<div class="accordion accordion-flush" id="instructions">
{{ accordion.header('Instructions danger zone', 'instructions-delete', 'instructions', expanded=true, danger=true) }}
{% if item.brickset %}
{% if item.rebrickable %}
<div class="d-flex justify-content-center">
{% with item=item.brickset %}
{% with item=item.rebrickable %}
{% include 'set/mini.html' %}
{% endwith %}
</div>
+2 -2
View File
@@ -8,9 +8,9 @@
</div>
<div class="accordion accordion-flush" id="instructions">
{{ accordion.header('Management', 'instructions-rename', 'instructions', expanded=true) }}
{% if item.brickset %}
{% if item.rebrickable %}
<div class="d-flex justify-content-center">
{% with item=item.brickset %}
{% with item=item.rebrickable %}
{% include 'set/mini.html' %}
{% endwith %}
</div>
+4 -4
View File
@@ -27,11 +27,11 @@
<span class="badge rounded-pill text-bg-light border fw-normal"><i class="ri-calendar-line"></i> {{ item.human_time() }}</span>
</td>
<td>
{% if item.number %}<span class="badge text-bg-secondary fw-normal"><i class="ri-hashtag"></i> {{ item.number }}</span>{% endif %}
{% if item.brickset %}{{ item.brickset.fields.name }}{% endif %}
{% if item.set %}<span class="badge text-bg-secondary fw-normal"><i class="ri-hashtag"></i> {{ item.set }}</span>{% endif %}
{% if item.rebrickable %}{{ item.rebrickable.fields.name }}{% endif %}
</td>
{% if item.brickset %}
{{ table.image(item.brickset.url_for_image(), caption=item.brickset.fields.name, alt=item.brickset.fields.set_num) }}
{% if item.rebrickable %}
{{ table.image(item.rebrickable.url_for_image(), caption=item.rebrickable.fields.name, alt=item.rebrickable.fields.set) }}
{% else %}
<td><i class="ri-file-warning-line"></i></td>
{% endif %}
+9 -5
View File
@@ -1,10 +1,14 @@
{% macro checkbox(kind, id, text, url, checked, delete=false) %}
{% macro checkbox(prefix, id, text, url, checked, delete=false) %}
{% if g.login.is_authenticated() %}
<input class="form-check-input" type="checkbox" id="{{ kind }}-{{ id }}" {% if checked %}checked{% endif %}
{% if not delete %}onchange="change_set_checkbox_status(this, '{{ kind }}', '{{ id }}', '{{ url }}')"{% else %}disabled{% endif %}
<input class="form-check-input" type="checkbox" id="{{ prefix }}-{{ id }}" {% if checked %}checked{% endif %}
{% if not delete %}
data-changer-id="{{ id }}" data-changer-prefix="{{ prefix }}" data-changer-url="{{ url }}" data-changer-parent="set"
{% else %}
disabled
{% endif %}
autocomplete="off">
<label class="form-check-label" for="{{ kind }}-{{ id }}">
{{ text }} <i id="status-{{ kind }}-{{ id }}" class="mb-1"></i>
<label class="form-check-label" for="{{ prefix }}-{{ id }}">
{{ text }} <i id="status-{{ prefix }}-{{ id }}" class="mb-1"></i>
</label>
{% else %}
<input class="form-check-input text-reset" type="checkbox" {% if checked %}checked{% endif %} disabled>
+3 -1
View File
@@ -1,6 +1,6 @@
{% extends 'base.html' %}
{% block title %} - Set {{ item.fields.name}} ({{ item.fields.set_num }}){% endblock %}
{% block title %} - Set {{ item.fields.name}} ({{ item.fields.set }}){% endblock %}
{% block main %}
<div class="container">
@@ -15,10 +15,12 @@
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", () => {
baguetteBox.run('[data-lightbox]');
setup_changers();
});
</script>
{% endblock %}
{% block scripts %}
<script src="{{ url_for('static', filename='scripts/changer.js') }}"></script>
<script src="{{ url_for('static', filename='scripts/set.js') }}"></script>
{% endblock %}
+19 -21
View File
@@ -3,18 +3,20 @@
{% import 'macro/card.html' as card %}
{% import 'macro/form.html' as form %}
<div {% if not solo %}id="set-{{ item.fields.u_id }}"{% endif %} class="card mb-3 flex-fill {% if solo %}card-solo{% endif %}"
{% if not solo %}data-index="{{ index }}" data-number="{{ item.fields.set_number }}" data-name="{{ item.fields.name | lower }}" data-parts="{{ item.fields.num_parts }}"
data-year="{{ item.fields.year }}" data-theme="{{ item.theme_name | lower }}" data-minifigures-collected="{{ item.fields.mini_col }}" data-set-collected="{{ item.fields.set_col }}"
data-set-checked="{{ item.fields.set_check }}" data-has-missing="{{ (item.fields.total_missing > 0) | int }}" data-has-minifigures="{{ (item.fields.total_minifigures > 0) | int }}"
data-has-missing-instructions="{{ (not (item.instructions | length)) | int }}" data-minifigures="{{ item.fields.total_minifigures }}" data-missing="{{ item.fields.total_missing }}"{% endif %}
<div {% if not solo %}id="set-{{ item.fields.id }}"{% endif %} class="card mb-3 flex-fill {% if solo %}card-solo{% endif %}"
{% if not solo %}
data-index="{{ index }}" data-number="{{ item.fields.set }}" data-name="{{ item.fields.name | lower }}" data-parts="{{ item.fields.number_of_parts }}"
data-year="{{ item.fields.year }}" data-theme="{{ item.theme.name | lower }}" data-minifigures="{{ item.fields.total_minifigures }}" data-has-minifigures="{{ (item.fields.total_minifigures > 0) | int }}"
data-has-missing="{{ (item.fields.total_missing > 0) | int }}" data-has-missing-instructions="{{ (not (item.instructions | length)) | int }}" data-missing="{{ item.fields.total_missing }}"
{% for checkbox in brickset_checkboxes %}data-{{ checkbox.as_dataset() }}="{{ item.fields[checkbox.as_column()] }}" {% endfor %}
{% endif %}
>
{{ card.header(item, item.fields.name, solo=solo, number=item.fields.set_num) }}
{{ card.image(item, solo=solo, last=last, caption=item.fields.name, alt=item.fields.set_num) }}
<div class="card-body {% if not solo %}p-1{% endif %}">
{{ badge.theme(item.theme_name, solo=solo, last=last) }}
{{ card.header(item, item.fields.name, solo=solo, number=item.fields.number) }}
{{ card.image(item, solo=solo, last=last, caption=item.fields.name, alt=item.fields.number) }}
<div class="card-body {% if solo %}border-bottom{% else %}p-1{% endif %}">
{{ badge.theme(item.theme.name, solo=solo, last=last) }}
{{ badge.year(item.fields.year, solo=solo, last=last) }}
{{ badge.parts(item.fields.num_parts, solo=solo, last=last) }}
{{ badge.parts(item.fields.number_of_parts, solo=solo, last=last) }}
{{ badge.total_minifigures(item.fields.total_minifigures, solo=solo, last=last) }}
{{ badge.total_missing(item.fields.total_missing, solo=solo, last=last) }}
{% if not last %}
@@ -24,17 +26,13 @@
{{ badge.rebrickable(item, solo=solo, last=last) }}
{% endif %}
</div>
{% if not tiny %}
<ul class="list-group list-group-flush card-check">
<li class="list-group-item {% if not solo %}p-1{% endif %}">
{{ form.checkbox('minifigures-collected', item.fields.u_id, 'Minifigures are collected', item.url_for_minifigures_collected(), item.fields.mini_col, delete=delete) }}
</li>
<li class="list-group-item {% if not solo %}p-1{% endif %}">
{{ form.checkbox('set-checked', item.fields.u_id, 'Set is checked', item.url_for_set_checked(), item.fields.set_check, delete=delete) }}
</li>
<li class="list-group-item {% if not solo %}p-1{% endif %}">
{{ form.checkbox('set-collected', item.fields.u_id, 'Set is collected and boxed', item.url_for_set_collected(), item.fields.set_col, delete=delete) }}
</li>
{% if not tiny and brickset_checkboxes | length %}
<ul class="list-group list-group-flush card-check border-top-0 {% if not solo %}border-bottom-0{% endif %}">
{% for checkbox in brickset_checkboxes %}
<li class="list-group-item {% if not solo %}p-1{% endif %}">
{{ form.checkbox(checkbox.as_dataset(), item.fields.id, checkbox.fields.name, checkbox.status_url(item.fields.id), item.fields[checkbox.as_column()], delete=delete) }}
</li>
{% endfor %}
</ul>
{% endif %}
{% if solo %}
+4 -4
View File
@@ -2,11 +2,11 @@
{% import 'macro/card.html' as card %}
<div class="card mb-3">
{{ card.header(item, item.fields.name, solo=true, number=item.fields.set_num) }}
{{ card.image(item, solo=true, last=false, caption=item.fields.name, alt=item.fields.set_num) }}
{{ card.header(item, item.fields.name, solo=true, number=item.fields.set) }}
{{ card.image(item, solo=true, last=false, caption=item.fields.name, alt=item.fields.set) }}
<div class="card-body p-1">
{{ badge.theme(item.theme_name) }}
{{ badge.theme(item.theme.name) }}
{{ badge.year(item.fields.year) }}
{{ badge.parts(item.fields.num_parts) }}
{{ badge.parts(item.fields.number_of_parts) }}
</div>
</div>
+6 -4
View File
@@ -19,12 +19,13 @@
<span class="input-group-text">Filter</span>
<select id="grid-filter" class="form-select form-select-sm" autocomplete="off">
<option value="" selected>All sets</option>
<option value="minifigures-collected">Minifigures are collected</option>
<option value="set-collected">Set is collected and boxed</option>
<option value="set-checked">Set is checked</option>
<option value="-has-missing">Set is complete</option>
<option value="has-missing">Set has missing pieces</option>
<option value="has-missing-instructions">Set has missing instructions</option>
{% for checkbox in brickset_checkboxes %}
<option value="{{ checkbox.as_dataset() }}">{{ checkbox.fields.name }}</option>
<option value="-{{ checkbox.as_dataset() }}">NOT: {{ checkbox.fields.name }}</option>
{% endfor %}
</select>
<select id="grid-theme" class="form-select form-select-sm" autocomplete="off">
<option value="" selected>All themes</option>
@@ -69,6 +70,7 @@
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", () => {
new BrickGrid("grid");
setup_changers();
});
</script>
</div>
@@ -79,5 +81,5 @@
{% block scripts %}
<script src="{{ url_for('static', filename='scripts/grid.js') }}"></script>
<script src="{{ url_for('static', filename='scripts/set.js') }}"></script>
<script src="{{ url_for('static', filename='scripts/changer.js') }}"></script>
{% endblock %}
+5 -5
View File
@@ -18,14 +18,14 @@
</thead>
<tbody>
{% for item in table_collection %}
{% set retirement_date = retired.get(item.fields.set_num) %}
{% set retirement_date = retired.get(item.fields.set) %}
<tr>
{{ table.image(item.url_for_image(), caption=item.fields.name, alt=item.fields.set_num) }}
<td>{{ item.fields.set_num }}</td>
{{ table.image(item.url_for_image(), caption=item.fields.name, alt=item.fields.set) }}
<td>{{ item.fields.set }}</td>
<td>{{ item.fields.name }}</td>
<td>{{ item.theme_name }}</td>
<td>{{ item.theme.name }}</td>
<td>{{ item.fields.year }}</td>
<td>{{ item.fields.num_parts }}</td>
<td>{{ item.fields.number_of_parts }}</td>
<td>{% if retirement_date %}{{ retirement_date }}{% else %}<span class="badge rounded-pill text-bg-light border">Not found</span>{% endif %}</td>
{% if g.login.is_authenticated() %}
<td>