mirror of
https://github.com/igorski/bitmappery.git
synced 2026-06-17 03:34:56 +02:00
Made zooming consistent with ideal document scale for window size. Added keyboard shortcut for zoom
This commit is contained in:
@@ -50,15 +50,16 @@ npm run lint
|
||||
# TODO / Roadmap
|
||||
|
||||
* Unit tests for factories
|
||||
* Zoom set original size isn't that accurate (check also on mobile views)
|
||||
* DrawableLayer should only draw when brush tool is active
|
||||
* Canvas clearRect() is not doing full width and height ? (might be related to drawable layer click color problem)
|
||||
* Document resizing doesn't rescale sprites properly?
|
||||
* Layer masking should be linked to a target layer
|
||||
* Layer view in options-panel: allow naming, repositioning, toggle visibility, change type (for masking), opacity
|
||||
* Canvas util : store transparency of images
|
||||
* Restored base64 images should be treated as binary once more (see layer-factory)
|
||||
* scale logic should move from zoomable-canvas into zCanvas (as handleInteraction needs to transform offsets by zoom ratio, see DrawableLayer!)
|
||||
* adjust scaling (on widescreen images scale in the width, rather than go for full height and zoomed out mode)
|
||||
* Zoom level 1 should be equal to the last ratio based rescale
|
||||
* Image position must be made persistent (now isn't on document switch)
|
||||
* Implement selections
|
||||
* Unload Blobs when images are no longer used in document (see sprite-factory disposeSprite, keep instance count of usages)
|
||||
|
||||
@@ -45,20 +45,30 @@
|
||||
import { mapState, mapGetters, mapMutations, mapActions } from "vuex";
|
||||
import ZoomableCanvas from "@/components/ui/zcanvas/zoomable-canvas";
|
||||
import DrawableLayer from "@/components/ui/zcanvas/drawable-layer";
|
||||
import { scaleToRatio } from "@/utils/image-math";
|
||||
import { MAX_ZOOM } from "@/definitions/tool-types";
|
||||
import { scaleToRatio, scaleValue, isPortrait } from "@/utils/image-math";
|
||||
import {
|
||||
createSpriteForLayer, runSpriteFn, flushLayerSprites, flushCache,
|
||||
} from "@/factories/sprite-factory";
|
||||
|
||||
/* internal non-reactive properties */
|
||||
|
||||
let lastDocument;
|
||||
let lastDocument, containerSize;
|
||||
// maintain a pool of sprites representing the layers within the active document
|
||||
// the sprites themselves are cached within the sprite-factory, this is merely
|
||||
// used for change detection in the current editing session (see watchers)
|
||||
const layerPool = new Map();
|
||||
// scale of the on-screen canvas relative to the document
|
||||
let xScale = 1, yScale = 1, zoom = 1, containerSize;
|
||||
let xScale = 1, yScale = 1, zoom = 1, maxScale = 1;
|
||||
|
||||
const MAX_IMAGE_SIZE = 8000; // in pixels, this determines the max zoom in factor
|
||||
const calculateMaxScale = ( width, height ) => {
|
||||
if ( isPortrait( width, height )) {
|
||||
maxScale = MAX_IMAGE_SIZE / height * 100 / MAX_ZOOM;
|
||||
} else {
|
||||
maxScale = MAX_IMAGE_SIZE / width * 100 / MAX_ZOOM;
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
@@ -67,21 +77,21 @@ export default {
|
||||
}),
|
||||
computed: {
|
||||
...mapState([
|
||||
"zCanvas",
|
||||
"windowSize"
|
||||
"windowSize",
|
||||
]),
|
||||
...mapGetters([
|
||||
"zCanvas",
|
||||
"activeDocument",
|
||||
"layers",
|
||||
"activeLayer",
|
||||
"activeTool",
|
||||
"zoomOptions",
|
||||
"zCanvasBaseDimensions",
|
||||
]),
|
||||
},
|
||||
watch: {
|
||||
windowSize() {
|
||||
this.cacheContainerSize();
|
||||
this.scaleCanvas();
|
||||
this.calcIdealDimensions();
|
||||
},
|
||||
activeDocument: {
|
||||
handler( document, oldValue = null ) {
|
||||
@@ -93,14 +103,6 @@ export default {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ( !this.zCanvas ) {
|
||||
this.createCanvas();
|
||||
this.$nextTick(() => {
|
||||
this.zCanvas.insertInPage( this.$refs.canvasContainer );
|
||||
this.cacheContainerSize();
|
||||
this.scaleCanvas();
|
||||
});
|
||||
}
|
||||
const { id, width, height } = document;
|
||||
// switching between documents
|
||||
if ( id !== lastDocument ) {
|
||||
@@ -108,7 +110,16 @@ export default {
|
||||
flushCache();
|
||||
layerPool.clear();
|
||||
}
|
||||
if ( this.zCanvas.width !== width || this.zCanvas.height !== height ) {
|
||||
if ( !this.zCanvas ) {
|
||||
this.createCanvas();
|
||||
this.$nextTick(() => {
|
||||
this.zCanvas.insertInPage( this.$refs.canvasContainer );
|
||||
this.cacheContainerSize();
|
||||
this.scaleCanvas();
|
||||
});
|
||||
calculateMaxScale( width, height );
|
||||
} else if ( this.zCanvas.width !== width || this.zCanvas.height !== height ) {
|
||||
calculateMaxScale( width, height );
|
||||
this.scaleCanvas();
|
||||
}
|
||||
},
|
||||
@@ -140,7 +151,9 @@ export default {
|
||||
return;
|
||||
}
|
||||
const { id } = layer;
|
||||
[ ...layerPool.entries() ].forEach(([ key, sprite ]) => sprite.setInteractive( key === id ));
|
||||
[ ...layerPool.entries() ].forEach(([ key, sprite ]) => {
|
||||
sprite.setInteractive( key === id )
|
||||
});
|
||||
},
|
||||
},
|
||||
activeTool( tool ) {
|
||||
@@ -159,7 +172,12 @@ export default {
|
||||
zoomOptions: {
|
||||
deep: true,
|
||||
handler({ level }) {
|
||||
zoom = level;
|
||||
// are we zooming in or out (relative from the base, not necessarily the previous value)
|
||||
if ( level > 0 ) {
|
||||
zoom = scaleValue( level, MAX_ZOOM, maxScale - 1 ) + 1;
|
||||
} else {
|
||||
zoom = 1 - scaleValue( Math.abs( level ), MAX_ZOOM, 1 - ( 1 / maxScale ));
|
||||
}
|
||||
|
||||
// cache the current scroll offset so we can zoom from the current offset
|
||||
// note that by default we zoom from the center (when document was unscrolled)
|
||||
@@ -167,7 +185,7 @@ export default {
|
||||
const ratioX = Math.round( scrollLeft / scrollWidth ) || .5;
|
||||
const ratioY = Math.round( scrollTop / scrollHeight ) || .5;
|
||||
|
||||
// rescale canvas, note we can omit the ratio check as the ratio will remain the same
|
||||
// rescale canvas, note we omit the best fit calculation as we zoom from the calculated base
|
||||
this.scaleCanvas( false );
|
||||
|
||||
// maintain relative scroll offset after rescale
|
||||
@@ -184,6 +202,7 @@ export default {
|
||||
methods: {
|
||||
...mapMutations([
|
||||
"setZCanvas",
|
||||
"setZCanvasBaseDimensions",
|
||||
]),
|
||||
...mapActions([
|
||||
"requestDocumentClose",
|
||||
@@ -202,25 +221,37 @@ export default {
|
||||
containerSize = this.$el.parentNode?.getBoundingClientRect();
|
||||
},
|
||||
/**
|
||||
* Ensures the canvas fills out the available space while also maintaining
|
||||
* the ratio of the document is is representing.
|
||||
* Scales the canvas to dimensions corresponding to document size and zoom.
|
||||
* When calculateBestFit is true, this ensures the canvas fills out the available
|
||||
* space while also maintaining the ratio of the document is is representing.
|
||||
* This resulting value is used as the baseline for the unzoomed level. This
|
||||
* should be recalculated on window resize.
|
||||
*/
|
||||
scaleCanvas( performRatioScale = true ) {
|
||||
scaleCanvas( calculateBestFit = true ) {
|
||||
if ( !this.activeDocument ) {
|
||||
return;
|
||||
}
|
||||
let { width, height } = this.activeDocument;
|
||||
if ( performRatioScale ) {
|
||||
({ width, height } = scaleToRatio( width, height, containerSize.width, containerSize.height ));
|
||||
if ( calculateBestFit ) {
|
||||
const { width, height } = this.activeDocument;
|
||||
const scaledSize = scaleToRatio( width, height, containerSize.width, containerSize.height );
|
||||
this.setZCanvasBaseDimensions( scaledSize );
|
||||
xScale = scaledSize.width / this.activeDocument.width;
|
||||
yScale = scaledSize.height / this.activeDocument.height;
|
||||
}
|
||||
this.wrapperHeight = `${window.innerHeight - containerSize.top - 20}px`;
|
||||
this.zCanvas.setDimensions( width * zoom, height * zoom, true, true ); // replace to not multiply by zoom
|
||||
xScale = width / this.activeDocument.width;
|
||||
yScale = height / this.activeDocument.height;
|
||||
// replace below by updated zCanvas lib to not multiply by zoom
|
||||
this.zCanvas.setDimensions(
|
||||
this.zCanvasBaseDimensions.width * zoom,
|
||||
this.zCanvasBaseDimensions.height * zoom,
|
||||
true, true
|
||||
);
|
||||
this.zCanvas.setZoomFactor( xScale * zoom, yScale * zoom ); // replace with zCanvas.setZoom()
|
||||
|
||||
this.centerCanvas = this.zCanvas.getWidth() < containerSize.width || this.zCanvas.getHeight() < containerSize.height ;
|
||||
},
|
||||
calcIdealDimensions() {
|
||||
this.cacheContainerSize();
|
||||
this.scaleCanvas();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<h2 v-t="'resizeDocument'"></h2>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="form">
|
||||
<div class="form" @keyup.enter="save()">
|
||||
<div class="wrapper input">
|
||||
<label v-t="'width'"></label>
|
||||
<input
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters, mapMutations } from "vuex";
|
||||
import { mapGetters, mapMutations } from "vuex";
|
||||
import Modal from "@/components/modal/modal";
|
||||
import SelectBox from '@/components/ui/select-box/select-box';
|
||||
import Slider from "@/components/ui/slider/slider";
|
||||
@@ -94,10 +94,8 @@ export default {
|
||||
quality: 95,
|
||||
}),
|
||||
computed: {
|
||||
...mapState([
|
||||
"zCanvas",
|
||||
]),
|
||||
...mapGetters([
|
||||
"zCanvas",
|
||||
"activeDocument",
|
||||
]),
|
||||
fileTypes() {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"en-US": {
|
||||
"zoomLevel": "Zoom level"
|
||||
"zoomLevel": "Zoom level",
|
||||
"bestFit": "Best fit",
|
||||
"original": "Original"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,39 +29,54 @@
|
||||
:max="max"
|
||||
:tooltip="'none'"
|
||||
/>
|
||||
<div class="actions">
|
||||
<button
|
||||
v-t="'bestFit'"
|
||||
type="button"
|
||||
class="button button--small"
|
||||
@click="setBestFit()"
|
||||
></button>
|
||||
<button
|
||||
v-t="'original'"
|
||||
type="button"
|
||||
class="button button--small"
|
||||
@click="setOriginalSize()"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapMutations } from "vuex";
|
||||
import ToolTypes, { MIN_ZOOM, MAX_ZOOM } from "@/definitions/tool-types";
|
||||
import Slider from "@/components/ui/slider/slider";
|
||||
import ToolTypes from "@/definitions/tool-types";
|
||||
import messages from "./messages.json";
|
||||
|
||||
const MAX_ZOOM = 3;
|
||||
|
||||
export default {
|
||||
i18n: { messages },
|
||||
components: {
|
||||
Slider,
|
||||
},
|
||||
data: () => ({
|
||||
min: 1,
|
||||
max: 10,
|
||||
min: MIN_ZOOM,
|
||||
max: MAX_ZOOM,
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters([
|
||||
"activeDocument",
|
||||
"zoomOptions",
|
||||
"zCanvas",
|
||||
"zCanvasBaseDimensions",
|
||||
]),
|
||||
zoomLevel: {
|
||||
get() {
|
||||
return ( this.zoomOptions.level / MAX_ZOOM ) * this.max;
|
||||
return this.zoomOptions.level;
|
||||
},
|
||||
set( value ) {
|
||||
this.setToolOptionValue({
|
||||
tool: ToolTypes.ZOOM,
|
||||
option: "level",
|
||||
value: ( value / this.max ) * MAX_ZOOM
|
||||
value
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -70,10 +85,25 @@ export default {
|
||||
...mapMutations([
|
||||
"setToolOptionValue",
|
||||
]),
|
||||
setBestFit() {
|
||||
this.zoomLevel = 0;
|
||||
},
|
||||
setOriginalSize() {
|
||||
this.zoomLevel = ( this.activeDocument.width / this.zCanvasBaseDimensions.width ) * window.devicePixelRatio;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/tool-option";
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
margin-top: $spacing-medium;
|
||||
button {
|
||||
flex: 1;
|
||||
margin: 0 $spacing-small;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
|
||||
<script>
|
||||
import "@simonwep/pickr/dist/themes/nano.min.css";
|
||||
import Pickr from "@simonwep/pickr/dist/pickr.es5.min"; // 3 x size of modern bundle
|
||||
//import Pickr from "@simonwep/pickr";
|
||||
//import Pickr from "@simonwep/pickr/dist/pickr.es5.min"; // 3 x size of modern bundle
|
||||
import Pickr from "@simonwep/pickr";
|
||||
|
||||
let pickrInstance;
|
||||
|
||||
|
||||
@@ -27,3 +27,5 @@ export default {
|
||||
};
|
||||
|
||||
export const MAX_BRUSH_SIZE = 100;
|
||||
export const MIN_ZOOM = -50; // zooming out from base (which is 0)
|
||||
export const MAX_ZOOM = 50; // zooming in from base (which is 0)
|
||||
|
||||
@@ -72,6 +72,7 @@ import Toolbox from "@/components/toolbox/toolbox";
|
||||
import DialogWindow from "@/components/dialog-window/dialog-window";
|
||||
import Notifications from '@/components/notifications/notifications';
|
||||
import { isMobile } from "@/utils/environment-util";
|
||||
import ToolTypes from "@/definitions/tool-types";
|
||||
import store from "./store";
|
||||
import messages from "./messages.json";
|
||||
import {
|
||||
@@ -149,12 +150,15 @@ export default {
|
||||
"setWindowSize",
|
||||
"closeModal",
|
||||
"setToolboxOpened",
|
||||
"setToolOptionValue",
|
||||
]),
|
||||
...mapActions([
|
||||
"setupServices",
|
||||
]),
|
||||
handleResize() {
|
||||
this.setWindowSize({ width: window.innerWidth, height: window.innerHeight });
|
||||
// prevent maximum zoom at previous small window size to lead to excessively large document canvas
|
||||
this.setToolOptionValue({ tool: ToolTypes.ZOOM, option: "level", value: 1 });
|
||||
},
|
||||
/**
|
||||
* Ensure the document container has optimal size. Ideally we'd like a pure
|
||||
@@ -168,7 +172,7 @@ export default {
|
||||
const toolboxWidth = this.$refs.toolbox?.$el.clientWidth;
|
||||
const optionsPanelWidth = this.$refs.optionsPanel?.$el.clientWidth;
|
||||
this.docWidth = `calc(100% - ${toolboxWidth + optionsPanelWidth + 32}px)`;
|
||||
this.$nextTick(() => this.$refs.documentCanvas?.scaleCanvas());
|
||||
this.$nextTick(() => this.$refs.documentCanvas?.calcIdealDimensions());
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import { ADD_LAYER } from "@/definitions/modal-windows";
|
||||
import ToolTypes, { MAX_BRUSH_SIZE } from "@/definitions/tool-types";
|
||||
import ToolTypes, { MAX_BRUSH_SIZE, MIN_ZOOM, MAX_ZOOM } from "@/definitions/tool-types";
|
||||
|
||||
let state, getters, commit, dispatch, listener,
|
||||
suspended = false, blockDefaults = true, optionDown = false, shiftDown = false;
|
||||
@@ -265,15 +265,27 @@ function handleKeyDown( event ) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 187: // +
|
||||
commit( "setToolOptionValue",
|
||||
{ tool: ToolTypes.ZOOM, option: "level", value: Math.min( MAX_ZOOM, getters.zoomOptions.level + ( MAX_ZOOM / 10 ))
|
||||
});
|
||||
break;
|
||||
|
||||
case 189: // -
|
||||
commit( "setToolOptionValue",
|
||||
{ tool: ToolTypes.ZOOM, option: "level", value: Math.max( MIN_ZOOM, getters.zoomOptions.level - ( MAX_ZOOM / 10 ))
|
||||
});
|
||||
break;
|
||||
|
||||
case 219: // [
|
||||
commit( "setToolOptionValue",
|
||||
{ tool: ToolTypes.BRUSH, option: "size", value: Math.max( 1, getters.brushOptions.size - 1 )
|
||||
{ tool: ToolTypes.BRUSH, option: "size", value: Math.max( 1, getters.brushOptions.size - 5 )
|
||||
});
|
||||
break;
|
||||
|
||||
case 221: // ]
|
||||
commit( "setToolOptionValue", {
|
||||
tool: ToolTypes.BRUSH, option: "size", value: Math.min( MAX_BRUSH_SIZE, getters.brushOptions.size + 1 )
|
||||
tool: ToolTypes.BRUSH, option: "size", value: Math.min( MAX_BRUSH_SIZE, getters.brushOptions.size + 5 )
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import LZString from "lz-string";
|
||||
import KeyboardService from "@/services/keyboard-service";
|
||||
import DocumentFactory from "@/factories/document-factory";
|
||||
import canvasModule from "./modules/canvas-module";
|
||||
import documentModule from "./modules/document-module";
|
||||
import imageModule from "./modules/image-module";
|
||||
import toolModule from "./modules/tool-module";
|
||||
@@ -39,6 +40,7 @@ const translate = ( key, optArgs ) => i18n?.t( key, optArgs ) ?? key;
|
||||
|
||||
export default {
|
||||
modules: {
|
||||
canvasModule,
|
||||
documentModule,
|
||||
imageModule,
|
||||
toolModule,
|
||||
@@ -51,7 +53,6 @@ export default {
|
||||
dialog: null, // currently opened dialog
|
||||
modal: null, // currently opened modal
|
||||
notifications: [], // notification message queue
|
||||
zCanvas: null, // zCanvas instance
|
||||
windowSize: {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
@@ -105,9 +106,6 @@ export default {
|
||||
clearNotifications( state ) {
|
||||
state.notifications = [];
|
||||
},
|
||||
setZCanvas( state, zCanvas ) {
|
||||
state.zCanvas = zCanvas;
|
||||
},
|
||||
/**
|
||||
* cache the resize in the store so components can react to these values
|
||||
* instead of maintaining multiple listeners at the expense of DOM trashing/performance hits
|
||||
|
||||
48
src/store/modules/canvas-module.js
Normal file
48
src/store/modules/canvas-module.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Igor Zinken 2020 - https://www.igorski.nl
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
import Vue from "vue";
|
||||
|
||||
export default {
|
||||
state: {
|
||||
zCanvas: null, // zCanvas instance
|
||||
// the base dimensions describe the "best fit" scale to represent
|
||||
// the currently active document at the current window size, this
|
||||
// is basically the base line used for the unzoomed document view
|
||||
zCanvasBaseDimensions: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
zCanvas: state => state.zCanvas,
|
||||
zCanvasBaseDimensions: state => state.zCanvasBaseDimensions,
|
||||
},
|
||||
mutations: {
|
||||
setZCanvas( state, zCanvas ) {
|
||||
state.zCanvas = zCanvas;
|
||||
},
|
||||
setZCanvasBaseDimensions( state, { width, height }) {
|
||||
Vue.set( state, "zCanvasBaseDimensions", { width, height });
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -24,7 +24,7 @@ import Vue from "vue";
|
||||
|
||||
import DocumentFactory from "@/factories/document-factory";
|
||||
import LayerFactory from "@/factories/layer-factory";
|
||||
import { flushLayerSprites } from "@/factories/sprite-factory";
|
||||
import { flushLayerSprites, runSpriteFn } from "@/factories/sprite-factory";
|
||||
|
||||
export default {
|
||||
state: {
|
||||
@@ -45,8 +45,16 @@ export default {
|
||||
},
|
||||
setActiveDocumentSize( state, { width, height }) {
|
||||
const document = state.documents[ state.activeIndex ];
|
||||
const ratio = width / document.width;
|
||||
document.width = width;
|
||||
document.height = height;
|
||||
document.layers?.forEach( layer => {
|
||||
layer.width = width;
|
||||
layer.height = height;
|
||||
});
|
||||
runSpriteFn( sprite => {
|
||||
sprite.setBounds( sprite.getX() * ratio, sprite.getY() * ratio, width, height );
|
||||
});
|
||||
},
|
||||
addNewDocument( state, nameOrDocument ) {
|
||||
const document = typeof nameOrDocument === "object" ? nameOrDocument : DocumentFactory.create({ name: nameOrDocument });
|
||||
|
||||
@@ -53,6 +53,15 @@ export const scaleToRatio = ( imageWidth, imageHeight, destWidth, destHeight ) =
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* convenience method to scale given value and its expected maxValue against
|
||||
* an arbitrary range (defined by maxCompareValue in relation to maxValue)
|
||||
*/
|
||||
export const scaleValue = ( value, maxValue, maxCompareValue ) => {
|
||||
const ratio = maxCompareValue / maxValue;
|
||||
return Math.min( maxValue, value ) * ratio;
|
||||
};
|
||||
|
||||
export const isPortrait = ( width, height ) => width < height;
|
||||
export const isLandscape = ( width, height ) => width > height;
|
||||
export const isSquare = ( width, height ) => width === height;
|
||||
|
||||
32
tests/unit/store/modules/canvas-module.spec.js
Normal file
32
tests/unit/store/modules/canvas-module.spec.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import storeModule from "@/store/modules/canvas-module";
|
||||
|
||||
const { getters, mutations } = storeModule;
|
||||
|
||||
describe( "Vuex canvas module", () => {
|
||||
describe( "getters", () => {
|
||||
it( "should be able to retrieve the current zCanvas instance", () => {
|
||||
const state = { zCanvas: { foo: "bar" } };
|
||||
expect( getters.zCanvas( state )).toEqual( state.zCanvas );
|
||||
});
|
||||
|
||||
it( "should be able to retrieve the zCanvas instance base dimensions", () => {
|
||||
const state = { zCanvasBaseDimensions: { width: 10, height: 5 } };
|
||||
expect( getters.zCanvasBaseDimensions( state )).toEqual( state.zCanvasBaseDimensions );
|
||||
});
|
||||
});
|
||||
|
||||
describe( "mutations", () => {
|
||||
it( "should be able to set the current zCanvas instance", () => {
|
||||
const state = { zCanvas: null };
|
||||
const canvas = { foo: "bar" };
|
||||
mutations.setZCanvas( state, canvas );
|
||||
expect( state.zCanvas ).toEqual( canvas );
|
||||
});
|
||||
|
||||
it( "should be able to set the zCanvas instance base dimensions", () => {
|
||||
const state = { zCanvasBaseDimensions: { width: 10, height: 5 } };
|
||||
mutations.setZCanvasBaseDimensions( state, { width: 50, height: 40 } );
|
||||
expect( state.zCanvasBaseDimensions ).toEqual({ width: 50, height: 40 });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,7 @@ const { getters, mutations } = DocumentModule;
|
||||
let mockUpdateFn;
|
||||
jest.mock( "@/factories/sprite-factory", () => ({
|
||||
flushLayerSprites: (...args) => mockUpdateFn?.( "flushLayerSprites", ...args ),
|
||||
runSpriteFn: (...args) => mockUpdateFn?.( "runSpriteFn", ...args ),
|
||||
}));
|
||||
jest.mock( "@/factories/layer-factory", () => ({
|
||||
create: (...args) => mockUpdateFn?.( "create", ...args ),
|
||||
|
||||
@@ -143,13 +143,6 @@ describe( "Vuex store", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it( "should be able to set the current zCanvas instance", () => {
|
||||
const state = { zCanvas: null };
|
||||
const canvas = { foo: "bar" };
|
||||
mutations.setZCanvas( state, canvas );
|
||||
expect( state.zCanvas ).toEqual( canvas );
|
||||
});
|
||||
|
||||
it( "should be able to set the window size", () => {
|
||||
const state = { windowSize: { width: 0, height: 0 }};
|
||||
const width = 500;
|
||||
|
||||
27
tests/unit/utils/image-math.spec.js
Normal file
27
tests/unit/utils/image-math.spec.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { scaleToRatio, isPortrait, isLandscape, isSquare } from "@/utils/image-math";
|
||||
|
||||
describe( "Image math utilities", () => {
|
||||
describe( "When determining ratios", () => {
|
||||
const PORTRAIT = { width: 3, height: 4 };
|
||||
const LANDSCAPE = { width: 4, height: 3 };
|
||||
const SQUARE = { width: 3, height: 3 };
|
||||
|
||||
it( "should correctly identify portrait images", () => {
|
||||
expect( isPortrait( PORTRAIT.width, PORTRAIT.height )).toBe( true );
|
||||
expect( isPortrait( LANDSCAPE.width, LANDSCAPE.height )).toBe( false );
|
||||
expect( isPortrait( SQUARE.width, SQUARE.height )).toBe( false );
|
||||
});
|
||||
|
||||
it( "should correctly identify landscape images", () => {
|
||||
expect( isLandscape( PORTRAIT.width, PORTRAIT.height )).toBe( false );
|
||||
expect( isLandscape( LANDSCAPE.width, LANDSCAPE.height )).toBe( true );
|
||||
expect( isLandscape( SQUARE.width, SQUARE.height )).toBe( false );
|
||||
});
|
||||
|
||||
it( "should correctly identify square images", () => {
|
||||
expect( isSquare( PORTRAIT.width, PORTRAIT.height )).toBe( false );
|
||||
expect( isSquare( LANDSCAPE.width, LANDSCAPE.height )).toBe( false );
|
||||
expect( isSquare( SQUARE.width, SQUARE.height )).toBe( true );
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user