parent
a572ca0d7c
commit
73bfff6a0f
13 changed files with 5942 additions and 13 deletions
@ -0,0 +1,14 @@ |
||||
<!DOCTYPE html> |
||||
<html> |
||||
|
||||
<head> |
||||
<title>Ripcord log viewer</title> |
||||
<link rel="stylesheet" href="style/layout.scss" /> |
||||
</head> |
||||
|
||||
<body> |
||||
<main></main> |
||||
<script src="index.tsx"></script> |
||||
</body> |
||||
|
||||
</html> |
@ -0,0 +1,48 @@ |
||||
import React from "react"; |
||||
import { render } from "react-dom"; |
||||
|
||||
import { ApolloProvider } from "@apollo/react-hooks"; |
||||
import ApolloClient, { gql } from "apollo-boost"; |
||||
import { InMemoryCache } from "apollo-cache-inmemory"; |
||||
|
||||
import App from "./src/App"; |
||||
|
||||
const cache = new InMemoryCache(); |
||||
const client = new ApolloClient({ |
||||
cache, |
||||
resolvers: { |
||||
Mutation: { |
||||
gotoChat: (_root, currentChat, { cache }) => { |
||||
const query = gql` |
||||
query GotoChatQuery { |
||||
currentChat { |
||||
workspace |
||||
channel |
||||
} |
||||
} |
||||
`;
|
||||
currentChat.__typename = "ChatInfo"; |
||||
cache.writeQuery({ |
||||
query, |
||||
data: { |
||||
currentChat |
||||
} |
||||
}); |
||||
return null; |
||||
} |
||||
} |
||||
}, |
||||
uri: "http://localhost:8080/graphql" |
||||
}); |
||||
cache.writeData({ |
||||
data: { |
||||
currentChat: null |
||||
} |
||||
}); |
||||
|
||||
render( |
||||
<ApolloProvider client={client}> |
||||
<App /> |
||||
</ApolloProvider>, |
||||
document.querySelector("main") |
||||
); |
@ -0,0 +1,26 @@ |
||||
{ |
||||
"name": "frontend", |
||||
"version": "1.0.0", |
||||
"main": "index.js", |
||||
"license": "MIT", |
||||
"scripts": { |
||||
"dev": "parcel index.html", |
||||
"build": "parcel build index.html" |
||||
}, |
||||
"dependencies": { |
||||
"@apollo/react-hooks": "^3.1.3", |
||||
"@types/react": "^16.9.19", |
||||
"@types/react-dom": "^16.9.5", |
||||
"@types/react-router-dom": "^5.1.3", |
||||
"apollo-boost": "^0.4.7", |
||||
"apollo-cache-inmemory": "^1.6.5", |
||||
"graphql": "^14.5.8", |
||||
"parcel-bundler": "^1.12.4", |
||||
"react": "^16.8.3", |
||||
"react-dom": "^16.12.0" |
||||
}, |
||||
"devDependencies": { |
||||
"sass": "^1.25.0", |
||||
"typescript": "^3.7.5" |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
import React from "react"; |
||||
import ChannelList from "./ChannelList"; |
||||
import Chatroom from "./ChatRoom"; |
||||
|
||||
export default function App() { |
||||
return ( |
||||
<div className="main"> |
||||
<section className="channelList"> |
||||
<ChannelList /> |
||||
</section> |
||||
<section className="chatroom"> |
||||
<Chatroom /> |
||||
</section> |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,69 @@ |
||||
import React from "react"; |
||||
import { useQuery, useMutation } from "@apollo/react-hooks"; |
||||
import { gql } from "apollo-boost"; |
||||
import { GQLWorkspace, GQLChannel } from "./gql-types"; |
||||
|
||||
function channelsFirst(a: GQLChannel, b: GQLChannel): number { |
||||
let sameStatus = a.isPrivate == b.isPrivate; |
||||
if (!sameStatus) { |
||||
return a.isPrivate ? 1 : -1; |
||||
} |
||||
return a.name > b.name ? 1 : -1; |
||||
} |
||||
|
||||
export default function ChannelList() { |
||||
const { loading, error, data } = useQuery(gql` |
||||
{ |
||||
workspace { |
||||
icon |
||||
name |
||||
channels { |
||||
name |
||||
isPrivate |
||||
} |
||||
} |
||||
} |
||||
`);
|
||||
|
||||
const [gotoChat] = useMutation(gql` |
||||
mutation { |
||||
gotoChat(workspace: $workspace, channel: $channel) @client |
||||
} |
||||
`);
|
||||
|
||||
if (loading) { |
||||
return <div>Loading</div>; |
||||
} |
||||
|
||||
if (error) { |
||||
return <div>{error}</div>; |
||||
} |
||||
|
||||
return ( |
||||
<div> |
||||
{data.workspace.map((ws: GQLWorkspace) => ( |
||||
<article key={ws.name} className="workspace"> |
||||
<div className="title"> |
||||
<img src={ws.icon}></img> {ws.name} |
||||
</div> |
||||
<div className="chats"> |
||||
<ul> |
||||
{ws.channels.sort(channelsFirst).map(ch => ( |
||||
<li |
||||
key={ch.name} |
||||
onClick={a => |
||||
gotoChat({ |
||||
variables: { workspace: ws.name, channel: ch.name } |
||||
}) |
||||
} |
||||
> |
||||
{ch.name} |
||||
</li> |
||||
))} |
||||
</ul> |
||||
</div> |
||||
</article> |
||||
))} |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,80 @@ |
||||
import React from "react"; |
||||
import { useQuery } from "@apollo/react-hooks"; |
||||
import { gql } from "apollo-boost"; |
||||
import { GQLMessage } from "./gql-types"; |
||||
|
||||
export default function Chatroom() { |
||||
const { |
||||
loading: localLoading, |
||||
error: localError, |
||||
data: localData |
||||
} = useQuery(gql` |
||||
{ |
||||
currentChat { |
||||
workspace |
||||
channel |
||||
} |
||||
} |
||||
`);
|
||||
const variables = { |
||||
workspace: localData.currentChat?.workspace || "", |
||||
channel: localData.currentChat?.channel || "" |
||||
}; |
||||
const { |
||||
loading: remoteLoading, |
||||
error: remoteError, |
||||
data: remoteData |
||||
} = useQuery( |
||||
gql` |
||||
query Messages($workspace: String!, $channel: String!) { |
||||
messages( |
||||
workspace: $workspace |
||||
order: DATE_ASC |
||||
filter: { channel: $channel } |
||||
) { |
||||
messages { |
||||
content |
||||
username |
||||
userRealname |
||||
time |
||||
messageId |
||||
} |
||||
} |
||||
} |
||||
`,
|
||||
{ |
||||
variables |
||||
} |
||||
); |
||||
|
||||
if (localLoading || remoteLoading) { |
||||
return <div>Loading</div>; |
||||
} |
||||
if (localData.currentChat == null) { |
||||
return <div>Select a channel from the left menu</div>; |
||||
} |
||||
|
||||
if (localError) { |
||||
return <div>{localError.message}</div>; |
||||
} |
||||
if (remoteError) { |
||||
return <div>{remoteError.message}</div>; |
||||
} |
||||
|
||||
return ( |
||||
<div> |
||||
<ul className="chatlog"> |
||||
{remoteData.messages.messages.map((msg: GQLMessage) => |
||||
<li key={msg.messageId}> |
||||
<article className="message"> |
||||
<header> |
||||
{msg.userRealname} ({msg.username}) ({msg.messageId}) |
||||
</header> |
||||
<p>{msg.content}</p> |
||||
</article> |
||||
</li>; |
||||
)} |
||||
</ul> |
||||
</div> |
||||
); |
||||
} |
@ -0,0 +1,43 @@ |
||||
export interface GQLMessageList { |
||||
messages: GQLMessage[]; |
||||
next?: string; |
||||
} |
||||
|
||||
export interface GQLMessage { |
||||
time: Date; |
||||
content: string; |
||||
username: string; |
||||
userRealname: string; |
||||
channelName: string; |
||||
messageId: string; |
||||
} |
||||
|
||||
export interface GQLWorkspace { |
||||
name: string; |
||||
icon: string; |
||||
channels: GQLChannel[]; |
||||
} |
||||
|
||||
export interface GQLChannel { |
||||
name: string; |
||||
isPrivate: boolean; |
||||
} |
||||
|
||||
export interface GQLPagination { |
||||
after?: string; |
||||
first?: number; |
||||
} |
||||
|
||||
export interface GQLMessageFilter { |
||||
channel?: string; |
||||
} |
||||
|
||||
export enum GQLSortOrder { |
||||
DateAsc, |
||||
DateDesc |
||||
} |
||||
|
||||
export interface GQLChatInfo { |
||||
workspace: string; |
||||
channel: string; |
||||
} |
@ -0,0 +1,12 @@ |
||||
@mixin full { |
||||
display: flex; |
||||
flex: 1; |
||||
} |
||||
|
||||
body, |
||||
.main { |
||||
@include full; |
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; |
||||
} |
||||
|
||||
@import "./sidebar.scss"; |
@ -0,0 +1,25 @@ |
||||
.channelList { |
||||
.workspace { |
||||
.title { |
||||
display: flex; |
||||
align-items: center; |
||||
|
||||
img { |
||||
max-height: 2em; |
||||
margin-right: 1em; |
||||
} |
||||
} |
||||
|
||||
.chats { |
||||
ul { |
||||
list-style-type: none; |
||||
margin: 0; |
||||
padding: 0; |
||||
} |
||||
|
||||
li { |
||||
padding: 2pt 0; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,6 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"esModuleInterop": true, |
||||
"jsx": "react" |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue