を構築するミニGoogleドライブクローンを迅速に


TL;DR you can find code at https://github.com/sergeyt/multidisk


私は比較的迅速にどのように比較的迅速に小さなGoogleのドライブのアプリやその種類を構築する方法を示します.私は、「Googleドライブ」が少し挑発的に聞こえるかもしれないということを知っています😄. また、私を信じていないが、私はいくつかの朝以内にアプリを行ってきた😄). 以下は、私がしたことと方法についての若干の詳細です.

ハイレベルアイデア
アイデアは簡単です.すべてのドライブに統一UIを作りたいです.簡素化のために出発点としてプラットフォームを少しだけ選んだ.

  • Uploadcare - ニースとシンプルなAPIで全く新しいものと無料プランを持っている

  • Dropbox - 何の紹介も必要ない
  • マルチディスクと呼びましょうhttps://www.odrive.com/ , しかし、非常に近代的な技術スタックを使用してWeb用に構築.

    今までやったこと
    機能の面では、アプリケーションは、次の機能が含まれます
  • 複数のドライブの管理を許可する
  • ファイルのアップロード、ダウンロード、削除
  • この時点で、私はUploadCareとDropboxのドライブを実装しましたが、特にGoogleのドライブのサポートを追加したいと思います
  • 簡単に見ることができますthis short video 上記の機能についてより多くのビジョンを持つこと.

    ツーリング
    アプリケーションを構築するには、次のツールを選択しました

  • TypeScript - 私は最近、どんなプロジェクトの開始から型を好む.この理由は別々に論じられる😄. 多分私が最初にプログラミングを始めたので

  • Uploadcare - 私のための新しいものは、一緒に新しい何かを学ぶことができます

  • Next.js - どんな導入も必要でない

  • Material UI - 良い反応のフレームワークのコンポーネントの多く.多分後で私は使用するアプリをリファクタリングしますChakra UI 私にも期待しているから
  • すべてのツールは私にアップロードとDropboxのAPIを除いて私によく知られています.グレート、それは学習機会です.

    どうやってやったの
    私の手順は
  • プロジェクトブートストラップ
  • ドライブ抽象化
  • アップロードドライブの実装
  • Dropboxドライブの実装
  • いくつかのUIページ
  • それです.ダイビングをして、いくつかの追加テキストで各ステップをカバーすることができます.

    プロジェクトブートストラップ
    によるとthis Next.js doc それはちょうど実行しているように簡単ですnpx create-next-app あなたのシェルのコマンドとあなたは文字通り行われます😄

    ドライブインタフェース
    このポストをいくつかのコードでポンピングする時です😄. 私は、どんなドライブとそのオブジェクトも抽象化するために、以下のインターフェースを思いつきました.これらの抽象化は、異なるストレージプロバイダを統一するために必要です.
    export type ItemType = "file" | "folder";
    
    export interface Item {
      type: ItemType;
      id: string;
      name: string;
      path: string;
      driveId: string;
    }
    
    export interface File extends Item {
      size: number;
      createdAt: Date;
      url?: string;
      download?: () => Promise<any>;
    }
    
    export interface Drive {
      options: any;
      provider: string;
    
      getItems(folderId?: string): Promise<Item[]>;
      deleteFile(fileId: string): Promise<void>;
      deleteFolder(folderId: string): Promise<void>;
    }
    

    アップロードの実装
    アップロードのAPIドキュメントはhttps://uploadcare.com/api-refs/rest-api/v0.5.0/ .
    私たちはちょうどaxios HTTPリクエストを作成するために、私は私のニーズのための任意のタイプのクライアントを見つけませんでした.私は、UploadCare APIクライアントのために小さなNPMパッケージをして満足です.私は知っているthis one , しかし、現在アップロードしてファイルをダウンロードし、それはアップロードAPIのすべての機能をカバーしていません.たぶんGitTubでそれを要求する必要があるかもしれない😄
    import axios from "axios";
    import { Drive, File, Item } from "../types";
    import { checkResponseOK } from "./utils";
    
    type Options = {
      type: string;
      id: string;
      name: string;
      publicKey: string;
      secretKey: string;
    };
    
    export default class UploadcareDrive implements Drive {
      private _options: Options;
    
      constructor(options: Options) {
        this._options = options;
      }
    
      get options(): Options {
        return this._options;
      }
    
      get provider() {
        return this.options.type;
      }
    
      get id() {
        return this.options.id;
      }
    
      get name() {
        return this.options.name;
      }
    
      axios() {
        return axios.create({
          headers: {
            Accept: "application/vnd.uploadcare-v0.5+json",
            Authorization: `Uploadcare.Simple ${this.options.publicKey}:${this.options.secretKey}`,
          },
        });
      }
    
      async getItems(folderId?: string): Promise<Item[]> {
        const resp = await this.axios().get("https://api.uploadcare.com/files/");
        checkResponseOK(resp);
        return resp.data.results.map(
          (f) =>
            ({
              type: "file",
              driveId: this.id,
              id: f.uuid,
              name: f.original_filename,
              createdAt: new Date(f.datetime_uploaded),
              url: f.original_file_url,
              size: f.size,
            } as File)
        );
      }
    
      async deleteFile(fileId: string): Promise<void> {
        const resp = await this.axios().delete(
          `https://api.uploadcare.com/files/${fileId}/`
        );
        checkResponseOK(resp);
      }
    
      deleteFolder(folderId: string): Promise<void> {
        return Promise.resolve(undefined);
      }
    }
    

    Dropboxドライブの実装
    Dropboxがa wonderful docs ともinteractive playground APIエクスプローラーと呼ばれます.私は、DropboxDriveのバージョンを実装しました.そして、それはさわやかなトークン能力なしで短命なトークンを使用しました.すみませんが、今度は今度は改定する時間を見つけます.
    現在のバージョンのコード:
    import axios from "axios";
    import trimStart from "lodash/trimStart";
    import { Drive, Item, File } from "../types";
    import { checkResponseOK, downloadBlob, trimPrefix } from "./utils";
    
    type Options = {
      type: string;
      id: string;
      name: string;
      accessToken: string;
    };
    
    export default class DropboxDrive implements Drive {
      private _options: Options;
    
      constructor(options: Options) {
        this._options = options;
      }
    
      get provider() {
        return "dropbox";
      }
      get id() {
        return this.options.id;
      }
      get name() {
        return this.options.name;
      }
      get options() {
        return this._options;
      }
    
      async getItems(folderId: string = ""): Promise<Item[]> {
        if (!folderId.startsWith("/")) {
          folderId = "/" + folderId;
        }
        const resp = await this.axios().post(
          "https://api.dropboxapi.com/2/files/list_folder",
          {
            path: folderId === "/" ? "" : folderId,
          }
        );
        checkResponseOK(resp);
        return resp.data.entries.map((entry) => {
          if (entry[".tag"] === "file") {
            return {
              type: "file",
              id: trimPrefix(entry.id, "id:"),
              name: entry.name,
              path: entry.path_display,
              createdAt: new Date(entry.server_modified),
              driveId: this.id,
              size: entry.size,
              download: async () => {
                const resp = await this.axios().post(
                  "https://content.dropboxapi.com/2/files/download",
                  undefined,
                  {
                    headers: {
                      "Dropbox-API-Arg": JSON.stringify({ path: entry.id }),
                    },
                    responseType: "blob",
                  }
                );
                downloadBlob(resp.data, entry.name);
              },
            } as File;
          }
          return {
            type: "folder",
            id: trimStart(entry.path_display, "/"),
            name: entry.name,
            path: entry.path_display,
            driveId: this.id,
          } as Item;
        });
      }
    
      async deleteFile(fileId: string): Promise<void> {
        const resp = this.axios().post(
          "https://api.dropboxapi.com/2/file_requests/delete",
          {
            ids: [fileId],
          }
        );
        checkResponseOK(resp);
      }
    
      deleteFolder(folderId: string): Promise<void> {
        return Promise.resolve(undefined);
      }
    
      axios() {
        return axios.create({
          headers: {
            Authorization: `Bearer ${this.options.accessToken}`,
            "Content-Type": "application/json",
          },
        });
      }
    }
    

    アプリケーション
    アプリのMVPバージョンわずか数ページといくつかのダイアログがあります
  • ドライブのリストによるホームページ
  • ファイルの一覧をドライブの詳細ページ
  • 新しいドライブダイアログ
  • 削除アクションの確認ダイアログ

  • のホームページ
    私たちはSWR 次の推奨データを取得します.JSフォークス.ホームは簡単な実装です.ホームページの完全なスクリプトはこちら
    import isEmpty from "lodash/isEmpty";
    import useSWR from "swr";
    import { getDrives } from "../core/store";
    import Loader from "../components/Loader";
    import Placeholder from "../components/Placeholder";
    import DriveList from "../components/DriveList";
    
    export default function Home() {
      const { data: drives } = useSWR("/drives", getDrives);
      if (!drives) {
        return <Loader />;
      }
    
      if (isEmpty(drives)) {
        return (
          <Placeholder>
            You don't any drives, but you can create one clicking on ADD DRIVE
            button
          </Placeholder>
        );
      }
    
      return <DriveList drives={drives} />;
    }
    
    Driveistコンポーネントが次のようにコーディングされている場合
    import Link from "next/link";
    import ListItem from "@material-ui/core/ListItem";
    import ListItemAvatar from "@material-ui/core/ListItemAvatar";
    import Avatar from "@material-ui/core/Avatar";
    import DriveIcon from "@material-ui/icons/Work";
    import ListItemText from "@material-ui/core/ListItemText";
    import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
    import List from "@material-ui/core/List";
    import { Drive } from "../types";
    import DriveMenu from "./DriveMenu";
    
    export default function DriveList({ drives }: { drives: Drive[] }) {
      const items = drives.map((d, k) => (
        <Link href={`/drive/${d.id}`} key={k}>
          <ListItem style={{ cursor: "pointer" }}>
            <ListItemAvatar>
              <Avatar>
                <DriveIcon />
              </Avatar>
            </ListItemAvatar>
            <ListItemText primary={d.name} secondary={d.driveType} />
            <ListItemSecondaryAction>
              <DriveMenu driveId={d.id} />
            </ListItemSecondaryAction>
          </ListItem>
        </Link>
      ));
      return <List>{items}</List>;
    }
    

    ドライブビューページ
    また以下のように簡単です.
    import { useRouter } from "next/router";
    import useSWR from "swr";
    import { Box } from "@material-ui/core";
    import { Widget } from "@uploadcare/react-widget";
    import { getDrive } from "../../core/store";
    import Loader from "../../components/Loader";
    import ItemList from "../../components/ItemList";
    
    export default function DriveView() {
      const router = useRouter();
      const { id } = router.query;
    
      const { data, revalidate } = useSWR(`/drive/${id}`, async () => {
        const drive = await getDrive(String(id));
        const items = await drive.getItems();
        return { drive, items };
      });
      if (!data) {
        return <Loader />;
      }
    
      return (
        <>
          <Box m={2} mb={2}>
            <label>Upload a file:&nbsp;</label>
            <Widget
              publicKey={data.drive.options.publicKey}
              onChange={revalidate}
            />
          </Box>
          <ItemList data={data.items} />
        </>
      );
    }
    
    ここでitemlistは以下のように符号化されます:
    import isEmpty from "lodash/isEmpty";
    import List from "@material-ui/core/List";
    import { Item, File, Folder } from "../types";
    import FileItem from "./FileItem";
    import FolderItem from "./FolderItem";
    import Placeholder from "./Placeholder";
    
    export default function ItemList({ data }: { data: Item[] }) {
      if (isEmpty(data)) {
        return (
          <Placeholder>
            This drive is empty, but you can fill it out with something :)
          </Placeholder>
        );
      }
    
      const items = data.map((item, k) => {
        switch (item.type) {
          case "file":
            return <FileItem key={k} item={item as File} />;
          case "folder":
            return <FolderItem key={k} item={item as Folder} />;
          default:
            return null;
        }
      });
      return <List>{items}</List>;
    }
    
    あなたがアップロードファイルがちょうど使用されて実装されることに気づいたかもしれないようにUploadcare ウィジェット-素敵なもう一つの大きな時間の節約.

    どうやってダイアログをやったの?
    あなたは本当にダイアログのコードを読みたいですか?それは退屈であるべきです.そして、多分、それはこのブログ柱のために十分でなければなりません😄
    とにかく、あなたは行くことができますthe github repo いくつかの楽しい情報の追加ビットを消費している😄

    次の手順
    次の時間は、おそらく次のようなアプリケーションでより多くの機能を追加しようとします.
  • グーグルドライブ
  • S 3ドライブ
  • フレームワークagnosticファイルマネージャウェブコンポーネント.多分使用Angular elements
  • フォルダ
  • ウィザードを作成するように
  • より良い承認、多分公式DropboxやGoogle Appsを作る
  • Mongoクラウドでドライブ接続を保存すると、別のデバイス間で設定を保持します😄)
  • 楽しむ!EOF😄
    リンクoriginal post