summaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'frontend')
-rw-r--r--frontend/src/components/modals/WarnThirdParty.tsx78
-rw-r--r--frontend/src/components/settings/pages/developer/index.tsx29
-rw-r--r--frontend/src/components/settings/pages/general/StoreSelect.tsx44
-rw-r--r--frontend/src/utils/TranslationHelper.tsx29
-rw-r--r--frontend/src/utils/globalTypes.ts4
5 files changed, 161 insertions, 23 deletions
diff --git a/frontend/src/components/modals/WarnThirdParty.tsx b/frontend/src/components/modals/WarnThirdParty.tsx
new file mode 100644
index 00000000..e7ff4561
--- /dev/null
+++ b/frontend/src/components/modals/WarnThirdParty.tsx
@@ -0,0 +1,78 @@
+import { ConfirmModal } from '@decky/ui';
+import { FC, useEffect, useState } from 'react';
+import { FaExclamationTriangle } from 'react-icons/fa';
+
+import { WarnThirdPartyType } from '../../utils/globalTypes';
+import TranslationHelper, { TranslationClass } from '../../utils/TranslationHelper';
+
+interface WarnThirdPartyProps {
+ seconds?: number;
+ type: WarnThirdPartyType;
+ onOK(): void;
+ onCancel(): void;
+ closeModal?(): void;
+}
+
+const WarnThirdParty: FC<WarnThirdPartyProps> = ({ seconds = 5, type, onOK, onCancel, closeModal }) => {
+ const [waitTimer, setWaitTimer] = useState(seconds);
+
+ useEffect(() => {
+ // exit early when we reach 0
+ if (waitTimer <= 0) return;
+
+ // save intervalId to clear the interval when the
+ // component re-renders
+ const intervalId = setInterval(() => {
+ setWaitTimer(waitTimer - 1);
+ }, 1000);
+
+ // clear interval on re-render to avoid memory leaks
+ return () => clearInterval(intervalId);
+ // add waitTimer as a dependency to re-rerun the effect
+ // when we update it
+ }, [waitTimer]);
+
+ return (
+ <ConfirmModal
+ bOKDisabled={waitTimer > 0}
+ closeModal={closeModal}
+ onOK={async () => {
+ await onOK();
+ }}
+ onCancel={async () => {
+ await onCancel();
+ }}
+ strTitle={
+ <div>
+ <FaExclamationTriangle />
+ <TranslationHelper transClass={TranslationClass.WARN_THIRD_PARTY} transText="title" warnType={type} />
+ </div>
+ }
+ strOKButtonText={
+ waitTimer > 0 ? (
+ <div>
+ <TranslationHelper
+ transClass={TranslationClass.WARN_THIRD_PARTY}
+ transText="button_processing"
+ i18nArgs={{
+ timer: waitTimer,
+ }}
+ />
+ </div>
+ ) : (
+ <div>
+ <TranslationHelper transClass={TranslationClass.WARN_THIRD_PARTY} transText="button_idle" />
+ </div>
+ )
+ }
+ >
+ <span style={{ color: 'red' }}>
+ <div>
+ <TranslationHelper transClass={TranslationClass.WARN_THIRD_PARTY} transText="desc" warnType={type} />
+ </div>
+ </span>
+ </ConfirmModal>
+ );
+};
+
+export default WarnThirdParty;
diff --git a/frontend/src/components/settings/pages/developer/index.tsx b/frontend/src/components/settings/pages/developer/index.tsx
index 05989806..8691ff7b 100644
--- a/frontend/src/components/settings/pages/developer/index.tsx
+++ b/frontend/src/components/settings/pages/developer/index.tsx
@@ -7,6 +7,7 @@ import {
Navigation,
TextField,
Toggle,
+ showModal,
} from '@decky/ui';
import { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -15,9 +16,11 @@ import { FaFileArchive, FaLink, FaReact, FaSteamSymbol, FaTerminal } from 'react
import { setShouldConnectToReactDevTools, setShowValveInternal } from '../../../../developer';
import Logger from '../../../../logger';
import { installFromURL } from '../../../../store';
+import { WarnThirdPartyType } from '../../../../utils/globalTypes';
import { useSetting } from '../../../../utils/hooks/useSetting';
import { getSetting } from '../../../../utils/settings';
import { FileSelectionType } from '../../../modals/filepicker';
+import WarnThirdParty from '../../../modals/WarnThirdParty';
import RemoteDebuggingSettings from '../general/RemoteDebugging';
const logger = new Logger('DeveloperIndex');
@@ -43,6 +46,8 @@ export default function DeveloperSettings() {
const [enableValveInternal, setEnableValveInternal] = useSetting<boolean>('developer.valve_internal', false);
const [reactDevtoolsEnabled, setReactDevtoolsEnabled] = useSetting<boolean>('developer.rdt.enabled', false);
const [reactDevtoolsIP, setReactDevtoolsIP] = useSetting<string>('developer.rdt.ip', '');
+ const [acceptedWarning, setAcceptedWarning] = useSetting<boolean>('developer.warn.third_party', false);
+ const waitTime = acceptedWarning ? 0 : 5;
const [pluginURL, setPluginURL] = useState('');
const textRef = useRef<HTMLDivElement>(null);
const { t } = useTranslation();
@@ -74,13 +79,23 @@ export default function DeveloperSettings() {
>
<DialogButton
disabled={pluginURL.length == 0}
- onClick={() => {
- if (/^https?:\/\//.test(pluginURL)) {
- installFromURL(pluginURL);
- } else {
- installFromURL('https://' + pluginURL);
- }
- }}
+ onClick={() =>
+ showModal(
+ <WarnThirdParty
+ type={WarnThirdPartyType.ZIP}
+ onOK={() => {
+ setAcceptedWarning(true);
+ if (/^https?:\/\//.test(pluginURL)) {
+ installFromURL(pluginURL);
+ } else {
+ installFromURL('https://' + pluginURL);
+ }
+ }}
+ onCancel={() => {}}
+ seconds={waitTime}
+ />,
+ )
+ }
>
{t('SettingsDeveloperIndex.third_party_plugins.button_install')}
</DialogButton>
diff --git a/frontend/src/components/settings/pages/general/StoreSelect.tsx b/frontend/src/components/settings/pages/general/StoreSelect.tsx
index 9cc7d5c9..80bc4930 100644
--- a/frontend/src/components/settings/pages/general/StoreSelect.tsx
+++ b/frontend/src/components/settings/pages/general/StoreSelect.tsx
@@ -1,17 +1,21 @@
-import { Dropdown, Field, TextField } from '@decky/ui';
+import { Dropdown, Field, TextField, showModal } from '@decky/ui';
import { FunctionComponent } from 'react';
import { useTranslation } from 'react-i18next';
import { FaShapes } from 'react-icons/fa';
import Logger from '../../../../logger';
import { Store } from '../../../../store';
+import { WarnThirdPartyType } from '../../../../utils/globalTypes';
import { useSetting } from '../../../../utils/hooks/useSetting';
+import WarnThirdParty from '../../../modals/WarnThirdParty';
const logger = new Logger('StoreSelect');
const StoreSelect: FunctionComponent<{}> = () => {
const [selectedStore, setSelectedStore] = useSetting<Store>('store', Store.Default);
const [selectedStoreURL, setSelectedStoreURL] = useSetting<string | null>('store-url', null);
+ const [acceptedWarning, setAcceptedWarning] = useSetting<boolean>('store_select.warn.third_party', false);
+ const waitTime = acceptedWarning ? 0 : 5;
const { t } = useTranslation();
const tStores = [
t('StoreSelect.store_channel.default'),
@@ -38,20 +42,30 @@ const StoreSelect: FunctionComponent<{}> = () => {
}}
/>
</Field>
- {selectedStore == Store.Custom && (
- <Field
- label={t('StoreSelect.custom_store.label')}
- indentLevel={1}
- description={
- <TextField
- label={t('StoreSelect.custom_store.url_label')}
- value={selectedStoreURL || undefined}
- onChange={(e) => setSelectedStoreURL(e?.target.value || null)}
- />
- }
- icon={<FaShapes style={{ display: 'block' }} />}
- ></Field>
- )}
+ {selectedStore == Store.Custom &&
+ showModal(
+ <WarnThirdParty
+ type={WarnThirdPartyType.REPO}
+ seconds={waitTime}
+ onOK={() => {
+ setAcceptedWarning(true);
+ }}
+ onCancel={() => setSelectedStore(Store.Default)}
+ />,
+ ) && (
+ <Field
+ label={t('StoreSelect.custom_store.label')}
+ indentLevel={1}
+ description={
+ <TextField
+ label={t('StoreSelect.custom_store.url_label')}
+ value={selectedStoreURL || undefined}
+ onChange={(e) => setSelectedStoreURL(e?.target.value || null)}
+ />
+ }
+ icon={<FaShapes style={{ display: 'block' }} />}
+ ></Field>
+ )}
</>
);
};
diff --git a/frontend/src/utils/TranslationHelper.tsx b/frontend/src/utils/TranslationHelper.tsx
index 4596060d..60af2065 100644
--- a/frontend/src/utils/TranslationHelper.tsx
+++ b/frontend/src/utils/TranslationHelper.tsx
@@ -2,10 +2,12 @@ import { FC } from 'react';
import { Translation } from 'react-i18next';
import Logger from '../logger';
+import { WarnThirdPartyType } from './globalTypes';
export enum TranslationClass {
PLUGIN_LOADER = 'PluginLoader',
DEVELOPER = 'Developer',
+ WARN_THIRD_PARTY = 'WarnThirdParty',
}
interface TranslationHelperProps {
@@ -13,11 +15,17 @@ interface TranslationHelperProps {
transText: string;
i18nArgs?: {};
installType?: number;
+ warnType?: WarnThirdPartyType;
}
const logger = new Logger('TranslationHelper');
-const TranslationHelper: FC<TranslationHelperProps> = ({ transClass, transText, i18nArgs = null }) => {
+const TranslationHelper: FC<TranslationHelperProps> = ({
+ transClass,
+ transText,
+ i18nArgs = null,
+ warnType = WarnThirdPartyType.REPO,
+}) => {
return (
<Translation>
{(t, {}) => {
@@ -30,6 +38,25 @@ const TranslationHelper: FC<TranslationHelperProps> = ({ transClass, transText,
return i18nArgs
? t(TranslationClass.DEVELOPER + '.' + transText, i18nArgs)
: t(TranslationClass.DEVELOPER + '.' + transText);
+ //Handle different messages in different class cases
+ case TranslationClass.WARN_THIRD_PARTY:
+ //Needed only for title and description
+ if (!transText.startsWith('button')) {
+ switch (warnType) {
+ case WarnThirdPartyType.REPO:
+ return i18nArgs
+ ? t(TranslationClass.WARN_THIRD_PARTY + '.' + transText + '_repo', i18nArgs)
+ : t(TranslationClass.WARN_THIRD_PARTY + '.' + transText + '_repo');
+ case WarnThirdPartyType.ZIP:
+ return i18nArgs
+ ? t(TranslationClass.WARN_THIRD_PARTY + '.' + transText + '_zip', i18nArgs)
+ : t(TranslationClass.WARN_THIRD_PARTY + '.' + transText + '_zip');
+ }
+ } else {
+ return i18nArgs
+ ? t(TranslationClass.WARN_THIRD_PARTY + '.' + transText, i18nArgs)
+ : t(TranslationClass.WARN_THIRD_PARTY + '.' + transText);
+ }
default:
logger.error('We should never fall in the default case!');
return '';
diff --git a/frontend/src/utils/globalTypes.ts b/frontend/src/utils/globalTypes.ts
new file mode 100644
index 00000000..26f7d1ae
--- /dev/null
+++ b/frontend/src/utils/globalTypes.ts
@@ -0,0 +1,4 @@
+export enum WarnThirdPartyType {
+ REPO = 0,
+ ZIP = 1,
+}