Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
65 changes: 65 additions & 0 deletions .github/workflows/pr-auto-upsert.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Auto Upsert Release PR

on:
# trigger on push to dev branch
push:
branches:
- dev
# trigger on manual workflow_dispatch
workflow_dispatch:

concurrency:
group: pr-upsert-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: write
pull-requests: write

jobs:
pr-upsert:
name: Upsert PR
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
ref: "dev"
# fetch all history so that git log can get all commit messages
fetch-depth: 0

# create a PR from dev to main, with title in form: Release <semver>
# where, <semver> is the next version number to be released, based on the last release in git tag
- name: Upsert PR Content
uses: actions/github-script@v6
with:
github-token: ${{ github.token }}
script: |
const prTitle = "Release to Production"
let body = `> *This PR is automatically created by actions defined in this repository. To see the run log of this action, please click [here](/${{ github.repository }}/actions/runs/${{ github.run_id }})*`
const existedPR = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
head: `${context.repo.owner}:dev`,
base: 'main'
})
if (existedPR.data.length > 0) {
core.info(`PR already exists: ${existedPR.data[0].html_url}. Updating body...`)
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: existedPR.data[0].number,
body: body
})
core.info(`PR updated: ${existedPR.data[0].html_url}`)
return
}
const pr = await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: prTitle,
body: body,
head: context.ref,
base: 'main',
draft: true
})
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 2025-02-17

- 修复了编辑作业时无法修改干员名称的问题 [@Gemini2035](https://github.com/Gemini2035)
- 修复了编辑作业时无法修改动作类型的问题 [@Gemini2035](https://github.com/Gemini2035)
- 添加了是否保存干员过滤器的选项 [@guansss](https://github.com/guansss)
- 使用自定义关卡时支持设置难度 [@guansss](https://github.com/guansss)
- 编辑作业时提醒缺失的干员 [@guansss](https://github.com/guansss)

## 2025-02-06

- 支持作业集内排序 [@hmydgz](https://github.com/hmydgz)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 10 additions & 22 deletions src/components/Operations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,17 @@ import {
} from '@blueprintjs/core'

import { UseOperationsParams } from 'apis/operation'
import { useAtom, useAtomValue } from 'jotai'
import { useAtom } from 'jotai'
import { debounce } from 'lodash-es'
import { ComponentType, useMemo, useState } from 'react'

import { CardTitle } from 'components/CardTitle'
import { OperationList } from 'components/OperationList'
import { OperationSetList } from 'components/OperationSetList'
import { neoLayoutAtom } from 'store/pref'
import {
selectedOperatorQueryAtom,
selectedOperatorsAtom,
} from 'store/selectedOperators'

import { authAtom } from '../store/auth'
import { OperatorSelect } from './OperatorSelect'
import { OperatorFilter } from './OperatorFilter'
import { withSuspensable } from './Suspensable'

export const Operations: ComponentType = withSuspensable(() => {
Expand All @@ -31,14 +27,13 @@ export const Operations: ComponentType = withSuspensable(() => {
limit: 10,
orderBy: 'hot',
})
const [selectedOperators, setSelectedOperators] = useAtom(
selectedOperatorsAtom,
)
const selectedOperatorQuery = useAtomValue(selectedOperatorQueryAtom)
const debouncedSetQueryParams = useMemo(
() => debounce(setQueryParams, 500),
[],
)

const [selectedOperators, setSelectedOperators] = useState<string[]>([])

const [authState] = useAtom(authAtom)
const [neoLayout, setNeoLayout] = useAtom(neoLayoutAtom)
const [listMode, setListMode] = useState<'operation' | 'operationSet'>(
Expand Down Expand Up @@ -104,14 +99,7 @@ export const Operations: ComponentType = withSuspensable(() => {
{listMode === 'operation' && (
<div className="flex flex-wrap items-end">
<div className="flex mr-4">
<FormGroup
helperText={
selectedOperators.length
? '点击干员标签以标记为排除该干员'
: undefined
}
className="max-w-md"
>
<div className="max-w-md">
<InputGroup
className="[&>input]:!rounded-md"
placeholder="标题、描述、神秘代码"
Expand Down Expand Up @@ -145,12 +133,12 @@ export const Operations: ComponentType = withSuspensable(() => {
}
onBlur={() => debouncedSetQueryParams.flush()}
/>
<OperatorSelect
<OperatorFilter
className="mt-2"
operators={selectedOperatorQuery.split(',')}
operators={selectedOperators}
onChange={setSelectedOperators}
/>
</FormGroup>
</div>
</div>
<div className="flex flex-col">
{filterNode}
Expand Down Expand Up @@ -221,7 +209,7 @@ export const Operations: ComponentType = withSuspensable(() => {
{listMode === 'operation' && (
<OperationList
{...queryParams}
operator={selectedOperatorQuery}
operator={selectedOperators.join(',')}
// 按热度排序时列表前几页的变化不会太频繁,可以不刷新第一页,节省点流量
revalidateFirstPage={queryParams.orderBy !== 'hot'}
/>
Expand Down
72 changes: 72 additions & 0 deletions src/components/OperatorFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Checkbox } from '@blueprintjs/core'

import { useAtom } from 'jotai'
import { FC, useEffect } from 'react'

import {
selectedOperatorsAtom,
shouldSaveSelectedOperatorsAtom,
} from '../store/selectedOperators'
import { OperatorSelect } from './OperatorSelect'

interface OperatorSelectProps {
className?: string
operators: string[]
onChange: (operators: string[]) => void
}

export const OperatorFilter: FC<OperatorSelectProps> = ({
className,
operators,
onChange,
}) => {
const [shouldSaveSelectedOperators, setShouldSaveSelectedOperators] = useAtom(
shouldSaveSelectedOperatorsAtom,
)
const [savedSelectedOperators, setSavedSelectedOperators] = useAtom(
selectedOperatorsAtom,
)

useEffect(() => {
// 用户在另一个标签页中修改选择时,savedSelectedOperators 会同步到当前标签页,需要手动更新给 operators
if (shouldSaveSelectedOperators) {
onChange(savedSelectedOperators)
}
}, [shouldSaveSelectedOperators, savedSelectedOperators, onChange])

const handleSelectOperator = (operators: string[]) => {
onChange(operators)

if (shouldSaveSelectedOperators) {
setSavedSelectedOperators(operators)
}
}

const handleShouldSaveSelectedOperators = (shouldSave: boolean) => {
setShouldSaveSelectedOperators(shouldSave)

if (shouldSave) {
setSavedSelectedOperators(operators)
}
}

return (
<div className={className}>
<OperatorSelect operators={operators} onChange={handleSelectOperator} />
{operators.length > 0 && (
<div className="flex justify-between items-baseline text-zinc-500">
<div>点击干员标签以标记为排除该干员</div>
<Checkbox
className="[&>input:checked~.bp4-control-indicator]:bg-zinc-500"
checked={shouldSaveSelectedOperators}
onChange={(e) =>
handleShouldSaveSelectedOperators(e.currentTarget.checked)
}
>
<span className="-ml-2">记住选择</span>
</Checkbox>
</div>
)}
</div>
)
}
16 changes: 12 additions & 4 deletions src/components/editor/OperationEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,18 @@ const DifficultyPicker: FC<{

const stageName = useWatch({ control, name: 'stageName' })
const { data: levels } = useLevels()
const invalid = useMemo(
() => !hasHardMode(levels, stageName),
[levels, stageName],
)
const invalid = useMemo(() => {
// if the stageName is a custom level, we always allow setting difficulty
if (!findLevelByStageName(levels, stageName)) {
return false
}

if (hasHardMode(levels, stageName)) {
return false
}

return true
}, [levels, stageName])

useEffect(() => {
if (invalid && value !== OpDifficulty.UNKNOWN) {
Expand Down
10 changes: 7 additions & 3 deletions src/components/editor/action/EditorActionAdd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,15 @@ export const EditorActionAdd = ({
[type],
)

useEffect(() => {
if (editingAction?.type) {
setValue('type', editingAction.type)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [editingAction?._id, setValue])

useEffect(() => {
if (editingAction) {
if ('type' in editingAction) {
setValue('type', editingAction.type)
}
// 修复切换type的时候,数据丢失的问题
// 原因:因为切换type的时候会触发页面绘制,导致form和对应的item组件丢失绑定,
// 当重置时没办法正常清空item组件内部的值。
Expand Down
67 changes: 52 additions & 15 deletions src/components/editor/operator/EditorPerformer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NonIdealState } from '@blueprintjs/core'
import { Callout, Icon, NonIdealState } from '@blueprintjs/core'
import {
Active,
DndContext,
Expand All @@ -17,12 +17,17 @@ import {
verticalListSortingStrategy,
} from '@dnd-kit/sortable'

import { uniqueId } from 'lodash-es'
import { FC, useEffect, useState } from 'react'
import { Control, UseFieldArrayMove, useFieldArray } from 'react-hook-form'
import { compact, uniq, uniqueId } from 'lodash-es'
import { FC, useEffect, useMemo, useState } from 'react'
import {
Control,
UseFieldArrayMove,
useFieldArray,
useWatch,
} from 'react-hook-form'
import { SetRequired } from 'type-fest'

import type { CopilotDocV1 } from 'models/copilot.schema'
import { CopilotDocV1 } from 'models/copilot.schema'

import { FactItem } from '../../FactItem'
import { Droppable, Sortable } from '../../dnd'
Expand Down Expand Up @@ -52,6 +57,7 @@ const getId = (performer: Operator | Group) => {
export const EditorPerformer: FC<EditorPerformerProps> = ({ control }) => {
const [editMode, setEditMode] = useState<PerformerType>('operator')
const sensors = useSensors(useSensor(PointerSensor))
const actions = useWatch({ control, name: 'actions' })

const {
fields: _operators,
Expand Down Expand Up @@ -79,6 +85,25 @@ export const EditorPerformer: FC<EditorPerformerProps> = ({ control }) => {
const operators: Operator[] = _operators
const groups: Group[] = _groups

const additionalOperatorsFromActions = useMemo(() => {
if (!actions) return []

const additionalOperators = actions.map((action) => {
if (
'name' in action &&
!operators.some(({ name }) => name === action.name) &&
!groups.some(({ opers }) =>
opers?.some(({ name }) => name === action.name),
)
) {
return action.name
}
return undefined
})

return uniq(compact(additionalOperators))
}, [actions, operators, groups])

const [draggingOperator, setDraggingOperator] = useState<Operator>()
const [draggingGroup, setDraggingGroup] = useState<Group>()
const [editingOperator, setEditingOperator] = useState<Operator>()
Expand Down Expand Up @@ -289,7 +314,7 @@ export const EditorPerformer: FC<EditorPerformerProps> = ({ control }) => {
addOperator()
} else {
updateOperator(
operators.findIndex(({ name }) => name === operator.name),
operators.findIndex(({ _id }) => _id === operator._id),
operator,
)
}
Expand Down Expand Up @@ -354,14 +379,16 @@ export const EditorPerformer: FC<EditorPerformerProps> = ({ control }) => {
<>
<div className="flex flex-wrap md:flex-nowrap">
<div className="w-full md:w-1/3 md:mr-8 flex flex-col pb-8">
<EditorSheetTrigger
submitOperator={submitOperator}
submitGroup={submitGroup}
existedOperators={operators}
existedGroups={groups}
removeOperator={removeOperator}
removeGroup={removeGroup}
/>
<div className="mb-2">
<EditorSheetTrigger
submitOperator={submitOperator}
submitGroup={submitGroup}
existedOperators={operators}
existedGroups={groups}
removeOperator={removeOperator}
removeGroup={removeGroup}
/>
</div>
<EditorPerformerAdd
mode={editMode}
operator={editingOperator}
Expand All @@ -377,7 +404,17 @@ export const EditorPerformer: FC<EditorPerformerProps> = ({ control }) => {
/>
</div>
<div className="w-full md:w-2/3 pb-8">
<div className="p-2 -mx-2 relative">
{additionalOperatorsFromActions.length > 0 && (
<Callout
className="flex items-center py-2 mb-2"
icon={null}
intent="primary"
>
<Icon icon="info-sign" className="mr-1" />
未加入干员:{additionalOperatorsFromActions.join(', ')}
</Callout>
)}
<div className="mt-2 relative">
<DndContext
sensors={sensors}
onDragStart={handleDragStart}
Expand Down
Loading