├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.js ├── config ├── webpack.demo-js-config.js ├── webpack.demo-react-config.js └── webpack.js-config.js ├── examples ├── js-plugin │ ├── dist │ │ ├── filerobot-uploader-widget.image-editor.69055e3179b2e4d4342c.js │ │ ├── filerobot-uploader-widget.vendors~gallery~icons~images.7284004b27a54c4cd773.js │ │ ├── filerobot-uploader-widget.vendors~gallery~tagging.7ad81a64a35a80c7b193.js │ │ ├── filerobot-uploader-widget.vendors~icons~images.6381058cf43531ba759c.js │ │ ├── filerobot-uploader-widget.vendors~image-editor.3437517d0a82b1bc5d1c.js │ │ ├── filerobot-uploader-widget.vendors~tagging.fda2709d04fd1d21f9f4.js │ │ └── index.html │ └── src │ │ ├── assets │ │ └── fonts │ │ │ └── helvetica-neue.css │ │ ├── index.html │ │ ├── index.js │ │ └── style.css └── react-plugin │ └── src │ ├── index.html │ └── index.js ├── package-lock.json ├── package.json └── projects ├── js-plugin └── index.js └── react-plugin ├── assets ├── fonts │ └── filerobot-uploader.font.css ├── styles │ ├── bg.css.js │ ├── colorScheme.js │ ├── drag-drop.css.js │ ├── icons.css.js │ ├── index.js │ ├── main.css │ ├── modal.css.js │ ├── search.css.js │ ├── styles.css.js │ ├── toastr.animation.css │ └── toastr.css └── translations │ ├── en.js │ ├── fr.js │ ├── index.js │ ├── ru.js │ └── utils.js ├── components ├── AirstoreUploader.js ├── AirstoreUploaderWrapper.js ├── AppState.js ├── Button.js ├── CloseBtn.js ├── IconsTab │ ├── IconAddTagModal.js │ ├── IconItem.js │ ├── IconMonoColorSettings.js │ ├── IconSidebar.js │ ├── IconTab.js │ ├── IconTags.js │ └── SearchBar.js ├── ImagesTab │ ├── ImageBox.js │ ├── ImagesColorPicker.js │ ├── ImagesSidebar.js │ └── ImagesTab.js ├── Modal.js ├── ProgressCircle.js ├── Spinner.js ├── TaggingTab │ ├── AutosuggestionInput.js │ ├── CropsBox.js │ ├── TaggingTab.js │ └── TaggingTab.styled.js ├── UploadImagesTab │ ├── PreUploadProcess.js │ ├── UserUploaderTab.js │ ├── UserUploaderTab.styled.js │ └── UserUploaderTab.utils.js ├── UploadedImagesTab │ ├── SortDropdown.js │ ├── SortTick.js │ ├── UploadedImagesContent.js │ ├── UploadedImagesTab.js │ └── folderManager │ │ ├── FolderItem.js │ │ ├── FolderManager.js │ │ ├── bread-crumbs.js │ │ └── folderManager.styled.js ├── VirtualizedImagesGrid.js ├── confirm-popup │ ├── confirm-popup.js │ ├── confirm-popup.styled.js │ └── index.js ├── hoc.js ├── imageEditor │ └── ImageEditorWrapper.js ├── loadable.js └── nav │ ├── Nav.js │ └── Nav.styled.js ├── config.js ├── index.js ├── mocks └── backgrounds.mock.json ├── package.json ├── polyfills.js ├── services ├── api.service.js ├── helper.service.js ├── iconsApi.service.js ├── imageGrid.service.js └── imagesApi.service.js ├── styledComponents ├── AirstoreUploader.styled.js ├── BackgroundTab.styled.js ├── IconTab.styled.js ├── UploadedImages.styled.js ├── index.js └── styleUtils.js └── utils ├── adjustAPI.utils.js ├── files-upload.js ├── global.utils.js ├── helper.utils.js ├── icons.utils.js ├── imageTransformation.utils.js ├── index.js ├── md5.js └── metadata.constants.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://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 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | 24 | .idea 25 | /.idea 26 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # .npmignore 2 | projects 3 | examples 4 | config 5 | build 6 | .babelrc 7 | .gitignore 8 | .idea -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | Types of changes: 8 | - `Added` for new features. 9 | - `Changed` for changes in existing functionality. 10 | - `Deprecated` for soon-to-be removed features. 11 | - `Removed` for now removed features. 12 | - `Fixed` for any bug fixes. 13 | - `Security` in case of vulnerabilities. 14 | 15 | > Date format: YYYY-MM-DD 16 | 17 | > If we have some "Breaking changes" we can mark it in message by `**BREAKING**` preffix, like: 18 | > `- **BREAKING**: Some message` 19 | 20 | ------------- 21 | 22 | ## TODOs 23 | > Todo list for future 24 | 25 | - ... 26 | 27 | ------------- 28 | # 2.15.20 - 2021-01-22 29 | ### Feat 30 | - fix: old image url after editing by Image editor post upload 31 | 32 | # 2.15.19 - 2021-01-21 33 | ### Feat 34 | - feat: add possibility to display/hide Delete button 35 | 36 | # 2.15.18 - 2020-12-21 37 | ### Fixed 38 | - fix: closeOnEdit prop bug 39 | 40 | # 2.15.17 - 2020-12-21 41 | ### Fixed 42 | - fix: add check for closeOnEdit and closeOnSave props in config 43 | 44 | # 2.15.16 - 2020-12-21 45 | ### Fixed 46 | - fix: add check for closeOnEdit and closeOnSave props in config 47 | 48 | # 2.15.15 - 2020-11-25 49 | ### Changed 50 | - refactor: remove react-focus-lock lib 51 | 52 | # 2.15.14 - 2020-11-23 53 | ### Fixed 54 | - fix: bug with empty tags list on upload images via Gallery tab for M1_EDGY version 55 | 56 | # 2.15.13 - 2020-11-23 57 | ### Changed 58 | - chore: update dependencies 59 | 60 | # 2.15.12 - 2020-11-23 61 | ### Fixed 62 | - fix: show confirm modal on click 'back' btn only in case of changes 63 | 64 | # 2.15.11 - 2020-11-20 65 | ### Changed 66 | - Clean imageSealing extra configs (everything must be configurated in user configs). 67 | 68 | # 2.15.10 - 2020-11-19 69 | ### Changed 70 | - Sealing container. 71 | 72 | # 2.15.9 - 2020-11-19 73 | ### Fixed 74 | - ImageEditor config filerobot.imageSealing issue. 75 | 76 | # 2.15.8 - 2020-11-19 77 | ### Changed 78 | - Updating filerobot-image-editor package for having latest version of image editor. 79 | 80 | # 2.15.7 - 2020-11-19 81 | ### Added 82 | - feat: Modify URL add sealing support 83 | 84 | # 2.15.6 - 2020-11-19 85 | ### Changed 86 | - refactor: show description field regardless of EDGY version 87 | 88 | # 2.15.5 - 2020-11-19 89 | ### Fixed 90 | - fix: searching bug 91 | 92 | # 2.15.4 - 2020-11-15 93 | ### Changed 94 | - Updating filerobot-image-editor package for having latest version of image editor. 95 | 96 | # 2.15.3 - 2020-11-10 97 | ### Fixed 98 | - fix: empty field with tags 99 | 100 | # 2.15.2 - 2020-11-03 101 | ### Fixed 102 | - fix: bug with visibility 'generate tags' button 103 | 104 | # 2.15.0 - 2020-11-03 105 | ### Added 106 | - feat: process tagging according token metadata version 107 | - feat: add 'closeOnSave' property 108 | 109 | # 2.14.11 - 2020-10-30 110 | ### Fixed 111 | - fix: tags input disabling 112 | 113 | # 2.14.10 - 2020-10-27 114 | ### Fixed 115 | - fix: fix field styles 116 | 117 | # 2.14.9 - 2020-10-27 118 | ### Fixed 119 | - fix: fix tags updating logic 120 | 121 | # 2.14.8 - 2020-10-27 122 | ### Changed 123 | - feat: add "description" property in config. Allows hide/show description field 124 | 125 | # 2.14.7 - 2020-10-27 126 | ### Fixed 127 | - Fix config preparing 128 | 129 | # 2.14.6 - 2020-10-27 130 | ### Changed 131 | - change updating logic for description and tags fields. Process them depends on filerobot metadata version (M1_EDGY) 132 | 133 | # 2.14.5 - 2020-10-22 134 | ### Changed 135 | - update dependencies 136 | 137 | # 2.14.4 - 2020-10-02 138 | ### Changed 139 | - add classes for wrappers 140 | 141 | # 2.14.3 - 2020-09-24 142 | ### Changed 143 | - update dependencies 144 | 145 | # 2.14.2 - 2020-09-24 146 | ### Changed 147 | - add confirm popup on click "Go back" and "Delete" buttons 148 | 149 | # 2.14.1 - 2020-09-23 150 | ### Changed 151 | - add possibility to hide 'Modify URL' button 152 | - add confirm modal on click delete image 153 | 154 | # 2.14.0 - 2020-08-17 155 | ### Changed 156 | - update dependencies 157 | 158 | # 2.13.6 - 2020-07-28 159 | ### Changed 160 | - update dependencies 161 | 162 | # 2.13.5 - 2020-07-27 163 | ### Changed 164 | - update dependencies 165 | 166 | # 2.13.4 - 2020-07-21 167 | 168 | ### Fixed 169 | - show alert modal on click 'go back' button after generating tags 170 | - bug if customFiled is undefined 171 | - delete warning in console 172 | 173 | # 2.13.3 - 2020-07-17 174 | 175 | ### Fixed 176 | - save data of uploaded image on switch tabs 177 | 178 | # 2.13.2 - 2020-07-17 179 | 180 | ### Fixed 181 | - remove warning about invalid upload key 182 | 183 | # 2.13.1 - 2020-06-14 184 | 185 | ### Fixed 186 | - sorting 187 | 188 | ### Changed 189 | - version of filerobot-image-editor 190 | 191 | # 2.13.0 - 2020-06-12 192 | 193 | ### Fixed 194 | - problem with conflicting two versions for camanjs 195 | 196 | ### Added 197 | - implement modify URL option 198 | 199 | # 2.12.3 - 2020-06-10 200 | 201 | ### Fixed 202 | - reset selected items on outside click in my gallery 203 | - add preprocess logic to my gallery tab 204 | 205 | # 2.12.2 - 2020-06-02 206 | 207 | ### Fixed 208 | - tip position for multi select checkbox; 209 | - bug with scroll to top on click checkbox 210 | 211 | ### Changed 212 | - delay for the controls tips 213 | 214 | # 2.12.1 - 2020-06-01 215 | 216 | ### Fixed 217 | - modal on go back 218 | 219 | 220 | ## 2.12.0 - 2020-06-01 221 | 222 | ### Added 223 | - add tag autocomplete/suggestions 224 | - possibility to auto tag multiply images 225 | - possibility to select/delete/tag multiply images 226 | - 'deselect all' button 227 | - tips on hover for image controls 228 | - alert if changes not saved 229 | 230 | ### Fixed 231 | - problems with cdn links 232 | - do not run auto tagging if file type is not image 233 | - problem with icons in My icons tab 234 | 235 | ### Changed 236 | - design: update item controls structure 237 | - config: rename predefinedTags to suggestionList 238 | - config: move suggestionList into tagging 239 | 240 | 241 | ## 2.11.0 - 2020-05-22 242 | 243 | ### Added 244 | - add possibility to delete image 245 | - add possibility to limit files according to the extension on upload 246 | 247 | ## 2.10.0 - 2020-04-05 248 | 249 | ### Added 250 | - bread crumbs in my gallery tab 251 | - configurable default sort params [check the doc](https://github.com/scaleflex/filerobot-uploader#sortparams-object) 252 | - limit root folder on server [check the doc](https://github.com/scaleflex/filerobot-uploader#folderbrowser-object) 253 | 254 | ### Changed 255 | - folderBrowser *bool* to *object* [check the doc](https://github.com/scaleflex/filerobot-uploader#folderbrowser-object). Now we can set two params: 256 | **show** bool (as previously folderBrowser param was used) - show folder manager, 257 | **rootFolder** string - limit access to some root folder on server 258 | 259 | ## Fixed 260 | - after upload stay in the same directory 261 | - on cancel search stay in the same directory 262 | 263 | 264 | ## 2.9.0 - 2020-04-03 265 | 266 | ### Added 267 | - sort by name, type, modified date, uploaded date in gallery tab 268 | 269 | ### Fixed 270 | - show more images on scroll 271 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 scaleflex 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 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(false); 3 | 4 | const presets = [ 5 | [ 6 | "@babel/env", 7 | { 8 | "targets": { 9 | "browsers": [ 10 | "last 2 versions", 11 | "ie 11" 12 | ] 13 | } 14 | } 15 | ], 16 | "@babel/react" 17 | ]; 18 | 19 | const plugins = [ 20 | "@babel/plugin-transform-object-assign", 21 | "@babel/plugin-syntax-dynamic-import", 22 | "@babel/plugin-proposal-class-properties", 23 | "@babel/plugin-proposal-export-default-from", 24 | "@babel/plugin-transform-arrow-functions", 25 | "@babel/plugin-syntax-jsx", 26 | "@babel/plugin-transform-react-jsx", 27 | "react-hot-loader/babel", 28 | "react-loadable/babel" 29 | ]; 30 | 31 | return { 32 | presets, 33 | plugins 34 | }; 35 | } -------------------------------------------------------------------------------- /config/webpack.demo-js-config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | const { ReactLoadablePlugin } = require("react-loadable/webpack"); 4 | var _ = require('lodash'); 5 | 6 | const htmlWebpackPlugin = new HtmlWebpackPlugin({ 7 | template: path.join(__dirname, "../examples/js-plugin/src/index.html"), 8 | filename: "./index.html" 9 | }); 10 | const reactLoadablePlugin = new ReactLoadablePlugin({ 11 | filename: "../build/react-loadable.json" 12 | }); 13 | 14 | module.exports = (env, options) => { 15 | const config = { 16 | entry: path.join(__dirname, "../examples/js-plugin/src/index.js"), 17 | output: { 18 | path: path.join(__dirname, "../examples/js-plugin/dist"), 19 | filename: "filerobot-uploader-widget.main.[chunkhash].js", 20 | chunkFilename: "filerobot-uploader-widget.[name].[chunkhash].js" 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.(js|jsx)$/, 26 | use: ["babel-loader"], 27 | exclude: /(node_modules|bower_components)\/(?!pretty-bytes\/).*/, 28 | }, 29 | { 30 | test: /\.css$/, 31 | use: ["style-loader", "css-loader"] 32 | } 33 | ] 34 | }, 35 | plugins: [htmlWebpackPlugin, reactLoadablePlugin], 36 | resolve: { 37 | extensions: ["*", ".js", ".jsx"] 38 | }, 39 | devtool: options.mode === 'development' ? "sourcemap" : "none", 40 | devServer: { 41 | port: 3005 42 | } 43 | }; 44 | 45 | //if (options.mode === 'production') { 46 | // config.output.jsonpFunction = 'webpackJsonp' + Date.now(); 47 | // config.output.publicPath = 'https://scaleflex.ultrafast.io/https://scaleflex.airstore.io/filerobot/dev/uploader/'; 48 | //} 49 | 50 | return config; 51 | } -------------------------------------------------------------------------------- /config/webpack.demo-react-config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | const htmlWebpackPlugin = new HtmlWebpackPlugin({ 4 | template: path.join(__dirname, "../examples/react-plugin/src/index.html"), 5 | filename: "./index.html" 6 | }); 7 | 8 | 9 | module.exports = (env, options) => { 10 | return { 11 | entry: path.join(__dirname, "../examples/react-plugin/src/index.js"), 12 | output: { 13 | path: path.join(__dirname, "../examples/react-plugin/dist"), 14 | filename: "filerobot-uploader-widget.[chunkhash].js", 15 | chunkFilename: 'filerobot-uploader-widget.[name].[chunkhash].js' 16 | }, 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.(js|jsx)$/, 21 | use: "babel-loader", 22 | exclude: /(node_modules|bower_components)\/(?!pretty-bytes\/).*/, 23 | }, 24 | { 25 | test: /\.css$/, 26 | use: ["style-loader", "css-loader"] 27 | } 28 | ] 29 | }, 30 | plugins: [htmlWebpackPlugin], 31 | resolve: { 32 | extensions: [".js", ".jsx"] 33 | }, 34 | devtool: options.mode === 'production' ? 'none' : "sourcemap", 35 | devServer: { 36 | port: 3001 37 | } 38 | } 39 | }; -------------------------------------------------------------------------------- /config/webpack.js-config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { ReactLoadablePlugin } = require('react-loadable/webpack'); 3 | const webpack = require('webpack'); 4 | const pkg = require('../package'); 5 | const now = new Date(); 6 | const banner = ` 7 | ${pkg.name} v${pkg.version} 8 | ${pkg.repository.url} 9 | 10 | Copyright (c) 2019 ${pkg.author} 11 | Released under the ${pkg.license} license 12 | 13 | Date: ${now.toISOString()} 14 | `; 15 | 16 | module.exports = (env = {}) => ({ 17 | entry: path.join(__dirname, "../projects/js-plugin/index.js"), 18 | output: env.latest ? 19 | { 20 | path: path.join(__dirname, `../build/${pkg.version.split('.')[0]}`), 21 | filename: `${pkg.name}.min.js`, 22 | chunkFilename: `[name].min.js`, 23 | jsonpFunction: 'webpackJsonp' + Date.now(), 24 | publicPath: `https://cdn.scaleflex.it/plugins/${pkg.name}/${pkg.version.split('.')[0]}/` 25 | } 26 | : 27 | { 28 | path: path.join(__dirname, `../build/${pkg.version}`), 29 | filename: `${pkg.name}.min.js`, 30 | chunkFilename: `[name].min.js`, 31 | jsonpFunction: 'webpackJsonp' + Date.now(), 32 | publicPath: `https://cdn.scaleflex.it/plugins/${pkg.name}/${pkg.version}/` 33 | }, 34 | module: { 35 | rules: [ 36 | { 37 | test: /\.(js|jsx)$/, 38 | use: "babel-loader", 39 | exclude: /(node_modules|bower_components)\/(?!pretty-bytes\/).*/, 40 | }, 41 | { 42 | test: /\.css$/, 43 | use: ["style-loader", "css-loader"] 44 | } 45 | ] 46 | }, 47 | plugins: [ 48 | new ReactLoadablePlugin({ 49 | filename: path.join(__dirname, `../build/react-loadable.json`) 50 | }), 51 | new webpack.BannerPlugin(banner), 52 | ], 53 | resolve: { 54 | extensions: ["*", ".js", ".jsx"] 55 | }, 56 | devtool: "sourcemap", 57 | devServer: { 58 | port: 3001 59 | } 60 | }); -------------------------------------------------------------------------------- /examples/js-plugin/dist/filerobot-uploader-widget.image-editor.69055e3179b2e4d4342c.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[5],{689:function(e,r,t){"use strict";t.r(r);var o=t(1),n=t.n(o),i=t(834),l=t.n(i),a=t(708);function u(e,r){return function(e){if(Array.isArray(e))return e}(e)||function(e,r){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(e)))return;var t=[],o=!0,n=!1,i=void 0;try{for(var l,a=e[Symbol.iterator]();!(o=(l=a.next()).done)&&(t.push(l.value),!r||t.length!==r);o=!0);}catch(e){n=!0,i=e}finally{try{o||null==a.return||a.return()}finally{if(n)throw i}}return t}(e,r)||function(e,r){if(!e)return;if("string"==typeof e)return c(e,r);var t=Object.prototype.toString.call(e).slice(8,-1);"Object"===t&&e.constructor&&(t=e.constructor.name);if("Map"===t||"Set"===t)return Array.from(e);if("Arguments"===t||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t))return c(e,r)}(e,r)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function c(e,r){(null==r||r>e.length)&&(r=e.length);for(var t=0,o=new Array(r);t2&&void 0!==arguments[2]?arguments[2]:{},o=arguments.length>3?arguments[3]:void 0;t.closeOnEdit?o():"TAGGING"===e?r(!0,"TAGGING","IMAGE_EDITOR"):r(!1)},b=function(e,r,t,o,n){var i=arguments.length>5&&void 0!==arguments[5]?arguments[5]:{},l=arguments.length>6?arguments[6]:void 0,u=arguments.length>7?arguments[7]:void 0,c=[p(p({},t),{},{public_link:Object(a.c)(t)})];if(u(c,{stage:"edit"}),i.closeOnEdit)l();else if("TAGGING"===e){var s=[p(p({},t),{},{public_link:Object(a.c)(t)})];o(s),n(!0,"TAGGING")}else n(!1)},g=function(e){var r=e.appState,t=e.files,o=u(t=void 0===t?{}:t,1)[0],i=void 0===o?{}:o,c=e.path,s=e.saveUploadedFiles,d=e.setPostUpload,g=e.options,_=e.closeModal,O=r.prevTab,m=r.config,j=r.modifyURL,y=m.uploadKey,v=m.baseAPI,A=m.container,E=m.uploadParams,P=m.cloudimageToken,h=m.uploadHandler,k=m.language,T=m.imageEditorConfig,w=void 0===T?{}:T,D="gif"===Object(a.c)(i).slice(-3).toLowerCase(),I=Object(a.a)(i);return n.a.createElement(l.a,{show:!0,config:p(p({isLowQualityPreview:!0,colorScheme:"dark",language:k,processWithCloudimage:D||j,uploadWithCloudimageLink:j?!j:D},w),{},{filerobot:p({baseAPI:v,token:A,uploadKey:y,container:A,uploadParams:p(p({},E),{},{dir:c||E.dir},w.filerobot&&w.filerobot.uploadParams)},w.filerobot),cloudimage:p({token:P},w.cloudimage),showGoBackBtn:!0}),closeOnLoad:!1,src:I,onComplete:function(e){if(j){var r=[p(p({},i),{},{modified_url:e,public_link:Object(a.c)(i)})];h(r,{stage:"modify"}),_()}else b(O,e,i,s,d,g,_,h)},onClose:function(){f(O,d,g,_)},showInModal:!1})};r.default=g;"undefined"!=typeof __REACT_HOT_LOADER__&&(__REACT_HOT_LOADER__.register(f,"goBack","D:\\Work\\Projects\\filerobot-uploader\\projects\\react-plugin\\components\\imageEditor\\ImageEditorWrapper.js"),__REACT_HOT_LOADER__.register(b,"uploadFiles","D:\\Work\\Projects\\filerobot-uploader\\projects\\react-plugin\\components\\imageEditor\\ImageEditorWrapper.js"),__REACT_HOT_LOADER__.register(g,"default","D:\\Work\\Projects\\filerobot-uploader\\projects\\react-plugin\\components\\imageEditor\\ImageEditorWrapper.js"))},708:function(e,r,t){"use strict";t.d(r,"b",(function(){return o})),t.d(r,"c",(function(){return n})),t.d(r,"a",(function(){return i}));var o=function(e){return e.url&&e.url.permalink?e.url.permalink:e.url_permalink?e.url_permalink:""},n=function(e){return e.url&&e.url.public?e.url.public:e.url_public?e.url_public:""},i=function(e){return e.url&&e.url.cdn?e.url.cdn:""};"undefined"!=typeof __REACT_HOT_LOADER__&&(__REACT_HOT_LOADER__.register(o,"getPermalink","D:\\Work\\Projects\\filerobot-uploader\\projects\\react-plugin\\utils\\adjustAPI.utils.js"),__REACT_HOT_LOADER__.register(n,"getPubliclink","D:\\Work\\Projects\\filerobot-uploader\\projects\\react-plugin\\utils\\adjustAPI.utils.js"),__REACT_HOT_LOADER__.register(i,"getCDNlink","D:\\Work\\Projects\\filerobot-uploader\\projects\\react-plugin\\utils\\adjustAPI.utils.js"))}}]); -------------------------------------------------------------------------------- /examples/js-plugin/src/assets/fonts/helvetica-neue.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Helvetica Neue'; 3 | src: url('https://scaleflex.ultrafast.io/https://cdn.scaleflex.it/filerobot/assets/fonts/helvetica-neue/HelveticaNeue-Medium.eot'); 4 | src: url('https://scaleflex.ultrafast.io/https://cdn.scaleflex.it/filerobot/assets/fonts/helvetica-neue/HelveticaNeue-Medium.eot?#iefix') format('embedded-opentype'), 5 | url('https://scaleflex.ultrafast.io/https://cdn.scaleflex.it/filerobot/assets/fonts/helvetica-neue/HelveticaNeue-Medium.woff2') format('woff2'), 6 | url('https://scaleflex.ultrafast.io/https://cdn.scaleflex.it/filerobot/assets/fonts/helvetica-neue/HelveticaNeue-Medium.woff') format('woff'), 7 | url('https://scaleflex.ultrafast.io/https://cdn.scaleflex.it/filerobot/assets/fonts/helvetica-neue/HelveticaNeue-Medium.ttf') format('truetype'), 8 | url('https://scaleflex.ultrafast.io/https://cdn.scaleflex.it/filerobot/assets/fonts/helvetica-neue/HelveticaNeue-Medium.svg#HelveticaNeue-Medium') format('svg'); 9 | font-weight: 500; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'Helvetica Neue'; 15 | src: url('https://scaleflex.ultrafast.io/https://cdn.scaleflex.it/filerobot/assets/fonts/helvetica-neue/HelveticaNeue-LightExt.eot'); 16 | src: url('https://scaleflex.ultrafast.io/https://cdn.scaleflex.it/filerobot/assets/fonts/helvetica-neue/HelveticaNeue-LightExt.eot?#iefix') format('embedded-opentype'), 17 | url('https://scaleflex.ultrafast.io/https://cdn.scaleflex.it/filerobot/assets/fonts/helvetica-neue/HelveticaNeue-LightExt.woff2') format('woff2'), 18 | url('https://scaleflex.ultrafast.io/https://cdn.scaleflex.it/filerobot/assets/fonts/helvetica-neue/HelveticaNeue-LightExt.woff') format('woff'), 19 | url('https://scaleflex.ultrafast.io/https://cdn.scaleflex.it/filerobot/assets/fonts/helvetica-neue/HelveticaNeue-LightExt.ttf') format('truetype'), 20 | url('https://scaleflex.ultrafast.io/https://cdn.scaleflex.it/filerobot/assets/fonts/helvetica-neue/HelveticaNeue-LightExt.svg#HelveticaNeue-LightExt') format('svg'); 21 | font-weight: 300; 22 | font-style: normal; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /examples/js-plugin/src/index.js: -------------------------------------------------------------------------------- 1 | import '../../../projects/js-plugin/index'; 2 | import './style.css'; 3 | import hljs from 'highlight.js/lib/highlight'; 4 | import javascript from 'highlight.js/lib/languages/javascript'; 5 | import 'highlight.js/styles/github.css'; 6 | import './assets/fonts/helvetica-neue.css'; 7 | hljs.registerLanguage('javascript', javascript); 8 | hljs.initHighlightingOnLoad(); 9 | 10 | let loadedImage = null; 11 | const spinner = document.getElementById('spinner'); 12 | const wrapper = document.getElementById('main'); 13 | const jsBtn = document.getElementById('js-btn'); 14 | const reactBtn = document.getElementById('react-btn'); 15 | const jsBox = document.getElementById('js-version-box'); 16 | const reactBox = document.getElementById('react-version-box'); 17 | const innerSpinner = document.getElementById('inner-spinner'); 18 | const imageContainer = document.getElementById('image-container'); 19 | const buttonEdit = document.getElementById('edit-btn'); 20 | const image = document.getElementById('image-box'); 21 | const description = document.getElementById('image-description'); 22 | const robotIcon = document.getElementById('robot-icon'); 23 | 24 | jsBtn.onclick = function() { 25 | if (jsBtn.className.indexOf('btn-primary') === -1) { 26 | jsBtn.classList.remove('btn-light'); 27 | jsBtn.classList.add('btn-primary'); 28 | reactBtn.classList.remove('btn-primary'); 29 | reactBtn.classList.add('btn-light'); 30 | 31 | reactBox.style.display = 'none'; 32 | jsBox.style.display = 'block'; 33 | } 34 | } 35 | 36 | reactBtn.onclick = function() { 37 | if (reactBtn.className.indexOf('btn-primary') === -1) { 38 | reactBtn.classList.remove('btn-light'); 39 | reactBtn.classList.add('btn-primary'); 40 | jsBtn.classList.remove('btn-primary'); 41 | jsBtn.classList.add('btn-light'); 42 | 43 | jsBox.style.display = 'none' 44 | reactBox.style.display = 'block'; 45 | } 46 | } 47 | 48 | let config = { 49 | modules: ['UPLOAD', 'MY_GALLERY', 'ICONS_GALLERY', 'IMAGES_GALLERY', 'TAGGING', 'IMAGE_EDITOR'], 50 | uploadParams: { 51 | dir:"/dima_test_8_en" 52 | //dir:"/test-folder" 53 | //dir:"/test2" //frank 54 | //dir:"/test2_dir" //emil 55 | }, 56 | container: 'fusqadtm', //M1_EDGY without tags 57 | filerobotUploadKey: '19692813e7364ef8ad6a6504d50a12ca', 58 | //container: 'ficttndm', //M1_EDGY with Tags field //emil 59 | //filerobotUploadKey: 'fc86c2afb6114856a31bcef9d299fccb', 60 | //container: 'fyeonxrm', //M0_LEGACY //frank 61 | //filerobotUploadKey: 'fa6d6f45f35844a7b01623c391fac260', 62 | initialTab: 'UPLOAD', 63 | folderBrowser: { 64 | show: true 65 | }, 66 | autoCropSuggestions: true, 67 | closeOnEdit: false, 68 | preUploadImageProcess: true, 69 | processBeforeUpload: { 70 | operation: 'resize', 71 | widthLimit: 2000, 72 | heightLimit: 2000 73 | }, 74 | tagging: { 75 | executeAfterUpload: true, 76 | autoTaggingButton: true, 77 | provider: 'google', 78 | confidence: 60, 79 | limit: 10, 80 | key: 'aaaa', 81 | customFields: [ 82 | { 83 | name: 'Test name 1', 84 | metaKey: 'test_key', 85 | type: 'text' 86 | }, 87 | { 88 | name: 'Test name 2', 89 | metaKey: 'test_key_2', 90 | type: 'textarea' 91 | } 92 | ], 93 | suggestionList: ['Color', 'Colored', 'Cobalt', 'Coral', 'Cobre'] 94 | }, 95 | language: 'en', 96 | colorScheme: { 97 | active: 'solarized' 98 | }, 99 | extensions: ['jpg', 'png'], 100 | imageEditorConfig: { 101 | // processWithCloudimage: true, 102 | // filerobot: { 103 | // token: 'fusqadtm', 104 | // doNotPrefixURL: true 105 | // } 106 | watermark: { 107 | url: 'https://cdn.scaleflex.it/demo/filerobot.png', 108 | urls: [ 109 | { url: 'https://cdn.scaleflex.it/demo/filerobot.png', label: 'filerobot logo' }, 110 | 'https://cdn.scaleflex.it/demo/superman.png' 111 | ], 112 | position: 'center', 113 | opacity: 0.7, 114 | applyByDefault: false, 115 | handleOpacity: true, 116 | fileUpload: true, 117 | } 118 | }, 119 | modifyURLButton: true, 120 | deleteButton: true 121 | }; 122 | 123 | // Configuration 124 | //let config = { 125 | // modules: ['UPLOAD'], 126 | // uploadParams: { dir: '/images/aaa' }, 127 | // container: 'fusqadtm', 128 | // filerobotUploadKey: '7cc1f659309c480cbc8a608dc6ba5f03', 129 | // platform: 'airstore', 130 | // initialTab: 'UPLOAD', 131 | // folderBrowser: true, 132 | // processBeforeUpload: { 133 | // operation: 'resize', 134 | // widthLimit: 1080, 135 | // heightLimit: 1080 136 | // }, 137 | // language: 'en', 138 | // colorScheme: { 139 | // active: 'solarized' 140 | // } 141 | // 142 | //}; 143 | 144 | window.addEventListener('load', function() { 145 | const FilerobotUploaderInstance = FilerobotUploader.init(config, onUploadHandler); 146 | const homeOpenBtn = document.getElementById('edit-btn'); 147 | 148 | homeOpenBtn.onclick = () => FilerobotUploaderInstance.open(); 149 | image.onclick = () => FilerobotUploaderInstance.open(); 150 | }); 151 | 152 | function onUploadHandler(files, props = {}) { 153 | const img = files[0]; 154 | const options = { 155 | weekday: "long", year: "numeric", month: "short", 156 | day: "numeric", hour: "2-digit", minute: "2-digit" 157 | }; 158 | const firstLoad = (img.created_at ? (new Date(img.created_at)) : new Date()).toLocaleTimeString("en", options); 159 | const lastModified = (img.modified_at ? (new Date(img.modified_at)) : new Date()).toLocaleTimeString("en", options); 160 | const publicURL = props.stage === 'modify' ? img.modified_url : img.url && img.url.public ? img.url.public : img.url_public; 161 | img.properties = img.properties || {}; 162 | img.properties.tags = img.properties.tags || []; 163 | 164 | loadedImage = img; 165 | 166 | innerSpinner.style.display = 'block'; 167 | imageContainer.style.opacity = '0.5'; 168 | 169 | image.src = props.stage === 'modify' ? img.modified_url : (img.url && img.url.cdn || publicURL) 170 | image.onload = () => { 171 | innerSpinner.style.display = 'none'; 172 | imageContainer.style.opacity = '1'; 173 | } 174 | description.innerHTML = ` 175 | 209 | `; 210 | } 211 | 212 | setTimeout(() => { 213 | wrapper.classList.add('active'); 214 | spinner.style.display = 'none'; 215 | }, 400); 216 | 217 | image.onmouseenter = onMouseEnter; 218 | robotIcon.onmouseenter = onMouseEnter; 219 | buttonEdit.onmouseenter = onMouseEnter; 220 | 221 | image.onmouseleave = onMouseLeave; 222 | robotIcon.onmouseleave = onMouseLeave; 223 | buttonEdit.onmouseleave = onMouseLeave; 224 | 225 | function onMouseEnter() { 226 | robotIcon.src = 'https://demo.cloudimg.io/width/500/q35.foil1/https://cdn.scaleflex.it/filerobot/assets/robot-with-smile-left.png'; 227 | } 228 | 229 | function onMouseLeave() { 230 | robotIcon.src = 'https://demo.cloudimg.io/width/500/q35.foil1/https://cdn.scaleflex.it/filerobot/assets/robot-icon-left.png'; 231 | } 232 | -------------------------------------------------------------------------------- /examples/react-plugin/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | Filerobot Uploader 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/react-plugin/src/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { render } from 'react-dom'; 3 | import FilerobotUploader from '../../../projects/react-plugin'; 4 | import prettyBytes from 'pretty-bytes'; 5 | 6 | 7 | const config = { 8 | modules: ['UPLOAD', 'MY_GALLERY', 'ICONS_GALLERY', 'IMAGES_GALLERY', 'TAGGING', 'IMAGE_EDITOR'], 9 | uploadParams: { dir:"/demo_filerobot_en" }, 10 | filerobotUploadKey: '19692813e7364ef8ad6a6504d50a12ca', 11 | container: 'fusqadtm' 12 | }; 13 | 14 | class App extends Component { 15 | constructor() { 16 | super(); 17 | 18 | this.state = { 19 | isShow: false, 20 | initialTab: 'UPLOAD', 21 | img: null, 22 | closeOnEdit: false 23 | } 24 | } 25 | 26 | render() { 27 | const { img, initialTab, closeOnEdit } = this.state; 28 | 29 | return ( 30 |
31 |

