Skip to content

Commit

Permalink
topic edit modal: Add new edit-topic UI.
Browse files Browse the repository at this point in the history
Fixes: zulip#5365
  • Loading branch information
Leslie Ngo authored and Leslie Ngo committed Nov 4, 2022
1 parent 0433c2a commit 7e680d1
Show file tree
Hide file tree
Showing 13 changed files with 302 additions and 12 deletions.
9 changes: 6 additions & 3 deletions src/ZulipMobile.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import AppEventHandlers from './boot/AppEventHandlers';
import { initializeSentry } from './sentry';
import ZulipSafeAreaProvider from './boot/ZulipSafeAreaProvider';
import { OfflineNoticeProvider } from './boot/OfflineNoticeProvider';
import TopicEditModalProvider from './boot/TopicEditModalProvider';

initializeSentry();

Expand Down Expand Up @@ -79,9 +80,11 @@ export default function ZulipMobile(): Node {
<TranslationProvider>
<ThemeProvider>
<OfflineNoticeProvider>
<ActionSheetProvider>
<ZulipNavigationContainer />
</ActionSheetProvider>
<TopicEditModalProvider>
<ActionSheetProvider>
<ZulipNavigationContainer />
</ActionSheetProvider>
</TopicEditModalProvider>
</OfflineNoticeProvider>
</ThemeProvider>
</TranslationProvider>
Expand Down
21 changes: 20 additions & 1 deletion src/action-sheets/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ type TopicArgs = {
zulipFeatureLevel: number,
dispatch: Dispatch,
_: GetText,
startEditTopic: (streamId: number, topic: string) => void,
...
};

Expand Down Expand Up @@ -171,6 +172,14 @@ const deleteMessage = {
},
};

const editTopic = {
title: 'Edit topic',
errorMessage: 'Failed to edit topic',
action: ({ streamId, topic, startEditTopic }) => {
startEditTopic(streamId, topic);
},
};

