diff --git a/README.md b/README.md index 2d4c5e49..b27146ca 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,24 @@ -# bodymovin-extension -Bodymovin UI extension panel +# Bodymovin for Telegram Stickers — After Effects extension for exporting Telegram animated stickers + +Bodymovin-TG is designed to help you export your animations in the **.TGS** format supported by the Telegram Animated Stickers platform. + +### Installing + +1. Close After Effects if it's open +2. Install [the ZXP Installer][zxp_installer] +3. Download the latest version of [bodymovin-tg][bodymovin_tg] (*bodymovin-tg.zxp*) +4. Open the ZXP Installer and drag the bodymovin-tg extension into the ZXP Installer window +5. Open After Effects. +**Windows:** Go to Edit > Preferences > Scripting & Expressions > and check "Allow Scripts to Write Files and Access Network" +**Mac:** Go to Adobe After Effects > Preferences > Scripting & Expressions > and check "Allow Scripts to Write Files and Access Network" +6. Under the menu Window > Extensions you should see **Bodymovin for Telegram Stickers**. Now you're good to go! + +For more information on creating and exporting Lottie animations, refer to [this guide][ae_guide]. + +For more information on Telegram Animated Stickers, see [this page][animated_stickers]. + +[//]: # (LINKS) +[zxp_installer]: https://zxpinstaller.com +[bodymovin_tg]: https://github.com/TelegramMessenger/bodymovin-extension/releases +[ae_guide]: http://airbnb.io/lottie/#/after-effects?id=creating-lottie-animations +[animated_stickers]: https://core.telegram.org/animated_stickers diff --git a/bundle/.debug b/bundle/.debug index 9ed36124..157bd7ea 100644 --- a/bundle/.debug +++ b/bundle/.debug @@ -1,6 +1,6 @@ - + diff --git a/bundle/CSXS/manifest.xml b/bundle/CSXS/manifest.xml index 79b05928..08e4cc88 100644 --- a/bundle/CSXS/manifest.xml +++ b/bundle/CSXS/manifest.xml @@ -1,13 +1,13 @@ - - + - - + + @@ -31,7 +31,7 @@ - + ./index_dev.html @@ -47,7 +47,7 @@ Panel - Bodymovin + Bodymovin for Telegram Stickers 500 diff --git a/bundle/jsx/compsManager.jsx b/bundle/jsx/compsManager.jsx index a153eb90..d9bdaefa 100644 --- a/bundle/jsx/compsManager.jsx +++ b/bundle/jsx/compsManager.jsx @@ -42,7 +42,7 @@ $.__bodymovin.bm_compsManager = (function () { uri = absoluteURI; } else { uri = Folder.desktop.absoluteURI + '/data'; - uri += standalone ? '.js' : '.json'; + uri += standalone ? '.js' : '.tgs'; } var f = new File(uri); var saveFileData = f.saveDlg(); diff --git a/bundle/jsx/dataManager.jsx b/bundle/jsx/dataManager.jsx index 3f27d060..bd7ee7aa 100644 --- a/bundle/jsx/dataManager.jsx +++ b/bundle/jsx/dataManager.jsx @@ -152,6 +152,7 @@ $.__bodymovin.bm_dataManager = (function () { } function deleteExtraParams(data, settings) { + delete data.markers; if (data.fonts.length === 0) { delete data.fonts; delete data.chars; @@ -188,34 +189,169 @@ $.__bodymovin.bm_dataManager = (function () { bm_eventDispatcher.sendEvent('bm:alert', {message: 'Could not create AVD file'}); _endCallback(); } + + var base64Decode = function F(s) + { + var ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + F.cache || F.cache = { + RE_NON_ALPHA: new RegExp('[^' + ALPHA + ']'), + RE_BAD_EQUALS: /\=([^=]|\=\=)/ + }; + + if( (n % 4) || F.cache.RE_NON_ALPHA.test(s) || F.cache.RE_BAD_EQUALS.test(s) ) + { + throw Error("Invalid Base64 data"); + } + + var fChr = String.fromCharCode, + n = s.length >>> 0, + a = [], + c = 0, + i0, i1, i2, i3, + b, b0, b1, b2; + + while( c < n ) + { + i0 = ALPHA.indexOf(s[c++]); + i1 = ALPHA.indexOf(s[c++]); + i2 = ALPHA.indexOf(s[c++]); + i3 = ALPHA.indexOf(s[c++]); + + b = (i0 << 18) + (i1 << 12) + ((i2 & 63) << 6) + (i3 & 63); + b0 = (b & (255 << 16)) >> 16; + b1 = (i2 == 64) ? -1 : (b & (255 << 8)) >> 8; + b2 = (i3 == 64) ? -1 : (b & 255); + + a[a.length] = fChr(b0); + if( 0 <= b1 ) a[a.length] = fChr(b1); + if( 0 <= b2 ) a[a.length] = fChr(b2); + } + + // Cleanup and return + // --- + s = a.join(''); + a.length = 0; + a = fChr = null; + return s; + }; + + function humanFileSize(size) { + var i = Math.floor( Math.log(size) / Math.log(1024) ); + return ( size / Math.pow(1024, i) ).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]; + } + + function dataCompressed(base64Data) { + var buf = base64Decode(base64Data) + if (buf.length > 1024 * 64) { + bm_eventDispatcher.sendEvent('bm:alert', {message: 'Failed!
Result file size of ' + humanFileSize(buf.length) + ' exceeds the limit of 64 KB.
Optimize or simplify your composition to meet the criteria'}); + _endCallback(); + return; + } + + try { + var f = new File(_destinationPath); + f.encoding = "BINARY"; + f.open ("w"); + f.write(buf) + f.close() + + } catch (errr) { + bm_eventDispatcher.sendEvent('bm:alert', {message: 'Could not write file.
Make sure you have enabled scripts to write files.
Edit > Preferences > General > Allow Scripts to Write Files and Access Network '}); + } + + _endCallback(); + } + + function checkItems(items, shapes) { + var error = null; + if (items != null) { + var i, itemsLen = items.length; + + for (i = 0; i < itemsLen; i += 1) { + if (items[i].ty == "rp") { + error = 'Composition should not include any Repeaters'; + break; + } + if (items[i].ty == "sr") { + error = 'Composition should not include any Star Shapes'; + break; + } + if (items[i].ty == "gs") { + error = 'Composition should not include any Gradient Strokes'; + break; + } + + if (error == null && shapes == true) { + error = checkItems(items[i].it, false) + if (error != null) { + break; + } + } + } + } + return error; + } + + function checkLayers(layers) { + var error = null; + if (layers != null) { + var i, layersLen = layers.length; +loop: + for (i = 0; i < layersLen; i += 1) { + if (layers[i].ddd != null && layers[i].ddd != 0) { + error = 'Composition should not include any 3D Layers'; + break; + } + if (layers[i].sr != null && layers[i].sr != 1) { + error = 'Composition should not include any Time Stretching'; + break; + } + if (layers[i].tm != null) { + error = 'Composition should not include any Time Remapping'; + break; + } + if (layers[i].ty === 1) { + error = 'Composition should not include any Solids'; + break; + } + if (layers[i].ty === 2) { + error = 'Composition should not include any Images'; + break; + } + if (layers[i].ty === 5) { + error = 'Composition should not include any Texts'; + break; + } + if (layers[i].ty === 9) { + error = 'Composition should not include any Videos'; + break; + } + if (layers[i].hasMask === true || layers[i].masksProperties != null) { + error = 'Composition should not include any Masks'; + break; + } + if (layers[i].ao === 1) { + error = 'Composition should not include any Auto-Oriented Layers'; + break; + } + + error = checkItems(layers[i].shapes, true); + if (error != null) { + break; + } + } + } + return error; + } function saveData(data, destinationPath, config, callback) { _endCallback = callback; _destinationPath = destinationPath; deleteExtraParams(data, config); separateComps(data.layers, data.comps); - var dataFile, segmentPath, s, string; - if (config.segmented) { - splitAnimation(data, config.segmentedTime); - var i, len = animationSegments.length; - var filePathName = destinationPath.substr(destinationPath.lastIndexOf('/') + 1); - filePathName = filePathName.substr(0, filePathName.lastIndexOf('.')); - for (i = 0; i < len; i += 1) { - segmentPath = destinationPath.substr(0, destinationPath.lastIndexOf('/') + 1); - segmentPath += filePathName + '_' + i + '.json'; - dataFile = new File(segmentPath); - dataFile.open('w', 'TEXT', '????'); - dataFile.encoding = 'UTF-8'; - string = JSON.stringify(animationSegments[i]); - try { - dataFile.write(string); //DO NOT ERASE, JSON UNFORMATTED - //dataFile.write(JSON.stringify(ob.renderData.exportData, null, ' ')); //DO NOT ERASE, JSON FORMATTED - dataFile.close(); - } catch (err) { - bm_eventDispatcher.sendEvent('bm:alert', {message: 'Could not write file.
Make sure you have enabled scripts to write files.
Edit > Preferences > General > Allow Scripts to Write Files and Access Network '}); - } - } - } else if (data.comps) { + + if (data.comps) { if (data.assets) { data.assets = data.assets.concat(data.comps); } else { @@ -224,59 +360,64 @@ $.__bodymovin.bm_dataManager = (function () { data.comps = null; delete data.comps; } - dataFile = new File(destinationPath); - dataFile.open('w', 'TEXT', '????'); - dataFile.encoding = 'UTF-8'; - string = JSON.stringify(data); + + var string = JSON.stringify(data); string = string.replace(/\n/g, ''); - //// - if (config.demo) { - var demoStr = bm_downloadManager.getDemoData(); - demoStr = demoStr.replace('"__[[ANIMATIONDATA]]__"', "" + string + ""); - if(data.ddd) { - demoStr = demoStr.replace('__[[RENDERER]]__', "html"); - } else { - demoStr = demoStr.replace('__[[RENDERER]]__', "svg"); - } - var filePathName = destinationPath.substr(destinationPath.lastIndexOf('/') + 1); - var demoDestinationPath = destinationPath.replace(filePathName,'demo.html'); - var demoFile = new File(demoDestinationPath); - demoFile.open('w', 'TEXT', '????'); - demoFile.encoding = 'UTF-8'; - try { - demoFile.write(demoStr); //DO NOT ERASE, JSON UNFORMATTED - //dataFile.write(JSON.stringify(ob.renderData.exportData, null, ' ')); //DO NOT ERASE, JSON FORMATTED - demoFile.close(); - } catch (errr) { - bm_eventDispatcher.sendEvent('bm:alert', {message: 'Could not write file.
Make sure you have enabled scripts to write files.
Edit > Preferences > General > Allow Scripts to Write Files and Access Network '}); + + var error = null; + + var frameRate = data.fr; + if (frameRate != 30.0 && frameRate != 60.0) { + error = 'Composition framerate must be exactly 30 or 60 FPS'; + } + + var totalFrames = data.op - data.ip; + + var duration = totalFrames / frameRate; + if (error == null && duration > 3.0) { + error = 'Composition duration must be not greater than 3 seconds'; + } + + if (error == null && !((data.w == 100 && data.h == 100) || (data.w == 512 && data.h >= 512 && data.h <= 832 && data.h % 16 == 0) || (data.h == 512 && data.w >= 512 && data.w <= 832 && data.w % 16 == 0))) { + error = 'Composition dimensions should be exactly 512x512px (or 100x100px for sticker set thumbnail)'; + } + + if (error == null && data.ddd != null && data.ddd != 0) { + error = 'Composition should not include any 3D Layers'; + } + + var assets = data.assets; + if (error == null && assets != null) { + var i, assetsLen = assets.length; + for (i = 0; i < assetsLen; i += 1) { + var layers = assets[i].layers; + error = checkLayers(layers); + if (error != null) { + break; + } } } - if (config.standalone) { - var bodymovinJsStr = bm_downloadManager.getStandaloneData(); - string = bodymovinJsStr.replace("\"__[ANIMATIONDATA]__\"", string ); - string = string.replace("\"__[STANDALONE]__\"", 'true'); + + if (error == null) { + error = checkLayers(data.layers); } - - //// - try { - dataFile.write(string); //DO NOT ERASE, JSON UNFORMATTED - //dataFile.write(JSON.stringify(ob.renderData.exportData, null, ' ')); //DO NOT ERASE, JSON FORMATTED - dataFile.close(); - } catch (errr) { - bm_eventDispatcher.sendEvent('bm:alert', {message: 'Could not write file.
Make sure you have enabled scripts to write files.
Edit > Preferences > General > Allow Scripts to Write Files and Access Network '}); + + if (error != null) { + bm_eventDispatcher.sendEvent('bm:alert', {message: 'Render Failed!
' + error}); + _endCallback() + return } + + bm_eventDispatcher.sendEvent('tg:compress', {path: destinationPath, message: string}); + animationSegments = []; segmentCount = 0; - if(config.avd) { - exportAVDVersion(data); - } else { - _endCallback(); - } } ob.saveData = saveData; ob.saveAVDData = saveAVDData; ob.saveAVDFailed = saveAVDFailed; + ob.dataCompressed = dataCompressed; return ob; }()); \ No newline at end of file diff --git a/bundle/jsx/elements/layerElement.jsx b/bundle/jsx/elements/layerElement.jsx index 8fec959b..712e40ef 100644 --- a/bundle/jsx/elements/layerElement.jsx +++ b/bundle/jsx/elements/layerElement.jsx @@ -127,7 +127,7 @@ $.__bodymovin.bm_layerElement = (function () { if (lType !== layerTypes.camera) { bm_transformHelper.exportTransform(layerInfo, layerData, frameRate); bm_maskHelper.exportMasks(layerInfo, layerData, frameRate); - bm_effectsHelper.exportEffects(layerInfo, layerData, frameRate, includeHiddenData); + bm_effectsHelper.exportEffects(layerInfo, layerData, frameRate); bm_layerStylesHelper.exportStyles(layerInfo, layerData, frameRate); bm_timeremapHelper.exportTimeremap(layerInfo, layerData, frameRate); } diff --git a/bundle/jsx/renderManager.jsx b/bundle/jsx/renderManager.jsx index c61d1afc..e5d3befe 100644 --- a/bundle/jsx/renderManager.jsx +++ b/bundle/jsx/renderManager.jsx @@ -143,6 +143,7 @@ $.__bodymovin.bm_renderManager = (function () { pendingLayers.length = 0; pendingComps.length = 0; var exportData = { + tgs: 1, v : version_number, fr : comp.frameRate, ip : comp.workAreaStart * comp.frameRate, @@ -156,7 +157,6 @@ $.__bodymovin.bm_renderManager = (function () { fonts : [], layers : [], markers : [] - }; currentExportedComps.push(currentCompID); ob.renderData.exportData = exportData; @@ -250,9 +250,6 @@ $.__bodymovin.bm_renderManager = (function () { } function clearNames(layers) { - if (hasExpressionsFlag) { - return; - } var i, len = layers.length; for (i = 0; i < len; i += 1) { layers[i].nm = null; @@ -260,8 +257,21 @@ $.__bodymovin.bm_renderManager = (function () { if (layers[i].ty === layerTypes.precomp && layers[i].layers) { clearNames(layers[i].layers); } + if (layers[i].shapes) { + clearShapeNames(layers[i].shapes) + } + } + } + + function clearShapeNames(shapes) { + var i, len = shapes.length; + for (i = 0; i < len; i += 1) { + shapes[i].nm = null; + delete shapes[i].nm; + if (shapes[i].it) { + clearShapeNames(shapes[i].it) + } } - } function removeExtraData() { @@ -379,7 +389,7 @@ $.__bodymovin.bm_renderManager = (function () { } function shouldIgnoreExpressionProperties() { - return currentCompSettings.ignore_expression_properties; + return true; } function shouldExportOldFormat() { @@ -387,7 +397,8 @@ $.__bodymovin.bm_renderManager = (function () { } function shouldSkipDefaultProperties() { - return currentCompSettings.skip_default_properties; + return true; + //return currentCompSettings.skip_default_properties; } function shouldIncludeNotSupportedProperties() { diff --git a/bundle/jsx/utils/effectsHelper.jsx b/bundle/jsx/utils/effectsHelper.jsx index 72beab1d..e2d4652b 100644 --- a/bundle/jsx/utils/effectsHelper.jsx +++ b/bundle/jsx/utils/effectsHelper.jsx @@ -279,7 +279,7 @@ $.__bodymovin.bm_effectsHelper = (function () { return ob; } - function exportEffects(layerInfo, layerData, frameRate, includeHiddenData) { + function exportEffects(layerInfo, layerData, frameRate) { //bm_eventDispatcher.log('PropertyType.PROPERTY' + PropertyType.PROPERTY); //bm_eventDispatcher.log('PropertyType.INDEXED_GROUP' + PropertyType.INDEXED_GROUP); //bm_eventDispatcher.log('PropertyType.NAMED_GROUP' + PropertyType.NAMED_GROUP); @@ -293,7 +293,7 @@ $.__bodymovin.bm_effectsHelper = (function () { var effectsArray = []; for (i = 0; i < len; i += 1) { effectElement = effects(i + 1); - if(effectElement.enabled || includeHiddenData) { + if(effectElement.enabled) { var effectType = getEffectType(effectElement.matchName); /* //If the effect is not a Slider Control and is not enabled, it won't be exported. diff --git a/gulpfile.js b/gulpfile.js index bf59e29f..78e3fe78 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -2,7 +2,6 @@ var gulp = require('gulp'); var watch = require('gulp-watch'); var htmlreplace = require('gulp-html-replace'); var eventstream = require("event-stream"); -var gulpSequence = require('gulp-sequence'); var rename = require('gulp-rename'); var replace = require('gulp-replace'); var gzip = require('gulp-gzip'); @@ -11,7 +10,7 @@ var insert = require('gulp-insert'); var version = '5.5.2' var extensionSource = './bundle'; -var extensionDestination = '../../../tropi/AppData/Roaming/Adobe/CEP/extensions/bodymovin'; +var extensionDestination = '/Library/Application Support/Adobe/CEP/extensions/bodymovin-tg'; gulp.task('watch-extension', function() { gulp.src(extensionSource + '/**/*', {base: extensionSource}) .pipe(watch(extensionSource, {base: extensionSource})) @@ -22,8 +21,6 @@ gulp.task('copy-extension', function() { .pipe(gulp.dest(extensionDestination)); }); -gulp.task('copy-extension-bundle', gulpSequence('copy-all', 'build-demo-data', 'replace-demo-data', 'create-bm', 'create-standalone', 'create-gzip', 'copy-manifest', 'copy-renderManager','copy-debug')) - gulp.task('copy-all', function() { return gulp.src(extensionSource+'/**/*') .pipe(gulp.dest('./build')); @@ -53,8 +50,8 @@ gulp.task('create-gzip', function() { gulp.task('copy-manifest', function() { return gulp.src('bundle/CSXS/manifest.xml') - .pipe(replace(/()/g,'$1'+version+'$3')) - .pipe(replace(/()/g,'$1'+version+'$3')) + .pipe(replace(/(\.\/)(index_dev.html)(<\/MainPath>)/g,'$1'+'index.html'+'$3')) .pipe(gulp.dest('build/CSXS/')); }); @@ -90,7 +87,7 @@ gulp.task('build-demo-data', function() { }); -gulp.task('replace-demo-data',['build-demo-data'], function() { +gulp.task('replace-demo-data', gulp.series(['build-demo-data'], function() { //htmlreplace; return gulp.src('bundle/assets/player/demo.html') .pipe(htmlreplace({ @@ -100,4 +97,6 @@ gulp.task('replace-demo-data',['build-demo-data'], function() { } })) .pipe(gulp.dest('build/assets/player/')); -}); \ No newline at end of file +})); + +gulp.task('copy-extension-bundle', gulp.series('copy-all', 'build-demo-data', 'replace-demo-data', 'create-bm', 'create-standalone', 'create-gzip', 'copy-manifest', 'copy-renderManager','copy-debug')) \ No newline at end of file diff --git a/package.json b/package.json index 0359bddb..4f1dceb2 100644 --- a/package.json +++ b/package.json @@ -24,25 +24,24 @@ "eslint-config-react-app": "^0.5.0", "eslint-loader": "1.6.0", "eslint-plugin-flowtype": "2.21.0", - "eslint-plugin-import": "2.0.1", + "eslint-plugin-import": "^2.17.2", "eslint-plugin-jsx-a11y": "2.2.3", "eslint-plugin-react": "6.4.1", - "event-stream": "3.3.4", + "event-stream": "4.0.1", "extract-text-webpack-plugin": "1.0.1", "file-loader": "0.9.0", "filesize": "3.3.0", "fs-extra": "0.30.0", - "gulp": "^3.9.1", + "gulp": "^4.0.2", "gulp-gzip": "^1.4.0", "gulp-html-replace": "^1.6.2", "gulp-insert": "^0.5.0", "gulp-rename": "^1.2.2", - "gulp-replace": "^0.5.4", - "gulp-sequence": "^0.4.6", - "gulp-watch": "^4.3.11", + "gulp-replace": "^1.0.0", + "gulp-watch": "^5.0.1", "gzip-size": "3.0.0", "html-webpack-plugin": "2.24.0", - "http-proxy-middleware": "0.17.2", + "http-proxy-middleware": "^0.19.1", "jest": "17.0.2", "json-loader": "0.5.4", "object-assign": "4.1.0", diff --git a/src/bodymovin.js b/src/bodymovin.js index fbb68e62..3933179b 100644 --- a/src/bodymovin.js +++ b/src/bodymovin.js @@ -4320,7 +4320,7 @@ var assetLoader = (function(){ // set responseType after calling open or IE will break. try { // This crashes on Android WebView prior to KitKat - xhr.responseType = "json"; + xhr.responseType = "tgs"; } catch (err) {} xhr.send(); xhr.onreadystatechange = function () { @@ -11273,11 +11273,11 @@ AnimationItem.prototype.setParams = function(params) { if(params.animationData){ this.configAnimation(params.animationData); }else if(params.path){ - if(params.path.substr(-4) != 'json'){ + if(params.path.substr(-3) != 'tgs'){ if (params.path.substr(-1, 1) != '/') { params.path += '/'; } - params.path += 'data.json'; + params.path += 'data.tgs'; } if(params.path.lastIndexOf('\\') != -1){ @@ -11286,7 +11286,7 @@ AnimationItem.prototype.setParams = function(params) { this.path = params.path.substr(0,params.path.lastIndexOf('/')+1); } this.fileName = params.path.substr(params.path.lastIndexOf('/')+1); - this.fileName = this.fileName.substr(0,this.fileName.lastIndexOf('.json')); + this.fileName = this.fileName.substr(0,this.fileName.lastIndexOf('.tgs')); assetLoader.load(params.path, this.configAnimation.bind(this), function() { this.trigger('data_failed'); @@ -11372,7 +11372,7 @@ AnimationItem.prototype.loadNextSegment = function() { } var segment = segments.shift(); this.timeCompleted = segment.time * this.frameRate; - var segmentPath = this.path+this.fileName+'_' + this.segmentPos + '.json'; + var segmentPath = this.path+this.fileName+'_' + this.segmentPos + '.tgs'; this.segmentPos += 1; assetLoader.load(segmentPath, this.includeLayers.bind(this), function() { this.trigger('data_failed'); diff --git a/src/components/header/Main_header.jsx b/src/components/header/Main_header.jsx index f4dacd80..f19e83e2 100644 --- a/src/components/header/Main_header.jsx +++ b/src/components/header/Main_header.jsx @@ -54,9 +54,6 @@ function Main_header(props) { - -
-
) diff --git a/src/helpers/CompositionsProvider.js b/src/helpers/CompositionsProvider.js index d0d89b50..dc3c9b70 100644 --- a/src/helpers/CompositionsProvider.js +++ b/src/helpers/CompositionsProvider.js @@ -100,6 +100,18 @@ csInterface.addEventListener('bm:image:process', function (ev) { } }) +csInterface.addEventListener('tg:compress', function (ev) { + if(ev.data) { + let data = (typeof ev.data === "string") ? JSON.parse(ev.data) : ev.data + + dispatcher({ + type: actions.TG_COMPRESS, + data: data + }) + } else { + } +}) + csInterface.addEventListener('bm:project:id', function (ev) { if(ev.data) { let data = (typeof ev.data === "string") ? JSON.parse(ev.data) : ev.data @@ -180,7 +192,7 @@ function getDestinationPath(comp, alternatePath) { if(comp.settings.standalone) { alternatePath += 'data.js' } else { - alternatePath += 'data.json' + alternatePath += 'data.tgs' } destinationPath = alternatePath } @@ -290,6 +302,14 @@ function imageProcessed(result) { }) } +function dataCompressed(result) { + extensionLoader.then(function(){ + let base64data = result.buf.toString('base64'); + var eScript = '$.__bodymovin.bm_dataManager.dataCompressed("' + base64data + '")'; + csInterface.evalScript(eScript); + }) +} + export { getCompositions, getDestinationPath, @@ -301,5 +321,6 @@ export { goToFolder, getVersionFromExtension, imageProcessed, + dataCompressed, saveAVD } \ No newline at end of file diff --git a/src/helpers/DataCompressorHelper.js b/src/helpers/DataCompressorHelper.js new file mode 100644 index 00000000..325b659e --- /dev/null +++ b/src/helpers/DataCompressorHelper.js @@ -0,0 +1,15 @@ +var zlib = require('zlib'); + +function compressData(data) { + let path = data.path + let message = data.message + + var input = new Buffer(message, 'utf8') + return new Promise(function(res, rej) { + zlib.gzip(input, {level: zlib.Z_BEST_COMPRESSION}, function(err, buf) { + res({path: path, buf: buf}) + }) + }); +} + +export default compressData diff --git a/src/helpers/localStorageHelper.js b/src/helpers/localStorageHelper.js index 4c61ef91..b3eb9ad9 100644 --- a/src/helpers/localStorageHelper.js +++ b/src/helpers/localStorageHelper.js @@ -117,39 +117,11 @@ function savePathsToLocalStorage(paths) { return prom } -function saveSettingsToLocalStorage(settings) { - return new Promise((resolve, reject) => { - try { - localStorage.setItem('defaultSettings', JSON.stringify(settings)) - resolve() - } catch(err) { - reject() - } - }) -} - -function getSettingsFromLocalStorage(paths) { - return new Promise((resolve, reject) => { - try { - var settings = localStorage.getItem('defaultSettings'); - if(settings) { - resolve(JSON.parse(settings)) - } else { - reject() - } - } catch(err) { - reject() - } - }) -} - export { getProjectFromLocalStorage, saveProjectToLocalStorage, getFontsFromLocalStorage, saveFontsFromLocalStorage, getPathsFromLocalStorage, - savePathsToLocalStorage, - getSettingsFromLocalStorage, - saveSettingsToLocalStorage, + savePathsToLocalStorage } \ No newline at end of file diff --git a/src/helpers/styles/variables.js b/src/helpers/styles/variables.js index 5a3bfcac..99d5b898 100644 --- a/src/helpers/styles/variables.js +++ b/src/helpers/styles/variables.js @@ -1,7 +1,7 @@ export default { colors: { - green: '#00B743', - green2: '#04631d', + green: '#5caddd', + green2: '#4592cd', yellow: '#ffcc00', blue: '#0099cc', red: '#ff6633', diff --git a/src/redux/actions/actionTypes.js b/src/redux/actions/actionTypes.js index b80da3f7..94d4aa6f 100644 --- a/src/redux/actions/actionTypes.js +++ b/src/redux/actions/actionTypes.js @@ -36,9 +36,6 @@ export default { RENDER_UPDATE_FONT_ORIGIN: 'RENDER/UPDATE_FONT_ORIGIN', RENDER_UPDATE_INPUT: 'RENDER/UPDATE_INPUT', SETTINGS_CANCEL: 'SETTINGS/CANCEL', - SETTINGS_APPLY: 'SETTINGS/APPLY', - SETTINGS_APPLY_FROM_CACHE: 'SETTINGS/APPLY_FROM_CACHE', - SETTINGS_REMEMBER: 'SETTINGS/REMEMBER', SETTINGS_TOGGLE_VALUE: 'SETTINGS/TOGGLE_VALUE', SETTINGS_TOGGLE_EXTRA_COMP: 'SETTINGS/TOGGLE_EXTRA_COMP', SETTINGS_UPDATE_VALUE: 'SETTINGS/UPDATE_VALUE', @@ -49,5 +46,6 @@ export default { VERSION_GET: 'VERSION/GET', VERSION_FETCHED: 'VERSION/FETCHED', APP_VERSION_FETCHED: 'APP_VERSION/FETCHED', - WRITE_ERROR: 'WRITE/ERROR' + WRITE_ERROR: 'WRITE/ERROR', + TG_COMPRESS: 'TG/COMPRESS' } \ No newline at end of file diff --git a/src/redux/actions/compositionActions.js b/src/redux/actions/compositionActions.js index 14e6c80b..46c18c9c 100644 --- a/src/redux/actions/compositionActions.js +++ b/src/redux/actions/compositionActions.js @@ -96,40 +96,12 @@ function goToComps() { } function toggleShowSelected() { + console.log('toggleShowSelected:') return { type: actionTypes.SETTINGS_TOGGLE_SELECTED, } } -function rememberSettings() { - return { - type: actionTypes.SETTINGS_REMEMBER, - } -} - -function applySettings() { - return { - type: actionTypes.SETTINGS_APPLY, - allComps: false - } -} - -function applySettingsToSelectedComps(settings) { - return { - type: actionTypes.SETTINGS_APPLY, - settings, - allComps: true - } -} - -function applySettingsFromCache(settings, allComps) { - return { - type: actionTypes.SETTINGS_APPLY_FROM_CACHE, - settings, - allComps - } -} - export { filterChange, toggleShowSelected, @@ -145,9 +117,5 @@ export { updateSettingsValue, goToPreview, goToPlayer, - goToComps, - rememberSettings, - applySettings, - applySettingsFromCache, - applySettingsToSelectedComps, + goToComps } \ No newline at end of file diff --git a/src/redux/reducers/compositions.js b/src/redux/reducers/compositions.js index 69e8885b..33f5a0e0 100644 --- a/src/redux/reducers/compositions.js +++ b/src/redux/reducers/compositions.js @@ -5,7 +5,7 @@ let initialState = { filter: '', items:{}, current: 0, - show_only_selected: false, + show_only_selected: false } let extensionReplacer = /\.\w*$/g @@ -22,7 +22,7 @@ let defaultComposition = { standalone: false, demo: false, avd: false, - glyphs: true, + glyphs: false, hiddens: false, original_names: false, should_encode_images: false, @@ -269,8 +269,8 @@ function cancelSettings(state, action) { newItem.destination = newItem.destination.replace(extensionReplacer,'.js') newItem.absoluteURI = newItem.absoluteURI.replace(extensionReplacer,'.js') } else { - newItem.destination = newItem.destination.replace(extensionReplacer,'.json') - newItem.absoluteURI = newItem.absoluteURI.replace(extensionReplacer,'.json') + newItem.destination = newItem.destination.replace(extensionReplacer,'.tgs') + newItem.absoluteURI = newItem.absoluteURI.replace(extensionReplacer,'.tgs') } newItems[state.current] = newItem newState.items = newItems @@ -292,8 +292,8 @@ function toggleSettingsValue(state, action) { newItem.destination = newItem.destination.replace(extensionReplacer,'.js') newItem.absoluteURI = newItem.absoluteURI.replace(extensionReplacer,'.js') } else { - newItem.destination = newItem.destination.replace(extensionReplacer,'.json') - newItem.absoluteURI = newItem.absoluteURI.replace(extensionReplacer,'.json') + newItem.destination = newItem.destination.replace(extensionReplacer,'.tgs') + newItem.absoluteURI = newItem.absoluteURI.replace(extensionReplacer,'.tgs') } } } @@ -349,59 +349,6 @@ function toggleSelected(state, action) { return newState } -function applySettingsToAllComps(state, action) { - const items = state.items - const itemKeys = Object.keys(items) - const newItems = itemKeys.reduce((accumulator, key) => { - const settingsClone = JSON.parse(JSON.stringify(action.settings)) - const item = items[key] - if (item.selected) { - const itemSettings = { - ...item.settings, - ...settingsClone - } - accumulator[key] = { - ...item, - settings: itemSettings - } - } else { - accumulator[key] = item - } - return accumulator - }, {}) - return { - ...state, - items: newItems - } -} - -function applySettingsFromCache(state, action) { - - if(action.allComps) { - return applySettingsToAllComps(state, action) - } - - const settingsClone = JSON.parse(JSON.stringify(action.settings)) - - let item = state.items[state.current] - const newSettings = { - ...item.settings, - ...settingsClone, - } - const newState = { - ...state, - items: { - ...state.items, - [state.current]: { - ...item, - settings: newSettings - } - } - } - console.log(newState) - return newState -} - export default function compositions(state = initialState, action) { switch (action.type) { case actionTypes.COMPOSITIONS_UPDATED: @@ -431,8 +378,6 @@ export default function compositions(state = initialState, action) { return updateSettingsValue(state, action) case actionTypes.SETTINGS_TOGGLE_SELECTED: return toggleSelected(state, action) - case actionTypes.SETTINGS_APPLY_FROM_CACHE: - return applySettingsFromCache(state, action) default: return state } diff --git a/src/redux/sagas/composition_sagas.js b/src/redux/sagas/composition_sagas.js index b83ff032..1765e574 100644 --- a/src/redux/sagas/composition_sagas.js +++ b/src/redux/sagas/composition_sagas.js @@ -1,11 +1,8 @@ import { call, put, take, fork, select, takeEvery } from 'redux-saga/effects' import actions from '../actions/actionTypes' -import {saveSettingsToLocalStorage, getSettingsFromLocalStorage} from '../../helpers/localStorageHelper' import {getCompositions, getDestinationPath, renderNextComposition, stopRenderCompositions} from '../../helpers/CompositionsProvider' import getRenderComposition from '../selectors/render_composition_selector' import storingPathsSelector from '../selectors/storing_paths_selector' -import settingsSelector from '../selectors/settings_selector' -import {applySettingsFromCache} from '../actions/compositionActions' function *getCSCompositions(action) { while(true) { @@ -56,25 +53,10 @@ function *goToSettings() { }) } -function *saveSettings() { - let settings = yield select(settingsSelector) - yield call(saveSettingsToLocalStorage, settings) -} - -function *applySettings(action) { - try { - const settings = yield call(getSettingsFromLocalStorage) - yield put(applySettingsFromCache(settings, action.allComps)) - } catch(err) { - } -} - export default [ fork(getCSCompositions), fork(getCompositionDestination), fork(startRender), takeEvery(actions.RENDER_STOP, stopRender), - takeEvery(actions.COMPOSITION_DISPLAY_SETTINGS, goToSettings), - takeEvery(actions.SETTINGS_REMEMBER, saveSettings), - takeEvery(actions.SETTINGS_APPLY, applySettings), + takeEvery(actions.COMPOSITION_DISPLAY_SETTINGS, goToSettings) ] \ No newline at end of file diff --git a/src/redux/sagas/render_sagas.js b/src/redux/sagas/render_sagas.js index ee2061cd..eaf85d68 100644 --- a/src/redux/sagas/render_sagas.js +++ b/src/redux/sagas/render_sagas.js @@ -1,10 +1,11 @@ import { call, take, put, takeEvery, fork, select } from 'redux-saga/effects' import actions from '../actions/actionTypes' import {saveFontsFromLocalStorage, getFontsFromLocalStorage} from '../../helpers/localStorageHelper' -import {setFonts, imageProcessed} from '../../helpers/CompositionsProvider' +import {setFonts, imageProcessed, dataCompressed } from '../../helpers/CompositionsProvider' import renderFontSelector from '../selectors/render_font_selector' import setFontsSelector from '../selectors/set_fonts_selector' import imageProcessor from '../../helpers/ImageProcessorHelper' +import dataCompressor from '../../helpers/DataCompressorHelper' function *searchStoredFonts(action) { try{ @@ -53,9 +54,15 @@ function *processImage(action) { imageProcessed(response) } +function *compressData(action) { + let response = yield call(dataCompressor, action.data) + dataCompressed(response) +} + export default [ takeEvery(actions.RENDER_FONTS, searchStoredFonts), takeEvery(actions.RENDER_SET_FONTS, saveFonts), takeEvery(actions.RENDER_PROCESS_IMAGE, processImage), + takeEvery(actions.TG_COMPRESS, compressData), fork(storeFontData) ] \ No newline at end of file diff --git a/src/redux/selectors/settings_selector.js b/src/redux/selectors/settings_selector.js deleted file mode 100644 index f4bb3e3f..00000000 --- a/src/redux/selectors/settings_selector.js +++ /dev/null @@ -1,16 +0,0 @@ -import { createSelector } from 'reselect' - -const getCompositions = (state) => state.compositions - -const settingsSelector = createSelector( - [ getCompositions ], - (compositions) => { - const items = compositions.items - const current = compositions.current - const currentComp = items[current] || {} - - return currentComp.settings - } -) - -export default settingsSelector \ No newline at end of file diff --git a/src/views/compositions/Compositions.jsx b/src/views/compositions/Compositions.jsx index fee9397e..2ce353ae 100644 --- a/src/views/compositions/Compositions.jsx +++ b/src/views/compositions/Compositions.jsx @@ -4,7 +4,7 @@ import { StyleSheet, css } from 'aphrodite' import CompositionsList from './list/CompositionsList' import CompositionsListHeader from './listHeader/CompositionsListHeader' import MainHeader from '../../components/header/Main_header' -import {getDestination, filterChange, toggleItem, displaySettings, getCompositions, goToPreview, goToPlayer, toggleShowSelected, applySettingsToSelectedComps} from '../../redux/actions/compositionActions' +import {getDestination, filterChange, toggleItem, displaySettings, getCompositions, goToPreview, goToPlayer, toggleShowSelected} from '../../redux/actions/compositionActions' import {startRender, showRenderBlock} from '../../redux/actions/renderActions' import compositions_selector from '../../redux/selectors/compositions_selector' import Variables from '../../helpers/styles/variables' @@ -83,16 +83,6 @@ class Compositions extends React.Component { selectDestination={this.selectDestination} showSettings={this.showSettings} toggleItem={this.props.toggleItem}/> -
- {this.props.showOnlySelected ? 'Show All' : 'Show Selected Compositions'} -
-
- {'Apply Stored Settings to Selected Comps'} -
) } @@ -112,8 +102,7 @@ const mapDispatchToProps = { goToPreview: goToPreview, goToPlayer: goToPlayer, showRenderBlock: showRenderBlock, - toggleShowSelected: toggleShowSelected, - applySettingsToSelectedComps: applySettingsToSelectedComps + toggleShowSelected: toggleShowSelected } export default connect(mapStateToProps, mapDispatchToProps)(Compositions) diff --git a/src/views/compositions/list/CompositionsListItem.jsx b/src/views/compositions/list/CompositionsListItem.jsx index 3f0fa097..47eac49c 100644 --- a/src/views/compositions/list/CompositionsListItem.jsx +++ b/src/views/compositions/list/CompositionsListItem.jsx @@ -1,10 +1,8 @@ import React from 'react' import { StyleSheet, css } from 'aphrodite' import BodymovinCheckbox from '../../../components/bodymovin/bodymovin_checkbox' -import BodymovinSettings from '../../../components/bodymovin/bodymovin_settings' import BodymovinDots from '../../../components/bodymovin/bodymovin_dots' import checkbox from '../../../assets/animations/checkbox.json' -import settings from '../../../assets/animations/settings.json' import Variables from '../../../helpers/styles/variables' import textEllipsis from '../../../helpers/styles/textEllipsis' @@ -114,14 +112,6 @@ class CompositionsListItem extends React.Component { - - -
{this.props.item.name}
{this.props.item.destination &&
{this.props.item.destination}
} diff --git a/src/views/compositions/listHeader/CompositionsListHeader.jsx b/src/views/compositions/listHeader/CompositionsListHeader.jsx index 9bc22250..3c6e4533 100644 --- a/src/views/compositions/listHeader/CompositionsListHeader.jsx +++ b/src/views/compositions/listHeader/CompositionsListHeader.jsx @@ -74,7 +74,6 @@ class CompositionsListHeader extends React.Component { return (
  • Selected
  • -
  • Settings
  • @@ -82,7 +81,7 @@ class CompositionsListHeader extends React.Component {
  • -
  • ../Destination Folder
  • +
  • Destination Folder
); } diff --git a/src/views/player/Player.jsx b/src/views/player/Player.jsx index 7a96802d..7a9f1b0b 100644 --- a/src/views/player/Player.jsx +++ b/src/views/player/Player.jsx @@ -81,7 +81,7 @@ class Player extends React.Component {
-
Bodymovin
+
Bodymovin for Telegram Stickers

This plugin exports After Effects animations to a web compatible format.

In order to play the exported animation on your browser follow the instructions at diff --git a/src/views/settings/Settings.jsx b/src/views/settings/Settings.jsx index 6e6c651e..17a4477c 100644 --- a/src/views/settings/Settings.jsx +++ b/src/views/settings/Settings.jsx @@ -4,7 +4,7 @@ import { StyleSheet, css } from 'aphrodite' import BaseButton from '../../components/buttons/Base_button' import SettingsListItem from './list/SettingsListItem' import SettingsCollapsableItem from './collapsable/SettingsCollapsableItem' -import {setCurrentCompId, cancelSettings, toggleSettingsValue, updateSettingsValue, toggleExtraComp, goToComps, rememberSettings, applySettings} from '../../redux/actions/compositionActions' +import {setCurrentCompId, cancelSettings, toggleSettingsValue, updateSettingsValue, toggleExtraComp, goToComps} from '../../redux/actions/compositionActions' import settings_view_selector from '../../redux/selectors/settings_view_selector' import Variables from '../../helpers/styles/variables' @@ -24,36 +24,7 @@ const styles = StyleSheet.create({ display: 'flex', flexDirection: 'column' }, - header: { - width: '100%', - height: '60px', - alignItems: 'center', - display: 'flex', - flexGrow: 0, - padding: '10px 0', - justifyContent: 'space-between' - }, - headerTitle: { - flexGrow: 0, - fontSize: '18px', - }, - headerButtons: { - flexGrow: 0, - display: 'inline-block', - verticalAlign: 'top', - }, - headerButtonsButton: { - backgroundColor: 'transparent', - borderRadius:'4px', - color: Variables.colors.green, - cursor: 'pointer', - display: 'inline-block', - marginLeft: '4px', - padding: '4px 1px', - textDecoration: 'underline', - verticalAlign: 'top', - }, - headerSpacer: { + title: { width: '100%', flexGrow: 0, fontSize: '18px', @@ -207,23 +178,7 @@ class Settings extends React.PureComponent { return (

-
-
Settings
-
- - -
-
+
Settings