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
2 changes: 2 additions & 0 deletions src/@types/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export interface Position {
y: number;
}

export type Shape = Required<DOMRectInit>;

export interface User {
id: string;
login: string;
Expand Down
5 changes: 5 additions & 0 deletions src/document/DocumentAnnotator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import BaseAnnotator, { Options } from '../common/BaseAnnotator';
import BaseManager from '../common/BaseManager';
import { centerHighlight, isHighlight } from '../highlight/highlightUtil';
import { centerRegion, isRegion, RegionManager } from '../region';
import { Event } from '../@types';
import { getAnnotation } from '../store/annotations';
Expand Down Expand Up @@ -145,6 +146,10 @@ export default class DocumentAnnotator extends BaseAnnotator {
scrollToLocation(this.annotatedEl, annotationPageEl, {
offsets: centerRegion(annotation.target.shape),
});
} else if (isHighlight(annotation)) {
scrollToLocation(this.annotatedEl, annotationPageEl, {
offsets: centerHighlight(annotation.target.shapes),
});
}
}
}
11 changes: 10 additions & 1 deletion src/document/__tests__/DocumentAnnotator-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import DocumentAnnotator from '../DocumentAnnotator';
import HighlightListener from '../../highlight/HighlightListener';
import RegionManager from '../../region/RegionManager';
import { Annotation, Event } from '../../@types';
import { annotation as highlight } from '../../highlight/__mocks__/data';
import { annotations as regions } from '../../region/__mocks__/data';
import { fetchAnnotationsAction } from '../../store';
import { HighlightManager } from '../../highlight';
Expand Down Expand Up @@ -39,7 +40,7 @@ describe('DocumentAnnotator', () => {
};

const payload = {
entries: regions as Annotation[],
entries: [...regions, highlight] as Annotation[],
limit: 1000,
next_marker: null,
previous_marker: null,
Expand Down Expand Up @@ -263,5 +264,13 @@ describe('DocumentAnnotator', () => {
annotator.scrollToAnnotation(null);
expect(scrollToLocation).not.toHaveBeenCalled();
});

test('should call scrollToLocation for highlight annotations', () => {
const parentEl = annotator.annotatedEl as HTMLElement;
const referenceEl = parentEl.querySelector('[data-page-number="1"]');

annotator.scrollToAnnotation('223');
expect(scrollToLocation).toHaveBeenCalledWith(parentEl, referenceEl, { offsets: { x: 15, y: 10 } });
});
});
});
44 changes: 44 additions & 0 deletions src/highlight/__tests__/highlightUtil-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { centerHighlight, getBoundingRect } from '../highlightUtil';
import { Shape } from '../../@types';

const shape1: Shape = {
height: 10,
width: 10,
x: 0,
y: 0,
};

const shape2: Shape = {
height: 10,
width: 20,
x: 10,
y: 10,
};

describe('highlightUtil', () => {
describe('getBoundingRect()', () => {
test('should be the same rect for a single shape', () => {
expect(getBoundingRect([shape1])).toEqual(shape1);
});

test('should get the bounding rect for multiple shapes', () => {
expect(getBoundingRect([shape1, shape2])).toEqual({
height: 20,
width: 30,
x: 0,
y: 0,
});
});
});

describe('centerHighlight()', () => {
test.each`
shapes | expectedCenter
${[shape1]} | ${{ x: 5, y: 5 }}
${[shape2]} | ${{ x: 20, y: 15 }}
${[shape1, shape2]} | ${{ x: 15, y: 10 }}
`('should get the center of the highlight to be $expectedCenter', ({ shapes, expectedCenter }) => {
expect(centerHighlight(shapes)).toEqual(expectedCenter);
});
});
});
57 changes: 56 additions & 1 deletion src/highlight/highlightUtil.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,59 @@
import { Annotation, AnnotationHighlight, Type } from '../@types';
import { Annotation, AnnotationHighlight, Position, Shape, Type } from '../@types';

