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源碼查看

相關文章
相關標籤/搜索