diff --git a/.vscode/settings.json b/.vscode/settings.json index 9bde8be..574acab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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 } -} +} \ No newline at end of file diff --git a/README.md b/README.md index bee6686..b79ac55 100644 --- a/README.md +++ b/README.md @@ -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 +``` diff --git a/docs/development/platform-services/_category_.json b/docs/development/backend/_category_.json similarity index 67% rename from docs/development/platform-services/_category_.json rename to docs/development/backend/_category_.json index 3c6b591..4f7c726 100644 --- a/docs/development/platform-services/_category_.json +++ b/docs/development/backend/_category_.json @@ -1,5 +1,5 @@ { - "label": "Platform Services", + "label": "Backend", "position": 3, "link": { "type": "generated-index" diff --git a/docs/development/platform-services/form-of-api.md b/docs/development/backend/form-of-api.md similarity index 100% rename from docs/development/platform-services/form-of-api.md rename to docs/development/backend/form-of-api.md diff --git a/docs/development/platform-services/swag-doc-gen.md b/docs/development/backend/swag-doc-gen.md similarity index 100% rename from docs/development/platform-services/swag-doc-gen.md rename to docs/development/backend/swag-doc-gen.md diff --git a/docs/development/dev-environment/_category_.json b/docs/development/environment/_category_.json similarity index 66% rename from docs/development/dev-environment/_category_.json rename to docs/development/environment/_category_.json index f9ea983..b127b58 100644 --- a/docs/development/dev-environment/_category_.json +++ b/docs/development/environment/_category_.json @@ -1,5 +1,5 @@ { - "label": "Develop Environment", + "label": "Environment", "position": 2, "link": { "type": "generated-index" diff --git a/docs/development/dev-environment/k8s-setup-guide.md b/docs/development/environment/k8s-setup-guide.md similarity index 100% rename from docs/development/dev-environment/k8s-setup-guide.md rename to docs/development/environment/k8s-setup-guide.md diff --git a/docs/development/dev-environment/ubuntu-setup-guide.md b/docs/development/environment/ubuntu-setup-guide.md similarity index 100% rename from docs/development/dev-environment/ubuntu-setup-guide.md rename to docs/development/environment/ubuntu-setup-guide.md diff --git a/docs/development/frontend/_category_.json b/docs/development/frontend/_category_.json new file mode 100644 index 0000000..83fc72f --- /dev/null +++ b/docs/development/frontend/_category_.json @@ -0,0 +1,7 @@ +{ + "label": "Frontend", + "position": 3, + "link": { + "type": "generated-index" + } +} diff --git a/docs/development/frontend/making-view.md b/docs/development/frontend/making-view.md new file mode 100644 index 0000000..473c96c --- /dev/null +++ b/docs/development/frontend/making-view.md @@ -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 +
+ {!props.children && } +
+ +// In Router.tsx +}> + } /> + {/* Other routes */} + +``` + +### 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 && ( +
+
+ +
+ +
+ ) + ); +}; +``` + +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; +} + +/** + * @param {string} props.avatarUrl + * @param {Array<{ name: string, href: string }>} props.navigation The name of navigation will be translated. + */ +const UserMenu: React.FC = (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 +
{ + 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 = (props) => { + const onClickDelete = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + + props.onClickDelete(); + const modal = document.getElementById( + "delete_confirm_modal", + ) as HTMLDialogElement; + modal?.showModal(); + }; + + return ( +
+ + +
+ ); +}; +``` + +In this example, the `onClickDelete` function is extracted from the `onClick` event handler. diff --git a/docs/development/frontend/models-and-apis.md b/docs/development/frontend/models-and-apis.md new file mode 100644 index 0000000..160a67e --- /dev/null +++ b/docs/development/frontend/models-and-apis.md @@ -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 { + let res = await axiosClient.get( + "/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(); + 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 = (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 = (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"; +``` diff --git a/docs/development/intro.md b/docs/development/intro.md index 7045d82..f674476 100644 --- a/docs/development/intro.md +++ b/docs/development/intro.md @@ -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.
🚧 This document is still under construction, please wait patiently. -You can try switching to the Chinese version. ::: diff --git a/docs/docusaurus/tutorial-extras/translate-your-site.md b/docs/docusaurus/tutorial-extras/translate-your-site.md index caeaffb..40ec1aa 100644 --- a/docs/docusaurus/tutorial-extras/translate-your-site.md +++ b/docs/docusaurus/tutorial-extras/translate-your-site.md @@ -41,7 +41,7 @@ npm run start -- --locale fr Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated. -:::caution +:::warning In development, you can only use one locale at a same time. diff --git a/i18n/zh-CN/code.json b/i18n/zh-CN/code.json index 64de7d0..5225a05 100644 --- a/i18n/zh-CN/code.json +++ b/i18n/zh-CN/code.json @@ -35,9 +35,9 @@ "message": "信息", "description": "The default label used for the Info admonition (:::info)" }, - "theme.admonition.caution": { + "theme.admonition.warning": { "message": "警告", - "description": "The default label used for the Caution admonition (:::caution)" + "description": "The default label used for the warning admonition (:::warning)" }, "theme.BackToTopButton.buttonAriaLabel": { "message": "回到顶部", diff --git a/i18n/zh-CN/docusaurus-plugin-content-blog/2023-10-6-welcome/index.md b/i18n/zh-CN/docusaurus-plugin-content-blog/2023-10-6-welcome/index.md index fa0e3f0..3015265 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-blog/2023-10-6-welcome/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-blog/2023-10-6-welcome/index.md @@ -37,7 +37,7 @@ OJ Lab一步步地完善,但我还是无法解决一些问题,其中最核 ### 参与 OJ Lab 开发的好处 -:::caution +:::warning 这里的内容是用来吸引开发者的,如果您暂时不打算成为开发者,可以无视这一部分。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-blog/index.md b/i18n/zh-CN/docusaurus-plugin-content-blog/index.md index fa0e3f0..3015265 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-blog/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-blog/index.md @@ -37,7 +37,7 @@ OJ Lab一步步地完善,但我还是无法解决一些问题,其中最核 ### 参与 OJ Lab 开发的好处 -:::caution +:::warning 这里的内容是用来吸引开发者的,如果您暂时不打算成为开发者,可以无视这一部分。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/development/intro.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/development/intro.md index e220f6c..7c784f5 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/development/intro.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/development/intro.md @@ -2,16 +2,11 @@ sidebar_position: 1 --- -# OJ Lab 学习 介绍 +# OJ Lab 学习介绍 -:::caution - -🤖 此系列文档的部分内容由AI生成,请注意甄别。 - -::: - -:::caution +:::warning +🤖 此系列文档的部分内容由AI生成,请注意甄别。
🚧 这篇文档还在施工中,欢迎参与完善。 ::: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/development/platform-services/form-of-api.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/development/platform-services/form-of-api.md deleted file mode 100644 index 0c4d93f..0000000 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/development/platform-services/form-of-api.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -sidebar_position: 1 ---- - -# API的形式 \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/docusaurus/tutorial-extras/translate-your-site.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/docusaurus/tutorial-extras/translate-your-site.md index caeaffb..40ec1aa 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/docusaurus/tutorial-extras/translate-your-site.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/docusaurus/tutorial-extras/translate-your-site.md @@ -41,7 +41,7 @@ npm run start -- --locale fr Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated. -:::caution +:::warning In development, you can only use one locale at a same time.