Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

コンテキストメニューのVue化 #1374

Merged
merged 102 commits into from
Jul 22, 2023

Conversation

thiramisu
Copy link
Contributor

@thiramisu thiramisu commented Jul 6, 2023

内容

コンテキストメニューの実装を Electron から Vue + Quasar に変更します。

設計の概要

既存のcomponents/MenuItem.vueをコンポーネントとして使用し、<q-menu touch-position context-menu><q-list dense>で囲いました。
コピーなどのアクションはpure javascript寄りでゴリゴリ書きました。
AudioCell.vueの// コピペしたときに句点と改行で区切る(175~213行目)のあたりは貼り付け処理を書きやすいようにリファクタリングをしただけです。

PR作成前からの不具合

その1: 以前の選択範囲が残る

詳細: #1364 (comment)
範囲選択状態からフォーカスを外し、そこからコンテキストメニューを開くと、なぜか以前の選択範囲が残ったままになる不具合です。以前の選択範囲を復元するのはinput.focus()時のデフォルトの動作のようです。調査に時間がかかりそうです。

その2: 特定条件下で貼り付け時にエラーが出る

空セルに実テキストが一行で末尾が\nの繰り返しで終わるテキスト(追記: 精確には改行を含むがAudioCellの数は増えないようなテキスト、でした)をCtrl+vで貼り付けようとすると開発者ツール上でエラーが出る不具合です。その動作を流用したためコンテキストメニューを使った貼り付けでも同じエラーが出ます。
フォーカスに失敗してるだけで致命的ではなさそうなので、直せたら別PRとして送ろうと思います。

関連 Issue / PR

スクリーンショット・動画など

その他

将来的な話:

  • 範囲選択せずに[コピー]や[切り取り]した時にAudioCell全体のコピペができるようにするのもアリかなと思いました。
  • clipboardAPIを使うとBlobデータ(つまりjsonとか)もコピペできるはずですが、2つのVOICEVOX間でデータをコピペ、とかすることはなさそうですね…

新たに発生する不具合

[追記: 解消の目途が立ちました]
コンテキストメニューを開いている間、テキスト選択範囲 (青の反転) などが表示されなくなります。
原因不明で、現在調査中です。
詳細: #1364 (comment)
electron_vs_quasar

報告後に気付いた調査の抜け:

  • Context Menu example を使った調査では<div>内にテキストを追加して試したのですが、<input>内の選択範囲は通常の選択範囲とは別の実装で操作する必要があるのを考慮すると、<q-input>内では別の結果になる可能性があります。

まだ試してないこと:

  • 上述の調査の抜け
  • ブラウザ版 を使ってブラウザでの挙動を確認する
  • Quasarのバージョンアップ

[追記: ここまで解消の目途が立ちました]

@@ -0,0 +1,32 @@
<template>
<q-menu touch-position context-menu>
Copy link
Member

@Hiroshiba Hiroshiba Jul 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここに no-focus をつけると行けそうでした!
フォーカスが外れて(blurされて)選択状態が解除され、選択範囲のレンダリングが消えるのが原因かなと思いました。

ちなみにgoogle documentとかは右クリックしても選択範囲消えないんですよね。
なにかinput要素に対して特別なことをやってるのかなと思って調べてみたら、canvasでした 😇
↓ google documentで右クリックしている時の例
image


アクセシビリティに詳しそうな @yncat さんにお聞きしたいことが 🙇‍♂️
no-focusをつけると右クリックのメニューにフォーカスされないので、キーボードのみの方は右クリックメニューを実行するのが大変になりそうです。
これって結構不便だったりされますか・・・?
なんとなく、そもそも右クリックメニューはほぼ使わず、ショートカットキーで操作される方が多いのかなと想像しています。
一般的な右クリックとアクセシビリティのことをご存知でしたらご教示いただきたく・・・!!

Copy link
Contributor Author

@thiramisu thiramisu Jul 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

正解がこんな足元に転がっているとは思っていませんでした…難しく考えすぎていました。
代わりに原因究明&解決までしていただき、ありがとうございます!🙇‍♂️🙇‍♂️🙇‍♂️

フォーカスされないことで一部実装が変わりそうなので、その辺はまた考えます。
とりあえずno-focusはつけておきました!


google document

canvas消したら見た目からは文字とか選択範囲とか消えたんですが、見えない文字がコピペできる…?!一体どうなってるんでしょう…

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

