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

在前一篇文章咱们已经学习到了使用Electron来构建咱们的文件浏览器了基础东西了,咱们以前已经完成了界面功能和显示文件或文件夹的功能了,想看以前文章,请点击这个连接  。如今咱们须要在以前的基础上来继续完成余下的功能,咱们以前的只完成了界面和显示文件夹或文件。那么这篇文章咱们须要完成以下功能:javascript

1. 若是它是一个文件夹,咱们能够对该文件夹进行双击,而后打开该文件夹。
2. 当前文件夹就是刚刚咱们双击的那个文件夹。
3. 若是它内部有子文件夹的时候,咱们也能够双击,而后重复第一步的操做步骤。css

那么在完成这些功能以前,咱们先来整理一下咱们的js文件,那么在整理以前,咱们先来看看咱们项目的整个目录架构是一个什么样,这样使咱们更加的清晰。而且了解下咱们各个文件的代码及做用,哪些文件具体作哪些事情的。这有助于咱们更进一步来说解其余方面的知识,使下面讲解的内容更通俗易解。html

下面是咱们的整个目录架构的结构以下:java

|----- 项目的根目录
|  |--- image                # 存放文件夹或文件图标
|  |--- node_modules         # 全部的依赖包
|  |--- .gitignore           # github排除文件
|  |--- app.css              # css文件
|  |--- app.js               # 功能实现代码的文件
|  |--- index.html           # html页面
|  |--- main.js              # electron界面启动代码
|  |--- package.json         

如上就是咱们在第一篇文章中项目目录结构,咱们首先来看下咱们的main.js 代码,该js文件最主要的是 启动electron桌面应用的程序,咱们在package.json已经默认指定了该js文件就是咱们默认要加载的文件。node

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",
  "dependencies": {
    "async": "^3.1.0",
    "fs": "0.0.1-security",
    "osenv": "^0.1.5",
    "path": "^0.12.7"
  }
}

而后咱们来看下咱们的main.js 文件代码,该文件的代码最主要的是 处理electron界面的启动,以下代码所示: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({
    webPreferences: {
      nodeIntegration: true
    }
  });

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

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

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

app.js 代码以下:github

'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();
};

如上app.js 代码就是咱们页面的功能代码,代码看起来有点混乱,所以咱们须要把该文件中的代码分离出来。也就是说以前咱们全部功能性的代码都放在咱们的app.js代码里面,这样之后业务愈来愈复杂的时候,js代码未来会愈来愈臃肿,所以如今咱们须要把该文件的功能性代码逻辑拆分开来。所以咱们须要把它分红三个js文件,app.js, fileSystem.js, userInterface.js.web

app.js 仍是负责入口文件。
fileSystem.js 负责处理对用户计算机中的文件或文件夹进行操做。
userInterface.js 负责处理界面上的交互。shell

所以 fileSystem.js 代码以下:

'use strict';

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

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);
}

module.exports = {
  getUsersHomeFolder,
  getFilesInFolder,
  inspectAndDescribeFiles
};

fileSystem.js文件把咱们以前的app.js中的 getUsersHomeFolder(), getFilesInFolder(), inspectAndDescribeFile(),
及 inspectAndDescribeFiles() 函数分离出来了,而且使用 module.exports 对暴露了 getUsersHomeFolder(), getFilesInFolder(), inspectAndDescribeFiles() 这三个函数。

userInterface.js 文件中的代码以下:

'use strict';

let document;

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 bindDocument (window) {
  if (!document) {
    document = window.document;
  }
}

module.exports = {
  bindDocument,
  displayFiles
};

在userInterface.js中咱们暴露了 bindDocument 和 displayFiles 两个函数,bindDocument该函数的做用是将window.document 上下文传递进去,displayFiles函数的做用是将全部的文件显示出来。

接下来就是咱们的app.js 代码了,该文件须要引入咱们刚刚 fileSystem.js 和 userInterface.js 的两个文件,所以咱们的app.js 文件代码被简化成以下代码:

'use strict';

