はじめに
Power Apps でタスク管理作成してみましたのでその作成方法を簡単にまとめます。
バトン託されたので作成したよ!
— コルネ (@koruneko32767) September 12, 2021
サイコロを振るアニメーションはめんどくさすぎて諦めましたwww
お次のバトン先ですが @Yo_Yon21 さんお願いできますでしょうか!?#PowerApps https://t.co/CnmbEf8Zsq pic.twitter.com/D4U7oNxVVn
こちらのアプリの作成した経緯はAkira さんからバトンをもらったからです。
面白い試み?企画?ですねー
#PowerApps @mk03168390 さんが作ってたアプリを、徹夜ついでに期待に応えて作成してみました。
— Akira_365 (@akira_365) September 9, 2021
意外と面白かったので、次は@koruneko32767 さんあたりにパスしようかな…(彼はこういうの面白がってやってくれるはず…) pic.twitter.com/EOYEwJvhyp
Akira さんはmk さんからバトンが回ってきて作成したようです。
できましたー!
— mk (@mk03168390) August 4, 2021
コレクションを使って、Rand関数で任意の行数の値を取得する形にしました。
Shuffle関数だと参加人数が10人未満の時に空白ができてしまうので、重複もできるようRand関数を使ってみました。(1~4人目はただの飾りです。) pic.twitter.com/tEQmwJik1v
関連記事
Akira さん
mk さん
タスク管理アプリを作成する
ヘッダを作成する
ヘッダ部分ですが以下みたいなちょっとポップなフォントにして、縁取りしたかったのですが
これはPower Apps のラベルコントロールだけでは実現できません。
フォントの問題に関しては、Power Apps のフォントプロパティの選択肢だと以下しか選べませんが
このようにフォント名を直接指定することで、 Font
であらかじめ定義されているフォント以外も指定することができます。
フォント名にスペースを含む場合は '
(シングルクォート)で囲めばよいです。
ただ、文字の縁取りは現状Power Apps のラベルコントロールでは行うことができません。
なので今回はSVG を使って文字を表示させたいと思います。
Image
"data:image/svg+xml,"& EncodeUrl( "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg'> </svg>" )
みたいに記載することで利用できます。
ほかの表現方法は
をご覧ください。
画像コントロールを追加し、以下のように式を記載します。
TitleCharImage.Image
"data:image/svg+xml,"& EncodeUrl( "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg'> <rect width='"& Self.Width & "' height='" & Self.Height & "' fill='aqua' /> <text x='"& (Self.Width - AuthorImage.Width) / 2 &"' y='"& Self.Height / 2 - 10 &"' font-family='HGPSoeiKakupoptai' font-size='60px' font-weight='bold' stroke='black' stroke-width='2px' fill='white' text-anchor='middle' > 今から <tspan dx='-100' dy='70'>なにする?</tspan> </text> </svg>" )
<rect>
部分が背景の塗りつぶし。
<text>
部分が文字の設定。
となっています。
今回は細かい説明は省きます。
ToDoList を作成する
ToDoList は以前Redo & Undo の実装方法の説明で紹介したもののほぼ使いまわしです。
少し変えた点として、Redo & Undo 時、詳細を表示するかの判定は含めないので、現在のコレクションの値を参照するように変更しました。
UndoIcon.OnSelect
Set(nowHistoryID, nowHistoryID - 1); With( { colVal: MatchAll(LookUp(History, _id = nowHistoryID, data), "(?<="":).*?(?=(,|}))") }, If( Mod(CountRows(colVal), 9) = 0, ClearCollect( Tasks, ForAll( Sequence(CountRows(colVal) / 9), { _id: Value(Last(FirstN(colVal, 9 * ThisRecord.Value - 8)).FullMatch), achievement:Value(Last(FirstN(colVal, 9 * ThisRecord.Value - 7)).FullMatch), details: Substitute(Last(FirstN(colVal, 9 * ThisRecord.Value - 6)).FullMatch, """", ""), endDate: Value(Last(FirstN(colVal, 9 * ThisRecord.Value - 5)).FullMatch), level: Substitute(Last(FirstN(colVal, 9 * ThisRecord.Value - 4)).FullMatch, """", ""), startDate: Value(Last(FirstN(colVal, 9 * ThisRecord.Value - 3)).FullMatch), status: Substitute(Last(FirstN(colVal, 9 * ThisRecord.Value - 2)).FullMatch, """", ""), task: Substitute(Last(FirstN(colVal, 9 * ThisRecord.Value - 1)).FullMatch, """", ""), - viewDetail: Substitute(Last(FirstN(colVal, 9 * ThisRecord.Value - 0)).FullMatch, """", "") = "true" + // 詳細の表示/非表示は履歴に含めないので元コレクション参照 + viewDetail: Last(FirstN(Tasks, ThisRecord.Value)).viewDetail } ) ) ) )
また、スクリーンを遷移しても変わらず設定したタスクを記憶しておきたかったので、 Screen.OnVisible
に記載されていた式は App.OnSelect
に変更しました。
App.OnSelect
ClearCollect( Tasks, { _id:1, task:"", details:"", startDate:Today(), endDate:Today() + 1, level:"Normal", status:"未開始", achievement:0, viewDetail:false } ); ClearCollect( History, { _id:1, data:JSON(Tasks, JSONFormat.IgnoreBinaryData) } ); Set(nowHistoryID, 1)
これにより、 nowHistoryID
がコンテキスト変数からグローバル変数に変更になったのでそれに伴って他で利用しているコンテキスト変数もグローバル変数を更新するように変更しておきましょう。
(RedoIcon.OnSelect
, UndoIcon.OnSelect
, SetHistory.OnSelect
が対象のはずです。)
サイコロを作成する
サイコロは以下のようなサイコロっぽいレイアウトで作成します。
Power Apps のアイコンコントロールに存在する、Rectangle だけではこのようなレイアウトを作成することはちょっとできないので、ここもSVG で表現します。
DiceImage.Image
"data:image/svg+xml,"& EncodeUrl( "<svg viewBox='0 0 "& Self.Width & " " & Self.Height & "' xmlns='http://www.w3.org/2000/svg'> <defs> <g id='cube' class='cube-unit'> <rect width='" & 31.5 * RoundDown(Self.Width / 75, 0) &"' height='" & 36 * RoundDown(Self.Height / 75, 0) &"' fill='#CCCCCC' stroke='#000000' transform='skewY(30) scale(1,1) rotate(0) translate(0 0)' /> <rect width='" & 31.5 * RoundDown(Self.Width / 75, 0) &"' height='" & 36 * RoundDown(Self.Height / 75, 0) &"' fill='#EEEEEE' stroke='#000000' transform='skewY(-30) scale(1,1) rotate(0) translate(" & 31.5 * RoundDown(Self.Width / 75, 0) &" " & 36 * RoundDown(Self.Height / 75, 0) &".3)' /> <rect width='" & 31.5 * RoundDown(Self.Width / 75, 0) &"' height='" & 31.5 * RoundDown(Self.Height / 75, 0) &"' fill='#FFFFFF' stroke='#000000' transform='skewY(0) scale(1.41,.81) rotate(45) translate(0 -" & 31.5 * RoundDown(Self.Width / 75, 0) &")' /> </g> </defs> <use href='#cube' x='" & 5 * RoundDown(Self.Width / 75, 0) &"' y='" & 20 * RoundDown(Self.Height / 75, 0) &"' /> <text x='" & (35 + Len(Text(Last(FirstN(Objectives, 1))._id))) * RoundDown(Self.Width / 75, 0) &"' y='" & 23 * RoundDown(Self.Height / 75, 0) &"' font-family='Verdana' font-size='35' text-anchor='middle' > "& Last(FirstN(Objectives, 1))._id &" </text> <text x='" & (20 + Len(Text(Last(FirstN(Objectives, 2))._id))) * RoundDown(Self.Width / 75, 0) &"' y='" & 39 * RoundDown(Self.Height / 75, 0) &"' font-family='Verdana' font-size='35' text-anchor='middle' transform='skewY(30) scale(1,1) rotate(0) translate(0 0)' > "& Last(FirstN(Objectives, 2))._id &" </text> <text x='" & (20 + Len(Text(Last(FirstN(Objectives, 3))._id))) * RoundDown(Self.Width / 75, 0) &"' y='" & 45 * RoundDown(Self.Height / 75, 0) &"' font-family='Verdana' font-size='35' text-anchor='middle' transform='skewY(-30) scale(1,1) rotate(0) translate(" & 31.5 * RoundDown(Self.Width / 75, 0) &" " & 36 * RoundDown(Self.Height / 75, 0) &".3)' > "& Last(FirstN(Objectives, 3))._id &" </text> </svg>" )
<rect>
要素は3つありますが、上から順に2, 3, 1(画像の数字)の四角形となっています。
<text>
要素も3つありますが、こちらは上から順に1, 2, 3(画像の数字)のテキストとなっています。
テキストで表示している数字はTodoList で作成したタスクの内部ID を表しています。(タスクのタイトルを表示してしまったら文字サイズや位置の調整などが面倒ですからね。。。)
ただ内部ID を表示されてもユーザはなにかわからないのでこのID がどのタスクを表しているのかわかるように参照できるリストを表示させましょう。
これは、ヘッダ部分はコンテナで作成し、リスト部分はギャラリーで作成しています。
サイコロでタスクの抽選を行う
「サイコロを動かす」というタイトルにしたかったのですが、サイコロを振るようなアニメーションをSVG で表現するのは大変面倒だったので、今回は辞めました。。。
animateTransform を利用するのではなく、立方体の向きが異なるイメージを数パターン用意しておいてそれをパラパラ漫画のように表示させる方式であればサイコロを振っているようにみえるかもです。
今回は数字をランダムに変えることで抽選を行うことにしたいと思います。(それサイコロの意味ある?という指摘は無しでwww)
数字は Objectives
コレクションの最初の3つの _id
を表示するようにしているのでこのコレクションの中身をShuffle 関数でランダムに変更させます。
まず初期表示時にもランダムな数値を表示させたいので、 OnVisible
に以下式を設定します。
また、 OnVisible
では、抽選中かどうかを判断させるための変数を宣言しておきます。
Screen.OnVisible
ClearCollect(Objectives, Shuffle(Filter(Tasks, !(_id in todayTasks._id)))); UpdateContext({isRoll:false})
Shuffle(Filter(Tasks, !(_id in todayTasks._id)))
の解説は後程行います。
抽選を行ったり止めたりするためのボタンを用意して、以下式を設定します。
RollBtn.OnSelect
UpdateContext({isRoll:!isRoll});
このボタンが押されたら(isRoll
が true
になったら)タイマーをスタートさせ、繰り返し処理を行われるようにして、 Objectives
の中身をシャッフルさせます。
RollTimer.Start
isRoll
RollTimer.OnTimerEnd
ClearCollect(Objectives, Shuffle(Filter(Tasks, !(_id in todayTasks._id)))); Select(TaskReferenceGallery, First(Objectives)._id)
Duration
はサイコロの数字の表示が切り替わる間隔になります。
私の設定では1 に設定しています。
OnTimerEnd
にて Select(TaskReferenceGallery, First(Objectives)._id)
という式を設定していますが、これはサイコロの右に設定しているID とタスクを紐づけために表示を行っているギャラリーでサイコロの一番上の数字(ID)のレコードを選択するための式となっています。
(このため、対象のギャラリーは現在選択されているレコードを塗りつぶすように設定しています。)
抽選されたタスクを記録する
抽選されたタスクを記録するためのコレクションを用意します。
タスクを記録するタイミングは抽選終了時なので、抽選ボタンの OnSelect
で設定します。
ただし、抽選を行うのは抽選を開始したタイミングではなく抽選を終了したタイミングということに注意が必要です。
RollBtn.OnSelect
UpdateContext({isRoll:!isRoll}); If( !isRoll, Collect( todayTasks, AddColumns( Table(First(Objectives)), "ToDo", Last(Sort(todayTasks, SortOrder.Descending)).ToDo + 1 ) ) )
このとき、抽選されたタスクをそのまま設定するだけでなく ToDo
フィールドを追加して"・やることそのX"という文字列を表示できるようにしておきます。
抽選で選択されたタスクは抽選結果で選ばれないようにする
一度選ばれたタスクはもう抽選されてほしくないので、除外するようにします。
それが先ほど後で解説するといった、 Shuffle(Filter(Tasks, !(_id in todayTasks._id)))
です。
in 演算子 では、 Tasks._id
の値の中に存在する todayTasks._id
を選択します。
ただ、今回は存在しないものを選択するようにしたいので !
で否定を取っています。
これで、選択されたタスクは抽選に含まれないようになりました。
ToDo フィールドを連番にする
追加されたToDo の値は連番で追加されていくようになっていますが、削除した場合はToDo の値は連番ではなく抜けてしまいます。
なので、削除したレコード以降のレコードのToDo の値を更新してあげる必要があります。
DeleteIcon の OnSelect
にて選択されたレコードを削除し、そのレコードの値以降にToDo フィールドの値を更新するようにします。
DeleteIcon_1.OnSelect
RemoveIf(todayTasks, _id = ThisItem._id); UpdateIf(todayTasks, ToDo >= ThisItem.ToDo, {ToDo:ToDo - 1})
おわりに
バトン企画楽しいですねー
同じようなテーマのアプリでも人によって作成するアプリが異なってきますし、実装方法も人それぞれなのが大変面白いと思いました。
他テーマでもやってみたいですねー
次はよーよんさん に(勝手に)バトン渡しましたが、どんなアプリが作成されるのか今から楽しみです!(ハードルを上げていく)