feat: per-task image/annotations, annotation editor toolbar, Tailwind compiled
- Add per-task file upload with image preview in task editor - Add dedicated annotation editor page (task_drawing.html) with Fabric.js - Add color picker, stroke width, and line dash controls to annotation toolbar - Apply property changes to selected objects in real-time - Disable style controls until a drawing tool or object is selected - Remove zoom/pan from annotation toolbar (simplified UX) - Auto-switch to select mode after placing annotation elements - Show annotation overlay on task image previews (read-only canvas) - Add file proxy route in measure blueprint for task file access - Add file_path/file_type fields to TaskCreate/TaskUpdate Pydantic schemas - Replace Tailwind CDN with compiled CSS (tailwind.config.js with full shades) - Fix Alpine.js x-init crash: extract annotations JSON to <script> tags (recipe_preview.html, task_execute.html) to avoid HTML attribute breakage Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -107,6 +107,30 @@ def task_editor(recipe_id: int):
|
||||
return render_template("maker/task_editor.html", recipe=recipe)
|
||||
|
||||
|
||||
@maker_bp.route("/recipes/<int:recipe_id>/tasks/<int:task_id>/drawing")
|
||||
@login_required
|
||||
@role_required("Maker")
|
||||
def task_drawing(recipe_id: int, task_id: int):
|
||||
"""Annotation editor for a specific task."""
|
||||
# Load recipe for breadcrumb context
|
||||
recipe_resp = api_client.get(f"/api/recipes/{recipe_id}")
|
||||
if recipe_resp.get("error"):
|
||||
flash(_("Errore nel caricamento della ricetta: %(error)s", error=recipe_resp.get("detail", "")), "error")
|
||||
return redirect(url_for("maker.recipe_list"))
|
||||
|
||||
# Load task details
|
||||
task_resp = api_client.get(f"/api/tasks/{task_id}")
|
||||
if task_resp.get("error"):
|
||||
flash(_("Task non trovato: %(error)s", error=task_resp.get("detail", "")), "error")
|
||||
return redirect(url_for("maker.task_editor", recipe_id=recipe_id))
|
||||
|
||||
return render_template(
|
||||
"maker/task_drawing.html",
|
||||
recipe=recipe_resp,
|
||||
task=task_resp,
|
||||
)
|
||||
|
||||
|
||||
@maker_bp.route("/recipes/<int:recipe_id>/preview")
|
||||
@login_required
|
||||
@role_required("Maker")
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
"""MeasurementTec blueprint - recipe selection and measurement execution."""
|
||||
import requests as http_requests
|
||||
|
||||
from flask import (
|
||||
Blueprint, flash, jsonify, redirect, render_template,
|
||||
Blueprint, Response, flash, jsonify, redirect, render_template,
|
||||
request, session, url_for,
|
||||
)
|
||||
from flask_babel import gettext as _
|
||||
|
||||
from blueprints.auth import login_required, role_required
|
||||
from config import Config
|
||||
from services.api_client import api_client
|
||||
|
||||
measure_bp = Blueprint("measure", __name__)
|
||||
@@ -272,3 +275,25 @@ def save_measurement():
|
||||
}), status_code if status_code >= 400 else 500
|
||||
|
||||
return jsonify(resp), 201
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Route: File proxy (browser can't send X-API-Key directly)
|
||||
# ---------------------------------------------------------------------------
|
||||
@measure_bp.route("/api/files/<path:file_path>", methods=["GET"])
|
||||
@login_required
|
||||
def api_get_file(file_path: str):
|
||||
"""Proxy: Serve file from API server (browser can't send X-API-Key)."""
|
||||
api_key = session.get("api_key", "")
|
||||
base_url = Config.API_SERVER_URL.rstrip("/")
|
||||
resp = http_requests.get(
|
||||
f"{base_url}/api/files/{file_path}",
|
||||
headers={"X-API-Key": api_key},
|
||||
timeout=30,
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
return Response(resp.text, status=resp.status_code)
|
||||
return Response(
|
||||
resp.content,
|
||||
content_type=resp.headers.get("content-type", "application/octet-stream"),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user