【初心者】動画作成アプリを作ってみた(フロントのみ)

#はじめに

今回はじめて投稿します、fuuk4といいます。

今回の内容は、卒業制作にて動画作成アプリ(簡易)をweb上でできるものを作成したときに、Reactを使ったので感想や悩んだことを、まとめていこうと思います！

今回作ったアプリのソースに関しては、膨大な量なので一部を抜粋して投稿しています。 
すべてのソースについてはこちらの[GitHub]("https://github.com/fuuk4/node-video-edit-simply")より閲覧の方をよろしくおねがいします。

完全に理解してコードを書いていた感じではないので、指摘等ありましたらよろしくおねがいします。

##今回自分が使ったもの
フロントエンド
React, Redux
バックエンド
Django

##初めて使った印象
今まで自分は、WebアプリケーションをJavaとかで作ってきましたが、それとは全く異なり、**SPAってスゲーー！**ってなりました。
１枚のページでいろんな操作ができるって便利だなーって印象でしたね〜。

###ただ、やっぱり難しかった...
もう、はじめは何に手をつければいいのか分からなかったです…
チュートリアルを見たり、色々作ってみている方々のページを片っ端から読み漁りましたね。

色々読んでみて思ったのは、Todoアプリを作ってみるのが一番データのやり取りを覚えやすかったなと今感じてます。

自分は、[Qiitaの記事]("https://qiita.com/TsutomuNakamura/items/72d8cf9f07a5a30be048")この先駆者様のおかげでやっと一歩踏み出せたかなって思います。

##実際に作ったもの
###こんな画面
![最初の画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503978/07a43be9-2e1f-7595-6798-150a57674b32.png)

###アプリの詳細
####左側:アップロードとコンテンツ部分
アップロードには`react-dropdown-upload`を使ってます。

```js:ここのコードの一部
  getUploadParams({ file, meta }) {
//ドラックまたはエクスプローラーからファイルを選択されたときにここが実行される
    const url = "cloudinary_url";

    const api_key = process.env.CLOUDINARY_API_KEY;
    const apiSecret = process.env.CLOUDINARY_API_SECRET_KEY;

    const eager = "w_470,h_330,c_pad";
    const timestanp = Math.floor(new Date().getTime() / 1000);
    const pubid = "your_public_url"
    const signature = sha1("cloudinary_signature");

    const setid = parseInt(
      pubid.split("/")[3].split(document.getElementById("uidb").value)[1]//idを付与するため
    );

    this.props.dispatch(
      addPreFile({
        id: setid,
        name: meta.name,
        index: this.getNextindex()//ファイルのインデックスを持ってくる自作メソッド,
        filetype: meta.type,
        url: "",
      })
    );

    //cloudinaryへアップロード
    const xhr = new XMLHttpRequest();
    const fd = new FormData();

    xhr.open("POST", url, true);
    xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");

    fd.append("file", file);
    fd.append("api_key", api_key);
    fd.append("eager", eager);
    fd.append("public_id", pubid);
    fd.append("timestamp", timestanp);
    fd.append("signature", signature);

    return {
      url: url,
      body: fd,
      headers: xhr,
    };
  }

  handleChangeStatus({ meta, xhr, remove }, status) {
    if (status === "done") {
      let pubid = JSON.parse(xhr.response).public_id;
      pubid = parseInt(
        pubid.split("/")[3].split(document.getElementById("uidb").value)[1]
      );
      this.props.dispatch(addurl(pubid, JSON.parse(xhr.response).secure_url));
      this.props.dispatch(addFile(pubid));
      remove();
    }
  }

.....中略......

//Render()内
<Dropzone
    getUploadParams={this.getUploadParams}
    onChangeStatus={this.handleChangeStatus}
    accept="image/*"
    maxFiles={4}
    multiple={true}
    inputContent={(files, extra) =>
      extra.reject
      ? "写真のみ選択可能です"
      : "クリックまたはドラッグ"
    }
     styles={{
       dropzoneReject: {
       borderColor: "red",
        backgroundColor: "#DAA",
       },
       inputLabel: (files, extra) =>
        extra.reject ? { color: "red" } : {},
       }}
/>
```

流れ的には、
　１．ファイルが来たcloudinaryアップロード用のURLを作成して保存します。
　２．アップロード終了後にReduxStore内にファイル情報を保存する。

となっています。

改良したいなと思っているのはファイルstoreの中が「prefileとfile」の２つに別れてしまっていて効率が悪いところです。


