feat: Phase 4 grouping with entry-break dividers
Add group.py route, phase4.html template, and supporting state changes. Photos are shown as a flat stream; clicking divider zones inserts entry-break boundaries that split photos into labelled groups. Labels persist via group_labels dict. Done materialises groups into state.groups and advances to write phase. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import uuid
|
||||
from flask import Blueprint, current_app, jsonify, redirect, render_template, request
|
||||
from app.state import Group, load_state, save_state
|
||||
|
||||
bp = Blueprint("group", __name__)
|
||||
|
||||
|
||||
def _build_groups(state):
|
||||
"""Compute display groups from kept photos + dividers."""
|
||||
kept = sorted(
|
||||
[p for p in state.photos if p.tag in ("journal", "story")],
|
||||
key=lambda p: p.order,
|
||||
)
|
||||
divider_orders = sorted(d["after_order"] for d in state.dividers)
|
||||
divider_ids = {d["after_order"]: d["id"] for d in state.dividers}
|
||||
|
||||
groups = []
|
||||
current_group = []
|
||||
for photo in kept:
|
||||
current_group.append(photo)
|
||||
if photo.order in divider_orders:
|
||||
div_id = divider_ids[photo.order]
|
||||
groups.append({
|
||||
"photos": current_group,
|
||||
"divider_id": div_id,
|
||||
"label": state.group_labels.get(div_id, ""),
|
||||
})
|
||||
current_group = []
|
||||
if current_group:
|
||||
groups.append({"photos": current_group, "divider_id": None, "label": ""})
|
||||
return groups, kept
|
||||
|
||||
|
||||
@bp.get("/group")
|
||||
def group():
|
||||
album_id = request.args["album_id"]
|
||||
state = load_state(album_id, current_app)
|
||||
groups, kept = _build_groups(state)
|
||||
return render_template(
|
||||
"phase4.html",
|
||||
state=state,
|
||||
groups=groups,
|
||||
kept=kept,
|
||||
current_phase="group",
|
||||
album_id=album_id,
|
||||
phase_stale=state.phase_stale,
|
||||
notes_content=state.notes,
|
||||
)
|
||||
|
||||
|
||||
@bp.post("/group/divider")
|
||||
def add_divider():
|
||||
body = request.get_json()
|
||||
state = load_state(body["album_id"], current_app)
|
||||
after_order = int(body["after_order"])
|
||||
if not any(d["after_order"] == after_order for d in state.dividers):
|
||||
state.dividers.append({"id": str(uuid.uuid4()), "after_order": after_order})
|
||||
save_state(state, current_app)
|
||||
return jsonify({"ok": True})
|
||||
|
||||
|
||||
@bp.post("/group/remove-divider")
|
||||
def remove_divider():
|
||||
body = request.get_json()
|
||||
state = load_state(body["album_id"], current_app)
|
||||
state.dividers = [d for d in state.dividers if d["id"] != body["divider_id"]]
|
||||
state.group_labels.pop(body["divider_id"], None)
|
||||
save_state(state, current_app)
|
||||
return jsonify({"ok": True})
|
||||
|
||||
|
||||
@bp.post("/group/label")
|
||||
def set_label():
|
||||
body = request.get_json()
|
||||
state = load_state(body["album_id"], current_app)
|
||||
state.group_labels[body["divider_id"]] = body["label"]
|
||||
save_state(state, current_app)
|
||||
return jsonify({"ok": True})
|
||||
|
||||
|
||||
@bp.post("/group/done")
|
||||
def done():
|
||||
body = request.get_json()
|
||||
state = load_state(body["album_id"], current_app)
|
||||
groups, _ = _build_groups(state)
|
||||
state.groups = []
|
||||
for g in groups:
|
||||
first_photo = g["photos"][0]
|
||||
state.groups.append(Group(
|
||||
id=str(uuid.uuid4()),
|
||||
photo_ids=[p.id for p in g["photos"]],
|
||||
entry_type=first_photo.tag,
|
||||
date=first_photo.local_datetime[:10],
|
||||
label=g["label"],
|
||||
))
|
||||
if "group" not in state.phases_completed:
|
||||
state.phases_completed.append("group")
|
||||
state.phase = "write"
|
||||
save_state(state, current_app)
|
||||
return jsonify({"ok": True, "redirect": f"/write?album_id={body['album_id']}"})
|
||||
|
||||
|
||||
@bp.post("/group/from-note")
|
||||
def from_note():
|
||||
body = request.get_json()
|
||||
state = load_state(body["album_id"], current_app)
|
||||
state.groups.append(Group(
|
||||
id=str(uuid.uuid4()),
|
||||
photo_ids=[],
|
||||
entry_type="journal",
|
||||
body=body.get("text", ""),
|
||||
))
|
||||
if "write" in state.phases_completed and "write" not in state.phase_stale:
|
||||
state.phase_stale.append("write")
|
||||
save_state(state, current_app)
|
||||
return jsonify({"ok": True})
|
||||
Reference in New Issue
Block a user