const fileSystem = require('./fileSystem');
const userInterface = require('./userInterface');

/*
 该函数的做用是:获取到用户我的文件夹的路径,并获取到该文件夹下的文件列表信息
*/
function main() {
  // 把window上下文传递进去
  userInterface.bindDocument(window);

  const folderPath = fileSystem.getUsersHomeFolder();

  fileSystem.getFilesInFolder(folderPath, (err, files) => {
    if (err) {
      console.log('对不起,您没有加载您的home folder');
    }
    fileSystem.inspectAndDescribeFiles(folderPath, files, userInterface.displayFiles);
  });
}

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

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>

最后咱们在咱们的项目根目录中运行 electron . 同样也能够看到以前同样的界面,以下所示:

一:实现文件夹双击功能

那么咱们如上代码重构完成后,咱们如今须要实现咱们对文件夹双击的功能了,那么须要实现该功能的话,咱们须要在 userInterface.js 中添加以下几个函数来处理这些事情。

1. 首先新增一个 displayFolderPath 函数,该函数的做用是更新界面中的当前文件夹路径。
2. 还须要新增 clearView 函数,该函数的做用是:显示在主区域中的当前文件夹中的文件和清除文件夹。
3. 还须要新增一个 loadDirectory函数,该函数的做用是:根据指定文件夹的路径,获取计算机中该路径下的文件或文件夹信息,
并将其显示在应用界面的主区域中。
4. 修改displayFiles函数,该函数的做用是:在文件夹图标上监听事件来触发加载该文件夹中的内容。

所以咱们的 userInterface.js 代码就变成以下了:

'use strict';

let document;

// 引入 fileSystem.js 中的模块代码
const fileSystem = require('./fileSystem');

// 更新当前文件夹路径的函数
function displayFolderPath(folderPath) {
  document.getElementById('current-folder').innerText = folderPath;
}

// 移除 main-area div元素中的内容
function clearView() {
  const mainArea = document.getElementById('main-area');
  let firstChild = mainArea.firstChild;
  while (firstChild) {
    mainArea.removeChild(firstChild);
    firstChild = mainArea.firstChild;
  }
}

