This commit is contained in:
parent
64e2438066
commit
3f4dcf2c4f
5 changed files with 315 additions and 36 deletions
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
94
packages/repl/src/lib/components/ui/table.tsx
Normal file
94
packages/repl/src/lib/components/ui/table.tsx
Normal 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}
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue