From 972ceac2e878259498dd07107d1d89f6d0dba232 Mon Sep 17 00:00:00 2001 From: Mohd Imran Date: Fri, 30 Oct 2020 17:10:39 +0530 Subject: [PATCH] add file from url feature completed --- client/constants.js | 4 + client/modules/IDE/actions/files.js | 37 +++++- client/modules/IDE/actions/ide.js | 14 +++ client/modules/IDE/components/FileNode.jsx | 46 +++++--- client/modules/IDE/components/Sidebar.jsx | 57 ++++++--- .../IDE/components/UploadFileByURLForm.jsx | 71 ++++++++++++ .../IDE/components/UploadFileByURLModal.jsx | 109 ++++++++++++++++++ client/modules/IDE/pages/IDEView.jsx | 11 ++ client/modules/IDE/reducers/ide.js | 8 +- client/styles/components/_modal.scss | 25 +++- translations/locales/en-US/translations.json | 20 +++- translations/locales/es-419/translations.json | 21 +++- 12 files changed, 387 insertions(+), 36 deletions(-) create mode 100644 client/modules/IDE/components/UploadFileByURLForm.jsx create mode 100644 client/modules/IDE/components/UploadFileByURLModal.jsx diff --git a/client/constants.js b/client/constants.js index 13c74e0504..2aba15cecf 100644 --- a/client/constants.js +++ b/client/constants.js @@ -51,6 +51,7 @@ export const SHOW_MODAL = 'SHOW_MODAL'; export const HIDE_MODAL = 'HIDE_MODAL'; export const CREATE_FILE = 'CREATE_FILE'; export const SET_BLOB_URL = 'SET_BLOB_URL'; +export const UPLOAD_FILE_BY_URL = 'UPLOAD_FILE_BY_URL'; export const EXPAND_SIDEBAR = 'EXPAND_SIDEBAR'; export const COLLAPSE_SIDEBAR = 'COLLAPSE_SIDEBAR'; @@ -84,6 +85,9 @@ export const SHOW_FOLDER_CHILDREN = 'SHOW_FOLDER_CHILDREN'; export const HIDE_FOLDER_CHILDREN = 'HIDE_FOLDER_CHILDREN'; export const OPEN_UPLOAD_FILE_MODAL = 'OPEN_UPLOAD_FILE_MODAL'; export const CLOSE_UPLOAD_FILE_MODAL = 'CLOSE_UPLOAD_FILE_MODAL'; +// Updates +export const OPEN_UPLOAD_FILE_BY_URL_MODAL = 'OPEN_UPLOAD_FILE_BY_URL_MODAL'; +export const CLOSE_UPLOAD_FILE_BY_URL_MODAL = 'CLOSE_UPLOAD_FILE_BY_URL_MODAL'; export const SHOW_SHARE_MODAL = 'SHOW_SHARE_MODAL'; export const CLOSE_SHARE_MODAL = 'CLOSE_SHARE_MODAL'; diff --git a/client/modules/IDE/actions/files.js b/client/modules/IDE/actions/files.js index c8a656c83c..6b8b1f8a21 100644 --- a/client/modules/IDE/actions/files.js +++ b/client/modules/IDE/actions/files.js @@ -3,8 +3,10 @@ import blobUtil from 'blob-util'; import { reset } from 'redux-form'; import apiClient from '../../../utils/apiClient'; import * as ActionTypes from '../../../constants'; -import { setUnsavedChanges, closeNewFolderModal, closeNewFileModal } from './ide'; +import { setUnsavedChanges, closeNewFolderModal, closeNewFileModal, closeUploadFileByURLModal } from './ide'; import { setProjectSavedTime } from './project'; +import { CREATE_FILE_REGEX } from '../../../../server/utils/fileUtils'; +import { showToast, setToastText } from './toast'; function appendToFilename(filename, string) { @@ -92,6 +94,39 @@ export function createFile(formProps) { }; } +export function uploadFileByURL(data) { + return (dispatch, getState) => { + const fileUrlArray = data.url.split('/'); + const fileName = fileUrlArray[fileUrlArray.length - 1]; + const file = { + name: fileName + }; + if (fileName.match(CREATE_FILE_REGEX)) { + fetch(data.url, { + method: 'GET' + }) + .then(r => r.text()) + .then((res) => { + file.content = res; + createFile(file)(dispatch, getState); + dispatch(showToast(2000)); + dispatch(setToastText('UploadFileByURL.Success')); + dispatch(closeUploadFileByURLModal()); + }) + .catch((err) => { + dispatch(showToast(2000)); + dispatch(setToastText('UploadFileByURL.Error')); + }); + } else { + file.url = data.url; + createFile(file)(dispatch, getState); + dispatch(showToast(2000)); + dispatch(setToastText('UploadFileByURL.Success')); + dispatch(closeUploadFileByURLModal()); + } + }; +} + export function createFolder(formProps) { return (dispatch, getState) => { const state = getState(); diff --git a/client/modules/IDE/actions/ide.js b/client/modules/IDE/actions/ide.js index 239dc6c757..33a8d9d8be 100644 --- a/client/modules/IDE/actions/ide.js +++ b/client/modules/IDE/actions/ide.js @@ -88,6 +88,20 @@ export function closeUploadFileModal() { }; } +export function openUploadFileByURLModal(parentId) { + return { + type: ActionTypes.OPEN_UPLOAD_FILE_BY_URL_MODAL, + parentId + }; +} + +export function closeUploadFileByURLModal() { + return { + type: ActionTypes.CLOSE_UPLOAD_FILE_BY_URL_MODAL + }; +} + + export function expandSidebar() { return { type: ActionTypes.EXPAND_SIDEBAR diff --git a/client/modules/IDE/components/FileNode.jsx b/client/modules/IDE/components/FileNode.jsx index f4b8166d31..3f0bd64c50 100644 --- a/client/modules/IDE/components/FileNode.jsx +++ b/client/modules/IDE/components/FileNode.jsx @@ -153,6 +153,11 @@ class FileNode extends React.Component { setTimeout(this.hideFileOptions, 0); } + handleClickUploadFileByURL = () => { + this.props.openUploadFileByURLModal(this.props.id); + setTimeout(this.hideFileOptions, 0); + } + handleClickDelete = () => { const prompt = this.props.t('Common.DeleteConfirmation', { name: this.props.name }); @@ -248,12 +253,12 @@ class FileNode extends React.Component { { !isRoot &&
- { isFile && + {isFile && } - { isFolder && + {isFolder &&
- + {this.props.authenticated && + +
  • + +
  • +
  • + +
  • +
    } } @@ -393,6 +410,7 @@ FileNode.propTypes = { hideFolderChildren: PropTypes.func.isRequired, canEdit: PropTypes.bool.isRequired, openUploadFileModal: PropTypes.func.isRequired, + openUploadFileByURLModal: PropTypes.func.isRequired, authenticated: PropTypes.bool.isRequired, t: PropTypes.func.isRequired, onClickFile: PropTypes.func diff --git a/client/modules/IDE/components/Sidebar.jsx b/client/modules/IDE/components/Sidebar.jsx index 5d4802788f..92b1e0048d 100644 --- a/client/modules/IDE/components/Sidebar.jsx +++ b/client/modules/IDE/components/Sidebar.jsx @@ -2,9 +2,11 @@ import PropTypes from 'prop-types'; import React from 'react'; import classNames from 'classnames'; import { withTranslation } from 'react-i18next'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; import ConnectedFileNode from './FileNode'; - +import { openUploadFileByURLModal } from '../actions/ide'; import DownArrowIcon from '../../../images/down-filled-triangle.svg'; class Sidebar extends React.Component { @@ -116,19 +118,34 @@ class Sidebar extends React.Component { { this.props.user.authenticated && -
  • - -
  • + +
  • + +
  • +
  • + +
  • +
    }
    @@ -155,6 +172,8 @@ Sidebar.propTypes = { closeProjectOptions: PropTypes.func.isRequired, newFolder: PropTypes.func.isRequired, openUploadFileModal: PropTypes.func.isRequired, + // Updates + openUploadFileByURLModal: PropTypes.func.isRequired, owner: PropTypes.shape({ id: PropTypes.string }), @@ -169,4 +188,12 @@ Sidebar.defaultProps = { owner: undefined }; -export default withTranslation()(Sidebar); +function mapStateToProps(state) { + return {}; +} + +function mapDispatchToProps(dispatch) { + return bindActionCreators({ openUploadFileByURLModal }, dispatch); +} + +export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(Sidebar)); diff --git a/client/modules/IDE/components/UploadFileByURLForm.jsx b/client/modules/IDE/components/UploadFileByURLForm.jsx new file mode 100644 index 0000000000..0e4213f626 --- /dev/null +++ b/client/modules/IDE/components/UploadFileByURLForm.jsx @@ -0,0 +1,71 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { withTranslation } from 'react-i18next'; +import { domOnlyProps } from '../../../utils/reduxFormUtils'; + + +import Button from '../../../common/Button'; + +class UploadFileByURLForm extends React.Component { + constructor(props) { + super(props); + this.createFile = this.props.createFile.bind(this); + } + + componentDidMount() { + this.fileName.focus(); + } + + + render() { + const { + fields: { url }, + handleSubmit, + } = this.props; + return ( +
    { + this.props.focusOnModal(); + handleSubmit(this.props.uploadFileByURL)(data); + }} + > +
    + + { this.fileName = element; }} + {...domOnlyProps(url)} + /> + +
    + {url.touched && url.error && ( + {url.error} + )} +
    + ); + } +} + +UploadFileByURLForm.propTypes = { + fields: PropTypes.shape({ + url: PropTypes.object.isRequired + }).isRequired, + handleSubmit: PropTypes.func.isRequired, + createFile: PropTypes.func.isRequired, + uploadFileByURL: PropTypes.func.isRequired, + focusOnModal: PropTypes.func.isRequired, + t: PropTypes.func.isRequired +}; + +export default withTranslation()(UploadFileByURLForm); diff --git a/client/modules/IDE/components/UploadFileByURLModal.jsx b/client/modules/IDE/components/UploadFileByURLModal.jsx new file mode 100644 index 0000000000..177fd77d81 --- /dev/null +++ b/client/modules/IDE/components/UploadFileByURLModal.jsx @@ -0,0 +1,109 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { Link } from 'react-router'; +import { withTranslation } from 'react-i18next'; +import prettyBytes from 'pretty-bytes'; +import { reduxForm } from 'redux-form'; +import { bindActionCreators, compose } from 'redux'; + +import getConfig from '../../../utils/getConfig'; +import { getreachedTotalSizeLimit } from '../selectors/users'; +import ExitIcon from '../../../images/exit.svg'; + +import UploadFileByURLForm from './UploadFileByURLForm'; +import { closeUploadFileByURLModal } from '../actions/ide'; +import { createFile, uploadFileByURL } from '../actions/files'; +import { EXTERNAL_LINK_REGEX, fileExtensionsArray } from '../../../../server/utils/fileUtils'; + + +const limit = getConfig('UPLOAD_LIMIT') || 250000000; +const limitText = prettyBytes(limit); + +class UploadFileByURLModal extends React.Component { + propTypes = { + createFile: PropTypes.func.isRequired, + reachedTotalSizeLimit: PropTypes.bool.isRequired, + closeModal: PropTypes.func.isRequired, + t: PropTypes.func.isRequired + } + + componentDidMount() { + this.focusOnModal(); + } + + focusOnModal = () => { + this.modal.focus(); + } + + render() { + return ( +
    { this.modal = element; }}> +
    +
    +

    {this.props.t('UploadFileByURLModal.Title')}

    + +
    + { this.props.reachedTotalSizeLimit && +

    + {this.props.t('UploadFileByURLModal.SizeLimitError', { sizeLimit: limitText })} + assets + . +

    + } + { !this.props.reachedTotalSizeLimit && + + } +
    +
    + ); + } +} + +function mapStateToProps(state) { + return { + reachedTotalSizeLimit: getreachedTotalSizeLimit(state) + }; +} + +function mapDispatchToProps(dispatch) { + return bindActionCreators({ createFile, uploadFileByURL }, dispatch); +} + +function validate(formProps) { + const errors = {}; + + if (!formProps.url) { + errors.url = 'please enter a url !'; + } else if (!formProps.url.match(EXTERNAL_LINK_REGEX)) { + errors.url = 'please enter a valid url !'; + } else { + const fileUrl = formProps.url.split('/'); + const fileName = fileUrl[fileUrl.length - 1].split('.'); + const extn = fileName[fileName.length - 1]; + + if (!fileExtensionsArray.includes(extn)) { + errors.url = 'invalid file type'; + } + } + + return errors; +} + +export default withTranslation()(compose( + connect(mapStateToProps, mapDispatchToProps), + reduxForm({ + form: 'upload-file-by-url', + fields: ['url'], + validate + }) +)(UploadFileByURLModal)); diff --git a/client/modules/IDE/pages/IDEView.jsx b/client/modules/IDE/pages/IDEView.jsx index 38962a8e4c..bb84451d8f 100644 --- a/client/modules/IDE/pages/IDEView.jsx +++ b/client/modules/IDE/pages/IDEView.jsx @@ -14,6 +14,7 @@ import Preferences from '../components/Preferences/index'; import NewFileModal from '../components/NewFileModal'; import NewFolderModal from '../components/NewFolderModal'; import UploadFileModal from '../components/UploadFileModal'; +import UploadFileByURLModal from '../components/UploadFileByURLModal';// Update import ShareModal from '../components/ShareModal'; import KeyboardShortcutModal from '../components/KeyboardShortcutModal'; import ErrorModal from '../components/ErrorModal'; @@ -315,6 +316,8 @@ class IDEView extends React.Component { owner={this.props.project.owner} openUploadFileModal={this.props.openUploadFileModal} closeUploadFileModal={this.props.closeUploadFileModal} + openUploadFileByURLModal={this.props.openUploadFileByURLModal} + closeUploadFileByURLModal={this.props.closeUploadFileByURLModal} /> )} + {this.props.ide.uploadFileByURLModalVisible && ( + + )} {this.props.location.pathname === '/about' && ( { @@ -118,6 +119,11 @@ const ide = (state = initialState, action) => { return Object.assign({}, state, { uploadFileModalVisible: true, parentId: action.parentId }); case ActionTypes.CLOSE_UPLOAD_FILE_MODAL: return Object.assign({}, state, { uploadFileModalVisible: false }); + case ActionTypes.OPEN_UPLOAD_FILE_BY_URL_MODAL: + return Object.assign({}, state, { uploadFileByURLModalVisible: true, parentId: action.parentId }); + case ActionTypes.CLOSE_UPLOAD_FILE_BY_URL_MODAL: + return Object.assign({}, state, { uploadFileByURLModalVisible: false }); + default: return state; } diff --git a/client/styles/components/_modal.scss b/client/styles/components/_modal.scss index e59ef29991..34de6456b0 100644 --- a/client/styles/components/_modal.scss +++ b/client/styles/components/_modal.scss @@ -32,15 +32,34 @@ margin-bottom: #{20 / $base-font-size}rem; } -.new-folder-form__input-wrapper, .new-file-form__input-wrapper { +.new-folder-form__input-wrapper, +.new-file-form__input-wrapper { display: flex; } -.new-file-form__name-label, .new-folder-form__name-label { +.upload-file-by-url-form__input-wrapper, +.upload-file-by-url-form__url-label, +.upload-file-by-url-form__url-input { + display: block; +} + +.upload-file-by-url-form__url-input { + margin-top: 15px; + width: 100%; +} + +.upload-file-by-url-form__submit { + margin-top: 15px; + margin-left: auto; +} + +.new-file-form__name-label, +.new-folder-form__name-label { @extend %hidden-element; } -.new-file-form__name-input, .new-folder-form__name-input { +.new-file-form__name-input, +.new-folder-form__name-input { margin-right: #{10 / $base-font-size}rem; flex: 1; } diff --git a/translations/locales/en-US/translations.json b/translations/locales/en-US/translations.json index 0b35ee8bbd..0b2287c312 100644 --- a/translations/locales/en-US/translations.json +++ b/translations/locales/en-US/translations.json @@ -187,7 +187,9 @@ "AddFile": "Create file", "AddFileARIA": "add file", "UploadFile": "Upload file", - "UploadFileARIA": "upload file" + "UploadFileARIA": "upload file", + "UploadFileByURL": "Upload file by URL", + "UploadFileByURLARIA": "upload file by url" }, "FileNode": { "OpenFolderARIA": "Open folder contents", @@ -199,6 +201,8 @@ "AddFileARIA": "add file", "UploadFile": "Upload file", "UploadFileARIA": "upload file", + "UploadFileByURL": "Upload file by URL", + "UploadFileByURLARIA": "upload file by url", "Rename": "Rename", "Delete": "Delete" }, @@ -375,6 +379,20 @@ "CloseButtonARIA": "Close upload file modal", "SizeLimitError": "Error: You cannot upload any more files. You have reached the total size limit of {{sizeLimit}}.\n If you would like to upload more, please remove the ones you aren't using anymore by\n in your " }, + "UploadFileByURL": { + "Success": "file added !", + "Error": "Failed to fetch resource !" + }, + "UploadFileByURLModal": { + "Title": "Add a file", + "CloseButtonARIA": "Close upload file by URL modal", + "SizeLimitError": "Error: You cannot upload any more files. You have reached the total size limit of {{sizeLimit}}.\n If you would like to upload more, please remove the ones you aren't using anymore by\n in your " + }, + "UploadFileByURLForm": { + "Label": "Copy the link (URL) below", + "Placeholder": "Be sure to include the whole link", + "Submit": "Add" + }, "FileUploader": { "DictDefaultMessage": "Drop files here or click to use the file browser" }, diff --git a/translations/locales/es-419/translations.json b/translations/locales/es-419/translations.json index 227ecac5fe..f3f17a4845 100644 --- a/translations/locales/es-419/translations.json +++ b/translations/locales/es-419/translations.json @@ -187,7 +187,9 @@ "AddFile": "Crear archivo", "AddFileARIA": "agregar archivo", "UploadFile": "Subir archivo", - "UploadFileARIA": "Subir archivo" + "UploadFileARIA": "Subir archivo", + "UploadFileByURL": "Subir archivo por URL", + "UploadFileByURLARIA": "Subir archivo por URL" }, "FileNode": { "OpenFolderARIA": "Abrir contenidos del directorio", @@ -199,6 +201,8 @@ "AddFileARIA": "agregar archivo", "UploadFile": "Subir archivo", "UploadFileARIA": "Subir archivo", + "UploadFileByURL": "Subir archivo por URL", + "UploadFileByURLARIA": "subir archivo por URL", "Rename": "Renombrar", "Delete": "Borrar" }, @@ -375,6 +379,21 @@ "CloseButtonARIA": "Cerrar diálogo para subir archivo", "SizeLimitError": "Error: No puedes subir archivos. Has alcanzado el limite de tamaño total de {{sizeLimit}}.\n Si quieres agregar más,por favor remueve alugnos que no estes usando en tus " }, + "UploadFileByURL": { + "Success": "archivo agregado !", + "Error": "No se pudo recuperar el recurso !" + }, + "UploadFileByURLModal": { + "Title": "Agregar un archivo", + "CloseButtonARIA": "Cerrar diálogo para subir archivo", + "SizeLimitError": "Error: No puedes subir archivos. Has alcanzado el limite de tamaño total de {{sizeLimit}}.\n Si quieres agregar más,por favor remueve alugnos que no estes usando en tus ", + "Label": "Copie el enlace (URL) a continuación" + }, + "UploadFileByURLForm": { + "Label": "Copy the link (URL) below", + "Placeholder": "Be sure to include the whole link", + "Submit": "Add" + }, "FileUploader": { "DictDefaultMessage": "Deposita los archivos aquí o haz click para usar el navegador de archivos" },