diff --git a/package-lock.json b/package-lock.json
index 1e8c9c9..03cad1f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,8 @@
"version": "0.0.0",
"dependencies": {
"@chatscope/chat-ui-kit-react": "^2.0.3",
+ "@dnd-kit/core": "^6.1.0",
+ "@dnd-kit/sortable": "^8.0.0",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.16.4",
@@ -53,6 +55,7 @@
"react-redux": "^9.1.2",
"react-virtualized": "^9.22.5",
"react-virtuoso": "^4.10.4",
+ "recoil": "^0.7.7",
"short-unique-id": "^5.2.0",
"slate": "^0.103.0",
"slate-react": "^0.109.0",
@@ -483,6 +486,55 @@
"resolved": "https://registry.npmjs.org/@chatscope/chat-ui-kit-styles/-/chat-ui-kit-styles-1.4.0.tgz",
"integrity": "sha512-016mBJD3DESw7Nh+lkKcPd22xG92ghA0VpIXIbjQtmXhC7Ve6wRazTy8z1Ahut+Tbv179+JxrftuMngsj/yV8Q=="
},
+ "node_modules/@dnd-kit/accessibility": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.0.tgz",
+ "integrity": "sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/core": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.1.0.tgz",
+ "integrity": "sha512-J3cQBClB4TVxwGo3KEjssGEXNJqGVWx17aRTZ1ob0FliR5IjYgTxl5YJbKTzA6IzrtelotH19v6y7uoIRUZPSg==",
+ "dependencies": {
+ "@dnd-kit/accessibility": "^3.1.0",
+ "@dnd-kit/utilities": "^3.2.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/sortable": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-8.0.0.tgz",
+ "integrity": "sha512-U3jk5ebVXe1Lr7c2wU7SBZjcWdQP+j7peHJfCspnA81enlu88Mgd7CC8Q+pub9ubP7eKVETzJW+IBAhsqbSu/g==",
+ "dependencies": {
+ "@dnd-kit/utilities": "^3.2.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@dnd-kit/core": "^6.1.0",
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/utilities": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
+ "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
"node_modules/@emotion/babel-plugin": {
"version": "11.11.0",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
@@ -5007,6 +5059,11 @@
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true
},
+ "node_modules/hamt_plus": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
+ "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA=="
+ },
"node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -9416,6 +9473,25 @@
"react-dom": ">=16 || >=17 || >= 18"
}
},
+ "node_modules/recoil": {
+ "version": "0.7.7",
+ "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz",
+ "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==",
+ "dependencies": {
+ "hamt_plus": "1.0.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.13.1"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
"node_modules/redent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
diff --git a/package.json b/package.json
index 07e3164..185fe71 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,8 @@
},
"dependencies": {
"@chatscope/chat-ui-kit-react": "^2.0.3",
+ "@dnd-kit/core": "^6.1.0",
+ "@dnd-kit/sortable": "^8.0.0",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.16.4",
@@ -57,6 +59,7 @@
"react-redux": "^9.1.2",
"react-virtualized": "^9.22.5",
"react-virtuoso": "^4.10.4",
+ "recoil": "^0.7.7",
"short-unique-id": "^5.2.0",
"slate": "^0.103.0",
"slate-react": "^0.109.0",
diff --git a/src/App.tsx b/src/App.tsx
index 53d88e8..48aaf6a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -98,6 +98,7 @@ import { DrawerComponent } from "./components/Drawer/Drawer";
import { AddressQRCode } from "./components/AddressQRCode";
import { Settings } from "./components/Group/Settings";
import { MainAvatar } from "./components/MainAvatar";
+import { useRetrieveDataLocalStorage } from "./useRetrieveDataLocalStorage";
type extStates =
| "not-authenticated"
@@ -322,6 +323,7 @@ function App() {
const [rootHeight, setRootHeight] = useState("100%");
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const qortalRequestCheckbox1Ref = useRef(null);
+ useRetrieveDataLocalStorage()
useEffect(() => {
if (!isMobile) return;
// Function to set the height of the app to the viewport height
diff --git a/src/atoms/global.ts b/src/atoms/global.ts
new file mode 100644
index 0000000..54d4186
--- /dev/null
+++ b/src/atoms/global.ts
@@ -0,0 +1,7 @@
+import { atom } from 'recoil';
+
+
+export const sortablePinnedAppsAtom = atom({
+ key: 'sortablePinnedAppsFromAtom',
+ default: [],
+});
\ No newline at end of file
diff --git a/src/components/Apps/AppViewer.tsx b/src/components/Apps/AppViewer.tsx
index 3f9486d..ba371ae 100644
--- a/src/components/Apps/AppViewer.tsx
+++ b/src/components/Apps/AppViewer.tsx
@@ -20,7 +20,7 @@ import { MyContext, getBaseApiReact } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import { Spacer } from "../../common/Spacer";
-import { executeEvent } from "../../utils/events";
+import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
import { useFrame } from "react-frame-component";
import { useQortalMessageListener } from "./useQortalMessageListener";
@@ -31,20 +31,40 @@ export const AppViewer = ({ app }) => {
const { rootHeight } = useContext(MyContext);
const iframeRef = useRef(null);
const { document, window } = useFrame();
- useQortalMessageListener(window)
+ const {path} = useQortalMessageListener(window)
+ const [url, setUrl] = useState('')
- const url = useMemo(()=> {
- return `${getBaseApiReact()}/render/${app?.service}/${app?.name}${app?.path != null ? app?.path : ''}?theme=dark&identifier=${(app?.identifier != null && app?.identifier != 'null') ? app?.identifier : ''}`
+ useEffect(()=> {
+ setUrl(`${getBaseApiReact()}/render/${app?.service}/${app?.name}${app?.path != null ? app?.path : ''}?theme=dark&identifier=${(app?.identifier != null && app?.identifier != 'null') ? app?.identifier : ''}`)
}, [app?.service, app?.name, app?.identifier, app?.path])
+ const defaultUrl = useMemo(()=> {
+ return url
+ }, [url])
+ const refreshAppFunc = (e) => {
+ const {tabId} = e.detail
+ if(tabId === app?.tabId){
+ const constructUrl = `${getBaseApiReact()}/render/${app?.service}/${app?.name}${path != null ? path : ''}?theme=dark&identifier=${app?.identifier != null ? app?.identifier : ''}&time=${new Date().getMilliseconds()}`
+ setUrl(constructUrl)
+ }
+ };
+
+ useEffect(() => {
+ subscribeToEvent("refreshApp", refreshAppFunc);
+
+ return () => {
+ unsubscribeFromEvent("refreshApp", refreshAppFunc);
+ };
+ }, [app, path]);
+
return (
);
diff --git a/src/components/Apps/AppViewerContainer.tsx b/src/components/Apps/AppViewerContainer.tsx
index 4152ab2..98303ad 100644
--- a/src/components/Apps/AppViewerContainer.tsx
+++ b/src/components/Apps/AppViewerContainer.tsx
@@ -2,12 +2,24 @@ import React, { useContext, useEffect, useRef } from 'react'
import { AppViewer } from './AppViewer'
import Frame from 'react-frame-component';
import { MyContext } from '../../App';
+import { subscribeToEvent, unsubscribeFromEvent } from '../../utils/events';
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 (
diff --git a/src/components/Apps/Apps.tsx b/src/components/Apps/Apps.tsx
index 1c532c2..0a6ea90 100644
--- a/src/components/Apps/Apps.tsx
+++ b/src/components/Apps/Apps.tsx
@@ -1,4 +1,4 @@
-import React, { useContext, useEffect, useRef, useState } from "react";
+import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { AppsHome } from "./AppsHome";
import { Spacer } from "../../common/Spacer";
import { MyContext, getBaseApiReact } from "../../App";
@@ -15,19 +15,27 @@ import { AppViewer } from "./AppViewer";
import AppViewerContainer from "./AppViewerContainer";
import ShortUniqueId from "short-unique-id";
import { AppPublish } from "./AppPublish";
+import { useRecoilState } from "recoil";
const uid = new ShortUniqueId({ length: 8 });
export const Apps = ({ mode, setMode, show , myName}) => {
const [availableQapps, setAvailableQapps] = useState([]);
- const [downloadedQapps, setDownloadedQapps] = useState([]);
const [selectedAppInfo, setSelectedAppInfo] = useState(null);
const [tabs, setTabs] = useState([]);
const [selectedTab, setSelectedTab] = useState(null);
const [isNewTabWindow, setIsNewTabWindow] = useState(false);
const [categories, setCategories] = useState([])
- const [myApp, setMyApp] = useState(null)
- const [myWebsite, setMyWebsite] = useState(null)
+
+
+ const myApp = useMemo(()=> {
+
+ return availableQapps.find((app)=> app.name === myName && app.service === 'APP')
+ }, [myName, availableQapps])
+ const myWebsite = useMemo(()=> {
+
+ return availableQapps.find((app)=> app.name === myName && app.service === 'WEBSITE')
+ }, [myName, availableQapps])
useEffect(() => {
setTimeout(() => {
@@ -62,12 +70,12 @@ export const Apps = ({ mode, setMode, show , myName}) => {
}
}, []);
- const getQapps = React.useCallback(async (myName) => {
+ const getQapps = React.useCallback(async () => {
try {
let apps = [];
let websites = [];
// dispatch(setIsLoadingGlobal(true))
- const url = `${getBaseApiReact()}/arbitrary/resources/search?service=APP&mode=ALL&includestatus=true&limit=0&includemetadata=true`;
+ const url = `${getBaseApiReact()}/arbitrary/resources/search?service=APP&mode=ALL&limit=0&includestatus=true&includemetadata=true`;
const response = await fetch(url, {
method: "GET",
@@ -77,7 +85,8 @@ export const Apps = ({ mode, setMode, show , myName}) => {
});
if (!response?.ok) return;
const responseData = await response.json();
- const urlWebsites = `${getBaseApiReact()}/arbitrary/resources/search?service=WEBSITE&mode=ALL&includestatus=true&limit=0&includemetadata=true`;
+ 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, {
method: "GET",
@@ -87,31 +96,20 @@ export const Apps = ({ mode, setMode, show , myName}) => {
});
if (!responseWebsites?.ok) return;
const responseDataWebsites = await responseWebsites.json();
- const findMyWebsite = responseDataWebsites.find((web)=> web.name === myName)
- if(findMyWebsite){
- setMyWebsite(findMyWebsite)
- }
- const findMyApp = responseData.find((web)=> web.name === myName)
- console.log('findMyApp', findMyApp)
- if(findMyApp){
- setMyWebsite(findMyApp)
- }
+
apps = responseData;
websites = responseDataWebsites;
const combine = [...apps, ...websites];
setAvailableQapps(combine);
- setDownloadedQapps(
- combine.filter((qapp) => qapp?.status?.status === "READY")
- );
} catch (error) {
} finally {
// dispatch(setIsLoadingGlobal(false))
}
}, []);
useEffect(() => {
- getQapps(myName);
+ getQapps();
getCategories()
- }, [getQapps, getCategories, myName]);
+ }, [getQapps, getCategories]);
const selectedAppInfoFunc = (e) => {
const data = e.detail?.data;
@@ -256,11 +254,10 @@ export const Apps = ({ mode, setMode, show , myName}) => {
>
{mode !== "viewer" && }
{mode === "home" && (
-
+
)}
{mode === "library" && (
{
{isNewTabWindow && mode === "viewer" && (
<>
-
+
>
)}
{mode !== "viewer" && }
diff --git a/src/components/Apps/AppsHome.tsx b/src/components/Apps/AppsHome.tsx
index d9142a1..2927694 100644
--- a/src/components/Apps/AppsHome.tsx
+++ b/src/components/Apps/AppsHome.tsx
@@ -11,8 +11,9 @@ import { Add } from "@mui/icons-material";
import { getBaseApiReact } from "../../App";
import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import { executeEvent } from "../../utils/events";
+import { SortablePinnedApps } from "./SortablePinnedApps";
-export const AppsHome = ({ downloadedQapps, setMode, myApp, myWebsite, myName }) => {
+export const AppsHome = ({ setMode, myApp, myWebsite, availableQapps }) => {
return (
Add
- {myApp &&(
- {
- executeEvent("addTab", {
- data: myApp
- })
- }}
- >
-
-
-
-
-
-
-
- {myApp?.name}
-
-
-
- )}
- {myWebsite &&(
- {
- executeEvent("addTab", {
- data: myWebsite
- })
- }}
- >
-
-
-
-
-
-
-
- {myWebsite?.name}
-
-
-
- )}
- {downloadedQapps?.filter((item)=> item?.name !== myName).map((app) => {
- return (
- {
- executeEvent("addTab", {
- data: app
- })
- }}
- >
-
-
-
-
-
-
-
- {app?.name}
-
-
-
- );
- })}
+
+
+
);
};
diff --git a/src/components/Apps/AppsLibrary.tsx b/src/components/Apps/AppsLibrary.tsx
index d220a4e..78f083a 100644
--- a/src/components/Apps/AppsLibrary.tsx
+++ b/src/components/Apps/AppsLibrary.tsx
@@ -74,7 +74,7 @@ const ScrollerStyled = styled('div')({
"-ms-overflow-style": "none",
});
-export const AppsLibrary = ({ downloadedQapps, availableQapps, setMode, myName, hasPublishApp }) => {
+export const AppsLibrary = ({ availableQapps, setMode, myName, hasPublishApp }) => {
const [searchValue, setSearchValue] = useState("");
const virtuosoRef = useRef();
const { rootHeight } = useContext(MyContext);
diff --git a/src/components/Apps/AppsNavBar.tsx b/src/components/Apps/AppsNavBar.tsx
index 09c4e22..0a703cf 100644
--- a/src/components/Apps/AppsNavBar.tsx
+++ b/src/components/Apps/AppsNavBar.tsx
@@ -7,15 +7,41 @@ import {
import NavBack from "../../assets/svgs/NavBack.svg";
import NavAdd from "../../assets/svgs/NavAdd.svg";
import NavMoreMenu from "../../assets/svgs/NavMoreMenu.svg";
-import { ButtonBase, Tab, Tabs } from "@mui/material";
+import { ButtonBase, ListItemIcon, ListItemText, Menu, MenuItem, Tab, Tabs } from "@mui/material";
import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events";
import TabComponent from "./TabComponent";
+import PushPinIcon from '@mui/icons-material/PushPin';
+import RefreshIcon from '@mui/icons-material/Refresh';
+import { useRecoilState } from "recoil";
+import { sortablePinnedAppsAtom } from "../../atoms/global";
+
+export function saveToLocalStorage(key, value) {
+ try {
+ const serializedValue = JSON.stringify(value);
+ localStorage.setItem(key, serializedValue);
+ console.log(`Data saved to localStorage with key: ${key}`);
+ } catch (error) {
+ console.error('Error saving to localStorage:', error);
+ }
+}
+
export const AppsNavBar = () => {
const [tabs, setTabs] = useState([])
const [selectedTab, setSelectedTab] = useState([])
const [isNewTabWindow, setIsNewTabWindow] = useState(false)
const tabsRef = useRef(null);
+ const [anchorEl, setAnchorEl] = useState(null);
+ const open = Boolean(anchorEl);
+ const [sortablePinnedApps, setSortablePinnedApps] = useRecoilState(sortablePinnedAppsAtom);
+
+ const handleClick = (event) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
useEffect(() => {
// Scroll to the last tab whenever the tabs array changes (e.g., when a new tab is added)
@@ -44,6 +70,8 @@ export const AppsNavBar = () => {
unsubscribeFromEvent("setTabsToNav", setTabsToNav);
};
}, []);
+
+ const isSelectedAppPinned = !!sortablePinnedApps?.find((item)=> item?.name === selectedTab?.name && item?.service === selectedTab?.service)
return (
@@ -95,13 +123,119 @@ export const AppsNavBar = () => {
width: '40px'
}} src={NavAdd} />
-
+ {
+ handleClick(e)
+ }}>
+
);
};
diff --git a/src/components/Apps/SortablePinnedApps.tsx b/src/components/Apps/SortablePinnedApps.tsx
new file mode 100644
index 0000000..374cb1d
--- /dev/null
+++ b/src/components/Apps/SortablePinnedApps.tsx
@@ -0,0 +1,166 @@
+import React, { useEffect, useMemo, useRef, useState } from 'react';
+import { DndContext, closestCenter } from '@dnd-kit/core';
+import { arrayMove, SortableContext, sortableKeyboardCoordinates, useSortable } from '@dnd-kit/sortable';
+import { KeyboardSensor, PointerSensor, TouchSensor, useSensor, useSensors } from '@dnd-kit/core';
+import { CSS } from '@dnd-kit/utilities';
+import { Avatar, ButtonBase } from '@mui/material';
+import { AppCircle, AppCircleContainer, AppCircleLabel } from './Apps-styles';
+import { getBaseApiReact } from '../../App';
+import { executeEvent } from '../../utils/events';
+import { sortablePinnedAppsAtom } from '../../atoms/global';
+import { useRecoilState } from 'recoil';
+import { saveToLocalStorage } from './AppsNavBar';
+
+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,
+ padding: '10px',
+ border: '1px solid #ccc',
+ marginBottom: '5px',
+ borderRadius: '4px',
+ backgroundColor: '#f9f9f9',
+ cursor: 'grab',
+ color: 'black'
+ };
+
+ return (
+ {
+ executeEvent("addTab", {
+ data: app
+ })
+ }}
+ >
+
+
+
+
+
+
+
+ {app?.metadata?.title || app?.name}
+
+
+
+ );
+};
+
+export const SortablePinnedApps = ({ myWebsite, myApp, availableQapps = [] }) => {
+ const [pinnedApps, setPinnedApps] = useRecoilState(sortablePinnedAppsAtom);
+
+ const transformPinnedApps = useMemo(()=> {
+ console.log({myWebsite, myApp, availableQapps, pinnedApps})
+ let pinned = [...pinnedApps]
+ const findMyWebsite = pinned?.find((item)=> item?.service === myWebsite?.service && item?.name === myWebsite?.name)
+ const findMyApp = pinned?.find((item)=> item?.service === myApp?.service && item?.name === myApp?.name)
+
+ if(myWebsite && !findMyWebsite){
+ pinned.unshift(myWebsite)
+ }
+ if(myApp && !findMyApp){
+ pinned.unshift(myApp)
+ }
+ pinned = pinned.map((pin)=> {
+ const findIndex = availableQapps?.findIndex((item)=> item?.service === pin?.service && item?.name === pin?.name)
+ if(findIndex !== -1) return availableQapps[findIndex]
+
+ return pin
+ })
+ 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: {
+ distance: 10, // Set a distance to avoid triggering drag on small movements
+ },
+ }),
+ useSensor(TouchSensor, {
+ activationConstraint: {
+ distance: 10, // Also apply to touch
+ },
+ }),
+ useSensor(KeyboardSensor, {
+ coordinateGetter: sortableKeyboardCoordinates,
+ })
+ );
+
+ const handleDragEnd = (event) => {
+ const { active, over } = event;
+
+ if (!over) return; // Make sure the drop target exists
+
+ if (active.id !== over.id) {
+ const oldIndex = transformPinnedApps.findIndex((item) => `${item?.service}-${item?.name}` === active.id);
+ const newIndex = transformPinnedApps.findIndex((item) => `${item?.service}-${item?.name}` === over.id);
+
+ const newOrder = arrayMove(transformPinnedApps, oldIndex, newIndex);
+ setPinnedApps(newOrder);
+ saveToLocalStorage('sortablePinnedApps', newOrder)
+
+ }
+ };
+ return (
+
+ `${app?.service}-${app?.name}`)}>
+ {transformPinnedApps.map((app) => (
+
+ ))}
+
+
+ );
+};
+
diff --git a/src/components/Apps/useQortalMessageListener.tsx b/src/components/Apps/useQortalMessageListener.tsx
index 270a540..c9ace3f 100644
--- a/src/components/Apps/useQortalMessageListener.tsx
+++ b/src/components/Apps/useQortalMessageListener.tsx
@@ -1,4 +1,4 @@
-import { useEffect } from 'react';
+import { useEffect, useState } from 'react';
class Semaphore {
constructor(count) {
@@ -313,6 +313,7 @@ const UIQortalRequests = [
}
export const useQortalMessageListener = (frameWindow) => {
+ const [path, setPath] = useState('')
useEffect(() => {
console.log("Listener added react");
@@ -376,7 +377,11 @@ export const useQortalMessageListener = (frameWindow) => {
error: 'Failed to prepare data for publishing',
});
}
- }
+ } else if(event?.data?.action === 'LINK_TO_QDN_RESOURCE' ||
+ event?.data?.action === 'QDN_RESOURCE_DISPLAYED'){
+ const pathUrl = event?.data?.path != null ? (event?.data?.path.startsWith('/') ? '' : '/') + event?.data?.path : null
+ setPath(pathUrl)
+ }
};
// Add the listener for messages coming from the frameWindow
@@ -401,5 +406,7 @@ export const useQortalMessageListener = (frameWindow) => {
return true; // Keep the message channel open for async response
}
});
+
+ return {path}
};
diff --git a/src/components/Mobile/MobileHeader.tsx b/src/components/Mobile/MobileHeader.tsx
index 38e6f73..00918cb 100644
--- a/src/components/Mobile/MobileHeader.tsx
+++ b/src/components/Mobile/MobileHeader.tsx
@@ -19,6 +19,7 @@ import { ArrowDownIcon } from "../../assets/Icons/ArrowDownIcon";
import { MessagingIcon } from "../../assets/Icons/MessagingIcon";
import { MessagingIcon2 } from "../../assets/Icons/MessagingIcon2";
import { HubsIcon } from "../../assets/Icons/HubsIcon";
+import { Save } from "../Save/Save";
const Header = ({
logoutFunc,
@@ -135,6 +136,7 @@ const Header = ({
/>
+
{
+ const [pinnedApps, setPinnedApps] = useRecoilState(sortablePinnedAppsAtom);
+ const [oldPinnedApps, setOldPinnedApps] = useState(pinnedApps)
+ console.log('oldpin', {oldPinnedApps, pinnedApps})
+
+ const hasChanged = useMemo(()=> {
+ return !isEqual(pinnedApps, oldPinnedApps)
+ }, [oldPinnedApps, pinnedApps])
+ return (
+ {hasChanged && 'Save'}
+ )
+}
diff --git a/src/main.tsx b/src/main.tsx
index 8a477a3..2bdf4b3 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -6,7 +6,7 @@ import './index.css'
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { CssBaseline } from '@mui/material';
import { MessageQueueProvider } from './MessageQueueContext.tsx';
-
+import { RecoilRoot } from 'recoil';
const theme = createTheme({
palette: {
primary: {
@@ -50,7 +50,9 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
,
diff --git a/src/useRetrieveDataLocalStorage.tsx b/src/useRetrieveDataLocalStorage.tsx
new file mode 100644
index 0000000..93831e0
--- /dev/null
+++ b/src/useRetrieveDataLocalStorage.tsx
@@ -0,0 +1,34 @@
+import React, { useCallback, useEffect } from 'react'
+import { useSetRecoilState } from 'recoil';
+import { sortablePinnedAppsAtom } from './atoms/global';
+
+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;
+ }
+}
+
+export const useRetrieveDataLocalStorage = () => {
+ const setSortablePinnedApps = useSetRecoilState(sortablePinnedAppsAtom);
+
+
+ const getSortablePinnedApps = useCallback(()=> {
+ const pinnedAppsLocal = fetchFromLocalStorage('sortablePinnedApps')
+ if(pinnedAppsLocal){
+ setSortablePinnedApps(pinnedAppsLocal)
+ }
+ }, [])
+ useEffect(()=> {
+
+ getSortablePinnedApps()
+ }, [getSortablePinnedApps])
+
+}