diff --git a/app/routes/instructions.py b/app/routes/instructions.py index 3027f08..9a90c6c 100644 --- a/app/routes/instructions.py +++ b/app/routes/instructions.py @@ -5,6 +5,7 @@ from app import db from app.models.set import Set from app.models.instruction import Instruction from app.services.file_handler import FileHandler +from app.services import page_order import os instructions_bp = Blueprint('instructions', __name__, url_prefix='/instructions') @@ -41,14 +42,7 @@ def upload(set_id): # Determine page number for images page_number = 1 if file_type == 'IMAGE': - # Get the highest page number for this set - max_page = db.session.query( - db.func.max(Instruction.page_number) - ).filter_by( - set_id=set_id, - file_type='IMAGE' - ).scalar() - page_number = (max_page or 0) + 1 + page_number = page_order.next_page_number(set_id) # Create instruction record instruction = Instruction( @@ -156,25 +150,25 @@ def view(instruction_id): @instructions_bp.route('//reorder', methods=['POST']) @login_required def reorder(set_id): - """Reorder image instructions for a set.""" - lego_set = Set.query.get_or_404(set_id) - - # Get new order from request - new_order = request.json.get('order', []) - + """Save a new page order for a set's image instructions.""" + Set.query.get_or_404(set_id) + + payload = request.get_json(silent=True) or {} + new_order = payload.get('order', []) + if not new_order: return jsonify({'error': 'No order provided'}), 400 - + try: - # Update page numbers - for index, instruction_id in enumerate(new_order, start=1): - instruction = Instruction.query.get(instruction_id) - if instruction and instruction.set_id == set_id: - instruction.page_number = index - - db.session.commit() - return jsonify({'success': True}) - + pages = page_order.apply_order(set_id, new_order) + return jsonify({ + 'success': True, + 'pages': [p.to_dict() for p in pages], + 'audit': page_order.audit(set_id), + }) + except ValueError as e: + db.session.rollback() + return jsonify({'error': str(e)}), 400 except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 500 @@ -201,8 +195,10 @@ def bulk_upload(set_id): # Determine file type file_type = FileHandler.get_file_type(file.filename) - # Save file - file_path, file_size = FileHandler.save_file( + # Save file (save_file returns three values; the thumbnail was being + # dropped here, which blew up every bulk upload with a tuple-unpack + # error before it ever reached the database) + file_path, file_size, thumbnail_path = FileHandler.save_file( file, lego_set.set_number, file_type @@ -211,13 +207,7 @@ def bulk_upload(set_id): # Determine page number for images page_number = 1 if file_type == 'IMAGE': - max_page = db.session.query( - db.func.max(Instruction.page_number) - ).filter_by( - set_id=set_id, - file_type='IMAGE' - ).scalar() - page_number = (max_page or 0) + 1 + page_number = page_order.next_page_number(set_id) # Create instruction record instruction = Instruction( @@ -227,6 +217,7 @@ def bulk_upload(set_id): file_name=secure_filename(file.filename), file_size=file_size, page_number=page_number, + thumbnail_path=thumbnail_path, user_id=current_user.id ) @@ -303,3 +294,116 @@ def debug_paths(set_id): debug_info['instructions'].append(info) return jsonify(debug_info) + + +# --------------------------------------------------------------------------- +# Page organiser: fixing the order of image instructions after upload. +# --------------------------------------------------------------------------- + +@instructions_bp.route('//organise') +@login_required +def organise(set_id): + """Drag-and-drop page organiser for a set's image instructions.""" + lego_set = Set.query.get_or_404(set_id) + pages = page_order.image_pages(set_id) + + if not pages: + flash('There are no image pages to organise for this set.', 'info') + return redirect(url_for('sets.view_set', set_id=set_id)) + + return render_template( + 'instructions/organise.html', + set=lego_set, + pages=pages, + page_hints={p.id: page_order.page_hint(p.file_name or '') for p in pages}, + strategies=page_order.strategy_choices(), + audit=page_order.audit(set_id), + ) + + +@instructions_bp.route('//auto-sort', methods=['POST']) +@login_required +def auto_sort(set_id): + """Sort a set's pages by filename, detected page number, or upload order.""" + Set.query.get_or_404(set_id) + + payload = request.get_json(silent=True) or request.form + strategy = (payload.get('strategy') or 'filename').strip() + reverse = str(payload.get('reverse', '')).lower() in ('1', 'true', 'yes', 'on') + + try: + if strategy == page_order.REVERSIBLE: + pages = page_order.reverse_order(set_id) + message = 'Page order reversed.' + else: + pages = page_order.auto_sort(set_id, strategy, reverse=reverse) + label = dict(page_order.strategy_choices()).get(strategy, strategy) + message = f'Sorted {len(pages)} pages by {label.lower()}.' + except ValueError as e: + if request.is_json: + return jsonify({'error': str(e)}), 400 + flash(str(e), 'danger') + return redirect(url_for('instructions.organise', set_id=set_id)) + except Exception as e: + db.session.rollback() + if request.is_json: + return jsonify({'error': str(e)}), 500 + flash(f'Could not sort pages: {e}', 'danger') + return redirect(url_for('instructions.organise', set_id=set_id)) + + if request.is_json: + return jsonify({ + 'success': True, + 'message': message, + 'pages': [p.to_dict() for p in pages], + }) + + flash(message, 'success') + return redirect(url_for('instructions.organise', set_id=set_id)) + + +@instructions_bp.route('//renumber', methods=['POST']) +@login_required +def renumber(set_id): + """Close gaps and clear duplicate page numbers without moving anything.""" + Set.query.get_or_404(set_id) + + try: + pages = page_order.renumber(set_id) + except Exception as e: + db.session.rollback() + if request.is_json: + return jsonify({'error': str(e)}), 500 + flash(f'Could not renumber pages: {e}', 'danger') + return redirect(url_for('instructions.organise', set_id=set_id)) + + message = f'Renumbered {len(pages)} pages 1 to {len(pages)}.' + + if request.is_json: + return jsonify({'success': True, 'message': message, + 'pages': [p.to_dict() for p in pages]}) + + flash(message, 'success') + return redirect(url_for('instructions.organise', set_id=set_id)) + + +@instructions_bp.route('//move', methods=['POST']) +@login_required +def move(instruction_id): + """Move one page to a given position, shuffling the rest around it.""" + payload = request.get_json(silent=True) or request.form + position = payload.get('position') + + try: + pages = page_order.move_page(instruction_id, int(position)) + except (TypeError, ValueError) as e: + db.session.rollback() + return jsonify({'error': str(e)}), 400 + except Exception as e: + db.session.rollback() + return jsonify({'error': str(e)}), 500 + + return jsonify({ + 'success': True, + 'pages': [p.to_dict() for p in pages], + })