我正在做一个项目,以帮助当地的倡议,作为这项工作的一部分,我需要研究创建一个漂亮的方式来显示、提供等存储在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';
}
下面是它的运行情况。
这个版本就到此为止,但正如我上面所说,我有一些想法,希望能把它做得更好,过几天我还会再来研究它。像往常一样,让我知道你的想法!