Electron构建一个文件浏览器应用(一)

在window、mac、linux系统中,他们都有一个共同之处就是以文件夹的形式来组织文件的。而且都有各自的组织方式,以及都有如何查询和显示哪些文件给用户的方法。那么从如今开始咱们来学习下如何使用Electron来构建文件浏览器这么一个应用。javascript

注意:我也是经过看书,看资料来学习的。这不重要,重要的是咱们学到东西。咱们知道如何使用 electron 来作一个桌面型应用软件。有这些知识点后,之后咱们作其余的桌面型应用软件会有基础。css

那么既然是文件浏览器,那么咱们能够给文件浏览器取一个名字,假如叫他为 FileBrowser. 那么该文件浏览器要具有以下功能:html

1. 用户能够浏览文件夹和查找文件。
2. 用户可使用默认的应用程序打开文件。java

Electron应用它是以一个js文件做为入口文件的,因此呢咱们须要和以前一篇文章讲的同样,咱们须要看下有以下目录结构:node

|------- FileBrowser
|  |--- main.js
|  |--- index.html
|  |--- package.json

package.json 目前的代码以下:linux

{
  "name": "electron-filebrowser",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

main.js 基本代码以下(和第一篇文章的实现hello world代码是同样的):git

'use strict';

// 引入 全局模块的 electron模块
const electron = require('electron');

// 建立 electron应用对象的引用

const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

// 定义变量 对应用视窗的引用 
let mainWindow = null;

// 监听视窗关闭的事件(在Mac OS 系统下是不会触发该事件的)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// 将index.html 载入应用视窗中
app.on('ready', () => {
  /*
   建立一个新的应用窗口,并将它赋值给 mainWindow变量。
  */
  mainWindow = new BrowserWindow();

  // 载入 index.html 文件
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // 当应用被关闭的时候,释放 mainWindow变量的引用
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

而后咱们的index.html 代码以下:github

<html>
  <head>
    <title>FileBrowser</title>
  </head>
  <body>
    <h1>welcome to FileBrowser</h1>
  </body>
</html>

而后咱们在项目的根目录下 运行 electron . 命运,便可打开应用窗口,以下所示:web

如今咱们须要实现以下这个样子的;以下所示:npm

1. 经过Node.js找到用户我的文件夹所在的路径

想要显示用户我的文件夹的路径,咱们先得想办法获取到该路径,而且要支持window、mac、及linux系统。在mac系统中,用户我的文件夹在 /User/<username> , 这里的username是用户名(我这边是 /User/tugenhua), 在linux系统中,用户的我的文件夹位于 /home/<username>. 在window10中,则位于C盘的 /User/<username>. 所以不一样的操做系统它处于的位置不一样。

在Node.js 中有一个叫 osenv 模块便可解决如上不一样位置的问题,有个函数 osenv.home()能够返回用户我的文件夹。
要使用该模块,咱们能够先进行安装,固然咱们也要安装fs模块,须要对文件操做,所以以下命令安装:

npm install osenv fs --save

所以咱们如今须要在 main.js 中加上该模块的代码,最终main.js 变成以下代码:

'use strict';

// 引入 全局模块的 electron模块
const electron = require('electron');

// 在应用中加载node模块
const fs = require('fs');
const osenv = require('osenv');

function getUsersHomeFolder() {
  return osenv.home();
}
// 使用 fs.readdir 来获取文件列表
function getFilesInFolder(folderPath, cb) {
  fs.readdir(folderPath, cb);
}
/*
 该函数的做用是:获取到用户我的文件夹的路径,并获取到该文件夹下的文件列表信息
*/
function main() {
  const folderPath = getUsersHomeFolder();
  getFilesInFolder(folderPath, (err, files) => {
    if (err) {
      console.log('对不起,您没有加载您的home folder');
    }
    files.forEach((file) => {
      console.log(`${folderPath}/${file}`);
    });
  });
}

main();

// 建立 electron应用对象的引用

const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

// 定义变量 对应用视窗的引用 
let mainWindow = null;

// 监听视窗关闭的事件(在Mac OS 系统下是不会触发该事件的)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// 将index.html 载入应用视窗中
app.on('ready', () => {
  /*
   建立一个新的应用窗口,并将它赋值给 mainWindow变量。
  */
  mainWindow = new BrowserWindow();

  // 载入 index.html 文件
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // 当应用被关闭的时候,释放 mainWindow变量的引用
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

而后咱们继续在命令行中运行:electron . ,而后咱们会看到以下效果:

如今咱们已经知道了如何获取用户我的文件夹下的文件列表了。如今咱们要考虑的问题是:如何获取文件名及文件类型(是文件仍是文件夹)。并将他们以不一样的图标在界面上显示出来。

如上代码咱们已经获取到了文件列表,如今咱们能够以文件列表做为参数,将它传递给Node.js文件系统的API中的另外一个函数,
该函数要作的事情是:可以识别是文件仍是文件夹以及他们的名字和完整的路径。要完成这些事情,咱们要作以下三件事:

1. 使用 fs.stat函数。来读取文件状态。
2. 使用 async模块来处理调用一系列异步函数的状况并收集他们的结果。
3. 将结果列表传递给另外一个函数将他们显示出来。

所以咱们首先要安装 async 模块,安装命令以下:

npm install async --save

所以咱们的main.js 继续添加代码,代码变成以下:

'use strict';

// 引入 全局模块的 electron模块
const electron = require('electron');

// 在应用中加载node模块
const fs = require('fs');
const osenv = require('osenv');

// 引入 aysnc模块
const async = require('async');
// 引入path模块
const path = require('path');

function getUsersHomeFolder() {
  return osenv.home();
}
// 使用 fs.readdir 来获取文件列表
function getFilesInFolder(folderPath, cb) {
  fs.readdir(folderPath, cb);
}

function inspectAndDescribeFile(filePath, cb) {
  let result = {
    file: path.basename(filePath),
    path: filePath,
    type: ''
  };
  fs.stat(filePath, (err, stat) => {
    if (err) {
      cb(err);
    } else {
      if (stat.isFile()) { // 判断是不是文件
        result.type = 'file';
      }
      if (stat.isDirectory()) { // 判断是不是目录
        result.type = 'directory';
      }
      cb(err, result);
    }
  });
}

function inspectAndDescribeFiles(folderPath, files, cb) {
  // 使用 async 模块调用异步函数并收集结果
  async.map(files, (file, asyncCB) => {
    const resolveFilePath = path.resolve(folderPath, file);
    inspectAndDescribeFile(resolveFilePath, asyncCB);
  }, cb);
}

// 该函数的做用是显示文件列表信息
function displayFiles(err, files) {
  if (err) {
    return alert('sorry, we could not display your files');
  }
  files.forEach((file) => {
    console.log(file);
  });
}


/*
 该函数的做用是:获取到用户我的文件夹的路径,并获取到该文件夹下的文件列表信息
*/
function main() {
  const folderPath = getUsersHomeFolder();
  getFilesInFolder(folderPath, (err, files) => {
    if (err) {
      console.log('对不起,您没有加载您的home folder');
    }
    /*
    files.forEach((file) => {
      console.log(`${folderPath}/${file}`);
    });
    */
    inspectAndDescribeFiles(folderPath, files, displayFiles);
  });
}

main();

// 建立 electron应用对象的引用

const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

// 定义变量 对应用视窗的引用 
let mainWindow = null;

// 监听视窗关闭的事件(在Mac OS 系统下是不会触发该事件的)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// 将index.html 载入应用视窗中
app.on('ready', () => {
  /*
   建立一个新的应用窗口,并将它赋值给 mainWindow变量。
  */
  mainWindow = new BrowserWindow();

  // 载入 index.html 文件
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // 当应用被关闭的时候,释放 mainWindow变量的引用
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

保存完该 main.js 后,咱们接着运行 electron . 命令便可在命令行窗口打印出 对象 {file: '', path: '', type: '' }这样的了,以下所示:

2. 视觉上显示文件和文件夹

在如上main.js 代码中,咱们在该文件中有个函数 displayFiles ,咱们能够继续在该函数内部处理将文件名以及对应的图标展现在界面上。因为要显示的文件比较多,所以咱们会将每一个文件定义一套模板,而后为每一个文件建立一个该模板的实列再渲染到界面上。

首先咱们在index.html文件中添加html模板,模板中包含一个div元素,在其中包含了要显示的文件信息,所以index.html代码变成以下:

<html>
  <head>
    <title>FileBrowser</title>
    <link rel="stylesheet" href="./app.css" />
  </head>
  <body>
    <template id="item-template">
      <div class="item">
        <img class='icon' />
        <div class="filename"></div>
      </div>
    </template>
    <div id="toolbar">
      <div id="current-folder">
      </div>
    </div>
    <!-- 该div元素是用来放置要显示的文件列表信息-->
    <div id="main-area"></div>
    <script src="./app.js" type="text/javascript"></script>
  </body>
</html>

如上代码 template模板元素的做用是:为每个渲染的文件信息定义一套HTML模板,真正被渲染到 id为 main-area 元素上,它会将用户我的文件夹中的每一个文件信息都显示出来。所以下面咱们须要在咱们的main.js中添加一些代码,用来建立模板实列并添加到界面上。为了把main.js 启动代码和业务代码分开,所以咱们再新建一个app.js,app.js 代码以下:

'use strict';

// 在应用中加载node模块
const fs = require('fs');
const osenv = require('osenv');

// 引入 aysnc模块
const async = require('async');
// 引入path模块
const path = require('path');

function getUsersHomeFolder() {
  return osenv.home();
}
// 使用 fs.readdir 来获取文件列表
function getFilesInFolder(folderPath, cb) {
  fs.readdir(folderPath, cb);
}

function inspectAndDescribeFile(filePath, cb) {
  let result = {
    file: path.basename(filePath),
    path: filePath,
    type: ''
  };
  fs.stat(filePath, (err, stat) => {
    if (err) {
      cb(err);
    } else {
      if (stat.isFile()) { // 判断是不是文件
        result.type = 'file';
      }
      if (stat.isDirectory()) { // 判断是不是目录
        result.type = 'directory';
      }
      cb(err, result);
    }
  });
}

function inspectAndDescribeFiles(folderPath, files, cb) {
  // 使用 async 模块调用异步函数并收集结果
  async.map(files, (file, asyncCB) => {
    const resolveFilePath = path.resolve(folderPath, file);
    inspectAndDescribeFile(resolveFilePath, asyncCB);
  }, cb);
}

function displayFile(file) {
  const mainArea = document.getElementById('main-area');
  const template = document.querySelector('#item-template');
  // 建立模板实列的副本
  let clone = document.importNode(template.content, true);
  
  // 加入文件名及对应的图标
  clone.querySelector('img').src = `images/${file.type}.svg`;
  clone.querySelector('.filename').innerText = file.file;

  mainArea.appendChild(clone);
}

// 该函数的做用是显示文件列表信息
function displayFiles(err, files) {
  if (err) {
    return alert('sorry, we could not display your files');
  }
  files.forEach(displayFile);
}

/*
 该函数的做用是:获取到用户我的文件夹的路径,并获取到该文件夹下的文件列表信息
*/
function main() {
  const folderPath = getUsersHomeFolder();

  getFilesInFolder(folderPath, (err, files) => {
    if (err) {
      console.log('对不起,您没有加载您的home folder');
    }
    console.log(files);
    /*
    files.forEach((file) => {
      console.log(`${folderPath}/${file}`);
    });
    */
    inspectAndDescribeFiles(folderPath, files, displayFiles);
  });
}

window.onload = function() {
  main();
};

而后main.js 代码以下:

'use strict';

// 引入 全局模块的 electron模块
const electron = require('electron');

// 建立 electron应用对象的引用

const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

// 定义变量 对应用视窗的引用 
let mainWindow = null;

// 监听视窗关闭的事件(在Mac OS 系统下是不会触发该事件的)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

// 将index.html 载入应用视窗中
app.on('ready', () => {
  /*
   建立一个新的应用窗口,并将它赋值给 mainWindow变量。
  */
  mainWindow = new BrowserWindow({
    webPreferences: {
      nodeIntegration: true
    }
  });

  // 添加以下代码 能够调试
  mainWindow.webContents.openDevTools();

  // 载入 index.html 文件
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // 当应用被关闭的时候,释放 mainWindow变量的引用
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

如上代码是目前全部的代码了,咱们运行下 electron . 命令后,能够看到以下所示:

如上图能够看到咱们的代码有调试代码了,那是由于在main.js加上了以下这句代码:

// 添加以下代码 能够调试
mainWindow.webContents.openDevTools();

而且若是咱们按照以前的代码,在main.js 实列化 BrowserWindow 的时候,以下实列化代码:

mainWindow = new BrowserWindow();

以下代码:

// 将index.html 载入应用视窗中
app.on('ready', () => {
  /*
   建立一个新的应用窗口,并将它赋值给 mainWindow变量。
  */
  mainWindow = new BrowserWindow();

  // 添加以下代码 能够调试
  mainWindow.webContents.openDevTools();

  // 载入 index.html 文件
  mainWindow.loadURL(`file://${__dirname}/index.html`);

  // 当应用被关闭的时候,释放 mainWindow变量的引用
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

在控制台中会报以下的错:

解决的方案就是加上以下配置:

mainWindow = new BrowserWindow({
  webPreferences: {
    nodeIntegration: true
  }
});

这是由于最新的electron@5.0系列中,这个nodeIntegration参数,默认改为false了。
而在之前版本的electron中,这个nodeIntegration参数,默认为true。

github源码查看

相关文章
相关标签/搜索