[MM-66419] Playbooks on mobile UX fixes (#9250)
* [MM-66419] Playbooks on mobile UX fixes * Address UX feedback
This commit is contained in:
parent
72b97f60f5
commit
db154996f4
14 changed files with 88 additions and 41 deletions
|
|
@ -219,6 +219,7 @@ function AutoCompleteSelector({
|
||||||
</Text>
|
</Text>
|
||||||
<CompassIcon
|
<CompassIcon
|
||||||
name='chevron-down'
|
name='chevron-down'
|
||||||
|
size={20}
|
||||||
color={changeOpacity(theme.centerChannelColor, 0.5)}
|
color={changeOpacity(theme.centerChannelColor, 0.5)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ const FloatingInputContainer = ({
|
||||||
testID,
|
testID,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const styles = getStyleSheet(theme);
|
const styles = getStyleSheet(theme);
|
||||||
const positions = useMemo(() => getLabelPositions(styles.textInput, styles.label, styles.smallLabel), [styles]);
|
const positions = useMemo(() => getLabelPositions(styles.textInput, styles.bigLabel, styles.smallLabel), [styles]);
|
||||||
const errorIcon = 'alert-outline';
|
const errorIcon = 'alert-outline';
|
||||||
const shouldShowError = !focused && error;
|
const shouldShowError = !focused && error;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import React, {useState, useRef, useImperativeHandle, forwardRef, useMemo, useCa
|
||||||
import {type LayoutChangeEvent, type NativeSyntheticEvent, type StyleProp, type TargetedEvent, TextInput, type TextInputFocusEventData, type TextInputProps, type TextStyle} from 'react-native';
|
import {type LayoutChangeEvent, type NativeSyntheticEvent, type StyleProp, type TargetedEvent, TextInput, type TextInputFocusEventData, type TextInputProps, type TextStyle} from 'react-native';
|
||||||
|
|
||||||
import {changeOpacity, getKeyboardAppearanceFromTheme, makeStyleSheetFromTheme} from '@utils/theme';
|
import {changeOpacity, getKeyboardAppearanceFromTheme, makeStyleSheetFromTheme} from '@utils/theme';
|
||||||
|
import {typography} from '@utils/typography';
|
||||||
|
|
||||||
import FloatingInputContainer from './floating_input_container';
|
import FloatingInputContainer from './floating_input_container';
|
||||||
import {onExecution} from './utils';
|
import {onExecution} from './utils';
|
||||||
|
|
@ -22,11 +23,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||||
paddingTop: 0,
|
paddingTop: 0,
|
||||||
paddingBottom: 0,
|
paddingBottom: 0,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
fontFamily: 'OpenSans',
|
|
||||||
fontSize: 16,
|
|
||||||
color: theme.centerChannelColor,
|
color: theme.centerChannelColor,
|
||||||
borderColor: changeOpacity(theme.centerChannelColor, 0.16),
|
borderColor: changeOpacity(theme.centerChannelColor, 0.16),
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
|
...typography('Body', 200),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,13 @@ describe('getLabelPositions', () => {
|
||||||
paddingTop: 10,
|
paddingTop: 10,
|
||||||
paddingBottom: 10,
|
paddingBottom: 10,
|
||||||
height: 50,
|
height: 50,
|
||||||
fontSize: 14,
|
|
||||||
padding: 20,
|
padding: 20,
|
||||||
};
|
};
|
||||||
const labelStyle = {fontSize: 15};
|
const labelStyle = {fontSize: 15};
|
||||||
const smallLabelStyle = {fontSize: 11};
|
const smallLabelStyle = {fontSize: 11};
|
||||||
|
|
||||||
const result = getLabelPositions(style, labelStyle, smallLabelStyle);
|
const result = getLabelPositions(style, labelStyle, smallLabelStyle);
|
||||||
expect(result).toEqual([24.4, -6.5]);
|
expect(result).toEqual([25, -6.5]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return correct positions when label and smallLabels styles are missing', () => {
|
test('should return correct positions when label and smallLabels styles are missing', () => {
|
||||||
|
|
@ -24,14 +23,13 @@ describe('getLabelPositions', () => {
|
||||||
paddingTop: 15,
|
paddingTop: 15,
|
||||||
paddingBottom: 15,
|
paddingBottom: 15,
|
||||||
height: 50,
|
height: 50,
|
||||||
fontSize: 14,
|
|
||||||
padding: 25,
|
padding: 25,
|
||||||
};
|
};
|
||||||
const labelStyle = {};
|
const labelStyle = {};
|
||||||
const smallLabelStyle = {};
|
const smallLabelStyle = {};
|
||||||
|
|
||||||
const result = getLabelPositions(style, labelStyle, smallLabelStyle);
|
const result = getLabelPositions(style, labelStyle, smallLabelStyle);
|
||||||
expect(result).toEqual([23.8, -6.5]);
|
expect(result).toEqual([23.2, -6.5]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return correct positions when all values are empty are provided', () => {
|
test('should return correct positions when all values are empty are provided', () => {
|
||||||
|
|
@ -49,7 +47,6 @@ describe('getLabelPositions', () => {
|
||||||
paddingTop: 0,
|
paddingTop: 0,
|
||||||
paddingBottom: 0,
|
paddingBottom: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
fontSize: 0,
|
|
||||||
padding: 0,
|
padding: 0,
|
||||||
};
|
};
|
||||||
const labelStyle = {fontSize: 0};
|
const labelStyle = {fontSize: 0};
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export const getLabelPositions = (style: TextStyle, labelStyle: TextStyle, small
|
||||||
const bottom: number = style.paddingBottom as number || 0;
|
const bottom: number = style.paddingBottom as number || 0;
|
||||||
|
|
||||||
const height: number = (style.height as number || (top + bottom) || style.padding as number) || 0;
|
const height: number = (style.height as number || (top + bottom) || style.padding as number) || 0;
|
||||||
const textInputFontSize = style.fontSize || 13;
|
const textInputFontSize = labelStyle.fontSize || 13;
|
||||||
const labelFontSize = labelStyle.fontSize || 16;
|
const labelFontSize = labelStyle.fontSize || 16;
|
||||||
const smallLabelFontSize = smallLabelStyle.fontSize || 10;
|
const smallLabelFontSize = smallLabelStyle.fontSize || 10;
|
||||||
const fontSizeDiff = textInputFontSize - labelFontSize;
|
const fontSizeDiff = textInputFontSize - labelFontSize;
|
||||||
|
|
|
||||||
|
|
@ -120,9 +120,10 @@ export async function goToSelectDate(
|
||||||
export async function goToSelectPlaybook(
|
export async function goToSelectPlaybook(
|
||||||
intl: IntlShape,
|
intl: IntlShape,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
|
channelId?: string,
|
||||||
) {
|
) {
|
||||||
const title = intl.formatMessage({id: 'playbooks.select_playbook.title', defaultMessage: 'Start a run'});
|
const title = intl.formatMessage({id: 'playbooks.select_playbook.title', defaultMessage: 'Start a run'});
|
||||||
goToScreen(Screens.PLAYBOOKS_SELECT_PLAYBOOK, title, {}, {
|
goToScreen(Screens.PLAYBOOKS_SELECT_PLAYBOOK, title, {channelId}, {
|
||||||
topBar: {
|
topBar: {
|
||||||
subtitle: {
|
subtitle: {
|
||||||
text: intl.formatMessage({id: 'playbooks.select_playbook.subtitle', defaultMessage: 'Select a playbook'}),
|
text: intl.formatMessage({id: 'playbooks.select_playbook.subtitle', defaultMessage: 'Select a playbook'}),
|
||||||
|
|
@ -132,10 +133,10 @@ export async function goToSelectPlaybook(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function goToStartARun(intl: IntlShape, theme: Theme, playbook: Playbook, onRunCreated: (run: PlaybookRun) => void) {
|
export async function goToStartARun(intl: IntlShape, theme: Theme, playbook: Playbook, onRunCreated: (run: PlaybookRun) => void, channelId?: string) {
|
||||||
const title = intl.formatMessage({id: 'playbooks.start_a_run.title', defaultMessage: 'Start a run'});
|
const title = intl.formatMessage({id: 'playbooks.start_a_run.title', defaultMessage: 'Start a run'});
|
||||||
const subtitle = playbook.title;
|
const subtitle = playbook.title;
|
||||||
goToScreen(Screens.PLAYBOOKS_START_A_RUN, title, {playbook, onRunCreated}, {
|
goToScreen(Screens.PLAYBOOKS_START_A_RUN, title, {playbook, onRunCreated, channelId}, {
|
||||||
topBar: {
|
topBar: {
|
||||||
subtitle: {
|
subtitle: {
|
||||||
text: subtitle,
|
text: subtitle,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import {defineMessages, useIntl} from 'react-intl';
|
||||||
import {View} from 'react-native';
|
import {View} from 'react-native';
|
||||||
|
|
||||||
import Loading from '@components/loading';
|
import Loading from '@components/loading';
|
||||||
import MenuDivider from '@components/menu_divider';
|
|
||||||
import SectionNotice from '@components/section_notice';
|
import SectionNotice from '@components/section_notice';
|
||||||
import {useServerUrl} from '@context/server';
|
import {useServerUrl} from '@context/server';
|
||||||
import {useTheme} from '@context/theme';
|
import {useTheme} from '@context/theme';
|
||||||
|
|
@ -83,6 +82,14 @@ const tabs: Array<TabDefinition<TabsNames>> = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const itemSeparatorStyle = {
|
||||||
|
height: 12,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ItemSeparator = () => {
|
||||||
|
return <View style={itemSeparatorStyle}/>;
|
||||||
|
};
|
||||||
|
|
||||||
const ParticipantPlaybooks = ({
|
const ParticipantPlaybooks = ({
|
||||||
currentUserId,
|
currentUserId,
|
||||||
componentId,
|
componentId,
|
||||||
|
|
@ -202,7 +209,7 @@ const ParticipantPlaybooks = ({
|
||||||
data={data}
|
data={data}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
contentContainerStyle={styles.container}
|
contentContainerStyle={styles.container}
|
||||||
ItemSeparatorComponent={MenuDivider}
|
ItemSeparatorComponent={ItemSeparator}
|
||||||
estimatedItemSize={CARD_HEIGHT}
|
estimatedItemSize={CARD_HEIGHT}
|
||||||
onEndReached={loadMore}
|
onEndReached={loadMore}
|
||||||
onEndReachedThreshold={0.1}
|
onEndReachedThreshold={0.1}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ describe('StatusUpdateIndicator', () => {
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const text = getByText(/Status update due/);
|
const text = getByText(/Update due/);
|
||||||
expect(text).toHaveStyle({color: changeOpacity(Preferences.THEMES.denim.centerChannelColor, 0.72)});
|
expect(text).toHaveStyle({color: changeOpacity(Preferences.THEMES.denim.centerChannelColor, 0.72)});
|
||||||
|
|
||||||
const icon = getByTestId('compass-icon');
|
const icon = getByTestId('compass-icon');
|
||||||
|
|
@ -55,7 +55,7 @@ describe('StatusUpdateIndicator', () => {
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const text = getByText(/Status update overdue/);
|
const text = getByText(/Update overdue/);
|
||||||
expect(text).toHaveStyle({color: Preferences.THEMES.denim.dndIndicator});
|
expect(text).toHaveStyle({color: Preferences.THEMES.denim.dndIndicator});
|
||||||
|
|
||||||
const icon = getByTestId('compass-icon');
|
const icon = getByTestId('compass-icon');
|
||||||
|
|
|
||||||
|
|
@ -55,20 +55,23 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => ({
|
||||||
overdueText: {
|
overdueText: {
|
||||||
color: theme.dndIndicator,
|
color: theme.dndIndicator,
|
||||||
},
|
},
|
||||||
|
bold: {
|
||||||
|
...typography('Body', 300, 'SemiBold'),
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
updateOverdue: {
|
updateOverdue: {
|
||||||
id: 'playbooks.playbook_run.status_update_overdue',
|
id: 'playbooks.playbook_run.status_update_overdue',
|
||||||
defaultMessage: 'Status update overdue {time}',
|
defaultMessage: 'Update overdue\n{time}',
|
||||||
},
|
},
|
||||||
updateDue: {
|
updateDue: {
|
||||||
id: 'playbooks.playbook_run.status_update_due',
|
id: 'playbooks.playbook_run.status_update_due',
|
||||||
defaultMessage: 'Status update due {time}',
|
defaultMessage: 'Update due\n{time}',
|
||||||
},
|
},
|
||||||
finished: {
|
finished: {
|
||||||
id: 'playbooks.playbook_run.status_update_finished',
|
id: 'playbooks.playbook_run.status_update_finished',
|
||||||
defaultMessage: 'Run finished {time}',
|
defaultMessage: 'Run finished\n{time}',
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
id: 'playbooks.playbook_run.status_update',
|
id: 'playbooks.playbook_run.status_update',
|
||||||
|
|
@ -86,13 +89,20 @@ const StatusUpdateIndicator = ({
|
||||||
const styles = getStyleSheet(theme);
|
const styles = getStyleSheet(theme);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const values = {time: <FriendlyDate value={timestamp}/>};
|
const values = useMemo(() => ({time: (
|
||||||
|
<FriendlyDate
|
||||||
|
style={styles.bold}
|
||||||
|
value={timestamp}
|
||||||
|
/>
|
||||||
|
)}), [timestamp, styles]);
|
||||||
|
|
||||||
const readOnly = !isParticipant || isFinished;
|
const readOnly = !isParticipant || isFinished;
|
||||||
|
const isOverdue = timestamp < Date.now();
|
||||||
|
|
||||||
let message = messages.updateDue;
|
let message = messages.updateDue;
|
||||||
if (isFinished) {
|
if (isFinished) {
|
||||||
message = messages.finished;
|
message = messages.finished;
|
||||||
} else if (timestamp < Date.now()) {
|
} else if (isOverdue) {
|
||||||
message = messages.updateOverdue;
|
message = messages.updateOverdue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,6 +147,7 @@ const StatusUpdateIndicator = ({
|
||||||
theme={theme}
|
theme={theme}
|
||||||
size='lg'
|
size='lg'
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
|
isDestructive={isOverdue && !isFinished}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -140,8 +140,8 @@ const PlaybookRuns = ({
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const startANewRun = useCallback(() => {
|
const startANewRun = useCallback(() => {
|
||||||
goToSelectPlaybook(intl, theme);
|
goToSelectPlaybook(intl, theme, channelId);
|
||||||
}, [intl, theme]);
|
}, [intl, theme, channelId]);
|
||||||
|
|
||||||
let content = (<EmptyState tab={activeTab}/>);
|
let content = (<EmptyState tab={activeTab}/>);
|
||||||
if (!isEmpty) {
|
if (!isEmpty) {
|
||||||
|
|
@ -162,6 +162,7 @@ const PlaybookRuns = ({
|
||||||
text={intl.formatMessage({id: 'playbooks.runs.start_a_new_run', defaultMessage: 'Start a new run'})}
|
text={intl.formatMessage({id: 'playbooks.runs.start_a_new_run', defaultMessage: 'Start a new run'})}
|
||||||
size='lg'
|
size='lg'
|
||||||
theme={theme}
|
theme={theme}
|
||||||
|
iconName='play-outline'
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {type ComponentProps, useCallback, useEffect, useMemo, useState} from 'react';
|
import {type ComponentProps, useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||||
import {FormattedMessage, useIntl} from 'react-intl';
|
import {FormattedMessage, useIntl} from 'react-intl';
|
||||||
import {Alert, Keyboard, Text, View} from 'react-native';
|
import {Alert, Keyboard, Text} from 'react-native';
|
||||||
|
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
|
||||||
|
|
||||||
import {getPosts} from '@actions/local/post';
|
import {getPosts} from '@actions/local/post';
|
||||||
import FloatingAutocompleteSelector from '@components/floating_input/floating_autocomplete_selector';
|
import FloatingAutocompleteSelector from '@components/floating_input/floating_autocomplete_selector';
|
||||||
|
|
@ -13,7 +14,9 @@ import OptionItem from '@components/option_item';
|
||||||
import {useServerUrl} from '@context/server';
|
import {useServerUrl} from '@context/server';
|
||||||
import {useTheme} from '@context/theme';
|
import {useTheme} from '@context/theme';
|
||||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||||
|
import {useAvoidKeyboard} from '@hooks/device';
|
||||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||||
|
import SecurityManager from '@managers/security_manager';
|
||||||
import {fetchPlaybookRun, fetchPlaybookRunMetadata, postStatusUpdate} from '@playbooks/actions/remote/runs';
|
import {fetchPlaybookRun, fetchPlaybookRunMetadata, postStatusUpdate} from '@playbooks/actions/remote/runs';
|
||||||
import {buildNavigationButton, popTopScreen, setButtons} from '@screens/navigation';
|
import {buildNavigationButton, popTopScreen, setButtons} from '@screens/navigation';
|
||||||
import {toSeconds} from '@utils/datetime';
|
import {toSeconds} from '@utils/datetime';
|
||||||
|
|
@ -35,7 +38,6 @@ type Props = {
|
||||||
|
|
||||||
const getStyles = makeStyleSheetFromTheme((theme: Theme) => ({
|
const getStyles = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
|
||||||
padding: 20,
|
padding: 20,
|
||||||
gap: 12,
|
gap: 12,
|
||||||
},
|
},
|
||||||
|
|
@ -46,6 +48,9 @@ const getStyles = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||||
introMessageBold: {
|
introMessageBold: {
|
||||||
...typography('Body', 200, 'SemiBold'),
|
...typography('Body', 200, 'SemiBold'),
|
||||||
},
|
},
|
||||||
|
flex: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const SAVE_BUTTON_ID = 'save-post-update';
|
const SAVE_BUTTON_ID = 'save-post-update';
|
||||||
|
|
@ -84,6 +89,9 @@ const PostUpdate = ({
|
||||||
const [alsoMarkRunAsFinished, setAlsoMarkRunAsFinished] = useState(false);
|
const [alsoMarkRunAsFinished, setAlsoMarkRunAsFinished] = useState(false);
|
||||||
const [canSave, setCanSave] = useState(false);
|
const [canSave, setCanSave] = useState(false);
|
||||||
|
|
||||||
|
const keyboardAwareRef = useRef<KeyboardAwareScrollView>(null);
|
||||||
|
useAvoidKeyboard(keyboardAwareRef);
|
||||||
|
|
||||||
const [followersCount, setFollowersCount] = useState<number>(0);
|
const [followersCount, setFollowersCount] = useState<number>(0);
|
||||||
const [broadcastChannelCount, setBroadcastChannelCount] = useState<number>(0);
|
const [broadcastChannelCount, setBroadcastChannelCount] = useState<number>(0);
|
||||||
|
|
||||||
|
|
@ -272,7 +280,12 @@ const PostUpdate = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<KeyboardAwareScrollView
|
||||||
|
contentContainerStyle={styles.container}
|
||||||
|
ref={keyboardAwareRef}
|
||||||
|
nativeID={SecurityManager.getShieldScreenId(componentId, false, true)}
|
||||||
|
style={styles.flex}
|
||||||
|
>
|
||||||
<Text style={styles.introMessage}>{introMessage}</Text>
|
<Text style={styles.introMessage}>{introMessage}</Text>
|
||||||
<FloatingTextInput
|
<FloatingTextInput
|
||||||
label={intl.formatMessage({id: 'playbooks.post_update.label', defaultMessage: 'Update message'})}
|
label={intl.formatMessage({id: 'playbooks.post_update.label', defaultMessage: 'Update message'})}
|
||||||
|
|
@ -281,6 +294,7 @@ const PostUpdate = ({
|
||||||
onChangeText={onChangeText}
|
onChangeText={onChangeText}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
multiline={true}
|
multiline={true}
|
||||||
|
multilineInputHeight={300}
|
||||||
/>
|
/>
|
||||||
<FloatingAutocompleteSelector
|
<FloatingAutocompleteSelector
|
||||||
options={dialogOptions}
|
options={dialogOptions}
|
||||||
|
|
@ -296,7 +310,7 @@ const PostUpdate = ({
|
||||||
selected={alsoMarkRunAsFinished}
|
selected={alsoMarkRunAsFinished}
|
||||||
type='toggle'
|
type='toggle'
|
||||||
/>
|
/>
|
||||||
</View>
|
</KeyboardAwareScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ export type Props = {
|
||||||
currentUserId: string;
|
currentUserId: string;
|
||||||
componentId: AvailableScreens;
|
componentId: AvailableScreens;
|
||||||
playbooksUsedInChannel: Set<string>;
|
playbooksUsedInChannel: Set<string>;
|
||||||
|
channelId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||||
|
|
@ -81,6 +82,10 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||||
flex: 0,
|
flex: 0,
|
||||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
backgroundColor: changeOpacity(theme.centerChannelColor, 0.1),
|
||||||
},
|
},
|
||||||
|
sectionHeaderContainer: {
|
||||||
|
backgroundColor: theme.centerChannelBg,
|
||||||
|
paddingVertical: 8,
|
||||||
|
},
|
||||||
sectionHeader: {
|
sectionHeader: {
|
||||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||||
...typography('Body', 75, 'SemiBold'),
|
...typography('Body', 75, 'SemiBold'),
|
||||||
|
|
@ -96,6 +101,7 @@ function SelectPlaybook({
|
||||||
currentUserId,
|
currentUserId,
|
||||||
componentId,
|
componentId,
|
||||||
playbooksUsedInChannel,
|
playbooksUsedInChannel,
|
||||||
|
channelId,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const serverUrl = useServerUrl();
|
const serverUrl = useServerUrl();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
@ -185,7 +191,12 @@ function SelectPlaybook({
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const renderNoResults = useCallback((): JSX.Element | null => {
|
const renderNoResults = useCallback((): JSX.Element | null => {
|
||||||
if (searching || (loading && page.current === -1)) {
|
if (loading && page.current === -1) {
|
||||||
|
// Already handled by the loading component
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searching) {
|
||||||
return (
|
return (
|
||||||
<Loading
|
<Loading
|
||||||
color={theme.buttonBg}
|
color={theme.buttonBg}
|
||||||
|
|
@ -214,8 +225,8 @@ function SelectPlaybook({
|
||||||
}, [intl, serverUrl]);
|
}, [intl, serverUrl]);
|
||||||
|
|
||||||
const onPress = useCallback((playbook: Playbook) => {
|
const onPress = useCallback((playbook: Playbook) => {
|
||||||
goToStartARun(intl, theme, playbook, onRunCreated);
|
goToStartARun(intl, theme, playbook, onRunCreated, channelId);
|
||||||
}, [intl, onRunCreated, theme]);
|
}, [intl, onRunCreated, theme, channelId]);
|
||||||
|
|
||||||
const renderItem = useCallback(({item}: ListRenderItemInfo<Playbook>) => {
|
const renderItem = useCallback(({item}: ListRenderItemInfo<Playbook>) => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -242,9 +253,11 @@ function SelectPlaybook({
|
||||||
|
|
||||||
const renderSectionHeader = useCallback(({section}: {section: SectionListData<Playbook, DefaultSectionT>}) => {
|
const renderSectionHeader = useCallback(({section}: {section: SectionListData<Playbook, DefaultSectionT>}) => {
|
||||||
return (
|
return (
|
||||||
<Text style={style.sectionHeader}>{section.title}</Text>
|
<View style={style.sectionHeaderContainer}>
|
||||||
|
<Text style={style.sectionHeader}>{section.title}</Text>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}, [style.sectionHeader]);
|
}, [style]);
|
||||||
|
|
||||||
const sections: Array<SectionListData<Playbook, DefaultSectionT>> = useMemo(() => {
|
const sections: Array<SectionListData<Playbook, DefaultSectionT>> = useMemo(() => {
|
||||||
type PlaybookSections = {
|
type PlaybookSections = {
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ export type Props = {
|
||||||
currentUserId: string;
|
currentUserId: string;
|
||||||
currentTeamId: string;
|
currentTeamId: string;
|
||||||
onRunCreated: (run: PlaybookRun) => void;
|
onRunCreated: (run: PlaybookRun) => void;
|
||||||
|
channelId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||||
|
|
@ -86,6 +87,7 @@ function StartARun({
|
||||||
currentUserId,
|
currentUserId,
|
||||||
currentTeamId,
|
currentTeamId,
|
||||||
onRunCreated,
|
onRunCreated,
|
||||||
|
channelId,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
@ -105,7 +107,7 @@ function StartARun({
|
||||||
return '';
|
return '';
|
||||||
});
|
});
|
||||||
const [channelOption, setChannelOption] = useState<ChannelOption>('existing');
|
const [channelOption, setChannelOption] = useState<ChannelOption>('existing');
|
||||||
const [channelId, setChannelId] = useState<string | undefined>(undefined);
|
const [selectedChannelId, setSelectedChannelId] = useState<string | undefined>(channelId);
|
||||||
const [createPublicChannel, setCreatePublicChannel] = useState(false);
|
const [createPublicChannel, setCreatePublicChannel] = useState(false);
|
||||||
|
|
||||||
const canSave = Boolean(runName.trim());
|
const canSave = Boolean(runName.trim());
|
||||||
|
|
@ -115,7 +117,7 @@ function StartARun({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await createPlaybookRun(serverUrl, playbook.id, currentUserId, currentTeamId, runName.trim(), runDescription.trim(), channelId, channelOption === 'new' ? createPublicChannel : undefined);
|
const res = await createPlaybookRun(serverUrl, playbook.id, currentUserId, currentTeamId, runName.trim(), runDescription.trim(), selectedChannelId, channelOption === 'new' ? createPublicChannel : undefined);
|
||||||
if (res.error || !res.data) {
|
if (res.error || !res.data) {
|
||||||
logDebug('error on createPlaybookRun', getFullErrorMessage(res.error));
|
logDebug('error on createPlaybookRun', getFullErrorMessage(res.error));
|
||||||
showPlaybookErrorSnackbar();
|
showPlaybookErrorSnackbar();
|
||||||
|
|
@ -123,7 +125,7 @@ function StartARun({
|
||||||
}
|
}
|
||||||
await popTopScreen(componentId);
|
await popTopScreen(componentId);
|
||||||
onRunCreated(res.data);
|
onRunCreated(res.data);
|
||||||
}, [runName, serverUrl, playbook.id, currentUserId, currentTeamId, runDescription, channelId, channelOption, createPublicChannel, componentId, onRunCreated]);
|
}, [runName, serverUrl, playbook.id, currentUserId, currentTeamId, runDescription, selectedChannelId, channelOption, createPublicChannel, componentId, onRunCreated]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function asyncWrapper() {
|
async function asyncWrapper() {
|
||||||
|
|
@ -174,7 +176,7 @@ function StartARun({
|
||||||
logDebug('on channel selected returned undefined, this should never happen');
|
logDebug('on channel selected returned undefined, this should never happen');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setChannelId(value.value);
|
setSelectedChannelId(value.value);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -232,7 +234,7 @@ function StartARun({
|
||||||
defaultMessage: 'Channel',
|
defaultMessage: 'Channel',
|
||||||
})}
|
})}
|
||||||
dataSource='channels'
|
dataSource='channels'
|
||||||
selected={channelId}
|
selected={selectedChannelId}
|
||||||
onSelected={onChannelSelected}
|
onSelected={onChannelSelected}
|
||||||
testID='start_run.existing_channel_selector'
|
testID='start_run.existing_channel_selector'
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1040,9 +1040,9 @@
|
||||||
"playbooks.playbook_run.participants_title": "Run Participants",
|
"playbooks.playbook_run.participants_title": "Run Participants",
|
||||||
"playbooks.playbook_run.run_details": "Run details",
|
"playbooks.playbook_run.run_details": "Run details",
|
||||||
"playbooks.playbook_run.status_update": "Post update",
|
"playbooks.playbook_run.status_update": "Post update",
|
||||||
"playbooks.playbook_run.status_update_due": "Status update due {time}",
|
"playbooks.playbook_run.status_update_due": "Update due\n{time}",
|
||||||
"playbooks.playbook_run.status_update_finished": "Run finished {time}",
|
"playbooks.playbook_run.status_update_finished": "Run finished\n{time}",
|
||||||
"playbooks.playbook_run.status_update_overdue": "Status update overdue {time}",
|
"playbooks.playbook_run.status_update_overdue": "Update overdue\n{time}",
|
||||||
"playbooks.playbook_run.tasks": "Tasks",
|
"playbooks.playbook_run.tasks": "Tasks",
|
||||||
"playbooks.playbook_run.title": "Playbook run",
|
"playbooks.playbook_run.title": "Playbook run",
|
||||||
"playbooks.playbooks_runs.title": "Playbook runs",
|
"playbooks.playbooks_runs.title": "Playbook runs",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue