React + Reduxの文字のビジュアルエディター。 概要、ユースケース、拡張

はじめに



みなさんこんにちは! 少し前、私は社内のメールサービスに視覚的な電子メールエディターを埋め込むタスクを受け取りました。人々が手でhtmlを入力し、文字の有効なテンプレートを作成するのに疲れていたからです。 インターネットをさまよう後、私は2人の編集者を見つけました。これらの編集者は、当時私にはこれらの目的に最適だと思われました。 トピックの最後にそれらへのリンクを提供します。 それらをより慎重に研究した後( EmailEditorは jQueryを使用して書かれており、私はかつて非常によく研究し、 MosaicoKnockoutJSを使用していましたが、表面的にのみ知っています)、私はEmailEditorに立ち寄り、1年前に成功しましたAngularIonicの助けを借りて、つまり、2〜3k行のファイル、さまざまな場所からさまざまな方法でDOMが広範かつランダムに変更されるなど、よくわかりました)。







1か月以上、すべてのバグの修正、配布に必要なビルディングブロックの接着などを試みて、私はあきらめました...私はMosaicoを試してみることにし、 Knockoutを積極的に研究し始めましたが、問題はこのモンスター(私はMosaicoについて話している)そのEmailEditorはそれほど悪くないようでした。 加えて、すべてに加えて、またはマイナスであることに加えて、Mosaicoには実質的に正しいドキュメントがありません。最初にすべてがどのように機能し、自分のブロックを作成するかを直感的に理解した場合、直感は役に立ちませんでした。 おそらく、十分な頭脳、忍耐、理解する欲求がなかったのかもしれませんが、あなたの余暇にこれらの編集者の出典を見てください...そして締め切りは厳しかったです...







どうする?!



私は自問自答し、「もちろん、車輪を再発明してください。金色のチェーンとラズベリーの車輪で!」と答えました。 偶然、私のペットプロジェクトの1つで、Webアプリケーションを構築するための現在人気のあるReact + Reduxアプローチの研究を開始する必要がありました。 Reduxについて読んだ後、私は夜明けしました! ここにある! アプリケーションの状態が1か所にある-レターテンプレートのJSON表現が変更されるアーキテクチャを構築するのは最良の選択肢ではありません! そして、私は書き始めました...数週間の眠れない夜の後、プロトタイプが当局に提示され、私のエディターを紹介しようとすることが決定されました。 リポジトリによると、最初はテンプレートの構造と作業の原則を決定するのが難しいことがわかりましたが、勉強しながらさまざまなアプローチを試してみましたが、それを複雑にしないことを決定しました:









それが店全体です。







編集者レビュー



開始する場所...以下では、 NodeJS 、npm、できればMongoDBがインストールされていること、およびそれらやReact + Reduxスタックの操作経験がほとんどないと仮定します。 プロジェクトはcreate-react-appを使用して記述されているため、ライブ開発の開始は簡単です。 そのため、リポジトリをコピーしたら、次のようにします。







npmインストール

npm start

プロジェクトフォルダーとブラウザーでアドレスhttp:// localhost:3000が開き、次のようなものが表示されます。







見る







利用可能なロケールのうち、これまでサポートされているのはenとruのみで、ダウンロードはtranslationsフォルダーのJSONファイルから直接行われます。残念ながら、ユーザーロケールがデフォルトで利用可能かどうかを確認するためのチェックはまだ書いていませんが、これらは些細なことです。アプリケーション-ルートsrc /の index.jsに初期ストアが設定され、リポジトリからIDによって取得されたロケール、ブロックリスト、テンプレート、またはIDが指定されていない場合はデフォルトテンプレートをロードするために3つのアクションがディスパッチされます。 最初の起動はパラメーターなしで行われるため、すべてがローカルファイルから読み込まれます。この段階ではサーバーのセットアップは必要ありません(ただし、テンプレートの保存/読み込み、画像の読み込み、テストレターの送信方法には必要です)。







インターフェイスは非常にシンプルです-左側には設定とブロックのパネルがあり、中央にはレターテンプレートがあり、テンプレートの両側にはボタンがあります。 ブロックはテンプレートにドラッグできます(ターゲットブロック上にあるかのように追加され、すべてが下に移動します)。ターゲットブロック上にマウスを移動すると、色が変わります。 ここでは、他の一部のエディターと同様に、「ファントムブロック」の実装について考えていますが、これは優先事項ではありません。 ブロックをクリックすると、選択したブロックの設定を含むタブがアクティブになり、このブロックが強調表示され、スクリーンショットに見られるように、ブロックの削除ボタンが表示されます。







