Various AT Protocol integrations with obsidian
1import { Modal, Notice } from "obsidian";
2import type AtmospherePlugin from "../main";
3import { createSembleCollection, createMarginCollection } from "../lib";
4
5type SourceName = "semble" | "margin";
6
7export class CreateCollectionModal extends Modal {
8 plugin: AtmospherePlugin;
9 availableSources: SourceName[];
10 selectedSource: SourceName;
11 onSuccess?: () => void;
12
13 constructor(plugin: AtmospherePlugin, availableSources: SourceName[], onSuccess?: () => void) {
14 super(plugin.app);
15 this.plugin = plugin;
16 this.availableSources = availableSources;
17 this.selectedSource = availableSources[0]!;
18 this.onSuccess = onSuccess;
19 }
20
21 onOpen() { this.render(); }
22
23 private render() {
24 const { contentEl } = this;
25 contentEl.empty();
26 contentEl.addClass("atmosphere-modal");
27 contentEl.createEl("h2", { text: "New collection" });
28
29 if (!this.plugin.client) {
30 contentEl.createEl("p", { text: "Not connected." });
31 return;
32 }
33
34 if (this.availableSources.length > 1) {
35 const toggleRow = contentEl.createEl("div", { cls: "atmosphere-source-toggle-row" });
36 for (const source of this.availableSources) {
37 const btn = toggleRow.createEl("button", {
38 text: source.charAt(0).toUpperCase() + source.slice(1),
39 cls: "atmosphere-source-toggle-btn" + (this.selectedSource === source ? " is-active" : ""),
40 type: "button",
41 });
42 btn.addEventListener("click", () => { this.selectedSource = source; this.render(); });
43 }
44 }
45
46 const form = contentEl.createEl("form", { cls: "atmosphere-form" });
47
48 const nameGroup = form.createEl("div", { cls: "atmosphere-form-group" });
49 nameGroup.createEl("label", { text: "Name", attr: { for: "collection-name" } });
50 const nameInput = nameGroup.createEl("input", {
51 type: "text",
52 cls: "atmosphere-input",
53 attr: { id: "collection-name", placeholder: "Collection name", required: "true" },
54 });
55
56 let iconInput: HTMLInputElement | null = null;
57 if (this.selectedSource === "margin") {
58 const iconGroup = form.createEl("div", { cls: "atmosphere-form-group" });
59 iconGroup.createEl("label", { text: "Icon (optional)", attr: { for: "collection-icon" } });
60 iconInput = iconGroup.createEl("input", {
61 type: "text",
62 cls: "atmosphere-input",
63 attr: { id: "collection-icon" },
64 });
65 }
66
67 const descGroup = form.createEl("div", { cls: "atmosphere-form-group" });
68 descGroup.createEl("label", { text: "Description", attr: { for: "collection-desc" } });
69 const descInput = descGroup.createEl("textarea", {
70 cls: "atmosphere-textarea",
71 attr: { id: "collection-desc", placeholder: "Optional description", rows: "3" },
72 });
73
74 const actions = form.createEl("div", { cls: "atmosphere-modal-actions" });
75 actions.createEl("button", {
76 text: "Cancel",
77 cls: "atmosphere-btn atmosphere-btn-secondary",
78 type: "button",
79 }).addEventListener("click", () => this.close());
80
81 const createBtn = actions.createEl("button", {
82 text: "Create",
83 cls: "atmosphere-btn atmosphere-btn-primary",
84 type: "submit",
85 });
86
87 form.addEventListener("submit", (e) => {
88 e.preventDefault();
89 void this.handleSubmit(nameInput, iconInput, descInput, createBtn);
90 });
91
92 nameInput.focus();
93 }
94
95 private async handleSubmit(
96 nameInput: HTMLInputElement,
97 iconInput: HTMLInputElement | null,
98 descInput: HTMLTextAreaElement,
99 createBtn: HTMLButtonElement
100 ) {
101 const name = nameInput.value.trim();
102 if (!name) {
103 new Notice("Please enter a collection name");
104 return;
105 }
106
107 createBtn.disabled = true;
108 createBtn.textContent = "Creating...";
109
110 try {
111 if (this.selectedSource === "margin") {
112 await createMarginCollection(
113 this.plugin.client,
114 this.plugin.settings.did!,
115 name,
116 descInput.value.trim() || undefined,
117 iconInput?.value.trim() || undefined
118 );
119 } else {
120 await createSembleCollection(
121 this.plugin.client,
122 this.plugin.settings.did!,
123 name,
124 descInput.value.trim()
125 );
126 }
127 new Notice(`Created collection "${name}"`);
128 this.close();
129 this.onSuccess?.();
130 } catch (err) {
131 const message = err instanceof Error ? err.message : String(err);
132 new Notice(`Failed to create collection: ${message}`);
133 createBtn.disabled = false;
134 createBtn.textContent = "Create";
135 }
136 }
137
138 onClose() { this.contentEl.empty(); }
139}