Massive rewrite
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - 404!{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="container">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<h4 class="alert-heading">Not found!</h4>
|
||||
{% if error %}
|
||||
<p>{{ error }}.</p>
|
||||
{% else %}
|
||||
<p>The page you are looking for does not exist.</p>
|
||||
{% endif %}
|
||||
<hr>
|
||||
<div class="text-end">
|
||||
<a class="btn btn-primary" href="{{ url_for('index.index') }}" role="button"><i class="ri-home-line"></i> Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,76 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - Add a set{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="container">
|
||||
{% if not config['HIDE_ADD_BULK_SET'].value %}
|
||||
<div class="alert alert-primary" role="alert">
|
||||
<h4 class="alert-heading">Too many to add?</h4>
|
||||
<p class="mb-0">You can import multiple sets at once with <a href="{{ url_for('add.bulk') }}" class="btn btn-primary"><i class="ri-function-add-line"></i> Bulk add</a>.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="ri-add-circle-line"></i> Add a set</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="add-fail" class="alert alert-danger d-none" role="alert"></div>
|
||||
<div id="add-complete" class="alert alert-success d-none" role="alert"></div>
|
||||
<div class="mb-3">
|
||||
<label for="add-set" class="form-label">Set number (only one)</label>
|
||||
<input type="text" class="form-control" id="add-set" placeholder="107-1 or 1642-1 or ...">
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="add-no-confirm">
|
||||
<label class="form-check-label" for="add-no-confirm">
|
||||
Add without confirmation
|
||||
</label>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="mb-3">
|
||||
<p>
|
||||
Progress <span id="add-count"></span>
|
||||
<span id="add-spinner" class="d-none">
|
||||
<span class="spinner-border spinner-border-sm" aria-hidden="true"></span>
|
||||
<span class="visually-hidden" role="status">Loading...</span>
|
||||
</span>
|
||||
</p>
|
||||
<div id="add-progress" class="progress" role="progressbar" aria-label="Add a set progress" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
|
||||
<div id="add-progress-bar" class="progress-bar" style="width: 0%"></div>
|
||||
</div>
|
||||
<p id="add-progress-message" class="text-center d-none"></p>
|
||||
</div>
|
||||
<div id="add-card" class="d-flex d-none justify-content-center">
|
||||
<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 id="add-card-name"></span>
|
||||
</h5>
|
||||
</div>
|
||||
<div id="add-card-image-container" class="card-img">
|
||||
<img id="add-card-image" loading="lazy">
|
||||
</div>
|
||||
<div id="add-card-footer" class="card-footer text-end d-none">
|
||||
<button id="add-card-dismiss" type="button" class="btn btn-danger"><i class="ri-close-line"></i> Dismiss</button>
|
||||
<button id="add-card-confirm" type="button" class="btn btn-primary"><i class="ri-check-double-line"></i> Confirm add</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-end">
|
||||
<span id="add-status-icon" class="me-1"></span><span id="add-status" class="me-1"></span><button id="add" type="button" class="btn btn-primary"><i class="ri-add-circle-line"></i> Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'set/socket.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='scripts/socket.js') }}"></script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,36 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - Administration{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="col-12">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="ri-settings-4-line"></i> Administration</h5>
|
||||
</div>
|
||||
<div class="accordion accordion-flush" id="admin">
|
||||
{% if delete_database %}
|
||||
{% include 'admin/database/delete.html' %}
|
||||
{% elif drop_database %}
|
||||
{% include 'admin/database/drop.html' %}
|
||||
{% elif import_database %}
|
||||
{% include 'admin/database/import.html' %}
|
||||
{% else %}
|
||||
{% include 'admin/logout.html' %}
|
||||
{% include 'admin/instructions.html' %}
|
||||
{% include 'admin/theme.html' %}
|
||||
{% include 'admin/retired.html' %}
|
||||
{% include 'admin/database.html' %}
|
||||
{% include 'admin/configuration.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,31 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
|
||||
{{ accordion.header('Configuration variables', 'configuration', 'admin', icon='list-settings-line') }}
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for entry in configuration %}
|
||||
<li class="list-group-item">
|
||||
<strong>{{ entry.name }}</strong>:
|
||||
{% if entry.value == none or entry.value == '' %}
|
||||
<span class="badge rounded-pill text-bg-secondary">Unset</span>
|
||||
{% elif entry.value == true %}
|
||||
<span class="badge rounded-pill text-bg-success">True</span>
|
||||
{% elif entry.value == false %}
|
||||
<span class="badge rounded-pill text-bg-danger">False</span>
|
||||
{% else %}
|
||||
{% if entry.is_secret() %}
|
||||
<span class="badge rounded-pill text-bg-success">Set</span>
|
||||
{% else %}
|
||||
<code>{{ entry.value }}</code>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if not entry.not_from_env %}
|
||||
<span class="badge rounded-pill text-bg-light border">Env: {{ entry.env_name }}</span>
|
||||
{% if entry.extra_name %}<span class="badge rounded-pill text-bg-light border">Env: {{ entry.extra_name }}</span>{% endif %}
|
||||
{% if entry.is_changed() %}
|
||||
<span class="badge rounded-pill text-bg-warning">Changed</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{{ accordion.footer() }}
|
||||
@@ -0,0 +1,48 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
|
||||
{{ accordion.header('Database', 'database', 'admin', expanded=open_database, icon='database-2-line') }}
|
||||
<h5 class="border-bottom">Status</h5>
|
||||
{% if exception %}<div class="alert alert-danger" role="alert">An exception occured while loading this page: {{ exception }}</div>{% endif %}
|
||||
{% if error %}<div class="alert alert-danger" role="alert"><strong>Error:</strong> {{ error }}.</div>{% endif %}
|
||||
{% if not is_init %}
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<p>The database is not initialized.</p>
|
||||
<hr>
|
||||
<form action="{{ url_for('admin.init_database') }}" method="post" class="text-end">
|
||||
<button type="submit" class="btn btn-warning"><i class="ri-reset-right-fill"></i> Initialize the database</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<p><i class="ri-checkbox-circle-line"></i> The database is initialized.</p>
|
||||
<p>
|
||||
<a href="{{ url_for('admin.download_database') }}" class="btn btn-primary" role="button"><i class="ri-download-line"></i> Download the database file</a>
|
||||
</p>
|
||||
<h5 class="border-bottom">Records</h5>
|
||||
<div class="d-flex justify-content-start">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<span><i class="ri-hashtag"></i> Sets</span> <span class="badge text-bg-primary rounded-pill ms-2">{{ counters['sets'] }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<span><i class="ri-group-line"></i> Minifigures</span> <span class="badge text-bg-primary rounded-pill ms-2">{{ counters['minifigures'] }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<span><i class="ri-shapes-line"></i> Parts</span> <span class="badge text-bg-primary rounded-pill ms-2">{{ counters['inventory'] }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<span><i class="ri-error-warning-line"></i> Missing</span> <span class="badge text-bg-primary rounded-pill ms-2">{{ counters['missing'] }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ accordion.footer() }}
|
||||
|
||||
{{ accordion.header('Database danger zone', 'database-danger', 'admin', danger=true, class='text-end') }}
|
||||
{% if error %}<div class="alert alert-danger text-start" role="alert"><strong>Error:</strong> {{ error }}.</div>{% endif %}
|
||||
<a href="{{ url_for('admin.import_database') }}" class="btn btn-warning" role="button"><i class="ri-upload-line"></i> Import a database file</a>
|
||||
<form action="{{ url_for('admin.init_database') }}" method="post" class="d-inline">
|
||||
<button type="submit" class="btn btn-warning"><i class="ri-reset-right-fill"></i> Initialize the database (only missing tables)</button>
|
||||
</form>
|
||||
<a href="{{ url_for('admin.drop_database') }}" class="btn btn-danger" role="button"><i class="ri-close-line"></i> Drop the database</a>
|
||||
<a href="{{ url_for('admin.delete_database') }}" class="btn btn-danger" role="button"><i class="ri-delete-bin-2-line"></i> Delete the database file</a>
|
||||
{{ accordion.footer() }}
|
||||
@@ -0,0 +1,10 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
|
||||
{{ accordion.header('Database danger zone', 'database-danger', 'admin', expanded=true, danger=true, class='text-end') }}
|
||||
<form action="{{ url_for('admin.do_delete_database') }}" 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 the database file</strong>. This action is irreversible.</div>
|
||||
<a class="btn btn-danger" href="{{ url_for('admin.admin') }}" 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 database file</strong></button>
|
||||
</form>
|
||||
{{ accordion.footer() }}
|
||||
@@ -0,0 +1,10 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
|
||||
{{ accordion.header('Database danger zone', 'database-danger', 'admin', expanded=true, danger=true, class='text-end') }}
|
||||
<form action="{{ url_for('admin.do_drop_database') }}" 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>drop all the tables from the database</strong>. This action is irreversible.</div>
|
||||
<a class="btn btn-danger" href="{{ url_for('admin.admin') }}" 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-close-line"></i> Drop <strong>the whole database</strong></button>
|
||||
</form>
|
||||
{{ accordion.footer() }}
|
||||
@@ -0,0 +1,16 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
|
||||
{{ accordion.header('Database danger zone', 'database-danger', 'admin', expanded=true, danger=true) }}
|
||||
<form action="{{ url_for('admin.do_import_database') }}" method="post" enctype="multipart/form-data">
|
||||
{% if error %}<div class="alert alert-danger text-start" role="alert"><strong>Error:</strong> {{ error }}.</div>{% endif %}
|
||||
<div class="alert alert-warning text-center" role="alert">You are about to <strong>import a database file</strong>. This will replace the <strong>whole content</strong> of the database. This action is irreversible.</div>
|
||||
<div class="mb-3">
|
||||
<label for="database" class="form-label">New database file</label>
|
||||
<input type="file" class="form-control" id="database" name="database" accept=".db">
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<a class="btn btn-danger" href="{{ url_for('admin.admin') }}" role="button"><i class="ri-arrow-left-long-line"></i> Back to the admin</a>
|
||||
<button type="submit" class="btn btn-warning"><i class="ri-upload-line"></i> Import a database file</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ accordion.footer() }}
|
||||
@@ -0,0 +1,32 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
|
||||
{{ accordion.header('Instructions', 'instructions', 'admin', expanded=open_instructions, icon='file-line') }}
|
||||
<h5 class="border-bottom">Folder</h5>
|
||||
<p>
|
||||
The instructions files folder is: <code>{{ config['INSTRUCTIONS_FOLDER'].value }}</code>. <br>
|
||||
Allowed file formats for instructions are the following: <code>{{ ', '.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value) }}</code>.
|
||||
</p>
|
||||
<h5 class="border-bottom">Counters</h5>
|
||||
<p>
|
||||
<div class="d-flex justify-content-start">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<span><i class="ri-hashtag"></i> Sets</span> <span class="badge text-bg-primary rounded-pill ms-2">{{ instructions.sets | length }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<span><i class="ri-file-line"></i> Instructions for sets</span> <span class="badge text-bg-primary rounded-pill ms-2">{{ instructions.sets_total }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<span><i class="ri-question-line"></i> Unknown</span> <span class="badge text-bg-primary rounded-pill ms-2">{{ instructions.unknown_total }}</span>
|
||||
</li>
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<span><i class="ri-prohibited-line"></i> Rejected files</span> <span class="badge text-bg-primary rounded-pill ms-2">{{ instructions.rejected_total }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</p>
|
||||
<h5 class="border-bottom">Refresh</h5>
|
||||
<p>
|
||||
<a href="{{ url_for('admin.refresh_instructions') }}" class="btn btn-primary" role="button"><i class="ri-refresh-line"></i> Refresh the instructions cache</a>
|
||||
</p>
|
||||
{{ accordion.footer() }}
|
||||
@@ -0,0 +1,9 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
|
||||
{% if g.login.is_enabled() %}
|
||||
{{ accordion.header('Authentication', 'authentication', 'admin', expanded=open_logout, icon='list-settings-line') }}
|
||||
<p>
|
||||
<a href="{{ url_for('login.logout') }}" class="btn btn-primary" role="button"><i class="ri-logout-circle-line"></i> Logout</a>
|
||||
</p>
|
||||
{{ accordion.footer() }}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,26 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
|
||||
{{ accordion.header('Retired sets', 'retired', 'admin', expanded=open_retired, icon='calendar-close-line') }}
|
||||
<h5 class="border-bottom">File</h5>
|
||||
{% if retired.exception %}<div class="alert alert-danger" role="alert">An exception occured while loading processing the retired sets: {{ retired.exception }}</div>{% endif %}
|
||||
<p>
|
||||
The retired sets file is: <code>{{ config['RETIRED_SETS_PATH'].value }}</code>.
|
||||
{% if retired.size %}<span class="badge rounded-pill text-bg-info fw-normal"><i class="ri-hard-drive-line"></i> {{ retired.human_size() }}</span>{% endif %}
|
||||
{% if retired.mtime %}<span class="badge rounded-pill text-bg-light border fw-normal"><i class="ri-calendar-line"></i> {{ retired.human_time() }}</span>{% endif %}
|
||||
</p>
|
||||
<h5 class="border-bottom">Counters</h5>
|
||||
<p>
|
||||
<div class="d-flex justify-content-start">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<span><i class="ri-calendar-close-line"></i> Retired sets</span> <span class="badge text-bg-primary rounded-pill ms-2">{{ retired.retired | length }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</p>
|
||||
<h5 class="border-bottom">Refresh</h5>
|
||||
<p>
|
||||
<a href="{{ url_for('admin.refresh_retired') }}" class="btn btn-primary" role="button"><i class="ri-refresh-line"></i> Refresh the retired sets cache</a>
|
||||
<a href="{{ url_for('admin.update_retired') }}" class="btn btn-primary" role="button"><i class="ri-download-line"></i> Update the retired sets file</a>
|
||||
</p>
|
||||
{{ accordion.footer() }}
|
||||
@@ -0,0 +1,26 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
|
||||
{{ accordion.header('Themes', 'theme', 'admin', expanded=open_theme, icon='price-tag-3-line') }}
|
||||
<h5 class="border-bottom">File</h5>
|
||||
{% if theme.exception %}<div class="alert alert-danger" role="alert">An exception occured while loading processing the themes: {{ theme.exception }}</div>{% endif %}
|
||||
<p>
|
||||
The themes file is: <code>{{ config['THEMES_PATH'].value }}</code>.
|
||||
{% if theme.size %}<span class="badge rounded-pill text-bg-info fw-normal"><i class="ri-hard-drive-line"></i> {{ theme.human_size() }}</span>{% endif %}
|
||||
{% if theme.mtime %}<span class="badge rounded-pill text-bg-light border fw-normal"><i class="ri-calendar-line"></i> {{ theme.human_time() }}</span>{% endif %}
|
||||
</p>
|
||||
<h5 class="border-bottom">Counters</h5>
|
||||
<p>
|
||||
<div class="d-flex justify-content-start">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||
<span><i class="ri-price-tag-3-line"></i> Themes</span> <span class="badge text-bg-primary rounded-pill ms-2">{{ theme.themes | length }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</p>
|
||||
<h5 class="border-bottom">Refresh</h5>
|
||||
<p>
|
||||
<a href="{{ url_for('admin.refresh_themes') }}" class="btn btn-primary" role="button"><i class="ri-refresh-line"></i> Refresh the themes cache</a>
|
||||
<a href="{{ url_for('admin.update_themes') }}" class="btn btn-primary" role="button"><i class="ri-download-line"></i> Update the themes file</a>
|
||||
</p>
|
||||
{{ accordion.footer() }}
|
||||
+76
-371
@@ -1,383 +1,88 @@
|
||||
<!doctype html>
|
||||
<html data-theme="light" class="has-navbar-fixed-top">
|
||||
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{{ tmp }} - {{ title }}</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 -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css">
|
||||
|
||||
<script src="https://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<style>
|
||||
|
||||
|
||||
.title-image {
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
table.sortable th:not(.sorttable_sorted):not(.sorttable_sorted_reverse):not(.sorttable_nosort):after {
|
||||
content: " \25B4\25BE"
|
||||
}
|
||||
table.sortable tbody tr:nth-child(2n) td {
|
||||
background: #ffffff;
|
||||
}
|
||||
table.sortable tbody tr:nth-child(2n+1) td {
|
||||
background: #ecf0f1;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
table {
|
||||
width: 100%; /* Ensure the table takes full width of its container */
|
||||
|
||||
}
|
||||
|
||||
td {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.name-class {
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 600px) {
|
||||
|
||||
.hidden-desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
/* horizontal scrollbar for tables if mobile screen */
|
||||
.hidden-mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.header {
|
||||
width:100%;
|
||||
text-align:center;
|
||||
z-index: 99;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
|
||||
.inputContainer {
|
||||
display: inline-block; /* Display as an inline block */
|
||||
vertical-align: middle; /* Center vertically within the cell */
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.inputContainer form {
|
||||
margin: 5% auto 5% auto;
|
||||
}
|
||||
|
||||
.inputField {
|
||||
display: flex;
|
||||
align-items: center; /* Align items vertically */
|
||||
width: 100%; /* Ensure inputField fills out inputContainer */
|
||||
height: 100%; /* Ensure inputField fills out inputContainer */
|
||||
}
|
||||
.inputField input {
|
||||
flex: 1;
|
||||
width: 70%;
|
||||
padding: 0px;
|
||||
margin: auto 2.5px auto 2.5px;
|
||||
}
|
||||
.inputField button {
|
||||
width: 30%;
|
||||
padding: 0px;
|
||||
margin: 0 2.5px 0 2.5px;
|
||||
}
|
||||
|
||||
.square-button {
|
||||
background-color: white;
|
||||
border: 1px solid #d1d1d1;
|
||||
}
|
||||
|
||||
|
||||
/* Modal Styles */
|
||||
#lightbox-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
padding-top: 50px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
.lightbox-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.lightbox-content {
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
#lightbox-text {
|
||||
position: absolute;
|
||||
bottom: 20px; /* Adjust as needed */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 10px;
|
||||
background-color: rgba(0, 0, 0, 0.5); /* Adjust background color and opacity */
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
#lightbox-modal .close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 25px;
|
||||
font-size: 50px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#lightbox-modal img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>BrickTracker{% block title %}{% endblock %}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||
<link href="https://cdn.jsdelivr.net/gh/tofsjonas/sortable@4.0.0/dist/sortable.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/baguettebox.js/1.12.0/baguetteBox.css" integrity="sha512-VZ783G3QIpxXpg7tWpzHn+XhjsOCIxFYoSWmyipKCB41OYaB9i4brxAWuY1c8gGCSqKo7uvckzPJhYcdBZQ9gg==" crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/simple-datatables@9.2.1/dist/style.min.css">
|
||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@4.6.0/fonts/remixicon.css" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='styles.css') }}" rel="stylesheet">
|
||||
<link rel="icon" type="image/png" sizes="48x48" href="{{ url_for('static', filename='brick.png') }}">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar is-dark is-fixed-top is-size-6" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
Home
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark sticky-top">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="{{ url_for('index.index') }}">
|
||||
<img src="{{ url_for('static', filename='brick.png') }}" width="30" height="30" alt="">
|
||||
BrickTracker
|
||||
</a>
|
||||
|
||||
{% if minifig_list | length > 0 %}
|
||||
<a class="navbar-item" href="#minifigs">Jump2Figs</a>
|
||||
{% endif %}
|
||||
|
||||
<a class="navbar-item hidden-desktop" id="expand-button">
|
||||
Expand Columns
|
||||
</a>
|
||||
<a class="navbar-item" id="filter-button">
|
||||
Missing Parts
|
||||
</a>
|
||||
|
||||
<a class="navbar-item hidden-desktop js-modal-trigger" data-target="modal-delete-set" >
|
||||
Delete
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="navMenu" class="navbar-menu">
|
||||
<div class="navbar-end">
|
||||
<!-- <a class="navbar-item" onclick="delete_set('{{ u_id }}','{{ tmp }}', '{{ title }}')">
|
||||
Delete
|
||||
</a> -->
|
||||
<a class="navbar-item js-modal-trigger" data-target="modal-delete-set">
|
||||
Delete
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
{% for item in config['_NAVBAR'] %}
|
||||
{% if item.flag and not config[item.flag].value %}
|
||||
<li class="nav-item px-1">
|
||||
<a {% if request.url_rule.endpoint == item.endpoint %}class="nav-link active" aria-current="page"{% else %}class="nav-link"{% endif %} href="{{ url_for(item.endpoint) }}">
|
||||
{% if item.icon %}
|
||||
<i class="ri-{{ item.icon }}"></i>
|
||||
{% endif %}
|
||||
{{ item.title }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if g.login.is_enabled() %}
|
||||
{% if current_user.is_authenticated %}
|
||||
<a href="{{ url_for('admin.admin', open_logout=true) }}" class="badge text-bg-success text-decoration-none">Authenticated</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('login.login', next=request.path) }}" class="badge text-bg-secondary text-decoration-none">Read-only</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="modal" id="modal-delete-set">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Delete set?</p>
|
||||
<button class="delete" aria-label="close"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
Are you sure you want to delete:
|
||||
<br>
|
||||
<b>{{ tmp }} - {{ title }}</b>
|
||||
<br><br>
|
||||
With unique ID: <br><b>{{ u_id }}</b>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<div class="buttons">
|
||||
<a href="{{ '/delete/' + u_id }}" class="button is-danger">Delete</a>
|
||||
<button class="button">Cancel</button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
<main class="pt-2">
|
||||
{% block main %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<div class="container">
|
||||
<center>
|
||||
<h1 class="title is-2 mt-4">{{ 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 }}">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
<footer class="d-flex justify-content-between mx-1 p-2 border-top">
|
||||
<div class="col-md-3 d-flex justify-content-start">
|
||||
<img src="{{ url_for('static', filename='brick.png') }}" width="24" height="24" alt="">
|
||||
<span class="ps-2 mb-3 mb-md-0 text-body-secondary">BrickTracker ({{ g.version }})</span>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 d-flex justify-content-center">
|
||||
<small>
|
||||
<i class="ri-timer-2-line"></i> {{ g.request_time() }}
|
||||
{%if config['DEBUG'].value and g.database_stats %}
|
||||
| <i class="ri-database-2-line"></i> {{g.database_stats.print() }}
|
||||
{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<ul class="nav col-md-3 d-flex list-unstyled justify-content-end">
|
||||
<li class="ms-3"><a class="text-body-secondary text-decoration-none" href="https://gitea.baerentsen.space/FrederikBaerentsen/BrickTracker" target="_blank"><img src="{{ url_for('static', filename='gitea.svg') }}" width="24" height="24" alt=""> Source</a></li>
|
||||
</ul>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/gh/tofsjonas/sortable@4.0.0/dist/sortable.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinysort/3.2.8/tinysort.min.js" integrity="sha512-P3lisBxY1PusizpelihVrBeciBfWA9r7Ff/8j16b4K399OWJIrY1UdzdE7/BctqKv6Xujr8hp3IA2XiQX3Bsew==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/baguettebox.js/1.12.0/baguetteBox.min.js" integrity="sha512-HzIuiABxntLbBS8ClRa7drXZI3cqvkAZ5DD0JCAkmRwUtykSGqzA9uItHivDhRUYnW3MMyY5xqk7qVUHOEMbMA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.8.1/socket.io.min.js" integrity="sha512-8ExARjWWkIllMlNzVg7JKq9RKWPlJABQUNq6YvAjE/HobctjH/NA+bSiDMDvouBVjp4Wwnf1VP1OEv7Zgjtuxw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/simple-datatables@9.2.1/dist/umd/simple-datatables.min.js"></script>
|
||||
<!-- Bootstrap tooltips -->
|
||||
<script type="text/javascript">
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
||||
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
||||
</script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
<script>
|
||||
function delete_set(u_id,tmp,title) {
|
||||
let text = "";
|
||||
if (confirm(text) == true) {
|
||||
text = "You pressed OK!";
|
||||
} else {
|
||||
text = "You canceled!";
|
||||
}
|
||||
document.getElementById("demo").innerHTML = text;
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Functions to open and close a modal
|
||||
function openModal($el) {
|
||||
$el.classList.add('is-active');
|
||||
}
|
||||
|
||||
function closeModal($el) {
|
||||
$el.classList.remove('is-active');
|
||||
}
|
||||
|
||||
function closeAllModals() {
|
||||
(document.querySelectorAll('.modal') || []).forEach(($modal) => {
|
||||
closeModal($modal);
|
||||
});
|
||||
}
|
||||
|
||||
// Add a click event on buttons to open a specific modal
|
||||
(document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => {
|
||||
const modal = $trigger.dataset.target;
|
||||
const $target = document.getElementById(modal);
|
||||
|
||||
$trigger.addEventListener('click', () => {
|
||||
openModal($target);
|
||||
});
|
||||
});
|
||||
|
||||
// Add a click event on various child elements to close the parent modal
|
||||
(document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button') || []).forEach(($close) => {
|
||||
const $target = $close.closest('.modal');
|
||||
|
||||
$close.addEventListener('click', () => {
|
||||
closeModal($target);
|
||||
});
|
||||
});
|
||||
|
||||
// Add a keyboard event to close all modals
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if(event.key === "Escape") {
|
||||
closeAllModals();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
document.getElementById("filter-button").addEventListener("click", () => {
|
||||
const table = document.getElementById("data");
|
||||
const rows = table.querySelectorAll("tbody tr");
|
||||
|
||||
// Check if any rows are currently hidden
|
||||
const isFiltered = Array.from(rows).some(row => row.classList.contains("hidden"));
|
||||
|
||||
rows.forEach(row => {
|
||||
const inputField = row.querySelector("input#missing");
|
||||
if (inputField) {
|
||||
const inputValue = inputField.value.trim();
|
||||
if (isFiltered) {
|
||||
// If currently filtered, show all rows
|
||||
row.classList.remove("hidden");
|
||||
} else {
|
||||
// If not filtered, hide rows with empty input fields
|
||||
if (inputValue === "") {
|
||||
row.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update button text based on the state
|
||||
document.getElementById("filter-button").textContent = isFiltered
|
||||
? "Only Show Missing"
|
||||
: "Show All Parts";
|
||||
});
|
||||
|
||||
*/
|
||||
document.getElementById("filter-button").addEventListener("click", () => {
|
||||
const tables = document.querySelectorAll("#data"); // Select all tables with the class 'data-table'
|
||||
let isFiltered = false;
|
||||
|
||||
// Check if any row in any table is hidden
|
||||
tables.forEach(table => {
|
||||
const rows = table.querySelectorAll("tbody tr");
|
||||
if (Array.from(rows).some(row => row.classList.contains("hidden"))) {
|
||||
isFiltered = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Apply toggle logic to each table
|
||||
tables.forEach(table => {
|
||||
const rows = table.querySelectorAll("tbody tr");
|
||||
rows.forEach(row => {
|
||||
const inputField = row.querySelector("input#missing");
|
||||
if (inputField) {
|
||||
const inputValue = inputField.value.trim();
|
||||
if (isFiltered) {
|
||||
row.classList.remove("hidden");
|
||||
} else {
|
||||
if (inputValue === "") {
|
||||
row.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Update button text based on the state
|
||||
document.getElementById("filter-button").textContent = isFiltered
|
||||
? "Missing Parts"
|
||||
: "All Parts";
|
||||
});
|
||||
|
||||
</script>
|
||||
</html>
|
||||
</html>
|
||||
@@ -1,336 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<center><button class="hidden-desktop" style="background-color: white;border: 1px solid black; color: black;" id="expand-button">Expand Columns</button></center>
|
||||
<div class="center-table">
|
||||
<table id="data" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th style="text-align:center;margin:0px;" class="fixed-width hidden-mobile">ID</th>
|
||||
<th class="fixed-width hidden-mobile">Name</th>
|
||||
<th class="fixed-width">Color</th>
|
||||
<th style="text-align: center;" class="fixed-width">Qty</th>
|
||||
{% for i in json_file['unit'] %}
|
||||
<th style="text-align: center;" class="fixed-width">Missing ({{ loop.index }})</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for brick in inventory_file.results %}
|
||||
{% if brick.is_spare == False %}
|
||||
<tr>
|
||||
{% if brick.element_id == None %}
|
||||
<td><img src="{{ '/static/none.jpg' }}" class="lightbox-trigger" style="height: 50px; width: auto;"></td>
|
||||
{% else %}
|
||||
<td><img src="{{ '/static/parts/' + brick.element_id + '.jpg' }}" class="lightbox-trigger" style="height: 50px; width: auto;"></td>
|
||||
{% endif %}
|
||||
<td style="text-align:center;margin:0px;" class="hidden-mobile">{{ brick.part.part_num }}</td>
|
||||
<td class="hidden-mobile">{{ brick.part.name }}</td>
|
||||
<td>{{ brick.color.name }}</td>
|
||||
<td style="text-align:center;">{{ brick.quantity }}</td>
|
||||
{% for i in json_file['unit'] %}
|
||||
|
||||
<td class="centered-cell">
|
||||
<div class="inputContainer">
|
||||
{% set ns = namespace(count='') %}
|
||||
<!-- <form id="number-form" action="/{{ tmp }}" method="post"> -->
|
||||
<form id="number-form">
|
||||
<input type="hidden" name="numberID" id="numberID" value="{{ tmp }}">
|
||||
<input type="hidden" name="brickpartpart_num" id="brickpartpart_num" value="{{ brick.part.part_num }}">
|
||||
<input type="hidden" name="count" value="{{ i }}">
|
||||
<input type="hidden" name="index" id="index" value="{{ loop.index0 }}">
|
||||
<input type="hidden" name="is_spare" id="is_spare" value="{{ brick.is_spare }}">
|
||||
<input type="hidden" name="brickcolorname" id="brickcolorname" value="{{ brick.color.name }}">
|
||||
{% for j in json_file['unit'][loop.index0]['bricks']['missing'] %}
|
||||
{% if j['brick']['ID'] == brick.part.part_num and j['brick']['color_name'] == brick.color.name %}
|
||||
|
||||
{% if j['brick']['is_spare']|lower == brick.is_spare|lower %}
|
||||
|
||||
{% set ns.count = j['brick']['amount'] %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class='inputField'>
|
||||
<input type="tel" style="text-align:center;font-size: 16px;" id="numberInput" name="numberInput" value="{{ ns.count }}" pattern="\d+">
|
||||
<span style="display:none">{{ ns.count }}</span>
|
||||
<button class="square-button" id="bnumber-form" type="submit">
|
||||
<img src="/static/save.svg" alt="Save Icon">
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if minifigs_file.figs | length > 0 %}
|
||||
|
||||
<h1>Minifigs</h1>
|
||||
{% for fig in minifigs_file.figs %}
|
||||
|
||||
<h2>{{ fig.name}}</h2>
|
||||
|
||||
<img src="{{ '/static/figs/' + fig.set_num + '.jpg' }}" class="lightbox-trigger" style="height: 100px; width: auto;"><span style="font-size: 50px;">X {{ fig.quantity }}</span>
|
||||
|
||||
<div class="center-table">
|
||||
<table id="sdata" class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th style="text-align:center;margin:0px;" class="fixed-width">ID</th>
|
||||
<th class="fixed-width">Name</th>
|
||||
<th>Total Qty</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for part in fig.parts %}
|
||||
<tr>
|
||||
<td><img src="{{ '/static/figs/' + part.part_num + '.jpg' }}" class="lightbox-trigger" style="height: 50px; width: auto;"></td>
|
||||
<td style="text-align:center;margin:0px;">{{ part.part_num }}</td>
|
||||
<td>{{ part.name }}</td>
|
||||
<td>{{ part.quantity * fig.quantity }}</td>
|
||||
{% for i in json_file['unit'] %}
|
||||
|
||||
<td class="centered-cell">
|
||||
<div class="inputContainer">
|
||||
{% set ns = namespace(count='') %}
|
||||
<!-- <form id="number-form" action="/{{ tmp }}" method="post"> -->
|
||||
<form id="number-form">
|
||||
<input type="hidden" name="numberID" id="numberID" value="{{ tmp }}">
|
||||
<input type="hidden" name="brickpartpart_num" id="brickpartpart_num" value="{{ part.part_num }}">
|
||||
<input type="hidden" name="count" value="{{ i }}">
|
||||
<input type="hidden" name="index" id="index" value="{{ loop.index0 }}">
|
||||
<input type="hidden" name="is_spare" id="is_spare" value="">
|
||||
<input type="hidden" name="brickcolorname" id="brickcolorname" value="{{ part.color_name }}">
|
||||
{% for j in json_file['unit'][loop.index0]['bricks']['missing'] %}
|
||||
{% if j['brick']['ID'] == part.part_num and j['brick']['color_name'] == part.color_name %}
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class='inputField'>
|
||||
<input type="tel" style="text-align:center;font-size: 16px;" id="numberInput" name="numberInput" value="{{ ns.count }}" pattern="\d+">
|
||||
<span style="display:none">{{ ns.count }}</span>
|
||||
<button class="square-button" id="bnumber-form" type="submit">
|
||||
<img src="/static/save.svg" alt="Save Icon">
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div id="lightbox-modal">
|
||||
<div class="lightbox-wrapper">
|
||||
<span class="close">×</span>
|
||||
<img class="lightbox-content" id="lightbox-image">
|
||||
<div class="text-container" id="lightbox-text"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block scripts %}
|
||||
|
||||
<script>
|
||||
// $(document).ready( function () {
|
||||
// $('#data').DataTable();
|
||||
// } );
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// document.addEventListener('DOMContentLoaded', function () {
|
||||
// const myDataTable = document.getElementById('data');
|
||||
|
||||
// myDataTable.addEventListener('click', function (event) {
|
||||
// const clickedRow = event.target.closest('td');
|
||||
// if (clickedRow) {
|
||||
// // Remove highlighting from all rows
|
||||
// const rows = myDataTable.querySelectorAll('tr');
|
||||
// rows.forEach(row => row.classList.remove('highlighted'));
|
||||
|
||||
// // Add highlighting to the clicked row
|
||||
// if (clickedRow.classList.contains('highlighted')) {
|
||||
|
||||
// clickedRow.classList.remove('highlighted');
|
||||
// } else {
|
||||
// clickedRow.classList.add('highlighted');
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
function goToPage(url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
|
||||
const expandButton = document.getElementById('expand-button');
|
||||
const myDataTable = document.getElementById('data');
|
||||
let isExpanded = false;
|
||||
|
||||
expandButton.addEventListener('click', () => {
|
||||
const hiddenColumns = myDataTable.querySelectorAll('.hidden-mobile');
|
||||
hiddenColumns.forEach(column => {
|
||||
if (isExpanded) {
|
||||
column.style.display = 'none';
|
||||
expandButton.textContent = 'Expand Columns';
|
||||
} else {
|
||||
column.style.display = 'table-cell';
|
||||
expandButton.textContent = 'Hide Columns';
|
||||
}
|
||||
});
|
||||
isExpanded = !isExpanded;
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const lightboxTrigger = document.querySelectorAll('.lightbox-trigger');
|
||||
const lightboxModal = document.getElementById('lightbox-modal');
|
||||
const lightboxImage = document.getElementById('lightbox-image');
|
||||
const lightboxText = document.getElementById('lightbox-text');
|
||||
const closeModal = document.querySelector('.close');
|
||||
|
||||
lightboxTrigger.forEach(function (element) {
|
||||
element.addEventListener('click', function () {
|
||||
const imgSrc = element.getAttribute('src');
|
||||
textContent = '';
|
||||
try {
|
||||
textContent = element.closest('tr').querySelector('td:nth-child(3)').textContent; // Adjust the index accordingly
|
||||
lightboxText.textContent = textContent;
|
||||
} catch (e) {
|
||||
textContent = '';
|
||||
}
|
||||
|
||||
lightboxImage.setAttribute('src', imgSrc);
|
||||
lightboxModal.style.display = 'block';
|
||||
});
|
||||
});
|
||||
|
||||
closeModal.addEventListener('click', function () {
|
||||
lightboxModal.style.display = 'none';
|
||||
});
|
||||
|
||||
lightboxModal.addEventListener('click', function (event) {
|
||||
if (event.target === lightboxModal || event.target === lightboxImage || event.target === lightboxText) {
|
||||
// Dismiss only if the clicked element is part of the content area
|
||||
lightboxModal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Dismiss the lightbox when clicking outside the content area (i.e., the background)
|
||||
lightboxModal.addEventListener('click', function (event) {
|
||||
if (event.target != lightboxModal) {
|
||||
lightboxModal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const numberInput = document.getElementById('numberInput');
|
||||
|
||||
// Add event listener to input to prevent non-numeric input
|
||||
numberInput.addEventListener('input', function () {
|
||||
// Remove non-numeric characters
|
||||
this.value = this.value.replace(/\D/g, '');
|
||||
});
|
||||
|
||||
// Force numeric keyboard on mobile devices
|
||||
numberInput.setAttribute('inputmode', 'numeric');
|
||||
});
|
||||
|
||||
|
||||
let options = {
|
||||
searchable: true,
|
||||
sortable: true,
|
||||
paging: false,
|
||||
perPage: 10,
|
||||
perPageSelect: [5, 10, 25, 50, ["All", -1]],
|
||||
columns: [
|
||||
|
||||
{ select: [0], sortable: false }
|
||||
|
||||
|
||||
]
|
||||
};
|
||||
|
||||
let myTable = document.querySelector("#data");
|
||||
let dataTable = new simpleDatatables.DataTable(myTable, options);
|
||||
|
||||
|
||||
$("body").on("click", "#number-form", function (event) {
|
||||
// $('#number-form').submit(function(event) {
|
||||
event.preventDefault();
|
||||
//console.log($(this)[0]);
|
||||
|
||||
brickpartpart_num = $(this)[0][1].value;
|
||||
brickcolorname = $(this)[0][5].value;
|
||||
index = $(this)[0][3].value;
|
||||
numberInput = $(this)[0][6].value;
|
||||
is_spare = $(this)[0][4].value;
|
||||
|
||||
// console.log('ID: ' + brickpartpart_num);
|
||||
// console.log('Color Name: ' + brickcolorname);
|
||||
// console.log('Index: ' + index);
|
||||
// console.log('Number Input: ' + numberInput);
|
||||
// console.log('is_spare: ' + is_spare);
|
||||
|
||||
$.ajax({
|
||||
url: '/'+$(this)[0][0].value,
|
||||
type: 'POST',
|
||||
data: {
|
||||
'brickpartpart_num': brickpartpart_num,
|
||||
'brickcolorname': brickcolorname,
|
||||
'index': index,
|
||||
'numberInput': numberInput,
|
||||
'is_spare': is_spare
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
// part_num = request.form.get('brickpartpart_num')
|
||||
// color = request.form.get('brickcolorname')
|
||||
// index = request.form.get('index')
|
||||
// number = request.form.get('numberInput')
|
||||
// is_spare = request.form.get('is_spare')
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - Bulk add sets{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="ri-add-circle-line"></i> Bulk add sets</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="add-fail" class="alert alert-danger d-none" role="alert"></div>
|
||||
<div id="add-complete"></div>
|
||||
<div class="mb-3">
|
||||
<label for="add-set" class="form-label">List of sets (separated by a comma)</label>
|
||||
<input type="text" class="form-control" id="add-set" placeholder="107-1, 1642-1, ...">
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" id="add-no-confirm" checked disabled>
|
||||
<label class="form-check-label" for="add-no-confirm">
|
||||
Add without confirmation
|
||||
</label>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="mb-3">
|
||||
<p>
|
||||
Progress <span id="add-count"></span>
|
||||
<span id="add-spinner" class="d-none">
|
||||
<span class="spinner-border spinner-border-sm" aria-hidden="true"></span>
|
||||
<span class="visually-hidden" role="status">Loading...</span>
|
||||
</span>
|
||||
</p>
|
||||
<div id="add-progress" class="progress" role="progressbar" aria-label="Add a set progress" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
|
||||
<div id="add-progress-bar" class="progress-bar" style="width: 0%"></div>
|
||||
</div>
|
||||
<p id="add-progress-message" class="text-center d-none"></p>
|
||||
</div>
|
||||
<div id="add-card" class="d-flex d-none justify-content-center">
|
||||
<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 id="add-card-name"></span>
|
||||
</h5>
|
||||
</div>
|
||||
<div id="add-card-image-container" class="card-img">
|
||||
<img id="add-card-image" loading="lazy">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-end">
|
||||
<span id="add-status-icon" class="me-1"></span><span id="add-status" class="me-1"></span><button id="add" type="button" class="btn btn-primary"><i class="ri-function-add-line"></i> Bulk add</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% with bulk=true %}
|
||||
{% include 'set/socket.html' %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='scripts/socket.js') }}"></script>
|
||||
{% endblock %}
|
||||
@@ -1,90 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="light" class="has-navbar-fixed-top">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Set Overview</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar is-dark is-fixed-top" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
Home
|
||||
</a>
|
||||
|
||||
<a class="navbar-item" href="/create">
|
||||
Add Set
|
||||
</a>
|
||||
<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>
|
||||
</a>
|
||||
</div>
|
||||
<div id="navMenu" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<p class="is-size-1">Database</p>
|
||||
{% if not db_is_there %}
|
||||
<p>Database does not exists </p>
|
||||
<form method="POST" action="/config">
|
||||
<input type="submit" class="button is-primary" value="Create Database" name="CreateDB"/>
|
||||
</form>
|
||||
{% else %}
|
||||
<ul>
|
||||
{% for file,data in row_counts.items() %}
|
||||
<li>{{ file }}: {{ data }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
<p class="is-size-1">Rebrickable Data</p>
|
||||
|
||||
<h1>Data is last updated:</h1>
|
||||
<table border="1">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>File</th>
|
||||
<th>Last Updated</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for file, date in creation_dates.items() %}
|
||||
<tr>
|
||||
<td>{{ file }}</td>
|
||||
<td>{{ date }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<form method="POST" action="/config">
|
||||
<input type="submit" class="button is-primary" value="Update local data" name="Update local data" />
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
<p>-----------</p>
|
||||
|
||||
<p class="is-size-1">Recreate Database</p>
|
||||
<p>Drop the tables in the database and recreate them. This will delete all your data!</p>
|
||||
<form method="POST" action="/config">
|
||||
<input type="submit" class="button is-warning" value="Delete Database" name="deletedb" />
|
||||
|
||||
</form>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,169 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="light" class="has-navbar-fixed-top">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title></title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.3.1/socket.io.js" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar is-dark is-fixed-top" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
Home
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="navMenu" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<form id="createForm" action="/create" method="post">
|
||||
<div class="field">
|
||||
<label class="label" for="inputField">Input:</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" id="inputField" name="inputField">
|
||||
</div>
|
||||
</div>
|
||||
<span id="progress-bar-text" style="left: 50%; font-size: 12px;"></span>
|
||||
|
||||
<progress id="progress-bar" class="progress is-large is-primary" value="0" max="100">
|
||||
</progress>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button id="submitButton" class="button is-primary" type="submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
<button class="js-modal-trigger" data-target="modal-js-example" id="modal-js">
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
<div class="modal" id="modal-js-example">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Cannot add set!</p>
|
||||
<button class="delete" aria-label="close"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
Set number does not exists:
|
||||
<br>
|
||||
<b id="error-code"></b>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<div class="buttons">
|
||||
<button class="button is-primary">OK</button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
var socket = io.connect(window.location.protocol + '//' + document.domain + ':' + location.port + '/progress', {
|
||||
transports: ['websocket'],
|
||||
});
|
||||
// Event listener for receiving progress updates
|
||||
socket.on('update_progress', function (msg) {
|
||||
var progress = msg.progress;
|
||||
var state = msg.desc;
|
||||
console.log(progress,state);
|
||||
// document.getElementById("progress-bar").style.width = progress + "%";
|
||||
document.getElementById("progress-bar").value = progress;
|
||||
document.getElementById("progress-bar").innerText = progress + "%";
|
||||
document.getElementById("progress-bar-text").innerText = state;
|
||||
});
|
||||
|
||||
// Event listener for the form submission
|
||||
document.getElementById("createForm").addEventListener("submit", function (event) {
|
||||
event.preventDefault(); // Prevent default form submission
|
||||
var inputField = document.getElementById("inputField").value;
|
||||
socket.emit('start_task', { inputField: inputField }); // Emit socket event with inputField value
|
||||
});
|
||||
|
||||
// Event listener for the 'task_completed' event
|
||||
socket.on('task_completed', function () {
|
||||
// Redirect to '/'
|
||||
document.getElementById("progress-bar-text").innerText = '';
|
||||
|
||||
window.location.href = '/';
|
||||
});
|
||||
|
||||
socket.on('task_failed', function () {
|
||||
// Redirect to '/'
|
||||
var inputField = document.getElementById("inputField").value;
|
||||
console.log(inputField);
|
||||
|
||||
document.getElementById("modal-js").click();
|
||||
document.getElementById("error-code").innerHTML = inputField;
|
||||
document.getElementById("inputField").value = "";
|
||||
// window.location.href = '/create';
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Functions to open and close a modal
|
||||
function openModal($el) {
|
||||
$el.classList.add('is-active');
|
||||
}
|
||||
|
||||
function closeModal($el) {
|
||||
$el.classList.remove('is-active');
|
||||
}
|
||||
|
||||
function closeAllModals() {
|
||||
(document.querySelectorAll('.modal') || []).forEach(($modal) => {
|
||||
closeModal($modal);
|
||||
});
|
||||
}
|
||||
|
||||
// Add a click event on buttons to open a specific modal
|
||||
(document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => {
|
||||
const modal = $trigger.dataset.target;
|
||||
const $target = document.getElementById(modal);
|
||||
|
||||
$trigger.addEventListener('click', () => {
|
||||
openModal($target);
|
||||
});
|
||||
});
|
||||
|
||||
// Add a click event on various child elements to close the parent modal
|
||||
(document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button') || []).forEach(($close) => {
|
||||
const $target = $close.closest('.modal');
|
||||
|
||||
$close.addEventListener('click', () => {
|
||||
closeModal($target);
|
||||
});
|
||||
});
|
||||
|
||||
// Add a keyboard event to close all modals
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === "Escape") {
|
||||
closeAllModals();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - Delete a set {{ item.fields.set_num }} ({{ item.fields.u_id }}){% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{% with solo=true, delete=true %}
|
||||
{% include 'set/card.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
baguetteBox.run('[data-lightbox]');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='scripts/set.js') }}"></script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,18 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - Error!{% endblock %}
|
||||
|
||||
|
||||
{% block main %}
|
||||
<div class="container">
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h4 class="alert-heading">{% if title %}{{ title }}{% else %}Error{% endif %}!</h4>
|
||||
<p>An error has occured: {{error}}.</p>
|
||||
<hr>
|
||||
<div class="text-end">
|
||||
<a class="btn btn-danger me-1" href="{{ url_for('admin.admin') }}" role="button"><i class="ri-settings-4-line"></i> Administration</a>
|
||||
<a class="btn btn-primary" href="{{ url_for('index.index') }}" role="button"><i class="ri-home-line"></i> Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,19 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - Exception!{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="container">
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h4 class="alert-heading">Exception!</h4>
|
||||
<p>A Python exception has occured.</p>
|
||||
<hr>
|
||||
<pre>{{name}} {% if line %}at line {{line}} of{% else %}in{% endif %} {{file}}: {{error}}</pre>
|
||||
<hr>
|
||||
<div class="text-end">
|
||||
<a class="btn btn-danger me-1" href="{{ url_for('admin.admin') }}" role="button"><i class="ri-settings-4-line"></i> Administration</a>
|
||||
<a class="btn btn-primary" href="{{ url_for('index.index') }}" role="button"><i class="ri-home-line"></i> Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,593 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="light">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Set Overview</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
|
||||
|
||||
<!-- CSS Reset -->
|
||||
<!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.css"> --->
|
||||
|
||||
<!-- Milligram CSS -->
|
||||
|
||||
|
||||
<!---
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.4.1/milligram.css">
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
|
||||
/>
|
||||
|
||||
--->
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(10%, 48%)); /* Between 33% and 50% width */
|
||||
/* grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); */
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
height:auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
height: 100%;
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
padding-bottom:30px; /* Height of the footer */
|
||||
}
|
||||
|
||||
/* Adjust height for smaller screens */
|
||||
@media (max-width: 768px) {
|
||||
.card {
|
||||
height: auto; /* Let the height adjust dynamically */
|
||||
}
|
||||
.grid-container {
|
||||
grid-template-columns: 100%; /* On mobile, set width to 100% */
|
||||
}
|
||||
.grid-item {
|
||||
height: auto; /* Let the height adjust dynamically */
|
||||
overflow: visible; /* Allow content to overflow if necessary */
|
||||
}
|
||||
}
|
||||
|
||||
.grid-item img {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
|
||||
}
|
||||
.card p {
|
||||
margin: 0; /* Remove default margin */
|
||||
}
|
||||
.card-footer {
|
||||
position:absolute;
|
||||
bottom:0;
|
||||
width:90%;
|
||||
height:30px; /* Height of the footer */
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: black;
|
||||
border-color: black;
|
||||
}
|
||||
.button.button-clear,
|
||||
.button.button-outline {
|
||||
background-color: transparent;
|
||||
color: black;
|
||||
}
|
||||
.button.button-clear {
|
||||
border-color: transparent;
|
||||
}
|
||||
.search-container {
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
|
||||
<div class="search-container">
|
||||
<input type="text" id="searchInput" onkeyup="searchFunction()" placeholder="Search...">
|
||||
</select>
|
||||
<center>
|
||||
<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_parts')">Sort by Parts</button>
|
||||
<button class="button button-outline" onclick="dynamicSort('set_theme')">Sort by Theme</button>
|
||||
<button class="button button-outline" onclick="dynamicSort('set_name')">Sort by Name</button>
|
||||
<button class="button button-outline" onclick="dynamicSort('s_col')">Sort by Collected</button>
|
||||
<button class="button button-outline" onclick="dynamicSort('s_check')">Sort by Checked</button>
|
||||
</center>
|
||||
<center>
|
||||
<button class="button button-outline" id="toggleButton">Show Collected</button>
|
||||
<button class="button button-outline" id="toggleButton2">Show Checked</button>
|
||||
<button class="button button-outline" id="toggleButton3">Show Complete</button>
|
||||
</center>
|
||||
<select id="themeDropdown">
|
||||
<option value="">Select Theme</option>
|
||||
</select>
|
||||
|
||||
<!-- Add more buttons for other text values if needed -->
|
||||
</div>
|
||||
<div class="grid-container" id="gridContainer">
|
||||
|
||||
{% for i in set_list %}
|
||||
{% if json_file[i['set_num']]['count'] == 1 %}
|
||||
<div class="grid-item" style="">
|
||||
<div class="card" style="">
|
||||
{% else %}
|
||||
<div class="grid-item" style="">
|
||||
<div class="card" style="">
|
||||
{% endif %}
|
||||
|
||||
<div class="columns" style="">
|
||||
<div class="column is-two-thirds" style="text-align: left;">
|
||||
<b class="is-size-5">
|
||||
<span class="set_id">{{ i['set_num'] }}</span> <span class="set_name">{{ i['name'] }}</span>
|
||||
</b>
|
||||
</div>
|
||||
<div class="column" style="text-align: left;">
|
||||
<div class="is-size-5">
|
||||
<b>Parts:</b>
|
||||
<span class='set_parts'>{{ i['num_parts'] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns" style="">
|
||||
<div class="column is-one-third" style="">
|
||||
<figure class="image is-4by3">
|
||||
<a href="/{{ i['set_num'] }}">
|
||||
<img style='height: 100%; width: 100%; object-fit: contain' src="/static/sets/{{ i['set_num'] }}/cover.jpg" alt="Image">
|
||||
</a>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
{% for j in json_file[i['set_num']]['unit'] %}
|
||||
<div class="column is-one-thid" style="margin:0;padding:0;">
|
||||
<p>Set #{{ loop.index }}</p>
|
||||
<div class="is-size-7">
|
||||
<label class="checkbox" >
|
||||
<input type="hidden" id="set_num" value="{{ i['set_num'] }}">
|
||||
<input type="hidden" id="index" value="{{ loop.index0 }}">
|
||||
{% if json_file[i['set_num']]['Minifigs Collected'] == 'true' %}
|
||||
<input class="s_fig" id="s_fig" type="checkbox" checked />
|
||||
{% else %}
|
||||
<input id="s_fig" type="checkbox" />
|
||||
{% endif %}
|
||||
Minifigs Collected
|
||||
</label>
|
||||
<br>
|
||||
<label class="checkbox" >
|
||||
<input type="hidden" id="set_num" value="{{ i['set_num'] }}">
|
||||
<input type="hidden" id="index" value="{{ loop.index0 }}">
|
||||
{% if json_file[i['set_num']]['Set Checked'] == 'true' %}
|
||||
<input class="s_check" id="s_check" type="checkbox" checked />
|
||||
{% else %}
|
||||
<input class="s_check" id="s_check" type="checkbox" />
|
||||
{% endif %}
|
||||
Set is checked
|
||||
</label>
|
||||
<br>
|
||||
<label class="checkbox" >
|
||||
<input type="hidden" id="set_num" value="{{ i['set_num'] }}">
|
||||
<input type="hidden" id="index" value="{{ loop.index0 }}">
|
||||
{% if json_file[i['set_num']]['Set Collected'] == 'true' %}
|
||||
<input class="s_col" id="s_col" type="checkbox" checked />
|
||||
{% else %}
|
||||
<input class="s_col" id="s_col" type="checkbox" />
|
||||
{% endif %}
|
||||
Set is collected and boxed
|
||||
</label>
|
||||
<br>
|
||||
<label class="checkbox" >
|
||||
{% if json_file[i['set_num']]['unit'][loop.index0]['bricks']['missing'] |length > 0 %}
|
||||
<input type="checkbox" class="s_com" onclick="return false;"/> Set is missing pieces
|
||||
{% else %}
|
||||
<input type="checkbox" class="s_com" onclick="return false;" checked /> Set is Complete
|
||||
{% endif %}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<footer class="card-footer" style="">
|
||||
<p class="card-footer-item">
|
||||
<span>
|
||||
<a class="is-size-7 set_theme" style="color: #363636;">{{ i['theme_id'] }}</a> <a class="is-size-7" style="color: #363636;"> (<span class='set_year'>{{ i['year'] }}</span>)</a>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<p class="card-footer-item">
|
||||
<span>
|
||||
<a class="is-size-6" style="color: #363636;" href="/{{ i['set_num'] }}">Inventory</a>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<p class="card-footer-item">
|
||||
<span>
|
||||
<a class="is-size-6" style="color: #363636;" href="/{{ i['set_num'] }}">Instructions</a>
|
||||
</span>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
<button class="js-modal-trigger" data-target="modal-js-example" id="modal-js">
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
<div class="modal" id="modal-js-example">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">First time?</p>
|
||||
<button class="delete" aria-label="close"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
The database tables does not exists. Create them?
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<div class="buttons">
|
||||
<button class="button is-primary">Create</button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
function searchFunction() {
|
||||
var input, filter, gridContainer, gridItems, i, txtValue;
|
||||
input = document.getElementById('searchInput');
|
||||
filter = input.value.toUpperCase();
|
||||
gridContainer = document.getElementById('gridContainer');
|
||||
gridItems = gridContainer.getElementsByClassName('grid-item');
|
||||
|
||||
for (i = 0; i < gridItems.length; i++) {
|
||||
txtValue = gridItems[i].textContent || gridItems[i].innerText;
|
||||
if (txtValue.toUpperCase().indexOf(filter) > -1) {
|
||||
gridItems[i].style.display = "";
|
||||
} else {
|
||||
gridItems[i].style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
function sortFunction() {
|
||||
var sortSelect, sortValue, gridContainer, gridItems, sortedItems, i;
|
||||
sortSelect = document.getElementById('sortSelect');
|
||||
sortValue = sortSelect.value;
|
||||
gridContainer = document.getElementById('gridContainer');
|
||||
gridItems = gridContainer.getElementsByClassName('grid-item');
|
||||
sortedItems = Array.from(gridItems);
|
||||
|
||||
if (sortValue === 'alphabetical') {
|
||||
sortedItems.sort(function(a, b) {
|
||||
var textA = a.textContent.trim().toUpperCase();
|
||||
var textB = b.textContent.trim().toUpperCase();
|
||||
if (textA < textB) {
|
||||
return -1;
|
||||
}
|
||||
if (textA > textB) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
// Remove existing items
|
||||
while (gridContainer.firstChild) {
|
||||
gridContainer.removeChild(gridContainer.firstChild);
|
||||
}
|
||||
|
||||
// Append sorted items
|
||||
for (i = 0; i < sortedItems.length; i++) {
|
||||
gridContainer.appendChild(sortedItems[i]);
|
||||
}
|
||||
}
|
||||
var sortOrder = 'asc';
|
||||
|
||||
function dynamicSort(className) {
|
||||
console.log(className);
|
||||
var gridContainer, gridItems, sortedItems, i;
|
||||
gridContainer = document.getElementById('gridContainer');
|
||||
gridItems = gridContainer.getElementsByClassName('grid-item');
|
||||
sortedItems = Array.from(gridItems);
|
||||
|
||||
sortedItems.sort(function(a, b) {
|
||||
var textA = a.getElementsByClassName(className)[0].textContent.trim();
|
||||
var textB = b.getElementsByClassName(className)[0].textContent.trim();
|
||||
// Check if the className corresponds to a checkbox
|
||||
if (className == 's_col' || className == 's_check') {
|
||||
var checkedA = a.getElementsByClassName(className)[0].checked;
|
||||
var checkedB = b.getElementsByClassName(className)[0].checked;
|
||||
// Sort by checked checkboxes (true first, false last)
|
||||
return (checkedA === checkedB) ? 0 : checkedA ? -1 : 1;
|
||||
}
|
||||
|
||||
|
||||
// Remove digits after hyphen if present
|
||||
textA = textA.replace(/-\d+$/, '').trim();
|
||||
textB = textB.replace(/-\d+$/, '').trim();
|
||||
// If text1 is an integer, parse it as a number
|
||||
if (!isNaN(textA) && !isNaN(textB)) {
|
||||
var result = parseInt(textA) - parseInt(textB);
|
||||
// Toggle result based on current sort order
|
||||
return sortOrder === 'asc' ? result : -result;
|
||||
}
|
||||
// If text1 is not an integer, treat it as a string
|
||||
else {
|
||||
textA = textA.toUpperCase();
|
||||
textB = textB.toUpperCase();
|
||||
var result = textA.localeCompare(textB);
|
||||
// Toggle result based on current sort order
|
||||
return sortOrder === 'asc' ? result : -result;
|
||||
}
|
||||
});
|
||||
|
||||
// Reverse sort order for next click
|
||||
sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
|
||||
|
||||
// Remove existing items
|
||||
while (gridContainer.firstChild) {
|
||||
gridContainer.removeChild(gridContainer.firstChild);
|
||||
}
|
||||
|
||||
// Append sorted items
|
||||
for (i = 0; i < sortedItems.length; i++) {
|
||||
gridContainer.appendChild(sortedItems[i]);
|
||||
}
|
||||
}
|
||||
|
||||
$("body").on("change", "#s_fig", function (event) {
|
||||
minif = $(this).prop('checked');
|
||||
set_num = $(this).siblings('#set_num').val();
|
||||
index = $(this).siblings('#index').val();
|
||||
|
||||
$.ajax({
|
||||
url: '/',
|
||||
type: 'POST',
|
||||
data: {
|
||||
'set_num': set_num,
|
||||
'minif': minif,
|
||||
'index': index
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("body").on("change", "#s_check", function (event) {
|
||||
scheck = $(this).prop('checked');
|
||||
set_num = $(this).siblings('#set_num').val();
|
||||
index = $(this).siblings('#index').val();
|
||||
|
||||
$.ajax({
|
||||
url: '/',
|
||||
type: 'POST',
|
||||
data: {
|
||||
'set_num': set_num,
|
||||
'scheck': scheck,
|
||||
'index': index
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("body").on("change", "#s_col", function (event) {
|
||||
scol = $(this).prop('checked');
|
||||
set_num = $(this).siblings('#set_num').val();
|
||||
index = $(this).siblings('#index').val();
|
||||
|
||||
$.ajax({
|
||||
url: '/',
|
||||
type: 'POST',
|
||||
data: {
|
||||
'set_num': set_num,
|
||||
'scol': scol,
|
||||
'index': index
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const toggleButton = document.getElementById('toggleButton');
|
||||
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) {
|
||||
// Check if the corresponding checkbox is checked
|
||||
const checkbox = item.querySelector('.s_col');
|
||||
if (isHidden || (checkbox && checkbox.checked)) {
|
||||
// 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';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const toggleButton = document.getElementById('toggleButton2');
|
||||
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) {
|
||||
// Check if the corresponding checkbox is checked
|
||||
const checkbox = item.querySelector('.s_check');
|
||||
if (isHidden || (checkbox && checkbox.checked)) {
|
||||
// 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';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const toggleButton = document.getElementById('toggleButton3');
|
||||
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) {
|
||||
// Check if the corresponding checkbox is checked
|
||||
const checkbox = item.querySelector('.s_com');
|
||||
if (isHidden || (checkbox && checkbox.checked)) {
|
||||
// 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';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const themeDropdown = document.getElementById('themeDropdown');
|
||||
|
||||
// Retrieve all unique values from the <a> elements
|
||||
const themes = [];
|
||||
const themeLinks = document.querySelectorAll('.set_theme');
|
||||
themeLinks.forEach(function(link) {
|
||||
const themeId = link.textContent.trim();
|
||||
if (!themes.includes(themeId)) {
|
||||
themes.push(themeId);
|
||||
}
|
||||
});
|
||||
|
||||
// Create options for the dropdown list
|
||||
themes.forEach(function(theme) {
|
||||
const option = document.createElement('option');
|
||||
option.value = theme;
|
||||
option.textContent = theme;
|
||||
themeDropdown.appendChild(option);
|
||||
});
|
||||
|
||||
// Add event listener to filter grid items
|
||||
themeDropdown.addEventListener('change', function() {
|
||||
const selectedTheme = themeDropdown.value;
|
||||
filterGridItems(selectedTheme);
|
||||
});
|
||||
|
||||
function filterGridItems(selectedTheme) {
|
||||
const gridItems = document.querySelectorAll('.grid-item');
|
||||
gridItems.forEach(function(item) {
|
||||
const themeLink = item.querySelector('.set_theme');
|
||||
const themeId = themeLink.textContent.trim();
|
||||
if (!selectedTheme || themeId === selectedTheme) {
|
||||
item.style.display = 'block'; // Show the grid item
|
||||
} else {
|
||||
item.style.display = 'none'; // Hide the grid item
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Functions to open and close a modal
|
||||
function openModal($el) {
|
||||
$el.classList.add('is-active');
|
||||
}
|
||||
|
||||
function closeModal($el) {
|
||||
$el.classList.remove('is-active');
|
||||
}
|
||||
|
||||
function closeAllModals() {
|
||||
(document.querySelectorAll('.modal') || []).forEach(($modal) => {
|
||||
closeModal($modal);
|
||||
});
|
||||
}
|
||||
|
||||
// Add a click event on buttons to open a specific modal
|
||||
(document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => {
|
||||
const modal = $trigger.dataset.target;
|
||||
const $target = document.getElementById(modal);
|
||||
|
||||
$trigger.addEventListener('click', () => {
|
||||
openModal($target);
|
||||
});
|
||||
});
|
||||
|
||||
// Add a click event on various child elements to close the parent modal
|
||||
(document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button') || []).forEach(($close) => {
|
||||
const $target = $close.closest('.modal');
|
||||
|
||||
$close.addEventListener('click', () => {
|
||||
closeModal($target);
|
||||
});
|
||||
});
|
||||
|
||||
// Add a keyboard event to close all modals
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === "Escape") {
|
||||
closeAllModals();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+40
-929
@@ -1,931 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="light" class="has-navbar-fixed-top">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Set Overview</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
{% extends 'base.html' %}
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
.my-input::placeholder {
|
||||
color: var(--bulma-grey-dark);
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
/* horizontal scrollbar for tables if mobile screen */
|
||||
.hidden-mobile {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
}
|
||||
.dropdown-menu {
|
||||
width: max-content; /* Remove default width */
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.dropdown.is-right .dropdown-menu {
|
||||
right: 0; /* Align dropdown to the right of the trigger */
|
||||
left: auto;
|
||||
}
|
||||
|
||||
.dropdown.is-left .dropdown-menu {
|
||||
left: 0; /* Align dropdown to the left of the trigger */
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
display: grid;
|
||||
/* grid-template-columns: repeat(auto-fit, minmax(10%, 23%)); /* Between 33% and 50% width */
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
height:auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
height: 100%;
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
padding-bottom:30px; /* Height of the footer */
|
||||
}
|
||||
|
||||
/* Adjust height for smaller screens */
|
||||
@media (max-width: 768px) {
|
||||
.card {
|
||||
height: auto; /* Let the height adjust dynamically */
|
||||
}
|
||||
.grid-container {
|
||||
grid-template-columns: 100%; /* On mobile, set width to 100% */
|
||||
}
|
||||
.grid-item {
|
||||
height: auto; /* Let the height adjust dynamically */
|
||||
overflow: visible; /* Allow content to overflow if necessary */
|
||||
}
|
||||
.navbar-item {
|
||||
&.has-dropdown {
|
||||
.navbar-dropdown {
|
||||
display: none;
|
||||
}
|
||||
&.is-hoverable:hover {
|
||||
.navbar-dropdown {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.grid-item img {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
|
||||
}
|
||||
.card p {
|
||||
margin: 0; /* Remove default margin */
|
||||
}
|
||||
.card-footer {
|
||||
|
||||
position:absolute;
|
||||
bottom:0;
|
||||
width:90%;
|
||||
height:30px; /* Height of the footer */
|
||||
}
|
||||
|
||||
|
||||
.search-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar is-dark is-fixed-top is-size-6" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
Home
|
||||
</a>
|
||||
|
||||
<a class="navbar-item" href="/create">
|
||||
Add Set
|
||||
</a>
|
||||
<a class="navbar-item hidden-mobile" href="/missing">
|
||||
Missing
|
||||
</a>
|
||||
<a class="navbar-item hidden-mobile" href="/parts">
|
||||
Parts
|
||||
</a>
|
||||
<a class="navbar-item hidden-mobile" href="/minifigs">
|
||||
Minifigs
|
||||
</a>
|
||||
<a class="navbar-item hidden-mobile" href="/wishlist">
|
||||
Wishlist
|
||||
</a>
|
||||
<a class="navbar-item hidden-mobile" href="/config">
|
||||
Config
|
||||
</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">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div id="navMenu" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<a class="navbar-link">
|
||||
Show
|
||||
</a>
|
||||
<div class="navbar-dropdown">
|
||||
<a class="navbar-item" id="toggleButton">
|
||||
Collected and boxed
|
||||
</a>
|
||||
<a class="navbar-item" id="toggleButton2">
|
||||
Checked for missing pieces
|
||||
</a>
|
||||
<a class="navbar-item" id="toggleButton3">
|
||||
Complete set without missing pieces
|
||||
</a>
|
||||
<a class="navbar-item" id="toggleButton4">
|
||||
Sets with missing pieces
|
||||
</a>
|
||||
<a class="navbar-item" id="toggleButton5">
|
||||
Missing instructions
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<a class="navbar-link">
|
||||
Select Theme
|
||||
</a>
|
||||
<div class="navbar-dropdown" id="themeDropdown">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<a class="navbar-link">
|
||||
Sort by
|
||||
</a>
|
||||
|
||||
<div class="navbar-dropdown">
|
||||
<a class="navbar-item" onclick="dynamicSort('set_id')">
|
||||
ID
|
||||
</a>
|
||||
<a class="navbar-item" onclick="dynamicSort('set_year')">
|
||||
Year
|
||||
</a>
|
||||
<a class="navbar-item" onclick="dynamicSort('set_parts')">
|
||||
Parts
|
||||
</a>
|
||||
<a class="navbar-item" onclick="dynamicSort('set_theme')">
|
||||
Theme
|
||||
</a>
|
||||
<a class="navbar-item" onclick="dynamicSort('set_name')">
|
||||
Name
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="search-container">
|
||||
<input class="input my-input" type="text" id="searchInput" onkeyup="searchFunction()" placeholder="Search title, set number, theme or parts...">
|
||||
<!-- <center hidden="true">
|
||||
<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_parts')">Sort by Parts</button>
|
||||
<button class="button button-outline" onclick="dynamicSort('set_theme')">Sort by Theme</button>
|
||||
<button class="button button-outline" onclick="dynamicSort('set_name')">Sort by Name</button>
|
||||
<button class="button button-outline" onclick="dynamicSort('s_col')">Sort by Collected</button>
|
||||
<button class="button button-outline" onclick="dynamicSort('s_check')">Sort by Checked</button>
|
||||
</center> -->
|
||||
<!-- <center>
|
||||
<button class="button button-outline" id="toggleButton">Show Collected</button>
|
||||
<button class="button button-outline" id="toggleButton2">Show Checked</button>
|
||||
<button class="button button-outline" id="toggleButton3">Show Complete</button>
|
||||
</center> -->
|
||||
|
||||
<!-- Add more buttons for other text values if needed -->
|
||||
{% block main %}
|
||||
<div class="container-fluid">
|
||||
<h2 class="border-bottom lh-base pb-1">
|
||||
<i class="ri-hashtag"></i> {% if config['RANDOM'].value %}Random selection of{% else %}Latest added{% endif %} sets
|
||||
{% if not config['HIDE_ALL_SETS'].value %}
|
||||
<a href="{{ url_for('set.list') }}" class="btn btn-sm btn-primary ms-1">All sets</a>
|
||||
{% endif %}
|
||||
</h2>
|
||||
{% if brickset_collection | length %}
|
||||
<div class="row" id="grid">
|
||||
{% for item in brickset_collection %}
|
||||
<div class="col-md-4 col-xl-2 d-flex align-items-stretch">
|
||||
{% with solo=false, tiny=true, last=true %}
|
||||
{% include 'set/card.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="grid-container" id="gridContainer">
|
||||
{% for i in set_list %}
|
||||
<div class="grid-item">
|
||||
<div class="card">
|
||||
<div class="columns" style="">
|
||||
<div class="column is-full" style="text-align: left;">
|
||||
<p class="is-size-5 searchTitle">
|
||||
{% if links == 'True' %}
|
||||
<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>
|
||||
<span></span>
|
||||
<a class="is-size-5" style="color: #363636;float:right;"><b>Parts:</b> <span class='set_parts'>{{ i[4] }}</span></a>
|
||||
</p>
|
||||
</div>
|
||||
<!--<div class="column" style="text-align: left;">
|
||||
<div class="is-size-5">
|
||||
<b>Parts:</b>
|
||||
<span class='set_parts'>{{ i[4] }}</span>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="columns" style="">
|
||||
<div class="column is-half" style="">
|
||||
<figure class="image is-4by3">
|
||||
<a href="/{{ i[0] }}/{{ i[11] }}">
|
||||
<img style='height: 100%; width: 100%; object-fit: contain' src="/static/sets/{{ i[0] }}.jpg" alt="Image" loading="lazy">
|
||||
</a>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
<div class="column is-half" style="display: flex;align-items: center;">
|
||||
<div class="is-size-7">
|
||||
{% if i[0] in minifigs %}
|
||||
<label class="checkbox" >
|
||||
<input type="hidden" id="set_num" value="{{ i[0] }}">
|
||||
<input type="hidden" id="u_id" value="{{ i[11] }}">
|
||||
{% if i[8] == 0 %}
|
||||
<input class="s_fig" id="s_fig" type="checkbox" />
|
||||
{% else %}
|
||||
<input class="s_fig" id="s_fig" type="checkbox" checked />
|
||||
{% endif %}
|
||||
<!-- {{ i[8] }}<br>
|
||||
{{ i[9] }}<br>
|
||||
{{ i[10] }}<br> -->
|
||||
|
||||
|
||||
Minifigs Collected
|
||||
</label>
|
||||
<br>
|
||||
{% endif %}
|
||||
<label class="checkbox" >
|
||||
<input type="hidden" id="set_num" value="{{ i[0] }}">
|
||||
<input type="hidden" id="u_id" value="{{ i[11] }}">
|
||||
{% if i[9] == 0 %}
|
||||
<input class="s_check" id="s_check" type="checkbox" />
|
||||
{% else %}
|
||||
<input class="s_check" id="s_check" type="checkbox" checked />
|
||||
{% endif %}
|
||||
Set is checked
|
||||
</label>
|
||||
<br>
|
||||
<label class="checkbox" >
|
||||
<input type="hidden" id="set_num" value="{{ i[0] }}">
|
||||
<input type="hidden" id="u_id" value="{{ i[11] }}">
|
||||
{% if i[10] == 0 %}
|
||||
<input class="s_col" id="s_col" type="checkbox" />
|
||||
{% else %}
|
||||
<input class="s_col" id="s_col" type="checkbox" checked />
|
||||
{% endif %}
|
||||
Set is collected and boxed
|
||||
</label>
|
||||
<br>
|
||||
<!-- <fieldset disabled> -->
|
||||
<label class="checkbox" >
|
||||
{% if i[11] in missing_list %}
|
||||
<input type="checkbox" class="s_com" onclick="return false;" checked/> Set is missing pieces
|
||||
{% else %}
|
||||
<input type="checkbox" class="s_com" onclick="return false;"/> Set is missing pieces
|
||||
{% endif %}
|
||||
</label>
|
||||
<!-- </fieldset> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="card-footer" style="width:95%;">
|
||||
<p class="card-footer-item"style="">
|
||||
<span>
|
||||
<span class="is-size-7">{{ i[11] }}</span>
|
||||
|
||||
</span>
|
||||
</p>
|
||||
|
||||
{% set ns = namespace(found=false) %}
|
||||
{% for file in files %}
|
||||
{% if ns.found is sameas false and file.startswith(i[0]) %}
|
||||
|
||||
<div class="card-footer-item dropdown">
|
||||
<div class="dropdown-trigger">
|
||||
<button class="is-size-6" aria-haspopup="true" aria-controls="dropdown-menu3">
|
||||
<span>
|
||||
<a id="inst-link" class="is-size-6" style="color: #363636;">Inst.</a>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-menu" id="dropdown-menu3" role="menu">
|
||||
<div class="dropdown-content" style="width:max-content;">
|
||||
<!-- <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 %}
|
||||
{% if x.startswith(i[0]) %}
|
||||
<a href="/files/{{ x }}" target="_blank" class="dropdown-item">{{ x }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% set ns.found = true %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<p class="card-footer-item" style="">
|
||||
<span>
|
||||
<a class="is-size-6" style="color: #363636;" href="/{{ i[0] }}/{{ i[11] }}">Inv.</a>
|
||||
</span>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" id="modal-inst">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Instructions</p>
|
||||
<button class="delete" aria-label="close"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<span>
|
||||
{% for file in files %}
|
||||
<span>
|
||||
<a> {{ file }} </a></span>
|
||||
{% if file.startswith(i[0]) %}
|
||||
<a class="inst is-size-6" style="color: #363636;" href="/files/{{ file }}.pdf">{{ file }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</span>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<div class="buttons">
|
||||
<button class="button">Cancel</button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
<script>
|
||||
|
||||
|
||||
|
||||
|
||||
//var observer = new IntersectionObserver(function(entries, observer) {
|
||||
// entries.forEach(function(entry) {
|
||||
// if (entry.isIntersecting) {
|
||||
// var link = entry.target;
|
||||
// var pdfUrl = link.getAttribute('href');
|
||||
// var xhr = new XMLHttpRequest();
|
||||
// xhr.open('HEAD', pdfUrl, true);
|
||||
|
||||
// xhr.onload = function() {
|
||||
// if (xhr.status !== 200) {
|
||||
// If PDF is not found, disable the link
|
||||
// link.style.pointerEvents = 'none';
|
||||
// link.style.color = '#ccc'; // Optionally, change link color to indicate it's disabled
|
||||
// } else {
|
||||
// link.style.pinterEvents = 'auto';
|
||||
// }
|
||||
|
||||
// };
|
||||
|
||||
// xhr.send();
|
||||
// observer.unobserve(link);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
// var links = document.querySelectorAll('.inst');
|
||||
// links.forEach(function(link) {
|
||||
// observer.observe(link);
|
||||
// });
|
||||
|
||||
function testURL(pdfUrl,link){
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('HEAD', pdfUrl, true);
|
||||
|
||||
xhr.onload = function() {
|
||||
if (xhr.status !== 200) {
|
||||
// If PDF is not found, disable the link
|
||||
link.style.pointerEvents = 'none';
|
||||
console.log('not')
|
||||
link.style.color = '#ccc'; // Optionally, change link color to indicate it's disabled
|
||||
} else {
|
||||
link.style.pinterEvents = 'auto';
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
}
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
|
||||
function searchFunction() {
|
||||
var input, filter, gridContainer, gridItems, i, txtValue;
|
||||
input = document.getElementById('searchInput');
|
||||
filter = input.value.toUpperCase();
|
||||
gridContainer = document.getElementById('gridContainer');
|
||||
gridItems = gridContainer.getElementsByClassName('grid-item');
|
||||
searchTerm = gridContainer.getElementsByClassName('searchTitle');
|
||||
for (i = 0; i < searchTerm.length; i++) {
|
||||
txtValue = searchTerm[i].textContent || searchTerm[i].innerText;
|
||||
if (txtValue.toUpperCase().indexOf(filter) > -1) {
|
||||
gridItems[i].style.display = "";
|
||||
} else {
|
||||
gridItems[i].style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
function sortFunction() {
|
||||
var sortSelect, sortValue, gridContainer, gridItems, sortedItems, i;
|
||||
sortSelect = document.getElementById('sortSelect');
|
||||
sortValue = sortSelect.value;
|
||||
gridContainer = document.getElementById('gridContainer');
|
||||
gridItems = gridContainer.getElementsByClassName('grid-item');
|
||||
sortedItems = Array.from(gridItems);
|
||||
|
||||
if (sortValue === 'alphabetical') {
|
||||
sortedItems.sort(function(a, b) {
|
||||
var textA = a.textContent.trim().toUpperCase();
|
||||
var textB = b.textContent.trim().toUpperCase();
|
||||
if (textA < textB) {
|
||||
return -1;
|
||||
}
|
||||
if (textA > textB) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
// Remove existing items
|
||||
while (gridContainer.firstChild) {
|
||||
gridContainer.removeChild(gridContainer.firstChild);
|
||||
}
|
||||
|
||||
// Append sorted items
|
||||
for (i = 0; i < sortedItems.length; i++) {
|
||||
gridContainer.appendChild(sortedItems[i]);
|
||||
}
|
||||
}
|
||||
var sortOrder = 'asc';
|
||||
|
||||
function dynamicSort(className) {
|
||||
console.log(className);
|
||||
var gridContainer, gridItems, sortedItems, i;
|
||||
gridContainer = document.getElementById('gridContainer');
|
||||
gridItems = gridContainer.getElementsByClassName('grid-item');
|
||||
sortedItems = Array.from(gridItems);
|
||||
|
||||
sortedItems.sort(function(a, b) {
|
||||
var textA = a.getElementsByClassName(className)[0].textContent.trim();
|
||||
var textB = b.getElementsByClassName(className)[0].textContent.trim();
|
||||
// Check if the className corresponds to a checkbox
|
||||
if (className == 's_col' || className == 's_check') {
|
||||
var checkedA = a.getElementsByClassName(className)[0].checked;
|
||||
var checkedB = b.getElementsByClassName(className)[0].checked;
|
||||
// Sort by checked checkboxes (true first, false last)
|
||||
return (checkedA === checkedB) ? 0 : checkedA ? -1 : 1;
|
||||
}
|
||||
|
||||
|
||||
// Remove digits after hyphen if present
|
||||
textA = textA.replace(/-\d+$/, '').trim();
|
||||
textB = textB.replace(/-\d+$/, '').trim();
|
||||
// If text1 is an integer, parse it as a number
|
||||
if (!isNaN(textA) && !isNaN(textB)) {
|
||||
var result = parseInt(textA) - parseInt(textB);
|
||||
// Toggle result based on current sort order
|
||||
return sortOrder === 'asc' ? result : -result;
|
||||
}
|
||||
// If text1 is not an integer, treat it as a string
|
||||
else {
|
||||
textA = textA.toUpperCase();
|
||||
textB = textB.toUpperCase();
|
||||
var result = textA.localeCompare(textB);
|
||||
// Toggle result based on current sort order
|
||||
return sortOrder === 'asc' ? result : -result;
|
||||
}
|
||||
});
|
||||
|
||||
// Reverse sort order for next click
|
||||
sortOrder = sortOrder === 'asc' ? 'desc' : 'asc';
|
||||
|
||||
// Remove existing items
|
||||
while (gridContainer.firstChild) {
|
||||
gridContainer.removeChild(gridContainer.firstChild);
|
||||
}
|
||||
|
||||
// Append sorted items
|
||||
for (i = 0; i < sortedItems.length; i++) {
|
||||
gridContainer.appendChild(sortedItems[i]);
|
||||
}
|
||||
}
|
||||
|
||||
$("body").on("change", "#s_fig", function (event) {
|
||||
minif = $(this).prop('checked');
|
||||
set_num = $(this).siblings('#set_num').val();
|
||||
u_id = $(this).siblings('#u_id').val();
|
||||
|
||||
$.ajax({
|
||||
url: '/',
|
||||
type: 'POST',
|
||||
data: {
|
||||
'set_num': set_num,
|
||||
'minif': minif,
|
||||
'u_id': u_id
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("body").on("change", "#s_check", function (event) {
|
||||
scheck = $(this).prop('checked');
|
||||
set_num = $(this).siblings('#set_num').val();
|
||||
u_id = $(this).siblings('#u_id').val();
|
||||
|
||||
$.ajax({
|
||||
url: '/',
|
||||
type: 'POST',
|
||||
data: {
|
||||
'set_num': set_num,
|
||||
'scheck': scheck,
|
||||
'u_id': u_id
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("body").on("change", "#s_col", function (event) {
|
||||
scol = $(this).prop('checked');
|
||||
set_num = $(this).siblings('#set_num').val();
|
||||
u_id = $(this).siblings('#u_id').val();
|
||||
|
||||
$.ajax({
|
||||
url: '/',
|
||||
type: 'POST',
|
||||
data: {
|
||||
'set_num': set_num,
|
||||
'scol': scol,
|
||||
'u_id': u_id
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const toggleButton = document.getElementById('toggleButton');
|
||||
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) {
|
||||
// Check if the corresponding checkbox is checked
|
||||
const checkbox = item.querySelector('.s_col');
|
||||
if (isHidden || (checkbox && checkbox.checked)) {
|
||||
// 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';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const toggleButton = document.getElementById('toggleButton2');
|
||||
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) {
|
||||
// Check if the corresponding checkbox is checked
|
||||
const checkbox = item.querySelector('.s_check');
|
||||
if (isHidden || (checkbox && checkbox.checked)) {
|
||||
// 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';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const toggleButton = document.getElementById('toggleButton3');
|
||||
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) {
|
||||
// Check if the corresponding checkbox is checked
|
||||
const checkbox = item.querySelector('.s_com');
|
||||
if (isHidden || (checkbox && !checkbox.checked)) {
|
||||
// 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';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const toggleButton = document.getElementById('toggleButton4');
|
||||
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) {
|
||||
// Check if the corresponding checkbox is checked
|
||||
const checkbox = item.querySelector('.s_com');
|
||||
if (isHidden || (checkbox && checkbox.checked)) {
|
||||
// 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';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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 to remove leading articles
|
||||
function removeArticle(theme) {
|
||||
return theme.replace(/^(a |the )/i, '');
|
||||
}
|
||||
|
||||
// Get the themes without leading articles
|
||||
const themeA = removeArticle(a);
|
||||
const themeB = removeArticle(b);
|
||||
|
||||
// Compare the themes without leading articles
|
||||
return themeA.localeCompare(themeB);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const themeDropdown = document.getElementById('themeDropdown');
|
||||
|
||||
// Retrieve all unique values from the <a> elements
|
||||
const themes = [];
|
||||
const themeLinks = document.querySelectorAll('.set_theme');
|
||||
themeLinks.forEach(function(link) {
|
||||
const themeId = link.textContent.trim();
|
||||
if (!themes.includes(themeId)) {
|
||||
themes.push(themeId);
|
||||
}
|
||||
});
|
||||
themes.sort(customSort);
|
||||
themes.unshift('> Show all <');
|
||||
// Create options for the dropdown list
|
||||
themes.forEach(function(theme) {
|
||||
const option = document.createElement('a');
|
||||
option.className = "navbar-item";
|
||||
option.value = theme;
|
||||
option.textContent = theme;
|
||||
themeDropdown.appendChild(option);
|
||||
option.addEventListener('click', function() {
|
||||
filterGridItems(theme);
|
||||
});
|
||||
});
|
||||
|
||||
// Add event listener to filter grid items
|
||||
// themeDropdown.addEventListener('click', function() {
|
||||
// console.log($(this));
|
||||
// const selectedTheme = themeDropdown.value;
|
||||
// filterGridItems(selectedTheme);
|
||||
// });
|
||||
|
||||
function filterGridItems(selectedTheme) {
|
||||
const gridItems = document.querySelectorAll('.grid-item');
|
||||
gridItems.forEach(function(item) {
|
||||
const themeLink = item.querySelector('.set_theme');
|
||||
const themeId = themeLink.textContent.trim();
|
||||
if (!selectedTheme || themeId === selectedTheme || selectedTheme == '> Show all <') {
|
||||
item.style.display = 'block'; // Show the grid item
|
||||
} else {
|
||||
item.style.display = 'none'; // Hide the grid item
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
// Check for click events on the navbar burger icon
|
||||
$(".navbar-burger").click(function() {
|
||||
|
||||
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
||||
$(".navbar-burger").toggleClass("is-active");
|
||||
$(".navbar-menu").toggleClass("is-active");
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
// Check for click events on the navbar burger icon
|
||||
$(".navbar-item").click(function() {
|
||||
|
||||
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
|
||||
$(".navbar-burger").removeClass("is-active");
|
||||
$(".navbar-menu").removeClass("is-active");
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Functions to open and close a modal
|
||||
function openModal($el) {
|
||||
$el.classList.add('is-active');
|
||||
}
|
||||
|
||||
function closeModal($el) {
|
||||
$el.classList.remove('is-active');
|
||||
}
|
||||
|
||||
function closeAllModals() {
|
||||
(document.querySelectorAll('.modal') || []).forEach(($modal) => {
|
||||
closeModal($modal);
|
||||
});
|
||||
}
|
||||
|
||||
// Add a click event on buttons to open a specific modal
|
||||
(document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => {
|
||||
const modal = $trigger.dataset.target;
|
||||
const $target = document.getElementById(modal);
|
||||
|
||||
$trigger.addEventListener('click', () => {
|
||||
openModal($target);
|
||||
});
|
||||
});
|
||||
|
||||
// Add a click event on various child elements to close the parent modal
|
||||
(document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button') || []).forEach(($close) => {
|
||||
const $target = $close.closest('.modal');
|
||||
|
||||
$close.addEventListener('click', () => {
|
||||
closeModal($target);
|
||||
});
|
||||
});
|
||||
|
||||
// Add a keyboard event to close all modals
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if(event.key === "Escape") {
|
||||
closeAllModals();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//var dropdown = document.querySelector('.dropdown');
|
||||
//dropdown.addEventListener('click', function(event) {
|
||||
// event.stopPropagation();
|
||||
// dropdown.classList.toggle('is-active');
|
||||
//});
|
||||
|
||||
|
||||
// Get all dropdowns on the page that aren't hoverable.
|
||||
const dropdowns = document.querySelectorAll('.dropdown:not(.is-hoverable)');
|
||||
|
||||
if (dropdowns.length > 0) {
|
||||
// For each dropdown, add event handler to open/close on click.
|
||||
dropdowns.forEach(function(el) {
|
||||
el.addEventListener('click', function(e) {
|
||||
// Prevent click from propagating to document
|
||||
e.stopPropagation();
|
||||
|
||||
// Toggle the 'is-active' class, which will open or close the dropdown
|
||||
el.classList.toggle('is-active');
|
||||
});
|
||||
});
|
||||
|
||||
// If user clicks outside dropdown, close it.
|
||||
document.addEventListener('click', function(e) {
|
||||
closeDropdowns();
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Close dropdowns by removing `is-active` class.
|
||||
*/
|
||||
function closeDropdowns() {
|
||||
dropdowns.forEach(function(el) {
|
||||
el.classList.remove('is-active');
|
||||
});
|
||||
}
|
||||
|
||||
// Close dropdowns if ESC pressed
|
||||
document.addEventListener('keydown', function (event) {
|
||||
let e = event || window.event;
|
||||
if (e.key === 'Esc' || e.key === 'Escape') {
|
||||
closeDropdowns();
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{% else %}
|
||||
{% include 'set/empty.html' %}
|
||||
{% endif %}
|
||||
{% if minifigure_collection | length %}
|
||||
<h2 class="border-bottom lh-base pb-1">
|
||||
<i class="ri-group-line"></i> {% if config['RANDOM'].value %}Random selection of{% else %}Latest added{% endif %} minifigures
|
||||
{% if not config['HIDE_ALL_MINIFIGURES'].value %}
|
||||
<a href="{{ url_for('minifigure.list') }}" class="btn btn-sm btn-primary ms-1">All minifigures</a>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<div class="row" id="grid">
|
||||
{% for item in minifigure_collection %}
|
||||
<div class="col-md-4 col-xl-2 d-flex align-items-stretch">
|
||||
{% with solo=false, tiny=true, last=true %}
|
||||
{% include 'minifigure/card.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,25 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - All instructions{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% if upload %}
|
||||
{% include 'instructions/upload.html' %}
|
||||
{% elif rename %}
|
||||
{% include 'instructions/rename.html' %}
|
||||
{% elif delete %}
|
||||
{% include 'instructions/delete.html' %}
|
||||
{% else %}
|
||||
<div class="container-fluid px-0">
|
||||
{% if g.login.is_authenticated() %}
|
||||
<p class="border-bottom pb-2 px-2 text-center">
|
||||
<a class="btn btn-primary" href="{{ url_for('instructions.upload') }}"><i class="ri-upload-line"></i> Upload an instructions file</a>
|
||||
<a href="{{ url_for('admin.admin', open_instructions=true) }}" class="btn btn-light border" role="button"><i class="ri-refresh-line"></i> Refresh the instructions cache</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% with all=true %}
|
||||
{% include 'instructions/table.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,35 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
{% import 'macro/card.html' as card %}
|
||||
|
||||
<div class="container">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="ri-file-line"></i> Instructions</h5>
|
||||
</div>
|
||||
<div class="accordion accordion-flush" id="instructions">
|
||||
{{ accordion.header('Instructions danger zone', 'instructions-delete', 'instructions', expanded=true, danger=true) }}
|
||||
{% if item.brickset %}
|
||||
<div class="d-flex justify-content-center">
|
||||
{% with item=item.brickset %}
|
||||
{% include 'set/mini.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form action="{{ url_for('instructions.do_delete', name=item.filename) }}" method="post">
|
||||
{% if error %}<div class="alert alert-danger" role="alert"><strong>Error:</strong> {{ error }}.</div>{% endif %}
|
||||
<div class="alert alert-danger text-center" role="alert">You are about to <strong>delete the instruction file {{ item.filename }}</strong>. This action is irreversible.</div>
|
||||
<div class="text-end">
|
||||
<a class="btn btn-danger" href="{{ url_for('instructions.list') }}" role="button"><i class="ri-arrow-left-long-line"></i> Back to the instructions</a>
|
||||
<button type="submit" class="btn btn-danger"><i class="ri-delete-bin-2-line"></i> Delete <strong>the instruction file {{ item.filename }}</strong></button>
|
||||
</div>
|
||||
</form>
|
||||
{{ accordion.footer() }}
|
||||
</div>
|
||||
<div class="card-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
baguetteBox.run('[data-lightbox]');
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,41 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
{% import 'macro/card.html' as card %}
|
||||
|
||||
<div class="container">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="ri-file-line"></i> Instructions</h5>
|
||||
</div>
|
||||
<div class="accordion accordion-flush" id="instructions">
|
||||
{{ accordion.header('Management', 'instructions-rename', 'instructions', expanded=true) }}
|
||||
{% if item.brickset %}
|
||||
<div class="d-flex justify-content-center">
|
||||
{% with item=item.brickset %}
|
||||
{% include 'set/mini.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form action="{{ url_for('instructions.do_rename', name=item.filename) }}" method="post">
|
||||
{% if error %}<div class="alert alert-danger" role="alert"><strong>Error:</strong> {{ error }}.</div>{% endif %}
|
||||
<div class="mb-3">
|
||||
<label for="filename" class="form-label">File name</label> (current name: <code>{{ item.filename }}</code>)
|
||||
<div class="input-group">
|
||||
<input type="input" class="form-control" id="filename" name="filename" value="{{ item.name }}" autocomplete="off">
|
||||
<span class="input-group-text">{{ item.extension }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<a class="btn btn-primary" href="{{ url_for('instructions.list') }}" role="button"><i class="ri-arrow-left-long-line"></i> Back to the instructions</a>
|
||||
<button type="submit" class="btn btn-primary"><i class="ri-edit-line"></i> Rename</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ accordion.footer() }}
|
||||
</div>
|
||||
<div class="card-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
baguetteBox.run('[data-lightbox]');
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,51 @@
|
||||
{% import 'macro/table.html' as table %}
|
||||
|
||||
<div class="table-responsive-sm">
|
||||
<table class="table table-striped align-middle" id="instructions">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><i class="ri-file-line fw-normal"></i> Filename</th>
|
||||
<th scope="col"><i class="ri-hashtag fw-normal"></i> Set</th>
|
||||
<th class="no-sort" scope="col"><i class="ri-image-line fw-normal"></i> Image</th>
|
||||
{% if g.login.is_authenticated() %}
|
||||
<th class="no-sort" scope="col"><i class="ri-settings-4-line fw-normal"></i> Actions</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in table_collection %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if item.allowed %}
|
||||
<a class="text-reset" href="{{ item.url() }}" target="_blank">
|
||||
{%- endif -%}
|
||||
<i class="ri-{{ item.icon() }}"></i> {{ item.filename }}
|
||||
{%- if item.allowed -%}
|
||||
</a>
|
||||
{% endif %}
|
||||
<span class="badge rounded-pill text-bg-info fw-normal"><i class="ri-hard-drive-line"></i> {{ item.human_size() }}</span>
|
||||
<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 %}
|
||||
</td>
|
||||
{% if item.brickset %}
|
||||
{{ table.image(item.brickset.url_for_image(), caption=item.brickset.fields.name, alt=item.brickset.fields.set_num) }}
|
||||
{% else %}
|
||||
<td><i class="ri-file-warning-line"></i></td>
|
||||
{% endif %}
|
||||
{% if g.login.is_authenticated() %}
|
||||
<td>
|
||||
<a href="{{ url_for('instructions.rename', name=item.filename) }}" class="btn btn-sm btn-primary"><i class="ri-edit-line"></i> Rename</a>
|
||||
<a href="{{ url_for('instructions.delete', name=item.filename) }}" class="btn btn-sm btn-danger"><i class="ri-delete-bin-2-line"></i> Delete</a>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if all %}
|
||||
{{ table.dynamic('instructions', no_sort='2,3')}}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,42 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
{% import 'macro/card.html' as card %}
|
||||
|
||||
<div class="container">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="ri-file-line"></i> Instructions</h5>
|
||||
</div>
|
||||
<div class="accordion accordion-flush" id="instructions">
|
||||
{{ accordion.header('Management', 'instructions-upload', 'instructions', expanded=true) }}
|
||||
<form action="{{ url_for('instructions.do_upload') }}" method="post" enctype="multipart/form-data">
|
||||
{% if error %}<div class="alert alert-danger" role="alert"><strong>Error:</strong> {{ error }}.</div>{% endif %}
|
||||
<div class="alert alert-info" role="alert">
|
||||
<strong>File naming convention:</strong> You are free to use any name you want for your intructions files. <br>
|
||||
However if you want an instructions file to be associated to a set, the filename must start with the set number (<code>number-version</code>). <br>
|
||||
For instance, you could use:
|
||||
<ul>
|
||||
<li>Instructions.pdf: will <strong>not be</strong> matched to any set</li>
|
||||
<li>107.pdf: will <strong>not be</strong> matched to set <code>107-1</code></li>
|
||||
<li>107-1.pdf: will be matched to set <code>107-1</code></li>
|
||||
<li>107-1-2.pdf: will be matched to set <code>107-1</code></li>
|
||||
<li>107-1 Special instructions.pdf: will be matched to set <code>107-1</code></li>
|
||||
<li>107-1_Special_instructions.pdf: will be matched to set <code>107-1</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="file" class="form-label">Instructions file</label>
|
||||
<div class="input-group">
|
||||
<input type="file" class="form-control" id="file" name="file" accept="{{ ','.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value) }}">
|
||||
<span class="input-group-text">{{ ', '.join(config['INSTRUCTIONS_ALLOWED_EXTENSIONS'].value) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<a class="btn btn-primary" href="{{ url_for('instructions.list') }}" role="button"><i class="ri-arrow-left-long-line"></i> Back to the instructions</a>
|
||||
<button type="submit" class="btn btn-primary"><i class="ri-upload-line"></i> Upload an instructions file</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ accordion.footer() }}
|
||||
</div>
|
||||
<div class="card-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,37 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - Login{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="col-12">
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0"><i class="ri-login-circle-line"></i> Login</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if wrong_password %}
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<h4 class="alert-heading">Wrong password!</h4>
|
||||
<p>The password you submitted is incorrect.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<form action="{{ url_for('login.do_login', next=next) }}" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="password" name="password">
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,69 @@
|
||||
{% macro header(title, id, parent, size=none, total=none, quantity=none, expanded=false, icon=none, class=none, danger=none, image=none, alt=none) %}
|
||||
{% if danger %}
|
||||
{% set icon='alert-fill' %}
|
||||
{% endif %}
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button {% if danger %}text-danger{% endif %} {% if not expanded %}collapsed{% endif %} {% if image %}py-0 ps-0{% endif %}" type="button" data-bs-toggle="collapse" data-bs-target="#{{ id }}" aria-expanded="{% if expanded %}true{% else %}false{% endif %}" aria-controls="{{ id }}">
|
||||
{% if image %}
|
||||
<img class="accordion-img" src="{{ image }}" {% if alt %}alt="{{ alt }}"{% endif %} loading="lazy">
|
||||
{% elif icon %}
|
||||
<i class="ri-{{ icon }} me-1"></i>
|
||||
{% endif %}
|
||||
{{ title }}
|
||||
{% if size and total %}
|
||||
({{ total }} total | {{ size }} unique)
|
||||
{% elif size and quantity %}
|
||||
(x{{ quantity }} | {{ size }} unique)
|
||||
{% elif size %}
|
||||
({{ size }})
|
||||
{% endif %}
|
||||
</button>
|
||||
</h2>
|
||||
<div id="{{ id }}" class="accordion-collapse collapse {% if expanded %}show{% endif %}" {% if not config['INDEPENDENT_ACCORDIONS'].value %}data-bs-parent="#{{ parent }}"{% endif %}>
|
||||
<div class="accordion-body {% if class %}{{ class }}{% endif %}">
|
||||
{% endmacro %}
|
||||
|
||||
{% macro footer() %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro cards(card_collection, title, id, parent, target, icon=none) %}
|
||||
{% set size=card_collection | length %}
|
||||
{% if size %}
|
||||
{{ header(title, id, parent, size=size, icon=icon) }}
|
||||
<div class="row">
|
||||
{% for item in card_collection %}
|
||||
<div class="col-md-6 col-xl-3 d-flex align-items-stretch">
|
||||
{% with solo=false, tiny=true %}
|
||||
{% include target %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ footer() }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro table(table_collection, title, id, parent, target, total=none, quantity=none, icon=none, image=none, alt=none, details=none, no_missing=none, read_only_missing=none) %}
|
||||
{% set size=table_collection | length %}
|
||||
{% if size %}
|
||||
{{ header(title, id, parent, size=size, total=total, quantity=quantity, icon=icon, class='p-0', image=image, alt=alt) }}
|
||||
{% if details %}
|
||||
<p class="border-top border-bottom p-2 text-center">
|
||||
{% if image %}
|
||||
<a data-lightbox data-caption="{{ title }}" href="{{ image }}" target="_blank" class="text-decoration-none text-reset">
|
||||
<img class="accordion-img" src="{{ image }}" {% if alt %}alt="{{ alt }}"{% endif %} loading="lazy">
|
||||
</a>
|
||||
{% endif %}
|
||||
<a class="btn border bg-secondary-text" href="{{ details }}">{% if icon %}<i class="ri-{{ icon }}"></i>{% endif %} Details</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% with solo=true, all=false %}
|
||||
{% include target %}
|
||||
{% endwith %}
|
||||
{{ footer() }}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,80 @@
|
||||
{% macro badge(check=none, url=none, solo=false, last=false, color='primary', blank=none, icon=none, alt=none, collapsible=none, text=none, tooltip=none) %}
|
||||
{% if check or url %}
|
||||
{% if url %}
|
||||
<a href="{{ url }}" {% if blank %}target="_blank"{% endif %}
|
||||
{% else %}
|
||||
<span
|
||||
{% endif %}
|
||||
class="badge text-bg-{{ color }} fw-normal mb-1 {% if solo %}fs-6{% endif %}" {% if alt %}alt="{{ alt }}"{% endif %}
|
||||
{% if tooltip %} data-bs-toggle="tooltip" title="{{ tooltip }}"{% endif %}
|
||||
>
|
||||
{% if icon %}<i class="ri-{{ icon }}"></i>{% endif %}
|
||||
{% if collapsible and not last %}<span {% if not solo %}class="d-none d-md-inline"{% endif %}> {{ collapsible }} </span>{% endif %}
|
||||
{% if text %}{{ text }}{% endif %}
|
||||
{% if url %}
|
||||
</a>
|
||||
{% else %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bricklink(item, solo=false, last=false) %}
|
||||
{{ badge(url=item.url_for_bricklink(), solo=solo, last=last, blank=true, color='light border', icon='external-link-line', collapsible='Bricklink', alt='Bricklink') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro instructions(item, solo=false, last=false) %}
|
||||
{{ badge(url=item.url_for_instructions(), solo=solo, last=last, blank=true, color='light border', icon='file-line', collapsible='Instructions:', text=item.instructions | length, alt='Instructions') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro parts(parts, solo=false, last=false) %}
|
||||
{{ badge(check=parts, solo=solo, last=last, color='success', icon='shapes-line', collapsible='Parts:', text=parts, alt='Parts') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro quantity(quantity, solo=false, last=false) %}
|
||||
{{ badge(check=quantity, solo=solo, last=last, color='success', icon='close-line', collapsible='Quantity:', text=quantity, alt='Quantity') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro set(set, solo=false, last=false, url=None, id=None) %}
|
||||
{% if id %}
|
||||
{% set url=url_for('set.details', id=id) %}
|
||||
{% endif %}
|
||||
{{ badge(check=set, url=url, solo=solo, last=last, color='secondary', icon='hashtag', collapsible='Set:', text=set, alt='Set') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro theme(theme, solo=false, last=false) %}
|
||||
{% if last %}
|
||||
{% set tooltip=theme %}
|
||||
{% else %}
|
||||
{% set text=theme %}
|
||||
{% endif %}
|
||||
{{ badge(check=theme, solo=solo, last=last, color='primary', icon='price-tag-3-line', text=text, alt='Theme', tooltip=tooltip) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro total_quantity(quantity, solo=false, last=false) %}
|
||||
{{ badge(check=quantity, solo=solo, last=last, color='success', icon='functions', collapsible='Quantity:', text=quantity, alt='Quantity') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro total_minifigures(minifigures, solo=false, last=false) %}
|
||||
{{ badge(check=minifigures, solo=solo, last=last, color='info', icon='group-line', collapsible='Minifigures:', text=minifigures, alt='Minifigures') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro total_missing(missing, solo=false, last=false) %}
|
||||
{{ badge(check=missing, solo=solo, last=last, color='danger', icon='error-warning-line', collapsible='Missing:', text=missing, alt='Missing') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro total_sets(sets, solo=false, last=false) %}
|
||||
{{ badge(check=sets, solo=solo, last=last, color='secondary', icon='hashtag', collapsible='Sets:', text=sets, alt='Sets') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro total_spare(spare, solo=false, last=false) %}
|
||||
{{ badge(check=spare, solo=solo, last=last, color='warning', icon='loop-left-line', collapsible='Spare:', text=spare, alt='Spare') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro rebrickable(item, solo=false, last=false) %}
|
||||
{{ badge(url=item.url_for_rebrickable(), solo=solo, last=last, blank=true, color='light border', icon='external-link-line', collapsible='Rebrickable', alt='Rebrickable') }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro year(year, solo=false, last=false) %}
|
||||
{{ badge(check=year, solo=solo, last=last, color='secondary', icon='calendar-line', collapsible='Year:', text=year, alt='Year') }}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,24 @@
|
||||
{% macro header(item, name, solo=false, number=none, color=none, icon='hashtag') %}
|
||||
<div class="card-header">
|
||||
{% if not solo %}
|
||||
<a class="text-decoration-none text-reset" href="{{ item.url() }}" data-bs-toggle="tooltip" title="{{ name }}">
|
||||
{% endif %}
|
||||
<h5 class="mb-0 {% if not solo %}fs-6 text-nowrap overflow-x-hidden text-truncate{% endif %}">
|
||||
{% if number %}<span class="badge text-bg-secondary fw-normal"><i class="ri-{{ icon }}"></i>{{ number }}</span>{% endif %}
|
||||
{% if color %}<span class="badge text-bg-light fw-normal border"><i class="ri-palette-line"></i> {{ color }}</span>{% endif %}
|
||||
{{ name }}
|
||||
</h5>
|
||||
{% if not solo %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro image(item, solo=false, last=false, caption=none, alt=none, medium=none) %}
|
||||
{% set image_url=item.url_for_image() %}
|
||||
<div class="card-img border-bottom" style="background-image: url({{ image_url }})">
|
||||
<a {% if solo %}data-lightbox {% if caption %}data-caption="{{ caption }}"{% endif %} href="{{ image_url }}" target="_blank"{% else %}href="{{ item.url() }}"{% endif %}>
|
||||
<img {% if solo and medium %}class="card-medium-img"{% endif %}{% if last %}class="card-last-img"{% endif %} src="{{ image_url }}" {% if alt %}alt="{{ alt }}"{% endif %} loading="lazy">
|
||||
</a>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,13 @@
|
||||
{% macro checkbox(kind, id, text, url, checked, delete=false) %}
|
||||
{% if g.login.is_readonly() %}
|
||||
<input class="form-check-input text-reset" type="checkbox" {% if checked %}checked{% endif %} disabled>
|
||||
{{ text }}
|
||||
{% else %}
|
||||
<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 %}
|
||||
autocomplete="off">
|
||||
<label class="form-check-label" for="{{ kind }}-{{ id }}">
|
||||
{{ text }} <i id="status-{{ kind }}-{{ id }}" class="mb-1"></i>
|
||||
</label>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,76 @@
|
||||
{% macro header(color=false, quantity=false, missing=false, missing_parts=false, sets=false, minifigures=false) %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="no-sort" scope="col"><i class="ri-image-line fw-normal"></i> Image</th>
|
||||
<th scope="col"><i class="ri-pencil-line fw-normal"></i> Name</th>
|
||||
{% if color %}
|
||||
<th scope="col"><i class="ri-palette-line fw-normal"></i> Color</th>
|
||||
{% endif %}
|
||||
{% if quantity %}
|
||||
<th scope="col"><i class="ri-functions fw-normal"></i> Quantity</th>
|
||||
{% endif %}
|
||||
{% if missing %}
|
||||
<th scope="col"><i class="ri-error-warning-line fw-normal"></i> Missing</th>
|
||||
{% endif %}
|
||||
{% if missing_parts %}
|
||||
<th scope="col"><i class="ri-error-warning-line fw-normal"></i> Missing parts</th>
|
||||
{% endif %}
|
||||
{% if sets %}
|
||||
<th scope="col"><i class="ri-hashtag fw-normal"></i> Sets</th>
|
||||
{% endif %}
|
||||
{% if minifigures %}
|
||||
<th scope="col"><i class="ri-group-line fw-normal"></i> Minifigures</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bricklink(item) %}
|
||||
{% set url=item.url_for_bricklink() %}
|
||||
{% if url %}
|
||||
<a href="{{ url }}" target="_blank" class="badge rounded-pill text-bg-light border fw-normal">
|
||||
<i class="ri-external-link-line"></i> Bricklink
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro image(image, caption=none, alt=none, accordion=false) %}
|
||||
<td class="py-0">
|
||||
<a data-lightbox data-caption="{{ caption }}" href="{{ image }}" target="_blank">
|
||||
<img class="{% if accordion %}accordion-img{% else %}table-img{% endif %}" src="{{ image }}" {% if alt %}alt="{{ alt }}"{% endif %} loading="lazy">
|
||||
</a>
|
||||
</td>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro rebrickable(item) %}
|
||||
{% set url=item.url_for_rebrickable() %}
|
||||
{% if url %}
|
||||
<a href="{{ url }}" target="_blank" class="badge rounded-pill text-bg-light border fw-normal">
|
||||
<i class="ri-external-link-line"></i> Rebrickable
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro dynamic(id, no_sort=none, number=none) %}
|
||||
<script type="text/javascript">
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
new simpleDatatables.DataTable("#{{ id }}", {
|
||||
columns: [
|
||||
{% if no_sort %}{ select: [{{ no_sort }}], sortable: false, searchable: false },{% endif %}
|
||||
{% if number %}{ select: [{{ number }}], type: "number", searchable: false },{% endif %}
|
||||
],
|
||||
pagerDelta: 1,
|
||||
perPage: {{ config['DEFAULT_TABLE_PER_PAGE'].value }},
|
||||
perPageSelect: [10, 25, 50, 100, 500, 1000],
|
||||
searchable: true,
|
||||
searchQuerySeparator: "",
|
||||
tableRender: () => {
|
||||
baguetteBox.run('[data-lightbox]')
|
||||
},
|
||||
pagerRender: () => {
|
||||
baguetteBox.run('[data-lightbox]')
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endmacro %}
|
||||
@@ -1,215 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="light" class="has-navbar-fixed-top">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Set Overview</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>
|
||||
|
||||
<style>
|
||||
|
||||
|
||||
table.sortable th:not(.sorttable_sorted):not(.sorttable_sorted_reverse):not(.sorttable_nosort):after {
|
||||
content: " \25B4\25BE"
|
||||
}
|
||||
|
||||
|
||||
table.sortable tbody tr:nth-child(2n) td {
|
||||
background: #ffffff;
|
||||
}
|
||||
table.sortable tbody tr:nth-child(2n+1) td {
|
||||
background: #ecf0f1;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
/* horizontal scrollbar for tables if mobile screen */
|
||||
img {
|
||||
margin:0;
|
||||
padding: 0;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 600px) {
|
||||
img {
|
||||
margin:0;
|
||||
padding: 0;
|
||||
height: 150px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* Modal Styles */
|
||||
#lightbox-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
padding-top: 50px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
.lightbox-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.lightbox-content {
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
height: auto;
|
||||
width: auto;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
#lightbox-text {
|
||||
position: absolute;
|
||||
bottom: 20px; /* Adjust as needed */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 10px;
|
||||
background-color: rgba(0, 0, 0, 0.5); /* Adjust background color and opacity */
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
#lightbox-modal .close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 25px;
|
||||
font-size: 50px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#lightbox-modal img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar is-dark is-fixed-top" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
Home
|
||||
</a>
|
||||
|
||||
<a class="navbar-item" href="/create">
|
||||
Add Set
|
||||
</a>
|
||||
<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>
|
||||
</a>
|
||||
</div>
|
||||
<div id="navMenu" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<center>
|
||||
<div class="center-table">
|
||||
<table id="data" class="table sortable tablemobile">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="fixed-width sorttable_nosort"></th>
|
||||
<th>Num.</th>
|
||||
<th>Name</th>
|
||||
<th>Qty</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for brick in missing_list %}
|
||||
<tr>
|
||||
<td><img src="{{ '/static/minifigs/' + brick[0] + '.jpg' }}" class="lightbox-trigger" alt="{{ brick[1] }}" loading="lazy">
|
||||
</td>
|
||||
<td>{{ brick[0] }}</td>
|
||||
<td>{{ brick[1] }}</td>
|
||||
<td>{{ brick[2] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</center>
|
||||
|
||||
<div id="lightbox-modal">
|
||||
<div class="lightbox-wrapper">
|
||||
<span class="close">×</span>
|
||||
<img class="lightbox-content" id="lightbox-image">
|
||||
<div class="text-container" id="lightbox-text"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const lightboxTrigger = document.querySelectorAll('.lightbox-trigger');
|
||||
const lightboxModal = document.getElementById('lightbox-modal');
|
||||
const lightboxImage = document.getElementById('lightbox-image');
|
||||
const lightboxText = document.getElementById('lightbox-text');
|
||||
const closeModal = document.querySelector('.close');
|
||||
|
||||
lightboxTrigger.forEach(function (element) {
|
||||
element.addEventListener('click', function () {
|
||||
const imgSrc = element.getAttribute('src');
|
||||
textContent = '';
|
||||
console.log(element.getAttribute('alt'));
|
||||
textContent = element.getAttribute('alt');
|
||||
// textContent = element.closest('tr').querySelector('td:nth-child(2)').textContent; // Adjust the index accordingly
|
||||
lightboxText.textContent = textContent;
|
||||
|
||||
|
||||
lightboxImage.setAttribute('src', imgSrc);
|
||||
lightboxModal.style.display = 'block';
|
||||
});
|
||||
});
|
||||
|
||||
closeModal.addEventListener('click', function () {
|
||||
lightboxModal.style.display = 'none';
|
||||
});
|
||||
|
||||
lightboxModal.addEventListener('click', function (event) {
|
||||
if (event.target === lightboxModal || event.target === lightboxImage || event.target === lightboxText) {
|
||||
// Dismiss only if the clicked element is part of the content area
|
||||
lightboxModal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Dismiss the lightbox when clicking outside the content area (i.e., the background)
|
||||
lightboxModal.addEventListener('click', function (event) {
|
||||
if (event.target != lightboxModal) {
|
||||
lightboxModal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - Minifigure {{ item.fields.name }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{% with solo=true, read_only_missing=true %}
|
||||
{% include 'minifigure/card.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
baguetteBox.run('[data-lightbox]');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,28 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
{% import 'macro/badge.html' as badge %}
|
||||
{% import 'macro/card.html' as card %}
|
||||
|
||||
<div class="card mb-3 flex-fill {% if solo %}card-solo{% endif %}">
|
||||
{{ card.header(item, item.fields.name, solo=solo, number=item.clean_number(), icon='user-line') }}
|
||||
{{ card.image(item, solo=solo, last=last, caption=item.fields.name, alt=item.fields.fig_num, medium=true) }}
|
||||
<div class="card-body border-bottom {% if not solo %}p-1{% endif %}">
|
||||
{% if last %}
|
||||
{{ badge.set(item.fields.set_num, solo=solo, last=last, id=item.fields.u_id) }}
|
||||
{{ badge.quantity(item.fields.quantity, solo=solo, last=last) }}
|
||||
{% endif %}
|
||||
{{ badge.total_sets(using | length, solo=solo, last=last) }}
|
||||
{{ badge.total_quantity(item.fields.total_quantity, solo=solo, last=last) }}
|
||||
{{ badge.total_missing(item.fields.total_missing, solo=solo, last=last) }}
|
||||
{% if not last %}
|
||||
{{ badge.rebrickable(item, solo=solo, last=last) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if solo %}
|
||||
<div class="accordion accordion-flush" id="minifigure-details">
|
||||
{{ accordion.table(item.generic_parts(), 'Parts', item.fields.fig_num, 'minifigure-details', 'part/table.html', icon='shapes-line', alt=item.fields.fig_num, read_only_missing=read_only_missing)}}
|
||||
{{ accordion.cards(using, 'Sets using this minifigure', 'using-inventory', 'minifigure-details', 'set/card.html', icon='hashtag') }}
|
||||
{{ accordion.cards(missing, 'Sets missing parts of this minifigure', 'missing-inventory', 'minifigure-details', 'set/card.html', icon='error-warning-line') }}
|
||||
</div>
|
||||
<div class="card-footer"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
{% import 'macro/table.html' as table %}
|
||||
|
||||
<div class="table-responsive-sm">
|
||||
<table class="table table-striped align-middle" id="minifigures">
|
||||
{{ table.header(quantity=true, missing_parts=true, sets=true) }}
|
||||
<tbody>
|
||||
{% for item in table_collection %}
|
||||
<tr>
|
||||
{{ table.image(item.url_for_image(), caption=item.fields.name, alt=item.fields.fig_num) }}
|
||||
<td >
|
||||
<a class="text-reset" href="{{ item.url() }}" style="max-width:auto">{{ item.fields.name }}</a>
|
||||
{% if all %}
|
||||
{{ table.rebrickable(item) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ item.fields.total_quantity }}</td>
|
||||
<td>{{ item.fields.total_missing }}</td>
|
||||
<td>{{ item.fields.total_sets }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if all %}
|
||||
{{ table.dynamic('minifigures', no_sort='0', number='2, 3, 4')}}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - All minifigures{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="container-fluid px-0">
|
||||
{% with all=true %}
|
||||
{% include 'minifigure/table.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
+8
-273
@@ -1,276 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="light" class="has-navbar-fixed-top">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Set Overview</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>
|
||||
<style>
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - Missing parts{% endblock %}
|
||||
|
||||
|
||||
table.sortable th:not(.sorttable_sorted):not(.sorttable_sorted_reverse):not(.sorttable_nosort):after {
|
||||
content: " \25B4\25BE"
|
||||
}
|
||||
|
||||
|
||||
table.sortable tbody tr:nth-child(2n) td {
|
||||
background: #ffffff;
|
||||
}
|
||||
table.sortable tbody tr:nth-child(2n+1) td {
|
||||
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) {
|
||||
/* horizontal scrollbar for tables if mobile screen */
|
||||
img {
|
||||
margin:0;
|
||||
padding: 0;
|
||||
min-width: 50px;
|
||||
min-height: 50px;
|
||||
display: block !important;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 600px) {
|
||||
img {
|
||||
margin:0;
|
||||
padding: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 600px) {
|
||||
|
||||
.hidden-desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
/* horizontal scrollbar for tables if mobile screen */
|
||||
.hidden-mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.name-class {
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.fixed-width {
|
||||
max-width:100px;
|
||||
}
|
||||
|
||||
|
||||
/* Modal Styles */
|
||||
#lightbox-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
padding-top: 50px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
.lightbox-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.lightbox-content {
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
height: auto;
|
||||
width: auto;
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
#lightbox-text {
|
||||
position: absolute;
|
||||
bottom: 20px; /* Adjust as needed */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 10px;
|
||||
background-color: rgba(0, 0, 0, 0.5); /* Adjust background color and opacity */
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
#lightbox-modal .close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 25px;
|
||||
font-size: 50px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#lightbox-modal img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar is-dark is-fixed-top" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
Home
|
||||
</a>
|
||||
|
||||
<a class="navbar-item" href="/create">
|
||||
Add Set
|
||||
</a>
|
||||
<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>
|
||||
</a>
|
||||
</div>
|
||||
<div id="navMenu" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container">
|
||||
<center>
|
||||
<h1 class="title is-2 mt-4">Missing Pieces</h1>
|
||||
<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">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:65px;" class="fixed-width sorttable_nosort"></th>
|
||||
<th class="fixed-width" >Part Num</th>
|
||||
<th class="fixed-width" >Color</th>
|
||||
<th class="fixed-width" >Element ID</th>
|
||||
<th class="fixed-width" >Qty</th>
|
||||
<th class="fixed-width sorttable_nosort">Sets</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for brick in missing_list %}
|
||||
<tr>
|
||||
{% 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>
|
||||
{% 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>
|
||||
{% endif %}
|
||||
<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><a target="_blank" href="https://www.rebrickable.com/elements/{{ brick[2] }}">{{ brick[2] }}</a></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>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</center>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="lightbox-modal">
|
||||
<div class="lightbox-wrapper">
|
||||
<span class="close">×</span>
|
||||
<img class="lightbox-content" id="lightbox-image">
|
||||
<div class="text-container" id="lightbox-text"></div>
|
||||
</div>
|
||||
{% block main %}
|
||||
<div class="container-fluid px-0">
|
||||
{% with all=true, no_quantity=true %}
|
||||
{% include 'part/table.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const lightboxTrigger = document.querySelectorAll('.lightbox-trigger');
|
||||
const lightboxModal = document.getElementById('lightbox-modal');
|
||||
const lightboxImage = document.getElementById('lightbox-image');
|
||||
const lightboxText = document.getElementById('lightbox-text');
|
||||
const closeModal = document.querySelector('.close');
|
||||
|
||||
lightboxTrigger.forEach(function (element) {
|
||||
element.addEventListener('click', function () {
|
||||
const imgSrc = element.getAttribute('src');
|
||||
textContent = '';
|
||||
console.log(element.getAttribute('alt'));
|
||||
textContent = element.getAttribute('alt');
|
||||
// textContent = element.closest('tr').querySelector('td:nth-child(2)').textContent; // Adjust the index accordingly
|
||||
lightboxText.textContent = textContent;
|
||||
|
||||
|
||||
lightboxImage.setAttribute('src', imgSrc);
|
||||
lightboxModal.style.display = 'block';
|
||||
});
|
||||
});
|
||||
|
||||
closeModal.addEventListener('click', function () {
|
||||
lightboxModal.style.display = 'none';
|
||||
});
|
||||
|
||||
lightboxModal.addEventListener('click', function (event) {
|
||||
if (event.target === lightboxModal || event.target === lightboxImage || event.target === lightboxText) {
|
||||
// Dismiss only if the clicked element is part of the content area
|
||||
lightboxModal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Dismiss the lightbox when clicking outside the content area (i.e., the background)
|
||||
lightboxModal.addEventListener('click', function (event) {
|
||||
if (event.target != lightboxModal) {
|
||||
lightboxModal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,20 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - Part {{ item.fields.name }} ({{ item.fields.color_name }}){% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{% with solo=true %}
|
||||
{% include 'part/card.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
baguetteBox.run('[data-lightbox]');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,24 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
{% import 'macro/badge.html' as badge %}
|
||||
{% import 'macro/card.html' as card %}
|
||||
|
||||
<div class="card mb-3 flex-fill card-solo">
|
||||
{{ card.header(item, item.fields.name, solo=solo, number=item.fields.part_num, color=item.fields.color_name, icon='shapes-line') }}
|
||||
{{ card.image(item, solo=solo, last=last, caption=item.fields.name, alt=item.fields.part_img_url_id, medium=true) }}
|
||||
<div class="card-body border-bottom {% if not solo %}p-1{% endif %}">
|
||||
{{ badge.total_sets(sets_using | length, solo=solo, last=last) }}
|
||||
{{ badge.total_minifigures(minifigures_using | length, solo=solo, last=last) }}
|
||||
{{ badge.total_quantity(item.fields.total_quantity, solo=solo, last=last) }}
|
||||
{{ badge.total_spare(item.fields.total_spare, solo=solo, last=last) }}
|
||||
{{ badge.total_missing(item.fields.total_missing, solo=solo, last=last) }}
|
||||
{{ badge.rebrickable(item, solo=solo, last=last) }}
|
||||
{{ badge.bricklink(item, solo=solo, last=last) }}
|
||||
</div>
|
||||
<div class="accordion accordion-flush" id="part-details">
|
||||
{{ accordion.cards(sets_using, 'Sets using this part', 'sets-using-inventory', 'part-details', 'set/card.html', icon='hashtag') }}
|
||||
{{ accordion.cards(sets_missing, 'Sets missing this part', 'sets-missing-inventory', 'part-details', 'set/card.html', icon='error-warning-line') }}
|
||||
{{ accordion.cards(minifigures_using, 'Minifigures using this part', 'minifigures-using-inventory', 'part-details', 'minifigure/card.html', icon='group-line') }}
|
||||
{{ accordion.cards(minifigures_missing, 'Minifigures missing this part', 'minifigures-missing-inventory', 'part-details', 'minifigure/card.html', icon='error-warning-line') }}
|
||||
</div>
|
||||
<div class="card-footer"></div>
|
||||
</div>
|
||||
@@ -0,0 +1,52 @@
|
||||
{% import 'macro/table.html' as table %}
|
||||
|
||||
<div class="table-responsive-sm">
|
||||
<table class="table table-striped align-middle {% if not all %}sortable mb-0{% endif %}" {% if all %}id="parts"{% endif %}>
|
||||
{{ table.header(color=true, quantity=not no_quantity, missing=not no_missing, sets=all, minifigures=all) }}
|
||||
<tbody>
|
||||
{% for item in table_collection %}
|
||||
<tr>
|
||||
{{ table.image(item.url_for_image(), caption=item.fields.name, alt=item.fields.part_num, accordion=solo) }}
|
||||
<td>
|
||||
<a class="text-reset" href="{{ item.url() }}">{{ item.fields.name }}</a>
|
||||
{% if item.fields.is_spare %}<span class="badge rounded-pill text-bg-warning fw-normal"><i class="ri-loop-left-line"></i> Spare</span>{% endif %}
|
||||
{% if all %}
|
||||
{{ table.rebrickable(item) }}
|
||||
{{ table.bricklink(item) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ item.fields.color_name }}</td>
|
||||
{% if not no_quantity %}
|
||||
<td>{% if quantity %}{{ item.fields.quantity * quantity }}{% else %}{{ item.fields.quantity }}{% endif %}</td>
|
||||
{% endif %}
|
||||
{% if not no_missing %}
|
||||
<td {% if not all %}id="sort-item-{{ item.fields.u_id }}-{{ item.fields.id }}" data-sort="{{ item.fields.total_missing }}"{% endif %} class="table-td-missing">
|
||||
{% if all or read_only_missing %}
|
||||
{{ item.fields.total_missing }}
|
||||
{% else %}
|
||||
<div class="input-group">
|
||||
{% if g.login.is_readonly() %}
|
||||
<input class="form-control form-control-sm" type="text" {% if item.fields.total_missing %}value="{{ item.fields.total_missing }}"{% endif %}
|
||||
disabled autocomplete="off">
|
||||
<span class="input-group-text ri-prohibited-line"></span>
|
||||
{% else %}
|
||||
<input class="form-control form-control-sm flex-shrink-1" type="text" {% if item.fields.total_missing %}value="{{ item.fields.total_missing }}"{% endif %}
|
||||
onchange="change_part_missing_amount(this, '{{ item.fields.u_id }}', '{{ item.fields.id }}', '{{ item.url_for_missing() }}')" autocomplete="off">
|
||||
<span id="status-item-{{ item.fields.u_id }}-{{ item.fields.id }}" class="input-group-text ri-save-line"></span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% if all %}
|
||||
<td>{{ item.fields.total_sets }}</td>
|
||||
<td>{{ item.fields.total_minifigures }}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if all %}
|
||||
{{ table.dynamic('parts', no_sort='0', number='3, 4, 5, 6')}}
|
||||
{% endif %}
|
||||
+8
-307
@@ -1,310 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html data-theme="light" class="has-navbar-fixed-top">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Set Overview</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>
|
||||
{% extends 'base.html' %}
|
||||
|
||||
<style>
|
||||
{% block title %} - All parts{% endblock %}
|
||||
|
||||
table.sortable th:not(.sorttable_sorted):not(.sorttable_sorted_reverse):not(.sorttable_nosort):after {
|
||||
content: " \25B4\25BE"
|
||||
}
|
||||
|
||||
|
||||
table.sortable tbody tr:nth-child(2n) td {
|
||||
background: #ffffff;
|
||||
}
|
||||
table.sortable tbody tr:nth-child(2n+1) td {
|
||||
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) {
|
||||
/* horizontal scrollbar for tables if mobile screen */
|
||||
img {
|
||||
margin:0;
|
||||
padding: 0;
|
||||
min-width: 50px;
|
||||
min-height: 50px;
|
||||
display: block !important;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 600px) {
|
||||
img {
|
||||
margin:0;
|
||||
padding: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 600px) {
|
||||
|
||||
.hidden-desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
/* horizontal scrollbar for tables if mobile screen */
|
||||
.hidden-mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.name-class {
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* Modal Styles */
|
||||
#lightbox-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
padding-top: 50px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
.lightbox-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.lightbox-content {
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
height: auto;
|
||||
width: auto;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
#lightbox-text {
|
||||
position: absolute;
|
||||
bottom: 20px; /* Adjust as needed */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 10px;
|
||||
background-color: rgba(0, 0, 0, 0.5); /* Adjust background color and opacity */
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
#lightbox-modal .close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 25px;
|
||||
font-size: 50px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#lightbox-modal img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="navbar is-dark is-fixed-top" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
Home
|
||||
</a>
|
||||
|
||||
<a class="navbar-item" href="/create">
|
||||
Add Set
|
||||
</a>
|
||||
<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>
|
||||
</a>
|
||||
</div>
|
||||
<div id="navMenu" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container">
|
||||
<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 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" style="widht:100%;height:100%;table-layout: fixed;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:65px;" class="sorttable_nosort"></th>
|
||||
<th >Part Num</th>
|
||||
<th >Color</th>
|
||||
<th class="name-class">Name</th>
|
||||
<th >element_id</th>
|
||||
<th >total_quantity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for brick in missing_list %}
|
||||
<tr>
|
||||
{% if brick[4] == 'nil' %}
|
||||
<td style="background-color:#ffffff;" ><img src="{{ '/static/none.jpg' }}" class="lightbox-trigger" alt="id: {{ brick[0] }}, color: {{ brick[3] }}, qty: {{ brick[6] }}" loading="lazy"></td>
|
||||
{% else %}
|
||||
<td style="background-color:#ffffff;" ><img src="{{ '/static/parts/' + brick[5] + '.jpg' }}" alt="id: {{ brick[0] }}, color: {{ brick[3] }}, qty: {{ brick[6] }}" class="lightbox-trigger" loading="lazy"></td>
|
||||
{% endif %}
|
||||
<td><a target="_blank" href="https://www.bricklink.com/v2/catalog/catalogitem.page?P={{ brick[1] }}">{{ brick[1] }}</a></td>
|
||||
<td class="name-class">{{ brick[3] }}</td>
|
||||
<td>{{ brick[7] }}</td>
|
||||
<td><a target="_blank" href="https://www.rebrickable.com/elements/{{ brick[4] }}">{{ brick[4] }}</a></td>
|
||||
<td>{{ brick[6] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</center>
|
||||
</div>
|
||||
<div id="lightbox-modal">
|
||||
<div class="lightbox-wrapper">
|
||||
<span class="close">×</span>
|
||||
<img class="lightbox-content" id="lightbox-image">
|
||||
<div class="text-container" id="lightbox-text"></div>
|
||||
</div>
|
||||
{% block main %}
|
||||
<div class="container-fluid px-0">
|
||||
{% with all=true %}
|
||||
{% include 'part/table.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const lightboxTrigger = document.querySelectorAll('.lightbox-trigger');
|
||||
const lightboxModal = document.getElementById('lightbox-modal');
|
||||
const lightboxImage = document.getElementById('lightbox-image');
|
||||
const lightboxText = document.getElementById('lightbox-text');
|
||||
const closeModal = document.querySelector('.close');
|
||||
|
||||
lightboxTrigger.forEach(function (element) {
|
||||
element.addEventListener('click', function () {
|
||||
const imgSrc = element.getAttribute('src');
|
||||
textContent = '';
|
||||
console.log(element.getAttribute('alt'));
|
||||
textContent = element.getAttribute('alt');
|
||||
// textContent = element.closest('tr').querySelector('td:nth-child(2)').textContent; // Adjust the index accordingly
|
||||
lightboxText.textContent = textContent;
|
||||
|
||||
|
||||
lightboxImage.setAttribute('src', imgSrc);
|
||||
lightboxModal.style.display = 'block';
|
||||
});
|
||||
});
|
||||
|
||||
closeModal.addEventListener('click', function () {
|
||||
lightboxModal.style.display = 'none';
|
||||
});
|
||||
|
||||
lightboxModal.addEventListener('click', function (event) {
|
||||
if (event.target === lightboxModal || event.target === lightboxImage || event.target === lightboxText) {
|
||||
// Dismiss only if the clicked element is part of the content area
|
||||
lightboxModal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Dismiss the lightbox when clicking outside the content area (i.e., the background)
|
||||
lightboxModal.addEventListener('click', function (event) {
|
||||
if (event.target != lightboxModal) {
|
||||
lightboxModal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
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>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,24 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - Set {{ item.fields.name}} ({{ item.fields.set_num }}){% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
{% with solo=true %}
|
||||
{% include 'set/card.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
baguetteBox.run('[data-lightbox]');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='scripts/set.js') }}"></script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,76 @@
|
||||
{% import 'macro/accordion.html' as accordion %}
|
||||
{% import 'macro/badge.html' as badge %}
|
||||
{% 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 %}
|
||||
>
|
||||
{{ 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) }}
|
||||
{{ badge.year(item.fields.year, solo=solo, last=last) }}
|
||||
{{ badge.parts(item.fields.num_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 %}
|
||||
{% if not solo %}
|
||||
{{ badge.instructions(item, solo=solo, last=last) }}
|
||||
{% endif %}
|
||||
{{ 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>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% if solo %}
|
||||
<div class="accordion accordion-flush" id="set-details">
|
||||
{% if not delete %}
|
||||
{{ accordion.header('Instructions', 'instructions', 'set-details', expanded=open_instructions, icon='file-line', class='p-0') }}
|
||||
<div class="list-group list-group-flush">
|
||||
{% if item.instructions | length %}
|
||||
{% for instruction in item.instructions %}
|
||||
<a class="list-group-item list-group-item-action" href="{{ instruction.url() }}" target="_blank"><i class="ri-arrow-right-long-line"></i> <i class="ri-{{ instruction.icon() }}"></i> {{ instruction.filename }}</a>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<a class="list-group-item list-group-item-action" href="{{ url_for('instructions.upload') }}"><i class="ri-upload-line"></i> Upload an instructions file</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{{ accordion.footer() }}
|
||||
{{ accordion.table(item.parts(), 'Parts', 'parts-inventory', 'set-details', 'part/table.html', total=item.fields.num_parts, icon='shapes-line')}}
|
||||
{% for minifigure in item.minifigures() %}
|
||||
{{ accordion.table(minifigure.parts(), minifigure.fields.name, minifigure.fields.fig_num, 'set-details', 'part/table.html', quantity=minifigure.fields.quantity, icon='group-line', image=minifigure.url_for_image(), alt=minifigure.fields.fig_num, details=minifigure.url())}}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if g.login.is_authenticated() %}
|
||||
{{ accordion.header('Danger zone', 'danger-zone', 'set-details', expanded=delete, danger=true, class='text-end') }}
|
||||
{% if delete %}
|
||||
<form action="{{ item.url_for_do_delete() }}" 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 delete a set. This action is irreversible.</div>
|
||||
<a class="btn btn-primary" href="{{ item.url() }}" role="button"><i class="ri-arrow-left-long-line"></i> Back to the set</a>
|
||||
<button type="submit" class="btn btn-danger"><i class="ri-close-line"></i> Delete the set</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<a href="{{ item.url_for_delete() }}" class="btn btn-danger" role="button"><i class="ri-close-line"></i> Delete the set</a>
|
||||
{% endif %}
|
||||
{{ accordion.footer() }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-footer"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
<div class="container">
|
||||
<div class="alert alert-primary" role="alert">
|
||||
<h4 class="alert-heading">It's empty in here...</h4>
|
||||
<p>You have not added a set yet.</p>
|
||||
<hr>
|
||||
<div class="text-end"><a href="{{ url_for('add.add') }}" class="btn btn-primary"><i class="ri-checkbox-circle-line"></i> Add a set</a></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
{% import 'macro/badge.html' as badge %}
|
||||
{% 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) }}
|
||||
<div class="card-body p-1">
|
||||
{{ badge.theme(item.theme_name) }}
|
||||
{{ badge.year(item.fields.year) }}
|
||||
{{ badge.parts(item.fields.num_parts) }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
<script type="text/javascript">
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
new BrickSocket('add', '{{ path }}', '{{ namespace }}', {
|
||||
COMPLETE: '{{ messages['COMPLETE'] }}',
|
||||
FAIL: '{{ messages['FAIL'] }}',
|
||||
IMPORT_SET: '{{ messages['IMPORT_SET'] }}',
|
||||
LOAD_SET: '{{ messages['LOAD_SET'] }}',
|
||||
PROGRESS: '{{ messages['PROGRESS'] }}',
|
||||
SET_LOADED: '{{ messages['SET_LOADED'] }}',
|
||||
}{% if bulk %}, true{% endif %});
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,83 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - All sets{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% if collection | length %}
|
||||
<div class="container-fluid">
|
||||
<div class="row row-cols-lg-auto g-1 justify-content-center align-items-center pb-2">
|
||||
<div class="col-12 flex-grow-1">
|
||||
<label class="visually-hidden" for="grid-search">Search</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Search</span>
|
||||
<input id="grid-search" class="form-control form-control-sm" type="text" placeholder="Set name, set number, set theme or number of parts..." value="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 flex-grow-1">
|
||||
<label class="visually-hidden" for="grid-filter">Filter</label>
|
||||
<div class="input-group">
|
||||
<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>
|
||||
</select>
|
||||
<select id="grid-theme" class="form-select form-select-sm" autocomplete="off">
|
||||
<option value="" selected>All themes</option>
|
||||
{% for theme in collection.themes %}
|
||||
<option value="{{ theme | lower }}">{{ theme }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div id="grid-sort" class="input-group">
|
||||
<span class="input-group-text">Sort</span>
|
||||
<button id="sort-number" type="button" class="btn btn-sm btn-outline-primary"
|
||||
data-sort-target="div#grid>div" data-sort-attribute="number" data-sort-natural="true">Num.</button>
|
||||
<button id="sort-name" type="button" class="btn btn-sm btn-outline-primary"
|
||||
data-sort-target="div#grid>div" data-sort-attribute="name">Name</button>
|
||||
<button id="sort-theme" type="button" class="btn btn-sm btn-outline-primary"
|
||||
data-sort-target="div#grid>div" data-sort-attribute="theme">Theme</button>
|
||||
<button id="sort-year" type="button" class="btn btn-sm btn-outline-primary"
|
||||
data-sort-target="div#grid>div" data-sort-attribute="year">Year</button>
|
||||
<button id="sort-minifigure" type="button" class="btn btn-sm btn-outline-primary"
|
||||
data-sort-target="div#grid>div" data-sort-attribute="minifigures" data-sort-desc="true">Fig.</button>
|
||||
<button id="sort-parts" type="button" class="btn btn-sm btn-outline-primary"
|
||||
data-sort-target="div#grid>div" data-sort-attribute="parts" data-sort-desc="true">Parts</button>
|
||||
<button id="sort-missing" type="button" class="btn btn-sm btn-outline-primary"
|
||||
data-sort-target="div#grid>div" data-sort-attribute="missing" data-sort-desc="true">Miss.</button>
|
||||
<button id="sort-clear" type="button" class="btn btn-sm btn-outline-dark"
|
||||
data-sort-target="div#grid>div" data-sort-clear="true"><i class="ri-filter-off-line"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" id="grid">
|
||||
{% for item in collection %}
|
||||
<div class="col-md-6 col-xl-3 d-flex align-items-stretch">
|
||||
{% with index=loop.index0 %}
|
||||
{% include 'set/card.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
new BrickGrid("grid");
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
{% else %}
|
||||
{% include 'set/empty.html' %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', filename='scripts/grid.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='scripts/set.js') }}"></script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,16 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - Success!{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="container">
|
||||
<div class="alert alert-success" role="alert">
|
||||
<h4 class="alert-heading">Success!</h4>
|
||||
<p>{{message}}</p>
|
||||
<hr>
|
||||
<div class="text-end">
|
||||
<a class="btn btn-primary" href="{{ url_for('index.index') }}" role="button"><i class="ri-home-line"></i> Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,293 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% 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);" >
|
||||
<table id="data" class="table tablemobile sortable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sorttable_nosort" style="width:65px;">Image</th>
|
||||
<th class="hidden-mobile name-class">Name</th>
|
||||
<th class="hidden-mobile">Color</th>
|
||||
<th style="text-align:center">Qty</th>
|
||||
<th class="sorttable_nosort" style="text-align:right;">Missing</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for brick in inventory_list %}
|
||||
<tr>
|
||||
{% if brick[5] == '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>
|
||||
{% else %}
|
||||
<td style="background-color: #ffffff;width:65px;height:55px;"><img src="{{ '/static/parts/' + brick[5] + '.jpg' }}" class="lightbox-trigger" alt="{{ brick[3] }}" style="height: 50px; width: 50px;margin:0;padding: 0;"loading="lazy"></td>
|
||||
{% endif %}
|
||||
|
||||
<td style="text-align:left;" class="hidden-mobile name-class">{{ brick[3] }}</td>
|
||||
<td style="text-align:left;white-space: nowrap;" class="hidden-mobile">{{ brick[7] }}</td>
|
||||
<td style="text-align: center;">{{ brick[8] }}</td>
|
||||
<td style="text-align:right;" class="centered-cell">
|
||||
<div class="inputContainer">
|
||||
{% set ns = namespace(count='') %}
|
||||
<form id="number-form">
|
||||
<input type="hidden" id="set_num" value="{{ brick[0] }}">
|
||||
<input type="hidden" id="id" value="{{ brick[1] }}">
|
||||
<input type="hidden" id="part_num" value="{{ brick[2] }}">
|
||||
<input type="hidden" id="color_id" value="{{ brick[6] }}">
|
||||
<input type="hidden" id="part_img_url_id" value="{{ brick[5] }}">
|
||||
<input type="hidden" id="element_id" value="{{ brick[10] }}">
|
||||
<input type="hidden" id="u_id" value="{{ brick[11] }}">
|
||||
|
||||
|
||||
<div class='inputField'>
|
||||
|
||||
{% for missing in missing_list %}
|
||||
{% if missing[1] == brick[1] and missing[2] | string == brick[2] | string and missing[4] == brick[6] and (missing[6] | string == brick[10] | string or missing[6] == 'None') %}
|
||||
{% set ns.count = missing[5] %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<input type="tel" style="text-align:center;font-size: 16px;" id="missing" value="{{ ns.count }}" pattern="\d+">
|
||||
<button class="square-button" id="bnumber-form" type="submit">
|
||||
<img src="/static/save.svg" alt="Save Icon" loading="lazy">
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="minifigs" style="margin: 2rem 0; padding: 2rem; border-bottom: 2px solid #eee;"></div>
|
||||
|
||||
{% if minifig_list | length > 0 %}
|
||||
<h1 class="title is-2 ">Minifigs</h1>
|
||||
{% for fig in minifig_list %}
|
||||
<h2 class="subtitle is-4 mt-4 ">{{ fig[2] }} ({{ fig[0] }})</h2>
|
||||
|
||||
<div style="display: flex; justify-content: center;">
|
||||
<div style="display: flex; align-items: center;">
|
||||
{% if fig[4] == None %}
|
||||
<img src="/static/nil_mf.jpg" class="lightbox-trigger" style="height: 100px; width: auto;" alt="{{ fig[2] }}" loading="lazy">
|
||||
{% else %}
|
||||
<img src="{{ '/static/minifigs/' + fig[0] + '.jpg' }}" class="lightbox-trigger" style="height: 100px; width: auto;" alt="{{ fig[2] }}" loading="lazy">
|
||||
{% endif %}
|
||||
<span style="font-size: 50px; margin-left: 10px;">X {{ fig[3] }}</span>
|
||||
</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);" >
|
||||
<table id="data" class="table tablemobile sortable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="fixed-width sorttable_nosort"></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" style="text-align: center;">Qty</th>
|
||||
<th class="fixed-width sorttable_nosort" style="text-align: right;">Missing</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
{% for parts in minifig_inventory_list %}
|
||||
{% for part in parts %}
|
||||
|
||||
{% if part[0] == fig[0] %}
|
||||
<tr>
|
||||
{% if part[5] == 'nil' %}
|
||||
<td><img src="{{ '/static/none.jpg' }}" class="lightbox-trigger" alt="{{ part[3] }}" style="height: 50px; width: 50px;margin:0;padding: 0;" loading="lazy"></td>
|
||||
{% else %}
|
||||
<td><img src="{{ '/static/parts/' + part[5] + '.jpg' }}" class="lightbox-trigger" alt="{{ part[3] }}" style="height: 50px; width: 50px;margin:0;padding: 0;" loading="lazy"></td>
|
||||
{% endif %}
|
||||
<td style="text-align:left;margin:0px;" class="hidden-mobile name-class">{{ part[3] }}</td>
|
||||
<td class="hidden-mobile">{{ part[7] }}</td>
|
||||
<td style="text-align: center;">{{ part[8] * fig[3] }}</td>
|
||||
<td class="centered-cell" style="text-align:right;">
|
||||
<div class="inputContainer">
|
||||
{% set ns = namespace(count='') %}
|
||||
<form id="number-form">
|
||||
<input type="hidden" id="set_num" value="{{ part[0] }}">
|
||||
<input type="hidden" id="id" value="{{ part[1] }}">
|
||||
<input type="hidden" id="part_num" value="{{ part[2] }}">
|
||||
<input type="hidden" id="color_id" value="{{ part[6] }}">
|
||||
<input type="hidden" id="part_img_url_id" value="{{ part[5] }}">
|
||||
<input type="hidden" id="element_id" value="{{ part[10] }}">
|
||||
<input type="hidden" id="u_id" value="{{ part[11] }}">
|
||||
|
||||
|
||||
<div class='inputField'>
|
||||
{% for missing in missing_list %}
|
||||
{% if missing[1] == part[1] and missing[2] | string == part[2] | string and missing[4] == part[6] and (missing[6] | string == part[10] | string or missing[6] == 'None') %}
|
||||
{% set ns.count = missing[5] %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<input type="tel" style="text-align:center;font-size: 16px;" id="missing" value="{{ ns.count }}" pattern="\d+">
|
||||
<button class="square-button" id="bnumber-form" type="submit">
|
||||
<img src="/static/save.svg" alt="Save Icon" loading="lazy">
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div id="lightbox-modal">
|
||||
<div class="lightbox-wrapper">
|
||||
<span class="close">×</span>
|
||||
<img style="background-color: white;" class="lightbox-content" id="lightbox-image">
|
||||
<div class="text-container" id="lightbox-text"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
{% block scripts %}
|
||||
|
||||
<script>
|
||||
// $(document).ready( function () {
|
||||
// $('#data').DataTable();
|
||||
// } );
|
||||
|
||||
|
||||
function goToPage(url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
|
||||
const expandButton = document.getElementById('expand-button');
|
||||
const myDataTable = document.getElementById('data');
|
||||
let isExpanded = false;
|
||||
|
||||
expandButton.addEventListener('click', () => {
|
||||
const hiddenColumns = myDataTable.querySelectorAll('.hidden-mobile');
|
||||
hiddenColumns.forEach(column => {
|
||||
if (isExpanded) {
|
||||
column.style.display = 'none';
|
||||
expandButton.textContent = 'Expand Columns';
|
||||
} else {
|
||||
column.style.display = 'table-cell';
|
||||
expandButton.textContent = 'Hide Columns';
|
||||
}
|
||||
});
|
||||
isExpanded = !isExpanded;
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const lightboxTrigger = document.querySelectorAll('.lightbox-trigger');
|
||||
const lightboxModal = document.getElementById('lightbox-modal');
|
||||
const lightboxImage = document.getElementById('lightbox-image');
|
||||
const lightboxText = document.getElementById('lightbox-text');
|
||||
const closeModal = document.querySelector('.close');
|
||||
|
||||
lightboxTrigger.forEach(function (element) {
|
||||
element.addEventListener('click', function () {
|
||||
const imgSrc = element.getAttribute('src');
|
||||
textContent = '';
|
||||
console.log(element.getAttribute('alt'));
|
||||
textContent = element.getAttribute('alt');
|
||||
// textContent = element.closest('tr').querySelector('td:nth-child(2)').textContent; // Adjust the index accordingly
|
||||
lightboxText.textContent = textContent;
|
||||
|
||||
|
||||
lightboxImage.setAttribute('src', imgSrc);
|
||||
lightboxModal.style.display = 'block';
|
||||
});
|
||||
});
|
||||
|
||||
closeModal.addEventListener('click', function () {
|
||||
lightboxModal.style.display = 'none';
|
||||
});
|
||||
|
||||
lightboxModal.addEventListener('click', function (event) {
|
||||
if (event.target === lightboxModal || event.target === lightboxImage || event.target === lightboxText) {
|
||||
// Dismiss only if the clicked element is part of the content area
|
||||
lightboxModal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Dismiss the lightbox when clicking outside the content area (i.e., the background)
|
||||
lightboxModal.addEventListener('click', function (event) {
|
||||
if (event.target != lightboxModal) {
|
||||
lightboxModal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const numberInput = document.getElementById('missing');
|
||||
|
||||
// Add event listener to input to prevent non-numeric input
|
||||
numberInput.addEventListener('input', function () {
|
||||
// Remove non-numeric characters
|
||||
this.value = this.value.replace(/\D/g, '');
|
||||
});
|
||||
|
||||
// Force numeric keyboard on mobile devices
|
||||
numberInput.setAttribute('inputmode', 'numeric');
|
||||
});
|
||||
|
||||
|
||||
$("body").on("submit", "#number-form", function (event) {
|
||||
// $('#number-form').submit(function(event) {
|
||||
event.preventDefault();
|
||||
console.log($(this)[0]);
|
||||
|
||||
set_num = $(this)[0][0].value;
|
||||
id = $(this)[0][1].value;
|
||||
part_num = $(this)[0][2].value;
|
||||
color_id = $(this)[0][3].value;
|
||||
part_img_url_id = $(this)[0][4].value;
|
||||
element_id = $(this)[0][5].value;
|
||||
u_id = $(this)[0][6].value;
|
||||
missing = $(this)[0][7].value
|
||||
|
||||
// console.log('set_num: ' + set_num);
|
||||
// console.log('id: ' + id);
|
||||
// console.log('part_num: ' + part_num);
|
||||
// console.log('color_id: ' + color_id);
|
||||
// console.log('element_id: ' + element_id);
|
||||
// console.log('u_id: ' + u_id);
|
||||
// console.log('missing: ' + missing);
|
||||
|
||||
$.ajax({
|
||||
url: '/'+set_num+'/'+u_id,
|
||||
type: 'POST',
|
||||
data: {
|
||||
'set_num': set_num,
|
||||
'id': id,
|
||||
'part_num': part_num,
|
||||
'part_img_url_id': part_img_url_id,
|
||||
'color_id': color_id,
|
||||
'element_id': element_id,
|
||||
'u_id': u_id,
|
||||
'missing': missing
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
// part_num = request.form.get('brickpartpart_num')
|
||||
// color = request.form.get('brickcolorname')
|
||||
// index = request.form.get('index')
|
||||
// number = request.form.get('numberInput')
|
||||
// is_spare = request.form.get('is_spare')
|
||||
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
{% import 'macro/table.html' as table %}
|
||||
|
||||
<div class="table-responsive-sm">
|
||||
<table class="table table-striped align-middle" id="wish">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="no-sort" scope="col"><i class="ri-image-line fw-normal"></i> Image</th>
|
||||
<th scope="col"><i class="ri-hashtag fw-normal"></i> Set</th>
|
||||
<th scope="col"><i class="ri-pencil-line fw-normal"></i> Name</th>
|
||||
<th scope="col"><i class="price-tag-3-line fw-normal"></i> Theme</th>
|
||||
<th scope="col"><i class="ri-calendar-line fw-normal"></i> Year</th>
|
||||
<th scope="col"><i class="ri-shapes-line fw-normal"></i> Parts</th>
|
||||
<th scope="col"><i class="ri-calendar-close-line fw-normal"></i> Retirement</th>
|
||||
{% if g.login.is_authenticated() %}
|
||||
<th class="no-sort" scope="col"><i class="ri-settings-4-line fw-normal"></i> Actions</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in table_collection %}
|
||||
{% set retirement_date = retired.get(item.fields.set_num) %}
|
||||
<tr>
|
||||
{{ table.image(item.url_for_image(), caption=item.fields.name, alt=item.fields.set_num) }}
|
||||
<td>{{ item.fields.set_num }}</td>
|
||||
<td>{{ item.fields.name }}</td>
|
||||
<td>{{ item.theme_name }}</td>
|
||||
<td>{{ item.fields.year }}</td>
|
||||
<td>{{ item.fields.num_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>
|
||||
<form action="{{ item.url_for_delete() }}" method="post">
|
||||
<button type="submit" class="btn btn-sm btn-danger"><i class="ri-delete-bin-2-line"></i> Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if all %}
|
||||
{{ table.dynamic('wish', no_sort='0,7')}}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,31 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} - Wishlist{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="container-fluid px-0">
|
||||
{% if g.login.is_authenticated() %}
|
||||
<div class="pb-2 px-2 border-bottom">
|
||||
{% if error %}<div class="alert alert-danger" role="alert"><strong>Error:</strong> {{ error }}.</div>{% endif %}
|
||||
<form action="{{ url_for('wish.add') }}" method="post" class="row row-cols-lg-auto g-1 justify-content-center align-items-center">
|
||||
<div class="col-12">
|
||||
<label class="visually-hidden" for="number">Set number</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-text"><i class="ri-hashtag"></i></div>
|
||||
<input type="text" class="form-control" id="number" name="number" placeholder="107-1 or 1642-1 or ..." value="" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary"><i class="ri-add-circle-line"></i> Add a set</button>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<a class="btn border bg-secondary-text" href="{{ url_for('admin.admin', open_retired=true) }}"><i class="ri-refresh-line"></i> Refresh retired sets</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% with all=true %}
|
||||
{% include 'wish/table.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,357 +0,0 @@
|
||||
<!doctype html>
|
||||
<html data-theme="light" class="has-navbar-fixed-top">
|
||||
|
||||
|
||||
<head>
|
||||
<title>Wishlist</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 -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bulma@1.0.0/css/bulma.min.css">
|
||||
|
||||
<script src="https://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<style>
|
||||
|
||||
table.sortable th:not(.sorttable_sorted):not(.sorttable_sorted_reverse):not(.sorttable_nosort):after {
|
||||
content: " \25B4\25BE"
|
||||
}
|
||||
table.sortable tbody tr:nth-child(2n) td {
|
||||
background: #ffffff;
|
||||
}
|
||||
table.sortable tbody tr:nth-child(2n+1) td {
|
||||
background: #ecf0f1;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%; /* Ensure the table takes full width of its container */
|
||||
|
||||
}
|
||||
|
||||
td {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.name-class {
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 600px) {
|
||||
|
||||
.hidden-desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
/* horizontal scrollbar for tables if mobile screen */
|
||||
.hidden-mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Chrome, Safari, Edge, Opera */
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.header {
|
||||
width:100%;
|
||||
text-align:center;
|
||||
z-index: 99;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
|
||||
.inputContainer {
|
||||
display: inline-block; /* Display as an inline block */
|
||||
vertical-align: middle; /* Center vertically within the cell */
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.inputContainer form {
|
||||
margin: 5% auto 5% auto;
|
||||
}
|
||||
|
||||
.inputField {
|
||||
display: flex;
|
||||
align-items: center; /* Align items vertically */
|
||||
width: 100%; /* Ensure inputField fills out inputContainer */
|
||||
height: 100%; /* Ensure inputField fills out inputContainer */
|
||||
}
|
||||
.inputField input {
|
||||
flex: 1;
|
||||
width: 70%;
|
||||
padding: 0px;
|
||||
margin: auto 2.5px auto 2.5px;
|
||||
}
|
||||
.inputField button {
|
||||
width: 30%;
|
||||
padding: 0px;
|
||||
margin: 0 2.5px 0 2.5px;
|
||||
}
|
||||
|
||||
.square-button {
|
||||
background-color: white;
|
||||
border: 1px solid #d1d1d1;
|
||||
}
|
||||
|
||||
|
||||
/* Modal Styles */
|
||||
#lightbox-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
padding-top: 50px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
.lightbox-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.lightbox-content {
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
#lightbox-text {
|
||||
position: absolute;
|
||||
bottom: 20px; /* Adjust as needed */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 10px;
|
||||
background-color: rgba(0, 0, 0, 0.5); /* Adjust background color and opacity */
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
#lightbox-modal .close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 25px;
|
||||
font-size: 50px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#lightbox-modal img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar is-dark is-fixed-top" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
Home
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="navMenu" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
|
||||
<form id="createForm" action="/wishlist" method="POST">
|
||||
<div class="field">
|
||||
<label class="label" for="inputField">Input:</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" id="inputField" name="inputField">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button id="submitButton" class="button is-primary" type="submit" name="create_submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="center-table">
|
||||
<table id="data" class="table tablemobile sortable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="fixed-width sorttable_nosort"></th>
|
||||
<th style="text-align:center;margin:0px;" class="fixed-width">ID</th>
|
||||
<th class="fixed-width">Name</th>
|
||||
<th style="text-align: center;" class="fixed-width hidden-mobile">Year</th>
|
||||
<th style="text-align: center;" class="fixed-width hidden-mobile">Parts</th>
|
||||
<th class="fixed-width hidden-mobile">Retirement Date</th>
|
||||
<th style="text-align: center;" class="sorttable_nosort">Delete</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody> <!-- set_num|name|year|theme_id|num_parts|set_img_url|set_url|last_modified_dt -->
|
||||
{% for sets in wishlist %}
|
||||
<tr>
|
||||
<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>{{ sets[1] }}</td>
|
||||
<td style="text-align:center;" class="hidden-mobile">{{ sets[2] }}</td>
|
||||
<td style="text-align:center;" class="hidden-mobile">{{ sets[4] }}</td>
|
||||
<td class="hidden-mobile">{{ sets[8] }}</td>
|
||||
<td style="text-align:center;">
|
||||
<form method="POST" style="margin:0">
|
||||
<input type="hidden" name="set_num" value="{{ sets[0] }}">
|
||||
<button type="submit" class="btn btn-primary" name="add_to_list">Delete</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<button class="js-modal-trigger" data-target="modal-js-example" id="modal-js">
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
<div class="modal" id="modal-js-example">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Cannot add set!</p>
|
||||
<button class="delete" aria-label="close"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
Set number does not exists:
|
||||
<br>
|
||||
<b id="error-code"></b>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<div class="buttons">
|
||||
<button class="button is-primary">OK</button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
|
||||
// Event listener for the form submission
|
||||
//document.getElementById("createForm").addEventListener("submit", function (event) {
|
||||
// event.preventDefault(); // Prevent default form submission
|
||||
// var inputField = document.getElementById("inputField").value;
|
||||
// console.log(inputField);
|
||||
// });
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const error = "{{ error }}"
|
||||
console.log("{{ error }}")
|
||||
if (error && error !== 'None') {
|
||||
document.getElementById("modal-js").click();
|
||||
document.getElementById("error-code").innerHTML = error;
|
||||
document.getElementById("inputField").value = "";
|
||||
const modal = document.getElementById('modal-js-example');
|
||||
modal.classList.add('is-active');
|
||||
}
|
||||
|
||||
// Functions to open and close a modal
|
||||
function openModal($el) {
|
||||
$el.classList.add('is-active');
|
||||
}
|
||||
|
||||
function closeModal($el) {
|
||||
$el.classList.remove('is-active');
|
||||
}
|
||||
|
||||
function closeAllModals() {
|
||||
(document.querySelectorAll('.modal') || []).forEach(($modal) => {
|
||||
closeModal($modal);
|
||||
});
|
||||
}
|
||||
|
||||
// Add a click event on buttons to open a specific modal
|
||||
(document.querySelectorAll('.js-modal-trigger') || []).forEach(($trigger) => {
|
||||
const modal = $trigger.dataset.target;
|
||||
const $target = document.getElementById(modal);
|
||||
|
||||
$trigger.addEventListener('click', () => {
|
||||
openModal($target);
|
||||
});
|
||||
});
|
||||
|
||||
// Add a click event on various child elements to close the parent modal
|
||||
(document.querySelectorAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button') || []).forEach(($close) => {
|
||||
const $target = $close.closest('.modal');
|
||||
|
||||
$close.addEventListener('click', () => {
|
||||
closeModal($target);
|
||||
});
|
||||
});
|
||||
|
||||
// Add a keyboard event to close all modals
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === "Escape") {
|
||||
closeAllModals();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user