iframe beta 2

This commit is contained in:
zsviczian
2023-06-23 23:01:52 +02:00
parent c3650fd0ff
commit 90b1bcbc3b
11 changed files with 407 additions and 25 deletions

File diff suppressed because one or more lines are too long

View File

@@ -612,6 +612,7 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
y: number,
w: number,
h: number,
link: string | null = null,
) {
return {
id,
@@ -640,11 +641,33 @@ export class ExcalidrawAutomate implements ExcalidrawAutomateInterface {
isDeleted: false,
groupIds: [] as any,
boundElements: [] as any,
link: null as string,
link,
locked: false,
};
}
/**
*
* @param topX
* @param topY
* @param width
* @param height
* @returns
*/
addIFrame(topX: number, topY: number, width: number, height: number, url?: string, file?: TFile): string {
const id = nanoid();
this.elementsDict[id] = this.boxedElement(
id,
"iframe",
topX,
topY,
width,
height,
url ? url : file ? `[[${file.path}]]` : "",
);
return id;
};
/**
*
* @param topX

View File

@@ -114,6 +114,7 @@ import { setDynamicStyle } from "./utils/DynamicStyling";
import { MenuLinks } from "./menu/MenuLinks";
import { InsertPDFModal } from "./dialogs/InsertPDFModal";
import { CustomIFrame, renderWebView, useDefaultExcalidrawFrame } from "./customIFrame";
import { insertIFrameToView, insertImageToView } from "./utils/ExcalidrawViewUtils";
declare const PLUGIN_VERSION:string;
@@ -2951,6 +2952,7 @@ export default class ExcalidrawView extends TextFileView {
case "image": msg = "Embed image";break;
case "image-fullsize": msg = "Embed image @100%"; break;
case "link": msg = "Insert link"; break;
case "iframe": msg = "Insert in interactive frame"; break;
}
} else if(e.dataTransfer.types.length === 1 && e.dataTransfer.types.includes("Files")) {
//drag from OS file manager
@@ -2961,6 +2963,7 @@ export default class ExcalidrawView extends TextFileView {
case "image-import": msg = "Import image to Vault"; break;
case "image-url": msg = "Insert image/thumbnail with URL"; break;
case "insert-link": msg = "Insert link"; break;
case "iframe": msg = "Insert in interactive frame"; break;
}
}
if(this.draginfoDiv.innerText !== msg) this.draginfoDiv.innerText = msg;
@@ -3173,6 +3176,7 @@ export default class ExcalidrawView extends TextFileView {
const internalDragAction = internalDragModifierType(event);
const externalDragAction = externalDragModifierType(event);
//Call Excalidraw Automate onDropHook
const onDropHook = (
type: "file" | "text" | "unknown",
files: TFile[],
@@ -3204,39 +3208,47 @@ export default class ExcalidrawView extends TextFileView {
}
};
//Obsidian internal drag event
//---------------------------------------------------------------------------------
// Obsidian internal drag event
//---------------------------------------------------------------------------------
switch (draggable?.type) {
case "file":
if (!onDropHook("file", [draggable.file], null)) {
const file:TFile = draggable.file;
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/422
if (draggable.file.path.match(REG_LINKINDEX_INVALIDCHARS)) {
if (file.path.match(REG_LINKINDEX_INVALIDCHARS)) {
new Notice(t("FILENAME_INVALID_CHARS"), 4000);
return false;
}
if (
["image", "image-fullsize"].contains(internalDragAction) &&
(IMAGE_TYPES.contains(draggable.file.extension) ||
draggable.file.extension === "md" ||
draggable.file.extension.toLowerCase() === "pdf" )
(IMAGE_TYPES.contains(file.extension) ||
file.extension === "md" ||
file.extension.toLowerCase() === "pdf" )
) {
const ea = getEA(this);
if(draggable.file.extension.toLowerCase() === "pdf") {
if(file.extension.toLowerCase() === "pdf") {
const insertPDFModal = new InsertPDFModal(this.plugin, this);
insertPDFModal.open(draggable.file);
insertPDFModal.open(file);
} else {
(async () => {
ea.canvas.theme = api.getAppState().theme;
await ea.addImage(
this.currentPosition.x,
this.currentPosition.y,
draggable.file,
!(internalDragAction==="image-fullsize"),
);
ea.addElementsToView(false, false, true);
})();
insertImageToView(
getEA(this),
this.currentPosition,
file,
!(internalDragAction==="image-fullsize")
);
}
return false;
}
if (internalDragAction === "iframe") {
insertIFrameToView(
getEA(this),
this.currentPosition,
file,
)
return false;
}
//internalDragAction === "link"
this.addText(
`[[${app.metadataCache.fileToLinktext(
@@ -3272,6 +3284,28 @@ export default class ExcalidrawView extends TextFileView {
}
return;
}
if (internalDragAction === "iframe") {
const ea = getEA(this) as ExcalidrawAutomate;
let column:number = 0;
let row:number = 0;
for (const f of draggable.files) {
await insertIFrameToView(
ea,
{
x:this.currentPosition.x + column*500,
y:this.currentPosition.y + row*550
},
f,
)
column = (column + 1) % 3;
if(column === 0) {
row++;
}
}
return false;
}
//internalDragAction === "link"
for (const f of draggable.files) {
await this.addText(
@@ -3289,7 +3323,9 @@ export default class ExcalidrawView extends TextFileView {
return false;
}
//externalDragAction
//---------------------------------------------------------------------------------
// externalDragAction
//---------------------------------------------------------------------------------
if (event.dataTransfer.types.includes("Files")) {
if (event.dataTransfer.types.includes("text/plain")) {
const text: string = event.dataTransfer.getData("text");
@@ -3312,6 +3348,15 @@ export default class ExcalidrawView extends TextFileView {
return false;
}
}
if(text && (externalDragAction === "iframe")) {
insertIFrameToView(
getEA(this),
this.currentPosition,
undefined,
text,
)
return false;
}
}
if(event.dataTransfer.types.includes("text/html")) {
@@ -3333,6 +3378,15 @@ export default class ExcalidrawView extends TextFileView {
return false;
}
}
if(src && (externalDragAction === "iframe")) {
insertIFrameToView(
getEA(this),
this.currentPosition,
undefined,
src[1],
)
return false;
}
}
return true;
}
@@ -3353,6 +3407,9 @@ export default class ExcalidrawView extends TextFileView {
return true;
}
if (!onDropHook("text", null, text)) {
if(text && (externalDragAction==="iframe") && /^(http|https):\/\/[^\s/$.?#].[^\s]*$/.test(text)) {
return true;
}
if(text && (externalDragAction==="image-url") && hyperlinkIsYouTubeLink(text)) {
this.addYouTubeThumbnail(text);
return false;

View File

@@ -185,7 +185,7 @@ function RenderObsidianView(
return;
}
isActiveRef.current = appState.activeIFrameElement === element;
isActiveRef.current = appState.activeIFrame?.element === element && appState.activeIFrame?.state === "active";
if(!isActiveRef.current) {
//@ts-ignore
@@ -194,7 +194,7 @@ function RenderObsidianView(
app.workspace.setActiveLeaf(view.leaf);
return;
}
}, [appState.activeIFrameElement, element]);
}, [appState.activeIFrame, element]);
return null;
};
@@ -220,6 +220,19 @@ export const CustomIFrame: React.FC<{element: NonDeletedExcalidrawElement; radiu
view={view}
containerRef={containerRef}
appState={appState}/>
{(appState.activeIFrame?.element === element && appState.activeIFrame?.state === "hover") && (<div
style={{
position: "absolute",
top: 0,
left: 0,
width: `100%`,
height: `100%`,
background: `radial-gradient(
ellipse at center,
rgba(0, 0, 0, 0) 20%,
rgba(0, 0, 0, 0.6) 80%
)`,
}}/>)}
</div>
)
}

View File

@@ -0,0 +1,189 @@
import { ButtonComponent, DropdownComponent, TFile, ToggleComponent } from "obsidian";
import ExcalidrawView from "../ExcalidrawView";
import ExcalidrawPlugin from "../main";
import { Modal, Setting, TextComponent } from "obsidian";
import { FileSuggestionModal } from "./FolderSuggester";
import { IMAGE_TYPES, REG_BLOCK_REF_CLEAN } from "src/Constants";
import { insertIFrameToView, insertImageToView } from "src/utils/ExcalidrawViewUtils";
import { getEA } from "src";
import { InsertPDFModal } from "./InsertPDFModal";
import { ExcalidrawImperativeAPI } from "@zsviczian/excalidraw/types/types";
import { MAX_IMAGE_SIZE } from "src/Constants";
const {
viewportCoordsToSceneCoords
//@ts-ignore
} = excalidrawLib;
export class UniversalInsertFileModal extends Modal {
private center: { x: number, y: number } = { x: 0, y: 0 };
constructor(
private plugin: ExcalidrawPlugin,
private view: ExcalidrawView,
) {
super(app);
const appState = (view.excalidrawAPI as ExcalidrawImperativeAPI).getAppState();
const containerRect = view.containerEl.getBoundingClientRect();
const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
const centerX = containerRect.left + containerRect.width / 2 - MAX_IMAGE_SIZE / 2;
const centerY = containerRect.top + containerRect.height / 2 - MAX_IMAGE_SIZE / 2;
const clientX = Math.max(0, Math.min(viewportWidth, centerX));
const clientY = Math.max(0, Math.min(viewportHeight, centerY));
this.center = viewportCoordsToSceneCoords ({clientX, clientY}, appState)
}
onOpen(): void {
this.containerEl.classList.add("excalidraw-release");
this.titleEl.setText(`Insert File From Vault`);
this.createForm();
}
async createForm() {
const ce = this.contentEl;
let sectionPicker: DropdownComponent;
let sectionPickerSetting: Setting;
let actionIFrame: ButtonComponent;
let actionImage: ButtonComponent;
let actionPDF: ButtonComponent;
let sizeToggleSetting: Setting
let anchorTo100: boolean = false;
let file: TFile;
const updateForm = async () => {
const ea = this.plugin.ea;
const isMarkdown = file && file.extension === "md" && !ea.isExcalidrawFile(file);
const isImage = file && (IMAGE_TYPES.contains(file.extension) || ea.isExcalidrawFile(file));
const isIFrame = file && !isImage;
const isPDF = file && file.extension === "pdf";
const isExcalidraw = file && ea.isExcalidrawFile(file);
if (isMarkdown) {
sectionPickerSetting.settingEl.style.display = "";
sectionPicker.selectEl.style.display = "block";
while(sectionPicker.selectEl.options.length > 0) {
sectionPicker.selectEl.remove(0);
}
sectionPicker.addOption("","");
(await app.metadataCache.blockCache
.getForFile({ isCancelled: () => false },file))
.blocks.filter((b: any) => b.display && b.node?.type === "heading")
.forEach((b: any) => {
sectionPicker.addOption(
`#${b.display.replaceAll(REG_BLOCK_REF_CLEAN, "").trim()}`,
b.display)
});
} else {
sectionPickerSetting.settingEl.style.display = "none";
sectionPicker.selectEl.style.display = "none";
}
if (isExcalidraw) {
sizeToggleSetting.settingEl.style.display = "";
} else {
sizeToggleSetting.settingEl.style.display = "none";
}
if (isImage || (file?.extension === "md")) {
actionImage.buttonEl.style.display = "block";
} else {
actionImage.buttonEl.style.display = "none";
}
if (isIFrame) {
actionIFrame.buttonEl.style.display = "block";
} else {
actionIFrame.buttonEl.style.display = "none";
}
if (isPDF) {
actionPDF.buttonEl.style.display = "block";
} else {
actionPDF.buttonEl.style.display = "none";
}
}
const search = new TextComponent(ce);
search.inputEl.style.width = "100%";
const suggester = new FileSuggestionModal(this.app, search,app.vault.getFiles().filter((f: TFile) => f!==this.view.file));
search.onChange(() => {
file = suggester.getSelectedItem();
updateForm();
});
sectionPickerSetting = new Setting(ce)
.setName("Select section heading")
.addDropdown(dropdown => {
sectionPicker = dropdown;
sectionPicker.selectEl.style.width = "100%";
})
sizeToggleSetting = new Setting(ce)
.setName("Anchor to 100% of original size")
.setDesc("This is a pro feature, use it only if you understand how it works. If enabled even if you change the size of the imported image in Excalidraw, the next time you open the drawing this image will pop back to 100% size. This is useful when embedding an atomic Excalidraw idea into another note and preserving relative sizing of text and icons.")
.addToggle(toggle => {
toggle.setValue(anchorTo100)
.onChange((value) => {
anchorTo100 = value;
})
})
new Setting(ce)
.addButton(button => {
button
.setButtonText("As IFrame")
.setCta()
.onClick(() => {
const path = app.metadataCache.fileToLinktext(
file,
this.view.file.path,
file.extension === "md",
)
insertIFrameToView (
getEA(this.view),
this.center,
//this.view.currentPosition,
undefined,
`[[${path}${sectionPicker.selectEl.value}]]`,
)
this.close();
})
actionIFrame = button;
})
.addButton(button => {
button
.setButtonText("As PDF")
.setCta()
.onClick(() => {
const insertPDFModal = new InsertPDFModal(this.plugin, this.view);
insertPDFModal.open(file);
this.close();
})
actionPDF = button;
})
.addButton(button => {
button
.setButtonText("As Image")
.setCta()
.onClick(() => {
insertImageToView (
getEA(this.view),
this.center,
//this.view.currentPosition,
file,
anchorTo100,
)
this.close();
})
actionImage = button;
})
search.inputEl.focus();
updateForm();
}
}

View File

@@ -59,6 +59,7 @@ export default {
IMPORT_SVG: "Import an SVG file as Excalidraw strokes (limited SVG support, TEXT currently not supported)",
INSERT_MD: "Insert markdown file from vault",
INSERT_PDF: "Insert PDF file from vault",
UNIVERSAL_ADD_FILE: "Add a file from the Vault to the drawing",
INSERT_LATEX:
`Insert LaTeX formula (e.g. \\binom{n}{k} = \\frac{n!}{k!(n-k)!}). ${labelALT()}+CLICK to watch a help video.`,
ENTER_LATEX: "Enter a valid LaTeX expression",

View File

@@ -103,6 +103,7 @@ import Taskbone from "./ocr/Taskbone";
import { emulateCTRLClickForLinks, linkClickModifierType, PaneTarget } from "./utils/ModifierkeyHelper";
import { InsertPDFModal } from "./dialogs/InsertPDFModal";
import { ExportDialog } from "./dialogs/ExportDialog";
import { UniversalInsertFileModal } from "./dialogs/UniversalInsertFileModal";
declare module "obsidian" {
interface App {
@@ -1351,6 +1352,23 @@ export default class ExcalidrawPlugin extends Plugin {
},
});
this.addCommand({
id: "universal-add-file",
name: t("UNIVERSAL_ADD_FILE"),
checkCallback: (checking: boolean) => {
if (checking) {
return Boolean(this.app.workspace.getActiveViewOfType(ExcalidrawView))
}
const view = this.app.workspace.getActiveViewOfType(ExcalidrawView);
if (view) {
const insertFileModal = new UniversalInsertFileModal(this, view);
insertFileModal.open();
return true;
}
return false;
},
});
this.addCommand({
id: "insert-LaTeX-symbol",
name: t("INSERT_LATEX"),

View File

@@ -583,6 +583,22 @@ export const ICONS = {
<path fillRule="evenodd" clipRule="evenodd" d="M211.1 303c8 37.5-1 85.2-27.5 131.6 22.2-46 33-90.1 24-131l3.5-.7Z" fill="url(#h)"/>
<path fillRule="evenodd" clipRule="evenodd" d="M302.7 299.5c43.5 16.3 60.3 52 72.8 81.9-15.5-31.2-37-65.7-74.4-78.5-28.4-9.8-52.4-8.6-93.5.7l-.9-4c43.6-10 66.4-11.2 96 0Z" fill="url(#i)"/>
</svg>
),
"add-file": (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="var(--icon-fill-color)"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/>
<polyline points="14 2 14 8 20 8"/>
<line x1="12" x2="12" y1="18" y2="12"/>
<line x1="9" x2="15" y1="15" y2="15"/>
</svg>
)
};

View File

@@ -9,6 +9,7 @@ import { PenStyle } from "src/PenTypes";
import { PENS } from "src/utils/Pens";
import ExcalidrawPlugin from "../main";
import { ICONS, penIcon, stringToSVG } from "./ActionIcons";
import { UniversalInsertFileModal } from "src/dialogs/UniversalInsertFileModal";
declare const PLUGIN_VERSION:string;
@@ -254,6 +255,23 @@ export class ObsidianMenu {
{ICONS.obsidian}
</div>
</label>
<label
className={clsx(
"ToolIcon",
"ToolIcon_size_medium",
{
"is-mobile": isMobile,
},
)}
onClick={() => {
const insertFileModal = new UniversalInsertFileModal(this.plugin, this.view);
insertFileModal.open();
}}
>
<div className="ToolIcon__icon" aria-hidden="true">
{ICONS["add-file"]}
</div>
</label>
{this.renderCustomPens(isMobile,appState)}
{this.renderPinnedScriptButtons(isMobile,appState)}
</>

View File

@@ -0,0 +1,45 @@
import { MAX_IMAGE_SIZE } from "src/Constants";
import { TFile } from "obsidian";
import { IMAGE_TYPES } from "src/Constants";
import { ExcalidrawAutomate } from "src/ExcalidrawAutomate";
export const insertImageToView = async (
ea: ExcalidrawAutomate,
position: { x: number, y: number },
file: TFile,
scale?: boolean,
) => {
ea.clear();
const api = ea.getExcalidrawAPI();
ea.canvas.theme = api.getAppState().theme;
await ea.addImage(
position.x,
position.y,
file,
scale,
);
ea.addElementsToView(false, false, true);
}
export const insertIFrameToView = async (
ea: ExcalidrawAutomate,
position: { x: number, y: number },
file?: TFile,
link?: string,
) => {
ea.clear();
if(file && IMAGE_TYPES.contains(file.extension) || ea.isExcalidrawFile(file)) {
await insertImageToView(ea, position, file);
} else {
ea.addIFrame(
position.x,
position.y,
MAX_IMAGE_SIZE,
MAX_IMAGE_SIZE,
link,
file,
);
ea.addElementsToView(false, false, true);
}
}

View File

@@ -2,8 +2,8 @@ import { DEVICE, isDarwin } from "src/Constants";
export type ModifierKeys = {shiftKey:boolean, ctrlKey: boolean, metaKey: boolean, altKey: boolean};
export type KeyEvent = PointerEvent | MouseEvent | KeyboardEvent | React.DragEvent | React.PointerEvent | React.MouseEvent | ModifierKeys;
export type PaneTarget = "active-pane"|"new-pane"|"popout-window"|"new-tab"|"md-properties";
export type ExternalDragAction = "insert-link"|"image-url"|"image-import";
export type InternalDragAction = "link"|"image"|"image-fullsize";
export type ExternalDragAction = "insert-link"|"image-url"|"image-import"|"iframe";
export type InternalDragAction = "link"|"image"|"image-fullsize"|"iframe";
export const labelCTRL = () => DEVICE.isIOS || DEVICE.isMacOS ? "CMD" : "CTRL";
export const labelALT = () => DEVICE.isIOS || DEVICE.isMacOS ? "OPT" : "ALT";
@@ -31,6 +31,7 @@ export const linkClickModifierType = (ev: KeyEvent):PaneTarget => {
}
export const externalDragModifierType = (ev: KeyEvent):ExternalDragAction => {
if( isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "iframe";
if(!isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "insert-link";
if(!isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && isMETA(ev)) return "insert-link";
if( isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image-import";
@@ -40,6 +41,7 @@ export const externalDragModifierType = (ev: KeyEvent):ExternalDragAction => {
//https://github.com/zsviczian/obsidian-excalidraw-plugin/issues/468
export const internalDragModifierType = (ev: KeyEvent):InternalDragAction => {
if( isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "iframe";
if( isSHIFT(ev) && !isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image";
if(!isSHIFT(ev) && isCTRL(ev) && !isALT(ev) && !isMETA(ev)) return "image";
if(scaleToFullsizeModifier(ev)) return "image-fullsize";