// 更新文本框中文件夹路径,而且更新主区域中的内容
function loadDirectory(folderPath) {
  return function (window) {
    if (!document) {
      document = window.document;
    }
    // 更新最上面的文本框中的文件夹路径
    displayFolderPath(folderPath);
    fileSystem.getFilesInFolder(folderPath, (err, files) => {
      // 先清除主区域中的内容
      clearView();
      if (err) {
        throw new Error('sorry, you could not load your folder');
      }
      fileSystem.inspectAndDescribeFiles(folderPath, files, displayFiles);
    });
  }
}

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`;

  // 须要判断若是该文件是目录的话,须要对目录图片绑定双击事件
  if (file.type === 'directory') {
    clone.querySelector('img').addEventListener('dblclick', () => {
      // 咱们双击完成后,就须要加载该文件夹下全部目录的文件
      loadDirectory(file.path)();
    }, false);
  }

  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 bindDocument (window) {
  if (!document) {
    document = window.document;
  }
}

module.exports = {
  bindDocument,
  displayFiles,
  loadDirectory
};

如上就是咱们的 userInterface函数添加的代码,咱们仔细看下也是很是简单的代码,我相信你们都可以理解掉了,首先 displayFolderPath 这个函数,该函数的做用是显示咱们 左上角文本框的路径,好比以下所示的:

而后咱们 clearView 这个函数,该函数的做用是:清除主区域中全部的文件或文件夹。

最后就是 loadDirectory 这个函数,该函数首先会调用displayFolderPath函数,来更新咱们的左上角输入框文件路径。期次就是调用 fileSystem.inspectAndDescribeFiles 函数来从新渲染主区域中的全部文件。

给这个 displayFile 函数,判断当前目录是否是文件夹,若是是文件夹的话,对该文件夹图标绑定了双击事件,双击后咱们又调用了 loadDirectory 函数,从新更新左上角输入框文件夹路径,而且从新渲染主区域中的内容。

如今咱们须要修改app.js 中的代码了,让他调用 userInterface.js 文件中的loadDirectory函数。从新初始化主区域内容,且更新左上角的输入框的文件夹路径。所以咱们的app.js 代码更改为以下所示:

'use strict';

const fileSystem = require('./fileSystem');
const userInterface = require('./userInterface');

/*
 该函数的做用是:获取到用户我的文件夹的路径,并获取到该文件夹下的文件列表信息
*/
function main() {
  // 把window上下文传递进去
  userInterface.bindDocument(window);

  const folderPath = fileSystem.getUsersHomeFolder();

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

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

如上全部的文件更改完成后,咱们如今再来重启咱们的应用程序,在项目中的根目录 运行 electron . 命运后便可重启,当咱们双击应用中的某个文件夹时,就能看到工具条中当前文件夹路径发送改变了,而且该文件夹下全部子目录也会更新了。
首先咱们看下咱们页面初始化的时候,以下所示:

而后当咱们点击我 工做文档 文件夹时候,会看到会更新工具条中的路径,而且子目录也会获得更新了。以下所示:

二:实现快速搜索

如今咱们目录中有不少不少文件及文件夹,可是当咱们想要找某个文件夹的时候,咱们很不方便,所以咱们如今须要一个搜索框,咱们只要搜索下咱们目录下的某个文件就能找到,所以咱们如今须要这么一个功能,所以第一步咱们须要在咱们应用项目中的右上角添加一个搜索框。咱们须要实现以下功能:

1. 在咱们的工具条的右上角添加一个搜索框。
2. 引入一个内存搜索库来对文件或文件夹进行搜索。
3. 将当前文件夹中的文件和文件夹信息加入搜索索引。
4. 用户开始搜索时,会对主区域显示的文件进行过滤。

2.1 在工具条中增长搜索框

首先咱们须要在 index.html 中的 current-folder div元素后面插入以下代码:

<input type="search" id="search" results="5" placeholder="Search" />

所以html部分代码变成以下:

<div id="toolbar">
  <div id="current-folder">
  </div>
  <input type="search" id="search" results="5" placeholder="Search" />
</div>

而后咱们在咱们的 app.css 代码中加入以下样式:

#search {
  float: right;
  padding: 0.5em;
  min-width: 10em;
  border-radius: 3em;
  margin: 2em 1em;
  border: none;
  outline: none;
}

加入后,咱们再来运行下咱们的应用程序,使用命令 electron . ,会看到以下图所示:

2.2 引入一个内存搜索库

上面咱们已经经过html+css在咱们的工具条右侧添加了一个搜索框,如今咱们要作的事情就是经过一个搜索库来对文件或文件夹列表进行搜索。值得幸运的是,网上已经有一款叫 Iunr.js 客户端搜索库了,它支持对文件或文件夹列表进行索引,咱们能够经过索引进行搜索。所以咱们须要在咱们项目中根目录命令行中来安装该模块了,以下npm命令安装:

npm i lunr --save

如今咱们须要在咱们的项目根目录下新建一个叫 search.js 文件,该文件最主要的做用是处理搜索。

所以咱们再来看下咱们整个目录架构变成以下这个样子了:

|----- 项目的根目录
|  |--- image                # 存放文件夹或文件图标
|  |--- node_modules         # 全部的依赖包
|  |--- .gitignore           # github排除文件
|  |--- app.css              # css文件
|  |--- app.js               # 功能实现代码的文件的入口
|  |--- index.html           # html页面
|  |--- main.js              # electron界面启动代码
|  |--- fileSystem.js        # 处理文件操做的js
|  |--- userInterface.js     # 处理应用程序界面的js
|  |--- search.js            # 处理文件搜索的js
|  |--- package.json      

想要了解 lunr 库的使用方法,请看这篇文章(http://www.uedsc.com/lunr-js.html

所以咱们的search.js 代码以下:

'use strict';

// 引入 lunr 包进来
const lunr = require('lunr');

let index;

// 重置搜索的索引函数

function resetIndex() {
  index = lunr(function() {
    this.field('file');
    this.field('type');
    this.ref('path');
  });
}

// 添加对文件的索引,用于后续的搜索
function addToIndex(file) {
  index.add(file);
}

// 对一个指定的文件进行查询
function find(query, cb) {
  if (!index) {
    resetIndex();
  }
  const results = index.search(query);
  cb(results);
}

module.exports = {
  addToIndex,
  find,
  resetIndex
};

如今咱们搜索库已经引入了,而且代码也编写完成后,咱们如今要作的事情就是如何来监听咱们的搜索框的事件了,那么须要使用的'keyup' 事件来监听,所以咱们须要在 userInterface.js 文件中添加以下一个函数,而且把该函数暴露出去。以下代码:

// 监听搜索函数
function bindSearchField(cb) {
  document.getElementById('search').addEventListener('keyup', cb, false);
}

module.exports = {
  bindDocument,
  displayFiles,
  loadDirectory,
  bindSearchField
}

如上代码就是监听搜索框 的keyup的事件了,当咱们每次搜索的时候,鼠标keyup的时候,就会触发一个cb函数,那么触发cb函数的时候,咱们须要获取输入框的值,而后把该值传递进去查询。若是没有值的话,那么不进行文件搜索,若是有值的话,咱们须要进行文件搜索,如今要实现这个搜索,咱们须要完成以下事情:

1. 当咱们的搜索框没有值的时候,确保全部的文件都显示在主区域中。
2. 当搜索框中有值的时候,咱们须要根据该值进行查询及过滤且显示出来。
3. 当搜索到某个文件夹的时候,咱们须要将该文件夹的全部的内容显示在主区域中,而且重置该索引值。
4. 当有新文件要显示在主区域中,须要将它添加到索引中。

所以首先咱们须要在咱们的 userInterface.js 文件中须要引入咱们的search.js ,引入后咱们就能够访问search模块了。

引入完成后,咱们须要改js中的 loadDirectory 函数,该函数的做用咱们以前也讲解过,就是更新左侧文本框的文件路径,而且更新主区域中的内容,所以在该函数内部,咱们每次调用该函数的时候都须要重置搜索索引,这样作的目的是能实现只针对当前文件夹内容进行搜索。所以loadDirectory函数代码改为以下:

// 引入search模块
const search = require('search');

// 更新文本框中文件夹路径,而且更新主区域中的内容
function loadDirectory(folderPath) {
  return function (window) {
    if (!document) {
      document = window.document;
    }

    // 添加剧置搜索索引的函数调用
    search.resetIndex();

    // 更新最上面的文本框中的文件夹路径
    displayFolderPath(folderPath);
    fileSystem.getFilesInFolder(folderPath, (err, files) => {
      // 先清除主区域中的内容
      clearView();
      if (err) {
        throw new Error('sorry, you could not load your folder');
      }
      fileSystem.inspectAndDescribeFiles(folderPath, files, displayFiles);
    });
  }
}

如上更改完成后,咱们须要更改下 displayFile 函数,在该函数中添加以下功能:

1. 把文件添加到搜索索引中的代码。
2. 将文件路径保存在图片元素的data-filePath属性中,这样的话,在文件过滤的时候,咱们能够根据该属性值来过滤或显示元素。

所以 displayFile 函数代码变成以下:

function displayFile(file) {
  const mainArea = document.getElementById('main-area');
  const template = document.querySelector('#item-template');
  // 建立模板实列的副本
  let clone = document.importNode(template.content, true);
  
  // 将文件添加到搜索索引中
  search.addToIndex(file);

  // 将文件路径保存在图片元素的data-filePath属性中
  clone.querySelector('img').setAttribute('data-filePath', file.path);

  // 加入文件名及对应的图标
  clone.querySelector('img').src = `images/${file.type}.svg`;

  // 须要判断若是该文件是目录的话,须要对目录图片绑定双击事件
  if (file.type === 'directory') {
    clone.querySelector('img').addEventListener('dblclick', () => {
      // 咱们双击完成后,就须要加载该文件夹下全部目录的文件
      loadDirectory(file.path)();
    }, false);
  }

  clone.querySelector('.filename').innerText = file.file;

  mainArea.appendChild(clone);
}

如上displayFile函数的做用是把全部的文件夹显示在主区域中,而且绑定了文件夹的双击事件,而且咱们把文件添加到索引中了,而且将文件路径保存到图片元素的 data-filePath属性中。

如今咱们须要新增一个函数用于处理在界面上显示搜索的结果的函数,该函数首先要获取到主区域中显示的文件或文件夹的路径,而后判断该路径是否知足用户在搜索框中条件,若是知足的话,直接过滤掉不知足的条件,显示出来,所以咱们在 userInterface.js文件中后面新增一个函数,好比叫 filterResults函数。

function filterResults(results) {
  // 获取搜索结果中的文件路径用于对比
  const validFilePaths = results.map((result) => {
    return result.ref;
  });
  const items = document.getElementsByClassName('item');
  for (let i = 0; i < items.length; i++) {
    let item = items[i];
    let filePath = item.getElementsByTagName('img')[0].getAttribute('data-filePath');
    // 文件路径匹配搜索结果
    if (validFilePaths.indexOf(filePath) !== -1) {
      item.style = null;
    } else {
      item.style = 'display:none;'; // 若是没有匹配到,则将其掩藏掉 
    }
  }
}

上面函数编写完成后,咱们还须要编写一个函数用于处理重置过滤结果的状况,当咱们搜索框值为空的时候,咱们须要调用该函数来显示文件出来。咱们能够把该函数名叫 resetFilter函数。

function resetFilter() {
  const items = document.getElementsByClassName('item');
  for (let i = 0; i < items.length; i++) {
    items[i].style = null;
  }
}

函数编写完成后,咱们还须要将该两个函数暴露出去,所以代码以下:

module.exports = {
  bindDocument,
  displayFiles,
  loadDirectory,
  bindSearchField,
  filterResults,
  resetFilter
};

userInterface.js 代码已经完成后,咱们如今须要在咱们的app.js上更新代码,如今咱们的app.js文件须要作以下事情:

1. 在界面上须要监听搜索框。
2. 将搜索关键词传给Iunr搜索工具。
3. 将搜索工具处理完的结果显示到界面上。

所以app.js 代码变成以下:

'use strict';

const fileSystem = require('./fileSystem');
const userInterface = require('./userInterface');
// 引入search模块
const search = require('./search');

/*
 该函数的做用是:获取到用户我的文件夹的路径,并获取到该文件夹下的文件列表信息
*/
function main() {
  // 把window上下文传递进去
  userInterface.bindDocument(window);

  const folderPath = fileSystem.getUsersHomeFolder();

  // 更新文本框中文件夹路径,而且更新主区域中的内容, 而且重置搜索索引的函数调用
  userInterface.loadDirectory(folderPath)(window);
  // 监听搜索框值的变化
  userInterface.bindSearchField((event) => {
    const val = event.target.value;
    if (val === '') {
      // 若是搜索框中的值为空的状况下, 重置过滤结果
      userInterface.resetFilter();
    } else {
      /*
       若是搜索框中有值的话,将该值传递到搜索模块的find函数处理并过滤结果显示在界面上
      */
      search.find(val, userInterface.filterResults);
    }
  });
}

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

如今咱们在咱们文件的根目录搜索 tuge 的内容的话,能够看到以下所示的过滤:以下所示:

而后如今咱们把搜索条件清空的话,咱们又能够看到全部的目录了,以下所示:

如今咱们再继续点击 工做文档,进入该目录后,咱们在该目录下继续搜索 18 这样的时候,咱们能够看到以下所示:

咱们接着清空内容后,咱们就能够看到 工做文档 目录下的全部内容了,以下图所示:

三:添加后退功能

如上咱们已经实现了文件或文件夹搜索功能及显示用户文件夹的详细路径,而且还能够双击文件夹进入内部的功能,如今咱们还须要实现后退功能,咱们双击完成后进入文件夹内部,咱们这个时候想后退的话不能后退,所以咱们如今要实现这样的功能。

想实现回退功能,咱们又没有和浏览器那样有后退按钮,所以咱们这边想实现后退功能,咱们能够点击文件夹路径后退便可,也就是说咱们须要实现每一个文件夹路径可点击功能。好比以下图对应的路径点击便可:

实现当前文件夹路径可单击

如上图一串路径,咱们但愿点击某一个路径的时候,但愿切换到对应的文件夹那个地方,而且显示文件夹下的全部内容,就像网页连接同样的。咱们看下咱们左侧的路径的源码能够看到,它是一个div元素显示路径的,以下图所示:

咱们来看下咱们的工具条上显示当前文件夹路径的代码,在咱们的userInterface.js中,有个函数,以下代码:

// 更新当前文件夹路径的函数
function displayFolderPath(folderPath) {
  document.getElementById('current-folder').innerText = folderPath;
}

把路径赋值给id为current-folder元素上,如今咱们须要作的是,再也不是把路径赋值该div元素上,而是但愿把该路径传递给另外一个函数去,而后该函数使用split分隔符分割('/')这样的变成一个数组,而后遍历这个数组,把它对应的文件名使用span标签,也就是每一个文件夹路径使用span标签包围起来,而且在该span标签上设置一个属性,好比叫 data-path 这样的,该属性值就是该文件夹的具体路径,如今咱们须要作这些事情了。

咱们须要接受folderPath路径字符串的函数,好比咱们如今叫 convertFolderPathIntoLinks 这个函数,把它放到咱们的 userInterface.js 中,该函数最主要作的事情就是分割路径,而且把各个路径使用span标签包围起来,所以咱们须要引用path模块进来,

const path = require('path');

在Mac OS 和 linux中,路径分隔符是斜杠(/), 可是在windows中,它是反斜杠(\), 所以咱们须要使用path模块的 path.sep 来获取分隔符。

convertFolderPathIntoLinks函数代码以下:

function convertFolderPathIntoLinks(folderPath) {
  const folders = folderPath.split(path.sep);
  const contents = [];
  let pathAtFolder = '';
  folders.forEach((folder) => {
    pathAtFolder += folder + path.sep;
    const str = `<span class="path" data-path="${pathAtFolder.slice(0, -1)}">${folder}</span>`;
    contents.push(str);
  });
  return contents.join(path.sep).toString();
}

如上函数接收 文件夹的路径做为参数,咱们会根据该路径上的分隔符将其变为一个包含路径上文件名的列表,有该列表,咱们就能够对其中每一个文件夹名建立一个span标签。每一个span标签包含一个名为path的类名以及一个data-path属性保存当前文件夹的路径。最后文件夹的名字以文本的形式包含在span标签中。咱们把全部的span标签存入一个数组 contents中,最后使用 分隔符分割,把数组的内容以字符串的形式返回回来。

咱们以前的 displayFolderPath 函数的代码以下:

// 更新当前文件夹路径的函数
function displayFolderPath(folderPath) {
  document.getElementById('current-folder').innerText = folderPath;
}

如今咱们要把代码改为以下了:

// 更新当前文件夹路径的函数
function displayFolderPath(folderPath) {
  document.getElementById('current-folder').innerHTML = convertFolderPathIntoLinks(folderPath);
}

那么这样的话,咱们的 div中的id为 current-folder 元素就包含span标签了,而且每一个span标签上都有 data-path 这个属性,咱们再来运行下咱们的代码能够看到以下所示:

span标签咱们已经弄好了,咱们如今须要的是再增长一个函数,该函数的做用是用于监听单击文件夹名的操做,并将单击的文件夹路径传递给回调函数,回调函数接收到咱们单击的文件夹路径后,将其传递给负责显示文件夹内容的函数。咱们把该函数名取为:bindCurrentFolderPath. 所以代码添加以下:

function bindCurrentFolderPath() {
  const load = (event) => {
    const folderPath = event.target.getAttribute('data-path');
    loadDirectory(folderPath)();
  }
  const paths = document.getElementsByClassName('path');
  for (var i = 0; i < paths.length; i++) {
    paths[i].addEventListener('click', load, false);
  }
}

// 更新当前文件夹路径的函数
function displayFolderPath(folderPath) {
  document.getElementById('current-folder').innerHTML = convertFolderPathIntoLinks(folderPath);
  // 调用绑定事件
  bindCurrentFolderPath();
}

如上代码实现完成后,咱们再来从新下咱们的运用程序,就能够看到效果了,咱们点击某一个文件夹进去的时候,再点击路径上的某一个文件夹便可返回到上一个页面,就能够看到效果了。

咱们须要添加一个简单的样式,就是说就是当咱们鼠标光标悬停到span元素上的时候,将鼠标光标显示为手状形式。咱们须要在咱们的app.css 添加以下代码:

span.path:hover {
  opacity: 0.7;
  cursor: pointer;
}

四:实现打开其余的文件(好比文本文件,视频及文档等)

咱们以前全部的功能是针对文件夹来作的,如今咱们还要针对文件,图片,视频,文档等这些类型的文件实现点击功能,要实现这些,咱们须要实现单击文件的功能。
所以咱们须要在咱们的 userInterface.js 文件中,在displayFile函数中,添加else代码;以下代码:

function displayFile(file) {
  const mainArea = document.getElementById('main-area');
  const template = document.querySelector('#item-template');
  // 建立模板实列的副本
  let clone = document.importNode(template.content, true);
  
  // 将文件添加到搜索索引中
  search.addToIndex(file);

  // 将文件路径保存在图片元素的data-filePath属性中
  clone.querySelector('img').setAttribute('data-filePath', file.path);

  // 加入文件名及对应的图标
  clone.querySelector('img').src = `images/${file.type}.svg`;

  // 须要判断若是该文件是目录的话,须要对目录图片绑定双击事件
  if (file.type === 'directory') {
    clone.querySelector('img').addEventListener('dblclick', () => {
      // 咱们双击完成后,就须要加载该文件夹下全部目录的文件
      loadDirectory(file.path)();
    }, false);
  } else {
    // 不属于文件夹之外的文件,好比文本文件,文档等
    clone.querySelector('img').addEventListener('dblclick', () => {
      fileSystem.openFile(file.path);
    })
  }

  clone.querySelector('.filename').innerText = file.file;

  mainArea.appendChild(clone);
}

如上代码,咱们的else里面实现了除了文件夹之外的文件也能够进行双击操做,而且在回调函数中咱们调用了 fileSystem.js 模块中的openFile函数,所以咱们须要实现 openFile函数的代码。那么该函数须要调用 shell API。shell API 可以使用系统默认的应用打开URL,文件以及其余类型的文档。所以在fileSystem.js 文件中,咱们须要添加以下代码:

const shell = require('electron').shell;

function openFile(filePath) {
  // 调用shell API的openItem函数
  shell.openItem(filePath);
}

而且导出函数中须要添加 openFile ,以下所示:

module.exports = {
  getUsersHomeFolder,
  getFilesInFolder,
  inspectAndDescribeFiles,
  openFile
};

如今咱们基本功能已经完成了,咱们如今还须要当咱们鼠标移动到文件或文件夹的时候,咱们须要让他变成鼠标的形式,所以咱们须要在咱们的app.css中加以下代码:

img:hover {
  opacity: 0.7;
  cursor: pointer;
}

如今咱们全部的代码已经完成了,咱们接下来从新启动下咱们的应用程序,在项目的根目录中使用命令 electron . 便可打开咱们的应用程序了。咱们能够双击点击不属于文件夹的文件,也可使用咱们的shell默认方式打开文件了。以下所示:

github源码查看

相关文章
相关标签/搜索