feat: a simple about page

This commit is contained in:
Haishan 2020-08-01 20:04:49 +08:00
parent 437733f4af
commit 4ae2c5c2f3
7 changed files with 164 additions and 19 deletions

26
src/api/version.ts Normal file
View file

@ -0,0 +1,26 @@
import { getURLAndInit } from 'src/misc/request-helper';
import { ClashAPIConfig } from 'src/types';
type VersionData = {
version: string;
premium?: boolean;
};
export async function fetchVersion(
endpoint: string,
apiConfig: ClashAPIConfig
): Promise<VersionData> {
let json = { rules: [] };
try {
const { url, init } = getURLAndInit(apiConfig);
const res = await fetch(url + endpoint, init);
if (res.ok) {
json = await res.json();
}
} catch (err) {
// log and ignore
// eslint-disable-next-line no-console
console.log(`failed to fetch ${endpoint}`, err);
}
return json;
}

View file

@ -3,6 +3,7 @@ import './Root.css';
import React, { Suspense } from 'react';
import { HashRouter as Router, Route, Routes } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
import { About } from 'src/components/about/About';
import { actions, initialState } from '../store';
import APIDiscovery from './APIDiscovery';
@ -41,6 +42,7 @@ const routes = [
['logs', '/logs', <Logs />],
['proxies', '/proxies', <Proxies />],
['rules', '/rules', <Rules />],
['about', '/about', <About />],
__DEV__ ? ['style', '/style', <StyleGuide />] : false,
].filter(Boolean);

View file

@ -2,7 +2,7 @@ import cx from 'clsx';
import { motion } from 'framer-motion';
import PropTypes from 'prop-types';
import React from 'react';
// import { Command, Activity, Globe, Link2, Settings, File } from 'react-feather';
import { Info } from 'react-feather';
import {
FcAreaChart,
FcDocument,
@ -16,7 +16,6 @@ import { Link, useLocation } from 'react-router-dom';
import { getTheme, switchTheme } from '../store/app';
import s from './SideBar.module.css';
import { connect } from './StateProvider';
import SvgYacd from './SvgYacd';
const { useCallback } = React;
@ -115,9 +114,17 @@ function SideBar({ dispatch, theme }) {
/>
))}
</div>
<button className={s.themeSwitchContainer} onClick={switchThemeHooked}>
{theme === 'light' ? <MoonA /> : <Sun />}
</button>
<div className={s.footer}>
<button
className={cx(s.iconWrapper, s.themeSwitchContainer)}
onClick={switchThemeHooked}
>
{theme === 'light' ? <MoonA /> : <Sun />}
</button>
<Link to="/about" className={s.iconWrapper}>
<Info size={20} />
</Link>
</div>
</div>
);
}

View file

@ -68,34 +68,44 @@
}
}
.themeSwitchContainer {
--sz: 40px;
@media (max-width: 768px) {
display: none;
}
.footer {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
}
@media (max-width: 768px) {
.footer {
display: none;
}
}
.iconWrapper {
--sz: 40px;
width: var(--sz);
height: var(--sz);
display: flex;
justify-content: center;
align-items: center;
color: var(--color-text);
padding: 5px;
color: var(--color-text);
border-radius: 100%;
border: 1px solid transparent;
}
.iconWrapper:hover {
opacity: 0.6;
}
.iconWrapper:focus {
border-color: var(--color-focus-blue);
}
.themeSwitchContainer {
appearance: none;
outline: none;
user-select: none;
background: none;
cursor: pointer;
border: 1px solid transparent;
border-radius: 100%;
&:focus {
border-color: var(--color-focus-blue);
}
}

View file

@ -0,0 +1,18 @@
.root {
padding: 6px 15px;
@media (--breakpoint-not-small) {
padding: 10px 40px;
}
}
.mono {
font-family: var(--font-mono);
}
.link {
color: var(--color-text-secondary);
display: inline-flex;
}
.link:hover {
color: var(--color-text-highlight);
}

View file

@ -0,0 +1,78 @@
import * as React from 'react';
import { GitHub } from 'react-feather';
import { useQuery } from 'react-query';
import { fetchVersion } from 'src/api/version';
import ContentHeader from 'src/components/ContentHeader';
import { connect } from 'src/components/StateProvider';
import { getClashAPIConfig } from 'src/store/app';
import { ClashAPIConfig } from 'src/types';
import s from './About.module.css';
type Props = { apiConfig: ClashAPIConfig };
function Version({
name,
link,
version,
}: {
name: string;
link: string;
version: string;
}) {
return (
<div className={s.root}>
<h2>{name}</h2>
<p>
<span>Version </span>
<span className={s.mono}>{version}</span>
</p>
<p>
<a
className={s.link}
href={link}
target="_blank"
rel="noopener noreferrer"
>
<GitHub size={20} />
<span>Source</span>
</a>
</p>
</div>
);
}
function AboutImpl(props: Props) {
const { data: version } = useQuery(
['/version', props.apiConfig],
fetchVersion,
{
suspense: true,
}
);
console.log(version);
return (
<>
<ContentHeader title="About" />
{version ? (
<Version
name="Clash"
version={version.version}
link="https://github.com/Dreamacro/clash"
/>
) : null}
<Version
name="Yacd"
version={__VERSION__}
link="https://github.com/haishanh/yacd"
/>
</>
);
}
const mapState = (s) => ({
apiConfig: getClashAPIConfig(s),
});
export const About = connect(mapState)(AboutImpl);

4
src/custom.d.ts vendored
View file

@ -3,3 +3,7 @@ declare module '*.module.css' {
const classes: { [key: string]: string };
export default classes;
}
// webpack definePlugin replacing variables
declare const __VERSION__: string;
declare const __DEV__: string;