├── .gitignore ├── LICENSE ├── README.md ├── bundle ├── .debug ├── CSInterface.js ├── CSXS │ └── manifest.xml ├── assets │ └── player │ │ ├── demo.html │ │ ├── lottie.js │ │ └── lottie.min.js ├── index_dev.html ├── index_server.html ├── js_fix └── jsx │ ├── JSON.jsx │ ├── compsManager.jsx │ ├── dataManager.jsx │ ├── downloadManager.jsx │ ├── elements │ └── layerElement.jsx │ ├── enums │ └── layerTypes.jsx │ ├── escodegen.jsx │ ├── esprima.jsx │ ├── eventManager.jsx │ ├── helpers │ ├── blendModes.jsx │ ├── boundingBox.jsx │ └── layerResolver.jsx │ ├── hostscript.jsx │ ├── initializer.jsx │ ├── projectManager.jsx │ ├── renderManager.jsx │ └── utils │ ├── ProjectParser.jsx │ ├── PropertyFactory.jsx │ ├── XMPParser.jsx │ ├── bez.jsx │ ├── cameraHelper.jsx │ ├── effectsHelper.jsx │ ├── expressionHelper.jsx │ ├── expressions │ ├── reservedPropertiesHelper.jsx │ ├── valueAssignmentHelper.jsx │ └── variableDeclarationHelper.jsx │ ├── generalUtils.jsx │ ├── keyframeHelper.jsx │ ├── layerStylesHelper.jsx │ ├── markerHelper.jsx │ ├── maskHelper.jsx │ ├── shapeHelper.jsx │ ├── sourceHelper.jsx │ ├── textAnimatorHelper.jsx │ ├── textHelper.jsx │ ├── textShapeHelper.jsx │ ├── timeremapHelper.jsx │ ├── transformHelper.jsx │ └── transformation-matrix.jsx ├── config ├── env.js ├── jest │ ├── cssTransform.js │ └── fileTransform.js ├── paths.js ├── polyfills.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── gulpfile.js ├── index_dev.html ├── package.json ├── player ├── lottie.js └── lottie.min.js ├── public ├── assets │ ├── fonts │ │ ├── Roboto-Black.ttf │ │ ├── Roboto-Bold.ttf │ │ └── Roboto-Regular.ttf │ └── svg │ │ ├── down_arrow.svg │ │ └── up_arrow.svg ├── favicon.ico ├── index.html ├── main.css └── reset.css ├── scripts ├── build.js ├── start.js └── test.js └── src ├── App.css ├── App.js ├── App.test.js ├── assets ├── animations │ ├── Flame.json │ ├── alert.json │ ├── bear.json │ ├── bm.json │ ├── checkbox.json │ ├── cube.json │ ├── data.json │ ├── data2.json │ ├── dots.json │ ├── expander.json │ ├── fluido.json │ ├── fonts.json │ ├── ghost.json │ ├── nessie.json │ ├── refresh.json │ ├── settings.json │ └── snapshot.json ├── fonts │ ├── Roboto-Black.ttf │ ├── Roboto-Bold.ttf │ └── Roboto-Regular.ttf └── svg │ ├── cancel_button.svg │ ├── complete_icon.svg │ ├── down_arrow.svg │ ├── folder_button.svg │ ├── glass.svg │ ├── preview_thumb.svg │ ├── refresh.svg │ └── up_arrow.svg ├── bodymovin.js ├── components ├── alerts │ └── Alerts.jsx ├── bodymovin │ ├── bodymovin.jsx │ ├── bodymovin_checkbox.jsx │ ├── bodymovin_dots.jsx │ ├── bodymovin_folder.jsx │ ├── bodymovin_refresh.jsx │ ├── bodymovin_settings.jsx │ └── bodymovin_toggle.jsx ├── buttons │ └── Base_button.jsx ├── footer │ └── Footer.jsx ├── header │ └── Main_header.jsx └── range │ └── Range.jsx ├── helpers ├── CSInterface.js ├── CSInterfaceHelper.js ├── CompositionsProvider.js ├── CompositionsStateSync.js ├── DataCompressorHelper.js ├── ExtensionLoader.js ├── FileBrowser.js ├── FileLoader.js ├── FileSaver.js ├── ImageProcessorHelper.js ├── RenderBridge.js ├── fs_proxy.js ├── localStorageHelper.js ├── storeDispatcher.js ├── styles │ ├── textEllipsis.js │ └── variables.js └── versionValidator.js ├── index.css ├── index.js ├── logo.svg ├── lottie.js ├── redux ├── actions │ ├── actionTypes.js │ ├── compositionActions.js │ ├── generalActions.js │ ├── previewActions.js │ └── renderActions.js ├── reducers │ ├── alerts.js │ ├── compositions.js │ ├── index.js │ ├── paths.js │ ├── preview.js │ ├── project.js │ ├── render.js │ └── routes.js ├── sagas │ ├── composition_sagas.js │ ├── index.js │ ├── preview_sagas.js │ ├── project_sagas.js │ └── render_sagas.js └── selectors │ ├── composition_selector.js │ ├── compositions_selector.js │ ├── fonts_view_selector.js │ ├── preview_view_selector.js │ ├── render_composition_selector.js │ ├── render_font_selector.js │ ├── render_selector.js │ ├── set_fonts_selector.js │ ├── settings_comp_selector.js │ ├── settings_view_selector.js │ ├── storing_data_selector.js │ └── storing_paths_selector.js ├── reset.css └── views ├── ViewsContainer.jsx ├── compositions ├── Compositions.jsx ├── list │ ├── CompositionsList.jsx │ └── CompositionsListItem.jsx └── listHeader │ ├── CompositionsListHeader.css │ └── CompositionsListHeader.jsx ├── fonts ├── Fonts.jsx └── form │ └── FontForm.jsx ├── player └── Player.jsx ├── preview ├── Preview.jsx ├── Settings.jsx ├── current_renders │ └── CurrentRenders.jsx ├── header │ └── PreviewHeader.jsx ├── scrubber │ └── PreviewScrubber.jsx └── viewer │ └── PreviewViewer.jsx ├── render ├── Render.jsx └── list │ └── RenderItem.jsx └── settings ├── Settings.jsx ├── collapsable └── SettingsCollapsableItem.jsx └── list └── SettingsListItem.jsx /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log 16 | signer.bat 17 | bodymovinCert.p12 18 | bodymovin.zxp 19 | ZXPSignCmd.exe 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 hernan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bodymovin for Telegram Stickers — After Effects extension for exporting Telegram animated stickers 2 | 3 | Bodymovin-TG is designed to help you export your animations in the **.TGS** format supported by the Telegram Animated Stickers platform. 4 | 5 | ### Installing 6 | 7 | 1. Close After Effects if it's open 8 | 2. Install [the ZXP Installer][zxp_installer] 9 | 3. Download the latest version of [bodymovin-tg][bodymovin_tg] (*bodymovin-tg.zxp*) 10 | 4. Open the ZXP Installer and drag the bodymovin-tg extension into the ZXP Installer window 11 | 5. Open After Effects. 12 | **Windows:** Go to Edit > Preferences > Scripting & Expressions > and check "Allow Scripts to Write Files and Access Network" 13 | **Mac:** Go to Adobe After Effects > Preferences > Scripting & Expressions > and check "Allow Scripts to Write Files and Access Network" 14 | 6. Under the menu Window > Extensions you should see **Bodymovin for Telegram Stickers**. Now you're good to go! 15 | 16 | For more information on creating and exporting Lottie animations, refer to [this guide][ae_guide]. 17 | 18 | For more information on Telegram Animated Stickers, see [this page][animated_stickers]. 19 | 20 | [//]: # (LINKS) 21 | [zxp_installer]: https://zxpinstaller.com 22 | [bodymovin_tg]: https://github.com/TelegramMessenger/bodymovin-extension/releases 23 | [ae_guide]: http://airbnb.io/lottie/#/after-effects?id=creating-lottie-animations 24 | [animated_stickers]: https://core.telegram.org/animated_stickers 25 | -------------------------------------------------------------------------------- /bundle/.debug: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /bundle/CSXS/manifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ./index_dev.html 38 | ./jsx/hostscript.jsx 39 | 40 | --enable-nodejs 41 | --mixed-context 42 | --enable-media-stream 43 | 44 | 45 | 46 | true 47 | 48 | 49 | Panel 50 | Bodymovin for Telegram Stickers 51 | 52 | 53 | 500 54 | 300 55 | 56 | 64 | 65 | 66 | 67 | ./icons/iconNormal.png 68 | ./icons/iconRollover.png 69 | ./icons/iconDisabled.png 70 | ./icons/iconDarkNormal.png 71 | ./icons/iconDarkRollover.png 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /bundle/assets/player/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | 40 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /bundle/index_dev.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /bundle/index_server.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 18 | 19 | 20 | 21 | 22 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /bundle/js_fix: -------------------------------------------------------------------------------- 1 | TreeElement.prototype.isEventWithinDisclosureTriangle = function(event) { 2 | var computedLeftPadding = 10; 3 | if(window.getComputedStyle(this._listItemNode).getPropertyCSSValue) { 4 | computedLeftPadding = window.getComputedStyle(this._listItemNode).getPropertyCSSValue("padding-left").getFloatValue(CSSPrimitiveValue.CSS_PX); 5 | } 6 | var left = this._listItemNode.totalOffsetLeft() + computedLeftPadding; 7 | return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren; 8 | } -------------------------------------------------------------------------------- /bundle/jsx/compsManager.jsx: -------------------------------------------------------------------------------- 1 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 2 | /*global bm_projectManager, bm_eventDispatcher, Folder, File */ 3 | 4 | $.__bodymovin.bm_compsManager = (function () { 5 | 'use strict'; 6 | 7 | var compositions = [], projectComps, ob, currentComposition; 8 | var bm_eventDispatcher = $.__bodymovin.bm_eventDispatcher; 9 | var bm_projectManager = $.__bodymovin.bm_projectManager; 10 | 11 | 12 | function getCompositionData(comp) { 13 | var i = 0, len = compositions.length, compData; 14 | while (i < len) { 15 | if (compositions[i].id === comp.id) { 16 | compData = compositions[i]; 17 | break; 18 | } 19 | i += 1; 20 | } 21 | if (!compData) { 22 | compData = { 23 | id: comp.id, 24 | name: comp.name 25 | }; 26 | } 27 | 28 | return compData; 29 | } 30 | 31 | function searchCompositionDestination(id, absoluteURI, standalone) { 32 | /*var i = 0, len = compositions.length, compData; 33 | while (i < len) { 34 | if (compositions[i].id === id) { 35 | compData = compositions[i]; 36 | break; 37 | } 38 | i += 1; 39 | }*/ 40 | var uri; 41 | if (absoluteURI) { 42 | uri = absoluteURI; 43 | } else { 44 | uri = Folder.desktop.absoluteURI + '/data'; 45 | uri += standalone ? '.js' : '.tgs'; 46 | } 47 | var f = new File(uri); 48 | var saveFileData = f.saveDlg(); 49 | if (saveFileData !== null) { 50 | //compData.absoluteURI = saveFileData.absoluteURI; 51 | //compData.destination = saveFileData.fsName; 52 | var compositionDestinationData = { 53 | absoluteURI: saveFileData.absoluteURI, 54 | destination: saveFileData.fsName, 55 | id: id 56 | } 57 | bm_eventDispatcher.sendEvent('bm:composition:destination_set', compositionDestinationData); 58 | } 59 | } 60 | 61 | //Opens folder where json is rendered 62 | function browseFolder(destination) { 63 | var file = new File(destination); 64 | file.parent.execute(); 65 | } 66 | 67 | function updateData(){ 68 | bm_projectManager.checkProject(); 69 | getCompositions(); 70 | } 71 | 72 | function getCompositions() { 73 | var compositions = []; 74 | projectComps = bm_projectManager.getCompositions(); 75 | var i, len = projectComps.length; 76 | for (i = 0; i < len; i += 1) { 77 | compositions.push(getCompositionData(projectComps[i])); 78 | } 79 | bm_eventDispatcher.sendEvent('bm:compositions:list', compositions); 80 | } 81 | 82 | function complete() { 83 | bm_eventDispatcher.sendEvent('bm:render:complete', currentComposition.id); 84 | } 85 | 86 | function renderComposition(compositionData) { 87 | ob.cancelled = false; 88 | currentComposition = compositionData; 89 | projectComps = bm_projectManager.getCompositions(); 90 | var comp; 91 | var i = 0, len = projectComps.length; 92 | while (i < len) { 93 | if (projectComps[i].id === currentComposition.id) { 94 | comp = projectComps[i]; 95 | break; 96 | } 97 | i += 1; 98 | } 99 | /*if (!comp) { 100 | bm_eventDispatcher.sendEvent('bm:render:complete'); 101 | return; 102 | } 103 | bm_eventDispatcher.sendEvent('bm:render:complete', currentComposition.id); 104 | return;*/ 105 | bm_eventDispatcher.sendEvent('bm:render:start', currentComposition.id); 106 | var destination = currentComposition.absoluteURI; 107 | var fsDestination = currentComposition.destination; 108 | $.__bodymovin.bm_renderManager.render(comp, destination, fsDestination, currentComposition.settings); 109 | } 110 | 111 | function renderComplete() { 112 | bm_eventDispatcher.sendEvent('bm:render:complete', currentComposition.id); 113 | } 114 | 115 | function cancel() { 116 | ob.cancelled = true; 117 | $.__bodymovin.bm_textShapeHelper.removeComps(); 118 | bm_eventDispatcher.sendEvent('bm:render:cancel'); 119 | } 120 | 121 | ob = { 122 | updateData : updateData, 123 | searchCompositionDestination : searchCompositionDestination, 124 | renderComplete : renderComplete, 125 | browseFolder : browseFolder, 126 | renderComposition : renderComposition, 127 | cancel : cancel, 128 | cancelled: false 129 | }; 130 | 131 | return ob; 132 | }()); -------------------------------------------------------------------------------- /bundle/jsx/downloadManager.jsx: -------------------------------------------------------------------------------- 1 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 2 | /*global Folder, File, bm_eventDispatcher, $ */ 3 | $.__bodymovin.bm_downloadManager = (function () { 4 | 'use strict'; 5 | var bm_eventDispatcher = $.__bodymovin.bm_eventDispatcher; 6 | var ob = {}; 7 | 8 | function getPlayer(zippedFlag) { 9 | var extensionPath = $.fileName.split('/').slice(0, -1).join('/') + '/'; 10 | var folder = new Folder(extensionPath); 11 | folder = folder.parent; 12 | var fileName; 13 | if (zippedFlag) { 14 | fileName = 'lottie.js.gz'; 15 | } else { 16 | fileName = 'lottie.js'; 17 | } 18 | var bmFile = new File(folder.absoluteURI + '/assets/player/' + fileName); 19 | bm_eventDispatcher.log(bmFile.exists) 20 | bm_eventDispatcher.log(bmFile.absoluteURI) 21 | 22 | var uri = Folder.desktop.absoluteURI + '/lottie.js'; 23 | var f = new File(uri); 24 | var saveFileData = f.saveDlg(); 25 | if (saveFileData !== null) { 26 | bm_eventDispatcher.log('PASO 1' + saveFileData.absoluteURI) 27 | if (bmFile.copy(saveFileData.absoluteURI)) { 28 | bm_eventDispatcher.sendEvent('bm:alert', {message: 'File saved', type: 'success'}); 29 | } else { 30 | bm_eventDispatcher.log('PASO 2') 31 | bm_eventDispatcher.sendEvent('bm:alert', {message: 'File could not be saved', type: 'fail'}); 32 | } 33 | } 34 | } 35 | 36 | function getStandaloneData() { 37 | var extensionPath = $.fileName.split('/').slice(0, -1).join('/') + '/'; 38 | var folder = new Folder(extensionPath); 39 | folder = folder.parent; 40 | var bmFile = new File(folder.absoluteURI + '/assets/player/standalone.js'); 41 | bmFile.open('r'); 42 | var str = bmFile.read(); 43 | return str; 44 | } 45 | 46 | function getDemoData() { 47 | var extensionPath = $.fileName.split('/').slice(0, -1).join('/') + '/'; 48 | var folder = new Folder(extensionPath); 49 | folder = folder.parent; 50 | var bmFile = new File(folder.absoluteURI + '/assets/player/demo.html'); 51 | bmFile.open('r'); 52 | var str = bmFile.read(); 53 | return str; 54 | } 55 | 56 | ob.getPlayer = getPlayer; 57 | ob.getStandaloneData = getStandaloneData; 58 | ob.getDemoData = getDemoData; 59 | 60 | return ob; 61 | }()); -------------------------------------------------------------------------------- /bundle/jsx/enums/layerTypes.jsx: -------------------------------------------------------------------------------- 1 | $.__bodymovin.layerTypes = { 2 | precomp : 0, 3 | solid : 1, 4 | still : 2, 5 | nullLayer : 3, 6 | shape : 4, 7 | text : 5, 8 | audio : 6, 9 | pholderVideo : 7, 10 | imageSeq : 8, 11 | video : 9, 12 | pholderStill : 10, 13 | guide : 11, 14 | adjustment : 12, 15 | camera : 13, 16 | light : 14 17 | } -------------------------------------------------------------------------------- /bundle/jsx/eventManager.jsx: -------------------------------------------------------------------------------- 1 | /*jslint vars: true , plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 2 | /*global $, Folder, ExternalObject, CSXSEvent*/ 3 | 4 | $.__bodymovin.bm_eventDispatcher = (function () { 5 | 'use strict'; 6 | 7 | var JSON = $.__bodymovin.JSON; 8 | var xLib; 9 | try { 10 | xLib = new ExternalObject('lib:\PlugPlugExternalObject'); 11 | } catch (e) { alert("Missing ExternalObject: "); } 12 | 13 | function sendEvent(type, data) { 14 | if (xLib) { 15 | if (data && data instanceof Object) { 16 | data = JSON.stringify(data); 17 | } 18 | if(typeof data === 'number') { 19 | data = data.toString() 20 | } 21 | var eventObj = new CSXSEvent(); 22 | eventObj.type = type; 23 | eventObj.data = data || ''; 24 | eventObj.dispatch(); 25 | } 26 | } 27 | 28 | function log(data) { 29 | sendEvent('console:log', data); 30 | } 31 | 32 | var ob = { 33 | sendEvent : sendEvent, 34 | log : log 35 | }; 36 | return ob; 37 | }()); -------------------------------------------------------------------------------- /bundle/jsx/helpers/boundingBox.jsx: -------------------------------------------------------------------------------- 1 | $.__bodymovin.bm_boundingBox = (function () { 2 | var ob = { 3 | getBoundingBox: getBoundingBox, 4 | isBoxInContainer: isBoxInContainer 5 | }; 6 | 7 | function getPoint(p1, p2, p3, p4, t) { 8 | var a = p1[0], b = p2[0], c = p3[0], d = p4[0]; 9 | var diffT = 1-t; 10 | var diffTSq = diffT*diffT; 11 | var diffTCu = diffTSq*diffT; 12 | var tSq = t * t; 13 | var tCu = tSq * t; 14 | var x = a * diffTCu + b * 3 * diffTSq * t + c * 3 * diffT * tSq + d * tCu; 15 | a = p1[1]; 16 | b = p2[1]; 17 | c = p3[1]; 18 | d = p4[1]; 19 | var y = a * diffTCu + b * 3 * diffTSq * t + c * 3 * diffT * tSq + d * tCu; 20 | return [x, y]; 21 | } 22 | 23 | function getTPos(p1, p2, p3, p4, arr) { 24 | var i; 25 | for (i = 0; i < 2; i += 1) { 26 | var c1 = p1[i], c2 = p2[i], c3 = p3[i], c4 = p4[i]; 27 | var a = 3 * (-c1 + 3 * c2 - 3 * c3 + c4); 28 | var b = 6 * (c1 - 2 * c2 + c3); 29 | var c = 3 * (c2 - c1); 30 | var toSquareTerm = b*b - 4 * a * c; 31 | if (toSquareTerm >= 0) { 32 | var sqRtToSquareTerm = Math.sqrt(toSquareTerm); 33 | var t1 = (-b + sqRtToSquareTerm) / (2 * a); 34 | var t2 = (-b - sqRtToSquareTerm) / (2 * a); 35 | if (t1 >= 0 && t1 <= 1) { 36 | arr.push(getPoint(p1, p2, p3, p4, t1)); 37 | } 38 | if (t2 >= 0 && t2 <= 1) { 39 | arr.push(getPoint(p1, p2, p3, p4, t2)); 40 | } 41 | } 42 | } 43 | } 44 | 45 | function getCurveBox(p1, p2, p3, p4) { 46 | var bounds = { 47 | l:10000000000, 48 | r:-10000000000, 49 | t:10000000000, 50 | b:-10000000000 51 | } 52 | var pts = [p1,p4]; 53 | getTPos(p1, p2, p3, p4, pts); 54 | 55 | var minX = bounds.l, minY = bounds.t, maxX = bounds.r, maxY = bounds.b, pt; 56 | var i, len = pts.length; 57 | for (i = 0; i < len; i += 1) { 58 | pt = pts[i]; 59 | if (minX > pt[0]) { 60 | minX = pt[0]; 61 | } 62 | if (maxX < pt[0]) { 63 | maxX = pt[0]; 64 | } 65 | if (minY > pt[1]) { 66 | minY = pt[1]; 67 | } 68 | if (maxY < pt[1]) { 69 | maxY = pt[1]; 70 | } 71 | } 72 | bounds.l = minX; 73 | bounds.t = minY; 74 | bounds.r = maxX; 75 | bounds.b = maxY; 76 | return bounds; 77 | } 78 | 79 | function getBoundingBox(shapeData, matrix) { 80 | var i, len = shapeData.v.length; 81 | var pt1,pt2,pt3,pt4, curveBox, box; 82 | var box = { 83 | l:10000000000, 84 | r:-10000000000, 85 | t:10000000000, 86 | b:-10000000000 87 | } 88 | 89 | for(i=0;i= box.r && container.t <= box.t && container.b >= box.b 125 | } 126 | 127 | return ob; 128 | }()) -------------------------------------------------------------------------------- /bundle/jsx/helpers/layerResolver.jsx: -------------------------------------------------------------------------------- 1 | $.__bodymovin.getLayerType = (function () { 2 | 3 | var layerTypes = $.__bodymovin.layerTypes; 4 | 5 | function avLayerType(lObj) { 6 | var lSource = lObj.source; 7 | if (lSource instanceof CompItem) { 8 | return layerTypes.precomp; 9 | } 10 | var lMainSource = lSource.mainSource; 11 | var lFile = lMainSource.file; 12 | if (!lObj.hasVideo) { 13 | return layerTypes.audio; 14 | } else if (lSource instanceof CompItem) { 15 | return layerTypes.precomp; 16 | } else if (lSource.frameDuration < 1) { 17 | if (lMainSource instanceof PlaceholderSource) { 18 | return layerTypes.pholderVideo; 19 | } else if (lSource.name.toString().indexOf("].") !== -1) { 20 | return layerTypes.imageSeq; 21 | } else { 22 | if(lMainSource.isStill) { 23 | return layerTypes.still; 24 | } else { 25 | return layerTypes.video; 26 | } 27 | } 28 | } else if (lSource.frameDuration === 1) { 29 | if (lMainSource instanceof PlaceholderSource) { 30 | return layerTypes.pholderStill; 31 | } else if (lMainSource.color) { 32 | return layerTypes.solid; 33 | } else { 34 | return layerTypes.still; 35 | } 36 | } 37 | } 38 | return function (layerOb) { 39 | try { 40 | var curLayer, instanceOfArray, instanceOfArrayLength, result; 41 | curLayer = layerOb; 42 | instanceOfArray = [AVLayer, CameraLayer, LightLayer, ShapeLayer, TextLayer]; 43 | instanceOfArrayLength = instanceOfArray.length; 44 | /*if (curLayer.guideLayer) { 45 | return layerTypes.guide; 46 | } else */if (curLayer.adjustmentLayer) { 47 | return layerTypes.adjustment; 48 | } else if (curLayer.nullLayer) { 49 | return layerTypes.nullLayer; 50 | } 51 | var i; 52 | for (i = 0; i < instanceOfArrayLength; i++) { 53 | if (curLayer instanceof instanceOfArray[i]) { 54 | result = instanceOfArray[i].name; 55 | break; 56 | } 57 | } 58 | if (result === "AVLayer") { 59 | result = avLayerType(curLayer); 60 | } else if (result === "CameraLayer") { 61 | result = layerTypes.camera; 62 | } else if (result === "LightLayer") { 63 | result = layerTypes.light; 64 | } else if (result === "ShapeLayer") { 65 | result = layerTypes.shape; 66 | } else if (result === "TextLayer") { 67 | result = layerTypes.text; 68 | } 69 | return result; 70 | } catch (err) {alert(err.line.toString + " " + err.toString()); } 71 | }; 72 | }()); -------------------------------------------------------------------------------- /bundle/jsx/hostscript.jsx: -------------------------------------------------------------------------------- 1 | /*jslint vars: true , plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 2 | /*global bm_eventDispatcher, File, Folder*/ 3 | 4 | $.__bodymovin = $.__bodymovin || {esprima:{}} 5 | $.__bodymovin.bm_main = (function () { 6 | 'use strict'; 7 | var ob = {}; 8 | 9 | function browseFile(path) { 10 | //openDlg() 11 | path = path ? path : Folder.desktop.absoluteURI 12 | var f = new File(path); 13 | var openFileData = f.openDlg(); 14 | if (openFileData !== null) { 15 | $.__bodymovin.bm_eventDispatcher.sendEvent('bm:file:uri', openFileData.fsName); 16 | } else { 17 | $.__bodymovin.bm_eventDispatcher.sendEvent('bm:file:cancel'); 18 | } 19 | 20 | } 21 | 22 | ob.browseFile = browseFile; 23 | 24 | return ob; 25 | }()); -------------------------------------------------------------------------------- /bundle/jsx/initializer.jsx: -------------------------------------------------------------------------------- 1 | /*jslint vars: true , plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 2 | /*global $*/ 3 | 4 | 5 | $.__bodymovin = $.__bodymovin || {esprima:{}} 6 | var extensionPath = $.fileName.split('/').slice(0, -1).join('/') + '/'; 7 | 8 | $.evalFile(extensionPath + 'JSON.jsx'); 9 | $.evalFile(extensionPath + 'enums/layerTypes.jsx'); 10 | $.evalFile(extensionPath + 'eventManager.jsx'); 11 | $.evalFile(extensionPath + 'downloadManager.jsx'); 12 | $.evalFile(extensionPath + 'utils/generalUtils.jsx'); 13 | $.evalFile(extensionPath + 'utils/expressions/reservedPropertiesHelper.jsx'); 14 | $.evalFile(extensionPath + 'utils/expressions/valueAssignmentHelper.jsx'); 15 | $.evalFile(extensionPath + 'utils/expressions/variableDeclarationHelper.jsx'); 16 | $.evalFile(extensionPath + 'utils/expressionHelper.jsx'); 17 | $.evalFile(extensionPath + 'esprima.jsx'); 18 | $.evalFile(extensionPath + 'escodegen.jsx'); 19 | $.evalFile(extensionPath + 'utils/bez.jsx'); 20 | $.evalFile(extensionPath + 'utils/PropertyFactory.jsx'); 21 | $.evalFile(extensionPath + 'utils/keyframeHelper.jsx'); 22 | $.evalFile(extensionPath + 'utils/transformHelper.jsx'); 23 | $.evalFile(extensionPath + 'utils/maskHelper.jsx'); 24 | $.evalFile(extensionPath + 'utils/timeremapHelper.jsx'); 25 | $.evalFile(extensionPath + 'utils/effectsHelper.jsx'); 26 | $.evalFile(extensionPath + 'utils/layerStylesHelper.jsx') 27 | $.evalFile(extensionPath + 'utils/cameraHelper.jsx'); 28 | $.evalFile(extensionPath + 'utils/XMPParser.jsx'); 29 | $.evalFile(extensionPath + 'utils/ProjectParser.jsx'); 30 | $.evalFile(extensionPath + 'utils/markerHelper.jsx'); 31 | $.evalFile(extensionPath + 'utils/textAnimatorHelper.jsx'); 32 | $.evalFile(extensionPath + 'utils/textHelper.jsx'); 33 | $.evalFile(extensionPath + 'helpers/boundingBox.jsx'); 34 | $.evalFile(extensionPath + 'helpers/layerResolver.jsx'); 35 | $.evalFile(extensionPath + 'helpers/blendModes.jsx'); 36 | $.evalFile(extensionPath + 'elements/layerElement.jsx'); 37 | $.evalFile(extensionPath + 'projectManager.jsx'); 38 | $.evalFile(extensionPath + 'compsManager.jsx'); 39 | $.evalFile(extensionPath + 'dataManager.jsx'); 40 | $.evalFile(extensionPath + 'renderManager.jsx'); 41 | $.evalFile(extensionPath + 'utils/sourceHelper.jsx'); 42 | $.evalFile(extensionPath + 'utils/shapeHelper.jsx'); 43 | $.evalFile(extensionPath + 'utils/textShapeHelper.jsx'); 44 | $.evalFile(extensionPath + 'utils/transformation-matrix.jsx'); 45 | 46 | var globalVariables = ['bm_eventDispatcher','bm_generalUtils','bm_expressionHelper','esprima', 'escodegen' 47 | , 'bez', 'PropertyFactory', 'bm_keyframeHelper', 'bm_transformHelper', 'bm_maskHelper', 'bm_timeremapHelper' 48 | , 'bm_effectsHelper', 'bm_layerStylesHelper', 'bm_cameraHelper', 'bm_XMPHelper', 'bm_ProjectHelper', 'bm_markerHelper' 49 | , 'bm_textHelper', 'bm_boundingBox', 'bm_layerElement', 'bm_projectManager', 'bm_compsManager', 'bm_dataManager' 50 | , 'bm_renderManager', 'bm_downloadManager', 'bm_sourceHelper', 'bm_shapeHelper', 'bm_textAnimatorHelper' 51 | , 'bm_textShapeHelper'] 52 | var i, len = globalVariables.length; 53 | for(i = 0; i < len; i += 1) { 54 | if(this[globalVariables[i]]) { 55 | this[globalVariables[i]] = null; 56 | delete this[globalVariables[i]]; 57 | //$.__bodymovin.bm_eventDispatcher.log(globalVariables[i] + ' exists'); 58 | } else { 59 | //$.__bodymovin.bm_eventDispatcher.log(globalVariables[i] + ' not exists'); 60 | } 61 | } -------------------------------------------------------------------------------- /bundle/jsx/projectManager.jsx: -------------------------------------------------------------------------------- 1 | /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 2 | /*global app, bm_eventDispatcher */ 3 | 4 | $.__bodymovin.bm_projectManager = (function () { 5 | 'use strict'; 6 | var bm_eventDispatcher = $.__bodymovin.bm_eventDispatcher; 7 | var bm_generalUtils = $.__bodymovin.bm_generalUtils; 8 | var bm_XMPHelper = $.__bodymovin.bm_XMPHelper; 9 | var commands = {}; 10 | var projectId = ''; 11 | var project; 12 | function getItemType(item) { 13 | var getType = {}; 14 | var type = getType.toString.call(item); 15 | var itemType = ''; 16 | switch (type) { 17 | case "[object FolderItem]": 18 | itemType = 'Folder'; 19 | break; 20 | case "[object FootageItem]": 21 | itemType = 'Footage'; 22 | break; 23 | case "[object CompItem]": 24 | itemType = 'Comp'; 25 | break; 26 | default: 27 | itemType = type; 28 | break; 29 | 30 | } 31 | return itemType; 32 | } 33 | 34 | function searchCommands() { 35 | //commands.shapesFromText = app.findMenuCommandId("Create Shapes from Text"); 36 | //commands.duplicate = app.findMenuCommandId("Duplicate"); 37 | commands.shapesFromText = 3781; 38 | commands.duplicate = 2080; 39 | } 40 | 41 | function getCommandID(key) { 42 | return commands[key]; 43 | } 44 | 45 | function checkProject() { 46 | //bm:application:id 47 | var storedProjectId; 48 | if(!bm_XMPHelper.created){ 49 | } else { 50 | storedProjectId = bm_XMPHelper.getMetadata('project_id'); 51 | } 52 | if(!app.project || app.project.numItems === 0) { 53 | return; 54 | } 55 | if(!storedProjectId) { 56 | storedProjectId = bm_generalUtils.random(20); 57 | bm_XMPHelper.setMetadata('project_id',storedProjectId); 58 | } 59 | if(projectId !== storedProjectId){ 60 | projectId = storedProjectId; 61 | bm_eventDispatcher.sendEvent('bm:project:id', {id:projectId}); 62 | } 63 | } 64 | 65 | function getCompositions() { 66 | 67 | project = app.project; 68 | var arr = []; 69 | if (!project) { 70 | return; 71 | } 72 | var i, numItems = project.numItems; 73 | for (i = 0; i < numItems; i += 1) { 74 | if (getItemType(project.item(i + 1)) === 'Comp') { 75 | arr.push(project.item(i + 1)); 76 | } 77 | } 78 | return arr; 79 | } 80 | 81 | function getCompositionById(id){ 82 | var i, numItems = project.numItems; 83 | for (i = 0; i < numItems; i += 1) { 84 | if (getItemType(project.item(i + 1)) === 'Comp') { 85 | if(project.item(i + 1).id == id){ 86 | return project.item(i + 1); 87 | } 88 | } 89 | } 90 | } 91 | 92 | var ob = { 93 | checkProject: checkProject, 94 | getCompositions: getCompositions, 95 | getCompositionById: getCompositionById, 96 | searchCommands: searchCommands, 97 | getCommandID: getCommandID 98 | }; 99 | return ob; 100 | }()); -------------------------------------------------------------------------------- /bundle/jsx/utils/XMPParser.jsx: -------------------------------------------------------------------------------- 1 | $.__bodymovin.bm_XMPHelper = (function(){ 2 | var ob = {}; 3 | ob.init = init; 4 | ob.created = true; 5 | ob.setMetadata = setMetadata; 6 | ob.getMetadata = getMetadata; 7 | var namespace = 'bodymovin'; 8 | 9 | function init(){ 10 | var proj = app.project; 11 | 12 | if(ExternalObject.AdobeXMPScript == undefined) { 13 | ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript'); 14 | } 15 | var schemaNS = XMPMeta.getNamespaceURI(namespace); 16 | if(schemaNS == "" || schemaNS == undefined) { 17 | schemaNS = XMPMeta.registerNamespace(namespace, namespace); 18 | } 19 | } 20 | 21 | function setMetadata(property, value) { 22 | var proj = app.project; 23 | 24 | if(ExternalObject.AdobeXMPScript == undefined) { 25 | ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript'); 26 | } 27 | var metaData = new XMPMeta(proj.xmpPacket); 28 | var schemaNS = XMPMeta.getNamespaceURI(namespace); 29 | if(schemaNS == "" || schemaNS == undefined) { 30 | } else { 31 | try { 32 | metaData.setProperty(schemaNS, namespace+":"+property, value); 33 | ob.created = true; 34 | } catch(err) { 35 | ob.created = false; 36 | } 37 | } 38 | proj.xmpPacket = metaData.serialize(); 39 | } 40 | 41 | 42 | // To get metadata from within a script, a function like so: 43 | function getMetadata(property) { 44 | var proj = app.project; 45 | 46 | 47 | if(ExternalObject.AdobeXMPScript == undefined) { 48 | ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript'); 49 | } 50 | var metaData = new XMPMeta(proj.xmpPacket); 51 | var schemaNS = XMPMeta.getNamespaceURI(namespace); 52 | if(schemaNS == "" || schemaNS == undefined) { 53 | return undefined; 54 | } 55 | var metaValue = metaData.getProperty(schemaNS, property); 56 | if(!metaValue) { 57 | return undefined; 58 | } 59 | return metaValue.value; 60 | } 61 | 62 | init(); 63 | 64 | return ob; 65 | }()) -------------------------------------------------------------------------------- /bundle/jsx/utils/cameraHelper.jsx: -------------------------------------------------------------------------------- 1 | /*jslint vars: true , plusplus: true, continue:true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 2 | /*global bm_keyframeHelper*/ 3 | $.__bodymovin.bm_cameraHelper = (function () { 4 | 'use strict'; 5 | var bm_keyframeHelper = $.__bodymovin.bm_keyframeHelper; 6 | var ob = {}; 7 | 8 | function exportCamera(layerInfo, data, frameRate) { 9 | var stretch = data.sr; 10 | data.pe = bm_keyframeHelper.exportKeyframes(layerInfo.property('ADBE Camera Options Group').property('ADBE Camera Zoom'), frameRate, stretch); 11 | data.ks = {}; 12 | if (layerInfo.transform.property('ADBE Anchor Point').canSetExpression) { 13 | data.ks.a = bm_keyframeHelper.exportKeyframes(layerInfo.transform.property('ADBE Anchor Point'), frameRate, stretch); 14 | } 15 | if (layerInfo.transform.position.dimensionsSeparated) { 16 | data.ks.p = {s: true}; 17 | data.ks.p.x = bm_keyframeHelper.exportKeyframes(layerInfo.transform.property('ADBE Position_0'), frameRate, stretch); 18 | data.ks.p.y = bm_keyframeHelper.exportKeyframes(layerInfo.transform.property('ADBE Position_1'), frameRate, stretch); 19 | data.ks.p.z = bm_keyframeHelper.exportKeyframes(layerInfo.transform.property('ADBE Position_2'), frameRate, stretch); 20 | } else { 21 | data.ks.p = bm_keyframeHelper.exportKeyframes(layerInfo.transform.position, frameRate, stretch); 22 | } 23 | data.ks.or = bm_keyframeHelper.exportKeyframes(layerInfo.transform.Orientation, frameRate, stretch); 24 | data.ks.rx = bm_keyframeHelper.exportKeyframes(layerInfo.transform.property('ADBE Rotate X'), frameRate, stretch); 25 | data.ks.ry = bm_keyframeHelper.exportKeyframes(layerInfo.transform.property('ADBE Rotate Y'), frameRate, stretch); 26 | data.ks.rz = bm_keyframeHelper.exportKeyframes(layerInfo.transform.property('ADBE Rotate Z'), frameRate, stretch); 27 | } 28 | 29 | ob.exportCamera = exportCamera; 30 | 31 | return ob; 32 | }()); 33 | -------------------------------------------------------------------------------- /bundle/jsx/utils/generalUtils.jsx: -------------------------------------------------------------------------------- 1 | /*jslint vars: true , plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 2 | /*global app, $, PropertyValueType, bm_eventDispatcher*/ 3 | $.__bodymovin.bm_generalUtils = (function () { 4 | 'use strict'; 5 | var bm_eventDispatcher = $.__bodymovin.bm_eventDispatcher; 6 | var ob = {}; 7 | ob.Gtlym = {}; 8 | ob.Gtlym.CALL = {}; 9 | 10 | function random(len) { 11 | var sequence = 'abcdefghijklmnoqrstuvwxyz1234567890', returnString = '', i; 12 | for (i = 0; i < len; i += 1) { 13 | returnString += sequence.charAt(Math.floor(Math.random() * sequence.length)); 14 | } 15 | return returnString; 16 | } 17 | 18 | function setTimeout(func, millis) { 19 | var guid = random(10); 20 | ob.Gtlym.CALL["interval_" + guid] = func; 21 | return app.scheduleTask('$.__bodymovin.bm_generalUtils.Gtlym.CALL["interval_' + guid + '"]();', millis, false); 22 | } 23 | 24 | function roundArray(arr, decimals) { 25 | var i, len = arr.length; 26 | var retArray = []; 27 | for (i = 0; i < len; i += 1) { 28 | if (typeof arr[i] === 'number') { 29 | retArray.push(roundNumber(arr[i], decimals)); 30 | } else { 31 | retArray.push(roundArray(arr[i], decimals)); 32 | } 33 | } 34 | return retArray; 35 | } 36 | 37 | function roundNumber(num, decimals) { 38 | num = num || 0; 39 | if (typeof num === 'number') { 40 | return parseFloat(num.toFixed(decimals)); 41 | } else { 42 | return roundArray(num, decimals); 43 | } 44 | } 45 | 46 | function rgbToHex(r, g, b) { 47 | return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); 48 | } 49 | 50 | function arrayRgbToHex(values) { 51 | return rgbToHex(Math.round(values[0] * 255), Math.round(values[1] * 255), Math.round(values[2] * 255)); 52 | } 53 | 54 | var iterateProperty = (function () { 55 | 56 | var response; 57 | 58 | function iterateProperties(property, ob) { 59 | ob.name = property.name; 60 | ob.matchName = property.matchName; 61 | if (property.numProperties) { 62 | ob.properties = []; 63 | var i = 0, len = property.numProperties; 64 | while (i < len) { 65 | var propertyOb = {}; 66 | ob.properties.push(propertyOb); 67 | iterateProperties(property(i + 1), propertyOb); 68 | i++; 69 | } 70 | } else { 71 | if (property.propertyValueType !== PropertyValueType.NO_VALUE && property.value !== undefined) { 72 | ob.value = property.value.toString(); 73 | } else { 74 | ob.value = '--- No Value:' + ' ---'; 75 | } 76 | } 77 | } 78 | 79 | return function (property) { 80 | response = {}; 81 | iterateProperties(property, response); 82 | bm_eventDispatcher.sendEvent('console:log', response); 83 | }; 84 | }()); 85 | 86 | function iterateOwnProperties(property){ 87 | var propsArray = []; 88 | for (var s in property) { 89 | if(property.hasOwnProperty(s)) { 90 | propsArray.push(s); 91 | } 92 | } 93 | bm_eventDispatcher.log(propsArray); 94 | } 95 | 96 | function convertPathsToAbsoluteValues(ks) { 97 | var i, len; 98 | if (ks.i) { 99 | len = ks.i.length; 100 | for (i = 0; i < len; i += 1) { 101 | ks.i[i][0] += ks.v[i][0]; 102 | ks.i[i][1] += ks.v[i][1]; 103 | ks.o[i][0] += ks.v[i][0]; 104 | ks.o[i][1] += ks.v[i][1]; 105 | } 106 | } else { 107 | len = ks.length; 108 | for (i = 0; i < len - 1; i += 1) { 109 | convertPathsToAbsoluteValues(ks[i].s[0]); 110 | convertPathsToAbsoluteValues(ks[i].e[0]); 111 | } 112 | } 113 | } 114 | 115 | function findAttributes(name){ 116 | var ob = { 117 | ln: null, 118 | cl: '', 119 | tg: '' 120 | } 121 | var regexElem = /[\.|#][a-zA-Z0-9\-_]*/g; 122 | var match,firstChar, matchString; 123 | while(match = regexElem.exec(name)){ 124 | matchString = match[0]; 125 | firstChar = matchString.substring(0,1); 126 | if (firstChar === '#') { 127 | ob.ln = matchString.substring(1); 128 | }else { 129 | ob.cl += ob.cl === '' ? '' : ' '; 130 | ob.cl += matchString.substring(1); 131 | } 132 | } 133 | regexElem = /<([a-zA-Z0-9\-_]*)>/g; 134 | while(match = regexElem.exec(name)){ 135 | bm_eventDispatcher.log('FOUND') 136 | bm_eventDispatcher.log(match[1]) 137 | ob.tg = match[1]; 138 | } 139 | return ob; 140 | } 141 | 142 | ob.random = random; 143 | ob.roundNumber = roundNumber; 144 | ob.setTimeout = setTimeout; 145 | ob.arrayRgbToHex = arrayRgbToHex; 146 | ob.iterateProperty = iterateProperty; 147 | ob.iterateOwnProperties = iterateOwnProperties; 148 | ob.convertPathsToAbsoluteValues = convertPathsToAbsoluteValues; 149 | ob.findAttributes = findAttributes; 150 | 151 | return ob; 152 | 153 | }()); -------------------------------------------------------------------------------- /bundle/jsx/utils/layerStylesHelper.jsx: -------------------------------------------------------------------------------- 1 | /*jslint vars: true , plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 2 | /*global bm_keyframeHelper*/ 3 | $.__bodymovin.bm_layerStylesHelper = (function () { 4 | 'use strict'; 5 | var bm_keyframeHelper = $.__bodymovin.bm_keyframeHelper; 6 | var ob = {}; 7 | var layerStyleTypes = { 8 | stroke: 0 9 | }; 10 | 11 | function getStyleType(name) { 12 | switch (name) { 13 | case 'frameFX/enabled': 14 | return layerStyleTypes.stroke; 15 | default: 16 | return ''; 17 | } 18 | } 19 | 20 | function exportStroke(style, frameRate, stretch) { 21 | var ob = {}; 22 | ob.ty = layerStyleTypes.stroke; 23 | ob.nm = style.name; 24 | ob.c = bm_keyframeHelper.exportKeyframes(style.property('frameFX/color'), frameRate, stretch); 25 | ob.s = bm_keyframeHelper.exportKeyframes(style.property('frameFX/size'), frameRate, stretch); 26 | return ob; 27 | } 28 | 29 | function exportStyles(layerInfo, layerData, frameRate) { 30 | if (!(layerInfo.property('Layer Styles') && layerInfo.property('Layer Styles').numProperties > 0)) { 31 | return; 32 | } 33 | var stretch = layerData.sr; 34 | var styles = layerInfo.property('Layer Styles'); 35 | var i, len = styles.numProperties, styleElement; 36 | var stylesArray = []; 37 | for (i = 0; i < len; i += 1) { 38 | styleElement = styles(i + 1); 39 | if (styleElement.isModified) { 40 | var styleType = getStyleType(styleElement.matchName); 41 | switch (styleType) { 42 | case layerStyleTypes.stroke: 43 | stylesArray.push(exportStroke(styleElement, frameRate, stretch)); 44 | break; 45 | } 46 | } 47 | } 48 | if (stylesArray.length) { 49 | layerData.sy = stylesArray; 50 | } 51 | } 52 | 53 | ob.exportStyles = exportStyles; 54 | 55 | return ob; 56 | }()); -------------------------------------------------------------------------------- /bundle/jsx/utils/markerHelper.jsx: -------------------------------------------------------------------------------- 1 | /*jslint vars: true , plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 2 | /*global */ 3 | $.__bodymovin.bm_markerHelper = (function () { 4 | 'use strict'; 5 | var ob = {}; 6 | 7 | function searchMarkers(comp, ob) { 8 | if (!(comp.marker && comp.marker.numProperties > 0)) { 9 | return; 10 | } 11 | var markersData = [], markerData, markers = comp.marker, i, len = markers.numProperties, markerElement; 12 | for (i = 0; i < len; i += 1) { 13 | markerData = {}; 14 | markerElement = markers(i + 1); 15 | markersData.push(markerData); 16 | } 17 | } 18 | 19 | ob.searchMarkers = searchMarkers; 20 | 21 | return ob; 22 | }()); -------------------------------------------------------------------------------- /bundle/jsx/utils/maskHelper.jsx: -------------------------------------------------------------------------------- 1 | /*jslint vars: true , plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 2 | /*global bm_keyframeHelper, MaskMode*/ 3 | $.__bodymovin.bm_maskHelper = (function () { 4 | 'use strict'; 5 | var bm_keyframeHelper = $.__bodymovin.bm_keyframeHelper; 6 | var ob = {}; 7 | 8 | function getMaskMode(num) { 9 | switch (num) { 10 | case MaskMode.NONE: 11 | return 'n'; 12 | case MaskMode.ADD: 13 | return 'a'; 14 | case MaskMode.SUBTRACT: 15 | return 's'; 16 | case MaskMode.INTERSECT: 17 | return 'i'; 18 | case MaskMode.LIGHTEN: 19 | return 'l'; 20 | case MaskMode.DARKEN: 21 | return 'd'; 22 | case MaskMode.DIFFERENCE: 23 | return 'f'; 24 | } 25 | } 26 | 27 | function exportMasks(layerInfo, layerData, frameRate) { 28 | if (!(layerInfo.mask && layerInfo.mask.numProperties > 0)) { 29 | return; 30 | } 31 | var stretch = layerData.sr; 32 | layerData.hasMask = true; 33 | layerData.masksProperties = []; 34 | var masks = layerInfo.mask; 35 | var i, len = masks.numProperties, maskShape, maskElement; 36 | for (i = 0; i < len; i += 1) { 37 | maskElement = masks(i + 1); 38 | maskShape = maskElement.property('maskShape').value; 39 | var shapeData = { 40 | inv: maskElement.inverted, 41 | mode: getMaskMode(maskElement.maskMode) 42 | }; 43 | shapeData.pt = bm_keyframeHelper.exportKeyframes(maskElement.property('maskShape'), frameRate, stretch); 44 | $.__bodymovin.bm_shapeHelper.checkVertexCount(shapeData.pt.k); 45 | shapeData.o = bm_keyframeHelper.exportKeyframes(maskElement.property('Mask Opacity'), frameRate, stretch); 46 | shapeData.x = bm_keyframeHelper.exportKeyframes(maskElement.property('Mask Expansion'), frameRate, stretch); 47 | if ($.__bodymovin.bm_renderManager.shouldIncludeNotSupportedProperties()) { 48 | shapeData.f = bm_keyframeHelper.exportKeyframes(maskElement.property('Mask Feather'), frameRate, stretch); 49 | } 50 | shapeData.nm = maskElement.name; 51 | layerData.masksProperties.push(shapeData); 52 | } 53 | } 54 | 55 | ob.exportMasks = exportMasks; 56 | 57 | return ob; 58 | }()); -------------------------------------------------------------------------------- /bundle/jsx/utils/timeremapHelper.jsx: -------------------------------------------------------------------------------- 1 | /*jslint vars: true , plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 2 | /*global bm_keyframeHelper*/ 3 | $.__bodymovin.bm_timeremapHelper = (function () { 4 | 'use strict'; 5 | var bm_keyframeHelper = $.__bodymovin.bm_keyframeHelper; 6 | 7 | var ob = {}; 8 | 9 | function exportTimeremap(layerInfo, layerData, frameRate, stretch) { 10 | if (layerInfo.canSetTimeRemapEnabled && layerInfo.timeRemapEnabled) { 11 | var stretch = layerData.sr; 12 | layerData.tm = bm_keyframeHelper.exportKeyframes(layerInfo['Time Remap'], frameRate, stretch); 13 | } 14 | } 15 | 16 | ob.exportTimeremap = exportTimeremap; 17 | 18 | return ob; 19 | 20 | }()); -------------------------------------------------------------------------------- /bundle/jsx/utils/transformHelper.jsx: -------------------------------------------------------------------------------- 1 | /*jslint vars: true , plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ 2 | /*global bm_keyframeHelper*/ 3 | $.__bodymovin.bm_transformHelper = (function () { 4 | 'use strict'; 5 | var bm_keyframeHelper = $.__bodymovin.bm_keyframeHelper; 6 | var ob = {}; 7 | 8 | function exportTransform(layerInfo, data, frameRate) { 9 | 10 | var skipDefaultProperties = $.__bodymovin.bm_renderManager.shouldSkipDefaultProperties() 11 | 12 | if (!layerInfo.transform) { 13 | return; 14 | } 15 | var stretch = data.sr; 16 | 17 | data.ks = {}; 18 | if (layerInfo.transform.opacity) { 19 | data.ks.o = bm_keyframeHelper.exportKeyframes(layerInfo.transform.opacity, frameRate, stretch); 20 | } 21 | if (layerInfo.threeDLayer) { 22 | data.ks.rx = bm_keyframeHelper.exportKeyframes(layerInfo.transform.property('ADBE Rotate X'), frameRate, stretch); 23 | data.ks.ry = bm_keyframeHelper.exportKeyframes(layerInfo.transform.property('ADBE Rotate Y'), frameRate, stretch); 24 | data.ks.rz = bm_keyframeHelper.exportKeyframes(layerInfo.transform.property('ADBE Rotate Z'), frameRate, stretch); 25 | data.ks.or = bm_keyframeHelper.exportKeyframes(layerInfo.transform.Orientation, frameRate, stretch); 26 | } else { 27 | data.ks.r = bm_keyframeHelper.exportKeyframes(layerInfo.transform.rotation, frameRate, stretch); 28 | } 29 | if (layerInfo.transform.position.dimensionsSeparated) { 30 | data.ks.p = {s: true}; 31 | data.ks.p.x = bm_keyframeHelper.exportKeyframes(layerInfo.transform.property('ADBE Position_0'), frameRate, stretch); 32 | data.ks.p.y = bm_keyframeHelper.exportKeyframes(layerInfo.transform.property('ADBE Position_1'), frameRate, stretch); 33 | if (layerInfo.threeDLayer) { 34 | data.ks.p.z = bm_keyframeHelper.exportKeyframes(layerInfo.transform.property('ADBE Position_2'), frameRate, stretch); 35 | } 36 | } else { 37 | data.ks.p = bm_keyframeHelper.exportKeyframes(layerInfo.transform.position, frameRate, stretch); 38 | } 39 | if (layerInfo.transform.property('ADBE Anchor Point')) { 40 | data.ks.a = bm_keyframeHelper.exportKeyframes(layerInfo.transform.property('ADBE Anchor Point'), frameRate, stretch); 41 | } 42 | if (layerInfo.transform.Scale) { 43 | data.ks.s = bm_keyframeHelper.exportKeyframes(layerInfo.transform.Scale, frameRate, stretch); 44 | } 45 | if(layerInfo.autoOrient === AutoOrientType.ALONG_PATH){ 46 | data.ao = 1; 47 | } else { 48 | data.ao = 0; 49 | } 50 | 51 | if(skipDefaultProperties) { 52 | if(data.ks.o && !data.ks.o.x && data.ks.o.k === 100) { 53 | delete data.ks.o 54 | } 55 | if(data.ks.r && !data.ks.r.x && data.ks.r.k === 0) { 56 | delete data.ks.r 57 | } 58 | 59 | if(data.ks.p && !data.ks.p.x && data.ks.p.k && data.ks.p.k.length) { 60 | if ((data.ks.p.k.length === 2 && data.ks.p.k[0] === 0 && data.ks.p.k[1] === 0) 61 | || (data.ks.p.k.length === 3 && data.ks.p.k[0] === 0 && data.ks.p.k[1] === 0 && data.ks.p.k[2] === 0)) { 62 | delete data.ks.p 63 | } 64 | } 65 | 66 | if(data.ks.a && !data.ks.a.x && data.ks.a.k && data.ks.a.k.length) { 67 | if ((data.ks.a.k.length === 2 && data.ks.a.k[0] === 0 && data.ks.a.k[1] === 0) 68 | || (data.ks.a.k.length === 3 && data.ks.a.k[0] === 0 && data.ks.a.k[1] === 0 && data.ks.a.k[2] === 0)) { 69 | delete data.ks.a 70 | } 71 | } 72 | 73 | if(data.ks.s && !data.ks.s.x && data.ks.s.k && data.ks.s.k.length) { 74 | if ((data.ks.s.k.length === 2 && data.ks.s.k[0] === 100 && data.ks.s.k[1] === 100) 75 | || (data.ks.s.k.length === 3 && data.ks.s.k[0] === 100 && data.ks.s.k[1] === 100 && data.ks.s.k[2] === 100)) { 76 | delete data.ks.s 77 | } 78 | } 79 | } 80 | } 81 | 82 | ob.exportTransform = exportTransform; 83 | return ob; 84 | }()); -------------------------------------------------------------------------------- /config/env.js: -------------------------------------------------------------------------------- 1 | // Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be 2 | // injected into the application via DefinePlugin in Webpack configuration. 3 | 4 | var REACT_APP = /^REACT_APP_/i; 5 | 6 | function getClientEnvironment(publicUrl) { 7 | var processEnv = Object 8 | .keys(process.env) 9 | .filter(key => REACT_APP.test(key)) 10 | .reduce((env, key) => { 11 | env[key] = JSON.stringify(process.env[key]); 12 | return env; 13 | }, { 14 | // Useful for determining whether we’re running in production mode. 15 | // Most importantly, it switches React into the correct mode. 16 | 'NODE_ENV': JSON.stringify( 17 | process.env.NODE_ENV || 'development' 18 | ), 19 | // Useful for resolving the correct path to static assets in `public`. 20 | // For example, . 21 | // This should only be used as an escape hatch. Normally you would put 22 | // images into the `src` and `import` them in code to get their paths. 23 | 'PUBLIC_URL': JSON.stringify(publicUrl) 24 | }); 25 | return {'process.env': processEnv}; 26 | } 27 | 28 | module.exports = getClientEnvironment; 29 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | // This is a custom Jest transformer turning style imports into empty objects. 2 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 3 | 4 | module.exports = { 5 | process() { 6 | return 'module.exports = {};'; 7 | }, 8 | getCacheKey(fileData, filename) { 9 | // The output is always the same. 10 | return 'cssTransform'; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | // This is a custom Jest transformer turning file imports into filenames. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process(src, filename) { 8 | return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /config/paths.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | 4 | // Make sure any symlinks in the project folder are resolved: 5 | // https://github.com/facebookincubator/create-react-app/issues/637 6 | var appDirectory = fs.realpathSync(process.cwd()); 7 | function resolveApp(relativePath) { 8 | return path.resolve(appDirectory, relativePath); 9 | } 10 | 11 | // We support resolving modules according to `NODE_PATH`. 12 | // This lets you use absolute paths in imports inside large monorepos: 13 | // https://github.com/facebookincubator/create-react-app/issues/253. 14 | 15 | // It works similar to `NODE_PATH` in Node itself: 16 | // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders 17 | 18 | // We will export `nodePaths` as an array of absolute paths. 19 | // It will then be used by Webpack configs. 20 | // Jest doesn’t need this because it already handles `NODE_PATH` out of the box. 21 | 22 | // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. 23 | // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. 24 | // https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 25 | 26 | var nodePaths = (process.env.NODE_PATH || '') 27 | .split(process.platform === 'win32' ? ';' : ':') 28 | .filter(Boolean) 29 | .filter(folder => !path.isAbsolute(folder)) 30 | .map(resolveApp); 31 | 32 | // config after eject: we're in ./config/ 33 | module.exports = { 34 | appBuild: resolveApp('build'), 35 | appPublic: resolveApp('public'), 36 | appHtml: resolveApp('public/index.html'), 37 | appIndexJs: resolveApp('src/index.js'), 38 | appPackageJson: resolveApp('package.json'), 39 | appSrc: resolveApp('src'), 40 | yarnLockFile: resolveApp('yarn.lock'), 41 | testsSetup: resolveApp('src/setupTests.js'), 42 | appNodeModules: resolveApp('node_modules'), 43 | ownNodeModules: resolveApp('node_modules'), 44 | nodePaths: nodePaths 45 | }; 46 | -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | if (typeof Promise === 'undefined') { 2 | // Rejection tracking prevents a common issue where React gets into an 3 | // inconsistent state due to an error, but it gets swallowed by a Promise, 4 | // and the user has no idea what causes React's erratic future behavior. 5 | require('promise/lib/rejection-tracking').enable(); 6 | window.Promise = require('promise/lib/es6-extensions.js'); 7 | } 8 | 9 | // fetch() polyfill for making API calls. 10 | require('whatwg-fetch'); 11 | 12 | // Object.assign() is commonly used with React. 13 | // It will use the native implementation if it's present and isn't buggy. 14 | Object.assign = require('object-assign'); 15 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var watch = require('gulp-watch'); 3 | var htmlreplace = require('gulp-html-replace'); 4 | var eventstream = require("event-stream"); 5 | var rename = require('gulp-rename'); 6 | var replace = require('gulp-replace'); 7 | var gzip = require('gulp-gzip'); 8 | var insert = require('gulp-insert'); 9 | 10 | var version = '5.5.2' 11 | 12 | var extensionSource = './bundle'; 13 | var extensionDestination = '/Library/Application Support/Adobe/CEP/extensions/bodymovin-tg'; 14 | gulp.task('watch-extension', function() { 15 | gulp.src(extensionSource + '/**/*', {base: extensionSource}) 16 | .pipe(watch(extensionSource, {base: extensionSource})) 17 | .pipe(gulp.dest(extensionDestination)); 18 | }); 19 | gulp.task('copy-extension', function() { 20 | gulp.src(extensionSource+'/**/*') 21 | .pipe(gulp.dest(extensionDestination)); 22 | }); 23 | 24 | gulp.task('copy-all', function() { 25 | return gulp.src(extensionSource+'/**/*') 26 | .pipe(gulp.dest('./build')); 27 | }); 28 | gulp.task('create-bm', function() { 29 | return gulp.src('player/lottie.min.js') 30 | .pipe(rename('lottie.js')) 31 | .pipe(gulp.dest('build/assets/player')); 32 | }); 33 | gulp.task('create-bm-extension-player', function() { 34 | return gulp.src('player/lottie.js') 35 | .pipe(rename('lottie.js')) 36 | .pipe(insert.prepend('/* eslint-disable */var define = define || null;')) 37 | .pipe(gulp.dest('./src/')); 38 | }); 39 | gulp.task('create-standalone', function() { 40 | return gulp.src('player/lottie.min.js') 41 | .pipe(rename('standalone.js')) 42 | .pipe(gulp.dest('build/assets/player')); 43 | }); 44 | gulp.task('create-gzip', function() { 45 | return gulp.src('player/lottie.min.js') 46 | .pipe(rename('lottie.js')) 47 | .pipe(gzip({ append: true })) 48 | .pipe(gulp.dest('build/assets/player')); 49 | }); 50 | 51 | gulp.task('copy-manifest', function() { 52 | return gulp.src('bundle/CSXS/manifest.xml') 53 | .pipe(replace(/()/g,'$1'+version+'$3')) 54 | .pipe(replace(/(\.\/)(index_dev.html)(<\/MainPath>)/g,'$1'+'index.html'+'$3')) 56 | .pipe(gulp.dest('build/CSXS/')); 57 | }); 58 | 59 | gulp.task('copy-debug', function() { 60 | return gulp.src('bundle/.debug') 61 | .pipe(gulp.dest('build/')); 62 | }); 63 | 64 | gulp.task('copy-renderManager', function() { 65 | return gulp.src('bundle/jsx/renderManager.jsx') 66 | //.pipe(replace(/(v : ')(.+)(',)/g,'$1'+version+'$3')) 67 | .pipe(replace(/(version_number = ')(.+)(';)/g,'$1'+version+'$3')) 68 | .pipe(gulp.dest('build/jsx/')); 69 | }); 70 | 71 | var demoBuiltData = ''; 72 | gulp.task('build-demo-data', function() { 73 | 74 | function saveToVar() { 75 | // you're going to receive Vinyl files as chunks 76 | function transform(file, cb) { 77 | // read and modify file contents 78 | demoBuiltData = String(file.contents); 79 | 80 | cb(null, file); 81 | } 82 | return eventstream.map(transform); 83 | } 84 | 85 | return gulp.src('player/lottie.min.js') 86 | .pipe(saveToVar()); 87 | }); 88 | 89 | 90 | gulp.task('replace-demo-data', gulp.series(['build-demo-data'], function() { 91 | //htmlreplace; 92 | return gulp.src('bundle/assets/player/demo.html') 93 | .pipe(htmlreplace({ 94 | scripto:{ 95 | src: demoBuiltData, 96 | tpl: '' 97 | } 98 | })) 99 | .pipe(gulp.dest('build/assets/player/')); 100 | })); 101 | 102 | 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')) -------------------------------------------------------------------------------- /index_dev.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-bodymovin", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": ".", 6 | "devDependencies": { 7 | "autoprefixer": "6.5.1", 8 | "babel-core": "6.17.0", 9 | "babel-eslint": "7.1.1", 10 | "babel-jest": "17.0.2", 11 | "babel-loader": "6.2.7", 12 | "babel-preset-es2015": "^6.24.1", 13 | "babel-preset-react-app": "^2.0.1", 14 | "babel-preset-stage-0": "^6.24.1", 15 | "case-sensitive-paths-webpack-plugin": "1.1.4", 16 | "chalk": "1.1.3", 17 | "concurrently": "^3.4.0", 18 | "connect-history-api-fallback": "1.3.0", 19 | "cross-spawn": "4.0.2", 20 | "css-loader": "0.26.0", 21 | "detect-port": "1.0.1", 22 | "dotenv": "2.0.0", 23 | "eslint": "3.8.1", 24 | "eslint-config-react-app": "^0.5.0", 25 | "eslint-loader": "1.6.0", 26 | "eslint-plugin-flowtype": "2.21.0", 27 | "eslint-plugin-import": "^2.17.2", 28 | "eslint-plugin-jsx-a11y": "2.2.3", 29 | "eslint-plugin-react": "6.4.1", 30 | "event-stream": "4.0.1", 31 | "extract-text-webpack-plugin": "1.0.1", 32 | "file-loader": "0.9.0", 33 | "filesize": "3.3.0", 34 | "fs-extra": "0.30.0", 35 | "gulp": "^4.0.2", 36 | "gulp-gzip": "^1.4.0", 37 | "gulp-html-replace": "^1.6.2", 38 | "gulp-insert": "^0.5.0", 39 | "gulp-rename": "^1.2.2", 40 | "gulp-replace": "^1.0.0", 41 | "gulp-watch": "^5.0.1", 42 | "gzip-size": "3.0.0", 43 | "html-webpack-plugin": "2.24.0", 44 | "http-proxy-middleware": "^0.19.1", 45 | "jest": "17.0.2", 46 | "json-loader": "0.5.4", 47 | "object-assign": "4.1.0", 48 | "path-exists": "2.1.0", 49 | "postcss-loader": "1.0.0", 50 | "promise": "7.1.1", 51 | "react-dev-utils": "^0.4.2", 52 | "recursive-readdir": "2.1.0", 53 | "strip-ansi": "3.0.1", 54 | "style-loader": "0.13.1", 55 | "url-loader": "0.5.7", 56 | "webpack": "1.14.0", 57 | "webpack-dev-server": "1.16.2", 58 | "webpack-manifest-plugin": "1.1.0", 59 | "whatwg-fetch": "1.0.0" 60 | }, 61 | "dependencies": { 62 | "@babel/preset-env": "^7.0.0", 63 | "aphrodite": "^1.1.0", 64 | "bodymovin-to-avd": "^1.0.8", 65 | "jimp": "^0.2.28", 66 | "png-to-jpeg": "^1.0.1", 67 | "react": "^15.4.2", 68 | "react-dom": "^15.4.2", 69 | "react-redux": "^5.0.2", 70 | "react-router": "^3.0.2", 71 | "redux": "^3.6.0", 72 | "redux-saga": "^0.14.3", 73 | "reselect": "^2.5.4" 74 | }, 75 | "scripts": { 76 | "start": "node scripts/start.js", 77 | "start-dev": "concurrently --kill-others \"node scripts/start.js\" \"gulp watch-extension\"", 78 | "watch-gulp": "gulp watch-extension", 79 | "build": "gulp create-bm-extension-player && node scripts/build.js && gulp copy-extension-bundle", 80 | "test": "node scripts/test.js --env=jsdom" 81 | }, 82 | "jest": { 83 | "collectCoverageFrom": [ 84 | "src/**/*.{js,jsx}" 85 | ], 86 | "setupFiles": [ 87 | "\\config\\polyfills.js" 88 | ], 89 | "testPathIgnorePatterns": [ 90 | "[/\\\\](build|docs|node_modules)[/\\\\]" 91 | ], 92 | "testEnvironment": "node", 93 | "testURL": "http://localhost", 94 | "transform": { 95 | "^.+\\.(js|jsx)$": "/node_modules/babel-jest", 96 | "^.+\\.css$": "\\config\\jest\\cssTransform.js", 97 | "^(?!.*\\.(js|jsx|css|json)$)": "\\config\\jest\\fileTransform.js" 98 | }, 99 | "transformIgnorePatterns": [ 100 | "[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$" 101 | ], 102 | "moduleNameMapper": { 103 | "^react-native$": "react-native-web" 104 | } 105 | }, 106 | "babel": { 107 | "presets": [ 108 | "react-app", 109 | "es2015", 110 | "babel-preset-es2015" 111 | ] 112 | }, 113 | "eslintConfig": { 114 | "extends": "react-app" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /public/assets/fonts/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelegramMessenger/bodymovin-extension/e35d611d1a67f9e6ebf2fecd4fc322447a05927a/public/assets/fonts/Roboto-Black.ttf -------------------------------------------------------------------------------- /public/assets/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelegramMessenger/bodymovin-extension/e35d611d1a67f9e6ebf2fecd4fc322447a05927a/public/assets/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /public/assets/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelegramMessenger/bodymovin-extension/e35d611d1a67f9e6ebf2fecd4fc322447a05927a/public/assets/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /public/assets/svg/down_arrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/svg/up_arrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelegramMessenger/bodymovin-extension/e35d611d1a67f9e6ebf2fecd4fc322447a05927a/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 19 | React App 20 | 21 | 22 |
23 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /public/main.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: Roboto; 3 | src: url('./assets/fonts/Roboto-Regular.ttf'); 4 | } 5 | 6 | @font-face { 7 | font-family: Roboto-Black; 8 | src: url('./assets/fonts/Roboto-Black.ttf'); 9 | } 10 | 11 | @font-face { 12 | font-family: Roboto-Bold; 13 | src: url('./assets/fonts/Roboto-Bold.ttf'); 14 | } 15 | 16 | *::-webkit-scrollbar { 17 | width: 15px; 18 | } 19 | 20 | *::-webkit-scrollbar-track { 21 | background: #5c5c5c; 22 | } 23 | 24 | *::-webkit-scrollbar-thumb { 25 | background: #484848; 26 | border: 1px solid #404040; 27 | } 28 | 29 | *::-webkit-scrollbar-button:vertical:decrement{ 30 | background-image:url('assets/svg/up_arrow.svg'); 31 | background-size: fit; 32 | background-repeat: no-repeat; 33 | background-position: center; 34 | background-color: #5c5c5c; 35 | cursor: pointer; 36 | } 37 | 38 | *::-webkit-scrollbar-button:vertical:increment{ 39 | background-image:url('assets/svg/down_arrow.svg'); 40 | background-size: fit; 41 | background-repeat: no-repeat; 42 | background-position: center; 43 | background-color: #5c5c5c; 44 | cursor: pointer; 45 | } -------------------------------------------------------------------------------- /public/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | box-sizing: border-box; 26 | } 27 | /* HTML5 display-role reset for older browsers */ 28 | article, aside, details, figcaption, figure, 29 | footer, header, hgroup, menu, nav, section { 30 | display: block; 31 | } 32 | body { 33 | line-height: 1; 34 | } 35 | ol, ul { 36 | list-style: none; 37 | } 38 | blockquote, q { 39 | quotes: none; 40 | } 41 | blockquote:before, blockquote:after, 42 | q:before, q:after { 43 | content: ''; 44 | content: none; 45 | } 46 | table { 47 | border-collapse: collapse; 48 | border-spacing: 0; 49 | } 50 | button { 51 | border: none 52 | } -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'test'; 2 | process.env.PUBLIC_URL = ''; 3 | 4 | // Load environment variables from .env file. Suppress warnings using silent 5 | // if this file is missing. dotenv will never modify any environment variables 6 | // that have already been set. 7 | // https://github.com/motdotla/dotenv 8 | require('dotenv').config({silent: true}); 9 | 10 | const jest = require('jest'); 11 | const argv = process.argv.slice(2); 12 | 13 | // Watch unless on CI or in coverage mode 14 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 15 | argv.push('--watch'); 16 | } 17 | 18 | // A temporary hack to clear terminal correctly. 19 | // You can remove this after updating to Jest 18 when it's out. 20 | // https://github.com/facebook/jest/pull/2230 21 | var realWrite = process.stdout.write; 22 | var CLEAR = process.platform === 'win32' ? '\x1Bc' : '\x1B[2J\x1B[3J\x1B[H'; 23 | process.stdout.write = function(chunk, encoding, callback) { 24 | if (chunk === '\x1B[2J\x1B[H') { 25 | chunk = CLEAR; 26 | } 27 | return realWrite.call(this, chunk, encoding, callback); 28 | }; 29 | 30 | 31 | jest.run(argv); 32 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | background-color: red; 4 | font-family: Roboto 5 | } -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Provider } from 'react-redux' 3 | import { applyMiddleware, compose, createStore } from 'redux' 4 | import ViewsContainer from './views/ViewsContainer' 5 | import Alerts from './components/alerts/Alerts' 6 | import Footer from './components/footer/Footer' 7 | import reducer from './redux/reducers/index' 8 | import createSagaMiddleware from 'redux-saga' 9 | import rootSaga from './redux/sagas' 10 | import './App.css'; 11 | import './reset.css'; 12 | import {setDispatcher} from './helpers/storeDispatcher' 13 | import {getCompositions} from './redux/actions/compositionActions' 14 | import {getPaths, getVersion} from './redux/actions/generalActions' 15 | 16 | const sagaMiddleware = createSagaMiddleware() 17 | 18 | let composeStore = compose( 19 | applyMiddleware( 20 | sagaMiddleware 21 | ) 22 | )(createStore) 23 | 24 | let store = composeStore(reducer) 25 | sagaMiddleware.run(rootSaga) 26 | 27 | 28 | class App extends Component { 29 | 30 | componentWillMount() { 31 | setDispatcher(store.dispatch) 32 | } 33 | 34 | componentDidMount() { 35 | store.dispatch(getCompositions()) 36 | window.onfocus = function(){ 37 | store.dispatch(getCompositions()) 38 | } 39 | store.dispatch(getPaths()) 40 | store.dispatch(getVersion()) 41 | } 42 | 43 | 44 | render() { 45 | 46 | return ( 47 | 48 |
49 | 50 | {/* 51 | 52 | 53 | 54 | 55 | 56 | 57 | */} 58 |
59 | 60 |
61 |
62 | ); 63 | } 64 | } 65 | 66 | export default App 67 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /src/assets/animations/data2.json: -------------------------------------------------------------------------------- 1 | {"assets":[],"layers":[{"ddd":0,"ind":0,"ty":4,"nm":"Shape Layer 1","ks":{"o":{"k":100},"r":{"k":0},"p":{"k":[75,75,0]},"a":{"k":[0,0,0]},"s":{"k":[100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"k":[100,100]},"p":{"k":[0,0]},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse"},{"ty":"fl","fillEnabled":true,"c":{"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[1,0,0,1],"e":[0.05,0.03,0.4,1]},{"t":59}]},"o":{"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[25],"e":[100]},{"t":59}]},"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill"},{"ty":"st","fillEnabled":true,"c":{"k":[0.34,0.34,0.34,1]},"o":{"k":100},"w":{"k":4},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"},{"ty":"tr","p":{"k":[0,0],"ix":2},"a":{"k":[0,0],"ix":1},"s":{"k":[100,100],"ix":3},"r":{"k":0,"ix":6},"o":{"k":100,"ix":7},"sk":{"k":0,"ix":4},"sa":{"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"mn":"ADBE Vector Group"}],"ip":0,"op":602,"st":0,"bm":0,"sr":1}],"v":"4.4.28","ddd":0,"ip":0,"op":60,"fr":30,"w":150,"h":150} -------------------------------------------------------------------------------- /src/assets/animations/expander.json: -------------------------------------------------------------------------------- 1 | {"v":"4.8.0","fr":60,"ip":0,"op":62,"w":100,"h":100,"nm":"toggle_expand","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"n":"0p667_1_0p333_0","t":0,"s":[50,60,0],"e":[50,50,0],"to":[0,-1.66666662693024,0],"ti":[0,1.66666662693024,0]},{"t":61}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"sr","sy":1,"d":1,"pt":{"a":0,"k":3,"ix":3},"p":{"a":0,"k":[0,0],"ix":4},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":0,"s":[0],"e":[360]},{"t":61}],"ix":5},"ir":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":0,"s":[20],"e":[40]},{"t":61}],"ix":6},"is":{"a":0,"k":0,"ix":8},"or":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":0,"s":[40],"e":[20]},{"t":61}],"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.717647075653,0.203921571374,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[0],"e":[100]},{"t":61}],"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"st","c":{"a":0,"k":[0.258823543787,0.258823543787,0.258823543787,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":16,"ix":5},"lc":2,"lj":2,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":1804,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50,50,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"rc","d":1,"s":{"a":0,"k":[100,100],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":0,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":1804,"st":0,"bm":0}],"markers":[]} -------------------------------------------------------------------------------- /src/assets/fonts/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelegramMessenger/bodymovin-extension/e35d611d1a67f9e6ebf2fecd4fc322447a05927a/src/assets/fonts/Roboto-Black.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelegramMessenger/bodymovin-extension/e35d611d1a67f9e6ebf2fecd4fc322447a05927a/src/assets/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TelegramMessenger/bodymovin-extension/e35d611d1a67f9e6ebf2fecd4fc322447a05927a/src/assets/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /src/assets/svg/cancel_button.svg: -------------------------------------------------------------------------------- 1 | Artboard 1 -------------------------------------------------------------------------------- /src/assets/svg/complete_icon.svg: -------------------------------------------------------------------------------- 1 | complete_icon -------------------------------------------------------------------------------- /src/assets/svg/down_arrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/folder_button.svg: -------------------------------------------------------------------------------- 1 | Artboard 1 -------------------------------------------------------------------------------- /src/assets/svg/glass.svg: -------------------------------------------------------------------------------- 1 | glass -------------------------------------------------------------------------------- /src/assets/svg/preview_thumb.svg: -------------------------------------------------------------------------------- 1 | preview_scrubber -------------------------------------------------------------------------------- /src/assets/svg/refresh.svg: -------------------------------------------------------------------------------- 1 | refresh -------------------------------------------------------------------------------- /src/assets/svg/up_arrow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/alerts/Alerts.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | import { StyleSheet, css } from 'aphrodite' 4 | import {hideAlert} from '../../redux/actions/generalActions' 5 | import alertAnim from '../../assets/animations/alert.json' 6 | import Bodymovin from '../bodymovin/bodymovin' 7 | import BaseButton from '../buttons/Base_button' 8 | import Variables from '../../helpers/styles/variables' 9 | 10 | const styles = StyleSheet.create({ 11 | wrapper: { 12 | width: '100%', 13 | height: '100%', 14 | padding: '10px', 15 | position: 'absolute', 16 | top:'0', 17 | left:'0', 18 | backgroundColor:'rgba(71,71,71,.9)', 19 | overflow: 'auto' 20 | }, 21 | aligner: { 22 | textAlign: 'center' 23 | }, 24 | pars_container:{ 25 | paddingBottom: '10px' 26 | }, 27 | par:{ 28 | fontFamily: 'Roboto-Bold', 29 | fontSize: '12px', 30 | lineHeight: '15px', 31 | textAlign: 'center', 32 | color: Variables.colors.white 33 | }, 34 | bm_container: { 35 | height: '250px' 36 | } 37 | }) 38 | 39 | class Alerts extends React.Component { 40 | 41 | createPars(pars){ 42 | return pars.map(function(par, index){ 43 | return

