2019-09-28 02:29:42 +00:00
|
|
|
import { saveAs } from 'file-saver';
|
|
|
|
import * as JSZip from 'jszip';
|
2019-09-26 01:59:16 +00:00
|
|
|
import { CanvasHandler } from './canvas_handler';
|
2019-09-25 03:16:07 +00:00
|
|
|
import { FileHandler } from './file_handler';
|
|
|
|
import { FrameHandler } from './frame_handler';
|
2019-09-26 01:59:16 +00:00
|
|
|
import { IAnimationData } from './Interfaces/IAnimationData';
|
|
|
|
import { IFrame } from './Interfaces/IFrame';
|
2019-10-01 04:58:04 +00:00
|
|
|
import { IProjectData } from './Interfaces/IProjectData';
|
2019-09-28 17:59:50 +00:00
|
|
|
import { PinHandler } from './pin_handler';
|
2019-09-25 03:16:07 +00:00
|
|
|
|
2019-09-25 00:01:54 +00:00
|
|
|
export class Page {
|
2019-09-25 03:16:07 +00:00
|
|
|
private static handleDragOver(evt: DragEvent) {
|
|
|
|
if (evt !== null) {
|
|
|
|
evt.stopPropagation();
|
|
|
|
evt.preventDefault();
|
|
|
|
evt.dataTransfer!.dropEffect = 'copy'; // Explicitly show this is a copy.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-28 17:59:50 +00:00
|
|
|
private pinHandler: PinHandler;
|
2019-09-26 01:59:16 +00:00
|
|
|
private frameHandler: FrameHandler;
|
|
|
|
private canvasHandler: CanvasHandler;
|
|
|
|
private animationData: IAnimationData;
|
2019-09-27 01:07:41 +00:00
|
|
|
private frameRateInput: HTMLInputElement;
|
2019-09-27 02:02:46 +00:00
|
|
|
private loopingInput: HTMLInputElement;
|
|
|
|
|
|
|
|
private canvasImage: HTMLCanvasElement;
|
|
|
|
private canvasContext: CanvasRenderingContext2DSettings;
|
2019-09-25 03:16:07 +00:00
|
|
|
|
2019-10-01 04:40:16 +00:00
|
|
|
private projectData: IProjectData;
|
2019-09-28 00:04:01 +00:00
|
|
|
private filenameInput: HTMLInputElement;
|
2019-09-27 23:45:52 +00:00
|
|
|
|
2019-10-03 23:33:32 +00:00
|
|
|
private message: HTMLElement;
|
|
|
|
|
2019-09-25 00:01:54 +00:00
|
|
|
public Load() {
|
2019-09-26 01:59:16 +00:00
|
|
|
// defining blank slate animation data
|
|
|
|
this.animationData = {
|
|
|
|
pins: [],
|
2019-10-03 23:33:32 +00:00
|
|
|
originX: -1,
|
|
|
|
originY: -1,
|
2019-09-26 01:59:16 +00:00
|
|
|
frameRate: 30,
|
|
|
|
loop: true,
|
|
|
|
frames: [
|
|
|
|
{
|
2019-10-01 04:40:16 +00:00
|
|
|
filename: ''
|
2019-09-26 01:59:16 +00:00
|
|
|
}
|
|
|
|
]
|
|
|
|
};
|
2019-09-28 02:29:42 +00:00
|
|
|
// blank slate canvas data
|
2019-10-01 04:40:16 +00:00
|
|
|
this.projectData = {
|
|
|
|
currentFrame: 0,
|
|
|
|
currentlySelectedPin: 0,
|
2019-09-27 23:45:52 +00:00
|
|
|
width: 0,
|
|
|
|
height: 0,
|
|
|
|
widthRatio: 0,
|
|
|
|
heightRatio: 0
|
|
|
|
};
|
2019-09-26 01:59:16 +00:00
|
|
|
|
2019-10-03 23:33:32 +00:00
|
|
|
this.message = document.getElementById('message') as HTMLElement;
|
2019-09-27 23:45:52 +00:00
|
|
|
const canvasElement = document.getElementById('canvasImage') as HTMLCanvasElement;
|
2019-09-27 02:02:46 +00:00
|
|
|
|
2019-09-27 23:45:52 +00:00
|
|
|
const imageElement = new Image();
|
2019-09-27 02:02:46 +00:00
|
|
|
|
2019-09-28 17:59:50 +00:00
|
|
|
this.pinHandler = new PinHandler(
|
|
|
|
document.getElementById('addpin') as HTMLElement,
|
2019-10-01 04:40:16 +00:00
|
|
|
document.getElementById('pinSettings') as HTMLElement,
|
|
|
|
document.getElementById('pinContainer') as HTMLElement,
|
|
|
|
document.getElementById('originPin') as HTMLElement,
|
|
|
|
this.projectData,
|
|
|
|
this.animationData
|
2019-09-28 17:59:50 +00:00
|
|
|
);
|
|
|
|
|
2019-09-27 23:45:52 +00:00
|
|
|
// setup canvas
|
|
|
|
this.canvasHandler = new CanvasHandler(
|
|
|
|
this.animationData,
|
2019-10-01 04:40:16 +00:00
|
|
|
this.projectData,
|
2019-09-27 23:45:52 +00:00
|
|
|
canvasElement,
|
|
|
|
imageElement,
|
|
|
|
document.getElementById('originInfo') as HTMLElement
|
|
|
|
);
|
2019-09-26 01:59:16 +00:00
|
|
|
|
2019-09-27 23:45:52 +00:00
|
|
|
// setup frame handler
|
2019-09-26 01:59:16 +00:00
|
|
|
this.frameHandler = new FrameHandler(
|
2019-09-27 00:45:28 +00:00
|
|
|
this.animationData,
|
2019-10-01 04:40:16 +00:00
|
|
|
this.projectData,
|
2019-09-27 23:45:52 +00:00
|
|
|
canvasElement,
|
|
|
|
canvasElement.getContext('2d')!,
|
|
|
|
document.getElementById('frameNumber') as HTMLElement,
|
2019-10-01 04:40:16 +00:00
|
|
|
imageElement,
|
2019-10-04 01:02:08 +00:00
|
|
|
this.projectData,
|
|
|
|
document.getElementById('frameViewer') as HTMLElement
|
2019-09-26 01:59:16 +00:00
|
|
|
);
|
2019-09-25 03:16:07 +00:00
|
|
|
|
2019-09-27 23:45:52 +00:00
|
|
|
// input elements
|
2019-09-27 01:07:41 +00:00
|
|
|
this.frameRateInput = document.getElementById('framerate') as HTMLInputElement;
|
2019-10-03 02:35:34 +00:00
|
|
|
this.frameRateInput.addEventListener('change', this.UpdateFrameRate);
|
2019-09-27 02:02:46 +00:00
|
|
|
this.loopingInput = document.getElementById('looping') as HTMLInputElement;
|
2019-10-03 02:35:34 +00:00
|
|
|
this.loopingInput.addEventListener('change', this.UpdateLooping);
|
2019-09-28 00:04:01 +00:00
|
|
|
this.filenameInput = document.getElementById('filename') as HTMLInputElement;
|
2019-09-27 01:07:41 +00:00
|
|
|
|
2019-09-25 03:16:07 +00:00
|
|
|
const dropZone = document.getElementById('dropZone') as HTMLElement;
|
|
|
|
|
|
|
|
dropZone.addEventListener('dragover', Page.handleDragOver, false);
|
|
|
|
dropZone.addEventListener('drop', this.handleFileSelect, false);
|
2019-09-25 00:42:35 +00:00
|
|
|
|
2019-10-03 02:35:34 +00:00
|
|
|
this.ResetProgram();
|
|
|
|
|
2019-09-25 03:16:07 +00:00
|
|
|
const keyDown = (event: KeyboardEvent) => {
|
|
|
|
switch (event.keyCode) {
|
2019-09-27 00:45:28 +00:00
|
|
|
case 48:
|
|
|
|
case 49:
|
|
|
|
case 50:
|
|
|
|
case 51:
|
|
|
|
case 52:
|
|
|
|
case 53:
|
|
|
|
case 54:
|
|
|
|
case 55:
|
|
|
|
case 56:
|
|
|
|
case 57: {
|
|
|
|
// goto frame w 1234567890
|
2019-10-04 01:11:26 +00:00
|
|
|
let targetFrame: number = event.keyCode - 49;
|
2019-09-27 00:45:28 +00:00
|
|
|
if (event.keyCode === 48) {
|
2019-10-04 01:11:26 +00:00
|
|
|
targetFrame = 9;
|
2019-09-27 00:45:28 +00:00
|
|
|
}
|
2019-10-04 01:11:26 +00:00
|
|
|
|
|
|
|
targetFrame %= this.animationData.frames.length;
|
|
|
|
|
2019-09-27 00:45:28 +00:00
|
|
|
this.frameHandler.StopPlayingAnimation();
|
2019-10-04 01:11:26 +00:00
|
|
|
this.frameHandler.GoToFrame(targetFrame);
|
|
|
|
this.frameHandler.RefreshFrameViewer();
|
2019-09-27 00:45:28 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-09-26 23:28:35 +00:00
|
|
|
case 39:
|
|
|
|
case 190: {
|
|
|
|
// right_arrow, carrot
|
2019-09-25 03:16:07 +00:00
|
|
|
console.log('next frame action');
|
2019-09-26 01:59:16 +00:00
|
|
|
this.frameHandler.AdvanceFrames(1);
|
2019-09-27 00:45:28 +00:00
|
|
|
this.frameHandler.StopPlayingAnimation();
|
2019-09-25 03:16:07 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-09-26 23:28:35 +00:00
|
|
|
case 37:
|
|
|
|
case 188: {
|
|
|
|
// left arrow, carrot
|
2019-09-25 03:16:07 +00:00
|
|
|
console.log('previous frame action');
|
2019-09-26 01:59:16 +00:00
|
|
|
this.frameHandler.AdvanceFrames(-1);
|
2019-09-27 00:45:28 +00:00
|
|
|
this.frameHandler.StopPlayingAnimation();
|
2019-09-25 03:16:07 +00:00
|
|
|
break;
|
|
|
|
}
|
2019-09-26 23:28:35 +00:00
|
|
|
|
|
|
|
case 40: {
|
|
|
|
// down arrow
|
|
|
|
this.frameHandler.GoToFrame(0);
|
2019-09-27 00:45:28 +00:00
|
|
|
this.frameHandler.StopPlayingAnimation();
|
|
|
|
break;
|
2019-09-26 23:28:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case 32: {
|
|
|
|
// spacebar
|
|
|
|
this.frameHandler.TogglePlayingAnimation();
|
2019-09-27 00:45:28 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case 83: {
|
2019-10-03 02:35:34 +00:00
|
|
|
if (document.activeElement === document.body) {
|
|
|
|
this.pinHandler.UpdateAnimationPinNames();
|
|
|
|
|
2019-10-04 01:02:08 +00:00
|
|
|
if (this.ProjectHasNeccesaryData()) {
|
2019-10-03 02:35:34 +00:00
|
|
|
const zip = new JSZip();
|
|
|
|
// name of project
|
|
|
|
const name = this.filenameInput.value;
|
|
|
|
// .anim file
|
|
|
|
zip.file(name + '.anim', JSON.stringify(this.animationData));
|
|
|
|
// pngs
|
|
|
|
const filenames = this.frameHandler.GetFilenames();
|
|
|
|
for (let i = 0; i < filenames.length; i++) {
|
|
|
|
const filedata = filenames[i].split('base64,')[1];
|
|
|
|
const padding = i.toString().padStart(3, '0');
|
|
|
|
zip.file(name + '_' + padding.toString() + '.png', filedata, { base64: true });
|
|
|
|
}
|
|
|
|
// save zip
|
|
|
|
zip.generateAsync({ type: 'blob' }).then((content) => {
|
|
|
|
// see FileSaver.js
|
|
|
|
saveAs(content, name + '.zip');
|
|
|
|
});
|
|
|
|
}
|
2019-09-28 00:04:01 +00:00
|
|
|
}
|
2019-09-26 23:28:35 +00:00
|
|
|
}
|
2019-09-25 03:16:07 +00:00
|
|
|
}
|
2019-09-25 00:42:35 +00:00
|
|
|
};
|
2019-09-25 03:16:07 +00:00
|
|
|
|
|
|
|
document.addEventListener('keydown', keyDown);
|
2019-09-25 00:01:54 +00:00
|
|
|
}
|
2019-09-25 03:16:07 +00:00
|
|
|
|
2019-10-04 01:02:08 +00:00
|
|
|
private ProjectHasNeccesaryData(): boolean {
|
|
|
|
let pass: boolean = true;
|
2019-10-03 23:33:32 +00:00
|
|
|
let errorString: string = '';
|
2019-10-04 01:02:08 +00:00
|
|
|
this.frameHandler.RefreshFrameViewer();
|
|
|
|
if (this.filenameInput.value === '') {
|
|
|
|
errorString += '- Missing name\n';
|
|
|
|
pass = false;
|
|
|
|
}
|
|
|
|
if (this.animationData.originX === null || this.animationData.originY === null) {
|
|
|
|
errorString += '- Missing origin data\n';
|
|
|
|
pass = false;
|
|
|
|
}
|
|
|
|
// check frames for data errors
|
|
|
|
let pinDataErrorString: string = '';
|
|
|
|
let passPinData: boolean = true;
|
|
|
|
for (let f = 0; f < this.animationData.frames.length; f++) {
|
|
|
|
let errorOnFrame: boolean = false;
|
|
|
|
if (this.animationData.pins !== undefined) {
|
|
|
|
for (let p = 0; p < this.animationData.pins.length; p++) {
|
|
|
|
if (this.animationData.pins[p] !== undefined) {
|
|
|
|
const pinIDtoCheck = this.animationData.pins[p].id;
|
|
|
|
console.log('checking frame ' + f + ' for pinID ' + this.animationData.pins[p].name);
|
|
|
|
if (this.animationData.frames[f][pinIDtoCheck] === undefined) {
|
|
|
|
if (!errorOnFrame) {
|
|
|
|
pinDataErrorString += ' Frame ' + f + ' :\n';
|
|
|
|
}
|
|
|
|
pinDataErrorString += ' Pin: ' + this.animationData.pins[p].name + '\n';
|
|
|
|
passPinData = false;
|
|
|
|
}
|
|
|
|
}
|
2019-10-03 23:33:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-10-04 01:02:08 +00:00
|
|
|
if (!passPinData) {
|
|
|
|
errorString += '- Missing pin data on some frames: \n' + pinDataErrorString;
|
|
|
|
pass = false;
|
2019-10-03 23:33:32 +00:00
|
|
|
}
|
2019-10-04 01:02:08 +00:00
|
|
|
if (!pass) {
|
|
|
|
alert(errorString);
|
2019-10-03 23:33:32 +00:00
|
|
|
}
|
2019-10-04 01:02:08 +00:00
|
|
|
return pass;
|
2019-10-03 02:35:34 +00:00
|
|
|
}
|
|
|
|
|
2019-09-26 01:59:16 +00:00
|
|
|
private handleFileSelect = async (event: DragEvent) => {
|
2019-10-03 02:35:34 +00:00
|
|
|
this.ResetProgram();
|
|
|
|
|
2019-09-25 03:16:07 +00:00
|
|
|
event.stopPropagation();
|
|
|
|
event.preventDefault();
|
|
|
|
|
2019-09-26 01:59:16 +00:00
|
|
|
const filenames = await FileHandler.ProcessImages(event.dataTransfer!.files);
|
|
|
|
this.frameHandler.loadFrames(filenames);
|
|
|
|
|
|
|
|
const newFrames: IFrame[] = [];
|
|
|
|
|
|
|
|
for (let i = 0; i < event.dataTransfer!.files.length; i++) {
|
|
|
|
newFrames.push({
|
2019-10-01 04:40:16 +00:00
|
|
|
filename: event.dataTransfer!.files[i].name
|
2019-09-26 01:59:16 +00:00
|
|
|
});
|
|
|
|
}
|
2019-09-25 03:16:07 +00:00
|
|
|
|
2019-09-26 01:59:16 +00:00
|
|
|
this.animationData.frames = newFrames;
|
2019-09-27 02:02:46 +00:00
|
|
|
this.frameHandler.GoToFrame(0);
|
|
|
|
this.frameHandler.StopPlayingAnimation();
|
|
|
|
this.frameHandler.TogglePlayingAnimation();
|
2019-09-28 01:35:27 +00:00
|
|
|
|
|
|
|
this.canvasHandler.ResizeCanvas();
|
|
|
|
|
2019-10-04 01:02:08 +00:00
|
|
|
this.frameHandler.ConstructFrameUI();
|
2019-09-25 03:16:07 +00:00
|
|
|
};
|
2019-09-27 00:45:28 +00:00
|
|
|
|
2019-10-03 02:35:34 +00:00
|
|
|
private ResetProgram = () => {
|
|
|
|
// defining blank slate animation data
|
|
|
|
this.animationData.pins = [];
|
2019-10-04 01:02:08 +00:00
|
|
|
this.animationData.originX = null;
|
|
|
|
this.animationData.originY = null;
|
2019-10-03 02:35:34 +00:00
|
|
|
this.animationData.frameRate = 30;
|
|
|
|
this.animationData.loop = true;
|
|
|
|
this.animationData.frames = [ { filename: '' } ];
|
2019-09-27 00:45:28 +00:00
|
|
|
|
2019-10-03 02:35:34 +00:00
|
|
|
// blank slate canvas data
|
|
|
|
this.projectData.currentFrame = 0;
|
|
|
|
this.projectData.currentlySelectedPin = 0;
|
|
|
|
this.projectData.width = 0;
|
|
|
|
this.projectData.widthRatio = 0;
|
|
|
|
this.projectData.height = 0;
|
|
|
|
this.projectData.heightRatio = 0;
|
|
|
|
|
|
|
|
// reset input displays
|
|
|
|
this.frameRateInput.value = this.animationData.frameRate.toString();
|
|
|
|
this.loopingInput.checked = this.animationData.loop;
|
|
|
|
this.filenameInput.value = '';
|
2019-09-27 00:45:28 +00:00
|
|
|
|
2019-10-03 02:35:34 +00:00
|
|
|
// destroy pin divs
|
|
|
|
this.pinHandler.RemoveAllPins();
|
|
|
|
this.projectData.currentlySelectedPin = 0;
|
|
|
|
};
|
2019-09-27 00:45:28 +00:00
|
|
|
|
2019-10-03 02:35:34 +00:00
|
|
|
private UpdateLooping = () => {
|
|
|
|
this.animationData.loop = this.loopingInput.checked;
|
|
|
|
};
|
2019-09-27 01:07:41 +00:00
|
|
|
|
2019-10-03 02:35:34 +00:00
|
|
|
private UpdateFrameRate = () => {
|
2019-09-27 01:07:41 +00:00
|
|
|
this.animationData.frameRate = this.frameRateInput.valueAsNumber;
|
2019-09-27 23:45:52 +00:00
|
|
|
this.frameHandler.StopPlayingAnimation();
|
|
|
|
this.frameHandler.TogglePlayingAnimation();
|
2019-09-27 01:07:41 +00:00
|
|
|
console.log('new frame rate = ' + this.animationData.frameRate);
|
|
|
|
};
|
2019-09-25 00:01:54 +00:00
|
|
|
}
|