2021-07-26Movidea開発日記
https://gyazo.com/f5ec0d0c1f2416f023576d43b5924157
---
ソフトウェア開発でドッグフーティングが有用ならメンタリングでも有用な可能性があるのでは?と今年は採択者に課せられる発表資料作りを自分もやってみてるのだけど、そうすると「中間発表までにユーザテストした方がいいね」って言いながら「自分のはまだテストできる段階でない」という気持ちが…
つまりこれは「作り手がユーザテストに適切だと思うタイミングと横で見てる人が適切だと思うタイミングにはズレがある」ということ。作り手は「あとちょっとこれを実装したらもっと良くなる」が予測できるので、何が起きるか予測不可能なユーザテストをやることよりも目先の実装を優先してしまう(自戒
今回僕はプロジェクト開始の発表の後に作り直しを決定したので「まだユーザテストできる状況ではない」と考えたくなってしまうが、これは典型的な「しない理由探し」なんだよな。今すぐやることが適切でないにしても、なるべく早く検証するには何をやるべきか考えたりタスクをブレイクダウンすべき ユーザテストをなぜやるかといえば「仮説を検証するため」
プロジェクトを進めてると未検証の仮説がたくさん生まれる(生まれたことに気づかないで「こうに違いない」と決めつけちゃうケースもあるが)
仮説の検証にはコストが掛かるからなんでも検証することはできない
だから、「これを検証しないままプロジェクトを進めたら大きな手戻り損失が発生するかも知れないぞ」という確率と損失額の掛け合わせで検証の優先度が決まるのだと思う。
そう考えると前回Regroupでは「紙でやって僕が価値を感じた手法、電子化すればもっと価値が上がるに違いない」が仮説であり、ReactとTypeScriptを学びながら作ったプロトタイプだが、僕自身がリピーターになることで仮説が検証された。で、他の人にも価値があるかを検証することが必要
Regroupのままユーザテストをしようとすると「新規作成に直感的でない2操作が必要、さらにオススメの初期状態にするために正解のわかりにくい操作が必要」がまず悪い、想定デバイス「iPad+Apple Pencil」も厳しい、だから作り直した
だから、まず検証すべきことは「初めて使う人が戸惑わずに使い始められるか」なのだと思う。その後に「初めて使う人が価値を感じられるか」が続く。使い始められないツールで価値を感じることはあり得ないので使い始めるところが最優先。
新規作成リンクをクリックしたら新規作成ダイアログが表示されるべき
付箋追加ダイアログと似てるけど別であるべき?
複数行テキストをペーストしてみよう、のテキストを用意してあげる?
単語
短いフレーズ
これは短い文章です
これはとてもとても長い文章なので付箋にすると文字が小さくなります
「適度な長さに刻むべき」は初めての人には負担が大きい
一度先に進まないと「なぜそれが必要か」を理解できないから
長すぎる行を刻むこと
自動でやる?
デフォルトONのチェックボックスをつける?
チュートリアルバルーンだす?
あった方が良い?
バルーンで出すのではなく動画のヘルプをページごとに見れたらいいのでは
Oxygen Not Includedスタイル
複数付箋追加が行われた時、バラの付箋であるべきか、グループ化されているべきか
グループ化されてると便利
うっかり既存の付箋の上に追加してしまった時に直感的に修復できる
グループ化されてることのデメリット
初めて使う人がいきなり「まずグループを選択して解除します」になる
いや、ならないか?
解除しないでグループ化したまま動かせばいい
ドラッグしてグループの外に出したらいい
「インポートしたけど使わなかったもの」をまとめて傍に退けておける
→グループ化されててOK
「新規作成に直感的でない2操作が必要、さらにオススメの初期状態にするために正解のわかりにくい操作が必要」が悪い
なぜそんなことになったのか振り返り
URLルートへのアクセスはリンク置き場になった
https://gyazo.com/d7bf516ac4f588a674a99db6e0b2696a
Routerの設計の問題
当初ヘルプ自体をRegroupでやろうとした
保存されないサンドボックスなどへのリンクを置いた
新規作成はクラウド保存されない
なぜ?
新規作成リンク
https://gyazo.com/08ff543690907be4bf19e83e8ac5c55a
Create Blank Map
クラウド保存済みリンク
新規タブで開く
環境によってポップアップブロックされるので後からダイアログに変更
ルーティングがどうであるべきか
初めての人が/に来るので、そこは余計な「選択肢から選ばせる」ことなくチュートリアルが開始するべき
Regroupは説明の乏しいリンクのリストがあるだけなのでよくない
Movideaはネット接続しないブランク画面
これはテストには便利なので残したいがこんな一等地にあるべきではない
viewとeditはURLでわかった方がよかった
Google Docsのメタファーだとviewとeditの他に「レイヤーを分けてコメントできる」があると面白そうだが、これは後ほどの実験とする
$ npx cypress open
code::
You are currently running Version 7.7.0
To get the latest, run the following command*:
npm:npm install --save-dev cypress@8.0.0
Regroupのコードを見る
ルーティングをどうやるのだろう、でreact-routerを見様見真似で使ってるが、典型的なユースケースにマッチしないので結局hashの値を自分で処理してる
そもそも典型的なユースケースは複数ページある風の表現だが、僕のアプリはシングルページであり、別の画面が必要な時もページ遷移ではなくダイアログ表示なので典型的なユースケースに当てはまらなかった
code:ts
import { Route, RouteComponentProps } from "react-router";
import { HashRouter } from "react-router-dom";
...
<HashRouter>
<Route path="/:id" component={WhenHashSpecified} />
<Route exact path="/">
...
...
const WhenHashSpecified: React.FC<RouteComponentProps<{
id: string;
}>> = props => {
return <App urlParam={props.match.params.id} />;
};
というわけでルーティング部分を実装した
テストコードのcy.visit("/")をcy.visit("/#blank")に変えたらテストがこけた
こけた原因はcy.visit("/#blank")でアプリの状態がリセットされないこと
テストコードの途中で状態リセットのつもりでvisitしてたがリセットされなかった
そのため、直前での範囲選択を引きずったまま新しい画面を描画しようとしてエラー
さらにはそこで発生した問題が次のテストケースに影響した風に見える
え?なんでリセットされないの?
cy.visit("/")ではリセットされてたよね?
cy.visit("/") の後の(A)では状態がリセットされているが、 cy.visit("/#blank")の後の(B)では値がリセットされていない
code:ts
describe("visit_reset", () => {
beforeEach(() => {
cy.visit("/");
});
it("main", () => {
cy.updateGlobal((g) => {
g.scale = 2;
})
cy.getGlobal(g => g.scale).should("eql", 2);
cy.visit("/");
cy.getGlobal(g => g.scale).should("eql", 1); // (A)
cy.updateGlobal((g) => {
g.scale = 2;
})
cy.visit("/#blank");
cy.getGlobal(g => g.scale).should("eql", 2); // (B)
});
});
テスト用のページに新しいURLを割り当てて、ルートにアクセスしたら付箋追加ダイアログが開いた状態で開始するようになった
次は複数付箋の追加をつける
ダイアログはつけてある
グループ化する
その次
付箋単体のドラッグでの移動(今はグループだけ)
グループの中から外へ
ドラッグターゲットがグループであるかどうかをハイライトで示したい
https://gyazo.com/036de58bf7b1abc5d6a47f4b38a7f90d
うーむ、記法をなんとかしないと
イベントハンドラ
行いたい処理
要素の包含関係によりstopPropagationで止める関係
時系列の動的スコープ
https://gyazo.com/3a2bfb6046f1470934bdb5fba8925ace
stopPropagation、そんな目立つ必要ないな、あと動的スコープはもっとはっきりしてた方がいいし、色が同じであることで同じものと認識するのは良くないな、名前があるべき
いま「選択状態か」が「選択されたものが存在するか」で判断してるけど、選択範囲に対象が存在しなかった時に選択範囲表示を消すか、選択対象がなくても選択状態に入るようにすべきか
後者の方が自然かな
閉じたグループの表札付箋を普通の付箋と同様にレンダリングしようとしてコードを再利用したのが良くなかった
再利用できるようにidがItemIdではなくstringになるようにしたため、ドラッグ対象に指定しようとして型エラー
再利用の単位が間違っていた
コンポーネントはFusenとNameplateFusenに分離した
共通して必要なフォントサイズ調節の処理はカスタムフックにする
付箋のdragstartが親のグループに伝搬しないようにする必要がある
図中の赤線
https://gyazo.com/63941a044cdf9b823c4d5fa040344dff
できた
https://gyazo.com/f5ec0d0c1f2416f023576d43b5924157
これでRegroup相当の移動はできたかな
図の更新
https://gyazo.com/d4f212e311dace98f0186f8535241eb3
いまはグループがdropを受け取らないので貫通してキャンバスにdropが起きてる
これをグループにdropしたら今の処理、キャンバスにdropしたらグループから出す、ということができれば良い
パターン発見した
https://gyazo.com/64a50b4a3cb7324d1ccc4339da5d680a
共通のイベントハンドラで異なる処理をしたい
片方が「処理をしない」なのも含まれる
この時「どちらの処理をするのか」の情報がイベントハンドラの外から得られる必要があり、それはつまり動的スコープである
キャンバスにドロップしたのかグループにドロップしたのかによって挙動が変わるなら今どちらであるのかをユーザがわかる必要がある
https://gyazo.com/e0840d722c87482ee1c5143acfd1c8e3
この実装で、図解にない処理が必要になった
https://gyazo.com/f87038d9dd418529f5805f9cfd2e9773
ドロップターゲットの中に別の要素がある時に、dragleaveイベントが発生してしまう
dragenterが先に起こるので参照カウント的な方法で外に出たかどうかを判断する
code:ts
let enter_count = 0;
const onDragEnter = (e: React.DragEvent<HTMLDivElement>) => {
enter_count++;
if (self.current !== null) {
self.current.style.borderColor = GROUP_HIGHLIGHTED_BORDER_COLOR;
}
e.stopPropagation();
}
const onDragLeave = (e: React.DragEvent<HTMLDivElement>) => {
enter_count--;
if (self.current !== null && enter_count === 0) {
self.current.style.borderColor = GROUP_BORDER_COLOR;
}
e.stopPropagation();
}
const onDrop = (e: React.DragEvent<HTMLDivElement>) => {
if (self.current !== null) {
self.current.style.borderColor = GROUP_BORDER_COLOR;
}
onGroupDrop(e, value);
}