export const getBoundingRect = (shapes: Shape[]): Shape => {
let minX = Number.MAX_VALUE;
let minY = Number.MAX_VALUE;
let maxX = Number.MIN_VALUE;
let maxY = Number.MIN_VALUE;

shapes.forEach(({ height, width, x, y }) => {
const x2 = x + width;
const y2 = y + height;

if (x < minX) {
minX = x;
}

if (y < minY) {
minY = y;
}

if (x2 > maxX) {
maxX = x2;
}

if (y2 > maxY) {
maxY = y2;
}
});

return {
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY,
};
};

const centerShape = (shape: Shape): Position => {
const { height, width } = shape;

return {
x: width / 2,
y: height / 2,
};
};

export const centerHighlight = (shapes: Shape[]): Position => {
const boundingShape = getBoundingRect(shapes);
const { x: shapeX, y: shapeY } = boundingShape;
const { x: centerX, y: centerY } = centerShape(boundingShape);

return {
x: centerX + shapeX,
y: centerY + shapeY,
};
};

export function isHighlight(annotation: Annotation): annotation is AnnotationHighlight {
return annotation?.target?.type === Type.highlight;
Expand Down
3 changes: 2 additions & 1 deletion src/region/RegionAnnotation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import classNames from 'classnames';
import noop from 'lodash/noop';
import { getIsCurrentFileVersion } from '../store';
import { MOUSE_PRIMARY } from '../constants';
import { Shape, styleShape } from './regionUtil';
import { Shape } from '../@types';
import { styleShape } from './regionUtil';
import './RegionAnnotation.scss';

type Props = {
Expand Down
3 changes: 2 additions & 1 deletion src/region/RegionRect.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import classNames from 'classnames';
import { Shape, styleShape } from './regionUtil';
import { Shape } from '../@types';
import { styleShape } from './regionUtil';
import './RegionRect.scss';

type Props = {
Expand Down
9 changes: 1 addition & 8 deletions src/region/regionUtil.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { Annotation, AnnotationRegion, Position } from '../@types';
import { Annotation, AnnotationRegion, Position, Shape } from '../@types';
import { invertYCoordinate, Point, rotatePoint, translatePoint } from './transformUtil';

// Possible rotation values as supplied by box-content-preview
Expand All @@ -15,13 +15,6 @@ export type Dimensions = {
width: number;
};

export type Shape = {
height: number;
width: number;
x: number;
y: number;
};

export type Translation = {
dx?: number;
dy?: number;
Expand Down
9 changes: 5 additions & 4 deletions src/store/selection/actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createAction } from '@reduxjs/toolkit';
import { DOMRectMini, SelectionItem } from './types';
import { SelectionItem } from './types';
import { Shape } from '../../@types';

export type SelectionArg = {
location: number;
Expand All @@ -10,7 +11,7 @@ type Payload = {
payload: SelectionItem | null;
};

export const getDOMRectMini = ({ height, width, x, y }: DOMRect): DOMRectMini => ({
export const getShape = ({ height, width, x, y }: DOMRect): Shape => ({
height,
width,
x,
Expand All @@ -30,9 +31,9 @@ export const setSelectionAction = createAction(

return {
payload: {
boundingRect: getDOMRectMini(range.getBoundingClientRect()),
boundingRect: getShape(range.getBoundingClientRect()),
location,
rects: Array.from(range.getClientRects()).map(getDOMRectMini),
rects: Array.from(range.getClientRects()).map(getShape),
},
};
},
Expand Down
6 changes: 3 additions & 3 deletions src/store/selection/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
export type DOMRectMini = Required<DOMRectInit>;
import { Shape } from '../../@types';

export type SelectionState = {
selection: SelectionItem | null;
};

export type SelectionItem = {
boundingRect: DOMRectMini;
boundingRect: Shape;
location: number;
rects: Array<DOMRectMini>;
rects: Array<Shape>;
};