mirror of https://github.com/kortix-ai/suna.git
fix: luckyviewer formatting
This commit is contained in:
parent
13fefdae77
commit
33739679b0
|
@ -31,28 +31,108 @@ function loadStyle(href: string): void {
|
|||
document.head.appendChild(l);
|
||||
}
|
||||
|
||||
function argbToHex(argb?: string): string | undefined {
|
||||
if (!argb || typeof argb !== 'string') return undefined;
|
||||
const v = argb.replace(/^#/, '');
|
||||
if (v.length === 8) return `#${v.slice(2)}`;
|
||||
if (v.length === 6) return `#${v}`;
|
||||
function argbToHex(argb?: string | number): string | undefined {
|
||||
if (!argb) return undefined;
|
||||
|
||||
if (typeof argb === 'number') {
|
||||
const indexColors: Record<number, string> = {
|
||||
0: '#000000', 1: '#FFFFFF', 2: '#FF0000', 3: '#00FF00',
|
||||
4: '#0000FF', 5: '#FFFF00', 6: '#FF00FF', 7: '#00FFFF',
|
||||
8: '#000000', 9: '#FFFFFF', 10: '#FF0000', 11: '#00FF00',
|
||||
12: '#0000FF', 13: '#FFFF00', 14: '#FF00FF', 15: '#00FFFF',
|
||||
16: '#800000', 17: '#008000', 18: '#000080', 19: '#808000',
|
||||
20: '#800080', 21: '#008080', 22: '#C0C0C0', 23: '#808080',
|
||||
24: '#9999FF', 25: '#993366', 26: '#FFFFCC', 27: '#CCFFFF',
|
||||
28: '#660066', 29: '#FF8080', 30: '#0066CC', 31: '#CCCCFF',
|
||||
32: '#000080', 33: '#FF00FF', 34: '#FFFF00', 35: '#00FFFF',
|
||||
36: '#800080', 37: '#800000', 38: '#008080', 39: '#0000FF',
|
||||
40: '#00CCFF', 41: '#CCFFFF', 42: '#CCFFCC', 43: '#FFFF99',
|
||||
44: '#99CCFF', 45: '#FF99CC', 46: '#CC99FF', 47: '#FFCC99',
|
||||
48: '#3366FF', 49: '#33CCCC', 50: '#99CC00', 51: '#FFCC00',
|
||||
52: '#FF9900', 53: '#FF6600', 54: '#666699', 55: '#969696',
|
||||
56: '#003366', 57: '#339966', 58: '#003300', 59: '#333300',
|
||||
60: '#993300', 61: '#993366', 62: '#333399', 63: '#333333',
|
||||
64: '#000000', 65: '#FFFFFF'
|
||||
};
|
||||
return indexColors[argb] || undefined;
|
||||
}
|
||||
|
||||
const v = String(argb).replace(/^#/, '').toUpperCase();
|
||||
if (v.length === 8) {
|
||||
return `#${v.slice(2)}`;
|
||||
}
|
||||
if (v.length === 6) {
|
||||
return `#${v}`;
|
||||
}
|
||||
if (v.startsWith('FF')) {
|
||||
return `#${v.slice(2)}`;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function mapType(t: string | undefined): string {
|
||||
switch (t) {
|
||||
case 'n':
|
||||
case 'd':
|
||||
case 'b':
|
||||
case 's':
|
||||
case 'n':
|
||||
case 'd':
|
||||
case 'b':
|
||||
case 's':
|
||||
case 'str':
|
||||
case 'e':
|
||||
case 'e':
|
||||
return t;
|
||||
default:
|
||||
return 'g';
|
||||
return 'g';
|
||||
}
|
||||
}
|
||||
|
||||
function convertNumberFormat(fmt?: string): string {
|
||||
if (!fmt) return 'General';
|
||||
const formatMap: Record<string, string> = {
|
||||
'0': '#,##0',
|
||||
'0.00': '#,##0.00',
|
||||
'#,##0': '#,##0',
|
||||
'#,##0.00': '#,##0.00',
|
||||
'$#,##0.00': '$#,##0.00',
|
||||
'0%': '0%',
|
||||
'0.00%': '0.00%',
|
||||
'mm/dd/yyyy': 'MM/DD/YYYY',
|
||||
'dd/mm/yyyy': 'DD/MM/YYYY',
|
||||
'yyyy-mm-dd': 'YYYY-MM-DD',
|
||||
};
|
||||
return formatMap[fmt] || fmt;
|
||||
}
|
||||
|
||||
function extractBorders(borders: any): any {
|
||||
if (!borders) return null;
|
||||
const borderConfig: any = {};
|
||||
|
||||
if (borders.top) {
|
||||
borderConfig.t = {
|
||||
style: borders.top.style === 'thin' ? 1 : 2,
|
||||
color: argbToHex(borders.top.color?.rgb) || '#000000'
|
||||
};
|
||||
}
|
||||
if (borders.bottom) {
|
||||
borderConfig.b = {
|
||||
style: borders.bottom.style === 'thin' ? 1 : 2,
|
||||
color: argbToHex(borders.bottom.color?.rgb) || '#000000'
|
||||
};
|
||||
}
|
||||
if (borders.left) {
|
||||
borderConfig.l = {
|
||||
style: borders.left.style === 'thin' ? 1 : 2,
|
||||
color: argbToHex(borders.left.color?.rgb) || '#000000'
|
||||
};
|
||||
}
|
||||
if (borders.right) {
|
||||
borderConfig.r = {
|
||||
style: borders.right.style === 'thin' ? 1 : 2,
|
||||
color: argbToHex(borders.right.color?.rgb) || '#000000'
|
||||
};
|
||||
}
|
||||
|
||||
return Object.keys(borderConfig).length > 0 ? borderConfig : null;
|
||||
}
|
||||
|
||||
export interface LuckysheetViewerProps {
|
||||
xlsxPath: string;
|
||||
sandboxId?: string;
|
||||
|
@ -89,9 +169,11 @@ export function LuckysheetViewer({ xlsxPath, sandboxId, className, height }: Luc
|
|||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
loadStyle('https://cdn.jsdelivr.net/npm/luckysheet@latest/dist/plugins/css/pluginsCss.css');
|
||||
loadStyle('https://cdn.jsdelivr.net/npm/luckysheet@latest/dist/plugins/plugins.css');
|
||||
loadStyle('https://cdn.jsdelivr.net/npm/luckysheet@latest/dist/css/luckysheet.css');
|
||||
loadStyle('https://cdn.jsdelivr.net/npm/luckysheet@latest/dist/fonts/iconfont.css');
|
||||
|
||||
await loadScript('https://cdn.jsdelivr.net/npm/jquery@3.6.4/dist/jquery.min.js');
|
||||
if (!window.$ && (window as any).jQuery) window.$ = (window as any).jQuery;
|
||||
|
@ -117,7 +199,14 @@ export function LuckysheetViewer({ xlsxPath, sandboxId, className, height }: Luc
|
|||
}
|
||||
|
||||
const XLSX = window.XLSX;
|
||||
const wb = XLSX.read(ab, { type: 'array', cellStyles: true });
|
||||
const wb = XLSX.read(ab, {
|
||||
type: 'array',
|
||||
cellStyles: true,
|
||||
cellDates: true,
|
||||
cellNF: true,
|
||||
sheetStubs: true,
|
||||
raw: false
|
||||
});
|
||||
|
||||
const sheetsForLucky: any[] = [];
|
||||
|
||||
|
@ -132,32 +221,152 @@ export function LuckysheetViewer({ xlsxPath, sandboxId, className, height }: Luc
|
|||
const addr = XLSX.utils.encode_cell({ r, c });
|
||||
const cell = ws[addr];
|
||||
if (!cell) continue;
|
||||
|
||||
const v: any = {
|
||||
v: cell.v,
|
||||
m: (cell.w ?? String(cell.v ?? '')),
|
||||
ct: { t: mapType(cell.t), fa: cell.z || 'General' },
|
||||
ct: {
|
||||
t: mapType(cell.t),
|
||||
fa: convertNumberFormat(cell.z) || 'General'
|
||||
},
|
||||
};
|
||||
const s = (cell as any).s || {};
|
||||
const font = s.font || {};
|
||||
const fill = s.fill || {};
|
||||
const alignment = s.alignment || {};
|
||||
|
||||
if (font.bold) v.bl = 1;
|
||||
if (font.italic) v.it = 1;
|
||||
if (font.sz) v.fs = Number(font.sz);
|
||||
const fc = font.color?.rgb || font.color?.rgbColor || font.color;
|
||||
const bg = fill.fgColor?.rgb || fill.bgColor?.rgb || fill.fgColor || fill.bgColor;
|
||||
const fcHex = argbToHex(typeof fc === 'string' ? fc : undefined);
|
||||
const bgHex = argbToHex(typeof bg === 'string' ? bg : undefined);
|
||||
if (fcHex) v.fc = fcHex;
|
||||
if (bgHex) v.bg = bgHex;
|
||||
|
||||
if (alignment) {
|
||||
if (alignment.horizontal) v.ht = alignment.horizontal;
|
||||
if (alignment.vertical) v.vt = alignment.vertical;
|
||||
if (alignment.wrapText) v.tb = 1;
|
||||
|
||||
const cellStyle = cell.s || {};
|
||||
const isHeader = r === 0;
|
||||
|
||||
if (cellStyle && typeof cellStyle === 'object') {
|
||||
if (cellStyle.fgColor || cellStyle.bgColor || cellStyle.patternType) {
|
||||
let bgColor = null;
|
||||
if (cellStyle.fgColor?.rgb) {
|
||||
bgColor = cellStyle.fgColor.rgb;
|
||||
} else if (cellStyle.bgColor?.rgb) {
|
||||
bgColor = cellStyle.bgColor.rgb;
|
||||
}
|
||||
|
||||
if (bgColor) {
|
||||
bgColor = bgColor.replace(/^#/, '').toUpperCase();
|
||||
if (bgColor.length === 8) {
|
||||
bgColor = '#' + bgColor.slice(2);
|
||||
} else if (bgColor.length === 6) {
|
||||
bgColor = '#' + bgColor;
|
||||
}
|
||||
if (bgColor.startsWith('#') && bgColor.length === 7) {
|
||||
v.bg = bgColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cellStyle.font) {
|
||||
if (cellStyle.font.bold) v.bl = 1;
|
||||
if (cellStyle.font.italic) v.it = 1;
|
||||
if (cellStyle.font.underline) v.un = 1;
|
||||
if (cellStyle.font.strike) v.cl = 1;
|
||||
if (cellStyle.font.sz) v.fs = Math.round(Number(cellStyle.font.sz));
|
||||
if (cellStyle.font.name) v.ff = cellStyle.font.name;
|
||||
if (cellStyle.font.color?.rgb) {
|
||||
let fc = cellStyle.font.color.rgb;
|
||||
fc = fc.replace(/^#/, '').toUpperCase();
|
||||
if (fc.length === 8) {
|
||||
fc = '#' + fc.slice(2);
|
||||
} else if (fc.length === 6) {
|
||||
fc = '#' + fc;
|
||||
}
|
||||
if (fc.startsWith('#') && fc.length === 7) {
|
||||
v.fc = fc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cellStyle.alignment) {
|
||||
const htMap: Record<string, number> = {
|
||||
'left': 1,
|
||||
'center': 0,
|
||||
'right': 2,
|
||||
};
|
||||
const vtMap: Record<string, number> = {
|
||||
'top': 0,
|
||||
'middle': 1,
|
||||
'center': 1,
|
||||
'bottom': 2,
|
||||
};
|
||||
if (cellStyle.alignment.horizontal) {
|
||||
v.ht = htMap[cellStyle.alignment.horizontal] ?? 0;
|
||||
}
|
||||
if (cellStyle.alignment.vertical) {
|
||||
v.vt = vtMap[cellStyle.alignment.vertical] ?? 1;
|
||||
}
|
||||
if (cellStyle.alignment.wrapText) v.tb = 2;
|
||||
}
|
||||
}
|
||||
if (typeof cellStyle === 'number' && wb.Styles) {
|
||||
const xfIndex = cellStyle;
|
||||
if (wb.Styles.CellXf && wb.Styles.CellXf[xfIndex]) {
|
||||
const xf = wb.Styles.CellXf[xfIndex];
|
||||
|
||||
if (xf.fontId !== undefined && wb.Styles.Fonts && wb.Styles.Fonts[xf.fontId]) {
|
||||
const font = wb.Styles.Fonts[xf.fontId];
|
||||
if (font.bold) v.bl = 1;
|
||||
if (font.italic) v.it = 1;
|
||||
if (font.underline) v.un = 1;
|
||||
if (font.strike) v.cl = 1;
|
||||
if (font.sz) v.fs = Math.round(Number(font.sz));
|
||||
if (font.name) v.ff = font.name;
|
||||
if (font.color?.rgb) {
|
||||
let fc = font.color.rgb;
|
||||
fc = fc.replace(/^#/, '').toUpperCase();
|
||||
if (fc.length === 8) {
|
||||
fc = '#' + fc.slice(2);
|
||||
} else if (fc.length === 6) {
|
||||
fc = '#' + fc;
|
||||
}
|
||||
if (fc.startsWith('#') && fc.length === 7) {
|
||||
v.fc = fc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xf.fillId !== undefined && xf.fillId > 0 && wb.Styles.Fills && wb.Styles.Fills[xf.fillId]) {
|
||||
const fill = wb.Styles.Fills[xf.fillId];
|
||||
if (fill.fgColor?.rgb || fill.bgColor?.rgb) {
|
||||
let bg = fill.fgColor?.rgb || fill.bgColor?.rgb;
|
||||
bg = bg.replace(/^#/, '').toUpperCase();
|
||||
if (bg.length === 8) {
|
||||
bg = '#' + bg.slice(2);
|
||||
} else if (bg.length === 6) {
|
||||
bg = '#' + bg;
|
||||
}
|
||||
if (bg.startsWith('#') && bg.length === 7) {
|
||||
v.bg = bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (xf.alignment) {
|
||||
const htMap: Record<string, number> = {
|
||||
'left': 1,
|
||||
'center': 0,
|
||||
'right': 2,
|
||||
};
|
||||
const vtMap: Record<string, number> = {
|
||||
'top': 0,
|
||||
'middle': 1,
|
||||
'center': 1,
|
||||
'bottom': 2,
|
||||
};
|
||||
if (xf.alignment.horizontal) {
|
||||
v.ht = htMap[xf.alignment.horizontal] ?? 0;
|
||||
}
|
||||
if (xf.alignment.vertical) {
|
||||
v.vt = vtMap[xf.alignment.vertical] ?? 1;
|
||||
}
|
||||
if (xf.alignment.wrapText) v.tb = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isHeader) {
|
||||
v.bl = 1;
|
||||
}
|
||||
|
||||
celldata.push({ r, c, v });
|
||||
}
|
||||
}
|
||||
|
@ -173,14 +382,14 @@ export function LuckysheetViewer({ xlsxPath, sandboxId, className, height }: Luc
|
|||
const columnlen: Record<number, number> = {};
|
||||
const cols = ws['!cols'] || [];
|
||||
cols.forEach((col: any, i: number) => {
|
||||
const wpx = col.wpx || (col.wch ? Math.round(col.wch * 7) : undefined);
|
||||
const wpx = col.wpx || (col.wch ? Math.round(col.wch * 7.5) : col.width ? col.width * 7 : undefined);
|
||||
if (wpx) columnlen[i] = wpx;
|
||||
});
|
||||
|
||||
const rowlen: Record<number, number> = {};
|
||||
const rows = ws['!rows'] || [];
|
||||
rows.forEach((row: any, i: number) => {
|
||||
const hpx = row.hpx || (row.hpt ? Math.round(row.hpt * 1.33) : undefined);
|
||||
const hpx = row.hpx || (row.hpt ? Math.round(row.hpt * 1.33) : row.h ? row.h : undefined);
|
||||
if (hpx) rowlen[i] = hpx;
|
||||
});
|
||||
|
||||
|
@ -196,11 +405,29 @@ export function LuckysheetViewer({ xlsxPath, sandboxId, className, height }: Luc
|
|||
order: idx,
|
||||
celldata,
|
||||
config,
|
||||
row: Math.max(range.e.r + 1, 20),
|
||||
column: Math.max(range.e.c + 1, 10),
|
||||
luckysheet_select_save: [],
|
||||
calcChain: [],
|
||||
isPivotTable: false,
|
||||
pivotTable: {},
|
||||
filter_select: {},
|
||||
filter: null,
|
||||
luckysheet_alternateformat_save: [],
|
||||
luckysheet_alternateformat_save_modelCustom: [],
|
||||
luckysheet_conditionformat_save: {},
|
||||
frozen: {},
|
||||
chart: [],
|
||||
zoomRatio: 1,
|
||||
image: [],
|
||||
showGridLines: 1,
|
||||
dataVerification: {}
|
||||
});
|
||||
});
|
||||
|
||||
if (!containerRef.current) return;
|
||||
containerRef.current.innerHTML = '';
|
||||
|
||||
window.luckysheet?.create({
|
||||
container: containerIdRef.current,
|
||||
data: sheetsForLucky,
|
||||
|
@ -208,7 +435,41 @@ export function LuckysheetViewer({ xlsxPath, sandboxId, className, height }: Luc
|
|||
showinfobar: false,
|
||||
showsheetbar: true,
|
||||
allowCopy: true,
|
||||
showConfigWindowResize: true,
|
||||
enableAddRow: false,
|
||||
enableAddBackTop: false,
|
||||
functionButton: true,
|
||||
showRowBar: true,
|
||||
showColumnBar: true,
|
||||
sheetFormulaBar: true,
|
||||
defaultFontSize: 11,
|
||||
allowEdit: false,
|
||||
editMode: false,
|
||||
cellRightClickConfig: {
|
||||
copy: true,
|
||||
copyAs: true,
|
||||
paste: true,
|
||||
insertRow: false,
|
||||
insertColumn: false,
|
||||
deleteRow: false,
|
||||
deleteColumn: false,
|
||||
deleteCell: false,
|
||||
hideRow: false,
|
||||
hideColumn: false,
|
||||
rowHeight: false,
|
||||
columnWidth: false,
|
||||
clear: false,
|
||||
matrix: false,
|
||||
sort: false,
|
||||
filter: false,
|
||||
chart: false,
|
||||
image: false,
|
||||
link: false,
|
||||
data: false,
|
||||
cellFormat: true
|
||||
}
|
||||
});
|
||||
|
||||
if (!disposed) setLoading(false);
|
||||
} catch (e: any) {
|
||||
if (!disposed) {
|
||||
|
|
Loading…
Reference in New Issue