Files
obsidian-excalidraw-plugin/src/shared/Dialogs/EmbeddableSettings.ts
zsviczian ec575c307a
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
updated type references matching Excalidraw component updates
2025-04-27 11:05:38 +02:00

238 lines
8.3 KiB
TypeScript

import { ExcalidrawEmbeddableElement } from "@zsviczian/excalidraw/types/element/src/types";
import { Mutable } from "@zsviczian/excalidraw/types/common/src/utility-types";
import { Modal, Notice, Setting, TFile, ToggleComponent } from "obsidian";
import { getEA } from "src/core";
import { ExcalidrawAutomate } from "src/shared/ExcalidrawAutomate";
import ExcalidrawView from "src/view/ExcalidrawView";
import { t } from "src/lang/helpers";
import ExcalidrawPlugin from "src/core/main";
import { getNewUniqueFilepath, getPathWithoutExtension, splitFolderAndFilename } from "src/utils/fileUtils";
import { addAppendUpdateCustomData, fragWithHTML } from "src/utils/utils";
import { getYouTubeStartAt, isValidYouTubeStart, isYouTube, updateYouTubeStartTime } from "src/utils/YoutTubeUtils";
import { EmbeddalbeMDFileCustomDataSettingsComponent } from "./EmbeddableMDFileCustomDataSettingsComponent";
import { isWinCTRLorMacCMD } from "src/utils/modifierkeyHelper";
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/excalidraw/types";
import { CaptureUpdateAction } from "src/constants/constants";
export type EmbeddableMDCustomProps = {
useObsidianDefaults: boolean;
backgroundMatchCanvas: boolean;
backgroundMatchElement: boolean;
backgroundColor: string;
backgroundOpacity: number;
borderMatchElement: boolean;
borderColor: string;
borderOpacity: number;
filenameVisible: boolean;
}
export class EmbeddableSettings extends Modal {
private ea: ExcalidrawAutomate;
private updatedFilepath: string = null;
private zoomValue: number;
private isYouTube: boolean;
private youtubeStart: string = null;
private isMDFile: boolean;
private notExcalidrawIsInternal: boolean;
private isLocalURI: boolean;
private mdCustomData: EmbeddableMDCustomProps;
private onKeyDown: (ev: KeyboardEvent) => void;
constructor(
private plugin: ExcalidrawPlugin,
private view: ExcalidrawView,
private file: TFile,
private element: ExcalidrawEmbeddableElement
) {
super(plugin.app);
this.ea = getEA(this.view);
this.ea.copyViewElementsToEAforEditing([this.element]);
this.zoomValue = element.scale[0];
this.isYouTube = isYouTube(this.element.link);
this.notExcalidrawIsInternal = this.file && !this.view.plugin.isExcalidrawFile(this.file)
this.isMDFile = this.file && this.file.extension === "md"; // && !this.view.plugin.isExcalidrawFile(this.file);
this.isLocalURI = this.element.link.startsWith("file://");
if(isYouTube) this.youtubeStart = getYouTubeStartAt(this.element.link);
this.mdCustomData = element.customData?.mdProps ?? view.plugin.settings.embeddableMarkdownDefaults;
if(!element.customData?.mdProps) {
const bgCM = this.plugin.ea.getCM(element.backgroundColor);
this.mdCustomData.backgroundColor = bgCM.stringHEX({alpha: false});
this.mdCustomData.backgroundOpacity = element.opacity;
const borderCM = this.plugin.ea.getCM(element.strokeColor);
this.mdCustomData.borderColor = borderCM.stringHEX({alpha: false});
this.mdCustomData.borderOpacity = element.opacity;
}
}
onOpen(): void {
this.containerEl.classList.add("excalidraw-release");
//this.titleEl.setText(t("ES_TITLE"));
this.createForm();
}
onClose() {
this.containerEl.removeEventListener("keydown",this.onKeyDown);
this.plugin = null;
this.view = null;
this.file = null;
this.element = null;
this.ea.destroy();
this.ea = null;
this.mdCustomData = null;
}
async createForm() {
this.contentEl.createEl("h1",{text: t("ES_TITLE")});
if(this.file) {
new Setting(this.contentEl)
.setName(t("ES_RENAME"))
.addText(text =>
text
.setValue(getPathWithoutExtension(this.file))
.onChange(async (value) => {
this.updatedFilepath = value;
})
)
}
const zoomValue = ():DocumentFragment => {
return fragWithHTML(`${t("ES_ZOOM_100_RELATIVE_DESC")}<br>Current zoom is <b>${Math.round(this.zoomValue*100)}%</b>`);
}
const zoomSetting = new Setting(this.contentEl)
.setName(t("ES_ZOOM"))
.setDesc(zoomValue())
.addButton(button =>
button
.setButtonText(t("ES_ZOOM_100"))
.onClick(() => {
const api = this.view.excalidrawAPI as ExcalidrawImperativeAPI;
this.zoomValue = 1/api.getAppState().zoom.value;
zoomSetting.setDesc(zoomValue());
})
)
.addSlider(slider =>
slider
.setLimits(10,400,5)
.setValue(this.zoomValue*100)
.onChange(value => {
this.zoomValue = value/100;
zoomSetting.setDesc(zoomValue());
})
)
if(this.isYouTube) {
new Setting(this.contentEl)
.setName(t("ES_YOUTUBE_START"))
.setDesc(t("ES_YOUTUBE_START_DESC"))
.addText(text =>
text
.setValue(this.youtubeStart)
.onChange(async (value) => {
this.youtubeStart = value;
})
)
}
if(this.isMDFile || this.notExcalidrawIsInternal) {
this.contentEl.createEl("h3",{text: t("ES_EMBEDDABLE_SETTINGS")});
new EmbeddalbeMDFileCustomDataSettingsComponent(this.contentEl,this.mdCustomData, undefined, this.isMDFile).render();
}
new Setting(this.contentEl)
.addButton(button =>
button
.setButtonText(t("PROMPT_BUTTON_CANCEL"))
.setTooltip("ESC")
.onClick(this.close.bind(this))
)
.addButton(button =>
button
.setButtonText(t("PROMPT_BUTTON_OK"))
.setTooltip("CTRL/Opt+Enter")
.setCta()
.onClick(this.applySettings.bind(this))
)
const onKeyDown = (ev: KeyboardEvent) => {
if(isWinCTRLorMacCMD(ev) && ev.key === "Enter") {
this.applySettings();
}
}
this.onKeyDown = onKeyDown;
this.containerEl.ownerDocument.addEventListener("keydown",onKeyDown);
}
private async applySettings() {
let dirty = false;
const el = this.ea.getElement(this.element.id) as Mutable<ExcalidrawEmbeddableElement>;
if(this.updatedFilepath) {
const newPathWithExt = `${this.updatedFilepath}.${this.file.extension}`;
if(newPathWithExt !== this.file.path) {
const fnparts = splitFolderAndFilename(newPathWithExt);
const newPath = getNewUniqueFilepath(
this.app.vault,
fnparts.filename,
fnparts.folderpath,
);
if(this.app.vault.getAbstractFileByPath(newPath)) {
new Notice("File rename failed. A file with this name already exists.\n"+newPath,10000);
} else {
try {
await this.app.fileManager.renameFile(this.file,newPath);
el.link = this.element.link.replace(
/(\[\[)([^#\]]*)([^\]]*]])/,`$1${
this.plugin.app.metadataCache.fileToLinktext(
this.file,this.view.file.path,true)
}$3`);
dirty = true;
} catch(e) {
new Notice("File rename failed. "+e,10000);
}
}
}
}
if(this.isYouTube && this.youtubeStart !== getYouTubeStartAt(this.element.link)) {
dirty = true;
if(this.youtubeStart === "" || isValidYouTubeStart(this.youtubeStart)) {
el.link = updateYouTubeStartTime(el.link,this.youtubeStart);
} else {
new Notice(t("ES_YOUTUBE_START_INVALID"));
}
}
if(
this.isMDFile && (
this.mdCustomData.backgroundColor !== this.element.customData?.backgroundColor ||
this.mdCustomData.borderColor !== this.element.customData?.borderColor ||
this.mdCustomData.backgroundOpacity !== this.element.customData?.backgroundOpacity ||
this.mdCustomData.borderOpacity !== this.element.customData?.borderOpacity ||
this.mdCustomData.filenameVisible !== this.element.customData?.filenameVisible)
) {
addAppendUpdateCustomData(el,{mdProps: this.mdCustomData});
dirty = true;
}
if(this.zoomValue !== this.element.scale[0]) {
dirty = true;
el.scale = [this.zoomValue,this.zoomValue];
}
if(dirty) {
(async() => {
await this.ea.addElementsToView();
this.ea.viewUpdateScene({appState: {}, captureUpdate: CaptureUpdateAction.NEVER});
this.close(); //close should only run once update scene is done
})();
} else {
this.close();
}
};
}