Skip to content

Commit

Permalink
Read excel file in main process, manipulate in renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
FlucTuAteDev committed Aug 7, 2021
1 parent 5d01953 commit 227127a
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 105 deletions.
12 changes: 10 additions & 2 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import Vue from "vue";
import Snackbar from "@/components/Snackbar.vue";
import { ipcRenderer } from "electron";
import { debounce } from "lodash";
import replaceTemplate, { bufferToWorkbook } from "@/utils/sheet-to-xlsx"
export default Vue.extend({
name: "App",
Expand All @@ -59,8 +60,12 @@ export default Vue.extend({
created() {
this.hide = debounce(() => { this.snackbar = false }, this.timeout);
ipcRenderer.on("export-query", () => {
ipcRenderer.send("export-reply", this.$store.state.sheets.sheet)
ipcRenderer.on("export-query", async (_, templateBuffer: Buffer) => {
let workbook = await bufferToWorkbook(templateBuffer)
let template = workbook.getWorksheet("Main")
await replaceTemplate(template, this.$store.state.sheets.sheet)
let outBuffer = await workbook.xlsx.writeBuffer()
ipcRenderer.send("export-reply", outBuffer)
})
ipcRenderer.on("zoom", (event, { zoom }) => {
Expand All @@ -69,6 +74,9 @@ export default Vue.extend({
this.hide();
});
},
destroyed() {
ipcRenderer.removeAllListeners("export-query")
}
});
</script>

Expand Down
Binary file modified src/assets/template.xlsx
Binary file not shown.
5 changes: 1 addition & 4 deletions src/config/appmenu.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { Menu } from "electron"
import { ImportSheet, ExportSheet } from "./sheet-io"
import { ExportSheet } from "./sheet-io"

const template: Electron.MenuItemConstructorOptions[] = [
{
label: "File",
submenu: [{
label: "Import",
click: ImportSheet
}, {
label: "Export",
click: ExportSheet
}]
Expand Down
113 changes: 15 additions & 98 deletions src/config/sheet-io.ts
Original file line number Diff line number Diff line change
@@ -1,103 +1,28 @@
import { dialog, BrowserWindow, ipcMain } from "electron"
import Excel from 'exceljs';
import { Sheet } from "@/model/schedule-sheet";
import _ from "lodash"
import Path from "path"
import { readFileSync, writeFileSync } from "fs";

let sheet: Sheet;
let bindings: Map<string, (...args: any[]) => Excel.CellValue>;
let templatePath = Path.join(__dirname, "../src/assets/template.xlsx");
let exportPath = Path.join(__dirname, "../src/assets/out.xlsx");

export function ImportSheet() {
const path = GetPathFromUser();
if (path) {
ReadFile(path);
}
}
// export function ImportSheet() {
// const path = GetPathFromUser();
// if (path) {
// ReadFile(path);
// }
// }

export function ExportSheet() {
let window = BrowserWindow.getFocusedWindow()!;
window.webContents.send("export-query");
let template = readFileSync(templatePath)
window.webContents.send("export-query", template);
}

ipcMain.on("export-reply", async (event: Event, s: Sheet) => {
sheet = s;
bindings = new Map<string, (...args: any[]) => Excel.CellValue>([
["year", () => sheet.year],
["month", () => sheet.month],
["startDate", () => {
return new Date(sheet.year, sheet.month, 1)
}],
["startsAt", (hour: number) => {
return hour
}]
])
let templatePath = Path.join(__dirname, "../src/assets/template.xlsx");
let exportPath = Path.join(__dirname, "../src/assets/out.xlsx");
await writeFile(templatePath, exportPath);
ipcMain.on("export-reply", (_, outBuffer: Buffer) => {
writeFileSync(exportPath, outBuffer)
})

export async function writeFile(templatePath: string, exportPath: string) {
let workbook = new Excel.Workbook();
await workbook.xlsx.readFile(templatePath)
let template = workbook.getWorksheet("Main");

template.eachRow({ includeEmpty: true }, (row, i) => {
row.eachCell({ includeEmpty: true }, (current, j) => {
current.value = replaceTemplate(current);
});
})

await workbook.xlsx.writeFile(exportPath)
}

let reg = /(?<=\$)\{.*?\}/g;

function replaceTemplate(cell: Excel.Cell) {
if (!cell.value || typeof cell.value !== "string") return cell.value;

let matches = Array.from(cell.value.matchAll(reg)).flat();
let result: Excel.CellValue = cell.value;

for (const match of matches) {
let obj: { [key: string]: any[] } = JSON.parse(jsonify(match));
if (!obj) {
console.error(`Couldn't parse ${match}`);
continue;
}
for (const key in obj) {
let func = bindings.get(key);
if (func) {
let retVal = func(...obj[key]);
if (!retVal) retVal = ""

// If the template is the only thing in the string keep the return type
if (cell.value.length === match.length + 1)
result = retVal;
// If there are other things convert it to a string
else
result = result!.toString().replace(`$${match}`, retVal.toString())
}
}
}

return result
}

// Corrects the incoming string so that it can be parsed by JSON
function jsonify(str: string) {
if (!str) return "{}";

// Trim leading and trailing curly braces
str = str.replace(/^\{?|\}?$/g, "");

let [key, value] = str.split(":", 2);
if (!key) return "{}";
// Trim leading and trailing quotation marks
key = key.replace(/^[\"\']?|[\"\']?$/g, "");

if (value) return `{"${key}": ${value}}`;
else return `{"${key}": []}`;
}

function GetPathFromUser(): string | undefined {
var result = dialog.showOpenDialogSync({
properties: ['openFile'],
Expand All @@ -110,13 +35,5 @@ function GetPathFromUser(): string | undefined {

return path
}
// @ts-ignore
const a = CountStartingTimes(undefined);
return undefined;
}

async function ReadFile(path: string) {
const workbook = new Excel.Workbook();
return await workbook.xlsx.readFile(path);
console.log(workbook.worksheets[0])
}
}
85 changes: 85 additions & 0 deletions src/utils/sheet-to-xlsx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import Excel from 'exceljs';
import { Sheet } from "@/model/schedule-sheet";
import _ from "lodash"
import { CountStartingTimes } from "@/model/aggregates"

type BindingMap = Map<string, (...args: any[]) => Excel.CellValue>

export async function bufferToWorkbook(buffer: Excel.Buffer) {
return await new Excel.Workbook().xlsx.load(buffer)
}

export default async function replaceTemplate(template: Excel.Worksheet, sheet: Sheet) {
let bindings: BindingMap = new Map<string, (...args: any[]) => Excel.CellValue>([
["year", () => sheet.year],
["month", () => sheet.month],
["startDate", () => {
return new Date(sheet.year, sheet.month, 1)
}],
["startsAt", (cell: Excel.Cell, hour: number) => {
let day = Number(cell.col) - 2; // Assuming days start at column B
return CountStartingTimes(sheet).get(hour)?.[day] ?? 0
}]
])

template.eachRow({ includeEmpty: true }, (row, i) => {
row.eachCell({ includeEmpty: true }, (current, j) => {
current.value = replaceTemplateCell(current, bindings);

// In the template the fomula's result is an error so the output file will be broken without this
if (current.formula)
current.value = ({ formula: current.formula, result: 0 } as Excel.CellFormulaValue)
});
})
}

let reg = /(?<=\$)\{.*?\}/g;

function replaceTemplateCell(cell: Excel.Cell, bindings: BindingMap) {
if (!cell.value || typeof cell.value !== "string") return cell.value;

let matches = Array.from(cell.value.matchAll(reg)).flat();
let result: Excel.CellValue = cell.value;

for (const match of matches) {
let obj: { [key: string]: any[] } = JSON.parse(jsonify(match));
if (!obj) {
console.error(`Couldn't parse ${match}`);
continue;
}
for (const key in obj) {
let func = bindings.get(key);
if (func) {
let retVal = func(cell, ...obj[key]);
if (retVal == null) retVal = ""

// If the template is the only thing in the string keep the return type
if (cell.value.length === match.length + 1)
result = retVal;
// If there are other things convert it to a string
else
result = result!.toString().replace(`$${match}`, retVal.toString())
}
}
}

return result
}

// Corrects the incoming string so that it can be parsed by JSON
function jsonify(str: string) {
if (!str) return "{}";

// Trim leading and trailing curly braces
str = str.replace(/^\{?|\}?$/g, "");

let [key, value] = str.split(/\:(.+)/gs).map(x => x.trim());
if (!key) return "{}";
// Trim leading and trailing quotation marks
key = key.replace(/^[\"\']?|[\"\']?$/gm, "");
// Trim leading and trailing square brackets
value = value?.replace(/^\[?|\]?$/gm, "")

if (value) return `{"${key}": [${value}]}`;
else return `{"${key}": []}`;
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "esnext",
"target": "ES2019",
"module": "esnext",
"strict": true,
"jsx": "preserve",
Expand Down

0 comments on commit 227127a

Please sign in to comment.