Skip to content

Commit

Permalink
feat: add title format; add search page
Browse files Browse the repository at this point in the history
  • Loading branch information
dreamerblue committed Sep 12, 2022
1 parent e5f070a commit f832ef0
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 21 deletions.
4 changes: 4 additions & 0 deletions .umirc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ export default defineConfig({
},
favicon: '/dist/favicon.ico',
hash: true,
title: false,
antd: {},
define: {
'process.env.API_BASE': isProd ? 'https://rl.algoux.org/api' : 'https://rl-dev.algoux.org',
},
analytics: {
ga: 'G-D6CVTJBDZT',
},
Expand Down
3 changes: 2 additions & 1 deletion src/components/StyledRanklist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import dayjs from 'dayjs';
import FileSaver from 'file-saver';
import { createCheckers } from 'ts-interface-checker';
import srkChecker from '@/lib/srk-checker/index.d.ti';
import { EyeOutlined } from '@ant-design/icons';

const { Ranklist: ranklistChecker } = createCheckers(srkChecker);

Expand Down Expand Up @@ -96,7 +97,7 @@ export default function StyledRanklist({ data, name, meta }: IStyledRanklistProp
const endAt = startAt + formatTimeDuration(data.contest.duration, 'ms');
const metaBlock = !meta ? null : (
<div className="text-center mt-1">
<span className="mr-2">{meta.viewCnt || '-'} views</span>
<span className="mr-2"><EyeOutlined /> {meta.viewCnt || '-'}</span>
<a className="pl-2 border-0 border-l border-solid border-gray-400" onClick={download}>
Download srk
</a>
Expand Down
81 changes: 81 additions & 0 deletions src/pages/search/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { useEffect } from 'react';
import { Helmet, Link, useHistory } from 'umi';
import { Input, List, Spin } from 'antd';
import { useReq } from '@/utils/request';
import { api } from '@/services/api';
import urlcat from 'urlcat';
import dayjs from 'dayjs';
import { EyeOutlined } from '@ant-design/icons';
import { formatTitle } from '@/utils/title-format.util';

export default function SearchPage({ location }: any) {
const kw = location.query.kw;
const history = useHistory();
const [isInit, setIsInit] = React.useState(false);

const onSearch = (value: string) => {
history.push({ search: `kw=${encodeURIComponent(value)}` });
};

const {
run: runSearch,
loading,
data,
} = useReq(api.searchRanklist.bind(api), {
manual: true,
});

const count = data?.ranks.length || 0;
const rows = data?.ranks || [];

useEffect(() => {
if (kw) {
setIsInit(true);
runSearch({ kw });
}
}, [kw]);

const renderResult = () => {
if (loading) {
return <Spin className="mt-10" />;
}
return (
<div className="mt-10">
<div className="opacity-70">搜索到 {count} 个结果</div>
{count > 0 && (
<div className="mt-2">
<List
key="id"
dataSource={rows}
size="small"
renderItem={(item) => (
<List.Item>
<p className="mb-0">
<Link to={urlcat('/ranklist/:uniqueKey', { uniqueKey: item.uniqueKey })}>{item.name}</Link>
<span className="ml-2 opacity-70"><EyeOutlined /> {item.viewCnt}</span>
</p>
<p className="mb-0 opacity-50 text-sm">
创建于 {dayjs(item.createdAt).format('YYYY-MM-DD HH:mm')}
</p>
</List.Item>
)}
/>
</div>
)}
</div>
);
};

return (
<div className="normal-content">
<Helmet>
<title>{formatTitle('Explore')}</title>
</Helmet>
<div>
<h3 className="mb-6">在榜单数据库中探索</h3>
<Input.Search defaultValue={kw || ''} placeholder="输入关键词" onSearch={onSearch} enterButton />
{isInit && renderResult()}
</div>
</div>
);
}
33 changes: 24 additions & 9 deletions src/services/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import type * as srk from '@algoux/standard-ranklist';
import requestAdapter, { RequestAdapter } from '@/utils/request';
import requestAdapter, { ApiException, HttpException, RequestAdapter } from '@/utils/request';
import { IApiRanklistInfo } from './interface';
import urlcat from 'urlcat';
import { LogicException, LogicExceptionKind } from './logic.exception';

export class ApiService {
public constructor(private readonly requestAdapter: RequestAdapter) {}

public getRanklistInfo(opts: { uniqueKey: string }) {
return this.requestAdapter.get<IApiRanklistInfo>(`/rank/${opts.uniqueKey}`);
return this.requestAdapter.get<IApiRanklistInfo>(urlcat('/rank/:key', { key: opts.uniqueKey }));
}

public async getSrkFile<T = srk.Ranklist>(opts: { fileID: string }): Promise<T> {
const res = await this.requestAdapter.get(`/file/download?id=${opts.fileID}`, {
const res = await this.requestAdapter.get(urlcat('/file/download', { id: opts.fileID }), {
getResponse: true,
});
switch (res.response.headers.get('content-type')) {
Expand All @@ -26,12 +28,25 @@ export class ApiService {
info: IApiRanklistInfo;
srk: T;
}> {
const info = await this.getRanklistInfo({ uniqueKey: opts.uniqueKey });
const srk = await this.getSrkFile<T>({ fileID: info.fileID });
return {
info,
srk,
};
try {
const info = await this.getRanklistInfo({ uniqueKey: opts.uniqueKey });
const srk = await this.getSrkFile<T>({ fileID: info.fileID });
return {
info,
srk,
};
} catch (e) {
if ((e instanceof ApiException && e.code === 11) || (e instanceof HttpException && e.status === 404)) {
throw new LogicException(LogicExceptionKind.NotFound);
}
throw e;
}
}

public async searchRanklist(opts: { kw?: string }) {
return this.requestAdapter.get<{
ranks: IApiRanklistInfo[];
}>(urlcat('/rank/search', { query: opts.kw }));
}
}

Expand Down
15 changes: 15 additions & 0 deletions src/services/api/logic.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export enum LogicExceptionKind {
NotFound = 'NotFound',
}

export class LogicException extends Error {
public kind: LogicExceptionKind;

public constructor(kind: LogicExceptionKind) {
super(`logic exception with kind: ${kind}`);
this.name = 'LogicException';
this.kind = kind;
// @ts-ignore
Error.captureStackTrace(this, this.constructor);
}
}
24 changes: 13 additions & 11 deletions src/utils/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,20 @@ const parseResponseMiddleware = async (ctx: Context, next: () => void) => {

const { res } = ctx;
if (!options.getResponse) {
let data: any;
try {
const data = await res.clone().json();
if (typeof data.code === 'number') {
if (data.code === 0) {
ctx.res = data.data;
return;
} else {
throw new ApiException(data.code, data.message);
}
}
data = await res.clone().json();
} catch (e) {
console.warn('Trying to parse response failed:', e);
}
if (typeof data?.code === 'number') {
if (data.code === 0) {
ctx.res = data.data;
return;
} else {
throw new ApiException(data.code, data.message);
}
}
throw new HttpException(res.status, res.statusText);
} else {
ctx.res = {
Expand All @@ -79,7 +80,7 @@ const parseResponseMiddleware = async (ctx: Context, next: () => void) => {

if (isBrowser()) {
requestAdapter = extend({
prefix: 'http://rl.localdev.algoux.org',
prefix: process.env.API_BASE,
timeout: 30000,
parseResponse: false,
});
Expand All @@ -92,12 +93,13 @@ if (isBrowser()) {
} else {
message.error(e.message);
}
throw e;
}
});
requestAdapter.use(parseResponseMiddleware);
} else {
requestAdapter = extend({
prefix: 'http://rl.localdev.algoux.org',
prefix: process.env.API_BASE,
timeout: 5000,
parseResponse: false,
});
Expand Down
3 changes: 3 additions & 0 deletions src/utils/title-format.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function formatTitle(title?: string) {
return title ? `${title} | RankLand` : 'RankLand';
}
1 change: 1 addition & 0 deletions typings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ declare module '*.svg' {
const url: string;
export default url;
}
declare module '*.txt';

0 comments on commit f832ef0

Please sign in to comment.