{par}

44 | }) 45 | } 46 | 47 | render() { 48 | if(!this.props.alerts.show){ 49 | return null 50 | }else{ 51 | return (
52 | 53 |
54 |
55 |
56 | {this.createPars(this.props.alerts.pars)} 57 |
58 |
59 | 60 |
61 |
) 62 | } 63 | 64 | } 65 | } 66 | 67 | function mapStateToProps(state) { 68 | return {alerts: state.alerts} 69 | } 70 | 71 | const mapDispatchToProps = { 72 | hideAlert: hideAlert 73 | } 74 | 75 | export default connect(mapStateToProps, mapDispatchToProps)(Alerts) -------------------------------------------------------------------------------- /src/components/bodymovin/bodymovin.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import bodymovin from '../../lottie' 3 | 4 | class Bodymovin extends React.Component { 5 | 6 | constructor() { 7 | super() 8 | this.animationLoaded = this.animationLoaded.bind(this) 9 | this.setElement = this.setElement.bind(this) 10 | this.element = null 11 | } 12 | 13 | animationLoaded() { 14 | if(this.props.animationLoaded) { 15 | this.props.animationLoaded() 16 | } 17 | } 18 | 19 | componentWillReceiveProps(props) { 20 | if(props.animationData !== this.props.animationData || props.path !== this.props.path) { 21 | if(this.animation) { 22 | this.animation.destroy() 23 | this.animation = null 24 | } 25 | } 26 | } 27 | 28 | componentDidUpdate() { 29 | this.attachAnimation() 30 | } 31 | 32 | setElement(elem) { 33 | this.element = elem 34 | this.attachAnimation() 35 | } 36 | 37 | attachAnimation() { 38 | if(!this.animation && this.element && (this.props.animationData || this.props.path)){ 39 | try{ 40 | let params = { 41 | container: this.element, 42 | renderer: this.props.renderer || 'svg', 43 | autoplay: this.props.autoplay, 44 | loop: this.props.loop, 45 | rendererSettings: { 46 | progressiveLoad:true, 47 | preserveAspectRatio: 'xMidYMid meet' 48 | } 49 | } 50 | if(this.props.animationData) { 51 | params.animationData = this.props.animationData 52 | } else if(this.props.path) { 53 | params.path = this.props.path 54 | } 55 | if(this.props.rendererSettings){ 56 | params.rendererSettings = {...params.rendererSettings,...this.props.rendererSettings} 57 | } 58 | this.animation = bodymovin.loadAnimation(params) 59 | this.animation.addEventListener('DOMLoaded', this.animationLoaded) 60 | } catch(err){ 61 | this.element.innerHTML = '' 62 | } 63 | } 64 | } 65 | 66 | goToAndPlay(num, isFrame) { 67 | if(this.animation){ 68 | this.animation.goToAndPlay(num, isFrame) 69 | } 70 | } 71 | 72 | goToAndStop(num, isFrame) { 73 | if(this.animation){ 74 | this.animation.goToAndStop(num, isFrame) 75 | } 76 | } 77 | 78 | playSegments(segments, forceFlag) { 79 | if(this.animation){ 80 | this.animation.playSegments(segments, forceFlag) 81 | } 82 | } 83 | 84 | setSegment(init, end) { 85 | if(this.animation){ 86 | this.animation.setSegment(init, end) 87 | } 88 | } 89 | 90 | setDirection(dir) { 91 | if(this.animation){ 92 | this.animation.setDirection(dir) 93 | } 94 | } 95 | 96 | resetSegments(flag) { 97 | if(this.animation){ 98 | this.animation.resetSegments(flag) 99 | } 100 | } 101 | 102 | play() { 103 | if(this.animation){ 104 | this.animation.play() 105 | } 106 | } 107 | 108 | stop() { 109 | if(this.animation){ 110 | this.animation.stop() 111 | } 112 | } 113 | 114 | loop(value) { 115 | if(this.animation){ 116 | this.animation.loop = value 117 | } 118 | } 119 | 120 | getCurrentFrame() { 121 | if(this.animation){ 122 | return this.animation.currentRawFrame 123 | } 124 | return 0 125 | } 126 | 127 | getFirstFrame() { 128 | if(this.animation){ 129 | return this.animation.firstFrame 130 | } 131 | return 0 132 | } 133 | 134 | render() { 135 | var cloned = React.cloneElement( 136 | this.props.children, 137 | { 138 | ref: this.setElement 139 | } 140 | ) 141 | return cloned 142 | } 143 | 144 | componentWillUnmount() { 145 | if(this.animation) { 146 | try { 147 | this.animation.destroy() 148 | } catch(err) { 149 | console.log('destroy error') 150 | } 151 | this.animation = null 152 | } 153 | if(this.element) { 154 | this.element = null 155 | } 156 | } 157 | } 158 | 159 | Bodymovin.defaultProps = { 160 | autoplay: true, 161 | loop: false 162 | } 163 | 164 | export default Bodymovin -------------------------------------------------------------------------------- /src/components/bodymovin/bodymovin_checkbox.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Bodymovin from './bodymovin' 3 | class BodymovinCheckbox extends React.Component { 4 | 5 | constructor() { 6 | super() 7 | this.checkAnimation = this.checkAnimation.bind(this) 8 | } 9 | 10 | componentWillReceiveProps(props) { 11 | if (props.animate && !this.props.animate) { 12 | this.bm_instance.goToAndPlay(0) 13 | } else if (!props.animate && this.props.animate) { 14 | this.bm_instance.goToAndStop(0) 15 | } 16 | } 17 | 18 | checkAnimation() { 19 | if(this.props.animate) { 20 | this.bm_instance.goToAndPlay(0) 21 | } else { 22 | this.bm_instance.goToAndStop(0) 23 | } 24 | } 25 | 26 | render() { 27 | return (this.bm_instance = elem} animationLoaded={this.checkAnimation} animationData={this.props.animationData}> 28 | {this.props.children} 29 | ) 30 | } 31 | } 32 | 33 | export default BodymovinCheckbox -------------------------------------------------------------------------------- /src/components/bodymovin/bodymovin_dots.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, css } from 'aphrodite' 3 | import Bodymovin from './bodymovin' 4 | import dots from '../../assets/animations/dots.json' 5 | 6 | const styles = StyleSheet.create({ 7 | container: { 8 | width: '100%', 9 | height: '100%', 10 | cursor: 'pointer', 11 | background: 'none', 12 | padding: 0 13 | } 14 | }) 15 | 16 | class BodymovinSettings extends React.Component { 17 | 18 | constructor() { 19 | super() 20 | this.mouseEnterHandler = this.mouseEnterHandler.bind(this) 21 | this.mouseLeaveHandler = this.mouseLeaveHandler.bind(this) 22 | } 23 | 24 | mouseEnterHandler() { 25 | this.bm_instance.resetSegments(true) 26 | this.bm_instance.playSegments([0,48], true) 27 | } 28 | 29 | mouseLeaveHandler() { 30 | let currentFrame = this.bm_instance.getCurrentFrame() 31 | this.bm_instance.resetSegments(true) 32 | this.bm_instance.playSegments([currentFrame,62], true) 33 | } 34 | 35 | render() { 36 | return (this.bm_instance = elem} animationData={dots} rendererSettings={{preserveAspectRatio:'xMinYMid meet'}} autoplay={false}> 37 | ) 78 | } 79 | } 80 | 81 | export default BodymovinFolder -------------------------------------------------------------------------------- /src/components/bodymovin/bodymovin_refresh.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Bodymovin from './bodymovin' 3 | import refresh from '../../assets/animations/refresh.json' 4 | import { StyleSheet, css } from 'aphrodite' 5 | 6 | const styles = StyleSheet.create({ 7 | wrapper: { 8 | width: '100%', 9 | height: '100%', 10 | background:'none', 11 | cursor: 'pointer' 12 | } 13 | }) 14 | 15 | class BodymovinCheckbox extends React.Component { 16 | 17 | constructor() { 18 | super() 19 | this.handleMouseEnter = this.handleMouseEnter.bind(this) 20 | this.handleMouseLeave = this.handleMouseLeave.bind(this) 21 | } 22 | 23 | handleMouseEnter(){ 24 | this.bm_instance.resetSegments(true) 25 | this.bm_instance.playSegments([[0,29],[29,58]], true) 26 | this.bm_instance.loop(true) 27 | } 28 | 29 | handleMouseLeave(){ 30 | let currentFrame = this.bm_instance.getCurrentFrame() 31 | let firstFrame = this.bm_instance.getFirstFrame() 32 | this.bm_instance.resetSegments(true) 33 | this.bm_instance.playSegments([[currentFrame + firstFrame,0]], true) 34 | this.bm_instance.loop(false) 35 | 36 | } 37 | 38 | render() { 39 | return (this.bm_instance = elem} animationLoaded={this.checkAnimation} animationData={refresh} autoplay={false} loop={true}> 40 | 41 | ) 42 | } 43 | } 44 | 45 | export default BodymovinCheckbox -------------------------------------------------------------------------------- /src/components/bodymovin/bodymovin_settings.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Bodymovin from './bodymovin' 3 | class BodymovinSettings extends React.Component { 4 | 5 | constructor() { 6 | super() 7 | this.checkAnimation = this.checkAnimation.bind(this) 8 | } 9 | 10 | componentWillReceiveProps(props) { 11 | if (props.animate && !this.props.animate) { 12 | this.bm_instance.goToAndPlay(0) 13 | } else if (!props.animate && this.props.animate) { 14 | this.bm_instance.goToAndStop(0) 15 | } 16 | } 17 | 18 | checkAnimation() { 19 | if(this.props.animate) { 20 | this.bm_instance.goToAndPlay(0) 21 | } else { 22 | this.bm_instance.goToAndStop(0) 23 | } 24 | } 25 | 26 | render() { 27 | return (this.bm_instance = elem} animationLoaded={this.checkAnimation} animationData={this.props.animationData}> 28 | {this.props.children} 29 | ) 30 | } 31 | } 32 | 33 | export default BodymovinSettings -------------------------------------------------------------------------------- /src/components/bodymovin/bodymovin_toggle.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Bodymovin from './bodymovin' 3 | class BodymovinToggle extends React.PureComponent { 4 | 5 | constructor() { 6 | super() 7 | this.checkAnimation = this.checkAnimation.bind(this) 8 | } 9 | 10 | componentWillReceiveProps(props) { 11 | if (props.toggle !== this.props.toggle) { 12 | if(props.toggle === 'on') { 13 | this.bm_instance.setDirection(1) 14 | } else { 15 | this.bm_instance.setDirection(-1) 16 | } 17 | this.bm_instance.play() 18 | } 19 | } 20 | 21 | checkAnimation() { 22 | if(this.props.toggle === 'on') { 23 | this.bm_instance.goToAndPlay(0) 24 | } else { 25 | this.bm_instance.goToAndStop(0) 26 | } 27 | } 28 | 29 | render() { 30 | return (this.bm_instance = elem} animationLoaded={this.checkAnimation} animationData={this.props.animationData}> 31 | {this.props.children} 32 | ) 33 | } 34 | } 35 | 36 | export default BodymovinToggle -------------------------------------------------------------------------------- /src/components/buttons/Base_button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, css } from 'aphrodite' 3 | import Variables from '../../helpers/styles/variables' 4 | import Bodymovin from '../bodymovin/bodymovin' 5 | 6 | const styles = StyleSheet.create({ 7 | container: { 8 | borderRadius: '3px', 9 | padding: '8px 20px', 10 | color: 'white', 11 | fontSize: '12px', 12 | fontFamily: 'Roboto-Bold', 13 | verticalAlign:'middle', 14 | cursor: 'pointer', 15 | whiteSpace: 'nowrap', 16 | overflow: 'hidden', 17 | textOverflow: 'ellipsis', 18 | minWidth: '80px' 19 | }, 20 | green: { 21 | backgroundColor: Variables.colors.green, 22 | color: Variables.colors.white, 23 | ':disabled': { 24 | color: Variables.colors.gray2, 25 | backgroundColor: Variables.colors.gray, 26 | cursor: 'default' 27 | }, 28 | ':hover': { 29 | backgroundColor: Variables.colors.green2, 30 | color: Variables.colors.white 31 | }, 32 | disabled:{ 33 | color: Variables.colors.gray2, 34 | backgroundColor: Variables.colors.gray, 35 | cursor: 'default' 36 | } 37 | }, 38 | gray: { 39 | backgroundColor: Variables.colors.gray_lighter, 40 | border: '1px solid ' + Variables.colors.button_gray_text, 41 | color: Variables.colors.button_gray_text, 42 | ':disabled': { 43 | color: Variables.colors.gray2, 44 | cursor: 'default' 45 | }, 46 | ':hover': { 47 | backgroundColor: Variables.colors.gray_lighter, 48 | color: Variables.colors.white, 49 | border: '1px solid ' + Variables.colors.white 50 | } 51 | }, 52 | icon: { 53 | display: 'inline-block', 54 | width: '23px', 55 | height: '16px', 56 | verticalAlign: 'top', 57 | marginRight: '10px' 58 | } 59 | 60 | }); 61 | 62 | class BaseButton extends React.Component{ 63 | 64 | constructor(){ 65 | super() 66 | this.mouseEnterHandler = this.mouseEnterHandler.bind(this) 67 | this.mouseLeaveHandler = this.mouseLeaveHandler.bind(this) 68 | } 69 | 70 | mouseEnterHandler(){ 71 | if(this.bm_instance){ 72 | this.bm_instance.goToAndPlay(0) 73 | } 74 | } 75 | 76 | mouseLeaveHandler(){ 77 | if(this.bm_instance){ 78 | this.bm_instance.goToAndStop(0) 79 | } 80 | } 81 | 82 | render(){ 83 | let containerClasses = css( 84 | styles.container, 85 | this.props.type === 'green' && styles.green, 86 | this.props.type === 'gray' && styles.gray, 87 | this.props.disabled && styles.disabled, 88 | this.props.classes 89 | ) 90 | 91 | return () 97 | } 98 | 99 | } 100 | 101 | BaseButton.defaultProps = { 102 | disabled: false 103 | } 104 | 105 | export default BaseButton -------------------------------------------------------------------------------- /src/components/footer/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | import { StyleSheet, css } from 'aphrodite' 4 | 5 | const styles = StyleSheet.create({ 6 | wrapper: { 7 | height: '10px', 8 | position: 'absolute', 9 | bottom:'4px', 10 | right:'0', 11 | overflow: 'hidden', 12 | 'font-size': '10px', 13 | padding: '0 10px', 14 | color: '#aaa' 15 | } 16 | }) 17 | 18 | class Footer extends React.PureComponent { 19 | 20 | render() { 21 | return (
{'Version: ' + this.props.version} 22 |
) 23 | } 24 | } 25 | 26 | function mapStateToProps(state) { 27 | return {version: state.project.version} 28 | } 29 | 30 | export default connect(mapStateToProps, null)(Footer) -------------------------------------------------------------------------------- /src/components/header/Main_header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, css } from 'aphrodite' 3 | import BaseButton from '../buttons/Base_button' 4 | import BodymovinRefresh from '../bodymovin/bodymovin_refresh' 5 | import Variables from '../../helpers/styles/variables' 6 | 7 | const styles = StyleSheet.create({ 8 | container: { 9 | width: '100%', 10 | marginBottom: '10px' 11 | }, 12 | right: { 13 | float: 'right' 14 | }, 15 | buttons_container: { 16 | width: '100%', 17 | height: '50px', 18 | display: 'flex', 19 | alignItems:'center' 20 | }, 21 | button: { 22 | marginRight:'7px', 23 | flexGrow: 0, 24 | }, 25 | buttons_separator: { 26 | flexGrow: 1 27 | }, 28 | refresh: { 29 | width: '40px', 30 | height: '31px', 31 | backgroundColor: 'transparent', 32 | verticalAlign:'middle', 33 | cursor: 'pointer', 34 | transition: 'transform 500ms ease-out', 35 | webkitFilter: 'saturate(100%)' 36 | }, 37 | refresh_image: { 38 | maxWidth: '100%', 39 | maxHeight: '100%' 40 | }, 41 | separator: { 42 | width: '100%', 43 | height: '1px', 44 | backgroundColor: Variables.colors.gray2, 45 | marginTop: '20px', 46 | marginBottom: '20px' 47 | } 48 | }) 49 | 50 | function Main_header(props) { 51 | return (
52 |
53 |
54 | 55 |
56 | 57 |
58 |
59 |
) 60 | } 61 | 62 | export default Main_header -------------------------------------------------------------------------------- /src/components/range/Range.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, css } from 'aphrodite' 3 | import Thumb from '../../assets/svg/preview_thumb.svg' 4 | import Variables from '../../helpers/styles/variables' 5 | 6 | const styles = StyleSheet.create({ 7 | container: { 8 | width: '100%', 9 | height: '30px', 10 | backgroundColor:'transparent', 11 | position: 'relative', 12 | top: 0, 13 | left: 0, 14 | cursor: 'pointer' 15 | }, 16 | progress: { 17 | width: '100%', 18 | height: '10px', 19 | position: 'absolute', 20 | margin: 'auto', 21 | top:0, 22 | bottom:0, 23 | backgroundColor: Variables.colors.gray_darkest, 24 | borderRadius: '5px', 25 | overflow:'hidden' 26 | }, 27 | progress_color: { 28 | width: '100%', 29 | height: '100%', 30 | background: Variables.gradients.blueGreenFull 31 | }, 32 | thumb: { 33 | width: '20px', 34 | height: '20px', 35 | position: 'absolute', 36 | margin: 'auto', 37 | top:0, 38 | bottom:0, 39 | pointerEvents: 'none' 40 | }, 41 | thumbDisplay: { 42 | width: '100%', 43 | height: '100%', 44 | transform: 'translateX(-50%)' 45 | } 46 | }) 47 | 48 | class Range extends React.Component { 49 | 50 | constructor(){ 51 | super() 52 | this.state = { 53 | isDown:false, 54 | width: 0, 55 | mix: 0 56 | } 57 | this.mouseUpHandler = this.mouseUpHandler.bind(this) 58 | this.mouseMoveHandler = this.mouseMoveHandler.bind(this) 59 | this.mouseDownHandler = this.mouseDownHandler.bind(this) 60 | this.resizeHandler = this.resizeHandler.bind(this) 61 | } 62 | 63 | componentWillUpdate(nextProps, nextState) { 64 | if(nextState.isDown === true && this.state.isDown === false) { 65 | document.addEventListener('mouseup', this.mouseUpHandler) 66 | document.addEventListener('mousemove', this.mouseMoveHandler) 67 | } 68 | } 69 | 70 | mouseMoveHandler(ev) { 71 | if(!this.state.isDown){ 72 | return 73 | } 74 | this.props.updateProgress(Math.min(Math.max(0,(ev.clientX - this.state.min) / (this.state.width)),1)) 75 | } 76 | 77 | mouseUpHandler(ev) { 78 | document.removeEventListener('mouseup', this.mouseUpHandler) 79 | document.removeEventListener('mousemove', this.mouseMoveHandler) 80 | this.setState({ 81 | isDown: false 82 | }) 83 | } 84 | 85 | mouseDownHandler(ev) { 86 | if(!this.props.canScrub){ 87 | return 88 | } 89 | var rect = this.container.getBoundingClientRect() 90 | this.setState({ 91 | isDown: true, 92 | width: rect.right - rect.left, 93 | min: rect.left 94 | }) 95 | this.props.updateProgress((ev.clientX - rect.left) / (rect.right - rect.left)) 96 | } 97 | 98 | resizeHandler() { 99 | var rect = this.container.getBoundingClientRect() 100 | this.setState({ 101 | width: rect.right - rect.left, 102 | min: rect.left, 103 | }) 104 | } 105 | 106 | componentDidMount() { 107 | window.addEventListener('resize', this.resizeHandler) 108 | this.resizeHandler() 109 | } 110 | 111 | componentWillUnmount() { 112 | window.removeEventListener('resize', this.resizeHandler) 113 | document.removeEventListener('mouseup', this.mouseUpHandler) 114 | document.removeEventListener('mousemove', this.mouseMoveHandler) 115 | } 116 | 117 | render() { 118 | let pos = this.state.width * this.props.progress 119 | let styler = { 120 | 'transform':'translateX(' + pos + 'px)', 121 | 'WebkitTransform':'translateX(' + pos + 'px)' 122 | } 123 | 124 | let progressStyler = { 125 | 'transform':'translateX(' + -(1-this.props.progress)*100 + '%)', 126 | 'WebkitTransform':'translateX(' + -(1-this.props.progress)*100 + '%)' 127 | } 128 | 129 | return (
this.container = elem} 131 | className={css(styles.container)} 132 | onMouseDown={this.mouseDownHandler} > 133 |
134 |
135 | 136 |
137 |
138 |
139 |
140 | Thumb 141 |
142 |
143 |
) 144 | } 145 | } 146 | 147 | export default Range -------------------------------------------------------------------------------- /src/helpers/CSInterfaceHelper.js: -------------------------------------------------------------------------------- 1 | import {CSInterface} from './CSInterface' 2 | var csInterface = new CSInterface(); 3 | 4 | export default csInterface -------------------------------------------------------------------------------- /src/helpers/CompositionsStateSync.js: -------------------------------------------------------------------------------- 1 | import csInterface from './CSInterfaceHelper' 2 | import extensionLoader from './ExtensionLoader' 3 | 4 | function setCompositionSelection(comp) { 5 | extensionLoader.then(function(){ 6 | var eScript = '$.__bodymovin.bm_compsManager.setCompositionSelectionState(' + comp.id + ',' + comp.selected + ')' 7 | csInterface.evalScript(eScript) 8 | }) 9 | } 10 | 11 | export { 12 | setCompositionSelection 13 | } -------------------------------------------------------------------------------- /src/helpers/DataCompressorHelper.js: -------------------------------------------------------------------------------- 1 | var zlib = require('zlib'); 2 | 3 | function compressData(data) { 4 | let path = data.path 5 | let message = data.message 6 | 7 | var input = new Buffer(message, 'utf8') 8 | return new Promise(function(res, rej) { 9 | zlib.gzip(input, {level: zlib.Z_BEST_COMPRESSION}, function(err, buf) { 10 | res({path: path, buf: buf}) 11 | }) 12 | }); 13 | } 14 | 15 | export default compressData 16 | -------------------------------------------------------------------------------- /src/helpers/ExtensionLoader.js: -------------------------------------------------------------------------------- 1 | import {SystemPath} from './CSInterface' 2 | import csInterface from './CSInterfaceHelper' 3 | 4 | var fileName = 'initializer.jsx'; 5 | var promise = new Promise(loadJSX); 6 | var isRunning = false; 7 | 8 | function loadJSX(resolve, reject) { 9 | function init(){ 10 | if(isRunning) { 11 | return; 12 | } 13 | isRunning = true; 14 | var extensionRoot = csInterface.getSystemPath(SystemPath.EXTENSION) + "/jsx/"; 15 | csInterface.evalScript('$.evalFile("' + extensionRoot + fileName + '");' 16 | , function(){ 17 | window.removeEventListener('focus', init); 18 | window.removeEventListener('click', init); 19 | window.removeEventListener('mousedown', init); 20 | window.removeEventListener('mouseenter', init); 21 | window.removeEventListener('mouseover', init); 22 | window.removeEventListener('mousemove', init); 23 | resolve(); 24 | }); 25 | } 26 | window.addEventListener('focus', init); 27 | window.addEventListener('click', init); 28 | window.addEventListener('mousedown', init); 29 | window.addEventListener('mouseenter', init); 30 | window.addEventListener('mouseover', init); 31 | window.addEventListener('mousemove', init); 32 | } 33 | 34 | export default promise; -------------------------------------------------------------------------------- /src/helpers/FileBrowser.js: -------------------------------------------------------------------------------- 1 | import csInterface from './CSInterfaceHelper' 2 | import extensionLoader from './ExtensionLoader' 3 | 4 | var resolve, reject 5 | 6 | csInterface.addEventListener('bm:file:uri', function (ev) { 7 | resolve(ev.data) 8 | }) 9 | 10 | csInterface.addEventListener('bm:file:cancel', function (ev) { 11 | reject() 12 | }) 13 | 14 | function browseFile(path) { 15 | var promise = new Promise(function(_resolve, _reject) { 16 | resolve = _resolve 17 | reject = _reject 18 | }) 19 | 20 | extensionLoader.then(function(){ 21 | path = path ? path.replace(/\\/g,"\\\\") : '' 22 | var eScript = '$.__bodymovin.bm_main.browseFile("' + path + '")'; 23 | csInterface.evalScript(eScript); 24 | }) 25 | return promise 26 | } 27 | 28 | export default browseFile -------------------------------------------------------------------------------- /src/helpers/FileLoader.js: -------------------------------------------------------------------------------- 1 | function loadBodymovinFileData(path) { 2 | var reject, resolve 3 | var promise = new Promise(function(_resolve, _reject) { 4 | resolve = _resolve 5 | reject = _reject 6 | }) 7 | var result = window.cep.fs.readFile(path) 8 | try { 9 | if(result.err === 0) { 10 | var jsonData = JSON.parse(result.data) 11 | if(jsonData.v) { 12 | resolve(jsonData) 13 | } 14 | } else { 15 | reject() 16 | } 17 | } catch(err) { 18 | reject() 19 | } 20 | 21 | return promise 22 | } 23 | 24 | export default loadBodymovinFileData -------------------------------------------------------------------------------- /src/helpers/FileSaver.js: -------------------------------------------------------------------------------- 1 | function saveFile(data, formats, name) { 2 | var result; 3 | try { 4 | result = window.cep.fs.showSaveDialogEx('Select location', '', formats, name); 5 | } catch(err) { 6 | result = window.cep.fs.showOpenDialog(false, true); 7 | } 8 | if(result.data) { 9 | var targetFilePath = (result.data instanceof Array) ? result.data[0] + '/snapshot.svg' : result.data; 10 | var writeResult = window.cep.fs.writeFile(targetFilePath, data); 11 | if (0 !== writeResult.err) { 12 | console.log('ERROR: ', writeResult.err) 13 | } 14 | } 15 | } 16 | 17 | export default saveFile -------------------------------------------------------------------------------- /src/helpers/RenderBridge.js: -------------------------------------------------------------------------------- 1 | import csInterface from './CSInterfaceHelper' 2 | import {dispatcher} from './storeDispatcher' 3 | import actions from '../redux/actions/actionTypes' 4 | 5 | csInterface.addEventListener('bm:render:start', function (ev) { 6 | if(ev.data) { 7 | } else { 8 | } 9 | }); 10 | 11 | csInterface.addEventListener('bm:render:complete', function (ev) { 12 | if(ev.data) { 13 | } else { 14 | } 15 | }); 16 | 17 | csInterface.addEventListener('bm:render:update', function (ev) { 18 | if(ev.data) { 19 | } else { 20 | } 21 | }); 22 | 23 | csInterface.addEventListener('bm:render:fonts', function (ev) { 24 | if(ev.data) { 25 | } else { 26 | } 27 | }); 28 | 29 | 30 | csInterface.addEventListener('bm:render:chars', function (ev) { 31 | if(ev.data) { 32 | } else { 33 | } 34 | }); 35 | 36 | -------------------------------------------------------------------------------- /src/helpers/fs_proxy.js: -------------------------------------------------------------------------------- 1 | function proxy_fs() { 2 | return window.__fs 3 | } 4 | 5 | module.exports = proxy_fs() -------------------------------------------------------------------------------- /src/helpers/localStorageHelper.js: -------------------------------------------------------------------------------- 1 | function getProjectFromLocalStorage(id) { 2 | let resolve, reject 3 | let prom = new Promise(function(_resolve, _reject){ 4 | resolve = _resolve 5 | reject = _reject 6 | }) 7 | try { 8 | var project = localStorage.getItem('project_' + id); 9 | if(project) { 10 | resolve(JSON.parse(project)) 11 | } else { 12 | reject() 13 | } 14 | } catch(err) { 15 | reject() 16 | } 17 | return prom 18 | } 19 | 20 | function saveProjectToLocalStorage(data, id) { 21 | let resolve, reject 22 | let prom = new Promise(function(_resolve, _reject){ 23 | resolve = _resolve 24 | reject = _reject 25 | }) 26 | try { 27 | let serialized = JSON.stringify(data) 28 | localStorage.setItem('project_' + id, serialized) 29 | resolve() 30 | } catch(err) { 31 | reject() 32 | } 33 | return prom 34 | } 35 | 36 | function getFontsFromLocalStorage(fonts) { 37 | let resolve, reject 38 | let prom = new Promise(function(_resolve, _reject){ 39 | resolve = _resolve 40 | reject = _reject 41 | }) 42 | var storedFonts = JSON.parse(localStorage.getItem('fonts')) 43 | storedFonts = storedFonts || [] 44 | let len = storedFonts.length, i = 0 45 | let storedFont 46 | let storedData = fonts.map(function(item){ 47 | storedFont = { 48 | fName: item.name, 49 | data:null 50 | } 51 | i = 0 52 | while(ianimVersion[0]){ 4 | return false; 5 | } else if(animVersion[0] > minimum[0]){ 6 | return true; 7 | } 8 | if(minimum[1]>animVersion[1]){ 9 | return false; 10 | } else if(animVersion[1] > minimum[1]){ 11 | return true; 12 | } 13 | if(minimum[2]>animVersion[2]){ 14 | return false; 15 | } else if(animVersion[2] > minimum[2]){ 16 | return true; 17 | } 18 | } 19 | 20 | export default checkVersion -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | *{ 2 | box-sizing: border-box; 3 | } 4 | 5 | html, body { 6 | margin: 0; 7 | padding: 0; 8 | font-family: Roboto, Tahoma, sans-serif; 9 | background-color: #474747; 10 | width: 100%; 11 | height: 100%; 12 | overflow: hidden; 13 | } 14 | 15 | button, input { 16 | font-family: Roboto, Tahoma, sans-serif; 17 | } 18 | 19 | button:focus { 20 | outline: none; 21 | } 22 | 23 | #root { 24 | height: 100%; 25 | } 26 | 27 | [data-reactroot] 28 | {height: 100% !important; } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import './index.css'; 5 | 6 | ReactDOM.render( 7 | , 8 | document.getElementById('root') 9 | ); -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/redux/actions/actionTypes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | COMPOSITION_DISPLAY_SETTINGS: 'COMPOSITIONS/DISPLAY_SETTINGS', 3 | COMPOSITIONS_FILTER_CHANGE: 'COMPOSITIONS/FILTER_CHANGE', 4 | COMPOSITIONS_GET_COMPS: 'COMPOSITIONS/GET_COMPS', 5 | COMPOSITION_GET_DESTINATION: 'COMPOSITIONS/GET_DESTINATION', 6 | COMPOSITION_SET_DESTINATION: 'COMPOSITIONS/SET_DESTINATION', 7 | COMPOSITIONS_TOGGLE_ITEM: 'COMPOSITIONS/TOGGLE_ITEM', 8 | COMPOSITIONS_UPDATED: 'COMPOSITIONS/UPDATED', 9 | COMPOSITIONS_SET_CURRENT_COMP_ID: 'COMPOSITIONS/SET_CURRENT_COMP_ID', 10 | GOTO_PREVIEW: 'GOTO/PREVIEW', 11 | GOTO_PLAYER: 'GOTO/PLAYER', 12 | GOTO_COMPS: 'GOTO/COMPS', 13 | GOTO_SETTINGS: 'GOTO/SETTINGS', 14 | GENERAL_LOG: 'GENERAL/LOG', 15 | PREVIEW_BROWSE_FILE: 'PREVIEW/BROWSE_FILE', 16 | PREVIEW_FILE_BROWSED: 'PREVIEW/FILE_BROWSED', 17 | PREVIEW_ANIMATION_LOADED: 'PREVIEW/ANIMATION_LOADED', 18 | PREVIEW_ANIMATION_LOAD_FAILED: 'PREVIEW/ANIMATION_LOAD_FAILED', 19 | PREVIEW_ANIMATION_PROGRESS: 'PREVIEW/ANIMATION_PROGRESS', 20 | PREVIEW_FROM_PATH: 'PREVIEW/FROM_PATH', 21 | PREVIEW_TOTAL_FRAMES: 'PREVIEW/TOTAL_FRAMES', 22 | PREVIEW_NO_CURRENT_RENDERS: 'PREVIEW/NO_CURRENT_RENDERS', 23 | RENDER_PROCESS_IMAGE: 'RENDER/PROCESS_IMAGE', 24 | PROJECT_SET_ID: 'PROJECT/SET_ID', 25 | PROJECT_STORED_DATA: 'PROJECT/STORED_DATA', 26 | RENDER_CREATE_AVD: 'RENDER/CREATE_AVD', 27 | RENDER_BLOCK: 'RENDER/BLOCK', 28 | RENDER_FONTS: 'RENDER/FONTS', 29 | RENDER_SET_FONTS: 'RENDER/SET_FONTS', 30 | RENDER_START: 'RENDER/START', 31 | RENDER_STOP: 'RENDER/STOP', 32 | RENDER_STORED_FONTS_FETCHED: 'RENDER/STORED_FONTS_FETCHED', 33 | RENDER_COMPLETE: 'RENDER/COMPLETE', 34 | RENDER_FINISHED: 'RENDER/FINISHED', 35 | RENDER_UPDATE: 'RENDER/UPDATE', 36 | RENDER_UPDATE_FONT_ORIGIN: 'RENDER/UPDATE_FONT_ORIGIN', 37 | RENDER_UPDATE_INPUT: 'RENDER/UPDATE_INPUT', 38 | SETTINGS_CANCEL: 'SETTINGS/CANCEL', 39 | SETTINGS_TOGGLE_VALUE: 'SETTINGS/TOGGLE_VALUE', 40 | SETTINGS_TOGGLE_EXTRA_COMP: 'SETTINGS/TOGGLE_EXTRA_COMP', 41 | SETTINGS_UPDATE_VALUE: 'SETTINGS/UPDATE_VALUE', 42 | SETTINGS_TOGGLE_SELECTED: 'SETTINGS/TOGGLE_SELECTED', 43 | ALERT_HIDE: 'ALERT/HIDE', 44 | PATHS_GET: 'PATHS/GET', 45 | PATHS_FETCHED: 'PATHS/FETCHED', 46 | VERSION_GET: 'VERSION/GET', 47 | VERSION_FETCHED: 'VERSION/FETCHED', 48 | APP_VERSION_FETCHED: 'APP_VERSION/FETCHED', 49 | WRITE_ERROR: 'WRITE/ERROR', 50 | TG_COMPRESS: 'TG/COMPRESS' 51 | } -------------------------------------------------------------------------------- /src/redux/actions/compositionActions.js: -------------------------------------------------------------------------------- 1 | import actionTypes from './actionTypes' 2 | 3 | function filterChange(event) { 4 | return { 5 | type: actionTypes.COMPOSITIONS_FILTER_CHANGE, 6 | value: event.target.value 7 | } 8 | } 9 | 10 | function toggleItem(comp) { 11 | return { 12 | type: actionTypes.COMPOSITIONS_TOGGLE_ITEM, 13 | id: comp.id 14 | } 15 | } 16 | 17 | function getCompositions() { 18 | return { 19 | type: actionTypes.COMPOSITIONS_GET_COMPS 20 | } 21 | } 22 | 23 | function setDestination(comp) { 24 | return { 25 | type: actionTypes.COMPOSITION_SET_DESTINATION, 26 | comp: comp 27 | } 28 | } 29 | 30 | function getDestination(comp) { 31 | return { 32 | type: actionTypes.COMPOSITION_GET_DESTINATION, 33 | comp: comp 34 | } 35 | } 36 | 37 | function displaySettings(id) { 38 | return { 39 | type: actionTypes.COMPOSITION_DISPLAY_SETTINGS, 40 | id: id 41 | } 42 | } 43 | 44 | function setCurrentCompId(id) { 45 | return { 46 | type: actionTypes.COMPOSITIONS_SET_CURRENT_COMP_ID, 47 | id: id 48 | } 49 | } 50 | 51 | function cancelSettings(storedSettings) { 52 | return { 53 | type: actionTypes.SETTINGS_CANCEL, 54 | storedSettings: storedSettings 55 | } 56 | } 57 | 58 | function toggleSettingsValue(name) { 59 | return { 60 | type: actionTypes.SETTINGS_TOGGLE_VALUE, 61 | name: name 62 | } 63 | } 64 | 65 | function updateSettingsValue(name, value) { 66 | return { 67 | type: actionTypes.SETTINGS_UPDATE_VALUE, 68 | value: value, 69 | name: name 70 | } 71 | } 72 | 73 | function toggleExtraComp(id) { 74 | return { 75 | type: actionTypes.SETTINGS_TOGGLE_EXTRA_COMP, 76 | id: id, 77 | } 78 | } 79 | 80 | function goToPreview() { 81 | return { 82 | type: actionTypes.GOTO_PREVIEW, 83 | } 84 | } 85 | 86 | function goToPlayer() { 87 | return { 88 | type: actionTypes.GOTO_PLAYER, 89 | } 90 | } 91 | 92 | function goToComps() { 93 | return { 94 | type: actionTypes.GOTO_COMPS, 95 | } 96 | } 97 | 98 | function toggleShowSelected() { 99 | console.log('toggleShowSelected:') 100 | return { 101 | type: actionTypes.SETTINGS_TOGGLE_SELECTED, 102 | } 103 | } 104 | 105 | export { 106 | filterChange, 107 | toggleShowSelected, 108 | toggleItem, 109 | getCompositions, 110 | getDestination, 111 | setDestination, 112 | displaySettings, 113 | setCurrentCompId, 114 | cancelSettings, 115 | toggleSettingsValue, 116 | toggleExtraComp, 117 | updateSettingsValue, 118 | goToPreview, 119 | goToPlayer, 120 | goToComps 121 | } -------------------------------------------------------------------------------- /src/redux/actions/generalActions.js: -------------------------------------------------------------------------------- 1 | import actionTypes from './actionTypes' 2 | 3 | function hideAlert() { 4 | return { 5 | type: actionTypes.ALERT_HIDE 6 | } 7 | } 8 | function getPaths() { 9 | return { 10 | type: actionTypes.PATHS_GET 11 | } 12 | } 13 | 14 | function getVersion() { 15 | return { 16 | type: actionTypes.VERSION_GET 17 | } 18 | } 19 | 20 | function versionFetched(version) { 21 | return { 22 | type: actionTypes.VERSION_FETCHED, 23 | version: version 24 | } 25 | } 26 | 27 | function appVersionFetched(version) { 28 | return { 29 | type: actionTypes.APP_VERSION_FETCHED, 30 | version: version 31 | } 32 | } 33 | 34 | export { 35 | hideAlert, 36 | getPaths, 37 | getVersion, 38 | versionFetched, 39 | appVersionFetched 40 | } -------------------------------------------------------------------------------- /src/redux/actions/previewActions.js: -------------------------------------------------------------------------------- 1 | import actionTypes from './actionTypes' 2 | 3 | function browsePreviewFile(event) { 4 | return { 5 | type: actionTypes.PREVIEW_BROWSE_FILE 6 | } 7 | } 8 | 9 | function previewFileBrowsed(event) { 10 | return { 11 | type: actionTypes.PREVIEW_FILE_BROWSED 12 | } 13 | } 14 | 15 | function updateProgress(progress) { 16 | return { 17 | type: actionTypes.PREVIEW_ANIMATION_PROGRESS, 18 | progress: progress 19 | } 20 | } 21 | 22 | function setTotalFrames(value) { 23 | return { 24 | type: actionTypes.PREVIEW_TOTAL_FRAMES, 25 | totalFrames: value 26 | } 27 | } 28 | 29 | function showNoCurrentRenders(pars) { 30 | return { 31 | type: actionTypes.PREVIEW_NO_CURRENT_RENDERS 32 | } 33 | } 34 | 35 | function previewFromPath(path) { 36 | return { 37 | type: actionTypes.PREVIEW_FILE_BROWSED, 38 | path: path 39 | } 40 | } 41 | 42 | export { 43 | browsePreviewFile, 44 | previewFileBrowsed, 45 | updateProgress, 46 | setTotalFrames, 47 | showNoCurrentRenders, 48 | previewFromPath 49 | } -------------------------------------------------------------------------------- /src/redux/actions/renderActions.js: -------------------------------------------------------------------------------- 1 | import actionTypes from './actionTypes' 2 | 3 | function startRender(comp) { 4 | return { 5 | type: actionTypes.RENDER_START 6 | } 7 | } 8 | 9 | function stopRender(comp) { 10 | return { 11 | type: actionTypes.RENDER_STOP 12 | } 13 | } 14 | 15 | function updateFontOrigin(origin, item) { 16 | return { 17 | type: actionTypes.RENDER_UPDATE_FONT_ORIGIN, 18 | item: item, 19 | origin: origin 20 | } 21 | } 22 | 23 | function updateInput(value, inputName, item) { 24 | return { 25 | type: actionTypes.RENDER_UPDATE_INPUT, 26 | item: item, 27 | inputName: inputName, 28 | value: value 29 | } 30 | } 31 | 32 | function setFonts() { 33 | return { 34 | type: actionTypes.RENDER_SET_FONTS 35 | } 36 | } 37 | 38 | function showRenderBlock(pars) { 39 | return { 40 | type: actionTypes.RENDER_BLOCK, 41 | pars: pars 42 | } 43 | } 44 | 45 | export { 46 | startRender, 47 | stopRender, 48 | updateFontOrigin, 49 | updateInput, 50 | setFonts, 51 | showRenderBlock 52 | } -------------------------------------------------------------------------------- /src/redux/reducers/alerts.js: -------------------------------------------------------------------------------- 1 | import actionTypes from '../actions/actionTypes' 2 | 3 | let initialState = { 4 | show: false, 5 | type: '', 6 | pars:[] 7 | } 8 | 9 | export default function project(state = initialState, action) { 10 | switch (action.type) { 11 | case actionTypes.RENDER_BLOCK: 12 | case actionTypes.WRITE_ERROR: 13 | return {...state, ...{show: true, pars:action.pars}} 14 | case actionTypes.PREVIEW_NO_CURRENT_RENDERS: 15 | return {...state, ...{show: true, pars:['You have no current renders to preview','Try browsing your files to select a .json file']}} 16 | case actionTypes.PREVIEW_ANIMATION_LOAD_FAILED: 17 | return {...state, ...{show: true, pars:['The animation could not be loaded']}} 18 | /*case actionTypes.GENERAL_LOG: 19 | return {...state, ...{show: true, pars:[action.data]}}*/ 20 | case actionTypes.ALERT_HIDE: 21 | return {...state, ...{show: false}} 22 | default: 23 | return state 24 | } 25 | } -------------------------------------------------------------------------------- /src/redux/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import compositions from './compositions' 3 | import render from './render' 4 | import project from './project' 5 | import preview from './preview' 6 | import alerts from './alerts' 7 | import paths from './paths' 8 | import routes from './routes' 9 | 10 | export default combineReducers({ 11 | routes, 12 | compositions, 13 | render, 14 | project, 15 | preview, 16 | alerts, 17 | paths 18 | }) -------------------------------------------------------------------------------- /src/redux/reducers/paths.js: -------------------------------------------------------------------------------- 1 | import actionTypes from '../actions/actionTypes' 2 | 3 | let initialState = { 4 | destinationPath: '', 5 | previewPath: '' 6 | } 7 | 8 | function setDestinationPath(state, action) { 9 | let newState = {...state} 10 | let destinationPath = action.compositionData.destination.substring(0,action.compositionData.destination.lastIndexOf('\\') + 1) 11 | newState.destinationPath = destinationPath 12 | return newState 13 | } 14 | 15 | function setPreviewPath(state, action) { 16 | let newState = {...state} 17 | let previewPath = action.path.substring(0,action.path.lastIndexOf('\\') + 1) 18 | newState.previewPath = previewPath 19 | return newState 20 | } 21 | 22 | export default function project(state = initialState, action) { 23 | switch (action.type) { 24 | case actionTypes.COMPOSITION_SET_DESTINATION: 25 | return setDestinationPath(state, action) 26 | case actionTypes.PREVIEW_FILE_BROWSED: 27 | return setPreviewPath(state, action) 28 | case actionTypes.PATHS_FETCHED: 29 | return action.pathsData 30 | default: 31 | return state 32 | } 33 | } -------------------------------------------------------------------------------- /src/redux/reducers/preview.js: -------------------------------------------------------------------------------- 1 | import actionTypes from '../actions/actionTypes' 2 | 3 | let initialState = { 4 | progress: 0, 5 | animationData: null, 6 | path: null, 7 | totalFrames:0 8 | } 9 | 10 | export default function project(state = initialState, action) { 11 | switch (action.type) { 12 | case actionTypes.PREVIEW_ANIMATION_LOADED: 13 | return {...state, ...{animationData: action.animationData, path: action.path}} 14 | case actionTypes.PREVIEW_ANIMATION_PROGRESS: 15 | return {...state, ...{progress: action.progress}} 16 | case actionTypes.PREVIEW_TOTAL_FRAMES: 17 | return {...state, ...{totalFrames: action.totalFrames}} 18 | default: 19 | return state 20 | } 21 | } -------------------------------------------------------------------------------- /src/redux/reducers/project.js: -------------------------------------------------------------------------------- 1 | import actionTypes from '../actions/actionTypes' 2 | 3 | let initialState = { 4 | id: '', 5 | version: '', 6 | app_version: '' 7 | } 8 | 9 | export default function project(state = initialState, action) { 10 | switch (action.type) { 11 | case actionTypes.PROJECT_SET_ID: 12 | return {...state, ...{id: action.id}} 13 | case actionTypes.VERSION_FETCHED: 14 | return {...state, ...{version: action.version}} 15 | case actionTypes.APP_VERSION_FETCHED: 16 | return {...state, ...{app_version: action.version ? action.version.substr(0, action.version.indexOf('x')) : '0.0.0'}} 17 | default: 18 | return state 19 | } 20 | } -------------------------------------------------------------------------------- /src/redux/reducers/render.js: -------------------------------------------------------------------------------- 1 | import actionTypes from '../actions/actionTypes' 2 | 3 | let initialState = { 4 | message: '', 5 | progress: 0, 6 | finished: false, 7 | cancelled: false, 8 | fonts: [] 9 | } 10 | 11 | function updateRenderData(state, action) { 12 | let newState = {...state, ...{message: action.data.message, progress: action.data.progress}} 13 | return newState 14 | } 15 | 16 | function updateFontsData(state, action) { 17 | let fontFormData = { 18 | origin: 0, 19 | fPath:'', 20 | fClass:'', 21 | fFamily:'', 22 | fWeight:'', 23 | fStyle:'', 24 | fName:'' 25 | } 26 | let fonts = [] 27 | let item, i, len = action.data.fonts.length 28 | for(i = 0; i < len; i += 1) { 29 | item = action.data.fonts[i] 30 | fonts.push({...fontFormData,...{fFamily: item.family, fStyle: item.style, fName: item.name}}) 31 | } 32 | let newState = {...state, ...{fonts: fonts}} 33 | return newState 34 | } 35 | 36 | function updateFontOrigin(state, action) { 37 | let fonts = state.fonts 38 | let index = fonts.indexOf(action.item) 39 | let newFontData = {...fonts[index], ...{origin: action.origin}} 40 | let newFonts = [...fonts.slice(0,index),newFontData,...fonts.slice(index + 1)] 41 | let newState ={...state, ...{fonts: newFonts}} 42 | return newState 43 | } 44 | 45 | function updateInput(state, action) { 46 | let fonts = state.fonts 47 | let index = fonts.indexOf(action.item) 48 | let newFontData = {...fonts[index], ...{[action.inputName]: action.value}} 49 | let newFonts = [...fonts.slice(0,index),newFontData,...fonts.slice(index + 1)] 50 | let newState ={...state, ...{fonts: newFonts}} 51 | return newState 52 | } 53 | 54 | function updateFontFromLocalData(state, action) { 55 | let fonts = state.fonts 56 | let storedFonts = action.storedFonts 57 | if(!storedFonts){ 58 | return state 59 | } 60 | let len = storedFonts.length 61 | let i 62 | let newFonts = fonts.map(function(item) { 63 | i = 0 64 | while(i state.compositions.items[id] 4 | 5 | const getCompositionsList = createSelector( 6 | [ getItem ], 7 | (item) => { 8 | return item 9 | } 10 | ) 11 | 12 | export default getCompositionsList -------------------------------------------------------------------------------- /src/redux/selectors/compositions_selector.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | 3 | const getFilter = (state) => state.compositions.filter 4 | const getSelected = (state) => state.compositions.show_only_selected 5 | const getItems = (state) => state.compositions.items 6 | const getList = (state) => state.compositions.list 7 | 8 | function getVisibleItems(items, list, filter, showOnlySelected) { 9 | filter = filter.toLowerCase(); 10 | let renderItemIds = list.filter((id, val) => { 11 | let item = items[id] 12 | //return true 13 | return item.name.toLowerCase().indexOf(filter) !== -1 || filter === '' 14 | }) 15 | let renderItems = renderItemIds.map((id) => { 16 | let item = items[id] 17 | /*let copyItem = { 18 | id: item.id, 19 | name: item.name, 20 | hidden: !(item.name.indexOf(filter) !== -1 || filter === '') 21 | }*/ 22 | return item 23 | }).filter((item) => (!showOnlySelected || (showOnlySelected && item.selected))) 24 | return renderItems 25 | } 26 | 27 | function checkRenderable(items, list) { 28 | let canRender = list.some((id, val) => { 29 | return items[id].selected && items[id].destination 30 | }) 31 | return canRender 32 | 33 | } 34 | 35 | const getCompositionsList = createSelector( 36 | [ getFilter, getItems, getList, getSelected ], 37 | (filter, items, list, showOnlySelected) => { 38 | return { 39 | canRender: checkRenderable(items, list), 40 | filter: filter, 41 | showOnlySelected: showOnlySelected, 42 | visibleItems: getVisibleItems(items, list, filter, showOnlySelected) 43 | } 44 | } 45 | ) 46 | 47 | export default getCompositionsList -------------------------------------------------------------------------------- /src/redux/selectors/fonts_view_selector.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | 3 | const getFonts = (state) => state.render.fonts 4 | 5 | const getFontsViewData = createSelector( 6 | [ getFonts ], 7 | (fonts) => { 8 | return { 9 | fonts: fonts 10 | } 11 | } 12 | ) 13 | 14 | export default getFontsViewData -------------------------------------------------------------------------------- /src/redux/selectors/preview_view_selector.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | 3 | const getPreview = (state) => state.preview 4 | const getCompositions = (state) => state.compositions 5 | 6 | function getRenderer(animationData) { 7 | var i = 0, len = animationData.layers.length; 8 | while(i < len){ 9 | if(animationData.layers[i].ddd) { 10 | return 'html' 11 | } 12 | i += 1; 13 | 14 | } 15 | return 'svg' 16 | } 17 | 18 | const previewViewSelector = createSelector( 19 | [ getPreview, getCompositions ], 20 | (preview, compositions) => { 21 | let totalFrames, renderer 22 | if(preview.animationData) { 23 | totalFrames = preview.animationData.op - preview.animationData.ip 24 | renderer = getRenderer(preview.animationData) 25 | } else { 26 | renderer = 'svg' 27 | totalFrames = 1 28 | } 29 | 30 | let previewableItems = compositions.list.filter(function(id){ 31 | return compositions.items[id].renderStatus === 1 && compositions.items[id].settings.standalone === false 32 | }).map(function(id){ 33 | return compositions.items[id] 34 | }) 35 | 36 | return { 37 | preview: preview, 38 | totalFrames: totalFrames, 39 | renderer: renderer, 40 | previewableItems: previewableItems 41 | } 42 | } 43 | ) 44 | 45 | export default previewViewSelector -------------------------------------------------------------------------------- /src/redux/selectors/render_composition_selector.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | 3 | const getItem = (state) => { 4 | if(state.render.cancelled){ 5 | return null 6 | } 7 | let i = 0, len = state.compositions.list.length 8 | while(i < len) { 9 | if(state.compositions.items[state.compositions.list[i]].selected && state.compositions.items[state.compositions.list[i]].destination && state.compositions.items[state.compositions.list[i]].renderStatus === 0){ 10 | return state.compositions.items[state.compositions.list[i]] 11 | } 12 | i += 1 13 | } 14 | return null 15 | } 16 | 17 | const getRenderComposition = createSelector( 18 | [ getItem ], 19 | (item) => { 20 | return item 21 | } 22 | ) 23 | 24 | export default getRenderComposition -------------------------------------------------------------------------------- /src/redux/selectors/render_font_selector.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | 3 | const getFonts = (state) => state.render.fonts 4 | 5 | const renderFontSelector = createSelector( 6 | [ getFonts ], 7 | (fonts) => { 8 | return fonts 9 | } 10 | ) 11 | 12 | export default renderFontSelector -------------------------------------------------------------------------------- /src/redux/selectors/render_selector.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | 3 | const getItems = (state) => state.compositions.items 4 | const getList = (state) => state.compositions.list 5 | const getRender = (state) => state.render 6 | 7 | const renderSelector = createSelector( 8 | [ getItems, getList, getRender ], 9 | (items, list, render) => { 10 | let renderingItems = list.reduce(function(a, id) { 11 | let item = items[id] 12 | if(item.selected && item.destination) { 13 | a.push(item) 14 | } 15 | return a 16 | }, []) 17 | return { 18 | renderingItems: renderingItems, 19 | render: render 20 | } 21 | } 22 | ) 23 | 24 | export default renderSelector -------------------------------------------------------------------------------- /src/redux/selectors/set_fonts_selector.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | 3 | const getFonts = (state) => state.render.fonts 4 | 5 | const setFontSelector = createSelector( 6 | [ getFonts ], 7 | (fonts) => { 8 | return fonts 9 | } 10 | ) 11 | 12 | export default setFontSelector -------------------------------------------------------------------------------- /src/redux/selectors/settings_comp_selector.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | 3 | const getItemId = (state) => { 4 | return state.compositions.current 5 | } 6 | 7 | const getRenderComposition = createSelector( 8 | [ getItemId ], 9 | (itemId) => { 10 | return itemId 11 | } 12 | ) 13 | 14 | export default getRenderComposition -------------------------------------------------------------------------------- /src/redux/selectors/settings_view_selector.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | import validateVersion from '../../helpers/versionValidator' 3 | 4 | const getItems = (state) => { 5 | return state.compositions.items 6 | } 7 | const getList = (state) => { 8 | return state.compositions.list 9 | } 10 | const getCurrentComp = (state) => { 11 | return state.compositions.current 12 | } 13 | const getProjectVersion = (state) => { 14 | return state.project.app_version 15 | } 16 | 17 | function getExtraCompsList(extra, list, items) { 18 | let extraComps = list.map(function(id, index){ 19 | let comp = items[id] 20 | let extraCompData = { 21 | id: id, 22 | name: comp.name, 23 | selected: extra.list.indexOf(id) !== -1 24 | } 25 | return extraCompData 26 | }) 27 | return extraComps 28 | } 29 | 30 | const getRenderComposition = createSelector( 31 | [getList, getItems, getCurrentComp, getProjectVersion ], 32 | (list, items, current, projectVersion) => { 33 | let canCompressAssets = validateVersion([10,0,0], projectVersion); 34 | return { 35 | settings: items[current] ? items[current].settings : null, 36 | extraCompsList: items[current] ? getExtraCompsList(items[current].settings.extraComps, list, items) : [], 37 | canCompressAssets: canCompressAssets 38 | } 39 | } 40 | ) 41 | 42 | export default getRenderComposition -------------------------------------------------------------------------------- /src/redux/selectors/storing_data_selector.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | 3 | const getCompositions = (state) => state.compositions.items 4 | const getID = (state) => state.project.id 5 | 6 | const storingDataSelector = createSelector( 7 | [ getCompositions, getID ], 8 | (compositions, id) => { 9 | return { 10 | data: { 11 | compositions: compositions 12 | }, 13 | id: id 14 | } 15 | } 16 | ) 17 | 18 | export default storingDataSelector -------------------------------------------------------------------------------- /src/redux/selectors/storing_paths_selector.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | 3 | const getPaths = (state) => state.paths 4 | 5 | const storingPathsSelector = createSelector( 6 | [ getPaths ], 7 | (paths) => { 8 | return paths 9 | } 10 | ) 11 | 12 | export default storingPathsSelector -------------------------------------------------------------------------------- /src/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } -------------------------------------------------------------------------------- /src/views/ViewsContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | 4 | import Render from './render/Render' 5 | import Compositions from './compositions/Compositions' 6 | import SettingsView from './settings/Settings' 7 | import PreviewView from './preview/Preview' 8 | import FontsView from './fonts/Fonts' 9 | import PlayerView from './player/Player' 10 | 11 | function getView(route) { 12 | switch(route) { 13 | case 0: 14 | return 15 | case 1: 16 | return 17 | case 2: 18 | return 19 | case 3: 20 | return 21 | case 4: 22 | return 23 | case 5: 24 | return 25 | default: 26 | return 27 | } 28 | } 29 | 30 | let ViewsContainer = (props) =>
{getView(props.route)}
31 | 32 | function mapStateToProps(state) { 33 | return state.routes 34 | } 35 | 36 | export default connect(mapStateToProps,null)(ViewsContainer) -------------------------------------------------------------------------------- /src/views/compositions/Compositions.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | import { StyleSheet, css } from 'aphrodite' 4 | import CompositionsList from './list/CompositionsList' 5 | import CompositionsListHeader from './listHeader/CompositionsListHeader' 6 | import MainHeader from '../../components/header/Main_header' 7 | import {getDestination, filterChange, toggleItem, displaySettings, getCompositions, goToPreview, goToPlayer, toggleShowSelected} from '../../redux/actions/compositionActions' 8 | import {startRender, showRenderBlock} from '../../redux/actions/renderActions' 9 | import compositions_selector from '../../redux/selectors/compositions_selector' 10 | import Variables from '../../helpers/styles/variables' 11 | 12 | const styles = StyleSheet.create({ 13 | wrapper: { 14 | width: '100%', 15 | height: '100%', 16 | padding: '10px', 17 | backgroundColor: '#474747' 18 | }, 19 | toggleButton: { 20 | fontSize: '12px', 21 | color: '#eee', 22 | textDecoration:'underline', 23 | cursor: 'pointer', 24 | paddingTop: '6px', 25 | ':hover': { 26 | color: Variables.colors.green, 27 | } 28 | } 29 | }) 30 | 31 | class Compositions extends React.Component { 32 | 33 | constructor() { 34 | super() 35 | this.selectDestination = this.selectDestination.bind(this) 36 | this.showSettings = this.showSettings.bind(this) 37 | this.renderComps = this.renderComps.bind(this) 38 | //this.goToPreview = this.goToPreview.bind(this) 39 | } 40 | 41 | selectDestination(comp) { 42 | this.props.getDestination(comp) 43 | } 44 | 45 | showSettings(item) { 46 | this.props.displaySettings(item.id) 47 | } 48 | 49 | 50 | 51 | renderComps() { 52 | if(!this.props.canRender){ 53 | this.props.showRenderBlock(['There are no Compositions to render.','Make sure you have at least one selected and a Destination Path set.']) 54 | } else { 55 | this.props.startRender() 56 | //browserHistory.push('/render') 57 | } 58 | 59 | } 60 | /*goToPreview() { 61 | //browserHistory.push('/preview') 62 | }*/ 63 | 64 | /*goToPlayer() { 65 | browserHistory.push('/player') 66 | }*/ 67 | 68 | render() { 69 | 70 | return ( 71 |
72 | 78 | 81 | 86 |
87 | ) 88 | } 89 | } 90 | 91 | function mapStateToProps(state) { 92 | return compositions_selector(state) 93 | } 94 | 95 | const mapDispatchToProps = { 96 | getDestination: getDestination, 97 | toggleItem: toggleItem, 98 | displaySettings: displaySettings, 99 | getCompositions: getCompositions, 100 | filterChange: filterChange, 101 | startRender: startRender, 102 | goToPreview: goToPreview, 103 | goToPlayer: goToPlayer, 104 | showRenderBlock: showRenderBlock, 105 | toggleShowSelected: toggleShowSelected 106 | } 107 | 108 | export default connect(mapStateToProps, mapDispatchToProps)(Compositions) 109 | -------------------------------------------------------------------------------- /src/views/compositions/list/CompositionsList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, css } from 'aphrodite' 3 | import CompositionsListItem from './CompositionsListItem' 4 | 5 | const styles = StyleSheet.create({ 6 | list: { 7 | height: 'calc( 100% - 180px)', 8 | overflow: 'auto' 9 | } 10 | }) 11 | 12 | class CompositionsList extends React.PureComponent { 13 | 14 | createItem(item) { 15 | return 21 | } 22 | 23 | render() { 24 | 25 | let items = this.props.items.map((item) => { 26 | return this.createItem(item) 27 | }) 28 | 29 | return ( 30 |
    31 | {items} 32 |
33 | ); 34 | } 35 | } 36 | 37 | export default CompositionsList -------------------------------------------------------------------------------- /src/views/compositions/list/CompositionsListItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, css } from 'aphrodite' 3 | import BodymovinCheckbox from '../../../components/bodymovin/bodymovin_checkbox' 4 | import BodymovinDots from '../../../components/bodymovin/bodymovin_dots' 5 | import checkbox from '../../../assets/animations/checkbox.json' 6 | import Variables from '../../../helpers/styles/variables' 7 | import textEllipsis from '../../../helpers/styles/textEllipsis' 8 | 9 | const styles = StyleSheet.create({ 10 | composition: { 11 | width: '100%', 12 | fontSize: '12px', 13 | color: '#ffffff', 14 | backgroundColor: Variables.colors.gray_darkest, 15 | height: '30px', 16 | marginBottom: '2px' 17 | }, 18 | composition__selected: { 19 | background: Variables.gradients.blueGreen 20 | }, 21 | item: { 22 | display: 'inline-block', 23 | verticalAlign: 'middle', 24 | backgroundColor:'transparent' 25 | }, 26 | itemContainer: { 27 | padding: '4px 0', 28 | width: '100%', 29 | height: '100%' 30 | }, 31 | radio: { 32 | height: '100%', 33 | width: '70px', 34 | padding:'2px', 35 | cursor: 'pointer' 36 | }, 37 | settings: { 38 | height: '100%', 39 | width: '80px', 40 | cursor: 'pointer' 41 | }, 42 | name: { 43 | width: 'calc( 60% - 75px)', 44 | lineHeight: '22px', 45 | padding: '0 10px' 46 | }, 47 | destination: { 48 | width: 'calc( 40% - 75px)', 49 | padding: '0 10px', 50 | color: Variables.colors.green 51 | }, 52 | destinationPlaceholder: { 53 | width: 'calc( 40% - 75px)', 54 | padding: '0 0 0 10px', 55 | height: '100%' 56 | }, 57 | hidden: { 58 | display: 'none' 59 | }, 60 | 'destination_placeholder--dot': { 61 | width: '4px', 62 | height: '4px', 63 | borderRadius: '50%', 64 | backgroundColor: Variables.colors.green, 65 | display: 'inline-block', 66 | marginRight: '2px' 67 | } 68 | }) 69 | 70 | class CompositionsListItem extends React.Component { 71 | 72 | constructor() { 73 | super() 74 | this.showSettings = this.showSettings.bind(this) 75 | this.toggleItem = this.toggleItem.bind(this) 76 | this.selectDestination = this.selectDestination.bind(this) 77 | this.settingsHovered = this.settingsHovered.bind(this) 78 | this.settingsLeft = this.settingsLeft.bind(this) 79 | this.state = { 80 | settingsHovered: false 81 | } 82 | } 83 | 84 | shouldComponentUpdate(nextProps, nextState) { 85 | return this.props.item !== nextProps.item || this.state !== nextState 86 | } 87 | 88 | showSettings() { 89 | this.props.showSettings(this.props.item) 90 | } 91 | 92 | toggleItem() { 93 | this.props.toggleItem(this.props.item) 94 | } 95 | 96 | selectDestination() { 97 | this.props.selectDestination(this.props.item) 98 | } 99 | 100 | settingsHovered() { 101 | this.setState({settingsHovered:true}) 102 | } 103 | 104 | settingsLeft() { 105 | this.setState({settingsHovered:false}) 106 | } 107 | 108 | render(){ 109 | return (
  • 111 |
    112 | 113 | 114 | 115 |
    {this.props.item.name}
    116 | {this.props.item.destination 117 | &&
    {this.props.item.destination}
    } 118 | {!this.props.item.destination &&
    119 | 120 |
    } 121 |
    122 |
  • ) 123 | } 124 | } 125 | 126 | export default CompositionsListItem -------------------------------------------------------------------------------- /src/views/compositions/listHeader/CompositionsListHeader.css: -------------------------------------------------------------------------------- 1 | .testClase{ 2 | width: 150px; 3 | background-color: red; 4 | } -------------------------------------------------------------------------------- /src/views/compositions/listHeader/CompositionsListHeader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StyleSheet, css } from 'aphrodite'; 3 | import glass from '../../../assets/svg/glass.svg' 4 | import Variables from '../../../helpers/styles/variables' 5 | import textEllipsis from '../../../helpers/styles/textEllipsis' 6 | 7 | const styles = StyleSheet.create({ 8 | container: { 9 | width: '100%', 10 | fontSize: '12px', 11 | color: '#eee', 12 | marginBottom: '10px' 13 | }, 14 | item: { 15 | display: 'inline-block', 16 | verticalAlign: 'middle', 17 | textAlign: 'center', 18 | padding: '0px 10px', 19 | }, 20 | radio: { 21 | width: '70px' 22 | }, 23 | settings: { 24 | width: '80px' 25 | }, 26 | name: { 27 | width: 'calc( 60% - 75px)', 28 | height: '24px' 29 | 30 | }, 31 | nameBox: { 32 | border: '2px solid ' + Variables.colors.gray2, 33 | backgroundColor: '#333', 34 | borderRadius: '6px', 35 | width: '100%', 36 | height: '100%' 37 | }, 38 | name_input: { 39 | display: 'inline-block', 40 | verticalAlign: 'top', 41 | height: '100%', 42 | width: 'calc( 100% - 30px)', 43 | background: 'none', 44 | color: '#eee', 45 | padding: '0px 3px', 46 | border: 'none', 47 | ':focus': { 48 | border: 'none', 49 | outline: 'none' 50 | } 51 | }, 52 | name_glass: { 53 | display: 'inline-block', 54 | verticalAlign: 'top', 55 | width: '30px', 56 | height: '100%', 57 | backgroundImage: 'url("'+glass+'")', 58 | backgroundRepeat: 'no-repeat', 59 | backgroundPosition: 'center', 60 | backgroundSize: '17px 17px' 61 | }, 62 | name_glass_image: { 63 | maxHeight: '50%', 64 | maxWidth: '50%' 65 | }, 66 | destination: { 67 | width: 'calc( 40% - 75px)', 68 | textAlign: 'left' 69 | } 70 | }); 71 | 72 | class CompositionsListHeader extends React.Component { 73 | render() { 74 | return ( 75 |
      76 |
    • Selected
    • 77 |
    • 78 |
      79 | 80 |
      81 |
      82 |
      83 |
    • 84 |
    • Destination Folder
    • 85 |
    86 | ); 87 | } 88 | } 89 | 90 | export default CompositionsListHeader -------------------------------------------------------------------------------- /src/views/fonts/Fonts.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | import { StyleSheet, css } from 'aphrodite' 4 | import fonts_view_selector from '../../redux/selectors/fonts_view_selector' 5 | import BaseButton from '../../components/buttons/Base_button' 6 | import FontForm from './form/FontForm' 7 | import {updateFontOrigin, updateInput, setFonts, stopRender} from '../../redux/actions/renderActions' 8 | import Bodymovin from '../../components/bodymovin/bodymovin' 9 | import fontsAnim from '../../assets/animations/fonts.json' 10 | import Variables from '../../helpers/styles/variables' 11 | 12 | const styles = StyleSheet.create({ 13 | wrapper: { 14 | width: '100%', 15 | height: '100%', 16 | padding: '10px', 17 | backgroundColor: '#474747' 18 | }, 19 | container: { 20 | width: '100%', 21 | height: '100%', 22 | display: 'flex', 23 | flexDirection:'column' 24 | }, 25 | header: { 26 | width: '100%', 27 | height: '60px', 28 | fontFamily: 'Roboto-Bold', 29 | fontSize: '12px', 30 | marginBottom: '20px', 31 | flexGrow: 0, 32 | flexShrink: 0, 33 | borderBottom: '1px solid ' + Variables.colors.gray2 34 | }, 35 | headerTitle:{ 36 | display: 'inline-block', 37 | verticalAlign: 'middle', 38 | color: Variables.colors.white 39 | }, 40 | header_animation_container: { 41 | height: '100%', 42 | width: '50px', 43 | display: 'inline-block', 44 | verticalAlign: 'middle' 45 | }, 46 | list: { 47 | width: '100%', 48 | flexGrow: 1, 49 | flexShrink: 1, 50 | position: 'relative', 51 | backgroundColor: Variables.colors.gray_darkest, 52 | padding: '4px', 53 | overflowY: 'hidden', 54 | overflowX: 'hidden', 55 | marginBottom: '20px' 56 | }, 57 | list_items: { 58 | position: 'absolute', 59 | top: '0', 60 | left: '0', 61 | overflowY: 'auto', 62 | width: '100%', 63 | height:'100%' 64 | }, 65 | buttons: { 66 | width: '100%', 67 | flexGrow: 0, 68 | flexShrink: 0, 69 | textAlign: 'center' 70 | }, 71 | button_separator: { 72 | width: '10px', 73 | display: 'inline-block' 74 | } 75 | }) 76 | 77 | class Fonts extends React.Component { 78 | 79 | constructor(){ 80 | super() 81 | this.cancelRender = this.cancelRender.bind(this) 82 | this.setFonts = this.setFonts.bind(this) 83 | } 84 | 85 | cancelRender() { 86 | this.props.stopRender() 87 | //browserHistory.push('/') 88 | } 89 | 90 | getFontsList() { 91 | return this.props.fonts.map((item) => { 92 | return 97 | }) 98 | } 99 | 100 | setFonts() { 101 | this.props.setFonts() 102 | //browserHistory.push('/render') 103 | } 104 | 105 | render() { 106 | return ( 107 |
    108 |
    109 |
    110 | 111 |
    112 | 113 |
    Select font families and font paths when necessary
    114 |
    115 |
    116 |
    117 | {this.getFontsList()} 118 |
    119 |
    120 |
    121 | 122 |
    123 | 124 |
    125 |
    126 |
    127 | ); 128 | } 129 | } 130 | 131 | function mapStateToProps(state) { 132 | return fonts_view_selector(state) 133 | } 134 | 135 | const mapDispatchToProps = { 136 | updateFontOrigin: updateFontOrigin, 137 | updateInput: updateInput, 138 | setFonts: setFonts, 139 | stopRender: stopRender 140 | } 141 | 142 | export default connect(mapStateToProps, mapDispatchToProps)(Fonts) 143 | -------------------------------------------------------------------------------- /src/views/fonts/form/FontForm.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, css } from 'aphrodite' 3 | import BodymovinCheckbox from '../../../components/bodymovin/bodymovin_checkbox' 4 | import anim from '../../../assets/animations/checkbox.json' 5 | import Variables from '../../../helpers/styles/variables' 6 | 7 | const styles = StyleSheet.create({ 8 | wrapper:{ 9 | marginTop: '5px', 10 | marginBottom: '15px', 11 | borderBottom: '2px solid ' + Variables.colors.gray_lighter 12 | }, 13 | originSelect: { 14 | display:'inline-block', 15 | verticalAlign:'middle', 16 | paddingBottom: '10px', 17 | marginRight: '15px' 18 | 19 | }, 20 | 'originSelect--radio': { 21 | width: '20px', 22 | padding:'2px', 23 | background:'none', 24 | display:'inline-block', 25 | verticalAlign:'middle', 26 | ':focus':{ 27 | border:'none', 28 | outline:'none' 29 | } 30 | }, 31 | 'originSelect--label': { 32 | display:'inline-block', 33 | verticalAlign:'middle', 34 | color:'white', 35 | fontSize: '10px' 36 | }, 37 | inputLabel:{ 38 | color:'white', 39 | fontSize: '10px' 40 | }, 41 | inputBox:{ 42 | color:'white', 43 | width:'100%', 44 | border: '1px solid ' + Variables.colors.white, 45 | backgroundColor: Variables.colors.gray_darkest, 46 | borderRadius: '6px', 47 | lineHeight:'20px', 48 | padding: '2px', 49 | ':focus':{ 50 | outline:'none' 51 | } 52 | }, 53 | inputBlock:{ 54 | padding: '0 4px 10px 4px' 55 | }, 56 | halfBlock:{ 57 | width:'50%', 58 | display: 'inline-block' 59 | }, 60 | fontNameTitle: { 61 | color: Variables.colors.white, 62 | fontFamily: 'Roboto-Bold', 63 | fontSize: '12px', 64 | padding: '0 4px 10px 4px' 65 | } 66 | }) 67 | 68 | let FontForm = function(props) { 69 | return ( 70 |
    71 |
    72 |

    {props.data.fName}

    73 |
      74 |
    • props.changeOrigin(0, props.data)}> 75 | 76 | 77 | 78 |
      None
      79 |
    • 80 |
    • props.changeOrigin(1, props.data)}> 81 | 82 | 83 | 84 |
      Google Font
      85 |
    • 86 |
    • props.changeOrigin(2, props.data)}> 87 | 88 | 89 | 90 |
      Typekit
      91 |
    • 92 |
    • props.changeOrigin(3, props.data)}> 93 | 94 | 95 | 96 |
      URL
      97 |
    • 98 |
    99 |
    100 |
    CSS Class
    101 | props.updateInput(ev.target.value, 'fClass', props.data)} 105 | value={props.data.fClass}/> 106 |
    107 |
    108 |
    Font Path
    109 | props.updateInput(ev.target.value, 'fPath', props.data)} 113 | value={props.data.fPath}/> 114 |
    115 |
    116 |
    Font Family
    117 | props.updateInput(ev.target.value, 'fFamily', props.data)} 121 | value={props.data.fFamily}/> 122 |
    123 |
    124 |
    Font Weight
    125 | props.updateInput(ev.target.value, 'fWeight', props.data)} 129 | value={props.data.fWeight}/> 130 |
    131 |
    132 |
    Font Style
    133 | props.updateInput(ev.target.value, 'fStyle', props.data)} 137 | value={props.data.fStyle}/> 138 |
    139 |
    140 |
    141 | ) 142 | } 143 | 144 | export default FontForm -------------------------------------------------------------------------------- /src/views/player/Player.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | import { StyleSheet, css } from 'aphrodite' 4 | import BaseButton from '../../components/buttons/Base_button' 5 | import Bodymovin from '../../components/bodymovin/bodymovin' 6 | import anim from '../../assets/animations/bm.json' 7 | import {openInBrowser, getPlayer} from '../../helpers/CompositionsProvider' 8 | import Variables from '../../helpers/styles/variables' 9 | import {goToComps} from '../../redux/actions/compositionActions' 10 | 11 | const styles = StyleSheet.create({ 12 | container: { 13 | width: '100%', 14 | height: '100%', 15 | display: 'flex', 16 | flexDirection:'column', 17 | padding: '10px 30px', 18 | backgroundColor :'#474747' 19 | }, 20 | back_container: { 21 | textAlign: 'right' 22 | }, 23 | anim_container: { 24 | textAlign: 'center' 25 | }, 26 | bm_container: { 27 | width: '80px', 28 | height: '80px', 29 | display: 'inline-block' 30 | }, 31 | text_container: { 32 | paddingBottom: '15px', 33 | textAlign: 'center' 34 | }, 35 | text_title: { 36 | color: Variables.colors.white, 37 | fontFamily: 'Roboto-Black', 38 | paddingBottom: '25px', 39 | fontSize: '14px' 40 | }, 41 | text_par: { 42 | color: '#fff', 43 | fontSize: '10px', 44 | lineHeight:'14px' 45 | }, 46 | link: { 47 | color: Variables.colors.green 48 | }, 49 | buttons_container: { 50 | textAlign: 'center' 51 | }, 52 | buttonSeparator: { 53 | width: '10px', 54 | display: 'inline-block' 55 | } 56 | }) 57 | 58 | class Player extends React.Component { 59 | 60 | openInBrowser(){ 61 | openInBrowser('https://github.com/airbnb/lottie-web') 62 | } 63 | 64 | getPlayer(){ 65 | getPlayer(false) 66 | } 67 | 68 | getPlayerZipped(){ 69 | getPlayer(true) 70 | } 71 | 72 | render() { 73 | return ( 74 |
    75 |
    76 | 77 |
    78 |
    79 | 80 |
    81 |
    82 |
    83 |
    84 |
    Bodymovin for Telegram Stickers
    85 |
    86 |

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

    87 |

    In order to play the exported animation on your browser follow the instructions at 88 | Lottie on github 89 |

    90 |
    91 |

    You can get the latest version of the player from the repository or copy the one included in the extension here.

    92 |
    93 |
    94 |
    95 | 96 |
    97 | 98 |
    99 |
    100 | ); 101 | } 102 | } 103 | const mapDispatchToProps = { 104 | goToComps: goToComps 105 | } 106 | 107 | export default connect(null, mapDispatchToProps)(Player) 108 | -------------------------------------------------------------------------------- /src/views/preview/Preview.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | import { StyleSheet, css } from 'aphrodite' 4 | import PreviewViewer from './viewer/PreviewViewer' 5 | import PreviewScrubber from './scrubber/PreviewScrubber' 6 | import PreviewHeader from './header/PreviewHeader' 7 | import CurrentRenders from './current_renders/CurrentRenders' 8 | import {browsePreviewFile, updateProgress, setTotalFrames, showNoCurrentRenders, previewFromPath} from '../../redux/actions/previewActions' 9 | import {goToComps} from '../../redux/actions/compositionActions' 10 | import preview_view_selector from '../../redux/selectors/preview_view_selector' 11 | import FileSaver from '../../helpers/FileSaver' 12 | 13 | const styles = StyleSheet.create({ 14 | wrapper: { 15 | width: '100%', 16 | height: '100%', 17 | padding: '10px', 18 | backgroundColor:'#474747' 19 | }, 20 | container: { 21 | width: '100%', 22 | height: '100%', 23 | display: 'flex', 24 | flexDirection:'column' 25 | }, 26 | header: { 27 | width: '100%', 28 | flexGrow: 0, 29 | flexShrink: 0 30 | }, 31 | animation: { 32 | width: '100%', 33 | flexGrow: 1, 34 | flexShrink: 1, 35 | position: 'relative' 36 | }, 37 | scrubber: { 38 | width: '100%', 39 | flexGrow: 0, 40 | flexShrink: 0 41 | } 42 | }) 43 | 44 | class Preview extends React.Component { 45 | 46 | constructor() { 47 | super() 48 | this.updateProgress = this.updateProgress.bind(this) 49 | this.saveFile = this.saveFile.bind(this) 50 | this.itemSelected = this.itemSelected.bind(this) 51 | this.selectCurrentRenders = this.selectCurrentRenders.bind(this) 52 | this.closeSelection = this.closeSelection.bind(this) 53 | this.state = { 54 | showingCurrentRenders: false 55 | } 56 | } 57 | 58 | changeStart() { 59 | //console.log('changeStart') 60 | } 61 | 62 | updateProgress(value) { 63 | this.props.updateProgress(value) 64 | } 65 | 66 | saveFile(fileData) { 67 | 68 | var svgData = this.previewViewer.snapshot() 69 | FileSaver(svgData, ['svg'], 'snapshot.svg') 70 | } 71 | 72 | itemSelected(item) { 73 | this.props.previewFromPath(item.destination) 74 | this.closeSelection() 75 | } 76 | 77 | closeSelection() { 78 | this.setState({ 79 | showingCurrentRenders: false 80 | }) 81 | } 82 | 83 | selectCurrentRenders() { 84 | if(!this.props.previewableItems.length){ 85 | this.props.showNoCurrentRenders() 86 | } else { 87 | this.setState({ 88 | showingCurrentRenders: true 89 | }) 90 | } 91 | } 92 | 93 | render() { 94 | return ( 95 |
    96 |
    97 |
    98 | 102 |
    103 |
    104 | this.previewViewer = elem)} /> 111 |
    112 |
    113 | 119 |
    120 |
    121 | {this.state.showingCurrentRenders && } 125 |
    126 | ); 127 | } 128 | } 129 | 130 | function mapStateToProps(state) { 131 | return preview_view_selector(state) 132 | } 133 | 134 | const mapDispatchToProps = { 135 | browsePreviewFile: browsePreviewFile, 136 | previewFromPath: previewFromPath, 137 | updateProgress: updateProgress, 138 | setTotalFrames: setTotalFrames, 139 | goToComps: goToComps, 140 | showNoCurrentRenders: showNoCurrentRenders 141 | } 142 | 143 | export default connect(mapStateToProps, mapDispatchToProps)(Preview) 144 | -------------------------------------------------------------------------------- /src/views/preview/current_renders/CurrentRenders.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, css } from 'aphrodite' 3 | import BaseButton from '../../../components/buttons/Base_button' 4 | import Variables from '../../../helpers/styles/variables' 5 | import textEllipsis from '../../../helpers/styles/textEllipsis' 6 | 7 | const styles = StyleSheet.create({ 8 | container: { 9 | width: '100%', 10 | height: '100%', 11 | backgroundColor:'rgba(100,100,100,.8)', 12 | position: 'absolute', 13 | top: '0', 14 | left: '0', 15 | display: 'flex', 16 | flexDirection:'column', 17 | padding: '20px' 18 | }, 19 | list:{ 20 | flexGrow: 1, 21 | backgroundColor: Variables.colors.gray_darkest, 22 | width: '100%', 23 | overflowX: 'hidden', 24 | overflowY: 'auto' 25 | }, 26 | nav:{ 27 | flexGrow: 0, 28 | textAlign: 'right', 29 | marginBottom: '10px' 30 | }, 31 | list_item:{ 32 | width: '100%', 33 | height: '30px', 34 | fontSize: '12px', 35 | lineHeight: '26px', 36 | padding: '2px', 37 | color: Variables.colors.white, 38 | backgroundColor: Variables.colors.gray, 39 | borderBottom: '2px solid ' + Variables.colors.gray2, 40 | cursor: 'pointer', 41 | overflow: 'hidden', 42 | ':hover' : { 43 | color: Variables.colors.green, 44 | } 45 | } 46 | }) 47 | 48 | function getItems(items, itemSelected) { 49 | return items.map(function(item, index){ 50 | return
  • itemSelected(item)} className={css(styles.list_item, textEllipsis)}>{item.name}
  • 51 | }) 52 | } 53 | 54 | let CurrentRenders = (props) => { 55 | return (
    56 |
    57 | 58 |
    59 |
      60 | {getItems(props.items, props.itemSelected)} 61 |
    62 |
    ) 63 | } 64 | 65 | export default CurrentRenders -------------------------------------------------------------------------------- /src/views/preview/header/PreviewHeader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, css } from 'aphrodite' 3 | import Variables from '../../../helpers/styles/variables' 4 | import BaseButton from '../../../components/buttons/Base_button' 5 | 6 | const styles = StyleSheet.create({ 7 | container: { 8 | width: '100%', 9 | marginBottom: '10px' 10 | }, 11 | right: { 12 | float: 'right' 13 | }, 14 | buttons_container: { 15 | width: '100%', 16 | height: '50px', 17 | display: 'flex', 18 | alignItems:'center' 19 | }, 20 | button: { 21 | marginRight:'5px', 22 | flexGrow: 0 23 | }, 24 | buttons_separator: { 25 | flexGrow: 1 26 | }, 27 | refresh: { 28 | width: '40px', 29 | height: '34px', 30 | backgroundColor: 'transparent', 31 | verticalAlign:'middle' 32 | }, 33 | refresh_image: { 34 | maxWidth: '100%', 35 | maxHeight: '100%' 36 | }, 37 | separator: { 38 | width: '100%', 39 | height: '1px', 40 | backgroundColor: Variables.colors.gray2, 41 | marginTop: '20px', 42 | marginBottom: '20px' 43 | } 44 | }) 45 | 46 | function PreviewHeader(props) { 47 | return (
    48 |
    49 | 50 | 51 |
    52 | 53 |
    54 |
    55 |
    ) 56 | } 57 | 58 | export default PreviewHeader -------------------------------------------------------------------------------- /src/views/preview/scrubber/PreviewScrubber.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, css } from 'aphrodite' 3 | import Range from '../../../components/range/Range' 4 | import BaseButton from '../../../components/buttons/Base_button' 5 | import Variables from '../../../helpers/styles/variables' 6 | import snapshot from '../../../assets/animations/snapshot.json' 7 | 8 | const styles = StyleSheet.create({ 9 | container: { 10 | width: '100%', 11 | height: '80px' 12 | }, 13 | navContainer: { 14 | width: '100%', 15 | height: '36px', 16 | display: 'flex' 17 | }, 18 | progressNumberContainer: { 19 | fontSize: '20px', 20 | lineHeight: '28px', 21 | color: Variables.colors.blue, 22 | flexGrow: 0, 23 | cursor: 'pointer' 24 | }, 25 | progressNumber: { 26 | fontSize: '20px', 27 | lineHeight: '28px', 28 | color: Variables.colors.blue, 29 | display: 'inline-block' 30 | }, 31 | inputNumber: { 32 | backgroundColor: Variables.colors.gray, 33 | border: '2px solid ' + Variables.colors.gray2, 34 | width: 'auto', 35 | ':focus' :{ 36 | outline: 'none' 37 | } 38 | }, 39 | emptySpace: { 40 | flexGrow: 1, 41 | backgroundColor: 'transparent' 42 | }, 43 | button: { 44 | flexGrow: 0 45 | } 46 | }) 47 | 48 | class PreviewScrubber extends React.Component { 49 | 50 | constructor() { 51 | super() 52 | this.state = { 53 | numberFocused: false, 54 | inputValue:0 55 | } 56 | this.focusNumber = this.focusNumber.bind(this) 57 | this.updateValue = this.updateValue.bind(this) 58 | this.setInitialValue = this.setInitialValue.bind(this) 59 | this.handleBlur = this.handleBlur.bind(this) 60 | this.handleKey = this.handleKey.bind(this) 61 | } 62 | 63 | focusNumber(){ 64 | if(this.props.totalFrames === 0){ 65 | //return 66 | } 67 | this.setState({ 68 | numberFocused: true 69 | }) 70 | } 71 | 72 | updateValue(ev) { 73 | if(ev.target.value === ''){ 74 | this.setState({ 75 | inputValue: '' 76 | }) 77 | return 78 | } 79 | let newValue = parseInt(ev.target.value, 10) 80 | if(isNaN(newValue) || newValue < 0 || newValue > this.props.totalFrames){ 81 | return 82 | } 83 | this.setState({ 84 | inputValue: newValue 85 | }) 86 | this.props.updateProgress(newValue/this.props.totalFrames) 87 | } 88 | 89 | setInitialValue() { 90 | this.setState({ 91 | inputValue: Math.round(this.props.totalFrames * this.props.progress) 92 | }) 93 | } 94 | 95 | handleBlur() { 96 | this.setState({ 97 | numberFocused: false 98 | }) 99 | } 100 | 101 | handleKey(ev){ 102 | if(ev.keyCode === 40 && this.state.inputValue > 0){ 103 | let newValue = this.state.inputValue - 1 104 | this.setState({ 105 | inputValue: newValue 106 | }) 107 | this.props.updateProgress(newValue/this.props.totalFrames) 108 | ev.preventDefault() 109 | }else if(ev.keyCode === 38 && this.state.inputValue < this.props.totalFrames){ 110 | let newValue = this.state.inputValue + 1 111 | this.setState({ 112 | inputValue: newValue 113 | }) 114 | this.props.updateProgress(newValue/this.props.totalFrames) 115 | ev.preventDefault() 116 | } 117 | } 118 | 119 | render() { 120 | 121 | let inputLength = this.props.totalFrames.toString().length 122 | 123 | return ( 124 |
    125 | 126 |
    127 |
    128 | {!this.state.numberFocused 129 | &&
    {Math.round(this.props.totalFrames * this.props.progress)}
    } 130 | {this.state.numberFocused 131 | && } 141 |
     / {this.props.totalFrames}
    142 |
    143 |
    144 | 145 |
    146 |
    147 | ); 148 | } 149 | } 150 | 151 | PreviewScrubber.defaultProps = { 152 | totalFrames: 0, 153 | progress: 0, 154 | max: 1 155 | } 156 | 157 | export default PreviewScrubber 158 | -------------------------------------------------------------------------------- /src/views/preview/viewer/PreviewViewer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, css } from 'aphrodite' 3 | import Bodymovin from '../../../components/bodymovin/bodymovin' 4 | 5 | const styles = StyleSheet.create({ 6 | container: { 7 | width: '100%', 8 | height: '100%', 9 | borderRadius:'2px', 10 | position: 'absolute', 11 | backgroundColor:'#333' 12 | } 13 | }) 14 | 15 | class PreviewViewer extends React.Component { 16 | 17 | constructor() { 18 | super() 19 | 20 | this.animationLoaded = this.animationLoaded.bind(this) 21 | } 22 | 23 | componentWillReceiveProps(props) { 24 | if(props.progress !== this.props.progress && this.bm_instance.animation) { 25 | try{ 26 | this.bm_instance.goToAndStop(parseInt(this.bm_instance.animation.totalFrames * props.progress, 10), true) 27 | } catch(err) { 28 | console.log('errr: ', err) 29 | } 30 | } 31 | if(this.props.animationData !== props.animationData){ 32 | this.selectRenderer() 33 | } 34 | } 35 | 36 | animationLoaded() { 37 | this.props.setTotalFrames(this.bm_instance.animation.totalFrames) 38 | } 39 | 40 | snapshot() { 41 | return this.bm_instance.element.innerHTML 42 | } 43 | 44 | selectRenderer() { 45 | 46 | } 47 | 48 | render() { 49 | return ( 50 | this.bm_instance = elem} renderer={this.props.renderer} path={this.props.path} autoplay={false} animationLoaded={this.animationLoaded} > 51 |
    52 | 53 | ); 54 | } 55 | } 56 | 57 | export default PreviewViewer 58 | -------------------------------------------------------------------------------- /src/views/render/Render.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {connect} from 'react-redux' 3 | import { StyleSheet, css } from 'aphrodite' 4 | import {stopRender} from '../../redux/actions/renderActions' 5 | import {goToComps} from '../../redux/actions/compositionActions' 6 | import render_selector from '../../redux/selectors/render_selector' 7 | import RenderItem from './list/RenderItem' 8 | import BaseButton from '../../components/buttons/Base_button' 9 | import Variables from '../../helpers/styles/variables' 10 | import {goToFolder} from '../../helpers/CompositionsProvider' 11 | import Bodymovin from '../../components/bodymovin/bodymovin' 12 | import fluido from '../../assets/animations/fluido.json' 13 | 14 | const styles = StyleSheet.create({ 15 | wrapper: { 16 | width: '100%', 17 | height: '100%', 18 | padding: '10px', 19 | backgroundColor: '#474747' 20 | }, 21 | container: { 22 | width: '100%', 23 | height: '100%', 24 | fontSize: '12px', 25 | color: Variables.colors.white, 26 | display: 'flex', 27 | flexDirection: 'column' 28 | }, 29 | header: { 30 | width: '100%', 31 | color: Variables.colors.white, 32 | flexGrow: 0, 33 | marginBottom: '10px', 34 | height: '40px' 35 | }, 36 | message: { 37 | overflow: 'hidden', 38 | textOverflow: 'ellipsis', 39 | whiteSpace: 'nowrap', 40 | color: Variables.colors.white, 41 | display: 'inline-block', 42 | fontSize: '12px', 43 | fontFamily: 'Roboto-Bold', 44 | verticalAlign: 'middle' 45 | }, 46 | headerAnim: { 47 | width: '50px', 48 | height: '100%', 49 | display: 'inline-block', 50 | verticalAlign: 'middle' 51 | }, 52 | renderBar: { 53 | borderRadius:'4px', 54 | height:'8px', 55 | width: '100%', 56 | overflow: 'hidden', 57 | position:'relative', 58 | flexGrow: 0, 59 | marginBottom: '20px' 60 | }, 61 | renderBarBackground: { 62 | borderRadius:'4px', 63 | height:'100%', 64 | width: '100%', 65 | backgroundColor: '#303030' 66 | }, 67 | renderBarProgress: { 68 | borderRadius:'4px', 69 | height:'100%', 70 | width: '100%', 71 | position: 'absolute', 72 | top:0, 73 | left:0, 74 | background: 'linear-gradient(left, rgb(0,142,211) 15%,rgb(0,182,72) 85%)' 75 | }, 76 | compsListContainer: { 77 | width: '100%', 78 | background: 'black', 79 | flexGrow: 1, 80 | overflow: 'hidden', 81 | position: 'relative' 82 | }, 83 | compsList: { 84 | width: '100%', 85 | height: '100%', 86 | position: 'absolute', 87 | top: '0', 88 | left: '0', 89 | overflow: 'auto' 90 | }, 91 | bottomNavigation: { 92 | borderRadius:'4px', 93 | width: '100%', 94 | flexGrow: 0, 95 | height: '40px', 96 | marginBottom: '20px', 97 | marginTop: '20px', 98 | textAlign: 'center' 99 | } 100 | }) 101 | 102 | class Render extends React.Component { 103 | 104 | constructor() { 105 | super() 106 | this.endRender = this.endRender.bind(this) 107 | this.getItem = this.getItem.bind(this) 108 | } 109 | 110 | getItem(item) { 111 | return () 115 | } 116 | 117 | getItems() { 118 | return this.props.renderingItems.map(this.getItem) 119 | } 120 | 121 | endRender() { 122 | if(!this.props.render.finished) { 123 | this.props.stopRender() 124 | } else { 125 | this.props.goToComps() 126 | } 127 | //browserHistory.push('/') 128 | } 129 | 130 | navigateToFolder(item) { 131 | goToFolder(item.destination) 132 | } 133 | 134 | 135 | render() { 136 | let progress = this.props.render.progress 137 | let barStyle = {'transform':'translateX(-' + 100 * (1 - progress) + '%)'} 138 | let finishText = this.props.render.finished ? 'Done' : 'Cancel' 139 | return ( 140 |
    141 |
    142 |
    143 | 144 |
    145 |
    146 |
    {this.props.render.message}
    147 |
    148 |
    149 |
    150 |
    151 |
    152 |
    153 |
      154 | {this.getItems()} 155 |
    156 |
    157 |
    158 | 159 |
    160 |
    161 |
    162 | ); 163 | } 164 | } 165 | 166 | function mapStateToProps(state) { 167 | return render_selector(state) 168 | } 169 | 170 | const mapDispatchToProps = { 171 | stopRender: stopRender, 172 | goToComps: goToComps 173 | } 174 | 175 | export default connect(mapStateToProps, mapDispatchToProps)(Render) 176 | -------------------------------------------------------------------------------- /src/views/render/list/RenderItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, css } from 'aphrodite' 3 | import status_button from '../../../assets/svg/cancel_button.svg' 4 | import complete_icon from '../../../assets/svg/complete_icon.svg' 5 | import Variables from '../../../helpers/styles/variables' 6 | import BodymovinFolder from '../../../components/bodymovin/bodymovin_folder' 7 | 8 | const styles = StyleSheet.create({ 9 | compElement: { 10 | width: '100%', 11 | height: '33px', 12 | backgroundColor: Variables.colors.gray_darkest, 13 | position: 'relative' 14 | }, 15 | compElementProgress: { 16 | width: '100%', 17 | height: '100%', 18 | background: Variables.gradients.blueGreen, 19 | opacity: 0.05, 20 | position: 'absolute', 21 | top: 0, 22 | left: 0 23 | }, 24 | compElementContent: { 25 | width: '100%', 26 | height: '100%', 27 | position: 'absolute', 28 | top: 0, 29 | left: 0, 30 | display: 'flex', 31 | alignItems: 'center' 32 | }, 33 | compElementContentFolder: { 34 | width: '50px', 35 | height: '100%', 36 | flexGrow: 0, 37 | padding: '2px 20px 2px 0px' 38 | }, 39 | compElementContentFolder__button: { 40 | background: 'none', 41 | padding: 0, 42 | width: '100%', 43 | height: '100%', 44 | cursor: 'pointer' 45 | }, 46 | compElementContentFolder__image: { 47 | width: '100%', 48 | height: '100%' 49 | }, 50 | compElementContentToggle: { 51 | width: '30px', 52 | height: '100%', 53 | flexGrow: 0, 54 | padding: '5px' 55 | }, 56 | compElementContentToggleImage: { 57 | width: '100%', 58 | height: '100%' 59 | }, 60 | compElementContentName: { 61 | width: '100%', 62 | flexGrow: 1, 63 | padding: '0 4px', 64 | whiteSpace: 'nowrap', 65 | textOverflow: 'ellipsis', 66 | overflow: 'hidden' 67 | } 68 | }) 69 | 70 | let RenderItem = (props) => { 71 | return (
  • 72 |
    73 |
    74 |
    {props.item.name}
    75 | {props.item.renderStatus === 0 &&
    76 | 79 |
    } 80 | {props.item.renderStatus === 1 &&
    81 | toggle 82 |
    } 83 |
    84 |
    props.navigateToFolder(props.item)}> 85 | 86 |
    87 |
    88 |
    89 |
  • ) 90 | } 91 | 92 | export default RenderItem -------------------------------------------------------------------------------- /src/views/settings/collapsable/SettingsCollapsableItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, css } from 'aphrodite' 3 | import BodymovinToggle from '../../../components/bodymovin/bodymovin_toggle' 4 | import expander from '../../../assets/animations/expander.json' 5 | import Variables from '../../../helpers/styles/variables' 6 | import textEllipsis from '../../../helpers/styles/textEllipsis' 7 | 8 | const styles = StyleSheet.create({ 9 | wrapper: { 10 | width: '100%', 11 | height: 'auto', 12 | backgroundColor: Variables.colors.gray_darkest, 13 | }, 14 | composition: { 15 | width: '100%', 16 | fontSize: '12px', 17 | color: Variables.colors.white, 18 | height: '40px', 19 | padding: '4px 0', 20 | display: 'flex', 21 | alignItems: 'center', 22 | cursor: 'pointer' 23 | }, 24 | composition__active: { 25 | background: Variables.gradients.blueGreen 26 | }, 27 | item: { 28 | flexGrow: 0, 29 | flexShrink: 0, 30 | backgroundColor:'transparent' 31 | }, 32 | radio: { 33 | width: '60px', 34 | height: '20px', 35 | padding:'2px', 36 | display:'inline-block' 37 | }, 38 | title: { 39 | width: '60px', 40 | padding:'2px', 41 | display:'inline-block' 42 | }, 43 | name: { 44 | flexGrow: 1, 45 | flexShrink: 1, 46 | padding: '0 4px' 47 | }, 48 | 'name--title': { 49 | color: '#fff', 50 | fontSize: '14px', 51 | marginRight: '10px' 52 | }, 53 | 'name--desc': { 54 | color: '#ccc', 55 | fontSize: '12px' 56 | }, 57 | disabled: { 58 | opacity: .3 59 | }, 60 | children_container: { 61 | padding: '0 0 0 25px' 62 | } 63 | }) 64 | 65 | class SettingsCollapsableItem extends React.PureComponent { 66 | 67 | constructor(props) { 68 | super(props) 69 | this.state = { 70 | toggle: 'off' 71 | } 72 | } 73 | 74 | renderItems() { 75 | 76 | } 77 | 78 | toggleOptions = () => { 79 | this.setState({ 80 | toggle: this.state.toggle === 'on' ? 'off' : 'on' 81 | }) 82 | } 83 | 84 | render(){ 85 | return (
  • 87 |
    88 | 89 | 90 | 91 |
    92 | {this.props.title} 93 | {this.props.description} 94 |
    95 |
    96 | {this.state.toggle === 'on' &&
      97 | {this.props.children} 98 |
    } 99 |
  • ) 100 | } 101 | } 102 | 103 | export default SettingsCollapsableItem -------------------------------------------------------------------------------- /src/views/settings/list/SettingsListItem.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { StyleSheet, css } from 'aphrodite' 3 | import BodymovinCheckbox from '../../../components/bodymovin/bodymovin_checkbox' 4 | import checkbox from '../../../assets/animations/checkbox.json' 5 | import Variables from '../../../helpers/styles/variables' 6 | 7 | const styles = StyleSheet.create({ 8 | wrapper: { 9 | width: '100%', 10 | paddingBottom: '10px', 11 | minHeight: '40px', 12 | backgroundColor: Variables.colors.gray_darkest, 13 | }, 14 | composition: { 15 | width: '100%', 16 | fontSize: '12px', 17 | color: Variables.colors.white, 18 | padding: '4px 0', 19 | height: '100%', 20 | display: 'flex', 21 | alignItems: 'end' 22 | }, 23 | composition__active: { 24 | background: Variables.gradients.blueGreen 25 | }, 26 | item: { 27 | flexGrow: 0, 28 | flexShrink: 0, 29 | backgroundColor:'transparent' 30 | }, 31 | radio: { 32 | width: '60px', 33 | height: '20px', 34 | padding:'2px', 35 | cursor: 'pointer' 36 | }, 37 | name: { 38 | flexGrow: 1, 39 | flexShrink: 1, 40 | padding: '0 4px' 41 | }, 42 | 'name--title': { 43 | color: '#fff', 44 | fontSize: '14px', 45 | marginRight: '10px', 46 | paddingBottom: '4px', 47 | }, 48 | 'name--desc': { 49 | color: '#ccc', 50 | fontSize: '12px', 51 | lineHeight: '14px' 52 | }, 53 | inputBox: { 54 | border: '1px solid ' + Variables.colors.white, 55 | backgroundColor: '#333', 56 | borderRadius: '6px', 57 | maxWidth:'50px', 58 | marginRight:'20px' , 59 | padding: '3px' 60 | }, 61 | inputInput: { 62 | background: 'none', 63 | width:'100%', 64 | border: 'none', 65 | ':focus': { 66 | border: 'none', 67 | outline: 'none' 68 | }, 69 | color: Variables.colors.white 70 | }, 71 | disabled: { 72 | opacity: .3 73 | } 74 | }) 75 | 76 | class SettingsListItem extends React.PureComponent { 77 | 78 | render(){ 79 | return (
  • 81 |
    82 | 83 | 84 | 85 |
    86 |
    {this.props.title}
    87 |
    {this.props.description}
    88 |
    89 | {this.props.needsInput &&
    90 | 91 |
    } 92 |
    93 |
  • ) 94 | } 95 | } 96 | 97 | export default SettingsListItem --------------------------------------------------------------------------------