ネイティブのクロスプラットフォームのクリップボードマネージャを構築しましょう


今日、我々はクロスプラットホーム(Windows、MacOS、Linux)クリップボードマネージャを構築するつもりですRevery .
あなたがReveryについて聞いていないならば、それはあなたに開発者経験を犠牲にすることなく本当に自国の反応をもたらすことを目的とする図書館です.
リリーを書くReason , しかし、この記事のいずれかの技術の以前の知識が必要です.
我々は、我々は沿って移動し、あなたの目を少し目をつぶれば、基本的に行くだろう、理由はJavascriptのようにかなり見えます.

始める


準備しましたminimal template for Revery 私たちを取得し、実行するので、そのクローンによって開始しましょう.
git clone [email protected]:lessp/revery-minimal-template.git revery-clipboard-manager
cd revery-clipboard-manager

依存関係のインストール


理由ネイティブで働くとき、我々が使うパッケージマネージャは呼ばれます esy .
それは非常に他の偉大なツールのようなワークフローを持っている yarn or npm そして、それをインストールする最も簡単な方法は npm . それでは、先に行きましょう!
npm i -g esy@latest
それでesy 'sを指定した場合、package.json .
esy install
# and build them
esy build
# or we could simply do, `esy`, which would combine these steps
ここで注意すべきことがいくつかあります.
最初のオフ、リリーに依存skia これは、オープンソースのグラフィックスエンジンは、Google Chrome、Android、フラッターとより多くの電源です.SKIAはかなり大きな依存関係であり、スクラッチ(他の依存関係の中から)を構築しているので、これも30分以上かかるかもしれません.😲 それで、それが永遠のように聞こえるかもしれないとしても、この時、それは予想されます.
しかし、一旦構築されると、その後のビルドはほぼ瞬時になります.
第二に、プラットフォームによっては、追加のパッケージが必要になるかもしれません.最新のリストについては、ここの特定のプラットフォームを探してください.Building & Installing Revery
すべてを言うと、飲み物をつかむために、少しリラックスする準備ができている.🙂

エディタプラグイン


若干の時間を節約するために、我々はこのチュートリアルでVSCodeをカバーするだけです(個人的に私はVimを使用します、しかし、あなたがそのアップをコメントに設定することに興味があるならば、私はできるだけ最高に助けるように努力します).使用するプラグインはOCaml Platform . vscodeにインストールしてください.

基本アプリケーションの設定


それで、うまくいけば、我々はこのステージに達しましたstart -スクリプトpackage.json アプリケーションを実行するには、次の手順に従います.
esy start
これに似たウィンドウを開くべきです.かなりクール!

コードの中の何を見てみましょう.
インsrc 一つのファイルがあることがわかります.App.re , 次のようになります.
open Revery;
open Revery.UI;
open Revery.UI.Components;

let init = app => {
  let window =
    App.createWindow(
      app,
      "Our window title",
      ~createOptions=WindowCreateOptions.create(
        ~width=512,
        ~height=384,
        ()
      ),
    );

  let _update = UI.start(
    window,
    <Text text="Hello DEV.to-readers!" />
  );

  ();
};

App.start(init);
これは、基本的なリリーアプリケーションを作成するために必要なすべてです.もっと詳しく説明しましょう.
頂上でopen reveryのためのいくつかの基本モジュールは、このアプリケーションを作成するために必要なすべての機能とモジュールを含んでいます.モジュールを開くことで、モジュール自体を参照することなく、すべてのシンボルを利用できます.
JavaScriptでは、基本的に以下のようになります.
import * from Revery;
次に、関数名を作成しますinit . この関数はRevery.App.t 私たちは、後にApp.start -関数.
中でinit -関数は、タイトル、幅、高さを指定しながらアプリケーションをレンダリングするウィンドウを作成します.

Note: If you're wondering what the ~ is for, it's a labeled argument. I find labeled arguments very handy as they allow us to pass arguments without worrying about order, and also have the benefit of documenting the meaning of the argument.
e.g.

create(~width=100, ~height=200);
create(100, 200);

我々は、我々の作成されたウィンドウを渡すことによって我々のアプリケーションを起動しますUI.start -関数.

Note: If you're wondering why we're prefixing the update-variable with a _ it's to let the compiler know that we'll not be using this variable to silent the warning the compiler would otherwise have given us.


最後に、我々はそれから戻ります() これはunit , あなたはそれを見ることができますvoid 他のプログラミング言語で.

Note: In Reason a function always needs a return-value even in this case where we're only doing side-effects.

In JavaScript for example, a function without an explicit return-value will return undefined.


クリップボードマネージャの作成


したがって、この時点で、我々は(何か)実行している.それはすべての正直さで、それは非常に多くをしないので、それを変更しましょう.
カスタムコンポーネントを作成します.上記のlet init -定義を追加します
let%component clipboardManager = () => {
  let%hook (clipboardText, setClipboardText) = Hooks.state("Nothing to see here, yet.");

  <Text text=clipboardText />;
}
置換
let _update = UI.start(win, <Text text="Hello DEV.to-readers!" />);
カスタムコンポーネントを使用すると、次のようになります.
let _update = UI.start(win, <clipboardManager />);
コードを歩きましょう.
私たちはlet%component これは、これがstatentコンポーネントであることを意味します.では、state -フック、私たちに利用可能Revery.Hooks -モジュールです.
let%hook (clipboardText, setClipboardText) = Hooks.state("Nothing to see here, yet.");
これはuseState を返します. tuple を返します.
値を渡すclipboardText 我々にText -現在、我々のハードコードされた値(絶望ではなく、我々は1分でそれを変えるでしょう)を常にレンダリングします.
とにかく、これを実行すれば、次のようなものを見るべきです.

それは以前にあったものとは全く違っていません.良いニュースは、我々はフックとカスタムコンポーネントを作成したことです.

クリップボードに何をつかむ


我々のクリップボードにあるものにアクセスするにはSDL .
SDLは、キーボード、マウス、オーディオのようなかなりの数のシステムAPIにアクセスするためのクロスプラットフォームAPIを提供します.
REILは、バインドをSDLライブラリに公開します.オートコンプリートでは、クリップボードに関連する3つの方法があります.

私たちはgetText を取るunit , 言い換えればno arguments , を返し、option(string) .
理由は、そのようなものはないnull , しかし、私たちはしばしば何かを表現する必要があるのでSome(thing) or None , 我々は option -type 両方のケースを扱うことを強制します.
我々の例を呼び出しに更新しましょうgetText .
let%component clipboardManager = () => {
  let%hook (clipboardText, setClipboardText) = Hooks.state("");

  switch (Sdl2.Clipboard.getText()) {
  | Some(clipboardText) =>
    setClipboardText(_previousText => clipboardText);
  | None => ()
  };

  <Text text=clipboardText />;
};
今の例を再実行するならば、私にとって、全く驚くべきことに、私はこの記事に関連した何かを得ます.

あなたが何か他のものをコピーしようとするならば、あなたはアプリケーションですぐに反映される変化を見ることができなければなりません.それは私たちが絶え間なく呼んでいるからですsetClipboardText . おそらく60 fpsのようなもの.これがブラウザにあったなら、私たちはおそらく「最大呼び出しスタックを超えました」を見るでしょう.

タイマーの使用


それで、我々の現在のアプローチは、大きくありません.我々はクリップボードからの値は、おそらく上のビットは、おそらくレンダリングすることができますレンダリングを高速化している.
何かを継続的に行うには、いくつかのオプションがあります.この場合、我々はtick -フック.

つの最初の引数を見るtick -フックは、それがラベルされた引数をとるのを見ることができます~tickRate of Time.t そして、私たちを与えるコールバックTime.t and expects unit 戻り値の型.
以下に例を示します.
Hooks.tick(
  ~tickRate=Time.ms(100),
  (time: Time.t) => Console.log(Time.toString(time))
);
コードを更新しましょうtick -フックを呼び出すgetText -コードを1秒ごとに1回.
let%component clipboardManager = () => {
  let%hook (clipboardText, setClipboardText) = Hooks.state("");

  let handleTick = _time => {
    switch (Sdl2.Clipboard.getText()) {
    | Some(clipboardText) =>
      setClipboardText(_previousText => clipboardText);
    | None => ()
    };
  };

  let%hook () = Hooks.tick(~tickRate=Time.ms(1000), handleTick);

  <Text text=clipboardText />;
};

複数の値を示す


