forked from Ivasoft/mattermost-mobile
Compare commits
13 Commits
release-1.
...
release-1.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc27aa1794 | ||
|
|
bc0a19fde0 | ||
|
|
633085b5c0 | ||
|
|
a8c0f26a71 | ||
|
|
c4b683a58f | ||
|
|
2e37722368 | ||
|
|
18ae9fd11f | ||
|
|
a833a936e2 | ||
|
|
f0390f2463 | ||
|
|
cbdb820cbe | ||
|
|
0bbbbb98cb | ||
|
|
782dfc85dc | ||
|
|
74365b11a7 |
8
.github/ISSUE_TEMPLATE.md
vendored
8
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,12 +1,12 @@
|
||||
Per Mattermost guidelines, GitHub issues are for bug reports: <https://handbook.mattermost.com/contributors/contributors/ways-to-contribute>.
|
||||
Per Mattermost guidelines, GitHub issues are for bug reports: <http://www.mattermost.org/filing-issues/>.
|
||||
|
||||
For troubleshooting see: https://forum.mattermost.com/.
|
||||
For feature proposals see: https://www.mattermost.com/feature-ideas/
|
||||
For troubleshooting see: http://forum.mattermost.org/.
|
||||
For feature proposals see: http://www.mattermost.org/feature-requests/
|
||||
|
||||
If you've found a bug--something appears unintentional--please follow these steps:
|
||||
|
||||
1. Confirm you’re filing a new issue. [Search existing tickets in Jira](https://mattermost.atlassian.net/jira/software/c/projects/MM/issues/) to ensure that the ticket does not already exist.
|
||||
2. Confirm your issue does not involve security. Otherwise, please see our [Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/).
|
||||
2. Confirm your issue does not involve security. Otherwise, please see our [Responsible Disclosure Policy](https://about.mattermost.com/report-security-issue/).
|
||||
3. [File a new issue](https://github.com/mattermost/mattermost-mobile/issues/new) using the format below. Mattermost will confirm steps to reproduce and file in Jira, or ask for more details if there is trouble reproducing it. If there's already an existing bug in Jira, it will be linked back to the GitHub issue so you can track when it gets fixed.
|
||||
|
||||
#### Summary
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Submit feature requests to https://www.mattermost.com/feature-ideas/. File non-security related bugs here in the following format:
|
||||
Submit feature requests to http://www.mattermost.org/feature-requests/. File non-security related bugs here in the following format:
|
||||
|
||||
#### Summary
|
||||
Issue in one concise sentence.
|
||||
|
||||
38
NOTICE.txt
38
NOTICE.txt
@@ -12,7 +12,7 @@ This document includes a list of open source components used in Mattermost Mobil
|
||||
|
||||
This product contains '@mattermost/react-native-paste-input' by Mattermost.
|
||||
|
||||
React Native TextInput component have functionality to capture text input from a user by using the soft and hardware keyboards but lacks the ability to restrict copy & paste options as well as allowing pasting different files formats copied from other apps, like images & videos from the Photos gallery app.
|
||||
React Native TextInput component have functionality to capture text input from a user by using the soft and hardware keyboards but lacks the ability to restrict copy & paste options as well as allwing pasting different files formats copied from other apps, like images & videos from the Photos gallery app.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/mattermost/react-native-paste-input
|
||||
@@ -2273,42 +2273,6 @@ SOFTWARE.
|
||||
|
||||
---
|
||||
|
||||
## react-native-math-view
|
||||
|
||||
This product contains 'react-native-math-view' by Shachar.
|
||||
|
||||
A react native view used to easily display and handle math. The library doesn't use WebView.
|
||||
|
||||
* HOMEPAGE:
|
||||
* https://github.com/ShaMan123/react-native-math-view
|
||||
|
||||
* LICENSE: MIT
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 ShaMan123
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
© 2022 GitHub, Inc.
|
||||
|
||||
---
|
||||
|
||||
## react-native-mmkv-storage
|
||||
|
||||
This product contains 'react-native-mmkv-storage' by Ammar Ahmed.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Mattermost Mobile App
|
||||
[](https://mattermost.com)
|
||||
|
||||
- **Minimum Server versions:** Current ESR version (6.3.0)
|
||||
- **Minimum Server versions:** Current ESR version (5.37.0)
|
||||
- **Supported iOS versions:** 12.1+
|
||||
- **Supported Android versions:** 7.0+
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ Security updates
|
||||
|
||||
Mattermost has a mandatory upgrade policy, and updates are only provided for the latest release. Critical updates are delivered as dot releases. Details on security updates are announced 30 days after the availability of the update.
|
||||
|
||||
For more details about the security content of past releases, see the [Security Updates](https://mattermost.com/security-updates/) page on the Mattermost website. For timely notifications about new security updates, subscribe to the [Security Bulletins Mailing List](https://mattermost.com/security-updates/#sign-up).
|
||||
For more details about the security content of past releases, see the [Security Updates](https://mattermost.com/security-updates/) page on the Mattermost website. For timely notifications about new security updates, subscribe to the [Security Bulletins Mailing List](https://about.mattermost.com/security-bulletin).
|
||||
|
||||
Contributing to this policy
|
||||
---------------------------
|
||||
|
||||
@@ -131,8 +131,8 @@ android {
|
||||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 398
|
||||
versionName "1.52.0"
|
||||
versionCode 386
|
||||
versionName "1.49.1"
|
||||
multiDexEnabled = true
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -3,75 +3,17 @@
|
||||
|
||||
import {sendEphemeralPost} from '@actions/views/post';
|
||||
import {Client4} from '@client/rest';
|
||||
import {AppCallResponseTypes} from '@mm-redux/constants/apps';
|
||||
import {AppCallTypes, AppCallResponseTypes} from '@mm-redux/constants/apps';
|
||||
import {ActionFunc, DispatchFunc} from '@mm-redux/types/actions';
|
||||
import {AppCallResponse, AppCallRequest, AppContext, AppBinding} from '@mm-redux/types/apps';
|
||||
import {AppCallResponse, AppCallRequest, AppCallType, AppContext} from '@mm-redux/types/apps';
|
||||
import {CommandArgs} from '@mm-redux/types/integrations';
|
||||
import {Post} from '@mm-redux/types/posts';
|
||||
import {cleanForm, createCallRequest, makeCallErrorResponse} from '@utils/apps';
|
||||
import {cleanForm, makeCallErrorResponse} from '@utils/apps';
|
||||
|
||||
export function handleBindingClick<Res=unknown>(binding: AppBinding, context: AppContext, intl: any): ActionFunc {
|
||||
return async (dispatch: DispatchFunc) => {
|
||||
// Fetch form
|
||||
if (binding.form?.source) {
|
||||
const callRequest = createCallRequest(
|
||||
binding.form.source,
|
||||
context,
|
||||
);
|
||||
|
||||
const res = await dispatch(doAppFetchForm<Res>(callRequest, intl));
|
||||
return res;
|
||||
}
|
||||
|
||||
// Open form
|
||||
if (binding.form) {
|
||||
// This should come properly formed, but using preventive checks
|
||||
if (!binding.form?.submit) {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.malformed_binding',
|
||||
defaultMessage: 'This binding is not properly formed. Contact the App developer.',
|
||||
});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
|
||||
const res: AppCallResponse = {
|
||||
type: AppCallResponseTypes.FORM,
|
||||
form: binding.form,
|
||||
};
|
||||
return {data: res};
|
||||
}
|
||||
|
||||
// Submit binding
|
||||
// This should come properly formed, but using preventive checks
|
||||
if (!binding.submit) {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.malformed_binding',
|
||||
defaultMessage: 'This binding is not properly formed. Contact the App developer.',
|
||||
});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
|
||||
const callRequest = createCallRequest(
|
||||
binding.submit,
|
||||
context,
|
||||
);
|
||||
|
||||
const res = await dispatch(doAppSubmit<Res>(callRequest, intl));
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
export function doAppSubmit<Res=unknown>(inCall: AppCallRequest, intl: any): ActionFunc {
|
||||
export function doAppCall<Res=unknown>(call: AppCallRequest, type: AppCallType, intl: any): ActionFunc {
|
||||
return async () => {
|
||||
try {
|
||||
const call: AppCallRequest = {
|
||||
...inCall,
|
||||
context: {
|
||||
...inCall.context,
|
||||
track_as_submit: true,
|
||||
},
|
||||
};
|
||||
const res = await Client4.executeAppCall(call, true) as AppCallResponse<Res>;
|
||||
const res = await Client4.executeAppCall(call, type) as AppCallResponse<Res>;
|
||||
const responseType = res.type || AppCallResponseTypes.OK;
|
||||
|
||||
switch (responseType) {
|
||||
@@ -80,10 +22,10 @@ export function doAppSubmit<Res=unknown>(inCall: AppCallRequest, intl: any): Act
|
||||
case AppCallResponseTypes.ERROR:
|
||||
return {error: res};
|
||||
case AppCallResponseTypes.FORM: {
|
||||
if (!res.form?.submit) {
|
||||
if (!res.form) {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.responses.form.no_form',
|
||||
defaultMessage: 'Response type is `form`, but no valid form was included in response.',
|
||||
defaultMessage: 'Response type is `form`, but no form was included in response.',
|
||||
});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
@@ -101,6 +43,14 @@ export function doAppSubmit<Res=unknown>(inCall: AppCallRequest, intl: any): Act
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
|
||||
if (type !== AppCallTypes.SUBMIT) {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.responses.navigate.no_submit',
|
||||
defaultMessage: 'Response type is `navigate`, but the call was not a submission.',
|
||||
});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
|
||||
return {data: res};
|
||||
default: {
|
||||
const errMsg = intl.formatMessage({
|
||||
@@ -122,73 +72,6 @@ export function doAppSubmit<Res=unknown>(inCall: AppCallRequest, intl: any): Act
|
||||
};
|
||||
}
|
||||
|
||||
export function doAppFetchForm<Res=unknown>(call: AppCallRequest, intl: any): ActionFunc {
|
||||
return async () => {
|
||||
try {
|
||||
const res = await Client4.executeAppCall(call, false) as AppCallResponse<Res>;
|
||||
const responseType = res.type || AppCallResponseTypes.OK;
|
||||
|
||||
switch (responseType) {
|
||||
case AppCallResponseTypes.ERROR:
|
||||
return {error: res};
|
||||
case AppCallResponseTypes.FORM:
|
||||
if (!res.form?.submit) {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.responses.form.no_form',
|
||||
defaultMessage: 'Response type is `form`, but no valid form was included in response.',
|
||||
});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
cleanForm(res.form);
|
||||
return {data: res};
|
||||
default: {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.responses.unknown_type',
|
||||
defaultMessage: 'App response type not supported. Response type: {type}.',
|
||||
}, {type: responseType});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
const errMsg = error.message || intl.formatMessage({
|
||||
id: 'apps.error.responses.unexpected_error',
|
||||
defaultMessage: 'Received an unexpected error.',
|
||||
});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function doAppLookup<Res=unknown>(call: AppCallRequest, intl: any): ActionFunc {
|
||||
return async () => {
|
||||
try {
|
||||
const res = await Client4.executeAppCall(call, false) as AppCallResponse<Res>;
|
||||
const responseType = res.type || AppCallResponseTypes.OK;
|
||||
|
||||
switch (responseType) {
|
||||
case AppCallResponseTypes.OK:
|
||||
return {data: res};
|
||||
case AppCallResponseTypes.ERROR:
|
||||
return {error: res};
|
||||
|
||||
default: {
|
||||
const errMsg = intl.formatMessage({
|
||||
id: 'apps.error.responses.unknown_type',
|
||||
defaultMessage: 'App response type not supported. Response type: {type}.',
|
||||
}, {type: responseType});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
const errMsg = error.message || intl.formatMessage({
|
||||
id: 'apps.error.responses.unexpected_error',
|
||||
defaultMessage: 'Received an unexpected error.',
|
||||
});
|
||||
return {error: makeCallErrorResponse(errMsg)};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function postEphemeralCallResponseForPost(response: AppCallResponse, message: string, post: Post): ActionFunc {
|
||||
return (dispatch: DispatchFunc) => {
|
||||
return dispatch(sendEphemeralPost(
|
||||
|
||||
@@ -373,7 +373,7 @@ export function showSearchModal(initialValue = '') {
|
||||
showModal(name, title, passProps, options);
|
||||
}
|
||||
|
||||
export const showAppForm = async (form, context, theme) => {
|
||||
export const showAppForm = async (form, call, theme) => {
|
||||
const closeButton = await CompassIcon.getImageSource('close', 24, theme.sidebarHeaderTextColor);
|
||||
|
||||
let submitButtons;
|
||||
@@ -397,7 +397,7 @@ export const showAppForm = async (form, context, theme) => {
|
||||
},
|
||||
};
|
||||
|
||||
const passProps = {form, context};
|
||||
const passProps = {form, call};
|
||||
showModal('AppForm', form.title, passProps, options);
|
||||
};
|
||||
|
||||
|
||||
@@ -232,6 +232,7 @@ export function handleSelectChannel(channelId) {
|
||||
dispatch(batchActions(actions, 'BATCH_SWITCH_CHANNEL'));
|
||||
|
||||
if (appsEnabled(state)) {
|
||||
//TODO improve sync method
|
||||
dispatch(fetchAppBindings(currentUserId, channelId));
|
||||
}
|
||||
console.log('channel switch to', channel?.display_name, channelId, (Date.now() - dt), 'ms'); //eslint-disable-line
|
||||
|
||||
@@ -166,9 +166,6 @@ describe('Actions.Views.Channel', () => {
|
||||
[currentTeamId]: {},
|
||||
},
|
||||
},
|
||||
apps: {
|
||||
pluginEnabled: true,
|
||||
},
|
||||
general: {
|
||||
config: {
|
||||
EnableLegacySidebar: 'true',
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import {doAppSubmit, postEphemeralCallResponseForCommandArgs} from '@actions/apps';
|
||||
import {doAppCall, postEphemeralCallResponseForCommandArgs} from '@actions/apps';
|
||||
import {AppCommandParser} from '@components/autocomplete/slash_suggestion/app_command_parser/app_command_parser';
|
||||
import {IntegrationTypes} from '@mm-redux/action_types';
|
||||
import {executeCommand as executeCommandService} from '@mm-redux/actions/integrations';
|
||||
import {AppCallResponseTypes} from '@mm-redux/constants/apps';
|
||||
import {AppCallResponseTypes, AppCallTypes} from '@mm-redux/constants/apps';
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
import {DispatchFunc, GetStateFunc, ActionFunc} from '@mm-redux/types/actions';
|
||||
import {AppCallResponse} from '@mm-redux/types/apps';
|
||||
@@ -41,19 +41,19 @@ export function executeCommand(message: string, channelId: string, rootId: strin
|
||||
if (appsAreEnabled) {
|
||||
const parser = new AppCommandParser({dispatch, getState}, intl, args.channel_id, args.team_id, args.root_id);
|
||||
if (parser.isAppCommand(msg)) {
|
||||
const {creq, errorMessage} = await parser.composeCommandSubmitCall(msg);
|
||||
const {call, errorMessage} = await parser.composeCallFromCommand(msg);
|
||||
const createErrorMessage = (errMessage: string) => {
|
||||
return {error: {message: errMessage}};
|
||||
};
|
||||
|
||||
if (!creq) {
|
||||
if (!call) {
|
||||
return createErrorMessage(errorMessage!);
|
||||
}
|
||||
|
||||
const res = await dispatch(doAppSubmit(creq, intl));
|
||||
const res = await dispatch(doAppCall(call, AppCallTypes.SUBMIT, intl));
|
||||
if (res.error) {
|
||||
const errorResponse = res.error as AppCallResponse;
|
||||
return createErrorMessage(errorResponse.text || intl.formatMessage({
|
||||
return createErrorMessage(errorResponse.error || intl.formatMessage({
|
||||
id: 'apps.error.unknown',
|
||||
defaultMessage: 'Unknown error.',
|
||||
}));
|
||||
@@ -61,14 +61,14 @@ export function executeCommand(message: string, channelId: string, rootId: strin
|
||||
const callResp = res.data as AppCallResponse;
|
||||
switch (callResp.type) {
|
||||
case AppCallResponseTypes.OK:
|
||||
if (callResp.text) {
|
||||
dispatch(postEphemeralCallResponseForCommandArgs(callResp, callResp.text, args));
|
||||
if (callResp.markdown) {
|
||||
dispatch(postEphemeralCallResponseForCommandArgs(callResp, callResp.markdown, args));
|
||||
}
|
||||
return {data: {}};
|
||||
case AppCallResponseTypes.FORM:
|
||||
return {data: {
|
||||
form: callResp.form,
|
||||
call: creq,
|
||||
call,
|
||||
}};
|
||||
case AppCallResponseTypes.NAVIGATE:
|
||||
return {data: {
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import AppsTypes from '@mm-redux/action_types/apps';
|
||||
import {refreshAppBindings} from '@mm-redux/actions/apps';
|
||||
import {fetchAppBindings, fetchThreadAppBindings} from '@mm-redux/actions/apps';
|
||||
import {getThreadAppsBindingsChannelId} from '@mm-redux/selectors/entities/apps';
|
||||
import {getCurrentChannelId} from '@mm-redux/selectors/entities/common';
|
||||
import {getCurrentUserId} from '@mm-redux/selectors/entities/users';
|
||||
import {ActionResult, DispatchFunc, GetStateFunc} from '@mm-redux/types/actions';
|
||||
import {appsEnabled} from '@utils/apps';
|
||||
|
||||
export function handleRefreshAppsBindings() {
|
||||
return (dispatch: DispatchFunc, getState: GetStateFunc): ActionResult => {
|
||||
if (appsEnabled(getState())) {
|
||||
dispatch(refreshAppBindings());
|
||||
const state = getState();
|
||||
if (!appsEnabled(state)) {
|
||||
return {data: true};
|
||||
}
|
||||
|
||||
dispatch(fetchAppBindings(getCurrentUserId(state), getCurrentChannelId(state)));
|
||||
|
||||
const threadChannelID = getThreadAppsBindingsChannelId(state);
|
||||
if (threadChannelID) {
|
||||
dispatch(fetchThreadAppBindings(getCurrentUserId(state), threadChannelID));
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export function handleAppsPluginEnabled() {
|
||||
return {
|
||||
type: AppsTypes.APPS_PLUGIN_ENABLED,
|
||||
};
|
||||
}
|
||||
|
||||
export function handleAppsPluginDisabled() {
|
||||
return {
|
||||
type: AppsTypes.APPS_PLUGIN_DISABLED,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,12 +7,11 @@ import {loadMe} from '@actions/views/user';
|
||||
import {Client4} from '@client/rest';
|
||||
import {WebsocketEvents} from '@constants';
|
||||
import {ChannelTypes, GeneralTypes, PreferenceTypes, TeamTypes, UserTypes, RoleTypes} from '@mm-redux/action_types';
|
||||
import {refreshAppBindings} from '@mm-redux/actions/apps';
|
||||
import {getThreads} from '@mm-redux/actions/threads';
|
||||
import {getProfilesByIds, getStatusesByIds} from '@mm-redux/actions/users';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {getCurrentChannelId, getCurrentChannelStats} from '@mm-redux/selectors/entities/channels';
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
import {getConfig, getFeatureFlagValue} from '@mm-redux/selectors/entities/general';
|
||||
import {getPostIdsInChannel} from '@mm-redux/selectors/entities/posts';
|
||||
import {isCollapsedThreadsEnabled} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
@@ -24,7 +23,7 @@ import {TeamMembership} from '@mm-redux/types/teams';
|
||||
import {WebSocketMessage} from '@mm-redux/types/websocket';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
import {removeUserFromList} from '@mm-redux/utils/user_utils';
|
||||
import {batchLoadCalls} from '@mmproducts/calls/store/actions/calls';
|
||||
import {loadCalls} from '@mmproducts/calls/store/actions/calls';
|
||||
import {
|
||||
handleCallStarted,
|
||||
handleCallUserConnected,
|
||||
@@ -36,15 +35,13 @@ import {
|
||||
handleCallChannelEnabled,
|
||||
handleCallChannelDisabled,
|
||||
handleCallScreenOn,
|
||||
handleCallScreenOff, handleCallUserRaiseHand, handleCallUserUnraiseHand,
|
||||
handleCallScreenOff,
|
||||
} from '@mmproducts/calls/store/actions/websockets';
|
||||
import {isSupportedServer} from '@mmproducts/calls/store/selectors/calls';
|
||||
import {appsConfiguredAsEnabled} from '@utils/apps';
|
||||
import {getChannelSinceValue} from '@utils/channels';
|
||||
import {semverFromServerVersion} from '@utils/general';
|
||||
import websocketClient from '@websocket';
|
||||
|
||||
import {handleAppsPluginDisabled, handleAppsPluginEnabled, handleRefreshAppsBindings} from './apps';
|
||||
import {handleRefreshAppsBindings} from './apps';
|
||||
import {handleSidebarCategoryCreated, handleSidebarCategoryDeleted, handleSidebarCategoryOrderUpdated, handleSidebarCategoryUpdated} from './categories';
|
||||
import {
|
||||
handleChannelConvertedEvent,
|
||||
@@ -153,7 +150,6 @@ export function doReconnect(now: number) {
|
||||
const currentChannelId = getCurrentChannelId(state);
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const users = getUsers(state);
|
||||
const isSupportedServerCalls = isSupportedServer(state);
|
||||
const {lastDisconnectAt} = state.websocket;
|
||||
const actions: GenericAction[] = [];
|
||||
|
||||
@@ -162,16 +158,12 @@ export function doReconnect(now: number) {
|
||||
setChannelRetryFailed(false),
|
||||
], 'BATCH_WS_SUCCESS'));
|
||||
|
||||
if (appsConfiguredAsEnabled(state)) {
|
||||
dispatch(refreshAppBindings());
|
||||
}
|
||||
|
||||
try {
|
||||
const {data: me}: any = await dispatch(loadMe(null, null, true));
|
||||
|
||||
if (!me.error) {
|
||||
if (isSupportedServerCalls) {
|
||||
dispatch(batchLoadCalls(true));
|
||||
if (getFeatureFlagValue(getState(), 'CallsMobile') === 'true') {
|
||||
dispatch(loadCalls());
|
||||
}
|
||||
|
||||
const roles = [];
|
||||
@@ -438,10 +430,6 @@ function handleEvent(msg: WebSocketMessage) {
|
||||
return dispatch(handleThreadFollowChanged(msg));
|
||||
case WebsocketEvents.APPS_FRAMEWORK_REFRESH_BINDINGS:
|
||||
return dispatch(handleRefreshAppsBindings());
|
||||
case WebsocketEvents.APPS_FRAMEWORK_PLUGIN_ENABLED:
|
||||
return dispatch(handleAppsPluginEnabled());
|
||||
case WebsocketEvents.APPS_FRAMEWORK_PLUGIN_DISABLED:
|
||||
return dispatch(handleAppsPluginDisabled());
|
||||
case WebsocketEvents.SIDEBAR_CATEGORY_CREATED:
|
||||
return dispatch(handleSidebarCategoryCreated(msg));
|
||||
case WebsocketEvents.SIDEBAR_CATEGORY_UPDATED:
|
||||
@@ -450,37 +438,36 @@ function handleEvent(msg: WebSocketMessage) {
|
||||
return dispatch(handleSidebarCategoryDeleted(msg));
|
||||
case WebsocketEvents.SIDEBAR_CATEGORY_ORDER_UPDATED:
|
||||
return dispatch(handleSidebarCategoryOrderUpdated(msg));
|
||||
}
|
||||
|
||||
// Calls ws events:
|
||||
case WebsocketEvents.CALLS_CHANNEL_ENABLED:
|
||||
return dispatch(handleCallChannelEnabled(msg));
|
||||
case WebsocketEvents.CALLS_CHANNEL_DISABLED:
|
||||
return dispatch(handleCallChannelDisabled(msg));
|
||||
case WebsocketEvents.CALLS_USER_CONNECTED:
|
||||
handleCallUserConnected(dispatch, getState, msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_USER_DISCONNECTED:
|
||||
return dispatch(handleCallUserDisconnected(msg));
|
||||
case WebsocketEvents.CALLS_USER_MUTED:
|
||||
return dispatch(handleCallUserMuted(msg));
|
||||
case WebsocketEvents.CALLS_USER_UNMUTED:
|
||||
return dispatch(handleCallUserUnmuted(msg));
|
||||
case WebsocketEvents.CALLS_USER_VOICE_ON:
|
||||
handleCallUserVoiceOn(msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_USER_VOICE_OFF:
|
||||
handleCallUserVoiceOff(msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_CALL_START:
|
||||
return dispatch(handleCallStarted(msg));
|
||||
case WebsocketEvents.CALLS_SCREEN_ON:
|
||||
return dispatch(handleCallScreenOn(msg));
|
||||
case WebsocketEvents.CALLS_SCREEN_OFF:
|
||||
return dispatch(handleCallScreenOff(msg));
|
||||
case WebsocketEvents.CALLS_USER_RAISE_HAND:
|
||||
return dispatch(handleCallUserRaiseHand(msg));
|
||||
case WebsocketEvents.CALLS_USER_UNRAISE_HAND:
|
||||
return dispatch(handleCallUserUnraiseHand(msg));
|
||||
if (getFeatureFlagValue(getState(), 'CallsMobile') === 'true') {
|
||||
switch (msg.event) {
|
||||
case WebsocketEvents.CALLS_CHANNEL_ENABLED:
|
||||
return dispatch(handleCallChannelEnabled(msg));
|
||||
case WebsocketEvents.CALLS_CHANNEL_DISABLED:
|
||||
return dispatch(handleCallChannelDisabled(msg));
|
||||
case WebsocketEvents.CALLS_USER_CONNECTED:
|
||||
handleCallUserConnected(dispatch, getState, msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_USER_DISCONNECTED:
|
||||
return dispatch(handleCallUserDisconnected(msg));
|
||||
case WebsocketEvents.CALLS_USER_MUTED:
|
||||
return dispatch(handleCallUserMuted(msg));
|
||||
case WebsocketEvents.CALLS_USER_UNMUTED:
|
||||
return dispatch(handleCallUserUnmuted(msg));
|
||||
case WebsocketEvents.CALLS_USER_VOICE_ON:
|
||||
handleCallUserVoiceOn(msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_USER_VOICE_OFF:
|
||||
handleCallUserVoiceOff(msg);
|
||||
break;
|
||||
case WebsocketEvents.CALLS_CALL_START:
|
||||
return dispatch(handleCallStarted(msg));
|
||||
case WebsocketEvents.CALLS_SCREEN_ON:
|
||||
return dispatch(handleCallScreenOn(msg));
|
||||
case WebsocketEvents.CALLS_SCREEN_OFF:
|
||||
return dispatch(handleCallScreenOff(msg));
|
||||
}
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
|
||||
@@ -84,7 +84,6 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
const channel2 = TestHelper.fakeChannelWithId(team.id);
|
||||
const cMember1 = TestHelper.fakeChannelMember(me.id, channel1.id);
|
||||
const cMember2 = TestHelper.fakeChannelMember(me.id, channel2.id);
|
||||
const post1 = TestHelper.fakePost(channel1.id);
|
||||
|
||||
const currentTeamId = team.id;
|
||||
const currentUserId = me.id;
|
||||
@@ -93,9 +92,7 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
const initialState = {
|
||||
entities: {
|
||||
general: {
|
||||
config: {
|
||||
FeatureFlagAppsEnabled: 'false',
|
||||
},
|
||||
config: {},
|
||||
},
|
||||
teams: {
|
||||
currentTeamId,
|
||||
@@ -109,7 +106,7 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
channels: {
|
||||
currentChannelId,
|
||||
channels: {
|
||||
currentChannelId: channel1.id,
|
||||
currentChannelId: channel1,
|
||||
},
|
||||
},
|
||||
users: {
|
||||
@@ -122,20 +119,8 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
myPreferences: {},
|
||||
},
|
||||
posts: {
|
||||
posts: {
|
||||
[post1.id]: post1,
|
||||
},
|
||||
postsInChannel: {
|
||||
[channel1.id]: [
|
||||
{
|
||||
recent: true,
|
||||
order: [post1.id],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
apps: {
|
||||
pluginEnabled: true,
|
||||
posts: {},
|
||||
postsInChannel: {},
|
||||
},
|
||||
},
|
||||
websocket: {
|
||||
@@ -143,13 +128,6 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
lastConnectAt: 0,
|
||||
lastDisconnectAt: 0,
|
||||
},
|
||||
views: {
|
||||
channel: {
|
||||
lastGetPosts: {
|
||||
[channel1.id]: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -192,12 +170,14 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
await TestHelper.tearDown();
|
||||
});
|
||||
|
||||
it('handle doReconnect base case', async () => {
|
||||
it('handle doReconnect', async () => {
|
||||
const state = {...initialState};
|
||||
const testStore = await mockStore(state);
|
||||
const timestamp = 1000;
|
||||
const expectedActions = [
|
||||
'BATCH_WS_SUCCESS',
|
||||
];
|
||||
const expectedMissingActions = [
|
||||
'BATCH_WS_RECONNECT',
|
||||
];
|
||||
|
||||
@@ -217,6 +197,7 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
await TestHelper.wait(300);
|
||||
const actionTypes = testStore.getActions().map((a) => a.type);
|
||||
expect(actionTypes).toEqual(expectedActions);
|
||||
expect(actionTypes).not.toEqual(expect.arrayContaining(expectedMissingActions));
|
||||
});
|
||||
|
||||
it('handle doReconnect after the current channel was archived or the user left it', async () => {
|
||||
@@ -279,6 +260,8 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
const timestamp = 1000;
|
||||
const expectedActions = [
|
||||
'BATCH_WS_SUCCESS',
|
||||
];
|
||||
const expectedMissingActions = [
|
||||
'BATCH_WS_RECONNECT',
|
||||
];
|
||||
|
||||
@@ -299,6 +282,7 @@ describe('Actions.Websocket doReconnect', () => {
|
||||
|
||||
const actions = testStore.getActions().map((a) => a.type);
|
||||
expect(actions).toEqual(expect.arrayContaining(expectedActions));
|
||||
expect(actions).not.toEqual(expect.arrayContaining(expectedMissingActions));
|
||||
});
|
||||
|
||||
it('handle doReconnect after the current channel was archived and setting is off', async () => {
|
||||
|
||||
@@ -3,21 +3,21 @@
|
||||
|
||||
import {buildQueryString} from '@mm-redux/utils/helpers';
|
||||
|
||||
import type {AppBinding, AppCallRequest, AppCallResponse} from '@mm-redux/types/apps';
|
||||
import type {AppBinding, AppCallRequest, AppCallResponse, AppCallType} from '@mm-redux/types/apps';
|
||||
|
||||
export interface ClientAppsMix {
|
||||
executeAppCall: (call: AppCallRequest, trackAsSubmit: boolean) => Promise<AppCallResponse>;
|
||||
executeAppCall: (call: AppCallRequest, type: AppCallType) => Promise<AppCallResponse>;
|
||||
getAppsBindings: (userID: string, channelID: string, teamID: string) => Promise<AppBinding[]>;
|
||||
}
|
||||
|
||||
const ClientApps = (superclass: any) => class extends superclass {
|
||||
executeAppCall = async (call: AppCallRequest, trackAsSubmit: boolean) => {
|
||||
executeAppCall = async (call: AppCallRequest, type: AppCallType) => {
|
||||
const callCopy = {
|
||||
...call,
|
||||
path: `${call.path}/${type}`,
|
||||
context: {
|
||||
...call.context,
|
||||
user_agent: 'mobile',
|
||||
track_as_submit: trackAsSubmit,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import {RNFetchBlobFetchRepsonse} from 'rn-fetch-blob';
|
||||
import urlParse from 'url-parse';
|
||||
|
||||
import Calls from '@constants/calls';
|
||||
import {Options} from '@mm-redux/types/client4';
|
||||
|
||||
import * as ClientConstants from './constants';
|
||||
@@ -287,16 +286,12 @@ export default class ClientBase {
|
||||
return `${this.getUserThreadsRoute(userId, teamId)}/${threadId}`;
|
||||
}
|
||||
|
||||
getPluginsRoute() {
|
||||
return `${this.getBaseRoute()}/plugins`;
|
||||
}
|
||||
|
||||
getAppsProxyRoute() {
|
||||
return `${this.url}/plugins/com.mattermost.apps`;
|
||||
}
|
||||
|
||||
getCallsRoute() {
|
||||
return `${this.url}/plugins/${Calls.PluginId}`;
|
||||
return `${this.url}/plugins/com.mattermost.calls`;
|
||||
}
|
||||
|
||||
// Client Helpers
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import ClientPlugins, {ClientPluginsMix} from '@client/rest/plugins';
|
||||
import ClientCalls, {ClientCallsMix} from '@mmproducts/calls/client/rest';
|
||||
import mix from '@utils/mix';
|
||||
|
||||
@@ -37,8 +36,7 @@ interface Client extends ClientBase,
|
||||
ClientTeamsMix,
|
||||
ClientTosMix,
|
||||
ClientUsersMix,
|
||||
ClientCallsMix,
|
||||
ClientPluginsMix
|
||||
ClientCallsMix
|
||||
{}
|
||||
|
||||
class Client extends mix(ClientBase).with(
|
||||
@@ -57,7 +55,6 @@ class Client extends mix(ClientBase).with(
|
||||
ClientTos,
|
||||
ClientUsers,
|
||||
ClientCalls,
|
||||
ClientPlugins,
|
||||
) {}
|
||||
|
||||
const Client4 = new Client();
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {ClientPluginManifest} from '@mm-redux/types/plugins';
|
||||
|
||||
export interface ClientPluginsMix {
|
||||
getPluginsManifests: () => Promise<ClientPluginManifest[]>;
|
||||
}
|
||||
|
||||
const ClientPlugins = (superclass: any) => class extends superclass {
|
||||
getPluginsManifests = async () => {
|
||||
return this.doFetch(
|
||||
`${this.getPluginsRoute()}/webapp`,
|
||||
{method: 'get'},
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default ClientPlugins;
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from './app_command_parser';
|
||||
import {
|
||||
AppCallResponseTypes,
|
||||
AppCallTypes,
|
||||
AutocompleteSuggestion,
|
||||
} from './app_command_parser_dependencies';
|
||||
import {
|
||||
@@ -36,7 +37,6 @@ describe('AppCommandParser', () => {
|
||||
bindings,
|
||||
threadBindings: bindings,
|
||||
threadBindingsForms: {},
|
||||
pluginEnabled: true,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
@@ -243,7 +243,7 @@ describe('AppCommandParser', () => {
|
||||
autocomplete: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('create');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/create-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/create-issue');
|
||||
expect(parsed.incomplete).toBe('epic2');
|
||||
expect(parsed.incompleteStart).toBe(75);
|
||||
expect(parsed.values?.project).toBe('P 1');
|
||||
@@ -254,7 +254,7 @@ describe('AppCommandParser', () => {
|
||||
submit: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('create');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/create-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/create-issue');
|
||||
expect(parsed.values?.project).toBe('P 1');
|
||||
expect(parsed.values?.epic).toBe('epic2');
|
||||
expect(parsed.values?.summary).toBe('SUM MA RY');
|
||||
@@ -267,7 +267,7 @@ describe('AppCommandParser', () => {
|
||||
autocomplete: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('create');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/create-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/create-issue');
|
||||
expect(parsed.incomplete).toBe('epic2');
|
||||
expect(parsed.incompleteStart).toBe(75);
|
||||
expect(parsed.values?.project).toBe('P 1');
|
||||
@@ -278,7 +278,7 @@ describe('AppCommandParser', () => {
|
||||
submit: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('create');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/create-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/create-issue');
|
||||
expect(parsed.values?.project).toBe('P 1');
|
||||
expect(parsed.values?.epic).toBe('epic2');
|
||||
expect(parsed.values?.summary).toBe('SUM MA RY');
|
||||
@@ -291,7 +291,7 @@ describe('AppCommandParser', () => {
|
||||
autocomplete: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('create');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/create-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/create-issue');
|
||||
expect(parsed.incomplete).toBe('M');
|
||||
expect(parsed.incompleteStart).toBe(65);
|
||||
expect(parsed.values?.project).toBe('KT');
|
||||
@@ -300,7 +300,7 @@ describe('AppCommandParser', () => {
|
||||
submit: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('create');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/create-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/create-issue');
|
||||
expect(parsed.values?.epic).toBe('M');
|
||||
}},
|
||||
},
|
||||
@@ -310,7 +310,7 @@ describe('AppCommandParser', () => {
|
||||
autocomplete: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('view');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/view-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/view-issue');
|
||||
expect(parsed.incomplete).toBe('MM-123');
|
||||
expect(parsed.incompleteStart).toBe(33);
|
||||
expect(parsed.values?.project).toBe('P 1');
|
||||
@@ -319,7 +319,7 @@ describe('AppCommandParser', () => {
|
||||
submit: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('view');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/view-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/view-issue');
|
||||
expect(parsed.values?.project).toBe('P 1');
|
||||
expect(parsed.values?.issue).toBe('MM-123');
|
||||
}},
|
||||
@@ -330,7 +330,7 @@ describe('AppCommandParser', () => {
|
||||
submit: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.StartParameter);
|
||||
expect(parsed.binding?.label).toBe('view');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/view-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/view-issue');
|
||||
expect(parsed.incomplete).toBe('');
|
||||
expect(parsed.incompleteStart).toBe(17);
|
||||
expect(parsed.values).toEqual({});
|
||||
@@ -342,14 +342,14 @@ describe('AppCommandParser', () => {
|
||||
autocomplete: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.FlagValueSeparator);
|
||||
expect(parsed.binding?.label).toBe('create');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/create-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/create-issue');
|
||||
expect(parsed.incomplete).toBe('');
|
||||
expect(parsed.values).toEqual({});
|
||||
}},
|
||||
submit: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndValue);
|
||||
expect(parsed.binding?.label).toBe('create');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/create-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/create-issue');
|
||||
expect(parsed.incomplete).toBe('');
|
||||
expect(parsed.values).toEqual({
|
||||
summary: '',
|
||||
@@ -362,7 +362,7 @@ describe('AppCommandParser', () => {
|
||||
autocomplete: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.TickValue);
|
||||
expect(parsed.binding?.label).toBe('view');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/view-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/view-issue');
|
||||
expect(parsed.incomplete).toBe('P 1');
|
||||
expect(parsed.incompleteStart).toBe(27);
|
||||
expect(parsed.values?.project).toBe(undefined);
|
||||
@@ -376,7 +376,7 @@ describe('AppCommandParser', () => {
|
||||
autocomplete: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.QuotedValue);
|
||||
expect(parsed.binding?.label).toBe('view');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/view-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/view-issue');
|
||||
expect(parsed.incomplete).toBe('P 1');
|
||||
expect(parsed.incompleteStart).toBe(27);
|
||||
expect(parsed.values?.project).toBe(undefined);
|
||||
@@ -390,7 +390,7 @@ describe('AppCommandParser', () => {
|
||||
autocomplete: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndQuotedValue);
|
||||
expect(parsed.binding?.label).toBe('view');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/view-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/view-issue');
|
||||
expect(parsed.incomplete).toBe('P 1');
|
||||
expect(parsed.incompleteStart).toBe(27);
|
||||
expect(parsed.values?.project).toBe(undefined);
|
||||
@@ -399,7 +399,7 @@ describe('AppCommandParser', () => {
|
||||
submit: {verify: (parsed: ParsedCommand): void => {
|
||||
expect(parsed.state).toBe(ParseState.EndQuotedValue);
|
||||
expect(parsed.binding?.label).toBe('view');
|
||||
expect(parsed.resolvedForm?.submit?.path).toBe('/view-issue');
|
||||
expect(parsed.form?.call?.path).toBe('/view-issue');
|
||||
expect(parsed.values?.project).toBe('P 1');
|
||||
expect(parsed.values?.issue).toBe(undefined);
|
||||
}},
|
||||
@@ -923,7 +923,7 @@ describe('AppCommandParser', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('composeCommandSubmitCall', () => {
|
||||
describe('composeCallFromCommand', () => {
|
||||
const base = {
|
||||
context: {
|
||||
app_id: 'jira',
|
||||
@@ -939,9 +939,13 @@ describe('AppCommandParser', () => {
|
||||
const cmd = '/jira issue create';
|
||||
const values = {};
|
||||
|
||||
const {creq} = await parser.composeCommandSubmitCall(cmd);
|
||||
expect(creq).toEqual({
|
||||
const {call} = await parser.composeCallFromCommand(cmd);
|
||||
expect(call).toEqual({
|
||||
...base,
|
||||
context: {
|
||||
...base.context,
|
||||
location: '/command/jira/issue/create',
|
||||
},
|
||||
raw_command: cmd,
|
||||
expand: {},
|
||||
query: undefined,
|
||||
@@ -958,13 +962,17 @@ describe('AppCommandParser', () => {
|
||||
label: 'Dylan Epic',
|
||||
value: 'epic1',
|
||||
},
|
||||
verbose: true,
|
||||
verbose: 'true',
|
||||
project: '',
|
||||
};
|
||||
|
||||
const {creq} = await parser.composeCommandSubmitCall(cmd);
|
||||
expect(creq).toEqual({
|
||||
const {call} = await parser.composeCallFromCommand(cmd);
|
||||
expect(call).toEqual({
|
||||
...base,
|
||||
context: {
|
||||
...base.context,
|
||||
location: '/command/jira/issue/create',
|
||||
},
|
||||
expand: {},
|
||||
selected_field: undefined,
|
||||
query: undefined,
|
||||
@@ -1001,7 +1009,7 @@ describe('AppCommandParser', () => {
|
||||
team_id: 'team_id',
|
||||
},
|
||||
expand: {},
|
||||
path: '/create-issue-lookup',
|
||||
path: '/create-issue',
|
||||
query: 'special',
|
||||
raw_command: '/jira issue create --summary "The summary" --epic epic1 --project special',
|
||||
selected_field: 'project',
|
||||
@@ -1012,7 +1020,7 @@ describe('AppCommandParser', () => {
|
||||
value: 'epic1',
|
||||
},
|
||||
},
|
||||
}, false);
|
||||
}, AppCallTypes.LOOKUP);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,22 +3,18 @@
|
||||
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
export {doAppFetchForm, doAppLookup} from '@actions/apps';
|
||||
|
||||
export {getChannelByNameAndTeamName, getChannel, autocompleteChannels} from '@mm-redux/actions/channels';
|
||||
import {getUserByUsername, getUser, autocompleteUsers, autocompleteUsersInChannel} from '@mm-redux/actions/users';
|
||||
export {AppsTypes} from '@mm-redux/action_types';
|
||||
export {makeAppBindingsSelector, makeRHSAppBindingSelector, getAppCommandForm, getAppRHSCommandForm} from '@mm-redux/selectors/entities/apps';
|
||||
export {getChannel as selectChannel, getCurrentChannel, getChannelByName as selectChannelByName} from '@mm-redux/selectors/entities/channels';
|
||||
export {getPost} from '@mm-redux/selectors/entities/posts';
|
||||
import {getUserByUsername, getUser, autocompleteUsers} from '@mm-redux/actions/users';
|
||||
import {getCurrentTeamId, getCurrentTeam} from '@mm-redux/selectors/entities/teams';
|
||||
export {getUserByUsername as selectUserByUsername, getUser as selectUser} from '@mm-redux/selectors/entities/users';
|
||||
import {DispatchFunc} from '@mm-redux/types/actions';
|
||||
import ReduxStore from '@store/store';
|
||||
import {
|
||||
ActionFunc,
|
||||
DispatchFunc,
|
||||
} from '@mm-redux/types/actions';
|
||||
import Store from '@store/store';
|
||||
|
||||
import type {ParsedCommand} from './app_command_parser';
|
||||
import type {AutocompleteSuggestion} from '@mm-redux/types/integrations';
|
||||
|
||||
export type {
|
||||
AppCall,
|
||||
AppCallRequest,
|
||||
AppBinding,
|
||||
AppField,
|
||||
@@ -33,22 +29,36 @@ export type {
|
||||
AutocompleteUserSelect,
|
||||
AutocompleteChannelSelect,
|
||||
AppLookupResponse,
|
||||
UserAutocomplete,
|
||||
} from '@mm-redux/types/apps';
|
||||
export type {Channel} from '@mm-redux/types/channels';
|
||||
import type {AutocompleteSuggestion} from '@mm-redux/types/integrations';
|
||||
import type {GlobalState} from '@mm-redux/types/store';
|
||||
|
||||
export type {
|
||||
DispatchFunc,
|
||||
GlobalState,
|
||||
};
|
||||
DoAppCallResult,
|
||||
} from 'types/actions/apps';
|
||||
|
||||
export {AppsTypes} from '@mm-redux/action_types';
|
||||
|
||||
export type {AutocompleteSuggestion};
|
||||
|
||||
export type {DoAppCallResult} from 'types/actions/apps';
|
||||
export type {
|
||||
Channel,
|
||||
} from '@mm-redux/types/channels';
|
||||
|
||||
export type {
|
||||
GlobalState,
|
||||
} from '@mm-redux/types/store';
|
||||
|
||||
export type {
|
||||
DispatchFunc,
|
||||
};
|
||||
|
||||
export type {
|
||||
UserProfile,
|
||||
} from '@mm-redux/types/users';
|
||||
|
||||
export {
|
||||
AppBindingLocations,
|
||||
AppCallTypes,
|
||||
AppFieldTypes,
|
||||
AppCallResponseTypes,
|
||||
COMMAND_SUGGESTION_ERROR,
|
||||
@@ -56,37 +66,43 @@ export {
|
||||
COMMAND_SUGGESTION_USER,
|
||||
} from '@mm-redux/constants/apps';
|
||||
|
||||
export {makeAppBindingsSelector, makeRHSAppBindingSelector, getAppCommandForm, getAppRHSCommandForm} from '@mm-redux/selectors/entities/apps';
|
||||
|
||||
export {getPost} from '@mm-redux/selectors/entities/posts';
|
||||
export {getChannel as selectChannel, getCurrentChannel, getChannelByName as selectChannelByName} from '@mm-redux/selectors/entities/channels';
|
||||
|
||||
export {
|
||||
getCurrentTeamId,
|
||||
getCurrentTeam,
|
||||
};
|
||||
|
||||
export {getUserByUsername as selectUserByUsername, getUser as selectUser} from '@mm-redux/selectors/entities/users';
|
||||
|
||||
export {
|
||||
getUserByUsername,
|
||||
getUser,
|
||||
autocompleteUsers,
|
||||
autocompleteUsersInChannel,
|
||||
};
|
||||
|
||||
export {getChannelByNameAndTeamName, getChannel, autocompleteChannels} from '@mm-redux/actions/channels';
|
||||
|
||||
export {doAppCall} from '@actions/apps';
|
||||
export {
|
||||
createCallRequest,
|
||||
filterEmptyOptions,
|
||||
} from '@utils/apps';
|
||||
|
||||
export interface Store {
|
||||
dispatch: DispatchFunc;
|
||||
getState: () => GlobalState;
|
||||
}
|
||||
export const getStore = () => Store.redux;
|
||||
|
||||
export const getStore = () => ReduxStore.redux as Store;
|
||||
export const autocompleteUsersInChannel = (prefix: string, channelID: string): ActionFunc => {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const currentTeamID = getCurrentTeamId(state);
|
||||
return dispatch(autocompleteUsers(prefix, currentTeamID, channelID));
|
||||
};
|
||||
};
|
||||
|
||||
export const EXECUTE_CURRENT_COMMAND_ITEM_ID = '_execute_current_command';
|
||||
export const OPEN_COMMAND_IN_MODAL_ITEM_ID = '_open_command_in_modal';
|
||||
|
||||
export const getOpenInModalSuggestion = (_: ParsedCommand): AutocompleteSuggestion | null => { // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
// Not supported on mobile yet
|
||||
return null;
|
||||
};
|
||||
|
||||
export type ExtendedAutocompleteSuggestion = AutocompleteSuggestion & {
|
||||
type?: string;
|
||||
|
||||
@@ -27,6 +27,7 @@ export const reduxTestState = {
|
||||
display_name: 'Default',
|
||||
delete_at: 0,
|
||||
type: 'O',
|
||||
total_msg_count: 10,
|
||||
team_id: 'team_id',
|
||||
},
|
||||
current_user_id__existingId: {
|
||||
@@ -35,16 +36,13 @@ export const reduxTestState = {
|
||||
display_name: 'Default',
|
||||
delete_at: 0,
|
||||
type: '0',
|
||||
total_msg_count: 0,
|
||||
team_id: 'team_id',
|
||||
},
|
||||
},
|
||||
channelsInTeam: {
|
||||
'team-id': ['current_channel_id'],
|
||||
},
|
||||
messageCounts: {
|
||||
current_channel_id: {total: 10},
|
||||
current_user_id__existingId: {total: 0},
|
||||
},
|
||||
},
|
||||
teams: {
|
||||
currentTeamId: 'team-id',
|
||||
@@ -105,7 +103,7 @@ export const viewCommand: AppBinding = {
|
||||
location: '/command/jira/issue/view',
|
||||
description: 'View details of a Jira issue',
|
||||
form: {
|
||||
submit: {
|
||||
call: {
|
||||
path: '/view-issue',
|
||||
},
|
||||
fields: [
|
||||
@@ -116,9 +114,6 @@ export const viewCommand: AppBinding = {
|
||||
type: AppFieldTypes.DYNAMIC_SELECT,
|
||||
hint: 'The Jira project hint',
|
||||
is_required: true,
|
||||
lookup: {
|
||||
path: '/view-issue-lookup',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'issue',
|
||||
@@ -140,7 +135,7 @@ export const createCommand: AppBinding = {
|
||||
icon: 'Create icon',
|
||||
hint: 'Create hint',
|
||||
form: {
|
||||
submit: {
|
||||
call: {
|
||||
path: '/create-issue',
|
||||
},
|
||||
fields: [
|
||||
@@ -150,9 +145,6 @@ export const createCommand: AppBinding = {
|
||||
description: 'The Jira project description',
|
||||
type: AppFieldTypes.DYNAMIC_SELECT,
|
||||
hint: 'The Jira project hint',
|
||||
lookup: {
|
||||
path: '/create-issue-lookup',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'summary',
|
||||
@@ -197,7 +189,7 @@ export const restCommand: AppBinding = {
|
||||
icon: 'rest icon',
|
||||
hint: 'rest hint',
|
||||
form: {
|
||||
submit: {
|
||||
call: {
|
||||
path: '/create-issue',
|
||||
},
|
||||
fields: [
|
||||
@@ -260,9 +252,6 @@ export const testBindings: AppBinding[] = [
|
||||
label: 'sub1',
|
||||
description: 'Some Description',
|
||||
form: {
|
||||
submit: {
|
||||
path: '/submit_other',
|
||||
},
|
||||
fields: [{
|
||||
name: 'fieldname',
|
||||
label: 'fieldlabel',
|
||||
|
||||
@@ -32,7 +32,6 @@ const makeStore = async (bindings: AppBinding[]) => {
|
||||
bindingsForms: {},
|
||||
threadBindings: bindings,
|
||||
threadBindingsForms: {},
|
||||
pluginEnabled: true,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
@@ -28,10 +28,7 @@ const makeStore = async (bindings: AppBinding[]) => {
|
||||
...reduxTestState,
|
||||
entities: {
|
||||
...reduxTestState.entities,
|
||||
apps: {
|
||||
bindings,
|
||||
pluginEnabled: true,
|
||||
},
|
||||
apps: {bindings},
|
||||
},
|
||||
} as any;
|
||||
const testStore = await mockStore(initialState);
|
||||
|
||||
@@ -21,7 +21,6 @@ import FormattedText from '@components/formatted_text';
|
||||
import Loading from '@components/loading';
|
||||
import StatusBar from '@components/status_bar';
|
||||
import TextInputWithLocalizedPlaceholder from '@components/text_input_with_localized_placeholder';
|
||||
import {ViewTypes} from '@constants';
|
||||
import DEVICE from '@constants/device';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {t} from '@utils/i18n';
|
||||
@@ -131,7 +130,7 @@ export default class EditChannelInfo extends PureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
const displayNameExists = displayName && displayName.length >= ViewTypes.MIN_CHANNELNAME_LENGTH;
|
||||
const displayNameExists = displayName && displayName.length >= 2;
|
||||
this.props.enableRightButton(displayNameExists);
|
||||
};
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ describe('Friendly Date', () => {
|
||||
expect(daysAgoMaxText.getByText('30 days ago')).toBeTruthy();
|
||||
|
||||
const monthsAgo = new Date();
|
||||
monthsAgo.setDate(monthsAgo.getDate() - 60);
|
||||
monthsAgo.setMonth(monthsAgo.getMonth() - 2);
|
||||
const monthsAgoText = renderWithIntl(
|
||||
<FriendlyDate value={monthsAgo}/>,
|
||||
);
|
||||
|
||||
@@ -37,7 +37,7 @@ export function getFriendlyDate(intl: typeof intlShape, inputDate: number | Date
|
||||
|
||||
// Message: Minutes Ago
|
||||
if (difference < DateTypes.SECONDS.HOUR) {
|
||||
const minutes = Math.floor(Math.round((10 * difference) / DateTypes.SECONDS.MINUTE) / 10);
|
||||
const minutes = Math.floor(difference / DateTypes.SECONDS.MINUTE);
|
||||
return intl.formatMessage({
|
||||
id: 'friendly_date.minsAgo',
|
||||
defaultMessage: '{count} {count, plural, one {min} other {mins}} ago',
|
||||
@@ -48,7 +48,7 @@ export function getFriendlyDate(intl: typeof intlShape, inputDate: number | Date
|
||||
|
||||
// Message: Hours Ago
|
||||
if (difference < DateTypes.SECONDS.DAY) {
|
||||
const hours = Math.floor(Math.round((10 * difference) / DateTypes.SECONDS.HOUR) / 10);
|
||||
const hours = Math.floor(difference / DateTypes.SECONDS.HOUR);
|
||||
return intl.formatMessage({
|
||||
id: 'friendly_date.hoursAgo',
|
||||
defaultMessage: '{count} {count, plural, one {hour} other {hours}} ago',
|
||||
@@ -67,7 +67,7 @@ export function getFriendlyDate(intl: typeof intlShape, inputDate: number | Date
|
||||
}
|
||||
const completedAMonth = today.getMonth() !== date.getMonth() && today.getDate() >= date.getDate();
|
||||
if (!completedAMonth) {
|
||||
const days = Math.floor(Math.round((10 * difference) / DateTypes.SECONDS.DAY) / 10) || 1;
|
||||
const days = Math.floor(difference / DateTypes.SECONDS.DAY) || 1;
|
||||
return intl.formatMessage({
|
||||
id: 'friendly_date.daysAgo',
|
||||
defaultMessage: '{count} {count, plural, one {day} other {days}} ago',
|
||||
@@ -83,7 +83,7 @@ export function getFriendlyDate(intl: typeof intlShape, inputDate: number | Date
|
||||
today.getMonth() >= date.getMonth() &&
|
||||
today.getDate() >= date.getDate();
|
||||
if (!completedAnYear) {
|
||||
const months = Math.floor(Math.round((10 * difference) / DateTypes.SECONDS.DAYS_30) / 10) || 1;
|
||||
const months = Math.floor(difference / DateTypes.SECONDS.DAYS_30) || 1;
|
||||
return intl.formatMessage({
|
||||
id: 'friendly_date.monthsAgo',
|
||||
defaultMessage: '{count} {count, plural, one {month} other {months}} ago',
|
||||
@@ -94,7 +94,7 @@ export function getFriendlyDate(intl: typeof intlShape, inputDate: number | Date
|
||||
}
|
||||
|
||||
// Message: Years Ago
|
||||
const years = Math.floor(Math.round((10 * difference) / DateTypes.SECONDS.DAYS_365) / 10) || 1;
|
||||
const years = Math.floor(difference / DateTypes.SECONDS.DAYS_365) || 1;
|
||||
return intl.formatMessage({
|
||||
id: 'friendly_date.yearsAgo',
|
||||
defaultMessage: '{count} {count, plural, one {year} other {years}} ago',
|
||||
|
||||
@@ -17,8 +17,6 @@ function mapStateToProps(state, ownProps) {
|
||||
mentionKeys: ownProps.mentionKeys || getAllUserMentionKeys(state),
|
||||
minimumHashtagLength: MinimumHashtagLength ? parseInt(MinimumHashtagLength, 10) : 3,
|
||||
theme: getTheme(state),
|
||||
enableLatex: getConfig(state).EnableLatex === 'true',
|
||||
enableInlineLatex: getConfig(state).EnableInlineLatex === 'true',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LatexCodeBlock should have 2 lines 1`] = `
|
||||
<TouchableWithFeedbackIOS
|
||||
onLongPress={[Function]}
|
||||
onPress={[Function]}
|
||||
type="opacity"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"borderColor": "rgba(63,67,80,0.15)",
|
||||
"borderRadius": 3,
|
||||
"borderWidth": 0.5,
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column",
|
||||
"paddingLeft": 6,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginLeft": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{2}{1} = 2"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginLeft": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="2_{nd} = line"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#192a4d",
|
||||
"justifyContent": "center",
|
||||
"opacity": 0.8,
|
||||
"padding": 6,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontSize": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
LaTeX
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithFeedbackIOS>
|
||||
`;
|
||||
|
||||
exports[`LatexCodeBlock should have moreLinesText 1`] = `
|
||||
<TouchableWithFeedbackIOS
|
||||
onLongPress={[Function]}
|
||||
onPress={[Function]}
|
||||
type="opacity"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"borderColor": "rgba(63,67,80,0.15)",
|
||||
"borderRadius": 3,
|
||||
"borderWidth": 0.5,
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column",
|
||||
"paddingLeft": 6,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginLeft": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{5}{2} = 2"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginLeft": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="2_{nd} = line"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="+{count, number} more {count, plural, one {line} other {lines}}"
|
||||
id="mobile.markdown.code.plusMoreLines"
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(63,67,80,0.4)",
|
||||
"fontSize": 11,
|
||||
"marginTop": 2,
|
||||
}
|
||||
}
|
||||
values={
|
||||
Object {
|
||||
"count": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#192a4d",
|
||||
"justifyContent": "center",
|
||||
"opacity": 0.8,
|
||||
"padding": 6,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontSize": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
LaTeX
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithFeedbackIOS>
|
||||
`;
|
||||
|
||||
exports[`LatexCodeBlock should match snapshot 1`] = `
|
||||
<TouchableWithFeedbackIOS
|
||||
onLongPress={[Function]}
|
||||
onPress={[Function]}
|
||||
type="opacity"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"borderColor": "rgba(63,67,80,0.15)",
|
||||
"borderRadius": 3,
|
||||
"borderWidth": 0.5,
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column",
|
||||
"paddingLeft": 6,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginLeft": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{1}{2} == 0.5^{0*\\\\pi} - 0.5"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#192a4d",
|
||||
"justifyContent": "center",
|
||||
"opacity": 0.8,
|
||||
"padding": 6,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontSize": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
LaTeX
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithFeedbackIOS>
|
||||
`;
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import LatexCodeBlock from './latex_code_block';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(LatexCodeBlock);
|
||||
@@ -1,209 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {PropTypes} from 'prop-types';
|
||||
import React from 'react';
|
||||
import {Keyboard, View, Text, StyleSheet, Platform} from 'react-native';
|
||||
import MathView from 'react-native-math-view';
|
||||
|
||||
import {goToScreen} from '@actions/navigation';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {splitLatexCodeInLines} from '@utils/latex';
|
||||
import {getDisplayNameForLanguage} from '@utils/markdown';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import MarkdownCodeBlock from '../markdown_code_block/markdown_code_block';
|
||||
|
||||
const MAX_LINES = 2;
|
||||
|
||||
export default class LatexCodeBlock extends MarkdownCodeBlock {
|
||||
static propTypes = {
|
||||
theme: PropTypes.object.isRequired,
|
||||
content: PropTypes.string.isRequired,
|
||||
textStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
};
|
||||
|
||||
handlePress = preventDoubleTap(() => {
|
||||
const {content} = this.props;
|
||||
const {intl} = this.context;
|
||||
const screen = 'Latex';
|
||||
const passProps = {
|
||||
content,
|
||||
};
|
||||
|
||||
const languageDisplayName = getDisplayNameForLanguage('latex');
|
||||
let title;
|
||||
if (languageDisplayName) {
|
||||
title = intl.formatMessage(
|
||||
{
|
||||
id: 'mobile.routes.code',
|
||||
defaultMessage: '{language} Code',
|
||||
},
|
||||
{
|
||||
language: languageDisplayName,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
title = intl.formatMessage({
|
||||
id: 'mobile.routes.code.noLanguage',
|
||||
defaultMessage: 'LaTeX Code',
|
||||
});
|
||||
}
|
||||
|
||||
Keyboard.dismiss();
|
||||
requestAnimationFrame(() => {
|
||||
goToScreen(screen, title, passProps);
|
||||
});
|
||||
});
|
||||
|
||||
splitContent = (content) => {
|
||||
const lines = splitLatexCodeInLines(content);
|
||||
|
||||
const numberOfLines = lines.length;
|
||||
|
||||
if (numberOfLines > MAX_LINES) {
|
||||
return {
|
||||
content: lines.slice(0, MAX_LINES),
|
||||
numberOfLines,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: lines,
|
||||
numberOfLines,
|
||||
};
|
||||
};
|
||||
|
||||
onErrorMessage = (errorMsg) => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return <Text style={style.errorText}>{'Error: ' + errorMsg.message}</Text>;
|
||||
};
|
||||
|
||||
onRenderErrorMessage = (errorMsg) => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return <Text style={style.errorText}>{'Render error: ' + errorMsg.error.message}</Text>;
|
||||
};
|
||||
|
||||
render() {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
let language = null;
|
||||
const languageDisplayName = getDisplayNameForLanguage('latex');
|
||||
|
||||
if (languageDisplayName) {
|
||||
language = (
|
||||
<View style={style.language}>
|
||||
<Text style={style.languageText}>
|
||||
{languageDisplayName}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const {content, numberOfLines} = this.splitContent(this.props.content);
|
||||
|
||||
let plusMoreLines = null;
|
||||
if (numberOfLines > MAX_LINES) {
|
||||
plusMoreLines = (
|
||||
<FormattedText
|
||||
style={style.plusMoreLinesText}
|
||||
id='mobile.markdown.code.plusMoreLines'
|
||||
defaultMessage='+{count, number} more {count, plural, one {line} other {lines}}'
|
||||
values={{
|
||||
count: numberOfLines - MAX_LINES,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note on the error behavior of math view:
|
||||
* - onError returns an Error object
|
||||
* - renderError returns an options object with an error attribute that contains the real Error.
|
||||
*/
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={this.handlePress}
|
||||
onLongPress={this.handleLongPress}
|
||||
type={'opacity'}
|
||||
>
|
||||
<View style={style.container}>
|
||||
<View style={style.rightColumn}>
|
||||
{content.map((latexCode) => (
|
||||
<View
|
||||
style={style.code}
|
||||
key={latexCode}
|
||||
>
|
||||
<MathView
|
||||
math={latexCode}
|
||||
onError={this.onErrorMessage}
|
||||
renderError={this.onRenderErrorMessage}
|
||||
resizeMode={'cover'}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
{plusMoreLines}
|
||||
</View>
|
||||
{language}
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
const codeVerticalPadding = Platform.select({
|
||||
ios: 4,
|
||||
android: 0,
|
||||
});
|
||||
|
||||
return {
|
||||
container: {
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.15),
|
||||
borderRadius: 3,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
flexDirection: 'row',
|
||||
flex: 1,
|
||||
},
|
||||
rightColumn: {
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
paddingLeft: 6,
|
||||
paddingVertical: 4,
|
||||
},
|
||||
code: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
marginLeft: 5,
|
||||
paddingVertical: codeVerticalPadding,
|
||||
},
|
||||
plusMoreLinesText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.4),
|
||||
fontSize: 11,
|
||||
marginTop: 2,
|
||||
},
|
||||
language: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
justifyContent: 'center',
|
||||
opacity: 0.8,
|
||||
padding: 6,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0,
|
||||
},
|
||||
languageText: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
fontSize: 12,
|
||||
},
|
||||
errorText: {
|
||||
fontSize: 14,
|
||||
marginHorizontal: 5,
|
||||
color: theme.errorTextColor,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
import LatexCodeBlock from './latex_code_block';
|
||||
|
||||
describe('LatexCodeBlock', () => {
|
||||
const baseProps = {
|
||||
theme: Preferences.THEMES.denim,
|
||||
language: 'latex',
|
||||
textStyle: {},
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<LatexCodeBlock
|
||||
{...baseProps}
|
||||
content={'\\frac{1}{2} == 0.5^{0*\\pi} - 0.5'}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should have 2 lines', () => {
|
||||
const wrapper = shallow(
|
||||
<LatexCodeBlock
|
||||
{...baseProps}
|
||||
content={'\\frac{2}{1} = 2 \\\\ 2_{nd} = line'}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should have moreLinesText', () => {
|
||||
const wrapper = shallow(
|
||||
<LatexCodeBlock
|
||||
{...baseProps}
|
||||
content={'\\frac{5}{2} = 2 \\\\ 2_{nd} = line \\\\ \\pi = 3.14'}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -1,71 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LatexCodeBlock should have maxWidth 10 1`] = `
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"flexWrap": "wrap",
|
||||
"marginBottom": -7,
|
||||
},
|
||||
Object {
|
||||
"height": undefined,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{1}{2} == 0.5^{0*\\\\pi} - 0.5"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="contain"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"marginBottom": -7,
|
||||
},
|
||||
Object {
|
||||
"maxWidth": 10,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
`;
|
||||
|
||||
exports[`LatexCodeBlock should match snapshot 1`] = `
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"flexWrap": "wrap",
|
||||
"marginBottom": -7,
|
||||
},
|
||||
Object {
|
||||
"height": undefined,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{1}{2} == 0.5^{0*\\\\pi} - 0.5"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="contain"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"marginBottom": -7,
|
||||
},
|
||||
Object {
|
||||
"maxWidth": "100%",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
`;
|
||||
@@ -1,16 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import LatexInline from './latex_inline';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(LatexInline);
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, {PureComponent} from 'react';
|
||||
import {Platform, Text, View} from 'react-native';
|
||||
import MathView from 'react-native-math-view';
|
||||
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
export default class LatexInline extends PureComponent {
|
||||
static propTypes = {
|
||||
content: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
onLayout: PropTypes.func,
|
||||
maxMathWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
mathHeight: PropTypes.number,
|
||||
};
|
||||
|
||||
onErrorMessage = (errorMsg) => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return <Text style={style.errorText}>{'Latex error: ' + errorMsg.message}</Text>;
|
||||
};
|
||||
|
||||
onRenderErrorMessage = (errorMsg) => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return <Text style={style.errorText}>{'Latex render error: ' + errorMsg.error.message}</Text>;
|
||||
};
|
||||
|
||||
render() {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
const viewStyleObj = Platform.select({
|
||||
ios: () => [style.viewStyle, {height: this.props.mathHeight}],
|
||||
android: () => [style.viewStyle],
|
||||
})();
|
||||
|
||||
return (
|
||||
<View
|
||||
style={viewStyleObj}
|
||||
key={this.props.content}
|
||||
onLayout={this.props.onLayout}
|
||||
>
|
||||
<MathView
|
||||
style={[style.mathStyle, {maxWidth: this.props.maxMathWidth ? this.props.maxMathWidth : '100%'}]}
|
||||
math={this.props.content}
|
||||
onError={this.onErrorMessage}
|
||||
renderError={this.onRenderErrorMessage}
|
||||
resizeMode={'contain'}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
mathStyle: {
|
||||
marginBottom: -7,
|
||||
},
|
||||
viewStyle: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
...Platform.select({
|
||||
ios: {
|
||||
marginBottom: -7,
|
||||
},
|
||||
}),
|
||||
},
|
||||
errorText: {
|
||||
flexDirection: 'row',
|
||||
color: theme.errorTextColor,
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
import LatexInline from './latex_inline';
|
||||
|
||||
describe('LatexCodeBlock', () => {
|
||||
const baseProps = {
|
||||
theme: Preferences.THEMES.denim,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<LatexInline
|
||||
{...baseProps}
|
||||
content={'\\frac{1}{2} == 0.5^{0*\\pi} - 0.5'}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should have maxWidth 10', () => {
|
||||
const wrapper = shallow(
|
||||
<LatexInline
|
||||
{...baseProps}
|
||||
content={'\\frac{1}{2} == 0.5^{0*\\pi} - 0.5'}
|
||||
maxMathWidth={10}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
Platform,
|
||||
Text,
|
||||
View,
|
||||
Dimensions,
|
||||
} from 'react-native';
|
||||
|
||||
import AtMention from '@components/at_mention';
|
||||
@@ -20,8 +19,6 @@ import Hashtag from '@components/markdown/hashtag';
|
||||
import {blendColors, concatStyles, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {getScheme} from '@utils/url';
|
||||
|
||||
import LatexCodeBlock from './latex_code_block';
|
||||
import LatexInline from './latex_inline';
|
||||
import MarkdownBlockQuote from './markdown_block_quote';
|
||||
import MarkdownCodeBlock from './markdown_code_block';
|
||||
import MarkdownImage from './markdown_image';
|
||||
@@ -62,8 +59,6 @@ export default class Markdown extends PureComponent {
|
||||
disableChannelLink: PropTypes.bool,
|
||||
disableAtChannelMentionHighlight: PropTypes.bool,
|
||||
disableGallery: PropTypes.bool,
|
||||
enableLatex: PropTypes.bool,
|
||||
enableInlineLatex: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -83,10 +78,6 @@ export default class Markdown extends PureComponent {
|
||||
|
||||
this.parser = this.createParser();
|
||||
this.renderer = this.createRenderer();
|
||||
|
||||
this.state = {
|
||||
inlineLatexHeight: 20,
|
||||
};
|
||||
}
|
||||
|
||||
createParser = () => {
|
||||
@@ -99,7 +90,7 @@ export default class Markdown extends PureComponent {
|
||||
urlFilter = (url) => {
|
||||
const scheme = getScheme(url);
|
||||
|
||||
return !scheme || this.props.autolinkedUrlSchemes.indexOf(scheme.toLowerCase()) !== -1;
|
||||
return !scheme || this.props.autolinkedUrlSchemes.indexOf(scheme) !== -1;
|
||||
};
|
||||
|
||||
createRenderer = () => {
|
||||
@@ -117,7 +108,6 @@ export default class Markdown extends PureComponent {
|
||||
channelLink: this.renderChannelLink,
|
||||
emoji: this.renderEmoji,
|
||||
hashtag: this.renderHashtag,
|
||||
latexInline: this.renderLatexInline,
|
||||
|
||||
paragraph: this.renderParagraph,
|
||||
heading: this.renderHeading,
|
||||
@@ -156,7 +146,6 @@ export default class Markdown extends PureComponent {
|
||||
if (node.type === 'image') {
|
||||
extraProps.reactChildren = node.react.children;
|
||||
extraProps.linkDestination = node.linkDestination;
|
||||
extraProps.size = (typeof node.size === 'object') ? node.size : undefined;
|
||||
}
|
||||
|
||||
return extraProps;
|
||||
@@ -167,11 +156,6 @@ export default class Markdown extends PureComponent {
|
||||
return contextStyles.length ? concatStyles(baseStyle, contextStyles) : baseStyle;
|
||||
};
|
||||
|
||||
onInlineLatexLayout = (event) => {
|
||||
const mathLineHeight = Math.max(event.nativeEvent.layout.height, this.state.inlineLatexHeight);
|
||||
this.setState({inlineLatexHeight: mathLineHeight});
|
||||
};
|
||||
|
||||
renderText = ({context, literal}) => {
|
||||
if (context.indexOf('image') !== -1) {
|
||||
// If this text is displayed, it will be styled by the image component
|
||||
@@ -199,7 +183,7 @@ export default class Markdown extends PureComponent {
|
||||
return <Text style={this.computeTextStyle([this.props.baseTextStyle, this.props.textStyles.code], context)}>{literal}</Text>;
|
||||
};
|
||||
|
||||
renderImage = ({linkDestination, reactChildren, context, src, size}) => {
|
||||
renderImage = ({linkDestination, reactChildren, context, src}) => {
|
||||
if (!this.props.imagesMetadata) {
|
||||
return null;
|
||||
}
|
||||
@@ -227,7 +211,6 @@ export default class Markdown extends PureComponent {
|
||||
isReplyPost={this.props.isReplyPost}
|
||||
postId={this.props.postId}
|
||||
source={src}
|
||||
sourceSize={size}
|
||||
>
|
||||
{reactChildren}
|
||||
</MarkdownImage>
|
||||
@@ -294,25 +277,6 @@ export default class Markdown extends PureComponent {
|
||||
);
|
||||
};
|
||||
|
||||
renderLatexInline = ({context, latexCode}) => {
|
||||
if (!this.props.enableInlineLatex) {
|
||||
return this.renderText({context, literal: `$${latexCode}$`});
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={{lineHeight: this.state.inlineLatexHeight}}
|
||||
>
|
||||
<LatexInline
|
||||
content={latexCode}
|
||||
onLayout={this.onInlineLatexLayout}
|
||||
maxMathWidth={Dimensions.get('window').width * 0.75}
|
||||
mathHeight={this.state.inlineLatexHeight}
|
||||
/>
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
renderParagraph = ({children, first}) => {
|
||||
if (!children || children.length === 0) {
|
||||
return null;
|
||||
@@ -352,16 +316,6 @@ export default class Markdown extends PureComponent {
|
||||
// These sometimes include a trailing newline
|
||||
const content = props.literal.replace(/\n$/, '');
|
||||
|
||||
if (this.props.enableLatex && props.language === 'latex') {
|
||||
return (
|
||||
<LatexCodeBlock
|
||||
content={content}
|
||||
language={props.language}
|
||||
textStyle={this.props.textStyles.codeBlock}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MarkdownCodeBlock
|
||||
content={content}
|
||||
|
||||
@@ -4,13 +4,11 @@
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import {General} from '../../mm-redux/constants';
|
||||
|
||||
import Markdown from './markdown.js';
|
||||
|
||||
describe('Markdown', () => {
|
||||
const baseProps = {
|
||||
autolinkedUrlSchemes: General.DEFAULT_AUTOLINKED_URL_SCHEMES,
|
||||
autolinkedUrlSchemes: ['mattermost'],
|
||||
baseTextStyle: {},
|
||||
mentionKeys: [{key: 'user.name'}, {key: '@channel'}, {key: '@all'}, {key: '@here'}],
|
||||
minimumHashtagLength: 3,
|
||||
@@ -58,31 +56,4 @@ describe('Markdown', () => {
|
||||
<Markdown {...props}/>,
|
||||
);
|
||||
});
|
||||
|
||||
test('should return true for an uncapitalized scheme', () => {
|
||||
const url = 'https://www.mattermost.com/';
|
||||
const wrapper = shallow(
|
||||
<Markdown {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.instance().urlFilter(url)).toBe(true);
|
||||
});
|
||||
|
||||
test('should return true for a capitalized scheme', () => {
|
||||
const url = 'Https://www.mattermost.com/';
|
||||
const wrapper = shallow(
|
||||
<Markdown {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.instance().urlFilter(url)).toBe(true);
|
||||
});
|
||||
|
||||
test('should return false for an unknown scheme', () => {
|
||||
const url = 'unknown://www.mattermost.com/';
|
||||
const wrapper = shallow(
|
||||
<Markdown {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.instance().urlFilter(url)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,24 +22,15 @@ import ImageViewPort from '@components/image_viewport';
|
||||
import ProgressiveImage from '@components/progressive_image';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import mattermostManaged from '@mattermost-managed';
|
||||
import {changeOpacity, makeStyleFromTheme} from '@mm-redux/utils/theme_utils';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import BottomSheet from '@utils/bottom_sheet';
|
||||
import {generateId} from '@utils/file';
|
||||
import {openGalleryAtIndex} from '@utils/gallery';
|
||||
import {calculateDimensions, getViewPortWidth, isGifTooLarge} from '@utils/images';
|
||||
import {getMarkdownImageSize} from '@utils/markdown';
|
||||
import {normalizeProtocol, tryOpenURL} from '@utils/url';
|
||||
|
||||
const ANDROID_MAX_HEIGHT = 4096;
|
||||
const ANDROID_MAX_WIDTH = 4096;
|
||||
const getStyleSheet = makeStyleFromTheme((theme) => ({
|
||||
svg: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.06),
|
||||
borderRadius: 8,
|
||||
flex: 1,
|
||||
},
|
||||
}));
|
||||
|
||||
export default class MarkdownImage extends ImageViewPort {
|
||||
static propTypes = {
|
||||
@@ -51,7 +42,6 @@ export default class MarkdownImage extends ImageViewPort {
|
||||
linkDestination: PropTypes.string,
|
||||
postId: PropTypes.string,
|
||||
source: PropTypes.string.isRequired,
|
||||
sourceSize: PropTypes.object,
|
||||
theme: PropTypes.object,
|
||||
};
|
||||
|
||||
@@ -63,12 +53,10 @@ export default class MarkdownImage extends ImageViewPort {
|
||||
super(props);
|
||||
|
||||
const metadata = props.imagesMetadata?.[props.source] || Object.values(props.imagesMetadata || {})?.[0];
|
||||
const size = getMarkdownImageSize(props.isReplyPost, this.hasPermanentSidebar(), props.sourceSize, metadata);
|
||||
|
||||
this.fileId = generateId();
|
||||
this.state = {
|
||||
originalHeight: size.height,
|
||||
originalWidth: size.width,
|
||||
originalHeight: metadata?.height || 0,
|
||||
originalWidth: metadata?.width || 0,
|
||||
failed: isGifTooLarge(metadata),
|
||||
format: metadata?.format,
|
||||
uri: null,
|
||||
@@ -219,18 +207,6 @@ export default class MarkdownImage extends ImageViewPort {
|
||||
{this.props.children}
|
||||
</Text>
|
||||
);
|
||||
} else if (fileInfo?.format === 'svg') {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
image = (
|
||||
<SvgUri
|
||||
uri={fileInfo.uri}
|
||||
style={style.svg}
|
||||
width={width}
|
||||
height={height}
|
||||
onError={this.handleSizeFailed}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
// React Native complains if we try to pass resizeMode as a style
|
||||
const source = fileInfo.uri ? {uri: fileInfo.uri} : null;
|
||||
@@ -249,6 +225,14 @@ export default class MarkdownImage extends ImageViewPort {
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
} else if (fileInfo?.format === 'svg') {
|
||||
image = (
|
||||
<SvgUri
|
||||
uri={fileInfo.uri}
|
||||
style={{flex: 1}}
|
||||
onError={this.handleSizeFailed}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (image && this.props.linkDestination) {
|
||||
|
||||
@@ -112,8 +112,6 @@ const MarkTableImage = ({disable, imagesMetadata, postId, serverURL, source, the
|
||||
<SvgUri
|
||||
uri={source}
|
||||
style={styles.container}
|
||||
width={width}
|
||||
height={height}
|
||||
|
||||
//@ts-expect-error onError not defined in the types
|
||||
onError={onLoadFailed}
|
||||
|
||||
@@ -313,7 +313,7 @@ export default class DraftInput extends PureComponent {
|
||||
}
|
||||
|
||||
if (data.form) {
|
||||
showAppForm(data.form, data.call.context, theme);
|
||||
showAppForm(data.form, data.call, theme);
|
||||
}
|
||||
|
||||
this.setInputValue('');
|
||||
|
||||
@@ -6,13 +6,13 @@ import {intlShape, injectIntl} from 'react-intl';
|
||||
import Button from 'react-native-button';
|
||||
|
||||
import {showAppForm} from '@actions/navigation';
|
||||
import {AppBindingLocations, AppCallResponseTypes} from '@mm-redux/constants/apps';
|
||||
import {AppExpandLevels, AppBindingLocations, AppCallTypes, AppCallResponseTypes} from '@mm-redux/constants/apps';
|
||||
import {ActionResult} from '@mm-redux/types/actions';
|
||||
import {AppBinding} from '@mm-redux/types/apps';
|
||||
import {Post} from '@mm-redux/types/posts';
|
||||
import {Theme} from '@mm-redux/types/theme';
|
||||
import {HandleBindingClick, PostEphemeralCallResponseForPost} from '@mm-types/actions/apps';
|
||||
import {createCallContext} from '@utils/apps';
|
||||
import {DoAppCall, PostEphemeralCallResponseForPost} from '@mm-types/actions/apps';
|
||||
import {createCallContext, createCallRequest} from '@utils/apps';
|
||||
import {getStatusColors} from '@utils/message_attachment_colors';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
@@ -21,7 +21,7 @@ import ButtonBindingText from './button_binding_text';
|
||||
|
||||
type Props = {
|
||||
binding: AppBinding;
|
||||
handleBindingClick: HandleBindingClick;
|
||||
doAppCall: DoAppCall;
|
||||
intl: typeof intlShape;
|
||||
post: Post;
|
||||
postEphemeralCallResponseForPost: PostEphemeralCallResponseForPost;
|
||||
@@ -53,7 +53,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
};
|
||||
});
|
||||
|
||||
const ButtonBinding = ({binding, handleBindingClick, intl, post, postEphemeralCallResponseForPost, teamID, theme, handleGotoLocation}: Props) => {
|
||||
const ButtonBinding = ({binding, doAppCall, intl, post, postEphemeralCallResponseForPost, teamID, theme, handleGotoLocation}: Props) => {
|
||||
const pressed = useRef(false);
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
@@ -62,6 +62,12 @@ const ButtonBinding = ({binding, handleBindingClick, intl, post, postEphemeralCa
|
||||
return;
|
||||
}
|
||||
|
||||
const call = binding.form?.call || binding.call;
|
||||
|
||||
if (!call) {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = createCallContext(
|
||||
binding.app_id,
|
||||
AppBindingLocations.IN_POST + binding.location,
|
||||
@@ -70,14 +76,25 @@ const ButtonBinding = ({binding, handleBindingClick, intl, post, postEphemeralCa
|
||||
post.id,
|
||||
);
|
||||
|
||||
const callRequest = createCallRequest(
|
||||
call,
|
||||
context,
|
||||
{post: AppExpandLevels.EXPAND_ALL},
|
||||
);
|
||||
|
||||
if (binding.form) {
|
||||
showAppForm(binding.form, callRequest, theme);
|
||||
return;
|
||||
}
|
||||
|
||||
pressed.current = true;
|
||||
|
||||
const res = await handleBindingClick(binding, context, intl);
|
||||
const res = await doAppCall(callRequest, AppCallTypes.SUBMIT, intl);
|
||||
pressed.current = false;
|
||||
|
||||
if (res.error) {
|
||||
const errorResponse = res.error;
|
||||
const errorMessage = errorResponse.text || intl.formatMessage({
|
||||
const errorMessage = errorResponse.error || intl.formatMessage({
|
||||
id: 'apps.error.unknown',
|
||||
defaultMessage: 'Unknown error occurred.',
|
||||
});
|
||||
@@ -89,15 +106,15 @@ const ButtonBinding = ({binding, handleBindingClick, intl, post, postEphemeralCa
|
||||
|
||||
switch (callResp.type) {
|
||||
case AppCallResponseTypes.OK:
|
||||
if (callResp.text) {
|
||||
postEphemeralCallResponseForPost(callResp, callResp.text, post);
|
||||
if (callResp.markdown) {
|
||||
postEphemeralCallResponseForPost(callResp, callResp.markdown, post);
|
||||
}
|
||||
return;
|
||||
case AppCallResponseTypes.NAVIGATE:
|
||||
handleGotoLocation(callResp.navigate_to_url!, intl);
|
||||
return;
|
||||
case AppCallResponseTypes.FORM:
|
||||
showAppForm(callResp.form, context, theme);
|
||||
showAppForm(callResp.form, call, theme);
|
||||
return;
|
||||
default: {
|
||||
const errorMessage = intl.formatMessage({
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {handleBindingClick, postEphemeralCallResponseForPost} from '@actions/apps';
|
||||
import {doAppCall, postEphemeralCallResponseForPost} from '@actions/apps';
|
||||
import {handleGotoLocation} from '@mm-redux/actions/integrations';
|
||||
import {getChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {getPost} from '@mm-redux/selectors/entities/posts';
|
||||
@@ -32,7 +32,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
handleBindingClick,
|
||||
doAppCall,
|
||||
postEphemeralCallResponseForPost,
|
||||
handleGotoLocation,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import {AppBindingLocations} from '@mm-redux/constants/apps';
|
||||
@@ -39,16 +39,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
});
|
||||
|
||||
const EmbeddedBinding = ({embed, postId, theme}: Props) => {
|
||||
const [cleanedBindings, setCleanedBindings] = useState<AppBinding[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const copiedBindings = JSON.parse(JSON.stringify(embed)) as AppBinding;
|
||||
const bindings = cleanBinding(copiedBindings, AppBindingLocations.IN_POST)?.bindings;
|
||||
setCleanedBindings(bindings!);
|
||||
}, [embed]);
|
||||
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const bindings = cleanBinding(embed, AppBindingLocations.IN_POST)?.bindings;
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={style.container}>
|
||||
@@ -64,9 +58,9 @@ const EmbeddedBinding = ({embed, postId, theme}: Props) => {
|
||||
theme={theme}
|
||||
/>
|
||||
}
|
||||
{Boolean(cleanedBindings?.length) &&
|
||||
{Boolean(bindings?.length) &&
|
||||
<EmbedSubBindings
|
||||
bindings={cleanedBindings}
|
||||
bindings={bindings!}
|
||||
postId={postId}
|
||||
theme={theme}
|
||||
/>
|
||||
|
||||
@@ -19,7 +19,7 @@ const EmbeddedSubBindings = ({bindings, postId, theme}: Props) => {
|
||||
const content = [] as React.ReactNode[];
|
||||
|
||||
bindings.forEach((binding) => {
|
||||
if (!binding.app_id || !(binding.submit || binding.form?.submit || binding.form?.source || binding.bindings?.length)) {
|
||||
if (!binding.app_id || !binding.call) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {handleBindingClick, postEphemeralCallResponseForPost} from '@actions/apps';
|
||||
import {doAppCall, postEphemeralCallResponseForPost} from '@actions/apps';
|
||||
import {handleGotoLocation} from '@mm-redux/actions/integrations';
|
||||
import {getChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {getPost} from '@mm-redux/selectors/entities/posts';
|
||||
@@ -32,7 +32,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
handleBindingClick,
|
||||
doAppCall,
|
||||
postEphemeralCallResponseForPost,
|
||||
handleGotoLocation,
|
||||
};
|
||||
|
||||
@@ -6,19 +6,19 @@ import {intlShape, injectIntl} from 'react-intl';
|
||||
|
||||
import {showAppForm} from '@actions/navigation';
|
||||
import AutocompleteSelector from '@components/autocomplete_selector';
|
||||
import {AppBindingLocations, AppCallResponseTypes} from '@mm-redux/constants/apps';
|
||||
import {AppExpandLevels, AppBindingLocations, AppCallTypes, AppCallResponseTypes} from '@mm-redux/constants/apps';
|
||||
import {ActionResult} from '@mm-redux/types/actions';
|
||||
import {Theme} from '@mm-redux/types/theme';
|
||||
import {createCallContext} from '@utils/apps';
|
||||
import {createCallContext, createCallRequest} from '@utils/apps';
|
||||
|
||||
import type {AppBinding} from '@mm-redux/types/apps';
|
||||
import type {PostActionOption} from '@mm-redux/types/integration_actions';
|
||||
import type {Post} from '@mm-redux/types/posts';
|
||||
import type {HandleBindingClick, PostEphemeralCallResponseForPost} from '@mm-types/actions/apps';
|
||||
import type {DoAppCall, PostEphemeralCallResponseForPost} from '@mm-types/actions/apps';
|
||||
|
||||
type Props = {
|
||||
binding: AppBinding;
|
||||
handleBindingClick: HandleBindingClick;
|
||||
doAppCall: DoAppCall;
|
||||
intl: typeof intlShape;
|
||||
post: Post;
|
||||
postEphemeralCallResponseForPost: PostEphemeralCallResponseForPost;
|
||||
@@ -27,7 +27,7 @@ type Props = {
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
const MenuBinding = ({binding, handleBindingClick, intl, post, postEphemeralCallResponseForPost, handleGotoLocation, teamID, theme}: Props) => {
|
||||
const MenuBinding = ({binding, doAppCall, intl, post, postEphemeralCallResponseForPost, handleGotoLocation, teamID, theme}: Props) => {
|
||||
const [selected, setSelected] = useState<PostActionOption>();
|
||||
|
||||
const onSelect = useCallback(async (picked?: PostActionOption) => {
|
||||
@@ -42,6 +42,12 @@ const MenuBinding = ({binding, handleBindingClick, intl, post, postEphemeralCall
|
||||
return;
|
||||
}
|
||||
|
||||
const call = bind.form?.call || bind.call;
|
||||
|
||||
if (!call) {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = createCallContext(
|
||||
bind.app_id,
|
||||
AppBindingLocations.IN_POST + bind.location,
|
||||
@@ -50,10 +56,21 @@ const MenuBinding = ({binding, handleBindingClick, intl, post, postEphemeralCall
|
||||
post.id,
|
||||
);
|
||||
|
||||
const res = await handleBindingClick(bind, context, intl);
|
||||
const callRequest = createCallRequest(
|
||||
call,
|
||||
context,
|
||||
{post: AppExpandLevels.EXPAND_ALL},
|
||||
);
|
||||
|
||||
if (bind.form) {
|
||||
showAppForm(bind.form, callRequest);
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await doAppCall(callRequest, AppCallTypes.SUBMIT, intl);
|
||||
if (res.error) {
|
||||
const errorResponse = res.error;
|
||||
const errorMessage = errorResponse.text || intl.formatMessage({
|
||||
const errorMessage = errorResponse.error || intl.formatMessage({
|
||||
id: 'apps.error.unknown',
|
||||
defaultMessage: 'Unknown error occurred.',
|
||||
});
|
||||
@@ -64,15 +81,15 @@ const MenuBinding = ({binding, handleBindingClick, intl, post, postEphemeralCall
|
||||
const callResp = res.data!;
|
||||
switch (callResp.type) {
|
||||
case AppCallResponseTypes.OK:
|
||||
if (callResp.text) {
|
||||
postEphemeralCallResponseForPost(callResp, callResp.text, post);
|
||||
if (callResp.markdown) {
|
||||
postEphemeralCallResponseForPost(callResp, callResp.markdown, post);
|
||||
}
|
||||
return;
|
||||
case AppCallResponseTypes.NAVIGATE:
|
||||
handleGotoLocation(callResp.navigate_to_url!, intl);
|
||||
return;
|
||||
case AppCallResponseTypes.FORM:
|
||||
showAppForm(callResp.form, context, theme);
|
||||
showAppForm(callResp.form, call, theme);
|
||||
return;
|
||||
default: {
|
||||
const errorMessage = intl.formatMessage({
|
||||
|
||||
@@ -29,7 +29,7 @@ const contentType: Record<string, string> = {
|
||||
};
|
||||
|
||||
const Content = ({isReplyPost, post, theme}: ContentProps) => {
|
||||
let type: string = post.metadata?.embeds?.[0]?.type;
|
||||
let type: string = post.metadata?.embeds[0]?.type;
|
||||
if (!type && post.props?.app_bindings) {
|
||||
type = contentType.app_bindings;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {showPermalink} from '@actions/views/permalink';
|
||||
import {THREAD} from '@constants/screen';
|
||||
import {removePost} from '@mm-redux/actions/posts';
|
||||
import {getChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {getConfig} from '@mm-redux/selectors/entities/general';
|
||||
import {getConfig, getFeatureFlagValue} from '@mm-redux/selectors/entities/general';
|
||||
import {getPost, isRootPost} from '@mm-redux/selectors/entities/posts';
|
||||
import {getMyPreferences, getTeammateNameDisplaySetting, isCollapsedThreadsEnabled} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
@@ -49,6 +49,7 @@ function mapSateToProps(state: GlobalState, ownProps: OwnProps) {
|
||||
const teammateNameDisplay = getTeammateNameDisplaySetting(state);
|
||||
const enablePostUsernameOverride = config.EnablePostUsernameOverride === 'true';
|
||||
const isConsecutivePost = post && previousPost && !author?.is_bot && !isRootPost(state, post.id) && areConsecutivePosts(post, previousPost);
|
||||
const callsFeatureEnabled = getFeatureFlagValue(state, 'CallsMobile') === 'true';
|
||||
let isFirstReply = true;
|
||||
let isLastReply = true;
|
||||
let canDelete = false;
|
||||
@@ -97,6 +98,7 @@ function mapSateToProps(state: GlobalState, ownProps: OwnProps) {
|
||||
teammateNameDisplay,
|
||||
thread,
|
||||
threadStarter: getUser(state, post.user_id),
|
||||
callsFeatureEnabled,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ type PostProps = {
|
||||
theme: Theme;
|
||||
thread: UserThread;
|
||||
threadStarter: UserProfile;
|
||||
callsFeatureEnabled: boolean;
|
||||
};
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
@@ -114,7 +115,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
const Post = ({
|
||||
canDelete, collapsedThreadsEnabled, enablePostUsernameOverride, highlight, highlightPinnedOrFlagged = true, intl, isConsecutivePost, isFirstReply, isFlagged, isLastReply,
|
||||
location, post, removePost, rootPostAuthor, shouldRenderReplyButton, skipFlaggedHeader, skipPinnedHeader, showAddReaction = true, showPermalink, style,
|
||||
teammateNameDisplay, testID, theme, thread, threadStarter,
|
||||
teammateNameDisplay, testID, theme, thread, threadStarter, callsFeatureEnabled,
|
||||
}: PostProps) => {
|
||||
const pressDetected = useRef(false);
|
||||
const styles = getStyleSheet(theme);
|
||||
@@ -240,7 +241,7 @@ const Post = ({
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
} else if (post.type === Posts.POST_TYPES.CUSTOM_CALLS) {
|
||||
} else if (post.type === 'custom_calls' && callsFeatureEnabled) {
|
||||
body = (
|
||||
<CallMessage
|
||||
post={post}
|
||||
|
||||
@@ -808,7 +808,118 @@ exports[`ChannelItem should match snapshot for no displayName 1`] = `null`;
|
||||
|
||||
exports[`ChannelItem should match snapshot for showUnreadForMsgs 1`] = `null`;
|
||||
|
||||
exports[`ChannelItem should match snapshot when there is a call 1`] = `
|
||||
exports[`ChannelItem should match snapshot when there is a call and but calls are disabled 1`] = `
|
||||
<TouchableHighlight
|
||||
onPress={[Function]}
|
||||
underlayColor="rgba(40,66,123,0.5)"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"height": 44,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
testID="main.sidebar.channels_list.list.channel_item"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
"paddingLeft": 16,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
testID="main.sidebar.channels_list.list.channel_item.channel_id"
|
||||
>
|
||||
<ChannelIcon
|
||||
channelId="channel_id"
|
||||
hasDraft={false}
|
||||
isActive={false}
|
||||
isArchived={false}
|
||||
isInfo={false}
|
||||
isUnread={true}
|
||||
membersCount={1}
|
||||
size={24}
|
||||
statusStyle={
|
||||
Object {
|
||||
"backgroundColor": "#1e325c",
|
||||
"borderColor": "transparent",
|
||||
}
|
||||
}
|
||||
testID="main.sidebar.channels_list.list.channel_item.channel_icon"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc1f",
|
||||
"buttonBg": "#1c58d9",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3f4350",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#d24b4e",
|
||||
"errorTextColor": "#d24b4e",
|
||||
"linkColor": "#386fe5",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionColor": "#1e325c",
|
||||
"mentionHighlightBg": "#ffd470",
|
||||
"mentionHighlightLink": "#1b1d22",
|
||||
"newMessageSeparator": "#cc8f00",
|
||||
"onlineIndicator": "#3db887",
|
||||
"sidebarBg": "#1e325c",
|
||||
"sidebarHeaderBg": "#192a4d",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarTeamBarBg": "#14213e",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#5d89ea",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#28427b",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Denim",
|
||||
}
|
||||
}
|
||||
type="O"
|
||||
/>
|
||||
<Text
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"alignSelf": "center",
|
||||
"color": "rgba(255,255,255,0.6)",
|
||||
"fontFamily": "Open Sans",
|
||||
"fontSize": 16,
|
||||
"lineHeight": 24,
|
||||
"marginLeft": 13,
|
||||
"maxWidth": "80%",
|
||||
"paddingRight": 10,
|
||||
},
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontWeight": "500",
|
||||
"maxWidth": "70%",
|
||||
"opacity": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
testID="main.sidebar.channels_list.list.channel_item.display_name"
|
||||
>
|
||||
display_name
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
`;
|
||||
|
||||
exports[`ChannelItem should match snapshot when there is a call and calls are enabled 1`] = `
|
||||
<TouchableHighlight
|
||||
onPress={[Function]}
|
||||
underlayColor="rgba(40,66,123,0.5)"
|
||||
|
||||
@@ -43,6 +43,7 @@ export default class ChannelItem extends PureComponent {
|
||||
viewingGlobalThreads: PropTypes.bool,
|
||||
customStatusEnabled: PropTypes.bool.isRequired,
|
||||
channelHasCall: PropTypes.bool.isRequired,
|
||||
callsFeatureEnabled: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
@@ -216,7 +217,7 @@ export default class ChannelItem extends PureComponent {
|
||||
</Text>
|
||||
{customStatus}
|
||||
{badge}
|
||||
{this.props.channelHasCall &&
|
||||
{this.props.callsFeatureEnabled && this.props.channelHasCall &&
|
||||
<CompassIcon
|
||||
name='phone-in-talk'
|
||||
size={16}
|
||||
|
||||
@@ -41,6 +41,7 @@ describe('ChannelItem', () => {
|
||||
isBot: false,
|
||||
customStatusEnabled: true,
|
||||
channelHasCall: false,
|
||||
callsFeatureEnabled: false,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
@@ -51,9 +52,24 @@ describe('ChannelItem', () => {
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot when there is a call', () => {
|
||||
test('should match snapshot when there is a call and calls are enabled', () => {
|
||||
const newProps = {
|
||||
...baseProps,
|
||||
callsFeatureEnabled: true,
|
||||
channelHasCall: true,
|
||||
};
|
||||
|
||||
const wrapper = shallowWithIntl(
|
||||
<ChannelItem {...newProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot when there is a call and but calls are disabled', () => {
|
||||
const newProps = {
|
||||
...baseProps,
|
||||
callsFeatureEnabled: false,
|
||||
channelHasCall: true,
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
makeGetChannel,
|
||||
shouldHideDefaultChannel,
|
||||
} from '@mm-redux/selectors/entities/channels';
|
||||
import {getFeatureFlagValue} from '@mm-redux/selectors/entities/general';
|
||||
import {getTheme, getTeammateNameDisplaySetting, isCollapsedThreadsEnabled} from '@mm-redux/selectors/entities/preferences';
|
||||
import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
|
||||
import {getMsgCountInChannel, getUserIdFromChannelName, isChannelMuted} from '@mm-redux/utils/channel_utils';
|
||||
@@ -74,6 +75,7 @@ function makeMapStateToProps() {
|
||||
if (member && member.notify_props) {
|
||||
showUnreadForMsgs = member.notify_props.mark_unread !== General.MENTION;
|
||||
}
|
||||
const callsFeatureEnabled = getFeatureFlagValue(state, 'CallsMobile') === 'true';
|
||||
|
||||
const viewingGlobalThreads = getViewingGlobalThreads(state);
|
||||
return {
|
||||
@@ -95,6 +97,7 @@ function makeMapStateToProps() {
|
||||
viewingGlobalThreads,
|
||||
customStatusEnabled: isCustomStatusEnabled(state),
|
||||
channelHasCall,
|
||||
callsFeatureEnabled,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,37 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SettingsSidebar Custom Status should close sidebar when update custom status succeeds 1`] = `
|
||||
<DrawerLayoutAdapter
|
||||
drawerPosition="right"
|
||||
drawerWidth={710}
|
||||
forwardRef={
|
||||
Object {
|
||||
"current": null,
|
||||
}
|
||||
}
|
||||
onDrawerClose={[Function]}
|
||||
onDrawerOpen={[Function]}
|
||||
renderNavigationView={[Function]}
|
||||
useNativeAnimations={true}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`SettingsSidebar Custom Status should keep sidebar open when update custom status fails 1`] = `
|
||||
<DrawerLayoutAdapter
|
||||
drawerPosition="right"
|
||||
drawerWidth={710}
|
||||
forwardRef={
|
||||
Object {
|
||||
"current": null,
|
||||
}
|
||||
}
|
||||
onDrawerClose={[Function]}
|
||||
onDrawerOpen={[Function]}
|
||||
renderNavigationView={[Function]}
|
||||
useNativeAnimations={true}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`SettingsSidebar should match snapshot 1`] = `
|
||||
<DrawerLayoutAdapter
|
||||
drawerPosition="right"
|
||||
|
||||
@@ -5,7 +5,6 @@ import React from 'react';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
import {CustomStatusDuration} from '@mm-redux/types/users';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
import {shallowWithIntl} from '@test/intl-test-helper';
|
||||
|
||||
import SettingsSidebar from './settings_sidebar.ios';
|
||||
@@ -68,44 +67,4 @@ describe('SettingsSidebar', () => {
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('Custom Status', () => {
|
||||
const tests = [
|
||||
{case: 'should keep sidebar open when update custom status fails', error: 'update custom status fail'},
|
||||
{case: 'should close sidebar when update custom status succeeds', error: undefined},
|
||||
];
|
||||
|
||||
tests.forEach((test) => {
|
||||
it(test.case, () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<SettingsSidebar
|
||||
{...baseProps}
|
||||
isCustomStatusEnabled={true}
|
||||
customStatus={{
|
||||
...customStatus,
|
||||
duration: CustomStatusDuration.DATE_AND_TIME,
|
||||
expires_at: '2200-04-13T18:09:12.451Z',
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
const instance = wrapper.instance();
|
||||
instance.componentDidMount();
|
||||
|
||||
const spy = jest.spyOn(instance, 'closeSettingsSidebar');
|
||||
|
||||
EventEmitter.emit('set_custom_status', test.error);
|
||||
|
||||
if (test.error) {
|
||||
expect(spy).not.toBeCalled();
|
||||
expect(wrapper.state('showRetryMessage')).toBe(true);
|
||||
} else {
|
||||
expect(spy).toBeCalled();
|
||||
expect(wrapper.state('showRetryMessage')).toBe(false);
|
||||
}
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -57,7 +57,7 @@ export default class SettingsSidebarBase extends PureComponent {
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
EventEmitter.on(NavigationTypes.CLOSE_SETTINGS_SIDEBAR, this.closeSettingsSidebar);
|
||||
EventEmitter.on(CustomStatus.SET_CUSTOM_STATUS, this.handleSetCustomStatus);
|
||||
EventEmitter.on(CustomStatus.SET_CUSTOM_STATUS_FAILURE, this.handleSetCustomStatusFailure);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -67,20 +67,13 @@ export default class SettingsSidebarBase extends PureComponent {
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
EventEmitter.off(NavigationTypes.CLOSE_SETTINGS_SIDEBAR, this.closeSettingsSidebar);
|
||||
EventEmitter.off(CustomStatus.SET_CUSTOM_STATUS, this.handleSetCustomStatus);
|
||||
EventEmitter.off(CustomStatus.SET_CUSTOM_STATUS_FAILURE, this.handleSetCustomStatusFailure);
|
||||
}
|
||||
|
||||
handleSetCustomStatus = (error) => {
|
||||
if (error) {
|
||||
this.setState({showRetryMessage: true});
|
||||
} else {
|
||||
this.setState({
|
||||
showStatus: true,
|
||||
showRetryMessage: false,
|
||||
});
|
||||
|
||||
this.closeSettingsSidebar();
|
||||
}
|
||||
handleSetCustomStatusFailure = () => {
|
||||
this.setState({
|
||||
showRetryMessage: true,
|
||||
});
|
||||
};
|
||||
|
||||
handleCustomStatusChange = (prevCustomStatus, customStatus) => {
|
||||
@@ -174,6 +167,7 @@ export default class SettingsSidebarBase extends PureComponent {
|
||||
};
|
||||
|
||||
goToCustomStatusScreen = (intl) => {
|
||||
this.closeSettingsSidebar();
|
||||
showModal('CustomStatus', intl.formatMessage({id: 'mobile.routes.custom_status', defaultMessage: 'Set a Status'}));
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export default {
|
||||
TERMS_OF_SERVICE: 'https://mattermost.com/terms-of-use/',
|
||||
PRIVACY_POLICY: 'https://mattermost.com/privacy-policy/',
|
||||
TERMS_OF_SERVICE: 'https://about.mattermost.com/default-terms/',
|
||||
PRIVACY_POLICY: 'https://about.mattermost.com/default-privacy-policy/',
|
||||
};
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
const RefreshConfigMillis = 20 * 60 * 1000; // Refresh config after 20 minutes
|
||||
|
||||
const RequiredServer = {
|
||||
FULL_VERSION: '6.3.0',
|
||||
MAJOR_VERSION: 6,
|
||||
MIN_VERSION: 3,
|
||||
PATCH_VERSION: 0,
|
||||
};
|
||||
|
||||
const PluginId = 'com.mattermost.calls';
|
||||
|
||||
export default {RequiredServer, RefreshConfigMillis, PluginId};
|
||||
@@ -45,4 +45,4 @@ export const durationValues = {
|
||||
},
|
||||
};
|
||||
export const CUSTOM_STATUS_TEXT_CHARACTER_LIMIT = 100;
|
||||
export const SET_CUSTOM_STATUS = 'set_custom_status';
|
||||
export const SET_CUSTOM_STATUS_FAILURE = 'set_custom_status_failure';
|
||||
|
||||
@@ -9,7 +9,6 @@ import DeepLinkTypes from './deep_linking';
|
||||
import DeviceTypes from './device';
|
||||
import ListTypes from './list';
|
||||
import NavigationTypes from './navigation';
|
||||
import Sso from './sso';
|
||||
import Types from './types';
|
||||
import ViewTypes from './view';
|
||||
import WebsocketEvents from './websocket';
|
||||
@@ -22,7 +21,6 @@ export {
|
||||
DateTypes,
|
||||
ListTypes,
|
||||
NavigationTypes,
|
||||
Sso,
|
||||
Types,
|
||||
ViewTypes,
|
||||
WebsocketEvents,
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import keyMirror from '@mm-redux/utils/key_mirror';
|
||||
|
||||
export const REDIRECT_URL_SCHEME = 'mmauth://';
|
||||
export const REDIRECT_URL_SCHEME_DEV = 'mmauthbeta://';
|
||||
|
||||
const constants = keyMirror({
|
||||
SAML: null,
|
||||
GITLAB: null,
|
||||
GOOGLE: null,
|
||||
OFFICE365: null,
|
||||
OPENID: null,
|
||||
});
|
||||
|
||||
export default {
|
||||
...constants,
|
||||
REDIRECT_URL_SCHEME,
|
||||
REDIRECT_URL_SCHEME_DEV,
|
||||
};
|
||||
@@ -129,7 +129,7 @@ export default {
|
||||
FEATURE_TOGGLE_PREFIX: 'feature_enabled_',
|
||||
EMBED_PREVIEW: 'embed_preview',
|
||||
LINK_PREVIEW_DISPLAY: 'link_previews',
|
||||
MIN_CHANNELNAME_LENGTH: 1,
|
||||
MIN_CHANNELNAME_LENGTH: 2,
|
||||
MAX_CHANNELNAME_LENGTH: 64,
|
||||
ANDROID_TOP_LANDSCAPE: 46,
|
||||
ANDROID_TOP_PORTRAIT: 56,
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import Calls from '@constants/calls';
|
||||
|
||||
const WebsocketEvents = {
|
||||
POSTED: 'posted',
|
||||
POST_EDITED: 'post_edited',
|
||||
@@ -50,24 +47,20 @@ const WebsocketEvents = {
|
||||
THREAD_FOLLOW_CHANGED: 'thread_follow_changed',
|
||||
THREAD_READ_CHANGED: 'thread_read_changed',
|
||||
APPS_FRAMEWORK_REFRESH_BINDINGS: 'custom_com.mattermost.apps_refresh_bindings',
|
||||
APPS_FRAMEWORK_PLUGIN_ENABLED: 'custom_com.mattermost.apps_plugin_enabled',
|
||||
APPS_FRAMEWORK_PLUGIN_DISABLED: 'custom_com.mattermost.apps_plugin_disabled',
|
||||
SIDEBAR_CATEGORY_CREATED: 'sidebar_category_created',
|
||||
SIDEBAR_CATEGORY_UPDATED: 'sidebar_category_updated',
|
||||
SIDEBAR_CATEGORY_DELETED: 'sidebar_category_deleted',
|
||||
SIDEBAR_CATEGORY_ORDER_UPDATED: 'sidebar_category_order_updated',
|
||||
CALLS_CHANNEL_ENABLED: `custom_${Calls.PluginId}_channel_enable_voice`,
|
||||
CALLS_CHANNEL_DISABLED: `custom_${Calls.PluginId}_channel_disable_voice`,
|
||||
CALLS_USER_CONNECTED: `custom_${Calls.PluginId}_user_connected`,
|
||||
CALLS_USER_DISCONNECTED: `custom_${Calls.PluginId}_user_disconnected`,
|
||||
CALLS_USER_MUTED: `custom_${Calls.PluginId}_user_muted`,
|
||||
CALLS_USER_UNMUTED: `custom_${Calls.PluginId}_user_unmuted`,
|
||||
CALLS_USER_VOICE_ON: `custom_${Calls.PluginId}_user_voice_on`,
|
||||
CALLS_USER_VOICE_OFF: `custom_${Calls.PluginId}_user_voice_off`,
|
||||
CALLS_CALL_START: `custom_${Calls.PluginId}_call_start`,
|
||||
CALLS_SCREEN_ON: `custom_${Calls.PluginId}_user_screen_on`,
|
||||
CALLS_SCREEN_OFF: `custom_${Calls.PluginId}_user_screen_off`,
|
||||
CALLS_USER_RAISE_HAND: `custom_${Calls.PluginId}_user_raise_hand`,
|
||||
CALLS_USER_UNRAISE_HAND: `custom_${Calls.PluginId}_user_unraise_hand`,
|
||||
CALLS_CHANNEL_ENABLED: 'custom_com.mattermost.calls_channel_enable_voice',
|
||||
CALLS_CHANNEL_DISABLED: 'custom_com.mattermost.calls_channel_disable_voice',
|
||||
CALLS_USER_CONNECTED: 'custom_com.mattermost.calls_user_connected',
|
||||
CALLS_USER_DISCONNECTED: 'custom_com.mattermost.calls_user_disconnected',
|
||||
CALLS_USER_MUTED: 'custom_com.mattermost.calls_user_muted',
|
||||
CALLS_USER_UNMUTED: 'custom_com.mattermost.calls_user_unmuted',
|
||||
CALLS_USER_VOICE_ON: 'custom_com.mattermost.calls_user_voice_on',
|
||||
CALLS_USER_VOICE_OFF: 'custom_com.mattermost.calls_user_voice_off',
|
||||
CALLS_CALL_START: 'custom_com.mattermost.calls_call_start',
|
||||
CALLS_SCREEN_ON: 'custom_com.mattermost.calls_user_screen_on',
|
||||
CALLS_SCREEN_OFF: 'custom_com.mattermost.calls_user_screen_off',
|
||||
};
|
||||
export default WebsocketEvents;
|
||||
|
||||
@@ -5,10 +5,8 @@ import keyMirror from '@mm-redux/utils/key_mirror';
|
||||
export default keyMirror({
|
||||
RECEIVED_APP_BINDINGS: null,
|
||||
RECEIVED_THREAD_APP_BINDINGS: null,
|
||||
FAILED_TO_FETCH_APP_BINDINGS: null,
|
||||
CLEAR_APP_BINDINGS: null,
|
||||
CLEAR_THREAD_APP_BINDINGS: null,
|
||||
RECEIVED_APP_COMMAND_FORM: null,
|
||||
RECEIVED_APP_RHS_COMMAND_FORM: null,
|
||||
APPS_PLUGIN_ENABLED: null,
|
||||
APPS_PLUGIN_DISABLED: null,
|
||||
});
|
||||
|
||||
@@ -3,38 +3,20 @@
|
||||
|
||||
import {Client4} from '@client/rest';
|
||||
import {AppsTypes} from '@mm-redux/action_types';
|
||||
import {getThreadAppsBindingsChannelId} from '@mm-redux/selectors/entities/apps';
|
||||
import {getChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {getCurrentChannelId, getCurrentTeamId, getCurrentUserId} from '@mm-redux/selectors/entities/common';
|
||||
import {ActionFunc, DispatchFunc, GetStateFunc} from '@mm-redux/types/actions';
|
||||
|
||||
import {bindClientFunc} from './helpers';
|
||||
|
||||
export function refreshAppBindings(): ActionFunc {
|
||||
return (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
const state = getState();
|
||||
|
||||
dispatch(fetchAppBindings(getCurrentUserId(state), getCurrentChannelId(state)));
|
||||
|
||||
const threadChannelID = getThreadAppsBindingsChannelId(state);
|
||||
if (threadChannelID) {
|
||||
dispatch(fetchThreadAppBindings(getCurrentUserId(state), threadChannelID));
|
||||
}
|
||||
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAppBindings(userID: string, channelID: string): ActionFunc {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
const state = getState();
|
||||
const channel = getChannel(state, channelID);
|
||||
const teamID = channel?.team_id || getCurrentTeamId(state);
|
||||
const channel = getChannel(getState(), channelID);
|
||||
const teamID = channel?.team_id || '';
|
||||
|
||||
return dispatch(bindClientFunc({
|
||||
clientFunc: () => Client4.getAppsBindings(userID, channelID, teamID),
|
||||
onSuccess: AppsTypes.RECEIVED_APP_BINDINGS,
|
||||
onFailure: AppsTypes.FAILED_TO_FETCH_APP_BINDINGS,
|
||||
onFailure: AppsTypes.CLEAR_APP_BINDINGS,
|
||||
}));
|
||||
};
|
||||
}
|
||||
@@ -51,7 +33,6 @@ export function fetchThreadAppBindings(userID: string, channelID: string): Actio
|
||||
},
|
||||
onSuccess: AppsTypes.RECEIVED_THREAD_APP_BINDINGS,
|
||||
onRequest: AppsTypes.CLEAR_THREAD_APP_BINDINGS,
|
||||
onFailure: AppsTypes.FAILED_TO_FETCH_APP_BINDINGS,
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import {Client4} from '@client/rest';
|
||||
import {analytics} from '@init/analytics';
|
||||
import {UserTypes, TeamTypes} from '@mm-redux/action_types';
|
||||
import {getCurrentTeamId} from '@mm-redux/selectors/entities/teams';
|
||||
import {getCurrentUserId, getUsers} from '@mm-redux/selectors/entities/users';
|
||||
import {Action, ActionFunc, ActionResult, batchActions, DispatchFunc, GetStateFunc} from '@mm-redux/types/actions';
|
||||
import {TeamMembership} from '@mm-redux/types/teams';
|
||||
@@ -742,14 +741,6 @@ export function autocompleteUsers(term: string, teamId = '', channelId = '', opt
|
||||
};
|
||||
}
|
||||
|
||||
export function autocompleteUsersInChannel(prefix: string, channelID: string): ActionFunc {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const currentTeamID = getCurrentTeamId(state);
|
||||
return dispatch(autocompleteUsers(prefix, currentTeamID, channelID));
|
||||
};
|
||||
}
|
||||
|
||||
export function searchProfiles(term: string, options: any = {}): ActionFunc {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
const {currentUserId} = getState().entities.users;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {AppCallResponseType, AppExpandLevel, AppFieldType} from '@mm-redux/types/apps';
|
||||
import {AppCallResponseType, AppCallType, AppExpandLevel, AppFieldType} from '@mm-redux/types/apps';
|
||||
|
||||
export const AppBindingLocations = {
|
||||
POST_MENU_ITEM: '/post_menu',
|
||||
@@ -22,6 +22,13 @@ export const AppCallResponseTypes: { [name: string]: AppCallResponseType } = {
|
||||
NAVIGATE: 'navigate',
|
||||
};
|
||||
|
||||
export const AppCallTypes: { [name: string]: AppCallType } = {
|
||||
SUBMIT: 'submit',
|
||||
LOOKUP: 'lookup',
|
||||
FORM: 'form',
|
||||
CANCEL: 'cancel',
|
||||
};
|
||||
|
||||
export const AppExpandLevels: { [name: string]: AppExpandLevel } = {
|
||||
EXPAND_DEFAULT: '',
|
||||
EXPAND_NONE: 'none',
|
||||
|
||||
@@ -28,7 +28,6 @@ export const PostTypes = {
|
||||
COMBINED_USER_ACTIVITY: 'system_combined_user_activity',
|
||||
ME: 'me',
|
||||
ADD_BOT_TEAMS_CHANNELS: 'add_bot_teams_channels',
|
||||
CUSTOM_CALLS: 'custom_calls',
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
@@ -7,11 +7,7 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "1",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "a",
|
||||
"location": "/post_menu/locA",
|
||||
},
|
||||
@@ -23,11 +19,7 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "2",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "a",
|
||||
"location": "/post_menu/locA",
|
||||
},
|
||||
@@ -39,22 +31,14 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "1",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"icon": "icon",
|
||||
"label": "b",
|
||||
"location": "/channel_header/locB",
|
||||
},
|
||||
Object {
|
||||
"app_id": "1",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "c",
|
||||
"location": "/channel_header/locC",
|
||||
},
|
||||
@@ -66,11 +50,14 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "2",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"icon": "icon",
|
||||
"label": "locB",
|
||||
"location": "/channel_header/locB",
|
||||
},
|
||||
Object {
|
||||
"app_id": "2",
|
||||
"call": Object {},
|
||||
"icon": "icon",
|
||||
"label": "c",
|
||||
"location": "/channel_header/locC",
|
||||
@@ -83,21 +70,13 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "3",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "locB",
|
||||
"location": "/channel_header/locB",
|
||||
},
|
||||
Object {
|
||||
"app_id": "3",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "c",
|
||||
"location": "/channel_header/locC",
|
||||
},
|
||||
@@ -109,11 +88,7 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "3",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "c",
|
||||
"location": "/command/locC",
|
||||
},
|
||||
@@ -130,21 +105,13 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "1",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "a",
|
||||
"location": "/post_menu/locA",
|
||||
},
|
||||
Object {
|
||||
"app_id": "1",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "a",
|
||||
"location": "/post_menu/locB",
|
||||
},
|
||||
@@ -156,11 +123,7 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "1",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"icon": "icon",
|
||||
"label": "b",
|
||||
"location": "/channel_header/locB",
|
||||
@@ -176,11 +139,13 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "3",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "subC1",
|
||||
"location": "/command/locC/subC1",
|
||||
},
|
||||
Object {
|
||||
"app_id": "3",
|
||||
"call": Object {},
|
||||
"label": "c2",
|
||||
"location": "/command/locC/subC2",
|
||||
},
|
||||
@@ -188,6 +153,31 @@ Array [
|
||||
"label": "c",
|
||||
"location": "/command/locC",
|
||||
},
|
||||
Object {
|
||||
"app_id": "3",
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "3",
|
||||
"call": Object {},
|
||||
"label": "subC1",
|
||||
"location": "/command/locD/subC1",
|
||||
},
|
||||
],
|
||||
"label": "d",
|
||||
"location": "/command/locD",
|
||||
},
|
||||
],
|
||||
"location": "/command",
|
||||
},
|
||||
Object {
|
||||
"app_id": "1",
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "1",
|
||||
"call": Object {},
|
||||
"label": "locC",
|
||||
"location": "/command/locC",
|
||||
},
|
||||
],
|
||||
"location": "/command",
|
||||
},
|
||||
@@ -199,25 +189,18 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "2",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "c1",
|
||||
"location": "/command/locC/subC1",
|
||||
},
|
||||
Object {
|
||||
"app_id": "2",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "c2",
|
||||
"location": "/command/locC/subC2",
|
||||
},
|
||||
],
|
||||
"call": Object {},
|
||||
"label": "c",
|
||||
"location": "/command/locC",
|
||||
},
|
||||
@@ -234,11 +217,13 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "1",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "locA",
|
||||
"location": "/post_menu/locA",
|
||||
},
|
||||
Object {
|
||||
"app_id": "1",
|
||||
"call": Object {},
|
||||
"label": "a",
|
||||
"location": "/post_menu/locB",
|
||||
},
|
||||
@@ -250,37 +235,37 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "2",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "a",
|
||||
"location": "/post_menu/locA",
|
||||
},
|
||||
Object {
|
||||
"app_id": "2",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "b",
|
||||
"location": "/post_menu/locB",
|
||||
},
|
||||
],
|
||||
"location": "/post_menu",
|
||||
},
|
||||
Object {
|
||||
"app_id": "3",
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "3",
|
||||
"call": Object {},
|
||||
"label": "locA",
|
||||
"location": "/post_menu/locA",
|
||||
},
|
||||
],
|
||||
"location": "/post_menu",
|
||||
},
|
||||
Object {
|
||||
"app_id": "1",
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "1",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"icon": "icon",
|
||||
"label": "b",
|
||||
"location": "/channel_header/locB",
|
||||
@@ -293,11 +278,7 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "3",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "c",
|
||||
"location": "/command/locC",
|
||||
},
|
||||
@@ -314,11 +295,7 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "1",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "a",
|
||||
"location": "/post_menu/locA",
|
||||
},
|
||||
@@ -330,11 +307,7 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "2",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "a",
|
||||
"location": "/post_menu/locA",
|
||||
},
|
||||
@@ -346,11 +319,7 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "1",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"icon": "icon",
|
||||
"label": "b",
|
||||
"location": "/channel_header/locB",
|
||||
@@ -363,11 +332,7 @@ Array [
|
||||
"bindings": Array [
|
||||
Object {
|
||||
"app_id": "3",
|
||||
"form": Object {
|
||||
"submit": Object {
|
||||
"path": "/submit_url",
|
||||
},
|
||||
},
|
||||
"call": Object {},
|
||||
"label": "c",
|
||||
"location": "/command/locC",
|
||||
},
|
||||
|
||||
@@ -7,11 +7,7 @@ import * as Reducers from './apps';
|
||||
|
||||
describe('bindings', () => {
|
||||
const initialState = [];
|
||||
const basicSubmitForm = {
|
||||
submit: {
|
||||
path: '/submit_url',
|
||||
},
|
||||
};
|
||||
|
||||
test('No element get filtered', () => {
|
||||
const data = [
|
||||
{
|
||||
@@ -21,7 +17,7 @@ describe('bindings', () => {
|
||||
{
|
||||
location: 'locA',
|
||||
label: 'a',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -32,7 +28,7 @@ describe('bindings', () => {
|
||||
{
|
||||
location: 'locA',
|
||||
label: 'a',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -44,7 +40,7 @@ describe('bindings', () => {
|
||||
location: 'locB',
|
||||
label: 'b',
|
||||
icon: 'icon',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -55,7 +51,7 @@ describe('bindings', () => {
|
||||
{
|
||||
location: 'locC',
|
||||
label: 'c',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -81,7 +77,7 @@ describe('bindings', () => {
|
||||
{
|
||||
location: 'locA',
|
||||
label: 'a',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -92,7 +88,7 @@ describe('bindings', () => {
|
||||
{
|
||||
location: 'locA',
|
||||
label: 'a',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -104,12 +100,12 @@ describe('bindings', () => {
|
||||
location: 'locB',
|
||||
label: 'b',
|
||||
icon: 'icon',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
{
|
||||
location: 'locC',
|
||||
label: 'c',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -118,14 +114,15 @@ describe('bindings', () => {
|
||||
location: '/channel_header',
|
||||
bindings: [
|
||||
{
|
||||
location: 'locB',
|
||||
icon: 'icon',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
{
|
||||
location: 'locC',
|
||||
label: 'c',
|
||||
icon: 'icon',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -135,12 +132,12 @@ describe('bindings', () => {
|
||||
bindings: [
|
||||
{
|
||||
location: 'locB',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
{
|
||||
location: 'locC',
|
||||
label: 'c',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -151,7 +148,7 @@ describe('bindings', () => {
|
||||
{
|
||||
location: 'locC',
|
||||
label: 'c',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -175,12 +172,13 @@ describe('bindings', () => {
|
||||
location: '/post_menu',
|
||||
bindings: [
|
||||
{
|
||||
form: basicSubmitForm,
|
||||
location: 'locA',
|
||||
call: {},
|
||||
},
|
||||
{
|
||||
location: 'locB',
|
||||
label: 'a',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -191,12 +189,12 @@ describe('bindings', () => {
|
||||
{
|
||||
location: 'locA',
|
||||
label: 'a',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
{
|
||||
location: 'locB',
|
||||
label: 'b',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -205,7 +203,8 @@ describe('bindings', () => {
|
||||
location: '/post_menu',
|
||||
bindings: [
|
||||
{
|
||||
form: basicSubmitForm,
|
||||
location: 'locA',
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -217,7 +216,7 @@ describe('bindings', () => {
|
||||
location: 'locB',
|
||||
label: 'b',
|
||||
icon: 'icon',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -228,7 +227,7 @@ describe('bindings', () => {
|
||||
{
|
||||
location: 'locC',
|
||||
label: 'c',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -254,12 +253,12 @@ describe('bindings', () => {
|
||||
{
|
||||
location: 'locA',
|
||||
label: 'a',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
{
|
||||
location: 'locB',
|
||||
label: 'a',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -271,7 +270,7 @@ describe('bindings', () => {
|
||||
location: 'locB',
|
||||
label: 'b',
|
||||
icon: 'icon',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -284,12 +283,13 @@ describe('bindings', () => {
|
||||
label: 'c',
|
||||
bindings: [
|
||||
{
|
||||
form: basicSubmitForm,
|
||||
location: 'subC1',
|
||||
call: {},
|
||||
},
|
||||
{
|
||||
location: 'subC2',
|
||||
label: 'c2',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -298,7 +298,8 @@ describe('bindings', () => {
|
||||
label: 'd',
|
||||
bindings: [
|
||||
{
|
||||
form: basicSubmitForm,
|
||||
location: 'subC1',
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -309,7 +310,8 @@ describe('bindings', () => {
|
||||
location: '/command',
|
||||
bindings: [
|
||||
{
|
||||
form: basicSubmitForm,
|
||||
location: 'locC',
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -320,16 +322,17 @@ describe('bindings', () => {
|
||||
{
|
||||
location: 'locC',
|
||||
label: 'c',
|
||||
call: {},
|
||||
bindings: [
|
||||
{
|
||||
location: 'subC1',
|
||||
label: 'c1',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
{
|
||||
location: 'subC2',
|
||||
label: 'c2',
|
||||
form: basicSubmitForm,
|
||||
call: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -17,8 +17,7 @@ export function bindings(state: AppBinding[] = [], action: GenericAction): AppBi
|
||||
}
|
||||
return newBindings;
|
||||
}
|
||||
case AppsTypes.FAILED_TO_FETCH_APP_BINDINGS:
|
||||
case AppsTypes.APPS_PLUGIN_DISABLED:
|
||||
case AppsTypes.CLEAR_APP_BINDINGS:
|
||||
if (state.length > 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -43,11 +42,12 @@ export function bindingsForms(state: AppCommandFormMap = {}, action: GenericActi
|
||||
};
|
||||
return newState;
|
||||
}
|
||||
case AppsTypes.FAILED_TO_FETCH_APP_BINDINGS:
|
||||
case AppsTypes.CLEAR_APP_BINDINGS: {
|
||||
if (Object.keys(state).length) {
|
||||
return {};
|
||||
}
|
||||
return state;
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@@ -108,26 +108,10 @@ export function threadBindingsForms(state: AppCommandFormMap = {}, action: Gener
|
||||
}
|
||||
}
|
||||
|
||||
export function pluginEnabled(state = true, action: GenericAction): boolean {
|
||||
switch (action.type) {
|
||||
case AppsTypes.APPS_PLUGIN_ENABLED:
|
||||
return true;
|
||||
case AppsTypes.APPS_PLUGIN_DISABLED:
|
||||
return false;
|
||||
case AppsTypes.RECEIVED_APP_BINDINGS:
|
||||
return true;
|
||||
case AppsTypes.FAILED_TO_FETCH_APP_BINDINGS:
|
||||
return false;
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default (combineReducers({
|
||||
bindings,
|
||||
bindingsForms,
|
||||
threadBindings,
|
||||
threadBindingsForms,
|
||||
threadBindingsChannelId,
|
||||
pluginEnabled,
|
||||
}) as (b: AppsState, a: GenericAction) => AppsState);
|
||||
|
||||
@@ -8,6 +8,7 @@ export type AppManifest = {
|
||||
display_name: string;
|
||||
description?: string;
|
||||
homepage_url?: string;
|
||||
root_url: string;
|
||||
}
|
||||
|
||||
export type AppModalState = {
|
||||
@@ -21,7 +22,6 @@ export type AppsState = {
|
||||
threadBindings: AppBinding[];
|
||||
threadBindingsForms: AppCommandFormMap;
|
||||
threadBindingsChannelId: string;
|
||||
pluginEnabled: boolean;
|
||||
};
|
||||
|
||||
export type AppBinding = {
|
||||
@@ -50,17 +50,19 @@ export type AppBinding = {
|
||||
depends_on_user?: boolean;
|
||||
depends_on_post?: boolean;
|
||||
|
||||
// A Binding is either an action (makes a call), a Form, or is a
|
||||
// "container" for other locations - i.e. menu sub-items or subcommands.
|
||||
// A Binding is either to a Call, or is a "container" for other locations -
|
||||
// i.e. menu sub-items or subcommands.
|
||||
call?: AppCall;
|
||||
bindings?: AppBinding[];
|
||||
form?: AppForm;
|
||||
submit?: AppCall;
|
||||
};
|
||||
|
||||
export type AppCallValues = {
|
||||
[name: string]: any;
|
||||
};
|
||||
|
||||
export type AppCallType = string;
|
||||
|
||||
export type AppCall = {
|
||||
path: string;
|
||||
expand?: AppExpand;
|
||||
@@ -79,8 +81,9 @@ export type AppCallResponseType = string;
|
||||
|
||||
export type AppCallResponse<Res = unknown> = {
|
||||
type: AppCallResponseType;
|
||||
text?: string;
|
||||
markdown?: string;
|
||||
data?: Res;
|
||||
error?: string;
|
||||
navigate_to_url?: string;
|
||||
use_external_browser?: boolean;
|
||||
call?: AppCall;
|
||||
@@ -104,7 +107,6 @@ export type AppContext = {
|
||||
root_id?: string;
|
||||
props?: AppContextProps;
|
||||
user_agent?: string;
|
||||
track_as_submit?: boolean;
|
||||
};
|
||||
|
||||
export type AppContextProps = {
|
||||
@@ -134,19 +136,8 @@ export type AppForm = {
|
||||
submit_buttons?: string;
|
||||
cancel_button?: boolean;
|
||||
submit_on_cancel?: boolean;
|
||||
fields?: AppField[];
|
||||
|
||||
// source is used in 2 cases:
|
||||
// - if submit is not set, it is used to fetch the submittable form from
|
||||
// the app.
|
||||
// - if a select field change triggers a refresh, the form is refreshed
|
||||
// from source.
|
||||
source?: AppCall;
|
||||
|
||||
// submit is called when one of the submit buttons is pressed, or the
|
||||
// command is executed.
|
||||
submit?: AppCall;
|
||||
|
||||
fields: AppField[];
|
||||
call?: AppCall;
|
||||
depends_on?: string[];
|
||||
};
|
||||
|
||||
@@ -185,7 +176,6 @@ export type AppField = {
|
||||
refresh?: boolean;
|
||||
options?: AppSelectOption[];
|
||||
multiselect?: boolean;
|
||||
lookup?: AppCall;
|
||||
|
||||
// Text props
|
||||
subtype?: string;
|
||||
|
||||
@@ -62,7 +62,6 @@ export type Config = {
|
||||
EnableGuestAccounts: string;
|
||||
EnableIncomingWebhooks: string;
|
||||
EnableLatex: string;
|
||||
EnableInlineLatex: string;
|
||||
EnableLdap: string;
|
||||
EnableLegacySidebar: string;
|
||||
EnableLinkPreviews: string;
|
||||
|
||||
@@ -1,29 +1,15 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {ServerChannelState, ServerConfig} from '@mmproducts/calls/store/types/calls';
|
||||
import type {ServerChannelState} from '@mmproducts/calls/store/types/calls';
|
||||
|
||||
export interface ClientCallsMix {
|
||||
getEnabled: () => Promise<Boolean>;
|
||||
getCalls: () => Promise<ServerChannelState[]>;
|
||||
getCallsConfig: () => Promise<ServerConfig>;
|
||||
enableChannelCalls: (channelId: string) => Promise<ServerChannelState>;
|
||||
disableChannelCalls: (channelId: string) => Promise<ServerChannelState>;
|
||||
}
|
||||
|
||||
const ClientCalls = (superclass: any) => class extends superclass {
|
||||
getEnabled = async () => {
|
||||
try {
|
||||
await this.doFetch(
|
||||
`${this.getCallsRoute()}/version`,
|
||||
{method: 'get'},
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
getCalls = async () => {
|
||||
return this.doFetch(
|
||||
`${this.getCallsRoute()}/channels`,
|
||||
@@ -35,7 +21,7 @@ const ClientCalls = (superclass: any) => class extends superclass {
|
||||
return this.doFetch(
|
||||
`${this.getCallsRoute()}/config`,
|
||||
{method: 'get'},
|
||||
) as ServerConfig;
|
||||
);
|
||||
};
|
||||
|
||||
enableChannelCalls = async (channelId: string) => {
|
||||
|
||||
@@ -3,155 +3,194 @@
|
||||
exports[`CallAvatar should match snapshot muted 1`] = `
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"borderRadius": 20,
|
||||
"height": 40,
|
||||
"marginBottom": 5,
|
||||
"width": 40,
|
||||
},
|
||||
Object {
|
||||
"shadowColor": "rgb(61, 184, 135)",
|
||||
"shadowOffset": Object {
|
||||
"height": 0,
|
||||
"width": 0,
|
||||
},
|
||||
"shadowOpacity": 1,
|
||||
"shadowRadius": 10,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.16)",
|
||||
"borderRadius": 28,
|
||||
"height": 56,
|
||||
"marginRight": 4,
|
||||
"padding": 4,
|
||||
"width": 56,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(ProfilePicture)
|
||||
showStatus={false}
|
||||
size={40}
|
||||
userId="user-id"
|
||||
/>
|
||||
<CompassIcon
|
||||
name="microphone-off"
|
||||
size={12}
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "black",
|
||||
"borderColor": "black",
|
||||
"borderRadius": 10,
|
||||
"borderWidth": 2,
|
||||
"bottom": -5,
|
||||
"color": "white",
|
||||
"height": 20,
|
||||
"overflow": "hidden",
|
||||
"padding": 2,
|
||||
"position": "absolute",
|
||||
"right": -5,
|
||||
"textAlign": "center",
|
||||
"textAlignVertical": "center",
|
||||
"width": 20,
|
||||
"backgroundColor": "rgba(255,255,255,0.24)",
|
||||
"borderRadius": 24,
|
||||
"height": 48,
|
||||
"padding": 4,
|
||||
"width": 48,
|
||||
}
|
||||
}
|
||||
/>
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": 20,
|
||||
"height": 40,
|
||||
"width": 40,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(ProfilePicture)
|
||||
showStatus={false}
|
||||
size={40}
|
||||
userId="user-id"
|
||||
/>
|
||||
<CompassIcon
|
||||
name="microphone-off"
|
||||
size={16}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "black",
|
||||
"borderColor": "black",
|
||||
"borderRadius": 12,
|
||||
"borderWidth": 2,
|
||||
"bottom": -5,
|
||||
"color": "white",
|
||||
"height": 24,
|
||||
"overflow": "hidden",
|
||||
"padding": 2,
|
||||
"position": "absolute",
|
||||
"right": -5,
|
||||
"textAlign": "center",
|
||||
"textAlignVertical": "center",
|
||||
"width": 24,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
|
||||
exports[`CallAvatar should match snapshot size large 1`] = `
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"borderRadius": 36,
|
||||
"height": 72,
|
||||
"marginBottom": 5,
|
||||
"width": 72,
|
||||
},
|
||||
Object {
|
||||
"shadowColor": "rgb(61, 184, 135)",
|
||||
"shadowOffset": Object {
|
||||
"height": 0,
|
||||
"width": 0,
|
||||
},
|
||||
"shadowOpacity": 1,
|
||||
"shadowRadius": 10,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.16)",
|
||||
"borderRadius": 44,
|
||||
"height": 88,
|
||||
"marginRight": 4,
|
||||
"padding": 4,
|
||||
"width": 88,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(ProfilePicture)
|
||||
showStatus={false}
|
||||
size={72}
|
||||
userId="user-id"
|
||||
/>
|
||||
<CompassIcon
|
||||
name="microphone"
|
||||
size={16}
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#3DB887",
|
||||
"borderColor": "black",
|
||||
"borderRadius": 12,
|
||||
"borderWidth": 2,
|
||||
"bottom": -5,
|
||||
"color": "white",
|
||||
"height": 24,
|
||||
"overflow": "hidden",
|
||||
"padding": 2,
|
||||
"position": "absolute",
|
||||
"right": -5,
|
||||
"textAlign": "center",
|
||||
"textAlignVertical": "center",
|
||||
"width": 24,
|
||||
"backgroundColor": "rgba(255,255,255,0.24)",
|
||||
"borderRadius": 40,
|
||||
"height": 80,
|
||||
"padding": 4,
|
||||
"width": 80,
|
||||
}
|
||||
}
|
||||
/>
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": 36,
|
||||
"height": 72,
|
||||
"width": 72,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(ProfilePicture)
|
||||
showStatus={false}
|
||||
size={72}
|
||||
userId="user-id"
|
||||
/>
|
||||
<CompassIcon
|
||||
name="microphone"
|
||||
size={16}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#3DB887",
|
||||
"borderColor": "black",
|
||||
"borderRadius": 12,
|
||||
"borderWidth": 2,
|
||||
"bottom": -5,
|
||||
"color": "white",
|
||||
"height": 24,
|
||||
"overflow": "hidden",
|
||||
"padding": 2,
|
||||
"position": "absolute",
|
||||
"right": -5,
|
||||
"textAlign": "center",
|
||||
"textAlignVertical": "center",
|
||||
"width": 24,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
|
||||
exports[`CallAvatar should match snapshot unmuted 1`] = `
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"borderRadius": 20,
|
||||
"height": 40,
|
||||
"marginBottom": 5,
|
||||
"width": 40,
|
||||
},
|
||||
Object {
|
||||
"shadowColor": "rgb(61, 184, 135)",
|
||||
"shadowOffset": Object {
|
||||
"height": 0,
|
||||
"width": 0,
|
||||
},
|
||||
"shadowOpacity": 1,
|
||||
"shadowRadius": 10,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.16)",
|
||||
"borderRadius": 28,
|
||||
"height": 56,
|
||||
"marginRight": 4,
|
||||
"padding": 4,
|
||||
"width": 56,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(ProfilePicture)
|
||||
showStatus={false}
|
||||
size={40}
|
||||
userId="user-id"
|
||||
/>
|
||||
<CompassIcon
|
||||
name="microphone"
|
||||
size={12}
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#3DB887",
|
||||
"borderColor": "black",
|
||||
"borderRadius": 10,
|
||||
"borderWidth": 2,
|
||||
"bottom": -5,
|
||||
"color": "white",
|
||||
"height": 20,
|
||||
"overflow": "hidden",
|
||||
"padding": 2,
|
||||
"position": "absolute",
|
||||
"right": -5,
|
||||
"textAlign": "center",
|
||||
"textAlignVertical": "center",
|
||||
"width": 20,
|
||||
"backgroundColor": "rgba(255,255,255,0.24)",
|
||||
"borderRadius": 24,
|
||||
"height": 48,
|
||||
"padding": 4,
|
||||
"width": 48,
|
||||
}
|
||||
}
|
||||
/>
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": 20,
|
||||
"height": 40,
|
||||
"width": 40,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(ProfilePicture)
|
||||
showStatus={false}
|
||||
size={40}
|
||||
userId="user-id"
|
||||
/>
|
||||
<CompassIcon
|
||||
name="microphone"
|
||||
size={16}
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#3DB887",
|
||||
"borderColor": "black",
|
||||
"borderRadius": 12,
|
||||
"borderWidth": 2,
|
||||
"bottom": -5,
|
||||
"color": "white",
|
||||
"height": 24,
|
||||
"overflow": "hidden",
|
||||
"padding": 2,
|
||||
"position": "absolute",
|
||||
"right": -5,
|
||||
"textAlign": "center",
|
||||
"textAlignVertical": "center",
|
||||
"width": 24,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`EnableDisableCalls should match snapshot if calls are disabled 1`] = `
|
||||
<React.Fragment>
|
||||
<Separator
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc1f",
|
||||
"buttonBg": "#1c58d9",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3f4350",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#d24b4e",
|
||||
"errorTextColor": "#d24b4e",
|
||||
"linkColor": "#386fe5",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionColor": "#1e325c",
|
||||
"mentionHighlightBg": "#ffd470",
|
||||
"mentionHighlightLink": "#1b1d22",
|
||||
"newMessageSeparator": "#cc8f00",
|
||||
"onlineIndicator": "#3db887",
|
||||
"sidebarBg": "#1e325c",
|
||||
"sidebarHeaderBg": "#192a4d",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarTeamBarBg": "#14213e",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#5d89ea",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#28427b",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Denim",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Enable Calls"
|
||||
icon="phone-outline"
|
||||
rightArrow={false}
|
||||
shouldRender={true}
|
||||
testID="test-id"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc1f",
|
||||
"buttonBg": "#1c58d9",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3f4350",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#d24b4e",
|
||||
"errorTextColor": "#d24b4e",
|
||||
"linkColor": "#386fe5",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionColor": "#1e325c",
|
||||
"mentionHighlightBg": "#ffd470",
|
||||
"mentionHighlightLink": "#1b1d22",
|
||||
"newMessageSeparator": "#cc8f00",
|
||||
"onlineIndicator": "#3db887",
|
||||
"sidebarBg": "#1e325c",
|
||||
"sidebarHeaderBg": "#192a4d",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarTeamBarBg": "#14213e",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#5d89ea",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#28427b",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Denim",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
||||
exports[`EnableDisableCalls should match snapshot if calls are enabled 1`] = `
|
||||
<React.Fragment>
|
||||
<Separator
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc1f",
|
||||
"buttonBg": "#1c58d9",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3f4350",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#d24b4e",
|
||||
"errorTextColor": "#d24b4e",
|
||||
"linkColor": "#386fe5",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionColor": "#1e325c",
|
||||
"mentionHighlightBg": "#ffd470",
|
||||
"mentionHighlightLink": "#1b1d22",
|
||||
"newMessageSeparator": "#cc8f00",
|
||||
"onlineIndicator": "#3db887",
|
||||
"sidebarBg": "#1e325c",
|
||||
"sidebarHeaderBg": "#192a4d",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarTeamBarBg": "#14213e",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#5d89ea",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#28427b",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Denim",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Disable Calls"
|
||||
icon="phone-outline"
|
||||
rightArrow={false}
|
||||
shouldRender={true}
|
||||
testID="test-id"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc1f",
|
||||
"buttonBg": "#1c58d9",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3f4350",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#d24b4e",
|
||||
"errorTextColor": "#d24b4e",
|
||||
"linkColor": "#386fe5",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionColor": "#1e325c",
|
||||
"mentionHighlightBg": "#ffd470",
|
||||
"mentionHighlightLink": "#1b1d22",
|
||||
"newMessageSeparator": "#cc8f00",
|
||||
"onlineIndicator": "#3db887",
|
||||
"sidebarBg": "#1e325c",
|
||||
"sidebarHeaderBg": "#192a4d",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarTeamBarBg": "#14213e",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#5d89ea",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#28427b",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Denim",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</React.Fragment>
|
||||
`;
|
||||
@@ -1,43 +1,21 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FloatingCallContainer should match snapshot 1`] = `
|
||||
<RNCSafeAreaProvider
|
||||
onInsetsChange={[Function]}
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
"position": "absolute",
|
||||
"top": 91,
|
||||
"width": "100%",
|
||||
"zIndex": 9,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Context.Provider
|
||||
value={
|
||||
Object {
|
||||
"height": 0,
|
||||
"width": 0,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Context.Provider
|
||||
value={
|
||||
Object {
|
||||
"bottom": 0,
|
||||
"left": 0,
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<FloatingCallContainer>
|
||||
<Text>
|
||||
test
|
||||
</Text>
|
||||
</FloatingCallContainer>
|
||||
</Context.Provider>
|
||||
</Context.Provider>
|
||||
</RNCSafeAreaProvider>
|
||||
<Text>
|
||||
test
|
||||
</Text>
|
||||
</View>
|
||||
`;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {View, StyleSheet, Text, Platform} from 'react-native';
|
||||
import {View, StyleSheet} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
@@ -11,21 +11,15 @@ type Props = {
|
||||
userId?: string;
|
||||
volume: number;
|
||||
muted?: boolean;
|
||||
sharingScreen?: boolean;
|
||||
raisedHand?: boolean;
|
||||
size?: 'm' | 'l';
|
||||
}
|
||||
|
||||
const getStyleSheet = (props: Props) => {
|
||||
const baseSize = props.size === 'm' || !props.size ? 40 : 72;
|
||||
const smallIcon = props.size === 'm' || !props.size;
|
||||
const widthHeight = smallIcon ? 20 : 24;
|
||||
const borderRadius = smallIcon ? 10 : 12;
|
||||
const padding = smallIcon ? 1 : 2;
|
||||
|
||||
return StyleSheet.create({
|
||||
pictureHalo: {
|
||||
backgroundColor: 'rgba(61, 184, 135,' + (0.24 * props.volume) + ')',
|
||||
backgroundColor: 'rgba(255,255,255,' + (0.16 * props.volume) + ')',
|
||||
height: baseSize + 16,
|
||||
width: baseSize + 16,
|
||||
padding: 4,
|
||||
@@ -33,32 +27,25 @@ const getStyleSheet = (props: Props) => {
|
||||
borderRadius: (baseSize + 16) / 2,
|
||||
},
|
||||
pictureHalo2: {
|
||||
backgroundColor: 'rgba(61, 184, 135,' + (0.32 * props.volume) + ')',
|
||||
backgroundColor: 'rgba(255,255,255,' + (0.24 * props.volume) + ')',
|
||||
height: baseSize + 8,
|
||||
width: baseSize + 8,
|
||||
padding: 3,
|
||||
padding: 4,
|
||||
borderRadius: (baseSize + 8) / 2,
|
||||
},
|
||||
picture: {
|
||||
borderRadius: baseSize / 2,
|
||||
height: baseSize,
|
||||
width: baseSize,
|
||||
marginBottom: 5,
|
||||
},
|
||||
voiceShadow: {
|
||||
shadowColor: 'rgb(61, 184, 135)',
|
||||
shadowOffset: {width: 0, height: 0},
|
||||
shadowOpacity: 1,
|
||||
shadowRadius: 10,
|
||||
},
|
||||
mute: {
|
||||
position: 'absolute',
|
||||
bottom: -5,
|
||||
right: -5,
|
||||
width: widthHeight,
|
||||
height: widthHeight,
|
||||
borderRadius,
|
||||
padding,
|
||||
width: 24,
|
||||
height: 24,
|
||||
borderRadius: 12,
|
||||
padding: 2,
|
||||
backgroundColor: props.muted ? 'black' : '#3DB887',
|
||||
borderColor: 'black',
|
||||
borderWidth: 2,
|
||||
@@ -66,116 +53,36 @@ const getStyleSheet = (props: Props) => {
|
||||
textAlign: 'center',
|
||||
textAlignVertical: 'center',
|
||||
overflow: 'hidden',
|
||||
...Platform.select(
|
||||
{
|
||||
ios: {
|
||||
padding: 2,
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
raisedHand: {
|
||||
position: 'absolute',
|
||||
overflow: 'hidden',
|
||||
top: 0,
|
||||
right: -5,
|
||||
backgroundColor: 'black',
|
||||
borderColor: 'black',
|
||||
borderRadius,
|
||||
padding,
|
||||
borderWidth: 2,
|
||||
width: widthHeight,
|
||||
height: widthHeight,
|
||||
fontSize: smallIcon ? 10 : 12,
|
||||
...Platform.select(
|
||||
{
|
||||
android: {
|
||||
paddingLeft: 4,
|
||||
paddingTop: 2,
|
||||
color: 'rgb(255, 188, 66)',
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
screenSharing: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: -5,
|
||||
width: widthHeight,
|
||||
height: widthHeight,
|
||||
borderRadius,
|
||||
padding: padding + 1,
|
||||
backgroundColor: '#D24B4E',
|
||||
borderColor: 'black',
|
||||
borderWidth: 2,
|
||||
color: 'white',
|
||||
textAlign: 'center',
|
||||
textAlignVertical: 'center',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const CallAvatar = (props: Props) => {
|
||||
const style = getStyleSheet(props);
|
||||
const profileSize = props.size === 'm' || !props.size ? 40 : 72;
|
||||
const iconSize = props.size === 'm' || !props.size ? 12 : 16;
|
||||
const styleShadow = props.volume > 0 ? style.voiceShadow : {};
|
||||
|
||||
// Only show one or the other.
|
||||
let topRightIcon: JSX.Element | null = null;
|
||||
if (props.sharingScreen) {
|
||||
topRightIcon = (
|
||||
<CompassIcon
|
||||
name={'monitor'}
|
||||
size={iconSize}
|
||||
style={style.screenSharing}
|
||||
/>
|
||||
);
|
||||
} else if (props.raisedHand) {
|
||||
topRightIcon = (
|
||||
<Text style={style.raisedHand}>
|
||||
{'✋'}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
const view = (
|
||||
<View style={[style.picture, styleShadow]}>
|
||||
{
|
||||
props.userId ?
|
||||
<ProfilePicture
|
||||
userId={props.userId}
|
||||
size={profileSize}
|
||||
showStatus={false}
|
||||
/> :
|
||||
<CompassIcon
|
||||
name='account-outline'
|
||||
size={profileSize}
|
||||
/>
|
||||
}
|
||||
{
|
||||
props.muted !== undefined &&
|
||||
<CompassIcon
|
||||
name={props.muted ? 'microphone-off' : 'microphone'}
|
||||
size={iconSize}
|
||||
style={style.mute}
|
||||
/>
|
||||
}
|
||||
{topRightIcon}
|
||||
</View>
|
||||
);
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
return (
|
||||
<View style={style.pictureHalo}>
|
||||
<View style={style.pictureHalo2}>
|
||||
{view}
|
||||
return (
|
||||
<View style={style.pictureHalo}>
|
||||
<View style={style.pictureHalo2}>
|
||||
<View style={style.picture}>
|
||||
{props.userId ?
|
||||
<ProfilePicture
|
||||
userId={props.userId}
|
||||
size={props.size === 'm' || !props.size ? 40 : 72}
|
||||
showStatus={false}
|
||||
/> :
|
||||
<CompassIcon
|
||||
name='account-outline'
|
||||
size={props.size === 'm' || !props.size ? 40 : 72}
|
||||
/>
|
||||
}
|
||||
{props.muted !== undefined &&
|
||||
<CompassIcon
|
||||
name={props.muted ? 'microphone-off' : 'microphone'}
|
||||
size={16}
|
||||
style={style.mute}
|
||||
/>}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return view;
|
||||
</View>
|
||||
);
|
||||
};
|
||||
export default CallAvatar;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import React from 'react';
|
||||
import {Alert, TouchableOpacity} from 'react-native';
|
||||
|
||||
import {Posts} from '@mm-redux/constants';
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
import {shallowWithIntl} from '@test/intl-test-helper';
|
||||
|
||||
@@ -20,7 +19,7 @@ describe('CallMessage', () => {
|
||||
props: {
|
||||
start_at: 100,
|
||||
},
|
||||
type: Posts.POST_TYPES.CUSTOM_CALLS,
|
||||
type: 'custom_calls',
|
||||
},
|
||||
user: {
|
||||
id: 'user-1-id',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import moment from 'moment-timezone';
|
||||
import React, {useCallback} from 'react';
|
||||
import {injectIntl, intlShape, IntlShape} from 'react-intl';
|
||||
import {injectIntl, IntlShape} from 'react-intl';
|
||||
import {View, TouchableOpacity, Text} from 'react-native';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
@@ -19,7 +19,7 @@ import type {UserProfile} from '@mm-redux/types/users';
|
||||
|
||||
type CallMessageProps = {
|
||||
actions: {
|
||||
joinCall: (channelId: string, intl: typeof intlShape) => void;
|
||||
joinCall: (channelId: string) => void;
|
||||
};
|
||||
post: Post;
|
||||
user: UserProfile;
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {intlShape} from 'react-intl';
|
||||
|
||||
import {Theme} from '@mm-redux/types/theme';
|
||||
import EnableDisableCalls from '@mmproducts/calls/components/channel_info/enable_disable_calls';
|
||||
import StartCall from '@mmproducts/calls/components/channel_info/start_call';
|
||||
import {useCallsChannelSettings} from '@mmproducts/calls/hooks';
|
||||
|
||||
type Props = {
|
||||
theme: Theme;
|
||||
joinCall: (channelId: string, intl: typeof intlShape) => void;
|
||||
}
|
||||
|
||||
const CallsChannelInfo = ({theme, joinCall}: Props) => {
|
||||
const [enabled, canEnableDisable] = useCallsChannelSettings();
|
||||
|
||||
return (
|
||||
<>
|
||||
{enabled &&
|
||||
<StartCall
|
||||
testID='channel_info.start_call.action'
|
||||
theme={theme}
|
||||
joinCall={joinCall}
|
||||
/>
|
||||
}
|
||||
{canEnableDisable &&
|
||||
<EnableDisableCalls
|
||||
testID='channel_info.start_call.action'
|
||||
theme={theme}
|
||||
enabled={enabled}
|
||||
/>
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CallsChannelInfo;
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import {getCurrentChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {Theme} from '@mm-redux/types/theme';
|
||||
import {useTryCallsFunction} from '@mmproducts/calls/hooks';
|
||||
import {disableChannelCalls, enableChannelCalls} from '@mmproducts/calls/store/actions/calls';
|
||||
import ChannelInfoRow from '@screens/channel_info/channel_info_row';
|
||||
import Separator from '@screens/channel_info/separator';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
type Props = {
|
||||
testID: string;
|
||||
theme: Theme;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const EnableDisableCalls = ({testID, theme, enabled}: Props) => {
|
||||
const currentChannel = useSelector(getCurrentChannel);
|
||||
|
||||
const toggleCalls = useCallback(() => {
|
||||
if (enabled) {
|
||||
disableChannelCalls(currentChannel.id);
|
||||
} else {
|
||||
enableChannelCalls(currentChannel.id);
|
||||
}
|
||||
}, [enabled, currentChannel.id]);
|
||||
|
||||
const [tryOnPress, msgPostfix] = useTryCallsFunction(toggleCalls);
|
||||
const handleEnableDisableCalls = preventDoubleTap(tryOnPress);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Separator theme={theme}/>
|
||||
<ChannelInfoRow
|
||||
testID={testID}
|
||||
action={handleEnableDisableCalls}
|
||||
defaultMessage={(enabled ? 'Disable Calls' : 'Enable Calls') + msgPostfix}
|
||||
icon='phone-outline'
|
||||
theme={theme}
|
||||
rightArrow={false}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnableDisableCalls;
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {injectIntl, intlShape, IntlShape} from 'react-intl';
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import {getChannel, getCurrentChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {GlobalState} from '@mm-redux/types/store';
|
||||
import {Theme} from '@mm-redux/types/theme';
|
||||
import leaveAndJoinWithAlert from '@mmproducts/calls/components/leave_and_join_alert';
|
||||
import {useTryCallsFunction} from '@mmproducts/calls/hooks';
|
||||
import {getCalls, getCurrentCall} from '@mmproducts/calls/store/selectors/calls';
|
||||
import ChannelInfoRow from '@screens/channel_info/channel_info_row';
|
||||
import Separator from '@screens/channel_info/separator';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
type Props = {
|
||||
testID: string;
|
||||
theme: Theme;
|
||||
intl: typeof IntlShape;
|
||||
joinCall: (channelId: string, intl: typeof intlShape) => void;
|
||||
}
|
||||
|
||||
const StartCall = ({testID, theme, intl, joinCall}: Props) => {
|
||||
const currentChannel = useSelector(getCurrentChannel);
|
||||
const call = useSelector(getCalls)[currentChannel.id];
|
||||
const currentCall = useSelector(getCurrentCall);
|
||||
const currentCallChannelId = currentCall?.channelId || '';
|
||||
const callChannelName = useSelector((state: GlobalState) => getChannel(state, currentCallChannelId)?.display_name) || '';
|
||||
|
||||
const confirmToJoin = Boolean(currentCall && currentCall.channelId !== currentChannel.id);
|
||||
const alreadyInTheCall = Boolean(currentCall && call && currentCall.channelId === call.channelId);
|
||||
const ongoingCall = Boolean(call);
|
||||
|
||||
const leaveAndJoin = useCallback(() => {
|
||||
leaveAndJoinWithAlert(intl, currentChannel.id, callChannelName, currentChannel.display_name, confirmToJoin, joinCall);
|
||||
}, [intl, currentChannel.id, callChannelName, currentChannel.display_name, confirmToJoin, joinCall]);
|
||||
const [tryLeaveAndJoin, msgPostfix] = useTryCallsFunction(leaveAndJoin);
|
||||
const handleStartCall = useCallback(preventDoubleTap(tryLeaveAndJoin), [tryLeaveAndJoin]);
|
||||
|
||||
if (alreadyInTheCall) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Separator theme={theme}/>
|
||||
<ChannelInfoRow
|
||||
testID={testID}
|
||||
action={handleStartCall}
|
||||
defaultMessage={(ongoingCall ? 'Join Ongoing Call' : 'Start Call') + msgPostfix}
|
||||
icon='phone-in-talk'
|
||||
theme={theme}
|
||||
rightArrow={false}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default injectIntl(StartCall);
|
||||
39
app/products/calls/components/enable_disable_calls.test.js
Normal file
39
app/products/calls/components/enable_disable_calls.test.js
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
import EnableDisableCalls from './enable_disable_calls';
|
||||
|
||||
describe('EnableDisableCalls', () => {
|
||||
const baseProps = {
|
||||
testID: 'test-id',
|
||||
theme: Preferences.THEMES.denim,
|
||||
onPress: jest.fn(),
|
||||
canEnableDisableCalls: true,
|
||||
enabled: false,
|
||||
};
|
||||
|
||||
test('should match snapshot if calls are disabled', () => {
|
||||
const wrapper = shallow(<EnableDisableCalls {...baseProps}/>);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot if calls are enabled', () => {
|
||||
const props = {...baseProps, enabled: true};
|
||||
const wrapper = shallow(<EnableDisableCalls {...props}/>);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should be null if you can not enable/disable calls', () => {
|
||||
const props = {...baseProps, canEnableDisableCalls: false};
|
||||
const wrapper = shallow(<EnableDisableCalls {...props}/>);
|
||||
|
||||
expect(wrapper.getElement()).toBeNull();
|
||||
});
|
||||
});
|
||||
43
app/products/calls/components/enable_disable_calls.tsx
Normal file
43
app/products/calls/components/enable_disable_calls.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
|
||||
import {Theme} from '@mm-redux/types/theme';
|
||||
import ChannelInfoRow from '@screens/channel_info/channel_info_row';
|
||||
import Separator from '@screens/channel_info/separator';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
type Props = {
|
||||
testID?: string;
|
||||
theme: Theme;
|
||||
onPress: (channelId: string) => void;
|
||||
canEnableDisableCalls: boolean;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const EnableDisableCalls = (props: Props) => {
|
||||
const {testID, canEnableDisableCalls, theme, onPress, enabled} = props;
|
||||
|
||||
const handleEnableDisableCalls = useCallback(preventDoubleTap(onPress), [onPress]);
|
||||
|
||||
if (!canEnableDisableCalls) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Separator theme={theme}/>
|
||||
<ChannelInfoRow
|
||||
testID={testID}
|
||||
action={handleEnableDisableCalls}
|
||||
defaultMessage={enabled ? 'Disable Calls' : 'Enable Calls'}
|
||||
icon='phone-outline'
|
||||
theme={theme}
|
||||
rightArrow={false}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnableDisableCalls;
|
||||
@@ -4,28 +4,12 @@
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
import {Text} from 'react-native';
|
||||
import {SafeAreaProvider} from 'react-native-safe-area-context';
|
||||
|
||||
import FloatingCallContainer from './floating_call_container';
|
||||
|
||||
function testSafeAreaProvider(children) {
|
||||
return (
|
||||
<SafeAreaProvider
|
||||
initialMetrics={{
|
||||
frame: {x: 0, y: 0, width: 0, height: 0},
|
||||
insets: {top: 0, left: 0, right: 0, bottom: 0},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
}
|
||||
|
||||
describe('FloatingCallContainer', () => {
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(testSafeAreaProvider(
|
||||
<FloatingCallContainer><Text>{'test'}</Text></FloatingCallContainer>,
|
||||
));
|
||||
const wrapper = shallow(<FloatingCallContainer><Text>{'test'}</Text></FloatingCallContainer>);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -1,61 +1,54 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useState, useEffect, useMemo} from 'react';
|
||||
import {View, Platform, StyleSheet} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
import React, {useState, useEffect} from 'react';
|
||||
import {View, Platform} from 'react-native';
|
||||
|
||||
import {ViewTypes} from '@constants';
|
||||
import EventEmitter from '@mm-redux/utils/event_emitter';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
const {
|
||||
IOS_TOP_PORTRAIT,
|
||||
STATUS_BAR_HEIGHT,
|
||||
ANDROID_TOP_PORTRAIT,
|
||||
} = ViewTypes;
|
||||
|
||||
let topBarHeight = ANDROID_TOP_PORTRAIT;
|
||||
if (Platform.OS === 'ios') {
|
||||
topBarHeight = (IOS_TOP_PORTRAIT - STATUS_BAR_HEIGHT);
|
||||
}
|
||||
const getStyleSheet = makeStyleSheetFromTheme(() => {
|
||||
const topBarHeight = Platform.select({android: 9, ios: IOS_TOP_PORTRAIT - STATUS_BAR_HEIGHT}) || 0;
|
||||
|
||||
const style = StyleSheet.create({
|
||||
wrapper: {
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
...Platform.select({
|
||||
android: {
|
||||
elevation: 9,
|
||||
},
|
||||
ios: {
|
||||
zIndex: 9,
|
||||
},
|
||||
}),
|
||||
},
|
||||
return {
|
||||
wrapper: {
|
||||
position: 'absolute',
|
||||
top: topBarHeight + ViewTypes.STATUS_BAR_HEIGHT + 27,
|
||||
width: '100%',
|
||||
...Platform.select({
|
||||
android: {
|
||||
elevation: 9,
|
||||
},
|
||||
ios: {
|
||||
zIndex: 9,
|
||||
},
|
||||
}),
|
||||
},
|
||||
withIndicatorBar: {
|
||||
top: topBarHeight + ViewTypes.STATUS_BAR_HEIGHT + 27 + ViewTypes.INDICATOR_BAR_HEIGHT,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
type Props = {
|
||||
children: React.ReactChildren;
|
||||
children: React.ReactNodeArray;
|
||||
}
|
||||
|
||||
const FloatingCallContainer = (props: Props) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
const style = getStyleSheet(props);
|
||||
const [indicatorBarVisible, setIndicatorBarVisible] = useState(false);
|
||||
useEffect(() => {
|
||||
EventEmitter.on(ViewTypes.INDICATOR_BAR_VISIBLE, setIndicatorBarVisible);
|
||||
return () => EventEmitter.off(ViewTypes.INDICATOR_BAR_VISIBLE, setIndicatorBarVisible);
|
||||
}, []);
|
||||
|
||||
const wrapperTop = useMemo(() => ({
|
||||
top: topBarHeight + insets.top,
|
||||
}), [insets]);
|
||||
|
||||
const withIndicatorBar = useMemo(() => ({
|
||||
top: wrapperTop.top + ViewTypes.INDICATOR_BAR_HEIGHT,
|
||||
}), [wrapperTop]);
|
||||
|
||||
return (
|
||||
<View style={[style.wrapper, wrapperTop, indicatorBarVisible ? withIndicatorBar : undefined]}>
|
||||
<View style={[style.wrapper, indicatorBarVisible ? style.withIndicatorBar : undefined]}>
|
||||
{props.children}
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ import type {Call} from '@mmproducts/calls/store/types/calls';
|
||||
|
||||
type Props = {
|
||||
actions: {
|
||||
joinCall: (channelId: string, intl: typeof IntlShape) => void;
|
||||
joinCall: (channelId: string) => any;
|
||||
};
|
||||
theme: Theme;
|
||||
call: Call;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import {IntlShape} from 'react-intl';
|
||||
import {Alert} from 'react-native';
|
||||
|
||||
export default function leaveAndJoinWithAlert(intl: typeof IntlShape, channelId: string, callChannelName: string, currentChannelName: string, confirmToJoin: boolean, joinCall: (channelId: string, intl: typeof IntlShape) => void) {
|
||||
export default function leaveAndJoinWithAlert(intl: typeof IntlShape, channelId: string, callChannelName: string, currentChannelName: string, confirmToJoin: boolean, joinCall: (channelId: string) => void) {
|
||||
if (confirmToJoin) {
|
||||
Alert.alert(
|
||||
'Are you sure you want to switch to a different call?',
|
||||
@@ -15,12 +15,12 @@ export default function leaveAndJoinWithAlert(intl: typeof IntlShape, channelId:
|
||||
},
|
||||
{
|
||||
text: 'Leave & Join',
|
||||
onPress: () => joinCall(channelId, intl),
|
||||
onPress: () => joinCall(channelId),
|
||||
style: 'cancel',
|
||||
},
|
||||
],
|
||||
);
|
||||
} else {
|
||||
joinCall(channelId, intl);
|
||||
joinCall(channelId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {StyleProp, View, ViewStyle} from 'react-native';
|
||||
import Svg, {Path} from 'react-native-svg';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
fill?: string;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
svgStyle?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
export default function RaisedHandIcon({width = 25, height = 27, style, svgStyle, ...props}: Props) {
|
||||
return (
|
||||
<View style={style}>
|
||||
<Svg
|
||||
{...props}
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox='0 0 25 27'
|
||||
style={svgStyle}
|
||||
>
|
||||
|
||||
<Path
|
||||
d='M24.6999 6.15117V21.873C24.6999 23.0918 24.2733 24.1074 23.4202 24.9199C22.5671 25.773 21.5515 26.1996 20.3733 26.1996H12.4515C11.2327 26.1996 10.1968 25.773 9.34365 24.9199L0.87334 16.2668L2.2749 14.9262C2.51865 14.723 2.80303 14.6215 3.12803 14.6215C3.37178 14.6215 3.59521 14.6824 3.79834 14.8043L8.42959 17.4855V4.5668C8.42959 4.11992 8.59209 3.73398 8.91709 3.40898C9.24209 3.08398 9.62803 2.92148 10.0749 2.92148C10.5218 2.92148 10.9077 3.08398 11.2327 3.40898C11.5577 3.73398 11.7202 4.11992 11.7202 4.5668V12.123H12.7562V1.82461C12.7562 1.37773 12.9187 1.01211 13.2437 0.727734C13.5687 0.402734 13.9546 0.240234 14.4015 0.240234C14.8483 0.240234 15.2343 0.402734 15.5593 0.727734C15.8843 1.01211 16.0468 1.37773 16.0468 1.82461V12.123H17.1437V2.92148C17.1437 2.47461 17.2858 2.08867 17.5702 1.76367C17.8952 1.43867 18.2812 1.27617 18.728 1.27617C19.1749 1.27617 19.5608 1.43867 19.8858 1.76367C20.2108 2.08867 20.3733 2.47461 20.3733 2.92148V12.123H21.4702V6.15117C21.4702 5.7043 21.6124 5.33867 21.8968 5.0543C22.2218 4.7293 22.6077 4.5668 23.0546 4.5668C23.5015 4.5668 23.8874 4.7293 24.2124 5.0543C24.5374 5.33867 24.6999 5.7043 24.6999 6.15117Z'
|
||||
/>
|
||||
</Svg>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`StartCall should match snapshot 1`] = `
|
||||
<React.Fragment>
|
||||
<Separator
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc1f",
|
||||
"buttonBg": "#1c58d9",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3f4350",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#d24b4e",
|
||||
"errorTextColor": "#d24b4e",
|
||||
"linkColor": "#386fe5",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionColor": "#1e325c",
|
||||
"mentionHighlightBg": "#ffd470",
|
||||
"mentionHighlightLink": "#1b1d22",
|
||||
"newMessageSeparator": "#cc8f00",
|
||||
"onlineIndicator": "#3db887",
|
||||
"sidebarBg": "#1e325c",
|
||||
"sidebarHeaderBg": "#192a4d",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarTeamBarBg": "#14213e",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#5d89ea",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#28427b",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Denim",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Start Call"
|
||||
icon="phone-in-talk"
|
||||
rightArrow={false}
|
||||
shouldRender={true}
|
||||
testID="test-id"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc1f",
|
||||
"buttonBg": "#1c58d9",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3f4350",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#d24b4e",
|
||||
"errorTextColor": "#d24b4e",
|
||||
"linkColor": "#386fe5",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionColor": "#1e325c",
|
||||
"mentionHighlightBg": "#ffd470",
|
||||
"mentionHighlightLink": "#1b1d22",
|
||||
"newMessageSeparator": "#cc8f00",
|
||||
"onlineIndicator": "#3db887",
|
||||
"sidebarBg": "#1e325c",
|
||||
"sidebarHeaderBg": "#192a4d",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarTeamBarBg": "#14213e",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#5d89ea",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#28427b",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Denim",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</React.Fragment>
|
||||
`;
|
||||
|
||||
exports[`StartCall should match snapshot when there is already an ongoing call in the channel 1`] = `
|
||||
<React.Fragment>
|
||||
<Separator
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc1f",
|
||||
"buttonBg": "#1c58d9",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3f4350",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#d24b4e",
|
||||
"errorTextColor": "#d24b4e",
|
||||
"linkColor": "#386fe5",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionColor": "#1e325c",
|
||||
"mentionHighlightBg": "#ffd470",
|
||||
"mentionHighlightLink": "#1b1d22",
|
||||
"newMessageSeparator": "#cc8f00",
|
||||
"onlineIndicator": "#3db887",
|
||||
"sidebarBg": "#1e325c",
|
||||
"sidebarHeaderBg": "#192a4d",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarTeamBarBg": "#14213e",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#5d89ea",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#28427b",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Denim",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<channelInfoRow
|
||||
action={[Function]}
|
||||
defaultMessage="Join Ongoing Call"
|
||||
icon="phone-in-talk"
|
||||
rightArrow={false}
|
||||
shouldRender={true}
|
||||
testID="test-id"
|
||||
theme={
|
||||
Object {
|
||||
"awayIndicator": "#ffbc1f",
|
||||
"buttonBg": "#1c58d9",
|
||||
"buttonColor": "#ffffff",
|
||||
"centerChannelBg": "#ffffff",
|
||||
"centerChannelColor": "#3f4350",
|
||||
"codeTheme": "github",
|
||||
"dndIndicator": "#d24b4e",
|
||||
"errorTextColor": "#d24b4e",
|
||||
"linkColor": "#386fe5",
|
||||
"mentionBg": "#ffffff",
|
||||
"mentionColor": "#1e325c",
|
||||
"mentionHighlightBg": "#ffd470",
|
||||
"mentionHighlightLink": "#1b1d22",
|
||||
"newMessageSeparator": "#cc8f00",
|
||||
"onlineIndicator": "#3db887",
|
||||
"sidebarBg": "#1e325c",
|
||||
"sidebarHeaderBg": "#192a4d",
|
||||
"sidebarHeaderTextColor": "#ffffff",
|
||||
"sidebarTeamBarBg": "#14213e",
|
||||
"sidebarText": "#ffffff",
|
||||
"sidebarTextActiveBorder": "#5d89ea",
|
||||
"sidebarTextActiveColor": "#ffffff",
|
||||
"sidebarTextHoverBg": "#28427b",
|
||||
"sidebarUnreadText": "#ffffff",
|
||||
"type": "Denim",
|
||||
}
|
||||
}
|
||||
togglable={false}
|
||||
/>
|
||||
</React.Fragment>
|
||||
`;
|
||||
34
app/products/calls/components/start_call/index.ts
Normal file
34
app/products/calls/components/start_call/index.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators, Dispatch} from 'redux';
|
||||
|
||||
import {getChannel, getCurrentChannelId} from '@mm-redux/selectors/entities/channels';
|
||||
import {joinCall} from '@mmproducts/calls/store/actions/calls';
|
||||
import {getCalls, getCurrentCall} from '@mmproducts/calls/store/selectors/calls';
|
||||
|
||||
import StartCall from './start_call';
|
||||
|
||||
import type {GlobalState} from '@mm-redux/types/store';
|
||||
|
||||
function mapStateToProps(state: GlobalState) {
|
||||
const currentChannelId = getCurrentChannelId(state);
|
||||
const call = getCalls(state)[currentChannelId];
|
||||
const currentCall = getCurrentCall(state);
|
||||
return {
|
||||
confirmToJoin: Boolean(currentCall && currentCall.channelId !== currentChannelId),
|
||||
alreadyInTheCall: Boolean(currentCall && call && currentCall.channelId === call.channelId),
|
||||
callChannelName: currentCall ? getChannel(state, currentCall.channelId)?.display_name : '',
|
||||
ongoingCall: Boolean(call),
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: Dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
joinCall,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(StartCall);
|
||||
75
app/products/calls/components/start_call/start_call.test.js
Normal file
75
app/products/calls/components/start_call/start_call.test.js
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Alert, TouchableHighlight} from 'react-native';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
import ChannelInfoRow from '@screens/channel_info/channel_info_row';
|
||||
import {shallowWithIntl} from '@test/intl-test-helper';
|
||||
|
||||
import StartCall from './start_call';
|
||||
|
||||
describe('StartCall', () => {
|
||||
const baseProps = {
|
||||
actions: {
|
||||
joinCall: jest.fn(),
|
||||
},
|
||||
testID: 'test-id',
|
||||
theme: Preferences.THEMES.denim,
|
||||
currentChannelId: 'channel-id',
|
||||
currentChannelName: 'Channel Name',
|
||||
canStartCall: true,
|
||||
callChannelName: 'Call channel name',
|
||||
confirmToJoin: false,
|
||||
alreadyInTheCall: false,
|
||||
ongoingCall: false,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallowWithIntl(<StartCall {...baseProps}/>).dive();
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should match snapshot when there is already an ongoing call in the channel', () => {
|
||||
const props = {...baseProps, ongoingCall: true};
|
||||
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should be null when you are already in the channel call', () => {
|
||||
const props = {...baseProps, alreadyInTheCall: true};
|
||||
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
|
||||
|
||||
expect(wrapper.getElement()).toBeNull();
|
||||
});
|
||||
|
||||
test('should be null if you can not start a call', () => {
|
||||
const props = {...baseProps, canStartCall: false};
|
||||
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
|
||||
|
||||
expect(wrapper.getElement()).toBeNull();
|
||||
});
|
||||
|
||||
test('should join on click', () => {
|
||||
const joinCall = jest.fn();
|
||||
const props = {...baseProps, actions: {joinCall}};
|
||||
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
|
||||
|
||||
wrapper.find(ChannelInfoRow).dive().find(TouchableHighlight).simulate('press');
|
||||
expect(Alert.alert).not.toHaveBeenCalled();
|
||||
expect(props.actions.joinCall).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should ask for confirmation on click', () => {
|
||||
const joinCall = jest.fn();
|
||||
const props = {...baseProps, confirmToJoin: true, actions: {joinCall}};
|
||||
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
|
||||
|
||||
wrapper.find(ChannelInfoRow).dive().find(TouchableHighlight).simulate('press');
|
||||
expect(Alert.alert).toHaveBeenCalled();
|
||||
expect(props.actions.joinCall).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
59
app/products/calls/components/start_call/start_call.tsx
Normal file
59
app/products/calls/components/start_call/start_call.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import {injectIntl, IntlShape} from 'react-intl';
|
||||
|
||||
import {Theme} from '@mm-redux/types/theme';
|
||||
import leaveAndJoinWithAlert from '@mmproducts/calls/components/leave_and_join_alert';
|
||||
import ChannelInfoRow from '@screens/channel_info/channel_info_row';
|
||||
import Separator from '@screens/channel_info/separator';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
||||
type Props = {
|
||||
actions: {
|
||||
joinCall: (channelId: string) => any;
|
||||
};
|
||||
testID?: string;
|
||||
theme: Theme;
|
||||
currentChannelId: string;
|
||||
currentChannelName: string;
|
||||
callChannelName: string;
|
||||
confirmToJoin: boolean;
|
||||
alreadyInTheCall: boolean;
|
||||
canStartCall: boolean;
|
||||
ongoingCall: boolean;
|
||||
intl: typeof IntlShape;
|
||||
}
|
||||
|
||||
const StartCall = (props: Props) => {
|
||||
const {testID, canStartCall, theme, actions, currentChannelId, callChannelName, currentChannelName, confirmToJoin, alreadyInTheCall, ongoingCall, intl} = props;
|
||||
|
||||
const handleStartCall = useCallback(preventDoubleTap(() => {
|
||||
leaveAndJoinWithAlert(intl, currentChannelId, callChannelName, currentChannelName, confirmToJoin, actions.joinCall);
|
||||
}), [actions.joinCall, currentChannelId]);
|
||||
|
||||
if (!canStartCall) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (alreadyInTheCall) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Separator theme={theme}/>
|
||||
<ChannelInfoRow
|
||||
testID={testID}
|
||||
action={handleStartCall}
|
||||
defaultMessage={ongoingCall ? 'Join Ongoing Call' : 'Start Call'}
|
||||
icon='phone-in-talk'
|
||||
theme={theme}
|
||||
rightArrow={false}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default injectIntl(StartCall);
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {StyleProp, View, ViewStyle} from 'react-native';
|
||||
import Svg, {Path} from 'react-native-svg';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
fill?: string;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
svgStyle?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
export default function UnraisedHandIcon({width = 24, height = 24, style, svgStyle, ...props}: Props) {
|
||||
return (
|
||||
<View style={style}>
|
||||
<Svg
|
||||
{...props}
|
||||
width={width}
|
||||
height={height}
|
||||
viewBox='0 0 24 24'
|
||||
style={svgStyle}
|
||||
>
|
||||
|
||||
<Path
|
||||
d='M20.84 22.73L19.17 21.06C17.7 22.85 15.5 24 13 24C9.74 24 6.81 22 5.6 19L2.57 11.37C2.26 10.58 3 9.79 3.81 10.05L4.6 10.31C5.16 10.5 5.62 10.92 5.84 11.47L7.25 15H8V9.89L1.11 3L2.39 1.73L22.11 21.46L20.84 22.73M14 1.25C14 .56 13.44 0 12.75 0S11.5 .56 11.5 1.25V8.3L14 10.8V1.25M21 16V5.75C21 5.06 20.44 4.5 19.75 4.5S18.5 5.06 18.5 5.75V12H17.5V2.75C17.5 2.06 16.94 1.5 16.25 1.5S15 2.06 15 2.75V11.8L20.83 17.63C20.94 17.11 21 16.56 21 16M10.5 3.25C10.5 2.56 9.94 2 9.25 2S8 2.56 8 3.25V4.8L10.5 7.3V3.25Z'
|
||||
/>
|
||||
</Svg>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export let client: any = null;
|
||||
const websocketConnectTimeout = 3000;
|
||||
|
||||
export async function newClient(channelID: string, closeCb: () => void, setScreenShareURL: (url: string) => void) {
|
||||
let peer: Peer | null = null;
|
||||
let peer: any = null;
|
||||
let stream: MediaStream;
|
||||
let voiceTrackAdded = false;
|
||||
let voiceTrack: MediaStreamTrack | null = null;
|
||||
@@ -50,15 +50,13 @@ export async function newClient(channelID: string, closeCb: () => void, setScree
|
||||
streams.forEach((s) => {
|
||||
s.getTracks().forEach((track: MediaStreamTrack) => {
|
||||
track.stop();
|
||||
track.release();
|
||||
});
|
||||
});
|
||||
|
||||
peer?.destroy(undefined, undefined, () => {
|
||||
// Wait until the peer connection is closed, which avoids the following racy error that can cause problems with accessing the audio system in the future:
|
||||
// AVAudioSession_iOS.mm:1243 Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session.
|
||||
InCallManager.stop();
|
||||
});
|
||||
if (peer) {
|
||||
peer.destroy();
|
||||
}
|
||||
InCallManager.stop();
|
||||
|
||||
if (closeCb) {
|
||||
closeCb();
|
||||
@@ -69,7 +67,7 @@ export async function newClient(channelID: string, closeCb: () => void, setScree
|
||||
if (!peer) {
|
||||
return;
|
||||
}
|
||||
if (voiceTrackAdded && voiceTrack) {
|
||||
if (voiceTrackAdded) {
|
||||
peer.replaceTrack(voiceTrack, null, stream);
|
||||
}
|
||||
if (voiceTrack) {
|
||||
@@ -96,18 +94,6 @@ export async function newClient(channelID: string, closeCb: () => void, setScree
|
||||
}
|
||||
};
|
||||
|
||||
const raiseHand = () => {
|
||||
if (ws) {
|
||||
ws.send('raise_hand');
|
||||
}
|
||||
};
|
||||
|
||||
const unraiseHand = () => {
|
||||
if (ws) {
|
||||
ws.send('unraise_hand');
|
||||
}
|
||||
};
|
||||
|
||||
ws.on('error', (err) => {
|
||||
console.log('WS (CALLS) ERROR', err); // eslint-disable-line no-console
|
||||
ws.close();
|
||||
@@ -162,7 +148,7 @@ export async function newClient(channelID: string, closeCb: () => void, setScree
|
||||
ws.on('message', ({data}) => {
|
||||
const msg = JSON.parse(data);
|
||||
if (msg.type === 'answer' || msg.type === 'offer') {
|
||||
peer?.signal(data);
|
||||
peer.signal(data);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -193,8 +179,6 @@ export async function newClient(channelID: string, closeCb: () => void, setScree
|
||||
mute,
|
||||
unmute,
|
||||
waitForReady,
|
||||
raiseHand,
|
||||
unraiseHand,
|
||||
};
|
||||
|
||||
return client;
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {useEffect, useState} from 'react';
|
||||
import {Alert} from 'react-native';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
|
||||
import {Client4} from '@client/rest';
|
||||
import {General} from '@mm-redux/constants';
|
||||
import {getCurrentChannel} from '@mm-redux/selectors/entities/channels';
|
||||
import {getCurrentUserRoles} from '@mm-redux/selectors/entities/users';
|
||||
import {isAdmin as checkIsAdmin, isChannelAdmin as checkIsChannelAdmin} from '@mm-redux/utils/user_utils';
|
||||
import {loadConfig} from '@mmproducts/calls/store/actions/calls';
|
||||
import {
|
||||
getConfig,
|
||||
isCallsExplicitlyDisabled,
|
||||
isCallsExplicitlyEnabled,
|
||||
isCallsPluginEnabled,
|
||||
} from '@mmproducts/calls/store/selectors/calls';
|
||||
|
||||
// Check if calls is enabled. If it is, then run fn; if it isn't, show an alert and set
|
||||
// msgPostfix to ' (Not Available)'.
|
||||
export const useTryCallsFunction = (fn: (channelId: string) => void) => {
|
||||
const [msgPostfix, setMsgPostfix] = useState('');
|
||||
|
||||
const tryFn = async (channelId: string) => {
|
||||
if (await Client4.getEnabled()) {
|
||||
setMsgPostfix('');
|
||||
fn(channelId);
|
||||
return;
|
||||
}
|
||||
|
||||
Alert.alert(
|
||||
'Calls is not enabled',
|
||||
'Please contact your system administrator to enable the feature.',
|
||||
[
|
||||
{
|
||||
text: 'OK',
|
||||
style: 'cancel',
|
||||
},
|
||||
],
|
||||
);
|
||||
setMsgPostfix(' (Not Available)');
|
||||
};
|
||||
|
||||
return [tryFn, msgPostfix];
|
||||
};
|
||||
|
||||
export const useCallsChannelSettings = () => {
|
||||
const dispatch = useDispatch();
|
||||
const config = useSelector(getConfig);
|
||||
const currentChannel = useSelector(getCurrentChannel);
|
||||
const pluginEnabled = useSelector(isCallsPluginEnabled);
|
||||
const explicitlyDisabled = useSelector(isCallsExplicitlyDisabled);
|
||||
const explicitlyEnabled = useSelector(isCallsExplicitlyEnabled);
|
||||
const roles = useSelector(getCurrentUserRoles);
|
||||
|
||||
useEffect(() => {
|
||||
if (pluginEnabled) {
|
||||
dispatch(loadConfig());
|
||||
}
|
||||
}, []);
|
||||
|
||||
const isDirectMessage = currentChannel.type === General.DM_CHANNEL;
|
||||
const isGroupMessage = currentChannel.type === General.GM_CHANNEL;
|
||||
const isAdmin = checkIsAdmin(roles);
|
||||
const isChannelAdmin = isAdmin || checkIsChannelAdmin(roles);
|
||||
|
||||
const enabled = pluginEnabled && (explicitlyEnabled || (!explicitlyDisabled && config.DefaultEnabled));
|
||||
let canEnableDisable;
|
||||
if (!pluginEnabled) {
|
||||
canEnableDisable = false;
|
||||
} else if (config.AllowEnableCalls) {
|
||||
canEnableDisable = isDirectMessage || isGroupMessage || isChannelAdmin;
|
||||
} else {
|
||||
canEnableDisable = isAdmin;
|
||||
}
|
||||
|
||||
return [enabled, canEnableDisable];
|
||||
};
|
||||
@@ -28,9 +28,6 @@ exports[`CallScreen Landscape should match snapshot 1`] = `
|
||||
"flexDirection": "row",
|
||||
"height": 64,
|
||||
"padding": 0,
|
||||
"paddingLeft": 14,
|
||||
"paddingRight": 14,
|
||||
"paddingTop": 10,
|
||||
"position": "absolute",
|
||||
"top": -1000,
|
||||
"width": "100%",
|
||||
@@ -104,8 +101,6 @@ exports[`CallScreen Landscape should match snapshot 1`] = `
|
||||
>
|
||||
<CallAvatar
|
||||
muted={false}
|
||||
raisedHand={false}
|
||||
sharingScreen={false}
|
||||
size="l"
|
||||
userId="user-1-id"
|
||||
volume={0}
|
||||
@@ -135,8 +130,6 @@ exports[`CallScreen Landscape should match snapshot 1`] = `
|
||||
>
|
||||
<CallAvatar
|
||||
muted={true}
|
||||
raisedHand={false}
|
||||
sharingScreen={false}
|
||||
size="l"
|
||||
userId="user-2-id"
|
||||
volume={0}
|
||||
@@ -149,7 +142,6 @@ exports[`CallScreen Landscape should match snapshot 1`] = `
|
||||
}
|
||||
>
|
||||
User 2
|
||||
(you)
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
@@ -223,28 +215,21 @@ exports[`CallScreen Landscape should match snapshot 1`] = `
|
||||
"flexDirection": "column",
|
||||
}
|
||||
}
|
||||
testID="toggle-speakerphone"
|
||||
>
|
||||
<CompassIcon
|
||||
name="volume-high"
|
||||
name="message-text-outline"
|
||||
size={24}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"color": "#ffffff",
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
@@ -254,11 +239,10 @@ exports[`CallScreen Landscape should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
Speaker
|
||||
Chat thread
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
@@ -267,38 +251,21 @@ exports[`CallScreen Landscape should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<RaisedHandIcon
|
||||
fill="#ffffff"
|
||||
height={24}
|
||||
<CompassIcon
|
||||
name="settings-outline"
|
||||
size={24}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 34,
|
||||
},
|
||||
]
|
||||
}
|
||||
svgStyle={
|
||||
Object {
|
||||
"position": "relative",
|
||||
"right": 13,
|
||||
"top": -12,
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
}
|
||||
}
|
||||
width={24}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
@@ -307,7 +274,7 @@ exports[`CallScreen Landscape should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
Raise hand
|
||||
Settings
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
@@ -417,9 +384,6 @@ exports[`CallScreen Landscape should match snapshot with screenshare 1`] = `
|
||||
"flexDirection": "row",
|
||||
"height": 64,
|
||||
"padding": 0,
|
||||
"paddingLeft": 14,
|
||||
"paddingRight": 14,
|
||||
"paddingTop": 10,
|
||||
"position": "absolute",
|
||||
"top": -1000,
|
||||
"width": "100%",
|
||||
@@ -489,7 +453,7 @@ exports[`CallScreen Landscape should match snapshot with screenshare 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
You are viewing User 2's screen
|
||||
You are seeing User 2 screen
|
||||
</Text>
|
||||
</Pressable>
|
||||
<View
|
||||
@@ -561,28 +525,21 @@ exports[`CallScreen Landscape should match snapshot with screenshare 1`] = `
|
||||
"flexDirection": "column",
|
||||
}
|
||||
}
|
||||
testID="toggle-speakerphone"
|
||||
>
|
||||
<CompassIcon
|
||||
name="volume-high"
|
||||
name="message-text-outline"
|
||||
size={24}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"color": "#ffffff",
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
@@ -592,11 +549,10 @@ exports[`CallScreen Landscape should match snapshot with screenshare 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
Speaker
|
||||
Chat thread
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
@@ -605,38 +561,21 @@ exports[`CallScreen Landscape should match snapshot with screenshare 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<RaisedHandIcon
|
||||
fill="#ffffff"
|
||||
height={24}
|
||||
<CompassIcon
|
||||
name="settings-outline"
|
||||
size={24}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 34,
|
||||
},
|
||||
]
|
||||
}
|
||||
svgStyle={
|
||||
Object {
|
||||
"position": "relative",
|
||||
"right": 13,
|
||||
"top": -12,
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
}
|
||||
}
|
||||
width={24}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
@@ -645,7 +584,7 @@ exports[`CallScreen Landscape should match snapshot with screenshare 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
Raise hand
|
||||
Settings
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
@@ -752,9 +691,7 @@ exports[`CallScreen Portrait should match snapshot 1`] = `
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"paddingLeft": 14,
|
||||
"paddingRight": 14,
|
||||
"paddingTop": 10,
|
||||
"padding": 14,
|
||||
"width": "100%",
|
||||
"zIndex": 4,
|
||||
}
|
||||
@@ -826,8 +763,6 @@ exports[`CallScreen Portrait should match snapshot 1`] = `
|
||||
>
|
||||
<CallAvatar
|
||||
muted={false}
|
||||
raisedHand={false}
|
||||
sharingScreen={false}
|
||||
size="l"
|
||||
userId="user-1-id"
|
||||
volume={0}
|
||||
@@ -857,8 +792,6 @@ exports[`CallScreen Portrait should match snapshot 1`] = `
|
||||
>
|
||||
<CallAvatar
|
||||
muted={true}
|
||||
raisedHand={false}
|
||||
sharingScreen={false}
|
||||
size="l"
|
||||
userId="user-2-id"
|
||||
volume={0}
|
||||
@@ -871,7 +804,6 @@ exports[`CallScreen Portrait should match snapshot 1`] = `
|
||||
}
|
||||
>
|
||||
User 2
|
||||
(you)
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
@@ -978,28 +910,21 @@ exports[`CallScreen Portrait should match snapshot 1`] = `
|
||||
"flexDirection": "column",
|
||||
}
|
||||
}
|
||||
testID="toggle-speakerphone"
|
||||
>
|
||||
<CompassIcon
|
||||
name="volume-high"
|
||||
name="message-text-outline"
|
||||
size={24}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"color": "#ffffff",
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
@@ -1009,11 +934,10 @@ exports[`CallScreen Portrait should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
Speaker
|
||||
Chat thread
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
@@ -1022,38 +946,21 @@ exports[`CallScreen Portrait should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<RaisedHandIcon
|
||||
fill="#ffffff"
|
||||
height={24}
|
||||
<CompassIcon
|
||||
name="settings-outline"
|
||||
size={24}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 34,
|
||||
},
|
||||
]
|
||||
}
|
||||
svgStyle={
|
||||
Object {
|
||||
"position": "relative",
|
||||
"right": 13,
|
||||
"top": -12,
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
}
|
||||
}
|
||||
width={24}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
@@ -1062,7 +969,7 @@ exports[`CallScreen Portrait should match snapshot 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
Raise hand
|
||||
Settings
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
@@ -1132,9 +1039,7 @@ exports[`CallScreen Portrait should match snapshot with screenshare 1`] = `
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"paddingLeft": 14,
|
||||
"paddingRight": 14,
|
||||
"paddingTop": 10,
|
||||
"padding": 14,
|
||||
"width": "100%",
|
||||
"zIndex": 4,
|
||||
}
|
||||
@@ -1206,8 +1111,6 @@ exports[`CallScreen Portrait should match snapshot with screenshare 1`] = `
|
||||
>
|
||||
<CallAvatar
|
||||
muted={false}
|
||||
raisedHand={false}
|
||||
sharingScreen={false}
|
||||
size="m"
|
||||
userId="user-1-id"
|
||||
volume={0}
|
||||
@@ -1237,8 +1140,6 @@ exports[`CallScreen Portrait should match snapshot with screenshare 1`] = `
|
||||
>
|
||||
<CallAvatar
|
||||
muted={true}
|
||||
raisedHand={false}
|
||||
sharingScreen={true}
|
||||
size="m"
|
||||
userId="user-2-id"
|
||||
volume={0}
|
||||
@@ -1251,7 +1152,6 @@ exports[`CallScreen Portrait should match snapshot with screenshare 1`] = `
|
||||
}
|
||||
>
|
||||
User 2
|
||||
(you)
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
@@ -1287,7 +1187,7 @@ exports[`CallScreen Portrait should match snapshot with screenshare 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
You are viewing User 2's screen
|
||||
You are seeing User 2 screen
|
||||
</Text>
|
||||
</Pressable>
|
||||
<View
|
||||
@@ -1392,28 +1292,21 @@ exports[`CallScreen Portrait should match snapshot with screenshare 1`] = `
|
||||
"flexDirection": "column",
|
||||
}
|
||||
}
|
||||
testID="toggle-speakerphone"
|
||||
>
|
||||
<CompassIcon
|
||||
name="volume-high"
|
||||
name="message-text-outline"
|
||||
size={24}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"color": "#ffffff",
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
@@ -1423,11 +1316,10 @@ exports[`CallScreen Portrait should match snapshot with screenshare 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
Speaker
|
||||
Chat thread
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
@@ -1436,38 +1328,21 @@ exports[`CallScreen Portrait should match snapshot with screenshare 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
<RaisedHandIcon
|
||||
fill="#ffffff"
|
||||
height={24}
|
||||
<CompassIcon
|
||||
name="settings-outline"
|
||||
size={24}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 34,
|
||||
},
|
||||
]
|
||||
}
|
||||
svgStyle={
|
||||
Object {
|
||||
"position": "relative",
|
||||
"right": 13,
|
||||
"top": -12,
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
}
|
||||
}
|
||||
width={24}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
@@ -1476,7 +1351,7 @@ exports[`CallScreen Portrait should match snapshot with screenshare 1`] = `
|
||||
}
|
||||
}
|
||||
>
|
||||
Raise hand
|
||||
Settings
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
@@ -1549,9 +1424,6 @@ exports[`CallScreen should show controls in landscape view on click the screen s
|
||||
"flexDirection": "row",
|
||||
"height": 64,
|
||||
"padding": 0,
|
||||
"paddingLeft": 14,
|
||||
"paddingRight": 14,
|
||||
"paddingTop": 10,
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
"width": "100%",
|
||||
@@ -1621,7 +1493,7 @@ exports[`CallScreen should show controls in landscape view on click the screen s
|
||||
}
|
||||
}
|
||||
>
|
||||
You are viewing User 2's screen
|
||||
You are seeing User 2 screen
|
||||
</Text>
|
||||
</Pressable>
|
||||
<View
|
||||
@@ -1693,28 +1565,21 @@ exports[`CallScreen should show controls in landscape view on click the screen s
|
||||
"flexDirection": "column",
|
||||
}
|
||||
}
|
||||
testID="toggle-speakerphone"
|
||||
>
|
||||
<CompassIcon
|
||||
name="volume-high"
|
||||
name="message-text-outline"
|
||||
size={24}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"color": "#ffffff",
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
@@ -1724,11 +1589,10 @@ exports[`CallScreen should show controls in landscape view on click the screen s
|
||||
}
|
||||
}
|
||||
>
|
||||
Speaker
|
||||
Chat thread
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
@@ -1737,38 +1601,21 @@ exports[`CallScreen should show controls in landscape view on click the screen s
|
||||
}
|
||||
}
|
||||
>
|
||||
<RaisedHandIcon
|
||||
fill="#ffffff"
|
||||
height={24}
|
||||
<CompassIcon
|
||||
name="settings-outline"
|
||||
size={24}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 34,
|
||||
},
|
||||
]
|
||||
}
|
||||
svgStyle={
|
||||
Object {
|
||||
"position": "relative",
|
||||
"right": 13,
|
||||
"top": -12,
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
}
|
||||
}
|
||||
width={24}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
@@ -1777,7 +1624,7 @@ exports[`CallScreen should show controls in landscape view on click the screen s
|
||||
}
|
||||
}
|
||||
>
|
||||
Raise hand
|
||||
Settings
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
@@ -1887,9 +1734,6 @@ exports[`CallScreen should show controls in landscape view on click the users li
|
||||
"flexDirection": "row",
|
||||
"height": 64,
|
||||
"padding": 0,
|
||||
"paddingLeft": 14,
|
||||
"paddingRight": 14,
|
||||
"paddingTop": 10,
|
||||
"position": "absolute",
|
||||
"top": 0,
|
||||
"width": "100%",
|
||||
@@ -1963,8 +1807,6 @@ exports[`CallScreen should show controls in landscape view on click the users li
|
||||
>
|
||||
<CallAvatar
|
||||
muted={false}
|
||||
raisedHand={false}
|
||||
sharingScreen={false}
|
||||
size="l"
|
||||
userId="user-1-id"
|
||||
volume={0}
|
||||
@@ -1994,8 +1836,6 @@ exports[`CallScreen should show controls in landscape view on click the users li
|
||||
>
|
||||
<CallAvatar
|
||||
muted={true}
|
||||
raisedHand={false}
|
||||
sharingScreen={false}
|
||||
size="l"
|
||||
userId="user-2-id"
|
||||
volume={0}
|
||||
@@ -2008,7 +1848,6 @@ exports[`CallScreen should show controls in landscape view on click the users li
|
||||
}
|
||||
>
|
||||
User 2
|
||||
(you)
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
@@ -2082,28 +1921,21 @@ exports[`CallScreen should show controls in landscape view on click the users li
|
||||
"flexDirection": "column",
|
||||
}
|
||||
}
|
||||
testID="toggle-speakerphone"
|
||||
>
|
||||
<CompassIcon
|
||||
name="volume-high"
|
||||
name="message-text-outline"
|
||||
size={24}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"color": "#ffffff",
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
@@ -2113,11 +1945,10 @@ exports[`CallScreen should show controls in landscape view on click the users li
|
||||
}
|
||||
}
|
||||
>
|
||||
Speaker
|
||||
Chat thread
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
@@ -2126,38 +1957,21 @@ exports[`CallScreen should show controls in landscape view on click the users li
|
||||
}
|
||||
}
|
||||
>
|
||||
<RaisedHandIcon
|
||||
fill="#ffffff"
|
||||
height={24}
|
||||
<CompassIcon
|
||||
name="settings-outline"
|
||||
size={24}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
},
|
||||
Object {
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 34,
|
||||
},
|
||||
]
|
||||
}
|
||||
svgStyle={
|
||||
Object {
|
||||
"position": "relative",
|
||||
"right": 13,
|
||||
"top": -12,
|
||||
"backgroundColor": "rgba(255,255,255,0.12)",
|
||||
"borderRadius": 34,
|
||||
"color": "#ffffff",
|
||||
"height": 68,
|
||||
"margin": 10,
|
||||
"overflow": "hidden",
|
||||
"padding": 22,
|
||||
"width": 68,
|
||||
}
|
||||
}
|
||||
width={24}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
@@ -2166,7 +1980,7 @@ exports[`CallScreen should show controls in landscape view on click the users li
|
||||
}
|
||||
}
|
||||
>
|
||||
Raise hand
|
||||
Settings
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
|
||||
@@ -45,26 +45,6 @@ describe('CallScreen', () => {
|
||||
screenOn: '',
|
||||
threadId: false,
|
||||
},
|
||||
participants: [{
|
||||
id: 'user-1-id',
|
||||
muted: false,
|
||||
isTalking: false,
|
||||
profile: {
|
||||
id: 'user-1-id',
|
||||
username: 'user-1-username',
|
||||
nickname: 'User 1',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'user-2-id',
|
||||
muted: true,
|
||||
isTalking: true,
|
||||
profile: {
|
||||
id: 'user-2-id',
|
||||
username: 'user-2-username',
|
||||
nickname: 'User 2',
|
||||
},
|
||||
}],
|
||||
currentParticipant: {
|
||||
id: 'user-2-id',
|
||||
muted: true,
|
||||
@@ -97,11 +77,7 @@ describe('CallScreen', () => {
|
||||
});
|
||||
|
||||
test('should show controls in landscape view on click the screen share', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
call: {...baseProps.call, screenOn: 'user-2-id'},
|
||||
screenShareURL: 'screen-share-url',
|
||||
};
|
||||
const props = {...baseProps, call: {...baseProps.call, screenOn: 'user-2-id'}, screenShareURL: 'screen-share-url'};
|
||||
const wrapper = shallow(<CallScreen {...props}/>);
|
||||
wrapper.find({testID: 'screen-share-container'}).simulate('press');
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
@@ -132,11 +108,7 @@ describe('CallScreen', () => {
|
||||
});
|
||||
|
||||
test('should match snapshot with screenshare', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
call: {...baseProps.call, screenOn: 'user-2-id'},
|
||||
screenShareURL: 'screen-share-url',
|
||||
};
|
||||
const props = {...baseProps, call: {...baseProps.call, screenOn: 'user-2-id'}, screenShareURL: 'screen-share-url'};
|
||||
const wrapper = shallow(<CallScreen {...props}/>);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
@@ -194,38 +166,6 @@ describe('CallScreen', () => {
|
||||
expect(props.actions.muteMyself).not.toHaveBeenCalled();
|
||||
expect(props.actions.unmuteMyself).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should turn speakerphone on if it is off', () => {
|
||||
const setSpeakerphoneOn = jest.fn();
|
||||
const props = {
|
||||
...baseProps,
|
||||
actions: {
|
||||
...baseProps.actions,
|
||||
setSpeakerphoneOn,
|
||||
},
|
||||
speakerphoneOn: false,
|
||||
};
|
||||
const wrapper = shallow(<CallScreen {...props}/>);
|
||||
|
||||
wrapper.find({testID: 'toggle-speakerphone'}).simulate('press');
|
||||
expect(props.actions.setSpeakerphoneOn).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
test('should turn speakerphone off if it is on', () => {
|
||||
const setSpeakerphoneOn = jest.fn();
|
||||
const props = {
|
||||
...baseProps,
|
||||
actions: {
|
||||
...baseProps.actions,
|
||||
setSpeakerphoneOn,
|
||||
},
|
||||
speakerphoneOn: true,
|
||||
};
|
||||
const wrapper = shallow(<CallScreen {...props}/>);
|
||||
|
||||
wrapper.find({testID: 'toggle-speakerphone'}).simulate('press');
|
||||
expect(props.actions.setSpeakerphoneOn).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user