From 02b1f035d35b9be0eb31e879d03123ab7c26768c Mon Sep 17 00:00:00 2001 From: zsviczian Date: Sat, 22 Jul 2023 10:02:07 +0200 Subject: [PATCH] ea.newFilePrompt --- src/ExcalidrawAutomate.ts | 41 ++++++++++++++++++++++- src/ExcalidrawData.ts | 8 ++--- src/dialogs/Prompt.ts | 59 ++++++++++++++++++++++------------ src/lang/locale/en.ts | 17 ++++++++++ src/types.d.ts | 15 +++++++++ src/utils/ModifierkeyHelper.ts | 51 +++++++++++++++++++++++++++++ src/utils/Utils.ts | 3 -- 7 files changed, 165 insertions(+), 29 deletions(-) diff --git a/src/ExcalidrawAutomate.ts b/src/ExcalidrawAutomate.ts index c949b99..25c2f72 100644 --- a/src/ExcalidrawAutomate.ts +++ b/src/ExcalidrawAutomate.ts @@ -48,7 +48,7 @@ import { getAttachmentsFolderAndFilePath, getNewOrAdjacentLeaf, isObsidianThemeD import { AppState, BinaryFileData, DataURL, ExcalidrawImperativeAPI, Point } from "@zsviczian/excalidraw/types/types"; import { EmbeddedFile, EmbeddedFilesLoader, FileData } from "./EmbeddedFileLoader"; import { tex2dataURL } from "./LaTeX"; -import { Prompt } from "./dialogs/Prompt"; +import { NewFileActions, Prompt } from "./dialogs/Prompt"; import { t } from "./lang/helpers"; import { ScriptEngine } from "./Scripts"; import { ConnectionPoint, ExcalidrawAutomateInterface } from "./types"; @@ -70,6 +70,7 @@ import { TInput } from "colormaster/types"; import {ConversionResult, svgToExcalidraw} from "./svgToExcalidraw/parser" import { ROUNDNESS } from "./Constants"; import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard"; +import { emulateKeysForLinkClick, KeyEvent, PaneTarget } from "./utils/ModifierkeyHelper"; extendPlugins([ HarmonyPlugin, @@ -114,6 +115,44 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface { return getNewUniqueFilepath(app.vault, filename, folderAndPath.folder); } + /** + * Prompts the user with a dialog to select new file action. + * - create markdown file + * - create excalidraw file + * - cancel action + * The new file will be relative to this.targetView.file.path, unless parentFile is provided. + * If shouldOpenNewFile is true, the new file will be opened in a workspace leaf. + * targetPane control which leaf will be used for the new file. + * Returns the TFile for the new file or null if the user cancelled the action. + * @param newFileNameOrPath + * @param shouldOpenNewFile + * @param targetPane //type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties"; + * @param parentFile + * @returns + */ + public async newFilePrompt( + newFileNameOrPath: string, + shouldOpenNewFile: boolean, + targetPane?: PaneTarget, + parentFile?: TFile, + ): Promise { + if (!this.targetView || !this.targetView?.file) { + errorMessage("targetView not set", "newFileActions()"); + return null; + } + const modifierKeys = emulateKeysForLinkClick(targetPane); + const newFilePrompt = new NewFileActions( + this.plugin, + newFileNameOrPath, + modifierKeys, + this.targetView, + shouldOpenNewFile, + parentFile + ) + newFilePrompt.open(); + return await newFilePrompt.waitForClose; + } + plugin: ExcalidrawPlugin; elementsDict: {[key:string]:any}; //contains the ExcalidrawElements currently edited in Automate indexed by el.id imagesDict: {[key: FileId]: any}; //the images files including DataURL, indexed by fileId diff --git a/src/ExcalidrawData.ts b/src/ExcalidrawData.ts index 55f5ccc..085094d 100644 --- a/src/ExcalidrawData.ts +++ b/src/ExcalidrawData.ts @@ -504,12 +504,12 @@ export class ExcalidrawData { const prompt = new ConfirmationPrompt( this.plugin, "This file contains embedded frames " + - "that will be migrated to a newer version for compatibility with " + + "which will be migrated to a newer version for compatibility with " + "excalidraw.com.
🔄 If you're using Obsidian on " + - "multiple devices, you may proceed now, but please before opening this " + - "converted file on those, update Excalidraw on them as well.
🔍 More info is available "+ + "multiple devices, you may proceed now, but please, before opening this " + + "file on your other devices, update Excalidraw on those as well.
🔍 More info is available "+ "here.
🌐 " + - "" + + "" + "Translate.", ); prompt.contentEl.focus(); diff --git a/src/dialogs/Prompt.ts b/src/dialogs/Prompt.ts index e01de77..a742003 100644 --- a/src/dialogs/Prompt.ts +++ b/src/dialogs/Prompt.ts @@ -15,6 +15,7 @@ import { sleep } from "../utils/Utils"; import { getLeaf } from "../utils/ObsidianUtils"; import { checkAndCreateFolder, splitFolderAndFilename } from "src/utils/FileUtils"; import { KeyEvent, isCTRL } from "src/utils/ModifierkeyHelper"; +import { t } from "src/lang/helpers"; export type ButtonDefinition = { caption: string; tooltip?:string; action: Function }; @@ -267,15 +268,15 @@ export class GenericInputPrompt extends Modal { this.submitClickCallback, ).setCta().buttonEl.style.marginRight = "0"; } - this.createButton(actionButtonContainer, "❌", this.cancelClickCallback, "Cancel"); + this.createButton(actionButtonContainer, "❌", this.cancelClickCallback, t("PROMPT_BUTTON_CANCEL")); if(this.displayEditorButtons) { - this.createButton(editorButtonContainer, "⏎", ()=>this.insertStringBtnClickCallback("\n"), "Insert new line", "0"); + this.createButton(editorButtonContainer, "⏎", ()=>this.insertStringBtnClickCallback("\n"), t("PROMPT_BUTTON_INSERT_LINE"), "0"); this.createButton(editorButtonContainer, "⌫", this.delBtnClickCallback, "Delete"); - this.createButton(editorButtonContainer, "⎵", ()=>this.insertStringBtnClickCallback(" "), "Insert space"); + this.createButton(editorButtonContainer, "⎵", ()=>this.insertStringBtnClickCallback(" "), t("PROMPT_BUTTON_INSERT_SPACE")); if(this.view) { - this.createButton(editorButtonContainer, "🔗", this.linkBtnClickCallback, "Insert markdown link to file"); + this.createButton(editorButtonContainer, "🔗", this.linkBtnClickCallback, t("PROMPT_BUTTON_INSERT_LINK")); } - this.createButton(editorButtonContainer, "🔠", this.uppercaseBtnClickCallback, "Uppercase"); + this.createButton(editorButtonContainer, "🔠", this.uppercaseBtnClickCallback, t("PROMPT_BUTTON_UPPERCASE")); } } @@ -456,35 +457,51 @@ export class GenericSuggester extends FuzzySuggestModal { } export class NewFileActions extends Modal { + public waitForClose: Promise; + private resolvePromise: (file: TFile|null) => void; + private rejectPromise: (reason?: any) => void; + private newFile: TFile = null; + constructor( private plugin: ExcalidrawPlugin, private path: string, private keys: KeyEvent, private view: ExcalidrawView, + private openNewFile: boolean = true, + private parentFile?: TFile, ) { super(plugin.app); + if(!parentFile) this.parentFile = view.file; + this.waitForClose = new Promise((resolve, reject) => { + this.resolvePromise = resolve; + this.rejectPromise = reject; + }); } onOpen(): void { this.createForm(); } - async onClose() {} - openFile(file: TFile): void { - if (!file) { + this.newFile = file; + if (!file || !this.openNewFile) { return; } const leaf = getLeaf(this.plugin,this.view.leaf,this.keys) leaf.openFile(file, {active:true}); } + onClose() { + super.onClose(); + this.resolvePromise(this.newFile); + } + createForm(): void { - this.titleEl.setText("New File"); + this.titleEl.setText(t("PROMPT_TITLE_NEW_FILE")); this.contentEl.createDiv({ cls: "excalidraw-prompt-center", - text: "File does not exist. Do you want to create it?", + text: t("PROMPT_FILE_DOES_NOT_EXIST"), }); this.contentEl.createDiv({ cls: "excalidraw-prompt-center filepath", @@ -497,12 +514,12 @@ export class NewFileActions extends Modal { const checks = (): boolean => { if (!this.path || this.path === "") { - new Notice("Error: Filename for new file may not be empty"); + new Notice(t("PROMPT_ERROR_NO_FILENAME")); return false; } - if (!this.view.file) { + if (!this.parentFile) { new Notice( - "Unknown error. It seems as if your drawing was closed or the drawing file is missing", + t("PROMPT_ERROR_DRAWING_CLOSED"), ); return false; } @@ -511,8 +528,8 @@ export class NewFileActions extends Modal { const createFile = async (data: string): Promise => { if (!this.path.includes("/")) { - const re = new RegExp(`${this.view.file.name}$`, "g"); - this.path = this.view.file.path.replace(re, this.path); + const re = new RegExp(`${this.parentFile.name}$`, "g"); + this.path = this.parentFile.path.replace(re, this.path); } if (!this.path.match(/\.md$/)) { this.path = `${this.path}.md`; @@ -523,7 +540,7 @@ export class NewFileActions extends Modal { return f; }; - const bMd = el.createEl("button", { text: "Create Markdown" }); + const bMd = el.createEl("button", { text: t("PROMPT_BUTTON_CREATE_MARKDOWN") }); bMd.onclick = async () => { if (!checks) { return; @@ -533,7 +550,7 @@ export class NewFileActions extends Modal { this.close(); }; - const bEx = el.createEl("button", { text: "Create Excalidraw" }); + const bEx = el.createEl("button", { text: t("PROMPT_BUTTON_CREATE_EXCALIDRAW") }); bEx.onclick = async () => { if (!checks) { return; @@ -545,7 +562,7 @@ export class NewFileActions extends Modal { }; const bCancel = el.createEl("button", { - text: "Never Mind", + text: t("PROMPT_BUTTON_NEVERMIND"), }); bCancel.onclick = () => { this.close(); @@ -575,7 +592,7 @@ export class ConfirmationPrompt extends Modal { private display() { this.contentEl.empty(); - this.titleEl.textContent = "Confirmation"; + this.titleEl.textContent = t("PROMPT_TITLE_CONFIRMATION"); const messageEl = this.contentEl.createDiv(); messageEl.style.marginBottom = "1rem"; @@ -585,10 +602,10 @@ export class ConfirmationPrompt extends Modal { buttonContainer.style.display = "flex"; buttonContainer.style.justifyContent = "flex-end"; - const cancelButton = this.createButton(buttonContainer, "Cancel", this.cancelClickCallback); + const cancelButton = this.createButton(buttonContainer, t("PROMPT_BUTTON_CANCEL"), this.cancelClickCallback); cancelButton.buttonEl.style.marginRight = "0.5rem"; - const confirmButton = this.createButton(buttonContainer, "OK", this.confirmClickCallback); + const confirmButton = this.createButton(buttonContainer, t("PROMPT_BUTTON_OK"), this.confirmClickCallback); confirmButton.buttonEl.style.marginRight = "0"; cancelButton.buttonEl.focus(); diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index d932345..765dfac 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -516,4 +516,21 @@ FILENAME_HEAD: "Filename", ZOOM_TO_FIT: "Zoom to fit", RELOAD: "Reload original link", OPEN_IN_BROWSER: "Open current link in browser", + + //Prompts.ts + PROMPT_FILE_DOES_NOT_EXIST: "File does not exist. Do you want to create it?", + PROMPT_ERROR_NO_FILENAME: "Error: Filename for new file may not be empty", + PROMPT_ERROR_DRAWING_CLOSED: "Unknown error. It seems as if your drawing was closed or the drawing file is missing", + PROMPT_TITLE_NEW_FILE: "New File", + PROMPT_TITLE_CONFIRMATION: "Confirmation", + PROMPT_BUTTON_CREATE_EXCALIDRAW: "Create Excalidraw", + PROMPT_BUTTON_CREATE_MARKDOWN: "Create Markdown", + PROMPT_BUTTON_NEVERMIND: "Nevermind", + PROMPT_BUTTON_OK: "OK", + PROMPT_BUTTON_CANCEL: "Cancel", + PROMPT_BUTTON_INSERT_LINE: "Insert new line", + PROMPT_BUTTON_INSERT_SPACE: "Insert space", + PROMPT_BUTTON_INSERT_LINK: "Insert markdown link to file", + PROMPT_BUTTON_UPPERCASE: "Uppercase", + }; diff --git a/src/types.d.ts b/src/types.d.ts index 9fb3033..5547887 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -8,6 +8,7 @@ import ExcalidrawPlugin from "./main"; import { ColorMaster } from "colormaster"; import { TInput } from "colormaster/types"; import { ClipboardData } from "@zsviczian/excalidraw/types/clipboard"; +import { PaneTarget } from "./utils/ModifierkeyHelper"; export type ConnectionPoint = "top" | "bottom" | "left" | "right" | null; @@ -25,6 +26,20 @@ export type DynamicStyle = "none" | "gray" | "colorful"; export interface ExcalidrawAutomateInterface { get obsidian(): any; //returns the Obsidian Module getAttachmentFilepath(filename: string): Promise; + newFilePrompt( + // Prompts the user with a dialog to select new file action. + // - create markdown file + // - create excalidraw file + // - cancel action + // The new file will be relative to this.targetView.file.path, unless parentFile is provided. + // If shouldOpenNewFile is true, the new file will be opened in a workspace leaf. + // targetPane control which leaf will be used for the new file. + // Returns the TFile for the new file or null if the user cancelled the action. + newFileNameOrPath: string, + shouldOpenNewFile: boolean, + targetPane?: PaneTarget, //type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties"; + parentFile?: TFile, + ): Promise; plugin: ExcalidrawPlugin; elementsDict: {[key:string]:any}; //contains the ExcalidrawElements currently edited in Automate indexed by el.id imagesDict: {[key: FileId]: any}; //the images files including DataURL, indexed by fileId diff --git a/src/utils/ModifierkeyHelper.ts b/src/utils/ModifierkeyHelper.ts index 4cbbcb5..805a01e 100644 --- a/src/utils/ModifierkeyHelper.ts +++ b/src/utils/ModifierkeyHelper.ts @@ -15,6 +15,29 @@ export const isALT = (e:KeyEvent) => e.altKey; export const isMETA = (e:KeyEvent) => DEVICE.isIOS || DEVICE.isMacOS ? e.ctrlKey : e.metaKey; export const isSHIFT = (e:KeyEvent) => e.shiftKey; +export const setCTRL = (e:ModifierKeys, value: boolean): ModifierKeys => { + if(DEVICE.isIOS || DEVICE.isMacOS) + e.metaKey = value; + else + e.ctrlKey = value; + return e; +} +export const setALT = (e:ModifierKeys, value: boolean): ModifierKeys => { + e.altKey = value; + return e; +} +export const setMETA = (e:ModifierKeys, value: boolean): ModifierKeys => { + if(DEVICE.isIOS || DEVICE.isMacOS) + e.ctrlKey = value; + else + e.metaKey = value; + return e; +} +export const setSHIFT = (e:ModifierKeys, value: boolean): ModifierKeys => { + e.shiftKey = value; + return e; +} + export const mdPropModifier = (ev: KeyEvent): boolean => !isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && isMETA(ev); export const scaleToFullsizeModifier = (ev: KeyEvent) => ( isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && isMETA(ev)) || @@ -57,4 +80,32 @@ export const emulateCTRLClickForLinks = (e:KeyEvent) => { metaKey: e.metaKey || (DEVICE.isIOS || DEVICE.isMacOS), altKey: e.altKey } +} + +export const emulateKeysForLinkClick = (action: PaneTarget): ModifierKeys => { + const ev = {shiftKey: false, ctrlKey: false, metaKey: false, altKey: false}; + if(!action) return ev; + switch(action) { + case "active-pane": + setCTRL(ev, true); + setSHIFT(ev, true); + break; + case "new-pane": + setCTRL(ev, true); + setALT(ev, true); + break; + case "popout-window": + setCTRL(ev, true); + setALT(ev, true); + setSHIFT(ev, true); + break; + case "new-tab": + setCTRL(ev, true); + break; + case "md-properties": + setCTRL(ev, true); + setMETA(ev, true); + break; + } + return ev; } \ No newline at end of file diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts index b72d598..3d7d14f 100644 --- a/src/utils/Utils.ts +++ b/src/utils/Utils.ts @@ -27,9 +27,6 @@ import { compressToBase64, decompressFromBase64 } from "lz-string"; import { getIMGFilename } from "./FileUtils"; import ExcalidrawScene from "../svgToExcalidraw/elements/ExcalidrawScene"; import { IMAGE_TYPES } from "../Constants"; -import { ExcalidrawAutomate } from "lib/ExcalidrawAutomate"; -import { getEA } from "src"; -import elements from "src/svgToExcalidraw/elements"; import { generateEmbeddableLink } from "./CustomEmbeddableUtils"; declare const PLUGIN_VERSION:string;