update interface (fixes issue #6)
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
/*global define*/
|
||||
define(
|
||||
[ 'util/object', 'util/addpublishers' ],
|
||||
function ( objectHelper, addPublishers ) {
|
||||
// the controlsmodel stores and manages all
|
||||
// values of the input sliders
|
||||
function ControlsModel ( initialValues ) {
|
||||
if ( ! ( this instanceof ControlsModel ) ) {
|
||||
return new ControlsModel( initialValues );
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var publishers = addPublishers( self, [ 'update' ] );
|
||||
var limits = getLimits( initialValues ) || { };
|
||||
var values = getValues( initialValues ) || { };
|
||||
|
||||
function updateValues ( newValues ) {
|
||||
if ( newValues ) {
|
||||
newValues = objectHelper.getCopy( newValues );
|
||||
|
||||
var referenceValue;
|
||||
var newValue;
|
||||
|
||||
for ( var key in newValues ) {
|
||||
limit = limits[key];
|
||||
newValue = newValues[key];
|
||||
|
||||
if (
|
||||
typeof limit !== 'undefined' &&
|
||||
typeof newValue === 'number' &&
|
||||
! isNaN( newValue ) &&
|
||||
newValue >= limit.min &&
|
||||
newValue <= limit.max &&
|
||||
values[key] !== newValue
|
||||
) {
|
||||
values[key] = newValue;
|
||||
publishers.update.dispatch( key, values[key] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function setValue ( key, newValue ) {
|
||||
if (
|
||||
typeof values[key] === 'number' &&
|
||||
typeof newValue === 'number' &&
|
||||
! isNaN( newValue )
|
||||
) {
|
||||
values[key] = newValue;
|
||||
|
||||
publishers.update.dispatch( key, values[key] );
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function randomizeValues () {
|
||||
var randomValues = { };
|
||||
|
||||
for ( var key in limits ) {
|
||||
randomValues[key] = parseInt(
|
||||
Math.random() * ( limits[key].max - limits[key].min ) + limits[key].min,
|
||||
10
|
||||
);
|
||||
}
|
||||
|
||||
updateValues( randomValues );
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function getValues ( vals ) {
|
||||
var result = { };
|
||||
vals = vals || values;
|
||||
|
||||
for ( var key in vals ) {
|
||||
if ( typeof vals[key].value === 'number' ) {
|
||||
result[key] = vals[key].value;
|
||||
} else {
|
||||
result[key] = vals[key];
|
||||
}
|
||||
}
|
||||
|
||||
return objectHelper.getCopy( result );
|
||||
}
|
||||
|
||||
function getLimits ( vals ) {
|
||||
var result = { };
|
||||
|
||||
for ( var key in vals ) {
|
||||
result[key] = {
|
||||
min: vals[key].min,
|
||||
max: vals[key].max
|
||||
};
|
||||
}
|
||||
|
||||
return objectHelper.getCopy( result );
|
||||
}
|
||||
|
||||
self.randomizeValues = randomizeValues;
|
||||
self.setValue = setValue;
|
||||
self.getValues = getValues;
|
||||
}
|
||||
|
||||
return ControlsModel;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,81 @@
|
||||
/*global define*/
|
||||
define(
|
||||
[ 'config', 'lib/glitch-canvas-with-worker', 'util/addpublishers', 'util/canvas' ],
|
||||
function ( config, canvasGlitcher, addPublishers, canvasHelper ) {
|
||||
// the glitchModel takes care of glitching the image and passing it along as imageData
|
||||
function GlitchModel () {
|
||||
if ( ! ( this instanceof GlitchModel ) ) {
|
||||
return new GlitchModel();
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var publishers = addPublishers( self, 'glitch' );
|
||||
var params = { };
|
||||
var originalImageData;
|
||||
var glitchedImageData;
|
||||
var thumbnailCanvas = document.createElement( 'canvas' );
|
||||
var thumbnailImg = new Image();
|
||||
var glitchTimeoutId = NaN;
|
||||
|
||||
var glitcher = canvasGlitcher( config.workers.glitch ).init();
|
||||
|
||||
function updateGlitch () {
|
||||
if ( params && originalImageData ) {
|
||||
glitcher.glitch( originalImageData, params, glitchComplete );
|
||||
}
|
||||
}
|
||||
|
||||
function glitchComplete ( glitchData ) {
|
||||
glitchedImageData = glitchData;
|
||||
publishers.glitch.dispatch( glitchData );
|
||||
}
|
||||
|
||||
function setValue ( key, newValue ) {
|
||||
if ( params[key] !== newValue ) {
|
||||
params[key] = newValue;
|
||||
glitchTimeoutId = setTimeout( updateGlitch, 10 );
|
||||
}
|
||||
}
|
||||
|
||||
function setImageData ( newImageData ) {
|
||||
originalImageData = newImageData;
|
||||
updateGlitch()
|
||||
}
|
||||
|
||||
function generateImageURL ( callback, size, args ) {
|
||||
if ( originalImageData ) {
|
||||
size = size || { width: 50, height: 50 };
|
||||
|
||||
if ( typeof size === 'string' && size === 'original' ) {
|
||||
size = {
|
||||
width: glitchedImageData.width,
|
||||
height: glitchedImageData.height
|
||||
};
|
||||
}
|
||||
|
||||
canvasHelper.resizeImage( glitchedImageData, size, function ( imageUrl ) {
|
||||
args = Array.prototype.slice.call( args || [ ] );
|
||||
args.unshift( imageUrl );
|
||||
|
||||
callback.apply( callback, args );
|
||||
}, 'asDataURL' );
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function getImageGenerationFn ( callback, size ) {
|
||||
return function () {
|
||||
generateImageURL( callback, size, arguments )
|
||||
};
|
||||
}
|
||||
|
||||
self.setValue = setValue;
|
||||
self.setImageData = setImageData;
|
||||
self.generateImageURL = generateImageURL;
|
||||
self.getImageGenerationFn = getImageGenerationFn;
|
||||
}
|
||||
|
||||
return GlitchModel;
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,169 @@
|
||||
/*global define*/
|
||||
define(
|
||||
[ 'util/browser', 'util/addpublishers', 'util/time', 'util/canvas', 'util/localizetext' ],
|
||||
function ( browser, addPublishers , timeHelper, canvasHelper, loc ) {
|
||||
// the imageModel loads and stores the latest open image file
|
||||
// (the original, not the glitched version) and converts it into
|
||||
// a imageData object
|
||||
function ImageModel () {
|
||||
if ( ! ( this instanceof ImageModel ) ) {
|
||||
return new ImageModel();
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var publishers = addPublishers( self, [ 'load', 'update', 'error', 'statusmessage' ] );
|
||||
|
||||
var canvasEl = document.createElement( 'canvas' );
|
||||
var ctx = canvasEl.getContext( '2d' );
|
||||
var imageEl = new Image();
|
||||
|
||||
var fileReader;
|
||||
var canHandleFileImport = false;
|
||||
var lastImageSrc;
|
||||
var lastFileName;
|
||||
|
||||
// max image import filesize in megabytes.
|
||||
// because of not 100% clear base64 url size constraints
|
||||
// if the uploaded image is bigger, resize
|
||||
// var maxFileSize = Math.pow(1024, 2) * 1.5;
|
||||
var maxFileSize = 0.8 * Math.pow( 1000, 2 );
|
||||
var isScalingDown = true;
|
||||
|
||||
if ( fileReader = browser.getFeature( window, 'FileReader' ) ) {
|
||||
fileReader = new fileReader();
|
||||
canHandleFileImport = true;
|
||||
fileReader.addEventListener( 'load', fileLoaded );
|
||||
fileReader.addEventListener( 'error', fileLoadFailed );
|
||||
}
|
||||
|
||||
imageEl.addEventListener( 'load', imageLoaded );
|
||||
imageEl.addEventListener( 'error', imageLoadFailed );
|
||||
|
||||
function loadFromFile ( file ) {
|
||||
if ( canHandleFileImport ) {
|
||||
lastFileName = file.name;
|
||||
fileReader.readAsDataURL( file );
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function loadFromVideo ( videoEl ) {
|
||||
clearCanvas();
|
||||
canvasEl.width = videoEl.width || videoEl.videoWidth || videoEl.clientWidth;
|
||||
canvasEl.height = videoEl.height || videoEl.videoHeight || videoEl.clientHeight;
|
||||
|
||||
ctx.translate( canvasEl.width, 0 );
|
||||
ctx.scale( -1, 1 );
|
||||
ctx.drawImage( videoEl, 0, 0 );
|
||||
|
||||
lastImageSrc = canvasEl.toDataURL( 'image/png' );
|
||||
lastFileName = loc( 'webcam.picture', timeHelper.dateTimeToLocalStr( new Date() ) );
|
||||
|
||||
checkImageData( ctx.getImageData( 0, 0, canvasEl.width, canvasEl.height ), publishers.load.dispatch );
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function loadFromURL ( url, fileName ) {
|
||||
if ( fileName ) {
|
||||
lastFileName = fileName;
|
||||
}
|
||||
|
||||
url = decodeURIComponent( url );
|
||||
|
||||
publishers.update.dispatch( 'img', url, fileName );
|
||||
|
||||
if ( url && imageEl.src !== url ) {
|
||||
imageEl.src = '';
|
||||
lastImageSrc = url;
|
||||
imageEl.src = url;
|
||||
} else {
|
||||
imageLoaded();
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function clearCanvas () {
|
||||
ctx.clearRect( 0, 0, canvasEl.width, canvasEl.height );
|
||||
}
|
||||
|
||||
function imageLoaded () {
|
||||
var imageData = getImageData( imageEl );
|
||||
|
||||
checkImageData( imageData, publishers.load.dispatch );
|
||||
}
|
||||
|
||||
function imageLoadFailed ( err ) {
|
||||
publishers.error.dispatch( 'file.error.openimage', imageEl.src );
|
||||
}
|
||||
|
||||
function fileLoaded ( event ) {
|
||||
loadFromURL( event.target.result );
|
||||
}
|
||||
|
||||
function fileLoadFailed ( error ) {
|
||||
publishers.error.dispatch( 'file.error.openfile' );
|
||||
}
|
||||
|
||||
function checkImageData ( imageData, callback ) {
|
||||
if ( isScalingDown && imageData.data && imageData.data.length / 4 > maxFileSize ) {
|
||||
var widthToHeightRatio = imageData.width / imageData.height;
|
||||
var heightTiWidthRatio = imageData.height / imageData.width;
|
||||
|
||||
var newWidth = Math.round( Math.sqrt( maxFileSize / widthToHeightRatio ) );
|
||||
var newHeight = Math.round( newWidth * heightTiWidthRatio );
|
||||
var newSize = {
|
||||
width: newWidth,
|
||||
height: newHeight
|
||||
};
|
||||
|
||||
publishers.statusmessage.dispatch( 'file.message.resize' );
|
||||
|
||||
canvasHelper.resizeImage( imageData, newSize, function ( result ) {
|
||||
callback( result.imageData );
|
||||
|
||||
if ( typeof result.dataURL === 'string' ) {
|
||||
lastImageSrc = result.dataURL;
|
||||
}
|
||||
}, 'both' );
|
||||
} else {
|
||||
callback( imageData );
|
||||
}
|
||||
}
|
||||
|
||||
function settingUpdated ( key, value ) {
|
||||
if ( key === 'resizeUploadedImages' && typeof value === 'boolean' ) {
|
||||
isScalingDown = value;
|
||||
}
|
||||
}
|
||||
|
||||
function getImageData ( img ) {
|
||||
clearCanvas();
|
||||
canvasEl.width = img.naturalWidth;
|
||||
canvasEl.height = img.naturalHeight;
|
||||
ctx.drawImage( img, 0, 0, img.naturalWidth, img.naturalHeight );
|
||||
|
||||
return ctx.getImageData( 0, 0, img.naturalWidth, img.naturalHeight );
|
||||
}
|
||||
|
||||
function getLastImageSRC ( img ) {
|
||||
return lastImageSrc;
|
||||
}
|
||||
|
||||
function getLastFileName ( img ) {
|
||||
return lastFileName;
|
||||
}
|
||||
|
||||
self.loadFromFile = loadFromFile;
|
||||
self.loadFromVideo = loadFromVideo;
|
||||
self.getLastImageSRC = getLastImageSRC;
|
||||
self.getLastFileName = getLastFileName;
|
||||
self.loadFromURL = loadFromURL;
|
||||
self.settingUpdated = settingUpdated;
|
||||
}
|
||||
|
||||
return ImageModel;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,159 @@
|
||||
/*global define*/
|
||||
define(
|
||||
[ 'config', 'util/dom', 'util/object', 'util/string', 'util/addpublishers', 'lib/reqwest' ],
|
||||
function ( config, domHelper, objectHelper, stringHelper, addPublishers, reqwest ) {
|
||||
function LocalisationModel () {
|
||||
if ( ! ( this instanceof LocalisationModel ) ) {
|
||||
return new LocalisationModel();
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
var publishers = addPublishers( self, 'error' );
|
||||
|
||||
var textElData = [ ];
|
||||
var texts = '';
|
||||
var currentLanguage = '';
|
||||
|
||||
var animationFrameId = NaN;
|
||||
var languageWasLoaded = false;
|
||||
|
||||
var linkOptions = { links: { newTab: true } };
|
||||
|
||||
function setLanguage ( newLanguage ) {
|
||||
if ( newLanguage !== currentLanguage && newLanguage in config.availableLanguages ) {
|
||||
loadLanguage( newLanguage );
|
||||
}
|
||||
}
|
||||
|
||||
function loadLanguage ( languageName ) {
|
||||
languageWasLoaded = false;
|
||||
|
||||
reqwest( {
|
||||
url: config.language.dir + '/' + languageName + '.json',
|
||||
type: 'json',
|
||||
method: 'get',
|
||||
error: function ( err, one, two ) {
|
||||
// if this is the first language to load, that's really bad.
|
||||
languageWasLoaded = true;
|
||||
publishers.error.dispatch( 'I\'m really sorry. I failed to load the language file for ' + languageName + '. This is a serious error that makes the app very hard to use. Maybe you can try reloading?' );
|
||||
|
||||
},
|
||||
success: function ( res ) {
|
||||
languageLoaded( languageName, res );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
function languageLoaded ( newLanguageName, newTexts ) {
|
||||
currentLanguage = newLanguageName;
|
||||
languageWasLoaded = true;
|
||||
texts = newTexts;
|
||||
resetAllTexts();
|
||||
updateAllTexts();
|
||||
}
|
||||
|
||||
function updateAllTexts () {
|
||||
if ( languageWasLoaded ) {
|
||||
cancelAnimationFrame( animationFrameId );
|
||||
|
||||
animationFrameId = requestAnimationFrame( function () {
|
||||
var item;
|
||||
var args;
|
||||
|
||||
for ( var i = textElData.length - 1; i >= 0; i-- ) {
|
||||
item = textElData[i];
|
||||
|
||||
if ( domHelper.isElement( item.el ) ) {
|
||||
if ( ! item.wasUpdated ) {
|
||||
if ( item.attribute === 'innerHTML' ) {
|
||||
item.el.innerHTML = stringHelper.markdownToHtml( getTextForKey( item.key, item.args ), linkOptions );
|
||||
} else {
|
||||
item.el[item.attribute] = getTextForKey( item.key, item.args );
|
||||
}
|
||||
|
||||
item.wasUpdated = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
textElData.splice( i, 1 );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
function resetAllTexts () {
|
||||
textElData.forEach( function ( item ) {
|
||||
item.wasUpdated = false;
|
||||
} );
|
||||
}
|
||||
|
||||
function getTextForKey ( key, args ) {
|
||||
var result = '';
|
||||
|
||||
try {
|
||||
result = objectHelper.getObjectByString( key, texts );
|
||||
} catch ( e ) {
|
||||
if ( languageWasLoaded ) {
|
||||
result = key
|
||||
}
|
||||
};
|
||||
|
||||
if ( args && args.length ) {
|
||||
var regex;
|
||||
|
||||
args.forEach( function ( arg, index ) {
|
||||
regex = new RegExp( '{\\$' + ( index + 1 ) + '}' );
|
||||
result = result.replace( regex, arg );
|
||||
} );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// http://mzl.la/1RDxjVO
|
||||
function getArgs ( args, firstIndex ) {
|
||||
firstIndex = firstIndex || 3;
|
||||
var result;
|
||||
|
||||
if ( args.length > firstIndex ) {
|
||||
result = [ ];
|
||||
}
|
||||
|
||||
for ( var i = firstIndex, len = args.length; i < len; i++ ) {
|
||||
result[result.length] = args[i];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function settingUpdated ( name, value ) {
|
||||
if ( name === 'language' && value !== currentLanguage ) {
|
||||
loadLanguage( value );
|
||||
}
|
||||
}
|
||||
|
||||
function localizeText ( el, attribute, key ) {
|
||||
if ( el && attribute && key ) {
|
||||
textElData.push( { el: el, attribute: attribute, key: key, wasUpdated: false, args: getArgs( arguments ) } );
|
||||
} else {
|
||||
if ( typeof el === 'string' ) {
|
||||
return getTextForKey( el, getArgs( arguments, 1 ) );
|
||||
}
|
||||
}
|
||||
|
||||
updateAllTexts();
|
||||
}
|
||||
|
||||
self.setLanguage = setLanguage;
|
||||
self.localizeText = localizeText;
|
||||
self.settingUpdated = settingUpdated;
|
||||
self.randomNumber = ~~( Math.random() * 1000 );
|
||||
}
|
||||
|
||||
LocalisationModel.sharedInstance = LocalisationModel();
|
||||
|
||||
return LocalisationModel;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,39 @@
|
||||
/*global define*/
|
||||
define(
|
||||
[ 'util/addpublishers' ],
|
||||
function ( addPublishers ) {
|
||||
// the NetworkModel updates the app if the device connectivity changed
|
||||
function NetworkModel () {
|
||||
if ( ! ( this instanceof NetworkModel ) ) {
|
||||
return new NetworkModel();
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var publishers = addPublishers( self, 'connectivitychange', 'disconnect', 'connect' );
|
||||
var isConnected;
|
||||
|
||||
window.addEventListener( 'online', checkConnectivity );
|
||||
window.addEventListener( 'offline', checkConnectivity );
|
||||
|
||||
function checkConnectivity () {
|
||||
if ( navigator.onLine !== isConnected ) {
|
||||
isConnected = navigator.onLine;
|
||||
|
||||
publishers.connectivitychange.dispatch( isConnected );
|
||||
|
||||
if ( isConnected ) {
|
||||
publishers.connect.dispatch();
|
||||
} else {
|
||||
publishers.disconnect.dispatch();
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
self.checkConnectivity = checkConnectivity;
|
||||
}
|
||||
|
||||
return NetworkModel;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,178 @@
|
||||
/*global define*/
|
||||
define(
|
||||
[ 'config', 'util/browser', 'util/object', 'util/addpublishers', 'lib/localforage.nopromises' ],
|
||||
function ( config, browser, objectHelper, addPublishers, localforage ) {
|
||||
function SettingsModel ( storageKey ) {
|
||||
if ( ! ( this instanceof SettingsModel ) ) {
|
||||
return new SettingsModel( storageKey );
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var publisherNames = [ 'update', 'error' ];
|
||||
var publishers = addPublishers( self, publisherNames );
|
||||
var useLocalForage = browser.test( 'localforage' ) && localforage;
|
||||
var worker;
|
||||
var defaultSettings = config.settings;
|
||||
var userLanguage = ( navigator.language || navigator.userLanguage || '' ).toLowerCase();
|
||||
|
||||
if ( userLanguage !== '' && defaultSettings.language.options.indexOf( userLanguage ) > -1 ) {
|
||||
defaultSettings.language.value = userLanguage;
|
||||
}
|
||||
|
||||
var settings = { };
|
||||
|
||||
storageKey = storageKey || 'settings';
|
||||
|
||||
if ( useLocalForage && browser.test( 'webworker' ) && browser.test( 'browserdb' ) && ! browser.test( 'safari' ) ) {
|
||||
worker = new Worker( config.workers.settings );
|
||||
worker.addEventListener( 'message', workerResponded, false );
|
||||
sendMessageToWorker( 'setStorageKey', storageKey );
|
||||
sendMessageToWorker( 'setDefaultSettings', defaultSettings );
|
||||
} else {
|
||||
settingsUpdated( defaultSettings );
|
||||
}
|
||||
|
||||
function getSetting ( key ) {
|
||||
return settings[key];
|
||||
}
|
||||
|
||||
function getSettingValue ( key ) {
|
||||
var setting = getSetting( key );
|
||||
|
||||
if ( setting ) {
|
||||
return setting.value;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function getSettings () {
|
||||
return objectHelper.getCopy( settings );
|
||||
}
|
||||
|
||||
function setValue ( key, value ) {
|
||||
var wasUpdated = false;
|
||||
|
||||
if (
|
||||
typeof key === 'string' &&
|
||||
typeof settings[key] !== 'undefined' &&
|
||||
typeof value === typeof settings[key].value &&
|
||||
value !== settings[key].value
|
||||
) {
|
||||
settings[key].value = value;
|
||||
wasUpdated = true;
|
||||
}
|
||||
|
||||
if ( wasUpdated ) {
|
||||
if ( useLocalForage ) {
|
||||
save( settings );
|
||||
} else {
|
||||
settingsUpdated( settings );
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function save ( newSettings ) {
|
||||
if ( useLocalForage ) {
|
||||
if ( worker ) {
|
||||
sendMessageToWorker( 'save', newSettings );
|
||||
} else {
|
||||
localforage.setItem( storageKey, newSettings, function ( err, savedSettings ) {
|
||||
if ( err ) {
|
||||
sendError( 'settings.error.save' );
|
||||
console && console.log( 'localforage error', err );
|
||||
} else {
|
||||
if ( savedSettings ) {
|
||||
settingsUpdated( savedSettings );
|
||||
} else {
|
||||
console && console.log( 'no data was saved', savedSettings );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function load () {
|
||||
if ( useLocalForage ) {
|
||||
if ( worker ) {
|
||||
sendMessageToWorker( 'load' );
|
||||
} else {
|
||||
localforage.getItem( storageKey, function ( err, loadedSettings ) {
|
||||
if ( err ) {
|
||||
sendError( 'settings.error.load' );
|
||||
console && console.log( 'localforage error', err );
|
||||
} else {
|
||||
settingsUpdated( loadedSettings );
|
||||
}
|
||||
} );
|
||||
}
|
||||
} else {
|
||||
settingsUpdated( settings || defaultSettings );
|
||||
}
|
||||
}
|
||||
|
||||
function sendMessageToWorker ( type, data ) {
|
||||
if ( typeof type === 'string' ) {
|
||||
if ( typeof data === 'undefined' ) {
|
||||
data = type;
|
||||
}
|
||||
|
||||
var message = { };
|
||||
message[type] = data;
|
||||
worker.postMessage( message );
|
||||
}
|
||||
}
|
||||
|
||||
function workerResponded ( event ) {
|
||||
if ( event && event.data ) {
|
||||
publisherNames.forEach( function ( type ) {
|
||||
if ( typeof event.data[type] !== 'undefined' ) {
|
||||
if ( type === 'update' ) {
|
||||
settingsUpdated( event.data[type] );
|
||||
} else {
|
||||
if ( publishers[type] === type ) {
|
||||
publishers[type].dispatch()
|
||||
} else {
|
||||
publishers[type].dispatch( event.data[type] )
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
function settingsUpdated ( newSettings ) {
|
||||
if ( newSettings ) {
|
||||
settings = newSettings || defaultSettings;
|
||||
|
||||
if ( settings !== defaultSettings ) {
|
||||
for ( var name in defaultSettings ) {
|
||||
if ( ! settings[name] ) {
|
||||
settings[name] = defaultSettings[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ( var name in settings ) {
|
||||
publishers.update.dispatch( name, settings[name].value, settings[name].options );
|
||||
}
|
||||
|
||||
} else {
|
||||
settings = defaultSettings;
|
||||
save( settings );
|
||||
}
|
||||
}
|
||||
|
||||
self.getSetting = getSetting;
|
||||
self.getSettingValue = getSettingValue;
|
||||
self.getSettings = getSettings;
|
||||
self.setValue = setValue;
|
||||
self.load = load;
|
||||
}
|
||||
|
||||
return SettingsModel;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,107 @@
|
||||
/*global define*/
|
||||
define(
|
||||
[ 'config', 'lib/reqwest', 'util/addpublishers' ],
|
||||
function ( config, reqwest, addPublishers ) {
|
||||
// the ShareModel uploads and removes images from Imgur
|
||||
function ShareModel () {
|
||||
if ( ! ( this instanceof ShareModel ) ) {
|
||||
return new ShareModel();
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var base64Url;
|
||||
var publishers = addPublishers( self,
|
||||
'uploadstart', 'uploadend', 'uploadcomplete',
|
||||
'removestart', 'removeend', 'removecomplete',
|
||||
'error', 'statusmessage'
|
||||
);
|
||||
|
||||
// http://stackoverflow.com/questions/17805456/upload-a-canvas-image-to-imgur-api-v3-with-javascript
|
||||
function upload () {
|
||||
if ( base64Url ) {
|
||||
publishers.uploadstart.dispatch();
|
||||
|
||||
reqwest( {
|
||||
url: 'https://api.imgur.com/3/image.json',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: 'Client-ID ' + config.keys.imgur
|
||||
},
|
||||
data: {
|
||||
image: base64Url.split( ',' )[1],
|
||||
type: 'base64'
|
||||
},
|
||||
type: 'json',
|
||||
crossOrigin: true,
|
||||
success: uploadSuceeded,
|
||||
error: uploadFailed
|
||||
} );
|
||||
} else {
|
||||
uploadFailed( new Error( 'share.error.base64' ) );
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function uploadSuceeded ( res ) {
|
||||
publishers.uploadend.dispatch();
|
||||
|
||||
if ( res.status === 200 && res.data && res.data.link ) {
|
||||
var link = res.data.link.replace( 'http://', 'https://' );
|
||||
|
||||
publishers.uploadcomplete.dispatch( link, res.data.id, res.data.deletehash );
|
||||
publishers.statusmessage.dispatch( 'share.message.upload', { innerHTML: true, args: [ link ] } );
|
||||
}
|
||||
}
|
||||
|
||||
function uploadFailed ( err ) {
|
||||
publishers.uploadend.dispatch();
|
||||
publishers.error.dispatch( 'share.error.upload', { type: 'imguruploadfail' } );
|
||||
console && console.log( err.message || err );
|
||||
}
|
||||
|
||||
function updateUrl ( url ) {
|
||||
base64Url = url;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function remove ( uid, deleteHash ) {
|
||||
if ( deleteHash ) {
|
||||
publishers.removestart.dispatch( uid );
|
||||
|
||||
reqwest( {
|
||||
url: 'https://api.imgur.com/3/image/' + deleteHash,
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
Authorization: 'Client-ID ' + config.keys.imgur
|
||||
},
|
||||
crossOrigin: true,
|
||||
success: function () { removeSucceeded( uid ); },
|
||||
error: function ( err ) { removeFailed( uid, err ); }
|
||||
} );
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function removeSucceeded ( uid ) {
|
||||
publishers.removeend.dispatch( uid );
|
||||
publishers.removecomplete.dispatch( uid );
|
||||
publishers.statusmessage.dispatch( 'share.message.del' );
|
||||
}
|
||||
|
||||
function removeFailed ( uid, err ) {
|
||||
publishers.removeend.dispatch( uid );
|
||||
publishers.error.dispatch( 'share.error.del', { type: 'imgurremovefail', uid: uid } );
|
||||
console && console.log( err.message || err );
|
||||
}
|
||||
|
||||
self.upload = upload;
|
||||
self.updateUrl = updateUrl;
|
||||
self.remove = remove
|
||||
}
|
||||
|
||||
return ShareModel;
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,300 @@
|
||||
/*global define*/
|
||||
define(
|
||||
[ 'config', 'util/browser', 'util/addpublishers', 'lib/md5', 'lib/localforage.nopromises', 'models/localisationmodel' ],
|
||||
function ( config, browser, addPublishers, md5, localforage, LocalisationModel ) {
|
||||
// the StorageModel takes care of loading and saving data to localStorage.
|
||||
// It stores the used glitch values as well as the imgur address if the
|
||||
// image was shared.
|
||||
// It generares a UID for each image to avoid having to save the
|
||||
// same image multiple times.
|
||||
// If an image was updated (shared/deleted), it updates the corresponding
|
||||
// object in localstorage
|
||||
function StorageModel () {
|
||||
if ( ! ( this instanceof StorageModel ) ) {
|
||||
return new StorageModel();
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var isFirstVisit = false;
|
||||
var entries = { };
|
||||
var useLocalForage = browser.test( 'localforage' ) && localforage;
|
||||
var worker;
|
||||
|
||||
var publisherNames = [
|
||||
'update', 'save', 'loaditem',
|
||||
'removeall', 'removelocaldata', 'removeimgurdata',
|
||||
'firstvisit', 'error', 'statusmessage'
|
||||
];
|
||||
|
||||
var publishers = addPublishers( self, publisherNames );
|
||||
|
||||
storageKey = config.keys.storage;
|
||||
|
||||
if ( useLocalForage && browser.test( 'webworker' ) && browser.test( 'browserdb' ) && ! browser.test( 'safari' ) ) {
|
||||
worker = new Worker( config.workers.storage );
|
||||
worker.addEventListener( 'message', workerResponded, false );
|
||||
sendMessageToWorker( 'setStorageKey', storageKey );
|
||||
}
|
||||
|
||||
function add ( item ) {
|
||||
if ( useLocalForage && item ) {
|
||||
if ( worker ) {
|
||||
sendMessageToWorker( 'add', item );
|
||||
} else {
|
||||
var uid = createUID( item );
|
||||
|
||||
if (
|
||||
entries[uid] &&
|
||||
entries[uid].deleteHash === item.deleteHash &&
|
||||
entries[uid].publicUrl === item.publicUrl &&
|
||||
entries[uid].imgurID === item.imgurID
|
||||
) {
|
||||
publishers.statusmessage.dispatch( 'file.message.before' );
|
||||
} else {
|
||||
item.timestamp = Date.now();
|
||||
entries[uid] = item;
|
||||
|
||||
save( function () {
|
||||
publishers.statusmessage.dispatch( 'file.message.save' );
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function removeCompletely ( uid ) {
|
||||
if ( useLocalForage && uid ) {
|
||||
if ( worker ) {
|
||||
sendMessageToWorker( 'removeCompletely', uid );
|
||||
} else {
|
||||
if (
|
||||
entries[uid] &&
|
||||
(
|
||||
entries[uid].deleteHash ||
|
||||
entries[uid].publicUrl ||
|
||||
entries[uid].imgurID ||
|
||||
entries[uid].values ||
|
||||
entries[uid].src
|
||||
)
|
||||
) {
|
||||
console && console.log( 'cant delete storage item because theres still some data left', entries[uid] );
|
||||
} else {
|
||||
publishers.removeall.dispatch( entries[uid] );
|
||||
delete entries[uid];
|
||||
|
||||
save( function () {
|
||||
publishers.statusmessage.dispatch( 'file.message.del' );
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeLocalData ( uid ) {
|
||||
if ( useLocalForage && uid ) {
|
||||
if ( worker ) {
|
||||
sendMessageToWorker( 'removeLocalData', uid );
|
||||
} else {
|
||||
if ( entries[uid] ) {
|
||||
if ( entries[uid].src ) {
|
||||
delete entries[uid].src;
|
||||
|
||||
publishers.removelocaldata.dispatch( entries[uid] );
|
||||
}
|
||||
|
||||
if ( entries[uid].values ) {
|
||||
delete entries[uid].values;
|
||||
}
|
||||
|
||||
if ( ! ( entries[uid].deleteHash || entries[uid].publicUrl || entries[uid].imgurID ) ) {
|
||||
removeCompletely( uid );
|
||||
}
|
||||
|
||||
save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function removeImgurData ( uid ) {
|
||||
if ( useLocalForage && uid ) {
|
||||
if ( worker ) {
|
||||
sendMessageToWorker( 'removeImgurData', uid );
|
||||
} else {
|
||||
if ( entries[uid] ) {
|
||||
if ( entries[uid].deleteHash ) {
|
||||
publishers.removeimgurdata.dispatch( entries[uid] );
|
||||
delete entries[uid].deleteHash;
|
||||
}
|
||||
|
||||
if ( entries[uid].publicUrl ) {
|
||||
delete entries[uid].publicUrl;
|
||||
}
|
||||
|
||||
if ( entries[uid].imgurID ) {
|
||||
delete entries[uid].imgurID;
|
||||
}
|
||||
|
||||
if ( ! ( entries[uid].src || entries[uid].values ) ) {
|
||||
removeCompletely( uid );
|
||||
}
|
||||
|
||||
save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function load ( callback ) {
|
||||
if ( useLocalForage ) {
|
||||
if ( worker ) {
|
||||
sendMessageToWorker( 'load' );
|
||||
} else {
|
||||
localforage.getItem( storageKey, function ( err, loadedData ) {
|
||||
if ( err ) {
|
||||
publishers.error.dispatch( 'file.error.load' );
|
||||
console && console.log( 'localforage error', err );
|
||||
} else {
|
||||
entries = loadedData && loadedData.entries ? loadedData.entries : { };
|
||||
publishers.update.dispatch( entries );
|
||||
|
||||
isFirstVisit = ( loadedData && loadedData.lastVisit ) ? false : true;
|
||||
|
||||
if ( isFirstVisit ) {
|
||||
publishers.firstvisit.dispatch();
|
||||
save();
|
||||
}
|
||||
|
||||
if ( typeof callback === 'function' ) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function save ( callback ) {
|
||||
if ( useLocalForage ) {
|
||||
if ( worker ) {
|
||||
sendMessageToWorker( 'save' );
|
||||
} else {
|
||||
localforage.setItem( storageKey, { entries: entries, lastVisit: Date.now() }, function ( err, savedData ) {
|
||||
if ( err ) {
|
||||
publishers.error.dispatch( 'file.error.save' );
|
||||
console && console.log( 'localforage error', err );
|
||||
} else {
|
||||
if ( savedData ) {
|
||||
entries = savedData.entries;
|
||||
publishers.update.dispatch( entries );
|
||||
publishers.save.dispatch();
|
||||
|
||||
if ( typeof callback === 'function' ) {
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
console && console.log( 'no data was saved', savedData );
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function loadItem ( uid ) {
|
||||
if ( useLocalForage ) {
|
||||
if ( worker ) {
|
||||
sendMessageToWorker( 'loadItem', uid );
|
||||
} else {
|
||||
if ( entries[uid] ) {
|
||||
publishers.loaditem.dispatch( uid, entries[uid] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
function workerResponded ( event ) {
|
||||
if ( event && event.data ) {
|
||||
publisherNames.forEach( function ( type ) {
|
||||
if ( typeof event.data[type] !== 'undefined' ) {
|
||||
if ( type === 'loaditem' ) {
|
||||
publishers[type].dispatch( event.data[type].uid, event.data[type].entries );
|
||||
} else {
|
||||
if ( publishers[type] === type ) {
|
||||
// if message is empty, the worker sends the type as data.
|
||||
publishers[type].dispatch()
|
||||
} else {
|
||||
publishers[type].dispatch( event.data[type] )
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
function sendMessageToWorker ( type, data ) {
|
||||
if ( typeof type === 'string' ) {
|
||||
if ( typeof data === 'undefined' ) {
|
||||
data = type;
|
||||
}
|
||||
|
||||
var message = { };
|
||||
message[type] = data;
|
||||
worker.postMessage( message );
|
||||
}
|
||||
}
|
||||
|
||||
function createUID ( item ) {
|
||||
var inputStr = 'N' + item.name + 'S' + item.src + 'L' + item.thumbnail.length || 0 + 'V';
|
||||
|
||||
for ( var key in item.values ) {
|
||||
inputStr += key.substr( 0, 2 );
|
||||
inputStr += item.values[key];
|
||||
}
|
||||
|
||||
return md5( inputStr );
|
||||
}
|
||||
|
||||
function getLatestUID () {
|
||||
var latestUid;
|
||||
var latestTimestamp;
|
||||
var timestamp;
|
||||
|
||||
for ( var uid in entries ) {
|
||||
timestamp = entries[uid].timestamp;
|
||||
|
||||
if ( timestamp ) {
|
||||
if ( ! latestUid || timestamp > latestTimestamp ) {
|
||||
latestUid = uid;
|
||||
latestTimestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return latestUid;
|
||||
}
|
||||
|
||||
self.add = add;
|
||||
self.removeImgurData = removeImgurData;
|
||||
self.removeLocalData = removeLocalData;
|
||||
self.load = load;
|
||||
self.loadItem = loadItem;
|
||||
self.removeImgurData = removeImgurData;
|
||||
self.getLatestUID = getLatestUID;
|
||||
}
|
||||
|
||||
return StorageModel;
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user