mirror of
https://github.com/piskelapp/piskel.git
synced 2023-08-10 21:12:52 +03:00
Compare commits
56 Commits
v0.12.0
...
add-catch-
Author | SHA1 | Date | |
---|---|---|---|
a69b44eaec | |||
3e779a651f | |||
ab9bbce1ed | |||
cfd3773a2b | |||
0eface45f1 | |||
87893bb4ac | |||
77d26bffa9 | |||
652027bd3f | |||
bf4cc3302a | |||
95c8df1224 | |||
7445357368 | |||
a2369cac0c | |||
51538dff48 | |||
da739e78da | |||
dd8217e21b | |||
d502d3416b | |||
d1156954ca | |||
dc5209628c | |||
8568663949 | |||
fd3d828067 | |||
e1797b2008 | |||
0a43f6bbec | |||
b9423bc831 | |||
5e6280301d | |||
5671eb4782 | |||
35788b54ba | |||
629ecf83b4 | |||
c037b07693 | |||
c31b7a351c | |||
7de03f1e73 | |||
eab21e0839 | |||
2b3bd02479 | |||
4e86fa1570 | |||
170a7e4731 | |||
6b7f04b63e | |||
da2e9f99e4 | |||
530a949e54 | |||
4377c9e601 | |||
e0bbb88d47 | |||
9ff2ecbb45 | |||
8beba2088b | |||
ee45cdcc45 | |||
30ea7fa079 | |||
e9b39a5c61 | |||
d0a32b18c5 | |||
372ad1f513 | |||
c6e106fe2d | |||
f9570ea3c5 | |||
f9cb631acb | |||
ed749a747f | |||
30ecd41452 | |||
af65344c23 | |||
183133496e | |||
8a2c0191f9 | |||
a096dcabfd | |||
96d326ef12 |
@ -1,14 +1,9 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "4.1"
|
||||
- "7.4.0"
|
||||
before_install:
|
||||
- npm update -g npm
|
||||
- npm install -g grunt-cli
|
||||
- git clone git://github.com/n1k0/casperjs.git ~/casperjs
|
||||
- cd ~/casperjs
|
||||
- git checkout tags/1.1.3
|
||||
- export PATH=$PATH:`pwd`/bin
|
||||
- cd -
|
||||
before_script:
|
||||
- phantomjs --version
|
||||
- casperjs --version
|
||||
|
@ -17,7 +17,16 @@ function onCopy(err) {
|
||||
|
||||
console.log('Copied static files to piskel-website...');
|
||||
let previousPartialPath = path.resolve(PISKELAPP_PATH, 'templates/editor/main-partial.html');
|
||||
fs.unlink(previousPartialPath, onDeletePreviousPartial);
|
||||
fs.access(previousPartialPath, fs.constants.F_OK, function (err) {
|
||||
if (err) {
|
||||
// File does not exit, call next step directly.
|
||||
console.error('Previous main partial doesn\'t exist yet.');
|
||||
onDeletePreviousPartial();
|
||||
} else {
|
||||
// File exists, try to delete it before moving on.
|
||||
fs.unlink(previousPartialPath, onDeletePreviousPartial);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onDeletePreviousPartial(err) {
|
||||
|
80
misc/icons/source/common-backup.svg
Normal file
80
misc/icons/source/common-backup.svg
Normal file
@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="90"
|
||||
height="90"
|
||||
viewBox="0 0 89.999997 90"
|
||||
enable-background="new 0 0 89.231 100"
|
||||
xml:space="preserve"
|
||||
inkscape:version="0.92.1 r15371"
|
||||
sodipodi:docname="common-backup.svg"
|
||||
inkscape:export-filename="C:\Development\git\piskel\misc\icons\source\tool-rotate.png"
|
||||
inkscape:export-xdpi="45"
|
||||
inkscape:export-ydpi="45"><metadata
|
||||
id="metadata15"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs13" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1148"
|
||||
id="namedview11"
|
||||
showgrid="false"
|
||||
inkscape:zoom="7.75"
|
||||
inkscape:cx="13.031976"
|
||||
inkscape:cy="43.272537"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" /><g
|
||||
id="g3760"
|
||||
transform="matrix(0,-0.97677741,0.97203982,0,-2.1261998,91.355253)"
|
||||
style="fill:#ff00ff;fill-opacity:1"><path
|
||||
style="fill:#ff00ff;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3"
|
||||
d="m 29.229405,55.37008 c -0.387431,-1.333059 -0.642506,-2.72161 -0.738881,-4.152895 h -8.675106 c 0.115651,2.460738 0.552554,4.838559 1.260594,7.099021 z" /><path
|
||||
style="fill:#ff00ff;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5"
|
||||
d="m 29.023802,70.783821 5.579515,-6.601516 c -1.862622,-1.780814 -3.387929,-3.907969 -4.44999,-6.287065 l -8.152106,2.946124 c 1.604978,3.800815 4.017584,7.185766 7.022581,9.942457 z" /><path
|
||||
style="fill:#ff00ff;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path7"
|
||||
d="m 47.110967,69.703978 c -3.887799,-0.260871 -7.469766,-1.6322 -10.437498,-3.790608 l -5.577588,6.598963 c 4.487901,3.403448 10.011517,5.524225 16.015086,5.803594 z" /><path
|
||||
style="fill:#ff00ff;fill-opacity:1;stroke:none"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path9"
|
||||
d="M 48.464084,21.400659 V 14.532532 L 28.981398,25.698341 48.464084,36.86415 v -6.867489 c 11.042093,0 20.024317,8.91683 20.024317,19.877897 0,10.509484 -8.258763,19.134189 -18.671845,19.828145 v 8.611948 c 15.190751,-0.703524 27.330245,-13.189635 27.330245,-28.440093 0,-15.700763 -12.86681,-28.473899 -28.682717,-28.473899 z" /></g><g
|
||||
id="g4513"
|
||||
transform="translate(0,-2)"><rect
|
||||
y="32.516129"
|
||||
x="42"
|
||||
height="15.612903"
|
||||
width="7.9999986"
|
||||
id="rect4494"
|
||||
style="fill:#ff00f7;fill-opacity:1;stroke:#ffffed;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:6.04534006;stroke-opacity:1" /><rect
|
||||
transform="rotate(120)"
|
||||
y="-76.050484"
|
||||
x="12.680965"
|
||||
height="15.612903"
|
||||
width="7.9999986"
|
||||
id="rect4494-7"
|
||||
style="fill:#ff00f7;fill-opacity:1;stroke:#ffffed;stroke-width:0;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:6.04534006;stroke-opacity:1" /></g></svg>
|
After Width: | Height: | Size: 3.7 KiB |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "piskel",
|
||||
"version": "0.12.0",
|
||||
"version": "0.12.1",
|
||||
"description": "Pixel art editor",
|
||||
"author": "Julian Descottes <julian.descottes@gmail.com>",
|
||||
"contributors": [
|
||||
|
152
src/css/dialogs-browse-backups.css
Normal file
152
src/css/dialogs-browse-backups.css
Normal file
@ -0,0 +1,152 @@
|
||||
#dialog-container.browse-backups {
|
||||
width: 700px;
|
||||
height: 500px;
|
||||
top : 50%;
|
||||
left : 50%;
|
||||
position : absolute;
|
||||
margin-left: -350px;
|
||||
}
|
||||
|
||||
.backups-step-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.backups-step-content {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
flex-grow: 1;
|
||||
background: #444;
|
||||
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.backups-step-actions {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.show #dialog-container.browse-backups {
|
||||
margin-top: -250px;
|
||||
}
|
||||
|
||||
.browse-backups .browse-backups-disclaimer {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.browse-backups .browse-backups-disclaimer-content {
|
||||
padding: 0 20px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.browse-backups .browse-backups-disclaimer .backups-icon {
|
||||
border: 1px solid gold;
|
||||
flex-shrink: 0;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.browse-backups .centered-message {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
width: 200px;
|
||||
margin-top: 100px;
|
||||
margin-left: -130px;
|
||||
padding: 30px;
|
||||
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.browse-backups .session-list-empty,
|
||||
.browse-backups .snapshot-list-empty {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.browse-backups .session-list-error,
|
||||
.browse-backups .snapshot-list-error {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.browse-backups .session-item {
|
||||
/* Transition duration should be kept in sync with SelectSession.DELETE_TRANSITION_DURATION */
|
||||
transition: all 500ms;
|
||||
}
|
||||
|
||||
/* Hide and slide up next sessions when deleting an item */
|
||||
.browse-backups .session-item.deleting {
|
||||
opacity: 0;
|
||||
margin-bottom: -60px;
|
||||
}
|
||||
|
||||
.browse-backups .session-item,
|
||||
.browse-backups .snapshot-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
|
||||
margin-bottom: 10px;
|
||||
padding: 0 20px;
|
||||
|
||||
border: 1px solid #666;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.browse-backups .snapshot-preview {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
/* Keep synced with SessionDetails.PREVIEW_SIZE */
|
||||
height: 60px;
|
||||
width: 60px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.browse-backups .session-details,
|
||||
.browse-backups .snapshot-details {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.browse-backups .session-details-title,
|
||||
.browse-backups .snapshot-details-title {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.browse-backups .session-details-info,
|
||||
.browse-backups .snapshot-details-info {
|
||||
font-size: 11px;
|
||||
color: #bbb;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.browse-backups .session-actions,
|
||||
.browse-backups .snapshot-actions {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.browse-backups .session-actions button,
|
||||
.browse-backups .snapshot-actions button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.browse-backups .session-item:last-child,
|
||||
.browse-backups .snapshot-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
18
src/css/dialogs-import.css
vendored
18
src/css/dialogs-import.css
vendored
@ -183,22 +183,14 @@
|
||||
.import-meta-value,
|
||||
.import-meta-label {
|
||||
padding: 2px 4px;
|
||||
border: 1px solid gold;
|
||||
}
|
||||
|
||||
.import-meta-label {
|
||||
border-radius: 2px 0 0 2px;
|
||||
color: var(--highlight-color);
|
||||
border-right-width: 0;
|
||||
}
|
||||
|
||||
.import-meta-title .import-meta-label {
|
||||
border-right-width: 1px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.import-meta-value {
|
||||
border-radius: 0 2px 2px 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@ -242,7 +234,7 @@
|
||||
.insert-mode-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.import-resize-option :checked + span,
|
||||
@ -250,11 +242,15 @@
|
||||
color: var(--highlight-color);
|
||||
}
|
||||
|
||||
.import-resize-option input,
|
||||
.insert-mode-option input {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
/**
|
||||
* ADJUST SIZE
|
||||
*/
|
||||
.import-resize-anchor-info,
|
||||
.import-resize-option-label {
|
||||
.import-resize-anchor-info {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
@ -43,8 +43,29 @@
|
||||
float : left;
|
||||
}
|
||||
|
||||
.gif-export-warning {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.gif-export-warning.visible {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid red;
|
||||
padding: 5px;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.gif-export-warning-icon {
|
||||
flex-shrink: 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.gif-export-warning-message {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.preview-upload-ongoing:before{
|
||||
content: "Upload ongoing ...";
|
||||
content: "Upload in progress...";
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: 100%;
|
||||
|
BIN
src/img/icons/common/common-backup-white.png
Normal file
BIN
src/img/icons/common/common-backup-white.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
BIN
src/img/icons/common/common-backup-white@2x.png
Normal file
BIN
src/img/icons/common/common-backup-white@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
@ -67,6 +67,7 @@
|
||||
</div>
|
||||
|
||||
@@include('templates/misc-templates.html', {})
|
||||
@@include('templates/data-uri-export.html', {})
|
||||
@@include('templates/popup-preview.html', {})
|
||||
|
||||
<span class="cheatsheet-link icon-common-keyboard-gold"
|
||||
@ -75,9 +76,10 @@
|
||||
rel="tooltip" data-placement="left" title="Performance problem detected, learn more."> </div>
|
||||
|
||||
<!-- dialogs partials -->
|
||||
@@include('templates/dialogs/create-palette.html', {})
|
||||
@@include('templates/dialogs/browse-backups.html', {})
|
||||
@@include('templates/dialogs/browse-local.html', {})
|
||||
@@include('templates/dialogs/cheatsheet.html', {})
|
||||
@@include('templates/dialogs/create-palette.html', {})
|
||||
@@include('templates/dialogs/import.html', {})
|
||||
@@include('templates/dialogs/performance-info.html', {})
|
||||
@@include('templates/dialogs/unsupported-browser.html', {})
|
||||
|
@ -12,7 +12,7 @@ var Constants = {
|
||||
MAX_HEIGHT : 1024,
|
||||
MAX_WIDTH : 1024,
|
||||
|
||||
MAX_PALETTE_COLORS : 100,
|
||||
MAX_PALETTE_COLORS : 256,
|
||||
// allow current colors service to get up to 256 colors.
|
||||
// GIF generation is different if the color count goes over 256.
|
||||
MAX_WORKER_COLORS : 256,
|
||||
@ -58,8 +58,11 @@ var Constants = {
|
||||
// The datastore limit is 1 MiB, which we roughly approximate to 1 million characters.
|
||||
APPENGINE_SAVE_LIMIT : 1 * 1024 * 1024,
|
||||
|
||||
// Message displayed when an action will lead to erase the current animation.
|
||||
CONFIRM_OVERWRITE: 'This will replace your current animation, are you sure you want to continue?',
|
||||
|
||||
// SERVICE URLS
|
||||
APPENGINE_SAVE_URL : 'save',
|
||||
IMAGE_SERVICE_UPLOAD_URL : 'http://piskel-imgstore-b.appspot.com/__/upload',
|
||||
IMAGE_SERVICE_GET_URL : 'http://piskel-imgstore-b.appspot.com/img/'
|
||||
IMAGE_SERVICE_UPLOAD_URL : '{{protocol}}://piskel-imgstore-b.appspot.com/__/upload',
|
||||
IMAGE_SERVICE_GET_URL : '{{protocol}}://piskel-imgstore-b.appspot.com/img/'
|
||||
};
|
||||
|
@ -64,9 +64,10 @@ var Events = {
|
||||
SELECTION_CREATED: 'SELECTION_CREATED',
|
||||
SELECTION_MOVE_REQUEST: 'SELECTION_MOVE_REQUEST',
|
||||
SELECTION_DISMISSED: 'SELECTION_DISMISSED',
|
||||
SELECTION_COPY: 'SELECTION_COPY',
|
||||
SELECTION_CUT: 'SELECTION_CUT',
|
||||
SELECTION_PASTE: 'SELECTION_PASTE',
|
||||
|
||||
CLIPBOARD_COPY: 'CLIPBOARD_COPY',
|
||||
CLIPBOARD_CUT: 'CLIPBOARD_CUT',
|
||||
CLIPBOARD_PASTE: 'CLIPBOARD_PASTE',
|
||||
|
||||
SHOW_NOTIFICATION: 'SHOW_NOTIFICATION',
|
||||
HIDE_NOTIFICATION: 'HIDE_NOTIFICATION',
|
||||
|
@ -18,6 +18,9 @@
|
||||
*/
|
||||
this.isAppEngineVersion = !!pskl.appEngineToken_;
|
||||
|
||||
// This id is used to keep track of sessions in the BackupService.
|
||||
this.sessionId = pskl.utils.Uuid.generate();
|
||||
|
||||
this.shortcutService = new pskl.service.keyboard.ShortcutService();
|
||||
this.shortcutService.init();
|
||||
|
||||
@ -114,6 +117,9 @@
|
||||
this.canvasBackgroundController = new pskl.controller.CanvasBackgroundController();
|
||||
this.canvasBackgroundController.init();
|
||||
|
||||
this.indexedDbStorageService = new pskl.service.storage.IndexedDbStorageService(this.piskelController);
|
||||
this.indexedDbStorageService.init();
|
||||
|
||||
this.localStorageService = new pskl.service.storage.LocalStorageService(this.piskelController);
|
||||
this.localStorageService.init();
|
||||
|
||||
@ -168,6 +174,9 @@
|
||||
this.currentColorsService);
|
||||
this.performanceReportService.init();
|
||||
|
||||
this.clipboardService = new pskl.service.ClipboardService(this.piskelController);
|
||||
this.clipboardService.init();
|
||||
|
||||
this.drawingLoop = new pskl.rendering.DrawingLoop();
|
||||
this.drawingLoop.addCallback(this.render, this);
|
||||
this.drawingLoop.start();
|
||||
@ -195,6 +204,11 @@
|
||||
dialogId : 'unsupported-browser'
|
||||
});
|
||||
}
|
||||
|
||||
if (pskl.utils.Environment.isDebug()) {
|
||||
pskl.app.shortcutService.registerShortcut(pskl.service.keyboard.Shortcuts.DEBUG.RELOAD_STYLES,
|
||||
window.reloadStyles);
|
||||
}
|
||||
},
|
||||
|
||||
loadPiskel_ : function (piskelData) {
|
||||
|
@ -54,7 +54,9 @@
|
||||
var colors = this.getSelectedPaletteColors_();
|
||||
|
||||
if (colors.length > 0) {
|
||||
var html = colors.map(function (color, index) {
|
||||
var html = colors.filter(function (color) {
|
||||
return !!color;
|
||||
}).map(function (color, index) {
|
||||
return pskl.utils.Template.replace(this.paletteColorTemplate_, {
|
||||
color : color,
|
||||
index : index + 1,
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
this.localStorageItemTemplate_ = pskl.utils.Template.get('local-storage-item-template');
|
||||
|
||||
this.service_ = pskl.app.localStorageService;
|
||||
this.service_ = pskl.app.indexedDbStorageService;
|
||||
this.piskelList = $('.local-piskel-list');
|
||||
this.prevSessionContainer = $('.previous-session');
|
||||
|
||||
@ -36,24 +36,24 @@
|
||||
};
|
||||
|
||||
ns.BrowseLocalController.prototype.fillLocalPiskelsList_ = function () {
|
||||
var html = '';
|
||||
var keys = this.service_.getKeys();
|
||||
|
||||
keys.sort(function (k1, k2) {
|
||||
if (k1.date < k2.date) {return 1;}
|
||||
if (k1.date > k2.date) {return -1;}
|
||||
return 0;
|
||||
});
|
||||
|
||||
keys.forEach((function (key) {
|
||||
var date = pskl.utils.DateUtils.format(key.date, '{{Y}}/{{M}}/{{D}} {{H}}:{{m}}');
|
||||
html += pskl.utils.Template.replace(this.localStorageItemTemplate_, {
|
||||
name : key.name,
|
||||
date : date
|
||||
this.service_.getKeys().then(function (keys) {
|
||||
var html = '';
|
||||
keys.sort(function (k1, k2) {
|
||||
if (k1.date < k2.date) {return 1;}
|
||||
if (k1.date > k2.date) {return -1;}
|
||||
return 0;
|
||||
});
|
||||
}).bind(this));
|
||||
|
||||
var tableBody_ = this.piskelList.get(0).tBodies[0];
|
||||
tableBody_.innerHTML = html;
|
||||
keys.forEach((function (key) {
|
||||
var date = pskl.utils.DateUtils.format(key.date, '{{Y}}/{{M}}/{{D}} {{H}}:{{m}}');
|
||||
html += pskl.utils.Template.replace(this.localStorageItemTemplate_, {
|
||||
name : key.name,
|
||||
date : date
|
||||
});
|
||||
}).bind(this));
|
||||
|
||||
var tableBody_ = this.piskelList.get(0).tBodies[0];
|
||||
tableBody_.innerHTML = html;
|
||||
}.bind(this));
|
||||
};
|
||||
})();
|
||||
|
@ -25,6 +25,10 @@
|
||||
'unsupported-browser' : {
|
||||
template : 'templates/dialogs/unsupported-browser.html',
|
||||
controller : ns.UnsupportedBrowserController
|
||||
},
|
||||
'browse-backups' : {
|
||||
template : 'templates/dialogs/browse-backups.html',
|
||||
controller : ns.backups.BrowseBackups
|
||||
}
|
||||
};
|
||||
|
||||
|
81
src/js/controller/dialogs/backups/BrowseBackups.js
Normal file
81
src/js/controller/dialogs/backups/BrowseBackups.js
Normal file
@ -0,0 +1,81 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.controller.dialogs.backups');
|
||||
|
||||
var stepDefinitions = {
|
||||
'SELECT_SESSION' : {
|
||||
controller : ns.steps.SelectSession,
|
||||
template : 'backups-select-session'
|
||||
},
|
||||
'SESSION_DETAILS' : {
|
||||
controller : ns.steps.SessionDetails,
|
||||
template : 'backups-session-details'
|
||||
},
|
||||
};
|
||||
|
||||
ns.BrowseBackups = function (piskelController, args) {
|
||||
this.piskelController = piskelController;
|
||||
|
||||
// Backups data object used by steps to communicate and share their
|
||||
// results.
|
||||
this.backupsData = {
|
||||
sessions: [],
|
||||
selectedSession : null
|
||||
};
|
||||
};
|
||||
|
||||
pskl.utils.inherit(ns.BrowseBackups, pskl.controller.dialogs.AbstractDialogController);
|
||||
|
||||
ns.BrowseBackups.prototype.init = function () {
|
||||
this.superclass.init.call(this);
|
||||
|
||||
// Prepare wizard steps.
|
||||
this.steps = this.createSteps_();
|
||||
|
||||
// Start wizard widget.
|
||||
var wizardContainer = document.querySelector('.backups-wizard-container');
|
||||
this.wizard = new pskl.widgets.Wizard(this.steps, wizardContainer);
|
||||
this.wizard.init();
|
||||
|
||||
this.wizard.goTo('SELECT_SESSION');
|
||||
};
|
||||
|
||||
ns.BrowseBackups.prototype.back = function () {
|
||||
this.wizard.back();
|
||||
this.wizard.getCurrentStep().instance.onShow();
|
||||
};
|
||||
|
||||
ns.BrowseBackups.prototype.next = function () {
|
||||
var step = this.wizard.getCurrentStep();
|
||||
if (step.name === 'SELECT_SESSION') {
|
||||
this.wizard.goTo('SESSION_DETAILS');
|
||||
}
|
||||
};
|
||||
|
||||
ns.BrowseBackups.prototype.destroy = function (file) {
|
||||
Object.keys(this.steps).forEach(function (stepName) {
|
||||
var step = this.steps[stepName];
|
||||
step.instance.destroy();
|
||||
step.instance = null;
|
||||
step.el = null;
|
||||
}.bind(this));
|
||||
|
||||
this.superclass.destroy.call(this);
|
||||
};
|
||||
|
||||
ns.BrowseBackups.prototype.createSteps_ = function () {
|
||||
var steps = {};
|
||||
Object.keys(stepDefinitions).forEach(function (stepName) {
|
||||
var definition = stepDefinitions[stepName];
|
||||
var el = pskl.utils.Template.getAsHTML(definition.template);
|
||||
var instance = new definition.controller(this.piskelController, this, el);
|
||||
instance.init();
|
||||
steps[stepName] = {
|
||||
name: stepName,
|
||||
el: el,
|
||||
instance: instance
|
||||
};
|
||||
}.bind(this));
|
||||
|
||||
return steps;
|
||||
};
|
||||
})();
|
101
src/js/controller/dialogs/backups/steps/SelectSession.js
Normal file
101
src/js/controller/dialogs/backups/steps/SelectSession.js
Normal file
@ -0,0 +1,101 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.controller.dialogs.backups.steps');
|
||||
|
||||
// Should match the transition duration for.session-item defined in dialogs-browse-backups.css
|
||||
var DELETE_TRANSITION_DURATION = 500;
|
||||
/**
|
||||
* Helper that returns a promise that will resolve after waiting for a
|
||||
* given time (in ms).
|
||||
*
|
||||
* @param {Number} time
|
||||
* The time to wait.
|
||||
* @return {Promise} promise that resolves after time.
|
||||
*/
|
||||
var wait = function (time) {
|
||||
var deferred = Q.defer();
|
||||
setTimeout(function () {
|
||||
deferred.resolve();
|
||||
}, time);
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
ns.SelectSession = function (piskelController, backupsController, container) {
|
||||
this.piskelController = piskelController;
|
||||
this.backupsController = backupsController;
|
||||
this.container = container;
|
||||
};
|
||||
|
||||
ns.SelectSession.prototype.addEventListener = function (el, type, cb) {
|
||||
pskl.utils.Event.addEventListener(el, type, cb, this);
|
||||
};
|
||||
|
||||
ns.SelectSession.prototype.init = function () {
|
||||
this.addEventListener(this.container, 'click', this.onContainerClick_);
|
||||
};
|
||||
|
||||
ns.SelectSession.prototype.onShow = function () {
|
||||
this.update();
|
||||
};
|
||||
|
||||
ns.SelectSession.prototype.update = function () {
|
||||
pskl.app.backupService.list().then(function (sessions) {
|
||||
var html = this.getMarkupForSessions_(sessions);
|
||||
this.container.querySelector('.session-list').innerHTML = html;
|
||||
}.bind(this)).catch(function () {
|
||||
var html = pskl.utils.Template.get('session-list-error');
|
||||
this.container.querySelector('.session-list').innerHTML = html;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ns.SelectSession.prototype.getMarkupForSessions_ = function (sessions) {
|
||||
if (sessions.length === 0) {
|
||||
return pskl.utils.Template.get('session-list-empty');
|
||||
}
|
||||
|
||||
var sessionItemTemplate = pskl.utils.Template.get('session-list-item');
|
||||
return sessions.reduce(function (previous, session) {
|
||||
if (session.id === pskl.app.sessionId) {
|
||||
// Do not show backups for the current session.
|
||||
return previous;
|
||||
}
|
||||
var view = {
|
||||
id: session.id,
|
||||
name: session.name,
|
||||
description: session.description ? '- ' + session.description : '',
|
||||
date: pskl.utils.DateUtils.format(session.endDate, 'the {{Y}}/{{M}}/{{D}} at {{H}}:{{m}}'),
|
||||
count: session.count === 1 ? '1 snapshot' : session.count + ' snapshots'
|
||||
};
|
||||
return previous + pskl.utils.Template.replace(sessionItemTemplate, view);
|
||||
}, '');
|
||||
};
|
||||
|
||||
ns.SelectSession.prototype.destroy = function () {
|
||||
pskl.utils.Event.removeAllEventListeners(this);
|
||||
};
|
||||
|
||||
ns.SelectSession.prototype.onContainerClick_ = function (evt) {
|
||||
var sessionId = evt.target.dataset.sessionId;
|
||||
if (!sessionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
var action = evt.target.dataset.action;
|
||||
if (action == 'view') {
|
||||
this.backupsController.backupsData.selectedSession = sessionId;
|
||||
this.backupsController.next();
|
||||
} else if (action == 'delete') {
|
||||
if (window.confirm('Are you sure you want to delete this session?')) {
|
||||
evt.target.closest('.session-item').classList.add('deleting');
|
||||
Q.all([
|
||||
pskl.app.backupService.deleteSession(sessionId),
|
||||
// Wait for 500ms for the .hide opacity transition.
|
||||
wait(DELETE_TRANSITION_DURATION)
|
||||
]).then(function () {
|
||||
// Refresh the list of sessions
|
||||
this.update();
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
102
src/js/controller/dialogs/backups/steps/SessionDetails.js
Normal file
102
src/js/controller/dialogs/backups/steps/SessionDetails.js
Normal file
@ -0,0 +1,102 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.controller.dialogs.backups.steps');
|
||||
|
||||
// Should match the preview dimensions defined in dialogs-browse-backups.css
|
||||
var PREVIEW_SIZE = 60;
|
||||
|
||||
ns.SessionDetails = function (piskelController, backupsController, container) {
|
||||
this.piskelController = piskelController;
|
||||
this.backupsController = backupsController;
|
||||
this.container = container;
|
||||
};
|
||||
|
||||
ns.SessionDetails.prototype.init = function () {
|
||||
this.backButton = this.container.querySelector('.back-button');
|
||||
this.addEventListener(this.backButton, 'click', this.onBackClick_);
|
||||
this.addEventListener(this.container, 'click', this.onContainerClick_);
|
||||
};
|
||||
|
||||
ns.SessionDetails.prototype.destroy = function () {
|
||||
pskl.utils.Event.removeAllEventListeners(this);
|
||||
};
|
||||
|
||||
ns.SessionDetails.prototype.addEventListener = function (el, type, cb) {
|
||||
pskl.utils.Event.addEventListener(el, type, cb, this);
|
||||
};
|
||||
|
||||
ns.SessionDetails.prototype.onShow = function () {
|
||||
var sessionId = this.backupsController.backupsData.selectedSession;
|
||||
pskl.app.backupService.getSnapshotsBySessionId(sessionId).then(function (snapshots) {
|
||||
var html = this.getMarkupForSnapshots_(snapshots);
|
||||
this.container.querySelector('.snapshot-list').innerHTML = html;
|
||||
|
||||
// Load the image of the first frame for each sprite and update the list.
|
||||
snapshots.forEach(function (snapshot) {
|
||||
this.updateSnapshotPreview_(snapshot);
|
||||
}.bind(this));
|
||||
}.bind(this)).catch(function () {
|
||||
var html = pskl.utils.Template.get('snapshot-list-error');
|
||||
this.container.querySelector('.snapshot-list').innerHTML = html;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ns.SessionDetails.prototype.getMarkupForSnapshots_ = function (snapshots) {
|
||||
if (snapshots.length === 0) {
|
||||
// This should normally never happen, all sessions have at least one snapshot and snapshots
|
||||
// can not be individually deleted.
|
||||
console.warn('Could not retrieve snapshots for a session');
|
||||
return pskl.utils.Template.get('snapshot-list-empty');
|
||||
}
|
||||
|
||||
var sessionItemTemplate = pskl.utils.Template.get('snapshot-list-item');
|
||||
return snapshots.reduce(function (previous, snapshot) {
|
||||
var view = {
|
||||
id: snapshot.id,
|
||||
name: snapshot.name,
|
||||
description: snapshot.description ? '- ' + snapshot.description : '',
|
||||
date: pskl.utils.DateUtils.format(snapshot.date, 'the {{Y}}/{{M}}/{{D}} at {{H}}:{{m}}'),
|
||||
frames: snapshot.frames === 1 ? '1 frame' : snapshot.frames + ' frames',
|
||||
resolution: pskl.utils.StringUtils.formatSize(snapshot.width, snapshot.height),
|
||||
fps: snapshot.fps
|
||||
};
|
||||
return previous + pskl.utils.Template.replace(sessionItemTemplate, view);
|
||||
}, '');
|
||||
};
|
||||
|
||||
ns.SessionDetails.prototype.updateSnapshotPreview_ = function (snapshot) {
|
||||
pskl.utils.serialization.Deserializer.deserialize(
|
||||
JSON.parse(snapshot.serialized),
|
||||
function (piskel) {
|
||||
var selector = '.snapshot-item[data-snapshot-id="' + snapshot.id + '"] .snapshot-preview';
|
||||
var previewContainer = this.container.querySelector(selector);
|
||||
if (!previewContainer) {
|
||||
return;
|
||||
}
|
||||
var image = this.getFirstFrameAsImage_(piskel);
|
||||
previewContainer.appendChild(image);
|
||||
}.bind(this)
|
||||
);
|
||||
};
|
||||
|
||||
ns.SessionDetails.prototype.getFirstFrameAsImage_ = function (piskel) {
|
||||
var frame = pskl.utils.LayerUtils.mergeFrameAt(piskel.getLayers(), 0);
|
||||
var wZoom = PREVIEW_SIZE / piskel.width;
|
||||
var hZoom = PREVIEW_SIZE / piskel.height;
|
||||
var zoom = Math.min(hZoom, wZoom);
|
||||
return pskl.utils.FrameUtils.toImage(frame, zoom);
|
||||
};
|
||||
|
||||
ns.SessionDetails.prototype.onBackClick_ = function () {
|
||||
this.backupsController.back(this);
|
||||
};
|
||||
|
||||
ns.SessionDetails.prototype.onContainerClick_ = function (evt) {
|
||||
var action = evt.target.dataset.action;
|
||||
if (action == 'load' && window.confirm(Constants.CONFIRM_OVERWRITE)) {
|
||||
var snapshotId = evt.target.dataset.snapshotId * 1;
|
||||
pskl.app.backupService.loadSnapshotById(snapshotId).then(function () {
|
||||
$.publish(Events.DIALOG_HIDE);
|
||||
});
|
||||
}
|
||||
};
|
||||
})();
|
@ -80,7 +80,13 @@
|
||||
var step = this.wizard.getCurrentStep();
|
||||
|
||||
if (step.name === 'IMAGE_IMPORT') {
|
||||
this.wizard.goTo('SELECT_MODE');
|
||||
if (this.piskelController.isEmpty()) {
|
||||
// If the current sprite is empty finalize immediately and replace the current sprite.
|
||||
this.mergeData.importMode = ns.steps.SelectMode.MODES.REPLACE;
|
||||
this.finalizeImport_();
|
||||
} else {
|
||||
this.wizard.goTo('SELECT_MODE');
|
||||
}
|
||||
} else if (step.name === 'SELECT_MODE') {
|
||||
if (this.mergeData.importMode === ns.steps.SelectMode.MODES.REPLACE) {
|
||||
this.finalizeImport_();
|
||||
@ -144,9 +150,7 @@
|
||||
|
||||
if (mode === ns.steps.SelectMode.MODES.REPLACE) {
|
||||
// Replace the current piskel and close the dialog.
|
||||
var message = 'This will replace your current animation,' +
|
||||
' are you sure you want to continue?';
|
||||
if (window.confirm(message)) {
|
||||
if (window.confirm(Constants.CONFIRM_OVERWRITE)) {
|
||||
this.piskelController.setPiskel(piskel);
|
||||
this.closeDialog();
|
||||
}
|
||||
|
@ -69,16 +69,15 @@
|
||||
var anchorInfo = this.container.querySelector('.import-resize-anchor-info');
|
||||
if (isBigger && keep) {
|
||||
anchorInfo.innerHTML = [
|
||||
'<span class="import-resize-warning">',
|
||||
'<div class="import-resize-warning">',
|
||||
' Imported content will be cropped!',
|
||||
'</span>',
|
||||
' ',
|
||||
'Select crop origin'
|
||||
'</div>',
|
||||
'Select crop anchor:'
|
||||
].join('');
|
||||
} else if (isBigger) {
|
||||
anchorInfo.innerHTML = 'Select the anchor for resizing the canvas';
|
||||
anchorInfo.innerHTML = 'Select resize anchor:';
|
||||
} else {
|
||||
anchorInfo.innerHTML = 'Select where the import should be positioned';
|
||||
anchorInfo.innerHTML = 'Select position anchor:';
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -42,12 +42,17 @@
|
||||
this.addEventListener(this.frameOffsetY, 'keyup', this.onFrameInputKeyUp_);
|
||||
|
||||
pskl.utils.FileUtils.readImageFile(this.file_, this.onImageLoaded_.bind(this));
|
||||
|
||||
if (this.piskelController.isEmpty()) {
|
||||
this.nextButton.textContent = 'import';
|
||||
}
|
||||
};
|
||||
|
||||
ns.ImageImport.prototype.onNextClick = function () {
|
||||
this.container.classList.add('import-image-loading');
|
||||
this.createPiskelFromImage().then(function (piskel) {
|
||||
this.mergeData.mergePiskel = piskel;
|
||||
this.container.classList.remove('import-image-loading');
|
||||
this.superclass.onNextClick.call(this);
|
||||
}.bind(this)).catch(function (e) {
|
||||
console.error(e);
|
||||
@ -257,9 +262,7 @@
|
||||
context.lineTo(maxWidth * scaleX, y * scaleY);
|
||||
}
|
||||
|
||||
// Set the line style to dashed
|
||||
context.lineWidth = 1;
|
||||
// context.setLineDash([2, 1]);
|
||||
context.strokeStyle = 'gold';
|
||||
context.stroke();
|
||||
|
||||
|
@ -297,4 +297,12 @@
|
||||
ns.PiskelController.prototype.serialize = function () {
|
||||
return pskl.utils.serialization.Serializer.serialize(this.piskel);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the current sprite is empty. Emptiness here means no pixel has been filled
|
||||
* on any layer or frame for the current sprite.
|
||||
*/
|
||||
ns.PiskelController.prototype.isEmpty = function () {
|
||||
return pskl.app.currentColorsService.getCurrentColors().length === 0;
|
||||
};
|
||||
})();
|
||||
|
@ -14,6 +14,7 @@
|
||||
this.hiddenOpenPiskelInput = document.querySelector('[name="open-piskel-input"]');
|
||||
|
||||
this.addEventListener('.browse-local-button', 'click', this.onBrowseLocalClick_);
|
||||
this.addEventListener('.browse-backups-button', 'click', this.onBrowseBackupsClick_);
|
||||
this.addEventListener('.file-input-button', 'click', this.onFileInputClick_);
|
||||
|
||||
// different handlers, depending on the Environment
|
||||
@ -23,24 +24,6 @@
|
||||
this.addEventListener(this.hiddenOpenPiskelInput, 'change', this.onOpenPiskelChange_);
|
||||
this.addEventListener('.open-piskel-button', 'click', this.onOpenPiskelClick_);
|
||||
}
|
||||
|
||||
this.initRestoreSession_();
|
||||
};
|
||||
|
||||
ns.ImportController.prototype.initRestoreSession_ = function () {
|
||||
var previousSessionContainer = document.querySelector('.previous-session');
|
||||
var previousInfo = pskl.app.backupService.getPreviousPiskelInfo();
|
||||
if (previousInfo) {
|
||||
var previousSessionTemplate_ = pskl.utils.Template.get('previous-session-info-template');
|
||||
var date = pskl.utils.DateUtils.format(previousInfo.date, '{{H}}:{{m}} - {{Y}}/{{M}}/{{D}}');
|
||||
previousSessionContainer.innerHTML = pskl.utils.Template.replace(previousSessionTemplate_, {
|
||||
name : previousInfo.name,
|
||||
date : date
|
||||
});
|
||||
this.addEventListener('.restore-session-button', 'click', this.onRestorePreviousSessionClick_);
|
||||
} else {
|
||||
previousSessionContainer.innerHTML = 'No piskel backup was found on this browser.';
|
||||
}
|
||||
};
|
||||
|
||||
ns.ImportController.prototype.closeDrawer_ = function () {
|
||||
@ -77,6 +60,13 @@
|
||||
this.closeDrawer_();
|
||||
};
|
||||
|
||||
ns.ImportController.prototype.onBrowseBackupsClick_ = function (evt) {
|
||||
$.publish(Events.DIALOG_SHOW, {
|
||||
dialogId : 'browse-backups'
|
||||
});
|
||||
this.closeDrawer_();
|
||||
};
|
||||
|
||||
ns.ImportController.prototype.openPiskelFile_ = function (file) {
|
||||
if (this.isPiskel_(file)) {
|
||||
$.publish(Events.DIALOG_SHOW, {
|
||||
|
@ -34,7 +34,7 @@
|
||||
this.saveDesktopAsNewButton = document.querySelector('#save-desktop-as-new-button');
|
||||
this.saveFileDownloadButton = document.querySelector('#save-file-download-button');
|
||||
|
||||
this.safeAddEventListener_(this.saveLocalStorageButton, 'click', this.saveToLocalStorage_);
|
||||
this.safeAddEventListener_(this.saveLocalStorageButton, 'click', this.saveToIndexedDb_);
|
||||
this.safeAddEventListener_(this.saveGalleryButton, 'click', this.saveToGallery_);
|
||||
this.safeAddEventListener_(this.saveDesktopButton, 'click', this.saveToDesktop_);
|
||||
this.safeAddEventListener_(this.saveDesktopAsNewButton, 'click', this.saveToDesktopAsNew_);
|
||||
@ -99,7 +99,7 @@
|
||||
if (pskl.app.isLoggedIn()) {
|
||||
this.saveToGallery_();
|
||||
} else {
|
||||
this.saveToLocalStorage_();
|
||||
this.saveToIndexedDb_();
|
||||
}
|
||||
};
|
||||
|
||||
@ -111,8 +111,8 @@
|
||||
this.saveTo_('saveToGallery', false);
|
||||
};
|
||||
|
||||
ns.SaveController.prototype.saveToLocalStorage_ = function () {
|
||||
this.saveTo_('saveToLocalStorage', false);
|
||||
ns.SaveController.prototype.saveToIndexedDb_ = function () {
|
||||
this.saveTo_('saveToIndexedDb', false);
|
||||
};
|
||||
|
||||
ns.SaveController.prototype.saveToDesktop_ = function () {
|
||||
|
@ -14,14 +14,22 @@
|
||||
pskl.utils.inherit(ns.GifExportController, pskl.controller.settings.AbstractSettingController);
|
||||
|
||||
ns.GifExportController.prototype.init = function () {
|
||||
|
||||
this.uploadStatusContainerEl = document.querySelector('.gif-upload-status');
|
||||
this.previewContainerEl = document.querySelector('.gif-export-preview');
|
||||
this.uploadButton = document.querySelector('.gif-upload-button');
|
||||
this.downloadButton = document.querySelector('.gif-download-button');
|
||||
this.repeatCheckbox = document.querySelector('.gif-repeat-checkbox');
|
||||
|
||||
// Initialize repeatCheckbox state
|
||||
this.repeatCheckbox.checked = this.getRepeatSetting_();
|
||||
|
||||
this.addEventListener(this.uploadButton, 'click', this.onUploadButtonClick_);
|
||||
this.addEventListener(this.downloadButton, 'click', this.onDownloadButtonClick_);
|
||||
this.addEventListener(this.repeatCheckbox, 'change', this.onRepeatCheckboxChange_);
|
||||
|
||||
var currentColors = pskl.app.currentColorsService.getCurrentColors();
|
||||
var tooManyColors = currentColors.length >= MAX_GIF_COLORS;
|
||||
document.querySelector('.gif-export-warning').classList.toggle('visible', tooManyColors);
|
||||
};
|
||||
|
||||
ns.GifExportController.prototype.getZoom_ = function () {
|
||||
@ -105,6 +113,7 @@
|
||||
width: width * zoom,
|
||||
height: height * zoom,
|
||||
preserveColors : preserveColors,
|
||||
repeat: this.getRepeatSetting_() ? 0 : 1,
|
||||
transparent : transparent
|
||||
});
|
||||
|
||||
@ -149,6 +158,15 @@
|
||||
return transparentColor;
|
||||
};
|
||||
|
||||
ns.GifExportController.prototype.onRepeatCheckboxChange_ = function () {
|
||||
var checked = this.repeatCheckbox.checked;
|
||||
pskl.UserSettings.set(pskl.UserSettings.EXPORT_GIF_REPEAT, checked);
|
||||
};
|
||||
|
||||
ns.GifExportController.prototype.getRepeatSetting_ = function () {
|
||||
return pskl.UserSettings.get(pskl.UserSettings.EXPORT_GIF_REPEAT);
|
||||
};
|
||||
|
||||
ns.GifExportController.prototype.updateStatus_ = function (imageUrl, error) {
|
||||
if (imageUrl) {
|
||||
var linkTpl = '<a class="highlight" href="{{link}}" target="_blank">{{shortLink}}</a>';
|
||||
|
@ -207,6 +207,14 @@
|
||||
};
|
||||
|
||||
ns.PngExportController.prototype.onDataUriClick_ = function (evt) {
|
||||
window.open(this.createPngSpritesheet_().toDataURL('image/png'));
|
||||
var popup = window.open('about:blank');
|
||||
var dataUri = this.createPngSpritesheet_().toDataURL('image/png');
|
||||
window.setTimeout(function () {
|
||||
var html = pskl.utils.Template.getAndReplace('data-uri-export-partial', {
|
||||
src: dataUri
|
||||
});
|
||||
popup.document.title = dataUri;
|
||||
popup.document.body.innerHTML = html;
|
||||
}.bind(this), 500);
|
||||
};
|
||||
})();
|
||||
|
258
src/js/database/BackupDatabase.js
Normal file
258
src/js/database/BackupDatabase.js
Normal file
@ -0,0 +1,258 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.database');
|
||||
|
||||
var DB_NAME = 'PiskelSessionsDatabase';
|
||||
var DB_VERSION = 1;
|
||||
|
||||
// Simple wrapper to promisify a request.
|
||||
var _requestPromise = function (req) {
|
||||
var deferred = Q.defer();
|
||||
req.onsuccess = deferred.resolve.bind(deferred);
|
||||
req.onerror = deferred.reject.bind(deferred);
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* The BackupDatabase handles all the database interactions related
|
||||
* to piskel snapshots continuously saved while during the usage of
|
||||
* Piskel.
|
||||
*/
|
||||
ns.BackupDatabase = function () {
|
||||
this.db = null;
|
||||
};
|
||||
|
||||
ns.BackupDatabase.DB_NAME = DB_NAME;
|
||||
|
||||
/**
|
||||
* Open and initialize the database.
|
||||
* Returns a promise that resolves when the databse is opened.
|
||||
*/
|
||||
ns.BackupDatabase.prototype.init = function () {
|
||||
var request = window.indexedDB.open(DB_NAME, DB_VERSION);
|
||||
request.onupgradeneeded = this.onUpgradeNeeded_.bind(this);
|
||||
|
||||
return _requestPromise(request).then(function (event) {
|
||||
this.db = event.target.result;
|
||||
return this.db;
|
||||
}.bind(this)).catch(function (e) {
|
||||
console.log('Could not initialize the piskel backup database');
|
||||
});
|
||||
};
|
||||
|
||||
ns.BackupDatabase.prototype.onUpgradeNeeded_ = function (event) {
|
||||
// Set this.db early to allow migration scripts to access it in oncomplete.
|
||||
this.db = event.target.result;
|
||||
|
||||
// Create an object store "piskels" with the autoIncrement flag set as true.
|
||||
var objectStore = this.db.createObjectStore('snapshots', { keyPath: 'id', autoIncrement : true });
|
||||
|
||||
objectStore.createIndex('session_id', 'session_id', { unique: false });
|
||||
objectStore.createIndex('date', 'date', { unique: false });
|
||||
objectStore.createIndex('session_id, date', ['session_id', 'date'], { unique: false });
|
||||
|
||||
objectStore.transaction.oncomplete = function(event) {
|
||||
// Nothing to do at the moment!
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
ns.BackupDatabase.prototype.openObjectStore_ = function () {
|
||||
return this.db.transaction(['snapshots'], 'readwrite').objectStore('snapshots');
|
||||
};
|
||||
|
||||
/**
|
||||
* Send an add request for the provided snapshot.
|
||||
* Returns a promise that resolves the request event.
|
||||
*/
|
||||
ns.BackupDatabase.prototype.createSnapshot = function (snapshot) {
|
||||
var objectStore = this.openObjectStore_();
|
||||
var request = objectStore.add(snapshot);
|
||||
return _requestPromise(request);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a put request for the provided snapshot.
|
||||
* Returns a promise that resolves the request event.
|
||||
*/
|
||||
ns.BackupDatabase.prototype.updateSnapshot = function (snapshot) {
|
||||
var objectStore = this.openObjectStore_();
|
||||
var request = objectStore.put(snapshot);
|
||||
return _requestPromise(request);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a delete request for the provided snapshot.
|
||||
* Returns a promise that resolves the request event.
|
||||
*/
|
||||
ns.BackupDatabase.prototype.deleteSnapshot = function (snapshot) {
|
||||
var objectStore = this.openObjectStore_();
|
||||
var request = objectStore.delete(snapshot.id);
|
||||
return _requestPromise(request);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a get request for the provided snapshotId.
|
||||
* Returns a promise that resolves the request event.
|
||||
*/
|
||||
ns.BackupDatabase.prototype.getSnapshot = function (snapshotId) {
|
||||
var objectStore = this.openObjectStore_();
|
||||
var request = objectStore.get(snapshotId);
|
||||
return _requestPromise(request).then(function (event) {
|
||||
return event.target.result;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the last (most recent) snapshot that satisfies the accept filter provided.
|
||||
* Returns a promise that will resolve with the first matching snapshot (or null
|
||||
* if no valid snapshot is found).
|
||||
*
|
||||
* @param {Function} accept:
|
||||
* Filter method that takes a snapshot as argument and should return true
|
||||
* if the snapshot is valid.
|
||||
*/
|
||||
ns.BackupDatabase.prototype.findLastSnapshot = function (accept) {
|
||||
// Create the backup promise.
|
||||
var deferred = Q.defer();
|
||||
|
||||
// Open a transaction to the snapshots object store.
|
||||
var objectStore = this.db.transaction(['snapshots']).objectStore('snapshots');
|
||||
|
||||
var index = objectStore.index('date');
|
||||
var range = IDBKeyRange.upperBound(Infinity);
|
||||
index.openCursor(range, 'prev').onsuccess = function(event) {
|
||||
var cursor = event.target.result;
|
||||
var snapshot = cursor && cursor.value;
|
||||
|
||||
// Resolve null if we couldn't find a matching snapshot.
|
||||
if (!snapshot) {
|
||||
deferred.resolve(null);
|
||||
} else if (accept(snapshot)) {
|
||||
deferred.resolve(snapshot);
|
||||
} else {
|
||||
cursor.continue();
|
||||
}
|
||||
};
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve all the snapshots for a given session id, sorted by descending date order.
|
||||
* Returns a promise that resolves with an array of snapshots.
|
||||
*
|
||||
* @param {String} sessionId
|
||||
* The session id
|
||||
*/
|
||||
ns.BackupDatabase.prototype.getSnapshotsBySessionId = function (sessionId) {
|
||||
// Create the backup promise.
|
||||
var deferred = Q.defer();
|
||||
|
||||
// Open a transaction to the snapshots object store.
|
||||
var objectStore = this.db.transaction(['snapshots']).objectStore('snapshots');
|
||||
|
||||
// Loop on all the saved snapshots for the provided piskel id
|
||||
var index = objectStore.index('session_id, date');
|
||||
var keyRange = IDBKeyRange.bound(
|
||||
[sessionId, 0],
|
||||
[sessionId, Infinity]
|
||||
);
|
||||
|
||||
var snapshots = [];
|
||||
// Ordered by date in descending order.
|
||||
index.openCursor(keyRange, 'prev').onsuccess = function(event) {
|
||||
var cursor = event.target.result;
|
||||
if (cursor) {
|
||||
snapshots.push(cursor.value);
|
||||
cursor.continue();
|
||||
} else {
|
||||
// Consumed all piskel snapshots
|
||||
deferred.resolve(snapshots);
|
||||
}
|
||||
};
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
ns.BackupDatabase.prototype.getSessions = function () {
|
||||
// Create the backup promise.
|
||||
var deferred = Q.defer();
|
||||
|
||||
// Open a transaction to the snapshots object store.
|
||||
var objectStore = this.db.transaction(['snapshots']).objectStore('snapshots');
|
||||
|
||||
var sessions = {};
|
||||
|
||||
var _createSession = function (snapshot) {
|
||||
sessions[snapshot.session_id] = {
|
||||
startDate: snapshot.date,
|
||||
endDate: snapshot.date,
|
||||
name: snapshot.name,
|
||||
description: snapshot.description,
|
||||
id: snapshot.session_id,
|
||||
count: 1
|
||||
};
|
||||
};
|
||||
|
||||
var _updateSession = function (snapshot) {
|
||||
var s = sessions[snapshot.session_id];
|
||||
s.startDate = Math.min(s.startDate, snapshot.date);
|
||||
s.endDate = Math.max(s.endDate, snapshot.date);
|
||||
s.count++;
|
||||
|
||||
if (s.endDate === snapshot.date) {
|
||||
// If the endDate was updated, update also the session metadata to
|
||||
// reflect the latest state.
|
||||
s.name = snapshot.name;
|
||||
s.description = snapshot.description;
|
||||
}
|
||||
};
|
||||
|
||||
var index = objectStore.index('date');
|
||||
var range = IDBKeyRange.upperBound(Infinity);
|
||||
index.openCursor(range, 'prev').onsuccess = function(event) {
|
||||
var cursor = event.target.result;
|
||||
var snapshot = cursor && cursor.value;
|
||||
if (!snapshot) {
|
||||
deferred.resolve(sessions);
|
||||
} else {
|
||||
if (sessions[snapshot.session_id]) {
|
||||
_updateSession(snapshot);
|
||||
} else {
|
||||
_createSession(snapshot);
|
||||
}
|
||||
cursor.continue();
|
||||
}
|
||||
};
|
||||
|
||||
return deferred.promise.then(function (sessions) {
|
||||
// Convert the sessions map to an array.
|
||||
return Object.keys(sessions).map(function (key) {
|
||||
return sessions[key];
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
ns.BackupDatabase.prototype.deleteSnapshotsForSession = function (sessionId) {
|
||||
// Create the backup promise.
|
||||
var deferred = Q.defer();
|
||||
|
||||
// Open a transaction to the snapshots object store.
|
||||
var objectStore = this.openObjectStore_();
|
||||
|
||||
// Loop on all the saved snapshots for the provided piskel id
|
||||
var index = objectStore.index('session_id');
|
||||
var keyRange = IDBKeyRange.only(sessionId);
|
||||
|
||||
index.openCursor(keyRange).onsuccess = function(event) {
|
||||
var cursor = event.target.result;
|
||||
if (cursor) {
|
||||
cursor.delete();
|
||||
cursor.continue();
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
})();
|
139
src/js/database/PiskelDatabase.js
Normal file
139
src/js/database/PiskelDatabase.js
Normal file
@ -0,0 +1,139 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.database');
|
||||
|
||||
var DB_NAME = 'PiskelDatabase';
|
||||
var DB_VERSION = 1;
|
||||
|
||||
// Simple wrapper to promisify a request.
|
||||
var _requestPromise = function (req) {
|
||||
var deferred = Q.defer();
|
||||
req.onsuccess = deferred.resolve.bind(deferred);
|
||||
req.onerror = deferred.reject.bind(deferred);
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* The PiskelDatabase handles all the database interactions related
|
||||
* to the local piskel saved that can be performed in-browser.
|
||||
*/
|
||||
ns.PiskelDatabase = function (options) {
|
||||
this.db = null;
|
||||
};
|
||||
|
||||
ns.PiskelDatabase.DB_NAME = DB_NAME;
|
||||
|
||||
ns.PiskelDatabase.prototype.init = function () {
|
||||
var request = window.indexedDB.open(DB_NAME, DB_VERSION);
|
||||
request.onupgradeneeded = this.onUpgradeNeeded_.bind(this);
|
||||
|
||||
return _requestPromise(request).then(function (event) {
|
||||
this.db = event.target.result;
|
||||
return this.db;
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ns.PiskelDatabase.prototype.onUpgradeNeeded_ = function (event) {
|
||||
// Set this.db early to allow migration scripts to access it in oncomplete.
|
||||
this.db = event.target.result;
|
||||
|
||||
// Create an object store "piskels" with the autoIncrement flag set as true.
|
||||
var objectStore = this.db.createObjectStore('piskels', { keyPath : 'name' });
|
||||
objectStore.transaction.oncomplete = function(event) {
|
||||
pskl.database.migrate.MigrateLocalStorageToIndexedDb.migrate(this);
|
||||
}.bind(this);
|
||||
};
|
||||
|
||||
ns.PiskelDatabase.prototype.openObjectStore_ = function () {
|
||||
return this.db.transaction(['piskels'], 'readwrite').objectStore('piskels');
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a get request for the provided name.
|
||||
* Returns a promise that resolves the request event.
|
||||
*/
|
||||
ns.PiskelDatabase.prototype.get = function (name) {
|
||||
var objectStore = this.openObjectStore_();
|
||||
return _requestPromise(objectStore.get(name)).then(function (event) {
|
||||
return event.target.result;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* List all locally saved piskels.
|
||||
* Returns a promise that resolves an array of objects:
|
||||
* - name: name of the piskel
|
||||
* - description: description of the piskel
|
||||
* - date: save date
|
||||
*
|
||||
* The sprite content is not contained in the object and
|
||||
* needs to be retrieved with a separate get.
|
||||
*/
|
||||
ns.PiskelDatabase.prototype.list = function () {
|
||||
var deferred = Q.defer();
|
||||
|
||||
var piskels = [];
|
||||
var objectStore = this.openObjectStore_();
|
||||
var cursor = objectStore.openCursor();
|
||||
cursor.onsuccess = function(event) {
|
||||
var cursor = event.target.result;
|
||||
if (cursor) {
|
||||
piskels.push({
|
||||
name: cursor.value.name,
|
||||
date: cursor.value.date,
|
||||
description: cursor.value.description
|
||||
});
|
||||
cursor.continue();
|
||||
} else {
|
||||
// Cursor consumed all availabled piskels
|
||||
deferred.resolve(piskels);
|
||||
}
|
||||
};
|
||||
|
||||
cursor.onerror = function () {
|
||||
deferred.reject();
|
||||
};
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* Send an put request for the provided args.
|
||||
* Returns a promise that resolves the request event.
|
||||
*/
|
||||
ns.PiskelDatabase.prototype.update = function (name, description, date, serialized) {
|
||||
var data = {};
|
||||
|
||||
data.name = name;
|
||||
data.serialized = serialized;
|
||||
data.date = date;
|
||||
data.description = description;
|
||||
|
||||
var objectStore = this.openObjectStore_();
|
||||
return _requestPromise(objectStore.put(data));
|
||||
};
|
||||
|
||||
/**
|
||||
* Send an add request for the provided args.
|
||||
* Returns a promise that resolves the request event.
|
||||
*/
|
||||
ns.PiskelDatabase.prototype.create = function (name, description, date, serialized) {
|
||||
var data = {};
|
||||
|
||||
data.name = name;
|
||||
data.serialized = serialized;
|
||||
data.date = date;
|
||||
data.description = description;
|
||||
|
||||
var objectStore = this.openObjectStore_();
|
||||
return _requestPromise(objectStore.add(data));
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a saved piskel for the provided name.
|
||||
* Returns a promise that resolves the request event.
|
||||
*/
|
||||
ns.PiskelDatabase.prototype.delete = function (name) {
|
||||
var objectStore = this.openObjectStore_();
|
||||
return _requestPromise(objectStore.delete(name));
|
||||
};
|
||||
})();
|
76
src/js/database/migrate/MigrateLocalStorageToIndexedDb.js
Normal file
76
src/js/database/migrate/MigrateLocalStorageToIndexedDb.js
Normal file
@ -0,0 +1,76 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.database.migrate');
|
||||
|
||||
// Simple migration helper to move local storage saves to indexed db.
|
||||
ns.MigrateLocalStorageToIndexedDb = {};
|
||||
|
||||
ns.MigrateLocalStorageToIndexedDb.migrate = function (piskelDatabase) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
var localStorageService = pskl.app.localStorageService;
|
||||
|
||||
var localStorageKeys = localStorageService.getKeys();
|
||||
var migrationData = localStorageKeys.map(function (key) {
|
||||
return {
|
||||
name: key.name,
|
||||
description: key.description,
|
||||
date: key.date,
|
||||
serialized: localStorageService.getPiskel(key.name)
|
||||
};
|
||||
});
|
||||
|
||||
// Define the sequential migration process.
|
||||
// Wait for each sprite to be saved before saving the next one.
|
||||
var success = true;
|
||||
var migrateSprite = function (index) {
|
||||
var data = migrationData[index];
|
||||
if (!data) {
|
||||
console.log('Data migration from local storage to indexed db finished.');
|
||||
if (success) {
|
||||
console.log('Local storage piskels successfully migrated. Old copies will be deleted.');
|
||||
ns.MigrateLocalStorageToIndexedDb.deleteLocalStoragePiskels();
|
||||
}
|
||||
|
||||
deferred.resolve();
|
||||
} else {
|
||||
ns.MigrateLocalStorageToIndexedDb.save_(piskelDatabase, data)
|
||||
.then(function () {
|
||||
migrateSprite(index + 1);
|
||||
})
|
||||
.catch(function (e) {
|
||||
var success = false;
|
||||
console.error('Failed to migrate local storage sprite for name: ' + data.name);
|
||||
migrateSprite(index + 1);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Start the migration.
|
||||
migrateSprite(0);
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
ns.MigrateLocalStorageToIndexedDb.save_ = function (piskelDatabase, piskelData) {
|
||||
return piskelDatabase.get(piskelData.name).then(function (data) {
|
||||
if (typeof data !== 'undefined') {
|
||||
return piskelDatabase.update(piskelData.name, piskelData.description, piskelData.date, piskelData.serialized);
|
||||
} else {
|
||||
return piskelDatabase.create(piskelData.name, piskelData.description, piskelData.date, piskelData.serialized);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ns.MigrateLocalStorageToIndexedDb.deleteLocalStoragePiskels = function () {
|
||||
var localStorageKeys = pskl.app.localStorageService.getKeys();
|
||||
|
||||
// Remove all sprites.
|
||||
localStorageKeys.forEach(function (key) {
|
||||
window.localStorage.removeItem('piskel.' + key.name);
|
||||
});
|
||||
|
||||
// Remove keys.
|
||||
window.localStorage.removeItem('piskel.keys');
|
||||
};
|
||||
|
||||
})();
|
@ -32,6 +32,7 @@
|
||||
};
|
||||
|
||||
ns.DrawingTestPlayer.prototype.setupInitialState_ = function () {
|
||||
|
||||
var size = this.initialState.size;
|
||||
var piskel = this.createPiskel_(size.width, size.height);
|
||||
pskl.app.piskelController.setPiskel(piskel);
|
||||
@ -39,9 +40,10 @@
|
||||
$.publish(Events.SELECT_PRIMARY_COLOR, [this.initialState.primaryColor]);
|
||||
$.publish(Events.SELECT_SECONDARY_COLOR, [this.initialState.secondaryColor]);
|
||||
$.publish(Events.SELECT_TOOL, [this.initialState.selectedTool]);
|
||||
if (this.initialState.penSize) {
|
||||
pskl.app.penSizeService.setPenSize(this.initialState.penSize);
|
||||
}
|
||||
|
||||
// Old tests do not have penSize stored in initialState, fallback to 1.
|
||||
var penSize = this.initialState.penSize || 1;
|
||||
pskl.app.penSizeService.setPenSize(this.initialState.penSize);
|
||||
};
|
||||
|
||||
ns.DrawingTestPlayer.prototype.createPiskel_ = function (width, height) {
|
||||
@ -108,6 +110,8 @@
|
||||
this.playTransformToolEvent_(recordEvent);
|
||||
} else if (recordEvent.type === 'instrumented-event') {
|
||||
this.playInstrumentedEvent_(recordEvent);
|
||||
} else if (recordEvent.type === 'clipboard-event') {
|
||||
this.playClipboardEvent_(recordEvent);
|
||||
}
|
||||
|
||||
// Record the time spent replaying the event
|
||||
@ -169,6 +173,16 @@
|
||||
pskl.app.piskelController[recordEvent.methodName].apply(pskl.app.piskelController, recordEvent.args);
|
||||
};
|
||||
|
||||
ns.DrawingTestPlayer.prototype.playClipboardEvent_ = function (recordEvent) {
|
||||
$.publish(recordEvent.event.type, {
|
||||
preventDefault: function () {},
|
||||
clipboardData: {
|
||||
items: [],
|
||||
setData: function () {}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ns.DrawingTestPlayer.prototype.onTestEnd_ = function () {
|
||||
this.removeMouseShim_();
|
||||
// Restore the original drawing loop.
|
||||
|
@ -15,6 +15,10 @@
|
||||
$.subscribe(Events.TRANSFORMATION_EVENT, this.onTransformationEvent_.bind(this));
|
||||
$.subscribe(Events.PRIMARY_COLOR_SELECTED, this.onColorEvent_.bind(this, true));
|
||||
$.subscribe(Events.SECONDARY_COLOR_SELECTED, this.onColorEvent_.bind(this, false));
|
||||
$.subscribe(Events.CLIPBOARD_COPY, this.onClipboardEvent_.bind(this));
|
||||
$.subscribe(Events.CLIPBOARD_CUT, this.onClipboardEvent_.bind(this));
|
||||
$.subscribe(Events.CLIPBOARD_PASTE, this.onClipboardEvent_.bind(this));
|
||||
|
||||
|
||||
for (var key in this.piskelController) {
|
||||
if (typeof this.piskelController[key] == 'function') {
|
||||
@ -136,6 +140,15 @@
|
||||
}
|
||||
};
|
||||
|
||||
ns.DrawingTestRecorder.prototype.onClipboardEvent_ = function (evt) {
|
||||
if (this.isRecording) {
|
||||
var recordEvent = {};
|
||||
recordEvent.type = 'clipboard-event';
|
||||
recordEvent.event = evt;
|
||||
this.events.push(recordEvent);
|
||||
}
|
||||
};
|
||||
|
||||
ns.DrawingTestRecorder.prototype.onInstrumentedMethod_ = function (callee, methodName, args) {
|
||||
if (this.isRecording) {
|
||||
var recordEvent = {};
|
||||
|
@ -16,7 +16,6 @@
|
||||
this.descriptor = descriptor;
|
||||
this.savePath = null;
|
||||
this.fps = fps;
|
||||
|
||||
} else {
|
||||
throw 'Missing arguments in Piskel constructor : ' + Array.prototype.join.call(arguments, ',');
|
||||
}
|
||||
|
@ -5,9 +5,23 @@
|
||||
this.reset();
|
||||
};
|
||||
|
||||
ns.BaseSelection.prototype.stringify = function () {
|
||||
return JSON.stringify({
|
||||
pixels: this.pixels,
|
||||
time: this.time
|
||||
});
|
||||
};
|
||||
|
||||
ns.BaseSelection.prototype.parse = function (str) {
|
||||
var selectionData = JSON.parse(str);
|
||||
this.pixels = selectionData.pixels;
|
||||
this.time = selectionData.time;
|
||||
};
|
||||
|
||||
ns.BaseSelection.prototype.reset = function () {
|
||||
this.pixels = [];
|
||||
this.hasPastedContent = false;
|
||||
this.time = -1;
|
||||
};
|
||||
|
||||
ns.BaseSelection.prototype.move = function (colDiff, rowDiff) {
|
||||
@ -30,5 +44,8 @@
|
||||
});
|
||||
|
||||
this.hasPastedContent = true;
|
||||
// Keep track of the selection time to compare between local selection and
|
||||
// paste event selections.
|
||||
this.time = Date.now();
|
||||
};
|
||||
})();
|
||||
|
@ -17,14 +17,11 @@
|
||||
$.subscribe(Events.SELECTION_CREATED, $.proxy(this.onSelectionCreated_, this));
|
||||
$.subscribe(Events.SELECTION_DISMISSED, $.proxy(this.onSelectionDismissed_, this));
|
||||
$.subscribe(Events.SELECTION_MOVE_REQUEST, $.proxy(this.onSelectionMoved_, this));
|
||||
$.subscribe(Events.SELECTION_COPY, this.copy.bind(this));
|
||||
$.subscribe(Events.SELECTION_CUT, this.cut.bind(this));
|
||||
$.subscribe(Events.SELECTION_PASTE, this.paste.bind(this));
|
||||
$.subscribe(Events.CLIPBOARD_COPY, this.copy.bind(this));
|
||||
$.subscribe(Events.CLIPBOARD_CUT, this.copy.bind(this));
|
||||
$.subscribe(Events.CLIPBOARD_PASTE, this.paste.bind(this));
|
||||
|
||||
var shortcuts = pskl.service.keyboard.Shortcuts;
|
||||
pskl.app.shortcutService.registerShortcut(shortcuts.SELECTION.PASTE, this.paste.bind(this));
|
||||
pskl.app.shortcutService.registerShortcut(shortcuts.SELECTION.CUT, this.cut.bind(this));
|
||||
pskl.app.shortcutService.registerShortcut(shortcuts.SELECTION.COPY, this.copy.bind(this));
|
||||
pskl.app.shortcutService.registerShortcut(shortcuts.SELECTION.DELETE, this.onDeleteShortcut_.bind(this));
|
||||
pskl.app.shortcutService.registerShortcut(shortcuts.SELECTION.COMMIT, this.commit.bind(this));
|
||||
|
||||
@ -78,29 +75,85 @@
|
||||
scope : this,
|
||||
replay : {
|
||||
type : SELECTION_REPLAY.ERASE,
|
||||
pixels : JSON.parse(JSON.stringify(pixels.slice(0)))
|
||||
pixels : JSON.parse(JSON.stringify(pixels))
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ns.SelectionManager.prototype.cut = function() {
|
||||
if (this.currentSelection) {
|
||||
// Put cut target into the selection:
|
||||
ns.SelectionManager.prototype.copy = function(event, domEvent) {
|
||||
if (this.currentSelection && this.piskelController.getCurrentFrame()) {
|
||||
this.currentSelection.fillSelectionFromFrame(this.piskelController.getCurrentFrame());
|
||||
this.erase();
|
||||
if (domEvent) {
|
||||
domEvent.clipboardData.setData('text/plain', this.currentSelection.stringify());
|
||||
domEvent.preventDefault();
|
||||
}
|
||||
if (event.type === Events.CLIPBOARD_CUT) {
|
||||
this.erase();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ns.SelectionManager.prototype.paste = function() {
|
||||
if (!this.currentSelection || !this.currentSelection.hasPastedContent) {
|
||||
if (window.localStorage.getItem('piskel.clipboard')) {
|
||||
this.currentSelection = JSON.parse(window.localStorage.getItem('piskel.clipboard'));
|
||||
} else {
|
||||
return;
|
||||
ns.SelectionManager.prototype.paste = function(event, domEvent) {
|
||||
var items = domEvent ? domEvent.clipboardData.items : [];
|
||||
|
||||
try {
|
||||
for (var i = 0 ; i < items.length ; i++) {
|
||||
var item = items[i];
|
||||
|
||||
if (/^image/i.test(item.type)) {
|
||||
this.pasteImage_(item);
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
if (/^text\/plain/i.test(item.type)) {
|
||||
this.pasteText_(item);
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Some of the clipboard APIs are not available on Safari/IE
|
||||
// Allow Piskel to fallback on local currentSelection pasting.
|
||||
}
|
||||
|
||||
var pixels = this.currentSelection.pixels;
|
||||
// temporarily keeping this code path for tests and fallbacks.
|
||||
if (this.currentSelection && this.currentSelection.hasPastedContent) {
|
||||
this.pastePixelsOnCurrentFrame_(this.currentSelection.pixels);
|
||||
}
|
||||
};
|
||||
|
||||
ns.SelectionManager.prototype.pasteImage_ = function(clipboardItem) {
|
||||
var blob = clipboardItem.getAsFile();
|
||||
pskl.utils.FileUtils.readImageFile(blob, function (image) {
|
||||
pskl.app.fileDropperService.dropPosition_ = {x: 0, y: 0};
|
||||
pskl.app.fileDropperService.onImageLoaded_(image, blob);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ns.SelectionManager.prototype.pasteText_ = function(clipboardItem) {
|
||||
var blob = clipboardItem.getAsString(function (selectionString) {
|
||||
var selectionData = JSON.parse(selectionString);
|
||||
var time = selectionData.time;
|
||||
var pixels = selectionData.pixels;
|
||||
|
||||
if (this.currentSelection && this.currentSelection.time >= time) {
|
||||
// If the local selection is newer or equal to the one coming from the clipboard event
|
||||
// use the local one. The reason is that the "move" information is only updated locally
|
||||
// without synchronizing it to the clipboard.
|
||||
// TODO: the selection should store the origin of the selection and the selection itself
|
||||
// separately.
|
||||
pixels = this.currentSelection.pixels;
|
||||
}
|
||||
|
||||
if (pixels) {
|
||||
// If the current clipboard data is some random text, pixels will not be defined.
|
||||
this.pastePixelsOnCurrentFrame_(pixels);
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ns.SelectionManager.prototype.pastePixelsOnCurrentFrame_ = function (pixels) {
|
||||
var frame = this.piskelController.getCurrentFrame();
|
||||
|
||||
this.pastePixels_(frame, pixels);
|
||||
@ -123,8 +176,7 @@
|
||||
var tool = pskl.app.drawingController.currentToolBehavior;
|
||||
var isSelectionTool = tool instanceof pskl.tools.drawing.selection.BaseSelect;
|
||||
if (isSelectionTool) {
|
||||
var overlay = pskl.app.drawingController.overlayFrame;
|
||||
tool.commitSelection(overlay);
|
||||
tool.commitSelection();
|
||||
}
|
||||
};
|
||||
|
||||
@ -147,13 +199,6 @@
|
||||
});
|
||||
};
|
||||
|
||||
ns.SelectionManager.prototype.copy = function() {
|
||||
if (this.currentSelection && this.piskelController.getCurrentFrame()) {
|
||||
this.currentSelection.fillSelectionFromFrame(this.piskelController.getCurrentFrame());
|
||||
window.localStorage.setItem('piskel.clipboard', JSON.stringify(this.currentSelection));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
@ -1,65 +1,161 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.service');
|
||||
|
||||
// 1 minute = 1000 * 60
|
||||
var BACKUP_INTERVAL = 1000 * 60;
|
||||
var ONE_SECOND = 1000;
|
||||
var ONE_MINUTE = 60 * ONE_SECOND;
|
||||
|
||||
ns.BackupService = function (piskelController) {
|
||||
// Save every minute = 1000 * 60
|
||||
var BACKUP_INTERVAL = ONE_MINUTE;
|
||||
// Store a new snapshot every 5 minutes.
|
||||
var SNAPSHOT_INTERVAL = ONE_MINUTE * 5;
|
||||
// Store up to 12 snapshots for a piskel session, min. 1 hour of work
|
||||
var MAX_SNAPSHOTS_PER_SESSION = 12;
|
||||
var MAX_SESSIONS = 10;
|
||||
|
||||
ns.BackupService = function (piskelController, backupDatabase) {
|
||||
this.piskelController = piskelController;
|
||||
this.lastHash = null;
|
||||
// Immediately store the current when initializing the Service to avoid storing
|
||||
// empty sessions.
|
||||
this.lastHash = this.piskelController.getPiskel().getHash();
|
||||
this.nextSnapshotDate = -1;
|
||||
|
||||
// backupDatabase can be provided for testing purposes.
|
||||
this.backupDatabase = backupDatabase || new pskl.database.BackupDatabase();
|
||||
};
|
||||
|
||||
ns.BackupService.prototype.init = function () {
|
||||
var previousPiskel = window.localStorage.getItem('bkp.next.piskel');
|
||||
var previousInfo = window.localStorage.getItem('bkp.next.info');
|
||||
if (previousPiskel && previousInfo) {
|
||||
this.savePiskel_('prev', previousPiskel, previousInfo);
|
||||
}
|
||||
this.backupDatabase.init().then(function () {
|
||||
window.setInterval(this.backup.bind(this), BACKUP_INTERVAL);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
window.setInterval(this.backup.bind(this), BACKUP_INTERVAL);
|
||||
// This is purely exposed for testing, so that backup dates can be set programmatically.
|
||||
ns.BackupService.prototype.currentDate_ = function () {
|
||||
return Date.now();
|
||||
};
|
||||
|
||||
ns.BackupService.prototype.backup = function () {
|
||||
var piskel = this.piskelController.getPiskel();
|
||||
var descriptor = piskel.getDescriptor();
|
||||
var hash = piskel.getHash();
|
||||
var info = {
|
||||
name : descriptor.name,
|
||||
description : descriptor.info,
|
||||
date : Date.now(),
|
||||
hash : hash
|
||||
};
|
||||
|
||||
// Do not save an unchanged piskel
|
||||
if (hash !== this.lastHash) {
|
||||
this.lastHash = hash;
|
||||
var serializedPiskel = pskl.utils.serialization.Serializer.serialize(piskel);
|
||||
this.savePiskel_('next', serializedPiskel, JSON.stringify(info));
|
||||
if (hash === this.lastHash) {
|
||||
return Q.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
ns.BackupService.prototype.getPreviousPiskelInfo = function () {
|
||||
var previousInfo = window.localStorage.getItem('bkp.prev.info');
|
||||
if (previousInfo) {
|
||||
return JSON.parse(previousInfo);
|
||||
}
|
||||
};
|
||||
// Update the hash
|
||||
// TODO: should only be done after a successful save.
|
||||
this.lastHash = hash;
|
||||
|
||||
ns.BackupService.prototype.load = function() {
|
||||
var previousPiskel = window.localStorage.getItem('bkp.prev.piskel');
|
||||
previousPiskel = JSON.parse(previousPiskel);
|
||||
// Prepare the backup snapshot.
|
||||
var descriptor = piskel.getDescriptor();
|
||||
var date = this.currentDate_();
|
||||
var snapshot = {
|
||||
session_id: pskl.app.sessionId,
|
||||
date: date,
|
||||
name: descriptor.name,
|
||||
description: descriptor.description,
|
||||
frames: piskel.getFrameCount(),
|
||||
width: piskel.getWidth(),
|
||||
height: piskel.getHeight(),
|
||||
fps: piskel.getFPS(),
|
||||
serialized: pskl.utils.serialization.Serializer.serialize(piskel)
|
||||
};
|
||||
|
||||
pskl.utils.serialization.Deserializer.deserialize(previousPiskel, function (piskel) {
|
||||
pskl.app.piskelController.setPiskel(piskel);
|
||||
return this.getSnapshotsBySessionId(pskl.app.sessionId).then(function (snapshots) {
|
||||
var latest = snapshots[0];
|
||||
|
||||
if (latest && date < this.nextSnapshotDate) {
|
||||
// update the latest snapshot
|
||||
snapshot.id = latest.id;
|
||||
return this.backupDatabase.updateSnapshot(snapshot);
|
||||
} else {
|
||||
// add a new snapshot
|
||||
this.nextSnapshotDate = date + SNAPSHOT_INTERVAL;
|
||||
return this.backupDatabase.createSnapshot(snapshot).then(function () {
|
||||
if (snapshots.length >= MAX_SNAPSHOTS_PER_SESSION) {
|
||||
// remove oldest snapshot
|
||||
return this.backupDatabase.deleteSnapshot(snapshots[snapshots.length - 1]);
|
||||
}
|
||||
}.bind(this)).then(function () {
|
||||
var isNewSession = !latest;
|
||||
if (!isNewSession) {
|
||||
return;
|
||||
}
|
||||
return this.backupDatabase.getSessions().then(function (sessions) {
|
||||
if (sessions.length <= MAX_SESSIONS) {
|
||||
// If MAX_SESSIONS has not been reached, no need to delete
|
||||
// previous sessions.
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare an array containing all the ids of the sessions to be deleted.
|
||||
var sessionIdsToDelete = sessions.sort(function (s1, s2) {
|
||||
return s1.startDate - s2.startDate;
|
||||
}).map(function (s) {
|
||||
return s.id;
|
||||
}).slice(0, sessions.length - MAX_SESSIONS);
|
||||
|
||||
// Delete all the extra sessions.
|
||||
return Q.all(sessionIdsToDelete.map(function (id) {
|
||||
return this.deleteSession(id);
|
||||
}.bind(this)));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}
|
||||
}.bind(this)).catch(function (e) {
|
||||
console.error(e);
|
||||
});
|
||||
};
|
||||
|
||||
ns.BackupService.prototype.savePiskel_ = function (type, piskel, info) {
|
||||
try {
|
||||
window.localStorage.setItem('bkp.' + type + '.piskel', piskel);
|
||||
window.localStorage.setItem('bkp.' + type + '.info', info);
|
||||
} catch (e) {
|
||||
console.error('Could not save piskel backup in localStorage.', e);
|
||||
}
|
||||
ns.BackupService.prototype.getSnapshotsBySessionId = function (sessionId) {
|
||||
return this.backupDatabase.getSnapshotsBySessionId(sessionId);
|
||||
};
|
||||
|
||||
ns.BackupService.prototype.deleteSession = function (sessionId) {
|
||||
return this.backupDatabase.deleteSnapshotsForSession(sessionId);
|
||||
};
|
||||
|
||||
ns.BackupService.prototype.getPreviousPiskelInfo = function () {
|
||||
return this.backupDatabase.findLastSnapshot(function (snapshot) {
|
||||
return snapshot.session_id !== pskl.app.sessionId;
|
||||
});
|
||||
};
|
||||
|
||||
ns.BackupService.prototype.list = function() {
|
||||
return this.backupDatabase.getSessions();
|
||||
};
|
||||
|
||||
ns.BackupService.prototype.loadSnapshotById = function(snapshotId) {
|
||||
var deferred = Q.defer();
|
||||
|
||||
this.backupDatabase.getSnapshot(snapshotId).then(function (snapshot) {
|
||||
pskl.utils.serialization.Deserializer.deserialize(
|
||||
JSON.parse(snapshot.serialized),
|
||||
function (piskel) {
|
||||
pskl.app.piskelController.setPiskel(piskel);
|
||||
deferred.resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
// Load "latest" backup snapshot.
|
||||
ns.BackupService.prototype.load = function() {
|
||||
var deferred = Q.defer();
|
||||
|
||||
this.getPreviousPiskelInfo().then(function (snapshot) {
|
||||
pskl.utils.serialization.Deserializer.deserialize(
|
||||
JSON.parse(snapshot.serialized),
|
||||
function (piskel) {
|
||||
pskl.app.piskelController.setPiskel(piskel);
|
||||
deferred.resolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
})();
|
||||
|
@ -30,6 +30,8 @@
|
||||
};
|
||||
|
||||
ns.BeforeUnloadService.prototype.onBeforeUnload = function (evt) {
|
||||
// Attempt one last backup. Some of it may fail due to the asynchronous
|
||||
// nature of IndexedDB.
|
||||
pskl.app.backupService.backup();
|
||||
if (pskl.app.savedStatusService.isDirty()) {
|
||||
var confirmationMessage = 'Your current sprite has unsaved changes. Are you sure you want to quit?';
|
||||
|
25
src/js/service/ClipboardService.js
Normal file
25
src/js/service/ClipboardService.js
Normal file
@ -0,0 +1,25 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.service');
|
||||
|
||||
ns.ClipboardService = function (piskelController) {
|
||||
this.piskelController = piskelController;
|
||||
};
|
||||
|
||||
ns.ClipboardService.prototype.init = function () {
|
||||
window.addEventListener('copy', this._onCopy.bind(this), true);
|
||||
window.addEventListener('cut', this._onCut.bind(this), true);
|
||||
window.addEventListener('paste', this._onPaste.bind(this), true);
|
||||
};
|
||||
|
||||
ns.ClipboardService.prototype._onCut = function (event) {
|
||||
$.publish(Events.CLIPBOARD_CUT, event);
|
||||
};
|
||||
|
||||
ns.ClipboardService.prototype._onCopy = function (event) {
|
||||
$.publish(Events.CLIPBOARD_COPY, event);
|
||||
};
|
||||
|
||||
ns.ClipboardService.prototype._onPaste = function (event) {
|
||||
$.publish(Events.CLIPBOARD_PASTE, event);
|
||||
};
|
||||
})();
|
@ -35,13 +35,9 @@
|
||||
var isPiskel = /\.piskel$/i.test(file.name);
|
||||
var isPalette = /\.(gpl|txt|pal)$/i.test(file.name);
|
||||
if (isImage) {
|
||||
$.publish(Events.DIALOG_SHOW, {
|
||||
dialogId : 'import',
|
||||
initArgs : {
|
||||
rawFiles: [file]
|
||||
}
|
||||
});
|
||||
// pskl.utils.FileUtils.readImageFile(file, this.onImageLoaded_.bind(this));
|
||||
pskl.utils.FileUtils.readImageFile(file, function (image) {
|
||||
this.onImageLoaded_(image, file);
|
||||
}.bind(this));
|
||||
} else if (isPiskel) {
|
||||
pskl.utils.PiskelFileUtils.loadFromFile(file, this.onPiskelFileLoaded_, this.onPiskelFileError_);
|
||||
} else if (isPalette) {
|
||||
@ -56,7 +52,7 @@
|
||||
};
|
||||
|
||||
ns.FileDropperService.prototype.onPiskelFileLoaded_ = function (piskel) {
|
||||
if (window.confirm('This will replace your current animation')) {
|
||||
if (window.confirm(Constants.CONFIRM_OVERWRITE)) {
|
||||
pskl.app.piskelController.setPiskel(piskel);
|
||||
}
|
||||
};
|
||||
@ -65,10 +61,23 @@
|
||||
$.publish(Events.PISKEL_FILE_IMPORT_FAILED, [reason]);
|
||||
};
|
||||
|
||||
ns.FileDropperService.prototype.onImageLoaded_ = function (importedImage) {
|
||||
ns.FileDropperService.prototype.onImageLoaded_ = function (importedImage, file) {
|
||||
var piskelWidth = pskl.app.piskelController.getWidth();
|
||||
var piskelHeight = pskl.app.piskelController.getHeight();
|
||||
|
||||
if (this.isMultipleFiles_) {
|
||||
this.piskelController.addFrameAtCurrentIndex();
|
||||
this.piskelController.selectNextFrame();
|
||||
} else if (importedImage.width > piskelWidth || importedImage.height > piskelHeight) {
|
||||
// For single file imports, if the file is too big, trigger the import wizard.
|
||||
$.publish(Events.DIALOG_SHOW, {
|
||||
dialogId : 'import',
|
||||
initArgs : {
|
||||
rawFiles: [file]
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var currentFrame = this.piskelController.getCurrentFrame();
|
||||
|
@ -15,10 +15,17 @@
|
||||
data : imageData
|
||||
};
|
||||
|
||||
var protocol = pskl.utils.Environment.isHttps() ? 'https' : 'http';
|
||||
var wrappedSuccess = function (response) {
|
||||
success(Constants.IMAGE_SERVICE_GET_URL + response.responseText);
|
||||
var getUrl = pskl.utils.Template.replace(Constants.IMAGE_SERVICE_GET_URL, {
|
||||
protocol: protocol
|
||||
});
|
||||
success(getUrl + response.responseText);
|
||||
};
|
||||
|
||||
pskl.utils.Xhr.post(Constants.IMAGE_SERVICE_UPLOAD_URL, data, wrappedSuccess, error);
|
||||
var uploadUrl = pskl.utils.Template.replace(Constants.IMAGE_SERVICE_UPLOAD_URL, {
|
||||
protocol: protocol
|
||||
});
|
||||
pskl.utils.Xhr.post(uploadUrl, data, wrappedSuccess, error);
|
||||
};
|
||||
})();
|
||||
|
@ -84,6 +84,10 @@
|
||||
'123456789'.split(''), '1 to 9')
|
||||
},
|
||||
|
||||
DEBUG : {
|
||||
RELOAD_STYLES : createShortcut('move-left', 'Move viewport left', 'ctrl+alt+R'),
|
||||
},
|
||||
|
||||
CATEGORIES : ['TOOL', 'SELECTION', 'MISC', 'STORAGE', 'COLOR']
|
||||
};
|
||||
})();
|
||||
|
@ -3,11 +3,12 @@
|
||||
|
||||
ns.PaletteService = function () {
|
||||
this.dynamicPalettes = [];
|
||||
this.localStorageService = window.localStorage;
|
||||
// Exposed for tests.
|
||||
this.localStorageGlobal = window.localStorage;
|
||||
};
|
||||
|
||||
ns.PaletteService.prototype.getPalettes = function () {
|
||||
var palettesString = this.localStorageService.getItem('piskel.palettes');
|
||||
var palettesString = this.localStorageGlobal.getItem('piskel.palettes');
|
||||
var palettes = JSON.parse(palettesString) || [];
|
||||
palettes = palettes.map(function (palette) {
|
||||
return pskl.model.Palette.fromObject(palette);
|
||||
@ -54,7 +55,7 @@
|
||||
palettes = palettes.filter(function (palette) {
|
||||
return this.dynamicPalettes.indexOf(palette) === -1;
|
||||
}.bind(this));
|
||||
this.localStorageService.setItem('piskel.palettes', JSON.stringify(palettes));
|
||||
this.localStorageGlobal.setItem('piskel.palettes', JSON.stringify(palettes));
|
||||
$.publish(Events.PALETTE_LIST_UPDATED);
|
||||
};
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
*/
|
||||
ns.PerformanceReport = function (piskel, colorsCount) {
|
||||
var pixels = piskel.getWidth() * piskel.getHeight();
|
||||
this.resolution = pixels > (500 * 500);
|
||||
this.resolution = pixels > (512 * 512);
|
||||
|
||||
var layersCount = piskel.getLayers().length;
|
||||
this.layers = layersCount > 25;
|
||||
@ -24,10 +24,10 @@
|
||||
var framesCount = piskel.getLayerAt(0).size();
|
||||
this.frames = framesCount > 100;
|
||||
|
||||
this.colors = colorsCount > 100;
|
||||
this.colors = colorsCount >= 256;
|
||||
|
||||
var overallScore = (pixels / 2500) + (layersCount * 4) + framesCount + colorsCount;
|
||||
this.overall = overallScore > 100;
|
||||
var overallScore = (pixels / 2620) + (layersCount * 4) + framesCount + (colorsCount * 100 / 256);
|
||||
this.overall = overallScore > 200;
|
||||
};
|
||||
|
||||
ns.PerformanceReport.prototype.equals = function (report) {
|
||||
|
57
src/js/service/storage/IndexedDbStorageService.js
Normal file
57
src/js/service/storage/IndexedDbStorageService.js
Normal file
@ -0,0 +1,57 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.service.storage');
|
||||
|
||||
ns.IndexedDbStorageService = function (piskelController) {
|
||||
this.piskelController = piskelController;
|
||||
this.piskelDatabase = new pskl.database.PiskelDatabase();
|
||||
};
|
||||
|
||||
ns.IndexedDbStorageService.prototype.init = function () {
|
||||
this.piskelDatabase.init().catch(function (e) {
|
||||
console.log('Failed to initialize PiskelDatabase, local browser saves will be unavailable.');
|
||||
});
|
||||
};
|
||||
|
||||
ns.IndexedDbStorageService.prototype.save = function (piskel) {
|
||||
var name = piskel.getDescriptor().name;
|
||||
var description = piskel.getDescriptor().description;
|
||||
var date = Date.now();
|
||||
var serialized = pskl.utils.serialization.Serializer.serialize(piskel);
|
||||
|
||||
return this.save_(name, description, date, serialized);
|
||||
};
|
||||
|
||||
ns.IndexedDbStorageService.prototype.save_ = function (name, description, date, serialized) {
|
||||
return this.piskelDatabase.get(name).then(function (piskelData) {
|
||||
if (typeof piskelData !== 'undefined') {
|
||||
return this.piskelDatabase.update(name, description, date, serialized);
|
||||
} else {
|
||||
return this.piskelDatabase.create(name, description, date, serialized);
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
ns.IndexedDbStorageService.prototype.load = function (name) {
|
||||
return this.piskelDatabase.get(name).then(function (piskelData) {
|
||||
if (typeof piskelData !== 'undefined') {
|
||||
var serialized = piskelData.serialized;
|
||||
pskl.utils.serialization.Deserializer.deserialize(
|
||||
JSON.parse(serialized),
|
||||
function (piskel) {
|
||||
pskl.app.piskelController.setPiskel(piskel);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.log('no local browser save found for name: ' + name);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ns.IndexedDbStorageService.prototype.remove = function (name) {
|
||||
return this.piskelDatabase.delete(name);
|
||||
};
|
||||
|
||||
ns.IndexedDbStorageService.prototype.getKeys = function () {
|
||||
return this.piskelDatabase.list();
|
||||
};
|
||||
})();
|
@ -27,10 +27,15 @@
|
||||
return this.delegateSave_(pskl.app.galleryStorageService, piskel);
|
||||
};
|
||||
|
||||
// @deprecated, use saveToIndexedDb unless indexedDb is not available.
|
||||
ns.StorageService.prototype.saveToLocalStorage = function (piskel) {
|
||||
return this.delegateSave_(pskl.app.localStorageService, piskel);
|
||||
};
|
||||
|
||||
ns.StorageService.prototype.saveToIndexedDb = function (piskel) {
|
||||
return this.delegateSave_(pskl.app.indexedDbStorageService, piskel);
|
||||
};
|
||||
|
||||
ns.StorageService.prototype.saveToFileDownload = function (piskel) {
|
||||
return this.delegateSave_(pskl.app.fileDownloadStorageService, piskel);
|
||||
};
|
||||
@ -67,7 +72,7 @@
|
||||
// wrap in timeout in order to start saving only after event.preventDefault
|
||||
// has been done
|
||||
window.setTimeout(function () {
|
||||
this.saveToLocalStorage(this.piskelController.getPiskel());
|
||||
this.saveToIndexedDb(this.piskelController.getPiskel());
|
||||
}.bind(this), 0);
|
||||
}
|
||||
};
|
||||
|
@ -44,10 +44,17 @@
|
||||
};
|
||||
|
||||
ns.VerticalMirrorPen.prototype.getSymmetricCol_ = function(col, frame) {
|
||||
return frame.getWidth() - col - 1;
|
||||
return frame.getWidth() - col - this.getPenSizeOffset_();
|
||||
};
|
||||
|
||||
ns.VerticalMirrorPen.prototype.getSymmetricRow_ = function(row, frame) {
|
||||
return frame.getHeight() - row - 1;
|
||||
return frame.getHeight() - row - this.getPenSizeOffset_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Depending on the pen size, the mirrored index need to have an offset of 1 pixel.
|
||||
*/
|
||||
ns.VerticalMirrorPen.prototype.getPenSizeOffset_ = function(row, frame) {
|
||||
return pskl.app.penSizeService.getPenSize() % 2;
|
||||
};
|
||||
})();
|
||||
|
@ -16,7 +16,7 @@
|
||||
ns.AbstractDragSelect.prototype.onSelectStart_ = function (col, row, frame, overlay) {
|
||||
if (this.hasSelection) {
|
||||
this.hasSelection = false;
|
||||
this.commitSelection(overlay);
|
||||
this.commitSelection();
|
||||
} else {
|
||||
this.hasSelection = true;
|
||||
this.onDragSelectStart_(col, row);
|
||||
|
@ -26,6 +26,8 @@
|
||||
{key : 'ctrl+v', description : 'Paste the copied area'},
|
||||
{key : 'shift', description : 'Hold to move the content'}
|
||||
];
|
||||
|
||||
$.subscribe(Events.SELECTION_DISMISSED, this.onSelectionDismissed_.bind(this));
|
||||
};
|
||||
|
||||
pskl.utils.inherit(ns.BaseSelect, pskl.tools.drawing.BaseTool);
|
||||
@ -52,7 +54,7 @@
|
||||
this.mode = 'moveSelection';
|
||||
if (event.shiftKey && !this.isMovingContent_) {
|
||||
this.isMovingContent_ = true;
|
||||
$.publish(Events.SELECTION_CUT);
|
||||
$.publish(Events.CLIPBOARD_CUT);
|
||||
this.drawSelectionOnOverlay_(overlay);
|
||||
}
|
||||
this.onSelectionMoveStart_(col, row, frame, overlay);
|
||||
@ -111,16 +113,24 @@
|
||||
};
|
||||
|
||||
/**
|
||||
* Protected method, should be called when the selection is dismissed.
|
||||
* Protected method, should be called when the selection is committed,
|
||||
* typically by clicking outside of the selected area.
|
||||
*/
|
||||
ns.BaseSelect.prototype.commitSelection = function (overlay) {
|
||||
ns.BaseSelect.prototype.commitSelection = function () {
|
||||
if (this.isMovingContent_) {
|
||||
$.publish(Events.SELECTION_PASTE);
|
||||
$.publish(Events.CLIPBOARD_PASTE);
|
||||
this.isMovingContent_ = false;
|
||||
}
|
||||
|
||||
// Clean previous selection:
|
||||
$.publish(Events.SELECTION_DISMISSED);
|
||||
};
|
||||
|
||||
/**
|
||||
* Protected method, should be called when the selection is dismissed.
|
||||
*/
|
||||
ns.BaseSelect.prototype.onSelectionDismissed_ = function () {
|
||||
var overlay = pskl.app.drawingController.overlayFrame;
|
||||
overlay.clear();
|
||||
this.hasSelection = false;
|
||||
};
|
||||
|
@ -24,7 +24,7 @@
|
||||
ns.ShapeSelect.prototype.onSelectStart_ = function (col, row, frame, overlay) {
|
||||
if (this.hasSelection) {
|
||||
this.hasSelection = false;
|
||||
this.commitSelection(overlay);
|
||||
this.commitSelection();
|
||||
} else {
|
||||
this.hasSelection = true;
|
||||
// From the pixel clicked, get shape using an algorithm similar to the paintbucket one:
|
||||
|
@ -23,7 +23,15 @@
|
||||
|
||||
isIntegrationTest : function () {
|
||||
return window.location.href.indexOf('integration-test') !== -1;
|
||||
}
|
||||
},
|
||||
|
||||
isDebug : function () {
|
||||
return window.location.href.indexOf('debug') !== -1;
|
||||
},
|
||||
|
||||
isHttps : function () {
|
||||
return window.location.href.indexOf('https://') === 0;
|
||||
},
|
||||
};
|
||||
|
||||
})();
|
||||
|
@ -17,6 +17,7 @@
|
||||
LAYER_OPACITY : 'LAYER_OPACITY',
|
||||
EXPORT_SCALE: 'EXPORT_SCALE',
|
||||
EXPORT_TAB: 'EXPORT_TAB',
|
||||
EXPORT_GIF_REPEAT: 'EXPORT_GIF_REPEAT',
|
||||
PEN_SIZE : 'PEN_SIZE',
|
||||
RESIZE_SETTINGS: 'RESIZE_SETTINGS',
|
||||
COLOR_FORMAT: 'COLOR_FORMAT',
|
||||
@ -41,6 +42,7 @@
|
||||
'LAYER_PREVIEW' : true,
|
||||
'EXPORT_SCALE' : 1,
|
||||
'EXPORT_TAB' : 'gif',
|
||||
'EXPORT_GIF_REPEAT' : true,
|
||||
'PEN_SIZE' : 1,
|
||||
'RESIZE_SETTINGS': {
|
||||
maintainRatio : true,
|
||||
|
@ -68,12 +68,21 @@
|
||||
};
|
||||
loadScript('piskel-script-list.js', 'loadNextScript()');
|
||||
|
||||
var styles;
|
||||
window.loadStyles = function () {
|
||||
var styles = window.pskl_exports.styles;
|
||||
styles = window.pskl_exports.styles;
|
||||
for (var i = 0 ; i < styles.length ; i++) {
|
||||
loadStyle(styles[i]);
|
||||
}
|
||||
};
|
||||
|
||||
window.reloadStyles = function () {
|
||||
for (var i = 0 ; i < styles.length ; i++) {
|
||||
document.querySelector('link[href="' + styles[i] + '"]').remove();
|
||||
loadStyle(styles[i]);
|
||||
}
|
||||
};
|
||||
|
||||
loadScript('piskel-style-list.js', 'loadStyles()');
|
||||
} else {
|
||||
var script;
|
||||
|
@ -79,6 +79,11 @@
|
||||
"js/model/Palette.js",
|
||||
"js/model/Piskel.js",
|
||||
|
||||
// Database (IndexedDB)
|
||||
"js/database/BackupDatabase.js",
|
||||
"js/database/PiskelDatabase.js",
|
||||
"js/database/migrate/MigrateLocalStorageToIndexedDb.js",
|
||||
|
||||
// Selection
|
||||
"js/selection/SelectionManager.js",
|
||||
"js/selection/BaseSelection.js",
|
||||
@ -144,6 +149,9 @@
|
||||
"js/controller/dialogs/CreatePaletteController.js",
|
||||
"js/controller/dialogs/BrowseLocalController.js",
|
||||
"js/controller/dialogs/CheatsheetController.js",
|
||||
"js/controller/dialogs/backups/steps/SelectSession.js",
|
||||
"js/controller/dialogs/backups/steps/SessionDetails.js",
|
||||
"js/controller/dialogs/backups/BrowseBackups.js",
|
||||
"js/controller/dialogs/importwizard/steps/AbstractImportStep.js",
|
||||
"js/controller/dialogs/importwizard/steps/AdjustSize.js",
|
||||
"js/controller/dialogs/importwizard/steps/ImageImport.js",
|
||||
@ -170,6 +178,7 @@
|
||||
// Services
|
||||
"js/service/storage/StorageService.js",
|
||||
"js/service/storage/FileDownloadStorageService.js",
|
||||
"js/service/storage/IndexedDbStorageService.js",
|
||||
"js/service/storage/LocalStorageService.js",
|
||||
"js/service/storage/GalleryStorageService.js",
|
||||
"js/service/storage/DesktopStorageService.js",
|
||||
@ -195,15 +204,13 @@
|
||||
"js/service/keyboard/ShortcutService.js",
|
||||
"js/service/ImportService.js",
|
||||
"js/service/ImageUploadService.js",
|
||||
"js/service/ClipboardService.js",
|
||||
"js/service/CurrentColorsService.js",
|
||||
"js/service/FileDropperService.js",
|
||||
"js/service/SelectedColorsService.js",
|
||||
"js/service/MouseStateService.js",
|
||||
"js/service/performance/PerformanceReport.js",
|
||||
"js/service/performance/PerformanceReportService.js",
|
||||
"js/service/storage/LocalStorageService.js",
|
||||
"js/service/storage/GalleryStorageService.js",
|
||||
"js/service/storage/DesktopStorageService.js",
|
||||
|
||||
// Tools
|
||||
"js/tools/ToolsHelper.js",
|
||||
|
@ -18,6 +18,7 @@
|
||||
"css/icons.css",
|
||||
"css/color-picker-slider.css",
|
||||
"css/dialogs.css",
|
||||
"css/dialogs-browse-backups.css",
|
||||
"css/dialogs-browse-local.css",
|
||||
"css/dialogs-cheatsheet.css",
|
||||
"css/dialogs-create-palette.css",
|
||||
|
70
src/templates/data-uri-export.html
Normal file
70
src/templates/data-uri-export.html
Normal file
@ -0,0 +1,70 @@
|
||||
<div style="display:none">
|
||||
<script type="text/template" id="data-uri-export-partial">
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #444;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#image-wrapper {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
img {
|
||||
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABlBMVEXf39////8zI3BgAAAAHklEQVR4AWNghAIGCMDgjwgFCDDSw2M0PSCD0fQAACRcAgF4ciGUAAAAAElFTkSuQmCC') repeat;
|
||||
}
|
||||
|
||||
#bottom-wrapper {
|
||||
width: 600px;
|
||||
height: 300px;
|
||||
margin-top: 20px;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
textarea {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
border-style: none;
|
||||
|
||||
background: black;
|
||||
color: gold;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
span {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
font-size: 12px;
|
||||
padding: 5px;
|
||||
color: white;
|
||||
background-color: #444;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div id="image-wrapper">
|
||||
<img src="{{src}}" alt="Exported image as data-uri">
|
||||
</div>
|
||||
<div id="bottom-wrapper">
|
||||
<textarea spellcheck="false" onclick="this.select()">{{src}}</textarea>
|
||||
<span>Data-uri for the exported framesheet</span>
|
||||
</div>
|
||||
</body>
|
||||
</script>
|
||||
</div>
|
83
src/templates/dialogs/browse-backups.html
Normal file
83
src/templates/dialogs/browse-backups.html
Normal file
@ -0,0 +1,83 @@
|
||||
<script type="text/template" id="templates/dialogs/browse-backups.html">
|
||||
<div class="dialog-wrapper">
|
||||
<h3 class="dialog-head">
|
||||
Browse backups
|
||||
<span class="dialog-close">X</span>
|
||||
</h3>
|
||||
<div class="dialog-content backups-wizard-container"></div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="backups-select-session">
|
||||
<div class="backups-step-container">
|
||||
<div class="backups-step-content">
|
||||
<div class="browse-backups-disclaimer">
|
||||
<div class="backups-icon icon-common-backup-white"> </div>
|
||||
<div class="browse-backups-disclaimer-content">
|
||||
<!-- Keep in sync with MAX_SESSIONS in BackupService.js -->
|
||||
If you forgot to save your work or if Piskel crashed, try to restore one of the automatically backed up sessions below (up to 10 sessions).
|
||||
<br/>
|
||||
<br/>
|
||||
Backups may be erased without notice, so try to save your work to a file or to your gallery as soon as you can.
|
||||
</div>
|
||||
</div>
|
||||
<div class="session-list">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="session-list-empty">
|
||||
<div class="centered-message session-list-empty">No session found ...</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="session-list-error">
|
||||
<div class="centered-message session-list-error">Could not load backup sessions, something went wrong.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="session-list-item">
|
||||
<div class="session-item">
|
||||
<div class="session-details">
|
||||
<span class="session-details-title">{{name}} {{description}}</span>
|
||||
<span class="session-details-info">Session recorded {{date}}</span>
|
||||
<span class="session-details-info">{{count}} saved</span>
|
||||
</div>
|
||||
<div class="session-actions">
|
||||
<button class="button" data-session-id="{{id}}" data-action="delete">Delete</button>
|
||||
<button class="button button-primary" data-session-id="{{id}}" data-action="view">View</button>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="backups-session-details">
|
||||
<div class="backups-step-container">
|
||||
<div class="backups-step-content">
|
||||
<div class="snapshot-list"></div>
|
||||
</div>
|
||||
<div class="backups-step-actions">
|
||||
<button class="button back-button">back</button>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="snapshot-list-empty">
|
||||
<div class="centered-message snapshot-list-empty">No snapshot found ...</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="snapshot-list-error">
|
||||
<div class="centered-message snapshot-list-error">Could not load snapshots, something went wrong.</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="snapshot-list-item">
|
||||
<div class="snapshot-item" data-snapshot-id={{id}}>
|
||||
<div class="snapshot-preview lowcont-dark-picker-background"></div>
|
||||
<div class="snapshot-details">
|
||||
<span class="snapshot-details-title">{{name}} {{description}}</span>
|
||||
<span class="snapshot-details-info">Snapshot recorded {{date}}</span>
|
||||
<span class="snapshot-details-info">{{frames}}, size {{resolution}}, {{fps}}fps</span>
|
||||
</div>
|
||||
<div class="snapshot-actions">
|
||||
<button class="button button-primary" data-action="load" data-snapshot-id="{{id}}">Load</button>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
@ -67,11 +67,11 @@
|
||||
<div class="import-mode">
|
||||
<div class="import-mode-title">How do you want to import the new content?</div>
|
||||
<div class="import-mode-section">
|
||||
<span class="import-mode-section-description">Combine the imported content and your sprite.</span>
|
||||
<button class="import-mode-merge-button button-primary button">Merge</button>
|
||||
<span class="import-mode-section-description">Combine with your sprite</span>
|
||||
<button class="import-mode-merge-button button-primary button">Combine</button>
|
||||
</div>
|
||||
<div class="import-mode-section">
|
||||
<span class="import-mode-section-description">Replace your current sprite by the imported content.</span>
|
||||
<span class="import-mode-section-description">Replace your sprite</span>
|
||||
<button class="import-mode-replace-button button-primary button">Replace</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -131,9 +131,6 @@
|
||||
The imported image is bigger than the current sprite.
|
||||
</div>
|
||||
<div class="import-resize-section">
|
||||
<div class="import-resize-option-label">
|
||||
How do you want to proceed?
|
||||
</div>
|
||||
<label class="import-resize-option">
|
||||
<input type="radio" name="resize-mode" id="resize-option-expand" value="expand" {{expandChecked}}/>
|
||||
<span>Expand canvas to {{newSize}}</span>
|
||||
@ -162,19 +159,19 @@
|
||||
</div>
|
||||
|
||||
<div class="import-step-content">
|
||||
<div>Select a frame in your current sprite:</div>
|
||||
<div>Select the insertion frame:</div>
|
||||
<div class="insert-frame-preview"></div>
|
||||
<div class="insert-mode-container">
|
||||
<div class="insert-mode-option-label">
|
||||
How should the imported frames be inserted:
|
||||
Insert imported frames:
|
||||
</div>
|
||||
<label class="insert-mode-option">
|
||||
<input type="radio" name="insert-mode" id="insert-mode-add" value="add" checked="checked"/>
|
||||
<span>Add new frames</span>
|
||||
<span>as new frames</span>
|
||||
</label>
|
||||
<label class="insert-mode-option">
|
||||
<input type="radio" name="insert-mode" id="insert-mode-insert" value="insert"/>
|
||||
<span>Insert in existing frames</span>
|
||||
<span>in existing frames</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="import-step-buttons">
|
||||
@ -184,13 +181,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="import-invalid-file">
|
||||
<div class="import-step-container">
|
||||
<div>THIS IS AN INVALID FILEZ</div>
|
||||
<div class="import-step-buttons">
|
||||
<button class="import-back-button button">back</button>
|
||||
<button class="import-next-button button button-primary">next</button>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
@ -15,10 +15,10 @@
|
||||
<p>If you ignore this warning, please save often!</p>
|
||||
<p>To fix the issue, try adjusting your sprite settings:</p>
|
||||
<ul>
|
||||
<li>sprite resolution <sup title="recommended: lower than 256x256, max: 512x512" rel="tooltip" data-placement="top">?</sup></li>
|
||||
<li>number of layers <sup title="recommended: lower than 5, max: 20" rel="tooltip" data-placement="top">?</sup></li>
|
||||
<li>number of frames <sup title="recommended: lower than 25, max: 100" rel="tooltip" data-placement="top">?</sup></li>
|
||||
<li>number of colors <sup title="max: 100" rel="tooltip" data-placement="top">?</sup></li>
|
||||
<li>sprite resolution <sup title="recommended: lower than 512x512" rel="tooltip" data-placement="top">?</sup></li>
|
||||
<li>number of layers <sup title="recommended: less than 10" rel="tooltip" data-placement="top">?</sup></li>
|
||||
<li>number of frames <sup title="recommended: less than 50" rel="tooltip" data-placement="top">?</sup></li>
|
||||
<li>number of colors <sup title="recommended: less than 256" rel="tooltip" data-placement="top">?</sup></li>
|
||||
</ul>
|
||||
<p>We strive to improve Piskel, its performance and stability, but this is a personal project with limited time and resources.
|
||||
We prefer to warn you early rather than having you lose your work.</p>
|
||||
|
@ -1,7 +1,17 @@
|
||||
<script type="text/html" id="templates/settings/export/gif.html">
|
||||
<div class="export-panel-gif">
|
||||
<div class="export-panel-header export-info">
|
||||
Convert your sprite to an animated GIF. Opacity will not be preserved. Colors might be resampled.
|
||||
Convert your sprite to an animated GIF.
|
||||
</div>
|
||||
<div class="gif-export-warning">
|
||||
<div class="gif-export-warning-icon icon-common-warning-red"> </div>
|
||||
<div class="gif-export-warning-message">
|
||||
Too many colors: can not preserve original colors or transparency.
|
||||
</div>
|
||||
</div>
|
||||
<div class="export-panel-section export-panel-row">
|
||||
<input id="gif-repeat-checkbox" class="gif-repeat-checkbox checkbox-fix" type="checkbox" />
|
||||
<label for="gif-repeat-checkbox">Loop repeatedly</label>
|
||||
</div>
|
||||
<div class="export-panel-section export-panel-row">
|
||||
<button type="button" class="button button-primary gif-download-button">Download</button>
|
||||
|
@ -38,16 +38,11 @@
|
||||
<div class="settings-title">
|
||||
Recover recent sessions
|
||||
</div>
|
||||
<div class="settings-item previous-session">
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type="text/template" id="previous-session-info-template">
|
||||
<div>
|
||||
Restore a backup of <span class="highlight">{{name}}</span>, saved at <span style="color:white">{{date}}</span> ?
|
||||
<div style="margin-top:10px;">
|
||||
<button type="button" class="button button-primary restore-session-button">Restore</button>
|
||||
<div class="settings-item">
|
||||
Browse backups of previous sessions.
|
||||
<div style="margin-top:10px;">
|
||||
<button type="button" class="button button-primary browse-backups-button">Browse backups</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
@ -39,7 +39,7 @@
|
||||
<script type="text/template" id="save-gallery-unavailable-partial">
|
||||
<div class="settings-title">Save online</div>
|
||||
<div class="settings-item">
|
||||
<div class="save-status">Login to <a href="http://piskelapp.com" target="_blank">piskelapp.com</a> to save and share your sprites online.</div>
|
||||
<div class="save-status">Login to <a href="https://www.piskelapp.com" target="_blank">piskelapp.com</a> to save and share your sprites online.</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
'settings/test-export-png.js',
|
||||
'settings/test-export-png-scale.js',
|
||||
'settings/test-import-image.js',
|
||||
'settings/test-import-image-empty.js',
|
||||
'settings/test-import-image-twice.js',
|
||||
'settings/test-resize-complete.js',
|
||||
'settings/test-resize-content-complete.js',
|
||||
|
@ -9,7 +9,7 @@ casper.test.begin('PNG export test', 13, function(test) {
|
||||
|
||||
test.assert(!isDrawerExpanded(), 'settings drawer is closed');
|
||||
|
||||
// Setup test Piskel
|
||||
// Setup test Piskel
|
||||
setPiskelFromGrid('['+
|
||||
'[B, T],' +
|
||||
'[T, B],' +
|
||||
|
105
test/casperjs/integration/settings/test-import-image-empty.js
Normal file
105
test/casperjs/integration/settings/test-import-image-empty.js
Normal file
@ -0,0 +1,105 @@
|
||||
/* globals casper, setPiskelFromGrid, isDrawerExpanded, getValue, isChecked,
|
||||
evalLine, waitForEvent, replaceFunction, setPiskelFromImageSrc */
|
||||
|
||||
casper.test.begin('Image import test with an empty current sprite', 16, function(test) {
|
||||
test.timeout = test.fail.bind(test, ['Test timed out']);
|
||||
|
||||
// Helper to retrieve the text content of the provided selector
|
||||
// in the current wizard step.
|
||||
var getTextContent = function (selector) {
|
||||
return evalLine('document.querySelector(".current-step ' + selector +'").textContent');
|
||||
};
|
||||
|
||||
function onTestStart() {
|
||||
test.assertExists('#drawing-canvas-container canvas', 'Piskel ready, test starting');
|
||||
|
||||
test.assert(!isDrawerExpanded(), 'settings drawer is closed');
|
||||
|
||||
waitForEvent('PISKEL_RESET', onPiskelReset, test.timeout);
|
||||
|
||||
// 1x1 transparent pixel
|
||||
setPiskelFromGrid('['+
|
||||
'[T]' +
|
||||
']');
|
||||
}
|
||||
|
||||
function onPiskelReset() {
|
||||
// Check the expected piskel was correctly loaded.
|
||||
test.assertEquals(evalLine('pskl.app.currentColorsService.getCurrentColors().length'), 0, 'Has no color');
|
||||
test.assertEquals(evalLine('pskl.app.piskelController.isEmpty()'), true, 'Current piskel is considered as empty');
|
||||
test.assertEquals(evalLine('pskl.app.piskelController.getPiskel().getWidth()'), 1, 'Piskel width is 1 pixel');
|
||||
test.assertEquals(evalLine('pskl.app.piskelController.getPiskel().getHeight()'), 1, 'Piskel height is 1 pixel');
|
||||
|
||||
// Open export panel.
|
||||
test.assertDoesntExist('.settings-section-import', 'Check if import panel is closed');
|
||||
casper.click('[data-setting="import"]');
|
||||
|
||||
casper.waitForSelector('.settings-section-import', onImportPanelReady, test.timeout, 10000);
|
||||
}
|
||||
|
||||
function onImportPanelReady() {
|
||||
test.assert(isDrawerExpanded(), 'settings drawer is expanded');
|
||||
test.assertExists('.settings-section-import', 'Check if import panel is opened');
|
||||
|
||||
replaceFunction(test,
|
||||
'pskl.utils.FileUtils.readImageFile',
|
||||
function (file, callback) {
|
||||
var image = new Image();
|
||||
image.onload = callback.bind(null, image);
|
||||
// Source for a simple base64 encoded PNG, 2x2, with 2 different colors and 2 transparent pixels.
|
||||
image.src = [
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0',
|
||||
'kAAAAF0lEQVQYVwXBAQEAAACCIPw/uiAYi406Ig4EARK1RMAAAAAASUVORK5CYII='
|
||||
].join('');
|
||||
}
|
||||
);
|
||||
|
||||
casper.echo('Clicking on Browse Images button');
|
||||
test.assertExists('.file-input-button', 'The import image button is available');
|
||||
|
||||
// We can't really control the file picker from the test so we directly fire the event
|
||||
casper.evaluate(
|
||||
'function () {\
|
||||
$.publish(Events.DIALOG_SHOW, {\
|
||||
dialogId : "import",\
|
||||
initArgs : {\
|
||||
rawFiles: [{type: "image", name: "test-name.png"}]\
|
||||
}\
|
||||
});\
|
||||
}'
|
||||
);
|
||||
|
||||
casper.echo('Wait for .import-image-container');
|
||||
casper.waitForSelector('.current-step.import-image-container', onImageImportReady, test.timeout, 10000);
|
||||
}
|
||||
|
||||
function onImageImportReady() {
|
||||
casper.echo('Found import-image-container');
|
||||
|
||||
// Click on export again to close the settings drawer.
|
||||
test.assertEquals(getTextContent('.import-next-button'), 'import',
|
||||
'Next button found, with text content \'import\'');
|
||||
casper.click('.current-step .import-next-button');
|
||||
|
||||
// Since the current sprite is empty clicking on the button should directly finalize the import.
|
||||
casper.waitForSelector('#dialog-container-wrapper:not(.show)', onPopupClosed, test.timeout, 10000);
|
||||
}
|
||||
|
||||
function onPopupClosed() {
|
||||
casper.echo('Import popup is closed, check the imported piskel content');
|
||||
test.assertEquals(evalLine('pskl.app.piskelController.getPiskel().getWidth()'), 2, 'Piskel width is 2 pixels');
|
||||
test.assertEquals(evalLine('pskl.app.piskelController.getPiskel().getHeight()'), 2, 'Piskel height is 2 pixels');
|
||||
test.assertEquals(evalLine('pskl.app.piskelController.getLayers().length'), 1, 'Piskel has 1 layer');
|
||||
test.assertEquals(evalLine('pskl.app.piskelController.getFrameCount()'), 1, 'Piskel has 1 frame');
|
||||
}
|
||||
|
||||
casper
|
||||
.start(casper.cli.get('baseUrl')+"/?debug&integration-test")
|
||||
.then(function () {
|
||||
casper.echo("URL loaded");
|
||||
casper.waitForSelector('#drawing-canvas-container canvas', onTestStart, test.timeout, 20000);
|
||||
})
|
||||
.run(function () {
|
||||
test.done();
|
||||
});
|
||||
});
|
@ -1,7 +1,7 @@
|
||||
/* globals casper, setPiskelFromGrid, isDrawerExpanded, getValue, isChecked,
|
||||
evalLine, waitForEvent, replaceFunction, setPiskelFromImageSrc */
|
||||
|
||||
casper.test.begin('Double Image import test', 25, function(test) {
|
||||
casper.test.begin('Double Image import test', 26, function(test) {
|
||||
test.timeout = test.fail.bind(test, ['Test timed out']);
|
||||
|
||||
// Helper to retrieve the text content of the provided selector
|
||||
@ -54,18 +54,21 @@ casper.test.begin('Double Image import test', 25, function(test) {
|
||||
|
||||
test.assert(!isDrawerExpanded(), 'settings drawer is closed');
|
||||
|
||||
waitForEvent('PISKEL_RESET', onPiskelReset, test.timeout);
|
||||
|
||||
// 1x1 black pixel
|
||||
var src = [
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcS',
|
||||
'JAAAADUlEQVQYV2NgYGD4DwABBAEAcCBlCwAAAABJRU5ErkJggg=='
|
||||
].join('');
|
||||
setPiskelFromImageSrc(src);
|
||||
|
||||
// For this test the most important is that the color service picked up the color from the sprite
|
||||
// since it drives which flow will be used for the import.
|
||||
casper.waitForSelector('.palettes-list-color:nth-child(1)', onPiskelPaletteUpdated, test.timeout, 10000);
|
||||
}
|
||||
|
||||
function onPiskelReset() {
|
||||
function onPiskelPaletteUpdated() {
|
||||
// Check the expected piskel was correctly loaded.
|
||||
test.assertEquals(evalLine('pskl.app.currentColorsService.getCurrentColors().length'), 1, 'Has 1 color');
|
||||
test.assertEquals(evalLine('pskl.app.piskelController.getPiskel().getWidth()'), 1, 'Piskel width is 1 pixel');
|
||||
test.assertEquals(evalLine('pskl.app.piskelController.getPiskel().getHeight()'), 1, 'Piskel height is 1 pixel');
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* globals casper, setPiskelFromGrid, isDrawerExpanded, getValue, isChecked,
|
||||
evalLine, waitForEvent, replaceFunction, setPiskelFromImageSrc */
|
||||
|
||||
casper.test.begin('Simple Image import test', 26, function(test) {
|
||||
casper.test.begin('Simple Image import test', 27, function(test) {
|
||||
test.timeout = test.fail.bind(test, ['Test timed out']);
|
||||
|
||||
// Helper to retrieve the text content of the provided selector
|
||||
@ -30,18 +30,21 @@ casper.test.begin('Simple Image import test', 26, function(test) {
|
||||
|
||||
test.assert(!isDrawerExpanded(), 'settings drawer is closed');
|
||||
|
||||
waitForEvent('PISKEL_RESET', onPiskelReset, test.timeout);
|
||||
|
||||
// 1x1 black pixel
|
||||
var src = [
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcS',
|
||||
'JAAAADUlEQVQYV2NgYGD4DwABBAEAcCBlCwAAAABJRU5ErkJggg=='
|
||||
].join('');
|
||||
setPiskelFromImageSrc(src);
|
||||
|
||||
// For this test the most important is that the color service picked up the color from the sprite
|
||||
// since it drives which flow will be used for the import.
|
||||
casper.waitForSelector('.palettes-list-color:nth-child(1)', onPiskelPaletteUpdated, test.timeout, 10000);
|
||||
}
|
||||
|
||||
function onPiskelReset() {
|
||||
function onPiskelPaletteUpdated() {
|
||||
// Check the expected piskel was correctly loaded.
|
||||
test.assertEquals(evalLine('pskl.app.currentColorsService.getCurrentColors().length'), 1, 'Has 1 color');
|
||||
test.assertEquals(evalLine('pskl.app.piskelController.getPiskel().getWidth()'), 1, 'Piskel width is 1 pixel');
|
||||
test.assertEquals(evalLine('pskl.app.piskelController.getPiskel().getHeight()'), 1, 'Piskel height is 1 pixel');
|
||||
|
||||
|
@ -8,10 +8,11 @@
|
||||
"layers.duplicate.json",
|
||||
"layers.fun.json",
|
||||
"layers.merge.json",
|
||||
"layers.top.bottom",
|
||||
"layers.top.bottom.json",
|
||||
"lighten.darken.json",
|
||||
"move.json",
|
||||
"move-alllayers-allframes.json",
|
||||
"pen.mirror.pensize.json",
|
||||
"pen.secondary.color.json",
|
||||
"selection.rectangular.json",
|
||||
"squares.circles.json",
|
||||
|
@ -10,6 +10,7 @@
|
||||
"layers.top.bottom.json",
|
||||
"move.json",
|
||||
"move-alllayers-allframes.json",
|
||||
"pen.mirror.pensize.json",
|
||||
"pen.secondary.color.json",
|
||||
"selection.rectangular.json",
|
||||
"squares.circles.json",
|
||||
|
125
test/drawing/tests/pen.mirror.pensize.json
Normal file
125
test/drawing/tests/pen.mirror.pensize.json
Normal file
@ -0,0 +1,125 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"type": "pensize-event",
|
||||
"penSize": 2
|
||||
},
|
||||
{
|
||||
"type": "tool-event",
|
||||
"toolId": "tool-vertical-mirror-pen"
|
||||
},
|
||||
{
|
||||
"event": {
|
||||
"type": "mousedown",
|
||||
"button": 0,
|
||||
"shiftKey": false,
|
||||
"altKey": false,
|
||||
"ctrlKey": false
|
||||
},
|
||||
"coords": {
|
||||
"x": 1,
|
||||
"y": 1
|
||||
},
|
||||
"type": "mouse-event"
|
||||
},
|
||||
{
|
||||
"event": {
|
||||
"type": "mousemove",
|
||||
"button": 0,
|
||||
"shiftKey": false,
|
||||
"altKey": false,
|
||||
"ctrlKey": false
|
||||
},
|
||||
"coords": {
|
||||
"x": 1,
|
||||
"y": 1
|
||||
},
|
||||
"type": "mouse-event"
|
||||
},
|
||||
{
|
||||
"event": {
|
||||
"type": "mousemove",
|
||||
"button": 0,
|
||||
"shiftKey": false,
|
||||
"altKey": false,
|
||||
"ctrlKey": false
|
||||
},
|
||||
"coords": {
|
||||
"x": 1,
|
||||
"y": 2
|
||||
},
|
||||
"type": "mouse-event"
|
||||
},
|
||||
{
|
||||
"event": {
|
||||
"type": "mousemove",
|
||||
"button": 0,
|
||||
"shiftKey": false,
|
||||
"altKey": false,
|
||||
"ctrlKey": false
|
||||
},
|
||||
"coords": {
|
||||
"x": 1,
|
||||
"y": 3
|
||||
},
|
||||
"type": "mouse-event"
|
||||
},
|
||||
{
|
||||
"event": {
|
||||
"type": "mousemove",
|
||||
"button": 0,
|
||||
"shiftKey": false,
|
||||
"altKey": false,
|
||||
"ctrlKey": false
|
||||
},
|
||||
"coords": {
|
||||
"x": 1,
|
||||
"y": 4
|
||||
},
|
||||
"type": "mouse-event"
|
||||
},
|
||||
{
|
||||
"event": {
|
||||
"type": "mousemove",
|
||||
"button": 0,
|
||||
"shiftKey": false,
|
||||
"altKey": false,
|
||||
"ctrlKey": false
|
||||
},
|
||||
"coords": {
|
||||
"x": 1,
|
||||
"y": 5
|
||||
},
|
||||
"type": "mouse-event"
|
||||
},
|
||||
{
|
||||
"event": {
|
||||
"type": "mouseup",
|
||||
"button": 0,
|
||||
"shiftKey": false,
|
||||
"altKey": false,
|
||||
"ctrlKey": false
|
||||
},
|
||||
"coords": {
|
||||
"x": 1,
|
||||
"y": 5
|
||||
},
|
||||
"type": "mouse-event"
|
||||
},
|
||||
{
|
||||
"type": "pensize-event",
|
||||
"penSize": 1
|
||||
}
|
||||
],
|
||||
"initialState": {
|
||||
"size": {
|
||||
"width": 6,
|
||||
"height": 6
|
||||
},
|
||||
"primaryColor": "#000000",
|
||||
"secondaryColor": "rgba(0, 0, 0, 0)",
|
||||
"selectedTool": "tool-pen",
|
||||
"penSize": 1
|
||||
},
|
||||
"png": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAGElEQVQYV2NkYGD4zwABjFAazAdxBkwCAIsLDAFt5z4tAAAAAElFTkSuQmCC"
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
237
test/js/database/BackupDatabaseTest.js
Normal file
237
test/js/database/BackupDatabaseTest.js
Normal file
@ -0,0 +1,237 @@
|
||||
describe('BackupDatabase test', function () {
|
||||
|
||||
// Test object.
|
||||
var backupDatabase;
|
||||
|
||||
var _toSnapshot = function (session_id, name, description, date, serialized) {
|
||||
return {
|
||||
session_id: session_id,
|
||||
name: name,
|
||||
description: description,
|
||||
date: date,
|
||||
serialized: serialized
|
||||
};
|
||||
};
|
||||
|
||||
var _checkSnapshot = function (actual, expected) {
|
||||
expect(actual.session_id).toBe(expected.session_id);
|
||||
expect(actual.name).toBe(expected.name);
|
||||
expect(actual.description).toBe(expected.description);
|
||||
expect(actual.date).toBe(expected.date);
|
||||
expect(actual.serialized).toBe(expected.serialized);
|
||||
};
|
||||
|
||||
var _addSnapshots = function (snapshots) {
|
||||
var _add = function (index) {
|
||||
return backupDatabase.createSnapshot(snapshots[index]).then(function () {
|
||||
if (snapshots[index + 1]) {
|
||||
return _add(index + 1);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
return _add(0);
|
||||
};
|
||||
|
||||
beforeEach(function (done) {
|
||||
// Drop the database before each test.
|
||||
var dbName = pskl.database.BackupDatabase.DB_NAME;
|
||||
var req = window.indexedDB.deleteDatabase(dbName);
|
||||
req.onsuccess = done;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
// Close the database if it was still open.
|
||||
if (backupDatabase && backupDatabase.db) {
|
||||
backupDatabase.db.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('initializes the DB and returns a promise', function (done) {
|
||||
backupDatabase = new pskl.database.BackupDatabase();
|
||||
backupDatabase.init().then(done);
|
||||
});
|
||||
|
||||
it('can add snapshots and retrieve them', function (done) {
|
||||
var snapshot = _toSnapshot('session_1', 'name', 'desc', 0, 'serialized');
|
||||
|
||||
backupDatabase = new pskl.database.BackupDatabase();
|
||||
backupDatabase.init()
|
||||
.then(function (db) {
|
||||
// Create snapshot in backup database
|
||||
return backupDatabase.createSnapshot(snapshot);
|
||||
}).then(function () {
|
||||
// Get snapshots for session_1 in backup database
|
||||
return backupDatabase.getSnapshotsBySessionId('session_1');
|
||||
}).then(function (snapshots) {
|
||||
expect(snapshots.length).toBe(1);
|
||||
_checkSnapshot(snapshots[0], snapshot);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can update snapshots and retrieve them', function (done) {
|
||||
var snapshot = _toSnapshot('session_1', 'name', 'desc', 0, 'serialized');
|
||||
var updated = _toSnapshot('session_1', 'name_updated', 'desc_updated', 10, 'serialized_updated');
|
||||
|
||||
backupDatabase = new pskl.database.BackupDatabase();
|
||||
backupDatabase.init()
|
||||
.then(function () {
|
||||
// Create snapshot in backup database
|
||||
return backupDatabase.createSnapshot(snapshot);
|
||||
}).then(function () {
|
||||
// Retrieve snapshots to get the inserted snapshot id
|
||||
return backupDatabase.getSnapshotsBySessionId('session_1');
|
||||
}).then(function (snapshots) {
|
||||
// Update snapshot in backup database
|
||||
updated.id = snapshots[0].id;
|
||||
return backupDatabase.updateSnapshot(updated);
|
||||
}).then(function () {
|
||||
// Get snapshots for session_1 in backup database
|
||||
return backupDatabase.getSnapshotsBySessionId('session_1');
|
||||
}).then(function (snapshots) {
|
||||
expect(snapshots.length).toBe(1);
|
||||
_checkSnapshot(snapshots[0], updated);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can delete snapshots', function (done) {
|
||||
var testSnapshots = [
|
||||
_toSnapshot('session_1', 'name1', 'desc1', 0, 'serialized1'),
|
||||
_toSnapshot('session_1', 'name2', 'desc2', 0, 'serialized2'),
|
||||
_toSnapshot('session_2', 'name3', 'desc3', 0, 'serialized3')
|
||||
];
|
||||
|
||||
backupDatabase = new pskl.database.BackupDatabase();
|
||||
backupDatabase.init()
|
||||
.then(function () {
|
||||
return _addSnapshots(testSnapshots);
|
||||
}).then(function () {
|
||||
// Retrieve snapshots to get the inserted snapshot id
|
||||
return backupDatabase.getSnapshotsBySessionId('session_1');
|
||||
}).then(function (snapshots) {
|
||||
expect(snapshots.length).toBe(2);
|
||||
// Delete snapshot with 'name1' from backup database
|
||||
var snapshot = snapshots.filter(function (s) { return s.name === 'name1' })[0];
|
||||
return backupDatabase.deleteSnapshot(snapshot);
|
||||
}).then(function () {
|
||||
// Get snapshots for session_1 in backup database
|
||||
return backupDatabase.getSnapshotsBySessionId('session_1');
|
||||
}).then(function (snapshots) {
|
||||
expect(snapshots.length).toBe(1);
|
||||
_checkSnapshot(snapshots[0], testSnapshots[1]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an empty array when calling getSnapshots for an empty session', function (done) {
|
||||
var testSnapshots = [
|
||||
_toSnapshot('session_1', 'name1', 'desc1', 0, 'serialized1'),
|
||||
_toSnapshot('session_1', 'name2', 'desc2', 0, 'serialized2'),
|
||||
_toSnapshot('session_2', 'name3', 'desc3', 0, 'serialized3')
|
||||
];
|
||||
|
||||
backupDatabase = new pskl.database.BackupDatabase();
|
||||
backupDatabase.init()
|
||||
.then(function () {
|
||||
return _addSnapshots(testSnapshots);
|
||||
}).then(function () {
|
||||
// Retrieve snapshots for a session that doesn't exist
|
||||
return backupDatabase.getSnapshotsBySessionId('session_3');
|
||||
}).then(function (snapshots) {
|
||||
expect(snapshots.length).toBe(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can delete all snapshots for a session', function (done) {
|
||||
var testSnapshots = [
|
||||
_toSnapshot('session_1', 'name1', 'desc1', 0, 'serialized1'),
|
||||
_toSnapshot('session_1', 'name2', 'desc2', 0, 'serialized2'),
|
||||
_toSnapshot('session_2', 'name3', 'desc3', 0, 'serialized3')
|
||||
];
|
||||
|
||||
backupDatabase = new pskl.database.BackupDatabase();
|
||||
backupDatabase.init()
|
||||
.then(function () {
|
||||
return _addSnapshots(testSnapshots);
|
||||
}).then(function () {
|
||||
// Retrieve snapshots to get the inserted snapshot id
|
||||
return backupDatabase.getSnapshotsBySessionId('session_1');
|
||||
}).then(function (snapshots) {
|
||||
// Check that we have 2 snapshots for session_1
|
||||
expect(snapshots.length).toBe(2);
|
||||
// Delete snapshots for session_1
|
||||
return backupDatabase.deleteSnapshotsForSession('session_1');
|
||||
}).then(function () {
|
||||
// Get snapshots for session_1 in backup database
|
||||
return backupDatabase.getSnapshotsBySessionId('session_1');
|
||||
}).then(function (snapshots) {
|
||||
// All snapshots should have been deleted
|
||||
expect(snapshots.length).toBe(0);
|
||||
// Get snapshots for session_2 in backup database
|
||||
return backupDatabase.getSnapshotsBySessionId('session_2');
|
||||
}).then(function (snapshots) {
|
||||
// There should still be the snapshot for session_2
|
||||
expect(snapshots.length).toBe(1);
|
||||
_checkSnapshot(snapshots[0], testSnapshots[2]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('does a noop when calling deleteAllSnapshotsForSession for a missing session', function (done) {
|
||||
backupDatabase = new pskl.database.BackupDatabase();
|
||||
backupDatabase.init()
|
||||
.then(function () {
|
||||
// Delete snapshot with 'name1' from backup database
|
||||
return backupDatabase.deleteSnapshotsForSession('session_1');
|
||||
}).then(function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns sessions array when calling getSessions', function (done) {
|
||||
var testSnapshots = [
|
||||
_toSnapshot('session_1', 'name1', 'desc1', 5, 'serialized1'),
|
||||
_toSnapshot('session_1', 'name2', 'desc2', 10, 'serialized2'),
|
||||
_toSnapshot('session_2', 'name3', 'desc3', 15, 'serialized3')
|
||||
];
|
||||
|
||||
backupDatabase = new pskl.database.BackupDatabase();
|
||||
backupDatabase.init()
|
||||
.then(function () {
|
||||
return _addSnapshots(testSnapshots);
|
||||
}).then(function () {
|
||||
return backupDatabase.getSessions();
|
||||
}).then(function (sessions) {
|
||||
// Check that we have 2 sessions
|
||||
expect(sessions.length).toBe(2);
|
||||
|
||||
// Get the actual sessions
|
||||
var session1 = sessions.filter(function (s) { return s.id === 'session_1'; })[0];
|
||||
var session2 = sessions.filter(function (s) { return s.id === 'session_2'; })[0];
|
||||
|
||||
// Check the start/end date were computed properly
|
||||
expect(session1.startDate).toBe(5);
|
||||
expect(session1.endDate).toBe(10);
|
||||
expect(session2.startDate).toBe(15);
|
||||
expect(session2.endDate).toBe(15);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an empty array when calling getSessions on an empty DB', function (done) {
|
||||
backupDatabase = new pskl.database.BackupDatabase();
|
||||
backupDatabase.init()
|
||||
.then(function () {
|
||||
return backupDatabase.getSessions();
|
||||
}).then(function (sessions) {
|
||||
expect(sessions.length).toBe(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
158
test/js/database/PiskelDatabaseTest.js
Normal file
158
test/js/database/PiskelDatabaseTest.js
Normal file
@ -0,0 +1,158 @@
|
||||
describe('PiskelDatabase test', function () {
|
||||
|
||||
// Test object.
|
||||
var piskelDatabase;
|
||||
|
||||
var _toSnapshot = function (session_id, name, description, date, serialized) {
|
||||
return {
|
||||
session_id: session_id,
|
||||
name: name,
|
||||
description: description,
|
||||
date: date,
|
||||
serialized: serialized
|
||||
};
|
||||
};
|
||||
|
||||
var _checkPiskel = function (actual, expected) {
|
||||
expect(actual.name).toBe(expected[0]);
|
||||
expect(actual.description).toBe(expected[1]);
|
||||
expect(actual.date).toBe(expected[2]);
|
||||
expect(actual.serialized).toBe(expected[3]);
|
||||
};
|
||||
|
||||
var _addPiskels = function (piskels) {
|
||||
var _add = function (index) {
|
||||
var piskelData = piskels[index];
|
||||
return piskelDatabase.create.apply(piskelDatabase, piskelData)
|
||||
.then(function () {
|
||||
if (piskels[index + 1]) {
|
||||
return _add(index + 1);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return _add(0);
|
||||
};
|
||||
|
||||
beforeEach(function (done) {
|
||||
// Mock the migration script.
|
||||
spyOn(pskl.database.migrate.MigrateLocalStorageToIndexedDb, "migrate");
|
||||
|
||||
// Drop the database before each test.
|
||||
var dbName = pskl.database.PiskelDatabase.DB_NAME;
|
||||
var req = window.indexedDB.deleteDatabase(dbName);
|
||||
req.onsuccess = done;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
// Close the database if it was still open.
|
||||
if (piskelDatabase && piskelDatabase.db) {
|
||||
piskelDatabase.db.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('initializes the DB and returns a promise', function (done) {
|
||||
piskelDatabase = new pskl.database.PiskelDatabase();
|
||||
piskelDatabase.init().then(done);
|
||||
});
|
||||
|
||||
it('can add a piskel and retrieve it', function (done) {
|
||||
piskelDatabase = new pskl.database.PiskelDatabase();
|
||||
piskelDatabase.init()
|
||||
.then(function (db) {
|
||||
return piskelDatabase.create('name', 'desc', 0, 'serialized');
|
||||
}).then(function () {
|
||||
return piskelDatabase.get('name');
|
||||
}).then(function (piskel) {
|
||||
expect(piskel.name).toBe('name');
|
||||
expect(piskel.description).toBe('desc');
|
||||
expect(piskel.date).toBe(0);
|
||||
expect(piskel.serialized).toBe('serialized');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can delete piskel by name', function (done) {
|
||||
var piskels = [
|
||||
['n1', 'd1', 10, 's1'],
|
||||
['n2', 'd2', 20, 's2'],
|
||||
['n3', 'd3', 30, 's3'],
|
||||
];
|
||||
|
||||
piskelDatabase = new pskl.database.PiskelDatabase();
|
||||
piskelDatabase.init()
|
||||
.then(function (db) {
|
||||
return _addPiskels(piskels);
|
||||
}).then(function () {
|
||||
return piskelDatabase.delete('n2');
|
||||
}).then(function () {
|
||||
return piskelDatabase.get('n1');
|
||||
}).then(function (piskelData) {
|
||||
_checkPiskel(piskelData, piskels[0]);
|
||||
return piskelDatabase.get('n3');
|
||||
}).then(function (piskelData) {
|
||||
_checkPiskel(piskelData, piskels[2]);
|
||||
return piskelDatabase.get('n2');
|
||||
}).then(function (piskelData) {
|
||||
expect(piskelData).toBe(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can list piskels', function (done) {
|
||||
var piskels = [
|
||||
['n1', 'd1', 10, 's1'],
|
||||
['n2', 'd2', 20, 's2'],
|
||||
['n3', 'd3', 30, 's3'],
|
||||
];
|
||||
|
||||
piskelDatabase = new pskl.database.PiskelDatabase();
|
||||
piskelDatabase.init()
|
||||
.then(function (db) {
|
||||
return _addPiskels(piskels);
|
||||
}).then(function () {
|
||||
return piskelDatabase.list();
|
||||
}).then(function (piskels) {
|
||||
expect(piskels.length).toBe(3);
|
||||
piskels.forEach(function (piskelData) {
|
||||
expect(piskelData.name).toMatch(/n[1-3]/);
|
||||
expect(piskelData.description).toMatch(/d[1-3]/);
|
||||
expect(piskelData.date).toBeDefined();
|
||||
expect(piskelData.serialized).not.toBeDefined();
|
||||
})
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can update piskel with same name', function (done) {
|
||||
var piskels = [
|
||||
['n1', 'd1', 10, 's1'],
|
||||
['n2', 'd2', 20, 's2'],
|
||||
['n3', 'd3', 30, 's3'],
|
||||
];
|
||||
|
||||
piskelDatabase = new pskl.database.PiskelDatabase();
|
||||
piskelDatabase.init()
|
||||
.then(function (db) {
|
||||
return _addPiskels(piskels);
|
||||
}).then(function () {
|
||||
return piskelDatabase.update('n2', 'd2_updated', 40, 's2_updated');
|
||||
}).then(function (piskels) {
|
||||
return piskelDatabase.list();
|
||||
}).then(function (piskels) {
|
||||
expect(piskels.length).toBe(3);
|
||||
var p2 = piskels.filter(function (p) { return p.name === 'n2'})[0];
|
||||
expect(p2.name).toBe('n2');
|
||||
expect(p2.description).toBe('d2_updated');
|
||||
expect(p2.date).toBe(40);
|
||||
|
||||
return piskelDatabase.get('n2');
|
||||
}).then(function (piskel) {
|
||||
_checkPiskel(piskel, ['n2', 'd2_updated', 40, 's2_updated']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@ -29,6 +29,19 @@ describe("SelectionManager suite", function() {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @Mock
|
||||
*/
|
||||
var createMockCopyEvent = function () {
|
||||
return {
|
||||
clipboardData: {
|
||||
items: [],
|
||||
setData: function () {}
|
||||
},
|
||||
preventDefault: function () {}
|
||||
};
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
currentFrame = pskl.model.Frame.fromPixelGrid([
|
||||
[B, R, T],
|
||||
@ -52,7 +65,7 @@ describe("SelectionManager suite", function() {
|
||||
selectMiddleLine();
|
||||
|
||||
console.log('[SelectionManager] ... copy');
|
||||
selectionManager.copy();
|
||||
selectionManager.copy({ type: Events.CLIPBOARD_COPY }, createMockCopyEvent());
|
||||
|
||||
console.log('[SelectionManager] ... check selection content after copy contains correct colors');
|
||||
expect(selection.pixels.length).toBe(3); // or not to be ... lalalala ... french-only joke \o/
|
||||
@ -69,7 +82,7 @@ describe("SelectionManager suite", function() {
|
||||
checkContainsPixel(selection.pixels, 2, 2, R);
|
||||
|
||||
console.log('[SelectionManager] ... paste');
|
||||
selectionManager.paste();
|
||||
selectionManager.paste({ type: Events.CLIPBOARD_PASTE }, createMockCopyEvent());
|
||||
|
||||
console.log('[SelectionManager] ... check last line is identical to middle line after paste');
|
||||
frameEqualsGrid(currentFrame, [
|
||||
@ -87,7 +100,7 @@ describe("SelectionManager suite", function() {
|
||||
selectMiddleLine();
|
||||
|
||||
console.log('[SelectionManager] ... cut');
|
||||
selectionManager.cut();
|
||||
selectionManager.copy({ type: Events.CLIPBOARD_CUT }, createMockCopyEvent());
|
||||
|
||||
console.log('[SelectionManager] ... check middle line was cut in the source frame');
|
||||
frameEqualsGrid(currentFrame, [
|
||||
@ -97,7 +110,7 @@ describe("SelectionManager suite", function() {
|
||||
]);
|
||||
|
||||
console.log('[SelectionManager] ... paste');
|
||||
selectionManager.paste();
|
||||
selectionManager.paste({ type: Events.CLIPBOARD_PASTE }, createMockCopyEvent());
|
||||
|
||||
console.log('[SelectionManager] ... check middle line was restored by paste');
|
||||
frameEqualsGrid(currentFrame, [
|
||||
@ -118,7 +131,7 @@ describe("SelectionManager suite", function() {
|
||||
selection.move(2, 0);
|
||||
|
||||
console.log('[SelectionManager] ... copy out of bounds');
|
||||
selectionManager.copy();
|
||||
selectionManager.copy({ type: Events.CLIPBOARD_COPY }, createMockCopyEvent());
|
||||
console.log('[SelectionManager] ... check out of bound pixels were replaced by transparent pixels');
|
||||
checkContainsPixel(selection.pixels, 1, 2, R);
|
||||
checkContainsPixel(selection.pixels, 1, 3, T);
|
||||
@ -133,7 +146,7 @@ describe("SelectionManager suite", function() {
|
||||
checkContainsPixel(selection.pixels, 1, 3, T);
|
||||
|
||||
console.log('[SelectionManager] ... paste out of bounds');
|
||||
selectionManager.paste();
|
||||
selectionManager.paste({ type: Events.CLIPBOARD_PASTE }, createMockCopyEvent());
|
||||
|
||||
console.log('[SelectionManager] ... check pixel at (1,1) is red after paste');
|
||||
frameEqualsGrid(currentFrame, [
|
||||
@ -154,7 +167,7 @@ describe("SelectionManager suite", function() {
|
||||
selection.move(2, 0);
|
||||
|
||||
console.log('[SelectionManager] ... cut out of bounds');
|
||||
selectionManager.cut();
|
||||
selectionManager.copy({ type: Events.CLIPBOARD_CUT }, createMockCopyEvent());
|
||||
console.log('[SelectionManager] ... check last pixel of midle line was cut in the source frame');
|
||||
frameEqualsGrid(currentFrame, [
|
||||
[B, R, T],
|
||||
@ -165,7 +178,7 @@ describe("SelectionManager suite", function() {
|
||||
selection.move(-1, 0);
|
||||
|
||||
console.log('[SelectionManager] ... paste out of bounds');
|
||||
selectionManager.paste();
|
||||
selectionManager.paste({ type: Events.CLIPBOARD_PASTE }, createMockCopyEvent());
|
||||
|
||||
console.log('[SelectionManager] ... check middle line final state');
|
||||
frameEqualsGrid(currentFrame, [
|
||||
|
286
test/js/service/BackupServiceTest.js
Normal file
286
test/js/service/BackupServiceTest.js
Normal file
@ -0,0 +1,286 @@
|
||||
describe('BackupService test', function () {
|
||||
// Some helper const.
|
||||
var ONE_SECOND = 1000;
|
||||
var ONE_MINUTE = 60 * ONE_SECOND;
|
||||
|
||||
var mockBackupDatabase;
|
||||
var mockPiskel;
|
||||
var mockPiskelController;
|
||||
|
||||
// Globals used in stubs
|
||||
var stubValues = {
|
||||
snapshotDate: null,
|
||||
serializedPiskel: null
|
||||
};
|
||||
|
||||
// Main test object.
|
||||
var backupService;
|
||||
|
||||
beforeEach(function () {
|
||||
// Create mocks.
|
||||
mockBackupDatabase = {
|
||||
// Test property
|
||||
_sessions: {},
|
||||
init: function () {},
|
||||
getSnapshotsBySessionId: function (sessionId) {
|
||||
// Default implementation that looks up in _sessions or returns an
|
||||
// empty array.
|
||||
return Promise.resolve(this._sessions[sessionId] || []);
|
||||
},
|
||||
updateSnapshot: function () { return Promise.resolve(); },
|
||||
createSnapshot: function () { return Promise.resolve(); },
|
||||
deleteSnapshot: function () { return Promise.resolve(); },
|
||||
getSessions: function () { return Promise.resolve([]); },
|
||||
deleteSnapshotsForSession: function () { return Promise.resolve(); },
|
||||
findLastSnapshot: function () { return Promise.resolve(null); }
|
||||
};
|
||||
|
||||
mockPiskel = {
|
||||
_descriptor: {},
|
||||
_hash: null,
|
||||
getDescriptor: function () { return this._descriptor; },
|
||||
getHash: function () { return this._hash; },
|
||||
getWidth: function () { return 32; },
|
||||
getHeight: function () { return 32; },
|
||||
getFrameCount: function () { return 1; },
|
||||
getFPS: function () { return 12; },
|
||||
};
|
||||
|
||||
mockPiskelController = {
|
||||
getPiskel: function () { return mockPiskel; },
|
||||
setPiskel: function () {}
|
||||
};
|
||||
|
||||
spyOn(pskl.utils.serialization.Serializer, 'serialize').and.callFake(function () {
|
||||
return stubValues.serializedPiskel;
|
||||
});
|
||||
|
||||
// Create test backup service with mocks.
|
||||
backupService = new pskl.service.BackupService(
|
||||
mockPiskelController,
|
||||
mockBackupDatabase
|
||||
);
|
||||
// Override the currentDate_ internal helper in order to set
|
||||
// custom snapshot dates.
|
||||
backupService.currentDate_ = function () {
|
||||
return snapshotDate;
|
||||
}
|
||||
});
|
||||
|
||||
var createSnapshotObject = function (session_id, name, description, date, serialized) {
|
||||
return {
|
||||
session_id: session_id,
|
||||
name: name,
|
||||
description: description,
|
||||
date: date,
|
||||
serialized: serialized
|
||||
};
|
||||
};
|
||||
|
||||
var preparePiskelMocks = function (session_id, name, description, hash, serialized) {
|
||||
// Update the session id.
|
||||
pskl.app.sessionId = session_id;
|
||||
|
||||
// Update the piskel mock.
|
||||
mockPiskel._descriptor.name = name;
|
||||
mockPiskel._descriptor.description = description;
|
||||
mockPiskel._hash = hash;
|
||||
stubValues.serializedPiskel = serialized;
|
||||
};
|
||||
|
||||
it('calls create to backup', function (done) {
|
||||
preparePiskelMocks(1, 'piskel_name', 'piskel_desc', 'piskel_hash', 'serialized');
|
||||
|
||||
// Set snashot date.
|
||||
snapshotDate = 5;
|
||||
|
||||
// No snapshots currently saved.
|
||||
spyOn(mockBackupDatabase, 'createSnapshot').and.callThrough();
|
||||
|
||||
backupService.backup().then(function () {
|
||||
expect(mockBackupDatabase.createSnapshot).toHaveBeenCalled();
|
||||
var snapshot = mockBackupDatabase.createSnapshot.calls.mostRecent().args[0]
|
||||
expect(snapshot.session_id).toEqual(1);
|
||||
expect(snapshot.name).toEqual('piskel_name');
|
||||
expect(snapshot.description).toEqual('piskel_desc');
|
||||
expect(snapshot.date).toEqual(5);
|
||||
expect(snapshot.serialized).toEqual('serialized');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not call update to backup if the hash did not change', function (done) {
|
||||
var session = 1;
|
||||
var date1 = 0;
|
||||
var date2 = ONE_MINUTE;
|
||||
|
||||
var snapshot1 = createSnapshotObject(1, 'piskel_name1', 'piskel_desc1', date1, 'serialized1');
|
||||
preparePiskelMocks(session, 'piskel_name1', 'piskel_desc1', 'hash', 'serialized1');
|
||||
snapshotDate = date1;
|
||||
|
||||
// Prepare spies.
|
||||
spyOn(mockBackupDatabase, 'updateSnapshot').and.callThrough();
|
||||
spyOn(mockBackupDatabase, 'createSnapshot').and.callThrough();
|
||||
|
||||
backupService.backup().then(function () {
|
||||
// The snapshot should have been created using "createSnapshot".
|
||||
expect(mockBackupDatabase.createSnapshot).toHaveBeenCalled();
|
||||
expect(mockBackupDatabase.updateSnapshot.calls.any()).toBe(false);
|
||||
|
||||
// Prepare snapshot1 to be returned in the list of already existing sessions.
|
||||
mockBackupDatabase._sessions[session] = [snapshot1];
|
||||
|
||||
preparePiskelMocks(session, 'piskel_name2', 'piskel_desc2', 'hash', 'serialized2');
|
||||
snapshotDate = date2;
|
||||
|
||||
backupService.backup().then(function () {
|
||||
// Check that createSnapshot was not called again and updateSnapshot either.
|
||||
expect(mockBackupDatabase.createSnapshot.calls.count()).toEqual(1);
|
||||
expect(mockBackupDatabase.updateSnapshot.calls.count()).toEqual(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('calls update to backup if there is an existing & recent snapshot', function (done) {
|
||||
var session = 1;
|
||||
var date1 = 0;
|
||||
var date2 = ONE_MINUTE;
|
||||
|
||||
var snapshot1 = createSnapshotObject(1, 'piskel_name1', 'piskel_desc1', date1, 'serialized1');
|
||||
preparePiskelMocks(session, 'piskel_name1', 'piskel_desc1', 'piskel_hash1', 'serialized1');
|
||||
snapshotDate = date1;
|
||||
|
||||
// Prepare spies.
|
||||
spyOn(mockBackupDatabase, 'updateSnapshot').and.callThrough();
|
||||
spyOn(mockBackupDatabase, 'createSnapshot').and.callThrough();
|
||||
|
||||
backupService.backup().then(function () {
|
||||
// The snapshot should have been created using "createSnapshot".
|
||||
expect(mockBackupDatabase.createSnapshot).toHaveBeenCalled();
|
||||
|
||||
// Prepare snapshot1 to be returned in the list of already existing sessions.
|
||||
mockBackupDatabase._sessions[session] = [snapshot1];
|
||||
|
||||
preparePiskelMocks(session, 'piskel_name2', 'piskel_desc2', 'piskel_hash2', 'serialized2');
|
||||
snapshotDate = date2;
|
||||
|
||||
backupService.backup().then(function () {
|
||||
// Check that createSnapshot was not called again.
|
||||
expect(mockBackupDatabase.createSnapshot.calls.count()).toEqual(1);
|
||||
// Check that updateSnapshot was called with the expected arguments.
|
||||
expect(mockBackupDatabase.updateSnapshot).toHaveBeenCalled();
|
||||
var snapshot = mockBackupDatabase.updateSnapshot.calls.mostRecent().args[0]
|
||||
expect(snapshot.session_id).toEqual(session);
|
||||
expect(snapshot.name).toEqual('piskel_name2');
|
||||
expect(snapshot.description).toEqual('piskel_desc2');
|
||||
expect(snapshot.date).toEqual(date2);
|
||||
expect(snapshot.serialized).toEqual('serialized2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('creates a new snapshot if the time difference is big enough', function (done) {
|
||||
var session = 1;
|
||||
var date1 = 0;
|
||||
var date2 = 6 * ONE_MINUTE;
|
||||
|
||||
var snapshot1 = createSnapshotObject(1, 'piskel_name1', 'piskel_desc1', date1, 'serialized1');
|
||||
preparePiskelMocks(session, 'piskel_name1', 'piskel_desc1', 'piskel_hash1', 'serialized1');
|
||||
snapshotDate = date1;
|
||||
|
||||
// Prepare spies.
|
||||
spyOn(mockBackupDatabase, 'updateSnapshot').and.callThrough();
|
||||
spyOn(mockBackupDatabase, 'createSnapshot').and.callThrough();
|
||||
|
||||
backupService.backup().then(function () {
|
||||
// The snapshot should have been created using "createSnapshot".
|
||||
expect(mockBackupDatabase.createSnapshot).toHaveBeenCalled();
|
||||
|
||||
// Prepare snapshot1 to be returned in the list of already existing sessions.
|
||||
mockBackupDatabase._sessions[session] = [snapshot1];
|
||||
|
||||
preparePiskelMocks(session, 'piskel_name2', 'piskel_desc2', 'piskel_hash2', 'serialized2');
|
||||
snapshotDate = date2;
|
||||
|
||||
backupService.backup().then(function () {
|
||||
// Check that updateSnapshot was not called.
|
||||
expect(mockBackupDatabase.updateSnapshot.calls.count()).toEqual(0);
|
||||
// Check that updateSnapshot was called with the expected arguments.
|
||||
expect(mockBackupDatabase.createSnapshot).toHaveBeenCalled();
|
||||
var snapshot = mockBackupDatabase.createSnapshot.calls.mostRecent().args[0]
|
||||
expect(snapshot.session_id).toEqual(session);
|
||||
expect(snapshot.name).toEqual('piskel_name2');
|
||||
expect(snapshot.description).toEqual('piskel_desc2');
|
||||
expect(snapshot.date).toEqual(date2);
|
||||
expect(snapshot.serialized).toEqual('serialized2');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes old snapshots if there are too many of them', function (done) {
|
||||
var session = 1;
|
||||
var maxPerSession = 12;
|
||||
|
||||
preparePiskelMocks(session, 'piskel_name', 'piskel_desc', 'piskel_hash', 'serialized12');
|
||||
snapshotDate = 12 * 6 * ONE_MINUTE;
|
||||
|
||||
// Prepare spies.
|
||||
spyOn(mockBackupDatabase, 'deleteSnapshot').and.callThrough();
|
||||
spyOn(mockBackupDatabase, 'createSnapshot').and.callThrough();
|
||||
|
||||
// Prepare array of already saved snapshots.
|
||||
mockBackupDatabase._sessions[session] = [];
|
||||
for (var i = maxPerSession - 1 ; i >= 0 ; i--) {
|
||||
mockBackupDatabase._sessions[session].push(
|
||||
createSnapshotObject(session, 'piskel_name', 'piskel_desc', i * 6 * ONE_MINUTE, 'serialized' + i)
|
||||
);
|
||||
}
|
||||
|
||||
backupService.backup().then(function () {
|
||||
expect(mockBackupDatabase.createSnapshot).toHaveBeenCalled();
|
||||
expect(mockBackupDatabase.deleteSnapshot).toHaveBeenCalled();
|
||||
// It will simply attempt to delete the last item from the array of saved sessions
|
||||
var snapshot = mockBackupDatabase.deleteSnapshot.calls.mostRecent().args[0];
|
||||
expect(snapshot.session_id).toEqual(session);
|
||||
expect(snapshot.name).toEqual('piskel_name');
|
||||
expect(snapshot.description).toEqual('piskel_desc');
|
||||
expect(snapshot.date).toEqual(0);
|
||||
expect(snapshot.serialized).toEqual('serialized0');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('deletes a session if there are too many of them', function (done) {
|
||||
var session = 'session10';
|
||||
var maxSessions = 10;
|
||||
|
||||
preparePiskelMocks(session, 'piskel_name', 'piskel_desc', 'piskel_hash', 'serialized12');
|
||||
snapshotDate = 10 * ONE_MINUTE;
|
||||
|
||||
// Prepare array of sessions.
|
||||
var sessions = [];
|
||||
for (var i = 0 ; i < maxSessions + 1 ; i++) {
|
||||
sessions.push({
|
||||
id: 'session' + i,
|
||||
startDate: i * ONE_MINUTE
|
||||
});
|
||||
}
|
||||
|
||||
// Prepare spies.
|
||||
spyOn(mockBackupDatabase, 'getSessions').and.returnValue(Promise.resolve(sessions));
|
||||
spyOn(mockBackupDatabase, 'createSnapshot').and.callThrough();
|
||||
spyOn(mockBackupDatabase, 'deleteSnapshotsForSession').and.callThrough();
|
||||
|
||||
backupService.backup().then(function () {
|
||||
expect(mockBackupDatabase.createSnapshot).toHaveBeenCalled();
|
||||
expect(mockBackupDatabase.deleteSnapshotsForSession).toHaveBeenCalled();
|
||||
// It will simply attempt to delete the last item from the array of saved sessions
|
||||
var sessionId = mockBackupDatabase.deleteSnapshotsForSession.calls.mostRecent().args[0];
|
||||
expect(sessionId).toEqual('session0');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
@ -2,7 +2,7 @@ describe("Palette Service", function() {
|
||||
var paletteService = null;
|
||||
var localStorage = {};
|
||||
|
||||
var localStorageService;
|
||||
var localStorageGlobal;
|
||||
|
||||
|
||||
var addPalette = function (id, name, color) {
|
||||
@ -24,7 +24,7 @@ describe("Palette Service", function() {
|
||||
beforeEach(function() {
|
||||
localStorage = {};
|
||||
|
||||
localStorageService = {
|
||||
localStorageGlobal = {
|
||||
getItem : function (key) {
|
||||
if (localStorage.hasOwnProperty(key)) {
|
||||
return localStorage[key];
|
||||
@ -38,21 +38,21 @@ describe("Palette Service", function() {
|
||||
};
|
||||
|
||||
paletteService = new pskl.service.palette.PaletteService();
|
||||
paletteService.localStorageService = localStorageService;
|
||||
paletteService.localStorageGlobal = localStorageGlobal;
|
||||
});
|
||||
|
||||
it("returns an empty array when no palette is stored", function() {
|
||||
spyOn(localStorageService, 'getItem').and.callThrough();
|
||||
spyOn(localStorageGlobal, 'getItem').and.callThrough();
|
||||
|
||||
var palettes = paletteService.getPalettes();
|
||||
expect(Array.isArray(palettes)).toBe(true);
|
||||
expect(palettes.length).toBe(0);
|
||||
expect(localStorageService.getItem).toHaveBeenCalled();
|
||||
expect(localStorageGlobal.getItem).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("can store a palette", function() {
|
||||
// when
|
||||
spyOn(localStorageService, 'setItem').and.callThrough();
|
||||
spyOn(localStorageGlobal, 'setItem').and.callThrough();
|
||||
|
||||
var paletteId = 'palette-id';
|
||||
var paletteName = 'palette-name';
|
||||
@ -63,7 +63,7 @@ describe("Palette Service", function() {
|
||||
var palettes = paletteService.getPalettes();
|
||||
|
||||
// verify
|
||||
expect(localStorageService.setItem).toHaveBeenCalled();
|
||||
expect(localStorageGlobal.setItem).toHaveBeenCalled();
|
||||
|
||||
expect(Array.isArray(palettes)).toBe(true);
|
||||
expect(palettes.length).toBe(1);
|
||||
|
Reference in New Issue
Block a user