构建公共Google Drive文件夹的网络视图(附代码)

368 阅读6分钟

我正在做一个项目,以帮助当地的倡议,作为这项工作的一部分,我需要研究创建一个漂亮的方式来显示、提供等存储在Google Drive的文件。Google Drive可以让你公开一个文件夹,而且说实话,这个界面并不难用。我有一个文件夹,你可以自己打开drive.google.com/drive/folde…如果你不想点击,这里是它的样子。

这是一个相当简单的界面。你可以得到漂亮的缩略图,你可以查看几乎任何种类的文件,而且你可以下载它。这很好,但我们可以做得更好,对吗?我决定研究一下通过Pipedream使用Google Drive API,如果我可以得到一个以数据为中心的文件版本。然后我可以在我的网站上呈现它们,并对体验有更多的控制。我可以做过滤、排序、提供上下文等等,同时仍有一个动态的文件列表。我今天分享的是它的初始版本,但后续计划有一些更高级的功能。好了,让我们开始吧。

第一步 - 建立/决定文件夹

你将需要一个某种文件夹来测试。我在Google Drive中建立了一个新的文件夹,进入 "共享 "选项,并启用公众访问权限,以查看文件夹中的项目。这可以让任何人看到和下载文件,但不能让他们对其进行控制。在这样做的时候,我注意到了URL。

https://drive.google.com/drive/u/0/folders/1FYLaoscxWBV_BU5sFouf7XCrv7cKktBY

特别是folders 之后的值,我认为这是一个独特的文件夹标识符。

第二步 - 创建Pipedream工作流程

在Pipedream中,我使用HTTP请求触发器建立了一个新的工作流程。不要忘了Pipedream最近增加了对自动过滤favicon.ico请求的支持,你绝对应该把它打开。

然后我去添加一个动作来获取我的Google Drive文件夹。我知道Pipedream有内置的动作来处理Google Drive,而且数量还不少。

不幸的是,有点奇怪的是,他们还没有一个 "列出文件夹中的文件 "的动作。我在他们的Slack中提出了这个问题(在这里跟踪这个问题!),而且很有可能在你读到这篇文章的时候,它已经被支持了,但幸运的是,Pipedream让它变得异常简单。只要在前两个选项中选择一个,"使用任何Google Drive与..."。我选择了Node.js,你会得到这样的代码。

import { axios } from "@pipedream/platform"
export default defineComponent({
  props: {
    google_drive: {
      type: "app",
      app: "google_drive",
    }
  },
  async run({steps, $}) {
    return await axios($, {
      url: `https://www.googleapis.com/oauth2/v1/userinfo`,
      headers: {
        Authorization: `Bearer ${this.google_drive.$auth.oauth_access_token}`,
      },
    })
  },
})

在你用你的账户在步骤中进行认证后,你简直只需要改变url 。这就是了!大多数时候,你需要添加一些查询参数之类的,这可能是额外的工作,但100%的认证是由Pipedream处理的。我以前说过,现在我还要说--我非常喜欢那些无聊的、难以完成的部分,我可以专注于建设。

我首先看了一下Files/ListAPI。我需要弄清楚两件事。

首先,我们如何过滤到一个文件夹?这是通过使用语法来完成的。

q={FOLDERID} in parents

我提到需要从步骤中得到文件夹ID,这就是它的作用。所以我的代码当时看起来是这样的。

let folderId = '1FYLaoscxWBV_BU5sFouf7XCrv7cKktBY'
let q = `"${folderId}" in parents`
let url = `https://www.googleapis.com/drive/v3/files` + `?q=` + encodeURIComponent(q);

作为提醒,Pipedream支持动作中的动态道具,如果我想的话,我可以把folderId 移到代码之外,让它成为一个步骤参数。这次我很懒,没有打扰。

这本身就足够了,但在默认情况下,Google Drive每个文件可能返回3-4个字段。要增加更多,你可以指定一个字段列表。我用fields=* ,看到了所有的东西,然后决定得到:

  • 名称
  • mime类型
  • 查看、下载和缩略图链接
  • 大小

这是我现在更新后的代码:

import { axios } from "@pipedream/platform"
export default defineComponent({
  props: {
    google_drive: {
      type: "app",
      app: "google_drive",
    }
  },
  async run({steps, $}) {
    let folderId = '1FYLaoscxWBV_BU5sFouf7XCrv7cKktBY'
    let q = `"${folderId}" in parents`
    let url = `https://www.googleapis.com/drive/v3/files` + `?q=` + encodeURIComponent(q);
    url += '&fields=files/name,files/mimeType,files/webContentLink,files/thumbnailLink,files/size,files/webViewLink';

    return await axios($, {
      url,
      headers: {
        Authorization: `Bearer ${this.google_drive.$auth.oauth_access_token}`,
      },
    })
  },
})

要清楚的是,我所做的只是改变了URL。由于Pipedream,我花在研究API上的时间比实际写代码的时间多。

我又增加了一个代码步骤,只是为了返回我的信息:

export default defineComponent({
  async run({ steps, $ }) {
    let resp = steps.get_files.$return_value.files;
    await $.respond({
      status: 200,
      headers: {},
      body: resp
    })
  },
})

而这就是了。我仍然不能分享新的Pipedream工作流程,但你可以在这里看到自己的端点:https://eoemdgkqfhrtf27.m.pipedream.net/

