Remove unused online editor modules

This commit is contained in:
2026-06-09 07:46:10 +08:00
parent 0303ba977e
commit d286bbec8f
219 changed files with 0 additions and 63146 deletions
@@ -1,313 +0,0 @@
import { ref, onMounted, onUnmounted } from 'vue'
const DB_NAME = 'zq-smart-table-offline'
const DB_VERSION = 1
const STORE_TABLE_DATA = 'tableData'
const STORE_PENDING_OPS = 'pendingOps'
export interface PendingOperation {
id: string
type: 'updateCell' | 'createRecord' | 'deleteRecord'
tableId: string
recordId: string
payload: Record<string, any>
timestamp: number
}
let _db: IDBDatabase | null = null
function openDB(): Promise<IDBDatabase> {
if (_db) return Promise.resolve(_db)
return new Promise((resolve, reject) => {
const req = indexedDB.open(DB_NAME, DB_VERSION)
req.onupgradeneeded = () => {
const db = req.result
if (!db.objectStoreNames.contains(STORE_TABLE_DATA)) {
db.createObjectStore(STORE_TABLE_DATA, { keyPath: 'id' })
}
if (!db.objectStoreNames.contains(STORE_PENDING_OPS)) {
const store = db.createObjectStore(STORE_PENDING_OPS, { keyPath: 'id' })
store.createIndex('byTable', 'tableId', { unique: false })
store.createIndex('byTimestamp', 'timestamp', { unique: false })
}
}
req.onsuccess = () => {
_db = req.result
resolve(_db)
}
req.onerror = () => reject(req.error)
})
}
function promisify<T>(req: IDBRequest<T>): Promise<T> {
return new Promise((resolve, reject) => {
req.onsuccess = () => resolve(req.result)
req.onerror = () => reject(req.error)
})
}
function waitTx(tx: IDBTransaction): Promise<void> {
return new Promise((resolve, reject) => {
tx.oncomplete = () => resolve()
tx.onerror = () => reject(tx.error)
})
}
function genId(): string {
return `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
}
// ==================== Module-level exports (no Vue dependency) ====================
export async function cacheTableData(tableId: string, data: any) {
try {
const db = await openDB()
const store = db.transaction(STORE_TABLE_DATA, 'readwrite').objectStore(STORE_TABLE_DATA)
await promisify(store.put({ id: tableId, data, cachedAt: Date.now() }))
} catch {
// silently fail
}
}
export async function getCachedTableData(tableId: string): Promise<any | null> {
try {
const db = await openDB()
const store = db.transaction(STORE_TABLE_DATA, 'readonly').objectStore(STORE_TABLE_DATA)
const result = await promisify(store.get(tableId))
if (!result) return null
const maxAge = 24 * 60 * 60 * 1000
if (Date.now() - result.cachedAt > maxAge) return null
return result.data
} catch {
return null
}
}
export async function addPendingOp(op: Omit<PendingOperation, 'id' | 'timestamp'>) {
try {
const db = await openDB()
if (op.type === 'updateCell') {
const readTx = db.transaction(STORE_PENDING_OPS, 'readonly')
const readStore = readTx.objectStore(STORE_PENDING_OPS)
const existing: PendingOperation[] = await promisify(
readStore.index('byTable').getAll(op.tableId),
)
const toRemove = existing
.filter(
(prev) =>
prev.type === 'updateCell' &&
prev.recordId === op.recordId &&
prev.payload.fieldId === op.payload.fieldId,
)
.map((prev) => prev.id)
const writeTx = db.transaction(STORE_PENDING_OPS, 'readwrite')
const writeStore = writeTx.objectStore(STORE_PENDING_OPS)
for (const id of toRemove) {
writeStore.delete(id)
}
writeStore.put({ ...op, id: genId(), timestamp: Date.now() })
await waitTx(writeTx)
} else {
const tx = db.transaction(STORE_PENDING_OPS, 'readwrite')
const store = tx.objectStore(STORE_PENDING_OPS)
store.put({ ...op, id: genId(), timestamp: Date.now() })
await waitTx(tx)
}
} catch {
// silently fail
}
}
export async function getPendingOps(): Promise<PendingOperation[]> {
try {
const db = await openDB()
const store = db.transaction(STORE_PENDING_OPS, 'readonly').objectStore(STORE_PENDING_OPS)
return await promisify(store.index('byTimestamp').getAll())
} catch {
return []
}
}
export async function getPendingCount(): Promise<number> {
try {
const db = await openDB()
const store = db.transaction(STORE_PENDING_OPS, 'readonly').objectStore(STORE_PENDING_OPS)
return await promisify(store.count())
} catch {
return 0
}
}
export async function removePendingOps(ids: string[]) {
if (ids.length === 0) return
try {
const db = await openDB()
const tx = db.transaction(STORE_PENDING_OPS, 'readwrite')
const store = tx.objectStore(STORE_PENDING_OPS)
for (const id of ids) {
store.delete(id)
}
await waitTx(tx)
} catch {
// silently fail
}
}
export async function syncAllPendingOps(): Promise<number> {
const ops = await getPendingOps()
if (ops.length === 0) return 0
const {
updateCellApi,
batchUpdateCellsApi,
createRecordApi,
deleteRecordApi,
batchUpdateMultiRecordCellsApi,
} = await import('#/api/smart-table')
const cellOpsByTable: Record<string, { recordId: string; fieldId: string; value: unknown; opId: string }[]> = {}
const otherOps: PendingOperation[] = []
for (const op of ops) {
if (op.type === 'updateCell') {
if (!cellOpsByTable[op.tableId]) cellOpsByTable[op.tableId] = []
cellOpsByTable[op.tableId]!.push({
recordId: op.recordId,
fieldId: op.payload.fieldId as string,
value: op.payload.value,
opId: op.id,
})
} else {
otherOps.push(op)
}
}
let synced = 0
for (const [tableId, cellOps] of Object.entries(cellOpsByTable)) {
const byRecord: Record<string, Record<string, unknown>> = {}
for (const cop of cellOps) {
if (!byRecord[cop.recordId]) byRecord[cop.recordId] = {}
byRecord[cop.recordId]![cop.fieldId] = cop.value
}
const recordIds = Object.keys(byRecord)
try {
if (recordIds.length > 1) {
await batchUpdateMultiRecordCellsApi(
tableId,
recordIds.map((rid) => ({ record_id: rid, cells: byRecord[rid]! })),
)
} else if (recordIds.length === 1) {
const rid = recordIds[0]!
const cells = byRecord[rid]!
const fieldIds = Object.keys(cells)
if (fieldIds.length === 1) {
await updateCellApi(rid, fieldIds[0]!, cells[fieldIds[0]!])
} else {
await batchUpdateCellsApi(rid, cells)
}
}
await removePendingOps(cellOps.map((c) => c.opId))
synced += cellOps.length
} catch {
// keep pending ops for retry
}
}
for (const op of otherOps) {
try {
if (op.type === 'createRecord') {
await createRecordApi(op.tableId, op.payload.values)
} else if (op.type === 'deleteRecord') {
await deleteRecordApi(op.recordId)
}
await removePendingOps([op.id])
synced++
} catch {
// keep for retry
}
}
return synced
}
export async function clearOfflineCache(tableId?: string) {
try {
const db = await openDB()
const store = db.transaction(STORE_TABLE_DATA, 'readwrite').objectStore(STORE_TABLE_DATA)
if (tableId) {
await promisify(store.delete(tableId))
} else {
await promisify(store.clear())
}
} catch {
// silently fail
}
}
// ==================== Vue Composable (for component usage with lifecycle) ====================
export function useOfflineCache() {
const isOnline = ref(navigator.onLine)
const pendingCount = ref(0)
const syncing = ref(false)
async function handleOnline() {
isOnline.value = true
await doSync()
}
function handleOffline() {
isOnline.value = false
}
async function refreshCount() {
pendingCount.value = await getPendingCount()
}
async function doSync() {
if (syncing.value || !isOnline.value) return
syncing.value = true
try {
await syncAllPendingOps()
} finally {
syncing.value = false
await refreshCount()
}
}
onMounted(async () => {
window.addEventListener('online', handleOnline)
window.addEventListener('offline', handleOffline)
try {
await openDB()
await refreshCount()
} catch {
// IndexedDB not available
}
})
onUnmounted(() => {
window.removeEventListener('online', handleOnline)
window.removeEventListener('offline', handleOffline)
})
return {
isOnline,
pendingCount,
syncing,
cacheTableData,
getCachedTableData,
addPendingOp,
syncPendingOps: doSync,
clearCache: clearOfflineCache,
refreshCount,
}
}