Skip to content

Commit

Permalink
feat: rotate openlayers image (#2491)
Browse files Browse the repository at this point in the history
* experiment: rotate openlayers image

* feat: add rotate left/right icons

* chore: bump size limit

* test: spec useRotation composable

* test: spec display of rotation buttons

* fix: revert to global state

so that multiple components can share

* test: fix rotation specs

* feat: reset rotation when creating new view
  • Loading branch information
rwd authored Nov 27, 2024
1 parent 29d614d commit 2685cfd
Show file tree
Hide file tree
Showing 11 changed files with 361 additions and 117 deletions.
15 changes: 15 additions & 0 deletions packages/portal/docs/style/FontIcons.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,21 @@ Example:
<td><code>icon-reset</code></td>
<td><span class="icon icon-reset" /></td>
</tr>
<tr>
<td>Rotate left</td>
<td><code>icon-rotate-left</code></td>
<td><span class="icon icon-rotate-left" /></td>
</tr>
<tr>
<td>Rotate right</td>
<td><code>icon-rotate-right</code></td>
<td><span class="icon icon-rotate-right" /></td>
</tr>
<tr>
<td>Zoom out</td>
<td><code>icon-zoom-out</code></td>
<td><span class="icon icon-zoom-out" /></td>
</tr>
<tr>
<td>School</td>
<td><code>icon-school</code></td>
Expand Down
20 changes: 17 additions & 3 deletions packages/portal/src/components/media/MediaImageViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import { defaults } from 'ol/interaction/defaults';
import useItemMediaPresentation from '@/composables/itemMediaPresentation.js';
import useRotation from '@/composables/rotation.js';
import useZoom from '@/composables/zoom.js';
import EuropeanaMediaAnnotation from '@/utils/europeana/media/Annotation.js';
import EuropeanaMediaService from '@/utils/europeana/media/Service.js';
Expand Down Expand Up @@ -95,6 +96,10 @@
},
setup() {
const {
reset: resetRotation,
rotation
} = useRotation();
const {
current: currentZoom,
setCurrent: setCurrentZoom,
Expand All @@ -115,6 +120,8 @@
currentZoom,
hasAnnotations,
pageForAnnotationTarget,
resetRotation,
rotation,
setCurrentZoom,
setDefaultZoom,
setMaxZoom,
Expand Down Expand Up @@ -159,8 +166,9 @@
deep: true,
handler: 'highlightAnnotation'
},
url: '$fetch',
currentZoom: 'setZoom'
currentZoom: 'setZoom',
rotation: 'setRotation',
url: '$fetch'
},
mounted() {
Expand Down Expand Up @@ -267,12 +275,14 @@
initOlMap({ extent, layer, source } = {}) {
const projection = new Projection({ units: 'pixels', extent });
this.resetRotation();
const view = new View({
center: getCenter(extent),
constrainOnlyCenter: true,
maxZoom: 8,
projection,
resolutions: source.getTileGrid?.()?.getResolutions()
resolutions: source.getTileGrid?.()?.getResolutions(),
rotation: this.rotation
});
view.on('error', (olError) => this.handleOlError(olError, 'OpenLayers View error'));
Expand Down Expand Up @@ -406,6 +416,10 @@
this.highlightAnnotation();
},
setRotation() {
this.olMap.getView()?.setRotation(this.rotation);
},
setZoom() {
const view = this.olMap.getView();
Expand Down
34 changes: 32 additions & 2 deletions packages/portal/src/components/media/MediaImageViewerControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,37 @@
id="viewer-controls"
class="viewer-controls position-absolute d-inline-flex align-items-center justify-content-center mx-auto"
>
<b-button
v-b-tooltip.top="$t('media.controls.rotateLeft')"
:aria-label="$t('media.controls.rotateLeft')"
variant="light-flat"
class="button-icon-only btn-light-flat mr-2"
@click="rotateLess"
@mouseleave="hideTooltips"
>
<span
class="icon icon-rotate-left"
/>
</b-button>
<b-button
v-b-tooltip.top="$t('media.controls.rotateRight')"
:aria-label="$t('media.controls.rotateRight')"
variant="light-flat"
class="button-icon-only btn-light-flat mr-2"
@click="rotateMore"
@mouseleave="hideTooltips"
>
<span
class="icon icon-rotate-right"
/>
</b-button>
<span class="divider" />
<b-button
v-b-tooltip.top="$t('media.controls.zoomIn')"
:disabled="atMaxZoom"
:aria-label="$t('media.controls.zoomIn')"
variant="light-flat"
class="button-icon-only btn-light-flat mr-2"
class="button-icon-only btn-light-flat ml-3 mr-2"
@click="zoomIn"
@mouseleave="hideTooltips"
>
Expand Down Expand Up @@ -61,6 +86,7 @@

<script>
import hideTooltips from '@/mixins/hideTooltips';
import useRotation from '@/composables/rotation.js';
import useZoom from '@/composables/zoom.js';
export default {
Expand All @@ -76,6 +102,10 @@
},
setup() {
const {
rotateLess,
rotateMore
} = useRotation();
const {
atMin: atMinZoom,
atMax: atMaxZoom,
Expand All @@ -85,7 +115,7 @@
zoomOut
} = useZoom();
return { atMinZoom, atMaxZoom, atDefaultZoom, resetZoom, zoomIn, zoomOut };
return { atMinZoom, atMaxZoom, atDefaultZoom, resetZoom, rotateLess, rotateMore, zoomIn, zoomOut };
}
};
</script>
Expand Down
27 changes: 27 additions & 0 deletions packages/portal/src/composables/rotation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ref } from 'vue';