第三步--用Alpine.js构建前端

对于前端,我决定用Alpine.js构建一个简单的界面。在这第一次迭代中,我将获得文件并在一个表格中呈现它们。让我们从HTML开始。


<div x-data="app">
    <!-- on top, render a list of files in a table -->
    <h2>Available Files</h2>
    <table id="fileList">
        <thead>
            <tr>
                <th>Name</th>
                <th>Type</th>
                <th>Size</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            <template x-for="file in files">
                <tr>
                    <td><span x-text="file.name"></span></td>
                    <td><span x-text="file.mimeType"></span></td>
                    <td><span x-text="file.size"></span></td>
                    <td><a :href="file.webViewLink" target="_new">view</a>/<a :href="file.webContentLink">download</a></td>
                </tr>
            </template>
        </tbody>
    </table>
</div>

我有一个显示名称、类型和大小的表格,第四栏用于查看和下载。查看是在Google Drive上进行的,所以我使用一个新的标签来查看,而下载则是普通的工作。对于我的JavaScript,我保持它相当简单--在加载时,点击端点。

const FILES_API = 'https://eoemdgkqfhrtf27.m.pipedream.net/';

document.addEventListener('alpine:init', () => {
  Alpine.data('app', () => ({
        async init() {
            this.getFiles();
        },
        files: [],
        async getFiles() {
            let resp = await fetch(FILES_API);
            this.files = await resp.json();
        }
  }))
});

我真的应该在这里做一些错误处理,而且我肯定应该有一个加载指示器,但这里是第一稿的操作。

请看CodePen上Raymond Camden (@cfjedimaster)的Pen渲染Google Drive文件

对于我的第二稿,本着让非技术人员使用这个的心态,我做了三个改动。首先,我把尺寸改成了一个稍微能让人看懂的形式。我在StackOverflow上找到了一个名为humanFileSize的优秀的小函数,并更新了表格单元格以使用它。

<td><span x-text="humanFileSize(file.size,true)"></span></td>

接下来,我研究了改变MIME类型的问题。有几个选项,但没有一个是适合这个简单的前端应用的。因此,我决定简单地推出我自己的,并对所支持的内容作出判断。我决定支持:

  • PDF(当然)
  • 图片(我并不关心JPG与GIF的对比等等--普通人并不关心这个问题)
  • HTML
  • Word、PowerPoint和Excel

MDN有一个很好的常见mime类型的列表,我把它作为我的参考。

这是我写的函数:

function humanType(mime) {
    if(mime === 'application/pdf') return 'PDF';
    if(mine.startsWith('image/')) return 'Image';
    if(mime === 'text/html') return 'HTML';
    if(mime === 'application/word' || mime === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') return 'Word';
    if(mime === 'application/vnd.ms-excel' || mime === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') return 'Excel';
    if(mime === 'application/vnd.ms-powerpoint' || mime === 'application/vnd.openxmlformats-officedocument.presentationml.presentation') return 'PowerPoint';
    return 'File';
}

这里是我更新前端的方法:

<td><span x-text="humanType(file.mimeType)"></span></td>

最后,我添加了一个加载指标:

<h2>Available Files <span x-text="loading"></span></h2>

其中loading 默认为(Loading...) ,当,东西加载时,会被设置为一个空字符串。这就是现在的整个JavaScript:

const FILES_API = 'https://eoemdgkqfhrtf27.m.pipedream.net/';

document.addEventListener('alpine:init', () => {
  Alpine.data('app', () => ({
        async init() {
            this.getFiles();
        },
        files: [],
        loading:"(Loading...)",
        async getFiles() {
            let resp = await fetch(FILES_API);
            this.loading = '';
            this.files = await resp.json();
        }
  }))
});

// https://stackoverflow.com/a/14919494/52160
/**
 * Format bytes as human-readable text.
 * 
 * @param bytes Number of bytes.
 * @param si True to use metric (SI) units, aka powers of 1000. False to use 
 *           binary (IEC), aka powers of 1024.
 * @param dp Number of decimal places to display.
 * 
 * @return Formatted string.
 */
function humanFileSize(bytes, si=false, dp=1) {
  const thresh = si ? 1000 : 1024;

  if (Math.abs(bytes) < thresh) {
    return bytes + ' B';
  }

  const units = si 
    ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] 
    : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
  let u = -1;
  const r = 10**dp;

  do {
    bytes /= thresh;
    ++u;
  } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);


  return bytes.toFixed(dp) + ' ' + units[u];
}

function humanType(mime) {
    if(mime === 'application/pdf') return 'PDF';
    if(mine.startsWith('image/')) return 'Image';
    if(mime === 'text/html') return 'HTML';
    if(mime === 'application/word' || mime === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') return 'Word';
    if(mime === 'application/vnd.ms-excel' || mime === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') return 'Excel';
    if(mime === 'application/vnd.ms-powerpoint' || mime === 'application/vnd.openxmlformats-officedocument.presentationml.presentation') return 'PowerPoint';
    return 'File';
}

下面是它的运行情况。

这个版本就到此为止,但正如我上面所说,我有一些想法,希望能把它做得更好,过几天我还会再来研究它。像往常一样,让我知道你的想法!