diff --git a/src/App.tsx b/src/App.tsx index 2503d1d..899d0c4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -112,6 +112,7 @@ import { canSaveSettingToQdnAtom, fullScreenAtom, hasSettingsChangedAtom, + isUsingImportExportSettingsAtom, oldPinnedAppsAtom, settingsLocalLastUpdatedAtom, settingsQDNLastUpdatedAtom, @@ -509,6 +510,7 @@ function App() { settingsLocalLastUpdatedAtom ); const resetAtomOldPinnedAppsAtom = useResetRecoilState(oldPinnedAppsAtom); + const resetAtomIsUsingImportExportSettingsAtom = useResetRecoilState(isUsingImportExportSettingsAtom) const resetAllRecoil = () => { resetAtomSortablePinnedAppsAtom(); @@ -516,6 +518,7 @@ function App() { resetAtomSettingsQDNLastUpdatedAtom(); resetAtomSettingsLocalLastUpdatedAtom(); resetAtomOldPinnedAppsAtom(); + resetAtomIsUsingImportExportSettingsAtom(); }; useEffect(() => { if (!isMobile) return; diff --git a/src/atoms/global.ts b/src/atoms/global.ts index 0e0dc42..9d1f7ad 100644 --- a/src/atoms/global.ts +++ b/src/atoms/global.ts @@ -135,4 +135,9 @@ export const blobKeySelector = selectorFamily({ export const selectedGroupIdAtom = atom({ key: 'selectedGroupIdAtom', default: null, -}); \ No newline at end of file +}); + +export const isUsingImportExportSettingsAtom = atom({ + key: 'isUsingImportExportSettingsAtom', + default: null, +}); diff --git a/src/common/useFetchResources.tsx b/src/common/useFetchResources.tsx index 0aaec61..9099989 100644 --- a/src/common/useFetchResources.tsx +++ b/src/common/useFetchResources.tsx @@ -137,6 +137,16 @@ export const useFetchResources = () => { }, })); } + 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.current = setInterval(async () => { diff --git a/src/components/Apps/AppsNavBar.tsx b/src/components/Apps/AppsNavBar.tsx index 0007d03..18e28cc 100644 --- a/src/components/Apps/AppsNavBar.tsx +++ b/src/components/Apps/AppsNavBar.tsx @@ -33,8 +33,12 @@ import { sortablePinnedAppsAtom, } from "../../atoms/global"; -export function saveToLocalStorage(key, subKey, newValue) { +export function saveToLocalStorage(key, subKey, newValue, otherRootData = {}, deleteWholeKey) { try { + if(deleteWholeKey){ + localStorage.setItem(key, null); + return + } // Fetch existing data const existingData = localStorage.getItem(key); let combinedData = {}; @@ -45,12 +49,14 @@ export function saveToLocalStorage(key, subKey, newValue) { // Merge with the new data under the subKey combinedData = { ...parsedData, + ...otherRootData, timestamp: Date.now(), // Update the root timestamp [subKey]: newValue, // Assuming the data is an array }; } else { // If no existing data, just use the new data under the subKey combinedData = { + ...otherRootData, timestamp: Date.now(), // Set the initial root timestamp [subKey]: newValue, }; diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx index 582d4cd..745e0c1 100644 --- a/src/components/Chat/ChatGroup.tsx +++ b/src/components/Chat/ChatGroup.tsx @@ -993,7 +993,8 @@ const sendMessage = async ()=> { diff --git a/src/components/Embeds/AttachmentEmbed.tsx b/src/components/Embeds/AttachmentEmbed.tsx index 7f4d0f1..347b8f6 100644 --- a/src/components/Embeds/AttachmentEmbed.tsx +++ b/src/components/Embeds/AttachmentEmbed.tsx @@ -266,9 +266,7 @@ export const AttachmentCard = ({ {resourceData?.fileName && ( <> - {resourceData?.fileName} + {resourceDetails?.status?.status === 'DOWNLOADED' ? 'BUILDING' : resourceDetails?.status?.status} )} diff --git a/src/components/Save/Save.tsx b/src/components/Save/Save.tsx index dd5a222..1a0317f 100644 --- a/src/components/Save/Save.tsx +++ b/src/components/Save/Save.tsx @@ -4,12 +4,13 @@ import isEqual from "lodash/isEqual"; // Import deep comparison utility import { canSaveSettingToQdnAtom, hasSettingsChangedAtom, + isUsingImportExportSettingsAtom, oldPinnedAppsAtom, settingsLocalLastUpdatedAtom, settingsQDNLastUpdatedAtom, sortablePinnedAppsAtom, } from "../../atoms/global"; -import { Box, ButtonBase, Popover, Typography } from "@mui/material"; +import { Box, Button, ButtonBase, Popover, Typography } from "@mui/material"; import { objectToBase64 } from "../../qdn/encryption/group-encryption"; import { MyContext } from "../../App"; import { getFee } from "../../background"; @@ -19,6 +20,43 @@ import { IconWrapper } from "../Desktop/DesktopFooter"; import { Spacer } from "../../common/Spacer"; import { LoadingButton } from "@mui/lab"; import { saveToLocalStorage } from "../Apps/AppsNavBar"; +import { decryptData, encryptData } from "../../qortalRequests/get"; +import { saveFileToDiskGeneric } from "../../utils/generateWallet/generateWallet"; +import { base64ToUint8Array, uint8ArrayToObject } from "../../backgroundFunctions/encryption"; + +export const handleImportClick = async () => { + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = '.base64,.txt'; + + // Create a promise to handle file selection and reading synchronously + return await new Promise((resolve, reject) => { + fileInput.onchange = () => { + const file = fileInput.files[0]; + if (!file) { + reject(new Error('No file selected')); + return; + } + + const reader = new FileReader(); + reader.onload = (e) => { + resolve(e.target.result); // Resolve with the file content + }; + reader.onerror = () => { + reject(new Error('Error reading file')); + }; + + reader.readAsText(file); // Read the file as text (Base64 string) + }; + + // Trigger the file input dialog + fileInput.click(); + }); + +} + + + export const Save = ({ isDesktop, disableWidth, myName }) => { const [pinnedApps, setPinnedApps] = useRecoilState(sortablePinnedAppsAtom); const [settingsQdnLastUpdated, setSettingsQdnLastUpdated] = useRecoilState( @@ -28,7 +66,7 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { settingsLocalLastUpdatedAtom ); const setHasSettingsChangedAtom = useSetRecoilState(hasSettingsChangedAtom); - + const [isUsingImportExportSettings, setIsUsingImportExportSettings] = useRecoilState(isUsingImportExportSettingsAtom); const [canSave] = useRecoilState(canSaveSettingToQdnAtom); const [openSnack, setOpenSnack] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -212,14 +250,9 @@ export const Save = ({ isDesktop, disableWidth, myName }) => { vertical: "top", horizontal: "center", }} - // sx={{ - // width: "300px", - // maxWidth: "90%", - // maxHeight: "80%", - // overflow: "auto", - // }} > - { width: '100%' }} > - {!myName ? ( - - - You need a registered Qortal name to save your pinned apps to QDN. - - - ) : ( - <> - {hasChanged && ( - { fontSize: "14px", }} > - You have unsaved changes to your pinned apps. Save them to QDN. + You are using the export/import way of saving settings. - - - Save to QDN - - - {!isNaN(settingsQdnLastUpdated) && settingsQdnLastUpdated > 0 && ( - <> - - Don't like your current local changes? Would you like to - reset to your saved QDN pinned apps? - - - - Revert to QDN - - - )} - {!isNaN(settingsQdnLastUpdated) && settingsQdnLastUpdated === 0 && ( - <> - - Don't like your current local changes? Would you like to - reset to the default pinned apps? - - - - Revert to default - - - )} - - )} - {!isNaN(settingsQdnLastUpdated) && settingsQdnLastUpdated === -100 && ( - - - The app was unable to download your existing QDN-saved pinned - apps. Would you like to overwrite those changes? - - - + + + + )} + {!isUsingImportExportSettings && ( + {!myName ? ( + { fontSize: "14px", }} > - You currently do not have any changes to your pinned apps + You need a registered Qortal name to save your pinned apps to QDN. - - - )} - - )} - - + + ) : ( + <> + {hasChanged && ( + + + You have unsaved changes to your pinned apps. Save them to QDN. + + + + Save to QDN + + + {!isNaN(settingsQdnLastUpdated) && settingsQdnLastUpdated > 0 && ( + <> + + Don't like your current local changes? Would you like to + reset to your saved QDN pinned apps? + + + + Revert to QDN + + + )} + {!isNaN(settingsQdnLastUpdated) && settingsQdnLastUpdated === 0 && ( + <> + + Don't like your current local changes? Would you like to + reset to the default pinned apps? + + + + Revert to default + + + )} + + )} + {!isNaN(settingsQdnLastUpdated) && settingsQdnLastUpdated === -100 && isUsingImportExportSettings !== true && ( + + + The app was unable to download your existing QDN-saved pinned + apps. Would you like to overwrite those changes? + + + + Overwrite to QDN + + + )} + {!hasChanged && ( + + + You currently do not have any changes to your pinned apps + + + + )} + + )} + + + )} + + + { + try { + const fileContent = await handleImportClick(); + const decryptedData = await decryptData({ + encryptedData: fileContent, + }); + const decryptToUnit8ArraySubject = + base64ToUint8Array(decryptedData); + const responseData = uint8ArrayToObject( + decryptToUnit8ArraySubject + ); + if(Array.isArray(responseData)){ + saveToLocalStorage("ext_saved_settings_import_export", "sortablePinnedApps", responseData, { + isUsingImportExport: true + }); + setPinnedApps(responseData) + setOldPinnedApps(responseData) + setIsUsingImportExportSettings(true) + } + + } catch (error) { + console.log("error", error); + } + }}> + + Import + + { + try { + const data64 = await objectToBase64(pinnedApps); + + const encryptedData = await encryptData({ + data64, + }); + const blob = new Blob([encryptedData], { + type: "text/plain", + }); + + const timestamp = new Date() + .toISOString() + .replace(/:/g, "-"); // Safe timestamp for filenames + const filename = `qortal-new-ui-backup-settings-${timestamp}.txt`; + await saveFileToDiskGeneric(blob, filename) + setInfoSnack({ + type: "success", + message: "saved in INTERNAL storage under DOCUMENTS", + }); + setOpenSnack(true); + + } catch (error) { + console.log('error', error) + } + }}> + Export + + + { const setSettingsQDNLastUpdated = useSetRecoilState(settingsQDNLastUpdatedAtom); const [settingsLocalLastUpdated] = useRecoilState(settingsLocalLastUpdatedAtom); const [oldPinnedApps, setOldPinnedApps] = useRecoilState(oldPinnedAppsAtom) - + const [isUsingImportExportSettings] = useRecoilState(isUsingImportExportSettingsAtom); const getSavedSettings = useCallback(async (myName, settingsLocalLastUpdated)=> { try { const {hasPublishRecord, timestamp} = await getPublishRecord(myName) @@ -87,8 +87,9 @@ export const useQortalGetSaveSettings = (myName, isAuthenticated) => { } }, []) useEffect(()=> { - if(!myName || !settingsLocalLastUpdated || !isAuthenticated) return + if(!myName || !settingsLocalLastUpdated || !isAuthenticated || isUsingImportExportSettings === null) return + if(isUsingImportExportSettings) return getSavedSettings(myName, settingsLocalLastUpdated) - }, [getSavedSettings, myName, settingsLocalLastUpdated, isAuthenticated]) + }, [getSavedSettings, myName, settingsLocalLastUpdated, isAuthenticated, isUsingImportExportSettings]) } diff --git a/src/useRetrieveDataLocalStorage.tsx b/src/useRetrieveDataLocalStorage.tsx index 8f808ab..2a248bd 100644 --- a/src/useRetrieveDataLocalStorage.tsx +++ b/src/useRetrieveDataLocalStorage.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect } from 'react' import { useSetRecoilState } from 'recoil'; -import { settingsLocalLastUpdatedAtom, sortablePinnedAppsAtom } from './atoms/global'; +import { isUsingImportExportSettingsAtom, oldPinnedAppsAtom, settingsLocalLastUpdatedAtom, settingsQDNLastUpdatedAtom, sortablePinnedAppsAtom } from './atoms/global'; function fetchFromLocalStorage(key) { try { @@ -18,7 +18,10 @@ function fetchFromLocalStorage(key) { export const useRetrieveDataLocalStorage = () => { const setSortablePinnedApps = useSetRecoilState(sortablePinnedAppsAtom); const setSettingsLocalLastUpdated = useSetRecoilState(settingsLocalLastUpdatedAtom); - + const setIsUsingImportExportSettings = useSetRecoilState(isUsingImportExportSettingsAtom) + const setSettingsQDNLastUpdated = useSetRecoilState(settingsQDNLastUpdatedAtom); + const setOldPinnedApps = useSetRecoilState(oldPinnedAppsAtom) + const getSortablePinnedApps = useCallback(()=> { const pinnedAppsLocal = fetchFromLocalStorage('ext_saved_settings') if(pinnedAppsLocal?.sortablePinnedApps){ @@ -28,10 +31,25 @@ export const useRetrieveDataLocalStorage = () => { setSettingsLocalLastUpdated(-1) } + }, []) + const getSortablePinnedAppsImportExport = useCallback(()=> { + const pinnedAppsLocal = fetchFromLocalStorage('ext_saved_settings_import_export') + if(pinnedAppsLocal?.sortablePinnedApps){ + setOldPinnedApps(pinnedAppsLocal?.sortablePinnedApps) + + + setIsUsingImportExportSettings(true) + setSettingsQDNLastUpdated(pinnedAppsLocal?.timestamp || 0) + + } else { + setIsUsingImportExportSettings(false) + } + }, []) useEffect(()=> { getSortablePinnedApps() + getSortablePinnedAppsImportExport() }, [getSortablePinnedApps]) }