// current rotation in radians
const rotation = ref(0);

const reset = () => {
rotation.value = 0;
};
const rotateLess = () => {
rotation.value = rotation.value - (Math.PI / 2);
};
const rotateMore = () => {
rotation.value = rotation.value + (Math.PI / 2);
};
const setRotation = (value) => {
rotation.value = value;
};

export default function useRotation() {
return {
reset,
rotateLess,
rotateMore,
rotation,
setRotation
};
}
2 changes: 2 additions & 0 deletions packages/portal/src/i18n/lang/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,8 @@ export default {
"fullscreen": "Full screen",
"keyboardNavigation": "Use the +, - and arrow keys to zoom and pan around the image.",
"resetZoom": "Reset zoom",
"rotateLeft": "Rotate left",
"rotateRight": "Rotate right",
"zoomIn": "Zoom in",
"zoomOut": "Zoom out"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/portal/tests/size/.size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
{
"name": "portal.js modern",
"running": false,
"limit": "1800 KB",
"limit": "1805 KB",
"path": [
".nuxt/dist/client/*.modern.js"
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,22 @@ describe('components/media/MediaImageViewerControls', () => {
expect(viewerWrapper.isVisible()).toBe(true);
});

it('has a rotate left button', () => {
const wrapper = factory();

const rotateLeftButton = wrapper.find('.icon-rotate-left');

expect(rotateLeftButton.isVisible()).toBe(true);
});

it('has a rotate right button', () => {
const wrapper = factory();

const rotateRightButton = wrapper.find('.icon-rotate-right');

expect(rotateRightButton.isVisible()).toBe(true);
});

it('has a fullscreen button', () => {
const wrapper = factory();

Expand Down
64 changes: 64 additions & 0 deletions packages/portal/tests/unit/composables/rotation.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import sinon from 'sinon';
import useRotation from '@/composables/rotation.js';

describe('useRotation', () => {
afterEach(sinon.resetHistory);
afterAll(sinon.restore);

describe('refs', () => {
describe('rotation', () => {
it('defaults to 0', () => {
const { rotation } = useRotation();

expect(rotation.value).toBe(0);
});
});
});

describe('methods', () => {
describe('reset', () => {
it('resets rotation value to 0', () => {
const value = 1;
const { rotation, reset, setRotation } = useRotation();
setRotation(value);

reset();

expect(rotation.value).toBe(0);
});
});

describe('setRotation', () => {
it('sets rotation value', () => {
const value = 1;
const { rotation, setRotation } = useRotation();

setRotation(value);

expect(rotation.value).toBe(value);
});
});

describe('rotateLess', () => {
it('reduces rotation by quarter circle', () => {
const { reset, rotation, rotateLess } = useRotation();

reset();
rotateLess();

expect(rotation.value).toBe(0 - (Math.PI / 2));
});
});

describe('rotateMore', () => {
it('increases rotation by quarter circle', () => {
const { reset, rotation, rotateMore } = useRotation();

reset();
rotateMore();

expect(rotation.value).toBe(0 + (Math.PI / 2));
});
});
});
});
Loading

0 comments on commit 2685cfd

Please sign in to comment.