Skip to content

Commit

Permalink
feat: photos and i18n
Browse files Browse the repository at this point in the history
  • Loading branch information
BIYUEHU committed Aug 14, 2024
1 parent 6ea2083 commit bd9d8f1
Show file tree
Hide file tree
Showing 123 changed files with 2,158 additions and 489 deletions.
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,10 @@ lib
*.log
tsconfig.tsbuildinfo

.env
.env

migrations

packages/core/public/imgs/**/*.png
packages/core/public/imgs/**/*.jpg
packages/core/public/imgs/**/*.jpeg
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
# moehub
<!-- markdownlint-disable -->

Your anime character collection gallery, easily build, freely share.
<div align="center">
<img src="./packages/client/public/favicon.png" alt="logo"/>

# MoeHub

⚡ A modern and universal Meta-Framework to construct other frameworks. ⚡

</div>

## Example

> [👉 There](https://hotaru.icu/moehub.html)
> [👉 There](https://m.hotaru.icu)
## Usage

## Stacks

- Frontend: React, tailwind-css
- Backend: Node.js, Koa.js, Prisma
- Frontend: React, tailwind-css, @kotori-bot/i18n
- Backend: Node.js, Koa.js, Prisma, @kotori-bot/core
- Database: Mysql
- CI/CD: Github Actions
- Code Style: ESLint, Prettier
- Code Style: BiomeJs
- Version Control: Git, GitHub
- Project Management: pnpm workspace
4 changes: 4 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# TODO

- [ ] Static webpage version
- [ ] Supports character collections
16 changes: 12 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@
"license": "GPL-3.0",
"author": "Romi <[email protected]>",
"scripts": {
"core": "pnpm --filter @moehub/core",
"serve": "pnpm core serve",
"core": "pnpm --filter moehub",
"client": "pnpm --filter @moehub/client",
"common": "pnpm --filter @moehub/common",
"dev:core": "nodemon --watch",
"dev:client": "pnpm --filter @moehub/client dev",
"version": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
"version": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
"release": "tsx scripts/release",
"build": "pnpm -r build"
},
"devDependencies": {
"@biomejs/biome": "^1.8.3",
"@types/shelljs": "^0.8.15",
"conventional-changelog-cli": "^4.1.0",
"nodemon": "^3.1.3",
"prettier": "^3.3.0",
"tsup": "^8.1.0",
"tsup": "^8.2.4",
"tsx": "^4.11.2",
"typescript": "5.5.3"
},
Expand All @@ -27,8 +31,12 @@
"node": ">=17.9.0"
},
"nodemonConfig": {
"exec": "pnpm common exec tsc --build && tsx packages/core/src",
"exec": "pnpm common exec tsup && tsx packages/core/src",
"ext": "ts",
"ignore": ["packages/common", "packages/client"]
},
"dependencies": {
"@types/node": "^20.14.15",
"shelljs": "^0.8.5"
}
}
29 changes: 0 additions & 29 deletions packages/client/README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:

- Configure the top-level `parserOptions` property like this:

```js
export default {
// other rules...
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
}
```

- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
Binary file removed packages/client/client.zip
Binary file not shown.
4 changes: 2 additions & 2 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
"type": "module",
"scripts": {
"dev": "vite --host",
"build": "pnpm --filter @moehub/common build && tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"build": "pnpm --filter @moehub/common build && vite build",
"preview": "vite preview"
},
"files": ["package.json"],
"dependencies": {
"@ant-design/icons": "^5.3.7",
"@kotori-bot/core": "1.6.0-rc.1",
Expand Down
Binary file added packages/client/public/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions packages/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@ import { useEffect } from 'react'
import ErrorResult from './components/result/error'
import Loading from './components/Loading'
import { loadSettings } from './store/settingsReducer'
import { getLanguage } from './store/adminReducer'
import store from './store'
import i18n from './i18n'

function App() {
const language = getLanguage(store.getState())

i18n.set(language)

const dispatch = useDispatch()
const { data, error } = useSWR('/api/settings', getSettings)

Expand Down
59 changes: 31 additions & 28 deletions packages/client/src/components/CharacterForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type MoehubDataCharacterHandle = Omit<MoehubDataCharacterSubmit, 'birthda
}

export function handleMoehubDataCharacter(values: MoehubDataCharacterHandle): MoehubDataCharacterSubmit {
console.log(values.color)
// console.log(values.color)
return {
...values,
color: values.color ? (values.color.cleared === false ? values.color.toHex() : '') : undefined,
Expand Down Expand Up @@ -66,7 +66,7 @@ const items = (isDisabled: boolean, tags?: { label: string; value: string }[]) =
label: t`com.characterForm.label.2`,
children: (
<>
<Form.Item name="alias" label={t`com.characterForm.images.alias`}>
<Form.Item name="alias" label={t`com.characterForm.alias`}>
<Select mode="tags" />
</Form.Item>
<Form.Item label={t`com.characterForm.images`}>
Expand All @@ -78,26 +78,26 @@ const items = (isDisabled: boolean, tags?: { label: string; value: string }[]) =
)}
</ListForm>
</Form.Item>
<Form.Item name="description" label="描述">
<Form.Item name="description" label={t`com.characterForm.description`}>
<Input.TextArea rows={3} />
</Form.Item>
<Form.Item name="hitokoto" label="一言">
<Form.Item name="hitokoto" label={t`com.characterForm.hitokoto`}>
<Input />
</Form.Item>
<Form.Item name="birthday" label="生日">
<Form.Item name="birthday" label={t`com.characterForm.birthday`}>
<DatePicker format="MM-DD" />
</Form.Item>
<Form.Item name="comment" label="评价">
<Form.Item name="comment" label={t`com.characterForm.comment`}>
<Input.TextArea rows={3} />
</Form.Item>
<Form.Item name="tags" label="萌点">
<Form.Item name="tags" label={t`com.characterForm.tags`}>
<Select mode="tags" options={tags ?? []} />
</Form.Item>
<Form.Item name="color" label="代表色">
<Form.Item name="color" label={t`com.characterForm.color`}>
<ColorPicker showText allowClear />
</Form.Item>
<Form.Item name="songId" label="主题曲">
<InputNumber placeholder="请输入网易云歌曲 ID,如:2077744392" min="1" />
<Form.Item name="songId" label={t`com.characterForm.songId`}>
<InputNumber placeholder={t`com.characterForm.songId.placeholder`} min="1" />
</Form.Item>
</>
)
Expand All @@ -107,49 +107,52 @@ const items = (isDisabled: boolean, tags?: { label: string; value: string }[]) =
label: t`com.characterForm.label.3`,
children: (
<>
<Form.Item name="voice" label="声优">
<Form.Item name="voice" label={t`com.characterForm.voice`}>
<Input />
</Form.Item>
<Form.Item name="age" label="年龄" rules={[{ type: 'number' }]}>
<Form.Item name="age" label={t`com.characterForm.age`} rules={[{ type: 'number' }]}>
<InputNumber min={1} />
</Form.Item>
<Form.Item name="height" label="身高" rules={[{ type: 'number' }]}>
<Form.Item name="height" label={t`com.characterForm.height`} rules={[{ type: 'number' }]}>
<InputNumber min={1} />
</Form.Item>
<Form.Item name="weight" label="体重" rules={[{ type: 'number' }]}>
<Form.Item name="weight" label={t`com.characterForm.weight`} rules={[{ type: 'number' }]}>
<InputNumber min={1} />
</Form.Item>
<Form.Item name="bust" label="胸围" rules={[{ type: 'number' }]}>
<Form.Item name="bust" label={t`com.characterForm.bust`} rules={[{ type: 'number' }]}>
<InputNumber min={1} />
</Form.Item>
<Form.Item name="waist" label="腰围" rules={[{ type: 'number' }]}>
<Form.Item name="waist" label={t`com.characterForm.waist`} rules={[{ type: 'number' }]}>
<InputNumber min={1} />
</Form.Item>
<Form.Item name="hip" label="臀围" rules={[{ type: 'number' }]}>
<Form.Item name="hip" label={t`com.characterForm.hip`} rules={[{ type: 'number' }]}>
<InputNumber min={1} />
</Form.Item>
<Form.Item name="hairColor" label="发色">
<Form.Item name="hairColor" label={t`com.characterForm.hairColor`}>
<Input />
</Form.Item>
<Form.Item name="eyeColor" label="瞳色">
<Form.Item name="eyeColor" label={t`com.characterForm.eyeColor`}>
<Input />
</Form.Item>
<Form.Item name="bloodType" label="血型">
<Form.Item name="bloodType" label={t`com.characterForm.bloodType`}>
<Radio.Group>
<Radio value="A">A 型</Radio>
<Radio value="B">B 型</Radio>
<Radio value="AB">AB 型</Radio>
<Radio value="O">O 型</Radio>
<Radio value="A">{t`com.characterForm.bloodType.A`}</Radio>
<Radio value="B">{t`com.characterForm.bloodType.B`}</Radio>
<Radio value="AB">{t`com.characterForm.bloodType.AB`}</Radio>
<Radio value="O">{t`com.characterForm.bloodType.O`}</Radio>
</Radio.Group>
</Form.Item>
<Form.Item name="url" label="相关链接">
<Form.Item name="url" label={t`com.characterForm.url`}>
<Select mode="tags" />
</Form.Item>
<Form.Item name="order" label="权重(值越小排列越靠前)" rules={[{ type: 'number' }]}>
<Form.Item name="order" label={t`com.characterForm.order`} rules={[{ type: 'number' }]}>
<InputNumber min={1} />
</Form.Item>
<Form.Item name="hide" label="是否隐藏">
<Switch checkedChildren="隐藏" unCheckedChildren="显示" />
<Form.Item name="hide" label={t`com.characterForm.hide`}>
<Switch
checkedChildren={t`com.characterForm.hide.checked`}
unCheckedChildren={t`com.characterForm.hide.unchecked`}
/>
</Form.Item>
</>
)
Expand Down
18 changes: 14 additions & 4 deletions packages/client/src/components/Layout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Flex, Layout as AntLayout, Avatar } from 'antd'
import { PictureOutlined, PoweroffOutlined } from '@ant-design/icons'
import { Flex, Layout as AntLayout, Avatar, Button } from 'antd'
import { PictureOutlined, PoweroffOutlined, TranslationOutlined } from '@ant-design/icons'
import { Link, useNavigate } from 'react-router-dom'
import styles from './styles.module.css'
import { useEffect } from 'react'
import { getToken } from '@/store/adminReducer'
import { getToken, nextLanguage } from '@/store/adminReducer'
import { getCurrentBackground, getSettings } from '@/store/settingsReducer'
import { useSelector } from 'react-redux'
import { useDispatch, useSelector } from 'react-redux'

interface LayoutProps {
title: string
Expand All @@ -14,6 +14,7 @@ interface LayoutProps {
}

const Layout: React.FC<LayoutProps> = ({ title, outlet, isPrivate }) => {
const dispatch = useDispatch()
const navigate = useNavigate()
const isLogged = useSelector(getToken)
const settings = useSelector(getSettings)
Expand Down Expand Up @@ -42,6 +43,15 @@ const Layout: React.FC<LayoutProps> = ({ title, outlet, isPrivate }) => {
</Link>
</h1>
<div>
<a
// biome-ignore lint:
onClick={() => {
dispatch(nextLanguage())
navigate(0)
}}
>
<Avatar style={{ background: 'none', color: '#eee' }} icon={<TranslationOutlined />} />
</a>
<Link to="/photos">
<Avatar style={{ background: 'none', color: '#eee' }} icon={<PictureOutlined />} />
</Link>
Expand Down
9 changes: 5 additions & 4 deletions packages/client/src/components/result/error.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Result } from 'antd';
import { t } from '@/i18n'
import { Result } from 'antd'

const ErrorResult: React.FC = () => (
<Result status="error" title="获取数据失败" subTitle="请检查网络连接或接口地址是否配置正确" />
);
<Result status="error" title={t`com.resultError.title`} subTitle={t`com.resultError.subTitle`} />
)

export default ErrorResult;
export default ErrorResult
3 changes: 3 additions & 0 deletions packages/client/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
url: '/api'
}
4 changes: 4 additions & 0 deletions packages/client/src/http/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,7 @@ export async function updateLogin(newPassword: string, oldPassword: string): Pro
export async function getImgs(): Promise<string[]> {
return (await http.get('/settings/imgs')).data
}

export async function postEmail() {
return (await http.post('/settings/email')).data
}
3 changes: 0 additions & 3 deletions packages/client/src/http/config.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/client/src/http/http.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios from 'axios'
import { notification } from 'antd'
import config from './config.js'
import { getToken } from '@/store/adminReducer.js'
import config from '@/config'
import { getToken } from '@/store/adminReducer'
import store from '@/store/'

function axiosCreator() {
Expand Down
8 changes: 6 additions & 2 deletions packages/client/src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ i18n.use(locales.ja_JP, 'ja_JP')
i18n.use(locales.zh_TW, 'zh_TW')
i18n.use(locales.zh_CN, 'zh_CN')

i18n.set('zh_CN')

export const t = i18n.t.bind(i18n)

export const f = (locale: string, ...params: string[]) => {
let result = i18n.locale(locale)
for (const [index, value] of params.entries()) result = result.replaceAll(`{${index}}`, value)
return result
}

export default i18n
Loading

0 comments on commit bd9d8f1

Please sign in to comment.