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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,7 @@ dist-ssr
# Generated files
src/api/types.ts

.claude/
.claude/

# Example / reference files
example/
2,668 changes: 2,179 additions & 489 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
"@dnd-kit/utilities": "^3.2.2",
"@excalidraw/excalidraw": "^0.17.6",
"@glideapps/glide-data-grid-cells": "^3.4.1",
"@labbs/openblock-core": "v1.0.1",
"@labbs/openblock-react": "v1.0.1",
"@labbs/openblock-core": "v1.0.3",
"@labbs/openblock-react": "v1.0.3",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-checkbox": "^1.3.3",
Expand Down
14 changes: 2 additions & 12 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { QueryClientProvider } from '@tanstack/react-query'
import { queryClient } from '@/lib/query-client'
import { ThemeProvider } from '@/contexts/theme-provider'
import { AuthProvider } from '@/contexts/auth-context'
import { LanguageProvider } from '@/i18n/LanguageContext'
Expand Down Expand Up @@ -32,17 +33,6 @@ function PageLoader() {
)
}

const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 1,
refetchOnWindowFocus: false,
staleTime: 30_000, // 30s — data considered fresh after fetch
gcTime: 10 * 60_000, // 10min — keep unused cache longer
},
},
})

function App() {
return (
<QueryClientProvider client={queryClient}>
Expand Down
572 changes: 380 additions & 192 deletions src/components/database/common/view-tabs.tsx

Large diffs are not rendered by default.

158 changes: 86 additions & 72 deletions src/components/database/document/document-table-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ interface DocumentTableViewProps {
onUpdateColumnFormat?: (columnId: string, options: Partial<ColumnOptions>) => void
onDuplicateColumn?: (columnId: string) => void
isLoading?: boolean
/** Compact mode for inline/embedded use — hides row selectors, drag handles, and reduces chrome */
compact?: boolean
}

// Property type icons mapping
Expand Down Expand Up @@ -142,6 +144,7 @@ export function DocumentTableView({
onUpdateColumnFormat,
onDuplicateColumn,
isLoading = false,
compact = false,
}: DocumentTableViewProps) {
const { t } = useTranslation('database')
const [editingCell, setEditingCell] = useState<{ rowId: string; propertyId: string } | null>(null)
Expand Down Expand Up @@ -407,10 +410,10 @@ export function DocumentTableView({
<div className="w-full">
{/* Header skeleton */}
<div className="flex border-b bg-muted/30">
<div className="w-8 shrink-0" />
{!compact && <div className="w-8 shrink-0" />}
<div className="flex-1 flex">
{[1, 2, 3].map((i) => (
<div key={i} className="w-48 h-8 px-2 flex items-center border-r">
<div key={i} className="w-48 h-9 px-2 flex items-center border-r">
<div className="h-4 w-20 bg-muted animate-pulse rounded" />
</div>
))}
Expand All @@ -419,7 +422,7 @@ export function DocumentTableView({
{/* Row skeletons */}
{[1, 2, 3].map((i) => (
<div key={i} className="flex border-b">
<div className="w-8 shrink-0" />
{!compact && <div className="w-8 shrink-0" />}
<div className="flex-1 flex">
{[1, 2, 3].map((j) => (
<div key={j} className="w-48 h-9 px-2 flex items-center border-r">
Expand All @@ -437,24 +440,26 @@ export function DocumentTableView({
<div
ref={containerRef}
tabIndex={0}
className={cn("w-full overflow-x-auto outline-none", resizing && "select-none cursor-col-resize")}
className={cn("w-full overflow-x-auto outline-none px-6", resizing && "select-none cursor-col-resize")}
>
<table ref={tableRef} className="w-full border-collapse" style={{ tableLayout: 'fixed' }}>
<table ref={tableRef} className="w-full border-collapse database-table-view" style={{ tableLayout: 'fixed' }}>
{/* Header */}
<thead>
<tr className="border-t border-b">
{/* Row selector column */}
<th className="w-8 p-0 sticky left-0 bg-background z-10">
<div className="h-8 flex items-center justify-center">
<Checkbox
checked={isAllSelected}
// @ts-expect-error - indeterminate is a valid prop
indeterminate={isSomeSelected || undefined}
onCheckedChange={selectAllRows}
className="h-4 w-4"
/>
</div>
</th>
<tr className={cn("border-b", !compact && "border-t")}>
{/* Row selector column (hidden in compact mode) */}
{!compact && (
<th className="w-8 p-0 sticky left-0 bg-background z-10">
<div className="h-9 flex items-center justify-center">
<Checkbox
checked={isAllSelected}
// @ts-expect-error - indeterminate is a valid prop
indeterminate={isSomeSelected || undefined}
onCheckedChange={selectAllRows}
className="h-4 w-4"
/>
</div>
</th>
)}

{/* Property columns */}
{schema.map((property, index) => {
Expand All @@ -470,11 +475,12 @@ export function DocumentTableView({
key={propertyId}
className={cn(
'p-0 text-left font-normal relative',
index === 0 && 'sticky left-8 bg-background z-10'
index === 0 && !compact && 'sticky left-8 bg-background z-10',
index === 0 && compact && 'sticky left-0 bg-background z-10'
)}
style={{ width: `${width}px`, minWidth: `${width}px` }}
style={{ width: `${width}px`, minWidth: `${width}px`, borderRight: '1px solid hsl(var(--border))' }}
>
<div className="h-8 px-2 flex items-center gap-2 text-sm text-muted-foreground border-r hover:bg-muted/50 cursor-pointer group">
<div className="h-9 px-2 flex items-center gap-2 text-sm text-muted-foreground hover:bg-muted/50 cursor-pointer group">
{/* Custom icon or default type icon */}
{property.options?.customIcon ? (
<span className="text-sm shrink-0">{property.options.customIcon as string}</span>
Expand Down Expand Up @@ -975,7 +981,7 @@ export function DocumentTableView({
onAdd={onAddProperty}
trigger={
<button
className="h-8 w-full flex items-center justify-center text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors"
className="h-9 w-full flex items-center justify-center text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors"
title={t('table.addProperty')}
>
<Plus className="h-4 w-4" />
Expand Down Expand Up @@ -1009,17 +1015,20 @@ export function DocumentTableView({
onCellChange={handleCellChange}
onSetEditingCell={setEditingCell}
onSetFocusedCell={setFocusedCell}
compact={compact}
/>
))}

{/* Add row button */}
<tr className="group/addrow">
<td className="w-8 p-0 sticky left-0 bg-background z-10">
<div className="h-9 flex items-center justify-center opacity-0 group-hover/addrow:opacity-100">
<Plus className="h-3.5 w-3.5 text-muted-foreground" />
</div>
</td>
<td colSpan={schema.length + 2} className="p-0">
{!compact && (
<td className="w-8 p-0 sticky left-0 bg-background z-10">
<div className="h-9 flex items-center justify-center opacity-0 group-hover/addrow:opacity-100">
<Plus className="h-3.5 w-3.5 text-muted-foreground" />
</div>
</td>
)}
<td colSpan={schema.length + (compact ? 1 : 2)} className="p-0">
<button
className="w-full h-9 px-2 flex items-center gap-2 text-sm text-muted-foreground hover:bg-muted/30 transition-colors"
onClick={onAddRow}
Expand Down Expand Up @@ -1058,6 +1067,7 @@ interface MemoTableRowProps {
schema: PropertySchema[]
isSelected: boolean
isHovered: boolean
compact?: boolean
editingCell: { rowId: string; propertyId: string } | null
focusedCell: { rowIndex: number; colIndex: number } | null
columnWidths: Record<string, number>
Expand Down Expand Up @@ -1086,6 +1096,7 @@ const MemoTableRow = memo(function MemoTableRow({
onCellChange,
onSetEditingCell,
onSetFocusedCell,
compact = false,
}: MemoTableRowProps) {
const { t } = useTranslation('database')

Expand All @@ -1103,48 +1114,50 @@ const MemoTableRow = memo(function MemoTableRow({
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{/* Row selector with checkbox and menu */}
<td className="w-8 p-0 sticky left-0 bg-background z-10">
<div className="h-9 flex items-center justify-center group/selector">
<div className={cn(
"absolute",
(isHovered || isSelected) ? "opacity-100" : "opacity-0 group-hover/selector:opacity-100"
)}>
<Checkbox
checked={isSelected}
onCheckedChange={() => onToggleRowSelection(row.id, rowIndex)}
onClick={(e) => onToggleRowSelection(row.id, rowIndex, e as unknown as React.MouseEvent)}
className="h-4 w-4"
/>
</div>
<div className={cn(
'h-5 w-5 flex items-center justify-center',
isHovered && !isSelected ? 'opacity-100' : 'opacity-0'
)}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="h-5 w-5 flex items-center justify-center rounded hover:bg-muted text-muted-foreground">
<GripVertical className="h-3.5 w-3.5" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-48">
<DropdownMenuItem onClick={() => onOpenDocument(row.id)}>
<FileText className="h-4 w-4 mr-2" />
{t('table.openDocument')}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="text-destructive focus:text-destructive"
onClick={() => onDeleteRow(row.id)}
>
<Trash2 className="h-4 w-4 mr-2" />
{t('table.deleteDocument')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{/* Row selector with checkbox and menu (hidden in compact mode) */}
{!compact && (
<td className="w-8 p-0 sticky left-0 bg-background z-10">
<div className="h-9 flex items-center justify-center group/selector">
<div className={cn(
"absolute",
(isHovered || isSelected) ? "opacity-100" : "opacity-0 group-hover/selector:opacity-100"
)}>
<Checkbox
checked={isSelected}
onCheckedChange={() => onToggleRowSelection(row.id, rowIndex)}
onClick={(e) => onToggleRowSelection(row.id, rowIndex, e as unknown as React.MouseEvent)}
className="h-4 w-4"
/>
</div>
<div className={cn(
'h-5 w-5 flex items-center justify-center',
isHovered && !isSelected ? 'opacity-100' : 'opacity-0'
)}>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="h-5 w-5 flex items-center justify-center rounded hover:bg-muted text-muted-foreground">
<GripVertical className="h-3.5 w-3.5" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-48">
<DropdownMenuItem onClick={() => onOpenDocument(row.id)}>
<FileText className="h-4 w-4 mr-2" />
{t('table.openDocument')}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="text-destructive focus:text-destructive"
onClick={() => onDeleteRow(row.id)}
>
<Trash2 className="h-4 w-4 mr-2" />
{t('table.deleteDocument')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
</td>
</td>
)}

{/* Property cells */}
{schema.map((property, colIndex) => {
Expand All @@ -1162,11 +1175,12 @@ const MemoTableRow = memo(function MemoTableRow({
<td
key={propertyId}
className={cn(
'p-0 border-r',
colIndex === 0 && 'sticky left-8 bg-background z-10',
'p-0',
colIndex === 0 && !compact && 'sticky left-8 bg-background z-10',
colIndex === 0 && compact && 'sticky left-0 bg-background z-10',
isFocused && 'ring-2 ring-inset ring-primary'
)}
style={{ width: `${width}px`, minWidth: `${width}px` }}
style={{ width: `${width}px`, minWidth: `${width}px`, borderRight: '1px solid hsl(var(--border))' }}
onClick={() => onSetFocusedCell({ rowIndex, colIndex })}
>
<div className="relative group/cell">
Expand Down
1 change: 1 addition & 0 deletions src/components/database/document/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ export function DocumentDatabaseView() {
canDeleteView={views.length > 1}
externalFilterOpen={filterPanelOpen}
onExternalFilterOpenChange={setFilterPanelOpen}
onAddRow={handleAddRow}
/>
)}

Expand Down
Loading
Loading