Skip to content

Commit

Permalink
Update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
slhmy committed Aug 11, 2024
1 parent f57e039 commit 9babd67
Show file tree
Hide file tree
Showing 19 changed files with 358 additions and 31 deletions.
8 changes: 6 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
"editor.formatOnSave": true,
"markdownlint.config": {
"no-inline-html": {
"allowed_elements": ["img", "u"]
"allowed_elements": [
"img",
"u",
"br"
]
},
"no-empty-links": false,
"no-hard-tabs": false,
"single-h1": false
}
}
}
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,9 @@ This website is built using [Docusaurus](https://docusaurus.io/), a modern stati
npx docusaurus write-translations -l zh-CN
cp -rn docs/** i18n/zh-CN/docusaurus-plugin-content-docs/current
```

Start your site on the Chinese locale:

```shell
npm run start -- --locale zh-CN
```
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"label": "Platform Services",
"label": "Backend",
"position": 3,
"link": {
"type": "generated-index"
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"label": "Develop Environment",
"label": "Environment",
"position": 2,
"link": {
"type": "generated-index"
Expand Down
7 changes: 7 additions & 0 deletions docs/development/frontend/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"label": "Frontend",
"position": 3,
"link": {
"type": "generated-index"
}
}
187 changes: 187 additions & 0 deletions docs/development/frontend/making-view.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# Making View

This document will guide you through the process of creating a new view in
[OJ Lab front](https://github.com/oj-lab/oj-lab-front)
.

You will get to know with three main directories:
`/layouts`, `/pages` and `/components`.
So that you can understand how the view is structured and what kind of codes you need to provide in each directory.

:::tip

We will discuss the usage of
[React Hooks](https://reactjs.org/docs/hooks-intro.html) and state management
in some parts of the document.

If you are not familiar with React Hooks, you can check the official documentation.

:::

## Main Directories

### Layouts

Layout is a fundamental part of the view,
it usually provides `header`, `footer`, `sidebar`, `nav` and other common components.
In another word, layout is something it usually doesn't change between different pages.

The `/layouts` directory contains the layout components.
Currently, OJ Lab front has only one layout,
so all the components are placed in the root of the directory
(If we have more layouts in the future, we will create subdirectories for each layout).

In OJ Lab front's `Router.tsx`,
the layout component wraps other pages through React Router's [Outlet](https://reactrouter.com/en/main/components/outlet).

```tsx
// In Layout.tsx
<div>
{!props.children && <Outlet />}
</div>

// In Router.tsx
<Route path="/" element={<Layout />}>
<Route path="" element={<Navigate replace to="/problems" />} />
{/* Other routes */}
</Route>
```

### Pages

Pages compose the main content of the view by combining different components and hooking in necessary states.

```tsx
const Judge: React.FC = () => {
const uid = useParams().uid as string;
const { getJudge } = useJudge(uid);

const judge = getJudge();

return (
judge && (
<div className="grid gap-4 overflow-x-auto">
<div className="h-fit rounded border border-base-content/10 bg-base-100">
<JudgeTable data={[judge]} />
</div>
<MarkdownRender content={getCodeBlock(judge.language, judge.code)} />
</div>
)
);
};
```

In this example, we get the judge data by using the `useJudge` hook.
The judge data is got by calling backend's API
and then passed to the `JudgeTable` and `MarkdownRender` components.
When the judge data is ready, the page will render the components.

### Components

Components are reusable pieces of code that can be used in different pages.

:::warning

There are some special circumstances where you need to use state in components.
For example, the monaco editor component needs to change it's theme and size according to window settings.

But usually, components should be stateless.

:::

In OJ Lab front, components are classified into `control`,
`display`, `input`, `navigation` four categories.

- `control` components are used to control the view, such as buttons, dropdowns, etc.
- `display` components are used to display data, such as tables, cards, etc.
- `input` components are used to get user input, such as forms, inputs, etc.
- `navigation` components are used to navigate between pages, such as breadcrumbs, menus, etc.

```tsx
export interface UserMenuAction {
name: string;
href?: string;
onClick?: () => void;
}

export interface UserMenuProps {
avatarUrl?: string;
actions?: Array<UserMenuAction>;
}

/**
* @param {string} props.avatarUrl
* @param {Array<{ name: string, href: string }>} props.navigation The name of navigation will be translated.
*/
const UserMenu: React.FC<UserMenuProps> = (props) => {
```
When working on a new view, you should first check if there are any existing components that can be reused.
Then, if a new component is needed, you should design the props to pass into the component.
## Tools
### Tailwind CSS
OJ Lab front uses [Tailwind CSS](https://tailwindcss.com/) as the main CSS framework.
You can control the style of each component in a regular way by simply adding inline Tailwind CSS classes.
With VSCode's [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) extension,
writing Tailwind CSS classes becomes much easier.
You may also need to use `joinClass` utility function to dynamically add classes to a component.
```tsx
<div
className={joinClasses(
"dropdown dropdown-end",
open && "dropdown-open",
)}
onClick={() => {
setOpen(!open);
}}
>
```

:::tip

There are also many other useful utility functions
which can be used in different parts of the project
in the `utils` directory.

:::

## Best Practices

You should avoid using too many inline logic in the TSX code.
Instead, you should extract the logic into hooks or functions.

```tsx
const ProblemActions: React.FC<ActionsProps> = (props) => {
const onClickDelete = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();

props.onClickDelete();
const modal = document.getElementById(
"delete_confirm_modal",
) as HTMLDialogElement;
modal?.showModal();
};

return (
<div className="flex space-x-1">
<button className="btn btn-square btn-ghost btn-sm rounded hover:bg-primary/20">
<PencilIcon className="h-5 w-5 text-primary" />
</button>
<button
className="btn btn-square btn-ghost btn-sm rounded hover:bg-error/20"
onClick={onClickDelete}
>
<TrashIcon className="h-5 w-5 text-error" />
</button>
</div>
);
};
```

In this example, the `onClickDelete` function is extracted from the `onClick` event handler.
138 changes: 138 additions & 0 deletions docs/development/frontend/models-and-apis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Models and APIs

In this document, we will introduce how to use APIs
and how models help us safely constructing OJ Lab front's data transmission by TypeScript.

## Models

In OJ Lab front, models are classified into two types: `ServiceModel` and `ViewModel`.

- `ServiceModel` is expected to be exactly the format of the data returned by the backend APIs
- `ViewModel` on the other hand, provides a more user-friendly format for the front-end components.
They are usually transformed from the `ServiceModel` through `/pipes` module.

```ts
import { formatBytes, formatNanoSeconds } from "@/utils/unit";
import * as JudgeServiceModel from "@/models/service/judge";
import * as JudgeViewModel from "@/models/view/judge";

export const judgeVerdictPipe = (
judgeVerdict: JudgeServiceModel.JudgeVerdict,
): JudgeViewModel.JudgeVerdict => {
let verdict = judgeVerdict.verdict;
let time_usage = formatNanoSeconds(judgeVerdict.time_usage.nanos);
let memory_usage = formatBytes(judgeVerdict.memory_usage_bytes);

return { verdict, time_usage, memory_usage };
};
```

In this example, we define a pipe function to transform the `JudgeServiceModel.JudgeVerdict` to `JudgeViewModel.JudgeVerdict` with `formatBytes` and `formatNanoSeconds` utility functions.

## APIs

For RESTful APIs, we use `axios` to send requests and handle responses.

```ts
export async function getCurrentUser(): Promise<UserServiceModel.UserInfo> {
let res = await axiosClient.get<UserServiceModel.UserInfo>(
"/api/v1/user/current",
);
if (res.status !== 200) {
throw Error("failed to get current user");
}
return res.data;
}
```

`axiosClient` is a utility function that wraps `axios` with some default configurations.

### Use APIs in Hook

In OJ Lab front, we usually hook detailed APIs usage in customized hooks.

```ts
export const useJudge = (uid: string) => {
const [judge, setJudge] = useState<JudgeServiceModel.JudgeInfo>();
useEffect(() => {
JudgeService.getJudge(uid)
.then((res) => {
setJudge(res);
})
.catch((err) => {
console.log(err);
});
}, [uid]);

function getJudge() {
return judge;
}

return { getJudge };
};
```

In the page component, we can use the `useJudge` hook to get the judge data and render the components.
This part of code has been introduced in [Making View](./making-view.md#pages).

### Use APIs in Redux Saga

A more advanced usage is to use APIs in Redux Saga.
This approach is usually used in global state management,
for example, getting the current user information when the app is initialized.

```ts
function* getCurrentUserSaga() {
const user: UserServiceModel.UserInfo = yield call(getCurrentUser);
yield put(setUserInfo(user));
}

const GetCurrentUserSagaPattern = "user/getCurrentUser/saga";

export const getCurrentUserAction: Action = {
type: GetCurrentUserSagaPattern,
};

export function* watchGetUserInfo() {
yield takeEvery(GetCurrentUserSagaPattern, getCurrentUserSaga);
}
```

Then in the layout component, we can dispatch the `getCurrentUserAction` to trigger the saga.

```tsx
const Layout: React.FC<LayoutProps> = (props) => {
let dispatch = useDispatch();

useEffect(() => {
dispatch(getCurrentUserAction);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

// ...
};
```

Finally, you can use the `useSelector` hook to get the user information in any component.

```tsx
const UserMenu: React.FC<UserMenuProps> = (props) => {
// ...
const userInfo = useSelector(userInfoSelector);
const isLogined = userInfo !== undefined;

// ...
};
```

## Best Practices

### Import with Alias

To avoid the confusion and conflicts of the same name modules,
we should use alias to import the models and APIs.

```ts
import * as JudgeServiceModel from "@/models/service/judge";
import * as JudgeService from "@/apis/judge";
```
11 changes: 3 additions & 8 deletions docs/development/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,10 @@ sidebar_position: 1

# OJ Lab Development Intro

:::caution

🤖 This series of documents are partly generated by AI, please forgive me for any mistakes.

:::

:::caution
:::warning

🤖 This series of documents are partly generated by AI,
please forgive me for any mistakes.<br/>
🚧 This document is still under construction, please wait patiently.
You can try switching to the Chinese version.

:::
Loading

0 comments on commit 9babd67

Please sign in to comment.