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/convert": "^0.19.8",
|
||||||
"@mtcute/web": "^0.19.5",
|
"@mtcute/web": "^0.19.5",
|
||||||
"@nanostores/persistent": "^0.10.2",
|
"@nanostores/persistent": "^0.10.2",
|
||||||
|
"@tanstack/solid-table": "^8.20.5",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"filesize": "^10.1.6",
|
"filesize": "^10.1.6",
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import type { Tdata } from '@mtcute/convert'
|
import type { Tdata } from '@mtcute/convert'
|
||||||
import { hex } from '@fuman/utils'
|
import { hex } from '@fuman/utils'
|
||||||
import { workerInvoke } from 'mtcute-repl-worker/client'
|
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 { 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 { Dialog, DialogContent, DialogDescription, DialogHeader } from '../../../../lib/components/ui/dialog.tsx'
|
||||||
import { Spinner } from '../../../../lib/components/ui/spinner.tsx'
|
import { Spinner } from '../../../../lib/components/ui/spinner.tsx'
|
||||||
import { $accounts } from '../../../../store/accounts.ts'
|
import { $accounts } from '../../../../store/accounts.ts'
|
||||||
|
import { TdataDataTable } from './TdataTable.tsx'
|
||||||
|
|
||||||
interface TdataAccount {
|
interface TdataAccount {
|
||||||
telegramId: number
|
telegramId: number
|
||||||
|
@ -191,41 +191,28 @@ export function TdataImportDialog(props: {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<For each={accounts()}>
|
<TdataDataTable
|
||||||
{account => (
|
data={() => accounts().map(e => ({
|
||||||
<Checkbox
|
index: e.index,
|
||||||
class="ml-1 flex flex-row items-center gap-2"
|
dcId: e.dcId,
|
||||||
checked={account.toImport}
|
telegramId: e.telegramId,
|
||||||
onChange={checked => setAccounts(
|
disabled: accountExists(e.telegramId) ?? false,
|
||||||
accounts().map(it => it.index === account.index ? {
|
}))}
|
||||||
...it,
|
onChange={(s) => {
|
||||||
toImport: checked,
|
const nextAccounts = accounts().map(e => ({ ...e, toImport: false }))
|
||||||
} : it),
|
for (const [idx, v] of Object.entries(s)) {
|
||||||
)}
|
const acc = nextAccounts.find(e => e.index === Number(idx))
|
||||||
disabled={accountExists(account.telegramId)}
|
if (acc === undefined) continue
|
||||||
>
|
if (accountExists(acc.telegramId)) continue
|
||||||
<CheckboxControl />
|
|
||||||
<CheckboxLabel class="flex items-center gap-1 text-sm">
|
acc.toImport = v
|
||||||
<div class="text-foreground">
|
}
|
||||||
ID
|
|
||||||
{' '}
|
setAccounts(nextAccounts)
|
||||||
{account.telegramId}
|
}}
|
||||||
</div>
|
/>
|
||||||
<div class="text-xs text-muted-foreground">
|
|
||||||
(DC
|
|
||||||
{' '}
|
|
||||||
{account.dcId}
|
|
||||||
, index
|
|
||||||
{' '}
|
|
||||||
{account.index}
|
|
||||||
)
|
|
||||||
</div>
|
|
||||||
</CheckboxLabel>
|
|
||||||
</Checkbox>
|
|
||||||
)}
|
|
||||||
</For>
|
|
||||||
{error() && (
|
{error() && (
|
||||||
<div class="mt-2 text-sm text-error-foreground">
|
<div class="text-error-foreground mt-2 text-sm">
|
||||||
{error()}
|
{error()}
|
||||||
</div>
|
</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':
|
'@nanostores/persistent':
|
||||||
specifier: ^0.10.2
|
specifier: ^0.10.2
|
||||||
version: 0.10.2(nanostores@0.11.3)
|
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:
|
class-variance-authority:
|
||||||
specifier: ^0.7.1
|
specifier: ^0.7.1
|
||||||
version: 0.7.1
|
version: 0.7.1
|
||||||
|
@ -1168,6 +1171,16 @@ packages:
|
||||||
'@swc/helpers@0.5.15':
|
'@swc/helpers@0.5.15':
|
||||||
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
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':
|
'@types/babel__core@7.20.5':
|
||||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||||
|
|
||||||
|
@ -3721,6 +3734,13 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
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':
|
'@types/babel__core@7.20.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/parser': 7.26.5
|
'@babel/parser': 7.26.5
|
||||||
|
|
Loading…
Reference in a new issue