feat: added debug menu modal
ordered modals to take into account their z-indexespull/3281/head
parent
6001337581
commit
e1aaa52786
@ -0,0 +1,60 @@
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import styled from 'styled-components';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Flex } from '../../basic/Flex';
|
||||
import { SpacerMD, SpacerSM } from '../../basic/Text';
|
||||
import { updateDebugMenuModal } from '../../../state/ducks/modalDialog';
|
||||
import { AboutInfo, DebugActions, FeatureFlags, OtherInfo } from './components';
|
||||
import { SessionWrapperModal } from '../../SessionWrapperModal';
|
||||
|
||||
const StyledContent = styled(Flex)`
|
||||
padding-inline: var(--margins-sm);
|
||||
|
||||
h2 {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
margin: var(--margins-md) 0;
|
||||
padding: 0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
p,
|
||||
i {
|
||||
line-height: 1.4;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: start;
|
||||
}
|
||||
`;
|
||||
|
||||
export function DebugMenuModal() {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onClose = () => {
|
||||
dispatch(updateDebugMenuModal(null));
|
||||
};
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
<SessionWrapperModal title={'Debug Menu'} onClose={onClose} showExitIcon={true}>
|
||||
<StyledContent
|
||||
container={true}
|
||||
flexDirection="column"
|
||||
alignItems="flex-start"
|
||||
padding="var(--margins-sm) 0"
|
||||
>
|
||||
<DebugActions />
|
||||
<SpacerSM />
|
||||
<FeatureFlags flags={window.sessionFeatureFlags} />
|
||||
<SpacerMD />
|
||||
<AboutInfo />
|
||||
<OtherInfo />
|
||||
<SpacerMD />
|
||||
</StyledContent>
|
||||
</SessionWrapperModal>
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
@ -0,0 +1,310 @@
|
||||
import { isBoolean } from 'lodash';
|
||||
import useUpdate from 'react-use/lib/useUpdate';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { shell } from 'electron';
|
||||
import useBoolean from 'react-use/lib/useBoolean';
|
||||
import type { SessionFeatureFlagsKeys } from '../../../window';
|
||||
import { Flex } from '../../basic/Flex';
|
||||
import { SessionToggle } from '../../basic/SessionToggle';
|
||||
import { HintText, SpacerXS } from '../../basic/Text';
|
||||
import { localize } from '../../../localization/localeTools';
|
||||
import { CopyToClipboardIcon } from '../../buttons';
|
||||
import { saveLogToDesktop } from '../../../util/logging';
|
||||
import { Localizer } from '../../basic/Localizer';
|
||||
import { SessionButton } from '../../basic/SessionButton';
|
||||
import { ToastUtils, UserUtils } from '../../../session/utils';
|
||||
import { getLatestReleaseFromFileServer } from '../../../session/apis/file_server_api/FileServerApi';
|
||||
import { SessionSpinner } from '../../loading';
|
||||
|
||||
export const DebugActions = () => {
|
||||
const [loadingLatestRelease, setLoadingLatestRelease] = useBoolean(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Actions</h2>
|
||||
<SpacerXS />
|
||||
<Flex
|
||||
container={true}
|
||||
width="100%"
|
||||
justifyContent="flex-start"
|
||||
alignItems="flex-start"
|
||||
flexWrap="wrap"
|
||||
flexGap="var(--margins-md) var(--margins-lg)"
|
||||
>
|
||||
<SessionButton
|
||||
onClick={() => {
|
||||
void saveLogToDesktop();
|
||||
}}
|
||||
>
|
||||
<Localizer token="helpReportABugExportLogs" />
|
||||
</SessionButton>
|
||||
|
||||
{window.getCommitHash() ? (
|
||||
<SessionButton
|
||||
onClick={() => {
|
||||
void shell.openExternal(
|
||||
`https://github.com/session-foundation/session-desktop/commit/${window.getCommitHash()}`
|
||||
);
|
||||
}}
|
||||
>
|
||||
Go to commit
|
||||
</SessionButton>
|
||||
) : null}
|
||||
|
||||
<SessionButton
|
||||
onClick={() => {
|
||||
void shell.openExternal(
|
||||
`https://github.com/session-foundation/session-desktop/releases/tag/v${window.getVersion()}`
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Localizer token="updateReleaseNotes" />
|
||||
</SessionButton>
|
||||
|
||||
<SessionButton
|
||||
onClick={async () => {
|
||||
const userEd25519SecretKey = (await UserUtils.getUserED25519KeyPairBytes())
|
||||
?.privKeyBytes;
|
||||
if (!userEd25519SecretKey) {
|
||||
window.log.error('[debugMenu] no userEd25519SecretKey');
|
||||
return;
|
||||
}
|
||||
setLoadingLatestRelease(true);
|
||||
const versionNumber = await getLatestReleaseFromFileServer(userEd25519SecretKey);
|
||||
setLoadingLatestRelease(false);
|
||||
|
||||
if (versionNumber) {
|
||||
ToastUtils.pushToastInfo('debugLatestRelease', `v${versionNumber}`);
|
||||
} else {
|
||||
ToastUtils.pushToastError('debugLatestRelease', 'Failed to fetch latest release');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SessionSpinner loading={loadingLatestRelease} color={'var(--text-primary-color)'} />
|
||||
{!loadingLatestRelease ? 'Check latest release' : null}
|
||||
</SessionButton>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const unsupportedFlags = ['useTestNet'];
|
||||
const untestedFlags = ['useOnionRequests', 'useClosedGroupV3', 'replaceLocalizedStringsWithKeys'];
|
||||
|
||||
const handleFeatureFlagToggle = async (
|
||||
forceUpdate: () => void,
|
||||
flag: SessionFeatureFlagsKeys,
|
||||
parentFlag?: SessionFeatureFlagsKeys
|
||||
) => {
|
||||
const currentValue = parentFlag
|
||||
? (window as any).sessionFeatureFlags[parentFlag][flag]
|
||||
: (window as any).sessionFeatureFlags[flag];
|
||||
|
||||
if (parentFlag) {
|
||||
(window as any).sessionFeatureFlags[parentFlag][flag] = !currentValue;
|
||||
window.log.debug(`[debugMenu] toggled ${parentFlag}.${flag} to ${!currentValue}`);
|
||||
} else {
|
||||
(window as any).sessionFeatureFlags[flag] = !currentValue;
|
||||
window.log.debug(`[debugMenu] toggled ${flag} to ${!currentValue}`);
|
||||
}
|
||||
|
||||
forceUpdate();
|
||||
};
|
||||
|
||||
const FlagToggle = ({
|
||||
forceUpdate,
|
||||
flag,
|
||||
value,
|
||||
parentFlag,
|
||||
}: {
|
||||
forceUpdate: () => void;
|
||||
flag: SessionFeatureFlagsKeys;
|
||||
value: any;
|
||||
parentFlag?: SessionFeatureFlagsKeys;
|
||||
}) => {
|
||||
const key = `feature-flag-toggle${parentFlag ? `-${parentFlag}` : ''}-${flag}`;
|
||||
return (
|
||||
<Flex
|
||||
key={key}
|
||||
id={key}
|
||||
container={true}
|
||||
width="100%"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<span>
|
||||
{flag}
|
||||
{untestedFlags.includes(flag) ? <HintText>Untested</HintText> : null}
|
||||
</span>
|
||||
<SessionToggle
|
||||
active={value}
|
||||
onClick={() => void handleFeatureFlagToggle(forceUpdate, flag, parentFlag)}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const FeatureFlags = ({ flags }: { flags: Record<string, any> }) => {
|
||||
const forceUpdate = useUpdate();
|
||||
return (
|
||||
<Flex
|
||||
container={true}
|
||||
width={'100%'}
|
||||
flexDirection="column"
|
||||
justifyContent="flex-start"
|
||||
alignItems="flex-start"
|
||||
flexGap="var(--margins-xs)"
|
||||
>
|
||||
<Flex container={true} alignItems="center">
|
||||
<h2>Feature Flags</h2>
|
||||
<HintText>Experimental</HintText>
|
||||
</Flex>
|
||||
<i>
|
||||
Changes are temporary. You can clear them by reloading the window or restarting the app.
|
||||
</i>
|
||||
<SpacerXS />
|
||||
{Object.entries(flags).map(([key, value]) => {
|
||||
const flag = key as SessionFeatureFlagsKeys;
|
||||
if (unsupportedFlags.includes(flag)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isBoolean(value)) {
|
||||
return (
|
||||
<>
|
||||
<h3>{flag}</h3>
|
||||
{Object.entries(value).map(([k, v]: [string, any]) => {
|
||||
const nestedFlag = k as SessionFeatureFlagsKeys;
|
||||
return (
|
||||
<FlagToggle
|
||||
forceUpdate={forceUpdate}
|
||||
flag={nestedFlag}
|
||||
value={v}
|
||||
parentFlag={flag}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return <FlagToggle forceUpdate={forceUpdate} flag={flag} value={value} />;
|
||||
})}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const AboutInfo = () => {
|
||||
const environmentStates = [];
|
||||
|
||||
if (window.getEnvironment() !== 'production') {
|
||||
environmentStates.push(window.getEnvironment());
|
||||
}
|
||||
|
||||
if (window.getAppInstance()) {
|
||||
environmentStates.push(window.getAppInstance());
|
||||
}
|
||||
|
||||
const aboutInfo = [
|
||||
`${localize('updateVersion').withArgs({ version: window.getVersion() })}`,
|
||||
`${localize('systemInformationDesktop').withArgs({ information: window.getOSRelease() })}`,
|
||||
`${localize('commitHashDesktop').withArgs({ hash: window.getCommitHash() || window.i18n('unknown') })}`,
|
||||
`${environmentStates.join(' - ')}`,
|
||||
];
|
||||
|
||||
return (
|
||||
<Flex
|
||||
container={true}
|
||||
width={'100%'}
|
||||
flexDirection="column"
|
||||
justifyContent="flex-start"
|
||||
alignItems="flex-start"
|
||||
flexWrap="wrap"
|
||||
>
|
||||
<SpacerXS />
|
||||
<Flex container={true} width="100%" alignItems="center" flexGap="var(--margins-xs)">
|
||||
<h2>About</h2>
|
||||
<CopyToClipboardIcon iconSize={'medium'} copyContent={aboutInfo.join('\n')} />
|
||||
</Flex>
|
||||
<Flex
|
||||
container={true}
|
||||
width="100%"
|
||||
flexDirection="column"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
flexGap="var(--margins-xs)"
|
||||
>
|
||||
{aboutInfo.map((info, index) => (
|
||||
<Flex
|
||||
key={`debug-about-info-${index}`}
|
||||
container={true}
|
||||
width="100%"
|
||||
alignItems="flex-start"
|
||||
flexGap="var(--margins-xs)"
|
||||
>
|
||||
<p style={{ userSelect: 'text', lineHeight: 1.5 }}>{info}</p>
|
||||
<CopyToClipboardIcon iconSize={'medium'} copyContent={info} />
|
||||
</Flex>
|
||||
))}
|
||||
<SpacerXS />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const OtherInfo = () => {
|
||||
const otherInfo = useAsync(async () => {
|
||||
const { id, vbid } = await window.getUserKeys();
|
||||
return [`${localize('accountIdYours')}: ${id}`, `VBID: ${vbid}`];
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
container={true}
|
||||
width={'100%'}
|
||||
flexDirection="column"
|
||||
justifyContent="flex-start"
|
||||
alignItems="flex-start"
|
||||
flexWrap="wrap"
|
||||
>
|
||||
<SpacerXS />
|
||||
<Flex container={true} width="100%" alignItems="center" flexGap="var(--margins-xs)">
|
||||
<h2>Other Info</h2>
|
||||
{otherInfo.value ? (
|
||||
<CopyToClipboardIcon iconSize={'medium'} copyContent={otherInfo.value.join('\n')} />
|
||||
) : null}
|
||||
</Flex>
|
||||
<Flex
|
||||
container={true}
|
||||
width="100%"
|
||||
flexDirection="column"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
flexGap="var(--margins-xs)"
|
||||
>
|
||||
{otherInfo.loading ? (
|
||||
<p style={{ userSelect: 'text' }}>{localize('loading')}</p>
|
||||
) : otherInfo.error ? (
|
||||
<p style={{ userSelect: 'text' }}>
|
||||
<span style={{ color: 'var(--danger-color)' }}>
|
||||
{localize('theError')}: {otherInfo.error.message || localize('errorUnknown')}
|
||||
</span>
|
||||
</p>
|
||||
) : null}
|
||||
{otherInfo.value
|
||||
? otherInfo.value.map((info, index) => (
|
||||
<Flex
|
||||
key={`debug-other-info-${index}`}
|
||||
container={true}
|
||||
width="100%"
|
||||
alignItems="flex-start"
|
||||
flexGap="var(--margins-xs)"
|
||||
>
|
||||
<p style={{ userSelect: 'text', lineHeight: 1.5 }}>{info}</p>
|
||||
<CopyToClipboardIcon iconSize={'medium'} copyContent={info} />
|
||||
</Flex>
|
||||
))
|
||||
: null}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue