Skip to content

Release to Production #414

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 85 commits into from
May 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
a038770
feat: add localization support for operator names + implementation
Constrat May 12, 2025
130e8c1
style: sheet operator item layout
guansss May 14, 2025
9ece3a6
fix: follow react hooks rules ESLint
Constrat May 14, 2025
b0da401
chore: ota.maa.plus -> api.maa.plus
MistEO May 15, 2025
dc44c84
Merge pull request #407 from Constrat/feat/i18n-operators
guansss May 15, 2025
6bf44c6
feat(editor2): basic implementation
guansss Apr 15, 2025
c29e4f5
feat(editor2): add action editor
guansss Apr 18, 2025
3062a39
feat(editor2): fix invalid atom value when opening in create mode
guansss Apr 18, 2025
8144a4b
feat(editor2): discard react-hook-form and use jotai instead
guansss Apr 20, 2025
161b09b
feat(editor2): complete operator/group controls
guansss Apr 21, 2025
d532b68
feat(editor2): edit action location through level map
guansss Apr 21, 2025
339987a
feat(editor2): resizable panes
guansss Apr 21, 2025
43e671c
feat(editor2): advanced history controls
guansss Apr 21, 2025
c2b592f
feat(editor2): hotkeys
guansss Apr 21, 2025
5b3d7f9
feat(editor2): improve ux
guansss Apr 22, 2025
7c357ee
feat(editor2): source editor
guansss Apr 22, 2025
d626960
style(editor2): better appearance
guansss Apr 22, 2025
7f0addb
feat(editor2): adding action doc
guansss Apr 23, 2025
29d05b6
feat(editor2): replace atomFamily with inner atoms
guansss Apr 24, 2025
990afa8
feat(editor2): improve ux
guansss Apr 24, 2025
66027e1
feat(editor2): auto save
guansss Apr 24, 2025
65e6db0
feat(editor2): support mobile
guansss Apr 25, 2025
9a6fa1e
fix(editor2): fix warning about nested li tags
guansss Apr 25, 2025
7bebfd0
feat(editor2): validation and uploading
guansss Apr 28, 2025
4ce7080
feat(editor2): better submit handling
guansss Apr 28, 2025
e7dbb7d
feat(editor2): reset state after leaving editor page
guansss Apr 28, 2025
387992a
feat(editor2): editing operator's skill usage
guansss Apr 28, 2025
ca4af7e
perf(editor2): prevent unnecessary rerenders when switching active gr…
guansss Apr 29, 2025
92bf8aa
feat(editor2): favorite groups
guansss Apr 29, 2025
ca46b21
feat(editor2): CN translations for zod
guansss Apr 29, 2025
ae9baec
feat(editor2): add link for editor
guansss Apr 29, 2025
9cf0909
feat(editor2): dynamically import icons
guansss Apr 29, 2025
5a6b9fd
revert(editor2): revert unnecessary changes
guansss Apr 30, 2025
78f9ee4
fix(editor2): unable to submit
guansss Apr 30, 2025
d3711cc
feat(editor2): adding favorite operators
guansss Apr 30, 2025
53842d7
feat(editor2): operation versioning
guansss Apr 30, 2025
db153a4
fix(editor2): fix conflicts with i18n
guansss May 10, 2025
a3b17ed
fix(editor2): fix incompatibility with i18n
guansss May 10, 2025
d8971d8
refactor(editor2): simplify history API
guansss May 10, 2025
cf2167f
fix(editor2): skillUsage type mismatch
guansss May 10, 2025
7756587
refactor(editor2): rename _id to id
guansss May 11, 2025
c35411e
feat(editor2): merge two tabs into a single view
guansss May 11, 2025
fbf1a2b
feat(editor2): toggling selector panel
guansss May 11, 2025
734df53
feat(editor2): settings
guansss May 11, 2025
d6b744d
feat(editor2): i18n
guansss May 13, 2025
a4e056c
fix(editor2): mismatching _id vs id
guansss May 14, 2025
8a3cd77
fix(editor2): wrong i18n key
guansss May 14, 2025
99047af
fix(editor2): missing fields in schema
guansss May 14, 2025
bf76b98
fix(editor2): i18nDefer not working for lazy-loaded pages
guansss May 14, 2025
2ae148c
fix(editor2): support move camera action
guansss May 14, 2025
8ec8fc5
refactor(editor2): better history management
guansss May 14, 2025
d1775eb
feat(editor2): insert soft checkpoint on blur of text inputs
guansss May 14, 2025
a691b78
feat(editor2): better appearance
guansss May 14, 2025
6b7c9b8
feat: display operator requirements in operation viewer
guansss May 15, 2025
abe9fc1
feat(editor2): remove skills from operators.json and rename equips to…
guansss May 15, 2025
9910bcd
feat(editor2): remember modified skill levels
guansss May 15, 2025
597c2ee
feat(editor2): i18n operator names
guansss May 15, 2025
b10d935
fix(editor2): editor history not being reset
guansss May 15, 2025
ba92c07
feat(editor2): lazy init level map
guansss May 15, 2025
c045ea9
feat(editor2): use title as fallback for details
guansss May 15, 2025
80c6cbd
i18n: translation consistency
Constrat May 18, 2025
6820613
chore: remove unused json data
guansss May 15, 2025
cbb398f
feat(editor2): change module IDs from 'A' to 'α' and 'D' to 'Δ'
guansss May 19, 2025
c101d1b
fix(editor2): incorrect translation of rear_delay
guansss May 19, 2025
104f238
feat: make action accent colors more distinguishable
guansss May 19, 2025
d7d2b6f
fix(editor2): incorrect behavior on entity creation
guansss May 19, 2025
e675196
feat(editor2): add Beta mark
guansss May 19, 2025
58ba1c1
fix: condition to remove double `|` when activity is missing
Constrat May 19, 2025
6a5edd7
fix: remove double `|` when activity is missing (#421)
guansss May 19, 2025
3f2a378
fix(editor2): incorrect elite 1 level caps for rarity 4 and 6
guansss May 19, 2025
124c0a9
feat(editor2): remove hotkey for adding action
guansss May 19, 2025
f18da25
fix: nested <li> in editor
guansss May 19, 2025
493d0b4
feat: use i18n for subprofession in quick editor
Constrat May 20, 2025
43947f9
ci: scripts:update to default to capitalized subprofession id when en…
Constrat May 20, 2025
9067df6
feat: translate-archetypes (#422)
guansss May 21, 2025
d49f57d
fix: untranslated headers in action dropdown
guansss May 21, 2025
3fef9b1
fix: link translations not updated when changing language
guansss May 21, 2025
f26f273
Merge branch 'dev' into editor2
guansss May 22, 2025
50e129c
feat(editor2): better appearance for card shadows
guansss May 21, 2025
8d42d20
fix(editor2): overwrite current checkpoint with soft checkpoint only …
guansss May 21, 2025
cf4dc63
fix(editor2): unable to change action's doc color when doc is empty
guansss May 21, 2025
1160c1e
fix(editor2): skill level input should have resize cursor when focused
guansss May 22, 2025
e276421
fix(editor2): incorrect size of mastery icon on Firefox
guansss May 23, 2025
1df46a5
Editor v2 (#402)
guansss May 24, 2025
32b6862
docs: update changelog
guansss May 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
## 2025-05-25

- 添加干员和职业的英文名称 [@Constrat](https://github.com/Constrat)
- 修复了作业列表里的自定义关卡显示不正常的问题 [@Constrat](https://github.com/Constrat)
- 修复了部分文字的翻译显示不正常的问题 [@guansss](https://github.com/guansss)
- 添加编辑器v2 [@guansss](https://github.com/guansss)
- 优化首页搜索逻辑 [@ChingCdesu](https://github.com/ChingCdesu) [@Aliothmoon](https://github.com/Aliothmoon) [@dragove](https://github.com/dragove)

## 2025-05-11

- 添加 i18n 及英文翻译 [@Constrat](https://github.com/Constrat) [@guansss](https://github.com/guansss)
- 添加国际化及英文翻译 [@Constrat](https://github.com/Constrat) [@guansss](https://github.com/guansss)

## 2025-05-02

Expand Down
17 changes: 11 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
"@blueprintjs/core": "^4.15.1",
"@blueprintjs/popover2": "^1.12.1",
"@blueprintjs/select": "^4.8.18",
"@dnd-kit/core": "^6.0.5",
"@dnd-kit/sortable": "^7.0.1",
"@dnd-kit/utilities": "^3.2.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@iconify/icons-simple-icons": "^1.2.22",
"@iconify/react": "^3.2.2",
"@sentry/react": "^7.27.0",
Expand All @@ -40,7 +40,10 @@
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"fuse.js": "^6.6.2",
"immer": "^10.1.1",
"jotai": "^2.7.0",
"jotai-devtools": "^0.11.0",
"jotai-immer": "^0.4.1",
"linkify-react": "^3.0.4",
"linkifyjs": "^3.0.5",
"lodash-es": "^4.17.21",
Expand All @@ -54,6 +57,7 @@
"react-hook-form": "^7.33.1",
"react-markdown": "^8.0.5",
"react-rating": "^2.0.5",
"react-resizable-panels": "^2.1.8",
"react-rnd": "^10.4.1",
"react-router-dom": "6",
"react-spring": "^9.4.5",
Expand All @@ -62,9 +66,10 @@
"remark-gfm": "^3.0.1",
"snakecase-keys": "^5.4.4",
"swr": "^2.2.5",
"type-fest": "^4.10.2",
"type-fest": "^4.40.1",
"unfetch": "^4.2.0",
"vite-tsconfig-paths": "^3.5.0"
"vite-tsconfig-paths": "^3.5.0",
"zod": "^4.0.0-beta.20250424T163858"
},
"devDependencies": {
"@hookform/devtools": "^4.1.1",
Expand All @@ -75,7 +80,7 @@
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@typescript-eslint/parser": "^7.0.1",
"@vitejs/plugin-react": "^1.3.0",
"@vitejs/plugin-react": "^4.4.0",
"autoprefixer": "^10.4.7",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1",
Expand Down
64 changes: 51 additions & 13 deletions scripts/shared.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { access } from 'fs/promises'
import { uniq, uniqBy } from 'lodash-es'
import { capitalize, uniq, uniqBy } from 'lodash-es'
import fetch from 'node-fetch'
import { pinyin } from 'pinyin'
import simplebig from 'simplebig'

type Profession = { id: string; name: string }
type Profession = { id: string; name: string; name_en?: string }
type Professions = (Profession & { sub: Profession[] })[]

export async function fileExists(file: string) {
Expand Down Expand Up @@ -48,10 +48,14 @@ function transformOperatorName(name: string) {
}
}

const CHARACTER_TABLE_JSON_URL =
const CHARACTER_TABLE_JSON_URL_CN =
'https://raw.githubusercontent.com/Kengxxiao/ArknightsGameData/master/zh_CN/gamedata/excel/character_table.json'
const UNIEQUIP_TABLE_JSON_URL =
const UNIEQUIP_TABLE_JSON_URL_CN =
'https://raw.githubusercontent.com/Kengxxiao/ArknightsGameData/master/zh_CN/gamedata/excel/uniequip_table.json'
const CHARACTER_TABLE_JSON_URL_EN =
'https://raw.githubusercontent.com/Kengxxiao/ArknightsGameData_YoStar/main/en_US/gamedata/excel/character_table.json'
const UNIEQUIP_TABLE_JSON_URL_EN =
'https://raw.githubusercontent.com/Kengxxiao/ArknightsGameData_YoStar/main/en_US/gamedata/excel/uniequip_table.json'

const CHARACTER_BLOCKLIST = [
'char_512_aprot', // 暮落(集成战略):It's just not gonna be there.
Expand All @@ -74,51 +78,85 @@ async function json(url: string) {
}

export async function getOperators() {
const [charTable, uniequipTable] = await Promise.all([
json(CHARACTER_TABLE_JSON_URL),
json(UNIEQUIP_TABLE_JSON_URL),
])
const [charTableCN, uniequipTableCN, charTableEN, uniequipTableEN] =
await Promise.all([
json(CHARACTER_TABLE_JSON_URL_CN),
json(UNIEQUIP_TABLE_JSON_URL_CN),
json(CHARACTER_TABLE_JSON_URL_EN),
json(UNIEQUIP_TABLE_JSON_URL_EN),
])

const { subProfDict } = uniequipTable
const { subProfDict: subProfDictCN, equipDict } = uniequipTableCN
const { subProfDict: subProfDictEN } = uniequipTableEN
const equipsByOperatorId = Object.values(equipDict).reduce(
(acc: Record<string, any[]>, equip: any) => {
acc[equip.charId] ||= []
acc[equip.charId].push(equip)
return acc
},
{},
)

const opIds = Object.keys(charTable)
const opIds = Object.keys(charTableCN)
const professions: Professions = []
const result = uniqBy(
opIds.flatMap((id) => {
const op = charTable[id]
const op = charTableCN[id]
const enName = charTableEN[id]?.name || op.appellation || op.name

if (['TRAP'].includes(op.profession)) return []

if (!['TOKEN'].includes(op.profession)) {
const prof = professions.find((p) => p.id === op.profession)
if (!prof) {
const enSubProfName =
subProfDictEN?.[op.subProfessionId]?.subProfessionName ||
capitalize(op.subProfessionId)

professions.push({
id: op.profession,
name: PROFESSION_NAMES[op.profession],
name_en:
op.profession.charAt(0) + op.profession.slice(1).toLowerCase(),
sub: [
{
id: op.subProfessionId,
name: subProfDict[op.subProfessionId].subProfessionName,
name: subProfDictCN[op.subProfessionId].subProfessionName,
name_en: enSubProfName,
},
],
})
} else if (!prof.sub.find((p) => p.id === op.subProfessionId)) {
const enSubProfName =
subProfDictEN?.[op.subProfessionId]?.subProfessionName ||
capitalize(op.subProfessionId)

prof.sub.push({
id: op.subProfessionId,
name: subProfDict[op.subProfessionId].subProfessionName,
name: subProfDictCN[op.subProfessionId].subProfessionName,
name_en: enSubProfName,
})
}
}
const modules = equipsByOperatorId[id]
?.sort((a, b) => a.charEquipOrder - b.charEquipOrder)
.map(({ typeName1, typeName2 }) => {
return typeName1 === 'ORIGINAL' ? '' : typeName2
})
.map((m) => (m === 'A' ? 'α' : m === 'D' ? 'Δ' : m))
return [
{
id: id,
prof: op.profession,
subProf: op.subProfessionId,
name_en: enName,
...transformOperatorName(op.name),
rarity:
op.subProfessionId === 'notchar1'
? 0
: Number(op.rarity?.split('TIER_').join('') || 0),
alt_name: op.appellation,
modules,
},
]
}),
Expand Down
2 changes: 1 addition & 1 deletion src/apis/announcement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const isMock = process.env.NODE_ENV === 'development'

const announcementURL = isMock
? mockFile
: 'https://ota.maa.plus/MaaAssistantArknights/api/announcements/copilot.md'
: 'https://api.maa.plus/MaaAssistantArknights/api/announcements/copilot.md'

export const announcementBaseURL = isMock
? location.href
Expand Down
12 changes: 6 additions & 6 deletions src/apis/operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
CopilotInfoStatusEnum,
QueriesCopilotRequest,
} from 'maa-copilot-client'
import useSWR from 'swr'
import useSWR, { SWRConfiguration } from 'swr'
import useSWRInfinite from 'swr/infinite'

import { toCopilotOperation } from 'models/converter'
Expand Down Expand Up @@ -161,16 +161,15 @@ export function useRefreshOperations() {
return () => refresh((key) => key.includes('operations'))
}

interface UseOperationParams {
interface UseOperationParams extends SWRConfiguration {
id?: number
suspense?: boolean
}

export function useOperation({ id, suspense }: UseOperationParams) {
export function useOperation({ id, ...config }: UseOperationParams) {
return useSWR(
id ? ['operation', id] : null,
() => getOperation({ id: id! }),
{ suspense },
config,
)
}

Expand All @@ -196,7 +195,8 @@ export async function createOperation(req: {
content: string
status: CopilotInfoStatusEnum
}) {
await new OperationApi().uploadCopilot({ copilotCUDRequest: req })
return (await new OperationApi().uploadCopilot({ copilotCUDRequest: req }))
.data
}

export async function updateOperation(req: {
Expand Down
Binary file added src/assets/icons/elite_0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/icons/elite_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/icons/elite_2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion src/components/ActionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ export const ActionCard: FC<ActionCardProps> = ({
{action.preDelay ? formatDuration(action.preDelay) : '-'}
</InlineCondition>
<InlineCondition title={t.components.ActionCard.rear_delay}>
{action.rearDelay ? formatDuration(action.rearDelay) : '-'}
{action.rearDelay || action.postDelay
? formatDuration(action.rearDelay || action.postDelay!)
: '-'}
</InlineCondition>
</div>
</Card>
Expand Down
37 changes: 37 additions & 0 deletions src/components/MasteryIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { FC } from 'react'

interface MasteryIconProps extends React.SVGProps<SVGSVGElement> {
mastery: number
mainClassName?: string
subClassName?: string
}

export const MasteryIcon: FC<MasteryIconProps> = ({
mastery,
mainClassName = 'fill-current',
subClassName = 'fill-gray-300 dark:fill-gray-600',
...props
}) => {
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" {...props}>
<circle
className={mastery >= 1 ? mainClassName : subClassName}
cx="50"
cy="27"
r="22"
/>
<circle
className={mastery >= 2 ? mainClassName : subClassName}
cx="75"
cy="70"
r="22"
/>
<circle
className={mastery >= 3 ? mainClassName : subClassName}
cx="25"
cy="70"
r="22"
/>
</svg>
)
}
12 changes: 9 additions & 3 deletions src/components/OperationCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Button, Card, Elevation, H4, H5, Icon, Tag } from '@blueprintjs/core'
import { Tooltip2 } from '@blueprintjs/popover2'

import clsx from 'clsx'
import { useAtomValue } from 'jotai'
import { CopilotInfoStatusEnum } from 'maa-copilot-client'
import { copyShortCode, handleLazyDownloadJSON } from 'services/operation'

Expand All @@ -11,8 +12,9 @@ import { OperationRating } from 'components/viewer/OperationRating'
import { OpDifficulty, Operation } from 'models/operation'

import { useLevels } from '../apis/level'
import { useTranslation } from '../i18n/i18n'
import { languageAtom, useTranslation } from '../i18n/i18n'
import { createCustomLevel, findLevelByStageName } from '../models/level'
import { getLocalizedOperatorName } from '../models/operator'
import { Paragraphs } from './Paragraphs'
import { ReLinkDiv } from './ReLinkDiv'
import { UserName } from './UserName'
Expand Down Expand Up @@ -245,13 +247,14 @@ export const OperationCard = ({ operation }: { operation: Operation }) => {

const OperatorTags = ({ operation }: { operation: Operation }) => {
const t = useTranslation()
const language = useAtomValue(languageAtom)
const { opers, groups } = operation.parsedContent

return opers?.length || groups?.length ? (
<div>
{opers?.map(({ name, skill }, index) => (
<Tag key={index} className="mr-2 last:mr-0 mb-1 last:mb-0">
{`${name} ${skill ?? 1}`}
{`${getLocalizedOperatorName(name, language)} ${skill ?? 1}`}
</Tag>
))}
{groups?.map(({ name, opers }, index) => (
Expand All @@ -261,7 +264,10 @@ const OperatorTags = ({ operation }: { operation: Operation }) => {
placement="top"
content={
opers
?.map(({ name, skill }) => `${name} ${skill ?? 1}`)
?.map(
({ name, skill }) =>
`${getLocalizedOperatorName(name, language)} ${skill ?? 1}`,
)
.join(', ') || t.components.OperationCard.no_operators
}
>
Expand Down
15 changes: 9 additions & 6 deletions src/components/OperatorFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import {
} from '@blueprintjs/core'

import clsx from 'clsx'
import { getDefaultStore, useAtom } from 'jotai'
import { getDefaultStore, useAtom, useAtomValue } from 'jotai'
import { compact } from 'lodash-es'
import { FC, useEffect, useMemo, useState } from 'react'

import { useTranslation } from '../i18n/i18n'
import { languageAtom, useTranslation } from '../i18n/i18n'
import { OPERATORS } from '../models/operator'
import {
DEFAULT_OPERATOR_FILTER,
Expand Down Expand Up @@ -53,6 +53,7 @@ export const OperatorFilter: FC<OperatorFilterProps> = ({
onChange,
}) => {
const t = useTranslation()
const language = useAtomValue(languageAtom)
const [savedFilter, setSavedFilter] = useAtom(operatorFilterAtom)
const [dialogOpen, setDialogOpen] = useState(false)
const [editingFilter, setEditingFilter] = useState<typeof savedFilter>(filter)
Expand Down Expand Up @@ -137,27 +138,29 @@ export const OperatorFilter: FC<OperatorFilterProps> = ({
!filter.enabled && 'opacity-30',
)}
>
{includedOperators.map(({ id, name, rarity }) => (
{includedOperators.map(({ id, name, name_en, rarity }) => (
<Tag minimal key={id} className="py-0 pl-0" intent="primary">
<div className="flex items-center gap-1 text-sm">
<OperatorAvatar
className="w-8 h-8"
id={id}
rarity={rarity}
/>
&nbsp;{name}&nbsp;
&nbsp;{language === 'en' ? name_en : name}
&nbsp;
</div>
</Tag>
))}
{excludedOperators.map(({ id, name, rarity }) => (
{excludedOperators.map(({ id, name, name_en, rarity }) => (
<Tag minimal key={id} className="py-0 pl-0" intent="danger">
<div className="flex items-center gap-1 text-sm line-through">
<OperatorAvatar
className="w-8 h-8"
id={id}
rarity={rarity}
/>
&nbsp;{name}&nbsp; {/* 两边加空格让删除线更显眼一些 */}
&nbsp;{language === 'en' ? name_en : name}
&nbsp; {/* 两边加空格让删除线更显眼一些 */}
</div>
</Tag>
))}
Expand Down
Loading