updated to react 19

This commit is contained in:
PhilReact 2025-04-29 22:25:20 +03:00
parent 2d8bf8fb97
commit ea0de88b1f
44 changed files with 1513 additions and 2127 deletions

1479
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -20,30 +20,28 @@
"@capacitor/core": "^6.1.2", "@capacitor/core": "^6.1.2",
"@capacitor/filesystem": "^6.0.1", "@capacitor/filesystem": "^6.0.1",
"@capacitor/local-notifications": "^6.1.0", "@capacitor/local-notifications": "^6.1.0",
"@chatscope/chat-ui-kit-react": "^2.0.3", "@dnd-kit/core": "^6.3.0",
"@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/sortable": "^8.0.0",
"@electron/packager": "^18.3.6", "@electron/packager": "^18.3.6",
"@emotion/react": "^11.11.4", "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@evva/capacitor-secure-storage-plugin": "^3.0.1", "@evva/capacitor-secure-storage-plugin": "^3.0.1",
"@mui/icons-material": "^5.16.4", "@mui/icons-material": "^7.0.1",
"@mui/lab": "^5.0.0-alpha.173", "@mui/lab": "^7.0.0-beta.11",
"@mui/material": "^5.16.7", "@mui/material": "^7.0.1",
"@reduxjs/toolkit": "^2.2.7", "@tanstack/react-virtual": "^3.13.6",
"@tanstack/react-virtual": "^3.10.8",
"@testing-library/jest-dom": "^6.4.6", "@testing-library/jest-dom": "^6.4.6",
"@testing-library/user-event": "^14.5.2", "@testing-library/user-event": "^14.6.1",
"@tiptap/extension-color": "^2.5.9", "@tiptap/extension-color": "^2.11.7",
"@tiptap/extension-highlight": "^2.6.6", "@tiptap/extension-highlight": "^2.11.7",
"@tiptap/extension-image": "^2.6.6", "@tiptap/extension-image": "^2.11.7",
"@tiptap/extension-mention": "^2.9.1", "@tiptap/extension-mention": "^2.11.7",
"@tiptap/extension-placeholder": "^2.6.2", "@tiptap/extension-placeholder": "^2.11.7",
"@tiptap/extension-text-style": "^2.5.9", "@tiptap/extension-text-style": "^2.11.7",
"@tiptap/extension-underline": "^2.6.6", "@tiptap/extension-underline": "^2.11.7",
"@tiptap/pm": "^2.5.9", "@tiptap/pm": "^2.11.7",
"@tiptap/react": "^2.5.9", "@tiptap/react": "^2.11.7",
"@tiptap/starter-kit": "^2.5.9", "@tiptap/starter-kit": "^2.11.7",
"@transistorsoft/capacitor-background-fetch": "^6.0.1", "@transistorsoft/capacitor-background-fetch": "^6.0.1",
"@types/chrome": "^0.0.263", "@types/chrome": "^0.0.263",
"@uiw/react-color": "^2.5.1", "@uiw/react-color": "^2.5.1",
@ -66,30 +64,25 @@
"i18next-browser-languagedetector": "^8.0.5", "i18next-browser-languagedetector": "^8.0.5",
"i18next-http-backend": "^3.0.2", "i18next-http-backend": "^3.0.2",
"i18next-localstorage-backend": "^4.2.0", "i18next-localstorage-backend": "^4.2.0",
"jotai": "^2.12.3",
"jssha": "3.3.1", "jssha": "3.3.1",
"lit": "^3.2.1", "lit": "^3.2.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mime": "^4.0.4", "mime": "^4.0.4",
"moment": "^2.30.1", "moment": "^2.30.1",
"npm": "^10.8.3", "npm": "^10.8.3",
"quill-image-resize-module-react": "^3.0.0", "react": "^19.1.0",
"react": "^18.2.0",
"react-copy-to-clipboard": "^5.1.0",
"react-countdown-circle-timer": "^3.2.1", "react-countdown-circle-timer": "^3.2.1",
"react-dom": "^18.2.0", "react-dom": "^19.1.0",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-frame-component": "^5.2.7", "react-frame-component": "^5.2.7",
"react-i18next": "^15.4.1", "react-i18next": "^15.4.1",
"react-infinite-scroller": "^1.2.6", "react-intersection-observer": "^9.16.0",
"react-intersection-observer": "^9.13.0", "react-json-view-lite": "^2.4.1",
"react-json-view-lite": "^2.0.1",
"react-loader-spinner": "^6.1.6", "react-loader-spinner": "^6.1.6",
"react-qr-code": "^2.0.15", "react-qr-code": "^2.0.15",
"react-quill": "^2.0.0", "react-virtualized": "^9.22.6",
"react-redux": "^9.1.2",
"react-virtualized": "^9.22.5",
"react-virtuoso": "^4.10.4", "react-virtuoso": "^4.10.4",
"recoil": "^0.7.7",
"short-unique-id": "^5.2.0", "short-unique-id": "^5.2.0",
"slate": "^0.103.0", "slate": "^0.103.0",
"slate-react": "^0.109.0", "slate-react": "^0.109.0",
@ -101,14 +94,12 @@
"vite-plugin-wasm": "^3.3.0" "vite-plugin-wasm": "^3.3.0"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/dom": "^10.3.0", "@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.0.0", "@testing-library/react": "^16.3.0",
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",
"@types/lodash": "^4.17.7", "@types/lodash": "^4.17.7",
"@types/react": "^18.2.64", "@types/react": "^19.1.0",
"@types/react-copy-to-clipboard": "^5.0.7", "@types/react-dom": "^19.1.0",
"@types/react-dom": "^18.2.21",
"@types/react-infinite-scroller": "^1.2.5",
"@types/react-virtualized": "^9.21.30", "@types/react-virtualized": "^9.21.30",
"@typescript-eslint/eslint-plugin": "^7.1.1", "@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1", "@typescript-eslint/parser": "^7.1.1",
@ -122,5 +113,11 @@
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^5.1.6", "vite": "^5.1.6",
"vitest": "^1.6.1" "vitest": "^1.6.1"
},
"overrides": {
"react-loader-spinner": {
"react": "^18 || ^19",
"react-dom": "^18 || ^19"
}
} }
} }

View File

