Skip to content

Commit 53b9836

Browse files
committed
sad
1 parent faf64e9 commit 53b9836

File tree

12 files changed

+294
-38
lines changed

12 files changed

+294
-38
lines changed

packages/danmaku-anywhere/src/background/rpc/RpcManager.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ export class RpcManager {
166166
seasonMapDelete: async (data) => {
167167
return this.titleMappingService.remove(data.key)
168168
},
169+
seasonMapDeleteMany: async (data) => {
170+
return this.titleMappingService.removeMany(data.keys)
171+
},
169172

170173
seasonMapGetAll: async () => {
171174
const seasonMaps = await this.titleMappingService.getAll()

packages/danmaku-anywhere/src/background/services/persistence/TitleMappingService.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ export class TitleMappingService {
3939
await this.db.seasonMap.where({ key }).delete()
4040
}
4141

42+
async removeMany(keys: string[]) {
43+
this.logger.debug('Removing title mappings:', keys)
44+
await this.db.seasonMap.where('key').anyOf(keys).delete()
45+
}
46+
4247
async get(key: string) {
4348
const snapshot = await this.db.seasonMap.get({ key })
4449
return snapshot ? SeasonMap.fromSnapshot(snapshot) : undefined

packages/danmaku-anywhere/src/common/components/DanmakuSelector/components/MountPageToolbar.tsx

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
11
import type { DanmakuSourceType } from '@danmaku-anywhere/danmaku-converter'
2-
import { CheckBox, CheckBoxOutlined } from '@mui/icons-material'
3-
import {
4-
Button,
5-
Checkbox,
6-
Chip,
7-
Collapse,
8-
Stack,
9-
Typography,
10-
} from '@mui/material'
2+
import { Button, Checkbox, Collapse } from '@mui/material'
113
import { useTranslation } from 'react-i18next'
124
import { FilterButton } from '@/common/components/FilterButton'
135
import { TabToolbar } from '@/common/components/layout/TabToolbar'
146
import { DrilldownMenu } from '@/common/components/Menu/DrilldownMenu'
7+
import { MultiselectChip } from '@/common/components/MultiselectChip'
158
import { TypeSelector } from '@/common/components/TypeSelector'
169
import type { DAMenuItemConfig } from '../../Menu/DAMenuItemConfig'
1710

@@ -82,23 +75,7 @@ export const MountPageToolbar = ({
8275
selectedTypes={selectedTypes as DanmakuSourceType[]}
8376
setSelectedType={(types) => onSelectedTypesChange(types)}
8477
/>
85-
<Chip
86-
variant="outlined"
87-
label={
88-
<Stack direction="row" alignItems="center" gap={0.5}>
89-
{multiselect ? (
90-
<CheckBox fontSize="small" />
91-
) : (
92-
<CheckBoxOutlined fontSize="small" />
93-
)}
94-
<Typography variant="body2" fontSize="small">
95-
{t('common.multiselect', 'Multiselect')}
96-
</Typography>
97-
</Stack>
98-
}
99-
onClick={onToggleMultiselect}
100-
color="primary"
101-
/>
78+
<MultiselectChip active={multiselect} onToggle={onToggleMultiselect} />
10279

10380
{onUnmount && (
10481
<Collapse in={isMounted} unmountOnExit orientation="horizontal">

packages/danmaku-anywhere/src/common/components/DraggableList.tsx

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { CSS } from '@dnd-kit/utilities'
2020
import { DragIndicator } from '@mui/icons-material'
2121
import {
22+
Checkbox,
2223
List,
2324
ListItem,
2425
ListItemButton,
@@ -28,7 +29,7 @@ import {
2829
styled,
2930
} from '@mui/material'
3031
import type { ReactNode } from 'react'
31-
import { useEffect, useState } from 'react'
32+
import { useEffect, useMemo, useState } from 'react'
3233
import { createPortal } from 'react-dom'
3334
import { NothingHere } from '@/common/components/NothingHere'
3435
import { ScrollBox } from './layout/ScrollBox'
@@ -64,7 +65,10 @@ interface SortableItemProps<T extends DraggableItem> {
6465
item: T
6566
clickable?: boolean
6667
disableReorder?: boolean
68+
multiselect?: boolean
69+
selected?: boolean
6770
onEdit?: (item: T) => void
71+
onToggleSelect?: (item: T) => void
6872
renderPrimary: (item: T) => ReactNode
6973
renderSecondary?: (item: T) => ReactNode
7074
renderSecondaryAction?: (item: T) => ReactNode
@@ -74,7 +78,10 @@ function SortableItem<T extends DraggableItem>({
7478
item,
7579
clickable = true,
7680
disableReorder,
81+
multiselect,
82+
selected,
7783
onEdit,
84+
onToggleSelect,
7885
renderPrimary,
7986
renderSecondary,
8087
renderSecondaryAction,
@@ -89,7 +96,11 @@ function SortableItem<T extends DraggableItem>({
8996
} = useSortable({ id: item.id })
9097

9198
function handleClick() {
92-
onEdit?.(item)
99+
if (multiselect) {
100+
onToggleSelect?.(item)
101+
} else {
102+
onEdit?.(item)
103+
}
93104
}
94105

95106
const style = {
@@ -103,9 +114,22 @@ function SortableItem<T extends DraggableItem>({
103114
secondary: renderSecondary?.(item),
104115
}
105116

117+
const isClickable = multiselect || clickable
118+
106119
const listItemInner = (
107120
<>
108-
{!disableReorder ? (
121+
{multiselect ? (
122+
<ListItemIcon sx={{ minWidth: 0 }}>
123+
<Checkbox
124+
edge="start"
125+
checked={selected}
126+
disableRipple
127+
tabIndex={-1}
128+
size="small"
129+
sx={{ py: 0 }}
130+
/>
131+
</ListItemIcon>
132+
) : !disableReorder ? (
109133
<DraggableItemIcon {...listeners}>
110134
<DragIndicator />
111135
</DraggableItemIcon>
@@ -120,12 +144,16 @@ function SortableItem<T extends DraggableItem>({
120144
style={style}
121145
key={item.id}
122146
secondaryAction={
123-
renderSecondaryAction ? renderSecondaryAction(item) : null
147+
multiselect
148+
? null
149+
: renderSecondaryAction
150+
? renderSecondaryAction(item)
151+
: null
124152
}
125-
disablePadding={clickable}
153+
disablePadding={isClickable}
126154
{...attributes}
127155
>
128-
{clickable ? (
156+
{isClickable ? (
129157
<ListItemButton onClick={handleClick}>{listItemInner}</ListItemButton>
130158
) : (
131159
listItemInner
@@ -168,6 +196,9 @@ function DragOverlayItem<T extends DraggableItem>({
168196
export interface DraggableListProps<T extends DraggableItem> {
169197
items: T[]
170198
clickable?: boolean | ((item: T) => boolean)
199+
multiselect?: boolean
200+
selectedIds?: string[]
201+
onSelectionChange?: (ids: string[]) => void
171202
onEdit?: (item: T) => void
172203
onReorder?: (sourceIndex: number, destinationIndex: number) => void
173204
renderPrimary: (item: T) => ReactNode
@@ -181,6 +212,9 @@ export interface DraggableListProps<T extends DraggableItem> {
181212
export function DraggableList<T extends DraggableItem>({
182213
items,
183214
clickable,
215+
multiselect,
216+
selectedIds,
217+
onSelectionChange,
184218
overlayPortal,
185219
disableReorder,
186220
onEdit,
@@ -241,6 +275,20 @@ export function DraggableList<T extends DraggableItem>({
241275
setActiveId(null)
242276
}
243277

278+
const selectedIdSet = useMemo(() => new Set(selectedIds ?? []), [selectedIds])
279+
280+
function handleToggleSelect(item: T) {
281+
if (!onSelectionChange) {
282+
return
283+
}
284+
const currentIds = selectedIds ?? []
285+
if (selectedIdSet.has(item.id)) {
286+
onSelectionChange(currentIds.filter((id) => id !== item.id))
287+
} else {
288+
onSelectionChange([...currentIds, item.id])
289+
}
290+
}
291+
244292
function getIsClickable(item: T) {
245293
return typeof clickable === 'function' ? clickable(item) : clickable
246294
}
@@ -290,8 +338,11 @@ export function DraggableList<T extends DraggableItem>({
290338
key={item.id}
291339
clickable={getIsClickable(item)}
292340
item={item}
293-
disableReorder={disableReorder}
341+
disableReorder={multiselect || disableReorder}
342+
multiselect={multiselect}
343+
selected={selectedIdSet.has(item.id)}
294344
onEdit={onEdit}
345+
onToggleSelect={handleToggleSelect}
295346
renderPrimary={renderPrimary}
296347
renderSecondary={renderSecondary}
297348
renderSecondaryAction={renderSecondaryAction}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { CheckBox, CheckBoxOutlined } from '@mui/icons-material'
2+
import { Chip, Stack, Typography } from '@mui/material'
3+
import { useTranslation } from 'react-i18next'
4+
5+
interface MultiselectChipProps {
6+
active: boolean
7+
onToggle: () => void
8+
}
9+
10+
export const MultiselectChip = ({ active, onToggle }: MultiselectChipProps) => {
11+
const { t } = useTranslation()
12+
13+
return (
14+
<Chip
15+
variant="outlined"
16+
label={
17+
<Stack direction="row" alignItems="center" gap={0.5}>
18+
{active ? (
19+
<CheckBox fontSize="small" />
20+
) : (
21+
<CheckBoxOutlined fontSize="small" />
22+
)}
23+
<Typography variant="body2" fontSize="small">
24+
{t('common.multiselect', 'Multiselect')}
25+
</Typography>
26+
</Stack>
27+
}
28+
onClick={onToggle}
29+
color="primary"
30+
/>
31+
)
32+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Button, Collapse, Paper, Stack, Typography } from '@mui/material'
2+
import type { ReactNode } from 'react'
3+
import { useTranslation } from 'react-i18next'
4+
5+
interface SelectionBottomBarProps {
6+
open: boolean
7+
selectionCount: number
8+
onCancel: () => void
9+
children: ReactNode
10+
}
11+
12+
export const SelectionBottomBar = ({
13+
open,
14+
selectionCount,
15+
onCancel,
16+
children,
17+
}: SelectionBottomBarProps) => {
18+
const { t } = useTranslation()
19+
20+
return (
21+
<Collapse in={open} sx={{ flexShrink: 0 }}>
22+
<Paper
23+
elevation={3}
24+
sx={{
25+
p: 1,
26+
bgcolor: 'background.paper',
27+
borderTop: 1,
28+
borderColor: 'divider',
29+
}}
30+
>
31+
<Stack
32+
direction="row"
33+
justifyContent="space-between"
34+
alignItems="center"
35+
>
36+
<Stack direction="row" alignItems="center" gap={1}>
37+
<Button onClick={onCancel} size="small" sx={{ minWidth: 0, px: 1 }}>
38+
{t('common.cancel', 'Cancel')}
39+
</Button>
40+
<Typography variant="caption" color="text.secondary" lineHeight={1}>
41+
{t('mountPage.selectedCount', '{{count}} selected', {
42+
count: selectionCount,
43+
})}
44+
</Typography>
45+
</Stack>
46+
<Stack direction="row" gap={1}>
47+
{children}
48+
</Stack>
49+
</Stack>
50+
</Paper>
51+
</Collapse>
52+
)
53+
}

packages/danmaku-anywhere/src/common/components/TitleMapping/TitleMappingList.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
import { Chip } from '@mui/material'
1+
import { Delete } from '@mui/icons-material'
2+
import { Chip, IconButton } from '@mui/material'
23
import { useMemo } from 'react'
34
import { DraggableList } from '@/common/components/DraggableList'
45
import { ListItemPrimaryStack } from '@/common/components/ListItemPrimaryStack'
56
import type { SeasonMap } from '@/common/seasonMap/SeasonMap'
67

78
type TitleMappingListProps = {
89
mappings: SeasonMap[]
10+
multiselect?: boolean
11+
selectedIds?: string[]
12+
onSelectionChange?: (ids: string[]) => void
913
onSelect: (map: SeasonMap) => void
14+
onDelete: (key: string) => void
1015
}
1116

1217
interface DraggableSeasonMap {
@@ -16,7 +21,11 @@ interface DraggableSeasonMap {
1621

1722
export const TitleMappingList = ({
1823
mappings,
24+
multiselect,
25+
selectedIds,
26+
onSelectionChange,
1927
onSelect,
28+
onDelete,
2029
}: TitleMappingListProps) => {
2130
const items: DraggableSeasonMap[] = useMemo(
2231
() => mappings.map((map) => ({ id: map.key, original: map })),
@@ -27,14 +36,21 @@ export const TitleMappingList = ({
2736
<DraggableList<DraggableSeasonMap>
2837
items={items}
2938
clickable
39+
multiselect={multiselect}
40+
selectedIds={selectedIds}
41+
onSelectionChange={onSelectionChange}
3042
onEdit={(item) => onSelect(item.original)}
3143
disableReorder
3244
renderPrimary={(item) => (
3345
<ListItemPrimaryStack text={item.original.key}>
3446
<Chip label={item.original.seasonIds.length} size="small" />
3547
</ListItemPrimaryStack>
3648
)}
37-
renderSecondaryAction={() => null}
49+
renderSecondaryAction={(item) => (
50+
<IconButton edge="end" size="small" onClick={() => onDelete(item.id)}>
51+
<Delete fontSize="small" />
52+
</IconButton>
53+
)}
3854
/>
3955
)
4056
}

0 commit comments

Comments
 (0)