fix bug where touch events prevented focusing inputs, change input type of file transfer reception data

This commit is contained in:
marlon-erler
2026-08-30 23:34:45 +02:00
parent 6a014fddfb
commit f6511f4523
7 changed files with 203 additions and 1921 deletions
+13 -1759
View File
File diff suppressed because it is too large Load Diff
+20 -10
View File
@@ -1136,7 +1136,7 @@
function localeCompare(a, b) { function localeCompare(a, b) {
return a.localeCompare(b); return a.localeCompare(b);
} }
function implementPinchZoom(targetSelector) { function implementPinchZoom(canvas) {
let pinching = false; let pinching = false;
let lastElement = void 0; let lastElement = void 0;
let initialDistance, currentZoom, initialZoom, currentX, currentY, initialX, initialY, initialTouchX, initialTouchY; let initialDistance, currentZoom, initialZoom, currentX, currentY, initialX, initialY, initialTouchX, initialTouchY;
@@ -1154,8 +1154,7 @@
reset(); reset();
const MIN = 0.25; const MIN = 0.25;
const MAX = 5; const MAX = 5;
const element = () => canvas.querySelector(targetSelector); const element = () => canvas.querySelector(".zoom");
const parent = () => element().parentElement;
const distance = (e) => Math.hypot( const distance = (e) => Math.hypot(
e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageX - e.touches[1].pageX,
e.touches[0].pageY - e.touches[1].pageY e.touches[0].pageY - e.touches[1].pageY
@@ -1171,7 +1170,6 @@
currentY = y; currentY = y;
currentZoom = factor; currentZoom = factor;
} }
const canvas = document.body;
canvas.addEventListener("touchstart", (event) => { canvas.addEventListener("touchstart", (event) => {
document.activeElement.blur(); document.activeElement.blur();
if (lastElement != void 0 && lastElement != element()) reset(); if (lastElement != void 0 && lastElement != element()) reset();
@@ -1205,16 +1203,24 @@
const midY = midpoint(event, "y"); const midY = midpoint(event, "y");
const ratio = currentDistance / initialDistance; const ratio = currentDistance / initialDistance;
const difference = currentDistance - initialDistance; const difference = currentDistance - initialDistance;
apply(initialZoom * ratio, [initialX + (midX - difference - initialTouchX) / initialZoom, initialY + (midY - difference - initialTouchY) / initialZoom]); apply(initialZoom * ratio, [
initialX + (midX - difference - initialTouchX) / initialZoom,
initialY + (midY - difference - initialTouchY) / initialZoom
]);
}); });
canvas.addEventListener("wheel", (event) => { canvas.addEventListener("wheel", (event) => {
event.preventDefault(); event.preventDefault();
if (!event.shiftKey) { if (!event.shiftKey) {
apply(currentZoom, [currentX - event.deltaX, currentY - event.deltaY]); apply(currentZoom, [
currentX - event.deltaX,
currentY - event.deltaY
]);
return; return;
} }
; apply(currentZoom + (event.deltaY < 0 ? 1 : -1) * 0.1, [
apply(currentZoom + (event.deltaY < 0 ? 1 : -1) * 0.1, [initialX, initialY]); initialX,
initialY
]);
}); });
canvas.addEventListener("scroll", (event) => { canvas.addEventListener("scroll", (event) => {
event.preventDefault(); event.preventDefault();
@@ -1230,7 +1236,7 @@
this.connectionModel = connectionModel2; this.connectionModel = connectionModel2;
this.chatListModel = chatListModel2; this.chatListModel = chatListModel2;
this.fileTransferModel = fileTransferModel2; this.fileTransferModel = fileTransferModel2;
this.BUILD = "Build 26.08.30.E"; this.BUILD = "Build 26.08.30.F2";
// CONTEXT // CONTEXT
this.contextStack = /* @__PURE__ */ new Map(); this.contextStack = /* @__PURE__ */ new Map();
this.closeContext = (contextId, fromHistoryEvent = false) => { this.closeContext = (contextId, fromHistoryEvent = false) => {
@@ -5493,6 +5499,8 @@
} }
}) })
); );
const wrapper = /* @__PURE__ */ createElement("div", { class: "content main-content", "children:set": pages });
implementPinchZoom(wrapper);
const taskSettingsModal = createProxyState( const taskSettingsModal = createProxyState(
[boardViewModel.selectedTaskViewModel], [boardViewModel.selectedTaskViewModel],
() => { () => {
@@ -5564,7 +5572,7 @@
"on:click": boardViewModel.createTask "on:click": boardViewModel.createTask
}, },
/* @__PURE__ */ createElement("span", { class: "icon" }, "add") /* @__PURE__ */ createElement("span", { class: "icon" }, "add")
))), /* @__PURE__ */ createElement("div", { class: "content main-content", "children:set": pages }), BoardSettingsModal(coreViewModel2, boardViewModel), SearchModal( ))), wrapper, BoardSettingsModal(coreViewModel2, boardViewModel), SearchModal(
coreViewModel2, coreViewModel2,
boardViewModel.searchViewModel, boardViewModel.searchViewModel,
coreViewModel2.translations.chatPage.task.filterTasksHeadline, coreViewModel2.translations.chatPage.task.filterTasksHeadline,
@@ -7031,12 +7039,14 @@
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.receiveHeadline), /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.dataEntryInputDescription), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.transferChannelHeadline), /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.receiveHeadline), /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.dataEntryInputDescription), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.transferChannelHeadline), /* @__PURE__ */ createElement(
"input", "input",
{ {
type: "number",
"on:enter": fileTransferViewModel2.prepareReceivingData, "on:enter": fileTransferViewModel2.prepareReceivingData,
"bind:value": fileTransferViewModel2.receivingTransferChannel "bind:value": fileTransferViewModel2.receivingTransferChannel
} }
))), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.transferKeyHeadline), /* @__PURE__ */ createElement( ))), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.transferKeyHeadline), /* @__PURE__ */ createElement(
"input", "input",
{ {
type: "number",
"on:enter": fileTransferViewModel2.prepareReceivingData, "on:enter": fileTransferViewModel2.prepareReceivingData,
"bind:value": fileTransferViewModel2.receivingTransferKey "bind:value": fileTransferViewModel2.receivingTransferKey
} }
Vendored
+2 -2
View File
@@ -1,4 +1,4 @@
const CACHE_VERSION = "26.08.30.E"; const CACHE_VERSION = "3";
self.addEventListener("fetch", (event) => { self.addEventListener("fetch", (event) => {
event.respondWith(handleRequest(event)); event.respondWith(handleRequest(event));
@@ -22,7 +22,7 @@ async function getFromCache(event) {
async function fetchAndCache(event) { async function fetchAndCache(event) {
const request = event.request; const request = event.request;
const response = await fetch(request); const response = await fetch(request, {cache: "no-cache"});
if (response.status === 200) { if (response.status === 200) {
// Only cache successful responses // Only cache successful responses
const cache = await caches.open(CACHE_VERSION); const cache = await caches.open(CACHE_VERSION);
+51 -38
View File
@@ -279,28 +279,32 @@ export function localeCompare(a: string, b: string): number {
} }
// ui // ui
export function implementPinchZoom(targetSelector: string) { export function implementPinchZoom(canvas: HTMLElement) {
let pinching = false; let pinching = false;
let lastElement: HTMLElement|undefined = undefined; let lastElement: HTMLElement | undefined = undefined;
let initialDistance, let initialDistance: number,
currentZoom, initialZoom, currentZoom: number,
currentX, currentY, initialZoom: number,
initialX, initialY, currentX: number,
initialTouchX, initialTouchY; currentY: number,
initialX: number,
initialY: number,
initialTouchX: number,
initialTouchY: number;
function reset() { function reset() {
initialDistance = 0; initialDistance = 0;
currentZoom = 1; currentZoom = 1;
initialZoom = 1; initialZoom = 1;
currentX = 0; currentX = 0;
currentY = 0; currentY = 0;
initialX = 0; initialX = 0;
initialY = 0; initialY = 0;
initialTouchX = 0; initialTouchX = 0;
initialTouchY = 0; initialTouchY = 0;
} }
reset(); reset();
@@ -308,30 +312,28 @@ export function implementPinchZoom(targetSelector: string) {
const MIN = 0.25; const MIN = 0.25;
const MAX = 5; const MAX = 5;
const element = (): HTMLElement => canvas.querySelector(targetSelector); const element = (): HTMLElement => canvas.querySelector(".zoom");
const parent = (): HTMLElement => element().parentElement;
const distance = (e: TouchEvent) => const distance = (e: TouchEvent) =>
Math.hypot( Math.hypot(
e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageX - e.touches[1].pageX,
e.touches[0].pageY - e.touches[1].pageY, e.touches[0].pageY - e.touches[1].pageY,
); );
const point = (e: TouchEvent, direction: "x" | "y", i: number) => const point = (e: TouchEvent, direction: "x" | "y", i: number) =>
e.touches[i][direction == "x" ? "clientX" : "clientY"]; e.touches[i][direction == "x" ? "clientX" : "clientY"];
const midpoint = (e: TouchEvent, direction: "x" | "y") => const midpoint = (e: TouchEvent, direction: "x" | "y") =>
(point(e, direction, 0) + point(e, direction, 1)) / 2; (point(e, direction, 0) + point(e, direction, 1)) / 2;
function apply(factor: number, offset: [number, number]) { function apply(factor: number, offset: [number, number]) {
if (factor < MIN) return apply(MIN, offset); if (factor < MIN) return apply(MIN, offset);
if (factor > MAX) return apply(5, offset); if (factor > MAX) return apply(5, offset);
const [x, y] = offset; const [x, y] = offset;
element().style.transform = `scale(${(Math.floor(factor * 100) / 100).toString()}) translate(${x}px, ${y}px)`; element().style.transform = `scale(${(Math.floor(factor * 100) / 100).toString()}) translate(${x}px, ${y}px)`;
currentX = x; currentX = x;
currentY = y; currentY = y;
currentZoom = factor; currentZoom = factor;
} }
const canvas = document.body;
canvas.addEventListener("touchstart", (event: TouchEvent) => { canvas.addEventListener("touchstart", (event: TouchEvent) => {
(document.activeElement as HTMLElement).blur(); (document.activeElement as HTMLElement).blur();
@@ -357,8 +359,10 @@ export function implementPinchZoom(targetSelector: string) {
canvas.addEventListener("touchmove", (event: TouchEvent) => { canvas.addEventListener("touchmove", (event: TouchEvent) => {
if (!pinching) { if (!pinching) {
apply(currentZoom, [ apply(currentZoom, [
initialX + (((event.touches[0].clientX - initialTouchX)/initialZoom)), initialX +
initialY + ((event.touches[0].clientY - initialTouchY)/initialZoom), (event.touches[0].clientX - initialTouchX) / initialZoom,
initialY +
(event.touches[0].clientY - initialTouchY) / initialZoom,
]); ]);
return; return;
} }
@@ -369,15 +373,24 @@ export function implementPinchZoom(targetSelector: string) {
const midY = midpoint(event, "y"); const midY = midpoint(event, "y");
const ratio = currentDistance / initialDistance; const ratio = currentDistance / initialDistance;
const difference = currentDistance - initialDistance; const difference = currentDistance - initialDistance;
apply(initialZoom * ratio, [initialX + (midX - difference - initialTouchX)/initialZoom, initialY + (midY - difference - initialTouchY)/initialZoom]); apply(initialZoom * ratio, [
initialX + (midX - difference - initialTouchX) / initialZoom,
initialY + (midY - difference - initialTouchY) / initialZoom,
]);
}); });
canvas.addEventListener("wheel", (event: WheelEvent) => { canvas.addEventListener("wheel", (event: WheelEvent) => {
event.preventDefault(); event.preventDefault();
if (!event.shiftKey) { if (!event.shiftKey) {
apply(currentZoom, [currentX - event.deltaX, currentY - event.deltaY]); apply(currentZoom, [
currentX - event.deltaX,
currentY - event.deltaY,
]);
return; return;
}; }
apply(currentZoom + (event.deltaY < 0 ? 1 : -1) * 0.1, [initialX, initialY]); apply(currentZoom + (event.deltaY < 0 ? 1 : -1) * 0.1, [
initialX,
initialY,
]);
}); });
canvas.addEventListener("scroll", (event: MouseEvent) => { canvas.addEventListener("scroll", (event: MouseEvent) => {
event.preventDefault(); event.preventDefault();
+113 -110
View File
@@ -45,122 +45,125 @@ export function BoardPage(
}), }),
); );
const wrapper = <div class="content main-content" children:set={pages}></div>
Utility.implementPinchZoom(wrapper);
// task modal // task modal
const taskSettingsModal = React.createProxyState( const taskSettingsModal = React.createProxyState(
[boardViewModel.selectedTaskViewModel], [boardViewModel.selectedTaskViewModel],
() => { () => {
if (boardViewModel.selectedTaskViewModel.value == undefined) { if (boardViewModel.selectedTaskViewModel.value == undefined) {
return <div></div>; return <div></div>;
} else { } else {
ViewController.setFocusWithDelay(); ViewController.setFocusWithDelay();
return TaskSettingsModal( return TaskSettingsModal(
coreViewModel, coreViewModel,
boardViewModel.selectedTaskViewModel.value, boardViewModel.selectedTaskViewModel.value,
); );
} }
}, },
); );
return ( return (
<div class="pane"> <div class="pane">
<div class="toolbar"> <div class="toolbar">
<span> <span>
<button <button
class="ghost board-close-button" class="ghost board-close-button"
aria-label={ aria-label={
coreViewModel.translations.chatPage.task coreViewModel.translations.chatPage.task
.closeBoardButtonAudioLabel .closeBoardButtonAudioLabel
} }
on:click={boardViewModel.close} on:click={boardViewModel.close}
> >
<span class="icon">arrow_back</span> <span class="icon">arrow_back</span>
</button> </button>
<button <button
class="ghost board-toggle-button inset-outline" class="ghost board-toggle-button inset-outline"
aria-label={ aria-label={
coreViewModel.translations.chatPage.task coreViewModel.translations.chatPage.task
.toggleBoardButtonAudioLabel .toggleBoardButtonAudioLabel
} }
on:click={ on:click={
boardViewModel.taskPageViewModel.toggleBoardList boardViewModel.taskPageViewModel.toggleBoardList
} }
toggle:selected={ toggle:selected={
boardViewModel.taskPageViewModel.isShowingBoadList boardViewModel.taskPageViewModel.isShowingBoadList
} }
> >
<span class="icon">dock_to_right</span> <span class="icon">dock_to_right</span>
</button> </button>
<button <button
class="ghost" class="ghost"
aria-label={ aria-label={
coreViewModel.translations.chatPage.task coreViewModel.translations.chatPage.task
.boardSettingsHeadline .boardSettingsHeadline
} }
on:click={boardViewModel.showSettings} on:click={boardViewModel.showSettings}
> >
<span class="icon">settings</span> <span class="icon">settings</span>
</button> </button>
</span> </span>
<span class="scroll-h ribbon"> <span class="scroll-h ribbon">
{BoardViewToggleButton( {BoardViewToggleButton(
coreViewModel.translations.chatPage.task coreViewModel.translations.chatPage.task
.listViewButtonAudioLabel, .listViewButtonAudioLabel,
"view_list", "view_list",
BoardPageTypes.List, BoardPageTypes.List,
boardViewModel, boardViewModel,
)} )}
{BoardViewToggleButton( {BoardViewToggleButton(
coreViewModel.translations.chatPage.task coreViewModel.translations.chatPage.task
.kanbanViewButtonAudioLabel, .kanbanViewButtonAudioLabel,
"view_kanban", "view_kanban",
BoardPageTypes.Kanban, BoardPageTypes.Kanban,
boardViewModel, boardViewModel,
)} )}
{BoardViewToggleButton( {BoardViewToggleButton(
coreViewModel.translations.chatPage.task coreViewModel.translations.chatPage.task
.statusViewButtonAudioLabel, .statusViewButtonAudioLabel,
"grid_view", "grid_view",
BoardPageTypes.StatusGrid, BoardPageTypes.StatusGrid,
boardViewModel, boardViewModel,
)} )}
</span> </span>
<span> <span>
<button <button
class="ghost inset-outline" class="ghost inset-outline"
aria-label={ aria-label={
coreViewModel.translations.chatPage.task coreViewModel.translations.chatPage.task
.filterTasksButtonAudioLabel .filterTasksButtonAudioLabel
} }
on:click={boardViewModel.showFilterModal} on:click={boardViewModel.showFilterModal}
toggle:selected={boardViewModel.isFilterActive} toggle:selected={boardViewModel.isFilterActive}
> >
<span class="icon">filter_alt</span> <span class="icon">filter_alt</span>
</button> </button>
<button <button
class="ghost" class="ghost"
aria-label={ aria-label={
coreViewModel.translations.chatPage.task coreViewModel.translations.chatPage.task
.createTaskButtonAudioLabel .createTaskButtonAudioLabel
} }
on:click={boardViewModel.createTask} on:click={boardViewModel.createTask}
> >
<span class="icon">add</span> <span class="icon">add</span>
</button> </button>
</span> </span>
</div> </div>
<div class="content main-content" children:set={pages}></div> {wrapper}
{BoardSettingsModal(coreViewModel, boardViewModel)} {BoardSettingsModal(coreViewModel, boardViewModel)}
{SearchModal( {SearchModal(
coreViewModel, coreViewModel,
boardViewModel.searchViewModel, boardViewModel.searchViewModel,
coreViewModel.translations.chatPage.task.filterTasksHeadline, coreViewModel.translations.chatPage.task.filterTasksHeadline,
TaskViewModelToEntry, TaskViewModelToEntry,
boardViewModel.isPresentingFilterModal, boardViewModel.isPresentingFilterModal,
)} )}
<div children:set={taskSettingsModal}></div> <div children:set={taskSettingsModal}></div>
</div> </div>
); );
} }
+3 -1
View File
@@ -475,7 +475,8 @@ function TransferDataInputModal(
.transferChannelHeadline .transferChannelHeadline
} }
</span> </span>
<input <input
type="number"
on:enter={ on:enter={
fileTransferViewModel.prepareReceivingData fileTransferViewModel.prepareReceivingData
} }
@@ -496,6 +497,7 @@ function TransferDataInputModal(
} }
</span> </span>
<input <input
type="number"
on:enter={ on:enter={
fileTransferViewModel.prepareReceivingData fileTransferViewModel.prepareReceivingData
} }
+1 -1
View File
@@ -9,7 +9,7 @@ import ConnectionModel from "../../Model/Global/connectionModel";
import ChatListModel from "../../Model/Chat/chatListModel"; import ChatListModel from "../../Model/Chat/chatListModel";
export default class CoreViewModel { export default class CoreViewModel {
readonly BUILD = "Build 26.08.30.E"; readonly BUILD = "Build 26.08.30.F2";
translations: Translations; translations: Translations;