├── projects
├── react-plugin
│ ├── utils
│ │ ├── index.js
│ │ ├── metadata.constants.js
│ │ ├── adjustAPI.utils.js
│ │ ├── imageTransformation.utils.js
│ │ ├── icons.utils.js
│ │ ├── helper.utils.js
│ │ ├── global.utils.js
│ │ ├── files-upload.js
│ │ └── md5.js
│ ├── components
│ │ ├── hoc.js
│ │ ├── confirm-popup
│ │ │ ├── index.js
│ │ │ ├── confirm-popup.js
│ │ │ └── confirm-popup.styled.js
│ │ ├── UploadedImagesTab
│ │ │ ├── SortTick.js
│ │ │ ├── folderManager
│ │ │ │ ├── FolderItem.js
│ │ │ │ ├── bread-crumbs.js
│ │ │ │ ├── FolderManager.js
│ │ │ │ └── folderManager.styled.js
│ │ │ └── SortDropdown.js
│ │ ├── ImagesTab
│ │ │ ├── ImagesColorPicker.js
│ │ │ ├── ImageBox.js
│ │ │ └── ImagesSidebar.js
│ │ ├── IconsTab
│ │ │ ├── IconTags.js
│ │ │ ├── IconAddTagModal.js
│ │ │ ├── SearchBar.js
│ │ │ ├── IconItem.js
│ │ │ ├── IconSidebar.js
│ │ │ └── IconMonoColorSettings.js
│ │ ├── nav
│ │ │ ├── Nav.js
│ │ │ └── Nav.styled.js
│ │ ├── CloseBtn.js
│ │ ├── loadable.js
│ │ ├── AppState.js
│ │ ├── AirstoreUploaderWrapper.js
│ │ ├── ProgressCircle.js
│ │ ├── UploadImagesTab
│ │ │ ├── UserUploaderTab.utils.js
│ │ │ └── UserUploaderTab.styled.js
│ │ ├── TaggingTab
│ │ │ ├── AutosuggestionInput.js
│ │ │ └── CropsBox.js
│ │ ├── imageEditor
│ │ │ └── ImageEditorWrapper.js
│ │ ├── VirtualizedImagesGrid.js
│ │ └── Modal.js
│ ├── assets
│ │ ├── translations
│ │ │ ├── index.js
│ │ │ ├── utils.js
│ │ │ ├── en.js
│ │ │ ├── ru.js
│ │ │ └── fr.js
│ │ ├── styles
│ │ │ ├── index.js
│ │ │ ├── bg.css.js
│ │ │ ├── search.css.js
│ │ │ ├── modal.css.js
│ │ │ ├── main.css
│ │ │ ├── icons.css.js
│ │ │ ├── drag-drop.css.js
│ │ │ ├── colorScheme.js
│ │ │ ├── styles.css.js
│ │ │ ├── toastr.animation.css
│ │ │ └── toastr.css
│ │ └── fonts
│ │ │ └── filerobot-uploader.font.css
│ ├── package.json
│ ├── index.js
│ ├── styledComponents
│ │ ├── index.js
│ │ ├── AirstoreUploader.styled.js
│ │ ├── UploadedImages.styled.js
│ │ └── styleUtils.js
│ ├── polyfills.js
│ ├── services
│ │ ├── helper.service.js
│ │ ├── imageGrid.service.js
│ │ ├── imagesApi.service.js
│ │ └── iconsApi.service.js
│ └── config.js
└── js-plugin
│ └── index.js
├── .npmignore
├── .gitignore
├── examples
├── react-plugin
│ └── src
│ │ ├── index.html
│ │ └── index.js
└── js-plugin
│ ├── src
│ ├── assets
│ │ └── fonts
│ │ │ └── helvetica-neue.css
│ └── index.js
│ └── dist
│ └── filerobot-uploader-widget.image-editor.69055e3179b2e4d4342c.js
├── babel.config.js
├── LICENSE
├── config
├── webpack.demo-react-config.js
├── webpack.demo-js-config.js
└── webpack.js-config.js
├── package.json
└── CHANGELOG.md
/projects/react-plugin/utils/index.js:
--------------------------------------------------------------------------------
1 | export * from './helper.utils';
2 |
--------------------------------------------------------------------------------
/projects/react-plugin/components/hoc.js:
--------------------------------------------------------------------------------
1 | export const Aux = props => props.children;
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # .npmignore
2 | projects
3 | examples
4 | config
5 | build
6 | .babelrc
7 | .gitignore
8 | .idea
--------------------------------------------------------------------------------
/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/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "airstore-uploader-plugin",
4 | "main": "./index.js"
5 | }
--------------------------------------------------------------------------------
/projects/react-plugin/components/confirm-popup/index.js:
--------------------------------------------------------------------------------
1 | import ConfirmPopup from './confirm-popup';
2 |
3 | export default ConfirmPopup;
--------------------------------------------------------------------------------
/projects/react-plugin/index.js:
--------------------------------------------------------------------------------
1 | import AirstoreUploader from './components/AirstoreUploaderWrapper';
2 |
3 | export default AirstoreUploader;
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 }
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/examples/react-plugin/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 | Filerobot Uploader
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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;
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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/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/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 };
--------------------------------------------------------------------------------
/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 | };
--------------------------------------------------------------------------------
/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/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/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 |
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/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/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/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/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/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 | };
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 | ]
--------------------------------------------------------------------------------
/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 | });
--------------------------------------------------------------------------------
/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/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/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;
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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;
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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"))}}]);
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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 |