Skip to content

Commit 4f45f51

Browse files
delchevclaude
andcommitted
feat(harmonia): humanize generated property labels + task-form Details-card layout
Two presentation fixes to the generated Harmonia UI, reported together. 1. Humanized property labels. The label catalog value for a property defaulted to the raw PascalCase name (parameterUtils widgetLabel default -> generateUtils translate action), so list columns and the detail/document view showed "PaymentMethod" / "TaxEventDate" instead of "Payment Method" / "Tax Event Date" - while entity labels were already humanized. humanizeName moves to parameterUtils (the shared util module generateUtils already imports from), is exported, and now backs the widgetLabel default; an explicitly authored widgetLabel still wins. Affects every generated app (Angular + Harmonia); flows to the label catalog and the form/list templates alike. 2. Task-form Details-card layout. A BPM task form's read-only fields rendered as label-beside-value hbox rows; they now render as the entity detail / document view's headerless x-h-card of vbox detail cells (muted text-xs label above a text-sm value), so the Inbox task form matches the entity Details layout. Verified live: regenerated kf-mod-sales-invoices - the served label catalog humanizes (Payment Method / Tax Event Date / Bank Account) and the ApproveSalesInvoice task form renders the x-h-card + vbox gap-0 detail cells. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 19a1231 commit 4f45f51

3 files changed

Lines changed: 47 additions & 34 deletions

File tree

components/template/template-form-builder-harmonia/src/main/resources/META-INF/dirigible/template-form-builder-harmonia/ui/index.html.template

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
#macro(leaf $el)
22
#if($metadata.taskForm && $el.readonly && $el.model)
3-
<!-- BPM task form: a read-only field is a compact Label: Value row - a fixed-width muted
4-
label column with the value LEFT-aligned beside it, so values line up vertically like a
5-
classic detail card - one cell of the two-column grid.
3+
<!-- BPM task form: a read-only field renders as a detail cell - a muted label above its value,
4+
matching the entity detail/document view's headerless Details card (label text-xs muted,
5+
value text-sm), one cell of the two-column grid.
66
Editable opt-in fields (readonly=false) fall through to the input below.
77
A relation.field control's model is the resolver-set name/value variable, so e.g. listing
88
`customer.name` shows the customer's name here rather than the raw FK id. A number is
99
formatted with its pattern (grouped, fixed decimals); other values render as-is. -->
10-
<div class="hbox items-center gap-3">
11-
<span x-h-text.muted class="text-sm shrink-0" style="width: 9rem;">$!el.label</span>
10+
<div class="vbox gap-0">
11+
<span x-h-text.muted class="text-xs">$!el.label</span>
1212
#if($el.controlId == "input-number")
13-
<span x-h-text class="font-medium min-w-0 break-words" x-text="fmtNumber(model.$el.model, '$!el.pattern')"></span>
13+
<span x-h-text class="text-sm break-words" x-text="fmtNumber(model.$el.model, '$!el.pattern')"></span>
1414
#elseif($el.controlId == "input-date" || $el.controlId == "input-datetime-local" || $el.controlId == "input-time" || $el.controlId == "input-month")
15-
<span x-h-text class="font-medium min-w-0 break-words" x-text="fmtDate(model.$el.model, '$el.controlId')"></span>
15+
<span x-h-text class="text-sm break-words" x-text="fmtDate(model.$el.model, '$el.controlId')"></span>
1616
#else
17-
<span x-h-text class="font-medium min-w-0 break-words" x-text="(model.$el.model === null || model.$el.model === undefined || model.$el.model === '') ? '—' : model.$el.model"></span>
17+
<span x-h-text class="text-sm break-words" x-text="(model.$el.model === null || model.$el.model === undefined || model.$el.model === '') ? '—' : model.$el.model"></span>
1818
#end
1919
</div>
2020
#elseif($el.controlId == "header")
@@ -138,10 +138,13 @@
138138
<div x-h-alert-description x-text="message"></div>
139139
</div>
140140
#if($metadata.taskForm)
141-
<!-- Task form: a responsive two-column detail grid (single column below the sm breakpoint) -
142-
compact horizontal Label: Value rows; headers, separators, textareas and the action-button
143-
row span both columns. -->
144-
<div class="grid grid-cols-1 sm:grid-cols-2 gap-x-10 gap-y-3">
141+
<!-- Task form: read-only fields render inside a headerless Details card (framed), matching the
142+
entity detail / document view. A responsive two-column detail grid (single column below the
143+
sm breakpoint); headers, separators, textareas and the action-button row span both columns.
144+
Editable opt-in inputs and the actions live in the same card. -->
145+
<div x-h-card>
146+
<div x-h-card-content>
147+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-x-10 gap-y-3">
145148
#else
146149
<div x-h-field-group>
147150
#end
@@ -162,7 +165,13 @@
162165
#leaf($element)
163166
#end
164167
#end
168+
#if($metadata.taskForm)
169+
</div>
170+
</div>
165171
</div>
172+
#else
173+
</div>
174+
#end
166175
</form>
167176

