diff options
Diffstat (limited to 'web/react/components')
-rw-r--r-- | web/react/components/admin_console/team_settings.jsx | 34 | ||||
-rw-r--r-- | web/react/components/create_comment.jsx | 24 | ||||
-rw-r--r-- | web/react/components/file_attachment.jsx | 158 | ||||
-rw-r--r-- | web/react/components/team_signup_url_page.jsx | 10 | ||||
-rw-r--r-- | web/react/components/view_image.jsx | 89 |
5 files changed, 290 insertions, 25 deletions
diff --git a/web/react/components/admin_console/team_settings.jsx b/web/react/components/admin_console/team_settings.jsx index da4299714..9ecd14a1e 100644 --- a/web/react/components/admin_console/team_settings.jsx +++ b/web/react/components/admin_console/team_settings.jsx @@ -31,6 +31,7 @@ export default class TeamSettings extends React.Component { config.TeamSettings.RestrictCreationToDomains = ReactDOM.findDOMNode(this.refs.RestrictCreationToDomains).value.trim(); config.TeamSettings.EnableTeamCreation = ReactDOM.findDOMNode(this.refs.EnableTeamCreation).checked; config.TeamSettings.EnableUserCreation = ReactDOM.findDOMNode(this.refs.EnableUserCreation).checked; + config.TeamSettings.RestrictTeamNames = ReactDOM.findDOMNode(this.refs.RestrictTeamNames).checked; var MaxUsersPerTeam = 50; if (!isNaN(parseInt(ReactDOM.findDOMNode(this.refs.MaxUsersPerTeam).value, 10))) { @@ -209,6 +210,39 @@ export default class TeamSettings extends React.Component { </div> <div className='form-group'> + <label + className='control-label col-sm-4' + htmlFor='RestrictTeamNames' + > + {'Restrict Team Names: '} + </label> + <div className='col-sm-8'> + <label className='radio-inline'> + <input + type='radio' + name='RestrictTeamNames' + value='true' + ref='RestrictTeamNames' + defaultChecked={this.props.config.TeamSettings.RestrictTeamNames} + onChange={this.handleChange} + /> + {'true'} + </label> + <label className='radio-inline'> + <input + type='radio' + name='RestrictTeamNames' + value='false' + defaultChecked={!this.props.config.TeamSettings.RestrictTeamNames} + onChange={this.handleChange} + /> + {'false'} + </label> + <p className='help-text'>{'When true, You cannot create a team name with reserved words like www, admin, support, test, channel, etc'}</p> + </div> + </div> + + <div className='form-group'> <div className='col-sm-12'> {serverError} <button diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 12d1af6ff..435c7d542 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -13,8 +13,10 @@ const MsgTyping = require('./msg_typing.jsx'); const FileUpload = require('./file_upload.jsx'); const FilePreview = require('./file_preview.jsx'); const Utils = require('../utils/utils.jsx'); + const Constants = require('../utils/constants.jsx'); const ActionTypes = Constants.ActionTypes; +const KeyCodes = Constants.KeyCodes; export default class CreateComment extends React.Component { constructor(props) { @@ -25,6 +27,7 @@ export default class CreateComment extends React.Component { this.handleSubmit = this.handleSubmit.bind(this); this.commentMsgKeyPress = this.commentMsgKeyPress.bind(this); this.handleUserInput = this.handleUserInput.bind(this); + this.handleArrowUp = this.handleArrowUp.bind(this); this.handleUploadStart = this.handleUploadStart.bind(this); this.handleFileUploadComplete = this.handleFileUploadComplete.bind(this); this.handleUploadError = this.handleUploadError.bind(this); @@ -158,6 +161,26 @@ export default class CreateComment extends React.Component { $('.post-right__scroll').perfectScrollbar('update'); this.setState({messageText: messageText}); } + handleArrowUp(e) { + if (e.keyCode === KeyCodes.UP && this.state.messageText === '') { + e.preventDefault(); + + const channelId = ChannelStore.getCurrentId(); + const lastPost = PostStore.getCurrentUsersLatestPost(channelId, this.props.rootId); + if (!lastPost) { + return; + } + + AppDispatcher.handleViewAction({ + type: ActionTypes.RECIEVED_EDIT_POST, + refocusId: '#reply_textbox', + title: 'Comment', + message: lastPost.message, + postId: lastPost.id, + channelId: lastPost.channel_id + }); + } + } handleUploadStart(clientIds) { let draft = PostStore.getCommentDraft(this.props.rootId); @@ -290,6 +313,7 @@ export default class CreateComment extends React.Component { <Textbox onUserInput={this.handleUserInput} onKeyPress={this.commentMsgKeyPress} + onKeyDown={this.handleArrowUp} messageText={this.state.messageText} createMessage='Add a comment...' initialText='' diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index c6dff6550..57cccc4e0 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -10,9 +10,12 @@ export default class FileAttachment extends React.Component { super(props); this.loadFiles = this.loadFiles.bind(this); + this.playGif = this.playGif.bind(this); + this.stopGif = this.stopGif.bind(this); + this.addBackgroundImage = this.addBackgroundImage.bind(this); this.canSetState = false; - this.state = {fileSize: -1}; + this.state = {fileSize: -1, mime: '', playing: false, loading: false, format: ''}; } componentDidMount() { this.loadFiles(); @@ -28,15 +31,9 @@ export default class FileAttachment extends React.Component { var filename = this.props.filename; if (filename) { - var fileInfo = utils.splitFileLocation(filename); + var fileInfo = this.getFileInfoFromName(filename); var type = utils.getFileType(fileInfo.ext); - // This is a temporary patch to fix issue with old files using absolute paths - if (fileInfo.path.indexOf('/api/v1/files/get') !== -1) { - fileInfo.path = fileInfo.path.split('/api/v1/files/get')[1]; - } - fileInfo.path = utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path; - if (type === 'image') { var self = this; // Need this reference since we use the given "this" $('<img/>').attr('src', fileInfo.path + '_thumb.jpg').load(function loadWrapper(path, name) { @@ -58,11 +55,7 @@ export default class FileAttachment extends React.Component { $(imgDiv).addClass('normal'); } - var re1 = new RegExp(' ', 'g'); - var re2 = new RegExp('\\(', 'g'); - var re3 = new RegExp('\\)', 'g'); - var url = path.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29'); - $(imgDiv).css('background-image', 'url(' + url + '_thumb.jpg)'); + self.addBackgroundImage(name, path); } }; }(fileInfo.path, filename)); @@ -93,6 +86,75 @@ export default class FileAttachment extends React.Component { return true; } + playGif(e, filename) { + var img = new Image(); + var fileUrl = utils.getFileUrl(filename); + + this.setState({loading: true}); + img.load(fileUrl); + img.onload = () => { + var state = {playing: true, loading: false}; + + switch (true) { + case img.width > img.height: + state.format = 'landscape'; + break; + case img.height > img.width: + state.format = 'portrait'; + break; + default: + state.format = 'quadrat'; + break; + } + + this.setState(state); + + // keep displaying background image for a short moment while browser is + // loading gif, to prevent white background flashing through + setTimeout(() => this.removeBackgroundImage.bind(this)(filename), 100); + }; + img.onError = () => this.setState({loading: false}); + + e.stopPropagation(); + } + stopGif(e, filename) { + this.setState({playing: false}); + this.addBackgroundImage(filename); + e.stopPropagation(); + } + getFileInfoFromName(name) { + var fileInfo = utils.splitFileLocation(name); + + // This is a temporary patch to fix issue with old files using absolute paths + if (fileInfo.path.indexOf('/api/v1/files/get') !== -1) { + fileInfo.path = fileInfo.path.split('/api/v1/files/get')[1]; + } + fileInfo.path = utils.getWindowLocationOrigin() + '/api/v1/files/get' + fileInfo.path; + + return fileInfo; + } + addBackgroundImage(name, path) { + var fileUrl = path; + + if (name in this.refs) { + if (!path) { + fileUrl = this.getFileInfoFromName(name).path; + } + + var imgDiv = ReactDOM.findDOMNode(this.refs[name]); + var re1 = new RegExp(' ', 'g'); + var re2 = new RegExp('\\(', 'g'); + var re3 = new RegExp('\\)', 'g'); + var url = fileUrl.replace(re1, '%20').replace(re2, '%28').replace(re3, '%29'); + + $(imgDiv).css('background-image', 'url(' + url + '_thumb.jpg)'); + } + } + removeBackgroundImage(name) { + if (name in this.refs) { + $(ReactDOM.findDOMNode(this.refs[name])).css('background-image', 'initial'); + } + } render() { var filename = this.props.filename; @@ -100,15 +162,71 @@ export default class FileAttachment extends React.Component { var fileUrl = utils.getFileUrl(filename); var type = utils.getFileType(fileInfo.ext); - var thumbnail; - if (type === 'image') { - thumbnail = ( + var playbackControls = ''; + var loadedFile = ''; + var loadingIndicator = ''; + if (this.state.mime === 'image/gif') { + playbackControls = ( <div - ref={filename} - className='post__load' - style={{backgroundImage: 'url(/static/images/load.gif)'}} + className='file-playback-controls play' + onClick={(e) => this.playGif(e, filename)} + > + {"►"} + </div> + ); + } + if (this.state.playing) { + loadedFile = ( + <img + className={'file__loaded ' + this.state.format} + src={fileUrl} + /> + ); + playbackControls = ( + <div + className='file-playback-controls stop' + onClick={(e) => this.stopGif(e, filename)} + > + {"■"} + </div> + ); + } + if (this.state.loading) { + loadingIndicator = ( + <img + className='spinner file__loading' + src='/static/images/load.gif' /> ); + playbackControls = ''; + } + + var thumbnail; + if (type === 'image') { + if (this.state.playing) { + thumbnail = ( + <div + ref={filename} + className='post__load' + style={{backgroundImage: 'url(/static/images/load.gif)'}} + > + {playbackControls} + {loadedFile} + </div> + ); + } else { + thumbnail = ( + <div + ref={filename} + className='post__load' + style={{backgroundImage: 'url(/static/images/load.gif)'}} + > + {loadingIndicator} + {playbackControls} + {loadedFile} + </div> + ); + } } else { thumbnail = <div className={'file-icon ' + utils.getIconClassName(type)}/>; } @@ -119,7 +237,7 @@ export default class FileAttachment extends React.Component { filename, function success(data) { if (this.canSetState) { - this.setState({fileSize: parseInt(data.size, 10)}); + this.setState({fileSize: parseInt(data.size, 10), mime: data.mime}); } }.bind(this), function error() {} diff --git a/web/react/components/team_signup_url_page.jsx b/web/react/components/team_signup_url_page.jsx index 67e4c9dd7..75ec2dfd9 100644 --- a/web/react/components/team_signup_url_page.jsx +++ b/web/react/components/team_signup_url_page.jsx @@ -40,10 +40,12 @@ export default class TeamSignupUrlPage extends React.Component { return; } - for (let index = 0; index < Constants.RESERVED_TEAM_NAMES.length; index++) { - if (cleanedName.indexOf(Constants.RESERVED_TEAM_NAMES[index]) === 0) { - this.setState({nameError: 'URL is taken or contains a reserved word'}); - return; + if (global.window.config.RestrictTeamNames === 'true') { + for (let index = 0; index < Constants.RESERVED_TEAM_NAMES.length; index++) { + if (cleanedName.indexOf(Constants.RESERVED_TEAM_NAMES[index]) === 0) { + this.setState({nameError: 'URL is taken or contains a reserved word'}); + return; + } } } diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index 322e68c17..bea6ce7a5 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -38,7 +38,10 @@ export default class ViewImageModal extends React.Component { progress: progress, images: {}, fileSizes: {}, - showFooter: false + fileMimes: {}, + showFooter: false, + isPlaying: {}, + isLoading: {} }; } handleNext(e) { @@ -122,6 +125,36 @@ export default class ViewImageModal extends React.Component { this.setState({loaded}); } } + playGif(e, filename, fileUrl) { + var isLoading = this.state.isLoading; + var isPlaying = this.state.isPlaying; + + isLoading[filename] = fileUrl; + this.setState({isLoading}); + + var img = new Image(); + img.load(fileUrl); + img.onload = () => { + delete isLoading[filename]; + isPlaying[filename] = fileUrl; + this.setState({isPlaying, isLoading}); + }; + img.onError = () => { + delete isLoading[filename]; + this.setState({isLoading}); + }; + + e.stopPropagation(); + e.preventDefault(); + } + stopGif(e, filename) { + var isPlaying = this.state.isPlaying; + delete isPlaying[filename]; + this.setState({isPlaying}); + + e.stopPropagation(); + e.preventDefault(); + } componentDidMount() { $(window).on('keyup', this.handleKeyPress); @@ -154,6 +187,10 @@ export default class ViewImageModal extends React.Component { var fileType = Utils.getFileType(fileInfo.ext); if (fileType === 'image') { + if (filename in this.state.isPlaying) { + return this.state.isPlaying[filename]; + } + // This is a temporary patch to fix issue with old files using absolute paths if (fileInfo.path.indexOf('/api/v1/files/get') !== -1) { fileInfo.path = fileInfo.path.split('/api/v1/files/get')[1]; @@ -189,12 +226,62 @@ export default class ViewImageModal extends React.Component { var fileType = Utils.getFileType(fileInfo.ext); if (fileType === 'image') { + if (!(filename in this.state.fileMimes)) { + Client.getFileInfo( + filename, + (data) => { + if (this.canSetState) { + var fileMimes = this.state.fileMimes; + fileMimes[filename] = data.mime; + this.setState(fileMimes); + } + }, + () => {} + ); + } + + var playbackControls = ''; + if (this.state.fileMimes[filename] === 'image/gif' && !(filename in this.state.isLoading)) { + if (filename in this.state.isPlaying) { + playbackControls = ( + <div + className='file-playback-controls stop' + onClick={(e) => this.stopGif(e, filename)} + > + {"■"} + </div> + ); + } else { + playbackControls = ( + <div + className='file-playback-controls play' + onClick={(e) => this.playGif(e, filename, fileUrl)} + > + {"►"} + </div> + ); + } + } + + var loadingIndicator = ''; + if (this.state.isLoading[filename] === fileUrl) { + loadingIndicator = ( + <img + className='spinner file__loading' + src='/static/images/load.gif' + /> + ); + playbackControls = ''; + } + // image files just show a preview of the file content = ( <a href={fileUrl} target='_blank' > + {loadingIndicator} + {playbackControls} <img style={{maxHeight: this.state.imgHeight}} ref='image' |