Improve tdata import dialog
Some checks failed
Docs / build (push) Has been cancelled

This commit is contained in:
Полина 2025-01-24 20:18:16 +03:00
parent 64e2438066
commit 3f4dcf2c4f
5 changed files with 315 additions and 36 deletions

View file

@ -18,6 +18,7 @@
"@mtcute/convert": "^0.19.8",
"@mtcute/web": "^0.19.5",
"@nanostores/persistent": "^0.10.2",
"@tanstack/solid-table": "^8.20.5",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"filesize": "^10.1.6",

View file

@ -1,12 +1,12 @@
import type { Tdata } from '@mtcute/convert'
import { hex } from '@fuman/utils'
import { workerInvoke } from 'mtcute-repl-worker/client'
import { createEffect, createSignal, For, on, Show } from 'solid-js'
import { createEffect, createSignal, on, Show } from 'solid-js'
import { Button } from '../../../../lib/components/ui/button.tsx'
import { Checkbox, CheckboxControl, CheckboxLabel } from '../../../../lib/components/ui/checkbox.tsx'
import { Dialog, DialogContent, DialogDescription, DialogHeader } from '../../../../lib/components/ui/dialog.tsx'
import { Spinner } from '../../../../lib/components/ui/spinner.tsx'
import { $accounts } from '../../../../store/accounts.ts'
import { TdataDataTable } from './TdataTable.tsx'
interface TdataAccount {
telegramId: number
@ -191,41 +191,28 @@ export function TdataImportDialog(props: {
</div>
)}
>
<For each={accounts()}>
{account => (
<Checkbox
class="ml-1 flex flex-row items-center gap-2"
checked={account.toImport}
onChange={checked => setAccounts(
accounts().map(it => it.index === account.index ? {
...it,
toImport: checked,
} : it),
)}
disabled={accountExists(account.telegramId)}
>
<CheckboxControl />
<CheckboxLabel class="flex items-center gap-1 text-sm">
<div class="text-foreground">
ID
{' '}
{account.telegramId}
</div>
<div class="text-xs text-muted-foreground">
(DC
{' '}
{account.dcId}
, index
{' '}
{account.index}
)
</div>
</CheckboxLabel>
</Checkbox>
)}
</For>
<TdataDataTable
data={() => accounts().map(e => ({
index: e.index,
dcId: e.dcId,
telegramId: e.telegramId,
disabled: accountExists(e.telegramId) ?? false,
}))}
onChange={(s) => {
const nextAccounts = accounts().map(e => ({ ...e, toImport: false }))
for (const [idx, v] of Object.entries(s)) {
const acc = nextAccounts.find(e => e.index === Number(idx))
if (acc === undefined) continue
if (accountExists(acc.telegramId)) continue
acc.toImport = v
}
setAccounts(nextAccounts)
}}
/>
{error() && (
<div class="mt-2 text-sm text-error-foreground">
<div class="text-error-foreground mt-2 text-sm">
{error()}
</div>
)}

View file

@ -0,0 +1,177 @@
import type { ColumnDef, RowSelectionState } from '@tanstack/solid-table'
import type { Accessor } from 'solid-js'
import { createSolidTable, flexRender, getCoreRowModel } from '@tanstack/solid-table'
import { createSignal, For, Show, splitProps } from 'solid-js'
import { Checkbox, CheckboxControl } from '../../../../lib/components/ui/checkbox'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '../../../../lib/components/ui/table'
interface TdataDataTableProps {
data: Accessor<TdataAccountModel[] | undefined>
onChange?: (selection: RowSelectionState) => void
}
export interface TdataAccountModel {
index: number
dcId: number
telegramId: number
disabled: boolean
}
export const columns: ColumnDef<TdataAccountModel>[] = [
{
id: 'select',
header: props => (
<Checkbox
class="pl-1"
indeterminate={props.table.getIsSomePageRowsSelected()}
checked={(() => {
return props.table.getCoreRowModel?.().rows.reduce((acc, e) => acc && (e.getIsSelected() || e.original.disabled), true)
})()}
onChange={(value) => {
props.table.toggleAllPageRowsSelected(!!value)
props.table.setRowSelection((selection) => {
const next = { ...selection }
for (const row of props.table.getCoreRowModel().rows) {
if (row.original.disabled) {
next[row.original.index] = true
}
}
return next
})
}}
aria-label="Select all"
>
<CheckboxControl />
</Checkbox>
),
cell: props => (
<Checkbox
class="pl-1"
checked={props.row.original.disabled ? true : props.row.getIsSelected()}
onChange={(value) => {
props.row.toggleSelected(!!value)
props.table.setRowSelection((selection) => {
const next = { ...selection }
for (const row of props.table.getCoreRowModel().rows) {
if (row.original.disabled) {
next[row.original.index] = true
}
}
return next
})
}}
disabled={props.row.original.disabled}
aria-label="Select row"
>
<CheckboxControl />
</Checkbox>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: 'index',
header: 'Index',
},
{
accessorKey: 'dcId',
header: 'DC Id',
},
{
accessorKey: 'telegramId',
header: 'Telegram Id',
},
]
export function TdataDataTable(props: TdataDataTableProps) {
const [local] = splitProps(props, ['data', 'onChange'])
const initialSelection: RowSelectionState = {}
// eslint-disable-next-line solid/reactivity
for (const [idx, _] of (local.data?.() ?? []).entries()) {
initialSelection[idx] = true
}
const [rowSelection, setRowSelection] = createSignal(initialSelection)
const table = createSolidTable({
get data() {
return local.data() || []
},
columns,
onRowSelectionChange: (s) => {
const selection = setRowSelection(s)
local.onChange?.(selection)
},
getRowId: (e) => {
return e.index.toString()
},
state: {
get rowSelection() {
return rowSelection()
},
},
getCoreRowModel: getCoreRowModel(),
})
return (
<div class="rounded-md border">
<Table>
<TableHeader>
<For each={table.getHeaderGroups()}>
{headerGroup => (
<TableRow>
<For each={headerGroup.headers}>
{(header) => {
return (
<TableHead>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
)
}}
</For>
</TableRow>
)}
</For>
</TableHeader>
<TableBody>
<Show
when={table.getRowModel().rows?.length}
fallback={(
<TableRow>
<TableCell colSpan={columns.length} class="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
>
<For each={table.getRowModel().rows}>
{row => (
<TableRow data-state={row.getIsSelected() && 'selected'}>
<For each={row.getVisibleCells()}>
{cell => (
<TableCell>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
)}
</For>
</TableRow>
)}
</For>
</Show>
</TableBody>
</Table>
</div>
)
}

View file

@ -0,0 +1,94 @@
import { type ComponentProps, splitProps } from 'solid-js'
import { cn } from '../../utils.ts'
export function Table(props: ComponentProps<'table'>) {
const [local, rest] = splitProps(props, ['class'])
return (
<div class="w-full overflow-auto">
<table
class={cn('w-full caption-bottom text-sm', local.class)}
{...rest}
/>
</div>
)
}
export function TableHeader(props: ComponentProps<'thead'>) {
const [local, rest] = splitProps(props, ['class'])
return <thead class={cn('[&_tr]:border-b', local.class)} {...rest} />
}
export function TableBody(props: ComponentProps<'tbody'>) {
const [local, rest] = splitProps(props, ['class'])
return (
<tbody class={cn('[&_tr:last-child]:border-0', local.class)} {...rest} />
)
}
export function TableFooter(props: ComponentProps<'tfoot'>) {
const [local, rest] = splitProps(props, ['class'])
return (
<tbody
class={cn('bg-primary font-medium text-primary-foreground', local.class)}
{...rest}
/>
)
}
export function TableRow(props: ComponentProps<'tr'>) {
const [local, rest] = splitProps(props, ['class'])
return (
<tr
class={cn(
'border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',
local.class,
)}
{...rest}
/>
)
}
export function TableHead(props: ComponentProps<'th'>) {
const [local, rest] = splitProps(props, ['class'])
return (
<th
class={cn(
'h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
local.class,
)}
{...rest}
/>
)
}
export function TableCell(props: ComponentProps<'td'>) {
const [local, rest] = splitProps(props, ['class'])
return (
<td
class={cn(
'p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
local.class,
)}
{...rest}
/>
)
}
export function TableCaption(props: ComponentProps<'caption'>) {
const [local, rest] = splitProps(props, ['class'])
return (
<caption
class={cn('mt-4 text-sm text-muted-foreground', local.class)}
{...rest}
/>
)
}

View file

@ -88,6 +88,9 @@ importers:
'@nanostores/persistent':
specifier: ^0.10.2
version: 0.10.2(nanostores@0.11.3)
'@tanstack/solid-table':
specifier: ^8.20.5
version: 8.20.5(solid-js@1.9.4)
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@ -1168,6 +1171,16 @@ packages:
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
'@tanstack/solid-table@8.20.5':
resolution: {integrity: sha512-LsB/g/24CjBpccOcok+u+tfyqtU9SIQg5wf7ne54jRdEsy5YQnrpb5ATWZileHBduIG0p/1oE7UOA+DyjtnbDQ==}
engines: {node: '>=12'}
peerDependencies:
solid-js: '>=1.3'
'@tanstack/table-core@8.20.5':
resolution: {integrity: sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==}
engines: {node: '>=12'}
'@types/babel__core@7.20.5':
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
@ -3721,6 +3734,13 @@ snapshots:
dependencies:
tslib: 2.8.1
'@tanstack/solid-table@8.20.5(solid-js@1.9.4)':
dependencies:
'@tanstack/table-core': 8.20.5
solid-js: 1.9.4
'@tanstack/table-core@8.20.5': {}
'@types/babel__core@7.20.5':
dependencies:
'@babel/parser': 7.26.5