breaking apart app context

This commit is contained in:
Roland Osborne 2022-04-23 19:49:27 -07:00
parent 5f83b3e38f
commit 79373f26a6
22 changed files with 464 additions and 100 deletions

View File

@ -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()
}

View File

@ -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()
}

View File

@ -0,0 +1,4 @@
export function getProfileImageUrl(token, revision) {
return '/profile/image?agent=' + token + "&revision=" + revision
}

View File

@ -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);
}

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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,6 +20,12 @@ import 'antd/dist/antd.min.css';
function App() {
return (
<ChannelContextProvider>
<CardContextProvider>
<GroupContextProvider>
<ArticleContextProvider>
<ProfileContextProvider>
<AccountContextProvider>
<AppContextProvider>
<div style={{ position: 'absolute', width: '100vw', height: '100vh', backgroundColor: '#8fbea7' }}>
<img src={login} alt="" style={{ position: 'absolute', width: '33%', bottom: 0, right: 0 }}/>
@ -42,6 +54,12 @@ function App() {
</Router>
</div>
</AppContextProvider>
</AccountContextProvider>
</ProfileContextProvider>
</ArticleContextProvider>
</GroupContextProvider>
</CardContextProvider>
</ChannelContextProvider>
);
}

View File

@ -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 (
<AccountContext.Provider value={{ state, actions }}>
{children}
</AccountContext.Provider>
);
}

View File

@ -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 (
<ArticleContext.Provider value={{ state, actions }}>
{children}
</ArticleContext.Provider>
);
}

View File

@ -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 (
<CardContext.Provider value={{ state, actions }}>
{children}
</CardContext.Provider>
);
}

View File

@ -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 (
<ChannelContext.Provider value={{ state, actions }}>
{children}
</ChannelContext.Provider>
);
}

View File

@ -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 (
<GroupContext.Provider value={{ state, actions }}>
{children}
</GroupContext.Provider>
);
}

View File

@ -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 (
<ProfileContext.Provider value={{ state, actions }}>
{children}
</ProfileContext.Provider>
);
}

View File

@ -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 }
}

View File

@ -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) {

View File

@ -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 }
}

View File

@ -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 }
}

View File

@ -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 }
}

View File

@ -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 }
}

View File

@ -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 }
}

View File

@ -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 };
}

View File

@ -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 };
}