const markTopicAsRead = {
title: 'Mark topic as read',
errorMessage: 'Failed to mark topic as read',
Expand Down Expand Up @@ -532,9 +541,18 @@ export const constructTopicActionButtons = (args: {|

const buttons = [];
const unreadCount = getUnreadCountForTopic(unread, streamId, topic);
const isAdmin = roleIsAtLeast(ownUserRole, Role.Admin);
if (unreadCount > 0) {
buttons.push(markTopicAsRead);
}
// At present, the permissions for editing the topic of a message are highly complex.
// Until we move to a better set of policy options, we'll only display the edit topic
// button to admins.
// Issue: https://github.com/zulip/zulip/issues/21739
// Relevant comment: https://github.com/zulip/zulip-mobile/issues/5365#issuecomment-1197093294
if (isAdmin) {
buttons.push(editTopic);
}
if (isTopicMuted(streamId, topic, mute)) {
buttons.push(unmuteTopic);
} else {
Expand All @@ -545,7 +563,7 @@ export const constructTopicActionButtons = (args: {|
} else {
buttons.push(unresolveTopic);
}
if (roleIsAtLeast(ownUserRole, Role.Admin)) {
if (isAdmin) {
buttons.push(deleteTopic);
}
const sub = subscriptions.get(streamId);
Expand Down Expand Up @@ -705,6 +723,7 @@ export const showTopicActionSheet = (args: {|
showActionSheetWithOptions: ShowActionSheetWithOptions,
callbacks: {|
dispatch: Dispatch,
startEditTopic: (streamId: number, topic: string) => void,
_: GetText,
|},
backgroundData: $ReadOnly<{
Expand Down
56 changes: 56 additions & 0 deletions src/boot/TopicEditModalProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* @flow strict-local */
import React, { createContext, useState, useCallback, useContext } from 'react';
import type { Context, Node } from 'react';

import TopicEditModal from '../topics/TopicEditModal';

type Props = $ReadOnly<{|
children: Node,
|}>;

type TopicEditModalProviderState = {
streamId: number,
oldTopic: string,
} | null;

type StartEditTopicContext = (streamId: number, oldTopic: string) => void;

const TopicEditModalContext: Context<StartEditTopicContext> = createContext(() => {
throw new Error(
'Tried to open the edit-topic UI from a component without TopicEditModalProvider above it in the tree.',
);
});

export const useStartEditTopic = (): StartEditTopicContext => useContext(TopicEditModalContext);

export default function TopicEditModalProvider(props: Props): Node {
const { children } = props;

const [topicModalProviderState, setTopicModalProviderState] = useState<TopicEditModalProviderState>(null);

const startEditTopic = useCallback(
(streamIdArg, oldTopicArg) => {
if (topicModalProviderState) {
setTopicModalProviderState({
streamId: streamIdArg,
oldTopic: oldTopicArg,
});
}
},
[topicModalProviderState],
);

const closeEditTopicModal = () => {
setTopicModalProviderState(null);
};

return (
<TopicEditModalContext.Provider value={startEditTopic}>
<TopicEditModal
topicModalProviderState={topicModalProviderState}
closeEditTopicModal={closeEditTopicModal}
/>
{children}
</TopicEditModalContext.Provider>
);
}
3 changes: 3 additions & 0 deletions src/chat/ChatScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { showErrorAlert } from '../utils/info';
import { TranslationContext } from '../boot/TranslationProvider';
import * as api from '../api';
import { useConditionalEffect } from '../reactUtils';
import { useStartEditTopic } from '../boot/TopicEditModalProvider';

type Props = $ReadOnly<{|
navigation: AppNavigationProp<'chat'>,
Expand Down Expand Up @@ -132,6 +133,7 @@ export default function ChatScreen(props: Props): Node {
(value: EditMessage | null) => navigation.setParams({ editMessage: value }),
[navigation],
);
const startEditTopic = useStartEditTopic();

const isNarrowValid = useSelector(state => getIsNarrowValid(state, narrow));
const draft = useSelector(state => getDraftForNarrow(state, narrow));
Expand Down Expand Up @@ -219,6 +221,7 @@ export default function ChatScreen(props: Props): Node {
}
showMessagePlaceholders={showMessagePlaceholders}
startEditMessage={setEditMessage}
startEditTopic={startEditTopic}
/>
);
}
Expand Down
6 changes: 3 additions & 3 deletions src/common/ZulipTextButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,13 @@ type Props = $ReadOnly<{|

/**
* True just if the button is in the disabled state.
* https://material.io/design/interaction/states.html#disabled
*
* https://material.io/design/interaction/states.html#disabled
*/
disabled?: boolean,

/**
* Whether `onPress` is used even when
* `disabled` is true.
* Whether `onPress` is used even when `disabled` is true.
*/
isPressHandledWhenDisabled?: boolean,

Expand Down
3 changes: 3 additions & 0 deletions src/search/SearchMessagesCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { createStyleSheet } from '../styles';
import LoadingIndicator from '../common/LoadingIndicator';
import SearchEmptyState from '../common/SearchEmptyState';
import MessageList from '../webview/MessageList';
import { useStartEditTopic } from '../boot/TopicEditModalProvider';

const styles = createStyleSheet({
results: {
Expand All @@ -24,6 +25,7 @@ type Props = $ReadOnly<{|

export default function SearchMessagesCard(props: Props): Node {
const { narrow, isFetching, messages } = props;
const startEditTopic = useStartEditTopic();

if (isFetching) {
// Display loading indicator only if there are no messages to
Expand Down Expand Up @@ -55,6 +57,7 @@ export default function SearchMessagesCard(props: Props): Node {
// TODO: handle editing a message from the search results,
// or make this prop optional
startEditMessage={() => undefined}
startEditTopic={startEditTopic}
/>
</View>
);
Expand Down
4 changes: 3 additions & 1 deletion src/streams/TopicItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import { getMute } from '../mute/muteModel';
import { getUnread } from '../unread/unreadModel';
import { getOwnUserRole } from '../permissionSelectors';
import { useStartEditTopic } from '../boot/TopicEditModalProvider';

const componentStyles = createStyleSheet({
selectedRow: {
Expand Down Expand Up @@ -70,6 +71,7 @@ export default function TopicItem(props: Props): Node {
useActionSheet().showActionSheetWithOptions;
const _ = useContext(TranslationContext);
const dispatch = useDispatch();
const startEditTopic = useStartEditTopic();
const backgroundData = useSelector(state => ({
auth: getAuth(state),
mute: getMute(state),
Expand All @@ -88,7 +90,7 @@ export default function TopicItem(props: Props): Node {
onLongPress={() => {
showTopicActionSheet({
showActionSheetWithOptions,
callbacks: { dispatch, _ },
callbacks: { dispatch, startEditTopic, _ },
backgroundData,
streamId,
topic: name,
Expand Down
4 changes: 3 additions & 1 deletion src/title/TitleStream.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { showStreamActionSheet, showTopicActionSheet } from '../action-sheets';
import type { ShowActionSheetWithOptions } from '../action-sheets';
import { getUnread } from '../unread/unreadModel';
import { getOwnUserRole } from '../permissionSelectors';
import { useStartEditTopic } from '../boot/TopicEditModalProvider';

type Props = $ReadOnly<{|
narrow: Narrow,
Expand All @@ -51,6 +52,7 @@ export default function TitleStream(props: Props): Node {
const { narrow, color } = props;
const dispatch = useDispatch();
const stream = useSelector(state => getStreamInNarrow(state, narrow));
const startEditTopic = useStartEditTopic();
const backgroundData = useSelector(state => ({
auth: getAuth(state),
mute: getMute(state),
Expand All @@ -75,7 +77,7 @@ export default function TitleStream(props: Props): Node {
? () => {
showTopicActionSheet({
showActionSheetWithOptions,
callbacks: { dispatch, _ },
callbacks: { dispatch, startEditTopic, _ },
backgroundData,
streamId: stream.stream_id,
topic: topicOfNarrow(narrow),
Expand Down
Loading

0 comments on commit 7e680d1

Please sign in to comment.