From a28ade9f2a66c98908080794dfda98b2e9bda30e Mon Sep 17 00:00:00 2001 From: xiangyu <3170102889@zju.edu.cn> Date: Thu, 17 Nov 2022 17:26:57 +0800 Subject: [PATCH] add: dblclick to preview images --- addon/chrome/content/imageViewer.html | 96 +++++++++++++++ src/addon.ts | 3 + src/editor/imageViewerWindow.ts | 164 ++++++++++++++++++++++++++ src/zotero/events.ts | 13 ++ 4 files changed, 276 insertions(+) create mode 100644 addon/chrome/content/imageViewer.html create mode 100644 src/editor/imageViewerWindow.ts diff --git a/addon/chrome/content/imageViewer.html b/addon/chrome/content/imageViewer.html new file mode 100644 index 0000000..9beb70e --- /dev/null +++ b/addon/chrome/content/imageViewer.html @@ -0,0 +1,96 @@ + + + + Image Preview + + + + + +
+ +
+
+
+ + Previous +
+
+ + Next +
+
+ + diff --git a/src/addon.ts b/src/addon.ts index 4ae645a..3592a9f 100644 --- a/src/addon.ts +++ b/src/addon.ts @@ -19,6 +19,7 @@ import NoteExportWindow from "./note/noteExportWindow"; import NoteExport from "./note/noteExportController"; import EditorViews from "./editor/editorViews"; import EditorController from "./editor/editorController"; +import EditorImageViewer from "./editor/imageViewerWindow"; import TemplateWindow from "./template/templateWindow"; class Knowledge4Zotero { @@ -49,6 +50,7 @@ class Knowledge4Zotero { public NoteParse: NoteParse; public EditorViews: EditorViews; public EditorController: EditorController; + public EditorImageViewer: EditorImageViewer; constructor() { this.ZoteroEvents = new ZoteroEvents(this); @@ -59,6 +61,7 @@ class Knowledge4Zotero { this.WorkspaceMenu = new WorkspaceMenu(this); this.EditorViews = new EditorViews(this); this.EditorController = new EditorController(this); + this.EditorImageViewer = new EditorImageViewer(this); this.WizardWindow = new WizardWindow(this); this.SyncInfoWindow = new SyncInfoWindow(this); this.SyncListWindow = new SyncListWindow(this); diff --git a/src/editor/imageViewerWindow.ts b/src/editor/imageViewerWindow.ts new file mode 100644 index 0000000..0ab333a --- /dev/null +++ b/src/editor/imageViewerWindow.ts @@ -0,0 +1,164 @@ +/* + * This file contains image viewer for note editor. + */ + +import Knowledge4Zotero from "../addon"; +import AddonBase from "../module"; + +class EditorImageViewer extends AddonBase { + _window: Window; + scaling: number; + srcList: string[]; + idx: number; + title: string; + anchorPosition: { + left: number; + top: number; + }; + constructor(parent: Knowledge4Zotero) { + super(parent); + this.scaling = 1; + this.title = "Note"; + } + + async onInit(srcs: string[], idx: number, title: string) { + if (!this._window || this._window.closed) { + this._window = window.open( + "chrome://Knowledge4Zotero/content/imageViewer.html", + "", + "chrome,centerscreen,resizable,status,width=400,height=450" + ); + let t = 0; + console.log(this._window.document?.readyState); + // Wait for window + while (t < 500 && this._window.document.readyState !== "complete") { + console.log(this._window.document?.readyState); + await Zotero.Promise.delay(10); + t += 1; + } + + this._window.document + .querySelector("#left") + .addEventListener("click", (e) => { + this.setIndex("left"); + }); + this._window.document + .querySelector("#right") + .addEventListener("click", (e) => { + this.setIndex("right"); + }); + const container = this._window.document.querySelector( + ".container" + ) as HTMLDivElement; + const img = this._window.document.querySelector( + "#image" + ) as HTMLImageElement; + this._window.addEventListener("keydown", (e) => { + // ctrl+w or esc + if ((e.key === "w" && e.ctrlKey) || e.keyCode === 27) { + this._window.close(); + } + this.anchorPosition = { + left: img.scrollWidth / 2 - container.scrollLeft / 2, + top: img.scrollHeight / 2 - container.scrollLeft / 2, + }; + if (e.keyCode === 37 || e.keyCode === 40) { + this.setIndex("left"); + } + if (e.keyCode === 38 || e.keyCode === 39) { + this.setIndex("right"); + } + if (e.key === "0") { + this.setScale(1); + } else if (e.keyCode === 107 || e.keyCode === 187 || e.key === "=") { + this.setScale(this.scaling * 1.1); + } else if (e.key === "-") { + this.setScale(this.scaling / 1.1); + } + }); + this._window.addEventListener("wheel", async (e) => { + this.anchorPosition = { + left: e.pageX - container.offsetLeft, + top: e.pageY - container.offsetTop, + }; + function normalizeWheelEventDirection(evt) { + let delta = Math.hypot(evt.deltaX, evt.deltaY); + const angle = Math.atan2(evt.deltaY, evt.deltaX); + if (-0.25 * Math.PI < angle && angle < 0.75 * Math.PI) { + // All that is left-up oriented has to change the sign. + delta = -delta; + } + return delta; + } + if (e.ctrlKey) { + const delta = normalizeWheelEventDirection(e); + this.setScale( + this.scaling * + Math.pow(delta > 0 ? 1.1 : 1 / 1.1, Math.round(Math.abs(delta))) + ); + } + }); + } + + this.srcList = srcs; + this.idx = idx; + this.title = title || "Note"; + this.setImage(); + this.setScale(1); + this._window.focus(); + } + + private setImage() { + (this._window.document.querySelector("#image") as HTMLImageElement).src = + this.srcList[this.idx]; + this.setTitle(); + ( + this._window.document.querySelector("#left-container") as HTMLButtonElement + ).style.visibility = this.idx === 0 ? "hidden" : "visible"; + ( + this._window.document.querySelector("#right-container") as HTMLButtonElement + ).style.visibility = + this.idx === this.srcList.length - 1 ? "hidden" : "visible"; + } + + private setIndex(type: "left" | "right") { + if (type === "left") { + this.idx > 0 ? (this.idx -= 1) : undefined; + } + if (type === "right") { + this.idx < this.srcList.length - 1 ? (this.idx += 1) : undefined; + } + this.setImage(); + } + + private setScale(scaling: number) { + const oldScale = this.scaling; + this.scaling = scaling; + if (this.scaling > 10) { + this.scaling = 10; + } + if (this.scaling < 0.1) { + this.scaling = 0.1; + } + const container = this._window.document.querySelector( + ".container" + ) as HTMLDivElement; + ( + this._window.document.querySelector("#image") as HTMLImageElement + ).style.width = `calc(100% * ${this.scaling})`; + if (this.scaling > 1) { + container.scrollLeft += + this.anchorPosition.left * (this.scaling - oldScale); + container.scrollTop += + this.anchorPosition.top * (this.scaling - oldScale); + } + } + + private setTitle() { + this._window.document.querySelector("title").innerText = `${this.idx + 1}/${ + this.srcList.length + }:${this.title}`; + } +} + +export default EditorImageViewer; diff --git a/src/zotero/events.ts b/src/zotero/events.ts index 1604155..1a79321 100644 --- a/src/zotero/events.ts +++ b/src/zotero/events.ts @@ -319,6 +319,7 @@ class ZoteroEvents extends AddonBase { Zotero.debug(`Knowledge4Zotero: note editor initialized.`); if (!instance._knowledgeSelectionInitialized) { + // Put event listeners here to access Zotero instance instance._iframeWindow.document.addEventListener( "selectionchange", async (e) => { @@ -412,6 +413,18 @@ class ZoteroEvents extends AddonBase { } } ); + instance._iframeWindow.document.addEventListener("dblclick", (e) => { + if ((e.target as HTMLElement).tagName === "IMG") { + const imgs = instance._iframeWindow.document + .querySelector(".primary-editor") + ?.querySelectorAll("img"); + this._Addon.EditorImageViewer.onInit( + Array.prototype.map.call(imgs, (e: HTMLImageElement) => e.src), + Array.prototype.indexOf.call(imgs, e.target), + instance._item.getNoteTitle() + ); + } + }); instance._knowledgeSelectionInitialized = true; }