まだ試してないですが、たぶんno-focus付けてしまうとキーボードでコンテキストメニュー使えなくなる気がする...
要動作検証

@yncat
Copy link
Contributor

yncat commented Jul 8, 2023

アクセシビリティ周りに関して、見解を共有します。

まずは、正直な話をします。
今のmain branchの状態では、最終的にOS標準のメニューとして描画されるようで、アクセシビリティが高いです。
これを vue で独自実装すると、全体的にスクリーンリーダーでの操作性はかなり低下します。具体的には、OS標準で使えるメニューのキーボード操作がほとんど利用できなくなり、サブメニューのハンドリングもやりにくくなります。

ただ、それをやらないでくださいというのは本末転倒だと思うので、前向きな方向で考えると。

最終的には、実際に動作を確認してみて、操作できるかできないか、ということになると思います。

このPRをローカルに持って来て実行しようとしたら、 error? warning? で実行できませんでした。この部分の修正いただくことってしゅっと可能でしょうか?

もしかしたら warning で、ウィンドウを消せば使えるかもしれないのですが、 click outside (ダイアログの外側をクリック) の操作が画面見ないとできないので、自分ではこの画面を dismiss できず先に進めない状況です。

[plugin:vite:vue] Duplicate attribute.
D:/git/voicevox/src/components/AudioCell.vue:42:7
40 |        @keydown.prevent.down.exact="moveDownCell"
41 |        @keydown.prevent.enter.exact="pushAudioTextIfNeeded()"
42 |        :aria-label="`${textLineNumberIndex}行目`"
   |         ^
