Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 34 additions & 6 deletions src/components/AdnoEditor/AdnoEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ import "./AdnoEditor.css";
// Add translations
import { withTranslation } from "react-i18next";
import { projectDB } from "../../services/db";
import AdnoNavigator from '../AdnoNavigator/AdnoNavigator';

class AdnoEditor extends Component {
constructor(props) {
super(props);
this.state = {
isMovingItem: false
isMovingItem: false,
imageRatio: null,
navigatorLayout: null,
viewerReady: false
}
}

Expand Down Expand Up @@ -51,15 +55,29 @@ class AdnoEditor extends Component {
OpenSeadragon.setString("Tooltips.RotateRight", this.props.t('editor.rotate_right'));
OpenSeadragon.setString("Tooltips.Flip", this.props.t('editor.flip'));

this.AdnoAnnotorious = OpenSeadragon.Annotorious(OpenSeadragon({
this.openSeadragon = OpenSeadragon({
id: 'openseadragon1',
tileSources: tileSources,
prefixUrl: 'https://cdn.jsdelivr.net/gh/Benomrans/openseadragon-icons@main/images/',
// Enable rotation
toolbar: "toolbar-osd",
showRotationControl: this.props.rotation,
showFullPageControl: false,
}), {
});

this.openSeadragon.addOnceHandler('open', () => {
const item = this.openSeadragon.world.getItemAt(0);
if (item) {
const size = item.getContentSize();
const ratio = size.y / size.x;
let layout = 'bottom-right';
if (ratio < 0.30) layout = 'bottom-center';
else if (ratio > 3.33) layout = 'right-vertical';
this.setState({ imageRatio: ratio, navigatorLayout: layout, viewerReady: true });
}
});

this.AdnoAnnotorious = OpenSeadragon.Annotorious(this.openSeadragon, {
locale: 'auto',
drawOnSingleClick: true,
allowEmpty: true,
Expand Down Expand Up @@ -174,9 +192,19 @@ class AdnoEditor extends Component {
render() {
return (
<div>
<div id="openseadragon1">
<div id="toolbar-container"></div>
<div id="toolbar-osd"></div>
<div style={{ position: 'relative' }}>
<div id="openseadragon1">
<div id="toolbar-container"></div>
<div id="toolbar-osd"></div>
</div>
{this.props.showNavigator && this.state.viewerReady && (
<AdnoNavigator
viewer={this.openSeadragon}
imageRatio={this.state.imageRatio}
layout={this.state.navigatorLayout}
imgUrl={this.props.selectedProject.manifest_url ?? this.props.selectedProject.img_url}
/>
)}
</div>
{
this.state.isMovingItem &&
Expand Down
33 changes: 30 additions & 3 deletions src/components/AdnoEmbed/AdnoEmbed.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { withTranslation } from "react-i18next";
// Import Style
import "./AdnoEmbed.css";
import { extractIIIFContent } from "./IIIFHelper";
import AdnoNavigator from '../AdnoNavigator/AdnoNavigator';

class AdnoEmbed extends Component {
constructor(props) {
Expand All @@ -39,7 +40,11 @@ class AdnoEmbed extends Component {
currentTrack: undefined,
soundMode: 'no_sound',
audioContexts: [],
hasInteracted: false
hasInteracted: false,
imageRatio: null,
navigatorLayout: null,
viewerReady: false,
navigatorImgUrl: null
};
}

Expand Down Expand Up @@ -203,6 +208,18 @@ class AdnoEmbed extends Component {
prefixUrl: "https://cdn.jsdelivr.net/gh/Benomrans/openseadragon-icons@main/images/",
});

this.openSeadragon.addOnceHandler('open', () => {
const item = this.openSeadragon.world.getItemAt(0);
if (item) {
const size = item.getContentSize();
const ratio = size.y / size.x;
let layout = 'bottom-right';
if (ratio < 0.30) layout = 'bottom-center';
else if (ratio > 3.33) layout = 'right-vertical';
this.setState({ imageRatio: ratio, navigatorLayout: layout, viewerReady: true });
}
});

OpenSeadragon.setString("Tooltips.FullPage", this.props.t('editor.fullpage'));
OpenSeadragon.setString("Tooltips.Home", this.props.t('editor.home'));
OpenSeadragon.setString("Tooltips.ZoomIn", this.props.t('editor.zoom_in'));
Expand Down Expand Up @@ -852,6 +869,7 @@ class AdnoEmbed extends Component {
}
: [imported_project.source];

this.setState({ navigatorImgUrl: imported_project.source });
this.displayViewer(tileSources, annos);

// Add annotations to the state
Expand Down Expand Up @@ -980,7 +998,7 @@ class AdnoEmbed extends Component {
url,
};

this.setState({ isLoaded: true });
this.setState({ isLoaded: true, navigatorImgUrl: url });

this.displayViewer(tileSources, []);
}
Expand Down Expand Up @@ -1017,7 +1035,16 @@ class AdnoEmbed extends Component {
</div>

return (
<div id="adno-embed">
<div id="adno-embed" style={{ position: 'relative' }}>

{this.state.showNavigator && this.state.viewerReady && (
<AdnoNavigator
viewer={this.openSeadragon}
imageRatio={this.state.imageRatio}
layout={this.state.navigatorLayout}
imgUrl={this.state.navigatorImgUrl}
/>
)}

{
this.state.selectedAnno && this.state.selectedAnno.body &&
Expand Down
27 changes: 27 additions & 0 deletions src/components/AdnoNavigator/AdnoNavigator.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* AdnoNavigator.css */
.adno-navigator {
position: absolute;
cursor: crosshair;
border-radius: 4px;
border: 1px solid rgba(255, 255, 255, 0.25);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
z-index: 100;
background: #111;
}

.adno-navigator--bottom-right {
bottom: 12px;
right: 12px;
}

.adno-navigator--bottom-center {
bottom: 0;
left: 50%;
transform: translateX(-50%);
}

.adno-navigator--right-vertical {
right: 12px;
top: calc(50% + 58px);
transform: translateY(-50%);
}
161 changes: 161 additions & 0 deletions src/components/AdnoNavigator/AdnoNavigator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { Component, createRef } from "react";
import "./AdnoNavigator.css";

class AdnoNavigator extends Component {
constructor(props) {
super(props);
this.canvasRef = createRef();
this.isDragging = false;
this._bgImage = null;
this._timer = null;
}

componentDidMount() {
const { viewer } = this.props;

this.loadThumbnail();
this._timer = setTimeout(() => this.draw(), 500);
viewer.addHandler('animation', this.draw);
viewer.addHandler('resize', this.draw);
}

componentWillUnmount() {
const { viewer } = this.props;

clearTimeout(this._timer);
viewer.removeHandler('animation', this.draw);
viewer.removeHandler('resize', this.draw);
}

loadThumbnail = () => {
const { imgUrl } = this.props;
if (!imgUrl) return;

this._bgImage = new Image();
this._bgImage.crossOrigin = 'anonymous';
this._bgImage.src = imgUrl.endsWith('/info.json')
? `${imgUrl.replace('/info.json', '')}/full/!512,512/0/default.jpg`
: imgUrl;
this._bgImage.onload = () => this.draw();
}

draw = () => {
const { viewer } = this.props;
const canvas = this.canvasRef.current;
if (!canvas) return;

const ctx = canvas.getContext('2d');
const W = canvas.width;
const H = canvas.height;

ctx.clearRect(0, 0, W, H);

const item = viewer.world.getItemAt(0);
if (!item) return;

ctx.fillStyle = 'rgba(0, 0, 0, 0.85)';
ctx.fillRect(0, 0, W, H);

if (this._bgImage?.complete && this._bgImage.naturalWidth) {
ctx.drawImage(this._bgImage, 0, 0, W, H);
}

this.drawViewportRect(ctx, item, W, H);
}

drawViewportRect = (ctx, item, W, H) => {
const { viewer } = this.props;
const imgBounds = item.getBounds();
const viewBounds = viewer.viewport.getBounds(true);

const toCanvas = (x, y) => ({
x: ((x - imgBounds.x) / imgBounds.width) * W,
y: ((y - imgBounds.y) / imgBounds.height) * H,
});

const tl = toCanvas(viewBounds.x, viewBounds.y);
const br = toCanvas(
viewBounds.x + viewBounds.width,
viewBounds.y + viewBounds.height
);

const rx = Math.max(0, tl.x);
const ry = Math.max(0, tl.y);
const rw = Math.min(W, br.x) - rx;
const rh = Math.min(H, br.y) - ry;

ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
ctx.fillRect(0, 0, W, ry);
ctx.fillRect(0, ry + rh, W, H - ry - rh);
ctx.fillRect(0, ry, rx, rh);
ctx.fillRect(rx + rw, ry, W - rx - rw, rh);

ctx.strokeStyle = 'rgba(255, 220, 50, 0.9)';
ctx.lineWidth = 2;
ctx.strokeRect(rx, ry, rw, rh);
}

panTo = (e) => {
const { viewer } = this.props;
const canvas = this.canvasRef.current;
const item = viewer.world.getItemAt(0);
if (!canvas || !item) return;

const rect = canvas.getBoundingClientRect();
const imgBounds = item.getBounds();

const vpX = imgBounds.x + ((e.clientX - rect.left) / canvas.width) * imgBounds.width;
const vpY = imgBounds.y + ((e.clientY - rect.top) / canvas.height) * imgBounds.height;

viewer.viewport.panTo(new OpenSeadragon.Point(vpX, vpY), false);
}

handleMouseDown = (e) => { this.isDragging = true; this.panTo(e); }
handleMouseMove = (e) => { if (this.isDragging) this.panTo(e); }
handleMouseUp = () => { this.isDragging = false; }

computeSize = () => {
const { viewer, imageRatio, layout } = this.props;
const container = viewer?.container;
const cw = container?.clientWidth || 800;
const ch = container?.clientHeight || 600;

const fit = (w, h, maxW, maxH) => {
if (h > maxH) { h = Math.round(maxH); w = Math.round(h / imageRatio); }
if (w > maxW) { w = Math.round(maxW); h = Math.round(w * imageRatio); }
return { width: w, height: h };
};

if (layout === 'bottom-center') {
const w = Math.round(cw * 0.5);
return fit(w, Math.round(w * imageRatio), cw * 0.5, ch * 0.4);
}
if (layout === 'right-vertical') {
const h = Math.round(ch * 0.8);
return fit(Math.round(h / imageRatio), h, cw * 0.2, ch * 0.8);
}
const w = Math.round(cw * 0.25);
return fit(w, Math.round(w * imageRatio), cw * 0.25, ch * 0.3);
}

render() {
const { layout } = this.props;
const { width, height } = this.computeSize();

return (
<canvas
ref={this.canvasRef}
width={width}
height={height}
className={`adno-navigator adno-navigator--${layout}`}
onMouseDown={this.handleMouseDown}
onMouseMove={this.handleMouseMove}
onMouseUp={this.handleMouseUp}
onMouseLeave={this.handleMouseUp}
/>
);
}
}

export default AdnoNavigator;

Loading