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
3 changes: 3 additions & 0 deletions packages/danmaku-anywhere/src/background/rpc/RpcManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ export class RpcManager {
seasonMapDelete: async (data) => {
return this.titleMappingService.remove(data.key)
},
seasonMapDeleteMany: async (data) => {
return this.titleMappingService.removeMany(data.keys)
},

seasonMapGetAll: async () => {
const seasonMaps = await this.titleMappingService.getAll()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export class TitleMappingService {
await this.db.seasonMap.where({ key }).delete()
}

async removeMany(keys: string[]) {
this.logger.debug('Removing title mappings:', keys)
await this.db.seasonMap.where('key').anyOf(keys).delete()
}

async get(key: string) {
const snapshot = await this.db.seasonMap.get({ key })
return snapshot ? SeasonMap.fromSnapshot(snapshot) : undefined
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import type { DanmakuSourceType } from '@danmaku-anywhere/danmaku-converter'
import { CheckBox, CheckBoxOutlined } from '@mui/icons-material'
import {
Button,
Checkbox,
Chip,
Collapse,
Stack,
Typography,
} from '@mui/material'
import { Button, Checkbox, Collapse } from '@mui/material'
import { useTranslation } from 'react-i18next'
import { FilterButton } from '@/common/components/FilterButton'
import { TabToolbar } from '@/common/components/layout/TabToolbar'
import { DrilldownMenu } from '@/common/components/Menu/DrilldownMenu'
import { MultiselectChip } from '@/common/components/MultiselectChip'
import { TypeSelector } from '@/common/components/TypeSelector'
import type { DAMenuItemConfig } from '../../Menu/DAMenuItemConfig'

Expand Down Expand Up @@ -82,23 +75,7 @@ export const MountPageToolbar = ({
selectedTypes={selectedTypes as DanmakuSourceType[]}
setSelectedType={(types) => onSelectedTypesChange(types)}
/>
<Chip
variant="outlined"
label={
<Stack direction="row" alignItems="center" gap={0.5}>
{multiselect ? (
<CheckBox fontSize="small" />
) : (
<CheckBoxOutlined fontSize="small" />
)}
<Typography variant="body2" fontSize="small">
{t('common.multiselect', 'Multiselect')}
</Typography>
</Stack>
}
onClick={onToggleMultiselect}
color="primary"
/>
<MultiselectChip active={multiselect} onToggle={onToggleMultiselect} />

{onUnmount && (
<Collapse in={isMounted} unmountOnExit orientation="horizontal">
Expand Down
65 changes: 58 additions & 7 deletions packages/danmaku-anywhere/src/common/components/DraggableList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { CSS } from '@dnd-kit/utilities'
import { DragIndicator } from '@mui/icons-material'
import {
Checkbox,
List,
ListItem,
ListItemButton,
Expand All @@ -28,7 +29,7 @@ import {
styled,
} from '@mui/material'
import type { ReactNode } from 'react'
import { useEffect, useState } from 'react'
import { useEffect, useMemo, useState } from 'react'
import { createPortal } from 'react-dom'
import { NothingHere } from '@/common/components/NothingHere'
import { ScrollBox } from './layout/ScrollBox'
Expand Down Expand Up @@ -64,7 +65,10 @@ interface SortableItemProps<T extends DraggableItem> {
item: T
clickable?: boolean
disableReorder?: boolean
multiselect?: boolean
selected?: boolean
onEdit?: (item: T) => void
onToggleSelect?: (item: T) => void
renderPrimary: (item: T) => ReactNode
renderSecondary?: (item: T) => ReactNode
renderSecondaryAction?: (item: T) => ReactNode
Expand All @@ -74,7 +78,10 @@ function SortableItem<T extends DraggableItem>({
item,
clickable = true,
disableReorder,
multiselect,
selected,
onEdit,
onToggleSelect,
renderPrimary,
renderSecondary,
renderSecondaryAction,
Expand All @@ -89,7 +96,11 @@ function SortableItem<T extends DraggableItem>({
} = useSortable({ id: item.id })

function handleClick() {
onEdit?.(item)
if (multiselect) {
onToggleSelect?.(item)
} else {
onEdit?.(item)
}
}

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

const isClickable = multiselect || clickable

const listItemInner = (
<>
{!disableReorder ? (
{multiselect ? (
<ListItemIcon sx={{ minWidth: 0 }}>
<Checkbox
edge="start"
checked={selected}
disableRipple
tabIndex={-1}
size="small"
sx={{ py: 0 }}
/>
</ListItemIcon>
) : !disableReorder ? (
<DraggableItemIcon {...listeners}>
<DragIndicator />
</DraggableItemIcon>
Expand All @@ -120,12 +144,16 @@ function SortableItem<T extends DraggableItem>({
style={style}
key={item.id}
secondaryAction={
renderSecondaryAction ? renderSecondaryAction(item) : null
multiselect
? null
: renderSecondaryAction
? renderSecondaryAction(item)
: null
}
disablePadding={clickable}
disablePadding={isClickable}
{...attributes}
>
{clickable ? (
{isClickable ? (
<ListItemButton onClick={handleClick}>{listItemInner}</ListItemButton>
) : (
listItemInner
Expand Down Expand Up @@ -168,6 +196,9 @@ function DragOverlayItem<T extends DraggableItem>({
export interface DraggableListProps<T extends DraggableItem> {
items: T[]
clickable?: boolean | ((item: T) => boolean)
multiselect?: boolean
selectedIds?: string[]
onSelectionChange?: (ids: string[]) => void
onEdit?: (item: T) => void
onReorder?: (sourceIndex: number, destinationIndex: number) => void
renderPrimary: (item: T) => ReactNode
Expand All @@ -181,6 +212,9 @@ export interface DraggableListProps<T extends DraggableItem> {
export function DraggableList<T extends DraggableItem>({
items,
clickable,
multiselect,
selectedIds,
onSelectionChange,
overlayPortal,
disableReorder,
onEdit,
Expand Down Expand Up @@ -241,6 +275,20 @@ export function DraggableList<T extends DraggableItem>({
setActiveId(null)
}

const selectedIdSet = useMemo(() => new Set(selectedIds ?? []), [selectedIds])

function handleToggleSelect(item: T) {
if (!onSelectionChange) {
return
}
const currentIds = selectedIds ?? []
if (selectedIdSet.has(item.id)) {
onSelectionChange(currentIds.filter((id) => id !== item.id))
} else {
onSelectionChange([...currentIds, item.id])
}
}

function getIsClickable(item: T) {
return typeof clickable === 'function' ? clickable(item) : clickable
}
Expand Down Expand Up @@ -290,8 +338,11 @@ export function DraggableList<T extends DraggableItem>({
key={item.id}
clickable={getIsClickable(item)}
item={item}
disableReorder={disableReorder}
disableReorder={multiselect || disableReorder}
multiselect={multiselect}
selected={selectedIdSet.has(item.id)}
onEdit={onEdit}
onToggleSelect={handleToggleSelect}
renderPrimary={renderPrimary}
renderSecondary={renderSecondary}
renderSecondaryAction={renderSecondaryAction}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { CheckBox, CheckBoxOutlined } from '@mui/icons-material'
import { Chip, Stack, Typography } from '@mui/material'
import { useTranslation } from 'react-i18next'

interface MultiselectChipProps {
active: boolean
onToggle: () => void
}

export const MultiselectChip = ({ active, onToggle }: MultiselectChipProps) => {
const { t } = useTranslation()

return (
<Chip
variant="outlined"
label={
<Stack direction="row" alignItems="center" gap={0.5}>
{active ? (
<CheckBox fontSize="small" />
) : (
<CheckBoxOutlined fontSize="small" />
)}
<Typography variant="body2" fontSize="small">
{t('common.multiselect', 'Multiselect')}
</Typography>
</Stack>
}
onClick={onToggle}
color="primary"
/>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Button, Collapse, Paper, Stack, Typography } from '@mui/material'
import type { ReactNode } from 'react'
import { useTranslation } from 'react-i18next'

interface SelectionBottomBarProps {
open: boolean
selectionCount: number
onCancel: () => void
children: ReactNode
}

export const SelectionBottomBar = ({
open,
selectionCount,
onCancel,
children,
}: SelectionBottomBarProps) => {
const { t } = useTranslation()

return (
<Collapse in={open} sx={{ flexShrink: 0 }}>
<Paper
elevation={3}
sx={{
p: 1,
bgcolor: 'background.paper',
borderTop: 1,
borderColor: 'divider',
}}
>
<Stack
direction="row"
justifyContent="space-between"
alignItems="center"
>
<Stack direction="row" alignItems="center" gap={1}>
<Button onClick={onCancel} size="small" sx={{ minWidth: 0, px: 1 }}>
{t('common.cancel', 'Cancel')}
</Button>
<Typography variant="caption" color="text.secondary" lineHeight={1}>
{t('mountPage.selectedCount', '{{count}} selected', {
count: selectionCount,
})}
</Typography>
</Stack>
<Stack direction="row" gap={1}>
{children}
</Stack>
</Stack>
</Paper>
</Collapse>
)
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { Chip } from '@mui/material'
import { Delete } from '@mui/icons-material'
import { Chip, IconButton, Tooltip } from '@mui/material'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { DraggableList } from '@/common/components/DraggableList'
import { ListItemPrimaryStack } from '@/common/components/ListItemPrimaryStack'
import type { SeasonMap } from '@/common/seasonMap/SeasonMap'

type TitleMappingListProps = {
mappings: SeasonMap[]
multiselect?: boolean
selectedIds?: string[]
onSelectionChange?: (ids: string[]) => void
onSelect: (map: SeasonMap) => void
onDelete: (key: string) => void
}

interface DraggableSeasonMap {
Expand All @@ -16,8 +22,13 @@ interface DraggableSeasonMap {

export const TitleMappingList = ({
mappings,
multiselect,
selectedIds,
onSelectionChange,
onSelect,
onDelete,
}: TitleMappingListProps) => {
const { t } = useTranslation()
const items: DraggableSeasonMap[] = useMemo(
() => mappings.map((map) => ({ id: map.key, original: map })),
[mappings]
Expand All @@ -27,14 +38,23 @@ export const TitleMappingList = ({
<DraggableList<DraggableSeasonMap>
items={items}
clickable
multiselect={multiselect}
selectedIds={selectedIds}
onSelectionChange={onSelectionChange}
onEdit={(item) => onSelect(item.original)}
disableReorder
renderPrimary={(item) => (
<ListItemPrimaryStack text={item.original.key}>
<Chip label={item.original.seasonIds.length} size="small" />
</ListItemPrimaryStack>
)}
renderSecondaryAction={() => null}
renderSecondaryAction={(item) => (
<Tooltip title={t('common.delete', 'Delete')}>
<IconButton edge="end" size="small" onClick={() => onDelete(item.id)}>
<Delete fontSize="small" />
</IconButton>
</Tooltip>
)}
/>
)
}
Loading
Loading