@@ -212,6 +215,147 @@ watch(enabled, () => {
renderer.render();
}
});
+
+const penMode = ref<'fill' | 'blur' | 'pixelate' | null>(null);
+
+function showPenMenu(ev: MouseEvent) {
+ os.popupMenu([{
+ text: i18n.ts._imageEffector._fxs.fill,
+ action: () => {
+ penMode.value = 'fill';
+ },
+ }, {
+ text: i18n.ts._imageEffector._fxs.blur,
+ action: () => {
+ penMode.value = 'blur';
+ },
+ }, {
+ text: i18n.ts._imageEffector._fxs.pixelate,
+ action: () => {
+ penMode.value = 'pixelate';
+ },
+ }], ev.currentTarget ?? ev.target);
+}
+
+function onImagePointerdown(ev: PointerEvent) {
+ if (canvasEl.value == null || imageBitmap == null || penMode.value == null) return;
+
+ const AW = canvasEl.value.clientWidth;
+ const AH = canvasEl.value.clientHeight;
+ const BW = imageBitmap.width;
+ const BH = imageBitmap.height;
+
+ let xOffset = 0;
+ let yOffset = 0;
+
+ if (AW / AH < BW / BH) { // 横長
+ yOffset = AH - BH * (AW / BW);
+ } else { // 縦長
+ xOffset = AW - BW * (AH / BH);
+ }
+
+ xOffset /= 2;
+ yOffset /= 2;
+
+ let startX = ev.offsetX - xOffset;
+ let startY = ev.offsetY - yOffset;
+
+ if (AW / AH < BW / BH) { // 横長
+ startX = startX / (Math.max(AW, AH) / Math.max(BH / BW, 1));
+ startY = startY / (Math.max(AW, AH) / Math.max(BW / BH, 1));
+ } else { // 縦長
+ startX = startX / (Math.min(AW, AH) / Math.max(BH / BW, 1));
+ startY = startY / (Math.min(AW, AH) / Math.max(BW / BH, 1));
+ }
+
+ const id = genId();
+ if (penMode.value === 'fill') {
+ layers.push({
+ id,
+ fxId: 'fill',
+ params: {
+ offsetX: 0,
+ offsetY: 0,
+ scaleX: 0.1,
+ scaleY: 0.1,
+ angle: 0,
+ opacity: 1,
+ color: [1, 1, 1],
+ },
+ });
+ } else if (penMode.value === 'blur') {
+ layers.push({
+ id,
+ fxId: 'blur',
+ params: {
+ offsetX: 0,
+ offsetY: 0,
+ scaleX: 0.1,
+ scaleY: 0.1,
+ angle: 0,
+ radius: 3,
+ },
+ });
+ } else if (penMode.value === 'pixelate') {
+ layers.push({
+ id,
+ fxId: 'pixelate',
+ params: {
+ offsetX: 0,
+ offsetY: 0,
+ scaleX: 0.1,
+ scaleY: 0.1,
+ angle: 0,
+ strength: 0.2,
+ },
+ });
+ }
+
+ _move(ev.offsetX, ev.offsetY);
+
+ function _move(pointerX: number, pointerY: number) {
+ let x = pointerX - xOffset;
+ let y = pointerY - yOffset;
+
+ if (AW / AH < BW / BH) { // 横長
+ x = x / (Math.max(AW, AH) / Math.max(BH / BW, 1));
+ y = y / (Math.max(AW, AH) / Math.max(BW / BH, 1));
+ } else { // 縦長
+ x = x / (Math.min(AW, AH) / Math.max(BH / BW, 1));
+ y = y / (Math.min(AW, AH) / Math.max(BW / BH, 1));
+ }
+
+ const scaleX = Math.abs(x - startX);
+ const scaleY = Math.abs(y - startY);
+
+ const layerIndex = layers.findIndex((l) => l.id === id);
+ const layer = layerIndex !== -1 ? layers[layerIndex] : null;
+ if (layer != null) {
+ layer.params.offsetX = (x + startX) - 1;
+ layer.params.offsetY = (y + startY) - 1;
+ layer.params.scaleX = scaleX;
+ layer.params.scaleY = scaleY;
+ layers[layerIndex] = layer;
+ }
+ }
+
+ function move(ev: PointerEvent) {
+ _move(ev.offsetX, ev.offsetY);
+ }
+
+ function up() {
+ canvasEl.value?.removeEventListener('pointermove', move);
+ canvasEl.value?.removeEventListener('pointerup', up);
+ canvasEl.value?.removeEventListener('pointercancel', up);
+ canvasEl.value?.releasePointerCapture(ev.pointerId);
+
+ penMode.value = null;
+ }
+
+ canvasEl.value.addEventListener('pointermove', move);
+ canvasEl.value.addEventListener('pointerup', up);
+ canvasEl.value.setPointerCapture(ev.pointerId);
+}
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index 21104b41df..45a74e3f02 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
@@ -23,6 +23,8 @@ SPDX-License-Identifier: AGPL-3.0-only
[$style.t_mention]: notification.type === 'mention',
[$style.t_quote]: notification.type === 'quote',
[$style.t_pollEnded]: notification.type === 'pollEnded',
+ [$style.t_scheduledNotePosted]: notification.type === 'scheduledNotePosted',
+ [$style.t_scheduledNotePostFailed]: notification.type === 'scheduledNotePostFailed',
[$style.t_achievementEarned]: notification.type === 'achievementEarned',
[$style.t_exportCompleted]: notification.type === 'exportCompleted',
[$style.t_login]: notification.type === 'login',
@@ -39,6 +41,8 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
@@ -60,6 +64,8 @@ SPDX-License-Identifier: AGPL-3.0-only