Skip to content

Commit

Permalink
Implemented clipboard pasting of audio and Efflux project files
Browse files Browse the repository at this point in the history
  • Loading branch information
Igor Zinken committed Feb 27, 2022
1 parent af8b84d commit 5b41fc5
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 26 deletions.
23 changes: 20 additions & 3 deletions src/efflux-application.vue
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ import { loadSample } from "@/services/audio/sample-loader";
import PubSubService from "@/services/pubsub-service";
import PubSubMessages from "@/services/pubsub/messages";
import SampleFactory from "@/model/factories/sample-factory";
import { readDroppedFiles } from "@/utils/file-util";
import { readClipboardFiles, readDroppedFiles } from "@/utils/file-util";
import store from "@/store";
import messages from "@/messages.json";
Expand Down Expand Up @@ -187,6 +187,7 @@ export default {
"activeSong",
"displayHelp",
"displayWelcome",
"hasChanges",
"isLoading",
"timelineMode",
]),
Expand Down Expand Up @@ -323,11 +324,26 @@ export default {
}
}
for ( const file of projects ) {
this.loadSong({ file });
this.closeModal();
const confirm = () => {
this.loadSong({ file });
this.closeModal();
};
if ( this.hasChanges ) {
this.openDialog({
type: "confirm",
message: this.$t( "warnings.loadNewPendingChanges" ),
confirm
});
} else {
confirm();
}
}
};
window.addEventListener( "paste", event => {
loadFiles( readClipboardFiles( event?.clipboardData ));
}, false );
window.addEventListener( "dragover", event => {
event.stopPropagation();
event.preventDefault();
Expand Down Expand Up @@ -375,6 +391,7 @@ export default {
"setWindowSize",
"resetEditor",
"resetHistory",
"openDialog",
"openModal",
"closeModal",
"showNotification",
Expand Down
5 changes: 2 additions & 3 deletions src/services/keyboard-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -422,9 +422,8 @@ function handleKeyDown( event ) {
case 86: // V

// paste current selection
if ( hasOption ) {
if ( hasOption && store.getters.hasCopiedEvents ) {
store.commit( "saveState", createAction( Actions.PASTE_SELECTION, { store }));
preventDefault( event ); // override browser paste
}
break;

Expand All @@ -434,7 +433,7 @@ function handleKeyDown( event ) {

if ( hasOption ) {
store.commit( "saveState", createAction( Actions.CUT_SELECTION, { store }));
preventDefault(event); // override browser cut
preventDefault( event ); // override browser cut
}
break;

Expand Down
12 changes: 8 additions & 4 deletions src/store/modules/selection-module.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* The MIT License (MIT)
*
* Igor Zinken 2016-2021 - https://www.igorski.nl
* Igor Zinken 2016-2022 - 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
Expand Down Expand Up @@ -67,7 +67,7 @@ const getSelectionLength = state => ( state.maxSelectedStep - state.minSelectedS
* this model so it can later on be pasted from this model
*/
const copySelection = ( state, { song, activePattern, optOutputArray }) => {
if ( getSelectionLength(state) === 0 )
if ( getSelectionLength( state ) === 0 )
return;

let i, max = state.selectedChannels.length;
Expand All @@ -80,7 +80,7 @@ const copySelection = ( state, { song, activePattern, optOutputArray }) => {
for ( i = 0; i < max; ++i )
optOutputArray.push( [] );

let pattern = song.patterns[activePattern], stepValue;
let pattern = song.patterns[ activePattern ], stepValue;
let channel;
let copyIndex = 0;

Expand All @@ -96,6 +96,9 @@ const copySelection = ( state, { song, activePattern, optOutputArray }) => {
++copyIndex;
}
}
// by writing into the clipboard we ensure that copied files are no
// longer in the clipboard history (prevents double load on paste shortcut)
window.navigator.clipboard.writeText( JSON.stringify( optOutputArray ));
};

/**
Expand Down Expand Up @@ -254,7 +257,8 @@ const module = {
const out = [];
copySelection( state, { song, activePattern, out });
return out;
}
},
hasCopiedEvents: state => !!state.copySelection?.length,
},
mutations: {
setMinSelectedStep( state, value ) {
Expand Down
13 changes: 12 additions & 1 deletion src/utils/file-util.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* The MIT License (MIT)
*
* Igor Zinken 2019-2021 - https://www.igorski.nl
* Igor Zinken 2019-2022 - 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
Expand Down Expand Up @@ -121,6 +121,17 @@ export const openFileBrowser = ( handler, acceptedFileTypes ) => {
fileBrowser.addEventListener( "change", handler );
};

export const readClipboardFiles = clipboardData => {
const items = [ ...( clipboardData?.items || []) ]
.filter( item => item.kind === "file" )
.map( item => item.getAsFile() );

return {
sounds : items.filter( isSoundFile ),
projects : items.filter( isProjectFile )
};
};

export const readDroppedFiles = dataTransfer => {
const items = [ ...( dataTransfer?.files || []) ];
return {
Expand Down
41 changes: 26 additions & 15 deletions tests/unit/store/modules/selection-module.spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import SelectionModule from '@/store/modules/selection-module';
import SelectionModule from "@/store/modules/selection-module";

const { getters, mutations } = SelectionModule;

describe('Vuex selection module', () => {
describe("Vuex selection module", () => {
let state;
beforeEach(() => {
state = {
Expand All @@ -15,8 +15,8 @@ describe('Vuex selection module', () => {
};
});

describe('getters', () => {
it('should know the full length of its selection', () => {
describe("getters", () => {
it("should know the full length of its selection", () => {
mutations.setSelectionChannelRange(state, { firstChannel: 0, lastChannel: 1 });
const min = 0;
const max = 16;
Expand All @@ -28,18 +28,29 @@ describe('Vuex selection module', () => {
expect(expected).toEqual(getters.getSelectionLength(state));
});

it('should know whether it has a selection', () => {
it("should know whether it has a selection", () => {
expect(getters.hasSelection(state)).toBe(false);

mutations.setSelectionChannelRange(state, { firstChannel: 0, lastChannel: 4 });
mutations.setSelection(state, { selectionStart: 0, selectionEnd: 16 });

expect(getters.hasSelection(state)).toBe(true);
});

it("should know whether there is a range of copied events in the selection state", () => {
const state = { copySelection: null };
expect( getters.hasCopiedEvents( state )).toBe( false );

state.copySelection = [];
expect( getters.hasCopiedEvents( state )).toBe( false );

state.copySelection = [{ foo: "bar" }];
expect( getters.hasCopiedEvents( state )).toBe( true );
});
});

describe('mutations', () => {
it('should be able to select multiple channels for its selection', () => {
describe("mutations", () => {
it("should be able to select multiple channels for its selection", () => {
mutations.setSelectionChannelRange(state, { firstChannel: 0 });

expect(1).toEqual(state.selectedChannels.length);
Expand All @@ -51,7 +62,7 @@ describe('Vuex selection module', () => {
expect(3).toEqual(state.lastSelectedChannel);
});

it('should add indices to its current selection', () => {
it("should add indices to its current selection", () => {
let min = 0, i;
const max = 16;

Expand All @@ -63,7 +74,7 @@ describe('Vuex selection module', () => {
}
});

it('should know the minimum and maximum indices of its selection', () => {
it("should know the minimum and maximum indices of its selection", () => {
mutations.setSelectionChannelRange(state, { firstChannel: 0 }); // select a single channel

let min = 0;
Expand All @@ -76,7 +87,7 @@ describe('Vuex selection module', () => {
expect(max).toEqual(state.maxSelectedStep);
});

it('should add not add the same index twice to its current selection', () => {
it("should add not add the same index twice to its current selection", () => {
const activeChannel = 0, max = 1;

mutations.setSelectionChannelRange(state, { firstChannel: activeChannel, lastChannel: max });
Expand All @@ -89,7 +100,7 @@ describe('Vuex selection module', () => {
expect(2).toEqual(state.selectedChannels[activeChannel].length);
});

it('should be able to clear its selection', () => {
it("should be able to clear its selection", () => {
mutations.setSelectionChannelRange(state, { firstChannel: 0, lastChannel: 1 });

mutations.setSelection(state, { selectionStart: 0, selectionEnd: 1 });
Expand All @@ -101,7 +112,7 @@ describe('Vuex selection module', () => {
expect(getters.hasSelection(state)).toBe(false);
});

it('should be able to equalize the selection for all channels', () => {
it("should be able to equalize the selection for all channels", () => {
mutations.setSelectionChannelRange(state, { firstChannel: 0, lastChannel: 3 });

const activeChannel = 0;
Expand All @@ -114,14 +125,14 @@ describe('Vuex selection module', () => {
expect(JSON.stringify(state.selectedChannels[activeChannel])).toEqual(JSON.stringify(state.selectedChannels[otherChannel]));
});

it('should treat a single step as 1 unit range', () => {
it("should treat a single step as 1 unit range", () => {
mutations.setSelectionChannelRange(state, { firstChannel: 0, lastChannel: 1 });
mutations.setSelection(state, { selectionStart: 0 });

expect(1).toEqual(getters.getSelectionLength(state));
});

it('should be able to expand and shrink its selection when starting key selection to the right', () => {
it("should be able to expand and shrink its selection when starting key selection to the right", () => {
let keyCode = 39; // right
let selectedChannel = 2;
const selectedStep = 1;
Expand Down Expand Up @@ -163,7 +174,7 @@ describe('Vuex selection module', () => {
expect( state.lastSelectedChannel ).toEqual( 2 );
});

it('should be able to expand and shrink its selection when starting key selection to the left', () => {
it("should be able to expand and shrink its selection when starting key selection to the left", () => {
let keyCode = 37; // left
let selectedChannel = 2;
const selectedStep = 1;
Expand Down

0 comments on commit 5b41fc5

Please sign in to comment.