feat(ui/templates): derived-template action and slimmer composition row
Right-click a template now offers "New Derived Template" — opens TemplateCreate with the parent pre-selected via a new ?parentId query parameter. Composition rows in the tree drop the trailing "→ TargetName" muted text; the kind glyph plus the instance name carry enough meaning, and the composed template is one click away from the row's right-click menu.
This commit is contained in:
@@ -29,7 +29,7 @@ Inheritance is **not** rendered as tree nesting in the image, and it is not rend
|
|||||||
| Root-level templates | Allowed (`FolderId` nullable). Existing templates migrate with `FolderId = null`. |
|
| Root-level templates | Allowed (`FolderId` nullable). Existing templates migrate with `FolderId = null`. |
|
||||||
| Folder delete with contents | Blocked; structured error lists child counts. |
|
| Folder delete with contents | Blocked; structured error lists child counts. |
|
||||||
| Page layout | **Tree browser only** — no split-pane editor. Selecting a template navigates to `/design/templates/{id}` (TemplateEdit page); creating navigates to `/design/templates/create`. |
|
| Page layout | **Tree browser only** — no split-pane editor. Selecting a template navigates to `/design/templates/{id}` (TemplateEdit page); creating navigates to `/design/templates/create`. |
|
||||||
| Tree node visuals | Per `Component-TreeView.md` Visual Design Guide V7: Bootstrap Icons (`bi-folder` / `bi-folder2-open` / `bi-file-earmark-text` / `bi-arrow-return-right`), name-only labels (no count/inherit badges on template nodes), folder child-count pill, composition `→ $Target` muted secondary text. |
|
| Tree node visuals | Per `Component-TreeView.md` Visual Design Guide V7: Bootstrap Icons (`bi-folder` / `bi-folder2-open` / `bi-file-earmark-text` / `bi-arrow-return-right`), name-only labels (no count/inherit badges on template nodes; composition rows also name-only — the glyph signals the kind), folder child-count pill. |
|
||||||
|
|
||||||
## Data model
|
## Data model
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ private record TmplNode(
|
|||||||
**Inline node labels** (see `Component-TreeView.md` V7 for the canonical recipe):
|
**Inline node labels** (see `Component-TreeView.md` V7 for the canonical recipe):
|
||||||
- Folder: `<i class="bi bi-folder">` (closed) or `<i class="bi bi-folder2-open">` (expanded) + name (semibold when has children) + count-pill badge of direct children.
|
- Folder: `<i class="bi bi-folder">` (closed) or `<i class="bi bi-folder2-open">` (expanded) + name (semibold when has children) + count-pill badge of direct children.
|
||||||
- Template: `<i class="bi bi-file-earmark-text">` + `$Name` (semibold when has compositions). **No** inheritance hint, **no** attr/alarm/script count, **no** composition count on the node.
|
- Template: `<i class="bi bi-file-earmark-text">` + `$Name` (semibold when has compositions). **No** inheritance hint, **no** attr/alarm/script count, **no** composition count on the node.
|
||||||
- Composition: `<i class="bi bi-arrow-return-right">` + composition instance name + muted `→ $ComposedTemplateName` secondary text.
|
- Composition: `<i class="bi bi-arrow-return-right">` + composition instance name only. The composed template name is intentionally omitted from the tree — open the owning template's edit page to see/manage compositions.
|
||||||
|
|
||||||
**Search/filter:** out of scope for v1; the underlying component supports external filtering (per `Component-TreeView.md` R8) so it can be added later without component changes.
|
**Search/filter:** out of scope for v1; the underlying component supports external filtering (per `Component-TreeView.md` R8) so it can be added later without component changes.
|
||||||
|
|
||||||
@@ -160,8 +160,8 @@ private record TmplNode(
|
|||||||
| ▶ 📁 _Default Templates |
|
| ▶ 📁 _Default Templates |
|
||||||
| ▼ 📂 Dev |
|
| ▼ 📂 Dev |
|
||||||
| 📄 $TestMachine |
|
| 📄 $TestMachine |
|
||||||
| ↪ DelmiaReceiver → $DelmiaSvc |
|
| ↪ DelmiaReceiver |
|
||||||
| ↪ MESReceiver → $MesSvc |
|
| ↪ MESReceiver |
|
||||||
| 📄 $TestObject |
|
| 📄 $TestObject |
|
||||||
| ▶ 📁 System |
|
| ▶ 📁 System |
|
||||||
| 📄 $UnfiledTemplate |
|
| 📄 $UnfiledTemplate |
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ Three node kinds; concrete recipes following V1–V6.
|
|||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| Folder | `bi-folder` | `bi-folder2-open` | folder name (semibold when has children, regular otherwise) | — | count of direct children (subtle pill), only if ≥ 1 |
|
| Folder | `bi-folder` | `bi-folder2-open` | folder name (semibold when has children, regular otherwise) | — | count of direct children (subtle pill), only if ≥ 1 |
|
||||||
| Template | `bi-file-earmark-text` | same (templates with compositions still use the same glyph — chevron carries state) | `$Name` (semibold when has compositions, regular otherwise) | — | none |
|
| Template | `bi-file-earmark-text` | same (templates with compositions still use the same glyph — chevron carries state) | `$Name` (semibold when has compositions, regular otherwise) | — | none |
|
||||||
| Composition | `bi-arrow-return-right` | n/a (leaf, no expanded state) | composition instance name (regular weight) | `<span class="text-muted small ms-1">→ $TargetName</span>` | none |
|
| Composition | `bi-arrow-return-right` | n/a (leaf, no expanded state) | composition instance name (regular weight) | — | none |
|
||||||
|
|
||||||
**`NodeContent` fragment** for the templates page (replaces the current `RenderNodeLabel` in `Templates.razor`):
|
**`NodeContent` fragment** for the templates page (replaces the current `RenderNodeLabel` in `Templates.razor`):
|
||||||
|
|
||||||
@@ -452,13 +452,8 @@ Three node kinds; concrete recipes following V1–V6.
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TmplNodeKind.Composition:
|
case TmplNodeKind.Composition:
|
||||||
var composedName = _templates.FirstOrDefault(t => t.Id == node.Composition!.ComposedTemplateId)?.Name
|
|
||||||
?? $"#{node.Composition!.ComposedTemplateId}";
|
|
||||||
<span class="tv-glyph"><i class="bi bi-arrow-return-right"></i></span>
|
<span class="tv-glyph"><i class="bi bi-arrow-return-right"></i></span>
|
||||||
<span class="tv-label" title="@node.Label">
|
<span class="tv-label" title="@node.Label">@node.Label</span>
|
||||||
@node.Label
|
|
||||||
<span class="text-muted small ms-1">→ @composedName</span>
|
|
||||||
</span>
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
|
|
||||||
@code {
|
@code {
|
||||||
[SupplyParameterFromQuery] public int? FolderId { get; set; }
|
[SupplyParameterFromQuery] public int? FolderId { get; set; }
|
||||||
|
[SupplyParameterFromQuery] public int? ParentId { get; set; }
|
||||||
|
|
||||||
private List<Template> _templates = new();
|
private List<Template> _templates = new();
|
||||||
private bool _loading = true;
|
private bool _loading = true;
|
||||||
@@ -71,6 +72,10 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_templates = (await TemplateEngineRepository.GetAllTemplatesAsync()).ToList();
|
_templates = (await TemplateEngineRepository.GetAllTemplatesAsync()).ToList();
|
||||||
|
if (ParentId is int pid && _templates.Any(t => t.Id == pid))
|
||||||
|
{
|
||||||
|
_createParentId = pid;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -239,13 +239,8 @@
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TmplNodeKind.Composition:
|
case TmplNodeKind.Composition:
|
||||||
var composedName = _templates.FirstOrDefault(t => t.Id == node.Composition!.ComposedTemplateId)?.Name
|
|
||||||
?? $"#{node.Composition!.ComposedTemplateId}";
|
|
||||||
<span class="tv-glyph"><i class="bi bi-arrow-return-right"></i></span>
|
<span class="tv-glyph"><i class="bi bi-arrow-return-right"></i></span>
|
||||||
<span class="tv-label" title="@node.Label">
|
<span class="tv-label" title="@node.Label">@node.Label</span>
|
||||||
@node.Label
|
|
||||||
<span class="text-muted small ms-1">→ @composedName</span>
|
|
||||||
</span>
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -282,6 +277,7 @@
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case TmplNodeKind.Template:
|
case TmplNodeKind.Template:
|
||||||
|
<button class="dropdown-item" @onclick='() => NavigationManager.NavigateTo($"/design/templates/create?parentId={node.EntityId}")'>New Derived Template</button>
|
||||||
<button class="dropdown-item" @onclick="() => OpenMoveTemplateDialog(node.EntityId, node.Label)">Move to Folder…</button>
|
<button class="dropdown-item" @onclick="() => OpenMoveTemplateDialog(node.EntityId, node.Label)">Move to Folder…</button>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<button class="dropdown-item text-danger" @onclick="() => DeleteTemplate(node.Template!)">Delete</button>
|
<button class="dropdown-item text-danger" @onclick="() => DeleteTemplate(node.Template!)">Delete</button>
|
||||||
|
|||||||
@@ -116,7 +116,9 @@ public class TemplatesPageTests : BunitContext
|
|||||||
ownerToggle!.Click();
|
ownerToggle!.Click();
|
||||||
|
|
||||||
Assert.Contains("DelmiaReceiver", cut.Markup);
|
Assert.Contains("DelmiaReceiver", cut.Markup);
|
||||||
Assert.Contains("→", cut.Markup);
|
// The composition glyph appears via Bootstrap Icons; the composed template name
|
||||||
|
// is intentionally not rendered on the tree (V7 spec).
|
||||||
|
Assert.Contains("bi-arrow-return-right", cut.Markup);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user