Electron – 如何从本地路径获取文件并创建 Blob 文件

huangapple go评论58阅读模式
英文:

Electron - how to fetch a file from a local path and create a blob file

问题

I have this code in my background.js file of an electron app.

ipcMain.on('init', (event, ...args) => {
    fileSelector = dialog.showOpenDialog({
        title: 'Select template',
        buttonLabel: 'Upload file',
        filters: [
            {
                name: 'MS Word document', extensions: ['docx']
            }
        ]
    }).then((filePath) => {
        //fs.readFile(filePath.filePaths[0], (err, data) => {
            event.sender.send('documentPath', filePath.filePaths[0])
        //})
    }).catch((error) => console.warn(error))
})

Basically, I'm opening a native file selector, and I will send the path to the Vue front end of the app.

ipcRenderer.send('init')
ipcRenderer.receive('documentPath', (filePath) => {
    // this.store.docTemplate = new Blob(data, { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }).arrayBuffer()
    fs.readFile(filePath, (error, data) => {
        this.store.docTemplate = data
    })
    console.log(this.store.docTemplate)
})

I'm calling the listener on the ipcRenderer in the mounted hook of Vue.js. What I need to do is to read the file from the received file path and obtain a blob as would normally occur if the desired file is obtained using an <input type="file">. I need to pass the blob to this library, so is there a way to accomplish this? I've tried enabling node integration, but this causes problems with IPC processes.

Thank you for the help.

英文:

I have this code in my background.js file of an electron app.

ipcMain.on(&#39;init&#39;, (event, ...args) =&gt; {
    fileSelector = dialog.showOpenDialog({
        title: &#39;Select template&#39;,
        buttonLabel: &#39;Upload file&#39;,
        filters: [
            {
                name: &#39;MS Word document&#39;, extensions: [&#39;docx&#39;]
            }
        ]
    }).then( (filePath) =&gt; {
        //fs.readFile(filePath.filePaths[0], (err, data) =&gt; {
            event.sender.send(&#39;documentPath&#39;, filePath.filePaths[0])
        //})
    }).catch( (error) =&gt; console.warn(error) )
})

Basically I'm opening a native file selector and I will send the path on the vue front end of the app

		ipcRenderer.send(&#39;init&#39;)
		ipcRenderer.receive(&#39;documentPath&#39;, (filePath) =&gt; {
			//this.store.docTemplate = new Blob(data, { type: &#39;application/vnd.openxmlformats-officedocument.wordprocessingml.document&#39; }).arrayBuffer()
			fs.readFile(filePath, (error, data) =&gt; {
				this.store.docTemplate = data
			})
			console.log(this.store.docTemplate)
		})

I'm calling the listener on the ipcRenderer in the mounted hook of vuejs. What I need to do is to read the file from the received file path and obtain a blob like will occur normally if the desired file is obtained using an &lt;input type=&quot;file&quot;&gt;. I need to pass the blob to this library, so is there a way to accomplish this? I've tried to enable the node integration, but this will give me problems with ipc processes.

Thank you for the help.

答案1

得分: 1

以下是翻译好的部分:

经过评论讨论和本地测试,似乎 easy-template-x 不支持浏览器中的 ArrayBuffer 对象,尽管 README 中有提到支持。

但这不是问题,因为您可以完全在主线程中处理模板。这是应该完成这种工作的地方,这样您的渲染器可以执行更多的渲染工作。尽量充分利用第二个进程。JavaScript 只有一个线程,所以有两个进程是真正的奢侈!

我修改了您的示例代码,以展示如何完成这项工作,并使用了 async/await 使一切更加可读。您可能需要在周围添加一些更多的检查,但对我来说,使用 easy-template-x@3.1.0 完全没有问题。

我还将您的 send/on/receive 结构更改为更简单的结构,使用 ipcRenderer.invoke 配合 ipcMain.handle。优点是主进程可以返回对调用的响应,这允许某种形式的双向通信。有关更多信息,请参阅Electron 文档的此页

