Compare commits

...

2 Commits

Author SHA1 Message Date
Elian Doran
47a7fb5708 feat(client/mermaid): integrate same pan/zoom 2026-01-05 22:11:07 +02:00
Elian Doran
a7c0c52610 feat(client/image): integrate a better image viewer 2026-01-05 22:00:47 +02:00
4 changed files with 67 additions and 41 deletions

View File

@@ -62,6 +62,7 @@
"preact": "10.28.1", "preact": "10.28.1",
"react-i18next": "16.5.1", "react-i18next": "16.5.1",
"react-window": "2.2.3", "react-window": "2.2.3",
"react-zoom-pan-pinch": "3.7.0",
"reveal.js": "5.2.1", "reveal.js": "5.2.1",
"svg-pan-zoom": "3.6.2", "svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1", "tabulator-tables": "6.3.1",

View File

@@ -1,29 +1,20 @@
import "./Image.css";
import { useEffect, useRef, useState } from "preact/hooks"; import { useEffect, useRef, useState } from "preact/hooks";
import { TransformComponent,TransformWrapper } from "react-zoom-pan-pinch";
import image_context_menu from "../../menus/image_context_menu";
import { copyImageReferenceToClipboard } from "../../services/image";
import { createImageSrcUrl } from "../../services/utils"; import { createImageSrcUrl } from "../../services/utils";
import { useTriliumEvent, useUniqueName } from "../react/hooks"; import { useTriliumEvent, useUniqueName } from "../react/hooks";
import "./Image.css";
import { TypeWidgetProps } from "./type_widget";
import WheelZoom from 'vanilla-js-wheel-zoom';
import image_context_menu from "../../menus/image_context_menu";
import { refToJQuerySelector } from "../react/react_utils"; import { refToJQuerySelector } from "../react/react_utils";
import { copyImageReferenceToClipboard } from "../../services/image"; import { TypeWidgetProps } from "./type_widget";
export default function Image({ note, ntxId }: TypeWidgetProps) { export default function Image({ note, ntxId }: TypeWidgetProps) {
const uniqueId = useUniqueName("image"); const uniqueId = useUniqueName("image");
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const [ refreshCounter, setRefreshCounter ] = useState(0); const [ refreshCounter, setRefreshCounter ] = useState(0);
// Set up pan & zoom
useEffect(() => {
const zoomInstance = WheelZoom.create(`#${uniqueId}`, {
maxScale: 50,
speed: 1.3,
zoomOnClick: false
});
return () => zoomInstance.destroy();
}, [ note ]);
// Set up context menu // Set up context menu
useEffect(() => image_context_menu.setupContextMenu(refToJQuerySelector(containerRef)), []); useEffect(() => image_context_menu.setupContextMenu(refToJQuerySelector(containerRef)), []);
@@ -42,11 +33,23 @@ export default function Image({ note, ntxId }: TypeWidgetProps) {
return ( return (
<div ref={containerRef} className="note-detail-image-wrapper"> <div ref={containerRef} className="note-detail-image-wrapper">
<img <TransformWrapper
id={uniqueId} initialScale={1}
className="note-detail-image-view" centerOnInit
src={createImageSrcUrl(note)} >
/> <TransformComponent
wrapperStyle={{
width: "100%",
height: "100%"
}}
>
<img
id={uniqueId}
className="note-detail-image-view"
src={createImageSrcUrl(note)}
/>
</TransformComponent>
</TransformWrapper>
</div> </div>
) );
} }

View File

@@ -1,13 +1,15 @@
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import { t } from "../../../services/i18n";
import SplitEditor, { PreviewButton, SplitEditorProps } from "./SplitEditor";
import { RawHtmlBlock } from "../../react/RawHtml";
import server from "../../../services/server";
import svgPanZoom from "svg-pan-zoom";
import { RefObject } from "preact"; import { RefObject } from "preact";
import { useElementSize, useTriliumEvent } from "../../react/hooks"; import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import utils from "../../../services/utils"; import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import svgPanZoom from "svg-pan-zoom";
import { t } from "../../../services/i18n";
import server from "../../../services/server";
import toast from "../../../services/toast"; import toast from "../../../services/toast";
import utils from "../../../services/utils";
import { useElementSize, useTriliumEvent } from "../../react/hooks";
import { RawHtmlBlock } from "../../react/RawHtml";
import SplitEditor, { PreviewButton, SplitEditorProps } from "./SplitEditor";
interface SvgSplitEditorProps extends Omit<SplitEditorProps, "previewContent"> { interface SvgSplitEditorProps extends Omit<SplitEditorProps, "previewContent"> {
/** /**
@@ -117,11 +119,20 @@ export default function SvgSplitEditor({ ntxId, note, attachmentName, renderSvg,
onContentChanged={onContentChanged} onContentChanged={onContentChanged}
dataSaved={onSave} dataSaved={onSave}
previewContent={( previewContent={(
<RawHtmlBlock <TransformWrapper>
className="render-container" <TransformComponent
containerRef={containerRef} wrapperStyle={{
html={svg} width: "100%",
/> height: "100%"
}}
>
<RawHtmlBlock
className="render-container"
containerRef={containerRef}
html={svg}
/>
</TransformComponent>
</TransformWrapper>
)} )}
previewButtons={ previewButtons={
<> <>
@@ -144,7 +155,7 @@ export default function SvgSplitEditor({ ntxId, note, attachmentName, renderSvg,
} }
{...props} {...props}
/> />
) );
} }
function useResizer(containerRef: RefObject<HTMLDivElement>, noteId: string, svg: string | undefined) { function useResizer(containerRef: RefObject<HTMLDivElement>, noteId: string, svg: string | undefined) {
@@ -181,7 +192,7 @@ function useResizer(containerRef: RefObject<HTMLDivElement>, noteId: string, svg
lastPanZoom.current = { lastPanZoom.current = {
pan: zoomInstance.getPan(), pan: zoomInstance.getPan(),
zoom: zoomInstance.getZoom() zoom: zoomInstance.getZoom()
} };
zoomRef.current = undefined; zoomRef.current = undefined;
zoomInstance.destroy(); zoomInstance.destroy();
}; };

19
pnpm-lock.yaml generated
View File

@@ -295,6 +295,9 @@ importers:
react-window: react-window:
specifier: 2.2.3 specifier: 2.2.3
version: 2.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) version: 2.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react-zoom-pan-pinch:
specifier: 3.7.0
version: 3.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
reveal.js: reveal.js:
specifier: 5.2.1 specifier: 5.2.1
version: 5.2.1 version: 5.2.1
@@ -12144,6 +12147,13 @@ packages:
react: ^18.0.0 || ^19.0.0 react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0
react-zoom-pan-pinch@3.7.0:
resolution: {integrity: sha512-UmReVZ0TxlKzxSbYiAj+LeGRW8s8LraAFTXRAxzMYnNRgGPsxCudwZKVkjvGmjtx7SW/hZamt69NUmGf4xrkXA==}
engines: {node: '>=8', npm: '>=5'}
peerDependencies:
react: '*'
react-dom: '*'
react@16.14.0: react@16.14.0:
resolution: {integrity: sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==} resolution: {integrity: sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -15361,8 +15371,6 @@ snapshots:
'@ckeditor/ckeditor5-core': 47.3.0 '@ckeditor/ckeditor5-core': 47.3.0
'@ckeditor/ckeditor5-utils': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0 ckeditor5: 47.3.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)': '@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
dependencies: dependencies:
@@ -15428,8 +15436,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.3.0 '@ckeditor/ckeditor5-utils': 47.3.0
'@ckeditor/ckeditor5-watchdog': 47.3.0 '@ckeditor/ckeditor5-watchdog': 47.3.0
es-toolkit: 1.39.5 es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-dev-build-tools@54.2.3(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)': '@ckeditor/ckeditor5-dev-build-tools@54.2.3(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)':
dependencies: dependencies:
@@ -28467,6 +28473,11 @@ snapshots:
react: 19.2.3 react: 19.2.3
react-dom: 19.2.3(react@19.2.3) react-dom: 19.2.3(react@19.2.3)
react-zoom-pan-pinch@3.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
react@16.14.0: react@16.14.0:
dependencies: dependencies:
loose-envify: 1.4.0 loose-envify: 1.4.0