feat: show model titles in viewer graphs

This commit is contained in:
nejimakibird 2026-06-19 11:29:10 +09:00
parent 1f2c50742f
commit 8f6bc566dd
4 changed files with 231 additions and 11 deletions

91
main.js
View file

@ -8312,6 +8312,7 @@ function createMermaidShell(options) {
if (options.title) {
const title = activeDocument.createElement("h2");
title.textContent = options.title;
title.title = options.title;
title.addClass("model-weave-mermaid-title");
root.appendChild(title);
}
@ -19704,6 +19705,7 @@ var ModelingPreviewView = class extends import_obsidian7.ItemView {
...getClassDetailLabels(this.t),
showMermaidRenderDebug: this.viewerPreferences.showMermaidRenderDebug
});
ensureGraphIdentityTitle(mermaidRoot, buildGraphIdentityTitle(state.model));
this.appendRendererSelection(mermaidRoot, state.rendererSelection);
this.appendViewerToolbarControls(mermaidRoot);
shell3.topPane.appendChild(mermaidRoot);
@ -19723,6 +19725,7 @@ var ModelingPreviewView = class extends import_obsidian7.ItemView {
relatedList.remove();
shell3.bottomPane.appendChild(relatedList);
}
ensureGraphIdentityTitle(contextRoot, buildGraphIdentityTitle(state.model));
this.appendRendererSelection(contextRoot, state.rendererSelection);
this.appendViewerToolbarControls(contextRoot);
shell3.topPane.appendChild(contextRoot);
@ -19752,7 +19755,8 @@ var ModelingPreviewView = class extends import_obsidian7.ItemView {
shell3.topPane,
state.model.domains,
shell3.bottomPane,
state.colorScheme
state.colorScheme,
buildGraphIdentityTitle(state.model)
);
this.renderDomainTree(shell3.bottomPane, buildDomainTree(state.model.domains));
renderDiagnostics(
@ -19778,7 +19782,8 @@ var ModelingPreviewView = class extends import_obsidian7.ItemView {
shell3.topPane,
state.resolved.domains,
shell3.bottomPane,
state.colorScheme
state.colorScheme,
buildGraphIdentityTitle(state.resolved.diagram)
);
this.renderDomainTree(shell3.bottomPane, buildDomainTree(state.resolved.domains));
renderDiagnostics(
@ -20297,7 +20302,7 @@ var ModelingPreviewView = class extends import_obsidian7.ItemView {
getDiagnosticLanguage() {
return this.viewerPreferences.uiLanguage === "auto" ? void 0 : this.viewerPreferences.uiLanguage;
}
renderDomainMermaidDiagram(container, domains, sourcePanelContainer, colorScheme) {
renderDomainMermaidDiagram(container, domains, sourcePanelContainer, colorScheme, graphTitle) {
if (domains.length === 0) {
const section = this.createCollapsibleSection(
container,
@ -20313,7 +20318,7 @@ var ModelingPreviewView = class extends import_obsidian7.ItemView {
}
this.renderDomainDiagramModeSelector(container);
const diagramRoot = renderDomainsMermaidDiagram(domains, {
title: this.getDomainDiagramModeLabel(this.domainsDiagramMode),
title: graphTitle ?? this.getDomainDiagramModeLabel(this.domainsDiagramMode),
mode: this.domainsDiagramMode,
renderFailedMessage: this.t("domains.preview.diagramRenderFailed"),
fitVerticalAlign: "top",
@ -20327,6 +20332,7 @@ var ModelingPreviewView = class extends import_obsidian7.ItemView {
showMermaidRenderDebug: this.viewerPreferences.showMermaidRenderDebug,
colorScheme
});
ensureGraphIdentityTitle(diagramRoot, graphTitle ?? this.getDomainDiagramModeLabel(this.domainsDiagramMode));
this.appendViewerToolbarControls(diagramRoot);
container.appendChild(diagramRoot);
}
@ -20385,6 +20391,7 @@ var ModelingPreviewView = class extends import_obsidian7.ItemView {
onOpenLinkedFile: state.onOpenLinkedFile
}
);
ensureGraphIdentityTitle(screenRoot, buildSummaryGraphTitle(state));
this.appendViewerToolbarControls(screenRoot);
shell3.topPane.appendChild(screenRoot);
} else if (state.businessFlow) {
@ -20405,6 +20412,7 @@ var ModelingPreviewView = class extends import_obsidian7.ItemView {
state.filePath
)
});
ensureGraphIdentityTitle(businessFlowRoot, buildSummaryGraphTitle(state));
this.appendViewerToolbarControls(businessFlowRoot);
shell3.topPane.appendChild(businessFlowRoot);
this.renderSummaryDetails(shell3.bottomPane, state, {
@ -20508,6 +20516,7 @@ var ModelingPreviewView = class extends import_obsidian7.ItemView {
state.filePath
)
});
ensureGraphIdentityTitle(businessFlowRoot, buildSummaryGraphTitle(state));
this.appendViewerToolbarControls(businessFlowRoot);
section.appendChild(businessFlowRoot);
}
@ -21042,7 +21051,7 @@ var ModelingPreviewView = class extends import_obsidian7.ItemView {
renderContainer.empty();
const shell3 = createMermaidShell({
className: "model-weave-impact-weave-map-render",
title: this.t("relationship.weaveMap.title"),
title: buildWeaveMapGraphTitle(this.t, summary),
...getGraphExportLabels(this.t),
onExportPng: () => this.exportWeaveMapAsPng(renderContainer, summary.modelPath),
onExportAndOpenPng: () => this.exportWeaveMapAsPngAndOpen(renderContainer, summary.modelPath)
@ -21370,6 +21379,7 @@ var ModelingPreviewView = class extends import_obsidian7.ItemView {
...getClassDetailLabels(this.t),
showMermaidRenderDebug: this.viewerPreferences.showMermaidRenderDebug
});
ensureGraphIdentityTitle(diagramRoot, buildGraphIdentityTitle(state.model));
this.appendViewerToolbarControls(diagramRoot);
this.moveDetailSections(diagramRoot, shell3.bottomPane);
shell3.topPane.appendChild(diagramRoot);
@ -21403,6 +21413,7 @@ var ModelingPreviewView = class extends import_obsidian7.ItemView {
...getClassDetailLabels(this.t),
showMermaidRenderDebug: this.viewerPreferences.showMermaidRenderDebug
});
ensureGraphIdentityTitle(diagramRoot, buildGraphIdentityTitle(state.diagram.diagram));
this.appendRendererSelection(diagramRoot, state.rendererSelection);
this.appendViewerToolbarControls(diagramRoot);
this.moveDetailSections(diagramRoot, lowerSlots.details);
@ -22384,6 +22395,76 @@ function toModelWeaveUiLanguage(language) {
}
return "auto";
}
function asModelRecord(value) {
return typeof value === "object" && value !== null ? value : null;
}
function getStringField(value, key) {
const record = asModelRecord(value);
const field = record?.[key];
return typeof field === "string" && field.trim().length > 0 ? field.trim() : void 0;
}
function getFrontmatterString2(value, key) {
const frontmatter = asModelRecord(asModelRecord(value)?.frontmatter);
const field = frontmatter?.[key];
return typeof field === "string" && field.trim().length > 0 ? field.trim() : void 0;
}
function getModelDisplayName(value) {
return getStringField(value, "name") ?? getStringField(value, "title") ?? getStringField(value, "logicalName") ?? getStringField(value, "physicalName") ?? getFrontmatterString2(value, "name") ?? getFrontmatterString2(value, "title") ?? getModelId3(value);
}
function getModelId3(value) {
return getStringField(value, "id") ?? getFrontmatterString2(value, "id");
}
function getModelType(value) {
return getStringField(value, "fileType") ?? getFrontmatterString2(value, "type") ?? getStringField(value, "schema") ?? getFrontmatterString2(value, "schema");
}
function buildGraphIdentityTitle(value, fallbackName, fallbackType) {
const modelId = getModelId3(value);
const modelType = getModelType(value) ?? fallbackType;
const displayName = getModelDisplayName(value) ?? modelId ?? fallbackName ?? "Model";
const suffixParts = [
modelType,
modelId && modelId !== displayName ? modelId : void 0
].filter((part) => Boolean(part));
return suffixParts.length > 0 ? displayName + " (" + suffixParts.join(" / ") + ")" : displayName;
}
function findSummaryMetadataValue(state, keys) {
const normalizedKeys = new Set(keys.map((key) => key.toLowerCase()));
for (const entry of state.metadata) {
if (normalizedKeys.has(entry.label.toLowerCase())) {
return entry.value;
}
}
return void 0;
}
function buildSummaryGraphTitle(state) {
const summaryType = state.summaryKind === "screen" ? "screen" : state.businessFlow ? "app_process" : void 0;
return buildGraphIdentityTitle(
{
title: state.title,
fileType: summaryType,
path: state.filePath,
id: findSummaryMetadataValue(state, ["id", "model id", "model_id"])
},
state.title,
summaryType
);
}
function buildWeaveMapGraphTitle(t, summary) {
const target = summary.modelId || summary.modelLabel || summary.modelPath;
return t("relationship.weaveMap.title") + " \u2014 " + target;
}
function ensureGraphIdentityTitle(root, title) {
const existingTitle = root.querySelector(
".model-weave-mermaid-title, .model-weave-graph-identity-title"
);
const titleElement = existingTitle ?? root.ownerDocument.createElement("h2");
titleElement.textContent = title;
titleElement.title = title;
titleElement.addClass("model-weave-graph-identity-title");
if (!existingTitle) {
root.insertBefore(titleElement, root.firstChild);
}
}
// src/main.ts
var LEGACY_PREVIEW_VIEW_TYPES = [

View file

@ -103,6 +103,7 @@ export function createMermaidShell(
if (options.title) {
const title = activeDocument.createElement("h2");
title.textContent = options.title;
title.title = options.title;
title.addClass("model-weave-mermaid-title");
root.appendChild(title);
}

View file

@ -1221,6 +1221,7 @@ export class ModelingPreviewView extends ItemView {
...getClassDetailLabels(this.t),
showMermaidRenderDebug: this.viewerPreferences.showMermaidRenderDebug
});
ensureGraphIdentityTitle(mermaidRoot, buildGraphIdentityTitle(state.model));
this.appendRendererSelection(mermaidRoot, state.rendererSelection);
this.appendViewerToolbarControls(mermaidRoot);
shell.topPane.appendChild(mermaidRoot);
@ -1246,6 +1247,7 @@ export class ModelingPreviewView extends ItemView {
shell.bottomPane.appendChild(relatedList);
}
ensureGraphIdentityTitle(contextRoot, buildGraphIdentityTitle(state.model));
this.appendRendererSelection(contextRoot, state.rendererSelection);
this.appendViewerToolbarControls(contextRoot);
shell.topPane.appendChild(contextRoot);
@ -1284,7 +1286,8 @@ export class ModelingPreviewView extends ItemView {
shell.topPane,
state.model.domains,
shell.bottomPane,
state.colorScheme
state.colorScheme,
buildGraphIdentityTitle(state.model)
);
this.renderDomainTree(shell.bottomPane, buildDomainTree(state.model.domains));
@ -1316,7 +1319,8 @@ export class ModelingPreviewView extends ItemView {
shell.topPane,
state.resolved.domains,
shell.bottomPane,
state.colorScheme
state.colorScheme,
buildGraphIdentityTitle(state.resolved.diagram)
);
this.renderDomainTree(shell.bottomPane, buildDomainTree(state.resolved.domains));
@ -1947,7 +1951,8 @@ export class ModelingPreviewView extends ItemView {
container: HTMLElement,
domains: DomainEntry[],
sourcePanelContainer?: HTMLElement,
colorScheme?: ResolvedColorScheme
colorScheme?: ResolvedColorScheme,
graphTitle?: string
): void {
if (domains.length === 0) {
const section = this.createCollapsibleSection(
@ -1965,7 +1970,7 @@ export class ModelingPreviewView extends ItemView {
this.renderDomainDiagramModeSelector(container);
const diagramRoot = renderDomainsMermaidDiagram(domains, {
title: this.getDomainDiagramModeLabel(this.domainsDiagramMode),
title: graphTitle ?? this.getDomainDiagramModeLabel(this.domainsDiagramMode),
mode: this.domainsDiagramMode,
renderFailedMessage: this.t("domains.preview.diagramRenderFailed"),
fitVerticalAlign: "top",
@ -1979,6 +1984,7 @@ export class ModelingPreviewView extends ItemView {
showMermaidRenderDebug: this.viewerPreferences.showMermaidRenderDebug,
colorScheme
});
ensureGraphIdentityTitle(diagramRoot, graphTitle ?? this.getDomainDiagramModeLabel(this.domainsDiagramMode));
this.appendViewerToolbarControls(diagramRoot);
container.appendChild(diagramRoot);
}
@ -2047,6 +2053,7 @@ export class ModelingPreviewView extends ItemView {
onOpenLinkedFile: state.onOpenLinkedFile
}
);
ensureGraphIdentityTitle(screenRoot, buildSummaryGraphTitle(state));
this.appendViewerToolbarControls(screenRoot);
shell.topPane.appendChild(screenRoot);
} else if (state.businessFlow) {
@ -2067,6 +2074,7 @@ export class ModelingPreviewView extends ItemView {
state.filePath
)
});
ensureGraphIdentityTitle(businessFlowRoot, buildSummaryGraphTitle(state));
this.appendViewerToolbarControls(businessFlowRoot);
shell.topPane.appendChild(businessFlowRoot);
this.renderSummaryDetails(shell.bottomPane, state, {
@ -2191,6 +2199,7 @@ export class ModelingPreviewView extends ItemView {
state.filePath
)
});
ensureGraphIdentityTitle(businessFlowRoot, buildSummaryGraphTitle(state));
this.appendViewerToolbarControls(businessFlowRoot);
section.appendChild(businessFlowRoot);
}
@ -2821,7 +2830,7 @@ export class ModelingPreviewView extends ItemView {
renderContainer.empty();
const shell = createMermaidShell({
className: "model-weave-impact-weave-map-render",
title: this.t("relationship.weaveMap.title"),
title: buildWeaveMapGraphTitle(this.t, summary),
...getGraphExportLabels(this.t),
onExportPng: () => this.exportWeaveMapAsPng(renderContainer, summary.modelPath),
onExportAndOpenPng: () =>
@ -3225,6 +3234,7 @@ export class ModelingPreviewView extends ItemView {
...getClassDetailLabels(this.t),
showMermaidRenderDebug: this.viewerPreferences.showMermaidRenderDebug
});
ensureGraphIdentityTitle(diagramRoot, buildGraphIdentityTitle(state.model));
this.appendViewerToolbarControls(diagramRoot);
this.moveDetailSections(diagramRoot, shell.bottomPane);
shell.topPane.appendChild(diagramRoot);
@ -3260,6 +3270,7 @@ export class ModelingPreviewView extends ItemView {
...getClassDetailLabels(this.t),
showMermaidRenderDebug: this.viewerPreferences.showMermaidRenderDebug
});
ensureGraphIdentityTitle(diagramRoot, buildGraphIdentityTitle(state.diagram.diagram));
this.appendRendererSelection(diagramRoot, state.rendererSelection);
this.appendViewerToolbarControls(diagramRoot);
this.moveDetailSections(diagramRoot, lowerSlots.details);
@ -4606,3 +4617,121 @@ function toModelWeaveUiLanguage(language: string | undefined): ModelWeaveUiLangu
}
return "auto";
}
function asModelRecord(value: unknown): Record<string, unknown> | null {
return typeof value === "object" && value !== null
? value as Record<string, unknown>
: null;
}
function getStringField(value: unknown, key: string): string | undefined {
const record = asModelRecord(value);
const field = record?.[key];
return typeof field === "string" && field.trim().length > 0
? field.trim()
: undefined;
}
function getFrontmatterString(value: unknown, key: string): string | undefined {
const frontmatter = asModelRecord(asModelRecord(value)?.frontmatter);
const field = frontmatter?.[key];
return typeof field === "string" && field.trim().length > 0
? field.trim()
: undefined;
}
function getModelDisplayName(value: unknown): string | undefined {
return (
getStringField(value, "name") ??
getStringField(value, "title") ??
getStringField(value, "logicalName") ??
getStringField(value, "physicalName") ??
getFrontmatterString(value, "name") ??
getFrontmatterString(value, "title") ??
getModelId(value)
);
}
function getModelId(value: unknown): string | undefined {
return (
getStringField(value, "id") ??
getFrontmatterString(value, "id")
);
}
function getModelType(value: unknown): string | undefined {
return (
getStringField(value, "fileType") ??
getFrontmatterString(value, "type") ??
getStringField(value, "schema") ??
getFrontmatterString(value, "schema")
);
}
function buildGraphIdentityTitle(
value: unknown,
fallbackName?: string,
fallbackType?: string
): string {
const modelId = getModelId(value);
const modelType = getModelType(value) ?? fallbackType;
const displayName = getModelDisplayName(value) ?? modelId ?? fallbackName ?? "Model";
const suffixParts = [
modelType,
modelId && modelId !== displayName ? modelId : undefined
].filter((part): part is string => Boolean(part));
return suffixParts.length > 0
? displayName + " (" + suffixParts.join(" / ") + ")"
: displayName;
}
function findSummaryMetadataValue(
state: Extract<PreviewState, { mode: "summary" }>,
keys: string[]
): string | undefined {
const normalizedKeys = new Set(keys.map((key) => key.toLowerCase()));
for (const entry of state.metadata) {
if (normalizedKeys.has(entry.label.toLowerCase())) {
return entry.value;
}
}
return undefined;
}
function buildSummaryGraphTitle(state: Extract<PreviewState, { mode: "summary" }>): string {
const summaryType = state.summaryKind === "screen"
? "screen"
: state.businessFlow
? "app_process"
: undefined;
return buildGraphIdentityTitle(
{
title: state.title,
fileType: summaryType,
path: state.filePath,
id: findSummaryMetadataValue(state, ["id", "model id", "model_id"])
},
state.title,
summaryType
);
}
function buildWeaveMapGraphTitle(t: ModelWeaveTranslator, summary: ImpactSummary): string {
const target = summary.modelId || summary.modelLabel || summary.modelPath;
return t("relationship.weaveMap.title") + " — " + target;
}
function ensureGraphIdentityTitle(root: HTMLElement, title: string): void {
const existingTitle = root.querySelector<HTMLElement>(
".model-weave-mermaid-title, .model-weave-graph-identity-title"
);
const titleElement = existingTitle ?? root.ownerDocument.createElement("h2");
titleElement.textContent = title;
titleElement.title = title;
titleElement.addClass("model-weave-graph-identity-title");
if (!existingTitle) {
root.insertBefore(titleElement, root.firstChild);
}
}

View file

@ -246,8 +246,17 @@
min-height: 0;
}
.model-weave-mermaid-title {
.model-weave-mermaid-title,
.model-weave-graph-identity-title {
flex: 0 0 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin: 0 0 6px;
color: var(--text-normal);
font-size: var(--font-ui-medium);
font-weight: 600;
}
.model-weave-graph-canvas {