見る







さて、一般設定タブを選択すると、カスタムスタイルフラグを持つものを除くすべてのブロックに適用される設定のセットが表示されます。 また、テンプレートコンテナの背景を設定する機会があります。







見る







ボタンをクリックすると、テンプレートを保存し(テンプレートの名前を設定するように求められますが、簡単に切り取ることができます)、テストレターを送信してブロックを削除できます(元に戻す\やり直し機能を実装する計画もあります)







また、メインプロジェクトフォルダーでnpm run build実行した場合に表示されるビルドフォルダーをコピーした後、両方のフォルダーでnpm installを実行することを忘れないでください! サーバーでできること:saves \はテンプレート(?Id = your_id)を生成し、画像をアップロードします。また、テストレターを送信するときに「OK」と表示します=)。 理解するのは難しくないと思います。プロジェクトの構造は非常に単純です。複雑にするのは好きではありません...エントリポイントはapp.jsで、 アプリフォルダーにコントローラーがあります。すべての動作があります。 。







少し内側



src / componentsフォルダーには、 ブロックのサブフォルダーとオプションがあり、ブロックのテンプレートとこれらのブロックの設定があります。







hrを使用したブロックの例
import React from 'react'; const BlockHr = ({ blockOptions }) => { return ( <table width="550" cellPadding="0" cellSpacing="0" role="presentation" > <tbody> <tr> <td width="550" style={blockOptions.elements[0]} height={blockOptions.container.height} > <hr /> </td> </tr> </tbody> </table> ); }; export default BlockHr;
      
      





hrを使用したブロック設定の例
 import React from 'react'; const OptionsHr = ({ block, language, onPropChange }) => { return ( <div> <div> <label>{language["Custom style"]}: <input type="checkbox" checked={block.options.container.customStyle? 'checked': '' } onChange={(e) => onPropChange('customStyle', !block.options.container.customStyle, true)} /></label> </div> <hr /> <div> <label>{language["Height"]}: <input type="number" value={block.options.container.height} onChange={(e) => onPropChange('height', e.target.value, true)} /></label> </div> <div> <label>{language["Background"]}: <input type="color" value={block.options.container.backgroundColor} onChange={(e) => onPropChange('backgroundColor', e.target.value, true)} /></label> </div> </div> ); }; export default OptionsHr;
      
      





src / componentsフォルダーにもBlock.jsファイルがあります。このファイルには、 ブロックswitch ...のすべてのブロックが接続されており、 block_type (前述)がブロックのどのバージョンを返すかを決定します。







同じ原則は、設定用のOptions.jsファイルにもあります。 そして今、私はこのアーキテクチャからできるだけ早く逃げたいと思っています(誰かがどの方向に移行するのか考えているのでしょうか?) BlockList.jsファイルには、すべてがどのように機能するかを示すレターテンプレートが含まれています。tr > td要素はサイクルで構築されます。この場合、 tdは、要素を持つブロックが既に配置されているコンテナです。 コンテナ設定(block.options.containerのスタイル)もすぐに選択され、 DnDロジックも実装されます。 設定でもすべてが透明であり、onChangeハンドラーが入力ハングアップし、その中でonPropChange (prop、value、container?、element_index)がパラメーター(「色などの変更するプロパティ」、新しいプロパティ値、変更する要素(コンテナー- true、要素はfalse)、要素インデックス)。 原則として、これが主要なアイデアであり、これ以上伝えることはありません=)。 マインドマップでは、このパイプラインの動作をスケッチしようとしました。







見る







PSリポジトリには、 masterreact_email_editor_wordpressの 2つのブランチがあります。 原則として、特別な違いはありません。sagas/ api.jsファイル (WPにはAJAXへの独自のアプローチがあります)、フィードバックやソーシャルなどのブロックがあります(写真へのパスはそこで異なります... WP zhezh)。 エディターはWPに統合されており、現在テスト中です。







では、どのようにブロックを作成しますか?