@ -33,7 +33,6 @@ import DownloadIcon from '@mui/icons-material/Download';
import ltcLogo from './assets/ltc.png'; import ltcLogo from './assets/ltc.png';
import PersonSearchIcon from '@mui/icons-material/PersonSearch'; import PersonSearchIcon from '@mui/icons-material/PersonSearch';
import qortLogo from './assets/qort.png'; import qortLogo from './assets/qort.png';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { Return } from './assets/Icons/Return.tsx'; import { Return } from './assets/Icons/Return.tsx';
import WarningIcon from '@mui/icons-material/Warning'; import WarningIcon from '@mui/icons-material/Warning';
import './utils/seedPhrase/RandomSentenceGenerator'; import './utils/seedPhrase/RandomSentenceGenerator';
@ -96,7 +95,6 @@ import { Settings } from './components/Group/Settings';
import { MainAvatar } from './components/MainAvatar'; import { MainAvatar } from './components/MainAvatar';
import { useRetrieveDataLocalStorage } from './useRetrieveDataLocalStorage'; import { useRetrieveDataLocalStorage } from './useRetrieveDataLocalStorage';
import { useQortalGetSaveSettings } from './useQortalGetSaveSettings'; import { useQortalGetSaveSettings } from './useQortalGetSaveSettings';
import { useRecoilState, useResetRecoilState, useSetRecoilState } from 'recoil';
import { import {
canSaveSettingToQdnAtom, canSaveSettingToQdnAtom,
enabledDevModeAtom, enabledDevModeAtom,
@ -142,6 +140,8 @@ import LanguageSelector from './components/Language/LanguageSelector.tsx';
import { DownloadWallet } from './components/Auth/DownloadWallet.tsx'; import { DownloadWallet } from './components/Auth/DownloadWallet.tsx';
import { CopyIcon } from './assets/Icons/CopyIcon.tsx'; import { CopyIcon } from './assets/Icons/CopyIcon.tsx';
import { SuccessIcon } from './assets/Icons/SuccessIcon.tsx'; import { SuccessIcon } from './assets/Icons/SuccessIcon.tsx';
import { useAtom, useSetAtom } from 'jotai';
import { useResetAtom } from 'jotai/utils';
type extStates = type extStates =
| 'not-authenticated' | 'not-authenticated'
@ -330,9 +330,10 @@ function App() {
const [txList, setTxList] = useState([]); const [txList, setTxList] = useState([]);
const [memberGroups, setMemberGroups] = useState([]); const [memberGroups, setMemberGroups] = useState([]);
const [isFocused, setIsFocused] = useState(true); const [isFocused, setIsFocused] = useState(true);
const [hasSettingsChanged, setHasSettingsChanged] = useRecoilState( const [hasSettingsChanged, setHasSettingsChanged] = useAtom(
hasSettingsChangedAtom hasSettingsChangedAtom
); );
const balanceSetIntervalRef = useRef(null); const balanceSetIntervalRef = useRef(null);
const { downloadResource } = useFetchResources(); const { downloadResource } = useFetchResources();
const holdRefExtState = useRef<extStates>('not-authenticated'); const holdRefExtState = useRef<extStates>('not-authenticated');
@ -405,9 +406,10 @@ function App() {
const qortalRequestCheckbox1Ref = useRef(null); const qortalRequestCheckbox1Ref = useRef(null);
useRetrieveDataLocalStorage(userInfo?.address); useRetrieveDataLocalStorage(userInfo?.address);
useQortalGetSaveSettings(userInfo?.name, extState === 'authenticated'); useQortalGetSaveSettings(userInfo?.name, extState === 'authenticated');
const [isEnabledDevMode, setIsEnabledDevMode] = const setIsEnabledDevMode = useSetAtom(enabledDevModeAtom);
useRecoilState(enabledDevModeAtom);
const setIsDisabledEditorEnter = useSetRecoilState(isDisabledEditorEnterAtom); const setIsDisabledEditorEnter = useSetAtom(isDisabledEditorEnterAtom);
const [isOpenMinting, setIsOpenMinting] = useState(false); const [isOpenMinting, setIsOpenMinting] = useState(false);
const generatorRef = useRef(null); const generatorRef = useRef(null);
@ -453,45 +455,33 @@ function App() {
}, [extState, walletToBeDownloaded, shownTutorialsInitiated]); }, [extState, walletToBeDownloaded, shownTutorialsInitiated]);
//resets for recoil //resets for recoil
const resetAtomSortablePinnedAppsAtom = useResetRecoilState( const resetAtomSortablePinnedAppsAtom = useResetAtom(sortablePinnedAppsAtom);
sortablePinnedAppsAtom const resetAtomIsUsingImportExportSettingsAtom = useResetAtom(
);
const resetAtomIsUsingImportExportSettingsAtom = useResetRecoilState(
isUsingImportExportSettingsAtom isUsingImportExportSettingsAtom
); );
const resetAtomCanSaveSettingToQdnAtom = useResetRecoilState( const resetAtomCanSaveSettingToQdnAtom = useResetAtom(
canSaveSettingToQdnAtom canSaveSettingToQdnAtom
); );
const resetAtomSettingsQDNLastUpdatedAtom = useResetAtom(
const resetAtomSettingsQDNLastUpdatedAtom = useResetRecoilState(
settingsQDNLastUpdatedAtom settingsQDNLastUpdatedAtom
); );
const resetAtomSettingsLocalLastUpdatedAtom = useResetAtom(
const resetAtomSettingsLocalLastUpdatedAtom = useResetRecoilState(
settingsLocalLastUpdatedAtom settingsLocalLastUpdatedAtom
); );
const resetAtomOldPinnedAppsAtom = useResetAtom(oldPinnedAppsAtom);
const resetAtomOldPinnedAppsAtom = useResetRecoilState(oldPinnedAppsAtom); const resetAtomQMailLastEnteredTimestampAtom = useResetAtom(
const resetAtomQMailLastEnteredTimestampAtom = useResetRecoilState(
qMailLastEnteredTimestampAtom qMailLastEnteredTimestampAtom
); );
const resetAtomMailsAtom = useResetAtom(mailsAtom);
const resetAtomMailsAtom = useResetRecoilState(mailsAtom); const resetGroupPropertiesAtom = useResetAtom(groupsPropertiesAtom);
const resetGroupPropertiesAtom = useResetRecoilState(groupsPropertiesAtom); const resetLastPaymentSeenTimestampAtom = useResetAtom(
const resetLastPaymentSeenTimestampAtom = useResetRecoilState(
lastPaymentSeenTimestampAtom lastPaymentSeenTimestampAtom
); );
const resetGroupsOwnerNamesAtom = useResetRecoilState(groupsOwnerNamesAtom); const resetGroupsOwnerNamesAtom = useResetAtom(groupsOwnerNamesAtom);
const resetGroupAnnouncementsAtom = useResetRecoilState( const resetGroupAnnouncementsAtom = useResetAtom(groupAnnouncementsAtom);
groupAnnouncementsAtom const resetMutedGroupsAtom = useResetAtom(mutedGroupsAtom);
); const resetGroupChatTimestampsAtom = useResetAtom(groupChatTimestampsAtom);
const resetMutedGroupsAtom = useResetRecoilState(mutedGroupsAtom); const resetTimestampEnterAtom = useResetAtom(timestampEnterDataAtom);
const resetGroupChatTimestampsAtom = useResetRecoilState(
groupChatTimestampsAtom
);
const resetTimestampEnterAtom = useResetRecoilState(timestampEnterDataAtom);
const resetAllRecoil = () => { const resetAllRecoil = () => {
resetAtomSortablePinnedAppsAtom(); resetAtomSortablePinnedAppsAtom();
@ -1314,13 +1304,23 @@ function App() {
<Spacer height="32px" /> <Spacer height="32px" />
<CopyToClipboard text={rawWallet?.ltcAddress}> <ButtonBase
onClick={() => {
if (rawWallet?.ltcAddress) {
navigator.clipboard
.writeText(rawWallet.ltcAddress)
.catch((err) => {
console.error('Failed to copy LTC address:', err);
});
}
}}
>
<AddressBox> <AddressBox>
{rawWallet?.ltcAddress?.slice(0, 6)}... {rawWallet?.ltcAddress?.slice(0, 6)}...
{rawWallet?.ltcAddress?.slice(-4)}{' '} {rawWallet?.ltcAddress?.slice(-4)}{' '}
<CopyIcon color={theme.palette.text.primary} /> <CopyIcon color={theme.palette.text.primary} />
</AddressBox> </AddressBox>
</CopyToClipboard> </ButtonBase>
<Spacer height="10px" /> <Spacer height="10px" />
@ -1380,13 +1380,24 @@ function App() {
<Spacer height="10px" /> <Spacer height="10px" />
<CopyToClipboard text={rawWallet?.address0}> <ButtonBase
onClick={() => {
if (rawWallet?.address0) {
navigator.clipboard
.writeText(rawWallet.address0)
.catch((err) => {
console.error('Failed to copy address:', err);
});
}
}}
>
<AddressBox> <AddressBox>
{rawWallet?.address0?.slice(0, 6)}... {rawWallet?.address0?.slice(0, 6)}...
{rawWallet?.address0?.slice(-4)}{' '} {rawWallet?.address0?.slice(-4)}{' '}
<CopyIcon color={theme.palette.text.primary} /> <CopyIcon color={theme.palette.text.primary} />
</AddressBox> </AddressBox>
</CopyToClipboard> </ButtonBase>
<Spacer height="10px" /> <Spacer height="10px" />
{qortBalanceLoading && ( {qortBalanceLoading && (
<CircularProgress color="success" size={16} /> <CircularProgress color="success" size={16} />

View File

@ -1,273 +1,80 @@
import { atom, selectorFamily } from 'recoil'; import { atom } from 'jotai';
import { atomWithReset, atomFamily } from 'jotai/utils';
export const sortablePinnedAppsAtom = atom({ // Atoms (resettable)
key: 'sortablePinnedAppsFromAtom', export const sortablePinnedAppsAtom = atomWithReset([
default: [ { name: 'Q-Tube', service: 'APP' },
{ { name: 'Q-Mail', service: 'APP' },
name: 'Q-Tube', { name: 'Q-Share', service: 'APP' },
service: 'APP', { name: 'Q-Fund', service: 'APP' },
}, { name: 'Q-Shop', service: 'APP' },
{ { name: 'Q-Trade', service: 'APP' },
name: 'Q-Mail', { name: 'Q-Support', service: 'APP' },
service: 'APP', { name: 'Q-Manager', service: 'APP' },
}, { name: 'Q-Blog', service: 'APP' },
{ { name: 'Q-Mintership', service: 'APP' },
name: 'Q-Share', { name: 'Q-Wallets', service: 'APP' },
service: 'APP', { name: 'Q-Search', service: 'APP' },
}, { name: 'Q-Nodecontrol', service: 'APP' },
{ ]);
name: 'Q-Fund',
service: 'APP',
},
{
name: 'Q-Shop',
service: 'APP',
},
{
name: 'Q-Trade',
service: 'APP',
},
{
name: 'Q-Support',
service: 'APP',
},
{
name: 'Q-Manager',
service: 'APP',
},
{
name: 'Q-Blog',
service: 'APP',
},
{
name: 'Q-Mintership',
service: 'APP',
},
{
name: 'Q-Wallets',
service: 'APP',
},
{
name: 'Q-Search',
service: 'APP',
},
{
name: 'Q-Nodecontrol',
service: 'APP',
},
],
});
export const canSaveSettingToQdnAtom = atom({ export const canSaveSettingToQdnAtom = atomWithReset(false);
key: 'canSaveSettingToQdnAtom', export const settingsQDNLastUpdatedAtom = atomWithReset(-100);
default: false, export const settingsLocalLastUpdatedAtom = atomWithReset(0);
}); export const oldPinnedAppsAtom = atomWithReset([]);
export const isUsingImportExportSettingsAtom = atomWithReset(null);
export const fullScreenAtom = atomWithReset(false);
export const hasSettingsChangedAtom = atomWithReset(false);
export const navigationControllerAtom = atomWithReset({});
export const enabledDevModeAtom = atomWithReset(false);
export const myGroupsWhereIAmAdminAtom = atomWithReset([]);
export const promotionTimeIntervalAtom = atomWithReset(0);
export const promotionsAtom = atomWithReset([]);
export const resourceDownloadControllerAtom = atomWithReset({});
export const blobControllerAtom = atomWithReset({});
export const selectedGroupIdAtom = atomWithReset(null);
export const addressInfoControllerAtom = atomWithReset({});
export const isDisabledEditorEnterAtom = atomWithReset(false);
export const qMailLastEnteredTimestampAtom = atomWithReset(null);
export const lastPaymentSeenTimestampAtom = atomWithReset(null);
export const mailsAtom = atomWithReset([]);
export const groupsPropertiesAtom = atomWithReset({});
export const groupsOwnerNamesAtom = atomWithReset({});
export const isOpenBlockedModalAtom = atomWithReset(false);
export const groupAnnouncementsAtom = atomWithReset({});
export const mutedGroupsAtom = atomWithReset([]);
export const groupChatTimestampsAtom = atomWithReset({});
export const timestampEnterDataAtom = atomWithReset({});
export const settingsQDNLastUpdatedAtom = atom({ // Atom Families (replacing selectorFamily)
key: 'settingsQDNLastUpdatedAtom', export const resourceKeySelector = atomFamily((key) =>
default: -100, atom((get) => get(resourceDownloadControllerAtom)[key] || null)
}); );
export const settingsLocalLastUpdatedAtom = atom({ export const blobKeySelector = atomFamily((key) =>
key: 'settingsLocalLastUpdatedAtom', atom((get) => get(blobControllerAtom)[key] || null)
default: 0, );
});
export const oldPinnedAppsAtom = atom({ export const addressInfoKeySelector = atomFamily((key) =>
key: 'oldPinnedAppsAtom', atom((get) => get(addressInfoControllerAtom)[key] || null)
default: [], );
});
export const isUsingImportExportSettingsAtom = atom({ export const groupsOwnerNamesSelector = atomFamily((key) =>
key: 'isUsingImportExportSettingsAtom', atom((get) => get(groupsOwnerNamesAtom)[key] || null)
default: null, );
});
export const fullScreenAtom = atom({ export const groupAnnouncementSelector = atomFamily((key) =>
key: 'fullScreenAtom', atom((get) => get(groupAnnouncementsAtom)[key] || null)
default: false, );
});
export const hasSettingsChangedAtom = atom({ export const groupPropertySelector = atomFamily((key) =>
key: 'hasSettingsChangedAtom', atom((get) => get(groupsPropertiesAtom)[key] || null)
default: false, );
});
export const navigationControllerAtom = atom({ export const groupChatTimestampSelector = atomFamily((key) =>
key: 'navigationControllerAtom', atom((get) => get(groupChatTimestampsAtom)[key] || null)
default: {}, );
});
export const enabledDevModeAtom = atom({ export const timestampEnterDataSelector = atomFamily((key) =>
key: 'enabledDevModeAtom', atom((get) => get(timestampEnterDataAtom)[key] || null)
default: false, );
});
export const myGroupsWhereIAmAdminAtom = atom({
key: 'myGroupsWhereIAmAdminAtom',
default: [],
});
export const promotionTimeIntervalAtom = atom({
key: 'promotionTimeIntervalAtom',
default: 0,
});
export const promotionsAtom = atom({
key: 'promotionsAtom',
default: [],
});
export const resourceDownloadControllerAtom = atom({
key: 'resourceDownloadControllerAtom',
default: {},
});
export const resourceKeySelector = selectorFamily({
key: 'resourceKeySelector',
get:
(key) =>
({ get }) => {
const resources = get(resourceDownloadControllerAtom);
return resources[key] || null; // Return the value for the key or null if not found
},
});
export const blobControllerAtom = atom({
key: 'blobControllerAtom',
default: {},
});
export const blobKeySelector = selectorFamily({
key: 'blobKeySelector',
get:
(key) =>
({ get }) => {
const blobs = get(blobControllerAtom);
return blobs[key] || null; // Return the value for the key or null if not found
},
});
export const selectedGroupIdAtom = atom({
key: 'selectedGroupIdAtom',
default: null,
});
export const addressInfoControllerAtom = atom({
key: 'addressInfoControllerAtom',
default: {},
});
export const addressInfoKeySelector = selectorFamily({
key: 'addressInfoKeySelector',
get:
(key) =>
({ get }) => {
const userInfo = get(addressInfoControllerAtom);
return userInfo[key] || null; // Return the value for the key or null if not found
},
});
export const isDisabledEditorEnterAtom = atom({
key: 'isDisabledEditorEnterAtom',
default: false,
});
export const qMailLastEnteredTimestampAtom = atom({
key: 'qMailLastEnteredTimestampAtom',
default: null,
});
export const lastPaymentSeenTimestampAtom = atom<null | number>({
key: 'lastPaymentSeenTimestampAtom',
default: null,
});
export const mailsAtom = atom({
key: 'mailsAtom',
default: [],
});
export const groupsPropertiesAtom = atom({
key: 'groupsPropertiesAtom',
default: {},
});
export const groupsOwnerNamesAtom = atom({
key: 'groupsOwnerNamesAtom',
default: {},
});
export const isOpenBlockedModalAtom = atom({
key: 'isOpenBlockedModalAtom',
default: false,
});
export const groupsOwnerNamesSelector = selectorFamily({
key: 'groupsOwnerNamesSelector',
get:
(key) =>
({ get }) => {
const data = get(groupsOwnerNamesAtom);
return data[key] || null; // Return the value for the key or null if not found
},
});
export const groupAnnouncementsAtom = atom({
key: 'groupAnnouncementsAtom',
default: {},
});
export const groupAnnouncementSelector = selectorFamily({
key: 'groupAnnouncementSelector',
get:
(key) =>
({ get }) => {
const data = get(groupAnnouncementsAtom);
return data[key] || null; // Return the value for the key or null if not found
},
});
export const groupPropertySelector = selectorFamily({
key: 'groupPropertySelector',
get:
(key) =>
({ get }) => {
const data = get(groupsPropertiesAtom);
return data[key] || null; // Return the value for the key or null if not found
},
});
export const mutedGroupsAtom = atom({
key: 'mutedGroupsAtom',
default: [],
});
export const groupChatTimestampsAtom = atom({
key: 'groupChatTimestampsAtom',
default: {},
});
export const groupChatTimestampSelector = selectorFamily({
key: 'groupChatTimestampSelector',
get:
(key) =>
({ get }) => {
const data = get(groupChatTimestampsAtom);
return data[key] || null; // Return the value for the key or null if not found
},
});
export const timestampEnterDataAtom = atom({
key: 'timestampEnterDataAtom',
default: {},
});
export const timestampEnterDataSelector = selectorFamily({
key: 'timestampEnterDataSelector',
get:
(key) =>
({ get }) => {
const data = get(timestampEnterDataAtom);
return data[key] || null; // Return the value for the key or null if not found
},
});

View File

@ -1,168 +1,167 @@
import React, { useCallback, useRef } from 'react'; import React, { useCallback, useRef } from 'react';
import { useRecoilState } from 'recoil';
import { resourceDownloadControllerAtom } from '../atoms/global'; import { resourceDownloadControllerAtom } from '../atoms/global';
import { getBaseApiReact } from '../App'; import { getBaseApiReact } from '../App';
import { useSetAtom } from 'jotai';
export const useFetchResources = () => { export const useFetchResources = () => {
const [resources, setResources] = useRecoilState(resourceDownloadControllerAtom); const setResources = useSetAtom(resourceDownloadControllerAtom);
const downloadResource = useCallback(({ service, name, identifier }, build) => { const downloadResource = useCallback(
setResources((prev) => ({ ({ service, name, identifier }, build) => {
...prev, setResources((prev) => ({
[`${service}-${name}-${identifier}`]: { ...prev,
...(prev[`${service}-${name}-${identifier}`] || {}), [`${service}-${name}-${identifier}`]: {
service, ...(prev[`${service}-${name}-${identifier}`] || {}),
name, service,
identifier, name,
}, identifier,
})); },
}));
try { try {
let isCalling = false; let isCalling = false;
let percentLoaded = 0; let percentLoaded = 0;
let timer = 24; let timer = 24;
let tries = 0; let tries = 0;
let calledFirstTime = false let calledFirstTime = false;
let intervalId let intervalId;
let timeoutId let timeoutId;
const callFunction = async ()=> { const callFunction = async () => {
if (isCalling) return; if (isCalling) return;
isCalling = true; isCalling = true;
let res;
let res if (!build) {
if(!build){
const urlFirstTime = `${getBaseApiReact()}/arbitrary/resource/status/${service}/${name}/${identifier}`; const urlFirstTime = `${getBaseApiReact()}/arbitrary/resource/status/${service}/${name}/${identifier}`;
const resCall = await fetch(urlFirstTime, { const resCall = await fetch(urlFirstTime, {
method: "GET", method: 'GET',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
}, },
}); });
res = await resCall.json() res = await resCall.json();
if(tries > 18 ){ if (tries > 18) {
if(intervalId){ if (intervalId) {
clearInterval(intervalId) clearInterval(intervalId);
} }
if(timeoutId){ if (timeoutId) {
clearTimeout(timeoutId) clearTimeout(timeoutId);
} }
setResources((prev) => ({
...prev,
[`${service}-${name}-${identifier}`]: {
...(prev[`${service}-${name}-${identifier}`] || {}),
status: {
...res,
status: 'FAILED_TO_DOWNLOAD',
},
},
}));
return
}
tries = tries + 1
}
if(build || (calledFirstTime === false && res?.status !== 'READY')){
const url = `${getBaseApiReact()}/arbitrary/resource/properties/${service}/${name}/${identifier}?build=true`;
const resCall = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
res = await resCall.json();
}
calledFirstTime = true
isCalling = false;
if (res.localChunkCount) {
if (res.percentLoaded) {
if (res.percentLoaded === percentLoaded && res.percentLoaded !== 100) {
timer = timer - 5;
} else {
timer = 24;
}
if (timer < 0) {
timer = 24;
isCalling = true;
// Update Recoil state for refetching
setResources((prev) => ({ setResources((prev) => ({
...prev, ...prev,
[`${service}-${name}-${identifier}`]: { [`${service}-${name}-${identifier}`]: {
...(prev[`${service}-${name}-${identifier}`] || {}), ...(prev[`${service}-${name}-${identifier}`] || {}),
status: { status: {
...res, ...res,
status: 'REFETCHING', status: 'FAILED_TO_DOWNLOAD',
}, },
}, },
})); }));
timeoutId = setTimeout(() => {
isCalling = false;
downloadResource({ name, service, identifier }, true);
}, 25000);
return; return;
} }
tries = tries + 1;
percentLoaded = res.percentLoaded;
} }
// Update Recoil state for progress if (build || (calledFirstTime === false && res?.status !== 'READY')) {
setResources((prev) => ({ const url = `${getBaseApiReact()}/arbitrary/resource/properties/${service}/${name}/${identifier}?build=true`;
...prev, const resCall = await fetch(url, {
[`${service}-${name}-${identifier}`]: { method: 'GET',
...(prev[`${service}-${name}-${identifier}`] || {}),
status: res,
},
}));
}
// Check if progress is 100% and clear interval if true
if (res?.status === 'READY') {
if(intervalId){
clearInterval(intervalId);
}
if(timeoutId){
clearTimeout(timeoutId)
}
// Update Recoil state for completion
setResources((prev) => ({
...prev,
[`${service}-${name}-${identifier}`]: {
...(prev[`${service}-${name}-${identifier}`] || {}),
status: res,
},
}));
}
if(res?.status === 'DOWNLOADED'){
const url = `${getBaseApiReact()}/arbitrary/resource/status/${service}/${name}/${identifier}?build=true`;
const resCall = await fetch(url, {
method: "GET",
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
}, },
}); });
res = await resCall.json(); res = await resCall.json();
} }
calledFirstTime = true;
isCalling = false;
if (res.localChunkCount) {
if (res.percentLoaded) {
if (
res.percentLoaded === percentLoaded &&
res.percentLoaded !== 100
) {
timer = timer - 5;
} else {
timer = 24;
}
if (timer < 0) {
timer = 24;
isCalling = true;
// Update Recoil state for refetching
setResources((prev) => ({
...prev,
[`${service}-${name}-${identifier}`]: {
...(prev[`${service}-${name}-${identifier}`] || {}),
status: {
...res,
status: 'REFETCHING',
},
},
}));
timeoutId = setTimeout(() => {
isCalling = false;
downloadResource({ name, service, identifier }, true);
}, 25000);
return;
}
percentLoaded = res.percentLoaded;
}
// Update Recoil state for progress
setResources((prev) => ({
...prev,
[`${service}-${name}-${identifier}`]: {
...(prev[`${service}-${name}-${identifier}`] || {}),
status: res,
},
}));
}
// Check if progress is 100% and clear interval if true
if (res?.status === 'READY') {
if (intervalId) {
clearInterval(intervalId);
}
if (timeoutId) {
clearTimeout(timeoutId);
}
// Update Recoil state for completion
setResources((prev) => ({
...prev,
[`${service}-${name}-${identifier}`]: {
...(prev[`${service}-${name}-${identifier}`] || {}),
status: res,
},
}));
}
if (res?.status === 'DOWNLOADED') {
const url = `${getBaseApiReact()}/arbitrary/resource/status/${service}/${name}/${identifier}?build=true`;
const resCall = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
res = await resCall.json();
}
};
callFunction();
intervalId = setInterval(async () => {
callFunction();
}, 5000);
} catch (error) {
console.error('Error during resource fetch:', error);
} }
callFunction() },
intervalId = setInterval(async () => { [setResources]
callFunction() );
}, 5000);
} catch (error) {
console.error('Error during resource fetch:', error);
}
}, [setResources]);
return { downloadResource }; return { downloadResource };
}; };

View File

@ -28,21 +28,21 @@ import {
sortablePinnedAppsAtom, sortablePinnedAppsAtom,
} from '../../atoms/global'; } from '../../atoms/global';
import { saveToLocalStorage } from './AppsNavBarDesktop'; import { saveToLocalStorage } from './AppsNavBarDesktop';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { useAtom, useSetAtom } from 'jotai';
export const AppInfo = ({ app, myName }) => { export const AppInfo = ({ app, myName }) => {
const isInstalled = app?.status?.status === 'READY'; const isInstalled = app?.status?.status === 'READY';
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState( const [sortablePinnedApps, setSortablePinnedApps] = useAtom(
sortablePinnedAppsAtom sortablePinnedAppsAtom
); );
const theme = useTheme(); const theme = useTheme();
const isSelectedAppPinned = !!sortablePinnedApps?.find( const isSelectedAppPinned = !!sortablePinnedApps?.find(
(item) => item?.name === app?.name && item?.service === app?.service (item) => item?.name === app?.name && item?.service === app?.service
); );
const setSettingsLocalLastUpdated = useSetRecoilState( const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom);
settingsLocalLastUpdatedAtom
);
return ( return (
<AppsLibraryContainer <AppsLibraryContainer

View File

@ -17,12 +17,12 @@ import LogoSelected from '../../assets/svgs/LogoSelected.svg';
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
import { executeEvent } from '../../utils/events'; import { executeEvent } from '../../utils/events';
import { AppRating } from './AppRating'; import { AppRating } from './AppRating';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { import {
settingsLocalLastUpdatedAtom, settingsLocalLastUpdatedAtom,
sortablePinnedAppsAtom, sortablePinnedAppsAtom,
} from '../../atoms/global'; } from '../../atoms/global';
import { saveToLocalStorage } from './AppsNavBarDesktop'; import { saveToLocalStorage } from './AppsNavBarDesktop';
import { useAtom, useSetAtom } from 'jotai';
export const AppInfoSnippet = ({ export const AppInfoSnippet = ({
app, app,
@ -31,16 +31,15 @@ export const AppInfoSnippet = ({
parentStyles = {}, parentStyles = {},
}) => { }) => {
const isInstalled = app?.status?.status === 'READY'; const isInstalled = app?.status?.status === 'READY';
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState( const [sortablePinnedApps, setSortablePinnedApps] = useAtom(
sortablePinnedAppsAtom sortablePinnedAppsAtom
); );
const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom);
const isSelectedAppPinned = !!sortablePinnedApps?.find( const isSelectedAppPinned = !!sortablePinnedApps?.find(
(item) => item?.name === app?.name && item?.service === app?.service (item) => item?.name === app?.name && item?.service === app?.service
); );
const setSettingsLocalLastUpdated = useSetRecoilState(
settingsLocalLastUpdatedAtom
);
const theme = useTheme(); const theme = useTheme();
return ( return (

View File

@ -74,15 +74,18 @@ export const AppViewer = React.forwardRef(
}, [app, path, isDevMode]); }, [app, path, isDevMode]);
useEffect(() => { useEffect(() => {
if (!iframeRef?.current) return; const iframe = iframeRef?.current;
const targetOrigin = iframeRef.current if (!iframe) return;
? new URL(iframeRef.current.src).origin
: '*'; try {
// Send the navigation command after setting up the listener and timeout const targetOrigin = new URL(iframe.src).origin;
iframeRef.current.contentWindow.postMessage( iframe.contentWindow?.postMessage(
{ action: 'THEME_CHANGED', theme: themeMode, requestedHandler: 'UI' }, { action: 'THEME_CHANGED', theme: themeMode, requestedHandler: 'UI' },
targetOrigin targetOrigin
); );
} catch (err) {
console.error('Failed to send theme change to iframe:', err);
}
}, [themeMode]); }, [themeMode]);
const removeTrailingSlash = (str) => str.replace(/\/$/, ''); const removeTrailingSlash = (str) => str.replace(/\/$/, '');

View File

@ -20,11 +20,11 @@ import { HomeIcon } from '../../assets/Icons/HomeIcon';
import { MessagingIcon } from '../../assets/Icons/MessagingIcon'; import { MessagingIcon } from '../../assets/Icons/MessagingIcon';
import { Save } from '../Save/Save'; import { Save } from '../Save/Save';
import { IconWrapper } from '../Desktop/DesktopFooter'; import { IconWrapper } from '../Desktop/DesktopFooter';
import { useRecoilState } from 'recoil';
import { enabledDevModeAtom } from '../../atoms/global'; import { enabledDevModeAtom } from '../../atoms/global';
import { AppsIcon } from '../../assets/Icons/AppsIcon'; import { AppsIcon } from '../../assets/Icons/AppsIcon';
import { CoreSyncStatus } from '../CoreSyncStatus'; import { CoreSyncStatus } from '../CoreSyncStatus';
import { MessagingIconFilled } from '../../assets/Icons/MessagingIconFilled'; import { MessagingIconFilled } from '../../assets/Icons/MessagingIconFilled';
import { useAtom } from 'jotai';
const uid = new ShortUniqueId({ length: 8 }); const uid = new ShortUniqueId({ length: 8 });
@ -47,8 +47,8 @@ export const AppsDesktop = ({
const [isNewTabWindow, setIsNewTabWindow] = useState(false); const [isNewTabWindow, setIsNewTabWindow] = useState(false);
const [categories, setCategories] = useState([]); const [categories, setCategories] = useState([]);
const iframeRefs = useRef({}); const iframeRefs = useRef({});
const [isEnabledDevMode, setIsEnabledDevMode] = const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom);
useRecoilState(enabledDevModeAtom);
const { showTutorial } = useContext(GlobalContext); const { showTutorial } = useContext(GlobalContext);
const theme = useTheme(); const theme = useTheme();

View File

@ -13,16 +13,17 @@ import {
unsubscribeFromEvent, unsubscribeFromEvent,
} from '../../utils/events'; } from '../../utils/events';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import { useRecoilState } from 'recoil';
import { navigationControllerAtom } from '../../atoms/global'; import { navigationControllerAtom } from '../../atoms/global';
import { AppsDevModeTabComponent } from './AppsDevModeTabComponent'; import { AppsDevModeTabComponent } from './AppsDevModeTabComponent';
import { useAtom } from 'jotai';
export const AppsDevModeNavBar = () => { export const AppsDevModeNavBar = () => {
const [tabs, setTabs] = useState([]); const [tabs, setTabs] = useState([]);
const [selectedTab, setSelectedTab] = useState(null); const [selectedTab, setSelectedTab] = useState(null);
const [navigationController, setNavigationController] = useRecoilState( const [navigationController, setNavigationController] = useAtom(
navigationControllerAtom navigationControllerAtom
); );
const theme = useTheme(); const theme = useTheme();
const [isNewTabWindow, setIsNewTabWindow] = useState(false); const [isNewTabWindow, setIsNewTabWindow] = useState(false);
const tabsRef = useRef(null); const tabsRef = useRef(null);

View File

@ -26,12 +26,12 @@ import {
import TabComponent from './TabComponent'; import TabComponent from './TabComponent';
import PushPinIcon from '@mui/icons-material/PushPin'; import PushPinIcon from '@mui/icons-material/PushPin';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { import {
navigationControllerAtom, navigationControllerAtom,
settingsLocalLastUpdatedAtom, settingsLocalLastUpdatedAtom,
sortablePinnedAppsAtom, sortablePinnedAppsAtom,
} from '../../atoms/global'; } from '../../atoms/global';
import { useAtom, useSetAtom } from 'jotai';
export function saveToLocalStorage(key, subKey, newValue) { export function saveToLocalStorage(key, subKey, newValue) {
try { try {
@ -67,22 +67,21 @@ export function saveToLocalStorage(key, subKey, newValue) {
export const AppsNavBarDesktop = ({ disableBack }) => { export const AppsNavBarDesktop = ({ disableBack }) => {
const [tabs, setTabs] = useState([]); const [tabs, setTabs] = useState([]);
const [selectedTab, setSelectedTab] = useState(null); const [selectedTab, setSelectedTab] = useState(null);
const [navigationController, setNavigationController] = useRecoilState( const [navigationController, setNavigationController] = useAtom(
navigationControllerAtom navigationControllerAtom
); );
const [sortablePinnedApps, setSortablePinnedApps] = useAtom(
sortablePinnedAppsAtom
);
const theme = useTheme(); const theme = useTheme();
const [isNewTabWindow, setIsNewTabWindow] = useState(false); const [isNewTabWindow, setIsNewTabWindow] = useState(false);
const tabsRef = useRef(null); const tabsRef = useRef(null);
const [anchorEl, setAnchorEl] = useState(null); const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(
sortablePinnedAppsAtom
);
const setSettingsLocalLastUpdated = useSetRecoilState( const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom);
settingsLocalLastUpdatedAtom
);
const handleClick = (event) => { const handleClick = (event) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);

View File

@ -15,7 +15,6 @@ import {
} from '@mui/material'; } from '@mui/material';
import { useDropzone } from 'react-dropzone'; import { useDropzone } from 'react-dropzone';
import { useHandlePrivateApps } from './useHandlePrivateApps'; import { useHandlePrivateApps } from './useHandlePrivateApps';
import { useRecoilState } from 'recoil';
import { import {
groupsPropertiesAtom, groupsPropertiesAtom,
myGroupsWhereIAmAdminAtom, myGroupsWhereIAmAdminAtom,
@ -35,6 +34,7 @@ import { MyContext } from '../../App';
import { fileToBase64 } from '../../utils/fileReading'; import { fileToBase64 } from '../../utils/fileReading';
import { objectToBase64 } from '../../qdn/encryption/group-encryption'; import { objectToBase64 } from '../../qdn/encryption/group-encryption';
import { getFee } from '../../background'; import { getFee } from '../../background';
import { useAtom } from 'jotai';
const maxFileSize = 50 * 1024 * 1024; // 50MB const maxFileSize = 50 * 1024 * 1024; // 50MB
@ -44,11 +44,10 @@ export const AppsPrivate = ({ myName }) => {
const [logo, setLogo] = useState(null); const [logo, setLogo] = useState(null);
const [qortalUrl, setQortalUrl] = useState(''); const [qortalUrl, setQortalUrl] = useState('');
const [selectedGroup, setSelectedGroup] = useState(0); const [selectedGroup, setSelectedGroup] = useState(0);
const [groupsProperties] = useRecoilState(groupsPropertiesAtom);
const [valueTabPrivateApp, setValueTabPrivateApp] = useState(0); const [valueTabPrivateApp, setValueTabPrivateApp] = useState(0);
const [myGroupsWhereIAmAdminFromGlobal] = useRecoilState( const [groupsProperties] = useAtom(groupsPropertiesAtom);
myGroupsWhereIAmAdminAtom const [myGroupsWhereIAmAdminFromGlobal] = useAtom(myGroupsWhereIAmAdminAtom);
);
const myGroupsWhereIAmAdmin = useMemo(() => { const myGroupsWhereIAmAdmin = useMemo(() => {
return myGroupsWhereIAmAdminFromGlobal?.filter( return myGroupsWhereIAmAdminFromGlobal?.filter(

View File

@ -22,11 +22,11 @@ import {
settingsLocalLastUpdatedAtom, settingsLocalLastUpdatedAtom,
sortablePinnedAppsAtom, sortablePinnedAppsAtom,
} from '../../atoms/global'; } from '../../atoms/global';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { saveToLocalStorage } from './AppsNavBarDesktop'; import { saveToLocalStorage } from './AppsNavBarDesktop';
import { ContextMenuPinnedApps } from '../ContextMenuPinnedApps'; import { ContextMenuPinnedApps } from '../ContextMenuPinnedApps';
import LockIcon from '@mui/icons-material/Lock'; import LockIcon from '@mui/icons-material/Lock';
import { useHandlePrivateApps } from './useHandlePrivateApps'; import { useHandlePrivateApps } from './useHandlePrivateApps';
import { useAtom, useSetAtom } from 'jotai';
const SortableItem = ({ id, name, app, isDesktop }) => { const SortableItem = ({ id, name, app, isDesktop }) => {
const { openApp } = useHandlePrivateApps(); const { openApp } = useHandlePrivateApps();
@ -137,10 +137,8 @@ export const SortablePinnedApps = ({
myApp, myApp,
availableQapps = [], availableQapps = [],
}) => { }) => {
const [pinnedApps, setPinnedApps] = useRecoilState(sortablePinnedAppsAtom); const [pinnedApps, setPinnedApps] = useAtom(sortablePinnedAppsAtom);
const setSettingsLocalLastUpdated = useSetRecoilState( const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom);
settingsLocalLastUpdatedAtom
);
const transformPinnedApps = useMemo(() => { const transformPinnedApps = useMemo(() => {
// Clone the existing pinned apps list // Clone the existing pinned apps list

View File

@ -1,49 +1,44 @@
import React, { useContext, useState } from "react"; import React, { useContext, useState } from 'react';
import { executeEvent } from "../../utils/events"; import { executeEvent } from '../../utils/events';
import { getBaseApiReact, MyContext } from "../../App"; import { getBaseApiReact, MyContext } from '../../App';
import { createEndpoint } from "../../background"; import { createEndpoint } from '../../background';
import { useRecoilState, useSetRecoilState } from "recoil";
import { import {
settingsLocalLastUpdatedAtom, settingsLocalLastUpdatedAtom,
sortablePinnedAppsAtom, sortablePinnedAppsAtom,
} from "../../atoms/global"; } from '../../atoms/global';
import { saveToLocalStorage } from "./AppsNavBarDesktop"; import { saveToLocalStorage } from './AppsNavBarDesktop';
import { base64ToBlobUrl } from "../../utils/fileReading"; import { base64ToBlobUrl } from '../../utils/fileReading';
import { base64ToUint8Array } from "../../qdn/encryption/group-encryption"; import { base64ToUint8Array } from '../../qdn/encryption/group-encryption';
import { uint8ArrayToObject } from "../../backgroundFunctions/encryption"; import { uint8ArrayToObject } from '../../backgroundFunctions/encryption';
import { useAtom, useSetAtom } from 'jotai';
export const useHandlePrivateApps = () => { export const useHandlePrivateApps = () => {
const [status, setStatus] = useState(""); const [status, setStatus] = useState('');
const { const {
openSnackGlobal, openSnackGlobal,
setOpenSnackGlobal, setOpenSnackGlobal,
infoSnackCustom, infoSnackCustom,
setInfoSnackCustom, setInfoSnackCustom,
} = useContext(MyContext); } = useContext(MyContext);
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState( const setSortablePinnedApps = useSetAtom(sortablePinnedAppsAtom);
sortablePinnedAppsAtom
); const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom);
const setSettingsLocalLastUpdated = useSetRecoilState(
settingsLocalLastUpdatedAtom
);
const openApp = async ( const openApp = async (
privateAppProperties, privateAppProperties,
addToPinnedApps, addToPinnedApps,
setLoadingStatePrivateApp setLoadingStatePrivateApp
) => { ) => {
try { try {
if (setLoadingStatePrivateApp) {
if(setLoadingStatePrivateApp){
setLoadingStatePrivateApp(`Downloading and decrypting private app.`); setLoadingStatePrivateApp(`Downloading and decrypting private app.`);
} }
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
setInfoSnackCustom({ setInfoSnackCustom({
type: "info", type: 'info',
message: "Fetching app data", message: 'Fetching app data',
duration: null duration: null,
}); });
const urlData = `${getBaseApiReact()}/arbitrary/${ const urlData = `${getBaseApiReact()}/arbitrary/${
privateAppProperties?.service privateAppProperties?.service
@ -53,32 +48,30 @@ export const useHandlePrivateApps = () => {
let data; let data;
try { try {
const responseData = await fetch(urlData, { const responseData = await fetch(urlData, {
method: "GET", method: 'GET',
headers: { headers: {
"Content-Type": "application/json", 'Content-Type': 'application/json',
}, },
}); });
if(!responseData?.ok){ if (!responseData?.ok) {
if(setLoadingStatePrivateApp){ if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp("Error! Unable to download private app."); setLoadingStatePrivateApp('Error! Unable to download private app.');
} }
throw new Error("Unable to fetch app"); throw new Error('Unable to fetch app');
} }
data = await responseData.text(); data = await responseData.text();
if (data?.error) { if (data?.error) {
if(setLoadingStatePrivateApp){ if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp('Error! Unable to download private app.');
setLoadingStatePrivateApp("Error! Unable to download private app.");
} }
throw new Error("Unable to fetch app"); throw new Error('Unable to fetch app');
} }
} catch (error) { } catch (error) {
if(setLoadingStatePrivateApp){ if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp('Error! Unable to download private app.');
setLoadingStatePrivateApp("Error! Unable to download private app.");
} }
throw error; throw error;
} }
@ -87,7 +80,7 @@ export const useHandlePrivateApps = () => {
// eslint-disable-next-line no-useless-catch // eslint-disable-next-line no-useless-catch
try { try {
decryptedData = await window.sendMessage( decryptedData = await window.sendMessage(
"DECRYPT_QORTAL_GROUP_DATA", 'DECRYPT_QORTAL_GROUP_DATA',
{ {
base64: data, base64: data,
@ -95,16 +88,14 @@ export const useHandlePrivateApps = () => {
} }
); );
if (decryptedData?.error) { if (decryptedData?.error) {
if(setLoadingStatePrivateApp){ if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp('Error! Unable to decrypt private app.');
setLoadingStatePrivateApp("Error! Unable to decrypt private app.");
} }
throw new Error(decryptedData?.error); throw new Error(decryptedData?.error);
} }
} catch (error) { } catch (error) {
if(setLoadingStatePrivateApp){ if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp('Error! Unable to decrypt private app.');
setLoadingStatePrivateApp("Error! Unable to decrypt private app.");
} }
throw error; throw error;
} }
@ -112,19 +103,19 @@ export const useHandlePrivateApps = () => {
try { try {
const convertToUint = base64ToUint8Array(decryptedData); const convertToUint = base64ToUint8Array(decryptedData);
const UintToObject = uint8ArrayToObject(convertToUint); const UintToObject = uint8ArrayToObject(convertToUint);
if (decryptedData) { if (decryptedData) {
setInfoSnackCustom({ setInfoSnackCustom({
type: "info", type: 'info',
message: "Building app", message: 'Building app',
}); });
const endpoint = await createEndpoint( const endpoint = await createEndpoint(
`/arbitrary/APP/${privateAppProperties?.name}/zip?preview=true` `/arbitrary/APP/${privateAppProperties?.name}/zip?preview=true`
); );
const response = await fetch(endpoint, { const response = await fetch(endpoint, {
method: "POST", method: 'POST',
headers: { headers: {
"Content-Type": "text/plain", 'Content-Type': 'text/plain',
}, },
body: UintToObject?.app, body: UintToObject?.app,
}); });
@ -135,7 +126,7 @@ export const useHandlePrivateApps = () => {
); );
const res = await fetch(checkIfPreviewLinkStillWorksUrl); const res = await fetch(checkIfPreviewLinkStillWorksUrl);
if (res.ok) { if (res.ok) {
executeEvent("refreshApp", { executeEvent('refreshApp', {
tabId: tabId, tabId: tabId,
}); });
} else { } else {
@ -143,51 +134,50 @@ export const useHandlePrivateApps = () => {
`/arbitrary/APP/${privateAppProperties?.name}/zip?preview=true` `/arbitrary/APP/${privateAppProperties?.name}/zip?preview=true`
); );
const response = await fetch(endpoint, { const response = await fetch(endpoint, {
method: "POST", method: 'POST',
headers: { headers: {
"Content-Type": "text/plain", 'Content-Type': 'text/plain',
}, },
body: UintToObject?.app, body: UintToObject?.app,
}); });
const previewPath = await response.text(); const previewPath = await response.text();
executeEvent("updateAppUrl", { executeEvent('updateAppUrl', {
tabId: tabId, tabId: tabId,
url: await createEndpoint(previewPath), url: await createEndpoint(previewPath),
}); });
setTimeout(() => { setTimeout(() => {
executeEvent("refreshApp", { executeEvent('refreshApp', {
tabId: tabId, tabId: tabId,
}); });
}, 300); }, 300);
} }
}; };
const appName = UintToObject?.name; const appName = UintToObject?.name;
const logo = UintToObject?.logo const logo = UintToObject?.logo
? `data:image/png;base64,${UintToObject?.logo}` ? `data:image/png;base64,${UintToObject?.logo}`
: null; : null;
const dataBody = { const dataBody = {
url: await createEndpoint(previewPath), url: await createEndpoint(previewPath),
isPreview: true, isPreview: true,
isPrivate: true, isPrivate: true,
privateAppProperties: { ...privateAppProperties, logo, appName }, privateAppProperties: { ...privateAppProperties, logo, appName },
filePath: "", filePath: '',
refreshFunc: (tabId) => { refreshFunc: (tabId) => {
refreshfunc(tabId, privateAppProperties); refreshfunc(tabId, privateAppProperties);
}, },
}; };
executeEvent("addTab", { executeEvent('addTab', {
data: dataBody, data: dataBody,
}); });
setInfoSnackCustom({ setInfoSnackCustom({
type: "success", type: 'success',
message: "Opened", message: 'Opened',
}); });
if(setLoadingStatePrivateApp){ if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp(``);
setLoadingStatePrivateApp(``);
} }
if (addToPinnedApps) { if (addToPinnedApps) {
setSortablePinnedApps((prev) => { setSortablePinnedApps((prev) => {
@ -203,10 +193,10 @@ export const useHandlePrivateApps = () => {
}, },
}, },
]; ];
saveToLocalStorage( saveToLocalStorage(
"ext_saved_settings", 'ext_saved_settings',
"sortablePinnedApps", 'sortablePinnedApps',
updatedApps updatedApps
); );
return updatedApps; return updatedApps;
@ -215,20 +205,19 @@ export const useHandlePrivateApps = () => {
} }
} }
} catch (error) { } catch (error) {
if(setLoadingStatePrivateApp){ if (setLoadingStatePrivateApp) {
setLoadingStatePrivateApp(
setLoadingStatePrivateApp(`Error! ${error?.message || 'Unable to build private app.'}`); `Error! ${error?.message || 'Unable to build private app.'}`
);
} }
throw error throw error;
} }
} catch (error) {
setInfoSnackCustom({
type: 'error',
message: error?.message || 'Unable to fetch app',
});
} }
catch (error) {
setInfoSnackCustom({
type: "error",
message: error?.message || "Unable to fetch app",
});
}
}; };
return { return {
openApp, openApp,

View File

@ -1,15 +1,12 @@
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { executeEvent } from '../../utils/events'; import { executeEvent } from '../../utils/events';
import { useSetRecoilState } from 'recoil';
import { navigationControllerAtom } from '../../atoms/global'; import { navigationControllerAtom } from '../../atoms/global';
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem'; import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
import { saveFile } from '../../qortalRequests/get'; import { saveFile } from '../../qortalRequests/get';
import { mimeToExtensionMap } from '../../utils/memeTypes'; import { mimeToExtensionMap } from '../../utils/memeTypes';
import { MyContext } from '../../App'; import { MyContext } from '../../App';
import FileSaver from 'file-saver'; import FileSaver from 'file-saver';
import { useSetAtom } from 'jotai';
export const saveFileInChunks = async ( export const saveFileInChunks = async (
blob: Blob, blob: Blob,
@ -45,7 +42,6 @@ export const saveFileInChunks = async (
// Map MIME type to file extension // Map MIME type to file extension
const mimeTypeToExtension = (mimeType: string): string => { const mimeTypeToExtension = (mimeType: string): string => {
return mimeToExtensionMap[mimeType] || existingExtension || ''; // Use existing extension if MIME type not found return mimeToExtensionMap[mimeType] || existingExtension || ''; // Use existing extension if MIME type not found
}; };
@ -76,21 +72,18 @@ export const saveFileInChunks = async (
offset += chunkSize; offset += chunkSize;
isFirstChunk = false; isFirstChunk = false;
} }
} catch (error) { } catch (error) {
console.error('Error saving file in chunks:', error); console.error('Error saving file in chunks:', error);
} }
}; };
// Helper function to convert a Blob to a Base64 string // Helper function to convert a Blob to a Base64 string
const blobToBase64 = (blob: Blob): Promise<string> => { const blobToBase64 = (blob: Blob): Promise<string> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const reader = new FileReader(); const reader = new FileReader();
reader.onloadend = () => { reader.onloadend = () => {
const base64data = reader.result?.toString().split(",")[1]; const base64data = reader.result?.toString().split(',')[1];
resolve(base64data || ""); resolve(base64data || '');
}; };
reader.onerror = reject; reader.onerror = reject;
reader.readAsDataURL(blob); reader.readAsDataURL(blob);
@ -98,81 +91,81 @@ const blobToBase64 = (blob: Blob): Promise<string> => {
}; };
class Semaphore { class Semaphore {
constructor(count) { constructor(count) {
this.count = count this.count = count;
this.waiting = [] this.waiting = [];
} }
acquire() { acquire() {
return new Promise(resolve => { return new Promise((resolve) => {
if (this.count > 0) { if (this.count > 0) {
this.count-- this.count--;
resolve() resolve();
} else { } else {
this.waiting.push(resolve) this.waiting.push(resolve);
} }
})
}
release() {
if (this.waiting.length > 0) {
const resolve = this.waiting.shift()
resolve()
} else {
this.count++
}
}
}
let semaphore = new Semaphore(1)
let reader = new FileReader()
const fileToBase64 = (file) => new Promise(async (resolve, reject) => {
if (!reader) {
reader = new FileReader()
}
await semaphore.acquire()
reader.readAsDataURL(file)
reader.onload = () => {
const dataUrl = reader.result
if (typeof dataUrl === "string") {
const base64String = dataUrl.split(',')[1]
reader.onload = null
reader.onerror = null
resolve(base64String)
} else {
reader.onload = null
reader.onerror = null
reject(new Error('Invalid data URL'))
}
semaphore.release()
}
reader.onerror = (error) => {
reader.onload = null
reader.onerror = null
reject(error)
semaphore.release()
}
})
export function openIndexedDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open("fileStorageDB", 1);
request.onupgradeneeded = function (event) {
const db = event.target.result;
if (!db.objectStoreNames.contains("files")) {
db.createObjectStore("files", { keyPath: "id" });
}
};
request.onsuccess = function (event) {
resolve(event.target.result);
};
request.onerror = function () {
reject("Error opening IndexedDB");
};
}); });
} }
release() {
if (this.waiting.length > 0) {
const resolve = this.waiting.shift();
resolve();
} else {
this.count++;
}
}
}
let semaphore = new Semaphore(1);
let reader = new FileReader();
const fileToBase64 = (file) =>
new Promise(async (resolve, reject) => {
if (!reader) {
reader = new FileReader();
}
await semaphore.acquire();
reader.readAsDataURL(file);
reader.onload = () => {
const dataUrl = reader.result;
if (typeof dataUrl === 'string') {
const base64String = dataUrl.split(',')[1];
reader.onload = null;
reader.onerror = null;
resolve(base64String);
} else {
reader.onload = null;
reader.onerror = null;
reject(new Error('Invalid data URL'));
}
semaphore.release();
};
reader.onerror = (error) => {
reader.onload = null;
reader.onerror = null;
reject(error);
semaphore.release();
};
});
export function openIndexedDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('fileStorageDB', 1);
request.onupgradeneeded = function (event) {
const db = event.target.result;
if (!db.objectStoreNames.contains('files')) {
db.createObjectStore('files', { keyPath: 'id' });
}
};
request.onsuccess = function (event) {
resolve(event.target.result);
};
request.onerror = function () {
reject('Error opening IndexedDB');
};
});
}
export const listOfAllQortalRequests = [ export const listOfAllQortalRequests = [
'GET_USER_ACCOUNT', 'GET_USER_ACCOUNT',
@ -209,7 +202,7 @@ export const listOfAllQortalRequests = [
'DECRYPT_QORTAL_GROUP_DATA', 'DECRYPT_QORTAL_GROUP_DATA',
'DECRYPT_DATA_WITH_SHARING_KEY', 'DECRYPT_DATA_WITH_SHARING_KEY',
'DELETE_HOSTED_DATA', 'DELETE_HOSTED_DATA',
'GET_HOSTED_DATA', 'GET_HOSTED_DATA',
'PUBLISH_MULTIPLE_QDN_RESOURCES', 'PUBLISH_MULTIPLE_QDN_RESOURCES',
'PUBLISH_QDN_RESOURCE', 'PUBLISH_QDN_RESOURCE',
'ENCRYPT_DATA', 'ENCRYPT_DATA',
@ -259,8 +252,8 @@ export const listOfAllQortalRequests = [
'UPDATE_GROUP', 'UPDATE_GROUP',
'SELL_NAME', 'SELL_NAME',
'CANCEL_SELL_NAME', 'CANCEL_SELL_NAME',
'BUY_NAME' 'BUY_NAME',
] ];
export const UIQortalRequests = [ export const UIQortalRequests = [
'GET_USER_ACCOUNT', 'GET_USER_ACCOUNT',
@ -319,316 +312,327 @@ export const UIQortalRequests = [
'UPDATE_GROUP', 'UPDATE_GROUP',
'SELL_NAME', 'SELL_NAME',
'CANCEL_SELL_NAME', 'CANCEL_SELL_NAME',
'BUY_NAME' 'BUY_NAME',
]; ];
async function retrieveFileFromIndexedDB(fileId) {
const db = await openIndexedDB();
const transaction = db.transaction(['files'], 'readwrite');
const objectStore = transaction.objectStore('files');
return new Promise((resolve, reject) => {
const getRequest = objectStore.get(fileId);
getRequest.onsuccess = function (event) {
if (getRequest.result) {
async function retrieveFileFromIndexedDB(fileId) { // File found, resolve it and delete from IndexedDB
const file = getRequest.result.data;
objectStore.delete(fileId);
resolve(file);
} else {
reject('File not found in IndexedDB');
}
};
getRequest.onerror = function () {
reject('Error retrieving file from IndexedDB');
};
});
}
async function deleteQortalFilesFromIndexedDB() {
try {
const db = await openIndexedDB(); const db = await openIndexedDB();
const transaction = db.transaction(["files"], "readwrite"); const transaction = db.transaction(['files'], 'readwrite');
const objectStore = transaction.objectStore("files"); const objectStore = transaction.objectStore('files');
return new Promise((resolve, reject) => { // Create a request to get all keys
const getRequest = objectStore.get(fileId); const getAllKeysRequest = objectStore.getAllKeys();
getRequest.onsuccess = function (event) { getAllKeysRequest.onsuccess = function (event) {
if (getRequest.result) { const keys = event.target.result;
// File found, resolve it and delete from IndexedDB
const file = getRequest.result.data; // Iterate through keys to find and delete those containing '_qortalfile'
objectStore.delete(fileId); for (let key of keys) {
resolve(file); if (key.includes('_qortalfile')) {
} else { const deleteRequest = objectStore.delete(key);
reject("File not found in IndexedDB");
deleteRequest.onsuccess = function () {
console.log(
`File with key '${key}' has been deleted from IndexedDB`
);
};
deleteRequest.onerror = function () {
console.error(
`Failed to delete file with key '${key}' from IndexedDB`
);
};
} }
}; }
};
getRequest.onerror = function () {
reject("Error retrieving file from IndexedDB"); getAllKeysRequest.onerror = function () {
}; console.error('Failed to retrieve keys from IndexedDB');
}); };
}
transaction.oncomplete = function () {
async function deleteQortalFilesFromIndexedDB() { console.log('Transaction complete for deleting files from IndexedDB');
try { };
const db = await openIndexedDB();
const transaction = db.transaction(["files"], "readwrite"); transaction.onerror = function () {
const objectStore = transaction.objectStore("files"); console.error('Error occurred during transaction for deleting files');
};
// Create a request to get all keys } catch (error) {
const getAllKeysRequest = objectStore.getAllKeys(); console.error('Error opening IndexedDB:', error);
getAllKeysRequest.onsuccess = function (event) {
const keys = event.target.result;
// Iterate through keys to find and delete those containing '_qortalfile'
for (let key of keys) {
if (key.includes("_qortalfile")) {
const deleteRequest = objectStore.delete(key);
deleteRequest.onsuccess = function () {
console.log(`File with key '${key}' has been deleted from IndexedDB`);
};
deleteRequest.onerror = function () {
console.error(`Failed to delete file with key '${key}' from IndexedDB`);
};
}
}
};
getAllKeysRequest.onerror = function () {
console.error("Failed to retrieve keys from IndexedDB");
};
transaction.oncomplete = function () {
console.log("Transaction complete for deleting files from IndexedDB");
};
transaction.onerror = function () {
console.error("Error occurred during transaction for deleting files");
};
} catch (error) {
console.error("Error opening IndexedDB:", error);
}
} }
}
export const showSaveFilePicker = async (
data,
{ openSnackGlobal, setOpenSnackGlobal, infoSnackCustom, setInfoSnackCustom }
) => {
try {
const { filename, mimeType, blob, fileHandleOptions } = data;
setInfoSnackCustom({
type: 'info',
export const showSaveFilePicker = async (data, {openSnackGlobal, message: 'Saving file...',
setOpenSnackGlobal,
infoSnackCustom,
setInfoSnackCustom}) => {
try {
const { filename, mimeType, blob, fileHandleOptions } = data;
setInfoSnackCustom({
type: "info",
message:
"Saving file...",
});
setOpenSnackGlobal(true);
FileSaver.saveAs(blob, filename)
setInfoSnackCustom({
type: "success",
message:
"Saving file success!",
}); });
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
} catch (error) {
setInfoSnackCustom({ FileSaver.saveAs(blob, filename);
type: "error",
message: setInfoSnackCustom({
error?.message ? `Error saving file: ${error?.message}` : 'Error saving file', type: 'success',
}); message: 'Saving file success!',
});
setOpenSnackGlobal(true); setOpenSnackGlobal(true);
console.error("Error saving file:", error); } catch (error) {
setInfoSnackCustom({
type: 'error',
message: error?.message
? `Error saving file: ${error?.message}`
: 'Error saving file',
});
setOpenSnackGlobal(true);
console.error('Error saving file:', error);
}
};
declare var cordova: any;
async function storeFilesInIndexedDB(obj) {
// First delete any existing files in IndexedDB with '_qortalfile' in their ID
await deleteQortalFilesFromIndexedDB();
// Open the IndexedDB
const db = await openIndexedDB();
const transaction = db.transaction(['files'], 'readwrite');
const objectStore = transaction.objectStore('files');
// Handle the obj.file if it exists and is a File instance
if (obj.file) {
const fileId = Date.now() + 'objFile_qortalfile';
// Store the file in IndexedDB
const fileData = {
id: fileId,
data: obj.file,
};
objectStore.put(fileData);
// Replace the file object with the file ID in the original object
obj.fileId = fileId;
delete obj.file;
}
if (obj.blob) {
const fileId = Date.now() + 'objFile_qortalfile';
// Store the file in IndexedDB
const fileData = {
id: fileId,
data: obj.blob,
};
objectStore.put(fileData);
// Replace the file object with the file ID in the original object
let blobObj = {
type: obj.blob?.type,
};
obj.fileId = fileId;
delete obj.blob;
obj.blob = blobObj;
}
// Iterate through resources to find files and save them to IndexedDB
for (let resource of obj?.resources || []) {
if (resource.file) {
const fileId = resource.identifier + Date.now() + '_qortalfile';
// Store the file in IndexedDB
const fileData = {
id: fileId,
data: resource.file,
};
objectStore.put(fileData);
// Replace the file object with the file ID in the original object
resource.fileId = fileId;
delete resource.file;
} }
}
// Set transaction completion handlers
transaction.oncomplete = function () {
console.log('Files saved successfully to IndexedDB');
}; };
declare var cordova: any; transaction.onerror = function () {
console.error('Error saving files to IndexedDB');
};
return obj; // Updated object with references to stored files
}
export const useQortalMessageListener = (
async function storeFilesInIndexedDB(obj) { frameWindow,
// First delete any existing files in IndexedDB with '_qortalfile' in their ID iframeRef,
await deleteQortalFilesFromIndexedDB(); tabId,
isDevMode,
// Open the IndexedDB appName,
const db = await openIndexedDB(); appService,
const transaction = db.transaction(["files"], "readwrite"); skipAuth
const objectStore = transaction.objectStore("files"); ) => {
const [path, setPath] = useState('');
// Handle the obj.file if it exists and is a File instance
if (obj.file) {
const fileId = Date.now() + "objFile_qortalfile";
// Store the file in IndexedDB
const fileData = {
id: fileId,
data: obj.file,
};
objectStore.put(fileData);
// Replace the file object with the file ID in the original object
obj.fileId = fileId;
delete obj.file;
}
if (obj.blob) {
const fileId = Date.now() + "objFile_qortalfile";
// Store the file in IndexedDB
const fileData = {
id: fileId,
data: obj.blob,
};
objectStore.put(fileData);
// Replace the file object with the file ID in the original object
let blobObj = {
type: obj.blob?.type
}
obj.fileId = fileId;
delete obj.blob;
obj.blob = blobObj
}
// Iterate through resources to find files and save them to IndexedDB
for (let resource of (obj?.resources || [])) {
if (resource.file) {
const fileId = resource.identifier + Date.now() + "_qortalfile";
// Store the file in IndexedDB
const fileData = {
id: fileId,
data: resource.file,
};
objectStore.put(fileData);
// Replace the file object with the file ID in the original object
resource.fileId = fileId;
delete resource.file;
}
}
// Set transaction completion handlers
transaction.oncomplete = function () {
console.log("Files saved successfully to IndexedDB");
};
transaction.onerror = function () {
console.error("Error saving files to IndexedDB");
};
return obj; // Updated object with references to stored files
}
export const useQortalMessageListener = (frameWindow, iframeRef, tabId, isDevMode, appName, appService, skipAuth) => {
const [path, setPath] = useState('')
const [history, setHistory] = useState({ const [history, setHistory] = useState({
customQDNHistoryPaths: [], customQDNHistoryPaths: [],
currentIndex: -1, currentIndex: -1,
isDOMContentLoaded: false isDOMContentLoaded: false,
}) });
const setHasSettingsChangedAtom = useSetRecoilState(navigationControllerAtom); const setHasSettingsChangedAtom = useSetAtom(navigationControllerAtom);
const { openSnackGlobal,
const {
openSnackGlobal,
setOpenSnackGlobal, setOpenSnackGlobal,
infoSnackCustom, infoSnackCustom,
setInfoSnackCustom } = useContext(MyContext); setInfoSnackCustom,
} = useContext(MyContext);
useEffect(() => {
if (tabId && !isNaN(history?.currentIndex)) {
useEffect(()=> { setHasSettingsChangedAtom((prev) => {
if(tabId && !isNaN(history?.currentIndex)){
setHasSettingsChangedAtom((prev)=> {
return { return {
...prev, ...prev,
[tabId]: { [tabId]: {
hasBack: history?.currentIndex > 0, hasBack: history?.currentIndex > 0,
} },
} };
}) });
} }
}, [history?.currentIndex, tabId]) }, [history?.currentIndex, tabId]);
const changeCurrentIndex = useCallback((value) => {
const changeCurrentIndex = useCallback((value)=> { setHistory((prev) => {
setHistory((prev)=> {
return { return {
...prev, ...prev,
currentIndex: value currentIndex: value,
} };
}) });
}, []) }, []);
const resetHistory = useCallback(()=> { const resetHistory = useCallback(() => {
setHistory({ setHistory({
customQDNHistoryPaths: [], customQDNHistoryPaths: [],
currentIndex: -1, currentIndex: -1,
isManualNavigation: true, isManualNavigation: true,
isDOMContentLoaded: false isDOMContentLoaded: false,
}) });
}, []) }, []);
useEffect(() => { useEffect(() => {
const listener = async (event) => { const listener = async (event) => {
if (event?.data?.requestedHandler !== 'UI') return; if (event?.data?.requestedHandler !== 'UI') return;
const sendMessageToRuntime = (message, eventPort) => { const sendMessageToRuntime = (message, eventPort) => {
window.sendMessage(message.action, message.payload, 300000, message.isExtension, { window
name: appName, service: appService .sendMessage(
}, skipAuth) message.action,
.then((response) => { message.payload,
if (response.error) { 300000,
eventPort.postMessage({ message.isExtension,
result: null, {
error: { name: appName,
error: response?.error, service: appService,
message: typeof response?.error === 'string' ? response?.error : typeof response?.message === 'string' ? response?.message : 'An error has occurred' },
}, skipAuth
}); )
} else { .then((response) => {
eventPort.postMessage({ if (response.error) {
result: response, eventPort.postMessage({
error: null, result: null,
}); error: {
} error: response?.error,
}) message:
.catch((error) => { typeof response?.error === 'string'
console.error("Failed qortalRequest", error); ? response?.error
}); : typeof response?.message === 'string'
? response?.message
: 'An error has occurred',
},
});
} else {
eventPort.postMessage({
result: response,
error: null,
});
}
})
.catch((error) => {
console.error('Failed qortalRequest', error);
});
}; };
// Check if action is included in the predefined list of UI requests // Check if action is included in the predefined list of UI requests
if (UIQortalRequests.includes(event.data.action)) { if (UIQortalRequests.includes(event.data.action)) {
sendMessageToRuntime( sendMessageToRuntime(
{ action: event.data.action, type: 'qortalRequest', payload: event.data, isExtension: true }, {
action: event.data.action,
type: 'qortalRequest',
payload: event.data,
isExtension: true,
},
event.ports[0] event.ports[0]
); );
} else if(event?.data?.action === 'SAVE_FILE' } else if (event?.data?.action === 'SAVE_FILE') {
){
try { try {
const res = await saveFile( event.data, null, true, { const res = await saveFile(event.data, null, true, {
openSnackGlobal, openSnackGlobal,
setOpenSnackGlobal, setOpenSnackGlobal,
infoSnackCustom, infoSnackCustom,
setInfoSnackCustom setInfoSnackCustom,
}); });
} catch (error) {}
} catch (error) {
}
} else if ( } else if (
event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' || event?.data?.action === 'PUBLISH_MULTIPLE_QDN_RESOURCES' ||
event?.data?.action === 'PUBLISH_QDN_RESOURCE' || event?.data?.action === 'PUBLISH_QDN_RESOURCE' ||
event?.data?.action === 'ENCRYPT_DATA' || event?.data?.action === 'ENCRYPT_DATA_WITH_SHARING_KEY' || event?.data?.action === 'ENCRYPT_QORTAL_GROUP_DATA' event?.data?.action === 'ENCRYPT_DATA' ||
event?.data?.action === 'ENCRYPT_DATA_WITH_SHARING_KEY' ||
event?.data?.action === 'ENCRYPT_QORTAL_GROUP_DATA'
) { ) {
const data = event.data; const data = event.data;
if (data) { if (data) {
sendMessageToRuntime( sendMessageToRuntime(
{ action: event.data.action, type: 'qortalRequest', payload: data, isExtension: true }, {
action: event.data.action,
type: 'qortalRequest',
payload: data,
isExtension: true,
},
event.ports[0] event.ports[0]
); );
} else { } else {
@ -637,52 +641,73 @@ isDOMContentLoaded: false
error: 'Failed to prepare data for publishing', error: 'Failed to prepare data for publishing',
}); });
} }
} else if(event?.data?.action === 'LINK_TO_QDN_RESOURCE' || } else if (
event?.data?.action === 'QDN_RESOURCE_DISPLAYED'){ event?.data?.action === 'LINK_TO_QDN_RESOURCE' ||
const pathUrl = event?.data?.path != null ? (event?.data?.path.startsWith('/') ? '' : '/') + event?.data?.path : null event?.data?.action === 'QDN_RESOURCE_DISPLAYED'
setPath(pathUrl) ) {
if(appName?.toLowerCase() === 'q-mail'){ const pathUrl =
window.sendMessage("addEnteredQmailTimestamp").catch((error) => { event?.data?.path != null
? (event?.data?.path.startsWith('/') ? '' : '/') + event?.data?.path
: null;
setPath(pathUrl);
if (appName?.toLowerCase() === 'q-mail') {
window.sendMessage('addEnteredQmailTimestamp').catch((error) => {
// error // error
}); });
} else if(appName?.toLowerCase() === 'q-wallets'){ } else if (appName?.toLowerCase() === 'q-wallets') {
executeEvent('setLastEnteredTimestampPaymentEvent', {}) executeEvent('setLastEnteredTimestampPaymentEvent', {});
} }
} else if(event?.data?.action === 'NAVIGATION_HISTORY'){ } else if (event?.data?.action === 'NAVIGATION_HISTORY') {
if(event?.data?.payload?.isDOMContentLoaded){ if (event?.data?.payload?.isDOMContentLoaded) {
setHistory((prev)=> { setHistory((prev) => {
const copyPrev = {...prev} const copyPrev = { ...prev };
if((copyPrev?.customQDNHistoryPaths || []).at(-1) === (event?.data?.payload?.customQDNHistoryPaths || []).at(-1)) { if (
(copyPrev?.customQDNHistoryPaths || []).at(-1) ===
(event?.data?.payload?.customQDNHistoryPaths || []).at(-1)
) {
return { return {
...prev, ...prev,
currentIndex: prev.customQDNHistoryPaths.length - 1 === -1 ? 0 : prev.customQDNHistoryPaths.length - 1 currentIndex:
} prev.customQDNHistoryPaths.length - 1 === -1
? 0
: prev.customQDNHistoryPaths.length - 1,
};
} }
const copyHistory = {...prev} const copyHistory = { ...prev };
const paths = [...(copyHistory?.customQDNHistoryPaths.slice(0, copyHistory.currentIndex + 1) || []), ...(event?.data?.payload?.customQDNHistoryPaths || [])] const paths = [
...(copyHistory?.customQDNHistoryPaths.slice(
0,
copyHistory.currentIndex + 1
) || []),
...(event?.data?.payload?.customQDNHistoryPaths || []),
];
return { return {
...prev, ...prev,
customQDNHistoryPaths: paths, customQDNHistoryPaths: paths,
currentIndex: paths.length - 1 currentIndex: paths.length - 1,
} };
}) });
} else { } else {
setHistory(event?.data?.payload) setHistory(event?.data?.payload);
} }
} else if(event?.data?.action === 'SET_TAB' && !isDevMode){ } else if (event?.data?.action === 'SET_TAB' && !isDevMode) {
executeEvent("addTab", { executeEvent('addTab', {
data: event?.data?.payload data: event?.data?.payload,
}) });
const targetOrigin = iframeRef.current ? new URL(iframeRef.current.src).origin : "*"; const targetOrigin = iframeRef.current
? new URL(iframeRef.current.src).origin
: '*';
iframeRef.current.contentWindow.postMessage( iframeRef.current.contentWindow.postMessage(
{ action: 'SET_TAB_SUCCESS', requestedHandler: 'UI',payload: { {
name: event?.data?.payload?.name action: 'SET_TAB_SUCCESS',
} }, targetOrigin requestedHandler: 'UI',
payload: {
name: event?.data?.payload?.name,
},
},
targetOrigin
); );
} }
}; };
// Add the listener for messages coming from the frameWindow // Add the listener for messages coming from the frameWindow
@ -692,12 +717,7 @@ isDOMContentLoaded: false
return () => { return () => {
frameWindow.removeEventListener('message', listener); frameWindow.removeEventListener('message', listener);
}; };
}, [isDevMode, appName, appService]); // Empty dependency array to run once when the component mounts }, [isDevMode, appName, appService]); // Empty dependency array to run once when the component mounts
return { path, history, resetHistory, changeCurrentIndex };
return {path, history, resetHistory, changeCurrentIndex}
}; };

View File

@ -1,54 +0,0 @@
import {
MainContainer,
ChatContainer,
MessageList,
Message,
MessageInput,
Avatar,
} from '@chatscope/chat-ui-kit-react';
import '@chatscope/chat-ui-kit-styles/dist/default/styles.min.css';
export const ChatContainerComp = ({ messages }) => {
// const [messages, setMessages] = useState([
// { id: 1, text: "Hello! How are you?", sender: "Joe"},
// { id: 2, text: "I'm good, thank you!", sender: "Me" }
// ]);
// const loadMoreMessages = () => {
// // Simulate loading more messages (you could fetch these from an API)
// const moreMessages = [
// { id: 3, text: "What about you?", sender: "Joe", direction: "incoming" },
// { id: 4, text: "I'm great, thanks!", sender: "Me", direction: "outgoing" }
// ];
// setMessages((prevMessages) => [...moreMessages, ...prevMessages]);
// };
return (
<div style={{ height: '500px', width: '300px' }}>
<MainContainer>
<ChatContainer>
<MessageList>
{messages.map((msg) => (
<Message
key={msg.id}
model={{
message: msg.text,
sentTime: 'just now',
sender: msg.senderName,
direction: 'incoming',
position: 'single',
}}
>
{msg.direction === 'incoming' && (
<Avatar name={msg.senderName} />
)}
</Message>
))}
</MessageList>
<MessageInput placeholder="Type a message..." />
</ChatContainer>
</MainContainer>
</div>
);
};

View File

@ -8,7 +8,6 @@ import React, {
} from 'react'; } from 'react';
import { ChatList } from './ChatList'; import { ChatList } from './ChatList';
import '@chatscope/chat-ui-kit-styles/dist/default/styles.min.css';
import Tiptap from './TipTap'; import Tiptap from './TipTap';
import { CustomButton } from '../../styles/App-styles'; import { CustomButton } from '../../styles/App-styles';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';

View File

@ -12,7 +12,6 @@ import {
objectToBase64, objectToBase64,
} from '../../qdn/encryption/group-encryption'; } from '../../qdn/encryption/group-encryption';
import { ChatList } from './ChatList'; import { ChatList } from './ChatList';
import '@chatscope/chat-ui-kit-styles/dist/default/styles.min.css';
import Tiptap from './TipTap'; import Tiptap from './TipTap';
import { CustomButton } from '../../styles/App-styles'; import { CustomButton } from '../../styles/App-styles';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
@ -1238,8 +1237,6 @@ export const ChatGroup = ({
</Box> </Box>
)} )}
{/* <ChatContainerComp messages={formatMessages} /> */}
<LoadingSnackbar <LoadingSnackbar
open={isLoading} open={isLoading}
info={{ info={{

View File

@ -10,7 +10,6 @@ import {
base64ToUint8Array, base64ToUint8Array,
objectToBase64, objectToBase64,
} from '../../qdn/encryption/group-encryption'; } from '../../qdn/encryption/group-encryption';
import '@chatscope/chat-ui-kit-styles/dist/default/styles.min.css';
import Tiptap from './TipTap'; import Tiptap from './TipTap';
import { CustomButton } from '../../styles/App-styles'; import { CustomButton } from '../../styles/App-styles';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';

View File

@ -27,9 +27,9 @@ import tippy from 'tippy.js';
import 'tippy.js/dist/tippy.css'; import 'tippy.js/dist/tippy.css';
import { ReactRenderer } from '@tiptap/react'; import { ReactRenderer } from '@tiptap/react';
import MentionList from './MentionList.jsx'; import MentionList from './MentionList.jsx';
import { useRecoilState } from 'recoil';
import { isDisabledEditorEnterAtom } from '../../atoms/global.js'; import { isDisabledEditorEnterAtom } from '../../atoms/global.js';
import { Box, Checkbox, Typography, useTheme } from '@mui/material'; import { Box, Checkbox, Typography, useTheme } from '@mui/material';
import { useAtom } from 'jotai';
function textMatcher(doc, from) { function textMatcher(doc, from) {
const textBeforeCursor = doc.textBetween(0, from, ' ', ' '); const textBeforeCursor = doc.textBetween(0, from, ' ', ' ');
@ -368,9 +368,10 @@ export default ({
enableMentions, enableMentions,
}) => { }) => {
const theme = useTheme(); const theme = useTheme();
const [isDisabledEditorEnter, setIsDisabledEditorEnter] = useRecoilState( const [isDisabledEditorEnter, setIsDisabledEditorEnter] = useAtom(
isDisabledEditorEnterAtom isDisabledEditorEnterAtom
); );
const extensionsFiltered = isChat const extensionsFiltered = isChat
? extensions.filter((item) => item?.name !== 'image') ? extensions.filter((item) => item?.name !== 'image')
: extensions; : extensions;

View File

@ -10,8 +10,8 @@ import {
import MailOutlineIcon from '@mui/icons-material/MailOutline'; import MailOutlineIcon from '@mui/icons-material/MailOutline';
import NotificationsOffIcon from '@mui/icons-material/NotificationsOff'; import NotificationsOffIcon from '@mui/icons-material/NotificationsOff';
import { executeEvent } from '../utils/events'; import { executeEvent } from '../utils/events';
import { useRecoilState } from 'recoil';
import { mutedGroupsAtom } from '../atoms/global'; import { mutedGroupsAtom } from '../atoms/global';
import { useAtom } from 'jotai';
const CustomStyledMenu = styled(Menu)(({ theme }) => ({ const CustomStyledMenu = styled(Menu)(({ theme }) => ({
'& .MuiPaper-root': { '& .MuiPaper-root': {
@ -35,7 +35,8 @@ export const ContextMenu = ({ children, groupId, getUserSettings }) => {
const longPressTimeout = useRef(null); const longPressTimeout = useRef(null);
const preventClick = useRef(false); // Flag to prevent click after long-press or right-click const preventClick = useRef(false); // Flag to prevent click after long-press or right-click
const theme = useTheme(); const theme = useTheme();
const [mutedGroups] = useRecoilState(mutedGroupsAtom); const [mutedGroups] = useAtom(mutedGroupsAtom);
const isMuted = useMemo(() => { const isMuted = useMemo(() => {
return mutedGroups.includes(groupId); return mutedGroups.includes(groupId);
}, [mutedGroups, groupId]); }, [mutedGroups, groupId]);

View File

@ -9,8 +9,8 @@ import {
} from '@mui/material'; } from '@mui/material';
import PushPinIcon from '@mui/icons-material/PushPin'; import PushPinIcon from '@mui/icons-material/PushPin';
import { saveToLocalStorage } from './Apps/AppsNavBarDesktop'; import { saveToLocalStorage } from './Apps/AppsNavBarDesktop';
import { useRecoilState } from 'recoil';
import { sortablePinnedAppsAtom } from '../atoms/global'; import { sortablePinnedAppsAtom } from '../atoms/global';
import { useSetAtom } from 'jotai';
const CustomStyledMenu = styled(Menu)(({ theme }) => ({ const CustomStyledMenu = styled(Menu)(({ theme }) => ({
'& .MuiPaper-root': { '& .MuiPaper-root': {
@ -34,9 +34,9 @@ export const ContextMenuPinnedApps = ({ children, app, isMine }) => {
const maxHoldTimeout = useRef(null); const maxHoldTimeout = useRef(null);
const preventClick = useRef(false); const preventClick = useRef(false);
const startTouchPosition = useRef({ x: 0, y: 0 }); // Track initial touch position const startTouchPosition = useRef({ x: 0, y: 0 }); // Track initial touch position
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(
sortablePinnedAppsAtom const setSortablePinnedApps = useSetAtom(sortablePinnedAppsAtom);
);
const theme = useTheme(); const theme = useTheme();
const handleContextMenu = (event) => { const handleContextMenu = (event) => {

View File

@ -6,8 +6,9 @@ import AppIcon from '../../assets/svgs/AppIcon.svg';
import { HomeIcon } from '../../assets/Icons/HomeIcon'; import { HomeIcon } from '../../assets/Icons/HomeIcon';
import { Save } from '../Save/Save'; import { Save } from '../Save/Save';
import { useRecoilState } from 'recoil';
import { enabledDevModeAtom } from '../../atoms/global'; import { enabledDevModeAtom } from '../../atoms/global';
import { useAtom } from 'jotai';
export const IconWrapper = ({ export const IconWrapper = ({
children, children,
@ -65,8 +66,7 @@ export const DesktopFooter = ({
setIsOpenSideViewDirects, setIsOpenSideViewDirects,
setIsOpenSideViewGroups, setIsOpenSideViewGroups,
}) => { }) => {
const [isEnabledDevMode, setIsEnabledDevMode] = const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom);
useRecoilState(enabledDevModeAtom);
const theme = useTheme(); const theme = useTheme();

View File

@ -2,13 +2,13 @@ import { Box, ButtonBase, useTheme } from '@mui/material';
import { HomeIcon } from '../assets/Icons/HomeIcon'; import { HomeIcon } from '../assets/Icons/HomeIcon';
import { Save } from './Save/Save'; import { Save } from './Save/Save';
import { IconWrapper } from './Desktop/DesktopFooter'; import { IconWrapper } from './Desktop/DesktopFooter';
import { useRecoilState } from 'recoil';
import { enabledDevModeAtom } from '../atoms/global'; import { enabledDevModeAtom } from '../atoms/global';
import { AppsIcon } from '../assets/Icons/AppsIcon'; import { AppsIcon } from '../assets/Icons/AppsIcon';
import ThemeSelector from './Theme/ThemeSelector'; import ThemeSelector from './Theme/ThemeSelector';
import { CoreSyncStatus } from './CoreSyncStatus'; import { CoreSyncStatus } from './CoreSyncStatus';
import LanguageSelector from './Language/LanguageSelector'; import LanguageSelector from './Language/LanguageSelector';
import { MessagingIconFilled } from '../assets/Icons/MessagingIconFilled'; import { MessagingIconFilled } from '../assets/Icons/MessagingIconFilled';
import { useAtom } from 'jotai';
export const DesktopSideBar = ({ export const DesktopSideBar = ({
goToHome, goToHome,
@ -24,13 +24,10 @@ export const DesktopSideBar = ({
desktopViewMode, desktopViewMode,
myName, myName,
}) => { }) => {
const [isEnabledDevMode, setIsEnabledDevMode] = const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom);
useRecoilState(enabledDevModeAtom);
const theme = useTheme(); const theme = useTheme();
console.log('test', desktopViewMode === 'home');
return ( return (
<Box <Box
sx={{ sx={{

View File

@ -27,8 +27,7 @@ import { Spacer } from '../../common/Spacer';
import { FileAttachmentContainer, FileAttachmentFont } from './Embed-styles'; import { FileAttachmentContainer, FileAttachmentFont } from './Embed-styles';
import DownloadIcon from '@mui/icons-material/Download'; import DownloadIcon from '@mui/icons-material/Download';
import SaveIcon from '@mui/icons-material/Save'; import SaveIcon from '@mui/icons-material/Save';
import { useSetRecoilState } from 'recoil';
import { blobControllerAtom } from '../../atoms/global';
import { decodeIfEncoded } from '../../utils/decode'; import { decodeIfEncoded } from '../../utils/decode';
export const AttachmentCard = ({ export const AttachmentCard = ({

View File

@ -7,7 +7,6 @@ import { extractComponents } from '../Chat/MessageDisplay';
import { executeEvent } from '../../utils/events'; import { executeEvent } from '../../utils/events';
import { base64ToBlobUrl } from '../../utils/fileReading'; import { base64ToBlobUrl } from '../../utils/fileReading';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { import {
blobControllerAtom, blobControllerAtom,
blobKeySelector, blobKeySelector,
@ -19,6 +18,7 @@ import { PollCard } from './PollEmbed';
import { ImageCard } from './ImageEmbed'; import { ImageCard } from './ImageEmbed';
import { AttachmentCard } from './AttachmentEmbed'; import { AttachmentCard } from './AttachmentEmbed';
import { decodeIfEncoded } from '../../utils/decode'; import { decodeIfEncoded } from '../../utils/decode';
import { useAtom, useAtomValue, useSetAtom } from 'jotai';
const getPoll = async (name) => { const getPoll = async (name) => {
const pollName = name; const pollName = name;
@ -63,8 +63,9 @@ export const Embed = ({ embedLink }) => {
const [external, setExternal] = useState(null); const [external, setExternal] = useState(null);
const [imageUrl, setImageUrl] = useState(''); const [imageUrl, setImageUrl] = useState('');
const [parsedData, setParsedData] = useState(null); const [parsedData, setParsedData] = useState(null);
const setBlobs = useSetRecoilState(blobControllerAtom); const setBlobs = useSetAtom(blobControllerAtom);
const [selectedGroupId] = useRecoilState(selectedGroupIdAtom); const [selectedGroupId] = useAtom(selectedGroupIdAtom);
const resourceData = useMemo(() => { const resourceData = useMemo(() => {
const parsedDataOnTheFly = parseQortalLink(embedLink); const parsedDataOnTheFly = parseQortalLink(embedLink);
if ( if (
@ -98,7 +99,8 @@ export const Embed = ({ embedLink }) => {
return undefined; return undefined;
} }
}, [resourceData]); }, [resourceData]);
const blobUrl = useRecoilValue(blobKeySelector(keyIdentifier));
const blobUrl = useAtomValue(blobKeySelector(keyIdentifier));
const handlePoll = async (parsedData) => { const handlePoll = async (parsedData) => {
try { try {
@ -312,7 +314,7 @@ export const Embed = ({ embedLink }) => {
hasFetched.current = true; hasFetched.current = true;
}, [embedLink]); }, [embedLink]);
const resourceDetails = useRecoilValue(resourceKeySelector(keyIdentifier)); const resourceDetails = useAtomValue(resourceKeySelector(keyIdentifier));
const { parsedType, encryptionType } = useMemo(() => { const { parsedType, encryptionType } = useMemo(() => {
let parsedType; let parsedType;

View File

@ -19,7 +19,8 @@ import { Menu, MenuItem } from '@mui/material';
import { MoreVert as MoreIcon } from '@mui/icons-material'; import { MoreVert as MoreIcon } from '@mui/icons-material';
import { GlobalContext, getBaseApiReact } from '../../App'; import { GlobalContext, getBaseApiReact } from '../../App';
import { resourceKeySelector } from '../../atoms/global'; import { resourceKeySelector } from '../../atoms/global';
import { useRecoilValue } from 'recoil';
import { useAtomValue } from 'jotai';
const VideoContainer = styled(Box)` const VideoContainer = styled(Box)`
position: relative; position: relative;
display: flex; display: flex;
@ -80,7 +81,9 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
return undefined; return undefined;
} }
}, [service, name, identifier]); }, [service, name, identifier]);
const download = useRecoilValue(resourceKeySelector(keyIdentifier));
const download = useAtomValue(resourceKeySelector(keyIdentifier));
const { downloadResource } = useContext(GlobalContext); const { downloadResource } = useContext(GlobalContext);
const videoRef = useRef<HTMLVideoElement | null>(null); const videoRef = useRef<HTMLVideoElement | null>(null);

View File

@ -21,15 +21,16 @@ import {
import { validateAddress } from '../../utils/validateAddress'; import { validateAddress } from '../../utils/validateAddress';
import { getNameInfo, requestQueueMemberNames } from './Group'; import { getNameInfo, requestQueueMemberNames } from './Group';
import { useModal } from '../../common/useModal'; import { useModal } from '../../common/useModal';
import { useRecoilState } from 'recoil';
import { isOpenBlockedModalAtom } from '../../atoms/global'; import { isOpenBlockedModalAtom } from '../../atoms/global';
import InfoIcon from '@mui/icons-material/Info'; import InfoIcon from '@mui/icons-material/Info';
import { useAtom } from 'jotai';
export const BlockedUsersModal = () => { export const BlockedUsersModal = () => {
const theme = useTheme(); const theme = useTheme();
const [isOpenBlockedModal, setIsOpenBlockedModal] = useRecoilState( const [isOpenBlockedModal, setIsOpenBlockedModal] = useAtom(
isOpenBlockedModalAtom isOpenBlockedModalAtom
); );
const [hasChanged, setHasChanged] = useState(false); const [hasChanged, setHasChanged] = useState(false);
const [value, setValue] = useState(''); const [value, setValue] = useState('');
const [addressesWithNames, setAddressesWithNames] = useState({}); const [addressesWithNames, setAddressesWithNames] = useState({});

View File

@ -516,12 +516,7 @@ export const NewThread = ({
overrideMobile overrideMobile
customEditorHeight="240px" customEditorHeight="240px"
/> />
{/* <TextEditor
inlineContent={value}
setInlineContent={(val: any) => {
setValue(val);
}}
/> */}
</Box> </Box>
</InstanceListContainer> </InstanceListContainer>

View File

@ -1,40 +0,0 @@
import ReactQuill, { Quill } from 'react-quill';
import 'react-quill/dist/quill.snow.css';
import ImageResize from 'quill-image-resize-module-react';
import './texteditor.css';
Quill.register('modules/imageResize', ImageResize);
const modules = {
imageResize: {
parchment: Quill.import('parchment'),
modules: ['Resize', 'DisplaySize'],
},
toolbar: [
['bold', 'italic', 'underline', 'strike'], // styled text
['blockquote', 'code-block'], // blocks
[{ header: 1 }, { header: 2 }], // custom button values
[{ list: 'ordered' }, { list: 'bullet' }], // lists
[{ script: 'sub' }, { script: 'super' }], // superscript/subscript
[{ indent: '-1' }, { indent: '+1' }], // outdent/indent
[{ direction: 'rtl' }], // text direction
[{ size: ['small', false, 'large', 'huge'] }], // custom dropdown
[{ header: [1, 2, 3, 4, 5, 6, false] }], // custom button values
[{ color: [] }, { background: [] }], // dropdown with defaults
[{ font: [] }], // font family
[{ align: [] }], // text align
['clean'], // remove formatting
// ["image"], // image
],
};
export const TextEditor = ({ inlineContent, setInlineContent }: any) => {
return (
<ReactQuill
theme="snow"
value={inlineContent}
onChange={setInlineContent}
modules={modules}
/>
);
};

View File

@ -1,70 +0,0 @@
.ql-editor {
min-height: 200px;
width: 100%;
color: black;
font-size: 16px;
font-family: Roboto;
max-height: 225px;
overflow-y: scroll;
padding: 0px !important;
}
.ql-editor::-webkit-scrollbar-track {
background-color: transparent;
cursor: default;
}
.ql-editor::-webkit-scrollbar-track:hover {
background-color: transparent;
}
.ql-editor::-webkit-scrollbar {
width: 16px;
height: 10px;
background-color: rgba(229, 229, 229, 0.7);
}
.ql-editor::-webkit-scrollbar-thumb {
background-color: #b0b0b0;
border-radius: 8px;
background-clip: content-box;
border: 4px solid transparent;
}
.ql-editor img {
cursor: default;
}
.ql-editor-display {
min-height: 20px;
width: 100%;
color: black;
font-size: 16px;
font-family: Roboto;
padding: 0px !important;
}
.ql-editor-display img {
cursor: default;
}
.ql-container {
font-size: 16px;
}
.ql-toolbar .ql-stroke {
fill: none !important;
stroke: black !important;
}
.ql-toolbar .ql-fill {
fill: black !important;
stroke: none !important;
}
.ql-toolbar .ql-picker {
color: black !important;
}
.ql-toolbar .ql-picker-options {
background-color: white !important;
}

View File

@ -65,7 +65,7 @@ import { HubsIcon } from '../../assets/Icons/HubsIcon';
import { MessagingIcon } from '../../assets/Icons/MessagingIcon'; import { MessagingIcon } from '../../assets/Icons/MessagingIcon';
import { formatEmailDate } from './QMailMessages'; import { formatEmailDate } from './QMailMessages';
import { AdminSpace } from '../Chat/AdminSpace'; import { AdminSpace } from '../Chat/AdminSpace';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { import {
addressInfoControllerAtom, addressInfoControllerAtom,
groupAnnouncementsAtom, groupAnnouncementsAtom,
@ -85,6 +85,7 @@ import { BlockedUsersModal } from './BlockedUsersModal';
import { WalletsAppWrapper } from './WalletsAppWrapper'; import { WalletsAppWrapper } from './WalletsAppWrapper';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { GroupList } from './GroupList'; import { GroupList } from './GroupList';
import { useAtom, useSetAtom } from 'jotai';
export const getPublishesFromAdmins = async (admins: string[], groupId) => { export const getPublishesFromAdmins = async (admins: string[], groupId) => {
const queryString = admins.map((name) => `name=${name}`).join('&'); const queryString = admins.map((name) => `name=${name}`).join('&');
@ -415,9 +416,10 @@ export const Group = ({
const { setMemberGroups, rootHeight, isRunningPublicNode } = const { setMemberGroups, rootHeight, isRunningPublicNode } =
useContext(MyContext); useContext(MyContext);
const lastGroupNotification = useRef<null | number>(null); const lastGroupNotification = useRef<null | number>(null);
const [timestampEnterData, setTimestampEnterData] = useRecoilState( const [timestampEnterData, setTimestampEnterData] = useAtom(
timestampEnterDataAtom timestampEnterDataAtom
); );
const [chatMode, setChatMode] = useState('groups'); const [chatMode, setChatMode] = useState('groups');
const [newChat, setNewChat] = useState(false); const [newChat, setNewChat] = useState(false);
const [openSnack, setOpenSnack] = React.useState(false); const [openSnack, setOpenSnack] = React.useState(false);
@ -428,18 +430,19 @@ export const Group = ({
const [firstSecretKeyInCreation, setFirstSecretKeyInCreation] = const [firstSecretKeyInCreation, setFirstSecretKeyInCreation] =
React.useState(false); React.useState(false);
const [groupSection, setGroupSection] = React.useState('home'); const [groupSection, setGroupSection] = React.useState('home');
const [groupAnnouncements, setGroupAnnouncements] = useRecoilState( const [groupAnnouncements, setGroupAnnouncements] = useAtom(
groupAnnouncementsAtom groupAnnouncementsAtom
); );
const [defaultThread, setDefaultThread] = React.useState(null); const [defaultThread, setDefaultThread] = React.useState(null);
const [isOpenDrawer, setIsOpenDrawer] = React.useState(false); const [isOpenDrawer, setIsOpenDrawer] = React.useState(false);
const setIsOpenBlockedUserModal = useSetRecoilState(isOpenBlockedModalAtom); const setIsOpenBlockedUserModal = useSetAtom(isOpenBlockedModalAtom);
const [hideCommonKeyPopup, setHideCommonKeyPopup] = React.useState(false); const [hideCommonKeyPopup, setHideCommonKeyPopup] = React.useState(false);
const [isLoadingGroupMessage, setIsLoadingGroupMessage] = React.useState(''); const [isLoadingGroupMessage, setIsLoadingGroupMessage] = React.useState('');
const [drawerMode, setDrawerMode] = React.useState('groups'); const [drawerMode, setDrawerMode] = React.useState('groups');
const setMutedGroups = useSetRecoilState(mutedGroupsAtom); const setMutedGroups = useSetAtom(mutedGroupsAtom);
const [mobileViewMode, setMobileViewMode] = useState('home'); const [mobileViewMode, setMobileViewMode] = useState('home');
const [mobileViewModeKeepOpen, setMobileViewModeKeepOpen] = useState(''); const [mobileViewModeKeepOpen, setMobileViewModeKeepOpen] = useState('');
const isFocusedRef = useRef(true); const isFocusedRef = useRef(true);
@ -453,9 +456,10 @@ export const Group = ({
const settimeoutForRefetchSecretKey = useRef(null); const settimeoutForRefetchSecretKey = useRef(null);
const { clearStatesMessageQueueProvider } = useMessageQueue(); const { clearStatesMessageQueueProvider } = useMessageQueue();
const initiatedGetMembers = useRef(false); const initiatedGetMembers = useRef(false);
const [groupChatTimestamps, setGroupChatTimestamps] = useRecoilState( const [groupChatTimestamps, setGroupChatTimestamps] = useAtom(
groupChatTimestampsAtom groupChatTimestampsAtom
); );
const [appsMode, setAppsMode] = useState('home'); const [appsMode, setAppsMode] = useState('home');
const [appsModeDev, setAppsModeDev] = useState('home'); const [appsModeDev, setAppsModeDev] = useState('home');
const [isOpenSideViewDirects, setIsOpenSideViewDirects] = useState(false); const [isOpenSideViewDirects, setIsOpenSideViewDirects] = useState(false);
@ -465,12 +469,10 @@ export const Group = ({
const groupsOwnerNamesRef = useRef({}); const groupsOwnerNamesRef = useRef({});
const { t } = useTranslation(['core', 'group']); const { t } = useTranslation(['core', 'group']);
const [groupsProperties, setGroupsProperties] = const [groupsProperties, setGroupsProperties] = useAtom(groupsPropertiesAtom);
useRecoilState(groupsPropertiesAtom); const setGroupsOwnerNames = useSetAtom(groupsOwnerNamesAtom);
const [groupsOwnerNames, setGroupsOwnerNames] =
useRecoilState(groupsOwnerNamesAtom);
const setUserInfoForLevels = useSetRecoilState(addressInfoControllerAtom); const setUserInfoForLevels = useSetAtom(addressInfoControllerAtom);
const isPrivate = useMemo(() => { const isPrivate = useMemo(() => {
if (selectedGroup?.groupId === '0') return false; if (selectedGroup?.groupId === '0') return false;
@ -481,7 +483,8 @@ export const Group = ({
return null; return null;
}, [selectedGroup]); }, [selectedGroup]);
const setSelectedGroupId = useSetRecoilState(selectedGroupIdAtom); const setSelectedGroupId = useSetAtom(selectedGroupIdAtom);
const toggleSideViewDirects = () => { const toggleSideViewDirects = () => {
if (isOpenSideViewGroups) { if (isOpenSideViewGroups) {
setIsOpenSideViewGroups(false); setIsOpenSideViewGroups(false);

View File

@ -11,10 +11,10 @@ import { Box, ButtonBase, Collapse, Typography, useTheme } from '@mui/material';
import { CustomLoader } from '../../common/CustomLoader'; import { CustomLoader } from '../../common/CustomLoader';
import { MyContext, getBaseApiReact } from '../../App'; import { MyContext, getBaseApiReact } from '../../App';
import { myGroupsWhereIAmAdminAtom } from '../../atoms/global'; import { myGroupsWhereIAmAdminAtom } from '../../atoms/global';
import { useSetRecoilState } from 'recoil';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSetAtom } from 'jotai';
export const requestQueueGroupJoinRequests = new RequestQueueWithPromise(2); export const requestQueueGroupJoinRequests = new RequestQueueWithPromise(2);
export const GroupJoinRequests = ({ export const GroupJoinRequests = ({
@ -34,7 +34,8 @@ export const GroupJoinRequests = ({
); );
const [loading, setLoading] = React.useState(true); const [loading, setLoading] = React.useState(true);
const { txList, setTxList } = React.useContext(MyContext); const { txList, setTxList } = React.useContext(MyContext);
const setMyGroupsWhereIAmAdmin = useSetRecoilState(myGroupsWhereIAmAdminAtom); const setMyGroupsWhereIAmAdmin = useSetAtom(myGroupsWhereIAmAdminAtom);
const theme = useTheme(); const theme = useTheme();
const getJoinRequests = async () => { const getJoinRequests = async () => {
try { try {

View File

@ -28,8 +28,9 @@ import {
groupsOwnerNamesSelector, groupsOwnerNamesSelector,
timestampEnterDataSelector, timestampEnterDataSelector,
} from '../../atoms/global'; } from '../../atoms/global';
import { useRecoilValue } from 'recoil';
import { timeDifferenceForNotificationChats } from './Group'; import { timeDifferenceForNotificationChats } from './Group';
import { useAtomValue } from 'jotai';
export const GroupList = ({ export const GroupList = ({
selectGroupFunc, selectGroupFunc,
@ -211,17 +212,18 @@ export const GroupList = ({
const GroupItem = React.memo( const GroupItem = React.memo(
({ selectGroupFunc, group, selectedGroup, getUserSettings, myAddress }) => { ({ selectGroupFunc, group, selectedGroup, getUserSettings, myAddress }) => {
const theme = useTheme(); const theme = useTheme();
const ownerName = useRecoilValue(groupsOwnerNamesSelector(group?.groupId)); const ownerName = useAtomValue(groupsOwnerNamesSelector(group?.groupId));
const announcement = useRecoilValue( const announcement = useAtomValue(
groupAnnouncementSelector(group?.groupId) groupAnnouncementSelector(group?.groupId)
); );
const groupProperty = useRecoilValue(groupPropertySelector(group?.groupId)); const groupProperty = useAtomValue(groupPropertySelector(group?.groupId));
const groupChatTimestamp = useRecoilValue( const groupChatTimestamp = useAtomValue(
groupChatTimestampSelector(group?.groupId) groupChatTimestampSelector(group?.groupId)
); );
const timestampEnterData = useRecoilValue( const timestampEnterData = useAtomValue(
timestampEnterDataSelector(group?.groupId) timestampEnterDataSelector(group?.groupId)
); );
const selectGroupHandler = useCallback(() => { const selectGroupHandler = useCallback(() => {
selectGroupFunc(group); selectGroupFunc(group);
}, [group, selectGroupFunc]); }, [group, selectGroupFunc]);

View File

@ -34,7 +34,7 @@ import {
import { Spacer } from '../../common/Spacer'; import { Spacer } from '../../common/Spacer';
import { CustomLoader } from '../../common/CustomLoader'; import { CustomLoader } from '../../common/CustomLoader';
import { RequestQueueWithPromise } from '../../utils/queue/queue'; import { RequestQueueWithPromise } from '../../utils/queue/queue';
import { useRecoilState } from 'recoil';
import { import {
myGroupsWhereIAmAdminAtom, myGroupsWhereIAmAdminAtom,
promotionTimeIntervalAtom, promotionTimeIntervalAtom,
@ -49,6 +49,7 @@ import ErrorBoundary from '../../common/ErrorBoundary';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import { getFee } from '../../background'; import { getFee } from '../../background';
import { useAtom } from 'jotai';
export const requestQueuePromos = new RequestQueueWithPromise(3); export const requestQueuePromos = new RequestQueueWithPromise(3);
export function utf8ToBase64(inputString: string): string { export function utf8ToBase64(inputString: string): string {
@ -77,13 +78,14 @@ export const ListOfGroupPromotions = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [isShowModal, setIsShowModal] = useState(false); const [isShowModal, setIsShowModal] = useState(false);
const [text, setText] = useState(''); const [text, setText] = useState('');
const [myGroupsWhereIAmAdmin, setMyGroupsWhereIAmAdmin] = useRecoilState( const [myGroupsWhereIAmAdmin, setMyGroupsWhereIAmAdmin] = useAtom(
myGroupsWhereIAmAdminAtom myGroupsWhereIAmAdminAtom
); );
const [promotions, setPromotions] = useRecoilState(promotionsAtom); const [promotions, setPromotions] = useAtom(promotionsAtom);
const [promotionTimeInterval, setPromotionTimeInterval] = useRecoilState( const [promotionTimeInterval, setPromotionTimeInterval] = useAtom(
promotionTimeIntervalAtom promotionTimeIntervalAtom
); );
const [isExpanded, setIsExpanded] = React.useState(false); const [isExpanded, setIsExpanded] = React.useState(false);
const [openSnack, setOpenSnack] = useState(false); const [openSnack, setOpenSnack] = useState(false);

View File

@ -11,11 +11,12 @@ import MailIcon from '@mui/icons-material/Mail';
import MailOutlineIcon from '@mui/icons-material/MailOutline'; import MailOutlineIcon from '@mui/icons-material/MailOutline';
import { executeEvent } from '../../utils/events'; import { executeEvent } from '../../utils/events';
import { CustomLoader } from '../../common/CustomLoader'; import { CustomLoader } from '../../common/CustomLoader';
import { useRecoilState } from 'recoil';
import { mailsAtom, qMailLastEnteredTimestampAtom } from '../../atoms/global'; import { mailsAtom, qMailLastEnteredTimestampAtom } from '../../atoms/global';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import MarkEmailUnreadIcon from '@mui/icons-material/MarkEmailUnread'; import MarkEmailUnreadIcon from '@mui/icons-material/MarkEmailUnread';
import { useAtom } from 'jotai';
export const isLessThanOneWeekOld = (timestamp) => { export const isLessThanOneWeekOld = (timestamp) => {
// Current time in milliseconds // Current time in milliseconds
@ -46,10 +47,11 @@ export function formatEmailDate(timestamp: number) {
export const QMailMessages = ({ userName, userAddress }) => { export const QMailMessages = ({ userName, userAddress }) => {
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
const [mails, setMails] = useRecoilState(mailsAtom); const [mails, setMails] = useAtom(mailsAtom);
const [lastEnteredTimestamp, setLastEnteredTimestamp] = useRecoilState( const [lastEnteredTimestamp, setLastEnteredTimestamp] = useAtom(
qMailLastEnteredTimestampAtom qMailLastEnteredTimestampAtom
); );
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const theme = useTheme(); const theme = useTheme();

View File

@ -17,8 +17,9 @@ import Slide from '@mui/material/Slide';
import { TransitionProps } from '@mui/material/transitions'; import { TransitionProps } from '@mui/material/transitions';
import { Box, FormControlLabel, Switch, styled, useTheme } from '@mui/material'; import { Box, FormControlLabel, Switch, styled, useTheme } from '@mui/material';
import { enabledDevModeAtom } from '../../atoms/global'; import { enabledDevModeAtom } from '../../atoms/global';
import { useRecoilState } from 'recoil';
import ThemeManager from '../Theme/ThemeManager'; import ThemeManager from '../Theme/ThemeManager';
import { useAtom } from 'jotai';
const LocalNodeSwitch = styled(Switch)(({ theme }) => ({ const LocalNodeSwitch = styled(Switch)(({ theme }) => ({
padding: 8, padding: 8,
@ -64,8 +65,8 @@ const Transition = forwardRef(function Transition(
export const Settings = ({ address, open, setOpen }) => { export const Settings = ({ address, open, setOpen }) => {
const [checked, setChecked] = useState(false); const [checked, setChecked] = useState(false);
const [isEnabledDevMode, setIsEnabledDevMode] = const [isEnabledDevMode, setIsEnabledDevMode] = useAtom(enabledDevModeAtom);
useRecoilState(enabledDevModeAtom);
const theme = useTheme(); const theme = useTheme();
const handleChange = (event: ChangeEvent<HTMLInputElement>) => { const handleChange = (event: ChangeEvent<HTMLInputElement>) => {

View File

@ -7,18 +7,19 @@ import {
subscribeToEvent, subscribeToEvent,
unsubscribeFromEvent, unsubscribeFromEvent,
} from '../../utils/events'; } from '../../utils/events';
import { useRecoilState } from 'recoil';
import { navigationControllerAtom } from '../../atoms/global'; import { navigationControllerAtom } from '../../atoms/global';
import { AppsNavBarLeft, AppsNavBarParent } from '../Apps/Apps-styles'; import { AppsNavBarLeft, AppsNavBarParent } from '../Apps/Apps-styles';
import { NavBack } from '../../assets/Icons/NavBack.tsx'; import { NavBack } from '../../assets/Icons/NavBack.tsx';
import RefreshIcon from '@mui/icons-material/Refresh'; import RefreshIcon from '@mui/icons-material/Refresh';
import { useAtom } from 'jotai';
export const WalletsAppWrapper = () => { export const WalletsAppWrapper = () => {
const iframeRef = useRef(null); const iframeRef = useRef(null);
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [navigationController, setNavigationController] = useRecoilState( const [navigationController, setNavigationController] = useAtom(
navigationControllerAtom navigationControllerAtom
); );
const [selectedTab, setSelectedTab] = useState({ const [selectedTab, setSelectedTab] = useState({
tabId: '5558589', tabId: '5558589',
name: 'Q-Wallets', name: 'Q-Wallets',

View File

@ -1,20 +1,20 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useRecoilState } from 'recoil';
import { mailsAtom, qMailLastEnteredTimestampAtom } from '../atoms/global'; import { mailsAtom, qMailLastEnteredTimestampAtom } from '../atoms/global';
import { isLessThanOneWeekOld } from './Group/QMailMessages'; import { isLessThanOneWeekOld } from './Group/QMailMessages';
import { ButtonBase, Tooltip, useTheme } from '@mui/material'; import { ButtonBase, Tooltip, useTheme } from '@mui/material';
import { executeEvent } from '../utils/events'; import { executeEvent } from '../utils/events';
import { Mail } from '@mui/icons-material'; import { Mail } from '@mui/icons-material';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useAtom } from 'jotai';
export const QMailStatus = () => { export const QMailStatus = () => {
const { t } = useTranslation(['core']); const { t } = useTranslation(['core']);
const theme = useTheme(); const theme = useTheme();
const [lastEnteredTimestamp, setLastEnteredTimestamp] = useRecoilState( const [lastEnteredTimestamp, setLastEnteredTimestamp] = useAtom(
qMailLastEnteredTimestampAtom qMailLastEnteredTimestampAtom
); );
const [mails, setMails] = useRecoilState(mailsAtom); const [mails, setMails] = useAtom(mailsAtom);
const hasNewMail = useMemo(() => { const hasNewMail = useMemo(() => {
if (mails?.length === 0) return false; if (mails?.length === 0) return false;

View File

@ -1,5 +1,4 @@
import { useContext, useEffect, useMemo, useState } from 'react'; import { useContext, useEffect, useMemo, useState } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import isEqual from 'lodash/isEqual'; // TODO Import deep comparison utility import isEqual from 'lodash/isEqual'; // TODO Import deep comparison utility
import { import {
canSaveSettingToQdnAtom, canSaveSettingToQdnAtom,
@ -34,6 +33,7 @@ import {
uint8ArrayToObject, uint8ArrayToObject,
} from '../../backgroundFunctions/encryption'; } from '../../backgroundFunctions/encryption';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useAtom, useSetAtom } from 'jotai';
export const handleImportClick = async () => { export const handleImportClick = async () => {
const fileInput = document.createElement('input'); const fileInput = document.createElement('input');
@ -66,22 +66,21 @@ export const handleImportClick = async () => {
}; };
export const Save = ({ isDesktop, disableWidth, myName }) => { export const Save = ({ isDesktop, disableWidth, myName }) => {
const [pinnedApps, setPinnedApps] = useRecoilState(sortablePinnedAppsAtom); const [pinnedApps, setPinnedApps] = useAtom(sortablePinnedAppsAtom);
const [settingsQdnLastUpdated, setSettingsQdnLastUpdated] = useRecoilState( const [settingsQdnLastUpdated, setSettingsQdnLastUpdated] = useAtom(
settingsQDNLastUpdatedAtom settingsQDNLastUpdatedAtom
); );
const [settingsLocalLastUpdated] = useRecoilState( const [settingsLocalLastUpdated] = useAtom(settingsLocalLastUpdatedAtom);
settingsLocalLastUpdatedAtom const setHasSettingsChangedAtom = useSetAtom(hasSettingsChangedAtom);
const [isUsingImportExportSettings, setIsUsingImportExportSettings] = useAtom(
isUsingImportExportSettingsAtom
); );
const setHasSettingsChangedAtom = useSetRecoilState(hasSettingsChangedAtom);
const [isUsingImportExportSettings, setIsUsingImportExportSettings] =
useRecoilState(isUsingImportExportSettingsAtom);
const [canSave] = useRecoilState(canSaveSettingToQdnAtom);
const [openSnack, setOpenSnack] = useState(false); const [openSnack, setOpenSnack] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [infoSnack, setInfoSnack] = useState(null); const [infoSnack, setInfoSnack] = useState(null);
const [oldPinnedApps, setOldPinnedApps] = useRecoilState(oldPinnedAppsAtom); const [oldPinnedApps, setOldPinnedApps] = useAtom(oldPinnedAppsAtom);
const [anchorEl, setAnchorEl] = useState(null); const [anchorEl, setAnchorEl] = useState(null);
const { show } = useContext(MyContext); const { show } = useContext(MyContext);
const theme = useTheme(); const theme = useTheme();

View File

@ -2,9 +2,9 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { getBaseApiReact } from '../App'; import { getBaseApiReact } from '../App';
import { getData, storeData } from '../utils/chromeStorage'; import { getData, storeData } from '../utils/chromeStorage';
import { checkDifference, getNameInfoForOthers } from '../background'; import { checkDifference, getNameInfoForOthers } from '../background';
import { useRecoilState } from 'recoil';
import { lastPaymentSeenTimestampAtom } from '../atoms/global'; import { lastPaymentSeenTimestampAtom } from '../atoms/global';
import { subscribeToEvent, unsubscribeFromEvent } from '../utils/events'; import { subscribeToEvent, unsubscribeFromEvent } from '../utils/events';
import { useAtom } from 'jotai';
export const useHandlePaymentNotification = (address) => { export const useHandlePaymentNotification = (address) => {
const [latestTx, setLatestTx] = useState(null); const [latestTx, setLatestTx] = useState(null);
@ -12,8 +12,9 @@ export const useHandlePaymentNotification = (address) => {
const nameAddressOfSender = useRef({}); const nameAddressOfSender = useRef({});
const isFetchingName = useRef({}); const isFetchingName = useRef({});
const [lastEnteredTimestampPayment, setLastEnteredTimestampPayment] = const [lastEnteredTimestampPayment, setLastEnteredTimestampPayment] = useAtom(
useRecoilState(lastPaymentSeenTimestampAtom); lastPaymentSeenTimestampAtom
);
useEffect(() => { useEffect(() => {
if (lastEnteredTimestampPayment && address) { if (lastEnteredTimestampPayment && address) {

View File

@ -1,21 +1,18 @@
import ReactDOM from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import App from './App.tsx'; import App from './App.tsx';
import '../src/styles/index.css'; import '../src/styles/index.css';
import './messaging/messagesToBackground'; import './messaging/messagesToBackground';
import { MessageQueueProvider } from './MessageQueueContext.tsx'; import { MessageQueueProvider } from './MessageQueueContext.tsx';
import { RecoilRoot } from 'recoil';
import { ThemeProvider } from './components/Theme/ThemeContext.tsx'; import { ThemeProvider } from './components/Theme/ThemeContext.tsx';
import { CssBaseline } from '@mui/material'; import { CssBaseline } from '@mui/material';
import '../i18n'; import '../i18n';
ReactDOM.createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<> <>
<ThemeProvider> <ThemeProvider>
<CssBaseline /> <CssBaseline />
<MessageQueueProvider> <MessageQueueProvider>
<RecoilRoot> <App />
<App />
</RecoilRoot>
</MessageQueueProvider> </MessageQueueProvider>
</ThemeProvider> </ThemeProvider>
</> </>

View File

@ -1,5 +1,4 @@
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { import {
canSaveSettingToQdnAtom, canSaveSettingToQdnAtom,
isUsingImportExportSettingsAtom, isUsingImportExportSettingsAtom,
@ -14,6 +13,7 @@ import {
base64ToUint8Array, base64ToUint8Array,
uint8ArrayToObject, uint8ArrayToObject,
} from './backgroundFunctions/encryption'; } from './backgroundFunctions/encryption';
import { useAtom, useSetAtom } from 'jotai';
function fetchFromLocalStorage(key) { function fetchFromLocalStorage(key) {
try { try {
@ -67,19 +67,15 @@ const getPublish = async (myName) => {
}; };
export const useQortalGetSaveSettings = (myName, isAuthenticated) => { export const useQortalGetSaveSettings = (myName, isAuthenticated) => {
const setSortablePinnedApps = useSetRecoilState(sortablePinnedAppsAtom); const setSortablePinnedApps = useSetAtom(sortablePinnedAppsAtom);
const setCanSave = useSetRecoilState(canSaveSettingToQdnAtom); const setCanSave = useSetAtom(canSaveSettingToQdnAtom);
const setSettingsQDNLastUpdated = useSetRecoilState( const setSettingsQDNLastUpdated = useSetAtom(settingsQDNLastUpdatedAtom);
settingsQDNLastUpdatedAtom
); const [settingsLocalLastUpdated] = useAtom(settingsLocalLastUpdatedAtom);
const [settingsLocalLastUpdated] = useRecoilState( const [isUsingImportExportSettings] = useAtom(
settingsLocalLastUpdatedAtom
);
const [isUsingImportExportSettings] = useRecoilState(
isUsingImportExportSettingsAtom isUsingImportExportSettingsAtom
); );
const setOldPinnedApps = useSetAtom(oldPinnedAppsAtom);
const [oldPinnedApps, setOldPinnedApps] = useRecoilState(oldPinnedAppsAtom);
const getSavedSettings = useCallback( const getSavedSettings = useCallback(
async (myName, settingsLocalLastUpdated) => { async (myName, settingsLocalLastUpdated) => {

View File

@ -1,5 +1,4 @@
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import { import {
isUsingImportExportSettingsAtom, isUsingImportExportSettingsAtom,
oldPinnedAppsAtom, oldPinnedAppsAtom,
@ -7,6 +6,7 @@ import {
settingsQDNLastUpdatedAtom, settingsQDNLastUpdatedAtom,
sortablePinnedAppsAtom, sortablePinnedAppsAtom,
} from './atoms/global'; } from './atoms/global';
import { useSetAtom } from 'jotai';
function fetchFromLocalStorage(key) { function fetchFromLocalStorage(key) {
try { try {
@ -22,21 +22,13 @@ function fetchFromLocalStorage(key) {
} }
export const useRetrieveDataLocalStorage = (address) => { export const useRetrieveDataLocalStorage = (address) => {
const setSortablePinnedApps = useSetRecoilState(sortablePinnedAppsAtom); const setSortablePinnedApps = useSetAtom(sortablePinnedAppsAtom);
const setSettingsLocalLastUpdated = useSetAtom(settingsLocalLastUpdatedAtom);
const setSettingsLocalLastUpdated = useSetRecoilState( const setIsUsingImportExportSettings = useSetAtom(
settingsLocalLastUpdatedAtom
);
const setIsUsingImportExportSettings = useSetRecoilState(
isUsingImportExportSettingsAtom isUsingImportExportSettingsAtom
); );
const setSettingsQDNLastUpdated = useSetAtom(settingsQDNLastUpdatedAtom);
const setSettingsQDNLastUpdated = useSetRecoilState( const setOldPinnedApps = useSetAtom(oldPinnedAppsAtom);
settingsQDNLastUpdatedAtom
);
const setOldPinnedApps = useSetRecoilState(oldPinnedAppsAtom);
const getSortablePinnedApps = useCallback(() => { const getSortablePinnedApps = useCallback(() => {
const pinnedAppsLocal = fetchFromLocalStorage('ext_saved_settings'); const pinnedAppsLocal = fetchFromLocalStorage('ext_saved_settings');