####右側:プレビュー画面
ここでは特に難しいことはせず、バックエンドに情報を送って帰ってきたURLをvideoタグに乗せるだけの処理をしてます。

```js:コードの一部
render() {
    if (navigator.userAgent.match(/iPhone|Android.+Mobile/)) {
      return (
        <div id="prev-tabs">
          {(this.props.isLoading || this.props.error) && (
            <div class="lds-facebook">
              <div></div>
              <div></div>
              <div></div>
            </div>
          )}
          {this.props.success && (
            <video
              class="px-1"
              id="video-player"
              src={this.props.resurl}
              controls
              controlslist="nodownload"
              disablePictureInPicture
            ></video>
          )}
        </div>
      );
    } else {
      return (
        <div className="prev-tab-div">
          <ul className="nav nav-tabs">
            <li className="nav-item">
              <a
                href="#prev-tabs"
                className="nav-link nav-link-tab active"
                data-toggle="tab"
              >
                プレビュー
              </a>
            </li>
          </ul>
          <div className="tab-content">
            <div id="prev-tabs" className="tab-pane active">
              {(this.props.isLoading || this.props.error) && (
                <div class="lds-facebook">
                  <div></div>
                  <div></div>
                  <div></div>
                </div>
              )}
              {this.props.success && (
                <video
                  class="px-1"
                  id="video-player"
                  src={this.props.resurl}
                  controls
                  controlslist="nodownload"
                  disablePictureInPicture
                ></video>
              )}
            </div>
          </div>
        </div>
      );
    }
  }
```

モバイルとPC用にifで分けていますが、本来はここもアクセス時にstoreか何かに保存しておいたほうが処理がしやすかったのかな、と今になって思っています。

読み込み時はロード画面にするために、isLodingを使って分けています。

####下部:タイムライン画面
本来は、WeVideoみたいに直接操作ができるUIをイメージしていましたが、自分の技量不足で、擬似的なタイムラインを作って移動ができるくらいの物となってます…

使用ライブラリは``vis-timeline``となっています。

```js:コードの一部
constructor(props) {
　this.timeline = null;
}

componentDidMount() {
 const options = {vis-timelineの各種設定}
 const isMobile = navigator.userAgent.match(/iPhone|Android.+Mobile/);
    if (isMobile) {//モバイルならズームできる範囲を変更
      options.zoomMin = 10000;
      options.zoomMax = 32000;
    }
 this.timeline = new Vis(container, [], [], options);
 this.timeline.on("select", this.onSelect);//addイベントリスナーみたいなの
 this.timeline.on("timechange", this.onMoveLinebar);
 const defaultGroup = [
      {
        id: 1,
        content:
          "<span class='material-icons'>movie_creation</span><p>動画</p>",
      },
      {
        id: 2,
        content: "<span class='material-icons'>music_note</span><p>BGM</p>",
      },
      {
        id: 3,
        content: "<span class='material-icons'>graphic_eq</span><p>効果音</p>",
      },
    ];
    
　　//タイムラインにコンテンツを設置
    this.timeline.setData({
      items: this.props.items,
      groups: defaultGroup,
    });
}

....中略....

render(){
return(<div id="timeline"></div>)
}
```

単純に表示だけの部分を抜粋しました。


##悩んだこと
+ とにかく``this.props.~``が出てきて、大変 
初めて触ったとき、「このthisはこれでああでこうでって」ってなって、今どの状態にいるかを探るのが悩みました。 
reduxを入れてからは、その疑問も少なくはなったけれど、一瞬どうしてそうなるんや...ってことがありましたね。

+ こんなにmapやfilter・findってつかっていいのかな 
今回の記事には特に書いてはいないですが、storeの中身を確認するのや、必要な資源だけを取るためにmapとかを大量に使ったけれど、パフォーマンス的にはよろしくないのかなって思いながら組んでました。

+ buildファイルがデカすぎって注意がでる 
ライブラリを入れすぎたのか、buildすると注意がでて、焦ります。 
700kbくらいまで削りましたが、これでもパフォーマンス的には大丈夫なのかな〜…

##まとめ
今回は、初めてreactを使ってアプリを組んだ人の、感想などをまとめてみました。 

文章もイマイチでこんな記事でいいのかわからないけれども、読んでいただいてありがとうございました。 

今回のアプリのソースは、一部抜粋して表示しましたが、GITHUBの方にすべてのソースを置いておきますので、時間がある方は見ていただいて、ご意見をいただけるとありがたいです。