diff --git a/net/web/src/Api/getAccountStatus.js b/net/web/src/Api/getAccountStatus.js new file mode 100644 index 00000000..3f1f0fa6 --- /dev/null +++ b/net/web/src/Api/getAccountStatus.js @@ -0,0 +1,8 @@ +import { checkResponse, fetchWithTimeout } from './fetchUtil'; + +export async function getAccountStatus(token) { + let status = await fetchWithTimeout('/account/status?agent=' + token, { method: 'GET' }); + checkResponse(status); + return await status.json() +} + diff --git a/net/web/src/Api/getProfile.js b/net/web/src/Api/getProfile.js new file mode 100644 index 00000000..5fd6c1e2 --- /dev/null +++ b/net/web/src/Api/getProfile.js @@ -0,0 +1,9 @@ +import { checkResponse, fetchWithTimeout } from './fetchUtil'; + +export async function getProfile(token) { + let profile = await fetchWithTimeout(`/profile?agent=${token}`, { method: 'GET' }); + checkResponse(profile) + return await profile.json() +} + + diff --git a/net/web/src/Api/getProfileImageUrl.js b/net/web/src/Api/getProfileImageUrl.js new file mode 100644 index 00000000..038a7544 --- /dev/null +++ b/net/web/src/Api/getProfileImageUrl.js @@ -0,0 +1,4 @@ +export function getProfileImageUrl(token, revision) { + return '/profile/image?agent=' + token + "&revision=" + revision +} + diff --git a/net/web/src/Api/setAccountSearchable.js b/net/web/src/Api/setAccountSearchable.js new file mode 100644 index 00000000..84f357e5 --- /dev/null +++ b/net/web/src/Api/setAccountSearchable.js @@ -0,0 +1,7 @@ +import { checkResponse, fetchWithTimeout } from './fetchUtil'; + +export async function setAccountSearchable(token, flag) { + let res = await fetchWithTimeout('/account/searchable?agent=' + token, { method: 'PUT', body: JSON.stringify(flag) }) + checkResponse(res); +} + diff --git a/net/web/src/Api/setProfileData.js b/net/web/src/Api/setProfileData.js new file mode 100644 index 00000000..00a405fc --- /dev/null +++ b/net/web/src/Api/setProfileData.js @@ -0,0 +1,9 @@ +import { checkResponse, fetchWithTimeout } from './fetchUtil'; + +export async function setProfileData(token, name, location, description) { + let data = { name: name, location: location, description: description }; + let profile = await fetchWithTimeout(`/profile/data?agent=${token}`, { method: 'PUT', body: JSON.stringify(data) }); + checkResponse(profile) + return await profile.json() +} + diff --git a/net/web/src/Api/setProfileImage.js b/net/web/src/Api/setProfileImage.js new file mode 100644 index 00000000..9eb46a11 --- /dev/null +++ b/net/web/src/Api/setProfileImage.js @@ -0,0 +1,8 @@ +import { checkResponse, fetchWithTimeout } from './fetchUtil'; + +export async function setProfileImage(token, image) { + let profile = await fetchWithTimeout(`/profile/image?agent=${token}`, { method: 'PUT', body: JSON.stringify(image) }); + checkResponse(profile) + return await profile.json() +} + diff --git a/net/web/src/App.js b/net/web/src/App.js index 35f5d3ce..51e9f162 100644 --- a/net/web/src/App.js +++ b/net/web/src/App.js @@ -1,5 +1,11 @@ import login from './login.png'; import { AppContextProvider } from './AppContext/AppContext'; +import { AccountContextProvider } from './AppContext/AccountContext'; +import { ProfileContextProvider } from './AppContext/ProfileContext'; +import { ArticleContextProvider } from './AppContext/ArticleContext'; +import { GroupContextProvider } from './AppContext/GroupContext'; +import { CardContextProvider } from './AppContext/CardContext'; +import { ChannelContextProvider } from './AppContext/ChannelContext'; import { ConversationContextProvider } from './ConversationContext/ConversationContext'; import { Home } from './Home/Home'; import { Login } from './Login/Login'; @@ -14,34 +20,46 @@ import 'antd/dist/antd.min.css'; function App() { return ( - -
- -
-
- - - } /> - } /> - } /> - }> - } /> - } /> - - - - } /> - - - - } /> - - - -
-
+ + + + + + + +
+ +
+
+ + + } /> + } /> + } /> + }> + } /> + } /> + + + + } /> + + + + } /> + + + +
+
+
+
+
+
+
+
); } diff --git a/net/web/src/AppContext/AccountContext.js b/net/web/src/AppContext/AccountContext.js new file mode 100644 index 00000000..e4c34613 --- /dev/null +++ b/net/web/src/AppContext/AccountContext.js @@ -0,0 +1,14 @@ +import { createContext } from 'react'; +import { useAccountContext } from './useAccountContext.hook'; + +export const AccountContext = createContext({}); + +export function AccountContextProvider({ children }) { + const { state, actions } = useAccountContext(); + return ( + + {children} + + ); +} + diff --git a/net/web/src/AppContext/ArticleContext.js b/net/web/src/AppContext/ArticleContext.js new file mode 100644 index 00000000..8c9916b3 --- /dev/null +++ b/net/web/src/AppContext/ArticleContext.js @@ -0,0 +1,14 @@ +import { createContext } from 'react'; +import { useArticleContext } from './useArticleContext.hook'; + +export const ArticleContext = createContext({}); + +export function ArticleContextProvider({ children }) { + const { state, actions } = useArticleContext(); + return ( + + {children} + + ); +} + diff --git a/net/web/src/AppContext/CardContext.js b/net/web/src/AppContext/CardContext.js new file mode 100644 index 00000000..1bc8fb18 --- /dev/null +++ b/net/web/src/AppContext/CardContext.js @@ -0,0 +1,14 @@ +import { createContext } from 'react'; +import { useCardContext } from './useCardContext.hook'; + +export const CardContext = createContext({}); + +export function CardContextProvider({ children }) { + const { state, actions } = useCardContext(); + return ( + + {children} + + ); +} + diff --git a/net/web/src/AppContext/ChannelContext.js b/net/web/src/AppContext/ChannelContext.js new file mode 100644 index 00000000..103e537c --- /dev/null +++ b/net/web/src/AppContext/ChannelContext.js @@ -0,0 +1,14 @@ +import { createContext } from 'react'; +import { useChannelContext } from './useChannelContext.hook'; + +export const ChannelContext = createContext({}); + +export function ChannelContextProvider({ children }) { + const { state, actions } = useChannelContext(); + return ( + + {children} + + ); +} + diff --git a/net/web/src/AppContext/GroupContext.js b/net/web/src/AppContext/GroupContext.js new file mode 100644 index 00000000..365db76a --- /dev/null +++ b/net/web/src/AppContext/GroupContext.js @@ -0,0 +1,14 @@ +import { createContext } from 'react'; +import { useGroupContext } from './useGroupContext.hook'; + +export const GroupContext = createContext({}); + +export function GroupContextProvider({ children }) { + const { state, actions } = useGroupContext(); + return ( + + {children} + + ); +} + diff --git a/net/web/src/AppContext/ProfileContext.js b/net/web/src/AppContext/ProfileContext.js new file mode 100644 index 00000000..ef48f314 --- /dev/null +++ b/net/web/src/AppContext/ProfileContext.js @@ -0,0 +1,14 @@ +import { createContext } from 'react'; +import { useProfileContext } from './useProfileContext.hook'; + +export const ProfileContext = createContext({}); + +export function ProfileContextProvider({ children }) { + const { state, actions } = useProfileContext(); + return ( + + {children} + + ); +} + diff --git a/net/web/src/AppContext/useAccountContext.hook.js b/net/web/src/AppContext/useAccountContext.hook.js new file mode 100644 index 00000000..0d50833b --- /dev/null +++ b/net/web/src/AppContext/useAccountContext.hook.js @@ -0,0 +1,47 @@ +import { useEffect, useState, useRef } from 'react'; +import { setAccountSearchable } from '../Api/setAccountSearchable'; +import { getAccountStatus } from '../Api/getAccountStatus'; + +export function useAccountContext() { + const [state, setState] = useState({ + token: null, + revision: 0, + status: null, + }); + const next = useRef(null); + + const updateState = (value) => { + setState((s) => ({ ...s, ...value })) + } + + const setStatus = async (revision) => { + if (next.current == null) { + let status = await getAccountStatus(state.token); + updateState({ revision, status }); + if (next.current != null) { + let rev = next.current; + next.current = null; + setStatus(rev); + } + } + else { + next.current = revision; + } + } + + const actions = { + setToken: async (token) => { + updateState({ token }); + }, + setRevision: async (revision) => { + setStatus(revision); + }, + setSearchable: async (flag) => { + await setAccountSearchable(state.token, flag); + }, + } + + return { state, actions } +} + + diff --git a/net/web/src/AppContext/useAppContext.hook.js b/net/web/src/AppContext/useAppContext.hook.js index 93f65986..1d03432f 100644 --- a/net/web/src/AppContext/useAppContext.hook.js +++ b/net/web/src/AppContext/useAppContext.hook.js @@ -1,19 +1,16 @@ -import { useEffect, useState, useRef } from 'react'; -import { getContactProfile, setCardProfile, getCards, getCardImageUrl, getCardProfile, getCardDetail, getListingImageUrl, getListing, setProfileImage, setProfileData, getProfileImageUrl, getAccountStatus, setAccountSearchable, getProfile, getGroups, getAvailable, getUsername, setLogin, createAccount } from './fetchUtil'; +import { useEffect, useState, useRef, useContext } from 'react'; +import { getContactProfile, setCardProfile, getCards, getCardImageUrl, getCardProfile, getCardDetail, getListingImageUrl, getListing, getGroups, getAvailable, getUsername, setLogin, createAccount } from './fetchUtil'; import { getChannels } from '../Api/getChannels'; import { getChannel } from '../Api/getChannel'; import { getContactChannels } from '../Api/getContactChannels'; import { getContactChannel } from '../Api/getContactChannel'; -async function updateAccount(token, updateData) { - let status = await getAccountStatus(token); - updateData({ status: status }); -} - -async function updateProfile(token, updateData) { - let profile = await getProfile(token); - updateData({ profile: profile }) -} +import { AccountContext } from './AccountContext'; +import { ProfileContext } from './ProfileContext'; +import { ArticleContext } from './ArticleContext'; +import { GroupContext } from './GroupContext'; +import { CardContext } from './CardContext'; +import { ChannelContext } from './ChannelContext'; async function updateGroups(token, revision, groupMap, updateData) { let groups = await getGroups(token, revision); @@ -189,10 +186,9 @@ function appLogout(updateState, clearWebsocket) { localStorage.removeItem("session"); } - - export function useAppContext() { const [state, setState] = useState(null); + const [appRevision, setAppRevision] = useState(); const groupRevision = useRef(null); const accountRevision = useRef(null); @@ -217,6 +213,13 @@ export function useAppContext() { }) } + const accountContext = useContext(AccountContext); + const profileContext = useContext(ProfileContext); + const channelContext = useContext(ChannelContext); + const cardContext = useContext(CardContext); + const groupContext = useContext(GroupContext); + const articleContext = useContext(ArticleContext); + const mergeChannels = () => { let merged = []; cards.current.forEach((value, key, map) => { @@ -269,16 +272,6 @@ export function useAppContext() { appLogout(updateState, clearWebsocket); resetData(); }, - setProfileData: async (name, location, description) => { - await setProfileData(state.token, name, location, description); - }, - setProfileImage: async (image) => { - await setProfileImage(state.token, image); - }, - setAccountSearchable: async (flag) => { - await setAccountSearchable(state.token, flag); - }, - profileImageUrl: () => getProfileImageUrl(state.token, state.Data?.profile?.revision), getRegistry: async (node) => getListing(node), getRegistryImageUrl: (server, guid, revision) => getListingImageUrl(server, guid, revision), getCardImageUrl: (cardId, revision) => getCardImageUrl(state.token, cardId, revision), @@ -306,16 +299,21 @@ export function useAppContext() { available: getAvailable, } + useEffect(() => { + if (appRevision) { + accountContext.actions.setRevision(appRevision.account); + profileContext.actions.setRevision(appRevision.profile); + articleContext.actions.setRevision(appRevision.article); + groupContext.actions.setRevision(appRevision.group); + cardContext.actions.setRevision(appRevision.card); + channelContext.actions.setRevision(appRevision.channel); + } + }, [appRevision]); + const processRevision = async (token) => { while(revision.current != null) { let rev = revision.current; - // update profile if revision changed - if (rev.profile != profileRevision.current) { - await updateProfile(token, updateData) - profileRevision.current = rev.profile - } - // update group if revision changed if (rev.group != groupRevision.current) { await updateGroups(token, groupRevision.current, groups.current, updateData); @@ -334,12 +332,6 @@ export function useAppContext() { channelRevision.current = rev.channel } - // update account status if revision changed - if (rev.account != accountRevision.current) { - await updateAccount(token, updateData) - accountRevision.current = rev.account - } - // check if new revision was received during processing if (rev == revision.current) { revision.current = null @@ -348,6 +340,14 @@ export function useAppContext() { } const setWebsocket = (token) => { + + accountContext.actions.setToken(token); + profileContext.actions.setToken(token); + articleContext.actions.setToken(token); + groupContext.actions.setToken(token); + cardContext.actions.setToken(token); + channelContext.actions.setToken(token); + ws.current = new WebSocket("wss://" + window.location.host + "/status"); ws.current.onmessage = (ev) => { try { @@ -355,8 +355,10 @@ export function useAppContext() { revision.current = JSON.parse(ev.data); } else { - revision.current = JSON.parse(ev.data); - processRevision(token) + let rev = JSON.parse(ev.data); + revision.current = rev; + processRevision(token); + setAppRevision(rev); } } catch (err) { diff --git a/net/web/src/AppContext/useArticleContext.hook.js b/net/web/src/AppContext/useArticleContext.hook.js new file mode 100644 index 00000000..f5ad790c --- /dev/null +++ b/net/web/src/AppContext/useArticleContext.hook.js @@ -0,0 +1,28 @@ +import { useEffect, useState, useRef } from 'react'; + +export function useArticleContext() { + const [state, setState] = useState({ + token: null, + revision: 0, + }); + + useEffect(() => { + }, []); + + const updateState = (value) => { + setState((s) => ({ ...s, ...value })) + } + + const actions = { + setToken: (token) => { + updateState({ token }); + }, + setRevision: async (revision) => { + updateState({ revision }); + }, + } + + return { state, actions } +} + + diff --git a/net/web/src/AppContext/useCardContext.hook.js b/net/web/src/AppContext/useCardContext.hook.js new file mode 100644 index 00000000..b84b8ae2 --- /dev/null +++ b/net/web/src/AppContext/useCardContext.hook.js @@ -0,0 +1,25 @@ +import { useEffect, useState, useRef } from 'react'; + +export function useCardContext() { + const [state, setState] = useState({ + token: null, + revision: 0, + }); + + const updateState = (value) => { + setState((s) => ({ ...s, ...value })) + } + + const actions = { + setToken: async (token) => { + updateState({ token }); + }, + setRevision: async (revision) => { + updateState({ revision }); + }, + } + + return { state, actions } +} + + diff --git a/net/web/src/AppContext/useChannelContext.hook.js b/net/web/src/AppContext/useChannelContext.hook.js new file mode 100644 index 00000000..aeaa8deb --- /dev/null +++ b/net/web/src/AppContext/useChannelContext.hook.js @@ -0,0 +1,25 @@ +import { useEffect, useState, useRef } from 'react'; + +export function useChannelContext() { + const [state, setState] = useState({ + token: null, + revision: 0, + }); + + const updateState = (value) => { + setState((s) => ({ ...s, ...value })) + } + + const actions = { + setToken: (token) => { + updateState({ token }); + }, + setRevision: async (revision) => { + updateState({ revision }); + }, + } + + return { state, actions } +} + + diff --git a/net/web/src/AppContext/useGroupContext.hook.js b/net/web/src/AppContext/useGroupContext.hook.js new file mode 100644 index 00000000..9121d044 --- /dev/null +++ b/net/web/src/AppContext/useGroupContext.hook.js @@ -0,0 +1,28 @@ +import { useEffect, useState, useRef } from 'react'; + +export function useGroupContext() { + const [state, setState] = useState({ + token: null, + revision: 0, + }); + + useEffect(() => { + }, []); + + const updateState = (value) => { + setState((s) => ({ ...s, ...value })) + } + + const actions = { + setToken: async (token) => { + updateState({ token }); + }, + setRevision: async (revision) => { + updateState({ revision }); + }, + } + + return { state, actions } +} + + diff --git a/net/web/src/AppContext/useProfileContext.hook.js b/net/web/src/AppContext/useProfileContext.hook.js new file mode 100644 index 00000000..91415773 --- /dev/null +++ b/net/web/src/AppContext/useProfileContext.hook.js @@ -0,0 +1,53 @@ +import { useEffect, useState, useRef } from 'react'; +import { getProfile } from '../Api/getProfile'; +import { setProfileData } from '../Api/setProfileData'; +import { setProfileImage } from '../Api/setProfileImage'; +import { getProfileImageUrl } from '../Api/getProfileImageUrl'; + +export function useProfileContext() { + const [state, setState] = useState({ + token: null, + revision: 0, + profile: {}, + }); + const next = useRef(null); + + const updateState = (value) => { + setState((s) => ({ ...s, ...value })) + } + + const setProfile = async (revision) => { + if (next.current == null) { + let profile = await getProfile(state.token); + updateState({ revision, profile }); + if (next.current != null) { + let rev = next.current; + next.current = null; + setProfile(rev); + } + } + else { + next.current = revision; + } + } + + const actions = { + setToken: (token) => { + updateState({ token }); + }, + setRevision: (revision) => { + setProfile(revision); + }, + setProfileData: async (name, location, description) => { + await setProfileData(state.token, name, location, description); + }, + setProfileImage: async (image) => { + await setProfileImage(state.token, image); + }, + profileImageUrl: () => getProfileImageUrl(state.token, state.Data?.profile?.revision), + } + + return { state, actions } +} + + diff --git a/net/web/src/User/Profile/useProfile.hook.js b/net/web/src/User/Profile/useProfile.hook.js index 0a1222c9..6729980a 100644 --- a/net/web/src/User/Profile/useProfile.hook.js +++ b/net/web/src/User/Profile/useProfile.hook.js @@ -1,5 +1,6 @@ import { useContext, useState, useEffect } from 'react'; -import { AppContext } from '../../AppContext/AppContext'; +import { ProfileContext } from '../../AppContext/ProfileContext'; +import { AccountContext } from '../../AppContext/AccountContext'; import { useNavigate } from "react-router-dom"; const IMAGE_DIM = 256; @@ -22,7 +23,10 @@ export function useProfile() { }); const navigate = useNavigate(); - const app = useContext(AppContext); + const profile = useContext(ProfileContext); + const account = useContext(AccountContext); + +console.log("ACCOUNT:", account); const updateState = (value) => { setState((s) => ({ ...s, ...value })); @@ -52,7 +56,7 @@ export function useProfile() { if(!state.modalBusy) { updateState({ modalBusy: true }); try { - await app.actions.setProfileData(state.modalName, state.modalLocation, state.modalDescription); + await profile.actions.setProfileData(state.modalName, state.modalLocation, state.modalDescription); set = true } catch (err) { @@ -64,7 +68,7 @@ export function useProfile() { }, setSearchable: async (flag) => { try { - await app.actions.setAccountSearchable(flag); + await account.actions.setSearchable(flag); } catch (err) { window.alert(err); @@ -94,7 +98,7 @@ export function useProfile() { }; let dataUrl = await processImg(); let data = dataUrl.split(",")[1]; - await app.actions.setProfileImage(data); + await profile.actions.setProfileImage(data); set = true } catch (err) { @@ -107,28 +111,31 @@ export function useProfile() { }; useEffect(() => { - if (app?.state?.Data?.profile) { - let profile = app.state.Data.profile; - if (profile.image != null) { - updateState({ imageUrl: app.actions.profileImageUrl() }) - updateState({ modalImage: app.actions.profileImageUrl() }) + if (profile?.state?.profile) { + let identity = profile.state.profile; + if (identity.image != null) { + updateState({ imageUrl: profile.actions.profileImageUrl() }) + updateState({ modalImage: profile.actions.profileImageUrl() }) } else { updateState({ imageUrl: '' }) updateState({ modalImage: null }) } - updateState({ name: profile.name }); - updateState({ modalName: profile.name }); - updateState({ handle: profile.handle }); - updateState({ description: profile.description }); - updateState({ modalDescription: profile.description }); - updateState({ location: profile.location }); - updateState({ modalLocation: profile.location }); + updateState({ name: identity.name }); + updateState({ modalName: identity.name }); + updateState({ handle: identity.handle }); + updateState({ description: identity.description }); + updateState({ modalDescription: identity.description }); + updateState({ location: identity.location }); + updateState({ modalLocation: identity.location }); } - if (app?.state?.Data?.status) { - let status = app.state.Data.status; + }, [profile]); + + useEffect(() => { + if (account?.state?.status) { + let status = account.state.status; updateState({ searchable: status.searchable }); } - }, [app]) + }, [account]) return { state, actions }; } diff --git a/net/web/src/User/SideBar/Identity/useIdentity.hook.js b/net/web/src/User/SideBar/Identity/useIdentity.hook.js index 2f95b350..5d7a2c14 100644 --- a/net/web/src/User/SideBar/Identity/useIdentity.hook.js +++ b/net/web/src/User/SideBar/Identity/useIdentity.hook.js @@ -1,5 +1,6 @@ import { useContext, useState, useEffect } from 'react'; import { AppContext } from '../../../AppContext/AppContext'; +import { ProfileContext } from '../../../AppContext/ProfileContext'; import { useNavigate } from "react-router-dom"; export function useIdentity() { @@ -12,6 +13,14 @@ export function useIdentity() { image: null, }); + const navigate = useNavigate(); + const profile = useContext(ProfileContext); + const app = useContext(AppContext); + + const updateState = (value) => { + setState((s) => ({ ...s, ...value })); + } + const actions = { logout: async () => { app.actions.logout() @@ -24,23 +33,16 @@ export function useIdentity() { } }; - const navigate = useNavigate(); - const app = useContext(AppContext); - - const updateState = (value) => { - setState((s) => ({ ...s, ...value })); - } - useEffect(() => { - if (app?.state?.Data?.profile) { - let profile = app.state.Data.profile; - updateState({ imageUrl: app.actions.profileImageUrl() }) - updateState({ image: profile.image }); - updateState({ name: profile.name }); - updateState({ handle: profile.handle }); - updateState({ domain: profile.node }); + if (profile?.state?.profile) { + let identity = profile.state.profile; + updateState({ imageUrl: profile.actions.profileImageUrl() }) + updateState({ image: identity.image }); + updateState({ name: identity.name }); + updateState({ handle: identity.handle }); + updateState({ domain: identity.node }); } - }, [app]) + }, [profile]) return { state, actions }; }