Skip to content

Commit

Permalink
Merge pull request #11 from rocicorp/user-chip
Browse files Browse the repository at this point in the history
Add a user chip with a randomly chosen username and avatar.
  • Loading branch information
aboodman authored Mar 3, 2021
2 parents b86df1c + 0c69432 commit 27c2413
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 15 deletions.
10 changes: 7 additions & 3 deletions backend/rds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,21 @@ export async function ensureDatabase() {
}

async function createDatabase() {
await executeStatementInDatabase(null, "CREATE DATABASE " + dbName);
await executeStatementInDatabase(
null,
`CREATE DATABASE ${dbName}
CHARACTER SET utf8mb4`
);

await executeStatement(`CREATE TABLE Cookie (
Version BIGINT NOT NULL)`);
await executeStatement(`CREATE TABLE Client (
Id VARCHAR(255) PRIMARY KEY NOT NULL,
Id VARCHAR(100) PRIMARY KEY NOT NULL,
LastMutationID BIGINT NOT NULL,
LastModified TIMESTAMP NOT NULL
DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP)`);
await executeStatement(`CREATE TABLE Object (
K VARCHAR(255) PRIMARY KEY NOT NULL,
K VARCHAR(100) PRIMARY KEY NOT NULL,
V TEXT NOT NULL,
Version BIGINT NOT NULL,
LastModified TIMESTAMP NOT NULL
Expand Down
27 changes: 26 additions & 1 deletion frontend/data.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import Replicache, { ReadTransaction, WriteTransaction } from "replicache";
import { useSubscribe } from "replicache-react-util";
import { getShape, Shape, putShape, moveShape } from "../shared/shape";
import { getClientState, overShape } from "../shared/client-state";
import {
getClientState,
overShape,
initClientState,
} from "../shared/client-state";
import type Storage from "../shared/storage";
import type { UserInfo } from "../shared/client-state";
import { newID } from "../shared/id";

/**
Expand Down Expand Up @@ -39,6 +44,16 @@ export function createData(rep: Replicache) {
}
),

initClientState: rep.register(
"initClientState",
async (
tx: WriteTransaction,
args: { id: string; defaultUserInfo: UserInfo }
) => {
await initClientState(writeStorage(tx), args);
}
),

overShape: rep.register(
"overShape",
async (
Expand Down Expand Up @@ -71,6 +86,16 @@ export function createData(rep: Replicache) {
);
},

useUserInfo(): UserInfo | null {
return useSubscribe(
rep,
async (tx: ReadTransaction) => {
return (await getClientState(readStorage(tx), clientID)).userInfo;
},
null
);
},

useOverShapeID(): string | null {
return useSubscribe(
rep,
Expand Down
17 changes: 17 additions & 0 deletions frontend/nav.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,20 @@
background-color: rgb(75, 158, 244);
opacity: 1;
}

.user {
color: white;
display: flex;
margin-left: auto;
margin-right: 12px;
margin-top: auto;
margin-bottom: auto;
padding: 0 10px;
height: 30px;
align-items: center;
font-family: "Inter", sans-serif;
font-size: 13px;
font-weight: 400;
border-radius: 6px;
line-height: 1em;
}
19 changes: 14 additions & 5 deletions frontend/nav.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import styles from "./nav.module.css";
import { Data } from "./data";
import { newID } from "../shared/id";
import { randInt } from "../shared/rand";

const colors = ["red", "blue", "white", "green", "yellow"];

function randInt(min: number, max: number): number {
const range = max - min;
return Math.round(Math.random() * range);
}

export function Nav({ data }: { data: Data | null }) {
const userInfo = data?.useUserInfo();
console.log({ userInfo });

const onRectangle = async () => {
if (!data) {
return;
Expand Down Expand Up @@ -98,6 +97,16 @@ export function Nav({ data }: { data: Data | null }) {
></path>
</svg>
</div>
{userInfo && (
<div
className={styles.user}
style={{
backgroundColor: userInfo.color,
}}
>
{userInfo.avatar} {userInfo.name}
</div>
)}
</div>
);
}
28 changes: 28 additions & 0 deletions pages/_document.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Document, { Html, Head, Main, NextScript } from "next/document";

class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}

render() {
return (
<Html>
<Head>
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link
href="https://fonts.googleapis.com/css2?family=Inter&display=swap"
rel="stylesheet"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}

export default MyDocument;
17 changes: 16 additions & 1 deletion pages/api/replicache-push.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import * as t from "io-ts";
import { ExecuteStatementFn, transact } from "../../backend/rds";
import { putShape, moveShape, shape } from "../../shared/shape";
import { overShape } from "../../shared/client-state";
import {
initClientState,
overShape,
userInfo,
} from "../../shared/client-state";
import {
getObject,
putObject,
Expand Down Expand Up @@ -31,6 +35,14 @@ const mutation = t.union([
dy: t.number,
}),
}),
t.type({
id: t.number,
name: t.literal("initClientState"),
args: t.type({
id: t.string,
defaultUserInfo: userInfo,
}),
}),
t.type({
id: t.number,
name: t.literal("overShape"),
Expand Down Expand Up @@ -98,6 +110,9 @@ export default async (req: NextApiRequest, res: NextApiResponse) => {
case "overShape":
await overShape(s, mutation.args);
break;
case "initClientState":
await initClientState(s, mutation.args);
break;
}

await setLastMutationID(executor, push.clientID, expectedMutationID);
Expand Down
10 changes: 9 additions & 1 deletion pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Nav } from "../frontend/nav";
import Pusher from "pusher-js";

import type { Data } from "../frontend/data";
import { randUserInfo } from "../shared/client-state";

export default function Home() {
const [data, setData] = useState<Data | null>(null);
Expand All @@ -26,6 +27,13 @@ export default function Home() {
useMemstore: true,
pushDelay: 1,
});

const defaultUserInfo = randUserInfo();
const d = createData(rep);
d.initClientState({
id: d.clientID,
defaultUserInfo,
});
rep.sync();

Pusher.logToConsole = true;
Expand All @@ -37,7 +45,7 @@ export default function Home() {
rep.pull();
});

setData(createData(rep));
setData(d);
});

return (
Expand Down
75 changes: 71 additions & 4 deletions shared/client-state.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,85 @@
import * as t from "io-ts";
import { must } from "../backend/decode";
import Storage from "./storage";
import { randInt } from "./rand";

const colors = [
"#f94144",
"#f3722c",
"#f8961e",
"#f9844a",
"#f9c74f",
"#90be6d",
"#43aa8b",
"#4d908e",
"#577590",
"#277da1",
];
const avatars = [
["🐶", "Puppy"],
["🐱", "Kitty"],
["🐭", "Mouse"],
["🐹", "Hamster"],
["🐰", "Bunny"],
["🦊", "Fox"],
["🐻", "Bear"],
["🐼", "Panda"],
["🐻‍❄️", "Polar Bear"],
["🐨", "Koala"],
["🐯", "Tiger"],
["🦁", "Lion"],
["🐮", "Cow"],
["🐷", "Piggy"],
["🐵", "Monkey"],
["🐣", "Chick"],
];

export const userInfo = t.type({
avatar: t.string,
name: t.string,
color: t.string,
});

// TODO: It would be good to merge this with the first-class concept of `client`
// that Replicache itself manages if possible.
export const clientState = t.type({
overID: t.string,
userInfo: userInfo,
});

export type UserInfo = t.TypeOf<typeof userInfo>;
export type ClientState = t.TypeOf<typeof clientState>;

export async function initClientState(
storage: Storage,
{ id, defaultUserInfo }: { id: string; defaultUserInfo: UserInfo }
): Promise<void> {
if (await storage.getObject(key(id))) {
return;
}
await putClientState(storage, {
id,
clientState: {
overID: "",
userInfo: defaultUserInfo,
},
});
}

export async function getClientState(
storage: Storage,
id: string
): Promise<ClientState> {
const jv = await storage.getObject(key(id));
return jv ? must(clientState.decode(jv)) : { overID: "" };
if (!jv) {
throw new Error("Expected clientState to be initialized already");
}
return must(clientState.decode(jv));
}

export function putClientState(
storage: Storage,
id: string,
clientState: ClientState
{ id, clientState }: { id: string; clientState: ClientState }
): Promise<void> {
return storage.putObject(key(id), clientState);
}
Expand All @@ -32,7 +90,16 @@ export async function overShape(
): Promise<void> {
const client = await getClientState(storage, clientID);
client.overID = shapeID;
await putClientState(storage, clientID, client);
await putClientState(storage, { id: clientID, clientState: client });
}

export function randUserInfo() {
const [avatar, name] = avatars[randInt(0, avatars.length - 1)];
return {
avatar,
name: "Anonymous " + name,
color: colors[randInt(0, colors.length - 1)],
};
}

function key(id: string): string {
Expand Down
4 changes: 4 additions & 0 deletions shared/rand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export function randInt(min: number, max: number): number {
const range = max - min;
return Math.round(Math.random() * range);
}

0 comments on commit 27c2413

Please sign in to comment.