React Example

32 | 33 | 34 | 35 | {img && 36 |
37 |
    38 |
  • 39 | File name: 40 | {img.name} 41 |
  • 42 |
  • 43 | Public link: 44 | {img.url_public} 45 |
  • 46 |
  • 47 | Size: 48 | {prettyBytes(img.size || 0)} 49 |
  • 50 |
  • 51 | Description: 52 | {img.properties.description || ''} 53 |
  • 54 |
  • 55 | Tags: 56 | {img.properties.tags && img.properties.tags.join(', ')} 57 |
  • 58 |
59 |
} 60 | 61 | { this.setState({ isShow: false }); }} 68 | onUpload={(images) => { this.setState({ img: images[0] }) }} 69 | /> 70 |
71 | ) 72 | } 73 | } 74 | 75 | render(, document.getElementById('app')); 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "filerobot-uploader", 3 | "version": "2.15.20", 4 | "main": "dist/index.js", 5 | "description": "[DEPRECATED] The Filerobot Uploader is a multi-function Uploader that will make uploads super easy in your web sites and apps. It is fast to integrate allows end users to upload media, files and any assets via Filerobot's reverse CDN. Files are stored into scalable and flexible Cloud storage, optimised and delivered over CDN to your end users rocket fast. Features include inline image editing, auto-tagging, auto-cropping and many more.", 6 | "author": "scaleflex", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/scaleflex/filerobot-uploader" 11 | }, 12 | "homepage": "https://github.com/scaleflex/filerobot-uploader#readme", 13 | "keywords": [ 14 | "uploader", 15 | "reverse CDN", 16 | "asset management", 17 | "file manager", 18 | "file uploader", 19 | "image management", 20 | "image compression", 21 | "image resizing", 22 | "image acceleration", 23 | "fast uploader", 24 | "file listing", 25 | "image tagging", 26 | "image editor" 27 | ], 28 | "dependencies": { 29 | "core-js": "^3.7.0", 30 | "cropperjs": "^1.5.9", 31 | "filerobot-image-editor": "^3.12.7", 32 | "lodash.debounce": "^4.0.8", 33 | "lodash.find": "^4.6.0", 34 | "mobile-detect": "^1.4.4", 35 | "pretty-bytes": "^5.4.1", 36 | "prop-types": "^15.7.2", 37 | "rc-progress": "^2.6.1", 38 | "react-autosuggest": "^10.0.3", 39 | "react-color": "^2.19.3", 40 | "react-focus-lock": "^1.19.1", 41 | "react-i18nify": "^1.11.18", 42 | "react-load-script": "0.0.6", 43 | "react-loadable": "^5.5.0", 44 | "react-tagsinput": "^3.19.0", 45 | "react-toastr": "^3.0.0", 46 | "react-tooltip": "^3.11.6", 47 | "react-virtualized": "^9.22.2", 48 | "smartcrop": "^2.0.3", 49 | "styled-components": "^4.4.1" 50 | }, 51 | "peerDependencies": { 52 | "axios": "^0.17.1", 53 | "react": "^16.3.0", 54 | "react-dom": "^16.3.0" 55 | }, 56 | "devDependencies": { 57 | "@babel/cli": "^7.12.7", 58 | "@babel/core": "^7.12.7", 59 | "@babel/node": "^7.12.6", 60 | "@babel/plugin-proposal-class-properties": "^7.12.1", 61 | "@babel/plugin-proposal-export-default-from": "^7.12.1", 62 | "@babel/plugin-syntax-dynamic-import": "^7.8.3", 63 | "@babel/plugin-syntax-jsx": "^7.12.1", 64 | "@babel/plugin-transform-arrow-functions": "^7.12.1", 65 | "@babel/plugin-transform-modules-commonjs": "^7.12.1", 66 | "@babel/plugin-transform-object-assign": "^7.12.1", 67 | "@babel/plugin-transform-react-jsx": "^7.12.7", 68 | "@babel/preset-env": "^7.12.7", 69 | "@babel/preset-react": "^7.12.7", 70 | "axios": "^0.18.1", 71 | "babel-loader": "^8.2.1", 72 | "babel-plugin-dynamic-import-node": "^2.3.3", 73 | "css-loader": "0.28.7", 74 | "gh-pages": "^2.2.0", 75 | "highlight.js": "^9.18.5", 76 | "html-webpack-plugin": "^3.2.0", 77 | "react": "^16.14.0", 78 | "react-dom": "^16.14.0", 79 | "react-hot-loader": "^3.1.3", 80 | "style-loader": "0.19.0", 81 | "webpack": "^4.44.2", 82 | "webpack-cli": "^3.3.12", 83 | "webpack-dev-server": "^3.11.0" 84 | }, 85 | "scripts": { 86 | "start-demo-js": "webpack-dev-server --mode development --config config/webpack.demo-js-config.js", 87 | "start-demo-react": "webpack-dev-server --mode development --config config/webpack.demo-react-config.js", 88 | "clean-demo-js": "rm -rf examples/js-plugin/dist", 89 | "build-demo-js": "npm run clean-demo-js && webpack --mode production --config config/webpack.demo-js-config.js", 90 | "clean-demo-react": "rm -rf examples/react-plugin/dist", 91 | "build-demo-react": "npm run clean-demo-react && webpack --mode production --config config/webpack.demo-react-config.js", 92 | "clean-build": "rm -rf build", 93 | "build-new": "webpack --mode production --config config/webpack.js-config.js", 94 | "build-latest": "webpack --mode production --config config/webpack.js-config.js --env.latest", 95 | "build": "npm run clean-build && npm run build-new && npm run build-latest", 96 | "clean-dist": "rm -rf dist", 97 | "dist": "npm run clean-dist && npx babel projects/react-plugin --out-dir dist --copy-files", 98 | "deploy": "gh-pages -d examples/js-plugin/dist", 99 | "publish-demo": "npm run build-demo-js && npm run deploy" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /projects/js-plugin/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, unmountComponentAtNode } from 'react-dom'; 3 | import { AppContainer } from 'react-hot-loader'; 4 | import AirstoreUploaderWrapper from '../react-plugin/components/AirstoreUploaderWrapper'; 5 | 6 | 7 | window.AirstoreUploader = window.AirstoreUploader || {}; 8 | window.FilerobotUploader = window.FilerobotUploader || {}; 9 | window.AirstoreUploader.init = init; 10 | window.FilerobotUploader.init = init; 11 | 12 | function init(config = {}, onUpload = null, isOpened = false) { 13 | const elementId = config.elementId || config.ELEMENT_ID || 'airstore-uploader'; 14 | const initialTab = config.initialTab || config.INITIAL_TAB || 'UPLOAD'; 15 | let container = document.getElementById(elementId); 16 | 17 | if (!container) { 18 | container = document.createElement('div'); 19 | container.id = elementId; 20 | 21 | document.body.appendChild(container); 22 | } 23 | 24 | config.onUpload = config.onUpload || function() {}; 25 | 26 | window.AirstoreUploader.component = Component => { 27 | 28 | return render( 29 | 30 | 36 | , 37 | container, 38 | ) 39 | }; 40 | 41 | 42 | window.AirstoreUploader.component(AirstoreUploaderWrapper); 43 | window.AirstoreUploader.unmount = () => unmountComponentAtNode(container); 44 | 45 | window.FilerobotUploader.component = window.AirstoreUploader.component; 46 | window.FilerobotUploader.unmount = window.AirstoreUploader.unmount; 47 | 48 | return window.AirstoreUploader; 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /projects/react-plugin/assets/fonts/filerobot-uploader.font.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'filerobot-uploader-font'; 3 | src: url('https://cdn.scaleflex.it/plugins/filerobot-uploader/assets/fonts/filerobot-uploader-font/filerobot-uploader-font.eot?u6lcwr'); 4 | src: url('https://cdn.scaleflex.it/plugins/filerobot-uploader/assets/fonts/filerobot-uploader-font/filerobot-uploader-font.eot?u6lcwr#iefix') format('embedded-opentype'), 5 | url('https://cdn.scaleflex.it/plugins/filerobot-uploader/assets/fonts/filerobot-uploader-font/filerobot-uploader-font.ttf?u6lcwr') format('truetype'), 6 | url('https://cdn.scaleflex.it/plugins/filerobot-uploader/assets/fonts/filerobot-uploader-font/filerobot-uploader-font.woff?u6lcwr') format('woff'), 7 | url('https://cdn.scaleflex.it/plugins/filerobot-uploader/assets/fonts/filerobot-uploader-font/filerobot-uploader-font.svg?u6lcwr#filerobot-uploader-font') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | [class^="sfi-airstore-"], [class*=" sfi-airstore-"] { 13 | /* use !important to prevent issues with browser extensions that change fonts */ 14 | font-family: 'filerobot-uploader-font' !important; 15 | speak: none; 16 | font-style: normal; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | line-height: 1; 21 | 22 | /* Better Font Rendering =========== */ 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | .sfi-airstore-edit:before { 28 | content: "\e910"; 29 | } 30 | .sfi-airstore-delete:before { 31 | content: "\e911"; 32 | } 33 | .sfi-airstore-tag:before { 34 | content: "\e912"; 35 | } 36 | .sfi-airstore-folder-manager:before { 37 | content: "\e90f"; 38 | } 39 | .sfi-airstore-image-editor:before { 40 | content: "\e90d"; 41 | } 42 | .sfi-airstore-tags:before { 43 | content: "\e90e"; 44 | } 45 | .sfi-airstore-arrow-up:before { 46 | content: "\e906"; 47 | } 48 | .sfi-airstore-arrow-down:before { 49 | content: "\e907"; 50 | } 51 | .sfi-airstore-plus:before { 52 | content: "\e908"; 53 | } 54 | .sfi-airstore-arrow-right:before { 55 | content: "\e909"; 56 | } 57 | .sfi-airstore-arrow-left:before { 58 | content: "\e90a"; 59 | } 60 | .sfi-airstore-cross:before { 61 | content: "\e90b"; 62 | } 63 | .sfi-airstore-tick:before { 64 | content: "\e90c"; 65 | } 66 | .sfi-airstore-gallery:before { 67 | content: "\e900"; 68 | } 69 | .sfi-airstore-image:before { 70 | content: "\e901"; 71 | } 72 | .sfi-airstore-search:before { 73 | content: "\e902"; 74 | } 75 | .sfi-airstore-upload:before { 76 | content: "\e903"; 77 | } 78 | .sfi-airstore-uploaded-images:before { 79 | content: "\e904"; 80 | } 81 | .sfi-airstore-image-gallery:before { 82 | content: "\e905"; 83 | } 84 | 85 | 86 | @-webkit-keyframes fa-spin { 87 | 0% { 88 | -webkit-transform: rotate(0deg); 89 | transform: rotate(0deg); 90 | } 91 | 100% { 92 | -webkit-transform: rotate(359deg); 93 | transform: rotate(359deg); 94 | } 95 | } 96 | @keyframes fa-spin { 97 | 0% { 98 | -webkit-transform: rotate(0deg); 99 | transform: rotate(0deg); 100 | } 101 | 100% { 102 | -webkit-transform: rotate(359deg); 103 | transform: rotate(359deg); 104 | } 105 | } -------------------------------------------------------------------------------- /projects/react-plugin/assets/styles/bg.css.js: -------------------------------------------------------------------------------- 1 | export default { 2 | container: { 3 | display: 'flex', 4 | flexWrap: 'wrap', 5 | alignItems: 'stretch', 6 | paddingRight: 10, 7 | fontFamily: 'Roboto, sans-serif', 8 | // height: 1, 9 | flex: '1 1 0%', 10 | overflow: 'auto', 11 | 12 | item: { 13 | position: 'relative', 14 | display: 'inline-block', 15 | margin: '10px 0 0 10px', 16 | cursor: 'pointer', 17 | background: '#e7e9ee', 18 | 19 | alignmentBlock: { 20 | display: 'inline-block', 21 | verticalAlign: 'middle', 22 | height: '100%' 23 | }, 24 | 25 | img: { 26 | //display: 'inline-block', 27 | //verticalAlign: 'middle' 28 | }, 29 | 30 | loading: { 31 | active: {cursor: 'progress'}, 32 | 33 | notActive: {opacity: 0.1} 34 | }, 35 | 36 | ':focus': { 37 | outlineColor: 'rgb(77, 144, 254)', 38 | outlineOffset: -2, 39 | outlineStyle: 'auto', 40 | outlineWidth: 5 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /projects/react-plugin/assets/styles/colorScheme.js: -------------------------------------------------------------------------------- 1 | export default { 2 | colors: { 3 | text: { 4 | base: '#F9FAFB', 5 | dark: '#F4F6F8', 6 | mute: '#aaa', 7 | light: '#fff' 8 | }, 9 | 10 | dark: { 11 | base: '#1e262c', 12 | light: '#454F5B', 13 | lighter: '#637381', 14 | }, 15 | 16 | primary: { 17 | base: '#181830', 18 | light: '#263138', 19 | lighter: '#34444c', 20 | dark: '#101021', 21 | darker: '#090912', 22 | 23 | text: '#F9FAFB' 24 | }, 25 | 26 | secondary: { 27 | base: '#00707c', 28 | light: '#007E8A', 29 | lighter: '#008D99', 30 | dark: '#00616D', 31 | darker: '#005662', 32 | 33 | text: '#F9FAFB' 34 | } 35 | }, 36 | 37 | textFontSize: '14px', 38 | 39 | borderColor: '#70777f', 40 | borderDarkColor: '#161e23', 41 | 42 | textColor: '#e7f1f4', 43 | textColorHover: '#fff', 44 | textMuted: '#70777f', 45 | 46 | fieldWidth: '120px', 47 | } 48 | 49 | export const colorSchemes = { 50 | solarized: { 51 | mainBackground: '#f5f5f5', 52 | navBackground: '#181830', 53 | buttonBackground: '#00707C', 54 | hoverButtonBackground: '#096868', 55 | inputBackground: '#fff', 56 | inputOutlineColor: '#4d90fe', 57 | activeTabBackground: '#40545b', 58 | activeSidebarItemBackground: '#fff', 59 | tagsBackground: '#28a745', 60 | tagsColor: '#fff', 61 | text: '#5d636b', 62 | title: '#1e262c', 63 | inputTextColor: '#555555', 64 | tabTextColor: '#c0c1c1', 65 | activeTabTextColor: '#fff', 66 | buttonTextColor: '#fff', 67 | border: '#d8d8d8', 68 | overlay: '#787878' 69 | }, 70 | 71 | light: { 72 | mainBackground: '#f5f5f5', 73 | navBackground: '#e2e2e2', 74 | buttonBackground: '#86bc31', 75 | hoverButtonBackground: '#A4E63C', 76 | inputBackground: '#fff', 77 | inputOutlineColor: '#86bc31', 78 | activeTabBackground: '#f5f5f5', 79 | activeSidebarItemBackground: '#e2e2e2', 80 | tagsBackground: '#86bc31', 81 | tagsColor: '#fff', 82 | text: '#1a1a1a', 83 | title: '#1a1a1a', 84 | inputTextColor: '#999999', 85 | tabTextColor: '#1a1a1a', 86 | activeTabTextColor: '#1a1a1a', 87 | buttonTextColor: '#fff', 88 | border: '#606060', 89 | overlay: '#787878' 90 | }, 91 | 92 | dark: { 93 | mainBackground: '#606060', 94 | navBackground: '#424242', 95 | buttonBackground: '#86bc31', 96 | hoverButtonBackground: '#A4E63C', 97 | inputBackground: '#fff', 98 | inputOutlineColor: '#86bc31', 99 | activeTabBackground: '#606060', 100 | activeSidebarItemBackground: '#424242', 101 | tagsBackground: '#86bc31', 102 | tagsColor: '#fff', 103 | text: '#e6e6e6', 104 | title: '#e6e6e6', 105 | inputTextColor: '#999999', 106 | tabTextColor: '#fff', 107 | activeTabTextColor: '#fff', 108 | buttonTextColor: '#fff', 109 | border: '#e6e6e6', 110 | overlay: '#fff' 111 | }, 112 | 113 | purple: { 114 | mainBackground: '#6767a0', 115 | navBackground: '#181743', 116 | buttonBackground: '#37377a', 117 | hoverButtonBackground: '#4D4DAA', 118 | inputBackground: '#fff', 119 | inputOutlineColor: '#37377a', 120 | activeTabBackground: '#6767a0', 121 | activeSidebarItemBackground: '#181743', 122 | tagsBackground: '#37377a', 123 | tagsColor: '#fff', 124 | text: '#fff', 125 | title: '#fff', 126 | inputTextColor: '#999999', 127 | tabTextColor: '#fff', 128 | activeTabTextColor: '#fff', 129 | buttonTextColor: '#fff', 130 | border: '#ccc', 131 | overlay: '#787878' 132 | }, 133 | 134 | lilac: { 135 | mainBackground: '#f7f7ff', 136 | navBackground: '#402f80', 137 | buttonBackground: '#402f80', 138 | hoverButtonBackground: '#5B43B6', 139 | inputBackground: '#fff', 140 | inputOutlineColor: '#402f80', 141 | activeTabBackground: '#6767a0', 142 | activeSidebarItemBackground: '#fff', 143 | tagsBackground: '#402f80', 144 | tagsColor: '#fff', 145 | text: '#606060', 146 | title: '#606060', 147 | inputTextColor: '#999999', 148 | tabTextColor: '#fff', 149 | activeTabTextColor: '#fff', 150 | buttonTextColor: '#fff', 151 | border: '#606060', 152 | overlay: '#787878' 153 | } 154 | }; -------------------------------------------------------------------------------- /projects/react-plugin/assets/styles/drag-drop.css.js: -------------------------------------------------------------------------------- 1 | export default { 2 | fa: { 3 | display: 'inline-block', 4 | font: 'normal normal normal 14px/1 FontAwesome', 5 | textRendering: 'auto', 6 | width: 'auto', 7 | verticalAlign: 'middle' 8 | }, 9 | 10 | faSpin: { 11 | display: 'inline-block', 12 | WebkitAnimation: "fa-spin 2s infinite linear", 13 | animation: "fa-spin 2s infinite linear" 14 | }, 15 | 16 | faFw: { 17 | width: "1.28571429em", 18 | textAlign: "center" 19 | }, 20 | 21 | container: { 22 | fontFamily: 'Roboto, sans-serif', 23 | "minHeight": "200px", 24 | "position": "relative", 25 | "fontWeight": "300", 26 | "margin": "0 auto", 27 | height: '100%', 28 | color: '#5D636B', 29 | "padding": 10, 30 | boxSizing: 'border-box', 31 | 32 | uploadBlock: { 33 | width: '100%', 34 | minHeight: 240, 35 | "textAlign": "center", 36 | "border": "2px dashed #d8d8d8", 37 | "background": "#f5f5f5", 38 | "height": "100%", 39 | "alignItems": "center", 40 | "justifyContent": "center", 41 | "display": "flex", 42 | color: '#5D636B', 43 | boxSizing: 'border-box', 44 | 45 | inputBox: { 46 | file: { 47 | "width": ".1px", 48 | "height": ".1px", 49 | "opacity": "0", 50 | "overflow": "hidden", 51 | "position": "absolute", 52 | "zIndex": "-1" 53 | }, 54 | 55 | label: { 56 | "display": "flex", 57 | "flexDirection": "column", 58 | color: '#5D636B', 59 | 60 | dragDropText: { 61 | "display": "inline-block", 62 | "fontSize": "25px", 63 | "fontWeight": "300" 64 | }, 65 | 66 | orText: { 67 | "padding": "10px", 68 | "fontWeight": "200", 69 | "fontSize": 12 70 | }, 71 | 72 | uploadBtn: { 73 | "display": "inline-block", 74 | "zoom": "1", 75 | "color": "#fff", 76 | "backgroundColor": '#5D636B', 77 | "backgroundRepeat": "repeat-x", 78 | "backgroundImage": "linear-gradient(to bottom,#f4f4f4,#ddd)", 79 | "filter": "progid:DXImageTransform.Microsoft.gradient(startColorstr='#f4f4f4', endColorstr='#dddddd', GradientType=0)", 80 | "textShadow": "0 1px #fff", 81 | "border": "1px solid #ccc", 82 | "borderRadius": "5px", 83 | //"padding": "5px 10px", 84 | "cursor": "pointer", 85 | "fontWeight": "300", 86 | "padding": "3px 8px", 87 | "textDecoration": "none", 88 | outline: 0 89 | } 90 | }, 91 | 92 | submitBtn: { 93 | "color": "#e5edf1", 94 | "backgroundColor": "#39bfd3", 95 | "display": "none", 96 | "padding": "8px 16px", 97 | "margin": "40px auto 0" 98 | } 99 | }, 100 | 101 | uploadingBox: {}, 102 | 103 | errorBox: { 104 | "position": "absolute", 105 | "bottom": "5%", 106 | "width": "100%", 107 | //"display": "none", 108 | 109 | errorMsg: { 110 | "background": "#fdc", 111 | "borderRadius": "3px", 112 | "maxWidth": "50%", 113 | "display": "inline-block", 114 | "zoom": "1", 115 | "color": "#806f66", 116 | "padding": "6px 10px" 117 | } 118 | } 119 | } 120 | }, 121 | 122 | formControl: { 123 | "display": "block", 124 | "width": "100%", 125 | "height": "34px", 126 | "padding": "6px 12px", 127 | "fontSize": "14px", 128 | "lineHeight": "1.42857143", 129 | "color": "#555", 130 | "background": "#fff", 131 | "borderRadius": "4px", 132 | "WebkitBoxShadow": "inset 0 1px 1px rgba(0,0,0,.075)", 133 | "boxShadow": "inset 0 1px 1px rgba(0,0,0,.075)", 134 | "WebkitTransition": "border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s", 135 | "OTransition": "border-color ease-in-out .15s,box-shadow ease-in-out .15s", 136 | "transition": "border-color ease-in-out .15s,box-shadow ease-in-out .15s" 137 | } 138 | } -------------------------------------------------------------------------------- /projects/react-plugin/assets/styles/icons.css.js: -------------------------------------------------------------------------------- 1 | export default { 2 | container: { 3 | display: 'flex', 4 | height: '100%', 5 | position: 'relative', 6 | fontFamily: 'Roboto, sans-serif', 7 | 8 | // Sidebar 9 | sidebarWrap: { 10 | width: 160, 11 | borderRight: '1px solid rgb(221, 221, 221)', 12 | position: 'relative', 13 | 14 | sidebar: { 15 | overflow: 'auto', 16 | height: '100%', 17 | top: 0, 18 | position: 'absolute', 19 | width: '100%', 20 | 21 | colorType: { 22 | marginBottom: 15, 23 | marginTop: 15 24 | }, 25 | 26 | categoryItem: { 27 | padding: '5px 5px', 28 | fontSize: 12, 29 | color: 'rgb(85, 85, 85)', 30 | background: '#fff', 31 | borderLeft: '2px solid transparent', 32 | borderRight: '2px solid transparent', 33 | borderTop: '2px solid transparent', 34 | borderBottom: '2px solid transparent', 35 | textTransform: 'capitalize', 36 | display: 'flex', 37 | cursor: 'pointer', 38 | 39 | active: { 40 | //background: '#5D636B', 41 | //color: 'rgb(255, 255, 255)' 42 | }, 43 | 44 | name: { 45 | marginLeft: 5 46 | //whiteSpace: 'nowrap', 47 | //overflow: 'hidden', 48 | //textOverflow: 'ellipsis' 49 | }, 50 | 51 | count: { 52 | flex: 1, 53 | marginLeft: 5, 54 | fontSize: 10 55 | }, 56 | 57 | ':focus': { 58 | outline: 'none', 59 | borderBottom: '2px solid #4D90FE', 60 | borderLeft: '2px solid #4D90FE', 61 | borderRight: '2px solid #4D90FE', 62 | borderTop: '2px solid #4D90FE', 63 | boxShadow: '0px 0px 5px #4D90FE' 64 | } 65 | } 66 | } 67 | }, 68 | 69 | // Content 70 | content: { 71 | flex: 1, 72 | overflow: 'auto', 73 | color: '#5D636B', 74 | 75 | loading: { 76 | textAlign: 'center', 77 | padding: 20, 78 | width: '100%', 79 | textTransform: 'uppercase' 80 | }, 81 | 82 | results: { 83 | position: 'relative', 84 | display: 'flex', 85 | flexWrap: 'wrap', 86 | alignItems: 'stretch', 87 | justifyContent: 'center', 88 | } 89 | } 90 | }, 91 | 92 | search: { 93 | justifyContent: '', 94 | 95 | empty: { 96 | "height": "100%", 97 | "justifyContent": "center", 98 | "display": "flex", 99 | "alignItems": "center", 100 | "flexDirection": "column" 101 | }, 102 | 103 | title: { 104 | fontSize: 24, 105 | "marginTop": "-10%", 106 | fontWeight: 200 107 | }, 108 | 109 | searchBlock: { 110 | display: 'flex', 111 | padding: '20px', 112 | justifyContent: 'center' 113 | }, 114 | 115 | resultBlock: { 116 | display: 'flex', 117 | flexWrap: 'wrap', 118 | 119 | item: { 120 | width: '16.66%', 121 | padding: 1, 122 | cursor: 'pointer', 123 | 124 | loading: { 125 | active: {cursor: 'progress'}, 126 | notActive: {opacity: 0.1} 127 | } 128 | } 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /projects/react-plugin/assets/styles/index.js: -------------------------------------------------------------------------------- 1 | import CSS from './styles.css'; 2 | import DragDropCss from './drag-drop.css'; 3 | import BgCss from './bg.css'; 4 | import IconsCss from './icons.css'; 5 | import ModalCss from './modal.css'; 6 | import SearchCss from './search.css'; 7 | 8 | import './main.css'; 9 | import './toastr.css'; 10 | import './toastr.animation.css'; 11 | import '../fonts/filerobot-uploader.font.css'; 12 | 13 | export { 14 | CSS, 15 | DragDropCss, 16 | BgCss, 17 | IconsCss, 18 | ModalCss, 19 | SearchCss 20 | } -------------------------------------------------------------------------------- /projects/react-plugin/assets/styles/main.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes fa-spin { 2 | 0% { 3 | -webkit-transform: rotate(0deg); 4 | transform: rotate(0deg); 5 | } 6 | 100% { 7 | -webkit-transform: rotate(359deg); 8 | transform: rotate(359deg); 9 | } 10 | } 11 | @keyframes fa-spin { 12 | 0% { 13 | -webkit-transform: rotate(0deg); 14 | transform: rotate(0deg); 15 | } 16 | 100% { 17 | -webkit-transform: rotate(359deg); 18 | transform: rotate(359deg); 19 | } 20 | } 21 | 22 | /* cyrillic-ext */ 23 | @font-face { 24 | font-family: 'Roboto Mono'; 25 | font-style: normal; 26 | font-weight: 400; 27 | src: local('Roboto Mono'), local('RobotoMono-Regular'), url(https://fonts.gstatic.com/s/robotomono/v5/hMqPNLsu_dywMa4C_DEpY_ZraR2Tg8w2lzm7kLNL0-w.woff2) format('woff2'); 28 | unicode-range: U+0460-052F, U+20B4, U+2DE0-2DFF, U+A640-A69F; 29 | } 30 | /* cyrillic */ 31 | @font-face { 32 | font-family: 'Roboto Mono'; 33 | font-style: normal; 34 | font-weight: 400; 35 | src: local('Roboto Mono'), local('RobotoMono-Regular'), url(https://fonts.gstatic.com/s/robotomono/v5/hMqPNLsu_dywMa4C_DEpY14sYYdJg5dU2qzJEVSuta0.woff2) format('woff2'); 36 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 37 | } 38 | /* greek-ext */ 39 | @font-face { 40 | font-family: 'Roboto Mono'; 41 | font-style: normal; 42 | font-weight: 400; 43 | src: local('Roboto Mono'), local('RobotoMono-Regular'), url(https://fonts.gstatic.com/s/robotomono/v5/hMqPNLsu_dywMa4C_DEpY1BW26QxpSj-_ZKm_xT4hWw.woff2) format('woff2'); 44 | unicode-range: U+1F00-1FFF; 45 | } 46 | /* greek */ 47 | @font-face { 48 | font-family: 'Roboto Mono'; 49 | font-style: normal; 50 | font-weight: 400; 51 | src: local('Roboto Mono'), local('RobotoMono-Regular'), url(https://fonts.gstatic.com/s/robotomono/v5/hMqPNLsu_dywMa4C_DEpYwt_Rm691LTebKfY2ZkKSmI.woff2) format('woff2'); 52 | unicode-range: U+0370-03FF; 53 | } 54 | /* vietnamese */ 55 | @font-face { 56 | font-family: 'Roboto Mono'; 57 | font-style: normal; 58 | font-weight: 400; 59 | src: local('Roboto Mono'), local('RobotoMono-Regular'), url(https://fonts.gstatic.com/s/robotomono/v5/hMqPNLsu_dywMa4C_DEpY9DiNsR5a-9Oe_Ivpu8XWlY.woff2) format('woff2'); 60 | unicode-range: U+0102-0103, U+1EA0-1EF9, U+20AB; 61 | } 62 | /* latin-ext */ 63 | @font-face { 64 | font-family: 'Roboto Mono'; 65 | font-style: normal; 66 | font-weight: 400; 67 | src: local('Roboto Mono'), local('RobotoMono-Regular'), url(https://fonts.gstatic.com/s/robotomono/v5/hMqPNLsu_dywMa4C_DEpY6E8kM4xWR1_1bYURRojRGc.woff2) format('woff2'); 68 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 69 | } 70 | /* latin */ 71 | @font-face { 72 | font-family: 'Roboto Mono'; 73 | font-style: normal; 74 | font-weight: 400; 75 | src: local('Roboto Mono'), local('RobotoMono-Regular'), url(https://fonts.gstatic.com/s/robotomono/v5/hMqPNLsu_dywMa4C_DEpY4gp9Q8gbYrhqGlRav_IXfk.woff2) format('woff2'); 76 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215; 77 | } 78 | 79 | .tab-header-item:focus { 80 | outline: none !important; 81 | border: 2px solid #4D90FE !important; 82 | box-shadow: 0 0 5px #4D90FE !important; 83 | } 84 | 85 | .airstore-root-box > [data-focus-lock-disabled], 86 | .airstore-root-box > [data-focus-lock-disabled] > div{ 87 | width: 100%; 88 | height: 100%; 89 | } -------------------------------------------------------------------------------- /projects/react-plugin/assets/styles/modal.css.js: -------------------------------------------------------------------------------- 1 | export default { 2 | container: { 3 | "position": "fixed", 4 | "top": "0", 5 | "bottom": "0", 6 | "left": "0", 7 | "right": "0", 8 | "overflowY": "auto", 9 | "backgroundColor": "rgba(0,0,0,.6)", 10 | "zIndex": "3001", 11 | 12 | '@media (min-width: 768px)': { 13 | "padding": "10px" 14 | }, 15 | 16 | modal: { 17 | "position": "relative", 18 | "margin": "0", 19 | "backgroundColor": "#fff", 20 | "width": "100%", 21 | "height": "100%", 22 | "overflowY": "auto", 23 | 24 | '@media (min-width: 768px)': { 25 | "minHeight": "300px", 26 | "maxHeight": "550px", 27 | "left": "50%", 28 | "transform": "translate(-50%,0)", 29 | "borderRadius": "4px", 30 | "width": "700px", 31 | "height": "auto", 32 | "display": "table", 33 | "boxShadow": "0 3px 8px rgba(0,0,0,.5)", 34 | "position": "relative" 35 | }, 36 | 37 | '@media (min-width: 1200px)': { 38 | width: 930 39 | }, 40 | 41 | content: { 42 | "fontSize": "16px", 43 | "color": "#636972", 44 | "zIndex": "2100", 45 | "position": "relative", 46 | "overflow": "hidden", 47 | "margin": "0 auto", 48 | "borderRadius": "4px", 49 | "boxShadow": "0 1px 8px rgba(0,0,0,.7)", 50 | "opacity": "1", 51 | "WebkitTransition": "all .3s", 52 | "transition": "all .3s", 53 | "transform": "translate3d(0,0,0)", 54 | "WebkitTransform": "initial", 55 | "width": "100%", 56 | "height": "100%", 57 | "background": "#f5f5f5" 58 | }, 59 | 60 | removeBtn: { 61 | "zIndex": "3", 62 | "position": "absolute", 63 | "right": "12px", 64 | "top": "9px", 65 | "fontSize": "20px", 66 | "cursor": "pointer" 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /projects/react-plugin/assets/styles/search.css.js: -------------------------------------------------------------------------------- 1 | export default { 2 | container: { 3 | fontFamily: 'Roboto, sans-serif', 4 | 5 | empty: { 6 | "height": "100%", 7 | "justifyContent": "center", 8 | "display": "flex", 9 | "alignItems": "center", 10 | "flexDirection": "column" 11 | }, 12 | 13 | title: { 14 | "fontWeight": "200", 15 | "marginTop": "-10%", 16 | fontSize: 25, 17 | color: '#5D636B' 18 | }, 19 | 20 | searchBlock: { 21 | display: 'flex', 22 | padding: '20px', 23 | justifyContent: 'center' 24 | }, 25 | 26 | resultBlock: { 27 | display: 'flex', 28 | flexWrap: 'wrap', 29 | alignItems: 'stretch', 30 | paddingRight: 10, 31 | 32 | item: { 33 | width: 'calc(100% / 6 - 10px)', 34 | margin: '10px 0 0 10px', 35 | cursor: 'pointer', 36 | background: '#e7e9ee', 37 | 38 | alignmentBlock: { 39 | display: 'inline-block', 40 | verticalAlign: 'middle', 41 | height: '100%' 42 | }, 43 | 44 | img: { 45 | display: 'inline-block', 46 | verticalAlign: 'middle' 47 | }, 48 | 49 | loading: { 50 | active: {cursor: 'progress'}, 51 | notActive: {opacity: 0.1} 52 | }, 53 | 54 | ':focus': { 55 | outlineColor: 'rgb(77, 144, 254)', 56 | outlineOffset: -2, 57 | outlineStyle: 'auto', 58 | outlineWidth: 5 59 | } 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /projects/react-plugin/assets/styles/styles.css.js: -------------------------------------------------------------------------------- 1 | export default { 2 | fa: { 3 | display: 'inline-block', 4 | font: 'normal normal normal 14px/1 FontAwesome', 5 | textRendering: 'auto', 6 | width: 'auto', 7 | verticalAlign: 'middle' 8 | }, 9 | 10 | faSpin: { 11 | "WebkitAnimation": "fa-spin 2s infinite linear", 12 | "animation": "fa-spin 2s infinite linear" 13 | }, 14 | 15 | 16 | // TABS styles -> 17 | tabs: { 18 | fontFamily: 'Roboto, sans-serif', 19 | 20 | // HEADER 21 | header: { 22 | // "position": "absolute", 23 | "whiteSpace": "nowrap", 24 | // "top": "0", 25 | "lineHeight": "40px", 26 | "width": "100%", 27 | "height": "40px", 28 | "zIndex": "2", 29 | "fontSize": "16px", 30 | "fontWeight": "600", 31 | 32 | container: { 33 | // "position": "absolute", 34 | // "top": "3px", 35 | "lineHeight": "1", 36 | "height": "100%", 37 | "display": "flex", 38 | 39 | item: { 40 | fontFamily: 'Roboto, sans-serif', 41 | "color": "#c0c1c1", 42 | "textDecoration": "none", 43 | "fontSize": "12px", 44 | lineHeight: '21px', 45 | "padding": "9px 12px", 46 | "cursor": "pointer", 47 | "borderRadius": "3px 3px 0 0", 48 | "borderLeft": "2px solid transparent", 49 | "borderRight": "2px solid transparent", 50 | "borderTop": "2px solid transparent", 51 | "borderBottom": "2px solid transparent", 52 | "textTransform": "uppercase", 53 | "fontWeight": "400", 54 | "display": "inline-block", 55 | verticalAlign: 'top', 56 | 57 | ":first-child": { 58 | marginLeft: "5px" 59 | }, 60 | 61 | selected: { 62 | "color": "#fff", 63 | "backgroundColor": "rgb(64, 84, 91)" 64 | }, 65 | 66 | ':hover': { 67 | "color": "#fff" 68 | }, 69 | 70 | //':focus': { 71 | // outline: 'none', 72 | // border: '2px solid #4D90FE', 73 | // boxShadow: '0px 0px 5px #4D90FE' 74 | //}, 75 | 76 | i: { 77 | fontSize: 21, 78 | lineHeight: '21px', 79 | marginRight: 5, 80 | display: 'inline-block', 81 | verticalAlign: 'middle' 82 | }, 83 | 84 | text: { 85 | display: 'inline-block', 86 | verticalAlign: 'middle', 87 | '@media screen and (max-width: 850px)': { 88 | display: 'none' 89 | } 90 | } 91 | } 92 | } 93 | }, 94 | 95 | // CONTENT 96 | content: { 97 | position: "relative", 98 | "zIndex": "1", 99 | // "bottom": "0", 100 | // "right": "0", 101 | // "left": "0", 102 | // "top": "50px", 103 | "overflowY": "auto", 104 | "overflowX": "hidden", 105 | "WebkitTransition": "all .3s cubic-bezier(.25,.46,.45,.94)", 106 | "transition": "all .3s cubic-bezier(.25,.46,.45,.94)", 107 | "display": "flex", 108 | "height": "100%" 109 | } 110 | }, 111 | // <- TABS styles 112 | 113 | 114 | field: { 115 | "height": "34px", 116 | "padding": "6px 12px", 117 | "fontSize": "14px", 118 | "lineHeight": "1.42857", 119 | "color": "rgb(85, 85, 85)", 120 | "background": "rgb(255, 255, 255)", 121 | "borderRadius": "4px", 122 | "boxShadow": "rgba(0, 0, 0, 0.075) 0px 1px 1px inset", 123 | "transition": "border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out", 124 | "border": "1px solid rgb(204, 204, 204)", 125 | "marginRight": "10px", 126 | "outline": "0px", 127 | 128 | ':focus': { 129 | border: '1px solid #5D636B', 130 | outlineColor: 'rgb(77, 144, 254)', 131 | outlineOffset: -2, 132 | outlineStyle: 'auto', 133 | outlineWidth: 5 134 | } 135 | }, 136 | 137 | button: { 138 | fontFamily: 'Roboto, sans-serif', 139 | "height": "34px", 140 | "padding": "6px 12px", 141 | "lineHeight": "1.42857", 142 | "textTransform": 'uppercase', 143 | "color": "#5D636B", 144 | "backgroundColor": "transparent", 145 | "backgroundRepeat": "repeat-x", 146 | "textShadow": "rgb(255, 255, 255) 0px 1px", 147 | "border": "1px solid #5D636B", 148 | "borderRadius": "4px", 149 | "cursor": "pointer", 150 | "fontWeight": "500", 151 | "outline": "0", 152 | "fontSize": 12, 153 | 154 | ':hover': { 155 | "backgroundColor": '#6D737B', 156 | "color": "#fff" 157 | }, 158 | 159 | ':focus': { 160 | outlineColor: 'rgb(77, 144, 254)', 161 | outlineOffset: -2, 162 | outlineStyle: 'auto', 163 | outlineWidth: 5 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /projects/react-plugin/assets/styles/toastr.animation.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | /*! 4 | * animate.css -http://daneden.me/animate 5 | * Version - 3.5.1 6 | * Licensed under the MIT license - http://opensource.org/licenses/MIT 7 | * 8 | * Copyright (c) 2016 Daniel Eden 9 | */ 10 | 11 | .animated { 12 | -webkit-animation-duration: 1s; 13 | animation-duration: 1s; 14 | -webkit-animation-fill-mode: both; 15 | animation-fill-mode: both; 16 | } 17 | 18 | .animated.infinite { 19 | -webkit-animation-iteration-count: infinite; 20 | animation-iteration-count: infinite; 21 | } 22 | 23 | .animated.hinge { 24 | -webkit-animation-duration: 2s; 25 | animation-duration: 2s; 26 | } 27 | 28 | .animated.flipOutX, 29 | .animated.flipOutY, 30 | .animated.bounceIn, 31 | .animated.bounceOut { 32 | -webkit-animation-duration: .75s; 33 | animation-duration: .75s; 34 | } 35 | 36 | @-webkit-keyframes bounceIn { 37 | from, 20%, 40%, 60%, 80%, to { 38 | -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); 39 | animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); 40 | } 41 | 42 | 0% { 43 | opacity: 0; 44 | -webkit-transform: scale3d(.3, .3, .3); 45 | transform: scale3d(.3, .3, .3); 46 | } 47 | 48 | 20% { 49 | -webkit-transform: scale3d(1.1, 1.1, 1.1); 50 | transform: scale3d(1.1, 1.1, 1.1); 51 | } 52 | 53 | 40% { 54 | -webkit-transform: scale3d(.9, .9, .9); 55 | transform: scale3d(.9, .9, .9); 56 | } 57 | 58 | 60% { 59 | opacity: 1; 60 | -webkit-transform: scale3d(1.03, 1.03, 1.03); 61 | transform: scale3d(1.03, 1.03, 1.03); 62 | } 63 | 64 | 80% { 65 | -webkit-transform: scale3d(.97, .97, .97); 66 | transform: scale3d(.97, .97, .97); 67 | } 68 | 69 | to { 70 | opacity: 1; 71 | -webkit-transform: scale3d(1, 1, 1); 72 | transform: scale3d(1, 1, 1); 73 | } 74 | } 75 | 76 | @keyframes bounceIn { 77 | from, 20%, 40%, 60%, 80%, to { 78 | -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); 79 | animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); 80 | } 81 | 82 | 0% { 83 | opacity: 0; 84 | -webkit-transform: scale3d(.3, .3, .3); 85 | transform: scale3d(.3, .3, .3); 86 | } 87 | 88 | 20% { 89 | -webkit-transform: scale3d(1.1, 1.1, 1.1); 90 | transform: scale3d(1.1, 1.1, 1.1); 91 | } 92 | 93 | 40% { 94 | -webkit-transform: scale3d(.9, .9, .9); 95 | transform: scale3d(.9, .9, .9); 96 | } 97 | 98 | 60% { 99 | opacity: 1; 100 | -webkit-transform: scale3d(1.03, 1.03, 1.03); 101 | transform: scale3d(1.03, 1.03, 1.03); 102 | } 103 | 104 | 80% { 105 | -webkit-transform: scale3d(.97, .97, .97); 106 | transform: scale3d(.97, .97, .97); 107 | } 108 | 109 | to { 110 | opacity: 1; 111 | -webkit-transform: scale3d(1, 1, 1); 112 | transform: scale3d(1, 1, 1); 113 | } 114 | } 115 | 116 | .bounceIn { 117 | -webkit-animation-name: bounceIn; 118 | animation-name: bounceIn; 119 | } 120 | 121 | 122 | @-webkit-keyframes bounceOut { 123 | 20% { 124 | -webkit-transform: scale3d(.9, .9, .9); 125 | transform: scale3d(.9, .9, .9); 126 | } 127 | 128 | 50%, 55% { 129 | opacity: 1; 130 | -webkit-transform: scale3d(1.1, 1.1, 1.1); 131 | transform: scale3d(1.1, 1.1, 1.1); 132 | } 133 | 134 | to { 135 | opacity: 0; 136 | -webkit-transform: scale3d(.3, .3, .3); 137 | transform: scale3d(.3, .3, .3); 138 | } 139 | } 140 | 141 | @keyframes bounceOut { 142 | 20% { 143 | -webkit-transform: scale3d(.9, .9, .9); 144 | transform: scale3d(.9, .9, .9); 145 | } 146 | 147 | 50%, 55% { 148 | opacity: 1; 149 | -webkit-transform: scale3d(1.1, 1.1, 1.1); 150 | transform: scale3d(1.1, 1.1, 1.1); 151 | } 152 | 153 | to { 154 | opacity: 0; 155 | -webkit-transform: scale3d(.3, .3, .3); 156 | transform: scale3d(.3, .3, .3); 157 | } 158 | } 159 | 160 | .bounceOut { 161 | -webkit-animation-name: bounceOut; 162 | animation-name: bounceOut; 163 | } 164 | 165 | 166 | @-webkit-keyframes fadeIn { 167 | from { 168 | opacity: 0; 169 | } 170 | 171 | to { 172 | opacity: 1; 173 | } 174 | } 175 | 176 | @keyframes fadeIn { 177 | from { 178 | opacity: 0; 179 | } 180 | 181 | to { 182 | opacity: 1; 183 | } 184 | } 185 | 186 | .fadeIn { 187 | -webkit-animation-name: fadeIn; 188 | animation-name: fadeIn; 189 | } 190 | 191 | @-webkit-keyframes fadeOut { 192 | from { 193 | opacity: 1; 194 | } 195 | 196 | to { 197 | opacity: 0; 198 | } 199 | } 200 | 201 | @keyframes fadeOut { 202 | from { 203 | opacity: 1; 204 | } 205 | 206 | to { 207 | opacity: 0; 208 | } 209 | } 210 | 211 | .fadeOut { 212 | -webkit-animation-name: fadeOut; 213 | animation-name: fadeOut; 214 | } 215 | 216 | -------------------------------------------------------------------------------- /projects/react-plugin/assets/styles/toastr.css: -------------------------------------------------------------------------------- 1 | .toast-title { 2 | font-weight: bold; 3 | } 4 | .toast-message { 5 | -ms-word-wrap: break-word; 6 | word-wrap: break-word; 7 | } 8 | .toast-message a, 9 | .toast-message label { 10 | color: #FFFFFF; 11 | } 12 | .toast-message a:hover { 13 | color: #CCCCCC; 14 | text-decoration: none; 15 | } 16 | .toast-close-button { 17 | position: relative; 18 | right: -0.3em; 19 | top: -0.3em; 20 | float: right; 21 | font-size: 20px; 22 | font-weight: bold; 23 | color: #FFFFFF; 24 | -webkit-text-shadow: 0 1px 0 #ffffff; 25 | text-shadow: 0 1px 0 #ffffff; 26 | opacity: 0.8; 27 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); 28 | filter: alpha(opacity=80); 29 | line-height: 1; 30 | } 31 | .toast-close-button:hover, 32 | .toast-close-button:focus { 33 | color: #000000; 34 | text-decoration: none; 35 | cursor: pointer; 36 | opacity: 0.4; 37 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); 38 | filter: alpha(opacity=40); 39 | } 40 | .rtl .toast-close-button { 41 | left: -0.3em; 42 | float: left; 43 | right: 0.3em; 44 | } 45 | /*Additional properties for button version 46 | iOS requires the button element instead of an anchor tag. 47 | If you want the anchor version, it requires `href="#"`.*/ 48 | button.toast-close-button { 49 | padding: 0; 50 | cursor: pointer; 51 | background: transparent; 52 | border: 0; 53 | -webkit-appearance: none; 54 | } 55 | .toast-top-center { 56 | top: 0; 57 | right: 0; 58 | width: 100%; 59 | } 60 | .toast-bottom-center { 61 | bottom: 0; 62 | right: 0; 63 | width: 100%; 64 | } 65 | .toast-top-full-width { 66 | top: 0; 67 | right: 0; 68 | width: 100%; 69 | } 70 | .toast-bottom-full-width { 71 | bottom: 0; 72 | right: 0; 73 | width: 100%; 74 | } 75 | .toast-top-left { 76 | top: 12px; 77 | left: 12px; 78 | } 79 | .toast-top-right { 80 | top: 12px; 81 | right: 12px; 82 | } 83 | .toast-bottom-right { 84 | right: 12px; 85 | bottom: 12px; 86 | } 87 | .toast-bottom-left { 88 | bottom: 12px; 89 | left: 12px; 90 | } 91 | #toast-container { 92 | position: fixed; 93 | z-index: 999999999; 94 | pointer-events: none; 95 | /*overrides*/ 96 | } 97 | #toast-container * { 98 | -moz-box-sizing: border-box; 99 | -webkit-box-sizing: border-box; 100 | box-sizing: border-box; 101 | } 102 | #toast-container > div { 103 | position: relative; 104 | pointer-events: auto; 105 | overflow: hidden; 106 | margin: 0 0 6px; 107 | padding: 15px 15px 15px 50px; 108 | width: 300px; 109 | -moz-border-radius: 3px 3px 3px 3px; 110 | -webkit-border-radius: 3px 3px 3px 3px; 111 | border-radius: 3px 3px 3px 3px; 112 | background-position: 15px center; 113 | background-repeat: no-repeat; 114 | -moz-box-shadow: 0 0 12px #999999; 115 | -webkit-box-shadow: 0 0 12px #999999; 116 | box-shadow: 0 0 12px #999999; 117 | color: #FFFFFF; 118 | opacity: 0.8; 119 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); 120 | filter: alpha(opacity=80); 121 | } 122 | #toast-container > div.rtl { 123 | direction: rtl; 124 | padding: 15px 50px 15px 15px; 125 | background-position: right 15px center; 126 | } 127 | #toast-container > div:hover { 128 | -moz-box-shadow: 0 0 12px #000000; 129 | -webkit-box-shadow: 0 0 12px #000000; 130 | box-shadow: 0 0 12px #000000; 131 | opacity: 1; 132 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100); 133 | filter: alpha(opacity=100); 134 | cursor: pointer; 135 | } 136 | #toast-container > .toast-info { 137 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important; 138 | } 139 | #toast-container > .toast-error { 140 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important; 141 | } 142 | #toast-container > .toast-success { 143 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important; 144 | } 145 | #toast-container > .toast-warning { 146 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important; 147 | } 148 | #toast-container.toast-top-center > div, 149 | #toast-container.toast-bottom-center > div { 150 | width: 300px; 151 | margin-left: auto; 152 | margin-right: auto; 153 | } 154 | #toast-container.toast-top-full-width > div, 155 | #toast-container.toast-bottom-full-width > div { 156 | width: 96%; 157 | margin-left: auto; 158 | margin-right: auto; 159 | } 160 | .toast { 161 | background-color: #030303; 162 | } 163 | .toast-success { 164 | background-color: #51A351; 165 | } 166 | .toast-error { 167 | background-color: #BD362F; 168 | } 169 | .toast-info { 170 | background-color: #2F96B4; 171 | } 172 | .toast-warning { 173 | background-color: #F89406; 174 | } 175 | .toast-progress { 176 | position: absolute; 177 | left: 0; 178 | bottom: 0; 179 | height: 4px; 180 | background-color: #000000; 181 | opacity: 0.4; 182 | -ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40); 183 | filter: alpha(opacity=40); 184 | } 185 | /*Responsive Design*/ 186 | @media all and (max-width: 240px) { 187 | #toast-container > div { 188 | padding: 8px 8px 8px 50px; 189 | width: 11em; 190 | } 191 | #toast-container > div.rtl { 192 | padding: 8px 50px 8px 8px; 193 | } 194 | #toast-container .toast-close-button { 195 | right: -0.2em; 196 | top: -0.2em; 197 | } 198 | #toast-container .rtl .toast-close-button { 199 | left: -0.2em; 200 | right: 0.2em; 201 | } 202 | } 203 | @media all and (min-width: 241px) and (max-width: 480px) { 204 | #toast-container > div { 205 | padding: 8px 8px 8px 50px; 206 | width: 18em; 207 | } 208 | #toast-container > div.rtl { 209 | padding: 8px 50px 8px 8px; 210 | } 211 | #toast-container .toast-close-button { 212 | right: -0.2em; 213 | top: -0.2em; 214 | } 215 | #toast-container .rtl .toast-close-button { 216 | left: -0.2em; 217 | right: 0.2em; 218 | } 219 | } 220 | @media all and (min-width: 481px) and (max-width: 768px) { 221 | #toast-container > div { 222 | padding: 15px 15px 15px 50px; 223 | width: 25em; 224 | } 225 | #toast-container > div.rtl { 226 | padding: 15px 50px 15px 15px; 227 | } 228 | } -------------------------------------------------------------------------------- /projects/react-plugin/assets/translations/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | upload: { 3 | tab_title: 'Upload', 4 | browse_your_computer: 'Browse your computer', 5 | or: 'or', 6 | enter_url_to_upload_from_web: 'Enter URL to upload from web', 7 | upload_btn: 'Upload', 8 | drag_file_here: 'Drag file here', 9 | accepted_file_types: 'Accepted file types', 10 | accepted_file_size: 'Up to 10MB.', 11 | uploading: 'Uploading', 12 | error: 'Error', 13 | url_not_valid: 'URL not valid!', 14 | empty_url: 'Empty URL!', 15 | search: 'Search', 16 | found: 'Found', 17 | apply: 'Apply', 18 | categories: 'Categories', 19 | file_already_exists: 'File already exists in your gallery', 20 | invalid_file_extension: 'Invalid file extension', 21 | all: 'All', 22 | too_small: 'The Uploader is not available on mobile, please use it on a Desktop', 23 | close: 'Close', 24 | image_editor: 'Image Editor', 25 | smart_crop: 'Smart crop', 26 | face_detection: 'Face detection', 27 | resize: 'Resize', 28 | width: 'Width', 29 | height: 'Height', 30 | revert: 'Revert', 31 | crop: 'Crop', 32 | crop_and_resize: 'Crop / Resize', 33 | cancel: 'Cancel', 34 | selected_files: 'Selected files', 35 | total_size: 'total size', 36 | sort: 'Sort', 37 | name: 'Name', 38 | size: 'Size', 39 | type: 'Type', 40 | modified: 'Modified date', 41 | uploaded: 'Uploaded date', 42 | invalid_token: 'Invalid token' 43 | }, 44 | file_manager: { 45 | tab_title: 'My gallery', 46 | search_by_file_name_tag_desc: 'search by file name, tag or description', 47 | upload_images: 'Upload images', 48 | drag_images_here: 'Drag images here', 49 | change_folder: 'Change folder', 50 | media_library: 'Media Library', 51 | go_back: 'Go back', 52 | select: 'Select', 53 | modify: 'Modify URL', 54 | tag: 'Tag', 55 | edit: 'Edit', 56 | delete: 'Delete', 57 | no_subfolders: 'No subfolders', 58 | deselect_all: 'Deselect all', 59 | are_you_sure: 'Are you sure' 60 | }, 61 | icons: { 62 | tab_title: 'Free icons', 63 | zero_icons_was_found: '0 icons was found :(', 64 | set_icon_as_not_relevant: 'Set icon as not relevant', 65 | you_can_search_icons_here: 'You can search icons here', 66 | color_filter: 'Color filter', 67 | all: 'All', 68 | multi_color: 'Multi color', 69 | mono_color: 'Mono color', 70 | customize_your_icon: 'Customize your icon', 71 | theme_colors: 'Theme colors', 72 | new_tag_successfully_added: 'New tag successfully added!' 73 | }, 74 | images: { 75 | tab_title: 'Free images', 76 | zero_images_was_found: '0 images was found :(', 77 | color_filter: 'Color filter', 78 | add_color: 'add color', 79 | images: 'Backgrounds', 80 | you_can_search_images_here: 'You can search images here' 81 | }, 82 | tagging: { 83 | tab_title: 'Tagging', 84 | something_went_wrong_try_again: 'something went wrong, try again', 85 | will_automatically_generate_tags: 'will automatically generate tags based on image recognition technology', 86 | go_back: 'Go back', 87 | file_name: 'File name', 88 | size: 'Size', 89 | first_upload: 'First Upload', 90 | last_modified: 'Last Modified', 91 | add_description: 'Add description', 92 | add_a_tag_separate_by_pressing_enter: 'Add a tag (separate by pressing enter)', 93 | add_tags_field: 'You should add a "tags" field in your metadata model first', 94 | add_description_field: 'You should add a "description" field in your metadata model first', 95 | generate_tags: 'Generate tags', 96 | save: 'Save', 97 | save_and_modify: 'Save and Modify URL', 98 | asset_could_not_be_automatically_tagged: 'Asset could not be automatically tagged', 99 | could_not_be_automatically_tagged: 'could not be automatically tagged', 100 | auto_tagging_processing: 'Auto-tagging processing...', 101 | product_ref: 'Product ref', 102 | product_position: 'Product position', 103 | not_set: 'Not set', 104 | update_product_ref: 'Update product ref', 105 | update_product_position: 'Update product position', 106 | updating: 'updating...', 107 | common_tags: 'Common tags' 108 | }, 109 | tips: { 110 | edit: 'resize, crop, adjust the image', 111 | delete: 'delete {number} image', 112 | tag: 'mange tags and meta properties for {number} image', 113 | select: 'select {number} image', 114 | modify: 'modify url for the image', 115 | select_multiply: 'select multiple images' 116 | }, 117 | deletePopup: { 118 | title: 'Are you sure', 119 | accept: 'Delete', 120 | cancel: 'Cancel', 121 | msg: 'Do you want to delete', 122 | file: 'file', 123 | files: 'files', 124 | }, 125 | goBackPopup: { 126 | title: 'Are you sure', 127 | accept: 'Leave', 128 | cancel: 'Cancel', 129 | msg: 'Do you want to leave this tab? Changes you made may not be saved' 130 | } 131 | }; -------------------------------------------------------------------------------- /projects/react-plugin/assets/translations/fr.js: -------------------------------------------------------------------------------- 1 | export default { 2 | upload: { 3 | tab_title: 'Upload', 4 | browse_your_computer: 'Parcourir mon ordinateur', 5 | or: 'ou', 6 | enter_url_to_upload_from_web: 'URL publique du fichier', 7 | upload_btn: 'Télécharger', 8 | drag_file_here: 'Glisser-déposer mon fichier ici', 9 | accepted_file_types: 'Formats de fichiers acceptés', 10 | accepted_file_size: 'Jusqu\'à 10 Mo', 11 | uploading: 'Téléchargement', 12 | error: 'Erreur', 13 | url_not_valid: 'URL invalide !', 14 | empty_url: 'URL vide !', 15 | search: 'Rechercher', 16 | found: 'Trouvé', 17 | apply: 'Appliquer', 18 | categories: 'Catégories', 19 | file_already_exists: 'Le fichier existe déjà dans votre galerie', 20 | invalid_file_extension: 'Extension de fichier invalide', 21 | all: 'Tout afficher', 22 | too_small: 'Le téléchargement de fichier n\'est pas disponible sur mobile.', 23 | close: 'Fermer', 24 | image_editor: 'Editeur d\'image', 25 | smart_crop: 'Rognage automatique', 26 | face_detection: 'Détection de visages', 27 | resize: 'Redimensionnement', 28 | width: 'Taille', 29 | height: 'Hauteur', 30 | revert: 'Retour', 31 | crop: 'Rogner', 32 | crop_and_resize: 'Rogner / Redimensionnement', 33 | cancel: 'Annuler', 34 | selected_files: 'Sélectionner des fichiers', 35 | total_size: 'Taille totale', 36 | sort: 'Trier', 37 | name: 'Nom', 38 | size: 'Taille', 39 | type: 'Type', 40 | modified: 'Date de motification', 41 | uploaded: 'Date de création', 42 | product_position: 'Product position', 43 | product_ref: 'Product reference', 44 | invalid_token: 'Jeton invalide' 45 | }, 46 | file_manager: { 47 | tab_title: 'Ma galerie', 48 | search_by_file_name_tag_desc: 'Recherche par nom, tag ou description', 49 | upload_images: 'Télécharger', 50 | drag_images_here: 'Glisser-déposer mon fichier ici', 51 | change_folder: 'Changer de répertoire', 52 | media_library: 'Bibliothèque des médias', 53 | go_back: 'Retour', 54 | select: 'Sélectionner', 55 | modify: 'Modifer', 56 | tag: 'Taguer', 57 | edit: 'Editer', 58 | delete: 'Delete', 59 | no_subfolders: 'Pas de sous-dossier', 60 | deselect_all: 'Tout déselectionner', 61 | are_you_sure: 'Êtes-vous sûr' 62 | }, 63 | icons: { 64 | tab_title: 'Icônes gratuites', 65 | zero_icons_was_found: 'Aucune icône trouvée :(', 66 | set_icon_as_not_relevant: 'Marquer l\'icône comme non pertinente', 67 | you_can_search_icons_here: 'Rechercher des icônes', 68 | color_filter: 'Filtre couleur', 69 | all: 'Tout', 70 | multi_color: 'Multi couleurs', 71 | mono_color: 'Mono couleur', 72 | customize_your_icon: 'Personnaliser l\'icône', 73 | theme_colors: 'Couleurs de thème', 74 | new_tag_successfully_added: 'Le nouveau tag a bien été ajouté!' 75 | }, 76 | images: { 77 | tab_title: 'Images gratuites', 78 | zero_images_was_found: 'Aucune image trouvée :(', 79 | color_filter: 'Filtre couleur', 80 | add_color: 'Ajouter une couleur', 81 | images: 'Fonds d\'écran', 82 | you_can_search_images_here: 'Rechercher des images' 83 | }, 84 | tagging: { 85 | tab_title: 'Tagging', 86 | something_went_wrong_try_again: 'Un incident s\'est produit, veuillez réessayer', 87 | will_automatically_generate_tags: 'Génèrera automatiquement des tags basés sur une technologie de reconnaissance d\'images', 88 | go_back: 'Retour', 89 | file_name: 'Nom du fichier', 90 | size: 'Taille', 91 | first_upload: 'Ajouté le', 92 | last_modified: 'Modifié le', 93 | add_description: 'Ajouter description', 94 | add_a_tag_separate_by_pressing_enter: 'Ajouter un tag (séparer par ENTRÉE)', 95 | add_tags_field: 'Vous devez d\'abord ajouter un champs "tags" dans votre modèle de metadata', 96 | add_description_field: 'Vous devez d\'abord ajouter un champs "description" dans votre modèle de metadata.', 97 | generate_tags: 'Taggage auto', 98 | save: 'Sauvegarder', 99 | save_and_modify: 'Enregistrer et modifier l\'URL\n', 100 | asset_could_not_be_automatically_tagged : 'L\'image n\'a pas pu être tagguée automatiquement', 101 | could_not_be_automatically_tagged : 'n\'a pas pu être tagguée automatiquement', 102 | auto_tagging_processing: 'Auto-tagging en cours...', 103 | product_ref: 'Product ref', 104 | product_position: 'Product position', 105 | not_set: 'Not set', 106 | update_product_ref: 'Update product ref', 107 | update_product_position: 'Update product position', 108 | updating: 'updating...', 109 | common_tags: 'Balises courantes' 110 | }, 111 | tips: { 112 | edit: 'redimensionner, rogner, ajuster l\'image', 113 | delete: 'supprimer {number} image', 114 | tag: 'gérer les tags et lees metadata de {number} image', 115 | select: 'sélectionner {number} image', 116 | modify: 'Modifier l\'url de l\'image', 117 | select_multiply: 'sélectionner plusieurs images' 118 | }, 119 | deletePopup: { 120 | title: 'Êtes-vous sûr', 121 | accept: 'Effacer', 122 | cancel: 'Annuler', 123 | msg: 'Voulez-vous supprimer', 124 | file: 'fichier', 125 | files: 'fichiers', 126 | }, 127 | goBackPopup: { 128 | title: 'Êtes-vous sûr', 129 | accept: 'Quitter', 130 | cancel: 'Annuler', 131 | msg: 'Voulez-vous quitter cet onglet? Les modifications que vous avez apportées ne seront sûrement pas sauvegardées' 132 | } 133 | }; -------------------------------------------------------------------------------- /projects/react-plugin/assets/translations/index.js: -------------------------------------------------------------------------------- 1 | export en from './en'; 2 | export ru from './ru'; 3 | export fr from './fr'; -------------------------------------------------------------------------------- /projects/react-plugin/assets/translations/ru.js: -------------------------------------------------------------------------------- 1 | export default { 2 | upload: { 3 | tab_title: 'Загрузить', 4 | browse_your_computer: 'Обзор компьютера', 5 | or: 'или', 6 | enter_url_to_upload_from_web: 'Введите URL для загрузки из Интернета', 7 | upload_btn: 'Загрузить', 8 | drag_file_here: 'Перетащите файл сюда', 9 | accepted_file_types: 'Допустимые типы файлов', 10 | accepted_file_size: 'До 10 МБ.', 11 | uploading: 'Загрузка...', 12 | error: 'Ошибка', 13 | url_not_valid: 'неправильный URL!', 14 | empty_url: 'пустой URL!', 15 | search: 'Поиск', 16 | found: 'Найдено', 17 | apply: 'Применить', 18 | categories: 'Категории', 19 | file_already_exists: 'Файл уже существует в галерее', 20 | invalid_file_extension: 'Неверное расширение файла', 21 | all: 'Все', 22 | too_small: 'Нет поддержки на мобильных устройствах', 23 | close: 'Закрыть', 24 | image_editor: 'Редактор изображения', 25 | smart_crop: 'Умная обрезка', 26 | face_detection: 'Распознавание лиц', 27 | resize: 'Изменить размер', 28 | width: 'Ширина', 29 | height: 'Высота', 30 | revert: 'Вернуть', 31 | crop: 'Обрезать', 32 | crop_and_resize: 'Обрезать / Изменить размер', 33 | cancel: 'Отменить', 34 | selected_files: 'Добавленные файлы', 35 | total_size: 'размер', 36 | sort: 'Сортировать', 37 | name: 'Имя', 38 | size: 'Размер', 39 | type: 'Тип', 40 | modified: 'Изменен', 41 | uploaded: 'Загружен', 42 | product_position: 'Позиция', 43 | product_ref: 'Product reference', 44 | invalid_token: 'Некорректный токен' 45 | }, 46 | file_manager: { 47 | tab_title: 'Галерея', 48 | search_by_file_name_tag_desc: 'поиск по имени файла, тегу или описанию', 49 | upload_images: 'Загрузить изображения', 50 | drag_images_here: 'Перетащите изображения сюда', 51 | change_folder: 'Изменить папку', 52 | media_library: 'Медиагалерея', 53 | go_back: 'Назад', 54 | select: 'Выбрать', 55 | modify: 'Модифицировать', 56 | tag: 'Изменить', 57 | edit: 'Редактировать', 58 | delete: 'Удалить', 59 | no_subfolders: 'Нет подпапок', 60 | deselect_all: 'Отменить выделение', 61 | are_you_sure: 'Вы уверены' 62 | }, 63 | icons: { 64 | tab_title: 'Иконки', 65 | zero_icons_was_found: '0 иконок найдено :(', 66 | set_icon_as_not_relevant: 'Установить как не релевантную', 67 | you_can_search_icons_here: 'Вы можете искать иконки здесь', 68 | color_filter: 'Фильтр', 69 | all: 'Все', 70 | multi_color: 'Многоцветный', 71 | mono_color: 'Моно цвет', 72 | customize_your_icon: 'Настройте иконку', 73 | theme_colors: 'Цвета темы', 74 | apply: 'Применять', 75 | new_tag_successfully_added: 'Новый тег успешно добавлен!' 76 | }, 77 | images: { 78 | tab_title: 'Изображения', 79 | zero_images_was_found: '0 изображений найдено :(', 80 | color_filter: 'Цветовой Фильтр', 81 | add_color: 'добавить цвет', 82 | images: 'Фон', 83 | you_can_search_images_here: 'Вы можете искать изображения здесь' 84 | }, 85 | tagging: { 86 | tab_title: 'Метаданные', 87 | something_went_wrong_try_again: 'что-то пошло не так, попробуйте еще раз', 88 | will_automatically_generate_tags: 'Автоматически генерировать теги на основе технологии распознавания изображений', 89 | go_back: 'Вернуться', 90 | file_name: 'Имя файла', 91 | size: 'Размер', 92 | first_upload: 'Первая загрузка', 93 | last_modified: 'Последнее изменение', 94 | add_description: 'Добавить описание', 95 | add_a_tag_separate_by_pressing_enter: 'Добавить тег (отделите нажатием Enter)', 96 | generate_tags: 'Создать теги', 97 | save: 'Сохранить', 98 | save_and_modify: 'Save and Modify URL', 99 | asset_could_not_be_automatically_tagged: 'Файл не может быть автоматически тегирован', 100 | could_not_be_automatically_tagged: 'не может быть автоматически тегирован', 101 | auto_tagging_processing: 'Auto-tagging processing...', 102 | product_ref: 'Product ref', 103 | product_position: 'Product position', 104 | not_set: 'Not set', 105 | update_product_ref: 'Update product ref', 106 | update_product_position: 'Update product position', 107 | updating: 'updating...', 108 | common_tags: 'Общие тэги' 109 | }, 110 | tips: { 111 | edit: 'resize, crop, adjust the image', 112 | delete: 'delete {number} image', 113 | tag: 'mange tags and meta properties for {number} image', 114 | select: 'select {number} image', 115 | modify: 'modify url for the image', 116 | select_multiply: 'select multiple images' 117 | }, 118 | deletePopup: { 119 | title: 'Вы уверены', 120 | accept: 'Да', 121 | cancel: 'Отмена', 122 | msg: 'Вы хотите удалить', 123 | file: 'файл', 124 | files: 'файлов', 125 | }, 126 | goBackPopup: { 127 | title: 'Вы уверены', 128 | accept: 'Выйти', 129 | cancel: 'Отмена', 130 | msg: 'Вы хотите закрыть эту вкладку? Сделанные изменения могут быть не сохранены' 131 | } 132 | }; -------------------------------------------------------------------------------- /projects/react-plugin/assets/translations/utils.js: -------------------------------------------------------------------------------- 1 | export const getContentWithNumber = (content, amount) => !amount ? 2 | `${content.replace('{number}', '')}` 3 | : 4 | `${content.replace('{number}', amount)}${amount !== 1 ? 's' : ''}` 5 | ; -------------------------------------------------------------------------------- /projects/react-plugin/components/AirstoreUploaderWrapper.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../polyfills'; 3 | import AirstoreUploader from './AirstoreUploader'; 4 | import AppState from './AppState'; 5 | import { ThemeProvider } from 'styled-components'; 6 | import { I18n } from 'react-i18nify'; 7 | import * as translations from '../assets/translations'; 8 | import theme, { colorSchemes } from '../assets/styles/colorScheme'; 9 | 10 | I18n.setTranslations(translations); 11 | 12 | export default (props) => { 13 | const { 14 | config = {}, opened = false, onClose = () => {}, initialTab = null, onUpload = () => {}, ...otherProps 15 | } = props; 16 | config.colorScheme = config.colorScheme || {}; 17 | 18 | const colorTheme = config.colorScheme.active; 19 | const colors = colorTheme === 'custom' ? 20 | config.colorScheme[colorTheme] : colorSchemes[colorTheme || 'solarized']; 21 | 22 | return ( 23 | 24 | 25 | 33 | 34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /projects/react-plugin/components/AppState.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { deepCopy } from '../utils/helper.utils'; 3 | 4 | export const getInitialState = () => deepCopy({ 5 | hasChanged: false, 6 | isVisible: false, 7 | postUpload: false, 8 | activeTabId: 'UPLOAD', 9 | prevTab: 'UPLOAD', 10 | files: [], 11 | isTooSmall: window.innerWidth < 685, 12 | path: '' 13 | }); 14 | 15 | 16 | export default class AppState extends Component { 17 | state = { 18 | config: {}, 19 | activeModules: [], 20 | 21 | ...getInitialState() 22 | }; 23 | 24 | setAppState = (updater, callback) => { 25 | this.setState(updater, () => { 26 | if (this.props.debug) 27 | console.log("setAppState", JSON.stringify(this.state)); 28 | 29 | if (callback) callback(); 30 | }); 31 | }; 32 | 33 | render() { 34 | return ( 35 |
36 | {React.Children.map(this.props.children, child => { 37 | return React.cloneElement(child, { 38 | appState: this.state, 39 | setAppState: this.setAppState 40 | }); 41 | })} 42 |
43 | ); 44 | } 45 | } -------------------------------------------------------------------------------- /projects/react-plugin/components/CloseBtn.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { variables } from '../styledComponents/styleUtils'; 3 | 4 | const CloseBtn = styled.span.attrs(() => ({ 5 | role: 'button', 6 | className: 'sfi-airstore-cross' 7 | }))` 8 | cursor: pointer; 9 | position: absolute; 10 | font-weight: normal; 11 | top: ${props => props.t || '10px'}; 12 | right: ${props => props.r || '10px'}; 13 | left: ${props => props.l || 'auto'}; 14 | bottom: ${props => props.b || 'auto'}; 15 | font-size: ${props => props.fz || '18px'}; 16 | z-index: 10; 17 | color: ${p => p.theme.tabTextColor || variables.modal.colorMuted}; 18 | speak: none; 19 | font-style: normal; 20 | font-variant: normal; 21 | text-transform: none; 22 | line-height: 1; 23 | 24 | /* Better Font Rendering =========== */ 25 | -webkit-font-smoothing: antialiased; 26 | -moz-osx-font-smoothing: grayscale; 27 | 28 | :hover { 29 | color: ${variables.modal.colorMutedHover}; 30 | } 31 | `; 32 | 33 | export { CloseBtn }; -------------------------------------------------------------------------------- /projects/react-plugin/components/IconsTab/IconAddTagModal.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Aux } from '../hoc'; 3 | import { addTag } from '../../services/iconsApi.service'; 4 | import { 5 | ButtonSearch, Label, MonoIconSettings, Opacity, Input, IconAddTagInner 6 | } from '../../styledComponents'; 7 | import { I18n } from 'react-i18nify'; 8 | 9 | 10 | class IconAddTagModal extends Component { 11 | state = { tagName: '' }; 12 | 13 | componentDidMount() { 14 | setTimeout(() => { 15 | if (this._input && this._input.focus) this._input.focus(); 16 | }) 17 | } 18 | 19 | onAddTag = () => { 20 | const { activeIcon, onClose, showAlert } = this.props; 21 | const { tagName } = this.state; 22 | 23 | addTag(activeIcon.uid, tagName).then(() => { 24 | showAlert(I18n.t('icons.new_tag_successfully_added')); 25 | onClose(); 26 | }) 27 | } 28 | 29 | render() { 30 | const { onClose } = this.props; 31 | const { tagName } = this.state; 32 | 33 | return ( 34 | 35 | 36 | 37 | 38 | 39 | 40 | this._input = node} 42 | defaultValue={tagName} 43 | onKeyDown={event => { event.keyCode === 13 && this.onAddTag(); }} 44 | onChange={({ target }) => { this.setState({ tagName: target.value }) } } 45 | /> 46 | Add tag 47 | 48 | 49 | 50 | ) 51 | } 52 | } 53 | 54 | 55 | export default IconAddTagModal; -------------------------------------------------------------------------------- /projects/react-plugin/components/IconsTab/IconItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | IconTabImg, HoverWrapper, AddTagBtn, NotRelevantBtn, ActionsIconWrapper, IconBoxWrapperInner, IconWrapper 4 | } from '../../styledComponents'; 5 | 6 | 7 | class IconItem extends Component { 8 | state = { isHover: false }; 9 | hoverToggle(name, isHover) { this.setState({ [name]: isHover }) } 10 | 11 | render() { 12 | const { icon, onIconClick, addTag, isShowAddTagBtn, isShowNotRelevantBtn, setAsNotRelevant, 13 | onLoadImage, columnWidth, index 14 | } = this.props; 15 | const { isHover = false } = this.state; 16 | const resultWidth = Math.floor(columnWidth); 17 | 18 | return ( 19 | { onIconClick(icon); }} 21 | onKeyDown={event => { event.keyCode === 13 && onIconClick(icon); }} 22 | role="button" 23 | onMouseOver={ this.hoverToggle.bind(this, 'isHover', true)} 24 | onMouseLeave={ this.hoverToggle.bind(this, 'isHover', false)} 25 | tabIndex={index} 26 | > 27 | 28 | 29 | {isShowAddTagBtn && 30 | { addTag(event, icon); }}>+} 31 | {isShowNotRelevantBtn && 32 | { setAsNotRelevant(event, icon); }}>x} 33 | 34 | 35 | 36 | 37 | onLoadImage(target, icon)} 42 | /> 43 | 44 | 45 | ); 46 | } 47 | } 48 | 49 | 50 | export default IconItem; -------------------------------------------------------------------------------- /projects/react-plugin/components/IconsTab/IconMonoColorSettings.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Aux } from '../hoc'; 3 | import { 4 | ButtonSearch, Label, MonoIconSettings, ColorIcon, ColorsWrapper, Opacity, SettingsIcon, SettingsIconWrapper, 5 | MonoActionBlock, ThemeColors 6 | } from '../../styledComponents'; 7 | import { COLORS } from '../../config'; 8 | import { guid } from '../../services/helper.service'; 9 | import { Spinner } from '../Spinner'; 10 | import { SketchPicker } from 'react-color'; 11 | import { I18n } from 'react-i18nify'; 12 | 13 | 14 | class IconMonoColorSettings extends Component { 15 | state = { 16 | activeColor: '#000000', 17 | isLoading: true, 18 | displayColorPicker: false 19 | } 20 | 21 | componentDidMount() { 22 | setTimeout(() => { 23 | if (this._buttonSearch && this._buttonSearch.focus) this._buttonSearch.focus(); 24 | }) 25 | } 26 | 27 | shouldComponentUpdate(nextProps, nextState) { 28 | return ( 29 | nextState.activeColor !== this.state.activeColor || 30 | nextState.isLoading !== this.state.isLoading || 31 | nextState.displayColorPicker !== this.state.displayColorPicker 32 | ); 33 | } 34 | 35 | setColor = (color) => { 36 | this.setState({ activeColor: color, isLoading: true }); 37 | } 38 | 39 | onApply = () => { 40 | const { upload } = this.props; 41 | const width = 300; 42 | 43 | upload({ src: this.getIconUrl(width) }); 44 | } 45 | 46 | getIconUrl = (width) => { 47 | const { activeColor } = this.state; 48 | const { activeIconSrc } = this.props; 49 | const colorQuery = `tpng.transparentwhite.level${activeColor.replace('#', '')}`; 50 | 51 | return `https://demoaws.cloudimg.io/width/${width}/${colorQuery}/${activeIconSrc}&v=${guid()}`; 52 | } 53 | 54 | onLoad = () => { 55 | this.setState({ isLoading: false }); 56 | } 57 | 58 | handleClick = () => { 59 | this.setState({ displayColorPicker: !this.state.displayColorPicker }) 60 | }; 61 | 62 | handleChange = (color) => { 63 | this.setState({ activeColor: color.hex, isLoading: true }); 64 | }; 65 | 66 | onOutsideClick = () => { 67 | this.setState({ displayColorPicker: false }); 68 | this.props.onClose(); 69 | } 70 | 71 | render() { 72 | const { themeColors } = this.props; 73 | const { isLoading, displayColorPicker, activeColor } = this.state; 74 | const popover = { 75 | position: 'absolute', 76 | zIndex: '4', 77 | top: 0, 78 | right: -230 79 | } 80 | 81 | return ( 82 | 83 | { this.onOutsideClick(); }}/> 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | {COLORS.map((color, index) => ( 94 | { this.setColor(color); }} 96 | onKeyDown={event => { event.keyCode === 13 && this.setColor(color); }} 97 | bgColor={color} 98 | key={`color-${index}`} 99 | tabIndex={10000} 100 | role="button" 101 | /> 102 | ))} 103 | { event.keyCode === 13 && this.handleClick(); }} 106 | bgColor="transparent" 107 | bgImage={'//api.filerobot.com/example/v3/get/a842b7b1-ae10-5e27-8838-fbc7796305fb'} 108 | tabIndex={10000} 109 | role="button" 110 | /> 111 | 112 | {themeColors && 113 | 114 | { this.setColor(themeColors.primary); }} bgColor={themeColors.primary}/> 115 | { this.setColor(themeColors.secondary); }} bgColor={themeColors.secondary}/> 116 | } 117 | this._buttonSearch = node} 119 | fullBr={'4px'} 120 | onClick={this.onApply} 121 | tabIndex={10001} 122 | >{I18n.t('upload.apply')} 123 | 124 | {displayColorPicker ?
125 | 126 |
: null} 127 |
128 | 129 |
130 | ) 131 | } 132 | } 133 | 134 | 135 | export default IconMonoColorSettings; -------------------------------------------------------------------------------- /projects/react-plugin/components/IconsTab/IconSidebar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { 3 | SidebarWrap, SideBar, ColorWrapper, ColorItem, ColorItemName, Label, CountTag 4 | } from '../../styledComponents/index'; 5 | import { I18n } from 'react-i18nify'; 6 | 7 | const tags = [ 8 | { tag: 'sf-social', label: 'Social', count: '23' }, 9 | { tag: 'arrows', label: 'Arrows', count: '5414' }, 10 | { tag: 'audio', label: 'Audio & Video', count: '2716' }, 11 | { tag: 'date', label: 'Date & Time', count: '1523' }, 12 | { tag: 'currency', label: 'Currency', count: '3531' }, 13 | { tag: 'business', label: 'Business', count: '8882' } 14 | ]; 15 | 16 | 17 | class IconSidebar extends Component { 18 | render() { 19 | const { toggleColorType, activeColorType } = this.props; 20 | 21 | return ( 22 | 23 | 24 | 25 | 26 | { toggleColorType('all'); }} 30 | onKeyDown={event => { event.keyCode === 13 && toggleColorType('all'); }} 31 | tabIndex={0} 32 | role="button" 33 | > 34 | {I18n.t('icons.all')} 35 | 36 | 37 | { toggleColorType('multi'); }} 41 | onKeyDown={event => { event.keyCode === 13 && toggleColorType('multi'); }} 42 | tabIndex={0} 43 | role="button" 44 | > 45 | {I18n.t('icons.multi_color')} 46 | 47 | 48 | { toggleColorType('mono'); }} 52 | onKeyDown={event => { event.keyCode === 13 && toggleColorType('mono'); }} 53 | tabIndex={0} 54 | role="button" 55 | > 56 | {I18n.t('icons.mono_color')} 57 | 58 | 59 | 60 | 61 | {tags && tags 62 | .sort((a, b) => a.tag > b.tag ? 1 : -1) 63 | .map(tag => this.renderTag(tag)) 64 | } 65 | 66 | 67 | ); 68 | } 69 | 70 | renderTag = ({ tag, label, count }) => { 71 | const { activePresetTag, onActivatePresetTag } = this.props; 72 | 73 | return ( 74 | { onActivatePresetTag(tag); }} 78 | onKeyDown={event => { event.keyCode === 13 && onActivatePresetTag(tag); }} 79 | tabIndex={0} 80 | role="button" 81 | > 82 | {label || tag.replace(/_/g, ' ').trim()} 83 | ({count}) 84 | 85 | ) 86 | } 87 | } 88 | 89 | export default IconSidebar; -------------------------------------------------------------------------------- /projects/react-plugin/components/IconsTab/IconTags.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { TagsWrapper, Tag, CloseIcon } from '../../styledComponents'; 3 | 4 | 5 | const IconTags = (props) => { 6 | const { tagsList, searchPhrase, activeTags, toggleTag } = props; 7 | 8 | if (!(tagsList.length > 0)) return null; 9 | 10 | return ( 11 | 12 | {tagsList.filter(item => item.tag && !item.tag.includes('sf-')).map(item => ( 13 | { toggleTag(item.tag) }} 18 | > 19 | {item.tag} 20 | 21 | 22 | ))} 23 | 24 | ); 25 | } 26 | 27 | export default IconTags; -------------------------------------------------------------------------------- /projects/react-plugin/components/IconsTab/SearchBar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { isEnterClick } from '../../utils' 3 | import { 4 | SearchGroup, InputSearch, ButtonSearch, SearchWrapper, SearchTitle, AmountIcons 5 | } from '../../styledComponents'; 6 | import { I18n } from 'react-i18nify'; 7 | 8 | 9 | class SearchBar extends Component { 10 | componentDidMount() { 11 | setTimeout(() => { 12 | if (this.searchField && this.searchField.focus) this.searchField.focus(); 13 | }); 14 | } 15 | 16 | UNSAFE_componentWillReceiveProps(nextProps) { 17 | if (!nextProps.isLoading && (nextProps.isLoading !== this.props.isLoading) && 18 | this.searchField && this.searchField.focus) 19 | this.searchField.focus(); 20 | } 21 | 22 | render() { 23 | const { 24 | items, isSearching, searchPhrase, onSearch, onChangeSearchPhrase, title, count = 0, presetImagesCount = 0 25 | } = this.props; 26 | const isEmptyIcons = (!items || !items.length); 27 | 28 | return ( 29 | 30 | {title} 31 | 32 | this.searchField = node} 35 | autoFocus={true} 36 | value={searchPhrase} 37 | onChange={onChangeSearchPhrase} 38 | onKeyDown={ev => { isEnterClick(ev) && onSearch() }} 39 | /> 40 | { onSearch(); }}>{I18n.t('upload.search')} 41 | 42 | 43 | 44 | {I18n.t('upload.found')}: {presetImagesCount || count ? presetImagesCount || count : ''} 45 | 46 | 47 | ); 48 | } 49 | } 50 | 51 | export default SearchBar; -------------------------------------------------------------------------------- /projects/react-plugin/components/ImagesTab/ImageBox.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Img, ImageWrapper } from '../../styledComponents'; 3 | import * as ImageGridService from '../../services/imageGrid.service'; 4 | 5 | 6 | export default ({ props: { style, columnWidth, item, index }, upload, onKeyDown, cloudimageToken}) => ( 7 | { upload(item); }} 10 | tabIndex={index} 11 | onKeyDown={(event) => { onKeyDown(event, item); }} 12 | > 13 | 17 | 18 | ) -------------------------------------------------------------------------------- /projects/react-plugin/components/ImagesTab/ImagesColorPicker.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { I18n } from 'react-i18nify'; 3 | import { SketchPicker } from 'react-color'; 4 | import { SketchPickerWrapper, SketchPickerOverlay, ApplyColorBtn } from '../../styledComponents'; 5 | 6 | 7 | export default ({ colorFilter, handleClose, handleChange }) => { 8 | 9 | return ( 10 | 11 | 12 | 13 | {I18n.t('upload.apply')} 19 | 20 | ) 21 | } -------------------------------------------------------------------------------- /projects/react-plugin/components/ImagesTab/ImagesSidebar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { I18n } from 'react-i18nify'; 3 | import { Spinner } from '../Spinner'; 4 | import { 5 | SidebarWrap, ColorItem, ColorItemName, SideBar, AddColorBtn, Label, ColorFilterItem, CountTag 6 | } from '../../styledComponents'; 7 | 8 | 9 | export default (props) => { 10 | const { 11 | activePresetTag, activeColorFilters, tags, backgrounds, onChangeColorFilter, onRemoveColorFilter, addColorFilter, 12 | onActivatePresetTag 13 | } = props; 14 | 15 | return ( 16 | 17 | 18 | 19 | 20 |
21 | {activeColorFilters.map((colorFilter, index) => ( 22 | 29 | ))} 30 |
31 | 32 |
33 | { event.keyCode === 13 && addColorFilter(); }} 36 | tabIndex={0} 37 | role="button" 38 | >+ {I18n.t('images.add_color')} 39 |
40 | 41 | 42 | 43 | {tags.length && 44 | { onActivatePresetTag('backgrounds', 25); }} 48 | tabIndex={0} 49 | role="button" 50 | > 51 | {I18n.t('images.backgrounds')} 52 | (25+) 53 | } 54 | {tags.slice(0, 20).map(({ tag, label, count } = {}) => ( 55 | { onActivatePresetTag(tag, count); }} 59 | onKeyDown={event => { event.keyCode === 13 && onActivatePresetTag(tag); }} 60 | tabIndex={0} 61 | role="button" 62 | > 63 | 64 | {label || tag.replace(/_/g, ' ').trim()} 65 | 66 | 67 | ({count}) 68 | 69 | 70 | ))} 71 | {!tags.length ? : null} 72 |
73 |
74 | ) 75 | } -------------------------------------------------------------------------------- /projects/react-plugin/components/Modal.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | import { createPortal } from 'react-dom'; 3 | import styled from 'styled-components'; 4 | import { CloseBtn } from './CloseBtn'; 5 | import { variables } from '../styledComponents/styleUtils'; 6 | 7 | 8 | const ModalOverlay = styled.div` 9 | top: 0; 10 | left: 0; 11 | width: 100%; 12 | height: 100%; 13 | overflow: hidden; 14 | position: fixed; 15 | background: ${props => props.theme.overlay || variables.colors.background.base}; 16 | opacity: .4; 17 | z-index: 999999992; 18 | `; 19 | 20 | export const ModalHeader = styled.div` 21 | display: -webkit-box; 22 | display: -webkit-flex; 23 | display: -ms-flexbox; 24 | display: flex; 25 | -webkit-box-align: center; 26 | -webkit-align-items: center; 27 | -ms-flex-align: center; 28 | align-items: center; 29 | -webkit-box-pack: justify; 30 | -webkit-justify-content: space-between; 31 | -ms-flex-pack: justify; 32 | justify-content: space-between; 33 | padding: 15px; 34 | border-bottom: 1px solid #eceeef; 35 | `; 36 | 37 | export const ModalFooter = styled.div` 38 | display: -webkit-box; 39 | display: -webkit-flex; 40 | display: -ms-flexbox; 41 | display: flex; 42 | -webkit-box-align: center; 43 | -webkit-align-items: center; 44 | -ms-flex-align: center; 45 | align-items: center; 46 | -webkit-box-pack: end; 47 | -webkit-justify-content: flex-end; 48 | -ms-flex-pack: end; 49 | justify-content: flex-end; 50 | padding: 15px; 51 | border-top: 1px solid #eceeef; 52 | 53 | &>:not(:last-child) { 54 | margin-right: .25rem; 55 | } 56 | `; 57 | 58 | const ModalContent = styled.div` 59 | position: relative; 60 | display: -webkit-box; 61 | display: -webkit-flex; 62 | display: -ms-flexbox; 63 | display: flex; 64 | -webkit-box-orient: vertical; 65 | -webkit-box-direction: normal; 66 | -webkit-flex-direction: column; 67 | -ms-flex-direction: column; 68 | flex-direction: column; 69 | -webkit-background-clip: padding-box; 70 | background-clip: padding-box; 71 | border: ${props => props.noBorder ? 0 : '1px'} solid ${props => props.noBorder ? 'transparent' : variables.colors.border.base || '#B0B0B0'}; 72 | border-radius: ${props => props.noBorder ? 0 : variables.radii[3]}; 73 | overflow: hidden; 74 | outline: 0; 75 | height: ${props => props.h || props.height || 'auto'}; 76 | background: ${variables.colors.background.base || '#fff'}; 77 | color: ${variables.colors.text.base || '#3d3d3d'}; 78 | 79 | ${ModalHeader} { 80 | ${props => (props.tac || props.textAlignCenter) && ` 81 | justify-content: center; 82 | `} 83 | } 84 | 85 | ${ModalFooter} { 86 | ${props => (props.tac || props.textAlignCenter) && ` 87 | justify-content: center; 88 | `} 89 | } 90 | `; 91 | 92 | const ModalFullScreen = styled.div` 93 | azimuth: center; 94 | border-collapse: separate; 95 | border-spacing: 0; 96 | caption-side: top; 97 | cursor: auto; 98 | direction: ltr; 99 | elevation: level; 100 | empty-cells: show; 101 | font-size: medium; 102 | font-style: medium; 103 | font-variant: medium; 104 | font-weight: medium; 105 | letter-spacing: normal; 106 | line-height: medium; 107 | list-style-image: none; 108 | list-style-position: outside; 109 | list-style-type: disc; 110 | list-style: disc outside none; 111 | orphans: 2; 112 | pitch-range: 50; 113 | pitch: medium; 114 | quotes: '"' '"'; 115 | richness: 50; 116 | speak-header: once; 117 | speak-numeral: continuous; 118 | speak-punctuation: none; 119 | speak: normal; 120 | speech-rate: medium; 121 | stress: 50; 122 | text-align: left; 123 | text-indent: 0; 124 | text-transform: none; 125 | visibility: visible; 126 | voice-family: none; 127 | volume: medium; 128 | white-space: normal; 129 | widows: 2; 130 | word-spacing: 0; 131 | position: fixed; 132 | padding: ${props => props.p || props.padding || '0'}; 133 | top: 5%; 134 | left: 15%; 135 | right: 15%; 136 | bottom: 5%; 137 | color: ${props => variables.colors.text.base || '#3d3d3d'}; 138 | overflow: hidden; 139 | z-index: ${props => props.zIndex || '999999995'}; 140 | display: block; 141 | animation: scaleflexFadeInAnimation 350ms ease-in-out both; 142 | font-family: 'Roboto', 'Arial', sans-serif; 143 | 144 | ${props => props.isTooSmall ? getSmallModalStyle() : ''}; 145 | 146 | @keyframes scaleflexFadeInAnimation { 147 | from {opacity: 0;} 148 | to {opacity: 1;} 149 | } 150 | 151 | @media (max-width: 1000px) { 152 | top: 20px; 153 | left: 20px; 154 | bottom: 20px; 155 | right: 20px; 156 | } 157 | `; 158 | 159 | function getSmallModalStyle() { 160 | return ` 161 | top: 15px !important; 162 | left: 15px !important; 163 | right: 15px !important; 164 | bottom: 15px !important; 165 | ` 166 | } 167 | 168 | function getFullScreenSize(size) { 169 | switch(size) { 170 | case 'xs': 171 | return '35%'; 172 | case 'sm': 173 | return '30%'; 174 | case 'md': 175 | return '15%'; 176 | case 'lg': 177 | return '10%'; 178 | case 'xl': 179 | default: 180 | return '20px'; 181 | } 182 | } 183 | 184 | export class Modal extends Component { 185 | constructor() { 186 | super(); 187 | 188 | this.root = document.createElement('div'); 189 | this.root.classList.add('filerobot-uploader-root'); 190 | 191 | document.body.appendChild(this.root); 192 | } 193 | 194 | componentDidMount() { 195 | const { closeOnOutsideClick = true } = this.props; 196 | 197 | if (closeOnOutsideClick) { 198 | document.addEventListener('keydown', this.handleOutsideMouseClick); 199 | } 200 | } 201 | 202 | componentWillUnmount() { 203 | const { closeOnOutsideClick = true } = this.props; 204 | document.body.removeChild(this.root); 205 | 206 | if (closeOnOutsideClick) { 207 | document.removeEventListener('keydown', this.handleOutsideMouseClick); 208 | } 209 | } 210 | //todo add keycode to config 211 | handleOutsideMouseClick = event => { 212 | let { onClose = () => {} } = this.props; 213 | 214 | if (event.keyCode === 27) { 215 | event.stopPropagation(); 216 | onClose(); 217 | } 218 | } 219 | 220 | render() { 221 | let { onClose = () => {}, isHideCloseBtn, ...otherProps } = this.props; 222 | 223 | return createPortal( 224 | 225 | 226 | this._modal = node}> 227 | {!isHideCloseBtn && } 228 | 229 | {this.props.children} 230 | 231 | 232 | , this.root 233 | ) 234 | } 235 | } -------------------------------------------------------------------------------- /projects/react-plugin/components/ProgressCircle.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import styled from 'styled-components'; 3 | import { Circle } from 'rc-progress'; 4 | 5 | export const PROGRESS_COLORS = { 6 | DEFAULT: 'rgb(81,185,244)', 7 | SUCCESS: 'RGB(133,192,83)' 8 | }; 9 | 10 | export const ProgressCircle = ({ status, color }) => ( 11 | 12 | 13 | 14 | 22 | {status} % 23 | 24 | 25 | ); 26 | 27 | 28 | const ProgressCircleWrapper = styled.div` 29 | svg { 30 | position: absolute; 31 | top: 50%; 32 | left: 50%; 33 | margin-left: -100px; 34 | margin-top: -100px; 35 | width: 200px; 36 | height: 200px; 37 | z-index: 10000; 38 | } 39 | `; 40 | 41 | const Status = styled.div` 42 | position: absolute; 43 | margin: auto; 44 | top: 50%; 45 | left: 0; 46 | right: 0; 47 | margin-top: -10px; 48 | z-index: 11111; 49 | color: #fff; 50 | text-align: center; 51 | `; 52 | 53 | const Overlay = styled.div` 54 | top: 0; 55 | left: 0; 56 | width: 100%; 57 | height: 100%; 58 | overflow: hidden; 59 | position: absolute; 60 | background: ${props => props.overlay ? (props.color ? 'rgba(125, 125, 125, 0.84) ' : 'rgba(10,10,10,0.26)') : 'transparent'} !important; 61 | z-index: 1042; 62 | `; -------------------------------------------------------------------------------- /projects/react-plugin/components/TaggingTab/AutosuggestionInput.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Autosuggest from 'react-autosuggest'; 3 | import { Autosuggestion } from './TaggingTab.styled'; 4 | 5 | 6 | class AutosuggestionInput extends Component { 7 | state = { 8 | cursor: 0, 9 | result: [] 10 | }; 11 | 12 | componentDidMount() { 13 | this.input.click(); 14 | } 15 | 16 | storeInputReference = autosuggest => { 17 | if (autosuggest !== null) { 18 | this.input = autosuggest.input 19 | } 20 | }; 21 | 22 | handleOnChange = (e, { newValue, method }) => { 23 | if (method === 'enter') e.preventDefault(); 24 | if (method === 'up' || method === 'down') this.handleKeyDown(e); 25 | else this.props.onChange(e); 26 | }; 27 | 28 | handleKeyDown = (e) => { 29 | const { cursor, result } = this.state; 30 | if (e.keyCode === 38 && cursor > 0) { 31 | this.setState( prevState => ({ 32 | cursor: prevState.cursor - 1 33 | })) 34 | } else if (e.keyCode === 40 && cursor < result.length - 1) { 35 | this.setState( prevState => ({ 36 | cursor: prevState.cursor + 1 37 | })) 38 | } 39 | }; 40 | 41 | render() { 42 | const { value = '', suggestionList = [], addTag, ...rest } = this.props; 43 | const inputValue = (value && value.trim().toLowerCase()) || ''; 44 | const inputLength = inputValue.length; 45 | let suggestions = suggestionList.filter((tag) => tag.name.toLowerCase().slice(0, inputLength) === inputValue); 46 | 47 | return ( 48 | 49 | value && value.trim().length > 0} 53 | getSuggestionValue={(suggestion) => suggestion.name} 54 | renderSuggestion={(suggestion) => {suggestion.name}} 55 | inputProps={{ ...rest, value, onChange: this.handleOnChange, autoFocus: true }} 56 | onSuggestionSelected={(e, { suggestion }) => { addTag(suggestion.name); }} 57 | onSuggestionsClearRequested={() => {}} 58 | onSuggestionsFetchRequested={() => {}} 59 | /> 60 | 61 | ); 62 | } 63 | }; 64 | 65 | export default AutosuggestionInput; -------------------------------------------------------------------------------- /projects/react-plugin/components/TaggingTab/CropsBox.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | import { CropsBoxWrapper, Group, GroupLabel, Overlay } from './TaggingTab.styled'; 3 | import { CloseBtn } from '../CloseBtn'; 4 | import smartcrop from 'smartcrop'; 5 | import { I18n } from 'react-i18nify'; 6 | import * as API from '../../services/api.service'; 7 | import { encodePermalink } from '../../utils'; 8 | import md5 from '../../utils/md5'; 9 | import { getPubliclink } from '../../utils/adjustAPI.utils' 10 | 11 | 12 | const OPTIONS_1x1 = [ 13 | { height: 125, width: 125, debug: true, minScale: 1 }, 14 | { height: 125, width: 125, debug: true, minScale: 0.88 }, 15 | { height: 125, width: 125, debug: true, minScale: 0.77 }, 16 | { height: 125, width: 125, debug: true, minScale: 0.65 } 17 | ]; 18 | const OPTIONS_5x4 = [ 19 | { height: 125, width: 156.25, debug: true, minScale: 1 }, 20 | { height: 125, width: 156.25, debug: true, minScale: 0.82 }, 21 | { height: 125, width: 156.25, debug: true, minScale: 0.65 }, 22 | ]; 23 | const OPTIONS_4x3 = [ 24 | { height: 125, width: 166.7, debug: true, minScale: 1 }, 25 | { height: 125, width: 166.7, debug: true, minScale: 0.82 }, 26 | { height: 125, width: 166.7, debug: true, minScale: 0.65 }, 27 | ]; 28 | const OPTIONS_3x2 = [ 29 | { height: 125, width: 187.5, debug: true, minScale: 1 }, 30 | { height: 125, width: 187.5, debug: true, minScale: 0.82 }, 31 | { height: 125, width: 187.5, debug: true, minScale: 0.65 } 32 | ]; 33 | const OPTIONS_16x9 = [ 34 | { height: 125, width: 222, debug: true, minScale: 1 }, 35 | { height: 125, width: 222, debug: true, minScale: 0.82 }, 36 | { height: 125, width: 222, debug: true, minScale: 0.65 } 37 | ]; 38 | 39 | class CropsBox extends Component { 40 | componentDidMount() { 41 | this.uploadSuccess(this.props.src); 42 | } 43 | 44 | processAutoCrop = (group, groupName, img) => { 45 | group.forEach((options, index) => { 46 | smartcrop.crop(img, options).then(result => { 47 | const crop = result.topCrop; 48 | const canvas = this.refs[`${groupName}${index}`]; 49 | const ctx = canvas.getContext('2d'); 50 | 51 | this[groupName] = this[groupName] || []; 52 | this[groupName][index] = crop; 53 | canvas.width = options.width; 54 | canvas.height = options.height; 55 | ctx.drawImage( 56 | img, 57 | crop.x, 58 | crop.y, 59 | crop.width, 60 | crop.height, 61 | 0, 62 | 0, 63 | canvas.width, 64 | canvas.height 65 | ); 66 | }); 67 | }); 68 | } 69 | 70 | uploadSuccess = (src) => { 71 | const img = new Image(); 72 | img.crossOrigin = ''; 73 | img.src = this.props.src; 74 | 75 | img.onload = () => { 76 | this.processAutoCrop(OPTIONS_1x1, 'canvas_1x1_', img); 77 | this.processAutoCrop(OPTIONS_5x4, 'canvas_5x4_', img); 78 | this.processAutoCrop(OPTIONS_4x3, 'canvas_4x3_', img); 79 | this.processAutoCrop(OPTIONS_3x2, 'canvas_3x2_', img); 80 | this.processAutoCrop(OPTIONS_16x9, 'canvas_16x9_', img); 81 | } 82 | } 83 | 84 | uploadFromWeb = (url = null) => { 85 | const { config } = this.props.appState; 86 | const files = [url]; 87 | const dataType = 'application/json'; 88 | 89 | this.props.setSpinner(true); 90 | 91 | API.uploadFiles({ 92 | files, 93 | config, 94 | data_type: dataType, 95 | showAlert: this.props.showAlert 96 | }) 97 | .then(([files = [], isDuplicate, isReplacingData]) => { 98 | if (isReplacingData || isDuplicate) { 99 | this.props.showAlert('', I18n.t('upload.file_already_exists'), 'info'); 100 | } 101 | 102 | const file = files[0] || {}; 103 | this.uploadSuccess(encodePermalink(getPubliclink(file))); 104 | 105 | this.props.saveUploadedFiles(files); 106 | this.props.setSpinner(false); 107 | this.props.toggleCropMenu(); 108 | }) 109 | .catch((error) => { 110 | this.props.setSpinner(false); 111 | this.props.showAlert('', error.msg, 'error'); 112 | }) 113 | }; 114 | 115 | onCanvasClick = (groupName, index) => { 116 | const url = this.generateCloudimageURL(this[groupName][index]); 117 | 118 | this.uploadFromWeb(`${url}${this.props.src}`); 119 | } 120 | 121 | generateCloudimageURL = (cropParams = {}) => { 122 | const { config } = this.props.appState; 123 | const cloudUrl = config.cloudimageToken + '.cloudimg.io' + '/'; 124 | 125 | let { width, height, x, y } = cropParams; 126 | 127 | const cropQ = x + ',' + y + ',' + (x + width) + ',' + (y + height) + '-' + width + 'x' + height; 128 | 129 | return `https://${cloudUrl}crop_px/${cropQ}/n/`; 130 | } 131 | 132 | renderCanvas = (index, groupName) => { 133 | return ( 134 | { this.onCanvasClick(groupName, index); }} 136 | key={index} 137 | ref={`${groupName}${index}`} 138 | /> 139 | ) 140 | } 141 | 142 | render() { 143 | const { show } = this.props; 144 | 145 | return ( 146 | 147 | {show && } 148 | 149 | 150 | 151 | 1x1 152 | 153 | {OPTIONS_1x1.map((option, index) => this.renderCanvas(index, 'canvas_1x1_'))} 154 | 155 | 5x4 156 | 157 | {OPTIONS_5x4.map((option, index) => this.renderCanvas(index, 'canvas_5x4_'))} 158 | 159 | 4x3 160 | 161 | {OPTIONS_4x3.map((option, index) => this.renderCanvas(index, 'canvas_4x3_'))} 162 | 163 | 3x2 164 | 165 | {OPTIONS_3x2.map((option, index) => this.renderCanvas(index, 'canvas_3x2_'))} 166 | 167 | 16x9 168 | 169 | {OPTIONS_16x9.map((option, index) => this.renderCanvas(index, 'canvas_16x9_'))} 170 | 171 | 172 | 173 | ) 174 | } 175 | } 176 | 177 | export default CropsBox; -------------------------------------------------------------------------------- /projects/react-plugin/components/UploadImagesTab/UserUploaderTab.styled.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { ButtonSearch, InputSearch } from '../../styledComponents'; 3 | 4 | 5 | export const Container = styled.div` 6 | width: 100%; 7 | min-height: 240px; 8 | height: 100%; 9 | text-align: center; 10 | border: ${p => p.noBorder ? 'none' : `2px dashed ${p.theme.border}`}; 11 | background: ${p => p.isDragOver ? 'rgba(210, 253, 207, 0.5)' : p.theme.mainBackground}; 12 | align-items: center; 13 | justify-content: center; 14 | display: flex; 15 | color: #5D636B; 16 | box-sizing: border-box; 17 | `; 18 | 19 | export const Nav = styled.nav` 20 | line-height: 1; 21 | height: 100%; 22 | display: flex; 23 | background-color: ${p => p.theme.navBackground}; 24 | `; 25 | 26 | export const ItemName = styled.div` 27 | padding: 10px; 28 | padding-top: ${p => p.pt ? p.pt : 10}px; 29 | padding-bottom: ${p => p.pb ? p.pb : 10}px; 30 | font-weight: 200; 31 | font-size: 12px; 32 | color: ${p => p.theme.text}; 33 | `; 34 | 35 | export const BrowseButton = styled.button` 36 | height: 34px; 37 | padding: 6px 12px; 38 | line-height: 1.42857; 39 | color: ${p => p.theme.text || '#5D636B'}; 40 | background-color: transparent; 41 | background-repeat: repeat-x; 42 | text-shadow: rgb(255, 255, 255) 0px 1px; 43 | border-width: 1px; 44 | border-style: solid; 45 | border-color: ${p => p.theme.text || 'solid'}; 46 | border-radius: 4px; 47 | cursor: pointer; 48 | font-weight: 400; 49 | outline: 0; 50 | font-size: 12px; 51 | margin: auto; 52 | 53 | :hover { 54 | background-color: ${p => p.theme.text || '#6D737B'}; 55 | color: ${p => p.theme.text === '#fff' || p.theme.text === '#ffffff' ? '#000' : '#fff'}; 56 | } 57 | 58 | :focus { 59 | outline-color: ${p => p.theme.text || 'rgb(77, 144, 254)'}; 60 | outline-offset: -2px; 61 | outline-style: auto; 62 | outline-width: 5px; 63 | } 64 | `; 65 | 66 | export const PreviewFiles = styled.div` 67 | width: ${p => p.isAutoProcess ? '100%' : 'calc(100% - 350px)'}; 68 | display: flex; 69 | vertical-align: middle; 70 | justify-content: flex-start; 71 | flex-wrap: wrap; 72 | align-items: center; 73 | max-height: 100%; 74 | height: 100%; 75 | overflow: hidden; 76 | overflow-y: auto; 77 | background: #fff; 78 | `; 79 | 80 | export const PreviewFileWrapper = styled.div` 81 | width: ${p => p.oneImage ? '100%' : p.isAutoProcess ? 'calc(25% - 20px)' : 'calc(50% - 20px)'}; 82 | padding: 10px 10px 25px 10px; 83 | margin: 10px; 84 | background: #f5f5f5; 85 | border-radius: 4px; 86 | position: relative; 87 | height: ${p => p.h ? parseInt(p.h, 10) * (p.oneImage ? 2 : 1) : 285 * (p.oneImage ? 2 : 1)}px; 88 | display: flex; 89 | justify-content: center; 90 | align-items: center; 91 | 92 | img, canvas { 93 | width: auto !important; 94 | max-height: ${p => p.h ? (parseInt(p.h, 10) * (p.oneImage ? 2 : 1) - 35 ) : 250 * (p.oneImage ? 2 : 1) }px !important; 95 | max-width: 100% !important; 96 | } 97 | 98 | div { 99 | text-align: center; 100 | white-space: nowrap; 101 | text-overflow: ellipsis; 102 | overflow: hidden; 103 | position: absolute; 104 | width: 100%; 105 | height: 25px; 106 | bottom: 0; 107 | left: 0; 108 | } 109 | `; 110 | 111 | export const PreviewLabel = styled.label` 112 | display: inline-block; 113 | width: 100px; 114 | text-align: left; 115 | margin-bottom: 10px; 116 | `; 117 | 118 | export const ButtonAction = styled(ButtonSearch)` 119 | display: inline-block; 120 | margin: auto; 121 | margin-top: 20px; 122 | font-size: 16px; 123 | padding: 10px; 124 | height: auto; 125 | border-radius: 4px; 126 | line-height: 1.42857; 127 | border: 1px solid transparent; 128 | width: 145px; 129 | `; 130 | 131 | export const ButtonApplyTransforms = styled(BrowseButton)` 132 | display: inline-block; 133 | margin: auto; 134 | margin-top: 20px; 135 | font-size: 16px; 136 | padding: 10px; 137 | height: auto; 138 | border-radius: 4px; 139 | width: 145px; 140 | `; 141 | 142 | export const TransformationList = styled.div` 143 | position: relative; 144 | width: 200px; 145 | display: inline-block; 146 | text-align: left; 147 | margin-bottom: 10px; 148 | 149 | .options { 150 | position: absolute; 151 | width: 100%; 152 | display: none; 153 | background: #ffffff; 154 | border-radius: 0 0 4px 4px; 155 | overflow: hidden; 156 | box-shadow: rgba(0,0,0,0.15) 0 2px 7px; 157 | 158 | &.active { 159 | display: block; 160 | } 161 | } 162 | 163 | .item { 164 | font-size: 16px; 165 | text-align: left; 166 | cursor: pointer; 167 | padding: 4px 8px; 168 | 169 | :hover, &.active { 170 | color: #fff; 171 | background: #00707C; 172 | } 173 | 174 | :hover.disabled, &.disabled { 175 | background: #C9C9C9; 176 | color: #5D5D5D; 177 | cursor: wait; 178 | } 179 | } 180 | `; 181 | 182 | export const ActiveOperation = styled(InputSearch)` 183 | position: relative; 184 | width: 100%; 185 | border-radius: 4px; 186 | background-image: 187 | linear-gradient(45deg, transparent 50%, gray 50%), 188 | linear-gradient(135deg, gray 50%, transparent 50%), 189 | linear-gradient(to right, #ccc, #ccc); 190 | background-position: 191 | calc(100% - 20px) calc(1em + 2px), 192 | calc(100% - 15px) calc(1em + 2px), 193 | calc(100% - 2.5em) 0.5em; 194 | background-size: 195 | 5px 5px, 196 | 5px 5px, 197 | 1px 1.5em; 198 | background-repeat: no-repeat; 199 | cursor: pointer; 200 | 201 | &:focus { 202 | background-image: 203 | linear-gradient(45deg, green 50%, transparent 50%), 204 | linear-gradient(135deg, transparent 50%, green 50%), 205 | linear-gradient(to right, #ccc, #ccc); 206 | background-position: 207 | calc(100% - 15px) 1em, 208 | calc(100% - 20px) 1em, 209 | calc(100% - 2.5em) 0.5em; 210 | background-size: 211 | 5px 5px, 212 | 5px 5px, 213 | 1px 1.5em; 214 | background-repeat: no-repeat; 215 | border-color: green; 216 | outline: 0; 217 | } 218 | 219 | &:-moz-focusring { 220 | color: transparent; 221 | text-shadow: 0 0 0 #000; 222 | } 223 | 224 | &.active { 225 | border-radius: 4px 4px 0 0 !important; 226 | } 227 | `; 228 | 229 | export const OperationInput = styled(InputSearch)` 230 | width: 200px; 231 | display: inline-block; 232 | margin-bottom: 10px; 233 | `; -------------------------------------------------------------------------------- /projects/react-plugin/components/UploadImagesTab/UserUploaderTab.utils.js: -------------------------------------------------------------------------------- 1 | import { I18n } from 'react-i18nify'; 2 | 3 | 4 | export const validateExtensions = (files, configExtensions, showAlert, isFromWeb) => { 5 | if (!configExtensions.length) return true; 6 | const { isValid, invalidExtensions = '' } = checkIsValidExtension(files, configExtensions, isFromWeb); 7 | 8 | if (isValid) { 9 | return true; 10 | } else if (!isValid && files) { 11 | const alert = 12 | `${I18n.t('upload.invalid_file_extension')}${invalidExtensions ? `: ${invalidExtensions}. ` + 13 | `${I18n.t('upload.accepted_file_types')}: ${processExtensions(configExtensions).join(', ')}.` : ''}`; 14 | showAlert('', alert, 'warning'); 15 | return false; 16 | } else return false; 17 | }; 18 | 19 | const checkIsValidExtension = (files, userExtensions, isFromWeb = false) => { 20 | let nextUserExtensions = processExtensions(userExtensions); 21 | let invalidExtensions = []; 22 | 23 | const filesExtensions = isFromWeb ? 24 | [files.split('.').pop()] 25 | : 26 | [...files].map(file => file.type.split('/').pop()); 27 | const isValid = filesExtensions.every(ext => nextUserExtensions.some(userExt => ext.includes(userExt))); 28 | 29 | filesExtensions.forEach(file => { 30 | if (!nextUserExtensions.includes(file)) invalidExtensions.push(file); 31 | }); 32 | 33 | return { isValid, invalidExtensions: invalidExtensions.join(", ") }; 34 | }; 35 | 36 | export const processExtensions = userExtensions => { 37 | let nextUserExtensions = []; 38 | if (userExtensions.includes('jpeg')) nextUserExtensions = [...userExtensions, 'jpg']; 39 | else if (userExtensions.includes('jpg')) nextUserExtensions = [...userExtensions, 'jpeg']; 40 | 41 | return nextUserExtensions; 42 | }; -------------------------------------------------------------------------------- /projects/react-plugin/components/UploadedImagesTab/SortDropdown.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import SortTick from './SortTick'; 4 | import { I18n } from 'react-i18nify'; 5 | import { SortButton } from '../../styledComponents'; 6 | 7 | 8 | const SORT_COLS = [ 9 | ['name', 'Name'], 10 | //['size', 'Size'], 11 | ['type', 'Type'], 12 | ['uploaded_at', 'Uploaded'], 13 | ['modified_at', 'Modified'], 14 | ]; 15 | 16 | class SortDropdown extends Component { 17 | state = { isDropdownOpened: false }; 18 | 19 | constructor(props) { 20 | super(props); 21 | this.dropdownContainerRef = React.createRef(); 22 | 23 | this.state = { 24 | sortCols: [...SORT_COLS] 25 | } 26 | } 27 | 28 | componentDidMount() { 29 | window.addEventListener('click', this.globalClickHandler); 30 | } 31 | 32 | componentWillUnmount() { 33 | window.removeEventListener('click', this.globalClickHandler); 34 | } 35 | 36 | globalClickHandler = ({ target }) => { 37 | const dropdownElem = (this.dropdownContainerRef || {}).current || null; 38 | const isOutsideClick = dropdownElem && dropdownElem !== target && !dropdownElem.contains(target); 39 | 40 | if (isOutsideClick) { 41 | this.closeDropdown(); 42 | } 43 | } 44 | 45 | btnClickHandler = ev => { 46 | ev.preventDefault(); 47 | this.setState(({ isDropdownOpened }) => ({ isDropdownOpened: !isDropdownOpened })); 48 | } 49 | 50 | closeDropdown = () => { 51 | this.setState({ isDropdownOpened: false }); 52 | } 53 | 54 | sort = type => { 55 | const { sortParams: { field, isUp } = {}, applySort } = this.props; 56 | 57 | if (field === type) { 58 | return applySort({ field, isUp: !isUp }); 59 | } 60 | 61 | applySort({ field: type, isUp: true }); 62 | } 63 | 64 | generateI18nKey = str => (str || '').toLowerCase().replace(/\s/gi, '_') 65 | 66 | render() { 67 | const { sortParams: { field, isUp } = {} } = this.props; 68 | const { isDropdownOpened, sortCols } = this.state; 69 | const activeField = field ? sortCols.find(([key]) => key === field) : null; 70 | const activeFieldName = (activeField || [])[1] || null; 71 | 72 | return ( 73 | 86 |
87 | {field && } 88 | 89 | 90 | {field 91 | ? 92 | I18n.t(`upload.${this.generateI18nKey(activeFieldName || field)}`) 93 | : 94 | I18n.t('upload.sort') 95 | } 96 | 97 |
98 | 99 | {isDropdownOpened && 100 |
    105 | {sortCols.map(([key, name]) => ( 106 |
  • 107 | 112 |
  • 113 | ))} 114 |
} 115 |
116 | ); 117 | } 118 | } 119 | 120 | export default SortDropdown; 121 | 122 | SortDropdown.propTypes = { 123 | applySort: PropTypes.func.isRequired, 124 | sortParams: PropTypes.object.isRequired, 125 | }; 126 | -------------------------------------------------------------------------------- /projects/react-plugin/components/UploadedImagesTab/SortTick.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export const SortTick = ({ active, isUp, color, flex }) => ( 5 | 6 | {active && } 7 | 8 | ); 9 | 10 | SortTick.defaultProps = { 11 | active: false, 12 | isUp: false 13 | }; 14 | 15 | SortTick.propTypes = { 16 | active: PropTypes.bool.isRequired, 17 | isUp: PropTypes.bool.isRequired 18 | }; 19 | 20 | export default SortTick; -------------------------------------------------------------------------------- /projects/react-plugin/components/UploadedImagesTab/folderManager/FolderItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Folder, FolderIcon } from './folderManager.styled'; 3 | 4 | 5 | export default class FolderItem extends Component { 6 | onClickFolder = (event) => { 7 | event.preventDefault(); 8 | event.stopPropagation(); 9 | 10 | this.props.changeFolder(this.props.folder.path); 11 | } 12 | 13 | render() { 14 | const { folder } = this.props; 15 | 16 | return ( 17 | 18 | 19 | 20 | {folder.name} 21 | 22 | ) 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /projects/react-plugin/components/UploadedImagesTab/folderManager/FolderManager.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | import { 3 | EmptyNote, 4 | Folder, 5 | FolderIcon, 6 | FolderManagerWrapper, 7 | FolderToggleWrapper, 8 | Overlay 9 | } from './folderManager.styled'; 10 | import FolderItem from './FolderItem'; 11 | import { I18n } from 'react-i18nify'; 12 | import BreadCrumbs from './bread-crumbs'; 13 | 14 | 15 | class FolderManager extends Component { 16 | constructor() { 17 | super(); 18 | 19 | this.state = { 20 | showFileManager: false 21 | }; 22 | } 23 | 24 | toggleSideMenu = () => { 25 | const showFileManager = !this.state.showFileManager; 26 | 27 | this.setState({ showFileManager }); 28 | } 29 | 30 | render() { 31 | const { showFileManager } = this.state; 32 | const { folders = [], path, rootDir, isLoading } = this.props; 33 | 34 | return ( 35 | 36 | 37 | 38 | 39 | 40 | {/**/} 41 | {/* {I18n.t('file_manager.change_folder')}*/} 42 | {/**/} 43 | 44 | 45 | 46 | {/**/} 47 | {/**/} 48 | {/* */} 49 | {/* {I18n.t('file_manager.media_library')}*/} 50 | {/* */} 51 | {/* */} 52 | {/**/} 53 | 54 | {path && (path !== rootDir) && !isLoading && 55 | 56 | 57 | {'../'} 58 | } 59 | 60 | {!isLoading && folders.map(folder => ( 61 | 66 | ))} 67 | 68 | {!isLoading && folders.length === 0 && 69 | {I18n.t('file_manager.no_subfolders')}} 70 | 71 | 72 | 73 | 74 | ); 75 | } 76 | } 77 | 78 | export default FolderManager; -------------------------------------------------------------------------------- /projects/react-plugin/components/UploadedImagesTab/folderManager/bread-crumbs.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | 5 | function BreadCrumbs({ path }) { 6 | 7 | return ( 8 | 9 | Root { 10 | (path || '').split('/') 11 | .filter(item => item) 12 | .map((item, index) => /{item}) 13 | } 14 | 15 | ) 16 | } 17 | 18 | 19 | const Wrapper = styled('div')` 20 | display: inline-block; 21 | vertical-align: middle; 22 | color: ${p => p.theme.text}; 23 | margin-left: 10px; 24 | 25 | span { 26 | margin: 0 2px; 27 | opacity: 0.7; 28 | 29 | :last-child { 30 | opacity: 1; 31 | font-weight: bold; 32 | } 33 | } 34 | `; 35 | 36 | 37 | export default BreadCrumbs; -------------------------------------------------------------------------------- /projects/react-plugin/components/UploadedImagesTab/folderManager/folderManager.styled.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { CloseBtn } from '../../CloseBtn'; 3 | 4 | 5 | export const FolderTitle = styled.div` 6 | display: inline-block; 7 | vertical-align: middle; 8 | color: ${p => p.theme.text || '#5D636B'}; 9 | margin-left: 10px; 10 | font-size: 14px; 11 | `; 12 | 13 | export const FolderIcon = styled.div` 14 | width: ${props => props.small ? '14px' : '17px'}; 15 | height: ${props => props.small ? '10px' : '12px'}; 16 | margin: 0 auto; 17 | position: relative; 18 | display: inline-block; 19 | vertical-align: middle; 20 | background-color: ${props => props.theme.buttonBackground || '#708090'}; 21 | border-radius: ${props => props.small ? '0 1px 1px 1px' : '0 2px 2px 2px'}; 22 | box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.39); 23 | margin-right: ${props => props.mr ? props.mr : 'auto'}; 24 | 25 | :before { 26 | content: ''; 27 | width: 50%; 28 | height: ${props => props.small ? '2px' : '3px'}; 29 | border-radius: ${props => props.small ? '0 1px 0 0' : '0 2px 0 0'}; 30 | background-color: ${props => props.theme.buttonBackground || '#708090'}; 31 | position: absolute; 32 | top: ${props => props.small ? '-2px' : '-3px'}; 33 | left: 0px; 34 | } 35 | `; 36 | 37 | export const FolderToggleWrapper = styled.div` 38 | padding: 5px 0; 39 | cursor: pointer; 40 | 41 | :hover ${FolderIcon} { 42 | background-color: ${p => p.theme.buttonBackground || '#00707C'}; 43 | 44 | :before { 45 | background-color: ${p => p.theme.buttonBackground || '#00707C'}; 46 | } 47 | } 48 | 49 | :hover ${FolderTitle} { 50 | color: ${p => p.theme.buttonBackground || '#00707C'}; 51 | } 52 | `; 53 | 54 | export const FolderManagerWrapper = styled.div` 55 | position: absolute; 56 | left: 0; 57 | bottom: 0; 58 | top: 80px; 59 | color: #5D636B; 60 | margin-left: ${props => props.showFileManager ? '0px' : '-200px'}; 61 | //visibility: ${props => props.showFileManager ? 'visible' : 'hidden'};; 62 | width: 200px; 63 | background: ${p => p.theme.mainBackground}; 64 | border-right: 1px solid rgb(221,221,221); 65 | border-top: 1px solid rgb(221,221,221); 66 | z-index: 1045; 67 | transition: 0.3s margin; 68 | overflow: hidden; 69 | overflow-y: auto; 70 | `; 71 | 72 | export const ManagerHeader = styled.div` 73 | height: 50px; 74 | color: #5D636B; 75 | padding: 0 10px; 76 | white-space: nowrap; 77 | border-bottom: 1px solid rgb(221,221,221); 78 | `; 79 | 80 | export const ManagerHeaderTitle = styled.div` 81 | line-height: 50px; 82 | font-size: 14px; 83 | `; 84 | 85 | export const CloseManagerBtn = styled(CloseBtn)` 86 | font-size: 18px; 87 | top: 16px; 88 | 89 | :hover { 90 | color: #7b8189; 91 | } 92 | `; 93 | 94 | export const Folder = styled.div` 95 | position: relative; 96 | padding: 5px 20px 5px 10px; 97 | cursor: pointer; 98 | font-size: 14px; 99 | 100 | :hover { 101 | background: rgba(0, 0, 0, 0.1); 102 | 103 | .remove-icon-folder { 104 | display: inline-block !important; 105 | } 106 | 107 | .settings-icon-folder { 108 | display: inline-block !important; 109 | } 110 | } 111 | `; 112 | 113 | Folder.Icon = styled.div.attrs(() => ({ className: 'ai-icon-folder' }))` 114 | display: inline-block; 115 | vertical-align: middle; 116 | width: 20px; 117 | height: 20px; 118 | margin: 0 5px 0 0; 119 | color: #31b0d5; 120 | `; 121 | 122 | Folder.Name = styled.div` 123 | display: inline-block; 124 | vertical-align: middle; 125 | text-overflow: ellipsis; 126 | overflow: hidden; 127 | white-space: nowrap; 128 | width: ${props => props.isSelect ? 'calc(100% - 40px)' : 'calc(100% - 25px)'}; 129 | `; 130 | 131 | Folder.EditName = styled.input` 132 | display: inline-block; 133 | vertical-align: middle; 134 | line-height: 1 !important; 135 | width: ${props => props.isSelect ? 'calc(100% - 55px)' : 'calc(100% - 30px)'} !important; 136 | `; 137 | 138 | export const Overlay = styled.div` 139 | display: ${props => props.show ? 'block' : 'none'}; 140 | position: absolute; 141 | top: 0; 142 | bottom: 0; 143 | left: 0; 144 | right: 0; 145 | z-index: 199; 146 | `; 147 | 148 | export const EmptyNote = styled('div')` 149 | padding: 20px 5px; 150 | opacity: 0.7; 151 | text-align: center; 152 | `; 153 | -------------------------------------------------------------------------------- /projects/react-plugin/components/VirtualizedImagesGrid.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Masonry, CellMeasurer, CellMeasurerCache, AutoSizer, WindowScroller } from 'react-virtualized'; 3 | import { createCellPositioner } from 'react-virtualized/dist/es/Masonry'; 4 | import * as ImageGridService from '../services/imageGrid.service'; 5 | 6 | 7 | class ReactVirtualizedImagesGrid extends React.PureComponent { 8 | constructor(props) { 9 | super(props); 10 | 11 | this._columnWidth = props.columnWidth || 200; 12 | this._gutterSize = props.gutterSize || 10; 13 | this._columnCount = 0; 14 | 15 | this._cache = new CellMeasurerCache({ 16 | defaultHeight: Math.floor(props.imageContainerHeight) || 300, 17 | defaultWidth: Math.floor(this._columnWidth), 18 | fixedWidth: false, 19 | }); 20 | 21 | this.state = { 22 | columnWidth: Math.floor(this._columnWidth), 23 | height: Math.floor(props.imageContainerHeight) || 300, 24 | gutterSize: this._gutterSize, 25 | overscanByPixels: 0, 26 | windowScrollerEnabled: false 27 | }; 28 | } 29 | 30 | render() { 31 | const { overscanByPixels, windowScrollerEnabled, height } = this.state; 32 | let child; 33 | 34 | if (windowScrollerEnabled) { 35 | child = ( 36 | 37 | {this._renderAutoSizer} 38 | 39 | ); 40 | } else { 41 | child = this._renderAutoSizer({ height }); 42 | } 43 | 44 | return child; 45 | } 46 | 47 | _calculateColumnCount = () => { 48 | const { columnWidth, gutterSize } = this.state; 49 | this._columnCount = ImageGridService.getColumnCount(this._width, columnWidth, gutterSize); 50 | }; 51 | 52 | _cellRenderer = ({ index, key, parent, style }) => { 53 | const { list, cellContent } = this.props; 54 | const { columnWidth } = this.state; 55 | const item = list[index]; 56 | 57 | return ( 58 | 59 | {cellContent({ style, columnWidth, item, index, key })} 60 | 61 | ); 62 | }; 63 | 64 | _initCellPositioner = () => { 65 | if (typeof this._cellPositioner === 'undefined') { 66 | const { columnWidth, gutterSize } = this.state; 67 | 68 | this._cellPositioner = createCellPositioner({ 69 | cellMeasurerCache: this._cache, 70 | columnCount: this._columnCount, 71 | columnWidth, 72 | spacer: gutterSize, 73 | }); 74 | } 75 | }; 76 | 77 | _onResize = ({ width }) => { 78 | if (width) this._width = width; 79 | 80 | this._calculateColumnCount(); 81 | this._resetCellPositioner(); 82 | this._setMasonryRef.recomputeCellPositions(); 83 | }; 84 | 85 | _renderAutoSizer = ({ height, scrollTop }) => { 86 | this._height = height; 87 | this._scrollTop = scrollTop; 88 | 89 | const { overscanByPixels } = this.state; 90 | 91 | return ( 92 | this.child = node} 94 | disableHeight 95 | height={height} 96 | onResize={this._onResize} 97 | overscanByPixels={overscanByPixels} 98 | scrollTop={this._scrollTop} 99 | > 100 | {this._renderMasonry} 101 | 102 | ); 103 | }; 104 | 105 | onScroll = ({ clientHeight, scrollHeight, scrollTop }) => { 106 | const self = this; 107 | const { isShowMoreImages, onShowMoreImages, getImageGridWrapperPosition } = this.props; 108 | 109 | if (getImageGridWrapperPosition ) getImageGridWrapperPosition(); 110 | 111 | if (!onShowMoreImages) return; 112 | if ((clientHeight + scrollTop + 600 >= scrollHeight) && !isShowMoreImages) { 113 | this.props.onShowMoreImages(() => { 114 | const resizeTriggers = document.querySelector('div.resize-triggers').parentNode; 115 | if (resizeTriggers.style.paddingLeft === '9px') resizeTriggers.style.paddingLeft = '10px'; 116 | else resizeTriggers.style.paddingLeft = '9px'; 117 | if (resizeTriggers.style.paddingRight === '9px') resizeTriggers.style.paddingRight = '10px'; 118 | else resizeTriggers.style.paddingRight = '9px'; 119 | 120 | self.child._onResize(); 121 | }); 122 | } 123 | } 124 | 125 | getCoordinates = (index) => { 126 | const { imageGridWrapperWidth, ratio, additionalImageHeight = 0 } = this.props; 127 | const { columnWidth, gutterSize } = this.state; 128 | const perRow = this._columnCount || Math.floor(imageGridWrapperWidth / columnWidth); 129 | const row = Math.floor(index / perRow); 130 | const indexInRow = index % perRow; 131 | 132 | return { 133 | top: Math.floor((columnWidth / ratio) + gutterSize + additionalImageHeight) * row, 134 | left: (columnWidth + gutterSize) * indexInRow 135 | }; 136 | } 137 | 138 | _renderMasonry = ({ width }) => { 139 | this._width = width; 140 | 141 | this._calculateColumnCount(); 142 | this._initCellPositioner(); 143 | 144 | const { count, customPositionHandler } = this.props; 145 | const { height, overscanByPixels, windowScrollerEnabled } = this.state; 146 | 147 | return ( 148 | this._setMasonryRef = node} 158 | scrollTop={this._scrollTop} 159 | width={width} 160 | onScroll={this.onScroll} 161 | tabIndex={-1} 162 | onCellsRendered={this.props.getImageGridWrapperPosition} 163 | /> 164 | ); 165 | }; 166 | 167 | _resetCellPositioner = () => { 168 | const { columnWidth, gutterSize } = this.state; 169 | 170 | this._cellPositioner.reset({ 171 | columnCount: this._columnCount, 172 | columnWidth, 173 | spacer: gutterSize, 174 | }); 175 | }; 176 | 177 | _setMasonryRef = (node) => { 178 | this._masonry = node; 179 | }; 180 | } 181 | 182 | export default ReactVirtualizedImagesGrid; -------------------------------------------------------------------------------- /projects/react-plugin/components/confirm-popup/confirm-popup.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Overlay, Dialog, Content, Header, Body, Footer, Title, CloseButton, Button, CancelButton } from './confirm-popup.styled'; 4 | 5 | 6 | const ConfirmPopup = (props) => { 7 | const { onClickAccept, onClickCancel, accept, cancel, title, msg } = props; 8 | 9 | const onClickOverlay = (event) => { 10 | if (event.target.className.includes('dialog')) onClickCancel(); 11 | }; 12 | 13 | return ( 14 | 15 | 16 | 17 |
18 | {title} 19 | 20 | × 21 | 22 |
23 | 24 |

{msg}

25 | 26 |
27 | 28 | {cancel} 29 |
30 |
31 |
32 |
33 | ); 34 | }; 35 | 36 | ConfirmPopup.propTypes = { 37 | onClickCancel: PropTypes.func.isRequired, 38 | onClickAccept: PropTypes.func.isRequired, 39 | accept: PropTypes.string, 40 | cancel: PropTypes.string, 41 | title: PropTypes.string, 42 | msg: PropTypes.string, 43 | }; 44 | 45 | export default ConfirmPopup; 46 | -------------------------------------------------------------------------------- /projects/react-plugin/components/confirm-popup/confirm-popup.styled.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Overlay = styled('div')` 4 | position: fixed; 5 | top: 0; 6 | bottom: 0; 7 | left: 0; 8 | right: 0; 9 | background-color: rgba(0 , 0, 0, .3); 10 | color: ${p => p.theme.text}; 11 | `; 12 | 13 | export const Dialog = styled('div')` 14 | display: flex; 15 | align-items: center; 16 | justify-content: center; 17 | height: 100%; 18 | width: 100%; 19 | `; 20 | 21 | export const Content = styled('div')` 22 | position: relative; 23 | display: flex; 24 | flex-direction: column; 25 | width: 100%; 26 | max-width: 500px; 27 | pointer-events: auto; 28 | background-color: #fff; 29 | background-clip: padding-box; 30 | border: 1px solid rgba(0,0,0,.2); 31 | border-radius: .3rem; 32 | outline: 0; 33 | `; 34 | 35 | export const Header = styled('div')` 36 | display: flex; 37 | align-items: flex-start; 38 | justify-content: space-between; 39 | padding: 1rem; 40 | border-bottom: 1px solid #e9ecef; 41 | border-top-left-radius: .3rem; 42 | border-top-right-radius: .3rem; 43 | `; 44 | 45 | export const Body = styled('div')` 46 | position: relative; 47 | flex: 1 1 auto; 48 | padding: 1rem; 49 | `; 50 | 51 | export const Footer = styled('div')` 52 | display: flex; 53 | align-items: center; 54 | justify-content: flex-end; 55 | padding: 1rem; 56 | border-top: 1px solid #e9ecef; 57 | `; 58 | 59 | export const Title = styled('h5')` 60 | margin: 0; 61 | line-height: 1.5; 62 | color: ${p => p.theme.title}; 63 | `; 64 | 65 | export const CloseButton = styled('button')` 66 | float: right; 67 | font-size: 1.5rem; 68 | font-weight: 700; 69 | line-height: 1; 70 | color: #000; 71 | text-shadow: 0 1px 0 #fff; 72 | opacity: .5; 73 | cursor: pointer; 74 | padding: 1rem; 75 | margin: -1rem -1rem -1rem auto; 76 | background-color: transparent; 77 | border: 0; 78 | `; 79 | 80 | export const Button = styled('button')` 81 | display: inline-block; 82 | font-weight: 400; 83 | text-align: center; 84 | white-space: nowrap; 85 | vertical-align: middle; 86 | user-select: none; 87 | border: 1px solid transparent; 88 | padding: .375rem .75rem; 89 | font-size: 1rem; 90 | line-height: 1.5; 91 | border-radius: .25rem; 92 | transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; 93 | cursor: pointer; 94 | color: ${p => p.theme.buttonTextColor || '#fff'}; 95 | background-color: ${p => p.theme.buttonBackground || '#00707C'}; 96 | min-width: 65px; 97 | 98 | :not(:last-child) { 99 | margin-right: .25rem; 100 | } 101 | `; 102 | 103 | export const CancelButton = styled(Button)` 104 | background-color: transparent; 105 | color: ${p => p.theme.buttonBackground || '#00707C'}; 106 | border: 1px solid ${p => p.theme.buttonBackground || '#00707C'}; 107 | `; -------------------------------------------------------------------------------- /projects/react-plugin/components/confirm-popup/index.js: -------------------------------------------------------------------------------- 1 | import ConfirmPopup from './confirm-popup'; 2 | 3 | export default ConfirmPopup; -------------------------------------------------------------------------------- /projects/react-plugin/components/hoc.js: -------------------------------------------------------------------------------- 1 | export const Aux = props => props.children; -------------------------------------------------------------------------------- /projects/react-plugin/components/imageEditor/ImageEditorWrapper.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ImageEditor from 'filerobot-image-editor'; 3 | import { getPubliclink, getCDNlink } from '../../utils/adjustAPI.utils'; 4 | 5 | 6 | const goBack = (prevTab, setPostUpload, options = {}, closeModal) => { 7 | if (options.closeOnEdit) { 8 | closeModal(); 9 | 10 | return; 11 | } 12 | 13 | if (prevTab === 'TAGGING') 14 | setPostUpload(true, 'TAGGING', 'IMAGE_EDITOR'); 15 | else 16 | setPostUpload(false); 17 | }; 18 | 19 | const uploadFiles = (prevTab, url, file, saveUploadedFiles, setPostUpload, options = {}, closeModal, uploadHandler) => { 20 | const files = [{ ...file, public_link: getPubliclink(file) }]; 21 | 22 | uploadHandler(files, { stage: 'edit' }); 23 | 24 | if (options.closeOnEdit) { 25 | closeModal(); 26 | 27 | return; 28 | } 29 | 30 | if (prevTab === 'TAGGING') { 31 | const files = [{ ...file, public_link: getPubliclink(file) }]; 32 | 33 | saveUploadedFiles(files); 34 | 35 | setPostUpload(true, 'TAGGING'); 36 | } 37 | 38 | else 39 | setPostUpload(false); 40 | } 41 | 42 | 43 | 44 | export default ({ appState, files: [file = {}] = {}, path, saveUploadedFiles, setPostUpload, options, closeModal }) => { 45 | const { prevTab, config, modifyURL } = appState; 46 | const { uploadKey, baseAPI, container, uploadParams, cloudimageToken, uploadHandler, language, imageEditorConfig = {} } = config; 47 | const isGif = getPubliclink(file).slice(-3).toLowerCase() === 'gif'; 48 | const src = getCDNlink(file); 49 | 50 | const onComplete = (url, updatedFile) => { 51 | if (modifyURL) { 52 | const files = [{ ...updatedFile, modified_url: url, public_link: getPubliclink(updatedFile) }]; 53 | uploadHandler(files, { stage: 'modify' }); 54 | closeModal(); 55 | } else { 56 | uploadFiles(prevTab, url, updatedFile, saveUploadedFiles, setPostUpload, options, closeModal, uploadHandler); 57 | } 58 | } 59 | 60 | const imageEditorImageInfo = { 61 | imageProperties: file.properties, 62 | imageMeta: file.meta, 63 | imageName: file.name 64 | } 65 | 66 | return ( 67 | { goBack(prevTab, setPostUpload, options, closeModal); }} 102 | showInModal={false} 103 | /> 104 | ); 105 | } 106 | -------------------------------------------------------------------------------- /projects/react-plugin/components/loadable.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Loadable from 'react-loadable'; 3 | import { Spinner } from '../components/Spinner'; 4 | 5 | 6 | export const UploadedImagesTab = Loadable({ 7 | loader: () => import(/* webpackChunkName: "gallery" */ './UploadedImagesTab/UploadedImagesTab'), 8 | loading: () => , 9 | }); 10 | export const IconTab = Loadable({ 11 | loader: () => import(/* webpackChunkName: "icons" */ './IconsTab/IconTab'), 12 | loading: () => , 13 | }); 14 | export const BackgroundTab = Loadable({ 15 | loader: () => import(/* webpackChunkName: "images" */ './ImagesTab/ImagesTab'), 16 | loading: () => , 17 | }); 18 | export const TaggingTab = Loadable({ 19 | loader: () => import(/* webpackChunkName: "tagging" */ './TaggingTab/TaggingTab'), 20 | loading: () => , 21 | }); 22 | export const ImageEditor = Loadable({ 23 | loader: () => import(/* webpackChunkName: "image-editor" */ './imageEditor/ImageEditorWrapper'), 24 | loading: () => 25 | }); -------------------------------------------------------------------------------- /projects/react-plugin/components/nav/Nav.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tab } from './Nav.styled'; 3 | import { I18n } from 'react-i18nify'; 4 | import { Nav } from '../UploadImagesTab/UserUploaderTab.styled'; 5 | 6 | 7 | export default ({ tabs, activeTabId, ...props }) => ( 8 | 24 | ); -------------------------------------------------------------------------------- /projects/react-plugin/components/nav/Nav.styled.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | 4 | export const Tab = styled.a` 5 | font-family: Roboto, sans-serif; 6 | color: ${props => props.selected ? props.theme.activeTabTextColor : props.theme.tabTextColor}; 7 | background-color: ${props => props.selected ? props.theme.activeTabBackground : 'transparent'}; 8 | text-decoration: none; 9 | font-size: 12px; 10 | line-height: 21px; 11 | padding: 9px 12px; 12 | cursor: pointer; 13 | border-left: 2px solid transparent; 14 | border-right: 2px solid transparent; 15 | border-top: 2px solid transparent; 16 | border-bottom: 2px solid transparent; 17 | text-transform: uppercase; 18 | font-weight: 400; 19 | display: inline-block; 20 | vertical-align: top; 21 | 22 | // :first-child { 23 | // margin-left: 5px; 24 | // } 25 | 26 | :hover { 27 | color: ${props => props.selected ? props.theme.activeTabTextColor : props.theme.tabTextColor}; 28 | background-color: ${props => props.theme.activeTabBackground || 'transparent'}; 29 | } 30 | 31 | i { 32 | font-size: 21px; 33 | line-height: 21px; 34 | margin-right: 5px; 35 | display: inline-block; 36 | vertical-align: middle; 37 | } 38 | 39 | span { 40 | display: inline-block; 41 | vertical-align: middle; 42 | 43 | @media screen and (max-width: 850px) { 44 | display: none; 45 | } 46 | } 47 | `; -------------------------------------------------------------------------------- /projects/react-plugin/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | modules: ['UPLOAD', 'MY_GALLERY', 'ICONS_GALLERY', 'IMAGES_GALLERY'], 3 | initialTab: 'UPLOAD', 4 | container: 'example', 5 | elementID: 'airstore-uploader', 6 | openpixKey: 'xxxxxxxxxxxxxxx', 7 | cloudimageToken: 'demo', 8 | filerobotUploadKey: '', 9 | uploadParams: { 10 | dir: '/' 11 | }, 12 | language: 'en', 13 | folderBrowser: { 14 | show: true, 15 | rootFolder: '/' // use cannot go up above rootFolder on server 16 | }, 17 | tagging: { 18 | executeAfterUpload: false, 19 | autoTaggingButton: false, 20 | provider: 'google', 21 | confidence: 80, 22 | limit: 10, 23 | key: '' 24 | }, 25 | sortParams: { 26 | show: true, 27 | field: 'name', // ['name', 'type', 'uploaded_at', 'modified_at'], 28 | order: 'asc' // 'desc and asc' 29 | } 30 | }; 31 | 32 | export const DEFAULT_ICON_SIZE = 100; 33 | export const GALLERY_IMAGES_LIMIT = 50; 34 | export const DUPLICATE_CODE = 'SHA1_CONFLICT_STOP_UPLOAD'; 35 | export const REPLACING_DATA_CODE = 'FILE_EXISTS_REPLACING_DATA'; 36 | 37 | export const COLORS = [ 38 | '#96dc52', 39 | '#016df0', 40 | '#943dc5', 41 | '#feda48', 42 | '#d90028', 43 | '#ffffff', 44 | '#000000' 45 | ]; 46 | 47 | const DEFAULT_TAGS = [ 48 | 'accessibility', 49 | 'arrows', 50 | 'Audio & Video', 51 | 'Business', 52 | 'Charity', 53 | 'Chat', 54 | 'Chess', 55 | 'Code', 56 | 'Communication', 57 | 'Computers', 58 | 'Currency', 59 | 'Date & Time', 60 | 'Design', 61 | 'Editors', 62 | 'Files', 63 | 'Genders', 64 | 'Hands', 65 | 'Health', 66 | 'Images', 67 | 'Interfaces', 68 | 'Logistics', 69 | 'Maps', 70 | 'Medical', 71 | 'Moving', 72 | 'Objects', 73 | 'Payments & Shopping', 74 | 'Shapes', 75 | 'Spinners', 76 | 'Sports', 77 | 'Status', 78 | 'Users & People', 79 | 'Vehicles', 80 | 'Writing' 81 | ] -------------------------------------------------------------------------------- /projects/react-plugin/index.js: -------------------------------------------------------------------------------- 1 | import AirstoreUploader from './components/AirstoreUploaderWrapper'; 2 | 3 | export default AirstoreUploader; -------------------------------------------------------------------------------- /projects/react-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "airstore-uploader-plugin", 4 | "main": "./index.js" 5 | } -------------------------------------------------------------------------------- /projects/react-plugin/polyfills.js: -------------------------------------------------------------------------------- 1 | import 'core-js/features/array/find'; 2 | import 'core-js/features/array/for-each'; 3 | import 'core-js/features/array/includes'; 4 | import 'core-js/features/object/keys'; 5 | import 'core-js/features/promise'; -------------------------------------------------------------------------------- /projects/react-plugin/services/helper.service.js: -------------------------------------------------------------------------------- 1 | const guid = () => { 2 | function s4() { 3 | return Math.floor((1 + Math.random()) * 0x10000) 4 | .toString(16) 5 | .substring(1); 6 | } 7 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); 8 | } 9 | 10 | export { guid } -------------------------------------------------------------------------------- /projects/react-plugin/services/iconsApi.service.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | 4 | const api_endpoint = 'https://www.openpix.net/v3/icons'; 5 | 6 | const _send = (url, method = 'GET', data = null, headers = {}, responseType = "json") => 7 | new Promise((resolve, reject) => { 8 | 9 | axios({ 10 | url: url, 11 | method: method, 12 | data: data, 13 | responseType: responseType, 14 | headers: headers, 15 | timeout: 30000 16 | }).then( 17 | (response) => { 18 | const { data = {} } = response; 19 | const { status = 'error' } = data; 20 | 21 | if (status === 'success') { 22 | resolve(data); 23 | 24 | // store in cache 25 | if (method.toLowerCase() === 'get') 26 | sessionStorage.setItem(url, JSON.stringify(data)); 27 | } 28 | else 29 | reject(response); 30 | }, 31 | ({ data = {} }) => { reject(data) } 32 | ); 33 | }) 34 | 35 | 36 | export const getTags = () => 37 | _send(`${api_endpoint}tags`) 38 | .then( 39 | ({ tags = [] }) => tags 40 | ); 41 | 42 | export const searchIcons = (searchParams, relevantActiveTags = []) => { 43 | const { typeQuery, offset, openpixKey } = searchParams; 44 | const splittedString = searchParams.value.trim().split(' '); 45 | const value = `&q[]=${splittedString.map(string => string.trim()).join('&q[]=')}`; 46 | const tags = relevantActiveTags.map(tag => `&q[]=${tag}`).join(''); 47 | const key = `&key=${openpixKey}`; 48 | const limitQuery = `&limit=250`; 49 | const offsetQuery = `&offset=${offset}`; 50 | 51 | return ( 52 | _send(`${api_endpoint}?${value}${tags}${typeQuery}${limitQuery}${offsetQuery}${key}`) 53 | .then( 54 | ({ icons = [], count = 0, related_tags }) => ({ icons: icons || [], count, related_tags }) 55 | ) 56 | ); 57 | } 58 | 59 | export const addTag = (uid, tagName) => { 60 | return _send(`${api_endpoint}retag?uid=${uid}&op=ADD&tag=${tagName}`); 61 | } 62 | 63 | export const setAsNotRelevant = (searchParams, relevantActiveTags = [], uid) => { 64 | const splittedString = searchParams.value.trim().split(' '); 65 | const value = `&q[]=${splittedString.map(string => string.trim()).join('&q[]=')}`; 66 | const tags = relevantActiveTags.map(tag => `&q[]=${tag}`).join(''); 67 | 68 | return _send(`${api_endpoint}improve/relevancy?${value}${tags}&uid=${uid}`); 69 | } 70 | 71 | export const sendSelectionData = (searchParams, relevantActiveTags = [], uid, shownIcons) => { 72 | const splittedString = searchParams.value.trim().split(' '); 73 | const value = `q[]=${splittedString.map(string => string.trim()).join('&q[]=')}`; 74 | const tags = relevantActiveTags.map(tag => `&q[]=${tag}`).join(''); 75 | const data = `${value}${tags}&chosen_uid=${uid}&shown_icons_uid[]=${shownIcons.map(icon => icon.uid).join('&shown_icons_uid[]=')}`; 76 | 77 | return _send(`${api_endpoint}improve/selection`, 'POST', data, {}, 'application/x-www-form-urlencoded'); 78 | } -------------------------------------------------------------------------------- /projects/react-plugin/services/imageGrid.service.js: -------------------------------------------------------------------------------- 1 | 2 | export const getColumnCount = (containerWidth, columnWidth, gutterSize) => 3 | Math.floor((containerWidth + gutterSize) / (columnWidth + gutterSize)); 4 | 5 | export const getColumnWidth = (containerWidth, columnCount, gutterSize) => 6 | (containerWidth - ((columnCount - 1) * gutterSize)) / columnCount; 7 | 8 | 9 | export const getActualColumnWidth = (containerWidth = 0, minColumnWidth = 200, gutterSize = 10) => { 10 | const columnCount = getColumnCount(containerWidth, minColumnWidth, gutterSize); 11 | 12 | return getColumnWidth(containerWidth, columnCount, gutterSize); 13 | }; 14 | 15 | export const getResizeImageUrl = (url = '', width = 300, cloudimageToken = 'demo') => 16 | `https://${cloudimageToken}.cloudimg.io/v7/${processUrl(url)}w=${Math.round(width)}`; 17 | 18 | export const getFitResizeImageUrlWithCIcdn = (url = '', width = 300, height = 200, cloudimageToken = 'demo') => 19 | `https://${cloudimageToken}.cloudimg.io/v7/${processUrl(url)}func=fit&bg_color=ffffff&w=${Math.round(width)}&h=${Math.round(height)}`; 20 | 21 | export const getFitResizeImageUrl = (url = '', width = 300, height = 200, cloudimageToken = 'demo') => 22 | `${processUrl(url)}func=fit&bg_color=ffffff&w=${Math.round(width)}&h=${Math.round(height)}`; 23 | 24 | export const getCropImageUrl = (url = '', width = 300, height = 200, cloudimageToken = 'demo') => 25 | `https://${cloudimageToken}.cloudimg.io/v7/${processUrl(url)}func=crop&w=${Math.round(width)}&h=${Math.round(height)}`; 26 | 27 | const processUrl = url => url.includes('?') ? `${url}&` : `${url}?`; -------------------------------------------------------------------------------- /projects/react-plugin/services/imagesApi.service.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { send } from './api.service'; 3 | import { getBaseUrl } from './api.service'; 4 | import { getPermalink, getPubliclink } from '../utils/adjustAPI.utils' 5 | 6 | 7 | const api_endpoint = 'https://www.openpix.net/v3/'; 8 | const backgroundsAPI = `${getBaseUrl('jolipage-public-assets')}list?dir=/Backgrounds/v1`; 9 | 10 | const _send = (url, method = 'GET', data = null, headers = {}, responseType = "json") => 11 | new Promise((resolve, reject) => { 12 | 13 | axios({ 14 | url: url, 15 | method: method, 16 | data: data, 17 | responseType: responseType, 18 | headers: headers, 19 | timeout: 30000 20 | }).then( 21 | (response) => { 22 | const { data = {} } = response; 23 | const { status = 'error' } = data; 24 | 25 | if (status === 'success') { 26 | resolve(data); 27 | 28 | // store in cache 29 | if (method.toLowerCase() === 'get') 30 | sessionStorage.setItem(url, JSON.stringify(data)); 31 | } 32 | else 33 | reject(response); 34 | }, 35 | ({ data = {} }) => { reject(data) } 36 | ); 37 | }) 38 | 39 | 40 | export const getBackgrounds = () => 41 | send(`${backgroundsAPI}`) 42 | .then(({ files = [] }) => 43 | files.map((file = {}) => ({ 44 | src: getPubliclink(file) || getPermalink(file), 45 | id: file.uuid, 46 | name: file.name, 47 | alt: '' 48 | })) 49 | ); 50 | 51 | export const searchImages = (searchParams, relevantActiveTags = []) => { 52 | const { colorFiltersQuery, limit, offset, openpixKey } = searchParams; 53 | const splittedString = searchParams.value.trim().split(' '); 54 | const value = searchParams.value ? `&q[]=${splittedString.map(string => string.trim()).join('&q[]=')}` : ''; 55 | const tags = relevantActiveTags.map(tag => `&q[]=${tag}`).join(''); 56 | const limitQuery = `&limit=${limit}`; 57 | const offsetQuery = `&offset=${offset}`; 58 | const key = `&key=${openpixKey}`; 59 | 60 | return ( 61 | _send(`${api_endpoint}search?${value}${tags}${colorFiltersQuery}${limitQuery}${offsetQuery}${key}`) 62 | .then( 63 | ({ related_tags = [], related_top_colors = [], images = [], count = 0 }) => 64 | ({ images: images, count, related_tags, related_top_colors }) 65 | ) 66 | ); 67 | } 68 | 69 | export const getImagesTags = () => _send(`${api_endpoint}pictures/tags`).then(({ tags }) => tags); -------------------------------------------------------------------------------- /projects/react-plugin/styledComponents/AirstoreUploader.styled.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | 4 | export const Dialog = styled.div` 5 | display: flex; 6 | flex-direction: column; 7 | height: 100%; 8 | fontFamily: Roboto, sans-serif; 9 | background: ${p => p.theme.mainBackground || '#181830'}; 10 | 11 | ::-webkit-scrollbar { 12 | height: 6px !important; 13 | width: 6px !important; 14 | } 15 | 16 | ::-webkit-scrollbar-thumb { 17 | background: rgb(221,221,221); 18 | border-radius: 5px; 19 | } 20 | 21 | input[type="search"] { 22 | box-sizing: border-box !important; 23 | } 24 | 25 | * { 26 | box-sizing: border-box; 27 | } 28 | `; -------------------------------------------------------------------------------- /projects/react-plugin/styledComponents/UploadedImages.styled.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import { CloseBtn } from '../components/CloseBtn'; 4 | import { ButtonSearch } from './IconTab.styled'; 5 | 6 | 7 | export const UploadedImages = styled.div` 8 | display: flex; 9 | flex-direction: column; 10 | padding: 0 15px; 11 | height: 100%; 12 | box-sizing: border-box; 13 | `; 14 | 15 | export const HeaderWrap = styled.div` 16 | display: flex; 17 | justify-content: space-between; 18 | align-items: center; 19 | padding: 0; 20 | min-height: 50px; 21 | `; 22 | 23 | export const Nav = styled.nav` 24 | display: block; 25 | `; 26 | 27 | export const NavItem = styled.span` 28 | color: ${props => props.active ? '#1e262c' : '#70777f'}; 29 | font-size: 14px; 30 | padding-right: 25px; 31 | cursor: pointer; 32 | text-decoration: ${props => props.active ? 'underline' : 'none'}; 33 | `; 34 | 35 | export const UploadBoxWrapper = styled.div` 36 | display: inline-block; 37 | width: ${props => props.columnWidth || 300}px !important; 38 | height: ${props => props.height || 200}px !important;; 39 | `; 40 | 41 | export const UploadBox = styled.div` 42 | width: 100%; 43 | height: 100%; 44 | border: 2px dashed #d8d8d8; 45 | background: ${props => props.isDragOver ? 'rgba(210, 253, 207, 0.5)' : '#f5f5f5'}; 46 | display: flex; 47 | flex-direction: column; 48 | align-items: center; 49 | justify-content: center; 50 | color: #5D636B; 51 | box-sizing: border-box; 52 | `; 53 | 54 | export const Content = styled.div` 55 | color: #5D636B; 56 | height: 100%; 57 | 58 | ${UploadBox} { 59 | background: ${props => props.isDragOver ? 'rgba(210, 253, 207, 0.5) !important' : props.theme.activeSidebarItemBackground}; 60 | border: ${props => props.isDragOver ? '2px dashed #888888' : `2px dashed ${props.theme.border}`}; 61 | } 62 | `; 63 | 64 | export const UploadBoxIcon = styled.span` 65 | font-size: 80px; 66 | `; 67 | 68 | export const UploadInputBox = styled.input` 69 | width: .1px; 70 | height: .1px; 71 | opacity: 0; 72 | overflow: hidden; 73 | position: absolute; 74 | z-index: -1; 75 | `; 76 | 77 | export const ButtonClose = styled(CloseBtn)` 78 | left: 280px; 79 | width: 20px; 80 | top: 8px; 81 | 82 | :hover { 83 | color: ${p => p.theme.buttonBackground || '#00707C'}; 84 | } 85 | `; 86 | 87 | export const ActionButtonsWrapper = styled('div')` 88 | display: flex; 89 | 90 | `; 91 | 92 | export const SortButton = styled(ButtonSearch)` 93 | border-radius: 4px; 94 | margin-right: 5px; 95 | 96 | .options-dropdown-menu { 97 | position: absolute; 98 | box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3); 99 | background: ${p => p.theme.mainBackground}; 100 | padding: 0; 101 | margin: 0; 102 | list-style-type: none; 103 | border-radius: 3px; 104 | 105 | button, 106 | a { 107 | padding: 4px 8px; 108 | cursor: pointer; 109 | margin: 0; 110 | width: 100%; 111 | border: none; 112 | outline: none; 113 | color: ${p => p.theme.text}; 114 | background-color: ${p => p.theme.mainBackground}; 115 | text-align: left; 116 | font-size: 12px; 117 | 118 | &:hover:not([disabled]) { 119 | background: #d7d7d7; 120 | } 121 | } 122 | 123 | a { 124 | display: block; 125 | color: ${p => p.theme.text}; 126 | text-decoration: none; 127 | } 128 | } 129 | 130 | [class^="ai-icon-"], 131 | .sort-tick-wrapper { 132 | margin-right: 5px; 133 | } 134 | 135 | .sort-tick { 136 | display: inline-block; 137 | background: transparent no-repeat; 138 | background-size: cover; 139 | width: 7px; 140 | height: 7px; 141 | border: none; 142 | 143 | &.icon-up { 144 | background-image: url("//js.filerobot.com/airstore-explorer/static/media/caret-arrow-up.svg"); 145 | 146 | &.white { 147 | background-image: url("//js.filerobot.com/airstore-explorer/static/media/caret-arrow-up-white.svg"); 148 | } 149 | 150 | &.dark { 151 | background-image: url("//js.filerobot.com/airstore-explorer/static/media/caret-arrow-up-dark.svg"); 152 | } 153 | } 154 | 155 | &.icon-down { 156 | background-image: url("//js.filerobot.com/airstore-explorer/static/media/caret-down.svg"); 157 | 158 | &.white { 159 | background-image: url("//js.filerobot.com/airstore-explorer/static/media/caret-down-white.svg"); 160 | } 161 | 162 | &.dark { 163 | background-image: url("//js.filerobot.com/airstore-explorer/static/media/caret-down-dark.svg"); 164 | } 165 | } 166 | } 167 | `; -------------------------------------------------------------------------------- /projects/react-plugin/styledComponents/index.js: -------------------------------------------------------------------------------- 1 | export * from './BackgroundTab.styled'; 2 | export * from './IconTab.styled'; 3 | export * from './UploadedImages.styled' 4 | export * from './AirstoreUploader.styled'; -------------------------------------------------------------------------------- /projects/react-plugin/styledComponents/styleUtils.js: -------------------------------------------------------------------------------- 1 | const commonTheme = { 2 | textFontSize: '14px', 3 | 4 | colors: { 5 | success: '#5cb85c', 6 | warning: '#f0ad4e', 7 | error: '#d9534f', 8 | muted: '#636c72', 9 | 10 | base: 'light', 11 | 12 | light: { 13 | base: '#F9FAFB', 14 | dark: '#F4F6F8', 15 | darker: '#DFE4E8', 16 | }, 17 | 18 | dark: { 19 | base: '#1e262c', 20 | light: '#454F5B', 21 | lighter: '#637381', 22 | }, 23 | 24 | primary: { 25 | base: '#181830', 26 | light: '#263138', 27 | lighter: '#34444c', 28 | dark: '#101021', 29 | darker: '#090912', 30 | 31 | text: '#F9FAFB' 32 | }, 33 | 34 | secondary: { 35 | base: '#00707c', 36 | light: '#007E8A', 37 | lighter: '#008D99', 38 | dark: '#00616D', 39 | darker: '#005662', 40 | 41 | text: '#F9FAFB' 42 | }, 43 | 44 | text: { 45 | base: '#F9FAFB', 46 | dark: '#F4F6F8', 47 | mute: '#aaa', 48 | light: '#fff' 49 | }, 50 | 51 | background: { 52 | base: '#787878' 53 | }, 54 | 55 | border: { 56 | base: '#ccc' 57 | }, 58 | 59 | link: { 60 | base: '#00707c', 61 | over: '#00616D', 62 | }, 63 | 64 | brand: { 65 | success: '#5cb85c', 66 | warning: '#f0ad4e', 67 | error: '#d9534f', 68 | muted: '#636c72' 69 | } 70 | }, 71 | 72 | modal: { 73 | borderRadius: '4px', 74 | fluid: { 75 | xs: '35%', 76 | sm: '45%', 77 | md: '60%', 78 | lg: '80%', 79 | xl: '90%' 80 | }, 81 | fixed: { 82 | xs: '200px', 83 | sm: '300px', 84 | md: '450px', 85 | lg: '650px', 86 | xl: '800px' 87 | } 88 | }, 89 | 90 | button: { 91 | tt: 'none' 92 | } 93 | } 94 | 95 | export const variables = { 96 | ...commonTheme, 97 | 98 | // backgrounds 99 | mainBackgroundColor: '#1e262c', 100 | mainBackgroundColorHover: '#263138', 101 | mainBackgroundColorActive: '#34444c', 102 | 103 | secondBackgroundColor: '#263138', 104 | 105 | // text 106 | textColor: '#e7f1f4', 107 | textColorHover: '#fff', 108 | textMuted: '#70777f', 109 | 110 | // border 111 | borderColor: '#70777f', 112 | borderDarkColor: '#161e23', 113 | 114 | fieldWidth: '120px', 115 | borderInputColor: '#3b4d54', 116 | borderInputColorHover: '#52686d', 117 | borderInputColorActive: '#52686d', 118 | inputBackgroundColor: '#34444c', 119 | inputBoxShadowColor: '#03a9bb', 120 | inputPlaceholderColor: '#888d94', 121 | 122 | 123 | // main color 124 | mainThemeColor: '#00707c', 125 | mainThemeColorHover: '#00616D', 126 | mainThemeColorOpacity: 'rgba(0, 112, 124, 0.5)', 127 | 128 | btnSaveColor: '#009345', 129 | btnSaveColorHover: '#00b549', 130 | 131 | btnThemeColor: '#34444c', 132 | 133 | btnPaddingSm: '2px 14px', 134 | btnFontSizeSm: '12px', 135 | btnBorderRadius: '2px', 136 | 137 | modalOverlayColor: '#484848', 138 | 139 | btnMainColor: (props) => ` 140 | color: #fff; 141 | background-color: #00707c; 142 | border-color: #00707c; 143 | 144 | :hover { 145 | color: #fff; 146 | background-color: #00616D; 147 | border-color: #00616D; 148 | } 149 | 150 | :focus { 151 | -webkit-box-shadow: 0 0 0 2px rgba(0, 112, 124, 0.5); 152 | box-shadow: 0 0 0 2px rgba(0, 112, 124, 0.5); 153 | } 154 | 155 | :active { 156 | color: #fff; 157 | background-color: #00616D; 158 | background-image: none; 159 | border-color: #00616D; 160 | } 161 | 162 | ${props.disabled ? ` 163 | background-color: rgba(0, 112, 124, 0.5); 164 | border-color: rgba(0, 112, 124, 0.5); 165 | ` : ''} 166 | 167 | ${props.active ? ` 168 | color: #fff; 169 | background-color: #00616D; 170 | background-image: none; 171 | border-color: #00616D; 172 | ` : ''} 173 | `, 174 | 175 | button: { 176 | ...commonTheme.button, 177 | 178 | sm: { 179 | p: '4px 10px', 180 | fz: '12px' 181 | }, 182 | md: { 183 | p: '6px 12px', 184 | fz: '14px' 185 | }, 186 | lg: { 187 | p: '8px 14px', 188 | fz: '16px' 189 | } 190 | }, 191 | 192 | modal: { 193 | ...commonTheme.modal, 194 | 195 | backgroundColor: '#1e262c', 196 | color: '#e7f1f4', 197 | colorMuted: '#70777f', 198 | colorMutedHover: '#e7f1f4' 199 | } 200 | } 201 | 202 | function getModalWidth(props) { 203 | const { exact, xl, lg, md, sm, xs, fluid } = props; 204 | const size = exact ? 'exact' : xl ? 'xl' : lg ? 'lg' : md ? 'md' : sm ? 'sm' : xs ? 'xs' : 'md'; 205 | 206 | if (size === 'exact') return exact; 207 | 208 | return variables.modal[fluid ? 'fluid' : 'fixed'][size]; 209 | } 210 | 211 | function getFieldColorStyles(props) { 212 | if (props.dark) return ``; 213 | else return ` 214 | color: ${getColor(props, 'text')}; 215 | background-color: ${getColor(props, 'background')}; 216 | border: 1px solid ${getColor(props, 'border')}; 217 | 218 | &::-webkit-input-placeholder { 219 | color: ${getColor(props, 'text', 'muted')}; 220 | } 221 | &::-moz-placeholder { 222 | color: ${getColor(props, 'text', 'muted')}; 223 | } 224 | &:-ms-input-placeholder { 225 | color: ${getColor(props, 'text', 'muted')}; 226 | } 227 | &::-ms-input-placeholder { 228 | color: ${getColor(props, 'text', 'muted')}; 229 | } 230 | &::placeholder { 231 | color: ${getColor(props, 'text', 'muted')}; 232 | } 233 | 234 | &:focus { 235 | color: ${getColor(props, 'text')}; 236 | background-color: ${getColor(props, 'background')}; 237 | border-color: ${getColor(props, 'link')}; 238 | outline: 0; 239 | } 240 | &:focus::-ms-value { 241 | color: ${getColor(props, 'text')}; 242 | background-color: ${getColor(props, 'background')}; 243 | border-color: ${getColor(props, 'link')}; 244 | outline: 0; 245 | } 246 | `; 247 | } 248 | 249 | function shadeBlendConvert(p, from, to) { 250 | if(typeof(p)!="number"||p<-1||p>1||typeof(from)!="string"||(from[0]!='r'&&from[0]!='#')||(typeof(to)!="string"&&typeof(to)!="undefined"))return null; //ErrorCheck 251 | if(!window.sbcRip)window.sbcRip=(d)=>{ 252 | let l=d.length,RGB=new Object(); 253 | if(l>9){ 254 | d=d.split(","); 255 | if(d.length<3||d.length>4)return null;//ErrorCheck 256 | RGB[0]=i(d[0].slice(4)),RGB[1]=i(d[1]),RGB[2]=i(d[2]),RGB[3]=d[3]?parseFloat(d[3]):-1; 257 | }else{ 258 | if(l==8||l==6||l<4)return null; //ErrorCheck 259 | if(l<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(l>4?d[4]+""+d[4]:""); //3 digit 260 | d=i(d.slice(1),16),RGB[0]=d>>16&255,RGB[1]=d>>8&255,RGB[2]=d&255,RGB[3]=l==9||l==5?r(((d>>24&255)/255)*10000)/10000:-1; 261 | } 262 | return RGB;} 263 | var i=parseInt,r=Math.round,h=from.length>9,h=typeof(to)=="string"?to.length>9?true:to=="c"?!h:false:h,b=p<0,p=b?p*-1:p,to=to&&to!="c"?to:b?"#000000":"#FFFFFF",f=window.sbcRip(from),t=window.sbcRip(to); 264 | if(!f||!t)return null; //ErrorCheck 265 | if(h)return "rgb("+r((t[0]-f[0])*p+f[0])+","+r((t[1]-f[1])*p+f[1])+","+r((t[2]-f[2])*p+f[2])+(f[3]<0&&t[3]<0?")":","+(f[3]>-1&&t[3]>-1?r(((t[3]-f[3])*p+f[3])*10000)/10000:t[3]<0?f[3]:t[3])+")"); 266 | else return "#"+(0x100000000+(f[3]>-1&&t[3]>-1?r(((t[3]-f[3])*p+f[3])*255):t[3]>-1?r(t[3]*255):f[3]>-1?r(f[3]*255):255)*0x1000000+r((t[0]-f[0])*p+f[0])*0x10000+r((t[1]-f[1])*p+f[1])*0x100+r((t[2]-f[2])*p+f[2])).toString(16).slice(f[3]>-1||t[3]>-1?1:3); 267 | } 268 | 269 | function isHex(color) { 270 | return (/^#?([a-f\d])([a-f\d])([a-f\d])$/i).test(color) || (/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i).test(color); 271 | } 272 | 273 | function hexToRgb(hex) { 274 | // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") 275 | var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 276 | hex = hex.replace(shorthandRegex, function(m, r, g, b) { 277 | return r + r + g + g + b + b; 278 | }); 279 | 280 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 281 | return result ? { 282 | r: parseInt(result[1], 16), 283 | g: parseInt(result[2], 16), 284 | b: parseInt(result[3], 16) 285 | } : null; 286 | } 287 | 288 | export const getHoverColor = (color) => { 289 | if (!color) return null; 290 | 291 | const nextColor = shadeBlendConvert(-0.1, color) || ''; 292 | 293 | return nextColor.toLowerCase() === color.toLowerCase() ? shadeBlendConvert(0.25, color) : nextColor; 294 | } 295 | 296 | export const getWithOpacity = (color, opacity) => { 297 | if (!color) return null; 298 | 299 | const isHexColor = isHex(color); 300 | const hexColor = isHexColor ? color : shadeBlendConvert(0, color, 'c'); 301 | if (!hexColor) return null; 302 | const rgbColor = hexToRgb(hexColor); 303 | 304 | if (!rgbColor) return color; 305 | 306 | return `rgba(${rgbColor.r}, ${rgbColor.g}, ${rgbColor.b}, ${opacity})`; 307 | } 308 | 309 | export const getElementStylesBySize = (props, type, field) => { 310 | const { sm, lg } = props; 311 | const size = sm ? 'sm' : lg ? 'lg' : 'md'; 312 | 313 | return variables[type] && variables[type][size] && variables[type][size][field]; 314 | } 315 | 316 | export const getColor = (props, type, field = 'base', isThemeOverlay, isSupreme) => { 317 | const themeOverlay = isThemeOverlay ? variables.colors.base : null; 318 | 319 | return variables.colors[type][themeOverlay ? (themeOverlay + (isSupreme ? 'er' : '')) : field]; 320 | } 321 | 322 | 323 | export { getModalWidth, getFieldColorStyles }; -------------------------------------------------------------------------------- /projects/react-plugin/utils/adjustAPI.utils.js: -------------------------------------------------------------------------------- 1 | /* API change from file.url_permalink to file.url.permalink 2 | * */ 3 | 4 | export const getPermalink = file => { 5 | if (file.url && file.url.permalink) 6 | return file.url.permalink; 7 | else if (file.url_permalink) 8 | return file.url_permalink; 9 | else 10 | return ''; 11 | } 12 | 13 | export const getPubliclink = file => { 14 | if (file.url && file.url.public) 15 | return file.url.public; 16 | else if (file.url_public) 17 | return file.url_public; 18 | else 19 | return ''; 20 | } 21 | 22 | export const getCDNlink = file => { 23 | if (file.url && file.url.cdn) 24 | return file.url.cdn; 25 | else 26 | return ''; 27 | } -------------------------------------------------------------------------------- /projects/react-plugin/utils/files-upload.js: -------------------------------------------------------------------------------- 1 | import { send } from '../services/api.service'; 2 | 3 | const POST_MAX_FILESIZE_MB = 450; 4 | const POST_MAX_REQUEST_MULTI_FILES_SIZE_MB = 10; 5 | const MAX_FILES_COUNT_PER_REQUEST = 10; 6 | 7 | /** 8 | * https://docs.airstore.io/go/airstore-public-documentation/en/airstore-api-reference/upload-files#od_fae16fa2 9 | */ 10 | export const uploadFormDataFiles = (formData, url, onUploadProgress, requestOptions = {}) => { 11 | // Handle the most slow request 12 | function handleUploadProgress(id) { 13 | return progress => { 14 | requestsProgressPercent[id] = { 15 | ...(requestsProgressPercent[id] || {}), 16 | loaded: progress.loaded, 17 | total: progress.total, 18 | progress: progress.loaded / progress.total, 19 | }; 20 | 21 | const requestsProgressPercentArray = Object.keys(requestsProgressPercent) 22 | .map(propName => requestsProgressPercent[propName] || {}); 23 | let mostSlowRequest = requestsProgressPercentArray[0]; 24 | 25 | requestsProgressPercentArray.forEach(item => { 26 | if (item.progress < mostSlowRequest.progress) { 27 | mostSlowRequest = item; 28 | } 29 | }); 30 | 31 | onUploadProgress(mostSlowRequest); 32 | } 33 | } 34 | 35 | const requestsProgressPercent = {}; 36 | const putFiles = []; 37 | const postFiles = []; 38 | const postFilesChunks = []; // chunk size limit === POST_MAX_FILESIZE_MB 39 | 40 | [...formData.getAll('files[]')].forEach(file => 41 | (bytesToMb(file.size) <= POST_MAX_FILESIZE_MB ? postFiles : putFiles).push(file) 42 | ); 43 | 44 | // Sort from small to large, for better chunking 45 | postFiles.sort((a, b) => a.size === b.size ? 0 : (a.size < b.size ? -1 : 1)); 46 | 47 | // Chunking post files 48 | while (postFiles.length > 0) { 49 | /** 50 | * In chunk can be one file (for ex. 200MB) or max 10 files with total size <= 10MB 51 | */ 52 | const chunk = [postFiles.pop()]; 53 | let i = postFiles.length; 54 | 55 | if (bytesToMb(filesTotalBytesSize(chunk)) < POST_MAX_REQUEST_MULTI_FILES_SIZE_MB) { 56 | while (i--) { 57 | if (bytesToMb(filesTotalBytesSize(chunk) + postFiles[i].size) <= POST_MAX_REQUEST_MULTI_FILES_SIZE_MB) { 58 | chunk.push(postFiles.splice(i, 1)[0]); 59 | 60 | if (chunk.length === MAX_FILES_COUNT_PER_REQUEST) { 61 | break; 62 | } 63 | } 64 | } 65 | } 66 | 67 | postFilesChunks.push(chunk); 68 | } 69 | 70 | const promises = [ 71 | ...putFiles.map((file, index) => 72 | send( 73 | `${url}${/\?/.test(url) ? '&' : '?'}filename=${file.name}`, 74 | 'PUT', file, {}, 'json', 75 | { 76 | ...requestOptions, 77 | onUploadProgress: handleUploadProgress(`put-${index}`), 78 | headers: { 79 | 'Content-Type': isJson ? 'application/json' : 'multipart/form-data', 80 | ...((requestOptions || {}).headers || {}) 81 | } 82 | } 83 | ) 84 | ), 85 | 86 | ...postFilesChunks.map((chunk, index) => { 87 | const postChunkFormData = new FormData(); 88 | chunk.forEach(file => postChunkFormData.append("files[]", file)); 89 | 90 | return send( 91 | url, 'POST', postChunkFormData, {}, 'json', 92 | { 93 | ...requestOptions, 94 | onUploadProgress: handleUploadProgress(`post-chunk-${index}`), 95 | headers: { 96 | 'Content-Type': 'multipart/form-data', 97 | ...((requestOptions || {}).headers || {}) 98 | } 99 | } 100 | ); 101 | }) 102 | ]; 103 | 104 | return Promise.all(promises); 105 | }; 106 | 107 | 108 | /** 109 | * @param {number} bytes 110 | */ 111 | function bytesToMb(bytes) { 112 | return bytes / 1000000; 113 | } 114 | 115 | /** 116 | * @param {File[]} fileList 117 | */ 118 | function filesTotalBytesSize(fileList = []) { 119 | return fileList.map(file => file.size).reduce((a, b) => a + b) 120 | } 121 | -------------------------------------------------------------------------------- /projects/react-plugin/utils/global.utils.js: -------------------------------------------------------------------------------- 1 | import CONFIG from '../config'; 2 | import { isDefined } from './helper.utils'; 3 | import MobileDetect from 'mobile-detect'; 4 | import { getBaseAPI } from '../services/api.service'; 5 | 6 | 7 | const prepareConfig = (config = {}, onUpload = () => {}) => { 8 | const container = config.container || config.CONTAINER || CONFIG.container || ''; 9 | const platform = config.platform || 'filerobot'; 10 | const baseAPI = config.baseAPI; 11 | 12 | config.tagging = config.tagging || config.TAGGING || CONFIG.tagging || {}; 13 | config.modules = config.modules || []; 14 | config.myGallery = config.myGallery || {}; 15 | 16 | // supporting old config 17 | const uploadKey = config.filerobotUploadKey || config.airstoreUploadKey || config.AIRSTORE_UPLOAD_KEY; 18 | const md = new MobileDetect(window.navigator.userAgent); 19 | 20 | return { 21 | mobile: md.mobile(), 22 | container, 23 | platform, 24 | baseAPI, 25 | uploadPath: `${getBaseAPI(baseAPI, container, platform)}upload`, 26 | uploadParams: config.uploadParams || config.UPLOAD_PARAMS || CONFIG.uploadParams, 27 | uploadKey: uploadKey || CONFIG.airstoreUploadKey, 28 | openpixKey: config.openpixKey || config.OPENPIX_KEY || CONFIG.openpixKey, 29 | isShowAddTagBtn: isDefined(config.isShowAddTagBtn) ? config.isShowAddTagBtn : CONFIG.isShowAddTagBtn, 30 | isShowNotRelevantBtn: isDefined(config.isShowNotRelevantBtn) ? config.isShowNotRelevantBtn : CONFIG.isShowNotRelevantBtn, 31 | limit: config.limitImagesPerResponse || config.LIMIT_IMAGES_PER_RESPONSE || CONFIG.limitImagesPerResponse || 100, 32 | folderBrowser: processFolderBrowserParams(config.folderBrowser, CONFIG.folderBrowser), 33 | preUploadImageProcess: isDefined(config.preUploadImageProcess) ? config.preUploadImageProcess : false, 34 | processBeforeUpload: isDefined(config.processBeforeUpload) ? config.processBeforeUpload : null, 35 | language: isDefined(config.language) ? config.language : CONFIG.language, 36 | cloudimageToken: config.cloudimageToken || CONFIG.cloudimageToken, 37 | searchPhrase: config.searchPhrase, 38 | imageEditor: { 39 | active: config.modules.includes('IMAGE_EDITOR'), 40 | ...config.imageEditor 41 | }, 42 | tagging: { 43 | active: config.modules.includes('TAGGING'), 44 | ...config.tagging, 45 | suggestionList: config.tagging.suggestionList && config.tagging.suggestionList.length ? config.tagging.suggestionList.map(tag => ({ name: tag }) ) : [], 46 | }, 47 | autoCropSuggestions: config.autoCropSuggestions || false, 48 | 49 | uploadHandler: onUpload, 50 | 51 | imageEditorConfig: config.imageEditorConfig || {}, 52 | 53 | myGallery: { 54 | upload: isDefined(config.myGallery.upload) ? config.myGallery.upload : true 55 | }, 56 | 57 | sortParams: { 58 | ...CONFIG.sortParams, 59 | ...config.sortParams 60 | }, 61 | extensions: config.extensions || [], 62 | modifyURLButton: isDefined(config.modifyURLButton) ? config.modifyURLButton : true, 63 | deleteButton: isDefined(config.deleteButton) ? config.deleteButton : true, 64 | description: isDefined(config.description) ? config.description : true 65 | }; 66 | }; 67 | 68 | function processFolderBrowserParams(userParams, configParams) { 69 | if (typeof userParams === 'object') { 70 | return { 71 | ...configParams, 72 | ...userParams 73 | } 74 | } else if (typeof userParams === 'boolean') { 75 | return { 76 | ...configParams, 77 | show: userParams 78 | } 79 | } else { 80 | return configParams; 81 | } 82 | } 83 | 84 | export { 85 | prepareConfig 86 | } 87 | -------------------------------------------------------------------------------- /projects/react-plugin/utils/helper.utils.js: -------------------------------------------------------------------------------- 1 | import { METADATA_VERSIONS } from './metadata.constants'; 2 | 3 | 4 | const deepCopy = object => JSON.parse(JSON.stringify(object)); 5 | 6 | const cursorToEnd = contentEditableElement => { 7 | let range, selection; 8 | if (document.createRange) // Firefox, Chrome, Opera, Safari, IE 9+ 9 | { 10 | range = document.createRange(); // Create a range (a range is a like the selection but invisible) 11 | range.selectNodeContents(contentEditableElement); // Select the entire contents of the element with the range 12 | range.collapse(false); // collapse the range to the end point. false means collapse to end rather than the start 13 | selection = window.getSelection(); // get the selection object (allows you to change selection) 14 | selection.removeAllRanges(); // remove any selections already made 15 | selection.addRange(range); // make the range you have just created the visible selection 16 | } 17 | else if (document.selection) // IE 8 and lower 18 | { 19 | range = document.body.createTextRange(); // Create a range (a range is a like the selection but invisible) 20 | range.moveToElementText(contentEditableElement); // Select the entire contents of the element with the range 21 | range.collapse(false); // collapse the range to the end point. false means collapse to end rather than the start 22 | range.select(); // Select the range (make it the visible selection 23 | } 24 | }; 25 | 26 | const isEnterClick = event => event && (event.which || event.keyCode) === 13; 27 | 28 | const isEsc= event => event && (event.which || event.keyCode) === 27; 29 | 30 | const uniqueArrayOfStrings = array => array.filter((v, i, a) => a.indexOf(v) === i); 31 | 32 | const getTags = (isOneFile, file, tags, personalTags) => isOneFile ? 33 | tags 34 | : 35 | uniqueArrayOfStrings([...tags, ...personalTags[file.uuid] || []]); 36 | 37 | const uniqueArrayOfStringsInObject = object => { 38 | const nextObject = {}; 39 | Object.keys(object).forEach(key => nextObject[key] = object[key].filter((v, i, a) => a.indexOf(v) === i)) 40 | 41 | return nextObject; 42 | }; 43 | 44 | const nonUniqueArrayOfStrings = (tags, itemAmounts) => { 45 | const commonTags = []; 46 | const duplicates = tags.reduce((tag, value) => ({ 47 | ...tag, 48 | [value]: (tag[value] || 0) + 1 49 | }), {}); 50 | 51 | Object.keys(duplicates).forEach(key => { 52 | if (duplicates[key] === itemAmounts) commonTags.push(key) 53 | }); 54 | 55 | return commonTags; 56 | } 57 | 58 | const isDefined = param => typeof param !== 'undefined'; 59 | 60 | const encodePermalink = link => link; // link.replace(/\?/g, '%3F'); 61 | 62 | const checkIsEDGYMetadataVersion = metadataVersion => metadataVersion === 63 | (METADATA_VERSIONS.M1_EDGY 64 | || 65 | metadataVersion === METADATA_VERSIONS.DEPRECATED_V1_EDGY); 66 | 67 | export { 68 | uniqueArrayOfStrings, 69 | nonUniqueArrayOfStrings, 70 | uniqueArrayOfStringsInObject, 71 | isEnterClick, 72 | cursorToEnd, 73 | deepCopy, 74 | isEsc, 75 | isDefined, 76 | encodePermalink, 77 | getTags, 78 | checkIsEDGYMetadataVersion 79 | } 80 | -------------------------------------------------------------------------------- /projects/react-plugin/utils/icons.utils.js: -------------------------------------------------------------------------------- 1 | // We can get images here: https://www.flaticon.com/packs/file-types 2 | const fileTypeIcons = { 3 | _default: 'https://image.flaticon.com/icons/png/128/136/136549.png', 4 | png: 'https://image.flaticon.com/icons/png/128/136/136523.png', 5 | jpeg: 'https://image.flaticon.com/icons/png/128/136/136524.png', 6 | svg: 'https://image.flaticon.com/icons/png/128/136/136537.png', 7 | html: 'https://image.flaticon.com/icons/png/128/136/136528.png', 8 | photoshop: 'https://image.flaticon.com/icons/png/128/136/136535.png', 9 | txt: 'https://image.flaticon.com/icons/png/128/136/136538.png', 10 | js: 'https://image.flaticon.com/icons/png/128/136/136530.png', 11 | css: 'https://image.flaticon.com/icons/png/128/136/136527.png', 12 | xml: 'https://image.flaticon.com/icons/png/128/136/136526.png', 13 | zip: 'https://image.flaticon.com/icons/png/128/136/136544.png', 14 | json: 'https://image.flaticon.com/icons/png/128/136/136525.png', 15 | csv: 'https://image.flaticon.com/icons/png/128/136/136534.png', 16 | avi: 'https://image.flaticon.com/icons/png/128/136/136546.png', 17 | mp4: 'https://image.flaticon.com/icons/png/128/136/136545.png', 18 | mp3: 'https://image.flaticon.com/icons/png/128/136/136548.png', 19 | iso: 'https://image.flaticon.com/icons/png/128/136/136541.png', 20 | exe: 'https://image.flaticon.com/icons/png/128/136/136531.png', 21 | rtf: 'https://image.flaticon.com/icons/png/128/136/136539.png', 22 | dbf: 'https://image.flaticon.com/icons/png/128/136/136533.png', 23 | dwg: 'https://image.flaticon.com/icons/png/128/136/136542.png', 24 | fla: 'https://image.flaticon.com/icons/png/128/136/136547.png', 25 | }; 26 | 27 | const getFileType = fullType => { 28 | const availableTypes = Object.keys(fileTypeIcons).filter(_type => _type !== '_default'); 29 | const checkType = _type => { 30 | if (fullType === 'text/plain' && _type === 'txt') return true; 31 | 32 | return (new RegExp(_type)).test(fullType); 33 | }; 34 | 35 | return fullType ? availableTypes.find(_type => checkType(_type)) : null; 36 | }; 37 | 38 | const getFileIconSrcByType = type => { 39 | const fileType = getFileType(type); 40 | 41 | return fileTypeIcons[fileType] || fileTypeIcons._default; 42 | }; 43 | 44 | const isImage = type => type.indexOf('image') > -1; 45 | 46 | const isAllImages = files => files.every(file => file.type.indexOf('image') > -1); 47 | 48 | export { 49 | getFileIconSrcByType, 50 | getFileType, 51 | isImage, 52 | isAllImages 53 | } -------------------------------------------------------------------------------- /projects/react-plugin/utils/imageTransformation.utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Convert a base64 string in a Blob according to the data and contentType. 3 | * 4 | * @param b64Data {String} Pure base64 string without contentType 5 | * @param contentType {String} the content type of the file i.e (image/jpeg - image/png - text/plain) 6 | * @param sliceSize {Int} SliceSize to process the byteCharacters 7 | * @see http://stackoverflow.com/questions/16245767/creating-a-blob-from-a-base64-string-in-javascript 8 | * @return Blob 9 | */ 10 | function b64toBlob(b64Data, contentType, sliceSize) { 11 | contentType = contentType || ''; 12 | sliceSize = sliceSize || 512; 13 | 14 | const byteCharacters = atob(b64Data); 15 | const byteArrays = []; 16 | 17 | for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) { 18 | const slice = byteCharacters.slice(offset, offset + sliceSize); 19 | 20 | const byteNumbers = new Array(slice.length); 21 | for (let i = 0; i < slice.length; i++) { 22 | byteNumbers[i] = slice.charCodeAt(i); 23 | } 24 | 25 | const byteArray = new Uint8Array(byteNumbers); 26 | 27 | byteArrays.push(byteArray); 28 | } 29 | 30 | return new Blob(byteArrays, { type: contentType }); 31 | } 32 | 33 | export { b64toBlob }; -------------------------------------------------------------------------------- /projects/react-plugin/utils/index.js: -------------------------------------------------------------------------------- 1 | export * from './helper.utils'; 2 | -------------------------------------------------------------------------------- /projects/react-plugin/utils/md5.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* 4 | * Add integers, wrapping at 2^32. This uses 16-bit operations internally 5 | * to work around bugs in some JS interpreters. 6 | */ 7 | function safeAdd (x, y) { 8 | var lsw = (x & 0xffff) + (y & 0xffff) 9 | var msw = (x >> 16) + (y >> 16) + (lsw >> 16) 10 | return (msw << 16) | (lsw & 0xffff) 11 | } 12 | 13 | /* 14 | * Bitwise rotate a 32-bit number to the left. 15 | */ 16 | function bitRotateLeft (num, cnt) { 17 | return (num << cnt) | (num >>> (32 - cnt)) 18 | } 19 | 20 | /* 21 | * These functions implement the four basic operations the algorithm uses. 22 | */ 23 | function md5cmn (q, a, b, x, s, t) { 24 | return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b) 25 | } 26 | function md5ff (a, b, c, d, x, s, t) { 27 | return md5cmn((b & c) | (~b & d), a, b, x, s, t) 28 | } 29 | function md5gg (a, b, c, d, x, s, t) { 30 | return md5cmn((b & d) | (c & ~d), a, b, x, s, t) 31 | } 32 | function md5hh (a, b, c, d, x, s, t) { 33 | return md5cmn(b ^ c ^ d, a, b, x, s, t) 34 | } 35 | function md5ii (a, b, c, d, x, s, t) { 36 | return md5cmn(c ^ (b | ~d), a, b, x, s, t) 37 | } 38 | 39 | /* 40 | * Calculate the MD5 of an array of little-endian words, and a bit length. 41 | */ 42 | function binlMD5 (x, len) { 43 | /* append padding */ 44 | x[len >> 5] |= 0x80 << (len % 32) 45 | x[((len + 64) >>> 9 << 4) + 14] = len 46 | 47 | var i 48 | var olda 49 | var oldb 50 | var oldc 51 | var oldd 52 | var a = 1732584193 53 | var b = -271733879 54 | var c = -1732584194 55 | var d = 271733878 56 | 57 | for (i = 0; i < x.length; i += 16) { 58 | olda = a 59 | oldb = b 60 | oldc = c 61 | oldd = d 62 | 63 | a = md5ff(a, b, c, d, x[i], 7, -680876936) 64 | d = md5ff(d, a, b, c, x[i + 1], 12, -389564586) 65 | c = md5ff(c, d, a, b, x[i + 2], 17, 606105819) 66 | b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330) 67 | a = md5ff(a, b, c, d, x[i + 4], 7, -176418897) 68 | d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426) 69 | c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341) 70 | b = md5ff(b, c, d, a, x[i + 7], 22, -45705983) 71 | a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416) 72 | d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417) 73 | c = md5ff(c, d, a, b, x[i + 10], 17, -42063) 74 | b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162) 75 | a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682) 76 | d = md5ff(d, a, b, c, x[i + 13], 12, -40341101) 77 | c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290) 78 | b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329) 79 | 80 | a = md5gg(a, b, c, d, x[i + 1], 5, -165796510) 81 | d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632) 82 | c = md5gg(c, d, a, b, x[i + 11], 14, 643717713) 83 | b = md5gg(b, c, d, a, x[i], 20, -373897302) 84 | a = md5gg(a, b, c, d, x[i + 5], 5, -701558691) 85 | d = md5gg(d, a, b, c, x[i + 10], 9, 38016083) 86 | c = md5gg(c, d, a, b, x[i + 15], 14, -660478335) 87 | b = md5gg(b, c, d, a, x[i + 4], 20, -405537848) 88 | a = md5gg(a, b, c, d, x[i + 9], 5, 568446438) 89 | d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690) 90 | c = md5gg(c, d, a, b, x[i + 3], 14, -187363961) 91 | b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501) 92 | a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467) 93 | d = md5gg(d, a, b, c, x[i + 2], 9, -51403784) 94 | c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473) 95 | b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734) 96 | 97 | a = md5hh(a, b, c, d, x[i + 5], 4, -378558) 98 | d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463) 99 | c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562) 100 | b = md5hh(b, c, d, a, x[i + 14], 23, -35309556) 101 | a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060) 102 | d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353) 103 | c = md5hh(c, d, a, b, x[i + 7], 16, -155497632) 104 | b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640) 105 | a = md5hh(a, b, c, d, x[i + 13], 4, 681279174) 106 | d = md5hh(d, a, b, c, x[i], 11, -358537222) 107 | c = md5hh(c, d, a, b, x[i + 3], 16, -722521979) 108 | b = md5hh(b, c, d, a, x[i + 6], 23, 76029189) 109 | a = md5hh(a, b, c, d, x[i + 9], 4, -640364487) 110 | d = md5hh(d, a, b, c, x[i + 12], 11, -421815835) 111 | c = md5hh(c, d, a, b, x[i + 15], 16, 530742520) 112 | b = md5hh(b, c, d, a, x[i + 2], 23, -995338651) 113 | 114 | a = md5ii(a, b, c, d, x[i], 6, -198630844) 115 | d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415) 116 | c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905) 117 | b = md5ii(b, c, d, a, x[i + 5], 21, -57434055) 118 | a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571) 119 | d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606) 120 | c = md5ii(c, d, a, b, x[i + 10], 15, -1051523) 121 | b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799) 122 | a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359) 123 | d = md5ii(d, a, b, c, x[i + 15], 10, -30611744) 124 | c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380) 125 | b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649) 126 | a = md5ii(a, b, c, d, x[i + 4], 6, -145523070) 127 | d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379) 128 | c = md5ii(c, d, a, b, x[i + 2], 15, 718787259) 129 | b = md5ii(b, c, d, a, x[i + 9], 21, -343485551) 130 | 131 | a = safeAdd(a, olda) 132 | b = safeAdd(b, oldb) 133 | c = safeAdd(c, oldc) 134 | d = safeAdd(d, oldd) 135 | } 136 | return [a, b, c, d] 137 | } 138 | 139 | /* 140 | * Convert an array of little-endian words to a string 141 | */ 142 | function binl2rstr (input) { 143 | var i 144 | var output = '' 145 | var length32 = input.length * 32 146 | for (i = 0; i < length32; i += 8) { 147 | output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xff) 148 | } 149 | return output 150 | } 151 | 152 | /* 153 | * Convert a raw string to an array of little-endian words 154 | * Characters >255 have their high-byte silently ignored. 155 | */ 156 | function rstr2binl (input) { 157 | var i 158 | var output = [] 159 | output[(input.length >> 2) - 1] = undefined 160 | for (i = 0; i < output.length; i += 1) { 161 | output[i] = 0 162 | } 163 | var length8 = input.length * 8 164 | for (i = 0; i < length8; i += 8) { 165 | output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << (i % 32) 166 | } 167 | return output 168 | } 169 | 170 | /* 171 | * Calculate the MD5 of a raw string 172 | */ 173 | function rstrMD5 (s) { 174 | return binl2rstr(binlMD5(rstr2binl(s), s.length * 8)) 175 | } 176 | 177 | /* 178 | * Calculate the HMAC-MD5, of a key and some data (raw strings) 179 | */ 180 | function rstrHMACMD5 (key, data) { 181 | var i 182 | var bkey = rstr2binl(key) 183 | var ipad = [] 184 | var opad = [] 185 | var hash 186 | ipad[15] = opad[15] = undefined 187 | if (bkey.length > 16) { 188 | bkey = binlMD5(bkey, key.length * 8) 189 | } 190 | for (i = 0; i < 16; i += 1) { 191 | ipad[i] = bkey[i] ^ 0x36363636 192 | opad[i] = bkey[i] ^ 0x5c5c5c5c 193 | } 194 | hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8) 195 | return binl2rstr(binlMD5(opad.concat(hash), 512 + 128)) 196 | } 197 | 198 | /* 199 | * Convert a raw string to a hex string 200 | */ 201 | function rstr2hex (input) { 202 | var hexTab = '0123456789abcdef' 203 | var output = '' 204 | var x 205 | var i 206 | for (i = 0; i < input.length; i += 1) { 207 | x = input.charCodeAt(i) 208 | output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f) 209 | } 210 | return output 211 | } 212 | 213 | /* 214 | * Encode a string as utf-8 215 | */ 216 | function str2rstrUTF8 (input) { 217 | return unescape(encodeURIComponent(input)) 218 | } 219 | 220 | /* 221 | * Take string arguments and return either raw or hex encoded strings 222 | */ 223 | function rawMD5 (s) { 224 | return rstrMD5(str2rstrUTF8(s)) 225 | } 226 | function hexMD5 (s) { 227 | return rstr2hex(rawMD5(s)) 228 | } 229 | function rawHMACMD5 (k, d) { 230 | return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d)) 231 | } 232 | function hexHMACMD5 (k, d) { 233 | return rstr2hex(rawHMACMD5(k, d)) 234 | } 235 | 236 | function md5 (string, key, raw) { 237 | if (!key) { 238 | if (!raw) { 239 | return hexMD5(string) 240 | } 241 | return rawMD5(string) 242 | } 243 | if (!raw) { 244 | return hexHMACMD5(key, string) 245 | } 246 | return rawHMACMD5(key, string) 247 | } 248 | 249 | export default md5; -------------------------------------------------------------------------------- /projects/react-plugin/utils/metadata.constants.js: -------------------------------------------------------------------------------- 1 | export const METADATA_VERSIONS = { 2 | M0_LEGACY: "M0_LEGACY", 3 | M1_EDGY: "M1_EDGY", 4 | DEPRECATED_V0_LEGACY: "V0_LEGACY", 5 | DEPRECATED_V1_EDGY: "v1.EDGY", 6 | }; 7 | --------------------------------------------------------------------------------