Folders: Fix sharing (#3142)
This commit is contained in:
parent
d04177aea8
commit
4b96cc5bfe
@ -1681,7 +1681,7 @@ export async function editChatlistInvite({
|
||||
slug,
|
||||
title,
|
||||
peers: peers.map((peer) => buildInputPeer(peer.id, peer.accessHash)),
|
||||
}));
|
||||
}), undefined, true);
|
||||
if (!result) return undefined;
|
||||
|
||||
return buildApiChatlistExportedInvite(result);
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
cursor: var(--custom-cursor, pointer);
|
||||
margin-bottom: 1rem;
|
||||
padding-right: 3rem;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.moreMenu {
|
||||
|
||||
@ -18,12 +18,14 @@ type OwnProps = {
|
||||
title?: string;
|
||||
inviteLink: string;
|
||||
onRevoke?: VoidFunction;
|
||||
isDisabled?: boolean;
|
||||
};
|
||||
|
||||
const InviteLink: FC<OwnProps> = ({
|
||||
title,
|
||||
inviteLink,
|
||||
onRevoke,
|
||||
isDisabled,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
const { showNotification, openChatWithDraft } = getActions();
|
||||
@ -85,10 +87,20 @@ const InviteLink: FC<OwnProps> = ({
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<div className={styles.buttons}>
|
||||
<Button onClick={handleCopyPrimaryClicked} className={styles.button}>
|
||||
<Button
|
||||
onClick={handleCopyPrimaryClicked}
|
||||
className={styles.button}
|
||||
size="smaller"
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{lang('FolderLinkScreen.LinkActionCopy')}
|
||||
</Button>
|
||||
<Button onClick={handleShare} className={styles.button}>
|
||||
<Button
|
||||
onClick={handleShare}
|
||||
className={styles.button}
|
||||
size="smaller"
|
||||
disabled={isDisabled}
|
||||
>
|
||||
{lang('FolderLinkScreen.LinkActionShare')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -6,6 +6,7 @@ import { requestMutation } from '../../lib/fasterdom/fasterdom';
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
|
||||
import { isUserId } from '../../global/helpers';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||
import useInfiniteScroll from '../../hooks/useInfiniteScroll';
|
||||
import useLang from '../../hooks/useLang';
|
||||
@ -32,6 +33,7 @@ type OwnProps = {
|
||||
isLoading?: boolean;
|
||||
noScrollRestore?: boolean;
|
||||
isSearchable?: boolean;
|
||||
isRoundCheckbox?: boolean;
|
||||
lockedIds?: string[];
|
||||
onSelectedIdsChange?: (ids: string[]) => void;
|
||||
onFilterChange?: (value: string) => void;
|
||||
@ -55,6 +57,7 @@ const Picker: FC<OwnProps> = ({
|
||||
isLoading,
|
||||
noScrollRestore,
|
||||
isSearchable,
|
||||
isRoundCheckbox,
|
||||
lockedIds,
|
||||
onSelectedIdsChange,
|
||||
onFilterChange,
|
||||
@ -161,24 +164,37 @@ const Picker: FC<OwnProps> = ({
|
||||
onLoadMore={getMore}
|
||||
noScrollRestore={noScrollRestore}
|
||||
>
|
||||
{viewportIds.map((id) => (
|
||||
<ListItem
|
||||
key={id}
|
||||
className="chat-item-clickable picker-list-item"
|
||||
disabled={lockedIdsSet.has(id)}
|
||||
allowDisabledClick={Boolean(onDisabledClick)}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => handleItemClick(id)}
|
||||
ripple
|
||||
>
|
||||
<Checkbox label="" disabled={lockedIdsSet.has(id)} checked={selectedIds.includes(id)} />
|
||||
{isUserId(id) ? (
|
||||
<PrivateChatInfo userId={id} />
|
||||
) : (
|
||||
<GroupChatInfo chatId={id} />
|
||||
)}
|
||||
</ListItem>
|
||||
))}
|
||||
{viewportIds.map((id) => {
|
||||
const renderCheckbox = () => {
|
||||
return (
|
||||
<Checkbox
|
||||
label=""
|
||||
disabled={lockedIdsSet.has(id)}
|
||||
checked={selectedIds.includes(id)}
|
||||
round={isRoundCheckbox}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<ListItem
|
||||
key={id}
|
||||
className={buildClassName('chat-item-clickable picker-list-item', isRoundCheckbox && 'chat-item')}
|
||||
disabled={lockedIdsSet.has(id)}
|
||||
allowDisabledClick={Boolean(onDisabledClick)}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => handleItemClick(id)}
|
||||
ripple
|
||||
>
|
||||
{!isRoundCheckbox ? renderCheckbox() : undefined}
|
||||
{isUserId(id) ? (
|
||||
<PrivateChatInfo userId={id} />
|
||||
) : (
|
||||
<GroupChatInfo chatId={id} />
|
||||
)}
|
||||
{isRoundCheckbox ? renderCheckbox() : undefined}
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</InfiniteScroll>
|
||||
) : !isLoading && viewportIds && !viewportIds.length ? (
|
||||
<p className="no-results">{notFoundText || 'Sorry, nothing found.'}</p>
|
||||
|
||||
@ -6,8 +6,10 @@ import type { ApiChatFolder } from '../../../../api/types';
|
||||
import { SettingsScreens } from '../../../../types';
|
||||
import type { FolderEditDispatch, FoldersState } from '../../../../hooks/reducers/useFoldersReducer';
|
||||
|
||||
import { selectChatFilters } from '../../../../hooks/reducers/useFoldersReducer';
|
||||
|
||||
import SettingsFoldersMain from './SettingsFoldersMain';
|
||||
import SettingsFoldersEdit from './SettingsFoldersEdit';
|
||||
import SettingsFoldersEdit, { ERROR_NO_CHATS, ERROR_NO_TITLE } from './SettingsFoldersEdit';
|
||||
import SettingsFoldersChatFilters from './SettingsFoldersChatFilters';
|
||||
import SettingsShareChatlist from './SettingsShareChatlist';
|
||||
|
||||
@ -34,7 +36,11 @@ const SettingsFolders: FC<OwnProps> = ({
|
||||
onScreenSelect,
|
||||
onReset,
|
||||
}) => {
|
||||
const { openShareChatFolderModal } = getActions();
|
||||
const {
|
||||
openShareChatFolderModal,
|
||||
editChatFolder,
|
||||
addChatFolder,
|
||||
} = getActions();
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
if (
|
||||
@ -66,10 +72,50 @@ const SettingsFolders: FC<OwnProps> = ({
|
||||
currentScreen, onReset, onScreenSelect,
|
||||
]);
|
||||
|
||||
const isCreating = state.mode === 'create';
|
||||
|
||||
const saveState = useCallback((newState: FoldersState) => {
|
||||
const { title } = newState.folder;
|
||||
|
||||
if (!title) {
|
||||
dispatch({ type: 'setError', payload: ERROR_NO_TITLE });
|
||||
return false;
|
||||
}
|
||||
|
||||
const {
|
||||
selectedChatIds: includedChatIds,
|
||||
selectedChatTypes: includedChatTypes,
|
||||
} = selectChatFilters(newState, 'included');
|
||||
|
||||
if (!includedChatIds.length && !Object.keys(includedChatTypes).length) {
|
||||
dispatch({ type: 'setError', payload: ERROR_NO_CHATS });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isCreating) {
|
||||
editChatFolder({ id: newState.folderId!, folderUpdate: newState.folder });
|
||||
} else {
|
||||
addChatFolder({ folder: newState.folder as ApiChatFolder });
|
||||
}
|
||||
|
||||
dispatch({ type: 'setError', payload: undefined });
|
||||
dispatch({ type: 'setIsTouched', payload: false });
|
||||
|
||||
return true;
|
||||
}, [dispatch, isCreating]);
|
||||
|
||||
const handleSaveFolder = useCallback((cb?: NoneToVoidFunction) => {
|
||||
if (!saveState(state)) {
|
||||
return;
|
||||
}
|
||||
cb?.();
|
||||
}, [saveState, state]);
|
||||
|
||||
const handleSaveFilter = useCallback(() => {
|
||||
dispatch({ type: 'saveFilters' });
|
||||
const newState = dispatch({ type: 'saveFilters' });
|
||||
handleReset();
|
||||
}, [dispatch, handleReset]);
|
||||
saveState(newState);
|
||||
}, [dispatch, handleReset, saveState]);
|
||||
|
||||
const handleCreateFolder = useCallback(() => {
|
||||
dispatch({ type: 'reset' });
|
||||
@ -97,8 +143,9 @@ const SettingsFolders: FC<OwnProps> = ({
|
||||
|
||||
const handleShareFolder = useCallback(() => {
|
||||
openShareChatFolderModal({ folderId: state.folderId!, noRequestNextScreen: true });
|
||||
dispatch({ type: 'setIsChatlist', payload: true });
|
||||
onScreenSelect(SettingsScreens.FoldersShare);
|
||||
}, [onScreenSelect, state.folderId]);
|
||||
}, [dispatch, onScreenSelect, state.folderId]);
|
||||
|
||||
const handleOpenInvite = useCallback((url: string) => {
|
||||
openShareChatFolderModal({ folderId: state.folderId!, url, noRequestNextScreen: true });
|
||||
@ -139,6 +186,7 @@ const SettingsFolders: FC<OwnProps> = ({
|
||||
].includes(shownScreen)}
|
||||
isOnlyInvites={currentScreen === SettingsScreens.FoldersEditFolderInvites}
|
||||
onBack={onReset}
|
||||
onSaveFolder={handleSaveFolder}
|
||||
/>
|
||||
);
|
||||
case SettingsScreens.FoldersIncludedChats:
|
||||
|
||||
@ -4,7 +4,7 @@ import React, {
|
||||
} from '../../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../../global';
|
||||
|
||||
import type { ApiChatFolder, ApiChatlistExportedInvite } from '../../../../api/types';
|
||||
import type { ApiChatlistExportedInvite } from '../../../../api/types';
|
||||
|
||||
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
||||
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
||||
@ -45,6 +45,7 @@ type OwnProps = {
|
||||
isOnlyInvites?: boolean;
|
||||
onReset: () => void;
|
||||
onBack: () => void;
|
||||
onSaveFolder: (cb?: VoidFunction) => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -59,8 +60,8 @@ const SUBMIT_TIMEOUT = 500;
|
||||
|
||||
const INITIAL_CHATS_LIMIT = 5;
|
||||
|
||||
const ERROR_NO_TITLE = 'Please provide a title for this folder.';
|
||||
const ERROR_NO_CHATS = 'ChatList.Filter.Error.Empty';
|
||||
export const ERROR_NO_TITLE = 'Please provide a title for this folder.';
|
||||
export const ERROR_NO_CHATS = 'ChatList.Filter.Error.Empty';
|
||||
|
||||
const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
||||
state,
|
||||
@ -78,10 +79,9 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
||||
loadedArchivedChatIds,
|
||||
invites,
|
||||
maxInviteLinks,
|
||||
onSaveFolder,
|
||||
}) => {
|
||||
const {
|
||||
editChatFolder,
|
||||
addChatFolder,
|
||||
loadChatlistInvites,
|
||||
openLimitReachedModal,
|
||||
showNotification,
|
||||
@ -154,38 +154,23 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
||||
dispatch({ type: 'setTitle', payload: currentTarget.value.trim() });
|
||||
}, [dispatch]);
|
||||
|
||||
const handleSaveFolder = useCallback((cb?: NoneToVoidFunction) => {
|
||||
const { title } = state.folder;
|
||||
|
||||
if (!title) {
|
||||
dispatch({ type: 'setError', payload: ERROR_NO_TITLE });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!includedChatIds.length && !Object.keys(includedChatTypes).length) {
|
||||
dispatch({ type: 'setError', payload: ERROR_NO_CHATS });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isCreating) {
|
||||
editChatFolder({ id: state.folderId!, folderUpdate: state.folder });
|
||||
} else {
|
||||
addChatFolder({ folder: state.folder as ApiChatFolder });
|
||||
}
|
||||
cb?.();
|
||||
}, [dispatch, includedChatIds.length, includedChatTypes, isCreating, state.folder, state.folderId]);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
handleSaveFolder();
|
||||
|
||||
dispatch({ type: 'setIsLoading', payload: true });
|
||||
setTimeout(() => {
|
||||
onReset();
|
||||
}, SUBMIT_TIMEOUT);
|
||||
}, [dispatch, handleSaveFolder, onReset]);
|
||||
|
||||
onSaveFolder(() => {
|
||||
setTimeout(() => {
|
||||
onReset();
|
||||
}, SUBMIT_TIMEOUT);
|
||||
});
|
||||
}, [dispatch, onSaveFolder, onReset]);
|
||||
|
||||
const handleCreateInviteClick = useCallback(() => {
|
||||
if (!invites) return;
|
||||
if (!invites) {
|
||||
if (isCreating) {
|
||||
onSaveFolder(onShareFolder);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignoring global updates is a known drawback here
|
||||
if (!selectCanShareFolder(getGlobal(), state.folderId!)) {
|
||||
@ -195,7 +180,7 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
||||
|
||||
if (invites.length < maxInviteLinks) {
|
||||
if (state.isTouched) {
|
||||
handleSaveFolder(onShareFolder);
|
||||
onSaveFolder(onShareFolder);
|
||||
} else {
|
||||
onShareFolder();
|
||||
}
|
||||
@ -204,15 +189,17 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
||||
limit: 'chatlistInvites',
|
||||
});
|
||||
}
|
||||
}, [handleSaveFolder, invites, lang, maxInviteLinks, onShareFolder, state.folderId, state.isTouched]);
|
||||
}, [
|
||||
invites, state.folderId, state.isTouched, maxInviteLinks, isCreating, onSaveFolder, onShareFolder, lang,
|
||||
]);
|
||||
|
||||
const handleEditInviteClick = useCallback((e: React.MouseEvent<HTMLElement>, url: string) => {
|
||||
if (state.isTouched) {
|
||||
handleSaveFolder(() => onOpenInvite(url));
|
||||
onSaveFolder(() => onOpenInvite(url));
|
||||
} else {
|
||||
onOpenInvite(url);
|
||||
}
|
||||
}, [handleSaveFolder, onOpenInvite, state.isTouched]);
|
||||
}, [onSaveFolder, onOpenInvite, state.isTouched]);
|
||||
|
||||
function renderChatType(key: string, mode: 'included' | 'excluded') {
|
||||
const chatType = mode === 'included'
|
||||
@ -339,38 +326,36 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isCreating && (
|
||||
<div className="settings-item pt-3">
|
||||
<h4 className="settings-item-header mb-3" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
{lang('FolderLinkScreen.Title')}
|
||||
</h4>
|
||||
<div className="settings-item pt-3">
|
||||
<h4 className="settings-item-header mb-3" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
{lang('FolderLinkScreen.Title')}
|
||||
</h4>
|
||||
|
||||
<ListItem
|
||||
className="settings-folders-list-item color-primary mb-0"
|
||||
icon="add"
|
||||
onClick={handleCreateInviteClick}
|
||||
>
|
||||
{lang('ChatListFilter.CreateLinkNew')}
|
||||
</ListItem>
|
||||
|
||||
{invites?.map((invite) => (
|
||||
<ListItem
|
||||
className="settings-folders-list-item color-primary mb-0"
|
||||
icon="add"
|
||||
onClick={handleCreateInviteClick}
|
||||
className="settings-folders-list-item mb-0"
|
||||
icon="link"
|
||||
multiline
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={handleEditInviteClick}
|
||||
clickArg={invite.url}
|
||||
>
|
||||
{lang('ChatListFilter.CreateLinkNew')}
|
||||
<span className="title" dir="auto">{invite.title || invite.url}</span>
|
||||
<span className="subtitle">
|
||||
{lang('ChatListFilter.LinkLabelChatCount', invite.peerIds.length, 'i')}
|
||||
</span>
|
||||
</ListItem>
|
||||
))}
|
||||
|
||||
{invites?.map((invite) => (
|
||||
<ListItem
|
||||
className="settings-folders-list-item mb-0"
|
||||
icon="link"
|
||||
multiline
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={handleEditInviteClick}
|
||||
clickArg={invite.url}
|
||||
>
|
||||
<span className="title" dir="auto">{invite.title || invite.url}</span>
|
||||
<span className="subtitle">
|
||||
{lang('ChatListFilter.LinkLabelChatCount', invite.peerIds.length, 'i')}
|
||||
</span>
|
||||
</ListItem>
|
||||
))}
|
||||
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FloatingActionButton
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, {
|
||||
memo, useCallback, useEffect, useMemo, useState,
|
||||
memo, useCallback, useEffect, useMemo, useRef, useState,
|
||||
} from '../../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../../global';
|
||||
|
||||
@ -93,13 +93,19 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>(peerIds || []);
|
||||
|
||||
useEffectWithPrevDeps(([prevIsLoading]) => {
|
||||
if (isLoading && !prevIsLoading) {
|
||||
const isFirstRenderRef = useRef(true);
|
||||
useEffectWithPrevDeps(([prevUrl]) => {
|
||||
if (prevUrl !== url) {
|
||||
isFirstRenderRef.current = true;
|
||||
}
|
||||
if (!isFirstRenderRef.current) return;
|
||||
isFirstRenderRef.current = false;
|
||||
if (!url) {
|
||||
setSelectedIds(unlockedIds);
|
||||
} else if (peerIds) {
|
||||
setSelectedIds(peerIds);
|
||||
}
|
||||
}, [isLoading, unlockedIds, peerIds]);
|
||||
}, [url, unlockedIds, peerIds]);
|
||||
|
||||
const handleClickDisabled = useCallback((id: string) => {
|
||||
const global = getGlobal();
|
||||
@ -135,9 +141,10 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
|
||||
}, [folderId, selectedIds, url]);
|
||||
|
||||
const chatsCount = selectedIds.length;
|
||||
const isDisabled = !chatsCount || isLoading;
|
||||
|
||||
return (
|
||||
<div className="settings-content no-border custom-scroll">
|
||||
<div className="settings-content no-border custom-scroll SettingsFoldersChatsPicker">
|
||||
<div className="settings-content-header">
|
||||
<AnimatedIcon
|
||||
size={STICKER_SIZE_FOLDER_SETTINGS}
|
||||
@ -154,6 +161,7 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
|
||||
<InviteLink
|
||||
inviteLink={isLoading ? lang('Loading') : url!}
|
||||
onRevoke={handleRevoke}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
|
||||
<div className="settings-item settings-item-chatlist">
|
||||
@ -163,12 +171,13 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
|
||||
onSelectedIdsChange={handleSelectedIdsChange}
|
||||
selectedIds={selectedIds}
|
||||
onDisabledClick={handleClickDisabled}
|
||||
isRoundCheckbox
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FloatingActionButton
|
||||
isShown={isLoading || isTouched}
|
||||
disabled={isLoading}
|
||||
disabled={isDisabled}
|
||||
onClick={handleSubmit}
|
||||
ariaLabel="Save changes"
|
||||
>
|
||||
|
||||
@ -764,12 +764,29 @@ addActionHandler('addChatFolder', async (global, actions, payload): Promise<void
|
||||
// Clear fields from recommended folders
|
||||
const { id: recommendedId, description, ...newFolder } = folder;
|
||||
|
||||
const newId = maxId + 1;
|
||||
const folderUpdate = {
|
||||
id: newId,
|
||||
...newFolder,
|
||||
};
|
||||
await callApi('editChatFolder', {
|
||||
id: maxId + 1,
|
||||
folderUpdate: {
|
||||
id: maxId + 1,
|
||||
...newFolder,
|
||||
id: newId,
|
||||
folderUpdate,
|
||||
});
|
||||
|
||||
// Update called from the above `callApi` is throttled, but we need to apply changes immediately
|
||||
actions.apiUpdate({
|
||||
'@type': 'updateChatFolder',
|
||||
id: newId,
|
||||
folder: folderUpdate,
|
||||
});
|
||||
|
||||
actions.requestNextSettingsScreen({
|
||||
foldersAction: {
|
||||
type: 'setFolderId',
|
||||
payload: maxId + 1,
|
||||
},
|
||||
tabId,
|
||||
});
|
||||
|
||||
if (!description) {
|
||||
@ -2019,33 +2036,43 @@ addActionHandler('editChatlistInvite', async (global, actions, payload): Promise
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
|
||||
const result = await callApi('editChatlistInvite', { folderId, slug, peers });
|
||||
try {
|
||||
const result = await callApi('editChatlistInvite', { folderId, slug, peers });
|
||||
|
||||
if (!result) return;
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
global = getGlobal();
|
||||
global = {
|
||||
...global,
|
||||
chatFolders: {
|
||||
...global.chatFolders,
|
||||
invites: {
|
||||
...global.chatFolders.invites,
|
||||
[folderId]: global.chatFolders.invites[folderId]?.map((invite) => {
|
||||
if (invite.url === url) {
|
||||
return result;
|
||||
}
|
||||
return invite;
|
||||
}),
|
||||
global = getGlobal();
|
||||
global = {
|
||||
...global,
|
||||
chatFolders: {
|
||||
...global.chatFolders,
|
||||
invites: {
|
||||
...global.chatFolders.invites,
|
||||
[folderId]: global.chatFolders.invites[folderId]?.map((invite) => {
|
||||
if (invite.url === url) {
|
||||
return result;
|
||||
}
|
||||
return invite;
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
global = updateTabState(global, {
|
||||
shareFolderScreen: {
|
||||
...selectTabState(global, tabId).shareFolderScreen!,
|
||||
isLoading: false,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
};
|
||||
setGlobal(global);
|
||||
} catch (error) {
|
||||
actions.showDialog({ data: { ...(error as ApiError), hasErrorKey: true }, tabId });
|
||||
} finally {
|
||||
global = getGlobal();
|
||||
|
||||
global = updateTabState(global, {
|
||||
shareFolderScreen: {
|
||||
...selectTabState(global, tabId).shareFolderScreen!,
|
||||
isLoading: false,
|
||||
},
|
||||
}, tabId);
|
||||
setGlobal(global);
|
||||
}
|
||||
});
|
||||
|
||||
addActionHandler('deleteChatlistInvite', async (global, actions, payload): Promise<void> => {
|
||||
|
||||
@ -4,7 +4,7 @@ import type { ApiUpdateChat } from '../../../api/types';
|
||||
import type { ActionReturnType } from '../../types';
|
||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||
|
||||
import { ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, MAX_ACTIVE_PINNED_CHATS } from '../../../config';
|
||||
import { ARCHIVED_FOLDER_ID, MAX_ACTIVE_PINNED_CHATS } from '../../../config';
|
||||
import { buildCollectionByKey, omit } from '../../../util/iteratees';
|
||||
import { closeMessageNotifications, notifyAboutMessage } from '../../../util/notifications';
|
||||
import {
|
||||
@ -271,6 +271,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
...global.chatFolders,
|
||||
byId: newChatFoldersById,
|
||||
orderedIds: newOrderedIds,
|
||||
invites: omit(global.chatFolders.invites, [id]),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -252,7 +252,7 @@ export function selectCanInviteToChat<T extends GlobalState>(global: T, chatId:
|
||||
if (!chat) return false;
|
||||
|
||||
// https://github.com/TelegramMessenger/Telegram-iOS/blob/5126be83b3b9578fb014eb52ca553da9e7a8b83a/submodules/TelegramCore/Sources/TelegramEngine/Peers/Communities.swift#L6
|
||||
return Boolean(!isUserId(chatId) && ((isChatChannel(chat) || isChatSuperGroup(chat)) ? (
|
||||
return !chat.migratedTo && Boolean(!isUserId(chatId) && ((isChatChannel(chat) || isChatSuperGroup(chat)) ? (
|
||||
chat.isCreator || getHasAdminRight(chat, 'inviteUsers')
|
||||
|| (chat.usernames?.length && !chat.isJoinRequest)
|
||||
) : (chat.isCreator || getHasAdminRight(chat, 'inviteUsers'))));
|
||||
|
||||
@ -122,9 +122,10 @@ export type FoldersState = {
|
||||
};
|
||||
export type FoldersActions = (
|
||||
'setTitle' | 'saveFilters' | 'editFolder' | 'reset' | 'setChatFilter' | 'setIsLoading' | 'setError' |
|
||||
'editIncludeFilters' | 'editExcludeFilters' | 'setIncludeFilters' | 'setExcludeFilters'
|
||||
'editIncludeFilters' | 'editExcludeFilters' | 'setIncludeFilters' | 'setExcludeFilters' | 'setIsTouched' |
|
||||
'setFolderId' | 'setIsChatlist'
|
||||
);
|
||||
export type FolderEditDispatch = Dispatch<FoldersActions>;
|
||||
export type FolderEditDispatch = Dispatch<FoldersState, FoldersActions>;
|
||||
|
||||
const INITIAL_STATE: FoldersState = {
|
||||
mode: 'create',
|
||||
@ -150,6 +151,12 @@ const foldersReducer: StateReducer<FoldersState, FoldersActions> = (
|
||||
},
|
||||
isTouched: true,
|
||||
};
|
||||
case 'setFolderId':
|
||||
return {
|
||||
...state,
|
||||
folderId: action.payload,
|
||||
mode: 'edit',
|
||||
};
|
||||
case 'editIncludeFilters':
|
||||
return {
|
||||
...state,
|
||||
@ -221,6 +228,12 @@ const foldersReducer: StateReducer<FoldersState, FoldersActions> = (
|
||||
chatFilter: action.payload,
|
||||
};
|
||||
}
|
||||
case 'setIsTouched': {
|
||||
return {
|
||||
...state,
|
||||
isTouched: action.payload,
|
||||
};
|
||||
}
|
||||
case 'setIsLoading': {
|
||||
return {
|
||||
...state,
|
||||
@ -230,9 +243,18 @@ const foldersReducer: StateReducer<FoldersState, FoldersActions> = (
|
||||
case 'setError': {
|
||||
return {
|
||||
...state,
|
||||
isLoading: false,
|
||||
error: action.payload,
|
||||
};
|
||||
}
|
||||
case 'setIsChatlist':
|
||||
return {
|
||||
...state,
|
||||
folder: {
|
||||
...state.folder,
|
||||
isChatList: action.payload,
|
||||
},
|
||||
};
|
||||
case 'reset':
|
||||
return INITIAL_STATE;
|
||||
default:
|
||||
|
||||
@ -32,7 +32,7 @@ export type FormActions = (
|
||||
'changeBillingZip' | 'changeSaveInfo' | 'changeSaveCredentials' | 'setFormErrors' | 'resetState' | 'setTipAmount' |
|
||||
'changeSavedCredentialId'
|
||||
);
|
||||
export type FormEditDispatch = Dispatch<FormActions>;
|
||||
export type FormEditDispatch = Dispatch<FormState, FormActions>;
|
||||
|
||||
const INITIAL_STATE: FormState = {
|
||||
streetLine1: '',
|
||||
|
||||
@ -4,7 +4,7 @@ import useReducer from '../useReducer';
|
||||
export type TwoFaActions = (
|
||||
'setCurrentPassword' | 'setPassword' | 'setHint' | 'setEmail' | 'reset'
|
||||
);
|
||||
export type TwoFaDispatch = Dispatch<TwoFaActions>;
|
||||
export type TwoFaDispatch = Dispatch<TwoFaState, TwoFaActions>;
|
||||
|
||||
export type TwoFaState = {
|
||||
currentPassword: string;
|
||||
|
||||
@ -4,7 +4,7 @@ import useForceUpdate from './useForceUpdate';
|
||||
|
||||
export type ReducerAction<Actions> = { type: Actions; payload?: any };
|
||||
export type StateReducer<State, Actions> = (state: State, action: ReducerAction<Actions>) => State;
|
||||
export type Dispatch<Actions> = (action: ReducerAction<Actions>) => void;
|
||||
export type Dispatch<State, Actions> = (action: ReducerAction<Actions>) => State;
|
||||
|
||||
export default function useReducer<State, Actions>(
|
||||
reducer: StateReducer<State, Actions>,
|
||||
@ -17,10 +17,11 @@ export default function useReducer<State, Actions>(
|
||||
const dispatch = useCallback((action: ReducerAction<Actions>) => {
|
||||
state.current = reducerRef.current(state.current, action);
|
||||
forceUpdate();
|
||||
return state.current;
|
||||
}, []);
|
||||
|
||||
return [
|
||||
state.current,
|
||||
dispatch,
|
||||
] as [State, Dispatch<Actions>];
|
||||
] as [State, Dispatch<State, Actions>];
|
||||
}
|
||||
|
||||
@ -69,6 +69,8 @@ const READABLE_ERROR_MESSAGES: Record<string, string> = {
|
||||
FRESH_CHANGE_ADMINS_FORBIDDEN: 'You were just elected admin, you can\'t add or modify other admins yet',
|
||||
INPUT_USER_DEACTIVATED: 'The specified user was deleted',
|
||||
BOT_PRECHECKOUT_TIMEOUT: 'The request for payment has expired',
|
||||
|
||||
PEERS_LIST_EMPTY: 'No chats are added to the list',
|
||||
};
|
||||
|
||||
if (DEBUG) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user