Skip to content

Commit

Permalink
feat: multi-select v1
Browse files Browse the repository at this point in the history
  • Loading branch information
danielo515 committed Sep 15, 2023
1 parent 9688ffc commit e3ee492
Show file tree
Hide file tree
Showing 3 changed files with 321 additions and 312 deletions.
361 changes: 182 additions & 179 deletions src/FormModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,187 +11,190 @@ import { SvelteComponent } from "svelte";
export type SubmitFn = (formResult: FormResult) => void;

export class FormModal extends Modal {
modalDefinition: FormDefinition;
formResult: ModalFormData;
svelteComponents: SvelteComponent[] = [];
constructor(app: App, modalDefinition: FormDefinition, private onSubmit: SubmitFn) {
super(app);
this.modalDefinition = modalDefinition;
this.formResult = {};
}
modalDefinition: FormDefinition;
formResult: ModalFormData;
svelteComponents: SvelteComponent[] = [];
constructor(app: App, modalDefinition: FormDefinition, private onSubmit: SubmitFn) {
super(app);
this.modalDefinition = modalDefinition;
this.formResult = {};
}

onOpen() {
const { contentEl } = this;
contentEl.createEl("h1", { text: this.modalDefinition.title });
this.modalDefinition.fields.forEach((definition) => {
const fieldBase = new Setting(contentEl)
.setName(definition.label || definition.name)
.setDesc(definition.description);
// This intermediary constants are necessary so typescript can narrow down the proper types.
// without them, you will have to use the whole access path (definition.input.folder),
// and it is no specific enough when you use it in a switch statement.
const fieldInput = definition.input;
const type = fieldInput.type;
switch (type) {
case "text":
return fieldBase.addText((text) =>
text.onChange(async (value) => {
this.formResult[definition.name] = value;
})
);
case "number":
return fieldBase.addText((text) => {
text.inputEl.type = "number";
text.onChange(async (value) => {
if (value !== "") {
this.formResult[definition.name] =
Number(value) + "";
}
});
});
case "date":
return fieldBase.addText((text) => {
text.inputEl.type = "date";
text.onChange(async (value) => {
this.formResult[definition.name] = value;
});
});
case "time":
return fieldBase.addText((text) => {
text.inputEl.type = "time";
text.onChange(async (value) => {
this.formResult[definition.name] = value;
});
});
case "datetime":
return fieldBase.addText((text) => {
text.inputEl.type = "datetime-local";
text.onChange(async (value) => {
this.formResult[definition.name] = value;
});
});
case "toggle":
return fieldBase.addToggle((toggle) => {
toggle.setValue(false);
this.formResult[definition.name] = false;
return toggle.onChange(async (value) => {
this.formResult[definition.name] = value;
});
}
);
case "note":
return fieldBase.addText((element) => {
new FileSuggest(this.app, element.inputEl, {
renderSuggestion(file) {
return file.basename;
},
selectSuggestion(file) {
return file.basename;
},
}, fieldInput.folder);
element.onChange(async (value) => {
this.formResult[definition.name] = value;
});
});
case "slider":
return fieldBase.addSlider((slider) => {
slider.setLimits(fieldInput.min, fieldInput.max, 1);
slider.setDynamicTooltip();
slider.setValue(fieldInput.min);
slider.onChange(async (value) => {
this.formResult[definition.name] = value;
});
});
case 'multiselect':
{
this.formResult[definition.name] = this.formResult[definition.name] || []
this.svelteComponents.push(new MultiSelect({
target: fieldBase.controlEl,
props: {
selectedVales: this.formResult[definition.name] as string[],
availableOptions: ['a', 'b', 'c'],
setting: fieldBase,
}
}))
return;
}
case "dataview":
{
const query = fieldInput.query;
return fieldBase.addText((element) => {
new DataviewSuggest(element.inputEl, query, this.app);
element.onChange(async (value) => {
this.formResult[definition.name] = value;
});
});
}
case "select":
{
const source = fieldInput.source;
switch (source) {
case "fixed":
return fieldBase.addDropdown((element) => {
const options = fieldInput.options.reduce(
(
acc: Record<string, string>,
option
) => {
acc[option.value] = option.label;
return acc;
},
{}
);
element.addOptions(options);
element.onChange(async (value) => {
this.formResult[definition.name] =
value;
});
});
onOpen() {
const { contentEl } = this;
contentEl.createEl("h1", { text: this.modalDefinition.title });
this.modalDefinition.fields.forEach((definition) => {
const fieldBase = new Setting(contentEl)
.setName(definition.label || definition.name)
.setDesc(definition.description);
// This intermediary constants are necessary so typescript can narrow down the proper types.
// without them, you will have to use the whole access path (definition.input.folder),
// and it is no specific enough when you use it in a switch statement.
const fieldInput = definition.input;
const type = fieldInput.type;
switch (type) {
case "text":
return fieldBase.addText((text) =>
text.onChange(async (value) => {
this.formResult[definition.name] = value;
})
);
case "number":
return fieldBase.addText((text) => {
text.inputEl.type = "number";
text.onChange(async (value) => {
if (value !== "") {
this.formResult[definition.name] =
Number(value) + "";
}
});
});
case "date":
return fieldBase.addText((text) => {
text.inputEl.type = "date";
text.onChange(async (value) => {
this.formResult[definition.name] = value;
});
});
case "time":
return fieldBase.addText((text) => {
text.inputEl.type = "time";
text.onChange(async (value) => {
this.formResult[definition.name] = value;
});
});
case "datetime":
return fieldBase.addText((text) => {
text.inputEl.type = "datetime-local";
text.onChange(async (value) => {
this.formResult[definition.name] = value;
});
});
case "toggle":
return fieldBase.addToggle((toggle) => {
toggle.setValue(false);
this.formResult[definition.name] = false;
return toggle.onChange(async (value) => {
this.formResult[definition.name] = value;
});
}
);
case "note":
return fieldBase.addText((element) => {
new FileSuggest(this.app, element.inputEl, {
renderSuggestion(file) {
return file.basename;
},
selectSuggestion(file) {
return file.basename;
},
}, fieldInput.folder);
element.onChange(async (value) => {
this.formResult[definition.name] = value;
});
});
case "slider":
return fieldBase.addSlider((slider) => {
slider.setLimits(fieldInput.min, fieldInput.max, 1);
slider.setDynamicTooltip();
slider.setValue(fieldInput.min);
slider.onChange(async (value) => {
this.formResult[definition.name] = value;
});
});
case 'multiselect':
{
this.formResult[definition.name] = this.formResult[definition.name] || []
const options = fieldInput.source == 'fixed'
? fieldInput.options
: get_tfiles_from_folder(fieldInput.folder, this.app).map(file => file.basename);
this.svelteComponents.push(new MultiSelect({
target: fieldBase.controlEl,
props: {
selectedVales: this.formResult[definition.name] as string[],
availableOptions: options,
setting: fieldBase,
}
}))
return;
}
case "dataview":
{
const query = fieldInput.query;
return fieldBase.addText((element) => {
new DataviewSuggest(element.inputEl, query, this.app);
element.onChange(async (value) => {
this.formResult[definition.name] = value;
});
});
}
case "select":
{
const source = fieldInput.source;
switch (source) {
case "fixed":
return fieldBase.addDropdown((element) => {
const options = fieldInput.options.reduce(
(
acc: Record<string, string>,
option
) => {
acc[option.value] = option.label;
return acc;
},
{}
);
element.addOptions(options);
element.onChange(async (value) => {
this.formResult[definition.name] =
value;
});
});

case "notes":
return fieldBase.addDropdown((element) => {
const files = get_tfiles_from_folder(fieldInput.folder, this.app);
const options = files.reduce(
(
acc: Record<string, string>,
option
) => {
acc[option.basename] =
option.basename;
return acc;
},
{}
);
element.addOptions(options);
element.onChange(async (value) => {
this.formResult[definition.name] =
value;
});
});
default:
exhaustiveGuard(source);
}
}
break;
default:
return exhaustiveGuard(type);
case "notes":
return fieldBase.addDropdown((element) => {
const files = get_tfiles_from_folder(fieldInput.folder, this.app);
const options = files.reduce(
(
acc: Record<string, string>,
option
) => {
acc[option.basename] =
option.basename;
return acc;
},
{}
);
element.addOptions(options);
element.onChange(async (value) => {
this.formResult[definition.name] =
value;
});
});
default:
exhaustiveGuard(source);
}
});
new Setting(contentEl).addButton((btn) =>
btn
.setButtonText("Submit")
.setCta()
.onClick(() => {
this.onSubmit(new FormResult(this.formResult, "ok"));
this.close();
})
);
}
}
break;
default:
return exhaustiveGuard(type);
}
});
new Setting(contentEl).addButton((btn) =>
btn
.setButtonText("Submit")
.setCta()
.onClick(() => {
this.onSubmit(new FormResult(this.formResult, "ok"));
this.close();
})
);
}

onClose() {
const { contentEl } = this;
this.svelteComponents.forEach(component => component.$destroy())
contentEl.empty();
this.formResult = {};
}
onClose() {
const { contentEl } = this;
this.svelteComponents.forEach(component => component.$destroy())
contentEl.empty();
this.formResult = {};
}
}
Loading

0 comments on commit e3ee492

Please sign in to comment.