mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-04-23 19:37:52 +00:00
added save settings to qdn
This commit is contained in:
parent
affcd33dff
commit
082c9c11cf
@ -99,6 +99,7 @@ import { AddressQRCode } from "./components/AddressQRCode";
|
|||||||
import { Settings } from "./components/Group/Settings";
|
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";
|
||||||
|
|
||||||
type extStates =
|
type extStates =
|
||||||
| "not-authenticated"
|
| "not-authenticated"
|
||||||
@ -324,6 +325,7 @@ function App() {
|
|||||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||||
const qortalRequestCheckbox1Ref = useRef(null);
|
const qortalRequestCheckbox1Ref = useRef(null);
|
||||||
useRetrieveDataLocalStorage()
|
useRetrieveDataLocalStorage()
|
||||||
|
useQortalGetSaveSettings(userInfo?.name)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isMobile) return;
|
if (!isMobile) return;
|
||||||
// Function to set the height of the app to the viewport height
|
// Function to set the height of the app to the viewport height
|
||||||
|
10
src/assets/svgs/SaveIcon.tsx
Normal file
10
src/assets/svgs/SaveIcon.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export const SaveIcon = ({color = '#8F8F91'}) => {
|
||||||
|
return (
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.18182 0C0.976833 0 0 0.976833 0 2.18182V21.8182C0 23.0232 0.976833 24 2.18182 24H21.8182C23.0232 24 24 23.0232 24 21.8182V7.4492C24 6.87053 23.7701 6.31559 23.3609 5.90641L18.0936 0.639044C17.6844 0.229866 17.1295 0 16.5508 0H16.3636C15.7611 0 15.2727 0.488422 15.2727 1.09091V5.45455C15.2727 6.65953 14.2959 7.63636 13.0909 7.63636H6.54545C5.34047 7.63636 4.36364 6.65953 4.36364 5.45455V1.09091C4.36364 0.488422 3.87521 0 3.27273 0H2.18182ZM12 18.5455C13.8075 18.5455 15.2727 17.0803 15.2727 15.2727C15.2727 13.4652 13.8075 12 12 12C10.1925 12 8.72727 13.4652 8.72727 15.2727C8.72727 17.0803 10.1925 18.5455 12 18.5455Z" fill={color}/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
@ -4,4 +4,9 @@ import { atom } from 'recoil';
|
|||||||
export const sortablePinnedAppsAtom = atom({
|
export const sortablePinnedAppsAtom = atom({
|
||||||
key: 'sortablePinnedAppsFromAtom',
|
key: 'sortablePinnedAppsFromAtom',
|
||||||
default: [],
|
default: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const canSaveSettingToQdnAtom = atom({
|
||||||
|
key: 'canSaveSettingToQdnAtom',
|
||||||
|
default: false,
|
||||||
});
|
});
|
@ -42,7 +42,7 @@ const AppViewerContainer = ({app, isSelected, hide}) => {
|
|||||||
</style>
|
</style>
|
||||||
</>
|
</>
|
||||||
} style={{
|
} style={{
|
||||||
height: `calc(${rootHeight} - 60px - 45px)`,
|
height: `calc(${rootHeight} - 60px - 45px - 20px)`,
|
||||||
border: 'none',
|
border: 'none',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
display: (!isSelected || hide) && 'none'
|
display: (!isSelected || hide) && 'none'
|
||||||
|
@ -236,6 +236,7 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
|||||||
|
|
||||||
const setNewTabWindowFunc = (e) => {
|
const setNewTabWindowFunc = (e) => {
|
||||||
setIsNewTabWindow(true);
|
setIsNewTabWindow(true);
|
||||||
|
setSelectedTab(null)
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -252,18 +253,19 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
|||||||
display: !show && "none",
|
display: !show && "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{mode !== "viewer" && <Spacer height="30px" />}
|
{mode !== "viewer" && !selectedTab && <Spacer height="30px" />}
|
||||||
{mode === "home" && (
|
{mode === "home" && (
|
||||||
<AppsHome availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
<AppsHome availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
||||||
)}
|
)}
|
||||||
{mode === "library" && (
|
|
||||||
<AppsLibrary
|
<AppsLibrary
|
||||||
|
isShow={mode === "library" && !selectedTab}
|
||||||
availableQapps={availableQapps}
|
availableQapps={availableQapps}
|
||||||
setMode={setMode}
|
setMode={setMode}
|
||||||
myName={myName}
|
myName={myName}
|
||||||
hasPublishApp={!!(myApp || myWebsite)}
|
hasPublishApp={!!(myApp || myWebsite)}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
{mode === "appInfo" && <AppInfo app={selectedAppInfo} myName={myName} />}
|
{mode === "appInfo" && <AppInfo app={selectedAppInfo} myName={myName} />}
|
||||||
{mode === "publish" && <AppPublish names={myName ? [myName] : []} categories={categories} />}
|
{mode === "publish" && <AppPublish names={myName ? [myName] : []} categories={categories} />}
|
||||||
|
|
||||||
@ -283,7 +285,7 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
|||||||
<AppsHome availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
<AppsHome availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{mode !== "viewer" && <Spacer height="180px" />}
|
{mode !== "viewer" && !selectedTab && <Spacer height="180px" />}
|
||||||
</AppsParent>
|
</AppsParent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -74,7 +74,7 @@ const ScrollerStyled = styled('div')({
|
|||||||
"-ms-overflow-style": "none",
|
"-ms-overflow-style": "none",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp }) => {
|
export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, isShow }) => {
|
||||||
const [searchValue, setSearchValue] = useState("");
|
const [searchValue, setSearchValue] = useState("");
|
||||||
const virtuosoRef = useRef();
|
const virtuosoRef = useRef();
|
||||||
const { rootHeight } = useContext(MyContext);
|
const { rootHeight } = useContext(MyContext);
|
||||||
@ -133,7 +133,9 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp })
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppsLibraryContainer>
|
<AppsLibraryContainer sx={{
|
||||||
|
display: !isShow && 'none'
|
||||||
|
}}>
|
||||||
<AppsWidthLimiter>
|
<AppsWidthLimiter>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -124,6 +124,7 @@ export const AppsNavBar = () => {
|
|||||||
}} src={NavAdd} />
|
}} src={NavAdd} />
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
<ButtonBase onClick={(e)=> {
|
<ButtonBase onClick={(e)=> {
|
||||||
|
if(!selectedTab) return
|
||||||
handleClick(e)
|
handleClick(e)
|
||||||
}}>
|
}}>
|
||||||
<img style={{
|
<img style={{
|
||||||
|
@ -122,35 +122,25 @@ const Header = ({
|
|||||||
>
|
>
|
||||||
{/* Right Logout Icon */}
|
{/* Right Logout Icon */}
|
||||||
|
|
||||||
<IconButton
|
<ButtonBase
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setMobileViewModeKeepOpen("messaging");
|
setMobileViewModeKeepOpen("messaging");
|
||||||
}}
|
}}
|
||||||
edge="end"
|
|
||||||
color="inherit"
|
|
||||||
aria-label="logout"
|
|
||||||
|
|
||||||
// onClick={onLogoutClick}
|
|
||||||
>
|
>
|
||||||
<MessagingIcon2 height={20} color={hasUnreadDirects ? "var(--unread)" : "rgba(145, 145, 147, 1)"}
|
<MessagingIcon2 height={20} color={hasUnreadDirects ? "var(--unread)" : "rgba(145, 145, 147, 1)"}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</ButtonBase>
|
||||||
<Save />
|
<Save />
|
||||||
<IconButton
|
<ButtonBase
|
||||||
onClick={logoutFunc}
|
onClick={logoutFunc}
|
||||||
edge="end"
|
|
||||||
color="inherit"
|
|
||||||
aria-label="logout"
|
|
||||||
|
|
||||||
// onClick={onLogoutClick}
|
|
||||||
>
|
>
|
||||||
<LogoutIcon
|
<LogoutIcon
|
||||||
height={20}
|
height={20}
|
||||||
width={21}
|
width={21}
|
||||||
color="rgba(145, 145, 147, 1)"
|
color="rgba(145, 145, 147, 1)"
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</ButtonBase>
|
||||||
</Box>
|
</Box>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
<Menu
|
<Menu
|
||||||
|
@ -1,16 +1,135 @@
|
|||||||
import React, { useMemo, useState } from 'react'
|
import React, { useContext, useMemo, useState } from 'react'
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import isEqual from 'lodash/isEqual'; // Import deep comparison utility
|
import isEqual from 'lodash/isEqual'; // Import deep comparison utility
|
||||||
import { sortablePinnedAppsAtom } from '../../atoms/global';
|
import { canSaveSettingToQdnAtom, sortablePinnedAppsAtom } from '../../atoms/global';
|
||||||
|
import { ButtonBase } from '@mui/material';
|
||||||
|
import { objectToBase64 } from '../../qdn/encryption/group-encryption';
|
||||||
|
import { MyContext } from '../../App';
|
||||||
|
import { getFee } from '../../background';
|
||||||
|
import { CustomizedSnackbars } from '../Snackbar/Snackbar';
|
||||||
|
import { SaveIcon } from '../../assets/svgs/SaveIcon';
|
||||||
export const Save = () => {
|
export const Save = () => {
|
||||||
const [pinnedApps, setPinnedApps] = useRecoilState(sortablePinnedAppsAtom);
|
const [pinnedApps, setPinnedApps] = useRecoilState(sortablePinnedAppsAtom);
|
||||||
|
const [canSave, _] = useRecoilState(canSaveSettingToQdnAtom);
|
||||||
|
const [openSnack, setOpenSnack] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [infoSnack, setInfoSnack] = useState(null);
|
||||||
const [oldPinnedApps, setOldPinnedApps] = useState(pinnedApps)
|
const [oldPinnedApps, setOldPinnedApps] = useState(pinnedApps)
|
||||||
console.log('oldpin', {oldPinnedApps, pinnedApps})
|
console.log('oldpin', {oldPinnedApps, pinnedApps})
|
||||||
|
const { show } = useContext(MyContext);
|
||||||
|
|
||||||
const hasChanged = useMemo(()=> {
|
const hasChanged = useMemo(()=> {
|
||||||
return !isEqual(pinnedApps, oldPinnedApps)
|
const newChanges = {
|
||||||
|
sortablePinnedApps: pinnedApps.map((item)=> {
|
||||||
|
return {
|
||||||
|
name: item?.name,
|
||||||
|
service: item?.service
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const oldChanges = {
|
||||||
|
sortablePinnedApps: oldPinnedApps.map((item)=> {
|
||||||
|
return {
|
||||||
|
name: item?.name,
|
||||||
|
service: item?.service
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return !isEqual(oldChanges, newChanges)
|
||||||
}, [oldPinnedApps, pinnedApps])
|
}, [oldPinnedApps, pinnedApps])
|
||||||
|
|
||||||
|
const saveToQdn = async ()=> {
|
||||||
|
try {
|
||||||
|
setIsLoading(true)
|
||||||
|
const data64 = await objectToBase64({
|
||||||
|
sortablePinnedApps: pinnedApps
|
||||||
|
})
|
||||||
|
const encryptData = await new Promise((res, rej) => {
|
||||||
|
chrome?.runtime?.sendMessage(
|
||||||
|
{
|
||||||
|
action: "ENCRYPT_DATA",
|
||||||
|
type: "qortalRequest",
|
||||||
|
payload: {
|
||||||
|
data64
|
||||||
|
},
|
||||||
|
},
|
||||||
|
(response) => {
|
||||||
|
console.log("response", response);
|
||||||
|
if (response.error) {
|
||||||
|
rej(response?.message);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
res(response);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if(encryptData && !encryptData?.error){
|
||||||
|
const fee = await getFee('ARBITRARY')
|
||||||
|
|
||||||
|
await show({
|
||||||
|
message: "Would you like to publish your settings to QDN (encrypted) ?" ,
|
||||||
|
publishFee: fee.fee + ' QORT'
|
||||||
|
})
|
||||||
|
const response = await new Promise((res, rej) => {
|
||||||
|
chrome?.runtime?.sendMessage(
|
||||||
|
{
|
||||||
|
action: "publishOnQDN",
|
||||||
|
payload: {
|
||||||
|
data: encryptData,
|
||||||
|
identifier: "ext_saved_settings",
|
||||||
|
service: 'DOCUMENT_PRIVATE'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
(response) => {
|
||||||
|
|
||||||
|
if (!response?.error) {
|
||||||
|
res(response);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rej(response.error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
console.log('saved', response)
|
||||||
|
if(response?.identifier){
|
||||||
|
setOldPinnedApps(pinnedApps)
|
||||||
|
setInfoSnack({
|
||||||
|
type: "success",
|
||||||
|
message:
|
||||||
|
"Sucessfully published to QDN",
|
||||||
|
});
|
||||||
|
setOpenSnack(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('save encryptedData', encryptData)
|
||||||
|
} catch (error) {
|
||||||
|
setInfoSnack({
|
||||||
|
type: "error",
|
||||||
|
message:
|
||||||
|
error?.message || "Unable to save to QDN",
|
||||||
|
});
|
||||||
|
setOpenSnack(true);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div>{hasChanged && 'Save'}</div>
|
<>
|
||||||
|
<ButtonBase onClick={saveToQdn} disabled={!hasChanged || !canSave || isLoading}>
|
||||||
|
<SaveIcon
|
||||||
|
color={(hasChanged && !isLoading) ? '#5EB049' : '#8F8F91'}
|
||||||
|
/>
|
||||||
|
</ButtonBase>
|
||||||
|
<CustomizedSnackbars
|
||||||
|
duration={3500}
|
||||||
|
open={openSnack}
|
||||||
|
setOpen={setOpenSnack}
|
||||||
|
info={infoSnack}
|
||||||
|
setInfo={setInfoSnack}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
77
src/useQortalGetSaveSettings.tsx
Normal file
77
src/useQortalGetSaveSettings.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import React, { useCallback, useEffect } from 'react'
|
||||||
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
import { canSaveSettingToQdnAtom, sortablePinnedAppsAtom } from './atoms/global';
|
||||||
|
import { getArbitraryEndpointReact, getBaseApiReact } from './App';
|
||||||
|
import { decryptResource } from './components/Group/Group';
|
||||||
|
import { base64ToUint8Array, uint8ArrayToObject } from './backgroundFunctions/encryption';
|
||||||
|
|
||||||
|
function fetchFromLocalStorage(key) {
|
||||||
|
try {
|
||||||
|
const serializedValue = localStorage.getItem(key);
|
||||||
|
if (serializedValue === null) {
|
||||||
|
console.log(`No data found for key: ${key}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return JSON.parse(serializedValue);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching from localStorage:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPublishRecord = async (myName) => {
|
||||||
|
// const validApi = await findUsableApi();
|
||||||
|
const url = `${getBaseApiReact()}${getArbitraryEndpointReact()}?mode=ALL&service=DOCUMENT_PRIVATE&identifier=ext_saved_settings&exactmatchnames=true&limit=1&prefix=true&name=${myName}`;
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("network error");
|
||||||
|
}
|
||||||
|
const publishData = await response.json();
|
||||||
|
|
||||||
|
if(publishData?.length > 0) return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
};
|
||||||
|
const getPublish = async (myName) => {
|
||||||
|
let data
|
||||||
|
const res = await fetch(
|
||||||
|
`${getBaseApiReact()}/arbitrary/DOCUMENT_PRIVATE/${myName}/ext_saved_settings?encoding=base64`
|
||||||
|
);
|
||||||
|
data = await res.text();
|
||||||
|
|
||||||
|
|
||||||
|
if(!data) throw new Error('Unable to fetch publish')
|
||||||
|
|
||||||
|
const decryptedKey: any = await decryptResource(data);
|
||||||
|
|
||||||
|
const dataint8Array = base64ToUint8Array(decryptedKey.data);
|
||||||
|
const decryptedKeyToObject = uint8ArrayToObject(dataint8Array);
|
||||||
|
return decryptedKeyToObject
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useQortalGetSaveSettings = (myName) => {
|
||||||
|
const setSortablePinnedApps = useSetRecoilState(sortablePinnedAppsAtom);
|
||||||
|
const setCanSave = useSetRecoilState(canSaveSettingToQdnAtom);
|
||||||
|
|
||||||
|
|
||||||
|
const getSavedSettings = useCallback(async (myName)=> {
|
||||||
|
try {
|
||||||
|
const hasPublishRecord = await getPublishRecord(myName)
|
||||||
|
if(hasPublishRecord){
|
||||||
|
const settings = await getPublish(myName)
|
||||||
|
if(settings?.sortablePinnedApps){
|
||||||
|
fetchFromLocalStorage('sortablePinnedApps', settings.sortablePinnedApps)
|
||||||
|
setSortablePinnedApps(settings.sortablePinnedApps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setCanSave(true)
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
useEffect(()=> {
|
||||||
|
if(!myName) return
|
||||||
|
getSavedSettings(myName)
|
||||||
|
}, [getSavedSettings, myName])
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user