はじめに
#2で(下記)、TaskのCreateの関数を実装した続きから行う。(全3記事を予定)
プロジェクトの作成(#2の続きから)
MainとなるCanvas内でイベントを付与する。
- Canvas内で何もないところにDragしたら、Taskを新規作成する。
- Canvas内でTask自体をDragしたら、TaskをDragした最終位置に移動させる。
- TaskのResizerをDragしたら、横軸上で伸縮する。
main.js
/**
* TaskObjectの新規追加するとき、Objectを仮表示する関数
* @param {number} x - 位置x
* @param {number} y - 位置y
* @param {number} w - width
* @param {string} c - 表示色
*/
const createtempTask = (x, y, w, c) => {
blurTask();
let tempObj = app.stage.getChildByLabel(tempObjLabel);
if (tempObj) {
tempObj.destroy({ children: true, texture: true, textureSource: true, context: true });
}
let obj = new PIXI.Graphics()
.rect(x, y, w, objh)
.stroke({ width: 1, color: c })
.fill({ color: 0x000000, alpha: 0 });
obj.label = tempObjLabel + _Graphics;
obj.interactive = true;
obj.buttonMode = true;
// コンテナ作成
const container = new PIXI.Container();
container.zIndex = 5;
container.label = tempObjLabel;
container.my_x = x;
container.my_y = y;
container.my_width = w;
container.my_color = c;
container.addChild(obj);
app.stage.addChild(container);
}
/**
* TaskObjectの移動するとき、Objectを仮表示する関数
* @param {string} cursor - 表示カーソル
* @param {number} x - 位置x
* @param {number} y - 位置y
* @param {number} w - width
* @param {string} c - 表示色
*/
const createtempMoveTask = (cursor, x, y, w, c) => {
blurTask();
let tempMoveObj = app.stage.getChildByLabel(tempMoveObjLabel);
if (tempMoveObj) {
tempMoveObj.destroy({ children: true, texture: true, textureSource: true, context: true });
}
let obj = new PIXI.Graphics()
.rect(x, y, w, objh)
.fill(c);
obj.label = tempMoveObjLabel + _Graphics;
obj.interactive = true;
obj.buttonMode = true;
obj.cursor = cursor;
// コンテナ作成
const container = new PIXI.Container();
container.zIndex = 5;
container.label = tempMoveObjLabel;
container.my_x = x;
container.my_y = y;
container.my_width = w;
container.my_color = c;
container.addChild(obj);
app.stage.addChild(container);
}
app.stage.on('pointerdown', (e) => {
if (draggingTask) return; // ObjectをDrag中ならReturn
if (resizingTask) return; // ObjectをResize中ならReturn
createStart = { x: -1, y: -1 };
// 新しいオブジェクトの作成開始位置をグローバル座標で保存
const currentPos = e.data.global;
createStart = { x: currentPos.x, y: currentPos.y };
});
app.stage.on('pointermove', (e) => {
// ポインタの現在の位置を取得
const currentPos = e.data.global;
// ObjectをDrag中
if (draggingTask) {
let myObj = app.stage.getChildByLabel(draggingTask.label);
if (myObj) {
myObj.destroy({ children: true, texture: true, textureSource: true, context: true });
}
deletedistinctTask(draggingTask.label);
createtempMoveTask('move', currentPos.x - draggingTask.my_dragx, currentPos.y - objh / 2, draggingTask.my_width, draggingTask.my_color);
return;
}
// ObjectをResize中
if (resizingTask) {
let myObj = app.stage.getChildByLabel(resizingTask.label);
if (myObj) {
myObj.destroy({ children: true, texture: true, textureSource: true, context: true });
}
deletedistinctTask(resizingTask.label);
const x = resizingTask.my_x
const xw = resizingTask.my_x + resizingTask.my_width
const y = resizingTask.my_y
if (resizingTask.my_lr == 'R') {
if (currentPos.x - x >= cellWidth / 12) {
createtempMoveTask('col-resize', x, y, currentPos.x - x, resizingTask.my_color);
}
}
if (resizingTask.my_lr == 'L') {
if (xw - currentPos.x >= cellWidth / 12) {
createtempMoveTask('col-resize', currentPos.x, y, xw - currentPos.x, resizingTask.my_color);
}
}
return;
}
if (createStart.x == -1) return;
// ObjectをCreate中
if (currentPos.x - createStart.x > 0) {
// =>方向にCreate
createtempTask(createStart.x, createStart.y, currentPos.x - createStart.x, defaultcolor);
} else {
// <=方向にCreate
createtempTask(currentPos.x, createStart.y, createStart.x - currentPos.x, defaultcolor);
}
});
const pointerUp = async (e) => {
// ObjectをDrag中
if (draggingTask) {
let tempMoveObj = app.stage.getChildByLabel(tempMoveObjLabel);
if (tempMoveObj) {
await createTask(draggingTask.label, tempMoveObj.my_x, tempMoveObj.my_y, tempMoveObj.my_width, tempMoveObj.my_color, '');
tempMoveObj.destroy({ children: true, texture: true, textureSource: true, context: true });
}
draggingTask = false;
return;
}
// ObjectをResize中
if (resizingTask) {
let tempMoveObj = app.stage.getChildByLabel(tempMoveObjLabel);
if (tempMoveObj) {
if (tempMoveObj.my_width >= cellWidth / 12) {
await createTask(resizingTask.label, tempMoveObj.my_x, tempMoveObj.my_y, tempMoveObj.my_width, tempMoveObj.my_color, '');
}
else {
await createTask(resizingTask.label, tempMoveObj.my_x, tempMoveObj.my_y, cellWidth / 12, tempMoveObj.my_color, '');
}
tempMoveObj.destroy({ children: true, texture: true, textureSource: true, context: true });
}
resizingTask = false;
}
if (createStart.x == -1) return;
// ObjectをCreate中
let tempObj = app.stage.getChildByLabel(tempObjLabel);
if (tempObj) {
if (tempObj.my_width >= cellWidth / 12) {
await createTask(crypto.randomUUID().toString(), tempObj.my_x, tempObj.my_y, tempObj.my_width, defaultcolor, '');
}
tempObj.destroy({ children: true, texture: true, textureSource: true, context: true });
}
createStart = { x: -1, y: -1 };
}
app.stage.on('pointerup', pointerUp);
app.stage.on('pointerupoutside', pointerUp);
画面上部のtaskchangeのボタン押下のイベントで
対象Taskを一度削除しておき、指定されたInput内の値で新たにTaskを作成する。
main.js
taskchange.addEventListener('click', async (e) => {
const taskId = e.target.dataset.taskid;
if (taskId == '') return;
const color = taskcolor.value;
const title = tasktitle.value;
let targetcontainer = app.stage.getChildByLabel(taskId);
if (targetcontainer) {
blurTask();
targetcontainer.destroy({ children: true, texture: true, textureSource: true, context: true });
// re create
await createTask(taskId, targetcontainer.my_x, targetcontainer.my_y, targetcontainer.my_width, color, title);
focusTask(taskId);
}
});
画面上部のtaskdeleteのボタン押下のイベントでtaskを削除する。
main.js
taskdelete.addEventListener('click', async (e) => {
const taskId = e.target.dataset.taskid;
if (taskId == '') return;
let targetcontainer = app.stage.getChildByLabel(taskId);
if (targetcontainer) {
deletedistinctTask(taskId);
targetcontainer.destroy({ children: true, texture: true, textureSource: true, context: true });
// focus destroy
blurTask();
}
});
画面上部のcalendarsaveのボタン押下のイベントでtaskを保存する。
画面上部の左右のボタン押下のイベントでtaskを保存し、カレンダーを翌月OR先月にする。
main.js
/**
* Taskを保存する関数
*/
const saveTasks = async () => {
let count = 0;
const fmtYYYYMM = calendar_month.innerText.replaceAll('-', '');
for (const child of app.stage.children) {
if (child.my_color && child.my_title && child.my_width && child.my_x && child.my_y) {
const ret = await db.query(`INSERT INTO task_master (taskid,title,color,x,y,width,yearmonth)
SELECT $1,$2,$3,$4,$5,$6,$7
WHERE NOT EXISTS ( SELECT 1 FROM task_master WHERE taskid = $1 );`,
[child.label, child.my_title, child.my_color, child.my_x, child.my_y, child.my_width, Number(fmtYYYYMM)]);
count = count + ret.affectedRows;
}
}
if (count > 0) {
window.alert(`${calendar_month.innerText}のTaskを${count}件保存しました`)
}
}
calendarsave.addEventListener('click', async (e) => {
await saveTasks();
});
document.getElementById('arrowleft').addEventListener('click', async () => {
await saveTasks();
const prevdt = addMonth(parse({ date: calendar_month.innerText + '-01', format: 'YYYY-MM-DD', locale: 'ja', }), -1);
maxDays = monthDays(prevdt);
gridHeight = cellHeight * maxDays;
calendar_month.innerText = format({ date: prevdt, format: 'YYYY-MM', tz: 'Asia/Tokyo', });
blurTask();
const fmtYYYYMM = calendar_month.innerText.replaceAll('-', '');
while (app.stage.children.length > 0) {
app.stage.children.shift().destroy({ children: true, texture: true, textureSource: true, context: true });
}
await calenadarView(fmtYYYYMM);
})
document.getElementById('arrowright').addEventListener('click', async () => {
await saveTasks();
const nextdt = addMonth(parse({ date: calendar_month.innerText + '-01', format: 'YYYY-MM-DD', locale: 'ja', }), 1);
maxDays = monthDays(nextdt);
gridHeight = cellHeight * maxDays;
calendar_month.innerText = format({ date: nextdt, format: 'YYYY-MM', tz: 'Asia/Tokyo', });
blurTask();
const fmtYYYYMM = calendar_month.innerText.replaceAll('-', '');
while (app.stage.children.length > 0) {
app.stage.children.shift().destroy({ children: true, texture: true, textureSource: true, context: true });
}
await calenadarView(fmtYYYYMM);
})
まだまだ、微妙なところはありますが、完成!💪
今回の成果物
ソース
DemoURL
まとめ
全3記事でカレンダーアプリをPixiJSで実装してみた。
思いのほか、PGliteの処理が重たいので、時間があればSuspend処理や
シンプルにローカルストレージを使ったほうが見栄えはいいと思います。🤔
PixiJSは2Dに特化していて、学習コストも低かったので引き続き、学習していきたい。📝