diff --git a/ea-scripts/Toggle Fullscreen on Mobile.md b/ea-scripts/Archive/Toggle Fullscreen on Mobile.md similarity index 100% rename from ea-scripts/Toggle Fullscreen on Mobile.md rename to ea-scripts/Archive/Toggle Fullscreen on Mobile.md diff --git a/src/ActionButton.tsx b/src/ActionButton.tsx new file mode 100644 index 0000000..c42297e --- /dev/null +++ b/src/ActionButton.tsx @@ -0,0 +1,55 @@ +import * as React from "react"; +import ExcalidrawView, { TextMode } from "./ExcalidrawView"; + +type ButtonProps = { + title: string; + action: Function; + icon: JSX.Element; + view: ExcalidrawView; +} + +type ButtonState = { + visible: boolean; +} + +export class ActionButton extends React.Component { + toastMessageTimeout:number = 0; + + constructor(props: ButtonProps) { + super(props); + this.state = { + visible: true, + } + } + + render() { + return ( + + ); + } +} \ No newline at end of file diff --git a/src/ActionIcons.tsx b/src/ActionIcons.tsx new file mode 100644 index 0000000..61c60de --- /dev/null +++ b/src/ActionIcons.tsx @@ -0,0 +1,130 @@ +import * as React from "react"; + +export const ICONS = { + exportLibrary: ( + + ), + //far fa-image + insertImage: ( + + ), + //far fa-file-alt + insertMD: ( + + ), + //far fa-square-root-alt + insertLaTeX: ( + + ), + //fas fa-link + insertLink: ( + + ), + exportSVG: ( + + S + + ), + exportPNG: ( + + P + + ), + exportExcalidraw: ( + + ), + //fa-solid fa-magnifying-glass + search: ( + + ), + //fa-brands fa-markdown + switchToMarkdown: ( + + ), + //fa-solid fa-expand + gotoFullScreen: ( + + ), + //fa-solid fa-compress + exitFullScreen: ( + + ), + //fa-solid fa-book-open-reader + releaseNotes: ( + + ), + rawMode: ( + + ), + //fa-solid fa-glasses + parsedMode: ( + + ), + convertFile: ( + + ), + cog: ( + + ), + trayMode: ( + + ) +} \ No newline at end of file diff --git a/src/ExcalidrawView.ts b/src/ExcalidrawView.ts index b6700dc..b42aa49 100644 --- a/src/ExcalidrawView.ts +++ b/src/ExcalidrawView.ts @@ -76,7 +76,8 @@ import { FileData, } from "./EmbeddedFileLoader"; import { ScriptInstallPrompt } from "./ScriptInstallPrompt"; -import { ObsidianMenu, ToolsPanel } from "./ObsidianMenu"; +import { ObsidianMenu } from "./ObsidianMenu"; +import { ToolsPanel } from "./ToolsPanel"; export enum TextMode { parsed, diff --git a/src/ObsidianMenu.tsx b/src/ObsidianMenu.tsx index f543fa0..550c007 100644 --- a/src/ObsidianMenu.tsx +++ b/src/ObsidianMenu.tsx @@ -1,7 +1,11 @@ + + + import { AppState } from "@zsviczian/excalidraw/types/types"; import clsx from "clsx"; import { Notice, TFile } from "obsidian"; import * as React from "react"; +import { ICONS } from "./ActionIcons"; import { SCRIPT_INSTALL_FOLDER } from "./constants"; import { insertLaTeXToView, search } from "./ExcalidrawAutomate"; import ExcalidrawView, { TextMode } from "./ExcalidrawView"; @@ -10,525 +14,6 @@ import ExcalidrawPlugin from "./main"; import { ScriptIconMap } from "./Scripts"; import { getIMGFilename } from "./Utils"; -const dark = ' { - pos1: number = 0; - pos2: number = 0; - pos3: number = 0; - pos4: number = 0; - penDownX: number = 0; - penDownY: number = 0; - previousWidth: number = 0; - previousHeight: number = 0; - onRightEdge: boolean = false; - onBottomEdge: boolean = false; - private containerRef = React.createRef(); - - constructor(props: PanelProps) { - super(props); - this.state = { - visible: props.visible, - top: 50, - left: 200, - theme: "dark", - excalidrawViewMode: false, - minimized: false, - isFullscreen: false, - isPreviewMode: true, - scriptIconMap: {}, - } - } - - updateScriptIconMap(scriptIconMap:ScriptIconMap) { - this.setState(()=>{ - return {scriptIconMap} - }); - } - - setPreviewMode(isPreviewMode: boolean) { - this.setState(()=>{ - return { - isPreviewMode, - } - }) - } - - setFullscreen(isFullscreen: boolean) { - this.setState(()=>{ - return { - isFullscreen, - } - }) - } - - setExcalidrawViewMode(isViewModeEnabled: boolean) { - this.setState(()=>{ - return { - excalidrawViewMode: isViewModeEnabled, - } - }); - } - - toggleVisibility(isMobileOrZen: boolean) { - this.setTopCenter(isMobileOrZen); - this.setState((prevState: PanelState) => { - return { - visible: !prevState.visible - } - }); - } - - setTheme(theme:"dark"|"light") { - this.setState((prevState: PanelState) => { - return { - theme: theme - } - }); - } - - setTopCenter(isMobileOrZen:boolean) { - this.setState(()=>{ - return { - left: (this.containerRef.current.clientWidth - - TOOLS_PANEL_WIDTH - - (isMobileOrZen?0:TOOLS_PANEL_WIDTH+4) - ) / - 2+this.containerRef.current.parentElement.offsetLeft + - (isMobileOrZen?0:TOOLS_PANEL_WIDTH+4), - top: 64 + this.containerRef.current.parentElement.offsetTop - } - }) - } - - updatePosition(deltaY:number=0, deltaX:number=0) { - this.setState(()=>{ - const { - offsetTop, - offsetLeft, - clientWidth: width, - clientHeight: height - } = this.containerRef.current.firstElementChild as HTMLElement - - const top = offsetTop - deltaY; - const left = offsetLeft - deltaX; - - const { - clientWidth: parentWidth, - clientHeight: parentHeight, - offsetTop: parentOffsetTop, - offsetLeft: parentOffsetLeft - } = this.containerRef.current.parentElement; - - this.previousHeight = parentHeight; - this.previousWidth = parentWidth; - this.onBottomEdge = top >= parentHeight - height + parentOffsetTop; - this.onRightEdge = left >= parentWidth - width + parentOffsetLeft; - - return { - top: top < parentOffsetTop - ? parentOffsetTop - : this.onBottomEdge - ? parentHeight - height + parentOffsetTop - : top, - left: left < parentOffsetLeft - ? parentOffsetLeft - : this.onRightEdge - ? parentWidth - width + parentOffsetLeft - : left - } - }) - } - - render () { - return ( -
-
-
)=>{ - event.preventDefault(); - if(Math.abs(this.penDownX-this.pos3)>5 || Math.abs(this.penDownY-this.pos4)>5) return; - this.setState((prevState:PanelState)=>{ - return { - minimized: !prevState.minimized - } - }) - }} - - onPointerDown={(event: React.PointerEvent)=> { - const onDrag = (e:PointerEvent) => { - e.preventDefault(); - this.pos1 = this.pos3 - e.clientX; - this.pos2 = this.pos4 - e.clientY; - this.pos3 = e.clientX; - this.pos4 = e.clientY; - this.updatePosition(this.pos2,this.pos1) - } - - const onPointerUp = () => { - document.removeEventListener("pointerup",onPointerUp) - document.removeEventListener("pointermove",onDrag) - } - - event.preventDefault(); - this.penDownX = this.pos3 = event.clientX; - this.penDownY = this.pos4 = event.clientY; - document.addEventListener("pointerup",onPointerUp); - document.addEventListener("pointermove",onDrag); - }} - > - -
-
-
-
- Utility actions -
- { - search(this.props.view); - }} - icon={ICONS.search} - view={this.props.view} - /> - { - this.props.view.openAsMarkdown(); - }} - icon={ICONS.switchToMarkdown} - view={this.props.view} - /> - { this.state.isPreviewMode === null - ? ( { - this.props.view.convertExcalidrawToMD(); - }} - icon={ICONS.convertFile} - view={this.props.view} - />) - : ( { - if(this.state.isPreviewMode) { - this.props.view.changeTextMode(TextMode.raw); - } else { - this.props.view.changeTextMode(TextMode.parsed); - } - }} - icon={this.state.isPreviewMode ? ICONS.rawMode : ICONS.parsedMode} - view={this.props.view} - />) - } - { - this.props.view.toggleTrayMode(); - }} - icon={ICONS.trayMode} - view={this.props.view} - /> - { - if(this.state.isFullscreen) { - this.props.view.exitFullscreen(); - } else { - this.props.view.gotoFullscreen(); - } - }} - icon={this.state.isFullscreen ? ICONS.exitFullScreen : ICONS.gotoFullScreen} - view={this.props.view} - /> -
-
-
- Export actions -
- { - this.props.view.plugin.exportLibrary(); - }} - icon={ICONS.exportLibrary} - view={this.props.view} - /> - { - this.props.view.saveSVG(); - new Notice("File saved: " + getIMGFilename(this.props.view.file.path, "svg")); - }} - icon={ICONS.exportSVG} - view={this.props.view} - /> - { - this.props.view.savePNG(); - new Notice("File saved: "+getIMGFilename(this.props.view.file.path, "png")); - }} - icon={ICONS.exportPNG} - view={this.props.view} - /> - { - this.props.view.exportExcalidraw(); - }} - icon={ICONS.exportExcalidraw} - view={this.props.view} - /> -
-
-
- Insert actions -
- { - this.props.centerPointer(); - this.props.view.plugin.insertImageDialog.start( - this.props.view) - }} - icon={ICONS.insertImage} - view={this.props.view} - /> - { - this.props.centerPointer(); - this.props.view.plugin.insertMDDialog.start( - this.props.view) - }} - icon={ICONS.insertMD} - view={this.props.view} - /> - { - this.props.centerPointer(); - insertLaTeXToView(this.props.view) - }} - icon={ICONS.insertLaTeX} - view={this.props.view} - /> - { - this.props.centerPointer(); - this.props.view.plugin.insertLinkDialog.start( - this.props.view.file.path, - this.props.view.addText - ); - }} - icon={ICONS.insertLink} - view={this.props.view} - /> -
-
- {this.renderScriptButtons(false)} - {this.renderScriptButtons(true)} -
-
-
-
- ) - } - - private renderScriptButtons (isDownloaded:boolean) { - if(this.state.scriptIconMap === {}) { - return (""); - } - - const downloadedScriptsRoot = - `${this.props.view.plugin.settings - .scriptFolderPath}/${SCRIPT_INSTALL_FOLDER}/`; - - const filterCondition = (key:string):boolean => isDownloaded - ? key.startsWith(downloadedScriptsRoot) - : !key.startsWith(downloadedScriptsRoot); - - if(Object.keys(this.state.scriptIconMap) - .filter(k=>filterCondition(k)).length === 0 - ){ - return (""); - } - - return ( -
- {isDownloaded?"Downloaded":"User"} Scripts -
- {Object.keys(this.state.scriptIconMap) - .filter(k=>filterCondition(k)) - .sort() - .map((key:string) => - { - const f = this.props.view.app.vault.getAbstractFileByPath(key); - if(f && f instanceof TFile) { - this.props.view.plugin.scriptEngine.executeScript(this.props.view,f); - } - }} - icon={this.state.scriptIconMap[key].svgString - ? - : ICONS.cog} - view={this.props.view} - /> - )} -
-
- ) - } - -} - -type ButtonProps = { - title: string; - action: Function; - icon: JSX.Element; - //key: string; - view: ExcalidrawView; -} - -type ButtonState = { - visible: boolean; -} - -class ActionButton extends React.Component { - toastMessageTimeout:number = 0; - - constructor(props: ButtonProps) { - super(props); - this.state = { - visible: true, - } - } - - render() { - return ( - - - ); - } -} export class ObsidianMenu { plugin: ExcalidrawPlugin; toolsRef: React.MutableRefObject @@ -558,133 +43,4 @@ export class ObsidianMenu { ); } -} - -const ICONS = { - exportLibrary: ( - - ), - //far fa-image - insertImage: ( - - ), - //far fa-file-alt - insertMD: ( - - ), - //far fa-square-root-alt - insertLaTeX: ( - - ), - //fas fa-link - insertLink: ( - - ), - exportSVG: ( - - S - - ), - exportPNG: ( - - P - - ), - exportExcalidraw: ( - - ), - //fa-solid fa-magnifying-glass - search: ( - - ), - //fa-brands fa-markdown - switchToMarkdown: ( - - ), - //fa-solid fa-expand - gotoFullScreen: ( - - ), - //fa-solid fa-compress - exitFullScreen: ( - - ), - //fa-solid fa-book-open-reader - releaseNotes: ( - - ), - rawMode: ( - - ), - //fa-solid fa-glasses - parsedMode: ( - - ), - convertFile: ( - - ), - cog: ( - - ), - trayMode: ( - - ) } \ No newline at end of file diff --git a/src/ScriptInstallPrompt.ts b/src/ScriptInstallPrompt.ts index c3bcc63..cd66d11 100644 --- a/src/ScriptInstallPrompt.ts +++ b/src/ScriptInstallPrompt.ts @@ -3,7 +3,7 @@ import ExcalidrawPlugin from "./main"; import { errorlog, log } from "./Utils"; const URL = - "https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/index.md"; + "https://raw.githubusercontent.com/zsviczian/obsidian-excalidraw-plugin/master/ea-scripts/index-test.md"; export class ScriptInstallPrompt extends Modal { constructor(private plugin: ExcalidrawPlugin) { diff --git a/src/ToolsPanel.tsx b/src/ToolsPanel.tsx new file mode 100644 index 0000000..3c46031 --- /dev/null +++ b/src/ToolsPanel.tsx @@ -0,0 +1,473 @@ +import clsx from "clsx"; +import { Notice, TFile } from "obsidian"; +import * as React from "react"; +import { ActionButton } from "./ActionButton"; +import { ICONS } from "./ActionIcons"; +import { SCRIPT_INSTALL_FOLDER } from "./constants"; +import { insertLaTeXToView, search } from "./ExcalidrawAutomate"; +import ExcalidrawView, { TextMode } from "./ExcalidrawView"; +import { t } from "./lang/helpers"; +import { ScriptIconMap } from "./Scripts"; +import { getIMGFilename } from "./Utils"; + +const dark = ' { + pos1: number = 0; + pos2: number = 0; + pos3: number = 0; + pos4: number = 0; + penDownX: number = 0; + penDownY: number = 0; + previousWidth: number = 0; + previousHeight: number = 0; + onRightEdge: boolean = false; + onBottomEdge: boolean = false; + private containerRef = React.createRef(); + + constructor(props: PanelProps) { + super(props); + this.state = { + visible: props.visible, + top: 50, + left: 200, + theme: "dark", + excalidrawViewMode: false, + minimized: false, + isFullscreen: false, + isPreviewMode: true, + scriptIconMap: {}, + } + } + + updateScriptIconMap(scriptIconMap:ScriptIconMap) { + this.setState(()=>{ + return {scriptIconMap} + }); + } + + setPreviewMode(isPreviewMode: boolean) { + this.setState(()=>{ + return { + isPreviewMode, + } + }) + } + + setFullscreen(isFullscreen: boolean) { + this.setState(()=>{ + return { + isFullscreen, + } + }) + } + + setExcalidrawViewMode(isViewModeEnabled: boolean) { + this.setState(()=>{ + return { + excalidrawViewMode: isViewModeEnabled, + } + }); + } + + toggleVisibility(isMobileOrZen: boolean) { + this.setTopCenter(isMobileOrZen); + this.setState((prevState: PanelState) => { + return { + visible: !prevState.visible + } + }); + } + + setTheme(theme:"dark"|"light") { + this.setState((prevState: PanelState) => { + return { + theme: theme + } + }); + } + + setTopCenter(isMobileOrZen:boolean) { + this.setState(()=>{ + return { + left: (this.containerRef.current.clientWidth - + TOOLS_PANEL_WIDTH - + (isMobileOrZen?0:TOOLS_PANEL_WIDTH+4) + ) / + 2+this.containerRef.current.parentElement.offsetLeft + + (isMobileOrZen?0:TOOLS_PANEL_WIDTH+4), + top: 64 + this.containerRef.current.parentElement.offsetTop + } + }) + } + + updatePosition(deltaY:number=0, deltaX:number=0) { + this.setState(()=>{ + const { + offsetTop, + offsetLeft, + clientWidth: width, + clientHeight: height + } = this.containerRef.current.firstElementChild as HTMLElement + + const top = offsetTop - deltaY; + const left = offsetLeft - deltaX; + + const { + clientWidth: parentWidth, + clientHeight: parentHeight, + offsetTop: parentOffsetTop, + offsetLeft: parentOffsetLeft + } = this.containerRef.current.parentElement; + + this.previousHeight = parentHeight; + this.previousWidth = parentWidth; + this.onBottomEdge = top >= parentHeight - height + parentOffsetTop; + this.onRightEdge = left >= parentWidth - width + parentOffsetLeft; + + return { + top: top < parentOffsetTop + ? parentOffsetTop + : this.onBottomEdge + ? parentHeight - height + parentOffsetTop + : top, + left: left < parentOffsetLeft + ? parentOffsetLeft + : this.onRightEdge + ? parentWidth - width + parentOffsetLeft + : left + } + }) + } + + render () { + return ( +
+
+
)=>{ + event.preventDefault(); + if(Math.abs(this.penDownX-this.pos3)>5 || Math.abs(this.penDownY-this.pos4)>5) return; + this.setState((prevState:PanelState)=>{ + return { + minimized: !prevState.minimized + } + }) + }} + + onPointerDown={(event: React.PointerEvent)=> { + const onDrag = (e:PointerEvent) => { + e.preventDefault(); + this.pos1 = this.pos3 - e.clientX; + this.pos2 = this.pos4 - e.clientY; + this.pos3 = e.clientX; + this.pos4 = e.clientY; + this.updatePosition(this.pos2,this.pos1) + } + + const onPointerUp = () => { + document.removeEventListener("pointerup",onPointerUp) + document.removeEventListener("pointermove",onDrag) + } + + event.preventDefault(); + this.penDownX = this.pos3 = event.clientX; + this.penDownY = this.pos4 = event.clientY; + document.addEventListener("pointerup",onPointerUp); + document.addEventListener("pointermove",onDrag); + }} + > + +
+
+
+
+ Utility actions +
+ { + search(this.props.view); + }} + icon={ICONS.search} + view={this.props.view} + /> + { + this.props.view.openAsMarkdown(); + }} + icon={ICONS.switchToMarkdown} + view={this.props.view} + /> + { this.state.isPreviewMode === null + ? ( { + this.props.view.convertExcalidrawToMD(); + }} + icon={ICONS.convertFile} + view={this.props.view} + />) + : ( { + if(this.state.isPreviewMode) { + this.props.view.changeTextMode(TextMode.raw); + } else { + this.props.view.changeTextMode(TextMode.parsed); + } + }} + icon={this.state.isPreviewMode ? ICONS.rawMode : ICONS.parsedMode} + view={this.props.view} + />) + } + { + this.props.view.toggleTrayMode(); + }} + icon={ICONS.trayMode} + view={this.props.view} + /> + { + if(this.state.isFullscreen) { + this.props.view.exitFullscreen(); + } else { + this.props.view.gotoFullscreen(); + } + }} + icon={this.state.isFullscreen ? ICONS.exitFullScreen : ICONS.gotoFullScreen} + view={this.props.view} + /> +
+
+
+ Export actions +
+ { + this.props.view.plugin.exportLibrary(); + }} + icon={ICONS.exportLibrary} + view={this.props.view} + /> + { + this.props.view.saveSVG(); + new Notice("File saved: " + getIMGFilename(this.props.view.file.path, "svg")); + }} + icon={ICONS.exportSVG} + view={this.props.view} + /> + { + this.props.view.savePNG(); + new Notice("File saved: "+getIMGFilename(this.props.view.file.path, "png")); + }} + icon={ICONS.exportPNG} + view={this.props.view} + /> + { + this.props.view.exportExcalidraw(); + }} + icon={ICONS.exportExcalidraw} + view={this.props.view} + /> +
+
+
+ Insert actions +
+ { + this.props.centerPointer(); + this.props.view.plugin.insertImageDialog.start( + this.props.view) + }} + icon={ICONS.insertImage} + view={this.props.view} + /> + { + this.props.centerPointer(); + this.props.view.plugin.insertMDDialog.start( + this.props.view) + }} + icon={ICONS.insertMD} + view={this.props.view} + /> + { + this.props.centerPointer(); + insertLaTeXToView(this.props.view) + }} + icon={ICONS.insertLaTeX} + view={this.props.view} + /> + { + this.props.centerPointer(); + this.props.view.plugin.insertLinkDialog.start( + this.props.view.file.path, + this.props.view.addText + ); + }} + icon={ICONS.insertLink} + view={this.props.view} + /> +
+
+ {this.renderScriptButtons(false)} + {this.renderScriptButtons(true)} +
+
+
+
+ ) + } + + private renderScriptButtons (isDownloaded:boolean) { + if(Object.keys(this.state.scriptIconMap).length === 0) { + return (""); + } + + const downloadedScriptsRoot = + `${this.props.view.plugin.settings + .scriptFolderPath}/${SCRIPT_INSTALL_FOLDER}/`; + + const filterCondition = (key:string):boolean => isDownloaded + ? key.startsWith(downloadedScriptsRoot) + : !key.startsWith(downloadedScriptsRoot); + + if(Object.keys(this.state.scriptIconMap) + .filter(k=>filterCondition(k)).length === 0 + ){ + return (""); + } + + return ( +
+ {isDownloaded?"Downloaded":"User"} Scripts +
+ {Object.keys(this.state.scriptIconMap) + .filter(k=>filterCondition(k)) + .sort() + .map((key:string) => + { + const f = this.props.view.app.vault.getAbstractFileByPath(key); + if(f && f instanceof TFile) { + this.props.view.plugin.scriptEngine.executeScript(this.props.view,f); + } + }} + icon={this.state.scriptIconMap[key].svgString + ? + : ICONS.cog} + view={this.props.view} + /> + )} +
+
+ ) + } +} \ No newline at end of file diff --git a/src/actionIcons.ts b/src/actionIcons.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/main.ts b/src/main.ts index cee2186..b06e372 100644 --- a/src/main.ts +++ b/src/main.ts @@ -374,6 +374,9 @@ export default class ExcalidrawPlugin extends Plugin { const download = async (url:string, file:TFile, localPath: string):Promise => { const data = await request({ url }); + if(!data || data.startsWith("404: Not Found")) { + return null; + } if (file) { await this.app.vault.modify(file as TFile, data); } else { @@ -385,6 +388,10 @@ export default class ExcalidrawPlugin extends Plugin { try { scriptFile = await download(source,scriptFile as TFile,scriptPath); + if(!scriptFile) { + setButtonText("ERROR"); + throw("File not found"); + }; svgFile = await download(getIMGFilename(source,"svg"),svgFile as TFile,svgPath); setButtonText("UPTODATE"); new Notice(`Installed: ${(scriptFile as TFile).basename}`); diff --git a/styles.css b/styles.css index 98f9bee..890e96c 100644 --- a/styles.css +++ b/styles.css @@ -189,4 +189,15 @@ li[data-testid] { .excalidraw .ToolIcon__icon img{ height: 1em; -} \ No newline at end of file +} + +.excalidraw-scriptengine-install tbody>tr>td>div>img { + height:20px; + background-color: silver; + padding: 2px; +} + +.excalidraw-scriptengine-install tbody>tr>td>div { + width: 50px; + display: inline-block; +}