mirror of
https://github.com/igorski/bitmappery.git
synced 2026-07-25 08:17:42 +02:00
Added layer interaction to state history
This commit is contained in:
@@ -202,6 +202,7 @@ import {
|
||||
import { supportsFullscreen, setToggleButton } from "@/utils/environment-util";
|
||||
import { getRectangleForSelection } from "@/math/selection-math";
|
||||
import { getCanvasInstance, runSpriteFn, getSpriteForLayer } from "@/factories/sprite-factory";
|
||||
import { enqueueState } from "@/factories/history-state-factory";
|
||||
import messages from "./messages.json";
|
||||
|
||||
export default {
|
||||
@@ -283,10 +284,25 @@ export default {
|
||||
requestSelectionSave() {
|
||||
this.openModal( SAVE_SELECTION );
|
||||
},
|
||||
async requestCropToSelection() {
|
||||
requestCropToSelection() {
|
||||
const store = this.$store;
|
||||
const currentSize = {
|
||||
width: this.activeDocument.width,
|
||||
height: this.activeDocument.height
|
||||
};
|
||||
const { left, top, width, height } = getRectangleForSelection( this.activeLayer.selection );
|
||||
await this.cropActiveDocumentContent({ left, top });
|
||||
this.setActiveDocumentSize({ width, height });
|
||||
const commit = async () => {
|
||||
await store.commit( "cropActiveDocumentContent", { left, top });
|
||||
store.commit( "setActiveDocumentSize", { width, height });
|
||||
};
|
||||
commit();
|
||||
enqueueState("crop", {
|
||||
async undo() {
|
||||
await store.commit( "cropActiveDocumentContent", { left: -left, top: -top });
|
||||
store.commit( "setActiveDocumentSize", currentSize );
|
||||
},
|
||||
redo: commit
|
||||
});
|
||||
},
|
||||
requestDropboxLoad() {
|
||||
this.openModal( DROPBOX_FILE_SELECTOR );
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Igor Zinken 2020 - https://www.igorski.nl
|
||||
* Igor Zinken 2020-2021 - 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
|
||||
@@ -64,6 +64,7 @@
|
||||
import { mapGetters, mapMutations, mapActions } from "vuex";
|
||||
import Modal from "@/components/modal/modal";
|
||||
import SelectBox from '@/components/ui/select-box/select-box';
|
||||
import { enqueueState } from "@/factories/history-state-factory";
|
||||
import { LAYER_GRAPHIC, LAYER_TEXT } from "@/definitions/layer-types";
|
||||
|
||||
import messages from "./messages.json";
|
||||
@@ -80,6 +81,7 @@ export default {
|
||||
computed: {
|
||||
...mapGetters([
|
||||
"activeDocument",
|
||||
"activeLayerIndex",
|
||||
"layers",
|
||||
]),
|
||||
layerTypes() {
|
||||
@@ -98,18 +100,28 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([
|
||||
"addLayer",
|
||||
"closeModal",
|
||||
]),
|
||||
requestLayerAdd() {
|
||||
if ( !this.isValid ) {
|
||||
return;
|
||||
}
|
||||
this.addLayer({
|
||||
name: this.name,
|
||||
type: this.type,
|
||||
width: this.activeDocument.width,
|
||||
height: this.activeDocument.height
|
||||
const newLayer = {
|
||||
name : this.name,
|
||||
type : this.type,
|
||||
width : this.activeDocument.width,
|
||||
height : this.activeDocument.height
|
||||
};
|
||||
const store = this.$store;
|
||||
const commit = () => store.commit( "addLayer", newLayer );
|
||||
commit();
|
||||
const addedLayerIndex = this.activeLayerIndex;
|
||||
|
||||
enqueueState( "layerAdd", {
|
||||
undo() {
|
||||
store.commit( "removeLayer", addedLayerIndex );
|
||||
},
|
||||
redo: commit,
|
||||
});
|
||||
this.closeModal();
|
||||
},
|
||||
|
||||
@@ -116,6 +116,7 @@ import { mapGetters, mapMutations } from "vuex";
|
||||
import { ADD_LAYER, LAYER_FILTERS } from "@/definitions/modal-windows";
|
||||
import { createCanvas } from "@/utils/canvas-util";
|
||||
import { getSpriteForLayer } from "@/factories/sprite-factory";
|
||||
import { enqueueState } from "@/factories/history-state-factory";
|
||||
import KeyboardService from "@/services/keyboard-service";
|
||||
import messages from "./messages.json";
|
||||
|
||||
@@ -153,7 +154,6 @@ export default {
|
||||
...mapMutations([
|
||||
"openModal",
|
||||
"removeLayer",
|
||||
"updateLayer",
|
||||
"setActiveLayerIndex",
|
||||
"setActiveLayerMask",
|
||||
"openDialog",
|
||||
@@ -161,31 +161,33 @@ export default {
|
||||
requestLayerAdd() {
|
||||
this.openModal( ADD_LAYER );
|
||||
},
|
||||
requestMaskAdd() {
|
||||
this.updateLayer({
|
||||
index: this.activeLayerIndex,
|
||||
opts: {
|
||||
mask: createCanvas( this.activeLayer.width, this.activeLayer.height ).cvs
|
||||
}
|
||||
});
|
||||
},
|
||||
handleLayerDoubleClick( index ) {
|
||||
this.editable = true;
|
||||
},
|
||||
updateActiveLayerName({ target }) {
|
||||
this.updateLayer({
|
||||
index: this.activeLayerIndex,
|
||||
opts: {
|
||||
name: target.value
|
||||
}
|
||||
const newName = target.value;
|
||||
const currentName = this.activeLayer.name;
|
||||
const index = this.activeLayerIndex;
|
||||
const store = this.$store;
|
||||
const commit = () => store.commit( "updateLayer", { index, opts: { name: newName } });
|
||||
commit();
|
||||
enqueueState( `layerName_${index}`, {
|
||||
undo() {
|
||||
store.commit( "updateLayer", { index, opts: { name: currentName } });
|
||||
},
|
||||
redo: commit,
|
||||
});
|
||||
},
|
||||
toggleLayerVisibility( index ) {
|
||||
this.updateLayer({
|
||||
index,
|
||||
opts: {
|
||||
visible: !this.layers[ index ].visible
|
||||
}
|
||||
const originalVisibility = this.layers[ index ].visible;
|
||||
const store = this.$store;
|
||||
const commit = () => store.commit( "updateLayer", { index, opts: { visible: !originalVisibility } });
|
||||
commit();
|
||||
enqueueState( `layerVisibility_${index}`, {
|
||||
undo() {
|
||||
store.commit( "updateLayer", { index, opts: { visible: originalVisibility } });
|
||||
},
|
||||
redo: commit,
|
||||
});
|
||||
},
|
||||
handleFiltersClick( index ) {
|
||||
@@ -201,22 +203,53 @@ export default {
|
||||
}
|
||||
},
|
||||
requestLayerRemove( index ) {
|
||||
const layer = this.layers[ index ];
|
||||
this.openDialog({
|
||||
type: "confirm",
|
||||
title: this.$t( "areYouSure" ),
|
||||
message: this.$t( "doYouWantToRemoveLayerName", { name: this.layers[ index ]?.name }),
|
||||
message: this.$t( "doYouWantToRemoveLayerName", { name: layer.name }),
|
||||
confirm: () => {
|
||||
this.removeLayer( index );
|
||||
const store = this.$store;
|
||||
const commit = () => store.commit( "removeLayer", index );
|
||||
commit();
|
||||
enqueueState( `layerRemove_${index}`, {
|
||||
undo() {
|
||||
store.commit( "insertLayerAtIndex", { index, layer });
|
||||
},
|
||||
redo: commit,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
requestMaskAdd() {
|
||||
const index = this.activeLayerIndex;
|
||||
const mask = createCanvas( this.activeLayer.width, this.activeLayer.height ).cvs;
|
||||
const store = this.$store;
|
||||
const commit = () => store.commit( "updateLayer", { index, opts: { mask } });
|
||||
commit();
|
||||
enqueueState( `maskAdd_${index}`, {
|
||||
undo() {
|
||||
store.commit( "updateLayer", { index, opts: { mask: null } });
|
||||
},
|
||||
redo: commit,
|
||||
});
|
||||
},
|
||||
requestMaskRemove( index ) {
|
||||
const mask = this.activeLayer.mask;
|
||||
this.openDialog({
|
||||
type: "confirm",
|
||||
title: this.$t( "areYouSure" ),
|
||||
message: this.$t( "doYouWantToRemoveMaskName", { name: this.layers[ index ]?.name }),
|
||||
confirm: () => {
|
||||
this.updateLayer({ index, opts: { mask: null } });
|
||||
const store = this.$store;
|
||||
const commit = () => store.commit( "updateLayer", { index, opts: { mask: null } });
|
||||
commit();
|
||||
enqueueState( `maskRemove_${index}`, {
|
||||
undo() {
|
||||
store.commit( "updateLayer", { index, opts: { mask } });
|
||||
},
|
||||
redo: commit,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
*/
|
||||
import KeyboardService from "@/services/keyboard-service";
|
||||
import DocumentFactory from "@/factories/document-factory";
|
||||
import { initHistory } from "@/factories/history-state-factory";
|
||||
import LayerFactory from "@/factories/layer-factory";
|
||||
import { initHistory, enqueueState } from "@/factories/history-state-factory";
|
||||
import { LAYER_IMAGE } from "@/definitions/layer-types";
|
||||
import { runSpriteFn } from "@/factories/sprite-factory";
|
||||
import canvasModule from "./modules/canvas-module";
|
||||
@@ -193,15 +194,28 @@ export default {
|
||||
runSpriteFn( sprite => sprite.resetSelection(), getters.activeDocument );
|
||||
},
|
||||
pasteSelection({ commit, getters, dispatch, state }) {
|
||||
const { image, size } = state.selectionContent;
|
||||
commit( "addLayer", {
|
||||
const selection = state.selectionContent;
|
||||
const { image, size } = selection;
|
||||
const layer = LayerFactory.create({
|
||||
type: LAYER_IMAGE,
|
||||
source: image,
|
||||
...size,
|
||||
x: getters.activeDocument.width / 2 - size.width / 2,
|
||||
y: getters.activeDocument.height / 2 - size.height / 2,
|
||||
});
|
||||
dispatch( "clearSelection" );
|
||||
const index = getters.activeDocument.layers.length;
|
||||
const paste = () => {
|
||||
commit( "insertLayerAtIndex", { index, layer });
|
||||
dispatch( "clearSelection" );
|
||||
};
|
||||
paste();
|
||||
enqueueState( `paste_${selection.length}`, {
|
||||
undo() {
|
||||
commit( "setSelectionContent", selection );
|
||||
commit( "removeLayer", index );
|
||||
},
|
||||
redo: paste
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Install the services that will listen to global hardware events
|
||||
|
||||
@@ -84,6 +84,11 @@ export default {
|
||||
layers.push( LayerFactory.create( opts ));
|
||||
state.activeLayerIndex = layers.length - 1;
|
||||
},
|
||||
insertLayerAtIndex( state, { index, layer }) {
|
||||
const document = state.documents[ state.activeIndex ];
|
||||
document.layers.splice( index, 0, layer );
|
||||
state.activeLayerIndex = index;
|
||||
},
|
||||
removeLayer( state, index ) {
|
||||
const layer = state.documents[ state.activeIndex ]?.layers[ index ];
|
||||
if ( !layer ) {
|
||||
|
||||
@@ -233,6 +233,20 @@ describe( "Vuex document module", () => {
|
||||
{ name: "layer1" }, expect.any( Object )
|
||||
]);
|
||||
});
|
||||
|
||||
it( "should be able to add layers at specific indices in the layer list", () => {
|
||||
const state = {
|
||||
documents: [ { name: "foo", layers: [{ name: "layer1" }, { name: "layer2" }] }],
|
||||
activeIndex: 0,
|
||||
activeLayerIndex: 0,
|
||||
};
|
||||
const layer = { name: "layer3" };
|
||||
mutations.insertLayerAtIndex( state, { index: 1, layer });
|
||||
expect( state.documents[0].layers).toEqual([
|
||||
{ name: "layer1" }, layer, { name: "layer2" }
|
||||
]);
|
||||
expect( state.activeLayerIndex ).toEqual( 1 );
|
||||
});
|
||||
});
|
||||
|
||||
describe( "when removing layers", () => {
|
||||
|
||||
@@ -16,7 +16,12 @@ jest.mock( "@/factories/document-factory", () => ({
|
||||
jest.mock( "@/utils/file-util", () => ({
|
||||
selectFile: (...args) => mockUpdateFn?.( "selectFile", ...args ),
|
||||
saveBlobAsFile: (...args) => mockUpdateFn?.( "saveBlobAsFile", ...args ),
|
||||
}))
|
||||
}));
|
||||
jest.mock("@/utils/canvas-util", () => ({
|
||||
createCanvas: jest.fn(),
|
||||
imageToBase64: jest.fn(),
|
||||
base64ToLayerImage: jest.fn()
|
||||
}));
|
||||
|
||||
describe( "Vuex store", () => {
|
||||
describe( "getters", () => {
|
||||
@@ -270,18 +275,11 @@ describe( "Vuex store", () => {
|
||||
const state = {
|
||||
selectionContent: { image: { src: "foo" }, size: { width: 40, height: 30 } },
|
||||
};
|
||||
const mockedGetters = { activeDocument: { width: 200, height: 150 } };
|
||||
const mockedGetters = { activeDocument: { width: 200, height: 150, layers: [] } };
|
||||
const commit = jest.fn();
|
||||
const dispatch = jest.fn();
|
||||
await actions.pasteSelection({ state, getters: mockedGetters, commit, dispatch });
|
||||
expect( commit ).toHaveBeenCalledWith( "addLayer", {
|
||||
type: LAYER_IMAGE,
|
||||
source: state.selectionContent.image,
|
||||
width: state.selectionContent.size.width,
|
||||
height: state.selectionContent.size.height,
|
||||
x: 80,
|
||||
y: 60
|
||||
});
|
||||
expect( commit ).toHaveBeenCalledWith( "insertLayerAtIndex", { index: 0, layer: expect.any( Object ) });
|
||||
expect( dispatch ).toHaveBeenCalledWith( "clearSelection" );
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user