mirror of
https://github.com/lux-tel/session-desktop.git
synced 2026-10-30 20:33:24 +00:00
270 lines
8.4 KiB
TypeScript
270 lines
8.4 KiB
TypeScript
import { ipcRenderer } from 'electron';
|
|
import { useState } from 'react';
|
|
|
|
import { useSelector } from 'react-redux';
|
|
import useInterval from 'react-use/lib/useInterval';
|
|
import useTimeoutFn from 'react-use/lib/useTimeoutFn';
|
|
|
|
import useMount from 'react-use/lib/useMount';
|
|
import useThrottleFn from 'react-use/lib/useThrottleFn';
|
|
import styled from 'styled-components';
|
|
import { getAppDispatch } from '../../state/dispatch';
|
|
|
|
import {
|
|
getOurPrimaryConversation,
|
|
useGlobalUnreadMessageCount,
|
|
} from '../../state/selectors/conversations';
|
|
import { getOurNumber } from '../../state/selectors/user';
|
|
|
|
import { DecryptedAttachmentsManager } from '../../session/crypto/DecryptedAttachmentsManager';
|
|
|
|
import { DURATION } from '../../session/constants';
|
|
|
|
import {
|
|
onionPathModal,
|
|
updateDebugMenuModal,
|
|
userSettingsModal,
|
|
} from '../../state/ducks/modalDialog';
|
|
|
|
import { UserUtils } from '../../session/utils';
|
|
import { Avatar, AvatarSize } from '../avatar/Avatar';
|
|
import { SessionLucideIconButton } from '../icon/SessionIconButton';
|
|
import { LeftPaneSectionContainer } from './LeftPaneSectionContainer';
|
|
|
|
import { SnodePool } from '../../session/apis/snode_api/snodePool';
|
|
import { forceSyncConfigurationNowIfNeeded } from '../../session/utils/sync/syncUtils';
|
|
import { useFetchLatestReleaseFromFileServer } from '../../hooks/useFetchLatestReleaseFromFileServer';
|
|
import { useIsDarkTheme } from '../../state/theme/selectors/theme';
|
|
import { switchThemeTo } from '../../themes/switchTheme';
|
|
import { getOppositeTheme } from '../../util/theme';
|
|
|
|
import { useDebugMode } from '../../state/selectors/debug';
|
|
import { LUCIDE_ICONS_UNICODE } from '../icon/lucide';
|
|
import { themesArray } from '../../themes/constants/colors';
|
|
import { isDebugMode, isDevProd } from '../../shared/env_vars';
|
|
import { GearAvatarButton } from '../buttons/avatar/GearAvatarButton';
|
|
import { useZoomShortcuts } from '../../hooks/useZoomingShortcut';
|
|
import { OnionStatusLight } from '../dialog/OnionStatusPathDialog';
|
|
import { AvatarReupload } from '../../session/utils/job_runners/jobs/AvatarReuploadJob';
|
|
import { useDebugMenuModal } from '../../state/selectors/modal';
|
|
import { getFeatureFlagMemo } from '../../state/ducks/types/releasedFeaturesReduxTypes';
|
|
import { useDebugKey } from '../../hooks/useDebugKey';
|
|
import { UpdateProRevocationList } from '../../session/utils/job_runners/jobs/UpdateProRevocationListJob';
|
|
import { getIsProAvailableMemo } from '../../hooks/useIsProAvailable';
|
|
import { SettingsKey } from '../../data/settings-key';
|
|
|
|
const StyledContainerAvatar = styled.div`
|
|
padding: var(--margins-lg);
|
|
position: relative;
|
|
cursor: pointer;
|
|
`;
|
|
|
|
function handleThemeSwitch() {
|
|
const currentTheme = window.getSettingValue(SettingsKey.settingsTheme);
|
|
let newTheme = getOppositeTheme(currentTheme);
|
|
if (isDebugMode()) {
|
|
// rotate over the 4 themes
|
|
const newThemeIndex = (themesArray.indexOf(currentTheme) + 1) % themesArray.length;
|
|
newTheme = themesArray[newThemeIndex];
|
|
}
|
|
// We want to persist the primary color when using the color mode button
|
|
void switchThemeTo({
|
|
theme: newTheme,
|
|
mainWindow: true,
|
|
usePrimaryColor: true,
|
|
dispatch: window.inboxStore?.dispatch,
|
|
});
|
|
}
|
|
|
|
const cleanUpMediasInterval = DURATION.MINUTES * 60;
|
|
|
|
function useUpdateBadgeCount() {
|
|
const globalUnreadMessageCount = useGlobalUnreadMessageCount();
|
|
|
|
// Reuse the unreadToShow from the global state to update the badge count
|
|
// Note: useThrottleFn from react-use will run execute trailing edge too.
|
|
useThrottleFn(
|
|
(unreadCount: number) => {
|
|
if (unreadCount !== undefined) {
|
|
ipcRenderer.send('update-badge-count', unreadCount);
|
|
}
|
|
},
|
|
2000,
|
|
[globalUnreadMessageCount]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Small hook that ticks every minute to add a job to fetch the revocation list.
|
|
* Note: a job will only be added if it wasn't fetched recently, so there is no harm in running this every minute.
|
|
*/
|
|
function usePeriodicFetchRevocationList() {
|
|
const proAvailable = getIsProAvailableMemo();
|
|
useInterval(
|
|
() => {
|
|
if (!proAvailable) {
|
|
return;
|
|
}
|
|
void UpdateProRevocationList.queueNewJobIfNeeded();
|
|
},
|
|
isDevProd() ? 10 * DURATION.SECONDS : 1 * DURATION.MINUTES
|
|
);
|
|
}
|
|
|
|
function useDebugThemeSwitch() {
|
|
useDebugKey({
|
|
withCtrl: true,
|
|
key: 't',
|
|
callback: handleThemeSwitch,
|
|
});
|
|
}
|
|
|
|
function DebugMenuModalButton() {
|
|
const dispatch = getAppDispatch();
|
|
const debugMenuModalState = useDebugMenuModal();
|
|
|
|
useDebugKey({
|
|
withCtrl: true,
|
|
key: 'd',
|
|
callback: () => {
|
|
dispatch(updateDebugMenuModal(debugMenuModalState ? null : {}));
|
|
},
|
|
});
|
|
|
|
return (
|
|
<SessionLucideIconButton
|
|
iconSize="medium"
|
|
padding="var(--margins-lg)"
|
|
unicode={LUCIDE_ICONS_UNICODE.SQUARE_CODE}
|
|
dataTestId="debug-menu-section"
|
|
onClick={() => {
|
|
dispatch(updateDebugMenuModal({}));
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
|
|
// NOTE: [react-compiler] this has to live here for the hook to be identified as static
|
|
function useActionsPanelInternal() {
|
|
const [startCleanUpMedia, setStartCleanUpMedia] = useState(false);
|
|
const ourPrimaryConversation = useSelector(getOurPrimaryConversation);
|
|
const showDebugMenu = useDebugMode();
|
|
const ourNumber = useSelector(getOurNumber);
|
|
const isDarkTheme = useIsDarkTheme();
|
|
|
|
useFetchLatestReleaseFromFileServer();
|
|
// setup our own shortcuts so that it changes show in the appearance tab too
|
|
useZoomShortcuts();
|
|
useInterval(
|
|
DecryptedAttachmentsManager.cleanUpOldDecryptedMedias,
|
|
startCleanUpMedia ? cleanUpMediasInterval : null
|
|
);
|
|
|
|
// wait for cleanUpMediasInterval and then start cleaning up medias
|
|
// this would be way easier to just be able to not trigger a call with the setInterval
|
|
useMount(() => {
|
|
const timeout = setTimeout(() => setStartCleanUpMedia(true), cleanUpMediasInterval);
|
|
|
|
return () => clearTimeout(timeout);
|
|
});
|
|
|
|
return {
|
|
ourPrimaryConversation,
|
|
ourNumber,
|
|
showDebugMenu,
|
|
isDarkTheme,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* ActionsPanel is the far left banner (not the left pane).
|
|
* The panel with buttons to switch between the message/contact/settings/theme views
|
|
*/
|
|
export const ActionsPanel = () => {
|
|
const dispatch = getAppDispatch();
|
|
const { ourPrimaryConversation, ourNumber, showDebugMenu, isDarkTheme } =
|
|
useActionsPanelInternal();
|
|
|
|
const fsTTL30sEnabled = getFeatureFlagMemo('fsTTL30s');
|
|
useDebugThemeSwitch();
|
|
useUpdateBadgeCount();
|
|
usePeriodicFetchRevocationList();
|
|
|
|
useInterval(() => {
|
|
if (!ourPrimaryConversation) {
|
|
return;
|
|
}
|
|
void forceSyncConfigurationNowIfNeeded();
|
|
}, DURATION.DAYS * 2);
|
|
|
|
useInterval(() => {
|
|
if (!ourPrimaryConversation) {
|
|
return;
|
|
}
|
|
|
|
// trigger an updates from the snodes and swarm every hour
|
|
void SnodePool.forceRefreshRandomSnodePool();
|
|
void SnodePool.getFreshSwarmFor(UserUtils.getOurPubKeyStrFromCache());
|
|
}, DURATION.HOURS * 1);
|
|
|
|
useTimeoutFn(() => {
|
|
if (!ourPrimaryConversation) {
|
|
return;
|
|
}
|
|
// trigger an updates from the snodes after 5 minutes, once
|
|
void SnodePool.forceRefreshRandomSnodePool();
|
|
}, DURATION.MINUTES * 5);
|
|
|
|
useInterval(
|
|
() => {
|
|
if (!ourPrimaryConversation) {
|
|
return;
|
|
}
|
|
void AvatarReupload.addAvatarReuploadJob();
|
|
},
|
|
fsTTL30sEnabled ? DURATION.SECONDS * 1 : DURATION.DAYS * 1
|
|
);
|
|
|
|
if (!ourPrimaryConversation) {
|
|
window?.log?.warn('ActionsPanel: ourPrimaryConversation is not set');
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<LeftPaneSectionContainer data-testid="leftpane-section-container">
|
|
<StyledContainerAvatar
|
|
onClick={() => {
|
|
dispatch(userSettingsModal({ userSettingsPage: 'default' }));
|
|
}}
|
|
>
|
|
<Avatar
|
|
size={AvatarSize.S}
|
|
pubkey={ourNumber}
|
|
dataTestId="leftpane-primary-avatar"
|
|
imageDataTestId={`img-leftpane-primary-avatar`}
|
|
/>
|
|
<GearAvatarButton />
|
|
</StyledContainerAvatar>
|
|
{showDebugMenu ? <DebugMenuModalButton /> : null}
|
|
<OnionStatusLight
|
|
handleClick={() => {
|
|
dispatch(onionPathModal({}));
|
|
}}
|
|
inActionPanel={true}
|
|
/>
|
|
<SessionLucideIconButton
|
|
margin="0 0 0 0"
|
|
iconSize="medium"
|
|
padding="var(--margins-lg)"
|
|
unicode={isDarkTheme ? LUCIDE_ICONS_UNICODE.MOON : LUCIDE_ICONS_UNICODE.SUN_MEDIUM}
|
|
dataTestId="theme-section"
|
|
onClick={() => {
|
|
void handleThemeSwitch();
|
|
}}
|
|
/>
|
|
</LeftPaneSectionContainer>
|
|
</>
|
|
);
|
|
};
|