Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d0147b8061 | |||
| ca0de215ab | |||
| 05b259e494 | |||
| f03fd82be1 | |||
| a769e5464b |
@@ -134,6 +134,10 @@
|
|||||||
# Default: false
|
# Default: false
|
||||||
# BK_HIDE_TABLE_MISSING_PARTS=true
|
# BK_HIDE_TABLE_MISSING_PARTS=true
|
||||||
|
|
||||||
|
# Optional: Hide the 'Checked' column from the parts table.
|
||||||
|
# Default: false
|
||||||
|
# BK_HIDE_TABLE_CHECKED_PARTS=true
|
||||||
|
|
||||||
# Optional: Hide the 'Wishlist' entry from the menu. Does not disable the route.
|
# Optional: Hide the 'Wishlist' entry from the menu. Does not disable the route.
|
||||||
# Default: false
|
# Default: false
|
||||||
# BK_HIDE_WISHES=true
|
# BK_HIDE_WISHES=true
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ CONFIG: Final[list[dict[str, Any]]] = [
|
|||||||
{'n': 'HIDE_SET_INSTRUCTIONS', 'c': bool},
|
{'n': 'HIDE_SET_INSTRUCTIONS', 'c': bool},
|
||||||
{'n': 'HIDE_TABLE_DAMAGED_PARTS', 'c': bool},
|
{'n': 'HIDE_TABLE_DAMAGED_PARTS', 'c': bool},
|
||||||
{'n': 'HIDE_TABLE_MISSING_PARTS', 'c': bool},
|
{'n': 'HIDE_TABLE_MISSING_PARTS', 'c': bool},
|
||||||
|
{'n': 'HIDE_TABLE_CHECKED_PARTS', 'c': bool},
|
||||||
{'n': 'HIDE_WISHES', 'c': bool},
|
{'n': 'HIDE_WISHES', 'c': bool},
|
||||||
{'n': 'MINIFIGURES_DEFAULT_ORDER', 'd': '"rebrickable_minifigures"."name" ASC'}, # noqa: E501
|
{'n': 'MINIFIGURES_DEFAULT_ORDER', 'd': '"rebrickable_minifigures"."name" ASC'}, # noqa: E501
|
||||||
{'n': 'MINIFIGURES_FOLDER', 'd': 'minifigs', 's': True},
|
{'n': 'MINIFIGURES_FOLDER', 'd': 'minifigs', 's': True},
|
||||||
|
|||||||
@@ -159,6 +159,43 @@ class BrickPart(RebrickablePart):
|
|||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
# Update checked state for part walkthrough
|
||||||
|
def update_checked(self, json: Any | None, /) -> bool:
|
||||||
|
# Handle both direct 'checked' key and changer.js 'value' key format
|
||||||
|
if json:
|
||||||
|
checked = json.get('checked', json.get('value', False))
|
||||||
|
else:
|
||||||
|
checked = False
|
||||||
|
|
||||||
|
checked = bool(checked)
|
||||||
|
|
||||||
|
# Update the field
|
||||||
|
self.fields.checked = checked
|
||||||
|
|
||||||
|
BrickSQL().execute_and_commit(
|
||||||
|
'part/update/checked',
|
||||||
|
parameters=self.sql_parameters()
|
||||||
|
)
|
||||||
|
|
||||||
|
return checked
|
||||||
|
|
||||||
|
# Compute the url for updating checked state
|
||||||
|
def url_for_checked(self, /) -> str:
|
||||||
|
# Different URL for a minifigure part
|
||||||
|
if self.minifigure is not None:
|
||||||
|
figure = self.minifigure.fields.figure
|
||||||
|
else:
|
||||||
|
figure = None
|
||||||
|
|
||||||
|
return url_for(
|
||||||
|
'set.checked_part',
|
||||||
|
id=self.fields.id,
|
||||||
|
figure=figure,
|
||||||
|
part=self.fields.part,
|
||||||
|
color=self.fields.color,
|
||||||
|
spare=self.fields.spare,
|
||||||
|
)
|
||||||
|
|
||||||
# Update a problematic part
|
# Update a problematic part
|
||||||
def update_problem(self, problem: str, json: Any | None, /) -> int:
|
def update_problem(self, problem: str, json: Any | None, /) -> int:
|
||||||
amount: str | int = json.get('value', '') # type: ignore
|
amount: str | int = json.get('value', '') # type: ignore
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- description: Add checked field to bricktracker_parts table for part walkthrough tracking
|
||||||
|
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
-- Add checked field to the bricktracker_parts table
|
||||||
|
-- This allows users to track which parts they have checked during walkthroughs
|
||||||
|
ALTER TABLE "bricktracker_parts" ADD COLUMN "checked" BOOLEAN DEFAULT 0;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -9,6 +9,7 @@ SELECT
|
|||||||
--"bricktracker_parts"."rebrickable_inventory",
|
--"bricktracker_parts"."rebrickable_inventory",
|
||||||
"bricktracker_parts"."missing",
|
"bricktracker_parts"."missing",
|
||||||
"bricktracker_parts"."damaged",
|
"bricktracker_parts"."damaged",
|
||||||
|
"bricktracker_parts"."checked",
|
||||||
--"rebrickable_parts"."part",
|
--"rebrickable_parts"."part",
|
||||||
--"rebrickable_parts"."color_id",
|
--"rebrickable_parts"."color_id",
|
||||||
"rebrickable_parts"."color_name",
|
"rebrickable_parts"."color_name",
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
UPDATE "bricktracker_parts"
|
||||||
|
SET "checked" = :checked
|
||||||
|
WHERE "bricktracker_parts"."id" IS NOT DISTINCT FROM :id
|
||||||
|
AND "bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure
|
||||||
|
AND "bricktracker_parts"."part" IS NOT DISTINCT FROM :part
|
||||||
|
AND "bricktracker_parts"."color" IS NOT DISTINCT FROM :color
|
||||||
|
AND "bricktracker_parts"."spare" IS NOT DISTINCT FROM :spare
|
||||||
@@ -5,7 +5,7 @@ WHERE "bricktracker_sets"."id" IN (
|
|||||||
SELECT "bricktracker_parts"."id"
|
SELECT "bricktracker_parts"."id"
|
||||||
FROM "bricktracker_parts"
|
FROM "bricktracker_parts"
|
||||||
WHERE "bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure
|
WHERE "bricktracker_parts"."figure" IS NOT DISTINCT FROM :figure
|
||||||
AND "bricktracker_parts"."missing" > 0
|
AND "bricktracker_parts"."damaged" > 0
|
||||||
GROUP BY "bricktracker_parts"."id"
|
GROUP BY "bricktracker_parts"."id"
|
||||||
)
|
)
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
__version__: Final[str] = '1.2.5'
|
__version__: Final[str] = '1.3.0'
|
||||||
__database_version__: Final[int] = 17
|
__database_version__: Final[int] = 18
|
||||||
|
|||||||
@@ -294,6 +294,50 @@ def problem_part(
|
|||||||
return jsonify({problem: amount})
|
return jsonify({problem: amount})
|
||||||
|
|
||||||
|
|
||||||
|
# Update checked state of parts during walkthrough
|
||||||
|
@set_page.route('/<id>/parts/<part>/<int:color>/<int:spare>/checked', defaults={'figure': None}, methods=['POST']) # noqa: E501
|
||||||
|
@set_page.route('/<id>/minifigures/<figure>/parts/<part>/<int:color>/<int:spare>/checked', methods=['POST']) # noqa: E501
|
||||||
|
@login_required
|
||||||
|
@exception_handler(__file__, json=True)
|
||||||
|
def checked_part(
|
||||||
|
*,
|
||||||
|
id: str,
|
||||||
|
figure: str | None,
|
||||||
|
part: str,
|
||||||
|
color: int,
|
||||||
|
spare: int,
|
||||||
|
) -> Response:
|
||||||
|
brickset = BrickSet().select_specific(id)
|
||||||
|
|
||||||
|
if figure is not None:
|
||||||
|
brickminifigure = BrickMinifigure().select_specific(brickset, figure)
|
||||||
|
else:
|
||||||
|
brickminifigure = None
|
||||||
|
|
||||||
|
brickpart = BrickPart().select_specific(
|
||||||
|
brickset,
|
||||||
|
part,
|
||||||
|
color,
|
||||||
|
spare,
|
||||||
|
minifigure=brickminifigure,
|
||||||
|
)
|
||||||
|
|
||||||
|
checked = brickpart.update_checked(request.json)
|
||||||
|
|
||||||
|
# Info
|
||||||
|
logger.info('Set {set} ({id}): updated part ({part} color: {color}, spare: {spare}, minifigure: {figure}) checked state to {checked}'.format( # noqa: E501
|
||||||
|
set=brickset.fields.set,
|
||||||
|
id=brickset.fields.id,
|
||||||
|
figure=figure,
|
||||||
|
part=brickpart.fields.part,
|
||||||
|
color=brickpart.fields.color,
|
||||||
|
spare=brickpart.fields.spare,
|
||||||
|
checked=checked
|
||||||
|
))
|
||||||
|
|
||||||
|
return jsonify({'checked': checked})
|
||||||
|
|
||||||
|
|
||||||
# Refresh a set
|
# Refresh a set
|
||||||
@set_page.route('/refresh/<set>/', methods=['GET'])
|
@set_page.route('/refresh/<set>/', methods=['GET'])
|
||||||
@set_page.route('/<id>/refresh', methods=['GET'])
|
@set_page.route('/<id>/refresh', methods=['GET'])
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
// Bulk operations for parts in set details page
|
||||||
|
class PartsBulkOperations {
|
||||||
|
constructor(accordionId) {
|
||||||
|
this.accordionId = accordionId;
|
||||||
|
this.setupModal();
|
||||||
|
this.setupEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupModal() {
|
||||||
|
// Create Bootstrap modal if it doesn't exist
|
||||||
|
if (!document.getElementById('partsConfirmModal')) {
|
||||||
|
const modalHTML = `
|
||||||
|
<div class="modal fade" id="partsConfirmModal" tabindex="-1" aria-labelledby="partsConfirmModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="partsConfirmModalLabel">Confirm Action</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p id="partsConfirmModalMessage"></p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="partsConfirmModalConfirm">Confirm</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupEventListeners() {
|
||||||
|
// Mark all as missing (only if missing parts are not hidden)
|
||||||
|
const markAllMissingBtn = document.getElementById(`mark-all-missing-${this.accordionId}`);
|
||||||
|
if (markAllMissingBtn) {
|
||||||
|
markAllMissingBtn.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.confirmAndExecute(
|
||||||
|
'Mark all parts as missing?',
|
||||||
|
'This will set the missing count to the maximum quantity for all parts in this section.',
|
||||||
|
() => this.markAllMissing()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear all missing (only if missing parts are not hidden)
|
||||||
|
const clearAllMissingBtn = document.getElementById(`clear-all-missing-${this.accordionId}`);
|
||||||
|
if (clearAllMissingBtn) {
|
||||||
|
clearAllMissingBtn.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.confirmAndExecute(
|
||||||
|
'Clear all missing parts?',
|
||||||
|
'This will clear the missing field for all parts in this section.',
|
||||||
|
() => this.clearAllMissing()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check all checkboxes (only if checked parts are not hidden)
|
||||||
|
const checkAllBtn = document.getElementById(`check-all-${this.accordionId}`);
|
||||||
|
if (checkAllBtn) {
|
||||||
|
checkAllBtn.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.checkAll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uncheck all checkboxes (only if checked parts are not hidden)
|
||||||
|
const uncheckAllBtn = document.getElementById(`uncheck-all-${this.accordionId}`);
|
||||||
|
if (uncheckAllBtn) {
|
||||||
|
uncheckAllBtn.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
this.uncheckAll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmAndExecute(title, message, callback) {
|
||||||
|
const modal = document.getElementById('partsConfirmModal');
|
||||||
|
const modalTitle = document.getElementById('partsConfirmModalLabel');
|
||||||
|
const modalMessage = document.getElementById('partsConfirmModalMessage');
|
||||||
|
const confirmBtn = document.getElementById('partsConfirmModalConfirm');
|
||||||
|
|
||||||
|
// Set modal content
|
||||||
|
modalTitle.textContent = title;
|
||||||
|
modalMessage.textContent = message;
|
||||||
|
|
||||||
|
// Remove any existing event listeners and add new one
|
||||||
|
const newConfirmBtn = confirmBtn.cloneNode(true);
|
||||||
|
confirmBtn.parentNode.replaceChild(newConfirmBtn, confirmBtn);
|
||||||
|
|
||||||
|
newConfirmBtn.addEventListener('click', () => {
|
||||||
|
const modalInstance = bootstrap.Modal.getInstance(modal);
|
||||||
|
modalInstance.hide();
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
const modalInstance = new bootstrap.Modal(modal);
|
||||||
|
modalInstance.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
markAllMissing() {
|
||||||
|
const accordionElement = document.getElementById(this.accordionId);
|
||||||
|
if (!accordionElement) return;
|
||||||
|
|
||||||
|
// Find all rows in this accordion
|
||||||
|
const rows = accordionElement.querySelectorAll('tbody tr');
|
||||||
|
rows.forEach(row => {
|
||||||
|
// Find the quantity cell (usually 4th column)
|
||||||
|
const quantityCell = row.cells[3]; // Index 3 for quantity column
|
||||||
|
const missingInput = row.querySelector('input[id*="-missing-"]');
|
||||||
|
|
||||||
|
if (quantityCell && missingInput) {
|
||||||
|
// Extract quantity from cell text content
|
||||||
|
const quantityText = quantityCell.textContent.trim();
|
||||||
|
const quantity = parseInt(quantityText) || 1; // Default to 1 if can't parse
|
||||||
|
|
||||||
|
if (missingInput.value !== quantity.toString()) {
|
||||||
|
missingInput.value = quantity.toString();
|
||||||
|
// Trigger change event to activate BrickChanger
|
||||||
|
missingInput.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAllMissing() {
|
||||||
|
const accordionElement = document.getElementById(this.accordionId);
|
||||||
|
if (!accordionElement) return;
|
||||||
|
|
||||||
|
const missingInputs = accordionElement.querySelectorAll('input[id*="-missing-"]');
|
||||||
|
missingInputs.forEach(input => {
|
||||||
|
if (input.value !== '') {
|
||||||
|
input.value = '';
|
||||||
|
// Trigger change event to activate BrickChanger
|
||||||
|
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAll() {
|
||||||
|
const accordionElement = document.getElementById(this.accordionId);
|
||||||
|
if (!accordionElement) return;
|
||||||
|
|
||||||
|
const checkboxes = accordionElement.querySelectorAll('input[id*="-checked-"][type="checkbox"]');
|
||||||
|
checkboxes.forEach(checkbox => {
|
||||||
|
if (!checkbox.checked) {
|
||||||
|
checkbox.checked = true;
|
||||||
|
// Trigger change event to activate BrickChanger
|
||||||
|
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
uncheckAll() {
|
||||||
|
const accordionElement = document.getElementById(this.accordionId);
|
||||||
|
if (!accordionElement) return;
|
||||||
|
|
||||||
|
const checkboxes = accordionElement.querySelectorAll('input[id*="-checked-"][type="checkbox"]');
|
||||||
|
checkboxes.forEach(checkbox => {
|
||||||
|
if (checkbox.checked) {
|
||||||
|
checkbox.checked = false;
|
||||||
|
// Trigger change event to activate BrickChanger
|
||||||
|
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize bulk operations for all part accordions when DOM is ready
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Find all hamburger menus and initialize bulk operations
|
||||||
|
const hamburgerMenus = document.querySelectorAll('button[id^="hamburger-"]');
|
||||||
|
hamburgerMenus.forEach(button => {
|
||||||
|
const accordionId = button.id.replace('hamburger-', '');
|
||||||
|
new PartsBulkOperations(accordionId);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -50,6 +50,67 @@
|
|||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Checkbox column width constraint */
|
||||||
|
.table-td-input:has(.form-check-input[type="checkbox"]) {
|
||||||
|
width: 120px;
|
||||||
|
max-width: 120px;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reserve space for status icon to prevent layout shift */
|
||||||
|
.form-check-label i[id^="status-"] {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1.2em;
|
||||||
|
text-align: center;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hamburger menu styling */
|
||||||
|
.table th .dropdown {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th .dropdown-toggle {
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
border-color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th .dropdown-toggle:focus {
|
||||||
|
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th .dropdown-toggle:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style dropdown items */
|
||||||
|
.dropdown-menu .dropdown-header {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #6c757d;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu .dropdown-item {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu .dropdown-item:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu .dropdown-item i {
|
||||||
|
width: 1.25rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Fixes for sortable.js */
|
/* Fixes for sortable.js */
|
||||||
.sortable {
|
.sortable {
|
||||||
--th-color: #000 !important;
|
--th-color: #000 !important;
|
||||||
|
|||||||
@@ -105,6 +105,9 @@
|
|||||||
{% if request.endpoint == 'set.list' %}
|
{% if request.endpoint == 'set.list' %}
|
||||||
<script src="{{ url_for('static', filename='scripts/sets.js') }}"></script>
|
<script src="{{ url_for('static', filename='scripts/sets.js') }}"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if request.endpoint == 'set.details' %}
|
||||||
|
<script src="{{ url_for('static', filename='scripts/parts-bulk-operations.js') }}"></script>
|
||||||
|
{% endif %}
|
||||||
{% if request.endpoint == 'instructions.download' or request.endpoint == 'instructions.do_download' %}
|
{% if request.endpoint == 'instructions.download' or request.endpoint == 'instructions.do_download' %}
|
||||||
<script src="{{ url_for('static', filename='scripts/socket/peeron.js') }}"></script>
|
<script src="{{ url_for('static', filename='scripts/socket/peeron.js') }}"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% macro header(title, id, parent, quantity=none, expanded=false, icon=none, class=none, danger=none, image=none, alt=none) %}
|
{% macro header(title, id, parent, quantity=none, expanded=false, icon=none, class=none, danger=none, image=none, alt=none, hamburger_menu=none) %}
|
||||||
{% if danger %}
|
{% if danger %}
|
||||||
{% set icon='alert-fill' %}
|
{% set icon='alert-fill' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -43,10 +43,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro table(table_collection, title, id, parent, target, quantity=none, icon=none, image=none, alt=none, details=none, read_only=none) %}
|
{% macro table(table_collection, title, id, parent, target, quantity=none, icon=none, image=none, alt=none, details=none, read_only=none, hamburger_menu=none) %}
|
||||||
{% set size=table_collection | length %}
|
{% set size=table_collection | length %}
|
||||||
{% if size %}
|
{% if size %}
|
||||||
{{ header(title, id, parent, quantity=quantity, icon=icon, class='p-0', image=image, alt=alt) }}
|
{{ header(title, id, parent, quantity=quantity, icon=icon, class='p-0', image=image, alt=alt, hamburger_menu=hamburger_menu) }}
|
||||||
{% if details %}
|
{% if details %}
|
||||||
<p class="border-top border-bottom p-2 text-center">
|
<p class="border-top border-bottom p-2 text-center">
|
||||||
{% if image %}
|
{% if image %}
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
<a class="btn border bg-secondary-text" href="{{ details }}">{% if icon %}<i class="ri-{{ icon }}"></i>{% endif %} Details</a>
|
<a class="btn border bg-secondary-text" href="{{ details }}">{% if icon %}<i class="ri-{{ icon }}"></i>{% endif %} Details</a>
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% with solo=true, all=false %}
|
{% with solo=true, all=false, accordion_id=id %}
|
||||||
{% include target %}
|
{% include target %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{{ footer() }}
|
{{ footer() }}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% macro header(image=true, color=false, parts=false, quantity=false, missing=true, missing_parts=false, damaged=true, damaged_parts=false, sets=false, minifigures=false) %}
|
{% macro header(image=true, color=false, parts=false, quantity=false, missing=true, missing_parts=false, damaged=true, damaged_parts=false, sets=false, minifigures=false, checked=false, hamburger_menu=false, accordion_id='') %}
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{% if image %}
|
{% if image %}
|
||||||
@@ -26,6 +26,37 @@
|
|||||||
{% if minifigures %}
|
{% if minifigures %}
|
||||||
<th data-table-number="true" scope="col"><i class="ri-group-line fw-normal"></i> Minifigures</th>
|
<th data-table-number="true" scope="col"><i class="ri-group-line fw-normal"></i> Minifigures</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if checked and not config['HIDE_TABLE_CHECKED_PARTS'] %}
|
||||||
|
<th data-table-no-sort-and-search="true" class="no-sort" scope="col"><i class="ri-checkbox-line fw-normal"></i> Checked</th>
|
||||||
|
{% endif %}
|
||||||
|
{% if hamburger_menu and g.login.is_authenticated() %}
|
||||||
|
{% set show_missing_menu = not config['HIDE_TABLE_MISSING_PARTS'] %}
|
||||||
|
{% set show_checked_menu = not config['HIDE_TABLE_CHECKED_PARTS'] %}
|
||||||
|
{% if show_missing_menu or show_checked_menu %}
|
||||||
|
<th data-table-no-sort-and-search="true" class="no-sort text-end" scope="col">
|
||||||
|
<div class="dropdown">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" id="hamburger-{{ accordion_id }}" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="ri-menu-line"></i>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="hamburger-{{ accordion_id }}">
|
||||||
|
{% if show_missing_menu %}
|
||||||
|
<li><h6 class="dropdown-header">Missing Parts</h6></li>
|
||||||
|
<li><a class="dropdown-item" href="#" id="mark-all-missing-{{ accordion_id }}"><i class="ri-question-line me-2"></i>Mark all as missing</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" id="clear-all-missing-{{ accordion_id }}"><i class="ri-close-circle-line me-2"></i>Clear all missing</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if show_missing_menu and show_checked_menu %}
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
{% endif %}
|
||||||
|
{% if show_checked_menu %}
|
||||||
|
<li><h6 class="dropdown-header">Checked Status</h6></li>
|
||||||
|
<li><a class="dropdown-item" href="#" id="check-all-{{ accordion_id }}"><i class="ri-checkbox-line me-2"></i>Check all</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" id="uncheck-all-{{ accordion_id }}"><i class="ri-checkbox-blank-line me-2"></i>Uncheck all</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<div class="table-responsive-sm">
|
<div class="table-responsive-sm">
|
||||||
<table data-table="{% if all %}true{% endif %}" class="table table-striped align-middle {% if not all %}sortable mb-0{% endif %}" {% if all %}id="parts"{% endif %}>
|
<table data-table="{% if all %}true{% endif %}" 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, sets=all, minifigures=all) }}
|
{{ table.header(color=true, quantity=not no_quantity, sets=all, minifigures=all, checked=not all and not read_only, hamburger_menu=not all and not read_only, accordion_id=accordion_id|default('')) }}
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for item in table_collection %}
|
{% for item in table_collection %}
|
||||||
<tr>
|
<tr>
|
||||||
@@ -40,6 +40,19 @@
|
|||||||
{% if all %}
|
{% if all %}
|
||||||
<td>{{ item.fields.total_sets }}</td>
|
<td>{{ item.fields.total_sets }}</td>
|
||||||
<td>{{ item.fields.total_minifigures }}</td>
|
<td>{{ item.fields.total_minifigures }}</td>
|
||||||
|
{% else %}
|
||||||
|
{% if not config['HIDE_TABLE_CHECKED_PARTS'] and not read_only %}
|
||||||
|
<td class="table-td-input">
|
||||||
|
<center>{{ form.checkbox('', item.fields.id, item.html_id('checked'), item.url_for_checked(), item.fields.checked | default(false), parent='part', delete=read_only) }}</center>
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
{% if g.login.is_authenticated() and not read_only %}
|
||||||
|
{% set show_missing_menu = not config['HIDE_TABLE_MISSING_PARTS'] %}
|
||||||
|
{% set show_checked_menu = not config['HIDE_TABLE_CHECKED_PARTS'] %}
|
||||||
|
{% if show_missing_menu or show_checked_menu %}
|
||||||
|
<td></td>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -104,14 +104,14 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if g.login.is_authenticated() %}
|
{% if g.login.is_authenticated() %}
|
||||||
<a class="list-group-item list-group-item-action" href="{{ url_for('instructions.download', set=item.fields.set) }}"><i class="ri-download-line"></i> Download instructions from Rebrickable</a>
|
<a class="list-group-item list-group-item-action" href="{{ url_for('instructions.download', set=item.fields.set) }}"><i class="ri-download-line"></i> Download instructions</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{{ accordion.footer() }}
|
{{ accordion.footer() }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ accordion.table(item.parts(), 'Parts', 'parts-inventory', 'set-details', 'part/table.html', icon='shapes-line')}}
|
{{ accordion.table(item.parts(), 'Parts', 'parts-inventory', 'set-details', 'part/table.html', icon='shapes-line', hamburger_menu=g.login.is_authenticated())}}
|
||||||
{% for minifigure in item.minifigures() %}
|
{% for minifigure in item.minifigures() %}
|
||||||
{{ accordion.table(minifigure.parts(), minifigure.fields.name, minifigure.fields.figure, 'set-details', 'part/table.html', quantity=minifigure.fields.quantity, icon='group-line', image=minifigure.url_for_image(), alt=minifigure.fields.figure, details=minifigure.url())}}
|
{{ accordion.table(minifigure.parts(), minifigure.fields.name, minifigure.fields.figure, 'set-details', 'part/table.html', quantity=minifigure.fields.quantity, icon='group-line', image=minifigure.url_for_image(), alt=minifigure.fields.figure, details=minifigure.url(), hamburger_menu=g.login.is_authenticated())}}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% include 'set/management.html' %}
|
{% include 'set/management.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user