クール!我々は現在、ほとんどのものを持っている.しかし、クリップボードのマネージャーは本当に価値があるだけでなく、我々の以前の値を見ることができますので、それを修正しよう!
まずスイッチを切りますstate -テキスト文字列を保持するのではなく、フックのリストを初期値で空リストに設定します.
let%hook (clipboardItems, setClipboardItems) = Hooks.state([]);
第二に、私たちは少しずつ物事を変える必要があるでしょうhandleTick -関数.
let handleTick = _time => {
  switch (Sdl2.Clipboard.getText()) {
  | Some(clipboardText) =>
    let alreadyExists =
      clipboardItems
      |> List.find(~f=storedClipboardText =>
            storedClipboardText == clipboardText
          )
      |> Option.isSome;

    alreadyExists
      ? ()
      : setClipboardItems(currentItems => [clipboardText, ...currentItems]);
  | None => ()
  };
};
では、ここで何が変わったのか.
さて、私たちはリストに既に存在していない値を追加することに興味を持っています(あるいは、毎秒値を連続的に追加してしまうでしょう).List -モジュールです.
私たちはList.find を返します.option(string) . 再び、私たちの項目でマッチング値がないかもしれないので、この関数はoption .
しかし、私たちのケースでは、我々は値に興味がないので、値があるという事実だけで、我々はOption -モジュールを有効にするoption(string)bool , 最終的に我々のターンList.find + Option.isSomeList.exists -関数の時間Tablecloth , たぶん!
  • それが存在するならば、我々は単に何もしないで、帰りますunit .
  • 存在しない場合は、現在の項目にクリップボード内のテキストを追加します.
  • Note: The |> here is called the "pipe last-operator" because it "pipes"/passes the value into the last argument of the receiving function.


    最後に、コンポーネントの一覧を表示するためにコンポーネントを更新します.
    完全なコンポーネントは次のようになります.
    let%component clipboardManager = () => {
      let%hook (clipboardItems, setClipboardItems) = Hooks.state([]);
    
      let handleTick = _time => {
        switch (Sdl2.Clipboard.getText()) {
        | Some(clipboardText) =>
          let alreadyExists =
            clipboardItems
            |> List.find(~f=storedClipboardText =>
                  storedClipboardText == clipboardText
                )
            |> Option.isSome;
    
          alreadyExists
            ? ()
            : setClipboardItems(currentItems => [clipboardText, ...currentItems]);
        | None => ()
        };
      };
    
      let%hook () = Hooks.tick(~tickRate=Time.ms(1000), handleTick);
    
      let clipBoardElements =
        clipboardItems
        |> List.map(~f=text => <Text text />)
        |> React.listToElement;
    
      <Column> clipboardElements </Column>;
    };
    
    そして、私たちがそれを実行するならば、これはいくつかのアイテムをコピーした後に得るものです:

    現在のクリップボードのテキストを設定する


    わかりました、我々は長い道のりを来ました.つの最後の重要なことを加えましょう.
    項目をクリックすると、そのテキストに現在のクリップボードの値が変更されます.
    我々がどのように我々に3つの機能を持ったかについて覚えていてくださいClipboard -モジュール?hasText , getText and setText .setText 我々が後にしているように聞こえます.
    我々がマッピングしている線でclipboardItems , を加えましょうClickable コンポーネントは、次のようにコードを作ります
    let clipboardElements =
      clipboardItems
      |> List.map(~f=text =>
           <Clickable onClick={() => Sdl2.Clipboard.setText(text)}>
             <Text text />
           </Clickable>
         )
      |> React.listToElement;
    
    今、リスト内の項目をクリックすると、クリックした値でクリップボードを更新する必要があります.
    そして、それはすべてそこにある!

    最終コード


    我々が終わったものは、ここにあります.
    let%component clipboardManager = () => {
      let%hook (clipboardItems, setClipboardItems) = Hooks.state([]);
    
      let handleTick = _time => {
        switch (Sdl2.Clipboard.getText()) {
        | Some(clipboardText) =>
          let alreadyExists =
            clipboardItems
            |> List.find(~f=storedClipboardText =>
                 storedClipboardText == clipboardText
               )
            |> Option.isSome;
    
          alreadyExists
            ? ()
            : setClipboardItems(currentItems => [clipboardText, ...currentItems]);
        | None => ()
        };
      };
    
      let%hook () = Hooks.tick(~tickRate=Time.ms(1000), handleTick);
    
      let clipboardElements =
        clipboardItems
        |> List.map(~f=text =>
             <Clickable onClick={() => Sdl2.Clipboard.setText(text)}>
               <Text text />
             </Clickable>
           )
        |> React.listToElement;
    
      <Column> clipboardElements </Column>;
    };
    

    最終語


    多くのおかげであなたがこれを得るために管理した場合、うまくいけば、この興味深い発見!
    Reveryはかなりクールなプロジェクトであり、かなり新しいとはいえ、潜在的な可能性があると思う.
    興味があるならば、我々はパート2を作ることができて、それをより多くの製品のようにしようとすることができました.
    言うまでもなく、あなたが冒険を感じているし、自分のタッチを追加するような気がしたら、私はあなたが何を考え出すのを見てみたい!
    ハッピーコーディング!
    トム
    PS .特別な感謝Glenn and Bryan 記事のフィードバックのために.
    PSSあなたが質問、問題を持っているならば、あるいは、単に、非常に外に出ることが好きですhttps://discord.gg/UvQ2cFn ) もちろんあなたが参加する歓迎以上のものです!