Quick Save

This commit is contained in:
zkldi
2021-07-28 01:26:39 +01:00
parent a642aaf35a
commit 809a8349b9
15 changed files with 684 additions and 134 deletions
+4 -5
View File
@@ -1,15 +1,14 @@
# dependencies
/node_modules
.env
/reference
/_site
/build
/public/media/svg
/src/react-app-env.d.ts
.vscode
/src/react-app-env.d.ts
/src/react-app-env.d.ts
.pnpm-debug.log
+4 -1
View File
@@ -30,6 +30,9 @@
"@material-ui/lab": "4.0.0-alpha.53",
"@material-ui/pickers": "3.2.10",
"@material-ui/styles": "4.9.14",
"@nivo/calendar": "^0.73.0",
"@nivo/core": "^0.73.0",
"@nivo/line": "^0.73.0",
"@svgr/webpack": "4.3.2",
"@tanem/svg-injector": "8.0.50",
"apexcharts": "3.19.2",
@@ -58,7 +61,7 @@
"postcss-normalize": "7.0.1",
"postcss-preset-env": "6.7.0",
"postcss-safe-parser": "4.0.1",
"react": "16.12.0",
"react": "17",
"react-async": "10.0.1",
"react-bootstrap": "1.0.1",
"react-dom": "16.12.0",
+474 -118
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -9,8 +9,11 @@ import { UserContext } from "context/UserContext";
import { PublicUserDocument } from "tachi-common";
import toast from "react-hot-toast";
import { TachiConfig } from "lib/config";
import useSetSubheader from "components/layout/header/useSetSubheader";
export default function LoginPage() {
useSetSubheader("Login");
const [err, setErr] = useState("");
const { setUser } = useContext(UserContext);
const history = useHistory();
+3
View File
@@ -11,11 +11,14 @@ import toast from "react-hot-toast";
import { UseFormik } from "types/react";
import Divider from "components/util/Divider";
import { TachiConfig } from "lib/config";
import useSetSubheader from "components/layout/header/useSetSubheader";
// seconds it takes for a user to actually read the rules.
const RULES_READ_TIME = 30;
export default function RegisterPage() {
useSetSubheader("Register");
const [err, setErr] = useState("");
// not opened: user has not clicked the rules link
// opened: user has clicked the rules link
@@ -25,6 +25,7 @@ import { GamePT } from "types/react";
import { APIFetchV1 } from "util/api";
import AsyncLoader from "components/util/AsyncLoader";
import { Playtype } from "types/tachi";
import TimelineChart from "components/charts/TimelineChart";
export default function OverviewPage({
reqUser,
@@ -59,12 +60,34 @@ function RankingInfo({ reqUser, game, playtype }: { reqUser: PublicUserDocument
throw new Error(res.description);
}
return res.body;
const data: number[] = [];
for (let i = 0; i <= 90; i++) {
data.push(
(data[i - 1] ?? 50) +
Math.floor(Math.random() > 0.5 ? -Math.random() * 5 : Math.random() * 5)
);
}
return data.map((e, i) => ({
ranking: e,
timestamp: Date.now() - i * 1000 * 60 * 60 * 24,
}));
}}
>
{data => (
<Card className="mt-4" header="Ranking">
<div>{data.map(e => e.ranking)}</div>
<TimelineChart
height="30rem"
width="100%"
data={[
{
id: "ranking",
data: data.map(d => ({ x: d.timestamp, y: -d.ranking })),
},
]}
yAxisFormat={y => `#${-y}`}
/>
</Card>
)}
</AsyncLoader>
@@ -0,0 +1,31 @@
import React from "react";
import { PointTooltipProps } from "@nivo/line";
const defaultRenderFn = (point: PointTooltipProps["point"]) => (
<div>
{point.data.xFormatted} {point.data.yFormatted}
</div>
);
export default function ChartTooltip({
renderFn = defaultRenderFn,
point,
}: {
point: PointTooltipProps["point"];
renderFn?: (p: PointTooltipProps["point"]) => JSX.Element;
}) {
return (
<div
className="chart-tooltip d-flex align-items-center justify-content-center"
style={{
width: "10rem",
height: "5rem",
backgroundColor: "#131313",
borderRadius: "1 1 1 1",
boxShadow: "0px 0px 2px 0px black",
}}
>
{renderFn(point)}
</div>
);
}
@@ -0,0 +1,84 @@
import React from "react";
import { FormatDate, FormatDuration, FormatTime, FormatTimeSmall, MillisToSince } from "util/time";
import { ResponsiveLine, Serie } from "@nivo/line";
import { DateTime } from "luxon";
import { TACHI_CHART_THEME } from "util/constants/chart-theme";
import ChartTooltip from "./ChartTooltip";
export default function TimelineChart<T>({
width,
height,
data,
yAxisFormat,
}: {
width?: number | string;
height?: number | string;
data: Serie[];
yAxisFormat?: (y: number) => string;
}) {
return (
<div style={{ height, width }}>
<ResponsiveLine
data={data}
margin={{ right: 110, top: 30, bottom: 50, left: 60 }}
xScale={{ type: "time", format: "%Q" }}
xFormat="time:%Q"
axisBottom={{
format: x => FormatTimeSmall(Number(x)),
}}
yScale={{ type: "linear", min: "auto", max: "auto", stacked: true, reverse: false }}
axisLeft={{
tickSize: 5,
tickPadding: 5,
tickRotation: 0,
format: y => (Number.isInteger(y) ? `#${-y}` : ""),
}}
enablePoints={false}
enableArea={true}
colors={["#cc527a"]}
useMesh={true}
theme={TACHI_CHART_THEME}
tooltip={d => (
<ChartTooltip
point={d.point}
renderFn={p => (
<div>
{MillisToSince(+p.data.xFormatted)}: #{-p.data.yFormatted}
<br />
<small className="text-muted">
({FormatDate(+p.data.xFormatted)})
</small>
</div>
)}
/>
)}
legends={[
{
anchor: "bottom-right",
direction: "column",
justify: false,
translateX: 100,
translateY: 0,
itemsSpacing: 0,
itemDirection: "left-to-right",
itemWidth: 80,
itemHeight: 20,
itemOpacity: 0.75,
symbolSize: 12,
symbolShape: "circle",
symbolBorderColor: "rgba(0, 0, 0, .5)",
effects: [
{
on: "hover",
style: {
itemBackground: "rgba(0, 0, 0, .03)",
itemOpacity: 1,
},
},
],
},
]}
/>
</div>
);
}
@@ -17,9 +17,5 @@ export default function useSetSubheader(
setBreadcrumbs,
overrideTitle
);
return () => {
UpdateSubheader([], setTitle, setBreadcrumbs);
};
}, deps);
}
+9 -1
View File
@@ -14,4 +14,12 @@ import "./_metronic/_assets/plugins/flaticon2/flaticon.css";
const { PUBLIC_URL } = process.env;
ReactDOM.render(<App basename={PUBLIC_URL} />, document.getElementById("root"));
console.log(process.env);
// if (!PUBLIC_URL) {
// throw new Error(
// `No PUBLIC_URL was defined in environment. Have you forgot to set it in a .env file?`
// );
// }
ReactDOM.render(<App basename={PUBLIC_URL!} />, document.getElementById("root"));
+11
View File
@@ -7,15 +7,26 @@ if (!mode) {
}
let conf;
let colourConf;
if (mode === "ktchi") {
conf = StaticConfig.KTCHI_CONFIG;
colourConf = {
primary: "#e61c6e",
};
} else if (mode === "btchi") {
conf = StaticConfig.BTCHI_CONFIG;
colourConf = {
primary: "#4974a5",
};
} else if (mode === "omni") {
conf = StaticConfig.OMNI_CONFIG;
colourConf = {
primary: "#e61c6e",
};
} else {
throw new Error(`Invalid TCHIC_MODE of ${mode}. Cannot boot.`);
}
export const TachiConfig = conf;
export const ColourConfig = colourConf;
+2 -2
View File
@@ -6,9 +6,9 @@ export function ChangeOpacity(str: string, opacity: number) {
if (str.startsWith("#")) {
if (str.length === 7) {
return str + Math.floor(opacity * 255).toString(16);
} else {
return str.slice(0, 7) + Math.floor(opacity * 255).toString(16);
}
return str.slice(0, 7) + Math.floor(opacity * 255).toString(16);
} else if (str.startsWith("rgba(")) {
return str.replace(/, ?[0-9.]+\)$/u, `,${opacity})`);
} else if (str.startsWith("rgb(")) {
+25
View File
@@ -0,0 +1,25 @@
export const TACHI_CHART_THEME = {
background: "#2b292b",
textColor: "#ffffff",
fontSize: 11,
axis: {
domain: {
line: {
stroke: "#777777",
strokeWidth: 1,
},
},
ticks: {
line: {
stroke: "#777777",
strokeWidth: 1,
},
},
},
grid: {
line: {
stroke: "#131313",
strokeWidth: 1,
},
},
};
+1 -1
View File
@@ -7,7 +7,7 @@ export function UpdateSubheader(
setBreadcrumbs: SetState<string[]>,
customTitle?: string
) {
const title = customTitle ?? breadcrumbs[breadcrumbs.length - 1];
const title = customTitle ?? breadcrumbs[breadcrumbs.length - 1] ?? "Somewhere...";
document.title = `${title} | ${TachiConfig.name}`;
setTitle(title);
+8
View File
@@ -9,6 +9,14 @@ export function FormatTime(ms: number) {
return DateTime.fromMillis(ms).toLocaleString(DateTime.DATETIME_MED);
}
export function FormatDate(ms: number) {
return DateTime.fromMillis(ms).toLocaleString(DateTime.DATE_HUGE);
}
export function FormatDuration(ms: number) {
return humaniseDuration(ms);
}
export function FormatTimeSmall(ms: number) {
return DateTime.fromMillis(ms).toLocaleString(DateTime.DATE_SHORT);
}