From 478505e9e692fc81adaa01315e3a1b86698a7c54 Mon Sep 17 00:00:00 2001 From: Jacob Benz <jacob.benz@h-da.de> Date: Fri, 25 Apr 2025 09:15:56 +0200 Subject: [PATCH] inital work second window image viewer --- .../src/pages/imageviewer/imageViewer.css | 63 ++++++++++++ apps/commons/src/pages/imageviewer/index.tsx | 95 +++++++++++++++++++ apps/commons/src/pages/index.ts | 1 + apps/commons/src/routes.tsx | 3 +- .../src/components/editorToolbar/index.tsx | 34 +++++++ .../src/js/layout/panels/imageViewer/index.ts | 17 ++++ 6 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 apps/commons/src/pages/imageviewer/imageViewer.css create mode 100644 apps/commons/src/pages/imageviewer/index.tsx diff --git a/apps/commons/src/pages/imageviewer/imageViewer.css b/apps/commons/src/pages/imageviewer/imageViewer.css new file mode 100644 index 00000000..74500324 --- /dev/null +++ b/apps/commons/src/pages/imageviewer/imageViewer.css @@ -0,0 +1,63 @@ +.imageViewer { + display: table; + height: 100%; + width: 100%; +} + +.imageViewer .toolbar { + display: flex; + justify-content: space-between; + align-items: center; + height: 32px; + padding: 8px; + margin-top: 4px; +} + +.imageViewer .image { + display: table-row; + height: 100%; +} + +.imageViewer .toolbar .navigation { + display: flex; + align-items: center; + gap: 8px; +} + +.imageViewer .toolbar .zoom { + display: flex; + align-items: center; + gap: 8px; +} + +.imageViewer .lw-button { + display: flex !important; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 3px; +} + +.imageViewer .lw-button:hover { + cursor: pointer; + background-color: rgba(0, 0, 0, .1); + /* border: 1px solid #aaa; */ +} + +.imageViewer .pageInfo { + /* display: block; + position: relative; + float: right; + margin: 5px 3px; */ +} + +.imageViewer input.currPage { + height: 20px; + width: 20px; + text-align: right; +} + +.imageViewer .openseadragon-message { + white-space: pre; +} \ No newline at end of file diff --git a/apps/commons/src/pages/imageviewer/index.tsx b/apps/commons/src/pages/imageviewer/index.tsx new file mode 100644 index 00000000..47313a84 --- /dev/null +++ b/apps/commons/src/pages/imageviewer/index.tsx @@ -0,0 +1,95 @@ +import { Box, Link, Stack, Typography } from '@mui/material'; +import { Logo } from '@src/components'; +import { Page } from '@src/layouts'; +import { Trans, useTranslation } from 'react-i18next'; +import { Link as RouterLink } from 'react-router'; +import { BroadcastChannel } from 'broadcast-channel'; +import { useEffect, useState } from 'react'; +import OpenSeaDragon from 'openseadragon'; +import './imageViewer.css' + +export const ImageViewerPage = () => { + const { t } = useTranslation(); + const channel = new BroadcastChannel('imageViewer-channel'); + const [images, setImages] = useState("") + const id: string = "LEAF-Wwriter-SepImageViewer" + //var viewer: any; + + useEffect(() => { + const handleMessage = (event) => { + console.log(event) + if (event.tileSources) { + setImages(event.tileSources); + } + }; + + channel.onmessage = handleMessage; + + // Clean up the channel when the component unmounts + return () => { + channel.close(); + }; + }, [channel]); + + useEffect(() => { + channel.postMessage({imageViewerWindowReady: true}) + console.log("HIU") + }, []) + + useEffect(() => { + console.log(images) + }, [images]) + + useEffect(() => { + + let viewer = OpenSeaDragon({ + id: 'seadragon-viewer', + sequenceMode: true, + autoHideControls: false, + showFullPageControl: false, + previousButton: `${id}_prev`, + nextButton: `${id}_next`, + zoomInButton: `${id}_zoomIn`, + zoomOutButton: `${id}_zoomOut`, + homeButton: `${id}_home`, + }); + console.log(images) + viewer.open(images) + + // Cleanup (equal to componentWillUnmount) + return () => { + viewer.destroy(); + viewer = null; + }; + }, [images]); + + return ( + <div id={id} className="imageViewer"> + <div className="toolbar"> + <div className="navigation"> + <span id={id + "_prev"} className="lw-button"> + <i className="fas fa-arrow-left"></i> + </span> + <span id={id + "_next"} className="lw-button"> + <i className="fas fa-arrow-right"></i> + </span> + <span className="pageInfo"> + <input type="text" className="currPage" /> / <span className="totalPages"/> + </span> + </div> + <div className="zoom"> + <span id={id + "_zoomIn"} className="lw-button"> + <i className="fas fa-search-plus"></i> + </span> + <span id={id + "_zoomOut"} className="lw-button"> + <i className="fas fa-search-minus"></i> + </span> + <span id={id + "_home"} className="lw-button"> + <i className="fas fa-compress"></i> + </span> + </div> + </div> + <div id="seadragon-viewer" className="seadragon-viewer" /> + </div> + ); +}; diff --git a/apps/commons/src/pages/index.ts b/apps/commons/src/pages/index.ts index 56bd97c3..9d36d32d 100644 --- a/apps/commons/src/pages/index.ts +++ b/apps/commons/src/pages/index.ts @@ -2,3 +2,4 @@ export * from './LinkAccounts'; export * from './edit'; export * from './error/NotFoundView'; export * from './home'; +export * from './imageviewer'; diff --git a/apps/commons/src/routes.tsx b/apps/commons/src/routes.tsx index 071dd8a8..550fef73 100644 --- a/apps/commons/src/routes.tsx +++ b/apps/commons/src/routes.tsx @@ -1,5 +1,5 @@ import { BasicLayout } from './layouts'; -import { EditPage, HomePage, LinkAccountsPage, NotFoundPage } from './pages'; +import { EditPage, HomePage, LinkAccountsPage, NotFoundPage, ImageViewerPage } from './pages'; export const routes = [ { @@ -10,6 +10,7 @@ export const routes = [ { path: '/link-accounts', element: <LinkAccountsPage /> }, { path: '/edit', element: <EditPage /> }, { path: '/view', element: <EditPage /> }, + { path: '/imageviewer', element: <ImageViewerPage /> }, { index: true, element: <HomePage /> }, ], }, diff --git a/packages/cwrc-leafwriter/src/components/editorToolbar/index.tsx b/packages/cwrc-leafwriter/src/components/editorToolbar/index.tsx index 9eee8a96..c133ed4b 100644 --- a/packages/cwrc-leafwriter/src/components/editorToolbar/index.tsx +++ b/packages/cwrc-leafwriter/src/components/editorToolbar/index.tsx @@ -8,6 +8,9 @@ import { Button } from './Button'; import { IconButton } from './IconButton'; import { Toggle } from './Toggle'; import { Trans, useTranslation } from 'react-i18next'; +//import PortalComponent from './PortalComponent'; +import { BroadcastChannel } from 'broadcast-channel'; +import { useEffect, useState } from 'react'; type ItemType = 'button' | 'divider' | 'iconButton' | 'toggle'; type ItemGroup = 'action' | 'ui' | 'panel' | 'general'; @@ -42,6 +45,28 @@ export const EditorToolbar = () => { const container = useRef<HTMLDivElement>(null); + /* + const [showPortal, setShowPortal] = useState(false); + + const channel = new BroadcastChannel('imageViewer-channel'); + const [message, setMessage] = useState("") + + useEffect(() => { + const handleMessage = (event) => { + console.log(event) + if (event.imageViewerWindowReady && event.imageViewerWindowReady == true) { + channel.postMessage({dataIn: "Hi, I am Broadcast!"}) + } + }; + + channel.onmessage = handleMessage; + + // Clean up the channel when the component unmounts + return () => { + channel.close(); + }; +}, [channel]);*/ + const isSupported = useCallback( (name: EntityType) => window.writer.schemaManager.mapper.getEntitiesMapping().has(name), [schemaId], @@ -227,6 +252,15 @@ export const EditorToolbar = () => { title: t('Validate').toString(), type: 'iconButton', }, + { + group: 'ui', + icon: 'validate', + onClick: () => { + const externalWindow = window.open(window.location.origin + '/imageviewer', '', 'width=600,height=400,left=200,top=200'); + }, + title: t('Second Window Image Viewer').toString(), + type: 'iconButton', + }, { group: 'ui', type: 'divider', hide: isReadonly }, { group: 'ui', diff --git a/packages/cwrc-leafwriter/src/js/layout/panels/imageViewer/index.ts b/packages/cwrc-leafwriter/src/js/layout/panels/imageViewer/index.ts index d72fc5d8..8740534c 100644 --- a/packages/cwrc-leafwriter/src/js/layout/panels/imageViewer/index.ts +++ b/packages/cwrc-leafwriter/src/js/layout/panels/imageViewer/index.ts @@ -6,6 +6,7 @@ import { Octokit } from '@octokit/rest'; import { Buffer } from 'buffer/'; import axios, { AxiosInstance, AxiosResponse } from 'axios'; import i18next from '../../../../i18n'; +import { BroadcastChannel } from 'broadcast-channel'; const { t } = i18next; @@ -25,17 +26,23 @@ class ImageViewer { readonly osd: ReturnType<typeof OpenSeaDragon>; + readonly channel: any; + $pageBreaks: any; $oldPageBreaks: any; currentIndex = -1; ignoreScroll = false; inhibitScroll = false // Marker to inhibit scrolling when user has zoomed in on a particular image + globalTileSources: any; + constructor({ attribute, parentId, tag, writer }: ImageViewerProps) { this.writer = writer; this.id = `${parentId}_imageViewer`; this.tagName = tag ?? 'pb'; // page break element name this.attrName = attribute ?? 'facs'; // attribute that stores the image URL + this.channel = new BroadcastChannel('imageViewer-channel'); + this.channel.onmessage = this.handleMessage; const _this = this; @@ -149,6 +156,14 @@ class ImageViewer { }); } + + private handleMessage = (event) => { + console.log(event) + if (event.imageViewerWindowReady && event.imageViewerWindowReady == true) { + this.channel.postMessage({tileSources: this.globalTileSources}) + } + }; + // ensure page break tags are display block private cssHack() { if (!this.writer.editor) return; @@ -451,6 +466,8 @@ class ImageViewer { } } + this.globalTileSources = tileSources + this.osd.open(tileSources); // tileSources.length === 0 -- GitLab