diff options
Diffstat (limited to 'webapp/actions')
-rw-r--r-- | webapp/actions/analytics_actions.jsx | 12 | ||||
-rw-r--r-- | webapp/actions/global_actions.jsx | 480 | ||||
-rw-r--r-- | webapp/actions/team_actions.jsx | 38 | ||||
-rw-r--r-- | webapp/actions/user_actions.jsx | 22 | ||||
-rw-r--r-- | webapp/actions/websocket_actions.jsx | 270 |
5 files changed, 822 insertions, 0 deletions
diff --git a/webapp/actions/analytics_actions.jsx b/webapp/actions/analytics_actions.jsx new file mode 100644 index 000000000..05e4eeee2 --- /dev/null +++ b/webapp/actions/analytics_actions.jsx @@ -0,0 +1,12 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import Client from 'utils/web_client.jsx'; + +export function track(category, action, label, property, value) { + Client.track(category, action, label, property, value); +} + +export function trackPage() { + Client.trackPage(); +} diff --git a/webapp/actions/global_actions.jsx b/webapp/actions/global_actions.jsx new file mode 100644 index 000000000..91b51a9c2 --- /dev/null +++ b/webapp/actions/global_actions.jsx @@ -0,0 +1,480 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; +import ChannelStore from 'stores/channel_store.jsx'; +import PostStore from 'stores/post_store.jsx'; +import UserStore from 'stores/user_store.jsx'; +import BrowserStore from 'stores/browser_store.jsx'; +import ErrorStore from 'stores/error_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import PreferenceStore from 'stores/preference_store.jsx'; +import SearchStore from 'stores/search_store.jsx'; +import Constants from 'utils/constants.jsx'; +const ActionTypes = Constants.ActionTypes; +import * as AsyncClient from 'utils/async_client.jsx'; +import Client from 'utils/web_client.jsx'; +import * as Utils from 'utils/utils.jsx'; +import * as Websockets from './websocket_actions.jsx'; +import * as I18n from 'i18n/i18n.jsx'; + +import {trackPage} from 'actions/analytics_actions.jsx'; + +import {browserHistory} from 'react-router'; + +import en from 'i18n/en.json'; + +export function emitChannelClickEvent(channel) { + function userVisitedFakeChannel(chan, success, fail) { + const otherUserId = Utils.getUserIdFromChannelName(chan); + Client.createDirectChannel( + otherUserId, + (data) => { + success(data); + }, + () => { + fail(); + } + ); + } + function switchToChannel(chan) { + AsyncClient.getChannels(true); + AsyncClient.getChannelExtraInfo(chan.id); + AsyncClient.updateLastViewedAt(chan.id); + AsyncClient.getPosts(chan.id); + trackPage(); + + AppDispatcher.handleViewAction({ + type: ActionTypes.CLICK_CHANNEL, + name: chan.name, + id: chan.id, + prev: ChannelStore.getCurrentId() + }); + } + + if (channel.fake) { + userVisitedFakeChannel( + channel, + (data) => { + switchToChannel(data); + }, + () => { + browserHistory.push('/' + this.state.currentTeam.name); + } + ); + } else { + switchToChannel(channel); + } +} + +export function emitInitialLoad(callback) { + Client.getInitialLoad( + (data) => { + global.window.mm_config = data.client_cfg; + global.window.mm_license = data.license_cfg; + + UserStore.setNoAccounts(data.no_accounts); + + if (data.user && data.user.id) { + global.window.mm_user = data.user; + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_ME, + me: data.user + }); + } + + if (data.preferences) { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_PREFERENCES, + preferences: data.preferences + }); + } + + if (data.teams) { + var teams = {}; + data.teams.forEach((team) => { + teams[team.id] = team; + }); + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_ALL_TEAMS, + teams + }); + } + + if (data.team_members) { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_TEAM_MEMBERS, + team_members: data.team_members + }); + } + + if (data.direct_profiles) { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_DIRECT_PROFILES, + profiles: data.direct_profiles + }); + } + + if (callback) { + callback(); + } + }, + (err) => { + AsyncClient.dispatchError(err, 'getInitialLoad'); + + if (callback) { + callback(); + } + } + ); +} + +export function doFocusPost(channelId, postId, data) { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_FOCUSED_POST, + postId, + post_list: data + }); + AsyncClient.getChannels(true); + AsyncClient.getChannelExtraInfo(channelId); + AsyncClient.getPostsBefore(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS); + AsyncClient.getPostsAfter(postId, 0, Constants.POST_FOCUS_CONTEXT_RADIUS); +} + +export function emitPostFocusEvent(postId) { + AsyncClient.getChannels(true); + Client.getPermalinkTmp( + postId, + (data) => { + if (!data) { + return; + } + const channelId = data.posts[data.order[0]].channel_id; + doFocusPost(channelId, postId, data); + }, + () => { + browserHistory.push('/error?message=' + encodeURIComponent(Utils.localizeMessage('permalink.error.access', 'Permalink belongs to a channel you do not have access to'))); + } + ); +} + +export function emitProfilesForDmList() { + AsyncClient.getProfilesForDirectMessageList(); + AsyncClient.getTeamMembers(TeamStore.getCurrentId()); +} + +export function emitCloseRightHandSide() { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_SEARCH, + results: null + }); + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_POST_SELECTED, + postId: null + }); +} + +export function emitPostFocusRightHandSideFromSearch(post, isMentionSearch) { + Client.getPost( + post.channel_id, + post.id, + (data) => { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_POSTS, + id: post.channel_id, + numRequested: 0, + post_list: data + }); + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_POST_SELECTED, + postId: Utils.getRootId(post), + from_search: SearchStore.getSearchTerm() + }); + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_SEARCH, + results: null, + is_mention_search: isMentionSearch + }); + }, + (err) => { + AsyncClient.dispatchError(err, 'getPost'); + } + ); +} + +export function emitLoadMorePostsEvent() { + const id = ChannelStore.getCurrentId(); + loadMorePostsTop(id); +} + +export function emitLoadMorePostsFocusedTopEvent() { + const id = PostStore.getFocusedPostId(); + loadMorePostsTop(id); +} + +export function loadMorePostsTop(id) { + const earliestPostId = PostStore.getEarliestPost(id).id; + if (PostStore.requestVisibilityIncrease(id, Constants.POST_CHUNK_SIZE)) { + AsyncClient.getPostsBefore(earliestPostId, 0, Constants.POST_CHUNK_SIZE); + } +} + +export function emitLoadMorePostsFocusedBottomEvent() { + const id = PostStore.getFocusedPostId(); + const latestPostId = PostStore.getLatestPost(id).id; + AsyncClient.getPostsAfter(latestPostId, 0, Constants.POST_CHUNK_SIZE); +} + +export function emitPostRecievedEvent(post, msg) { + if (ChannelStore.getCurrentId() === post.channel_id) { + if (window.isActive) { + AsyncClient.updateLastViewedAt(); + } else { + AsyncClient.getChannel(post.channel_id); + } + } else if (msg && (TeamStore.getCurrentId() === msg.team_id || msg.props.channel_type === Constants.DM_CHANNEL)) { + AsyncClient.getChannel(post.channel_id); + } + + var websocketMessageProps = null; + if (msg) { + websocketMessageProps = msg.props; + } + + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_POST, + post, + websocketMessageProps + }); +} + +export function emitUserPostedEvent(post) { + AppDispatcher.handleServerAction({ + type: ActionTypes.CREATE_POST, + post + }); +} + +export function emitPostDeletedEvent(post) { + AppDispatcher.handleServerAction({ + type: ActionTypes.POST_DELETED, + post + }); +} + +export function showDeletePostModal(post, commentCount = 0) { + AppDispatcher.handleViewAction({ + type: ActionTypes.TOGGLE_DELETE_POST_MODAL, + value: true, + post, + commentCount + }); +} + +export function showGetPostLinkModal(post) { + AppDispatcher.handleViewAction({ + type: ActionTypes.TOGGLE_GET_POST_LINK_MODAL, + value: true, + post + }); +} + +export function showGetPublicLinkModal(filename) { + AppDispatcher.handleViewAction({ + type: ActionTypes.TOGGLE_GET_PUBLIC_LINK_MODAL, + value: true, + filename + }); +} + +export function showGetTeamInviteLinkModal() { + AppDispatcher.handleViewAction({ + type: Constants.ActionTypes.TOGGLE_GET_TEAM_INVITE_LINK_MODAL, + value: true + }); +} + +export function showInviteMemberModal() { + AppDispatcher.handleViewAction({ + type: ActionTypes.TOGGLE_INVITE_MEMBER_MODAL, + value: true + }); +} + +export function showRegisterAppModal() { + AppDispatcher.handleViewAction({ + type: ActionTypes.TOGGLE_REGISTER_APP_MODAL, + value: true + }); +} + +export function emitSuggestionPretextChanged(suggestionId, pretext) { + AppDispatcher.handleViewAction({ + type: ActionTypes.SUGGESTION_PRETEXT_CHANGED, + id: suggestionId, + pretext + }); +} + +export function emitSelectNextSuggestion(suggestionId) { + AppDispatcher.handleViewAction({ + type: ActionTypes.SUGGESTION_SELECT_NEXT, + id: suggestionId + }); +} + +export function emitSelectPreviousSuggestion(suggestionId) { + AppDispatcher.handleViewAction({ + type: ActionTypes.SUGGESTION_SELECT_PREVIOUS, + id: suggestionId + }); +} + +export function emitCompleteWordSuggestion(suggestionId, term = '') { + AppDispatcher.handleViewAction({ + type: Constants.ActionTypes.SUGGESTION_COMPLETE_WORD, + id: suggestionId, + term + }); +} + +export function emitClearSuggestions(suggestionId) { + AppDispatcher.handleViewAction({ + type: Constants.ActionTypes.SUGGESTION_CLEAR_SUGGESTIONS, + id: suggestionId + }); +} + +export function emitPreferenceChangedEvent(preference) { + if (preference.category === Constants.Preferences.CATEGORY_DIRECT_CHANNEL_SHOW) { + AsyncClient.getDirectProfiles(); + } + + AppDispatcher.handleServerAction({ + type: Constants.ActionTypes.RECEIVED_PREFERENCE, + preference + }); +} + +export function emitRemovePost(post) { + AppDispatcher.handleViewAction({ + type: Constants.ActionTypes.REMOVE_POST, + post + }); +} + +export function sendEphemeralPost(message, channelId) { + const timestamp = Utils.getTimestamp(); + const post = { + id: Utils.generateId(), + user_id: '0', + channel_id: channelId || ChannelStore.getCurrentId(), + message, + type: Constants.POST_TYPE_EPHEMERAL, + create_at: timestamp, + update_at: timestamp, + filenames: [], + props: {} + }; + + emitPostRecievedEvent(post); +} + +export function newLocalizationSelected(locale) { + if (locale === 'en') { + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_LOCALE, + locale, + translations: en + }); + } else { + Client.getTranslations( + I18n.getLanguageInfo(locale).url, + (data, res) => { + let translations = data; + if (!data && res.text) { + translations = JSON.parse(res.text); + } + AppDispatcher.handleServerAction({ + type: ActionTypes.RECEIVED_LOCALE, + locale, + translations + }); + }, + (err) => { + AsyncClient.dispatchError(err, 'getTranslations'); + } + ); + } +} + +export function loadBrowserLocale() { + let locale = (navigator.languages && navigator.languages.length > 0 ? navigator.languages[0] : + (navigator.language || navigator.userLanguage)).split('-')[0]; + + const user = UserStore.getCurrentUser(); + if (user) { + locale = user.locale || locale; + } + + if (!I18n.getLanguages()[locale]) { + locale = 'en'; + } + return newLocalizationSelected(locale); +} + +export function viewLoggedIn() { + AsyncClient.getChannels(); + AsyncClient.getChannelExtraInfo(); + + // Clear pending posts (shouldn't have pending posts if we are loading) + PostStore.clearPendingPosts(); +} + +var lastTimeTypingSent = 0; +export function emitLocalUserTypingEvent(channelId, parentId) { + const t = Date.now(); + if ((t - lastTimeTypingSent) > Constants.UPDATE_TYPING_MS) { + Websockets.sendMessage({channel_id: channelId, action: 'typing', props: {parent_id: parentId}, state: {}}); + lastTimeTypingSent = t; + } +} + +export function emitRemoteUserTypingEvent(channelId, userId, postParentId) { + AppDispatcher.handleViewAction({ + type: Constants.ActionTypes.USER_TYPING, + channelId, + userId, + postParentId + }); +} + +export function emitUserLoggedOutEvent(redirectTo) { + const rURL = (redirectTo && typeof redirectTo === 'string') ? redirectTo : '/'; + Client.logout( + () => { + BrowserStore.signalLogout(); + BrowserStore.clear(); + ErrorStore.clearLastError(); + PreferenceStore.clear(); + UserStore.clear(); + TeamStore.clear(); + browserHistory.push(rURL); + }, + () => { + browserHistory.push(rURL); + } + ); +} + +export function emitJoinChannelEvent(channel, success, failure) { + Client.joinChannel( + channel.id, + success, + failure, + ); +} diff --git a/webapp/actions/team_actions.jsx b/webapp/actions/team_actions.jsx new file mode 100644 index 000000000..2408e55fd --- /dev/null +++ b/webapp/actions/team_actions.jsx @@ -0,0 +1,38 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import UserStore from 'stores/user_store.jsx'; + +import Constants from 'utils/constants.jsx'; +const ActionTypes = Constants.ActionTypes; + +import * as AsyncClient from 'utils/async_client.jsx'; +import Client from 'utils/web_client.jsx'; +import AppDispatcher from '../dispatcher/app_dispatcher.jsx'; + +import {browserHistory} from 'react-router'; + +export function checkIfTeamExists(teamName, onSuccess, onError) { + Client.findTeamByName(teamName, onSuccess, onError); +} + +export function createTeam(team, onSuccess, onError) { + Client.createTeam(team, + (rteam) => { + AsyncClient.getDirectProfiles(); + + AppDispatcher.handleServerAction({ + type: ActionTypes.CREATED_TEAM, + team: rteam, + member: {team_id: rteam.id, user_id: UserStore.getCurrentId(), roles: 'admin'} + }); + + browserHistory.push('/' + rteam.name + '/channels/town-square'); + + if (onSuccess) { + onSuccess(rteam); + } + }, + onError + ); +} diff --git a/webapp/actions/user_actions.jsx b/webapp/actions/user_actions.jsx new file mode 100644 index 000000000..370bfc302 --- /dev/null +++ b/webapp/actions/user_actions.jsx @@ -0,0 +1,22 @@ +// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import Client from 'utils/web_client.jsx'; + +export function switchFromLdapToEmail(email, password, ldapPassword, onSuccess, onError) { + Client.ldapToEmail( + email, + password, + ldapPassword, + (data) => { + if (data.follow_link) { + window.location.href = data.follow_link; + } + + if (onSuccess) { + onSuccess(data); + } + }, + onError + ); +} diff --git a/webapp/actions/websocket_actions.jsx b/webapp/actions/websocket_actions.jsx new file mode 100644 index 000000000..e317b8db0 --- /dev/null +++ b/webapp/actions/websocket_actions.jsx @@ -0,0 +1,270 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import $ from 'jquery'; +import UserStore from 'stores/user_store.jsx'; +import TeamStore from 'stores/team_store.jsx'; +import PostStore from 'stores/post_store.jsx'; +import ChannelStore from 'stores/channel_store.jsx'; +import BrowserStore from 'stores/browser_store.jsx'; +import ErrorStore from 'stores/error_store.jsx'; +import NotificationStore from 'stores/notification_store.jsx'; //eslint-disable-line no-unused-vars + +import Client from 'utils/web_client.jsx'; +import * as Utils from 'utils/utils.jsx'; +import * as AsyncClient from 'utils/async_client.jsx'; +import * as GlobalActions from 'actions/global_actions.jsx'; + +import Constants from 'utils/constants.jsx'; +const SocketEvents = Constants.SocketEvents; + +import {browserHistory} from 'react-router'; + +const MAX_WEBSOCKET_FAILS = 7; +const WEBSOCKET_RETRY_TIME = 3000; + +var conn = null; +var connectFailCount = 0; +var pastFirstInit = false; +var manuallyClosed = false; + +export function initialize() { + if (window.WebSocket && !conn) { + let protocol = 'ws://'; + if (window.location.protocol === 'https:') { + protocol = 'wss://'; + } + + const connUrl = protocol + location.host + ((/:\d+/).test(location.host) ? '' : Utils.getWebsocketPort(protocol)) + Client.getUsersRoute() + '/websocket'; + + if (connectFailCount === 0) { + console.log('websocket connecting to ' + connUrl); //eslint-disable-line no-console + } + + manuallyClosed = false; + + conn = new WebSocket(connUrl); + + conn.onopen = () => { + if (connectFailCount > 0) { + console.log('websocket re-established connection'); //eslint-disable-line no-console + AsyncClient.getChannels(); + AsyncClient.getPosts(ChannelStore.getCurrentId()); + } + + if (pastFirstInit) { + ErrorStore.clearLastError(); + ErrorStore.emitChange(); + } + + pastFirstInit = true; + connectFailCount = 0; + }; + + conn.onclose = () => { + conn = null; + + if (connectFailCount === 0) { + console.log('websocket closed'); //eslint-disable-line no-console + } + + if (manuallyClosed) { + return; + } + + connectFailCount = connectFailCount + 1; + + if (connectFailCount > MAX_WEBSOCKET_FAILS) { + ErrorStore.storeLastError({message: Utils.localizeMessage('channel_loader.socketError', 'Please check connection, Mattermost unreachable. If issue persists, ask administrator to check WebSocket port.')}); + } + + ErrorStore.setConnectionErrorCount(connectFailCount); + ErrorStore.emitChange(); + + setTimeout( + () => { + initialize(); + }, + WEBSOCKET_RETRY_TIME + ); + }; + + conn.onerror = (evt) => { + if (connectFailCount <= 1) { + console.log('websocket error'); //eslint-disable-line no-console + console.log(evt); //eslint-disable-line no-console + } + }; + + conn.onmessage = (evt) => { + const msg = JSON.parse(evt.data); + handleMessage(msg); + }; + } +} + +function handleMessage(msg) { + // Let the store know we are online. This probably shouldn't be here. + UserStore.setStatus(msg.user_id, 'online'); + + switch (msg.action) { + case SocketEvents.POSTED: + case SocketEvents.EPHEMERAL_MESSAGE: + handleNewPostEvent(msg); + break; + + case SocketEvents.POST_EDITED: + handlePostEditEvent(msg); + break; + + case SocketEvents.POST_DELETED: + handlePostDeleteEvent(msg); + break; + + case SocketEvents.NEW_USER: + handleNewUserEvent(); + break; + + case SocketEvents.USER_ADDED: + handleUserAddedEvent(msg); + break; + + case SocketEvents.USER_REMOVED: + handleUserRemovedEvent(msg); + break; + + case SocketEvents.CHANNEL_VIEWED: + handleChannelViewedEvent(msg); + break; + + case SocketEvents.CHANNEL_DELETED: + handleChannelDeletedEvent(msg); + break; + + case SocketEvents.DIRECT_ADDED: + handleDirectAddedEvent(msg); + break; + + case SocketEvents.PREFERENCE_CHANGED: + handlePreferenceChangedEvent(msg); + break; + + case SocketEvents.TYPING: + handleUserTypingEvent(msg); + break; + + default: + } +} + +export function sendMessage(msg) { + if (conn && conn.readyState === WebSocket.OPEN) { + var teamId = TeamStore.getCurrentId(); + if (teamId && teamId.length > 0) { + msg.team_id = teamId; + } + + conn.send(JSON.stringify(msg)); + } else if (!conn || conn.readyState === WebSocket.Closed) { + conn = null; + initialize(); + } +} + +export function close() { + manuallyClosed = true; + connectFailCount = 0; + if (conn && conn.readyState === WebSocket.OPEN) { + conn.close(); + } +} + +function handleNewPostEvent(msg) { + const post = JSON.parse(msg.props.post); + GlobalActions.emitPostRecievedEvent(post, msg); +} + +function handlePostEditEvent(msg) { + // Store post + const post = JSON.parse(msg.props.post); + PostStore.storePost(post); + PostStore.emitChange(); + + // Update channel state + if (ChannelStore.getCurrentId() === msg.channel_id) { + if (window.isActive) { + AsyncClient.updateLastViewedAt(); + } + } +} + +function handlePostDeleteEvent(msg) { + const post = JSON.parse(msg.props.post); + GlobalActions.emitPostDeletedEvent(post); +} + +function handleNewUserEvent() { + AsyncClient.getProfiles(); + AsyncClient.getDirectProfiles(); + AsyncClient.getChannelExtraInfo(); +} + +function handleDirectAddedEvent(msg) { + AsyncClient.getChannel(msg.channel_id); + AsyncClient.getDirectProfiles(); +} + +function handleUserAddedEvent(msg) { + if (ChannelStore.getCurrentId() === msg.channel_id) { + AsyncClient.getChannelExtraInfo(); + } + + if (TeamStore.getCurrentId() === msg.team_id && UserStore.getCurrentId() === msg.user_id) { + AsyncClient.getChannel(msg.channel_id); + } +} + +function handleUserRemovedEvent(msg) { + if (UserStore.getCurrentId() === msg.user_id) { + AsyncClient.getChannels(); + + if (msg.props.remover_id !== msg.user_id && + msg.channel_id === ChannelStore.getCurrentId() && + $('#removed_from_channel').length > 0) { + var sentState = {}; + sentState.channelName = ChannelStore.getCurrent().display_name; + sentState.remover = UserStore.getProfile(msg.props.remover_id).username; + + BrowserStore.setItem('channel-removed-state', sentState); + $('#removed_from_channel').modal('show'); + } + } else if (ChannelStore.getCurrentId() === msg.channel_id) { + AsyncClient.getChannelExtraInfo(); + } +} + +function handleChannelViewedEvent(msg) { + // Useful for when multiple devices have the app open to different channels + if (TeamStore.getCurrentId() === msg.team_id && ChannelStore.getCurrentId() !== msg.channel_id && UserStore.getCurrentId() === msg.user_id) { + AsyncClient.getChannel(msg.channel_id); + } +} + +function handleChannelDeletedEvent(msg) { + if (ChannelStore.getCurrentId() === msg.channel_id) { + const teamUrl = TeamStore.getCurrentTeamRelativeUrl(); + browserHistory.push(teamUrl + '/channels/' + Constants.DEFAULT_CHANNEL); + } + AsyncClient.getChannels(); +} + +function handlePreferenceChangedEvent(msg) { + const preference = JSON.parse(msg.props.preference); + GlobalActions.emitPreferenceChangedEvent(preference); +} + +function handleUserTypingEvent(msg) { + if (TeamStore.getCurrentId() === msg.team_id) { + GlobalActions.emitRemoteUserTypingEvent(msg.channel_id, msg.user_id, msg.props.parent_id); + } +} |