organization

main
Valerie 2022-07-29 13:52:59 -04:00
parent 95bb2c6b46
commit 56b71709e2
23 changed files with 368 additions and 224 deletions

View File

@ -42,10 +42,8 @@ export function getClientId() {
return fileContents;
}
export function setClientId(id: string) {
if(!validUuid(id)) return false;
writeFileSync(clientIdPath, id);
return true;
export function setClientId(id: string | null) {
writeFileSync(clientIdPath, id ?? '');
}
export function getHomeServer() {
@ -58,12 +56,8 @@ export function getHomeServer() {
}
}
export function setHomeServer(url: string) {
if(url === null) {
writeFileSync(homeServerPath, '');
return null
}
writeFileSync(homeServerPath, url);
export function setHomeServer(url: string | null) {
writeFileSync(homeServerPath, url ?? '');
}
export function getSessionToken() {
@ -72,6 +66,6 @@ export function getSessionToken() {
return token;
}
export function setSessionToken(token: string) {
writeFileSync(sessionTokenPath, token);
export function setSessionToken(token: string | null) {
writeFileSync(sessionTokenPath, token ?? '');
}

View File

@ -0,0 +1,62 @@
import { createContext, useCallback, useEffect, useState, useMemo } from 'react';
import Channels from './pages/Channels';
import Chat from './pages/Chat';
import Sidebar from './components/Sidebar';
import NewAccount from './pages/NewAccount';
import ServerConnection from './components/ServerConnection';
import EphemeralState from './contexts/EphemeralState/EphemeralState';
import PersistentState from '/@/contexts/PersistentState/PersistentState';
import Router from './Router';
export default function App() {
const [transparent, setTransparent] = useState(false);
// font-size: 16px;
// font-family: 'Lato', sans-serif;
// font-family: 'Red Hat Text', sans-serif;
// font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
// color: #f8f8f2;
// background: #282a36;
return (
<>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin='' />
<link href={"https://fonts.googleapis.com/css2?family=Fira+Sans&family=Josefin+Sans&family=Lato&family=Radio+Canada&family=Readex+Pro&family=Red+Hat+Text&family=Rubik&family=Signika&family=Telex&display=swap"} rel="stylesheet" />
<style>{`
html {
--background: #282a36;
--current-line: #44475a;
--foreground: #f8f8f2;
--comment: #6272a4;
--cyan: #8be9fd;
--green: #50fa7b;
--orange: #ffb86c;
--pink: #ff79c6;
--purple: #bd93f9;
--red: #ff5555;
--yellow: #f1fa8c;
--primary: var(--purple);
}
a {
color: var(--cyan);
}
`}</style>
<div style={{
background: transparent ? 'rgba(0, 0, 0, 0)' : 'var(--background)',
color: transparent ? 'black' : 'var(--foreground)',
fontSize: '16px',
fontFamily: "'Red Hat Text', sans-serif",
width: '100%',
height: '100%'
}}>
<PersistentState>
<EphemeralState onTransparencyChange={setTransparent}>
<Router></Router>
</EphemeralState>
</PersistentState>
</div>
</>
);
}

View File

@ -0,0 +1,42 @@
import ServerConnection from "./components/ServerConnection";
import Sidebar from "./components/Sidebar";
import useHomeServer from "./contexts/PersistentState/useHomeServerNative";
import useClientId from "./hooks/useClientId";
import useSessionToken from "./hooks/useSessionToken";
import Channels from "./pages/Channels";
import Chat from "./pages/Chat";
import NewAccount from "./pages/NewAccount";
interface RouterProps {
[name: string]: React.ReactNode;
children?: React.ReactNode;
}
export default function Router(props: RouterProps) {
const { clientId } = useClientId();
const { sessionToken } = useSessionToken();
const { homeServer } = useHomeServer();
const configured =
homeServer !== null &&
clientId !== null &&
sessionToken !== null;
return (
configured ? (
<ServerConnection url={homeServer}>
<Sidebar
threshold={800}
sidebar={300}
>
<Channels></Channels>
<Chat></Chat>
</Sidebar>
</ServerConnection>
) : (
<NewAccount></NewAccount>
)
)
}

View File

@ -0,0 +1,10 @@
import useSessionToken from "../hooks/useSessionToken"
export default function Logout() {
const { setSessionToken } = useSessionToken();
return (
<button onClick={() => setSessionToken(null)}>LOGOUT</button>
)
}

View File

@ -1,6 +1,5 @@
import { createContext, PropsWithChildren, ReactNode, useEffect, useMemo } from "react";
import { connectApi } from "../lib/api";
import { usePrevious } from "./usePrevious";
interface ServerConnectionProps {
children: ReactNode,

View File

@ -1,15 +1,15 @@
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
import 'reactjs-popup/dist/index.css';
import { useApi } from '../lib/useApi';
import { ClientIdContext } from '../pages/App';
import QR from 'qrcode';
import { usePrevious } from './usePrevious';
import { usePrevious } from '../hooks/usePrevious';
import useClientId from '../hooks/useClientId';
export default function Totp () {
const [open, setOpen] = useState(false);
const previousOpen = usePrevious(open);
const { clientId } = useContext(ClientIdContext);
const { clientId } = useClientId()
const [qr, setQr] = useState<string | null>(null);
const [key, setKey] = useState<string>('');

View File

@ -0,0 +1,38 @@
import { createContext, useState, useMemo, useEffect } from "react";
export const ChannelContext = createContext<{
channel: string | null,
setChannel: (uid: string) => void
}>({
channel: null,
setChannel: () => {},
});
export const TransparencyContext = createContext<(transparent: boolean) => void>(() => {});
export default function EphemeralState(props: {
onTransparencyChange: (value: boolean) => void,
children?: React.ReactNode
}) {
const [channel, setChannel] = useState<string | null>(null);
const [transparent, setTransparent] = useState(false);
const channelContextValue = useMemo(() => {
return { channel, setChannel };
}, [channel, setChannel]);
useEffect(() => {
if('onTransparencyChange' in props) {
props.onTransparencyChange(transparent)
}
}, [transparent])
return (
<ChannelContext.Provider value={channelContextValue}>
<TransparencyContext.Provider value={setTransparent}>
{props.children}
</TransparencyContext.Provider>
</ChannelContext.Provider>
);
}

View File

@ -0,0 +1,46 @@
import { createContext } from "react";
import useHomeServerNative from "./useHomeServerNative";
import useClientIdNative from "./useClientIdNative";
import useSessionTokenNative from "./useSessionTokenNative";
export const ClientIdContext = createContext<{
clientId: string | null,
setClientId: (id: string | null) => void
}>({
clientId: null,
setClientId: () => {}
});
export const HomeServerContext = createContext<{
homeServer: string | null,
setHomeServer: (uid: string | null) => void
}>({
homeServer: null,
setHomeServer: () => {}
});
export const SessionTokenContext = createContext<{
sessionToken: string | null,
setSessionToken: (token: string | null) => void
}>({
sessionToken: null,
setSessionToken() {}
})
export default function PersistentState(props: any) {
const homeServerContextValue = useHomeServerNative();
const clientIdContextValue = useClientIdNative();
const sessionTokenContextValue = useSessionTokenNative();
return (
<HomeServerContext.Provider value={homeServerContextValue}>
<ClientIdContext.Provider value={clientIdContextValue}>
<SessionTokenContext.Provider value={sessionTokenContextValue}>
{props.children}
</SessionTokenContext.Provider>
</ClientIdContext.Provider>
</HomeServerContext.Provider>
)
}

View File

@ -0,0 +1,24 @@
import { useCallback, useMemo, useState } from 'react';
import {
getClientId,
setClientId
} from '/@/lib/native';
export default function useClientIdNative() {
const [cachedClientId, setCachedClientId] =
useState<string | null>(getClientId());
const setClientIdCallback = useCallback((id: string | null) => {
setClientId(id);
setCachedClientId(getClientId());
}, [cachedClientId]);
return useMemo(() => {
return {
clientId: cachedClientId,
setClientId: setClientIdCallback
};
}, [cachedClientId, setClientIdCallback]);
}

View File

@ -0,0 +1,23 @@
import { useCallback, useMemo, useState } from 'react';
import {
setHomeServer,
getHomeServer
} from '/@/lib/native';
export default function useHomeServer() {
const [cachedHomeServer, setCachedHomeServer] =
useState<string | null>(getHomeServer());
const setHomeServerCallback = useCallback((url: string | null) => {
setHomeServer(url);
setCachedHomeServer(getHomeServer());
}, [cachedHomeServer]);
return useMemo(() => {
return {
homeServer: cachedHomeServer,
setHomeServer: setHomeServerCallback
};
}, [cachedHomeServer, setHomeServerCallback])
}

View File

@ -0,0 +1,23 @@
import { useCallback, useMemo, useState } from 'react';
import {
getSessionToken,
setSessionToken
} from '/@/lib/native';
export default function useSessionTokenNative() {
const [cachedSessionToken, setCachedSessionToken] =
useState<string | null>(getSessionToken());
const setSessionTokenCallback = useCallback((token: string | null) => {
setSessionToken(token);
setCachedSessionToken(getSessionToken());
}, [cachedSessionToken]);
return useMemo(() => {
return {
sessionToken: cachedSessionToken,
setSessionToken: setSessionTokenCallback
};
}, [cachedSessionToken, setSessionTokenCallback])
}

View File

@ -0,0 +1,6 @@
import { useContext } from "react";
import { ChannelContext } from "../contexts/EphemeralState/EphemeralState";
export default function useChannel() {
return useContext(ChannelContext);
}

View File

@ -0,0 +1,7 @@
import { useContext } from "react";
import { ClientIdContext } from "../contexts/PersistentState/PersistentState";
export default function useClientId() {
return useContext(ClientIdContext);
}

View File

@ -0,0 +1,6 @@
import { useContext } from "react";
import { HomeServerContext } from "../contexts/PersistentState/PersistentState";
export default function useHomeServer() {
return useContext(HomeServerContext);
}

View File

@ -0,0 +1,6 @@
import { useContext } from "react";
import { SessionTokenContext } from "../contexts/PersistentState/PersistentState";
export default function useSessionToken() {
return useContext(SessionTokenContext);
}

View File

@ -1,7 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import Sidebar from './components/Sidebar';
import App from './pages/App';
import App from './App';
const container = document.getElementById('app');
if(container !== null) {

View File

@ -1,164 +0,0 @@
import { createContext, useCallback, useEffect, useState, useMemo } from 'react';
import Channels from './Channels';
import Chat from './Chat';
import {
getClientId,
setClientId,
getHomeServer,
setHomeServer,
getSessionToken,
setSessionToken
} from '../lib/native';
import { useApi } from '../lib/useApi';
import Sidebar from '../components/Sidebar';
import NewAccount from './NewAccount';
import ServerConnection from '../components/ServerConnection';
export const ChannelContext = createContext<{
channel: string | null,
setChannel: (uid: string) => void
}>({
channel: null,
setChannel: () => {},
});
export const ClientIdContext = createContext<{
clientId: string | null,
setClientId: (id: string | null) => void
}>({
clientId: null,
setClientId: () => {}
});
export const HomeServerContext = createContext<{
homeServer: string | null,
setHomeServer: (uid: string | null) => void
}>({
homeServer: null,
setHomeServer: () => {}
});
export const SessionTokenContext = createContext<{
sessionToken: string | null,
setSessionToken: (token: string) => void
}>({
sessionToken: null,
setSessionToken() {}
})
export const TransparencyContext = createContext<(transparent: boolean) => void>(() => {});
export default function App() {
const [channel, setChannel] = useState<string | null>(null);
const [clientId, setCachedClientId] = useState(getClientId());
const [homeServer, setCachedHomeServer] = useState<string | null>(getHomeServer());
const channelContextValue = { channel, setChannel }
const [cachedSessionToken, setCachedSessionToken] = useState<string | null>(null);
const [transparent, setTransparent] = useState(false);
const setHomeServerCallback = useCallback((url: string | null) => {
console.log('SETTING HOME SERVER', url)
setHomeServer(url);
setCachedHomeServer(getHomeServer());
}, [homeServer]);
const homeServerContextValue = useMemo(() => {
return {
homeServer,
setHomeServer: setHomeServerCallback
};
}, [homeServer, setHomeServerCallback])
// persist given clientId to disk
useEffect(() => {
if(clientId === null) return;
setClientId(clientId);
}, [clientId]);
const updateCachedSessionToken = useCallback((token?: string) => {
setSessionToken(token ?? '');
setCachedSessionToken(getSessionToken());
}, []);
const SessionTokenContextValue = useMemo(() => {
return {
sessionToken: cachedSessionToken,
setSessionToken: updateCachedSessionToken
}
}, [cachedSessionToken, updateCachedSessionToken])
// const { send } = useApi({
// 'client:new'(data: string) {
// setCachedClientId(data);
// },
// }, [setCachedClientId]);
// useEffect(() => {
// if(clientId !== null) return;
// send('client:new');
// }, [clientId]);
const clientIdContextValue = { clientId, setClientId: setCachedClientId };
// font-size: 16px;
// font-family: 'Lato', sans-serif;
// font-family: 'Red Hat Text', sans-serif;
// font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
// color: #f8f8f2;
// background: #282a36;
return (
<>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin='' />
<link href={"https://fonts.googleapis.com/css2?family=Fira+Sans&family=Josefin+Sans&family=Lato&family=Radio+Canada&family=Readex+Pro&family=Red+Hat+Text&family=Rubik&family=Signika&family=Telex&display=swap"} rel="stylesheet" />
<style>{`
html {
--background: #282a36;
--current-line: #44475a;
--foreground: #f8f8f2;
--comment: #6272a4;
--cyan: #8be9fd;
--green: #50fa7b;
--orange: #ffb86c;
--pink: #ff79c6;
--purple: #bd93f9;
--red: #ff5555;
--yellow: #f1fa8c;
--primary: var(--purple);
}
a {
color: var(--cyan);
}
`}</style>
<ClientIdContext.Provider value={clientIdContextValue}>
<ChannelContext.Provider value={channelContextValue}>
<HomeServerContext.Provider value={homeServerContextValue}>
<TransparencyContext.Provider value={setTransparent}>
<SessionTokenContext.Provider value={SessionTokenContextValue}>
<div style={{
background: transparent ? 'rgba(0, 0, 0, 0)' : 'var(--background)',
color: transparent ? 'black' : 'var(--foreground)',
fontSize: '16px',
fontFamily: "'Red Hat Text', sans-serif",
width: '100%',
height: '100%'
}}>
{(cachedSessionToken === null || homeServer === null) ? (
<NewAccount></NewAccount>
) : (
<ServerConnection url={homeServer}>
<Sidebar
threshold={800}
sidebar={300}
>
<Channels></Channels>
<Chat></Chat>
</Sidebar>
</ServerConnection>
)}
</div>
</SessionTokenContext.Provider>
</TransparencyContext.Provider>
</HomeServerContext.Provider>
</ChannelContext.Provider>
</ClientIdContext.Provider>
</>
);
}

View File

@ -1,10 +1,14 @@
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
import { ChannelContext, ClientIdContext, HomeServerContext } from './App';
import { useApi } from '../lib/useApi';
import type { IMessage } from './Message';
import NameTextbox from './NameTextbox';
import LoginQR from './LoginQR';
import Totp from '../components/Totp';
import useChannel from '../hooks/useChannel';
import useClientId from '../hooks/useClientId';
import useHomeServer from '../contexts/PersistentState/useHomeServerNative';
import Logout from '../components/Logout';
import { CgHashtag } from 'react-icons/cg';
interface IChannel {
uid: string;
@ -12,11 +16,11 @@ interface IChannel {
}
function Hashmark() {
return <span style={{
return <CgHashtag style={{
fontWeight: 'bold',
marginRight: '8px',
marginLeft: '8px',
}}>#</span>;
}}>#</CgHashtag>;
}
interface IUnreads {
@ -28,10 +32,10 @@ export default function Channels() {
const [channels, setChannels] = useState<IChannel[]>([]);
const [unreads, setUnreads] = useState<IUnreads>({});
const { channel, setChannel } = useContext(ChannelContext);
const { clientId } = useContext(ClientIdContext);
const { channel, setChannel } = useChannel()
const { clientId } = useClientId()
const { setHomeServer } = useContext(HomeServerContext);
const { setHomeServer } = useHomeServer();
const { send } = useApi({
'channels:list'(data: IChannel[]) {
@ -142,7 +146,7 @@ export default function Channels() {
// lineHeight: '20px'
}}>ADD</button>
<NameTextbox></NameTextbox><br></br>
<button onClick={() => setHomeServer(null)}>leave</button><br></br>
<Logout></Logout><br></br>
{/* <LoginQR></LoginQR> */}
{/* <Totp></Totp> */}
</div>

View File

@ -1,10 +1,11 @@
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
import { v4 } from 'uuid';
import { useApi } from '../lib/useApi';
import { ChannelContext, ClientIdContext } from './App';
import type { IMessage} from './Message';
import { Message } from './Message';
import { MdSend } from 'react-icons/md';
import useChannel from '../hooks/useChannel';
import useClientId from '../hooks/useClientId';
function createMessage(from: string, text: string,
channel: string, t = 0): IMessage {
@ -26,8 +27,8 @@ export default () => {
const textBoxRef = useRef<HTMLDivElement>(null);
const { channel, setChannel } = useContext(ChannelContext);
const { clientId } = useContext(ClientIdContext);
const { channel, setChannel } = useChannel();
const { clientId } = useClientId();
const { send } = useApi({
'message:message'(data: IMessage) {

View File

@ -1,10 +1,11 @@
import { useContext, useEffect, useState } from "react";
import { ClientIdContext, HomeServerContext } from "./App";
import QR from 'qrcode';
import useHomeServer from "../contexts/PersistentState/useHomeServerNative";
import useClientId from "../hooks/useClientId";
export default function LoginQR() {
const { homeServer } = useContext(HomeServerContext);
const { clientId } = useContext(ClientIdContext);
const { homeServer } = useHomeServer()
const { clientId } = useClientId();
const [qr, setQr] = useState<string | null>(null);
useEffect(() => {

View File

@ -5,12 +5,12 @@ import {
useRef,
useState,
} from 'react';
import { ClientIdContext } from './App';
import useClientId from '../hooks/useClientId';
import { useApi } from '../lib/useApi';
export default function NameTextbox() {
const { clientId } = useContext(ClientIdContext);
const { clientId } = useClientId()
const [name, setName] = useState<string | null>(null);
const [inputElement, setInputElement] = useState<HTMLInputElement | null>(null);

View File

@ -2,8 +2,8 @@ import { useEffect, useState } from "react";
import { useCallback, useContext, useRef } from "react"
import ServerConnection from "../components/ServerConnection";
import { useApi } from "../lib/useApi";
import { ClientIdContext, HomeServerContext, SessionTokenContext, TransparencyContext } from "./App"
import QR from 'qrcode';
import useSessionToken from "../hooks/useSessionToken";
export default function NewAccount() {
@ -73,27 +73,24 @@ export default function NewAccount() {
if(connecting) return;
setHomeServer(url);
setConnecting(true);
try {
const ws = new WebSocket(url);
ws.addEventListener('open', () => {
setConnecting(false);
setConnection(ws);
setConnectionError('');
});
ws.addEventListener('close', (e) => {
setConnecting(false);
setConnection(null);
console.log(e)
});
const ws = new WebSocket(url);
ws.addEventListener('error', (e) => {
setConnectionError('Couldn\'t connect to ' + url)
});
} catch(e: any) {
console.log('ASDFASDFASDF');
}
ws.addEventListener('open', () => {
setConnecting(false);
setConnection(ws);
setConnectionError('');
});
ws.addEventListener('close', (e) => {
setConnecting(false);
setConnection(null);
console.log(e)
});
ws.addEventListener('error', (e) => {
setConnectionError('Connection failed')
});
}, [connecting])
return (
@ -101,17 +98,36 @@ export default function NewAccount() {
display: 'grid',
placeContent: 'center center',
height: '100%',
textAlign: 'center'
}}>
{returning ? (
<>
Login
<div>
<span>
Login
</span>
&nbsp;
&nbsp;
&nbsp;
<a href="#" onClick={() => setReturning(false)}>Sign up</a>
</>
</div>
) : (
<>
<input ref={homeServerInputRef} defaultValue="wss://macos.valnet.xyz" disabled={connection !== null}></input>
<button onClick={() => connect(homeServerInputRef.current?.value ?? '')} disabled={connection !== null}>Next</button>
{connecting ? `Connecting to ${homeServer}` : connectionError}
<div>
<a href="#" onClick={() => setReturning(true)}>
Login
</a>
&nbsp;
&nbsp;
&nbsp;
<span>
Sign up
</span>
</div>
<br></br>
<label>Home Server URL</label>
<input style={{textAlign: 'center'}} ref={homeServerInputRef} defaultValue="wss://macos.valnet.xyz" disabled={connection !== null || connecting}></input>
<button onClick={() => connect(homeServerInputRef.current?.value ?? '')} disabled={connection !== null || connecting}>Next</button>
{connecting ? `Connecting...` : connectionError}
<br></br>
{connection !== null && (
<ServerConnection url={homeServer ?? ''}>
@ -144,7 +160,7 @@ const SignUp = (props: any) => {
const [clientId, setClientId] = useState<string | null>(null);
// const [totpToken, setTotpToken] = useState<string | null>(null);
const [qr, setQr] = useState<string | null>(null);
const { setSessionToken } = useContext(SessionTokenContext)
const { setSessionToken } = useSessionToken();
const { send } = useApi({
'client:new'(data: any) {