Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: use local storage to store some info about sessions ✨ #107

Merged
merged 6 commits into from
Sep 16, 2024
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
1 change: 1 addition & 0 deletions apps/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"react-resizable-panels": "^2.1.1",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7",
"usehooks-ts": "^3.1.0",
"zod": "^3.23.8",
"zustand": "^4.5.5"
},
Expand Down
17 changes: 13 additions & 4 deletions apps/core/src/hooks/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { getZodSchemaFromCols } from "@/commands/columns"
import { generateColumnsDefs } from "@/routes/dashboard/_layout/$tableName/-components/columns"
import { useSettings } from "@/settings/manager"
import { useTableState } from "@/state/tableState"
import { TableLocalStorage } from "@/types"
import { rankItem, type RankingInfo } from "@tanstack/match-sorter-utils"
import { useQuery } from "@tanstack/react-query"
import {
Expand All @@ -16,6 +17,7 @@ import {
type SortingState
} from "@tanstack/react-table"
import { useMemo, useRef, useState } from "react"
import { useLocalStorage } from "usehooks-ts"
import { useGetPaginatedRows } from "./row"

export const useGetTableColumns = (tableName: string) => {
Expand Down Expand Up @@ -60,6 +62,7 @@ const fuzzyFilter: FilterFn<any> = (row, columnId, value, addMeta) => {
type SetupReactTableOptions<TData, TValue> = {
columns: ColumnDef<TData, TValue>[]
tableName: string
connectionId: string
}

/**
Expand All @@ -68,10 +71,11 @@ type SetupReactTableOptions<TData, TValue> = {
*/
export const useSetupReactTable = <TData, TValue>({
tableName,
columns
columns,
connectionId
}: SetupReactTableOptions<TData, TValue>) => {
const { defaultData, pagination, setPagination, pageIndex, pageSize } =
useSetupPagination()
useSetupPagination(connectionId)
const { globalFilter, setGlobalFilter } = useTableState()

const { data: rows, isLoading: isRowsLoading } = useGetPaginatedRows(
Expand Down Expand Up @@ -121,10 +125,15 @@ export const useSetupReactTable = <TData, TValue>({
* Sets up the state and memoization for page index & page size
* to be used in paginating the rows.
*/
const useSetupPagination = () => {
const useSetupPagination = (connectionId: string) => {
const settings = useSettings()
const [{ pageIndex: persistedPageIndex }] =
useLocalStorage<TableLocalStorage>(`@tablex/${connectionId}`, {
tableName: "",
pageIndex: 0
})
const [{ pageIndex, pageSize }, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageIndex: persistedPageIndex,
pageSize: settings.pageSize
})
const defaultData = useMemo(() => [], [])
Expand Down
20 changes: 19 additions & 1 deletion apps/core/src/routes/connections/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from "@/components/ui/dropdown-menu"
import { Separator } from "@/components/ui/separator"
import { unwrapResult } from "@/lib/utils"
import { TableLocalStorage } from "@/types"
import { createFileRoute, redirect, useRouter } from "@tanstack/react-router"
import { MoreHorizontal, Trash } from "lucide-react"
import { Suspense } from "react"
Expand Down Expand Up @@ -54,9 +55,26 @@ function ConnectionsPage() {

if (connectionEstablishment === false) return

const connectionStorageData = localStorage.getItem(
`@tablex/${connectionId}`
)

if (connectionStorageData) {
const parsedConnectionData: TableLocalStorage = JSON.parse(
connectionStorageData
)
return router.navigate({
to: "/dashboard/$tableName",
params: {
tableName: parsedConnectionData.tableName
},
search: { connectionId }
})
}

router.navigate({
to: "/dashboard/land",
search: { connectionName: connectionDetails.connName }
search: { connectionId }
})
}

Expand Down
70 changes: 41 additions & 29 deletions apps/core/src/routes/dashboard/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,29 @@ import { unwrapResult } from "@/lib/utils"
import { cn } from "@tablex/lib/utils"
import { createFileRoute, Link, Outlet, redirect } from "@tanstack/react-router"
import { ArrowLeft, PanelLeftClose, Table } from "lucide-react"
import { useEffect, useRef, useState, type KeyboardEvent } from "react"
import { useEffect, useRef, useState } from "react"
import type { ImperativePanelHandle } from "react-resizable-panels"
import { useDebounceCallback } from "usehooks-ts"
import { z } from "zod"

const dashboardConnectionSchema = z.object({
connectionName: z.string().optional(),
connectionId: z.string().uuid(),
tableName: z.string().optional()
})

export const Route = createFileRoute("/dashboard/_layout")({
validateSearch: dashboardConnectionSchema,
loaderDeps: ({ search: { tableName, connectionName } }) => ({
connectionName,
loaderDeps: ({ search: { tableName, connectionId } }) => ({
connectionId,
tableName
}),
loader: async ({ deps: { connectionName } }) => {
const connName = connectionName || "Temp Connection"
loader: async ({ deps: { connectionId } }) => {
const connectionDetailsResult =
await commands.getConnectionDetails(connectionId)
const connectionDetails = unwrapResult(connectionDetailsResult)

if (!connectionDetails) throw redirect({ to: "/connections" })
const connName = connectionDetails.connName || "Temp Connection"

const tables = unwrapResult(await commands.getTables())
if (!tables)
Expand All @@ -49,9 +55,10 @@ export const Route = createFileRoute("/dashboard/_layout")({
function DashboardLayout() {
const deps = Route.useLoaderDeps()
const data = Route.useLoaderData()
const searchParams = Route.useSearch()
const keybindingsManager = useKeybindings()
const [, setSideBarCollapsed] = useState(false) // NOTE: I don't know why this is needed, but collapsing doesn't work without it.
const [tables, setTables] = useState<string[]>(data!.tables)
const [tables, setTables] = useState<string[]>(data.tables)
const [, setSideBarCollapsed] = useState(false)
const sidebarPanelRef = useRef<ImperativePanelHandle>(null)

useEffect(() => {
Expand All @@ -63,30 +70,31 @@ function DashboardLayout() {
])
}, [keybindingsManager])

let timeout: NodeJS.Timeout
const handleKeyUp = (e: KeyboardEvent<HTMLInputElement>) => {
const searchPattern = e.currentTarget.value
const handleTableSearch = (searchPattern: string) => {
if (searchPattern === "") return setTables(data!.tables)

clearTimeout(timeout)

timeout = setTimeout(() => {
const filteredTables = tables.filter((table) =>
table.includes(searchPattern)
)
setTables(filteredTables)
}, 100)
const filteredTables = tables.filter((table) =>
table.includes(searchPattern)
)
setTables(filteredTables)
}

const debounced = useDebounceCallback(handleTableSearch)

return (
<ResizablePanelGroup className="flex h-full w-full" direction="horizontal">
<ResizablePanelGroup
className="flex h-full w-full"
direction="horizontal"
autoSaveId={"@tablex/layout"}
>
<ResizablePanel
ref={sidebarPanelRef}
defaultSize={14}
onCollapse={() => setSideBarCollapsed(true)}
onExpand={() => setSideBarCollapsed(false)}
onCollapse={() => setSideBarCollapsed(true)}
collapsible
minSize={0}
minSize={12}
collapsedSize={0}
className={cn(
"flex flex-col items-start justify-between bg-zinc-800 p-4 pt-2 transition-all lg:p-6",
sidebarPanelRef.current?.isCollapsed() && "w-0 p-0 lg:w-0 lg:p-0"
Expand Down Expand Up @@ -125,13 +133,13 @@ function DashboardLayout() {
<div className="flex w-full items-center justify-center rounded-sm px-1">
<Input
id="search_input"
onKeyUp={handleKeyUp}
onChange={(e) => debounced(e.currentTarget.value)}
placeholder="Search..."
className="h-6 border-none text-sm transition-all placeholder:text-xs focus-visible:ring-0 focus-visible:ring-offset-0 lg:h-8 lg:w-[170px] lg:placeholder:text-base lg:focus:w-full"
/>
</div>
<div className="mb-4 overflow-y-auto">
<ul className="flex flex-col items-start gap-y-1 overflow-y-auto">
<div className="mb-4 w-full overflow-y-auto">
<ul className="flex w-full flex-col items-start gap-y-1 overflow-y-auto">
{tables.map((table, index) => {
return (
<Link
Expand All @@ -140,16 +148,20 @@ function DashboardLayout() {
tableName: table
}}
search={{
connectionName: deps.connectionName,
connectionId: deps.connectionId,
tableName: table
}}
preload={false}
key={index}
className="flex items-center justify-center gap-x-1 text-sm text-white lg:text-base"
className={cn(
"hover:bg-muted-foreground/30 flex w-full items-center gap-x-1 overflow-x-hidden rounded-md p-1 text-sm text-white lg:text-base",
searchParams.tableName === table &&
"bg-muted-foreground/30"
)}
role="button"
>
<Table size={16} className="fill-amber-600 text-black" />
{table}
<Table className="h-4 w-4 fill-amber-600 text-black" />
<p className="flex items-center justify-center">{table}</p>
</Link>
)
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ import TableActions from "./table-actions"

interface DataTableProps {
columns: ColumnDef<ColumnProps>[]
connectionId: string
}

const DataTable = ({ columns }: DataTableProps) => {
const DataTable = ({ columns, connectionId }: DataTableProps) => {
const { tableName, pkColumn } = useTableState()
const { isRowsLoading, contextMenuRow, setContextMenuRow, table, tableRef } =
useSetupReactTable({ columns, tableName })
useSetupReactTable({ columns, tableName, connectionId })
const queryClient = useQueryClient()
const keybindingsManager = useKeybindings()
const { isOpen, toggleSheet } = useEditRowSheetState()
Expand Down Expand Up @@ -83,14 +84,14 @@ const DataTable = ({ columns }: DataTableProps) => {
const virtualizer = useVirtualizer({
count: table.getRowCount(),
getScrollElement: () => parentRef.current,
estimateSize: () => 50, // I reached to this number by trial and error
estimateSize: () => 34, // I reached to this number by trial and error
overscan: 10,
debug: import.meta.env.DEV
})

return (
<Sheet open={isOpen} onOpenChange={toggleSheet}>
<TableActions table={table} />
<TableActions table={table} connectionId={connectionId} />
{isRowsLoading ? (
<LoadingSpinner />
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,21 @@ import {
} from "@/components/ui/dropdown-menu"
import { Input } from "@/components/ui/input"
import { useTableState } from "@/state/tableState"
import { TableLocalStorage } from "@/types"
import { useDebounceCallback, useLocalStorage } from "usehooks-ts"

type TableActionsProps = {
table: Table<any>
connectionId: string
}

const TableActions = ({ table }: TableActionsProps) => {
const TableActions = ({ table, connectionId }: TableActionsProps) => {
const { toggleDialog: toggleSqlEditor } = useSqlEditorState()
const { tableName, setGlobalFilter } = useTableState()
const debounced = useDebounceCallback(
(filter: string) => setGlobalFilter(filter),
500
)
return (
<>
<div className="flex items-end justify-between p-4">
Expand All @@ -39,13 +46,11 @@ const TableActions = ({ table }: TableActionsProps) => {
<Input
className="hidden min-w-[500px] placeholder:text-white/50 lg:block"
placeholder="Type something to filter..."
onChange={(value) =>
setGlobalFilter(String(value.currentTarget.value))
}
onChange={(value) => debounced(String(value.currentTarget.value))}
/>
</div>
<div className="flex flex-col items-end gap-y-1 lg:gap-y-3">
<DataTablePagination table={table} />
<DataTablePagination table={table} connectionId={connectionId} />
<div className="flex items-center gap-x-3">
<DataTableViewOptions table={table} />
<Button
Expand All @@ -69,11 +74,19 @@ export default TableActions

interface DataTablePaginationProps<TData> {
table: Table<TData>
connectionId: string
}

export function DataTablePagination<TData>({
table
table,
connectionId
}: DataTablePaginationProps<TData>) {
const [connectionStorage, setConnectionStorage] =
useLocalStorage<TableLocalStorage>(`@tablex/${connectionId}`, {
tableName: "",
pageIndex: 0
})

return (
<div className="flex items-center justify-between px-2">
<div className="text-muted-foreground hidden text-sm lg:flex">
Expand All @@ -89,7 +102,10 @@ export function DataTablePagination<TData>({
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => table.setPageIndex(0)}
onClick={() => {
table.setPageIndex(0)
setConnectionStorage({ ...connectionStorage, pageIndex: 0 })
}}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to first page</span>
Expand All @@ -98,7 +114,13 @@ export function DataTablePagination<TData>({
<Button
variant="outline"
className="h-8 w-8 p-0"
onClick={() => table.previousPage()}
onClick={() => {
setConnectionStorage({
...connectionStorage,
pageIndex: table.getState().pagination.pageIndex - 1
})
table.previousPage()
}}
disabled={!table.getCanPreviousPage()}
>
<span className="sr-only">Go to previous page</span>
Expand All @@ -107,7 +129,13 @@ export function DataTablePagination<TData>({
<Button
variant="outline"
className="h-8 w-8 p-0"
onClick={() => table.nextPage()}
onClick={() => {
setConnectionStorage({
...connectionStorage,
pageIndex: table.getState().pagination.pageIndex + 1
})
table.nextPage()
}}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to next page</span>
Expand All @@ -116,7 +144,13 @@ export function DataTablePagination<TData>({
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
onClick={() => {
table.setPageIndex(table.getPageCount() - 1)
setConnectionStorage({
...connectionStorage,
pageIndex: table.getPageCount() - 1
})
}}
disabled={!table.getCanNextPage()}
>
<span className="sr-only">Go to last page</span>
Expand Down
Loading
Loading