prev Kozaneba開発日記2021-08-16

image

Kakau.appを試す

  • 自由度の高さを求めるならKozaneba的な方法の方が良いのだがKakauを良いと感じるのはどこか
  • 左下のヘルプボタンを押して出てくるものがキーボードショートカット、これがヒントだと思う
  • つまり「自由度の高い配置をポインターによって実現」ではなく「自由度の高い配置をキーボードによって実現」
    • キーボードでできる部分を重視してると思われる
  • 確かにKozanebaはポインターがないと使えないからな
    • 「当然あるだろ」と思ってる
    • Regroupは「iPad+ApplePencilがあるだろ」と思ったけど、まあまだそんなに一般的ではなかった
    • Kozanebaも「二本指ジェスチャーが可能なタッチパネルがある」を前提にしてる
    • とはいえユーザによってデバイスはまちまちなのでどう組み合わせるかはカスタマイズ可能な方がいいな
  • もしもKozanebaをポインターがない環境で使うなら?
    • カーソルが必要、それも二つ
    • 追加したこざねは追加時間によって暗黙の順序がある、なので一次元のカーソル操作で選択することができる、これがインプットカーソル
    • 配置する側にアウトプットカーソルがある、キー操作によってインプットカーソルで選ばれたこざねがアウトプットカーソルの位置に転送される
    • 自由度が落ちるのは仕方ない、自由度を高くしようとして複雑怪奇になってはいけない
    • キーボード操作の時には暗黙のグループが作られ、そこに追加される
      • アウトラインプロセッサのデフォルト追加がタブ区切りみたいなもの
      • 改行に相当する「改グループ」すると新しいグループが作られる
    • カーソルで選ばれたグループを前後に動かしたり畳んだり、手前のグループの末尾に参加したり
    • これ、要するに表示の仕方が変わっただけでアウトラインプロセッサ
    • アウトラインプロセッサ(やScrapbox)からインデント深さで表現したツリーをツリーのまま読み込めたらいいな、それは僕にも必要
  • つまり「ツリー構造をグループ表示にする」と「グループの配置からツリー構造を推測する」ができればいい、後者が簡単なものは簡単だが違和感なく動かすためには大変

ところでcurly braceに関しては「グループの枠の装飾」という形で導入するのが自然そう


昨日の続き

コンテキストメニューが出なくなった

  • うわー、そうか
  • mousemoveにdiv Aがついてくる時に、マウスカーソルがその下のdiv Bに入ったことを検知したい
  • mousedownのタイミングでdiv Aにpointer-events: none;
  • div Aのmouseupイベントも取れなくなる
  • 一番奥にある親のdivの mousemove / mouseup で取ってるので大丈夫…
  • ではなかった
  • mouseupはclickより先に発生するが、mouseupの中でpointer-events: auto;に戻してもclickは取れない

mousemoveしないでmouseupした時にonClickを呼び出せばいい

  • →メニューの表示位置をevent.currentTargetにしてる
  • mousedownのタイミングで要素を取っておく必要がある

ts

let itemStore: {[id: string]: TItem};
let x = itemStore["foo"];  // can be undefined

x は undefined になり得るのに TItem型 になるのおかしくない?なんかオプションあるのかな??

テスト

  • 初期状態(例えばこざねの1つ入ったグループが二つある、など)の作り方の変遷
  • T1: 最初はsetGlobalを露出して、それを使って適切な内部状態をセットした
  • T2: 次にそれをcy.fixtureでjsonから読むようになった ts
cy.fixture("group_simple_json.json").then((json) => {
  cy.setGlobal(json);
});
  • T3:
    • setGlobalはなんでもできすぎるのでプロダクションではユーザに露出しないと思ってる
    • そこで露出するであろうAPIを使って初期状態を作ることで、APIのテストを兼ねる ts
export const ready_two_groups = () => {
  cy.movidea((m) => {
    m.make_one_kozane({
      id: "1" as ItemId,
      text: "1",
      position: [-100, 0] as TWorldCoord,
    });
    m.make_items_into_new_group(["1"], { id: "G1", text: "G1" });
  });

};
- 追記: このやり方はimportした時にテストケースごと取り込んじゃうのでユーティリティ関数はsupportsに置くことにしました

初期状態を作るだけのテストファイルを作ってある

  • 例えば「人間が操作して確認してみたいやって時にそのファイルをCypressで開いておけばソースコードを編集するたびにブラウザがリロードされてその初期状態になるから便利
    • Q: なぜ人間が操作するのか
    • A:Cypressのテストでは「この要素にこのイベントが発生したらこう振る舞う」と記述するが、人間が自然な操作をした時にそのイベントが発生するとは限らない
      • 先程の「onClickが発生しない」みたいに
      • なのでそういうところを壊しうる修正の時には手で確認してからそれをテストにしてる
  • 試行錯誤しながらテストを作って行ってた時はファイルが雑然と分割されていた
    • image
    • 「あっ、この初期状態からこういう操作をするのをテストしなきゃ」でテストファイルを追加したせい
    • これは準備とテストが分離したことできれいに書けるようになる ts
import { ready_one_kozane } from "./ready_one_kozane";
import { ready_two_groups } from "./ready_two_groups";
 
describe("drag", () => {
  beforeEach(() => {
    cy.visit("/#blank");
    cy.viewport(500, 500);
  });
 
  it("one kozane", () => {
    ready_one_kozane();
...
  it("two groups", () => {
    ready_two_groups();
...  

  • ネストしたグループのハイライトがちゃんと動かないな…ドラッグ自体はちゃんと動いてるみたいだが。
  • ハイライトを直してネストしたグループのテストもできた
  • 次はチュートリアルのテストを動かすか
  • 選択が壊れたままだった!チュートリアルがあると一通りの機能のチェックができて便利だな(ぇ
  • やったー、チュートリアルのテストが全部通った!
  • DragとClick系のイベントハンドラを全廃してMouse系で全部やる大手術が、意外と2日掛からずに終わった、めでたしめでたし

image あ、いけない、終わったつもりになってたけど「閉じたグループ」の描画をコメントアウトしてたの忘れてた

  • チュートリアルにまだグループの開閉が書かれてないからな
  • 直った

image

  • 1枚だけこざねの入ってるタイトルのないグループと、閉じたグループが区別つかない問題を解決

つけたい機能

  • ダイアログのOK的ボタンを基本的にCmd+Enterで実行できるようにしたい
  • splitダイアログにreplaceボタン
  • ユーザページを作って過去の保存一覧
    • 保存してないページを閉じようとしたら警告する?
  • Regroupにあった「開いた時点でコンテンツの全体表示」
    • 中央でないところにこざねを置いてて「あれ?消えた!?」ってなるから
    • スペースキーで全体表示
  • 拡大をマウスカーソル位置中心に
  • ユーザページでキーバインドや表示デザインを変更可能に
  • Keichoからのインポート
  • Scrapboxからのインポート
  • インデント箇条書きテキストのグループとしてのインポート
  • 書き込み権限を絞った共有

つけたけどまだチュートリアルに入ってないもの

  • グループの開閉
  • グループにタイトルをつける
  • 付箋の分割
    • 複製や更新にも使えるよ
    • Replaceついてからがいいな

  • "noUncheckedIndexedAccess": true,
    • した
    • 二次元ベクトルをnumber[]にしているとv[0]がundefinedの可能性が出てくるので[number, number]にした

ついでにこれ

  • Twitter ベクトルを「ワールド座標系のベクトル」と「スクリーン座標系のベクトル」に分けて型で区別したら取り違えのバグが判明するかと思ったが「スクリーン座標系のベクトル二つの差(相対ベクトル)をワールド座標系に変換してるバグ」だった

    • つまり「ワールド絶対」「ワールド相対」「スクリーン絶対」「スクリーン相対」の4種類のベクトル型が必要(待て

  • こちらの件、実際にやってみたのですが(マジかよ)
  • 例えばグループの中にあるこざねをドラッグしてグループの外に出した時にグループのpositionの分だけpositionが変化するのですが、もののpositionが絶対座標だとすると絶対座標同士の加算になってしまう。
    • これは「グループの中に入ってるこざねの位置は実は絶対座標ではなくグループの位置からの相対座標だから」なのだけど、そうなるとpositionは相対もしくは絶対という型になり、どちらであるかは親を見なければ特定できない(ダメだろその設計)
  • もう一つのやり方は「親がないオブジェクトの位置は原点からの相対座標である」とする方法。この場合オブジェクトの位置はすべて相対座標なので足しても引いても相対座標…計算結果に絶対座標の[0, 0]を足して絶対座標が得られる…
    • それだったら型を分けた意味がないよね…
  • それから重心の計算、N個の絶対座標を足し合わせてNで割ることによって辻褄があう計算。これはNに依存した型が必要で…
  • とまあそんな感じで、途中までやったけどこれを貫いても今後のコストが上がる割にバグ避けの効果はあんまりないなーと思ったのでやめることにしました
  • 値の側をいじるなら[x, y][x, y, 1]に拡張すれば解決する話で、差をとれば3番目が0になって相対ベクトルになり、N個足してNで割るのも3番目が1に戻ることで整合性検証できる。なんだけどこの方式でも結局最後に[0, 0, 1]を足すだけなんで複雑にする割にメリットがない

  • ✅拡大をマウスカーソル位置中心に
    • image

/forum-jp/このサイトについてを見て考えてたのだが「Kozanebaへの要望」にするのでいいのかどうか

  • プロジェクトは「細分化せずにまとめた方が良い」「メンバーによって区別」だとするなら、インバイトリンクを置いて誰でも入れるようにしたグループは一つでいい気がする
  • 一つになるならKozanebaに限らずKeichoも「エンジニアの知的生産術」も全部入れたらいいじゃん、となる
  • しかしそうなるとプロジェクトのタイトルが「西尾への要望」とかになる、それは変
  • もう少し正確に書くなら「西尾の知的生産性に関する執筆やソフトウェアに対する要望」とでもなるか、長ったらしい
  • そもそも「要望」って言葉が強い
    • だけども「要望を書いて良い場所だ」としたことによって、要望のある人に書き込むインセンティブが生まれたのかもしれない
    • 「議論しましょう」だと書き込まない人いるよね
    • 逆に自説を長々と書いてくる人もいそう

next Kozaneba開発日記2021-08-18