Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 73 additions & 7 deletions crates/ogar-render-askama/src/artifact_kinds/cells.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
//! Per-[`ColumnKind`](crate::ColumnKind) cell renderers — one askama
//! sub-template per variant. Called from
//! [`html_list_view`](super::html_list_view) at row-build time to
//! pre-render each cell's body, so the spine template just emits
//! `{{ cell.body_html|safe }}` with no runtime polymorphism.
//! sub-template per variant. Called from the list-view + detail-view
//! emitters at row-build time to pre-render each cell's body, so the
//! spine templates just emit `{{ cell.body_html|safe }}` with no
//! runtime polymorphism.
//!
//! Per Northstar §1.6 (mass-mail templates): each cell template is the
//! smallest bag of variables it needs (the `*Cell` binding-structs
//! below). Different cell kinds don't share a binding-struct.
//! [`render_cell_body`] is the shared dispatch entry point T2 / T3 / T4
//! all call (factored when T4 became the third caller — see Northstar
//! plan §1.6: "templates are mass-mail simple; the *binding-struct* is
//! the bag of variables; the dispatch is a Rust `match`").
//!
//! The catalog mirrors Redmine `queries_helper::column_value`'s 12-arm
//! case (see `docs/integration/REDMINE-QUERY-HARVEST.md` §1.4).

use askama::Template;

use super::html_list_view::CellData;

// ── Plain ────────────────────────────────────────────────────────────

#[derive(Template)]
Expand Down Expand Up @@ -119,3 +122,66 @@ pub(crate) struct UserEntry<'a> {
pub name: &'a str,
pub href: &'a str,
}

// ── Shared dispatch — one place T2 / T3 / T4 call ─────────────────────

/// Pre-render a [`CellData`] value into its HTML body via the matching
/// per-kind sub-template. The spine templates (`html_list_view`,
/// `html_detail_view`, `html_form`'s read-only fallback paths) consume
/// the resulting string with `|safe` because the sub-templates already
/// escape their own variables.
///
/// Factored from the duplicated `render_cell_body` helpers in T2 and T3
/// when T4 became the third caller. Northstar plan §1.6: three points
/// form a line.
pub(crate) fn render_cell_body(data: &CellData<'_>) -> Result<String, askama::Error> {
Ok(match data {
CellData::Plain { value } => PlainCell { value }.render()?,
CellData::IdLink { id, href } => IdLinkCell { id: *id, href }.render()?,
CellData::PrimaryLink { label, href } => PrimaryLinkCell { label, href }.render()?,
CellData::RecordRef {
label,
href,
target_concept,
} => RecordRefCell {
label,
href,
target_concept,
}
.render()?,
CellData::RichText { body } => RichTextCell { body }.render()?,
CellData::ProgressBar { pct } => ProgressBarCell { pct: *pct }.render()?,
CellData::RelationList { relations } => {
let mapped: Vec<RelationEntry<'_>> = relations
.iter()
.map(|r| RelationEntry {
id: r.id,
kind: r.kind.as_str(),
href: r.href.as_str(),
})
.collect();
RelationListCell { relations: mapped }.render()?
}
CellData::Hours { hours, href } => HoursCell { hours, href }.render()?,
CellData::AttachmentList { attachments } => {
let mapped: Vec<AttachmentEntry<'_>> = attachments
.iter()
.map(|a| AttachmentEntry {
filename: a.filename.as_str(),
href: a.href.as_str(),
})
.collect();
AttachmentListCell { attachments: mapped }.render()?
}
CellData::UserList { users } => {
let mapped: Vec<UserEntry<'_>> = users
.iter()
.map(|u| UserEntry {
name: u.name.as_str(),
href: u.href.as_str(),
})
.collect();
UserListCell { users: mapped }.render()?
}
})
}
47 changes: 4 additions & 43 deletions crates/ogar-render-askama/src/artifact_kinds/html_detail_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@

use askama::Template;

use super::cells::{
AttachmentEntry, AttachmentListCell, HoursCell, IdLinkCell, PlainCell, PrimaryLinkCell,
ProgressBarCell, RecordRefCell, RelationEntry, RelationListCell, RichTextCell, UserEntry,
UserListCell,
};
use super::html_list_view::{CellData, CellSource};
use super::ArtifactEmitter;
use crate::list_view::{ColumnKind, RenderColumn};
Expand Down Expand Up @@ -116,45 +111,11 @@ pub fn render_detail(
.render()
}

/// Same per-kind dispatch as the list view (kept duplicated here rather
/// than re-exported because the list-view side has crate-internal naming;
/// once T3 + T4 stabilise we factor the dispatch into one helper).
/// Pre-render this cell's body via the shared per-kind dispatch.
/// See [`super::cells::render_cell_body`]; factored when T4 became the
/// third caller (T2 list-view, T3 detail-view, T4 form-view fallback).
fn render_cell_body(src: &CellSource<'_>) -> Result<String, askama::Error> {
Ok(match &src.data {
CellData::Plain { value } => PlainCell { value }.render()?,
CellData::IdLink { id, href } => IdLinkCell { id: *id, href }.render()?,
CellData::PrimaryLink { label, href } => PrimaryLinkCell { label, href }.render()?,
CellData::RecordRef { label, href, target_concept } => RecordRefCell {
label,
href,
target_concept,
}
.render()?,
CellData::RichText { body } => RichTextCell { body }.render()?,
CellData::ProgressBar { pct } => ProgressBarCell { pct: *pct }.render()?,
CellData::RelationList { relations } => {
let mapped: Vec<RelationEntry<'_>> = relations
.iter()
.map(|r| RelationEntry { id: r.id, kind: r.kind.as_str(), href: r.href.as_str() })
.collect();
RelationListCell { relations: mapped }.render()?
}
CellData::Hours { hours, href } => HoursCell { hours, href }.render()?,
CellData::AttachmentList { attachments } => {
let mapped: Vec<AttachmentEntry<'_>> = attachments
.iter()
.map(|a| AttachmentEntry { filename: a.filename.as_str(), href: a.href.as_str() })
.collect();
AttachmentListCell { attachments: mapped }.render()?
}
CellData::UserList { users } => {
let mapped: Vec<UserEntry<'_>> = users
.iter()
.map(|u| UserEntry { name: u.name.as_str(), href: u.href.as_str() })
.collect();
UserListCell { users: mapped }.render()?
}
})
super::cells::render_cell_body(&src.data)
}

/// The codebook-only dispatch entry point used by [`for_kind`](super::for_kind).
Expand Down
Loading
Loading