/* * This file contains image viewer for note editor. */ import Knowledge4Zotero from "../addon"; import AddonBase from "../module"; import { CopyHelper, pick } from "../utils"; class EditorImageViewer extends AddonBase { _window: Window; scaling: number; srcList: string[]; idx: number; title: string; pined: boolean; anchorPosition: { left: number; top: number; }; icons: any; constructor(parent: Knowledge4Zotero) { super(parent); this.scaling = 1; this.title = "Note"; this.pined = false; this.icons = { pin: ``, pined: ``, }; } async onInit(srcs: string[], idx: number, title: string, pined: boolean) { if (!this._window || this._window.closed) { this._window = window.open( "chrome://Knowledge4Zotero/content/imageViewer.html", "betternotes-note-imagepreview", `chrome,centerscreen,resizable,status,width=500,height=550${ pined ? ",alwaysRaised=yes" : "" }` ); let t = 0; // Wait for window while (t < 500 && this._window.document.readyState !== "complete") { await Zotero.Promise.delay(10); t += 1; } const container = this._window.document.querySelector( ".container" ) as HTMLDivElement; const img = this._window.document.querySelector( "#image" ) as HTMLImageElement; this._window.document .querySelector("#left") .addEventListener("click", (e) => { this.setIndex("left"); }); this._window.document .querySelector("#bigger") .addEventListener("click", (e) => { this.anchorPosition = { left: img.scrollWidth / 2 - container.scrollLeft / 2, top: img.scrollHeight / 2 - container.scrollLeft / 2, }; this.setScale(this.scaling * 1.1); }); this._window.document .querySelector("#smaller") .addEventListener("click", (e) => { this.anchorPosition = { left: img.scrollWidth / 2 - container.scrollLeft / 2, top: img.scrollHeight / 2 - container.scrollLeft / 2, }; this.setScale(this.scaling / 1.1); }); this._window.document .querySelector("#resetwidth") .addEventListener("click", (e) => { this.setScale(1); }); this._window.document .querySelector("#right") .addEventListener("click", (e) => { this.setIndex("right"); }); this._window.document .querySelector("#copy") .addEventListener("click", (e) => { new CopyHelper().addImage(this.srcList[this.idx]).copy(); this._Addon.ZoteroViews.showProgressWindow( "Better Notes", "Image Copied." ); }); this._window.document .querySelector("#save") .addEventListener("click", async (e) => { let parts = this.srcList[this.idx].split(","); if (!parts[0].includes("base64")) { return; } let mime = parts[0].match(/:(.*?);/)[1]; let bstr = atob(parts[1]); let n = bstr.length; let u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } let ext = Zotero.MIME.getPrimaryExtension(mime, ""); const filename = await pick( Zotero.getString("noteEditor.saveImageAs"), "save", [[`Image(*.${ext})`, `*.${ext}`]], `${Zotero.getString("fileTypes.image").toLowerCase()}.${ext}` ); if (filename) { await OS.File.writeAtomic( this._Addon.NoteUtils.formatPath(filename), u8arr ); } this._Addon.ZoteroViews.showProgressWindow( "Better Notes", `Image Saved to ${filename}` ); }); this._window.document.querySelector("#pin").innerHTML = this.icons[pined ? "pined" : "pin"]; this._window.document.querySelector("#pin-tooltip").innerHTML = pined ? "Unpin" : "Pin"; this._window.document .querySelector("#pin") .addEventListener("click", (e) => { this.setPin(); }); 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; } const delta = normalizeWheelEventDirection(e); if (e.ctrlKey) { this.setScale( this.scaling * Math.pow(delta > 0 ? 1.1 : 1 / 1.1, Math.round(Math.abs(delta))) ); } else if (e.shiftKey) { container.scrollLeft -= delta * 10; } else { container.scrollLeft += e.deltaX * 10; container.scrollTop += e.deltaY * 10; } }); img.addEventListener("mousedown", (e) => { e.preventDefault(); // if (this.scaling <= 1) { // return; // } img.onmousemove = (e) => { e.preventDefault(); container.scrollLeft -= e.movementX; container.scrollTop -= e.movementY; }; img.onmouseleave = () => { img.onmousemove = null; img.onmouseup = null; }; img.onmouseup = () => { img.onmousemove = null; img.onmouseup = null; }; }); } this.srcList = srcs; this.idx = idx; this.title = title || "Note"; this.pined = pined; 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.opacity = this.idx === 0 ? "0.5" : "1"; ( this._window.document.querySelector( "#right-container" ) as HTMLButtonElement ).style.opacity = this.idx === this.srcList.length - 1 ? "0.5" : "1"; } 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); } ( this._window.document.querySelector( "#bigger-container" ) as HTMLButtonElement ).style.opacity = this.scaling === 10 ? "0.5" : "1"; ( this._window.document.querySelector( "#smaller-container" ) as HTMLButtonElement ).style.opacity = this.scaling === 0.1 ? "0.5" : "1"; // ( // this._window.document.querySelector("#image") as HTMLImageElement // ).style.cursor = this.scaling <= 1 ? "default" : "move"; } private setTitle() { this._window.document.querySelector("title").innerText = `${this.idx + 1}/${ this.srcList.length }:${this.title}`; } private setPin() { this._window.close(); this.onInit(this.srcList, this.idx, this.title, !this.pined); } } export default EditorImageViewer;