From f742766f235d3fd0dfdff7df5cc7ba1ca9a1af41 Mon Sep 17 00:00:00 2001 From: Julien Stroheker Date: Thu, 30 Jan 2020 20:17:07 -0500 Subject: [PATCH] Demo (#19) * Well let's try this ! (#16) * typo into release pipeline * Feat/add username in header (#14) * feat: add user in redux * feat: update app and topbar components * feat: avoid user to autolike his comment (#15) * feat: add user in redux * feat: update app and topbar components * feat: avoid user to autolike his comment * Feat/leaderboard (#18) * feat: add user in redux * feat: update app and topbar components * chore: recover leaderboard * chore: cleanup App.tsx from leaderboard * feat: finish leaderboard * feat: update scoring algo Co-authored-by: Yann RENAUDIN <4748419+emyann@users.noreply.github.com> --- cmd/web/package.json | 2 + cmd/web/src/App.tsx | 3 +- cmd/web/src/Leaderboard.tsx | 110 +++++++++++++++--- cmd/web/src/TopBar.tsx | 47 +++++++- .../src/store/authors/authors.selectors.ts | 39 ++++--- cmd/web/yarn.lock | 17 +++ 6 files changed, 187 insertions(+), 31 deletions(-) diff --git a/cmd/web/package.json b/cmd/web/package.json index c4736c7..2ef4d2a 100644 --- a/cmd/web/package.json +++ b/cmd/web/package.json @@ -16,6 +16,7 @@ "d3-scale": "^3.2.1", "date-fns": "^2.9.0", "js-cookie": "^2.2.1", + "mdi-react": "^6.6.0", "morphism": "^1.12.3", "react": "^16.12.0", "react-dom": "^16.12.0", @@ -47,6 +48,7 @@ ] }, "devDependencies": { + "@types/d3-scale": "^2.1.1", "@types/react-redux": "^7.1.5", "@types/redux-logger": "^3.0.7", "@types/webpack-env": "^1.14.1" diff --git a/cmd/web/src/App.tsx b/cmd/web/src/App.tsx index 3b59c48..fdfdbb3 100644 --- a/cmd/web/src/App.tsx +++ b/cmd/web/src/App.tsx @@ -29,11 +29,10 @@ export default function App() { dispatch(saveUser({ userId })); } }; - }, []); + }, [dispatch]); return ( - {/* */} diff --git a/cmd/web/src/Leaderboard.tsx b/cmd/web/src/Leaderboard.tsx index 06771e0..9d826d1 100644 --- a/cmd/web/src/Leaderboard.tsx +++ b/cmd/web/src/Leaderboard.tsx @@ -1,28 +1,112 @@ -import React, { FC } from 'react'; +import React, { FC, useEffect, useMemo } from 'react'; import { useSelector } from 'react-redux'; -import { getAuthors, getAuthorsWithScore } from './store/authors'; -import { TableContainer, Table, TableHead, TableRow, TableCell, TableBody } from '@material-ui/core'; +import { getAuthorsWithScore } from './store/authors'; +import { + TableContainer, + Table, + TableHead, + TableRow, + TableCell, + TableBody, + Typography, + withStyles, + Theme, + createStyles, + Paper, + makeStyles, + Avatar, + Badge +} from '@material-ui/core'; +import MedalIcon from 'mdi-react/MedalIcon'; +import { deepOrange, deepPurple, lightGreen, yellow } from '@material-ui/core/colors'; + +const StyledTableCell = withStyles((theme: Theme) => + createStyles({ + head: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white + }, + body: { + fontSize: 14 + } + }) +)(TableCell); + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + table: { + minWidth: 500 + }, + rankCell: { + alignItems: 'center', + justifyContent: 'center' + }, + green: { + color: theme.palette.getContrastText('#000000'), + backgroundColor: '#000000' + } + }) +); export const Leaderboard: FC = () => { + const classes = useStyles(); const authorsWithScore = useSelector(getAuthorsWithScore); + const sortedAuthorsWithScore = useMemo( + () => + authorsWithScore.sort((a, b) => { + return b.score - a.score; + }), + [authorsWithScore] + ); + + function getMedalColor(index: number) { + switch (index) { + case 1: { + return '#ffdf00'; + } + case 2: { + return '#aaa9ad'; + } + case 3: { + return '#cd7f32'; + } + default: + break; + } + } return ( - - + +
- Rank - ID - Score + Rank + ID + Score - {authorsWithScore.map(({ author, score }, index) => ( + {sortedAuthorsWithScore.map(({ author, score }, index) => ( - - {index + 1} + + : null} + > + {index + 1} + + + + {author.id} + + + + {score} + - {author.id} - {score} ))} diff --git a/cmd/web/src/TopBar.tsx b/cmd/web/src/TopBar.tsx index b7851a8..bca936c 100644 --- a/cmd/web/src/TopBar.tsx +++ b/cmd/web/src/TopBar.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import Typography from '@material-ui/core/Typography'; @@ -10,6 +10,9 @@ import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; import Zoom from '@material-ui/core/Zoom'; import { useSelector } from 'react-redux'; import { getUser } from './store/user'; +import { Button, Popover } from '@material-ui/core'; +import { Leaderboard } from './Leaderboard'; +import { TableChart } from '@material-ui/icons'; interface Props { /** @@ -63,6 +66,9 @@ const useStyles2 = makeStyles((theme: Theme) => createStyles({ title: { flexGrow: 1 + }, + username: { + padding: theme.spacing(2) } }) ); @@ -70,6 +76,16 @@ const useStyles2 = makeStyles((theme: Theme) => export default function BackToTop(props: Props) { const classes = useStyles2(); const user = useSelector(getUser); + const [anchorEl, setAnchorEl] = useState(null); + + function handleOpenLeaderboard(event: React.MouseEvent) { + setAnchorEl(event.currentTarget); + } + + function handleClose() { + setAnchorEl(null); + } + const open = Boolean(anchorEl); return ( @@ -78,7 +94,34 @@ export default function BackToTop(props: Props) { I Have A Question ?! - {user.id} + + + + + + {user.id} + diff --git a/cmd/web/src/store/authors/authors.selectors.ts b/cmd/web/src/store/authors/authors.selectors.ts index b7ee446..60bbec6 100644 --- a/cmd/web/src/store/authors/authors.selectors.ts +++ b/cmd/web/src/store/authors/authors.selectors.ts @@ -1,7 +1,8 @@ import { AppState } from '../rootReducer'; import { createSelector } from '@reduxjs/toolkit'; import { getAuthorMessagesById } from '../authorMessages/authorMessages.selectors'; -import { getMessage, messagesSelector } from '../messages/messages.selectors'; +import { getMessage, messagesSelector, getMessages } from '../messages/messages.selectors'; +import { scalePow } from 'd3-scale'; export const authorsSelector = (state: AppState) => state.authors; export const makeGetAuthor = createSelector(authorsSelector, state => (authorId: string) => state.byId[authorId]); @@ -16,19 +17,29 @@ export const getAuthorMessages = createSelector([getAuthorMessagesById, getMessa } }); -export const getAuthorsWithScore = createSelector([authorsSelector, getAuthorMessages], (state, getAuthorMessages) => - Object.values(state.byId).map(author => { - const messages = getAuthorMessages(author.id); - const countOfMessages = messages.length; - const countOfLikes = messages.reduce((acc, message) => { - acc += message.likes; - return acc; - }, 0); - return { - author, - score: (countOfLikes + 1) * countOfMessages - }; - }) +export const getAuthorsWithScore = createSelector( + [authorsSelector, getAuthorMessages, getMessages], + (state, getAuthorMessages, messages) => { + const allLikes = messages.map(message => message.likes); + const min = Math.min(...allLikes); + const max = Math.max(...allLikes); + const scale = scalePow() + .exponent(0.2) + .domain([min, max]) + .range([1, 10]); + return Object.values(state.byId).map(author => { + const messages = getAuthorMessages(author.id); + const countOfMessages = messages.length; + const countOfLikes = messages.reduce((acc, message) => { + acc += message.likes; + return acc; + }, 0); + return { + author, + score: Math.floor(scale(countOfLikes) * countOfMessages) + }; + }); + } ); export const getMessagesWithUser = createSelector(messagesSelector, makeGetAuthor, (state, getAuthor) => diff --git a/cmd/web/yarn.lock b/cmd/web/yarn.lock index 495f38e..2f67cce 100644 --- a/cmd/web/yarn.lock +++ b/cmd/web/yarn.lock @@ -1352,6 +1352,18 @@ dependencies: "@babel/types" "^7.3.0" +"@types/d3-scale@^2.1.1": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-2.1.1.tgz#405e58771ec6ae7b8f7b4178ee1887620759e8f7" + integrity sha512-kNTkbZQ+N/Ip8oX9PByXfDLoCSaZYm+VUOasbmsa6KD850/ziMdYepg/8kLg2plHzoLANdMqPoYQbvExevLUHg== + dependencies: + "@types/d3-time" "*" + +"@types/d3-time@*": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-1.0.10.tgz#d338c7feac93a98a32aac875d1100f92c7b61f4f" + integrity sha512-aKf62rRQafDQmSiv1NylKhIMmznsjRN+MnXRXTqHoqm0U/UZzVpdrtRnSIfdiLS616OuC1soYeX1dBg2n1u8Xw== + "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" @@ -6612,6 +6624,11 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" +mdi-react@^6.6.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/mdi-react/-/mdi-react-6.6.0.tgz#423d770f956119aa2e4c7e4c121508209c36e5d0" + integrity sha512-HF+Madrr2bROpLoAJaM5kLA3tevXmvnC3aCO5bb3czu6Tc5OogW3kpgpVAX64CAMVkgzWUd+IjCNKWMbAHdvCA== + mdn-data@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"