168177
<!-- Date/time controls (model + control type + editable) so form.js can convert the backend's

components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/generateUtils.js

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,14 @@
1111
*/
1212
import { Registry } from "@aerokit/sdk/platform";
1313
import { TemplateEngines as templateEngines } from "@aerokit/sdk/template";
14-
import { sanitizeJavaIdentifier } from "service-generate/template/parameterUtils";
14+
import { sanitizeJavaIdentifier, humanizeName } from "service-generate/template/parameterUtils";
1515

1616
function getTranslationId(str) {
1717
return `${str.replaceAll(' ', '').replaceAll('_', '').replaceAll('.', '').replaceAll(':', '')}`;
1818
}
1919

20-
// Humanize a PascalCase/camelCase identifier for display ("SalesInvoice" -> "Sales Invoice").
21-
// Mirrors org.eclipse.dirigible.components.intent.generator.IntentNaming.humanize, including its
22-
// acronym overrides, so a hand-authored .edm (no entityLabel/menuLabel baked by the intent
23-
// generator) still yields the same labels the intent path would.
24-
const HUMANIZE_OVERRIDES = { 'uom': 'Unit of Measure' };
25-
26-
function humanizeName(name) {
27-
if (!name) return '';
28-
const override = HUMANIZE_OVERRIDES[name.toLowerCase()];
29-
if (override) return override;
30-
let out = '';
31-
for (let i = 0; i < name.length; i++) {
32-
const c = name.charAt(i);
33-
if (i > 0 && c >= 'A' && c <= 'Z' && !(name.charAt(i - 1) >= 'A' && name.charAt(i - 1) <= 'Z')) {
34-
out += ' ';
35-
}
36-
out += i === 0 ? c.toUpperCase() : c;
37-
}
38-
return out;
39-
}
20+
// humanizeName lives in parameterUtils (the lower-level util module this file already imports from)
21+
// so the label default in parameterUtils and the label catalog built here share one implementation.
4022

4123
// Pluralize a humanized label's last word ("Sales Invoice" -> "Sales Invoices", "Country" ->
4224
// "Countries"). Mirrors IntentNaming.pluralize, including its irregular overrides.

components/ui/service-generate/src/main/resources/META-INF/dirigible/service-generate/template/parameterUtils.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,28 @@ import { Configurations } from "@aerokit/sdk/core";
1313
import { Base64 } from "@aerokit/sdk/utils";
1414
import { Bytes } from "@aerokit/sdk/io";
1515

16+
// Humanize a PascalCase/camelCase identifier for display ("PaymentMethod" -> "Payment Method",
17+
// "TaxEventDate" -> "Tax Event Date"). Mirrors
18+
// org.eclipse.dirigible.components.intent.generator.IntentNaming.humanize, including its acronym
19+
// overrides, so a hand-authored .edm with no widgetLabel yields the same labels the intent path
20+
// would. Exported so generateUtils (the label catalog builder) uses the same implementation.
21+
const HUMANIZE_OVERRIDES = { 'uom': 'Unit of Measure' };
22+
23+
export function humanizeName(name) {
24+
if (!name) return '';
25+
const override = HUMANIZE_OVERRIDES[name.toLowerCase()];
26+
if (override) return override;
27+
let out = '';
28+
for (let i = 0; i < name.length; i++) {
29+
const c = name.charAt(i);
30+
if (i > 0 && c >= 'A' && c <= 'Z' && !(name.charAt(i - 1) >= 'A' && name.charAt(i - 1) <= 'Z')) {
31+
out += ' ';
32+
}
33+
out += i === 0 ? c.toUpperCase() : c;
34+
}
35+
return out;
36+
}
37+
1638
export function process(model, parameters) {
1739
parameters.javaGenFolderName = sanitizeJavaIdentifier(parameters.genFolderName);
1840

@@ -77,7 +99,7 @@ export function process(model, parameters) {
7799
p.isCalculatedProperty = p.isCalculatedProperty === "true";
78100
p.isReadOnlyProperty = p.isReadOnlyProperty === "true";
79101
p.widgetIsMajor = p.widgetIsMajor === "true";
80-
p.widgetLabel = p.widgetLabel ? p.widgetLabel : p.name;
102+
p.widgetLabel = p.widgetLabel ? p.widgetLabel : humanizeName(p.name);
81103

82104
if (p.name === "ProcessId") {
83105
e.hasProcess = true;

0 commit comments

Comments
 (0)