From 0e801a4e70f3d9c8e3cf929aa2f7ac201ca87b52 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Wed, 28 Oct 2015 15:04:17 -0400 Subject: Add tutorial intro screens for new users. --- web/react/components/navbar_dropdown.jsx | 2 +- .../components/tutorial/tutorial_intro_screens.jsx | 150 +++++++++++++++++++++ web/react/utils/constants.jsx | 2 + 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 web/react/components/tutorial/tutorial_intro_screens.jsx (limited to 'web') diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx index dc21fad21..f43bdffdf 100644 --- a/web/react/components/navbar_dropdown.jsx +++ b/web/react/components/navbar_dropdown.jsx @@ -104,7 +104,7 @@ export default class NavbarDropdown extends React.Component { ); - if (this.props.teamType === 'O') { + if (this.props.teamType === Constants.OPEN_TEAM) { teamLink = (
  • +

    {'Welcome to:'}

    +

    {'Mattermost'}

    +
    + {'Your team communications all in one place,'} +
    + {'instantly searchable and available anywhere.'} +

    + {'Keep your team connected to help them'} +
    + {'achieve what matters most.'} +

    + {'[ x ][ ][ ]'} + + ); + } + createScreenTwo() { + return ( +
    +

    {'How Mattermost works:'}

    +
    + {'Communication happens in public discussion channels,'} +
    + {'private groups and direct messages.'} +

    + {'Everything is archived and searchable from'} +
    + {'any web-enabled laptop, tablet or phone.'} +

    + {'[ ][ x ][ ]'} +
    + ); + } + createScreenThree() { + const team = TeamStore.getCurrent(); + let inviteModalLink; + if (team.type === Constants.INVITE_TEAM) { + inviteModalLink = ( +
    + {'Invite teammates'} + + ); + } else { + inviteModalLink = ( + + ); + } + + return ( +
    +

    {'You’re all set'}

    +
    + {inviteModalLink} + {' when you’re ready.'} +

    + {'Need anything, just email us at '} +
    + {'feedback@mattermost.com.'} + +

    + {'Click “Next” to enter Town Square. This is the first channel'} +
    + {'teammates see when they sign up - use it for posting updates'} +
    + {'everyone needs to know.'} +

    + {'[ ][ ][ x ]'} +
    + ); + } + render() { + const screen = this.createScreen(); + + return ( +
    + {screen} +

    + +
    + ); + } +} diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 8884d1d10..57be5046e 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -315,6 +315,8 @@ module.exports = { CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show', CATEGORY_DISPLAY_SETTINGS: 'display_settings', CATEGORY_ADVANCED_SETTINGS: 'advanced_settings' + TUTORIAL_INTRO_COMPLETE: 'tutorial_intro_complete', + TUTORIAL_POPOVERS: 'tutorial_popovers' }, KeyCodes: { UP: 38, -- cgit v1.2.3-1-g7c22 From 97449a102e5592358a4f7f22d6720a9af21286a1 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Fri, 30 Oct 2015 11:35:16 -0400 Subject: Add tutorial popovers --- web/react/components/create_post.jsx | 62 +++++++++--- web/react/components/sidebar.jsx | 68 +++++++++++-- web/react/components/sidebar_header.jsx | 89 ++++++++++++++++- .../components/tutorial/tutorial_intro_screens.jsx | 14 ++- web/react/components/tutorial/tutorial_tip.jsx | 108 +++++++++++++++++++++ web/react/utils/constants.jsx | 11 ++- web/sass-files/sass/partials/_tutorial.scss | 21 ++++ web/sass-files/sass/styles.scss | 2 +- 8 files changed, 343 insertions(+), 32 deletions(-) create mode 100644 web/react/components/tutorial/tutorial_tip.jsx create mode 100644 web/sass-files/sass/partials/_tutorial.scss (limited to 'web') diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 7c601af4b..1dc30e251 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -1,21 +1,26 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. +const MsgTyping = require('./msg_typing.jsx'); +const Textbox = require('./textbox.jsx'); +const FileUpload = require('./file_upload.jsx'); +const FilePreview = require('./file_preview.jsx'); +const TutorialTip = require('./tutorial/tutorial_tip.jsx'); + const AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); const Client = require('../utils/client.jsx'); const AsyncClient = require('../utils/async_client.jsx'); +const Utils = require('../utils/utils.jsx'); + const ChannelStore = require('../stores/channel_store.jsx'); const PostStore = require('../stores/post_store.jsx'); const UserStore = require('../stores/user_store.jsx'); -const SocketStore = require('../stores/socket_store.jsx'); const PreferenceStore = require('../stores/preference_store.jsx'); -const MsgTyping = require('./msg_typing.jsx'); -const Textbox = require('./textbox.jsx'); -const FileUpload = require('./file_upload.jsx'); -const FilePreview = require('./file_preview.jsx'); -const Utils = require('../utils/utils.jsx'); +const SocketStore = require('../stores/socket_store.jsx'); const Constants = require('../utils/constants.jsx'); +const Preferences = Constants.Preferences; +const TutorialSteps = Constants.TutorialSteps; const ActionTypes = Constants.ActionTypes; const KeyCodes = Constants.KeyCodes; @@ -36,15 +41,16 @@ export default class CreatePost extends React.Component { this.handleTextDrop = this.handleTextDrop.bind(this); this.removePreview = this.removePreview.bind(this); this.onChange = this.onChange.bind(this); + this.onPreferenceChange = this.onPreferenceChange.bind(this); this.getFileCount = this.getFileCount.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this); this.handleResize = this.handleResize.bind(this); this.sendMessage = this.sendMessage.bind(this); - this.onPreferenceChange = this.onPreferenceChange.bind(this); PostStore.clearDraftUploads(); const draft = this.getCurrentDraft(); + const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'}); this.state = { channelId: ChannelStore.getCurrentId(), @@ -55,16 +61,12 @@ export default class CreatePost extends React.Component { initialText: draft.messageText, windowWidth: Utils.windowWidth(), windowHeight: Utils.windowHeight(), - ctrlSend: PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', {value: 'false'}).value + ctrlSend: PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', {value: 'false'}).value, + showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.POST_POPOVER }; PreferenceStore.addChangeListener(this.onPreferenceChange); } - onPreferenceChange() { - this.setState({ - ctrlSend: PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', {value: 'false'}).value - }); - } handleResize() { this.setState({ windowWidth: Utils.windowWidth(), @@ -318,11 +320,13 @@ export default class CreatePost extends React.Component { } componentDidMount() { ChannelStore.addChangeListener(this.onChange); + PreferenceStore.addChangeListener(this.onPreferenceChange); this.resizePostHolder(); window.addEventListener('resize', this.handleResize); } componentWillUnmount() { ChannelStore.removeChangeListener(this.onChange); + PreferenceStore.removeChangeListener(this.onPreferenceChange); window.removeEventListener('resize', this.handleResize); } onChange() { @@ -333,6 +337,13 @@ export default class CreatePost extends React.Component { this.setState({channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, serverError: null, postError: null, previews: draft.previews, uploadsInProgress: draft.uploadsInProgress}); } } + onPreferenceChange() { + const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'}); + this.setState({ + showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.POST_POPOVER, + ctrlSend: PreferenceStore.getPreference(Constants.Preferences.CATEGORY_ADVANCED_SETTINGS, 'send_on_ctrl_enter', {value: 'false'}).value + }); + } getFileCount(channelId) { if (channelId === this.state.channelId) { return this.state.previews.length + this.state.uploadsInProgress.length; @@ -367,6 +378,25 @@ export default class CreatePost extends React.Component { }); } } + createTutorialTip() { + const screens = []; + + screens.push( +
    +

    {'Sending Messages'}

    + {'Type here to write a message.'} +

    + {'Click the attachment button to upload an image or a file.'} +
    + ); + + return ( + + ); + } render() { let serverError = null; if (this.state.serverError) { @@ -398,6 +428,11 @@ export default class CreatePost extends React.Component { postFooterClassName += ' has-error'; } + let tutorialTip = null; + if (this.state.showTutorialTip) { + tutorialTip = this.createTutorialTip(); + } + return (
    + {tutorialTip}
    {postError} diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 023955e97..c3f43ff69 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -1,19 +1,26 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -const AsyncClient = require('../utils/async_client.jsx'); -const ChannelStore = require('../stores/channel_store.jsx'); -const Client = require('../utils/client.jsx'); -const Constants = require('../utils/constants.jsx'); -const PreferenceStore = require('../stores/preference_store.jsx'); const NewChannelFlow = require('./new_channel_flow.jsx'); const MoreDirectChannels = require('./more_direct_channels.jsx'); const SearchBox = require('./search_bar.jsx'); const SidebarHeader = require('./sidebar_header.jsx'); -const TeamStore = require('../stores/team_store.jsx'); const UnreadChannelIndicator = require('./unread_channel_indicator.jsx'); +const TutorialTip = require('./tutorial/tutorial_tip.jsx'); + +const ChannelStore = require('../stores/channel_store.jsx'); const UserStore = require('../stores/user_store.jsx'); +const TeamStore = require('../stores/team_store.jsx'); +const PreferenceStore = require('../stores/preference_store.jsx'); + +const AsyncClient = require('../utils/async_client.jsx'); +const Client = require('../utils/client.jsx'); const Utils = require('../utils/utils.jsx'); + +const Constants = require('../utils/constants.jsx'); +const Preferences = Constants.Preferences; +const TutorialSteps = Constants.TutorialSteps; + const Tooltip = ReactBootstrap.Tooltip; const OverlayTrigger = ReactBootstrap.OverlayTrigger; @@ -155,12 +162,15 @@ export default class Sidebar extends React.Component { visibleDirectChannels.sort(this.sortChannelsByDisplayName); + const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'}); + return { activeId: currentId, channels: ChannelStore.getAll(), members, visibleDirectChannels, - hiddenDirectChannelCount + hiddenDirectChannelCount, + showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.CHANNEL_POPOVER }; } @@ -308,6 +318,44 @@ export default class Sidebar extends React.Component { this.setState({showDirectChannelsModal: false}); } + createTutorialTip() { + const screens = []; + + screens.push( +
    +

    {'Channels'}

    + {'Channels'}{' organize conversations across different topics. They’re open to everyone on your team. To send private communications use '}{'Direct Messages'}{' for a single person or '}{'Private Groups'}{' for multiple people.'} +
    + ); + + screens.push( +
    +

    {'"Town Square" and "Off-Topic" channels'}

    + {'Here are two public channels to start:'} +

    + {'Town Square'}{' is a place for team-wide communication. Everyone in your team is a member of this channel.'} +

    + {'Off-Topic'}{' is a place for fun and humor outside of work-related channels. You and your team can decide what other channels to create.'} +
    + ); + + screens.push( +
    +

    {'Creating and Joining Channels'}

    + {'Click '}{'"More..."'}{' to create a new channel or join an existing one.'} +

    + {'You can also create a new channel or private group by clicking the '}{'"+" symbol'}{' next to the channel or private group header.'} +
    + ); + + return ( + + ); + } + createChannelElement(channel, index, arr, handleClose) { var members = this.state.members; var activeId = this.state.activeId; @@ -444,6 +492,11 @@ export default class Sidebar extends React.Component { rowClass += ' has-close'; } + let tutorialTip = null; + if (this.state.showTutorialTip && channel.name === Constants.DEFAULT_CHANNEL) { + tutorialTip = this.createTutorialTip(); + } + return (
  • + {tutorialTip}
  • ); } diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index 65e4c6d7e..96348f688 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -1,9 +1,16 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -var NavbarDropdown = require('./navbar_dropdown.jsx'); -var UserStore = require('../stores/user_store.jsx'); +const NavbarDropdown = require('./navbar_dropdown.jsx'); +const TutorialTip = require('./tutorial/tutorial_tip.jsx'); + +const UserStore = require('../stores/user_store.jsx'); +const PreferenceStore = require('../stores/preference_store.jsx'); + const Utils = require('../utils/utils.jsx'); +const Constants = require('../utils/constants.jsx'); +const Preferences = Constants.Preferences; +const TutorialSteps = Constants.TutorialSteps; const Tooltip = ReactBootstrap.Tooltip; const OverlayTrigger = ReactBootstrap.OverlayTrigger; @@ -13,8 +20,23 @@ export default class SidebarHeader extends React.Component { super(props); this.toggleDropdown = this.toggleDropdown.bind(this); + this.onPreferenceChange = this.onPreferenceChange.bind(this); - this.state = {}; + this.state = this.getStateFromStores(); + } + componentDidMount() { + PreferenceStore.addChangeListener(this.onPreferenceChange); + } + componentWillUnmount() { + PreferenceStore.removeChangeListener(this.onPreferenceChange); + } + getStateFromStores() { + const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'}); + + return {showTutorialTip: parseInt(tutorialPref.value, 10) === TutorialSteps.MENU_POPOVER}; + } + onPreferenceChange() { + this.setState(this.getStateFromStores()); } toggleDropdown(e) { e.preventDefault(); @@ -22,8 +44,63 @@ export default class SidebarHeader extends React.Component { this.refs.dropdown.blockToggle = false; return; } + console.log(this.refs.tip); + this.refs.tip.toggle(); $('.team__header').find('.dropdown-toggle').dropdown('toggle'); } + createTutorialTip() { + const screens = []; + + let teamSettingsLink = {'Team Settings'}; + if (Utils.isAdmin(UserStore.getCurrentUser().roles)) { + teamSettingsLink = ( + + {'Team Settings'} + + ); + } + + screens.push( +
    +

    {'Sending Messages'}

    + {'The '}{'Main Menu'}{' is where you can '} + + {'Invite New Members'} + + {', access your '} + + {'Account Settings'} + + {', and set your '}{'Theme Color'}{'.'} +

    + {'Team administrators can also access their '}{teamSettingsLink}{' from this menu.'} +
    + ); + + return ( +
    + +
    + ); + } render() { var me = UserStore.getCurrentUser(); var profilePicture = null; @@ -41,8 +118,14 @@ export default class SidebarHeader extends React.Component { ); } + let tutorialTip = null; + if (this.state.showTutorialTip) { + tutorialTip = this.createTutorialTip(); + } + return (
    + {tutorialTip} = this.props.screens.length - 1) { + let preference = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'}); + + const newValue = (parseInt(preference.value, 10) + 1).toString(); + + preference = PreferenceStore.setPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), newValue); + AsyncClient.savePreferences([preference]); + } + } + handleNext() { + if (this.state.currentScreen < this.props.screens.length - 1) { + this.setState({currentScreen: this.state.currentScreen + 1}); + return; + } + + this.toggle(); + } + skipTutorial(e) { + e.preventDefault(); + const preference = PreferenceStore.setPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), '999'); + AsyncClient.savePreferences([preference]); + } + render() { + const buttonText = this.state.currentScreen === this.props.screens.length - 1 ? 'Okay' : 'Next'; + + const dots = []; + if (this.props.screens.length > 1) { + for (let i = 0; i < this.props.screens.length; i++) { + if (i === this.state.currentScreen) { + dots.push({'[ x ]'}); + } else { + dots.push({'[ ]'}); + } + } + } + + return ( + + ); + } +} + +TutorialTip.propTypes = { + screens: React.PropTypes.array.isRequired, + placement: React.PropTypes.string.isRequired +}; diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 57be5046e..fd64b1554 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -314,9 +314,14 @@ module.exports = { Preferences: { CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show', CATEGORY_DISPLAY_SETTINGS: 'display_settings', - CATEGORY_ADVANCED_SETTINGS: 'advanced_settings' - TUTORIAL_INTRO_COMPLETE: 'tutorial_intro_complete', - TUTORIAL_POPOVERS: 'tutorial_popovers' + CATEGORY_ADVANCED_SETTINGS: 'advanced_settings', + TUTORIAL_STEP: 'tutorial_step' + }, + TutorialSteps: { + INTRO_SCREENS: 0, + POST_POPOVER: 1, + CHANNEL_POPOVER: 2, + MENU_POPOVER: 3 }, KeyCodes: { UP: 38, diff --git a/web/sass-files/sass/partials/_tutorial.scss b/web/sass-files/sass/partials/_tutorial.scss new file mode 100644 index 000000000..e80abf952 --- /dev/null +++ b/web/sass-files/sass/partials/_tutorial.scss @@ -0,0 +1,21 @@ +.tip-overlay { + position:absolute; + background-color:#EEE; + box-shadow:0 5px 10px rgba(0, 0, 0, 0.2); + border:1px solid #CCC; + border-radius:3px; + padding:10px; + z-index:999; +} + +.tip-button { + height:20px; + width:20px; + z-index:998; +} + +.tip-div { + position:absolute; + top:0px; + right:0px; +} diff --git a/web/sass-files/sass/styles.scss b/web/sass-files/sass/styles.scss index ad2cae194..34f7c6eed 100644 --- a/web/sass-files/sass/styles.scss +++ b/web/sass-files/sass/styles.scss @@ -38,7 +38,7 @@ @import "partials/loading"; @import "partials/get-link"; @import "partials/markdown"; -@import "partials/statistics"; +@import "partials/tutorial"; // Responsive Css @import "partials/responsive"; -- cgit v1.2.3-1-g7c22 From 393d253021e6b119ec35b92f9eeaa6f2d255008f Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Mon, 2 Nov 2015 11:40:53 +0500 Subject: Updating tutorial screens --- web/react/components/create_post.jsx | 7 +- web/react/components/sidebar.jsx | 30 +++--- .../components/tutorial/tutorial_intro_screens.jsx | 107 +++++++++---------- web/react/components/tutorial/tutorial_tip.jsx | 51 +++++---- web/sass-files/sass/partials/_tutorial.scss | 117 +++++++++++++++++++-- web/sass-files/sass/styles.scss | 1 + web/static/images/tutorialTip.gif | Bin 0 -> 12278 bytes 7 files changed, 215 insertions(+), 98 deletions(-) create mode 100644 web/static/images/tutorialTip.gif (limited to 'web') diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 1dc30e251..cb3148b7b 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -383,10 +383,9 @@ export default class CreatePost extends React.Component { screens.push(
    -

    {'Sending Messages'}

    - {'Type here to write a message.'} -

    - {'Click the attachment button to upload an image or a file.'} +

    {'Sending Messages'}

    +

    {'Type here to write a message.'}

    +

    {'Click the attachment button to upload an image or a file.'}

    ); diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index c3f43ff69..39d9808e9 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -323,28 +323,34 @@ export default class Sidebar extends React.Component { screens.push(
    -

    {'Channels'}

    - {'Channels'}{' organize conversations across different topics. They’re open to everyone on your team. To send private communications use '}{'Direct Messages'}{' for a single person or '}{'Private Groups'}{' for multiple people.'} +

    {'Channels'}

    +

    {'Channels'}{' organize conversations across different topics. They’re open to everyone on your team. To send private communications use '}{'Direct Messages'}{' for a single person or '}{'Private Groups'}{' for multiple people.'} +

    ); screens.push(
    -

    {'"Town Square" and "Off-Topic" channels'}

    - {'Here are two public channels to start:'} -

    - {'Town Square'}{' is a place for team-wide communication. Everyone in your team is a member of this channel.'} -

    - {'Off-Topic'}{' is a place for fun and humor outside of work-related channels. You and your team can decide what other channels to create.'} +

    {'"Town Square" and "Off-Topic" channels'}

    +

    {'Here are two public channels to start:'}

    +

    + {'Town Square'}{' is a place for team-wide communication. Everyone in your team is a member of this channel.'} +

    +

    + {'Off-Topic'}{' is a place for fun and humor outside of work-related channels. You and your team can decide what other channels to create.'} +

    ); screens.push(
    -

    {'Creating and Joining Channels'}

    - {'Click '}{'"More..."'}{' to create a new channel or join an existing one.'} -

    - {'You can also create a new channel or private group by clicking the '}{'"+" symbol'}{' next to the channel or private group header.'} +

    {'Creating and Joining Channels'}

    +

    + {'Click '}{'"More..."'}{' to create a new channel or join an existing one.'} +

    +

    + {'You can also create a new channel or private group by clicking the '}{'"+" symbol'}{' next to the channel or private group header.'} +

    ); diff --git a/web/react/components/tutorial/tutorial_intro_screens.jsx b/web/react/components/tutorial/tutorial_intro_screens.jsx index d423b4f1b..b37aac27c 100644 --- a/web/react/components/tutorial/tutorial_intro_screens.jsx +++ b/web/react/components/tutorial/tutorial_intro_screens.jsx @@ -20,6 +20,10 @@ export default class TutorialIntroScreens extends React.Component { this.state = {currentScreen: 0}; } + componentDidMount() { + const height = $(window).outerHeight() - 120; + $('.tutorial-steps__container').css('height', `${height}px`); + } handleNext() { if (this.state.currentScreen < 2) { this.setState({currentScreen: this.state.currentScreen + 1}); @@ -48,35 +52,29 @@ export default class TutorialIntroScreens extends React.Component { createScreenOne() { return (
    -

    {'Welcome to:'}

    -

    {'Mattermost'}

    -
    - {'Your team communications all in one place,'} -
    - {'instantly searchable and available anywhere.'} -

    - {'Keep your team connected to help them'} -
    - {'achieve what matters most.'} -

    - {'[ x ][ ][ ]'} +

    {'Welcome to:'}

    +

    {'Mattermost'}

    +

    {'Your team communications all in one place, instantly searchable and available anywhere.'}

    +

    {'Keep your team connected to help them achieve what matters most.'}

    +
    +
    +
    +
    +
    ); } createScreenTwo() { return (
    -

    {'How Mattermost works:'}

    -
    - {'Communication happens in public discussion channels,'} -
    - {'private groups and direct messages.'} -

    - {'Everything is archived and searchable from'} -
    - {'any web-enabled laptop, tablet or phone.'} -

    - {'[ ][ x ][ ]'} +

    {'How Mattermost works:'}

    +

    {'Communication happens in public discussion channels, private groups and direct messages.'}

    +

    {'Everything is archived and searchable from any web-enabled laptop, tablet or phone.'}

    +
    +
    +
    +
    +
    ); } @@ -111,26 +109,26 @@ export default class TutorialIntroScreens extends React.Component { return (
    -

    {'You’re all set'}

    -
    - {inviteModalLink} - {' when you’re ready.'} -

    - {'Need anything, just email us at '} - - {'feedback@mattermost.com.'} - -

    - {'Click “Next” to enter Town Square. This is the first channel'} -
    - {'teammates see when they sign up - use it for posting updates'} -
    - {'everyone needs to know.'} -

    - {'[ ][ ][ x ]'} +

    {'You’re all set'}

    +

    + {inviteModalLink} + {' when you’re ready.'} +

    +

    + {'Need anything, just email us at '} + + {'feedback@mattermost.com.'} + +

    + {'Click “Next” to enter Town Square. This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.'} +
    +
    +
    +
    +
    ); } @@ -138,16 +136,19 @@ export default class TutorialIntroScreens extends React.Component { const screen = this.createScreen(); return ( -
    - {screen} -

    - +
    +
    +
    + {screen} + +
    +
    ); } diff --git a/web/react/components/tutorial/tutorial_tip.jsx b/web/react/components/tutorial/tutorial_tip.jsx index 166d10d8a..3482a7cfa 100644 --- a/web/react/components/tutorial/tutorial_tip.jsx +++ b/web/react/components/tutorial/tutorial_tip.jsx @@ -52,9 +52,19 @@ export default class TutorialTip extends React.Component { if (this.props.screens.length > 1) { for (let i = 0; i < this.props.screens.length; i++) { if (i === this.state.currentScreen) { - dots.push({'[ x ]'}); + dots.push( +
    + ); } else { - dots.push({'[ ]'}); + dots.push( +
    + ); } } } @@ -63,7 +73,8 @@ export default class TutorialTip extends React.Component {
    @@ -77,24 +88,24 @@ export default class TutorialTip extends React.Component { >
    {this.props.screens[this.state.currentScreen]} -
    - {dots} - -
    - - {'Seen this before? '} - {dots}
    +
    + +
    + {'Seen this before? '} + + {'Opt out of these tips.'} + +
    +
    diff --git a/web/sass-files/sass/partials/_tutorial.scss b/web/sass-files/sass/partials/_tutorial.scss index e80abf952..fa93e46bf 100644 --- a/web/sass-files/sass/partials/_tutorial.scss +++ b/web/sass-files/sass/partials/_tutorial.scss @@ -1,17 +1,71 @@ +.tip-backdrop { + background: rgba(black, 0.5); + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 999; +} + .tip-overlay { + width: 350px; + max-width: 90%; position:absolute; - background-color:#EEE; - box-shadow:0 5px 10px rgba(0, 0, 0, 0.2); - border:1px solid #CCC; - border-radius:3px; - padding:10px; - z-index:999; + background-color: #fff; + @include border-radius(3px); + padding: 20px; + z-index: 1000; + h4 { + font-size: em(16px); + font-weight: 600; + margin: 5px 0 15px; + } + p { + font-size: 13px; + line-height: 1.6; + strong { + font-weight: 600; + } + } + .btn { + background: #cccccc; + color: #fff; + @include border-radius(3px); + border: none; + margin-bottom: 10px; + &:hover, &:active, &:focus { + color: #fff; + border: none; + background: #bbb; + } + } + .tip-opt { + font-size: 12px; + span { + @include opacity(0.5); + } + } + .tutorial__circles { + margin: 1.5em 0px -1.7em -4px; + .circle { + width: 7px; + height: 7px; + margin: 0 4px; + &.active { + background: #000; + @include opacity(0.4); + } + } + } } .tip-button { - height:20px; - width:20px; - z-index:998; + z-index: 998; + right: -10px; + top: -10px; + position: relative; + cursor: pointer; } .tip-div { @@ -19,3 +73,48 @@ top:0px; right:0px; } + +.tutorial-steps__container { + text-align: center; + width: 100%; + display: table; + .tutorial__content { + display: table-cell; + vertical-align: middle; + padding-bottom: 100px; + padding: 0 40px; + .tutorial__steps { + max-width: 310px; + min-height: 420px; + text-align: left; + display: inline-block; + } + } + h1 { + font-size: em(40px); + margin: -20px 0 30px; + font-weight: 600; + } + h3 { + font-size: em(24px); + margin-bottom: 30px; + font-weight: 600; + } +} + +.tutorial__circles { + margin: 2em 0; + .circle { + width: 9px; + height: 9px; + @include border-radius(9px); + @include opacity(0.1); + background: #000; + display: inline-block; + margin: 0 5px; + &.active { + background: $primary-color; + @include opacity(1); + } + } +} \ No newline at end of file diff --git a/web/sass-files/sass/styles.scss b/web/sass-files/sass/styles.scss index 34f7c6eed..5c83d5c5b 100644 --- a/web/sass-files/sass/styles.scss +++ b/web/sass-files/sass/styles.scss @@ -39,6 +39,7 @@ @import "partials/get-link"; @import "partials/markdown"; @import "partials/tutorial"; +@import "partials/statistics"; // Responsive Css @import "partials/responsive"; diff --git a/web/static/images/tutorialTip.gif b/web/static/images/tutorialTip.gif new file mode 100644 index 000000000..024dc3c09 Binary files /dev/null and b/web/static/images/tutorialTip.gif differ -- cgit v1.2.3-1-g7c22 From dedbc122c666a68de4759be7b7c70e698e3e2c28 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Mon, 2 Nov 2015 09:21:08 -0500 Subject: Overlay fixes and added class prop for tips --- web/react/components/create_post.jsx | 1 + web/react/components/sidebar.jsx | 1 + web/react/components/sidebar_header.jsx | 3 +-- web/react/components/tutorial/tutorial_tip.jsx | 15 +++++++++++++-- 4 files changed, 16 insertions(+), 4 deletions(-) (limited to 'web') diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index cb3148b7b..1545cdfaa 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -393,6 +393,7 @@ export default class CreatePost extends React.Component { ); } diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 39d9808e9..c47919885 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -358,6 +358,7 @@ export default class Sidebar extends React.Component { ); } diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index 96348f688..0d48c6f88 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -44,8 +44,6 @@ export default class SidebarHeader extends React.Component { this.refs.dropdown.blockToggle = false; return; } - console.log(this.refs.tip); - this.refs.tip.toggle(); $('.team__header').find('.dropdown-toggle').dropdown('toggle'); } createTutorialTip() { @@ -97,6 +95,7 @@ export default class SidebarHeader extends React.Component { ref='tip' placement='right' screens={screens} + overlayClass='tip-overlay--sidebar' />
    ); diff --git a/web/react/components/tutorial/tutorial_tip.jsx b/web/react/components/tutorial/tutorial_tip.jsx index 3482a7cfa..118209599 100644 --- a/web/react/components/tutorial/tutorial_tip.jsx +++ b/web/react/components/tutorial/tutorial_tip.jsx @@ -79,6 +79,12 @@ export default class TutorialTip extends React.Component { ref='target' /> + +
    + + this.refs.target} > -
    +
    {this.props.screens[this.state.currentScreen]}
    {dots}
    @@ -113,7 +119,12 @@ export default class TutorialTip extends React.Component { } } +TutorialTip.defaultProps = { + overlayClass: '' +}; + TutorialTip.propTypes = { screens: React.PropTypes.array.isRequired, - placement: React.PropTypes.string.isRequired + placement: React.PropTypes.string.isRequired, + overlayClass: React.PropTypes.string }; -- cgit v1.2.3-1-g7c22 From 4c5c42e37415df75d398027b6c2a6ea39e138688 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Mon, 2 Nov 2015 20:57:23 +0500 Subject: Updating tutorials UI --- web/react/components/sidebar_header.jsx | 9 ++-- web/react/components/tutorial/tutorial_tip.jsx | 3 +- web/sass-files/sass/partials/_responsive.scss | 22 +++++++- web/sass-files/sass/partials/_tutorial.scss | 68 +++++++++++++++++++++++++ web/static/images/tutorialTip.gif | Bin 12278 -> 18421 bytes 5 files changed, 97 insertions(+), 5 deletions(-) (limited to 'web') diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index 0d48c6f88..1bf70ce76 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -64,7 +64,8 @@ export default class SidebarHeader extends React.Component { screens.push( ); @@ -95,7 +98,7 @@ export default class SidebarHeader extends React.Component { ref='tip' placement='right' screens={screens} - overlayClass='tip-overlay--sidebar' + overlayClass='tip-overlay--header' />
    ); diff --git a/web/react/components/tutorial/tutorial_tip.jsx b/web/react/components/tutorial/tutorial_tip.jsx index 118209599..c85acb346 100644 --- a/web/react/components/tutorial/tutorial_tip.jsx +++ b/web/react/components/tutorial/tutorial_tip.jsx @@ -70,7 +70,7 @@ export default class TutorialTip extends React.Component { } return ( -
    +
    this.refs.target} >
    +
    {this.props.screens[this.state.currentScreen]}
    {dots}
    diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index 8f353c401..a49a98952 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -179,6 +179,15 @@ } @media screen and (max-width: 1140px) { + .tip-overlay { + &.tip-overlay--chat { + margin: -10px 0 0 -10px; + .arrow { + right: 15px; + left: auto; + } + } + } .inner__wrap { &.move--left { .file-overlay { @@ -266,6 +275,18 @@ } } @media screen and (max-width: 768px) { + .tip-div { + left: 15px; + right: auto; + } + .tip-overlay { + &.tip-overlay--chat { + margin-left: 10px; + .arrow { + left: 32px; + } + } + } .file-details__container { display: block; .file-details__preview { @@ -482,7 +503,6 @@ padding-bottom: 10px; display: table; width: 100%; - table-layout: fixed; .post-body__cell { display: table-cell; padding-left: 45px; diff --git a/web/sass-files/sass/partials/_tutorial.scss b/web/sass-files/sass/partials/_tutorial.scss index fa93e46bf..42183d599 100644 --- a/web/sass-files/sass/partials/_tutorial.scss +++ b/web/sass-files/sass/partials/_tutorial.scss @@ -16,38 +16,93 @@ @include border-radius(3px); padding: 20px; z-index: 1000; + + .arrow { + border-width: 10px; + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + } + + &.tip-overlay--sidebar { + max-width: 75%; + margin: 50px 0 0 10px; + .arrow { + top: 80px; + left: -10px; + margin-top: -10px; + border-left-width: 0; + border-right-color: #fff; + } + } + + &.tip-overlay--header { + margin: 10px 0 0 10px; + .arrow { + top: 15px; + left: -10px; + border-left-width: 0; + border-right-color: #fff; + } + } + + &.tip-overlay--chat { + margin-top: -10px; + .arrow { + left: 50%; + margin-left: -10px; + border-bottom-width: 0; + border-top-color: #fff; + bottom: -10px; + } + } + h4 { font-size: em(16px); font-weight: 600; margin: 5px 0 15px; } + p { font-size: 13px; line-height: 1.6; + strong { font-weight: 600; } + } + .btn { background: #cccccc; color: #fff; @include border-radius(3px); border: none; margin-bottom: 10px; + &:hover, &:active, &:focus { color: #fff; border: none; background: #bbb; } + } + .tip-opt { font-size: 12px; + span { @include opacity(0.5); } + } + .tutorial__circles { margin: 1.5em 0px -1.7em -4px; + .circle { width: 7px; height: 7px; @@ -56,8 +111,11 @@ background: #000; @include opacity(0.4); } + } + } + } .tip-button { @@ -72,6 +130,16 @@ position:absolute; top:0px; right:0px; + + &.tip-overlay--header { + top: 20px; + } + + &.tip-overlay--sidebar { + left: 0; + top: -9px; + } + } .tutorial-steps__container { diff --git a/web/static/images/tutorialTip.gif b/web/static/images/tutorialTip.gif index 024dc3c09..f185ff4b9 100644 Binary files a/web/static/images/tutorialTip.gif and b/web/static/images/tutorialTip.gif differ -- cgit v1.2.3-1-g7c22 From 51617a03bc191b1a0931c916abca21366fd2aeaf Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Mon, 2 Nov 2015 11:10:32 -0500 Subject: Remove modal links from tip --- web/react/components/sidebar_header.jsx | 31 +++---------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) (limited to 'web') diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index 1bf70ce76..949c82a3b 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -49,43 +49,18 @@ export default class SidebarHeader extends React.Component { createTutorialTip() { const screens = []; - let teamSettingsLink = {'Team Settings'}; - if (Utils.isAdmin(UserStore.getCurrentUser().roles)) { - teamSettingsLink = ( - - {'Team Settings'} - - ); - } - screens.push(

    {'Main Menu'}

    {'The '}{'Main Menu'}{' is where you can '} - - {'Invite New Members'} - + {'Invite New Members'} {', access your '} - - {'Account Settings'} - + {'Account Settings'} {', and set your '}{'Theme Color'}{'.'}

    - {'Team administrators can also access their '}{teamSettingsLink}{' from this menu.'} + {'Team administrators can also access their '}{'Team Settings'}{' from this menu.'}

    ); -- cgit v1.2.3-1-g7c22 From 95da41257155e4d1d8201471e7ae1f5d96450189 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Mon, 2 Nov 2015 11:13:21 -0500 Subject: Move period out of link --- web/react/components/tutorial/tutorial_intro_screens.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'web') diff --git a/web/react/components/tutorial/tutorial_intro_screens.jsx b/web/react/components/tutorial/tutorial_intro_screens.jsx index b37aac27c..0f3050156 100644 --- a/web/react/components/tutorial/tutorial_intro_screens.jsx +++ b/web/react/components/tutorial/tutorial_intro_screens.jsx @@ -120,8 +120,9 @@ export default class TutorialIntroScreens extends React.Component { href='mailto:feedback@mattermost.com' target='_blank' > - {'feedback@mattermost.com.'} + {'feedback@mattermost.com'} + {'.'}

    {'Click “Next” to enter Town Square. This is the first channel teammates see when they sign up - use it for posting updates everyone needs to know.'}
    -- cgit v1.2.3-1-g7c22 From 35b11e0e61fd3c7130c3f54601ba05dd0b52fba3 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Mon, 2 Nov 2015 15:10:46 -0500 Subject: Fix merge issues --- web/react/components/center_panel.jsx | 42 ++++++++++++++++++---- web/react/components/channel_view.jsx | 8 ++--- web/react/components/posts_view_container.jsx | 9 +++-- web/react/components/sidebar_header.jsx | 2 +- .../components/tutorial/tutorial_intro_screens.jsx | 4 --- 5 files changed, 47 insertions(+), 18 deletions(-) (limited to 'web') diff --git a/web/react/components/center_panel.jsx b/web/react/components/center_panel.jsx index b871fe81a..a471c8af1 100644 --- a/web/react/components/center_panel.jsx +++ b/web/react/components/center_panel.jsx @@ -1,17 +1,47 @@ // Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. // See License.txt for license information. -var CreatePost = require('../components/create_post.jsx'); -var PostsViewContainer = require('../components/posts_view_container.jsx'); -var ChannelHeader = require('../components/channel_header.jsx'); -var Navbar = require('../components/navbar.jsx'); -var FileUploadOverlay = require('../components/file_upload_overlay.jsx'); +const TutorialIntroScreens = require('./tutorial/tutorial_intro_screens.jsx'); +const CreatePost = require('./create_post.jsx'); +const PostsViewContainer = require('./posts_view_container.jsx'); +const ChannelHeader = require('./channel_header.jsx'); +const Navbar = require('./navbar.jsx'); +const FileUploadOverlay = require('./file_upload_overlay.jsx'); + +const PreferenceStore = require('../stores/preference_store.jsx'); +const UserStore = require('../stores/user_store.jsx'); + +const Constants = require('../utils/constants.jsx'); +const TutorialSteps = Constants.TutorialSteps; +const Preferences = Constants.Preferences; export default class CenterPanel extends React.Component { constructor(props) { super(props); + + this.onPreferenceChange = this.onPreferenceChange.bind(this); + + const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'}); + this.state = {showTutorialScreens: parseInt(tutorialPref.value, 10) === TutorialSteps.INTRO_SCREENS}; + } + componentDidMount() { + PreferenceStore.addChangeListener(this.onPreferenceChange); + } + componentWillUnmount() { + PreferenceStore.removeChangeListener(this.onPreferenceChange); + } + onPreferenceChange() { + const tutorialPref = PreferenceStore.getPreference(Preferences.TUTORIAL_STEP, UserStore.getCurrentId(), {value: '0'}); + this.setState({showTutorialScreens: parseInt(tutorialPref.value, 10) <= TutorialSteps.INTRO_SCREENS}); } render() { + let postsContainer + if (this.state.showTutorialScreens) { + postsContainer = ; + } else { + postsContainer = ; + } + return (
    @@ -32,7 +62,7 @@ export default class CenterPanel extends React.Component {
    - + {postsContainer}
    {'Invite New Members'} {', access your '} {'Account Settings'} - {', and set your '}{'Theme Color'}{'.'} + {' and set your '}{'Theme Color'}{'.'}

    {'Team administrators can also access their '}{'Team Settings'}{' from this menu.'} diff --git a/web/react/components/tutorial/tutorial_intro_screens.jsx b/web/react/components/tutorial/tutorial_intro_screens.jsx index 0f3050156..c7abccae3 100644 --- a/web/react/components/tutorial/tutorial_intro_screens.jsx +++ b/web/react/components/tutorial/tutorial_intro_screens.jsx @@ -20,10 +20,6 @@ export default class TutorialIntroScreens extends React.Component { this.state = {currentScreen: 0}; } - componentDidMount() { - const height = $(window).outerHeight() - 120; - $('.tutorial-steps__container').css('height', `${height}px`); - } handleNext() { if (this.state.currentScreen < 2) { this.setState({currentScreen: this.state.currentScreen + 1}); -- cgit v1.2.3-1-g7c22 From 6b1e80d1d55783016d295f3f116891ce65ae6b06 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Mon, 2 Nov 2015 15:24:21 -0500 Subject: Fix typo --- web/react/components/center_panel.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'web') diff --git a/web/react/components/center_panel.jsx b/web/react/components/center_panel.jsx index a471c8af1..242c2c637 100644 --- a/web/react/components/center_panel.jsx +++ b/web/react/components/center_panel.jsx @@ -35,7 +35,7 @@ export default class CenterPanel extends React.Component { this.setState({showTutorialScreens: parseInt(tutorialPref.value, 10) <= TutorialSteps.INTRO_SCREENS}); } render() { - let postsContainer + let postsContainer; if (this.state.showTutorialScreens) { postsContainer = ; } else { -- cgit v1.2.3-1-g7c22