Skip to content

Commit

Permalink
Add conflict warning (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
KennethWussmann authored Nov 7, 2024
1 parent e90075b commit c76299d
Show file tree
Hide file tree
Showing 10 changed files with 395 additions and 18 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@radix-ui/react-menubar": "^1.1.2",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-scroll-area": "^1.2.0",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slider": "^1.2.1",
Expand All @@ -31,6 +32,7 @@
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-tooltip": "^1.1.3",
"@tanstack/react-query": "^5.59.16",
"@tanstack/react-table": "^8.20.5",
"@uidotdev/usehooks": "^2.4.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
Expand Down
55 changes: 55 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

110 changes: 110 additions & 0 deletions src/components/ui/data-table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"use client"

import {
ColumnDef,
flexRender,
getCoreRowModel,
getPaginationRowModel,
useReactTable,
} from "@tanstack/react-table"

import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
import { Button } from "./button"
import { ChevronLeft, ChevronRight } from "lucide-react"

interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
}

export function DataTable<TData, TValue>({
columns,
data,
}: DataTableProps<TData, TValue>) {
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
initialState: {
pagination: {
pageIndex: 0,
pageSize: 5,
}
}
})

return (
<div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-2">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
<ChevronLeft />
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
<ChevronRight />
</Button>
</div>
</div>
)
}
46 changes: 46 additions & 0 deletions src/components/ui/scroll-area.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"

import { cn } from "@/lib/utils"

const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName

const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName

export { ScrollArea, ScrollBar }
52 changes: 48 additions & 4 deletions src/hooks/use-image-importer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,42 @@ import { useState } from "react";
import { ImageEntity } from "@/lib/database/image-entity";
import { useDatabase } from "@/lib/database/database-provider";
import { Database } from "@/lib/database/database";
export type ImportCaptionConflictStrategy = "skip" | "replace";

export class ConflictError extends Error {
constructor(public conflicts: CaptionFileConflict[]) {
super("Conflicting files detected")
}
}

export type CaptionFileConflict = {
filename: string
txtCaption: Caption
dbCaption: ImageEntity
}

export const useImageImporter = () => {
const { setAutoBackupEnabled } = useDatabase()
const { setAutoBackupEnabled, saveDatabaseBackup } = useDatabase()
const [separator] = useAtom(settings.caption.separator);
const { directoryHandle } = useDatasetDirectory();
const [imported, setImported] = useState(false)

const importImages = async (
database: Database,
existingImages: ImageEntity[],
imageFiles: ImageFile[]
imageFiles: ImageFile[],
conflictStrategy?: ImportCaptionConflictStrategy
) => {
if (!directoryHandle || imported) {
return
}
const conflicts: CaptionFileConflict[] = []
const imageDocs = await Promise.all(imageFiles.map(async (image): Promise<ImageEntity | null> => {
const existingImage = existingImages.find(existingImage => existingImage.filename === image.name)
let caption: Caption | null = null
const captionFilename = getFilenameWithoutExtension(image.name) + ".txt"
try {
const captionFileHandle = await directoryHandle.getFileHandle(getFilenameWithoutExtension(image.name) + ".txt")
const captionFileHandle = await directoryHandle.getFileHandle(captionFilename)
const file = await captionFileHandle.getFile()
const captionText = await file.text()

Expand All @@ -40,6 +56,28 @@ export const useImageImporter = () => {
// No caption file
}

const isConflict = existingImage?.caption && caption?.preview && existingImage.caption !== caption.preview

if (isConflict && caption) {
if (conflictStrategy === "replace") {
return {
id: image.name,
filename: image.name,
caption: caption.preview,
captionParts: caption.parts
}
} else if (conflictStrategy === "skip") {
return existingImage
} else {
conflicts.push({
filename: captionFilename,
txtCaption: caption,
dbCaption: existingImage
})
return null
}
}

if (caption) {
return {
id: image.name,
Expand All @@ -57,11 +95,17 @@ export const useImageImporter = () => {
}
}))

if (conflicts.length > 0) {
throw new ConflictError(conflicts)
}

await database.images.bulkPut(imageDocs.filter(doc => doc !== null))

setImported(true)
setAutoBackupEnabled(true)
console.log("Imported images")
console.log("Saving backup")
void saveDatabaseBackup()
setAutoBackupEnabled(true)
}

return { importImages, imported }
Expand Down
2 changes: 2 additions & 0 deletions src/lib/database/database-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type DatabaseContextType = {
isAutoBackupEnabled: boolean;
setAutoBackupEnabled: (enabled: boolean) => void;
initializeDatabase: (handle: FileSystemDirectoryHandle) => Promise<Database>;
saveDatabaseBackup: (localDb?: Database) => Promise<void>;
};

const DatabaseContext = createContext<DatabaseContextType | undefined>(
Expand Down Expand Up @@ -194,6 +195,7 @@ export const DatabaseProvider = ({ children }: { children: ReactNode }) => {
isAutoBackupEnabled,
setAutoBackupEnabled,
initializeDatabase,
saveDatabaseBackup,
database: database!
};

Expand Down
Loading

0 comments on commit c76299d

Please sign in to comment.