とても簡単です! まあ、それはそうだと思います、なぜなら私はそれをしっかりと毎日働いていたからです...

ブロックのタイプを選択することから始めます。 インターネットをサーフィンしているときに、1つの素晴らしいテンプレートに出会いました。







見る







WEBSITES、SERVICES、SEOの3つのアイコンが付いたブロックが気に入りました。 さて、このようなブロックを実装する方法を教えてみます。 まず、ブロックの構成を決定しましょう。 ここには6つの要素があります。3つの写真と3つのテキスト要素です。その後、このブロックのビジョンをプログラムできます。 構成をできるだけ柔軟にしようとしたため、ほとんどすべてのレイアウト(たとえば、3つの画像テキスト要素)を自由に思いつくことができ、これは非常に実現可能です。 十分な言葉でコーディングしてください!







public / components.jsonファイルを開き、次のJSONを追加します。







ブロック3アイコン
 ...  ... { "preview": "images/3_icons.png", "block": { "block_type": "3_icons", "options": { "container": { "padding": "0 50px", "color": "#333333", "fontSize": "20px", "customStyle": false, "backgroundColor": "#F7F8FA" }, "elements": [{ "source": "https://images.vexels.com/media/users/3/136010/isolated/preview/e7e28c15388e5196611aa2d7b7056165-ghost-skull-circle-icon-by-vexels.png" }, { "source": "http://www.1pengguna.com/1pengguna/uploads/images/tipimgdemo/kesihatan.gif" }, { "source": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/56/Circle-icons-cloud.svg/2000px-Circle-icons-cloud.svg.png" }, { "text": "DEADS", "textAlign": "center" }, { "text": "LOVES", "textAlign": "center" }, { "text": "CLOUDS", "textAlign": "center" }] } } }, ...  ...
      
      





したがって、 画像3_icons.pngのプレビュー、コンテナ、および6つの要素を使用して、タイプ3_iconsのブロックを定義しました。 それらは既に何らかの基本的なスタイル設定を持っているので、追加すると多少まともに見えます。 OK、次にGIMP(インストールされている場合)を開き、その中のpreview_template.xcfファイルを開きます。 このファイルはプロジェクトのルートにあります。 ブロックのプレビューをリベットするために、これを空白にしました。 テンプレートの元の画像から簡単な操作(カット\貼り付け\色付け)を行うと、将来のブロックのプレビューが表示されます。













src / imagesフォルダー(またはpublic / images 、または両方の場所)に保存し、エディターでページを更新します。 components.jsonで挿入した位置に新しいブロックが追加されていることがわかります。







たとえば、HEADERの後に立ち往生しています







次に、ブロックテンプレートを作成します。 新しいBlock3Icons.jsファイルをsrc / components / blocksフォルダーに追加します。







Block3Icons.js
 import React from 'react'; const Block3Icons = ({ blockOptions, onPropChange }) => { const alt="cool image"; return ( <table width="450" cellPadding="0" cellSpacing="0" role="presentation" > <tbody> <tr> <td width="150"> <a width="150" href={blockOptions.elements[0].source}> <img alt={alt} width="150" src={blockOptions.elements[0].source} /> </a> </td> <td width="150"> <a width="150" href={blockOptions.elements[1].source}> <img alt={alt} width="150" src={blockOptions.elements[1].source} /> </a> </td> <td width="150"> <a width="150" href={blockOptions.elements[2].source}> <img alt={alt} width="150" src={blockOptions.elements[2].source} /> </a> </td> </tr> <tr> <td style={blockOptions.elements[3]}>{blockOptions.elements[3].text}</td> <td style={blockOptions.elements[4]}>{blockOptions.elements[4].text}</td> <td style={blockOptions.elements[5]}>{blockOptions.elements[5].text}</td> </tr> </tbody> </table> ); }; export default Block3Icons;
      
      





ご覧のとおり、最も単純なブロックは2行3列です。 要素の設定から、これまでは画像要素のソーステキスト要素のテキストのみを利用可能にしてきましたが、コンテナスタイルはテキストで前述したBlockList.jsファイルで使用されます。







ブロック設定を作成します。 src / components / optionsフォルダーに新しいOptions3Icons.jsファイルを追加します。







Options3Icons.js
 import React from 'react'; const Options3Icons = ({ block, language, onFileChange, onPropChange }) => { let textIndex = 3; let imageIndex = 0; return ( <div> <div> <label>{language["Custom style"]}: <input type="checkbox" checked={block.options.container.customStyle? 'checked': '' } onChange={(e) => onPropChange('customStyle', !block.options.container.customStyle, true)} /></label> </div> <hr /> <div> <label>{language["Color"]}: <input type="color" value={block.options.container.color} onChange={(e) => onPropChange('color', e.target.value, true)} /></label> </div> <div> <label>{language["Background"]}: <input type="color" value={block.options.container.backgroundColor} onChange={(e) => onPropChange('backgroundColor', e.target.value, true)} /></label> </div> <hr /> <div> <label> {language["URL"]} <select onChange={e => imageIndex = +e.target.value}> <option value="0">{language["URL"]} 1</option> <option value="1">{language["URL"]} 2</option> <option value="2">{language["URL"]} 3</option> </select> </label> </div> <div> <label> {language["URL"]} {imageIndex + 1}: <label> <input type="file" onChange={(e) => { onFileChange(block, +imageIndex, e.target.files[0]); }} /> <div>&#8853;</div> </label> <input type="text" value={block.options.elements[+imageIndex].source} onChange={(e) => onPropChange('source', e.target.value, false, +imageIndex)} /> </label> </div> <hr /> <div> <label> {language["Text"]} <select onChange={e => textIndex = +e.target.value}> <option value="3">{language["Text"]} 1</option> <option value="4">{language["Text"]} 2</option> <option value="5">{language["Text"]} 3</option> </select> </label> </div> <div> <label> {language["Text"]} {textIndex - 2} <input type="text" value={block.options.elements[+textIndex].text} onChange={e => onPropChange('text', e.target.value, false, +textIndex)} /> </label> </div> </div> ); }; export default Options3Icons;
      
      





いいね! ほぼ完了! 私たちはすでにここで作成していることを願っています、あなたは少なくともやや指向ですか? ブロックでは、すべてが愚かです(それはダムコンポーネントであるため、つまり、その小道具に基づいてのみレンダリングされます)。 設定では、各入力要素(チェックボックス、入力など)が、プロパティに対してonPropChange呼び出されるハンドラーに関連付けられています(上記でも説明しました)。 これらのプロパティに基づいて、ブロックは再び動的にレンダリングされます。 すべてがシンプルです。 ここで作業の結果を適用し、最終的にこれがまったく機能するかどうかを確認しましょう=)。







これを行うには、新しいブロックのインポートとその戻り条件をsrc / components / Block.jsファイルに追加します。







 //...  import'... import Block3Icons from './blocks/Block3Icons'; //... ... //...  case'... case '3_icons': return <Block3Icons id={block.id} blockOptions={block.options} />; //...  ...
      
      





src / containers / Options.jsでほぼ同じことを行います







 //...  import'... import Options3Icons from '../components/options/Options3Icons'; //... ... //...  case'... case '3_icons': return <Options3Icons block={block} language={language} onFileChange={onFileChange} onPropChange={onPropChange} />; //...  ...
      
      





これで、すべてのファイルが保存され、以前にプロジェクトのルートでnpm startを実行した場合、すべてがエラーなしでコンパイルされます。 新しいブロックをテンプレートにドラッグし、選択して、その設定を再生します。 これは私にとってどのように見えるかの例です:







表示する







合計



エディターをできるだけシンプルで使いやすく、インターフェースの面で非常に便利なものにしようとしましたが、それを行うかどうかはもちろんあなた次第です。 私の意見では、 Mosaicoとは対照的に、コンポーネントベースの導入と拡張の点で、エントリのしきい値が低いエディターが判明しました。 また、 Mosaicoと比較してはるかに透明性が高く、( EmailEditorと比較して)バグの少ない実装であり、文字通り数時間(ほとんどの場合は数日)で構成し、拡張し、ニーズに簡単に対応できます。







計画は引き続き次の項目で機能します。









私は、アドバイス、批判、フィードバックを喜んで支援します。 これに基づいて、プロジェクトに参加し続けるかどうかを決定します=)。







これですべてです...ご清聴ありがとうございました! もちろん、プロジェクトが誰かに役立つ場合は、非常に大きな変更についてのみ書きます。







約束のリンク






All Articles