"use client"; import { AlignCenterIcon, AlignLeftIcon, AlignRightIcon, BoldIcon, ClipboardIcon, CopyIcon, EraserIcon, GripHorizontalIcon, GripVerticalIcon, ItalicIcon, PlusIcon, PaletteIcon, Redo2Icon, ScissorsIcon, StrikethroughIcon, Trash2Icon, Undo2Icon, UnderlineIcon, } from "lucide-react"; import Spreadsheet, { type ColumnIndicatorProps, type DataEditorProps, EntireColumnsSelection, EntireRowsSelection, type Point, type RowIndicatorProps, type Selection, type SpreadsheetRef, type TableProps, } from "react-spreadsheet"; import { useCallback, useEffect, useMemo, useRef, useState, type ClipboardEvent, type KeyboardEvent, } from "react"; import { closestCenter, DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors, type CollisionDetection, type DragEndEvent, } from "@dnd-kit/core"; import { horizontalListSortingStrategy, rectSortingStrategy, sortableKeyboardCoordinates, SortableContext, useSortable, verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { DeleteConfirmationDialog } from "@/components/admin/delete-confirmation-dialog"; import { RichTextContent } from "@/components/rich-text-content"; import { SimpleTooltip } from "@/components/tooltip"; import { Button } from "@/components/ui/button"; import { ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuTrigger, } from "@/components/ui/context-menu"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { InputGroup, InputGroupAddon, InputGroupTextarea, } from "@/components/ui/input-group"; import { Kbd } from "@/components/ui/kbd"; import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; import { addExtraSheetColumn, addExtraSheetRow, clearExtraSheetCellFormatting, DEFAULT_EXTRA_SHEET_COLUMN_WIDTH, MAX_EXTRA_SHEET_COLUMN_WIDTH, MIN_EXTRA_SHEET_COLUMN_WIDTH, deleteExtraSheetColumn, deleteExtraSheetRow, extraSheetRangeToTsv, extraSheetToMatrix, insertExtraSheetColumn, insertExtraSheetRow, matrixToExtraSheet, moveExtraSheetColumn, moveExtraSheetRow, nextExtraSheetColumnLabel, parseExtraSheetTsv, pasteExtraSheetText, resizeExtraSheetColumn, styleExtraSheetCells, updateExtraSheetCells, type ExtraSheetAlignment, type ExtraSheet, type ExtraSheetMatrix, } from "@/lib/guides/extra-sheet"; import { extraRichTextToPlainText, normalizeExtraRichText, type RichTextStylePatch, } from "@/lib/rich-text"; import { cn } from "@/lib/utils"; interface SheetHistory { items: ExtraSheet[]; index: number; value: ExtraSheet; } const DEFAULT_CELL_BACKGROUND = "#16A34A"; const SHEET_HISTORY_LIMIT = 80; const columnDragId = (id: string) => `column:${id}`; const rowDragId = (id: string) => `row:${id}`; const sameAxisCollision: CollisionDetection = (args) => { const axis = String(args.active.id).split(":", 1)[0]; return closestCenter({ ...args, droppableContainers: args.droppableContainers.filter((container) => String(container.id).startsWith(`${axis}:`), ), }); }; function movedIndex(current: number, from: number, to: number): number { if (current === from) return to; if (from < to && current > from && current <= to) return current - 1; if (to < from && current >= to && current < from) return current + 1; return current; } function spreadsheetColumnName(index: number): string { let value = index + 1; let label = ""; while (value > 0) { value -= 1; label = String.fromCharCode(65 + (value % 26)) + label; value = Math.floor(value / 26); } return label; } function LiteralDataViewer({ cell, }: { cell?: { value?: unknown; backgroundColor?: string; alignment?: ExtraSheetAlignment; }; }) { return (
); } function InlineDataEditor({ cell, onChange, exitEditMode, initialText, onReady, }: DataEditorProps<{ value: string }> & { initialText?: string; onReady?: () => void; }) { const inputRef = useRef(null); const originalText = useRef( extraRichTextToPlainText(String(cell?.value ?? "")), ); const [text, setText] = useState( () => initialText ?? extraRichTextToPlainText(String(cell?.value ?? "")), ); const committed = useRef(false); const commit = useCallback(() => { if (committed.current) return; committed.current = true; const nextValue = normalizeExtraRichText(text); if (text !== originalText.current) { onChange({ ...cell, value: nextValue }); } }, [cell, onChange, text]); useEffect(() => { onReady?.(); const input = inputRef.current; input?.focus(); input?.setSelectionRange(input.value.length, input.value.length); }, [onReady]); return (