переименования папок
This commit is contained in:
335
src/hook/useSpreadsheetEngine.ts
Normal file
335
src/hook/useSpreadsheetEngine.ts
Normal file
@@ -0,0 +1,335 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
CellUtils,
|
||||
CellValue,
|
||||
Workbook,
|
||||
} from '../lib/spreadsheet-engine/spreadsheet-engine'
|
||||
|
||||
interface CellData {
|
||||
value: string
|
||||
isSelected: boolean
|
||||
}
|
||||
|
||||
interface UseSpreadsheetEngineProps {
|
||||
rows?: number
|
||||
cols?: number
|
||||
sheetName?: string
|
||||
initialData?: string[][]
|
||||
}
|
||||
|
||||
export function useSpreadsheetEngine({
|
||||
rows = 50,
|
||||
cols = 20,
|
||||
sheetName = 'Sheet1',
|
||||
initialData,
|
||||
}: UseSpreadsheetEngineProps = {}) {
|
||||
const workbookRef = useRef<Workbook | null>(null)
|
||||
const [cells, setCells] = useState<CellData[][]>(() => {
|
||||
return Array(rows)
|
||||
.fill(null)
|
||||
.map(() =>
|
||||
Array(cols)
|
||||
.fill(null)
|
||||
.map(() => ({ value: '', isSelected: false }))
|
||||
)
|
||||
})
|
||||
const [isCalculating, setIsCalculating] = useState(false)
|
||||
|
||||
// Инициализация workbook
|
||||
useEffect(() => {
|
||||
if (!workbookRef.current) {
|
||||
workbookRef.current = new Workbook()
|
||||
workbookRef.current.addSheet(sheetName)
|
||||
// addCustomFunctions(workbookRef.current)
|
||||
}
|
||||
|
||||
// Загружаем начальные данные если они есть
|
||||
if (initialData) {
|
||||
loadDataFromArray(initialData)
|
||||
}
|
||||
}, [sheetName, initialData])
|
||||
|
||||
// Конвертация данных из движка в React состояние
|
||||
const syncFromEngine = useCallback(() => {
|
||||
if (!workbookRef.current) return
|
||||
|
||||
const sheet = workbookRef.current.getSheet(sheetName)
|
||||
if (!sheet) return
|
||||
|
||||
const newCells = Array(rows)
|
||||
.fill(null)
|
||||
.map(() =>
|
||||
Array(cols)
|
||||
.fill(null)
|
||||
.map(() => ({ value: '', isSelected: false }))
|
||||
)
|
||||
|
||||
// Получаем все ячейки из движка
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
const cellName = CellUtils.createCellName(col, row + 1)
|
||||
const cell = sheet.getCell(cellName)
|
||||
|
||||
// Показываем сырое значение (raw) в UI, а не вычисленное
|
||||
if (cell.raw !== null && cell.raw !== undefined) {
|
||||
newCells[row][col].value = String(cell.raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setCells(newCells)
|
||||
}, [rows, cols, sheetName])
|
||||
|
||||
// Установка значения ячейки
|
||||
const setCellValue = useCallback(
|
||||
(row: number, col: number, value: string) => {
|
||||
if (!workbookRef.current) return
|
||||
|
||||
const cellName = CellUtils.createCellName(col, row + 1)
|
||||
const qualifiedName = `${sheetName}!${cellName}`
|
||||
|
||||
setIsCalculating(true)
|
||||
|
||||
try {
|
||||
// Конвертируем пустую строку в null
|
||||
const cellValue: CellValue = value === '' ? null : value
|
||||
|
||||
// Если значение начинается с =, то это формула
|
||||
if (typeof cellValue === 'string' && cellValue.startsWith('=')) {
|
||||
workbookRef.current.setCell(qualifiedName, cellValue)
|
||||
} else {
|
||||
// Пытаемся конвертировать в число, если возможно
|
||||
const numValue = Number(cellValue)
|
||||
if (!isNaN(numValue) && cellValue !== null && cellValue !== '') {
|
||||
workbookRef.current.setCell(qualifiedName, numValue)
|
||||
} else {
|
||||
workbookRef.current.setCell(qualifiedName, cellValue)
|
||||
}
|
||||
}
|
||||
|
||||
// Пересчитываем все формулы
|
||||
workbookRef.current.recalc()
|
||||
|
||||
// Синхронизируем с React состоянием
|
||||
syncFromEngine()
|
||||
} catch (error) {
|
||||
console.error('Error setting cell value:', error)
|
||||
} finally {
|
||||
setIsCalculating(false)
|
||||
}
|
||||
},
|
||||
[sheetName, syncFromEngine]
|
||||
)
|
||||
|
||||
// Получение вычисленного значения ячейки
|
||||
const getCellValue = useCallback(
|
||||
(row: number, col: number): CellValue => {
|
||||
if (!workbookRef.current) return null
|
||||
|
||||
const cellName = CellUtils.createCellName(col, row + 1)
|
||||
const qualifiedName = `${sheetName}!${cellName}`
|
||||
|
||||
try {
|
||||
return workbookRef.current.getValue(qualifiedName)
|
||||
} catch (error) {
|
||||
console.error('Error getting cell value:', error)
|
||||
return '#ERROR!'
|
||||
}
|
||||
},
|
||||
[sheetName]
|
||||
)
|
||||
|
||||
// Получение сырого значения ячейки (для отображения в строке формул)
|
||||
const getCellRawValue = useCallback(
|
||||
(row: number, col: number): string => {
|
||||
if (!workbookRef.current) return ''
|
||||
|
||||
const cellName = CellUtils.createCellName(col, row + 1)
|
||||
const sheet = workbookRef.current.getSheet(sheetName)
|
||||
|
||||
if (!sheet) return ''
|
||||
|
||||
const cell = sheet.getCell(cellName)
|
||||
return cell.raw !== null && cell.raw !== undefined ? String(cell.raw) : ''
|
||||
},
|
||||
[sheetName]
|
||||
)
|
||||
|
||||
// Загрузка данных из массива
|
||||
const loadDataFromArray = useCallback(
|
||||
(data: string[][]) => {
|
||||
if (!workbookRef.current) return
|
||||
|
||||
const sheet = workbookRef.current.getSheet(sheetName)
|
||||
if (!sheet) return
|
||||
|
||||
setIsCalculating(true)
|
||||
|
||||
try {
|
||||
// Очищаем лист
|
||||
sheet.cells.clear()
|
||||
|
||||
// Загружаем данные
|
||||
for (let row = 0; row < data.length && row < rows; row++) {
|
||||
for (let col = 0; col < data[row].length && col < cols; col++) {
|
||||
const value = data[row][col]
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
const cellName = CellUtils.createCellName(col, row + 1)
|
||||
sheet.setCell(cellName, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Пересчитываем все формулы
|
||||
workbookRef.current.recalc()
|
||||
|
||||
// Синхронизируем с React состоянием
|
||||
syncFromEngine()
|
||||
} catch (error) {
|
||||
console.error('Error loading data:', error)
|
||||
} finally {
|
||||
setIsCalculating(false)
|
||||
}
|
||||
},
|
||||
[rows, cols, sheetName, syncFromEngine]
|
||||
)
|
||||
|
||||
// Экспорт данных в массив (для сохранения)
|
||||
const exportToArray = useCallback((): string[][] => {
|
||||
if (!workbookRef.current) return []
|
||||
|
||||
const sheet = workbookRef.current.getSheet(sheetName)
|
||||
if (!sheet) return []
|
||||
|
||||
const result: string[][] = Array(rows)
|
||||
.fill(null)
|
||||
.map(() => Array(cols).fill(''))
|
||||
|
||||
for (let row = 0; row < rows; row++) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
const cellName = CellUtils.createCellName(col, row + 1)
|
||||
const cell = sheet.getCell(cellName)
|
||||
|
||||
if (cell.raw !== null && cell.raw !== undefined) {
|
||||
result[row][col] = String(cell.raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}, [rows, cols, sheetName])
|
||||
|
||||
// Экспорт в JSON для бэкенда
|
||||
const exportToJSON = useCallback((): string => {
|
||||
if (!workbookRef.current) return '{}'
|
||||
return JSON.stringify(workbookRef.current.toJSON())
|
||||
}, [])
|
||||
|
||||
// Импорт из JSON с бэкенда
|
||||
const importFromJSON = useCallback(
|
||||
(jsonData: string) => {
|
||||
if (!workbookRef.current) return
|
||||
|
||||
setIsCalculating(true)
|
||||
|
||||
try {
|
||||
const data = JSON.parse(jsonData)
|
||||
workbookRef.current.fromJSON(data)
|
||||
// addCustomFunctions(workbookRef.current)
|
||||
workbookRef.current.recalc()
|
||||
syncFromEngine()
|
||||
} catch (error) {
|
||||
console.error('Error importing from JSON:', error)
|
||||
} finally {
|
||||
setIsCalculating(false)
|
||||
}
|
||||
},
|
||||
[syncFromEngine]
|
||||
)
|
||||
|
||||
// Добавление пользовательской функции
|
||||
const addFunction = useCallback(
|
||||
(name: string, func: (...args: any[]) => any) => {
|
||||
if (!workbookRef.current) return
|
||||
workbookRef.current.addFunction(name, func)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
// Пересчёт всех формул
|
||||
const recalculate = useCallback(() => {
|
||||
if (!workbookRef.current) return
|
||||
|
||||
setIsCalculating(true)
|
||||
|
||||
try {
|
||||
workbookRef.current.recalc()
|
||||
syncFromEngine()
|
||||
} catch (error) {
|
||||
console.error('Error recalculating:', error)
|
||||
} finally {
|
||||
setIsCalculating(false)
|
||||
}
|
||||
}, [syncFromEngine])
|
||||
|
||||
// Получение информации о зависимостях ячейки
|
||||
const getCellDependencies = useCallback(
|
||||
(row: number, col: number) => {
|
||||
if (!workbookRef.current) return { precedents: [], dependents: [] }
|
||||
|
||||
const cellName = CellUtils.createCellName(col, row + 1)
|
||||
const sheet = workbookRef.current.getSheet(sheetName)
|
||||
|
||||
if (!sheet) return { precedents: [], dependents: [] }
|
||||
|
||||
const cell = sheet.getCell(cellName)
|
||||
|
||||
return {
|
||||
precedents: Array.from(cell.precedents),
|
||||
dependents: Array.from(cell.dependents),
|
||||
}
|
||||
},
|
||||
[sheetName]
|
||||
)
|
||||
|
||||
// Получение статистики workbook
|
||||
const getWorkbookStats = useCallback(() => {
|
||||
if (!workbookRef.current) return { sheets: 0, cells: 0, formulas: 0 }
|
||||
|
||||
let totalCells = 0
|
||||
let totalFormulas = 0
|
||||
|
||||
for (const sheet of workbookRef.current.sheets.values()) {
|
||||
totalCells += sheet.cells.size
|
||||
|
||||
for (const cell of sheet.cells.values()) {
|
||||
if (typeof cell.raw === 'string' && cell.raw.startsWith('=')) {
|
||||
totalFormulas++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
sheets: workbookRef.current.sheets.size,
|
||||
cells: totalCells,
|
||||
formulas: totalFormulas,
|
||||
}
|
||||
}, [])
|
||||
|
||||
return {
|
||||
cells,
|
||||
setCellValue,
|
||||
getCellValue,
|
||||
getCellRawValue,
|
||||
loadDataFromArray,
|
||||
exportToArray,
|
||||
exportToJSON,
|
||||
importFromJSON,
|
||||
addFunction,
|
||||
recalculate,
|
||||
getCellDependencies,
|
||||
getWorkbookStats,
|
||||
isCalculating,
|
||||
workbook: workbookRef.current,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user