mindnet_obsidian/src/ui/LinkPromptModal.ts
Lars 58b6ffffed
Some checks are pending
Node.js build / build (20.x) (push) Waiting to run
Node.js build / build (22.x) (push) Waiting to run
Add semantic mapping builder functionality and settings
- Introduced a new command to build semantic mapping blocks by section, including user prompts for overwriting existing mappings.
- Enhanced settings interface with options for mapping wrapper type, title, folded state, default edge type, unassigned handling, and overwrite permissions.
- Implemented virtualization in the EntityPickerModal for improved performance when displaying large sets of entries, including scroll handling and item rendering optimizations.
- Updated UI components to reflect new settings and functionalities, ensuring a cohesive user experience.
2026-01-17 07:27:11 +01:00

184 lines
5.2 KiB
TypeScript

/**
* Modal for prompting edge type assignment for a single link.
*/
import { Modal } from "obsidian";
import type { LinkWorkItem } from "../mapping/worklistBuilder";
import type { EdgeVocabulary } from "../vocab/types";
import type { GraphSchema } from "../mapping/graphSchema";
import { EdgeTypeChooserModal, type EdgeTypeChoice } from "./EdgeTypeChooserModal";
import { computeEdgeSuggestions } from "../mapping/schemaHelper";
export type LinkPromptDecision =
| { action: "keep"; edgeType: string }
| { action: "change"; edgeType: string; alias?: string }
| { action: "skip" };
export class LinkPromptModal extends Modal {
private item: LinkWorkItem;
private vocabulary: EdgeVocabulary;
private sourceType: string | null;
private graphSchema: GraphSchema | null;
private result: LinkPromptDecision | null = null;
private resolve: ((result: LinkPromptDecision) => void) | null = null;
constructor(
app: any,
item: LinkWorkItem,
vocabulary: EdgeVocabulary,
sourceType: string | null,
graphSchema: GraphSchema | null = null
) {
super(app);
this.item = item;
this.vocabulary = vocabulary;
this.sourceType = sourceType;
this.graphSchema = graphSchema;
}
onOpen(): void {
const { contentEl } = this;
contentEl.empty();
contentEl.addClass("link-prompt-modal");
// Link info
const linkInfo = contentEl.createEl("div", { cls: "link-info" });
linkInfo.createEl("h2", { text: `Link: [[${this.item.link}]]` });
if (this.item.targetType) {
linkInfo.createEl("p", { text: `Target type: ${this.item.targetType}` });
}
// Current mapping (if exists)
if (this.item.currentType) {
const currentInfo = linkInfo.createEl("p", { cls: "current-mapping" });
currentInfo.textContent = `Current edge type: ${this.item.currentType}`;
// Check if current type is prohibited
if (this.graphSchema) {
const suggestions = computeEdgeSuggestions(
this.vocabulary,
this.sourceType,
this.item.targetType,
this.graphSchema
);
if (suggestions.prohibited.includes(this.item.currentType)) {
const warning = linkInfo.createEl("span", {
text: " ⚠️ Prohibited",
cls: "prohibited-warning",
});
warning.style.color = "var(--text-error)";
warning.style.fontWeight = "bold";
}
}
}
// Action buttons
const buttonContainer = contentEl.createEl("div", { cls: "action-buttons" });
buttonContainer.style.display = "flex";
buttonContainer.style.flexDirection = "column";
buttonContainer.style.gap = "0.5em";
buttonContainer.style.marginTop = "1em";
if (this.item.currentType) {
// Keep current
const keepBtn = buttonContainer.createEl("button", {
text: `✅ Keep (${this.item.currentType})`,
cls: "mod-cta",
});
keepBtn.onclick = () => {
this.result = { action: "keep", edgeType: this.item.currentType! };
this.close();
};
// Change type
const changeBtn = buttonContainer.createEl("button", {
text: "Change type...",
});
changeBtn.onclick = async () => {
const chooser = new EdgeTypeChooserModal(
this.app,
this.vocabulary,
this.sourceType,
this.item.targetType,
this.graphSchema
);
const choice = await chooser.show();
if (choice) {
this.result = {
action: "change",
edgeType: choice.edgeType,
alias: choice.alias,
};
this.close();
}
};
// Skip
const skipBtn = buttonContainer.createEl("button", {
text: "Skip link",
});
skipBtn.onclick = () => {
this.result = { action: "skip" };
this.close();
};
} else {
// No current mapping
// Skip
const skipBtn = buttonContainer.createEl("button", {
text: "⏩ Skip link",
});
skipBtn.onclick = () => {
this.result = { action: "skip" };
this.close();
};
// Choose type
const chooseBtn = buttonContainer.createEl("button", {
text: "Choose type...",
cls: "mod-cta",
});
chooseBtn.onclick = async () => {
const chooser = new EdgeTypeChooserModal(
this.app,
this.vocabulary,
this.sourceType,
this.item.targetType,
this.graphSchema
);
const choice = await chooser.show();
if (choice) {
this.result = {
action: "change",
edgeType: choice.edgeType,
alias: choice.alias,
};
this.close();
}
};
}
}
onClose(): void {
const { contentEl } = this;
contentEl.empty();
if (this.resolve && this.result) {
this.resolve(this.result);
} else if (this.resolve) {
// User closed without decision, treat as skip
this.resolve({ action: "skip" });
}
}
/**
* Show modal and return promise that resolves with user's decision.
*/
async show(): Promise<LinkPromptDecision> {
return new Promise((resolve) => {
this.resolve = resolve;
this.open();
});
}
}