main.js

const { readFile, writeFile } = require("fs/promises");
const { TemplateHandler } = require("easy-template-x");

ipcMain.handle("processTemplate", async (event, args) => {
  const loadDialog = await dialog.showOpenDialog({
    title: "Select template",
    buttonLabel: "Upload file",
    filters: [
      {
        name: "MS Word document",
        extensions: ["docx"],
      },
    ],
  });

  const selected = loadDialog.filePaths[0];

  if (selected) {
    const file = await readFile(selected);
  
    const handler = new TemplateHandler();
    const doc = await handler.process(file, params);
  
    const saveDialog = await dialog.showSaveDialog({
      title: "Save document",
      buttonLabel: "Save document",
      filters: [
        {
          name: "MS Word document",
          extensions: ["docx"],
        },
      ],
    });
    
    if (saveDialog.filePath) {
      await writeFile(saveDialog.filePath, doc);
    }
  }
});

preload.js

const { contextBridge, ipcRenderer } = require("electron");

contextBridge.exposeInMainWorld("templates", {
  processTemplate: (params) => ipcRenderer.invoke("processTemplate", params),
});

renderer.js

async function process() {
  await window.templates.processTemplate(templateParams);
}

如果对该代码有任何不清楚之处或有更多问题,请随时提问!

英文:

After discussion in the comments and some local testing, it has become apparent that easy-template-x does not seem to support ArrayBuffer objects in the browser, despite the README saying it does.

However, that is not a problem because you can completely handle the template in your main thread. That's where this kind of stuff should be done anyways so that your renderers can do more renderer stuff. It's best to take advantage of that second process as much as possible. JS has only a single thread, so having two processes is a real luxury!

I've modified your example code to show how that would be done and used async/await to make everything more readable. You might need to add some more checks around that, but it worked just fine for me using `easy-template-x@3.1.0

I've also changed your send/on/receive structure to a simpler one that uses ipcRenderer.invoke paired with ipcMain.handle The advantage is that the main process can return a response to the call, which allows some form of bidirectional communication. For more info, see this page of the Electron docs

main.js

const { readFile, writeFile } = require(&quot;fs/promises&quot;);
const { TemplateHandler } = require(&quot;easy-template-x&quot;);

ipcMain.handle(&quot;processTemplate&quot;, async (event, args) =&gt; {
  const loadDialog = await dialog.showOpenDialog({
    title: &quot;Select template&quot;,
    buttonLabel: &quot;Upload file&quot;,
    filters: [
      {
        name: &quot;MS Word document&quot;,
        extensions: [&quot;docx&quot;],
      },
    ],
  });

  const selected = loadDialog.filePaths[0];

  if (selected) {
    const file = await readFile(selected);
  
    const handler = new TemplateHandler();
    const doc = await handler.process(file, params);
  
    const saveDialog = await dialog.showSaveDialog({
      title: &quot;Save document&quot;,
      buttonLabel: &quot;Save document&quot;,
      filters: [
        {
          name: &quot;MS Word document&quot;,
          extensions: [&quot;docx&quot;],
        },
      ],
    });
    
    if (saveDialog.filePath) {
      await writeFile(saveDialog.filePath, doc);
    }
  }
});

preload.js

const { contextBridge, ipcRenderer } = require(&quot;electron&quot;);
contextBridge.exposeInMainWorld(&quot;templates&quot;, {
processTemplate: (params) =&gt; ipcRenderer.invoke(&quot;processTemplate&quot;, params),
});

renderer.js

async function process() {
  await window.templates.processTemplate(templateParams);
}

If anything about that code is unclear or you have more questions, feel free to ask!

huangapple
  • 本文由 发表于 2023年5月24日 21:22:09
  • 转载请务必保留本文链接:https://go.coder-hub.com/76324025.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定