mirror of
https://github.com/Qortal/Qortal-Hub.git
synced 2025-07-23 04:36:52 +00:00
finished apps feature for mobile
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import React from "react";
|
||||
import {
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
AppCircleLabel,
|
||||
AppDownloadButton,
|
||||
AppDownloadButtonText,
|
||||
AppInfoAppName,
|
||||
@@ -11,10 +10,8 @@ import {
|
||||
AppInfoSnippetMiddle,
|
||||
AppInfoSnippetRight,
|
||||
AppInfoUserName,
|
||||
AppsLibraryContainer,
|
||||
} from "./Apps-styles";
|
||||
import { Avatar, Box, ButtonBase, InputBase } from "@mui/material";
|
||||
import { Add } from "@mui/icons-material";
|
||||
import { Avatar, ButtonBase } from "@mui/material";
|
||||
import { getBaseApiReact } from "../../App";
|
||||
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
|
||||
|
||||
@@ -22,8 +19,7 @@ import { Spacer } from "../../common/Spacer";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
import { AppRating } from "./AppRating";
|
||||
|
||||
export const AppInfoSnippet = ({ app, myName }) => {
|
||||
|
||||
export const AppInfoSnippet = ({ app, myName, isFromCategory }) => {
|
||||
|
||||
const isInstalled = app?.status?.status === 'READY'
|
||||
return (
|
||||
@@ -35,6 +31,12 @@ export const AppInfoSnippet = ({ app, myName }) => {
|
||||
width: "60px",
|
||||
}}
|
||||
onClick={()=> {
|
||||
if(isFromCategory){
|
||||
executeEvent("selectedAppInfoCategory", {
|
||||
data: app,
|
||||
});
|
||||
return
|
||||
}
|
||||
executeEvent("selectedAppInfo", {
|
||||
data: app,
|
||||
});
|
||||
@@ -73,6 +75,12 @@ export const AppInfoSnippet = ({ app, myName }) => {
|
||||
</ButtonBase>
|
||||
<AppInfoSnippetMiddle>
|
||||
<ButtonBase onClick={()=> {
|
||||
if(isFromCategory){
|
||||
executeEvent("selectedAppInfoCategory", {
|
||||
data: app,
|
||||
});
|
||||
return
|
||||
}
|
||||
executeEvent("selectedAppInfo", {
|
||||
data: app,
|
||||
});
|
||||
@@ -91,6 +99,7 @@ export const AppInfoSnippet = ({ app, myName }) => {
|
||||
</AppInfoSnippetLeft>
|
||||
<AppInfoSnippetRight>
|
||||
<AppDownloadButton onClick={()=> {
|
||||
|
||||
executeEvent("addTab", {
|
||||
data: app
|
||||
})
|
||||
|
@@ -25,9 +25,7 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
const [openSnack, setOpenSnack] = useState(false);
|
||||
const [infoSnack, setInfoSnack] = useState(null);
|
||||
const hasCalledRef = useRef(false);
|
||||
console.log(`pollinfo-${app?.service}-${app?.name}`, value);
|
||||
|
||||
console.log("hasPublishedRating", hasPublishedRating);
|
||||
const getRating = useCallback(async (name, service) => {
|
||||
try {
|
||||
hasCalledRef.current = true;
|
||||
@@ -42,7 +40,6 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
});
|
||||
|
||||
const responseData = await response.json();
|
||||
console.log("responseData", responseData);
|
||||
if (responseData?.message?.includes("POLL_NO_EXISTS")) {
|
||||
setHasPublishedRating(false);
|
||||
} else if (responseData?.pollName) {
|
||||
@@ -67,14 +64,12 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
const initialValueVote = voteCount.find((vote) =>
|
||||
vote.optionName.startsWith("initialValue-")
|
||||
);
|
||||
console.log("initialValueVote", initialValueVote);
|
||||
if (initialValueVote) {
|
||||
// Convert "initialValue-X" to just "X" and add it to the ratingVotes array
|
||||
const initialRating = parseInt(
|
||||
initialValueVote.optionName.split("-")[1],
|
||||
10
|
||||
);
|
||||
console.log("initialRating", initialRating);
|
||||
ratingVotes.push({
|
||||
optionName: initialRating.toString(),
|
||||
voteCount: 1,
|
||||
@@ -91,14 +86,12 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
totalScore += rating * count; // Weighted score
|
||||
totalVotes += count; // Total number of votes
|
||||
});
|
||||
console.log("ratingVotes", ratingVotes, totalScore, totalVotes);
|
||||
|
||||
// Calculate average rating (ensure no division by zero)
|
||||
const averageRating = totalVotes > 0 ? totalScore / totalVotes : 0;
|
||||
setValue(averageRating);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("error rating", error);
|
||||
if (error?.message?.includes("POLL_NO_EXISTS")) {
|
||||
setHasPublishedRating(false);
|
||||
}
|
||||
@@ -114,7 +107,6 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
try {
|
||||
if (!myName) throw new Error("You need a name to rate.");
|
||||
if (!app?.name) return;
|
||||
console.log("newValue", newValue);
|
||||
const fee = await getFee("ARBITRARY");
|
||||
|
||||
await show({
|
||||
@@ -138,7 +130,6 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
},
|
||||
},
|
||||
(response) => {
|
||||
console.log("response", response);
|
||||
if (response.error) {
|
||||
rej(response?.message);
|
||||
return;
|
||||
@@ -172,7 +163,6 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
},
|
||||
},
|
||||
(response) => {
|
||||
console.log("response", response);
|
||||
if (response.error) {
|
||||
rej(response?.message);
|
||||
return;
|
||||
@@ -197,11 +187,7 @@ export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
|
||||
setOpenSnack(true);
|
||||
}
|
||||
};
|
||||
console.log(
|
||||
"vvotes",
|
||||
(votesInfo?.totalVotes ?? 0) + votesInfo?.voteCounts?.length === 6 ? 1 : 0,
|
||||
votesInfo
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Box
|
||||
|
@@ -8,17 +8,7 @@ const AppViewerContainer = ({app, isSelected, hide}) => {
|
||||
const { rootHeight } = useContext(MyContext);
|
||||
const frameRef = useRef(null);
|
||||
|
||||
const refreshAppFunc = (e) => {
|
||||
console.log('getting refresh', e)
|
||||
};
|
||||
|
||||
// useEffect(() => {
|
||||
// subscribeToEvent("refreshAPp", refreshAppFunc);
|
||||
|
||||
// return () => {
|
||||
// unsubscribeFromEvent("refreshApp", refreshAppFunc);
|
||||
// };
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<Frame id={`browser-iframe-${app?.tabId}` } ref={frameRef} head={
|
||||
|
@@ -2,7 +2,6 @@ import React, { useCallback, useContext, useEffect, useMemo, useRef, useState }
|
||||
import { AppsHome } from "./AppsHome";
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { MyContext, getBaseApiReact } from "../../App";
|
||||
import { AppsLibrary } from "./AppsLibrary";
|
||||
import { AppInfo } from "./AppInfo";
|
||||
import {
|
||||
executeEvent,
|
||||
@@ -16,12 +15,15 @@ import AppViewerContainer from "./AppViewerContainer";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { AppPublish } from "./AppPublish";
|
||||
import { useRecoilState } from "recoil";
|
||||
import { AppsCategory } from "./AppsCategory";
|
||||
import { AppsLibrary } from "./AppsLibrary";
|
||||
|
||||
const uid = new ShortUniqueId({ length: 8 });
|
||||
|
||||
export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
const [availableQapps, setAvailableQapps] = useState([]);
|
||||
const [selectedAppInfo, setSelectedAppInfo] = useState(null);
|
||||
const [selectedCategory, setSelectedCategory] = useState(null)
|
||||
const [tabs, setTabs] = useState([]);
|
||||
const [selectedTab, setSelectedTab] = useState(null);
|
||||
const [isNewTabWindow, setIsNewTabWindow] = useState(false);
|
||||
@@ -85,7 +87,6 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
});
|
||||
if (!response?.ok) return;
|
||||
const responseData = await response.json();
|
||||
console.log('responseData', responseData)
|
||||
const urlWebsites = `${getBaseApiReact()}/arbitrary/resources/search?service=WEBSITE&mode=ALL&limit=0&includestatus=true&includemetadata=true`;
|
||||
|
||||
const responseWebsites = await fetch(urlWebsites, {
|
||||
@@ -125,8 +126,44 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const selectedAppInfoCategoryFunc = (e) => {
|
||||
const data = e.detail?.data;
|
||||
setSelectedAppInfo(data);
|
||||
setMode("appInfo-from-category");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("selectedAppInfoCategory", selectedAppInfoCategoryFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("selectedAppInfoCategory", selectedAppInfoCategoryFunc);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
const selectedCategoryFunc = (e) => {
|
||||
const data = e.detail?.data;
|
||||
setSelectedCategory(data);
|
||||
setMode("category");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
subscribeToEvent("selectedCategory", selectedCategoryFunc);
|
||||
|
||||
return () => {
|
||||
unsubscribeFromEvent("selectedCategory", selectedCategoryFunc);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
const navigateBackFunc = (e) => {
|
||||
if (mode === "appInfo") {
|
||||
if(mode === 'category'){
|
||||
setMode("library");
|
||||
setSelectedCategory(null)
|
||||
} else if (mode === "appInfo-from-category") {
|
||||
setMode("category");
|
||||
} else if (mode === "appInfo") {
|
||||
setMode("library");
|
||||
} else if (mode === "library") {
|
||||
if (isNewTabWindow) {
|
||||
@@ -139,10 +176,8 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
} else {
|
||||
const iframeId = `browser-iframe-${selectedTab?.tabId}`;
|
||||
const iframe = document.getElementById(iframeId);
|
||||
console.log("iframe", iframe);
|
||||
// Go Back in the iframe's history
|
||||
if (iframe) {
|
||||
console.log(iframe.contentWindow);
|
||||
if (iframe && iframe.contentWindow) {
|
||||
const iframeWindow = iframe.contentWindow;
|
||||
if (iframeWindow && iframeWindow.history) {
|
||||
@@ -247,6 +282,7 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
};
|
||||
}, [tabs]);
|
||||
|
||||
|
||||
return (
|
||||
<AppsParent
|
||||
sx={{
|
||||
@@ -264,9 +300,12 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
setMode={setMode}
|
||||
myName={myName}
|
||||
hasPublishApp={!!(myApp || myWebsite)}
|
||||
categories={categories}
|
||||
/>
|
||||
|
||||
{mode === "appInfo" && <AppInfo app={selectedAppInfo} myName={myName} />}
|
||||
{mode === "appInfo-from-category" && <AppInfo app={selectedAppInfo} myName={myName} />}
|
||||
<AppsCategory availableQapps={availableQapps} isShow={mode === 'category' && !selectedTab} category={selectedCategory} myName={myName} />
|
||||
{mode === "publish" && <AppPublish names={myName ? [myName] : []} categories={categories} />}
|
||||
|
||||
{tabs.map((tab) => {
|
||||
@@ -282,7 +321,7 @@ export const Apps = ({ mode, setMode, show , myName}) => {
|
||||
{isNewTabWindow && mode === "viewer" && (
|
||||
<>
|
||||
<Spacer height="30px" />
|
||||
<AppsHome availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
||||
<AppsHome availableQapps={availableQapps} setMode={setMode} myApp={myApp} myWebsite={myWebsite} />
|
||||
</>
|
||||
)}
|
||||
{mode !== "viewer" && !selectedTab && <Spacer height="180px" />}
|
||||
|
188
src/components/Apps/AppsCategory.tsx
Normal file
188
src/components/Apps/AppsCategory.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
AppCircle,
|
||||
AppCircleContainer,
|
||||
AppCircleLabel,
|
||||
AppLibrarySubTitle,
|
||||
AppsContainer,
|
||||
AppsLibraryContainer,
|
||||
AppsParent,
|
||||
AppsSearchContainer,
|
||||
AppsSearchLeft,
|
||||
AppsSearchRight,
|
||||
AppsWidthLimiter,
|
||||
PublishQAppCTAButton,
|
||||
PublishQAppCTALeft,
|
||||
PublishQAppCTAParent,
|
||||
PublishQAppCTARight,
|
||||
PublishQAppDotsBG,
|
||||
} from "./Apps-styles";
|
||||
import { Avatar, Box, ButtonBase, InputBase, styled } from "@mui/material";
|
||||
import { Add } from "@mui/icons-material";
|
||||
import { MyContext, getBaseApiReact } from "../../App";
|
||||
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
|
||||
import IconSearch from "../../assets/svgs/Search.svg";
|
||||
import IconClearInput from "../../assets/svgs/ClearInput.svg";
|
||||
import qappDevelopText from "../../assets/svgs/qappDevelopText.svg";
|
||||
import qappDots from "../../assets/svgs/qappDots.svg";
|
||||
|
||||
import { Spacer } from "../../common/Spacer";
|
||||
import { AppInfoSnippet } from "./AppInfoSnippet";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
import { executeEvent } from "../../utils/events";
|
||||
const officialAppList = [
|
||||
"q-tube",
|
||||
"q-blog",
|
||||
"q-share",
|
||||
"q-support",
|
||||
"q-mail",
|
||||
"qombo",
|
||||
"q-fund",
|
||||
"q-shop",
|
||||
];
|
||||
|
||||
const ScrollerStyled = styled('div')({
|
||||
// Hide scrollbar for WebKit browsers (Chrome, Safari)
|
||||
"::-webkit-scrollbar": {
|
||||
width: "0px",
|
||||
height: "0px",
|
||||
},
|
||||
|
||||
// Hide scrollbar for Firefox
|
||||
scrollbarWidth: "none",
|
||||
|
||||
// Hide scrollbar for IE and older Edge
|
||||
"-ms-overflow-style": "none",
|
||||
});
|
||||
|
||||
const StyledVirtuosoContainer = styled('div')({
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
||||
// Hide scrollbar for WebKit browsers (Chrome, Safari)
|
||||
"::-webkit-scrollbar": {
|
||||
width: "0px",
|
||||
height: "0px",
|
||||
},
|
||||
|
||||
// Hide scrollbar for Firefox
|
||||
scrollbarWidth: "none",
|
||||
|
||||
// Hide scrollbar for IE and older Edge
|
||||
"-ms-overflow-style": "none",
|
||||
});
|
||||
|
||||
export const AppsCategory = ({ availableQapps, myName, category, isShow }) => {
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const virtuosoRef = useRef();
|
||||
const { rootHeight } = useContext(MyContext);
|
||||
|
||||
|
||||
|
||||
const categoryList = useMemo(() => {
|
||||
return availableQapps.filter(
|
||||
(app) =>
|
||||
app?.metadata?.category === category?.id
|
||||
);
|
||||
}, [availableQapps, category]);
|
||||
|
||||
const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value
|
||||
|
||||
// Debounce logic
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedValue(searchValue);
|
||||
}, 350);
|
||||
|
||||
// Cleanup timeout if searchValue changes before the timeout completes
|
||||
return () => {
|
||||
clearTimeout(handler);
|
||||
};
|
||||
}, [searchValue]); // Runs effect when searchValue changes
|
||||
|
||||
// Example: Perform search or other actions based on debouncedValue
|
||||
|
||||
const searchedList = useMemo(() => {
|
||||
if (!debouncedValue) return categoryList
|
||||
return categoryList.filter((app) =>
|
||||
app.name.toLowerCase().includes(debouncedValue.toLowerCase())
|
||||
);
|
||||
}, [debouncedValue, categoryList]);
|
||||
|
||||
const rowRenderer = (index) => {
|
||||
|
||||
let app = searchedList[index];
|
||||
return <AppInfoSnippet key={`${app?.service}-${app?.name}`} app={app} myName={myName} isFromCategory={true} />;
|
||||
};
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<AppsLibraryContainer sx={{
|
||||
display: !isShow && 'none'
|
||||
}}>
|
||||
<AppsWidthLimiter>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
width: "100%",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<AppsSearchContainer>
|
||||
<AppsSearchLeft>
|
||||
<img src={IconSearch} />
|
||||
<InputBase
|
||||
value={searchValue}
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
sx={{ ml: 1, flex: 1 }}
|
||||
placeholder="Search for apps"
|
||||
inputProps={{
|
||||
"aria-label": "Search for apps",
|
||||
fontSize: "16px",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
/>
|
||||
</AppsSearchLeft>
|
||||
<AppsSearchRight>
|
||||
{searchValue && (
|
||||
<ButtonBase
|
||||
onClick={() => {
|
||||
setSearchValue("");
|
||||
}}
|
||||
>
|
||||
<img src={IconClearInput} />
|
||||
</ButtonBase>
|
||||
)}
|
||||
</AppsSearchRight>
|
||||
</AppsSearchContainer>
|
||||
</Box>
|
||||
</AppsWidthLimiter>
|
||||
<Spacer height="25px" />
|
||||
<AppsWidthLimiter>
|
||||
<AppLibrarySubTitle>{`Category: ${category?.name}`}</AppLibrarySubTitle>
|
||||
|
||||
<Spacer height="25px" />
|
||||
</AppsWidthLimiter>
|
||||
<AppsWidthLimiter>
|
||||
<StyledVirtuosoContainer sx={{
|
||||
height: rootHeight
|
||||
}}>
|
||||
<Virtuoso
|
||||
ref={virtuosoRef}
|
||||
data={searchedList}
|
||||
itemContent={rowRenderer}
|
||||
atBottomThreshold={50}
|
||||
followOutput="smooth"
|
||||
components={{
|
||||
Scroller: ScrollerStyled // Use the styled scroller component
|
||||
}}
|
||||
/>
|
||||
</StyledVirtuosoContainer>
|
||||
</AppsWidthLimiter>
|
||||
|
||||
</AppsLibraryContainer>
|
||||
);
|
||||
};
|
@@ -74,23 +74,11 @@ const ScrollerStyled = styled('div')({
|
||||
"-ms-overflow-style": "none",
|
||||
});
|
||||
|
||||
export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, isShow }) => {
|
||||
export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, isShow, categories={categories} }) => {
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
const virtuosoRef = useRef();
|
||||
const { rootHeight } = useContext(MyContext);
|
||||
const [appStates, setAppStates] = useState({});
|
||||
|
||||
const handleStateChange = (appId, newState) => {
|
||||
setAppStates((prevState) => ({
|
||||
...prevState,
|
||||
[appId]: {
|
||||
...(prevState[appId] || {}), // Preserve existing state for the app
|
||||
...newState, // Merge in the new state properties
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
console.log('appStates', appStates)
|
||||
const officialApps = useMemo(() => {
|
||||
return availableQapps.filter(
|
||||
(app) =>
|
||||
@@ -121,12 +109,10 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
|
||||
app.name.toLowerCase().includes(debouncedValue.toLowerCase())
|
||||
);
|
||||
}, [debouncedValue]);
|
||||
console.log("officialApps", searchedList);
|
||||
|
||||
const rowRenderer = (index) => {
|
||||
|
||||
let app = searchedList[index];
|
||||
console.log('appi', app)
|
||||
return <AppInfoSnippet key={`${app?.service}-${app?.name}`} app={app} myName={myName} />;
|
||||
};
|
||||
|
||||
@@ -273,6 +259,49 @@ export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp, i
|
||||
|
||||
<Spacer height="18px" />
|
||||
<AppLibrarySubTitle>Categories</AppLibrarySubTitle>
|
||||
<Spacer height="18px" />
|
||||
<AppsWidthLimiter sx={{
|
||||
flexDirection: 'row',
|
||||
overflowX: 'auto',
|
||||
width: '100%',
|
||||
gap: '5px',
|
||||
"::-webkit-scrollbar": {
|
||||
width: "0px",
|
||||
height: "0px",
|
||||
},
|
||||
|
||||
// Hide scrollbar for Firefox
|
||||
scrollbarWidth: "none",
|
||||
|
||||
// Hide scrollbar for IE and older Edge
|
||||
"-ms-overflow-style": "none",
|
||||
}}>
|
||||
{categories?.map((category)=> {
|
||||
return (
|
||||
<ButtonBase key={category?.id} onClick={()=> {
|
||||
executeEvent('selectedCategory', {
|
||||
data: category
|
||||
})
|
||||
}}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '110px',
|
||||
width: '110px',
|
||||
background: 'linear-gradient(163.47deg, #4BBCFE 27.55%, #1386C9 86.56%)',
|
||||
color: '#1D1D1E',
|
||||
fontWeight: 700,
|
||||
fontSize: '16px',
|
||||
flexShrink: 0,
|
||||
borderRadius: '11px'
|
||||
}}>
|
||||
{category?.name}
|
||||
</Box>
|
||||
</ButtonBase>
|
||||
)
|
||||
})}
|
||||
</AppsWidthLimiter>
|
||||
</AppsWidthLimiter>
|
||||
</>
|
||||
)}
|
||||
|
@@ -41,7 +41,6 @@ export function saveToLocalStorage(key, subKey, newValue) {
|
||||
// Save combined data back to localStorage
|
||||
const serializedValue = JSON.stringify(combinedData);
|
||||
localStorage.setItem(key, serializedValue);
|
||||
console.log(`Data saved to localStorage with key: ${key} and subKey: ${subKey}`);
|
||||
} catch (error) {
|
||||
console.error('Error saving to localStorage:', error);
|
||||
}
|
||||
@@ -57,6 +56,7 @@ export const AppsNavBar = () => {
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(sortablePinnedAppsAtom);
|
||||
|
||||
const setSettingsLocalLastUpdated = useSetRecoilState(settingsLocalLastUpdatedAtom);
|
||||
|
||||
const handleClick = (event) => {
|
||||
@@ -71,7 +71,6 @@ export const AppsNavBar = () => {
|
||||
// Scroll to the last tab whenever the tabs array changes (e.g., when a new tab is added)
|
||||
if (tabsRef.current) {
|
||||
const tabElements = tabsRef.current.querySelectorAll('.MuiTab-root');
|
||||
console.log('tabElements', tabElements)
|
||||
if (tabElements.length > 0) {
|
||||
const lastTab = tabElements[tabElements.length - 1];
|
||||
lastTab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'end' });
|
||||
@@ -95,7 +94,6 @@ export const AppsNavBar = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
console.log('selectedTab', selectedTab)
|
||||
|
||||
const isSelectedAppPinned = !!sortablePinnedApps?.find((item)=> item?.name === selectedTab?.name && item?.service === selectedTab?.service)
|
||||
return (
|
||||
|
@@ -14,7 +14,6 @@ import { ContextMenuPinnedApps } from '../ContextMenuPinnedApps';
|
||||
|
||||
const SortableItem = ({ id, name, app }) => {
|
||||
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
|
||||
console.log('namednd', name)
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
@@ -86,7 +85,6 @@ export const SortablePinnedApps = ({ myWebsite, myApp, availableQapps = [] }) =
|
||||
const setSettingsLocalLastUpdated = useSetRecoilState(settingsLocalLastUpdatedAtom);
|
||||
|
||||
const transformPinnedApps = useMemo(() => {
|
||||
console.log({ myWebsite, myApp, availableQapps, pinnedApps });
|
||||
|
||||
// Clone the existing pinned apps list
|
||||
let pinned = [...pinnedApps];
|
||||
@@ -130,27 +128,7 @@ export const SortablePinnedApps = ({ myWebsite, myApp, availableQapps = [] }) =
|
||||
return pinned;
|
||||
}, [myApp, myWebsite, pinnedApps, availableQapps]);
|
||||
|
||||
console.log('transformPinnedApps', transformPinnedApps)
|
||||
// const hasSetPinned = useRef(false)
|
||||
// useEffect(() => {
|
||||
// if (!apps || apps.length === 0) return;
|
||||
|
||||
// setPinnedApps((prevPinnedApps) => {
|
||||
// // Create a map of the previous pinned apps for easy lookup
|
||||
// const pinnedAppsMap = new Map(prevPinnedApps.map(app => [`${app?.service}-${app?.name}`, app]));
|
||||
|
||||
// // Update the pinnedApps list based on new apps
|
||||
// const updatedPinnedApps = apps.map(app => {
|
||||
// const id = `${app?.service}-${app?.name}`;
|
||||
// // Keep the existing app from pinnedApps if it exists
|
||||
// return pinnedAppsMap.get(id) || app;
|
||||
// });
|
||||
|
||||
// return updatedPinnedApps;
|
||||
// });
|
||||
// }, [apps]);
|
||||
|
||||
console.log('dnd',{pinnedApps})
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
activationConstraint: {
|
||||
|
@@ -97,7 +97,6 @@ async function handleGetFileFromIndexedDB(fileId, sendResponse) {
|
||||
const deleteRequest = deleteObjectStore.delete(fileId);
|
||||
|
||||
deleteRequest.onsuccess = function () {
|
||||
console.log(`File with ID ${fileId} has been removed from IndexedDB`);
|
||||
try {
|
||||
sendResponse({ result: base64String });
|
||||
|
||||
@@ -171,7 +170,6 @@ const UIQortalRequests = [
|
||||
|
||||
async function deleteQortalFilesFromIndexedDB() {
|
||||
try {
|
||||
console.log("Opening IndexedDB for deleting files...");
|
||||
const db = await openIndexedDB();
|
||||
const transaction = db.transaction(["files"], "readwrite");
|
||||
const objectStore = transaction.objectStore("files");
|
||||
@@ -262,7 +260,6 @@ const UIQortalRequests = [
|
||||
obj.fileId = fileId;
|
||||
delete obj.file;
|
||||
}
|
||||
console.log(obj, obj.blob instanceof Blob, 'blob')
|
||||
if (obj.blob) {
|
||||
const fileId = "objFile_qortalfile";
|
||||
|
||||
@@ -315,7 +312,6 @@ const UIQortalRequests = [
|
||||
export const useQortalMessageListener = (frameWindow) => {
|
||||
const [path, setPath] = useState('')
|
||||
useEffect(() => {
|
||||
console.log("Listener added react");
|
||||
|
||||
const listener = async (event) => {
|
||||
event.preventDefault(); // Prevent default behavior
|
||||
@@ -325,7 +321,6 @@ export const useQortalMessageListener = (frameWindow) => {
|
||||
|
||||
const sendMessageToRuntime = (message, eventPort) => {
|
||||
chrome?.runtime?.sendMessage(message, (response) => {
|
||||
console.log('response', response);
|
||||
if (response.error) {
|
||||
eventPort.postMessage({
|
||||
result: null,
|
||||
@@ -342,7 +337,6 @@ export const useQortalMessageListener = (frameWindow) => {
|
||||
|
||||
// Check if action is included in the predefined list of UI requests
|
||||
if (UIQortalRequests.includes(event.data.action)) {
|
||||
console.log('event?.data', event?.data);
|
||||
sendMessageToRuntime(
|
||||
{ action: event.data.action, type: 'qortalRequest', payload: event.data, isExtension: true },
|
||||
event.ports[0]
|
||||
@@ -353,7 +347,6 @@ export const useQortalMessageListener = (frameWindow) => {
|
||||
event?.data?.action === 'ENCRYPT_DATA' || event?.data?.action === 'SAVE_FILE'
|
||||
|
||||
) {
|
||||
console.log('event?.data?', event?.data);
|
||||
let data;
|
||||
try {
|
||||
data = await storeFilesInIndexedDB(event.data);
|
||||
@@ -365,7 +358,6 @@ export const useQortalMessageListener = (frameWindow) => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log('data after', data)
|
||||
if (data) {
|
||||
sendMessageToRuntime(
|
||||
{ action: event.data.action, type: 'qortalRequest', payload: data, isExtension: true },
|
||||
@@ -396,7 +388,6 @@ export const useQortalMessageListener = (frameWindow) => {
|
||||
}, []); // Empty dependency array to run once when the component mounts
|
||||
|
||||
chrome.runtime?.onMessage.addListener( function (message, sender, sendResponse) {
|
||||
console.log('SHOWING UP')
|
||||
if(message.action === "SHOW_SAVE_FILE_PICKER"){
|
||||
showSaveFilePicker(message?.data)
|
||||
}
|
||||
|
@@ -127,7 +127,6 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
|
||||
|
||||
const forceCloseWebSocket = () => {
|
||||
if (socketRef.current) {
|
||||
console.log('Force closing the WebSocket');
|
||||
clearTimeout(timeoutIdRef.current);
|
||||
clearTimeout(groupSocketTimeoutRef.current);
|
||||
socketRef.current.close(1000, 'forced');
|
||||
@@ -161,7 +160,6 @@ export const ChatDirect = ({ myAddress, isNewChat, selectedDirect, setSelectedDi
|
||||
socketRef.current = new WebSocket(socketLink);
|
||||
|
||||
socketRef.current.onopen = () => {
|
||||
console.log('WebSocket connection opened');
|
||||
setTimeout(pingWebSocket, 50); // Initial ping
|
||||
};
|
||||
|
||||
|
@@ -824,98 +824,7 @@ export const Group = ({
|
||||
}
|
||||
}, [selectedGroup]);
|
||||
|
||||
// const handleNotification = async (data)=> {
|
||||
// try {
|
||||
// if(isFocusedRef.current){
|
||||
// throw new Error('isFocused')
|
||||
// }
|
||||
// const newActiveChats= data
|
||||
// const oldActiveChats = await new Promise((res, rej) => {
|
||||
// chrome?.runtime?.sendMessage(
|
||||
// {
|
||||
// action: "getChatHeads",
|
||||
// },
|
||||
// (response) => {
|
||||
// console.log({ response });
|
||||
// if (!response?.error) {
|
||||
// res(response);
|
||||
// }
|
||||
// rej(response.error);
|
||||
// }
|
||||
// );
|
||||
// });
|
||||
|
||||
// let results = []
|
||||
// newActiveChats?.groups?.forEach(newChat => {
|
||||
// let isNewer = true;
|
||||
// oldActiveChats?.data?.groups?.forEach(oldChat => {
|
||||
// if (newChat?.timestamp <= oldChat?.timestamp) {
|
||||
// isNewer = false;
|
||||
// }
|
||||
// });
|
||||
// if (isNewer) {
|
||||
// results.push(newChat)
|
||||
// console.log('This newChat is newer than all oldChats:', newChat);
|
||||
// }
|
||||
// });
|
||||
|
||||
// if(results?.length > 0){
|
||||
// if (!lastGroupNotification.current || (Date.now() - lastGroupNotification.current >= 60000)) {
|
||||
// console.log((Date.now() - lastGroupNotification.current >= 60000), lastGroupNotification.current)
|
||||
// chrome?.runtime?.sendMessage(
|
||||
// {
|
||||
// action: "notification",
|
||||
// payload: {
|
||||
// },
|
||||
// },
|
||||
// (response) => {
|
||||
// console.log({ response });
|
||||
// if (!response?.error) {
|
||||
|
||||
// }
|
||||
|
||||
// }
|
||||
// );
|
||||
// audio.play();
|
||||
// lastGroupNotification.current = Date.now()
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
// } catch (error) {
|
||||
// console.log('error not', error)
|
||||
// if(!isFocusedRef.current){
|
||||
// chrome?.runtime?.sendMessage(
|
||||
// {
|
||||
// action: "notification",
|
||||
// payload: {
|
||||
// },
|
||||
// },
|
||||
// (response) => {
|
||||
// console.log({ response });
|
||||
// if (!response?.error) {
|
||||
|
||||
// }
|
||||
|
||||
// }
|
||||
// );
|
||||
// audio.play();
|
||||
// lastGroupNotification.current = Date.now()
|
||||
// }
|
||||
|
||||
// } finally {
|
||||
|
||||
// chrome?.runtime?.sendMessage(
|
||||
// {
|
||||
// action: "setChatHeads",
|
||||
// payload: {
|
||||
// data,
|
||||
// },
|
||||
// }
|
||||
// );
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
const getAdmins = async (groupId) => {
|
||||
try {
|
||||
|
@@ -48,20 +48,7 @@ export const GroupJoinRequests = ({ myAddress, groups, setOpenManageMembers, get
|
||||
return true
|
||||
})
|
||||
|
||||
// const getJoinGroupRequests = groupsAsAdmin.map(async (group)=> {
|
||||
// console.log('getJoinGroupRequests', group)
|
||||
// const joinRequestResponse = await requestQueueGroupJoinRequests.enqueue(()=> {
|
||||
// return fetch(
|
||||
// `${getBaseApiReact()}/groups/joinrequests/${group.groupId}`
|
||||
// );
|
||||
// })
|
||||
|
||||
// const joinRequestData = await joinRequestResponse.json()
|
||||
// return {
|
||||
// group,
|
||||
// data: joinRequestData
|
||||
// }
|
||||
// })
|
||||
|
||||
await Promise.all(getAllGroupsAsAdmin)
|
||||
const res = await Promise.all(groupsAsAdmin.map(async (group)=> {
|
||||
|
||||
|
@@ -20,6 +20,10 @@ import { MessagingIcon } from "../../assets/Icons/MessagingIcon";
|
||||
import { MessagingIcon2 } from "../../assets/Icons/MessagingIcon2";
|
||||
import { HubsIcon } from "../../assets/Icons/HubsIcon";
|
||||
import { Save } from "../Save/Save";
|
||||
import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen';
|
||||
import { useRecoilState } from "recoil";
|
||||
import { fullScreenAtom, hasSettingsChangedAtom } from "../../atoms/global";
|
||||
import { useAppFullScreen } from "../../useAppFullscreen";
|
||||
|
||||
const Header = ({
|
||||
logoutFunc,
|
||||
@@ -33,16 +37,11 @@ const Header = ({
|
||||
myName,
|
||||
setSelectedDirect,
|
||||
setNewChat
|
||||
// selectedGroup,
|
||||
// onHomeClick,
|
||||
// onLogoutClick,
|
||||
// onGroupChange,
|
||||
// onWalletClick,
|
||||
// onNotificationClick,
|
||||
}) => {
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
const [fullScreen, setFullScreen] = useRecoilState(fullScreenAtom);
|
||||
const {exitFullScreen} = useAppFullScreen(setFullScreen)
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
@@ -77,10 +76,10 @@ const Header = ({
|
||||
width: "75px",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="home"
|
||||
<ButtonBase
|
||||
|
||||
|
||||
|
||||
onClick={() => {
|
||||
setMobileViewModeKeepOpen("");
|
||||
goToHome();
|
||||
@@ -88,15 +87,24 @@ const Header = ({
|
||||
// onClick={onHomeClick}
|
||||
>
|
||||
<HomeIcon height={20} width={27} color="rgba(145, 145, 147, 1)" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="home"
|
||||
</ButtonBase>
|
||||
<ButtonBase
|
||||
|
||||
onClick={handleClick}
|
||||
>
|
||||
<NotificationIcon height={20} width={21} color={hasUnreadDirects || hasUnreadGroups ? "var(--unread)" : "rgba(145, 145, 147, 1)"} />
|
||||
</IconButton>
|
||||
</ButtonBase>
|
||||
{fullScreen && (
|
||||
<ButtonBase onClick={()=> {
|
||||
exitFullScreen()
|
||||
setFullScreen(false)
|
||||
}}>
|
||||
<CloseFullscreenIcon sx={{
|
||||
color: 'rgba(145, 145, 147, 1)'
|
||||
}} />
|
||||
</ButtonBase>
|
||||
)}
|
||||
|
||||
</Box>
|
||||
|
||||
{/* Center Title */}
|
||||
@@ -254,6 +262,16 @@ const Header = ({
|
||||
>
|
||||
<HomeIcon color="rgba(145, 145, 147, 1)" />
|
||||
</ButtonBase>
|
||||
{fullScreen && (
|
||||
<ButtonBase onClick={()=> {
|
||||
exitFullScreen()
|
||||
setFullScreen(false)
|
||||
}}>
|
||||
<CloseFullscreenIcon sx={{
|
||||
color: 'rgba(145, 145, 147, 1)'
|
||||
}} />
|
||||
</ButtonBase>
|
||||
)}
|
||||
</Box>
|
||||
{/* Center Title */}
|
||||
<Typography
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import React, { useContext, useMemo, useState } from 'react'
|
||||
import { useRecoilState } from 'recoil';
|
||||
import React, { useContext, useEffect, useMemo, useState } from 'react'
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import isEqual from 'lodash/isEqual'; // Import deep comparison utility
|
||||
import { canSaveSettingToQdnAtom, oldPinnedAppsAtom, settingsLocalLastUpdatedAtom, settingsQDNLastUpdatedAtom, sortablePinnedAppsAtom } from '../../atoms/global';
|
||||
import { canSaveSettingToQdnAtom, hasSettingsChangedAtom, oldPinnedAppsAtom, settingsLocalLastUpdatedAtom, settingsQDNLastUpdatedAtom, sortablePinnedAppsAtom } from '../../atoms/global';
|
||||
import { ButtonBase } from '@mui/material';
|
||||
import { objectToBase64 } from '../../qdn/encryption/group-encryption';
|
||||
import { MyContext } from '../../App';
|
||||
@@ -12,13 +12,14 @@ export const Save = () => {
|
||||
const [pinnedApps, setPinnedApps] = useRecoilState(sortablePinnedAppsAtom);
|
||||
const [settingsQdnLastUpdated, setSettingsQdnLastUpdated] = useRecoilState(settingsQDNLastUpdatedAtom);
|
||||
const [settingsLocalLastUpdated] = useRecoilState(settingsLocalLastUpdatedAtom);
|
||||
const setHasSettingsChangedAtom = useSetRecoilState(hasSettingsChangedAtom);
|
||||
|
||||
const [canSave] = useRecoilState(canSaveSettingToQdnAtom);
|
||||
const [openSnack, setOpenSnack] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [infoSnack, setInfoSnack] = useState(null);
|
||||
const [oldPinnedApps, setOldPinnedApps] = useRecoilState(oldPinnedAppsAtom)
|
||||
console.log('oldpin', {oldPinnedApps, pinnedApps}, settingsQdnLastUpdated, settingsLocalLastUpdated, settingsQdnLastUpdated < settingsLocalLastUpdated,)
|
||||
|
||||
const { show } = useContext(MyContext);
|
||||
|
||||
const hasChanged = useMemo(()=> {
|
||||
@@ -38,11 +39,14 @@ export const Save = () => {
|
||||
}
|
||||
})
|
||||
}
|
||||
console.log('!isEqual(oldChanges, newChanges)', !isEqual(oldChanges, newChanges))
|
||||
if(settingsQdnLastUpdated === -100) return false
|
||||
return !isEqual(oldChanges, newChanges) && settingsQdnLastUpdated < settingsLocalLastUpdated
|
||||
}, [oldPinnedApps, pinnedApps, settingsQdnLastUpdated, settingsLocalLastUpdated])
|
||||
|
||||
useEffect(()=> {
|
||||
setHasSettingsChangedAtom(hasChanged)
|
||||
}, [hasChanged])
|
||||
|
||||
const saveToQdn = async ()=> {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
@@ -64,7 +68,6 @@ export const Save = () => {
|
||||
},
|
||||
},
|
||||
(response) => {
|
||||
console.log("response", response);
|
||||
if (response.error) {
|
||||
rej(response?.message);
|
||||
return;
|
||||
@@ -102,7 +105,6 @@ export const Save = () => {
|
||||
}
|
||||
);
|
||||
});
|
||||
console.log('saved', response)
|
||||
if(response?.identifier){
|
||||
setOldPinnedApps(pinnedApps)
|
||||
setSettingsQdnLastUpdated(Date.now())
|
||||
@@ -114,7 +116,6 @@ export const Save = () => {
|
||||
setOpenSnack(true);
|
||||
}
|
||||
}
|
||||
console.log('save encryptedData', encryptData)
|
||||
} catch (error) {
|
||||
setInfoSnack({
|
||||
type: "error",
|
||||
@@ -126,7 +127,6 @@ export const Save = () => {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
console.log('settingsQdnLastUpdated', settingsQdnLastUpdated)
|
||||
return (
|
||||
<>
|
||||
<ButtonBase onClick={saveToQdn} disabled={!hasChanged || !canSave || isLoading || settingsQdnLastUpdated === -100}>
|
||||
|
Reference in New Issue
Block a user