Browse Source

Entirely re-style using Tailwind CSS.

main
Titouan Rigoudy 4 years ago
parent
commit
a738fbdab7
21 changed files with 175 additions and 327 deletions
  1. +6
    -10
      src/components/ConnectForm.tsx
  2. +23
    -8
      src/components/Header.tsx
  3. +4
    -15
      src/components/SearchableList.tsx
  4. +2
    -2
      src/components/SolsticeApp.tsx
  5. +1
    -1
      src/containers/ConnectPage.tsx
  6. +1
    -1
      src/containers/Footer.tsx
  7. +18
    -0
      src/index.css
  8. +5
    -2
      src/modules/login/LoginStatusPane.tsx
  9. +13
    -4
      src/modules/room/RoomChat.tsx
  10. +18
    -19
      src/modules/room/RoomChatForm.tsx
  11. +5
    -3
      src/modules/room/RoomChatHeader.tsx
  12. +20
    -6
      src/modules/room/RoomChatMessageList.tsx
  13. +16
    -2
      src/modules/room/RoomList.tsx
  14. +12
    -6
      src/modules/room/RoomListEntry.tsx
  15. +5
    -3
      src/modules/room/RoomsPane.tsx
  16. +10
    -1
      src/modules/room/slice.ts
  17. +3
    -2
      src/modules/socket/SocketStatusPane.tsx
  18. +5
    -1
      src/modules/user/UserListEntry.tsx
  19. +5
    -3
      src/modules/user/UsersPane.tsx
  20. +0
    -238
      src/styles/styles.scss
  21. +3
    -0
      tailwind.config.js

+ 6
- 10
src/components/ConnectForm.tsx View File

