diff --git a/src/components/Configure/Configure.js b/src/components/Configure/Configure.js index ef1874f..9be5f39 100644 --- a/src/components/Configure/Configure.js +++ b/src/components/Configure/Configure.js @@ -107,6 +107,15 @@ function Configure(props) { props.changeSettings(true); } + function toggleSheetIsImageHandler(sheetIdx, colIdx) { + console.log('[Configure.js] toggleSheetIsImageHandler', sheetIdx); + const meta = props.meta; + const sheet = meta[sheetIdx]; + sheet.columns[colIdx].isImage = !sheet.columns[colIdx].isImage; + props.updateMeta(meta); + props.changeSettings(true); + } + function array_move(arr, old_index, new_index) { if (new_index >= arr.length) { var k = new_index - arr.length + 1; @@ -204,7 +213,7 @@ function Configure(props) { tabs={tabs} >
{ tab === 0 ? : null } - { tab === 1 ? : null } + { tab === 1 ? : null } { tab === 2 ? : null }
diff --git a/src/components/Configure/SelectColumns/SelectColumns.js b/src/components/Configure/SelectColumns/SelectColumns.js index 325d4f9..8156ee6 100644 --- a/src/components/Configure/SelectColumns/SelectColumns.js +++ b/src/components/Configure/SelectColumns/SelectColumns.js @@ -16,6 +16,7 @@ function SelectColumns(props) { colSelect={props.colSelect} changeName={props.changeName} changeOrder={props.changeOrder} + toggleIsImage={props.toggleIsImage} /> ); diff --git a/src/components/Configure/SelectColumns/Sheet/Column/Column.js b/src/components/Configure/SelectColumns/Sheet/Column/Column.js index 51a2f94..99ae4a3 100644 --- a/src/components/Configure/SelectColumns/Sheet/Column/Column.js +++ b/src/components/Configure/SelectColumns/Sheet/Column/Column.js @@ -17,7 +17,7 @@ const useStyles = makeStyles(theme => ({ alignItems: 'center', }, column: { - flexBasis: '50%', + flexBasis: '33%', }, label: { display: 'block', @@ -106,6 +106,16 @@ function Column(props) { props.changeOrder(value)} className={classes.stepper} /> +
+
+ + + +
+
diff --git a/src/components/Configure/SelectColumns/Sheet/Sheet.js b/src/components/Configure/SelectColumns/Sheet/Sheet.js index 5423f6c..38d9d70 100644 --- a/src/components/Configure/SelectColumns/Sheet/Sheet.js +++ b/src/components/Configure/SelectColumns/Sheet/Sheet.js @@ -11,8 +11,10 @@ function Sheets(props) { key={col.index} name={col.name} rename={col.changeName} + isImage={col.isImage} selected={col.selected} select={() => props.colSelect(props.id, index)} + toggleIsImage={() => props.toggleIsImage(props.id, index)} changeName={(name) => props.changeName(props.id, index, name)} cols={props.cols} changeOrder={(newPos) => props.changeOrder(props.id, index, newPos)} diff --git a/src/components/func/func.js b/src/components/func/func.js index 0eb67c7..4ac3bbe 100644 --- a/src/components/func/func.js +++ b/src/components/func/func.js @@ -1,4 +1,5 @@ import XLSX from 'xlsx'; +import ExcelJS from 'exceljs'; import { saveAs } from 'file-saver'; // Declare this so our linter knows that tableau is a global object @@ -76,6 +77,7 @@ const getSheetColumns = (sheet, existingCols, modified) => new Promise((resolve, col.dataType = columns[j].dataType; col.changeName = null; col.selected = false; + col.isImage = columns[j].isImage; cols.push(col); } for (var i = 0; i < existingCols.length; i++) { @@ -93,6 +95,7 @@ const getSheetColumns = (sheet, existingCols, modified) => new Promise((resolve, ret.selected = existingCols[eIdx].selected; ret.changeName = existingCols[eIdx].changeName; ret.order = eIdx; + ret.isImage = existingCols[eIdx].isImage; } else { ret.order = maxPos; maxPos += 1; @@ -106,6 +109,7 @@ const getSheetColumns = (sheet, existingCols, modified) => new Promise((resolve, newCol.name = columns[k].fieldName; newCol.dataType = columns[k].dataType; newCol.selected = true; + newCol.isImage = columns[k].isImage; newCol.order = k + 1; cols.push(newCol); } @@ -200,25 +204,54 @@ const revalidateMeta = (existing) => new Promise((resolve, reject) => { }); }); -const exportToExcel = (meta, env, filename) => new Promise((resolve, reject) => { - let xlsFile = "export.xlsx"; - if (filename && filename.length > 0) { - xlsFile = filename + ".xlsx"; - } - buildExcelBlob(meta).then(wb => { - // add ignoreEC:false to prevent excel crashes during text to column - var wopts = { bookType:'xlsx', bookSST:false, type:'array', ignoreEC:false }; - var wbout = XLSX.write(wb,wopts); - saveAs(new Blob([wbout],{type:"application/octet-stream"}), xlsFile); - resolve(); +const exportToExcel = async (meta, env, filename) => + new Promise(async (resolve, reject) => { + let xlsFile = "export.xlsx"; + if (filename && filename.length > 0) { + xlsFile = filename + ".xlsx"; + } + if (hasEmbedImage(meta)) { + const workbook = new ExcelJS.Workbook(); + await buildExcelBlobWithEmbedImage(workbook, meta); + const buffer = await workbook.xlsx.writeBuffer(); + saveAs( + new Blob([buffer], { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }), + xlsFile + ); + resolve(); + } else { + buildExcelBlob(meta).then(wb => { + // add ignoreEC:false to prevent excel crashes during text to column + var wopts = { bookType:'xlsx', bookSST:false, type:'array', ignoreEC:false }; + var wbout = XLSX.write(wb,wopts); + saveAs(new Blob([wbout],{type:"application/octet-stream"}), xlsFile); + resolve(); + }); + }; }); -}); +const hasEmbedImage = (meta) => { + let hasImage = false; + console.log("[func.js] Checking for images in meta", meta); + meta.forEach((sheet) => { + if (sheet && sheet.columns) { + sheet.columns.forEach((col) => { + console.log("[func.js] Checking column", col); + if (col && col.isImage) { + hasImage = true; + } + }); + } + }); + return hasImage; +}; // krisd: move excel creation to caller (to support extra export to methodss) // callback receives a blob to save or transfer -const buildExcelBlob = (meta) => new Promise((resolve, reject) => { +const buildExcelBlob = async (meta) => new Promise((resolve, reject) => { console.log("[func.js] Got Meta", meta); // func.saveSettings(meta, function(newSettings) { // console.log("Saved settings", newSettings); @@ -283,6 +316,84 @@ const buildExcelBlob = (meta) => new Promise((resolve, reject) => { }); }); + const buildExcelBlobWithEmbedImage = async (workbook, meta) => { + console.log("[func.js] buildExcelBlob: Got Meta", meta); + const worksheets = tableau.extensions.dashboardContent.dashboard.worksheets; + + for (const sheetMeta of meta) { + // 選択されていないシートはスキップ + if (!sheetMeta.selected) continue; + + let tabName = sheetMeta.changeName || sheetMeta.sheetName; + tabName = tabName.replace(/[*?/\\[\]]/gi, ''); + + const tableauSheet = worksheets.find(s => s.name === sheetMeta.sheetName); + if (!tableauSheet) continue; + + const summaryData = await tableauSheet.getSummaryDataAsync({ ignoreSelection: true }); + const columns = summaryData.columns.map(col => { + const cMeta = sheetMeta.columns.find(x => x.name === col.fieldName) || {}; + return { + ...col, + selected: cMeta.selected, + outputName: cMeta.changeName || cMeta.name, + isImage: cMeta.isImage || false // 画像列フラグ + }; + }); + + // 実データ部分をdecode + const decodedRows = await decodeDataset(columns, summaryData.data); + + const newSheet = workbook.addWorksheet(tabName); + + // まずはヘッダー行を設定(画像列含む全選択カラムのheaderを準備) + const selectedCols = columns.filter(c => c.selected); + const headers = selectedCols.map(c => c.outputName); + newSheet.addRow(headers); // 1行目にヘッダー + + for (let rIndex = 0; rIndex < decodedRows.length; rIndex++) { + const rowObj = decodedRows[rIndex]; + const rowValues = []; + selectedCols.forEach((col) => { + if (!col.isImage) { + rowValues.push(rowObj[col.outputName]?.v ?? null); + } else { + rowValues.push(null); + } + }); + + const newRow = newSheet.addRow(rowValues); + + await Promise.all(selectedCols.map(async (col, colIndex) => { + if (!col.isImage) return; + + const cellData = rowObj[col.outputName]; + const imageUrl = cellData?.v; + if (!imageUrl) return; + + try { + const resp = await fetch(imageUrl); + const arrayBuf = await resp.arrayBuffer(); + const imageId = workbook.addImage({ + buffer: arrayBuf, + extension: 'jpeg' + }); + newSheet.addImage(imageId, { + tl: { col: colIndex, row: (1 + (rIndex + 1)) }, + ext: { width: 50, height: 50 }, + editAs: 'oneCell' + }); + } catch (err) { + console.warn('Image fetch error:', err); + newRow.getCell(colIndex + 1).value = 'Image Error'; + } + })); + } + + const headerRow = newSheet.getRow(1); + headerRow.font = { bold: true }; + } + }; // krisd: Remove recursion to work with larger data sets // and translate cell data types