43 |      >
44 |        <template #error>
    at createCompilerError (D:\git\voicevox\node_modules\@vue\compiler-core\dist\compiler-core.cjs.js:19:19)
    at emitError (D:\git\voicevox\node_modules\@vue\compiler-core\dist\compiler-core.cjs.js:1613:29)
    at parseAttribute (D:\git\voicevox\node_modules\@vue\compiler-core\dist\compiler-core.cjs.js:1354:9)
    at parseAttributes (D:\git\voicevox\node_modules\@vue\compiler-core\dist\compiler-core.cjs.js:1330:22)
    at parseTag (D:\git\voicevox\node_modules\@vue\compiler-core\dist\compiler-core.cjs.js:1199:17)
    at parseElement (D:\git\voicevox\node_modules\@vue\compiler-core\dist\compiler-core.cjs.js:1128:21)
    at parseChildren (D:\git\voicevox\node_modules\@vue\compiler-core\dist\compiler-core.cjs.js:943:28)
    at parseElement (D:\git\voicevox\node_modules\@vue\compiler-core\dist\compiler-core.cjs.js:1144:22)
    at parseChildren (D:\git\voicevox\node_modules\@vue\compiler-core\dist\compiler-core.cjs.js:943:28)
    at parseElement (D:\git\voicevox\node_modules\@vue\compiler-core\dist\compiler-core.cjs.js:1144:22
Click outside or fix the code to dismiss.
You can also disable this overlay by setting server.hmr.overlay to false in vite.config.js.

@paste="pasteOnAudioCell"
@focus="setActiveAudioKey()"
@keydown.prevent.up.exact="moveUpCell"
@keydown.prevent.down.exact="moveDownCell"
@mouseup.right="onRightClickTextField"
@keydown.prevent.enter.exact="pushAudioTextIfNeeded()"
:aria-label="`${textLineNumberIndex}行目`"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なんか私が入れたやつが怒られてますね

Attribute ":aria-label" should go before "@keydown.prevent.enter.exact"

ほんとに順番入れ替えれば治る問題なんだろうかw

Copy link
Member

@Hiroshiba Hiroshiba Jul 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

いったんこちらに関してだけコメントをば 🙇
これはエラーというよりワーニングで、並び順は順不同で複数人で書いてるとごちゃごちゃになっちゃうので、順番を作ってそれを守りましょうという感じの意図のメッセージです!

@thiramisu
Copy link
Contributor Author

なるほど、勉強になります…

コンフリクトの解消についてよく分かってないのに適当にボタンポチポチしたせいで、2箇所エラー吐いてました!多分直りました。よろしくお願いします。

@yncat
Copy link
Contributor

yncat commented Jul 8, 2023

やっぱり、no-focusがついていることによって、スクリーンリーダーでコンテキストメニューに触ることが絶対に不可能になってしまいました。
フォーカスされないので、inputの中にずっといるように見えて、コンテキストメニューが開いたことすら認識できないです。

@thiramisu
Copy link
Contributor Author

thiramisu commented Jul 8, 2023

確認ありがとうございます!
windowsの標準録画機能にも映るようになってたり、矢印キーとかに反応しなくなってたりで、そんな気はしていました。

  • やっぱりelectronで実装する
  • no-focusを設定から変えられるようにする
  • 何かの方法で選択範囲を表示する

選択肢はこんな感じで、3番目を最初に検討すべきでしょうか?
まあ原因は分かったので対処も以前よりしやすいかなと思ってます!


google documentはしっかり選択範囲を表示しつつTabキーとかで操作できてたので、謎のcanvasとかはそこら辺も考慮しての実装なのかもですね。

@Hiroshiba
Copy link
Member

Hiroshiba commented Jul 8, 2023

@yncat さんありがとうございます!!
no-focus、一番最初にfocusが移動することを抑制するだけかなと思ってたのですが、フォーカスすることがそもそもできなくなるんですね・・・・・。

これは流石にだいぶ課題だなと感じました。


@thiramisu 3つの迂回策、同意見です。
3番めがやはりいちばん良いかなと思います。
quasarの中の、更にinputの中身をいじることになりそうなので、覚悟が必要そうですが・・・。

google doctumentほんとにすごいですね。。。
まあでもリッチなテキストエディタを作っている人は同じ課題に当たっているはずなので、その辺りの知見を活用できるかも?

そういえばニコ動も右クリックコンテキストメニューがあったので見てみたところ、クリックしたコメントがコンテキストメニューの一番上に表示される形になっていました!

長い文字列だった場合どうすればいいんだという問題はありますが、少なくとも表示範囲が消えるだけよりマシかな~と。
(一旦この妥協案もかなりありかなと思いました!)

@thiramisu
Copy link
Contributor Author

thiramisu commented Jul 22, 2023

選択範囲が残る挙動の変更、アプリ全体に適用されるようにしてみました。
ちなみにonBlur(onFocusout)での実装にしない理由は2点あります。

理由1.
以下だと正常に選択範囲を空にできるんですが、
https://codepen.io/thiramisu/pen/poQZbdr
実際にEditorHome.vueに以下を書くと、

  const on =
    //*/ この行先頭の/の付け外しで切り替え
    "focusout"; // eslint-disable-next-line prettier/prettier
    /*/
    "focusin"; // eslint-disable-next-line prettier/prettier
    //*/
  document.addEventListener(
    on,
    (event: FocusEvent) => {
      if (event.target instanceof HTMLInputElement) {
        event.target.selectionEnd = event.target.selectionStart;
        console.log(
          "to empty: ",
          event.target.selectionStart,
          ",",
          event.target.selectionEnd
        );
        console.log(event);
      }
    },
    { capture: true }
  );

発火自体はしていて、代入もされているんですが、なぜか効果がありません…
(念のため補足しておくと、ネイティブなエレメントに値を代入した場合、その時点で型チェックみたいなのが走るので、あえて不正な値を代入することで代入が実行されているかをある程度判断できます。例えば

        event.target.selectionEnd = -999;

とすると正常な値に補正されているのが分かると思います。)

理由2.
no-focusがついていないメニューを開く時はfocusEvent.relatedTarget === nullになるため、イベントがコンテキストメニュー由来かの判別が面倒になります。
閉じるときはno-focusありでもなしでもrelatedTargetが存在するので、判別が楽です。
参考: FocusEvent.relatedTarget


ちなみに、willDispatchFocusOrBlurも理由2により消せそうで消せません…回避のために開く際に手動でダミーエレメントをフォーカスすると、閉じる際に再フォーカスが必要になり、逆に処理が複雑になったりします。

@Hiroshiba
Copy link
Member

本でいうところの節の境目を空行1行、章の境目を空行2行で表すみたいな使い方してたので、空行維持の方が使いやすかったです。

なるほどです、よくわかりました!!
ただ、一長一短あるときに仕様変更すると、元の方が都合が良かった人の「なぜ変えてしまったのか」という反応に応えづらかったりします。
プルリクエストの内容とずれた仕様変更になりますので、一旦元のままにして頂けますと幸いです。申し訳ない🙇

一旦issue化してみました。
#1417

mousetrapと干渉して

なるほどです、今試してみたらたしかに問題が発生していそうでした!(ワークアラウンド適用後のコードで試していたかもしれません)
ボタンの押下を代替するキーコマンドはエンター以外にスペースなどもあり、このワークアラウンドでは不十分かもしれません。
可能であればここのコメントのようにmousetrap側を一旦無効にするのが良いのかなとか思いました。

一旦ワークアラウンドなしでマージし、その後に修正を多角的に検討して試みるのはどうでしょう?
(重要度がある程度高い問題だという意識はありますが、このプルリクエストがいつまでもマージされないということになりそうなのでそろそろマージに向かいたいなと・・・!)

選択範囲が残る挙動の変更、アプリ全体に適用されるようにしてみました。

あーー全要素への適用は副作用が怖いので慎重になりたいかもしれません。
他の方が別のことを試してなにか妙な挙動があったとき、流石にここに辿り着けないので・・・・・・・。
focusが当たり直すことがよくあり、選択範囲が残ってると不便なのは今のところAudioCellだけなので、影響範囲をここに絞っておくのはどうでしょう 🙇

@Hiroshiba
Copy link
Member

コード変更ありがとうございます!
ちょっと変更が重なっていて、一度レビューしたコードを何度もレビューするため若干効率が悪く感じ始めました。
一度マージを目指せれると幸いです!!

@thiramisu
Copy link
Contributor Author

thiramisu commented Jul 22, 2023

とりあえずマージを目指す、了解です!加減が分かっておらず、すいません。止めていただけて助かります。

一旦issue化してみました。

ありがとうございます。こちらは一応の確認をしたまでで、挙動はほぼ以前のままなので、マージ的な面では大丈夫だと思います。

エンター以外にスペースなどもあり

以下の実装を参考にして、ある程度妥当な実装にはできました。
https://craig.is/killing/mice#api.stopCallback
https://github.com/ccampbell/mousetrap/blob/master/plugins/pause/mousetrap-pause.js
同じ<q-item>だったのでついでに#1416 も直ったみたいです。
それはそれとしてスペースキーはまだ効かないのですが、Quasar 公式の例の サンプル でも効かないので、仕様に近いのかなと思います。
エンターはこの実装で、スペースのほうはひとまず諦めて良いと思うのですが、どうでしょうか?
もしこの実装自体が妥当でなさそうであれば、マージを優先し、エンターキーも諦めようかと思います。

全要素への適用は副作用が怖い

確かにそうですね、AudioCellのみの版に戻しておきました。


一応全てResolvedにはなっているので、他にやり残していることなどもあれば教えていただけると助かります。

@Hiroshiba
Copy link
Member

Hiroshiba commented Jul 22, 2023

以下の実装を参考にして、ある程度妥当な実装にはできました。

あーーーーなるほどです!!!!
なぜこのメニューでだけ問題が発生していてのか疑問だったのですが、たぶんq-itemが<div>でここにfocusが当たっていたからeventの伝播が止まらない、とかですかね!!
stopCallbackを上書きするの、良い手法に思いました!

Copy link
Member

@Hiroshiba Hiroshiba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ほぼほぼLGTMです!!!

1点アニメーションをなくす提案をしていて、これがもしかしたら結構変更を必要とするかもしれません。
その場合は申し訳ないです。。。

src/components/AudioCell.vue Outdated Show resolved Hide resolved
src/components/AudioCell.vue Show resolved Hide resolved
src/components/AudioCell.vue Outdated Show resolved Hide resolved
src/components/AudioCell.vue Outdated Show resolved Hide resolved
src/components/ContextMenu.vue Outdated Show resolved Hide resolved
src/components/ContextMenu.vue Outdated Show resolved Hide resolved
src/views/EditorHome.vue Outdated Show resolved Hide resolved
@thiramisu thiramisu requested a review from Hiroshiba July 22, 2023 21:15
@Hiroshiba
Copy link
Member

LGTM!!!!!!!!!!!!!!!

いや~~~~~~~~~かなり手強かったですね!!!!!!!
「操作を作る」点でとても良い勉強になりました。お疲れ様でした!!!

@yncat さんもありがとうございました!)

これでもともとのモチベーションであった「見た目上のテキストだけを編集」する機能の追加
#1353
のスキルツリーが解禁された形でしょうか。

じつは右クリックメニューに関してもう1つあったりします。こちらはこちらでどうやってダイアログに文字を渡すのかという課題がありそうですが、もしご興味あればぜひ・・・!!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants