mindnet_obsidian/src/ui/ProfileSelectionModal.ts

291 lines
9.2 KiB
TypeScript
Raw Normal View History

import { App, Modal, Notice, Setting, TFile } from "obsidian";
import type { InterviewConfig, InterviewProfile } from "../interview/types";
2026-01-17 08:01:04 +01:00
import { FolderTreeModal, type FolderTreeResult } from "./FolderTreeModal";
import { NoteIndex } from "../entityPicker/noteIndex";
export interface ProfileSelectionResult {
profile: InterviewProfile;
title: string;
2026-01-17 08:01:04 +01:00
folderPath: string; // Vault-relative path, empty string for root
}
export class ProfileSelectionModal extends Modal {
result: ProfileSelectionResult | null = null;
onSubmit: (result: ProfileSelectionResult) => void;
config: InterviewConfig;
initialTitle: string;
2026-01-17 08:01:04 +01:00
defaultFolderPath: string;
private noteIndex: NoteIndex;
constructor(
app: App,
config: InterviewConfig,
onSubmit: (result: ProfileSelectionResult) => void,
2026-01-17 08:01:04 +01:00
initialTitle: string = "",
defaultFolderPath: string = ""
) {
super(app);
this.config = config;
this.onSubmit = onSubmit;
this.initialTitle = initialTitle;
2026-01-17 08:01:04 +01:00
this.defaultFolderPath = defaultFolderPath;
this.noteIndex = new NoteIndex(app);
}
onOpen(): void {
const { contentEl } = this;
contentEl.empty();
contentEl.createEl("h2", { text: "Create Note from Profile" });
// Group profiles by group
const grouped = new Map<string, InterviewProfile[]>();
const ungrouped: InterviewProfile[] = [];
for (const profile of this.config.profiles) {
if (profile.group) {
if (!grouped.has(profile.group)) {
grouped.set(profile.group, []);
}
const groupProfiles = grouped.get(profile.group);
if (groupProfiles) {
groupProfiles.push(profile);
}
} else {
ungrouped.push(profile);
}
}
let selectedProfile: InterviewProfile | null = null;
let titleInput: string = this.initialTitle;
2026-01-17 08:01:04 +01:00
let folderPath: string = this.defaultFolderPath;
// Title input
const titleSetting = new Setting(contentEl)
.setName("Title")
.setDesc("Note title")
.addText((text) => {
text.setValue(this.initialTitle).onChange((value) => {
titleInput = value;
});
text.inputEl.focus();
text.inputEl.select();
});
2026-01-17 08:01:04 +01:00
// Folder selection
const folderDisplay = contentEl.createEl("div", { cls: "folder-display" });
folderDisplay.style.marginBottom = "1em";
folderDisplay.style.padding = "0.5em";
folderDisplay.style.backgroundColor = "var(--background-secondary)";
folderDisplay.style.borderRadius = "4px";
const folderLabel = folderDisplay.createEl("span", {
text: "Folder: ",
cls: "folder-label",
});
const folderPathSpan = folderDisplay.createEl("span", {
text: folderPath || "(root)",
cls: "folder-path",
});
folderPathSpan.style.fontWeight = "bold";
const folderButtonContainer = folderDisplay.createEl("div");
folderButtonContainer.style.display = "flex";
folderButtonContainer.style.gap = "0.5em";
folderButtonContainer.style.marginTop = "0.5em";
const pickFolderBtn = folderButtonContainer.createEl("button", {
text: "Pick…",
});
pickFolderBtn.onclick = async () => {
const folderModal = new FolderTreeModal(this.app, this.noteIndex, folderPath);
const result = await folderModal.show();
if (result) {
folderPath = result.folderPath;
folderPathSpan.textContent = folderPath || "(root)";
}
};
const newFolderBtn = folderButtonContainer.createEl("button", {
text: "New folder…",
});
newFolderBtn.onclick = async () => {
const folderName = await this.promptForFolderName();
if (folderName && folderName.trim()) {
const newFolderPath = folderPath
? `${folderPath}/${folderName.trim()}`
: folderName.trim();
try {
await this.app.vault.createFolder(newFolderPath);
folderPath = newFolderPath;
folderPathSpan.textContent = folderPath || "(root)";
new Notice(`Folder created: ${newFolderPath}`);
// Refresh note index to include new folder
this.noteIndex.refresh();
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
new Notice(`Failed to create folder: ${msg}`);
}
}
};
// Profile selection (grouped)
for (const [groupName, profiles] of grouped.entries()) {
contentEl.createEl("h3", { text: groupName });
for (const profile of profiles) {
const setting = new Setting(contentEl)
.setName(profile.label)
.setDesc(`Type: ${profile.note_type}`)
.addButton((button) => {
button.setButtonText("Select").onClick(() => {
// Clear previous selection
contentEl.querySelectorAll(".profile-selected").forEach((el) => {
if (el instanceof HTMLElement) {
el.removeClass("profile-selected");
}
});
// Mark as selected
setting.settingEl.addClass("profile-selected");
selectedProfile = profile;
2026-01-17 08:01:04 +01:00
// Update folder path from profile defaults
const profileFolder = (profile.defaults?.folder as string) || this.defaultFolderPath || "";
folderPath = profileFolder;
folderPathSpan.textContent = folderPath || "(root)";
// Log selection
console.log("Profile selected", {
key: profile.key,
label: profile.label,
noteType: profile.note_type,
stepCount: profile.steps?.length || 0,
2026-01-17 08:01:04 +01:00
defaultFolder: profileFolder,
});
});
});
}
}
// Ungrouped profiles
if (ungrouped.length > 0) {
if (grouped.size > 0) {
contentEl.createEl("h3", { text: "Other" });
}
for (const profile of ungrouped) {
const setting = new Setting(contentEl)
.setName(profile.label)
.setDesc(`Type: ${profile.note_type}`)
.addButton((button) => {
button.setButtonText("Select").onClick(() => {
// Clear previous selection
contentEl.querySelectorAll(".profile-selected").forEach((el) => {
if (el instanceof HTMLElement) {
el.removeClass("profile-selected");
}
});
// Mark as selected
setting.settingEl.addClass("profile-selected");
selectedProfile = profile;
2026-01-17 08:01:04 +01:00
// Update folder path from profile defaults
const profileFolder = (profile.defaults?.folder as string) || this.defaultFolderPath || "";
folderPath = profileFolder;
folderPathSpan.textContent = folderPath || "(root)";
// Log selection
console.log("Profile selected", {
key: profile.key,
label: profile.label,
noteType: profile.note_type,
stepCount: profile.steps?.length || 0,
2026-01-17 08:01:04 +01:00
defaultFolder: profileFolder,
});
});
});
}
}
// Submit button
new Setting(contentEl).addButton((button) => {
button
.setButtonText("Create Note")
.setCta()
.onClick(() => {
if (!selectedProfile) {
new Notice("Please select a profile");
return;
}
if (!titleInput.trim()) {
new Notice("Please enter a title");
return;
}
this.result = {
profile: selectedProfile,
title: titleInput.trim(),
2026-01-17 08:01:04 +01:00
folderPath: folderPath.trim(),
};
this.onSubmit(this.result);
this.close();
});
});
}
2026-01-17 08:01:04 +01:00
private async promptForFolderName(): Promise<string | null> {
return new Promise((resolve) => {
const modal = new Modal(this.app);
modal.titleEl.textContent = "New folder name";
const inputContainer = modal.contentEl.createEl("div");
const input = inputContainer.createEl("input", {
type: "text",
placeholder: "Folder name",
});
input.style.width = "100%";
input.style.marginBottom = "1em";
const buttonContainer = modal.contentEl.createEl("div");
buttonContainer.style.display = "flex";
buttonContainer.style.gap = "0.5em";
buttonContainer.style.justifyContent = "flex-end";
const cancelBtn = buttonContainer.createEl("button", { text: "Cancel" });
cancelBtn.onclick = () => {
modal.close();
resolve(null);
};
const createBtn = buttonContainer.createEl("button", {
text: "Create",
cls: "mod-cta",
});
createBtn.onclick = () => {
const value = input.value.trim();
modal.close();
resolve(value || null);
};
input.onkeydown = (evt) => {
if (evt.key === "Enter") {
createBtn.click();
} else if (evt.key === "Escape") {
cancelBtn.click();
}
};
modal.open();
input.focus();
input.select();
});
}
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}
}