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