@ -23,14 +23,14 @@ const ConnectForm: FC<Props> = ({ socket }) => {
const isSocketClosed = socket.state === SocketState.Closed;
return (
<div class="text-center bg-white py-5 px-10 rounded-xl shadow">
<h2 class="text-3xl font-bold mb-2 text-yellow-600">
<div className="text-center bg-white py-5 px-10 rounded-xl shadow">
<h1 className="text-3xl font-bold pb-3 text-yellow-700">
Connect to a solstice client
</h2>
</h1>
<Form
onSubmit={onSubmit}
render={({ handleSubmit, submitting }) => (
<form onSubmit={handleSubmit} class="my-2">
<form onSubmit={handleSubmit} className="my-2">
<Field
name="url"
component="input"
@ -38,16 +38,12 @@ const ConnectForm: FC<Props> = ({ socket }) => {
defaultValue="ws://localhost:2244"
required
pattern="wss?://.+"
class="border border-gray-400 rounded px-1 mx-1"
className="text-input"
/>
<button
type="submit"
disabled={submitting || !isSocketClosed}
class={
"border border-gray-500 rounded px-1 " +
"bg-yellow-200 focus:bg-red " +
"disabled:bg-gray-300 disabled:text-gray-500"
}
className="button ml-3"
>
Connect
</button>


+ 23
- 8
src/components/Header.tsx View File

@ -1,15 +1,30 @@
import { FC } from "react";
import { NavLink } from "react-router-dom";
interface LinkProps {
to: string;
}
const Link: FC<LinkProps> = ({ to, children }) => (
<NavLink to={to} className={"mx-5 button-lg"} activeClassName="font-bold">
{children}
</NavLink>
);
const Header: FC = () => (
<header>
<h1>Solstice web UI</h1>
<NavLink to="/rooms" activeClassName="active">
Rooms
</NavLink>
<NavLink to="/users" activeClassName="active">
Users
</NavLink>
<header className="flex p-4 items-center">
<h1
className={
"text-3xl font-bold text-yellow-800 bg-yellow-300 shadow " +
"mr-5 py-3 px-5 rounded-xl"
}
>
Solstice
</h1>
<nav className="flex items-center">
<Link to="/rooms">Rooms</Link>
<Link to="/users">Users</Link>
</nav>
</header>
);


+ 4
- 15
src/components/SearchableList.tsx View File

@ -1,4 +1,4 @@
import { ComponentType, FC, ReactEventHandler } from "react";
import { ComponentType, FC } from "react";
interface ItemProps<Item> {
name: string;
@ -8,14 +8,13 @@ interface ItemProps<Item> {
interface ListProps<Item> {
id: string;
map: { [key: string]: Item };
refresh: () => void;
}
// TODO: Add search box.
function SearchableList<Item>(
ItemComponent: ComponentType<ItemProps<Item>>
): FC<ListProps<Item>> {
return ({ id, map, refresh }: ListProps<Item>) => {
return ({ id, map }: ListProps<Item>) => {
const children = [];
for (const name in map) {
@ -26,19 +25,9 @@ function SearchableList<Item>(
);
}
const onClick: ReactEventHandler = (event) => {
event.preventDefault();
refresh();
};
return (
<div id={id}>
<div id={`${id}-header`}>
<div>
<button onClick={onClick}>Refresh</button>
</div>
</div>
<ul>{children}</ul>
<div id={id} className="flex flex-col h-full px-3">
<ul className="flex-1 flex flex-col">{children}</ul>
</div>
);
};


+ 2
- 2
src/components/SolsticeApp.tsx View File

@ -19,7 +19,7 @@ const MainPane: FC = () => {
const { path } = useRouteMatch();
return (
<main>
<main className="flex-1">
<Switch>
<Route path={`${path}rooms`}>
<RoomsPane />
@ -47,7 +47,7 @@ const ConnectedApp: FC = ({ children }) => {
);
}
return <div>{children}</div>;
return <div className="h-full w-full flex flex-col">{children}</div>;
};
const SolsticeApp = () => (


+ 1
- 1
src/containers/ConnectPage.tsx View File

@ -25,7 +25,7 @@ const ConnectPage: React.FC = () => {
}
return (
<div class="h-full w-full flex flex-col items-center justify-center">
<div className="h-full w-full flex flex-col items-center justify-center">
<ConnectForm socket={socket} />
</div>
);


+ 1
- 1
src/containers/Footer.tsx View File

@ -11,7 +11,7 @@ const Footer: FC = () => {
const socket = useSelector(selectSocket);
return (
<footer>
<footer className="flex items-center justify-center">
<SocketStatusPane socket={socket} />
<LoginStatusPane login={login} />
</footer>


+ 18
- 0
src/index.css View File

@ -9,3 +9,21 @@ body,
height: 100%;
width: 100%;
}
/* Utility classes. */
.button-yellow {
@apply bg-yellow-300 hover:bg-yellow-500 active:bg-yellow-700;
}
.button {
@apply px-3 py-1 rounded-lg shadow button-yellow;
}
.button-lg {
@apply px-5 py-3 rounded-xl shadow button-yellow;
}
.text-input {
@apply px-2 py-1 rounded border border-gray-400;
}

+ 5
- 2
src/modules/login/LoginStatusPane.tsx View File

@ -54,8 +54,11 @@ const LoginStatusPane: FC<Props> = ({ login }) => {
}
return (
<div id="login-status-pane">
<span id="login-status-text">Login status: {statusText}</span>
<div className="p-3">
<span>
<span className="font-bold text-yellow-800">Login status:</span>{" "}
{statusText}
</span>
{motd}
{reason}
</div>


+ 13
- 4
src/modules/room/RoomChat.tsx View File

@ -22,15 +22,24 @@ const RoomChat: FC<Props> = ({ loginUserName, room }) => {
const header = <RoomChatHeader room={room} />;
if (room === undefined || room.membership !== RoomMembership.Joined) {
return <div id="room-chat">{header}</div>;
return (
<div className="flex h-full justify-center items-center">{header}</div>
);
}
const { name, messages } = room;
return (
<div id="room-chat">
<div className="flex flex-col h-full p-3">
{header}
<RoomChatMessageList loginUserName={loginUserName} messages={messages} />
<RoomChatForm loginUserName={loginUserName} roomName={name} />
<div className="flex-1">
<RoomChatMessageList
loginUserName={loginUserName}
messages={messages}
/>
</div>
<div>
<RoomChatForm loginUserName={loginUserName} roomName={name} />
</div>
</div>
);
};


+ 18
- 19
src/modules/room/RoomChatForm.tsx View File

@ -28,25 +28,24 @@ const RoomChatForm: FC<Props> = ({ roomName, loginUserName }) => {
};
return (
<div id="room-chat-form">
<Form
onSubmit={onSubmit}
render={({ handleSubmit, submitting }) => (
<form onSubmit={handleSubmit}>
<Field
name="message"
component="input"
type="text"
placeholder="Type a message..."
required
/>
<button type="submit" disabled={submitting}>
Send
</button>
</form>
)}
/>
</div>
<Form
onSubmit={onSubmit}
render={({ handleSubmit, submitting }) => (
<form onSubmit={handleSubmit} className="flex px-3">
<Field
name="message"
component="input"
type="text"
placeholder="Type a message..."
required
className="text-input flex-grow"
/>
<button type="submit" disabled={submitting} className="button ml-3">
Send
</button>
</form>
)}
/>
);
};


+ 5
- 3
src/modules/room/RoomChatHeader.tsx View File

@ -9,8 +9,8 @@ interface InnerProps {
}
const InnerHeader: FC<InnerProps> = ({ title, children }) => (
<div id="room-chat-header">
<div id="room-chat-header-title">{title}</div>
<div className="flex justify-between px-3 py-1">
<h2 className="text-xl font-bold text-yellow-800">{title}</h2>
{children}
</div>
);
@ -47,7 +47,9 @@ const RoomChatHeader: FC<Props> = ({ room }) => {
return (
<InnerHeader title={room.name}>
<button onClick={onClickLeave}>Leave</button>
<button className="button" onClick={onClickLeave}>
Leave
</button>
</InnerHeader>
);
}


+ 20
- 6
src/modules/room/RoomChatMessageList.tsx View File

@ -7,6 +7,8 @@ interface Props {
messages: RoomMessage[];
}
const messageShape = "px-3 py-1 rounded-xl";
const RoomChatMessageList: FC<Props> = ({ loginUserName, messages }) => {
// Append all messages in the chat room.
const children = [];
@ -14,22 +16,34 @@ const RoomChatMessageList: FC<Props> = ({ loginUserName, messages }) => {
for (const { userName, message } of messages) {
if (userName === loginUserName) {
children.push(
<li key={i} className="room-chat-message room-chat-message-me">
<div className="room-chat-message-text">{message}</div>
<li key={i} className="self-end py-1 max-w-3/4">
<div className={`bg-yellow-600 text-white ${messageShape}`}>
{message}
</div>
</li>
);
} else {
children.push(
<li key={i} className="room-chat-message">
<div className="room-chat-message-user">{userName}</div>
<div className="room-chat-message-text">{message}</div>
<li key={i} className="self-start py-1 max-w-3/4">
<div className="text-sm text-yellow-600 px-1 py-1">{userName}</div>
<div className={`bg-yellow-100 ${messageShape}`}>{message}</div>
</li>
);
}
i++;
}
return <ul id="room-chat-message-list">{children}</ul>;
return (
<div className="h-full py-3 flex flex-col">
<ul
className={
"bg-white rounded-2xl p-3 flex-grow flex flex-col justify-end"
}
>
{children}
</ul>
</div>
);
};
export default RoomChatMessageList;

+ 16
- 2
src/modules/room/RoomList.tsx View File

@ -9,11 +9,25 @@ const SearchableRoomList = SearchableList(RoomListEntry);
const RoomList: FC<RoomSliceState> = ({ rooms }) => {
const dispatch = useDispatch();
const refresh = () => {
const onClick: ReactEventHandler = (event) => {
event.preventDefault();
dispatch(roomGetAll());
};
return <SearchableRoomList id="room-list" map={rooms} refresh={refresh} />;
return (
<div>
<div className="flex justify-between p-3">
<h1 className="pl-3 text-2xl font-bold text-yellow-800 text-center">
Chat Rooms
</h1>
<button onClick={onClick} className="button">
Refresh
</button>
</div>
<SearchableRoomList map={rooms} />
</div>
);
};
export default RoomList;

+ 12
- 6
src/modules/room/RoomListEntry.tsx View File

@ -12,19 +12,25 @@ interface Props {
const RoomListEntry: FC<Props> = ({ name, data }) => {
const { membership, userCount } = data;
const classes = ["room"];
let backgroundColor;
if (membership === RoomMembership.Joined) {
classes.push("room-joined");
backgroundColor = "bg-yellow-200";
} else {
backgroundColor = "";
}
return (
<NavLink
to={`/rooms/${encode(name)}`}
activeClassName="room-selected"
className={classes.join(" ")}
activeClassName="bg-yellow-300 font-bold"
className={`block p-3 ${backgroundColor} hover:bg-yellow-300 rounded-xl`}
>
<span className="room-name">{name}</span>
<span className="room-user-count">({userCount})</span>
<div className="flex justify-between">
<div>{name}</div>
<div>
({userCount} user{userCount > 1 ? "s" : ""})
</div>
</div>
</NavLink>
);
};


+ 5
- 3
src/modules/room/RoomsPane.tsx View File

@ -36,9 +36,11 @@ const RoomsPane: FC<{}> = () => {
const rooms = useSelector(selectAllRooms);
return (
<div id="rooms-pane">
<RoomList rooms={rooms} />
<div id="room-selected-pane">
<div className="h-full w-full flex">
<div className="w-80">
<RoomList rooms={rooms} />
</div>
<div className="flex-grow">
<Switch>
<Route exact path={path}>
<RoomChatPane rooms={rooms} loginUserName={login.username!} />


+ 10
- 1
src/modules/room/slice.ts View File

@ -49,7 +49,16 @@ const initialState: RoomSliceState = {
owner: "",
operators: [],
members: [],
messages: [],
messages: [
{
userName: "andre",
message: "hello everybody",
},
{
userName: "karandeep",
message: "namaste",
},
],
tickers: [],
},
bloop: {


+ 3
- 2
src/modules/socket/SocketStatusPane.tsx View File

@ -21,8 +21,9 @@ interface Props {
}
const SocketStatusPane: React.FC<Props> = ({ socket }: Props) => (
<div id="socket-status-pane">
Connection status: {socketStateToString(socket)}
<div className="p-3">
<span className="font-bold text-yellow-800">Connection status:</span>{" "}
{socketStateToString(socket)}
</div>
);


+ 5
- 1
src/modules/user/UserListEntry.tsx View File

@ -11,7 +11,11 @@ const UserListEntry: FC<Props> = ({ name }) => {
const path = `/users/${encode(name)}`;
return (
<NavLink to={path} className="user" activeClassName="user-selected">
<NavLink
to={path}
activeClassName="bg-yellow-300 font-bold"
className={`block p-3 hover:bg-yellow-300 rounded-xl`}
>
{name}
</NavLink>
);


+ 5
- 3
src/modules/user/UsersPane.tsx View File

@ -6,11 +6,13 @@ import UserList from "modules/user/UserList";
const UsersPane: FC<{}> = () => {
const users = useSelector(selectAllUsers);
console.log("Users:", users);
return (
<div id="users-pane">
<UserList users={users} />
<div className="h-full w-full flex">
<div className="w-80">
<UserList users={users} />
</div>
<div className="flex-grow">Coming soon: user details.</div>
</div>
);
};


+ 0
- 238
src/styles/styles.scss View File

@ -1,238 +0,0 @@
/* Styles */
body {
font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
line-height: 1.4em;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
font-smoothing: antialiased;
font-weight: 300;
padding: 0 !important;
margin: 0 !important;
height: 100%;
}
html {
padding: 0;
margin: 0;
height: 100vh;
}
#app,
#solstice-app {
height: 100%;
margin: 0;
padding: 0;
}
#solstice-app {
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
}
header {
margin: 0;
padding: 1.5em 2em;
display: flex;
flex-flow: row;
justify-content: space-around;
width: 100%;
}
header h1 {
margin: 0;
}
footer {
display: flex;
padding: 0.5em;
width: 100%;
box-sizing: border-box;
}
main {
width: 100%;
height: 85%;
margin: 0;
padding: 0;
}
#rooms-pane {
display: flex;
border: solid grey 0.1em;
height: 100%;
}
#room-chat {
border: solid grey 0.1em;
}
#room-list {
flex: 1;
display: flex;
flex-flow: column;
border: solid grey 0.1em;
height: 100%;
}
#room-selected-pane {
height: 100%;
flex: 3;
display: flex;
flex-flow: row;
}
#room-chat {
height: 100%;
max-width: 100%;
flex: 2;
display: flex;
flex-flow: column;
justify-content: space-between;
}
#room-user-list {
flex: 1;
display: block;
list-style: none;
margin: 0;
padding: 1em;
}
#room-chat-header {
flex: 1;
padding: 0.8em;
border: solid grey 0.1em;
display: flex;
flex-flow: row;
justify-content: space-between;
align-items: center;
}
#room-chat-header-title {
font-size: 1.3em;
text-align: center;
flex: 1;
}
#room-chat-header > button {
padding: 0.5em 1em;
}
#room-chat-message-list {
display: block;
height: 83%;
width: 100%;
box-sizing: border-box;
margin: 0;
padding: 1em;
overflow: auto;
list-style: none;
}
.room-chat-message {
display: flex;
flex-flow: column;
align-items: flex-start;
margin-right: 3em;
}
.room-chat-message-me {
align-items: flex-end;
margin-left: 3em;
margin-right: 0;
}
.room-chat-message-user {
font-weight: bold;
color: blue;
}
.room-chat-message-text {
padding: 0.5em 0.7em;
margin: 0.2em 0.5em;
border-radius: 0.8em;
background-color: lightgrey;
}
.room-chat-message-me > .room-chat-message-text {
background-color: blue;
color: white;
}
#room-chat-form {
width: 100%;
border: solid grey 0.1em;
}
#room-chat-form form {
width: 100%;
display: flex;
flex-flow: row;
}
#room-chat-form input {
flex: 1;
padding: 0.8em;
}
#room-list-header {
flex: 1;
display: flex;
flex-flow: row;
border: solid grey 0.1em;
}
#room-list-header > div {
flex: 1;
border: solid grey 0.1em;
}
#room-list ul {
display: block;
list-style: none;
padding: 0;
margin: 0;
height: 84%;
overflow-y: auto;
}
.room {
display: flex;
justify-content: space-between;
color: inherit;
text-decoration: inherit;
padding: 0.5em 1em;
border: solid grey 0.1em;
}
.room:hover {
background: lightgrey;
}
.room:active {
background: grey;
}
.room-joined {
background: lightgreen;
}
.room-selected {
background: lightblue;
}
#login-status-pane,
#socket-status-pane {
flex: 1;
}
#connect-form {
display: flex;
flex-flow: column;
align-items: center;
}

+ 3
- 0
tailwind.config.js View File

@ -3,6 +3,9 @@ module.exports = {
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
maxWidth: {
"3/4": "75%",
},
},
variants: {
extend: {


Loading…
Cancel
Save