diff --git a/dist/index.css b/dist/index.css index b2c172cb..9b0eebc0 100644 --- a/dist/index.css +++ b/dist/index.css @@ -475,6 +475,7 @@ body[showing-chat] #chat-page-wrapper { width: 100%; max-width: 100%; padding: 0px; + overflow: hidden; } #chat-page > div > #main > #task-page > .pane-wrapper > .pane > .main-content > div { animation: slide-up var(--animation-duration); @@ -485,14 +486,12 @@ body[showing-chat] #chat-page-wrapper { gap: var(--gap-between-tiles); padding: var(--content-padding); } -#chat-page > div > #main > #task-page .kanban-board-wrapper, -#chat-page > div > #main > #task-page .status-page-content { +#chat-page > div > #main > #task-page .zoom { display: flex; align-items: start; gap: var(--large-gap); - width: 100%; - height: 100%; - overflow: visible; + width: auto; + height: auto; transform-origin: top left; padding: var(--content-padding); } diff --git a/dist/index.js b/dist/index.js index e507e5c5..de2e9241 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1136,32 +1136,82 @@ function localeCompare(a, b) { return a.localeCompare(b); } - function implementPinchZoom(element) { - let initialDistance = 0; + function implementPinchZoom(targetSelector) { let pinching = false; + let initialDistance = 0; + let currentZoom = 1; + let initialZoom = 1; + let currentX = 0; + let currentY = 0; + let initialX = 0; + let initialY = 0; + let initialTouchX = 0; + let initialTouchY = 0; + const MIN = 0.25; + const MAX = 5; + const element = () => canvas.querySelector(targetSelector); + const parent = () => element().parentElement; const distance = (e) => Math.hypot( e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY ); - function apply(factor) { - element.style.transform = `scale(${(Math.floor(factor * 100) / 100).toString()})`; + const point = (e, direction, i) => e.touches[i][direction == "x" ? "clientX" : "clientY"]; + const midpoint = (e, direction) => (point(e, direction, 0) + point(e, direction, 1)) / 2; + function apply(factor, offset) { + if (factor < MIN) return apply(MIN, offset); + if (factor > MAX) return apply(5, offset); + if (!element) return; + const [x, y] = offset; + element().style.transform = `scale(${(Math.floor(factor * 100) / 100).toString()}) translate(${x}px, ${y}px)`; + currentX = x; + currentY = y; + currentZoom = factor; } - element.addEventListener("touchstart", (event) => { - if (event.touches.length != 2) return; - initialDistance = distance(event); + const canvas = document.body; + canvas.addEventListener("touchstart", (event) => { + document.activeElement.blur(); + initialZoom = currentZoom; + initialX = currentX; + initialY = currentY; + if (event.touches.length != 2) { + pinching = false; + initialTouchX = event.touches[0].clientX; + initialTouchY = event.touches[0].clientY; + return; + } pinching = true; - apply(1); + initialDistance = distance(event); + initialTouchX = midpoint(event, "x"); + initialTouchY = midpoint(event, "y"); }); - element.addEventListener("touchend", (event) => { - if (event.touches.length == 2) return; - pinching = false; - }); - element.addEventListener("touchmove", (event) => { - if (!pinching) return; + canvas.addEventListener("touchmove", (event) => { + if (!pinching) { + apply(currentZoom, [ + initialX + (event.touches[0].clientX - initialTouchX) / initialZoom, + initialY + (event.touches[0].clientY - initialTouchY) / initialZoom + ]); + return; + } + if (event.touches.length < 2) return; event.preventDefault(); const currentDistance = distance(event); - const factor = currentDistance / initialDistance; - apply(factor); + const midX = midpoint(event, "x"); + const midY = midpoint(event, "y"); + const ratio = currentDistance / initialDistance; + const difference = currentDistance - initialDistance; + apply(initialZoom * ratio, [initialX + (midX - difference - initialTouchX) / initialZoom, initialY + (midY - difference - initialTouchY) / initialZoom]); + }); + canvas.addEventListener("wheel", (event) => { + event.preventDefault(); + if (!event.shiftKey) { + apply(currentZoom, [currentX - event.deltaX, currentY - event.deltaY]); + return; + } + ; + apply(currentZoom + (event.deltaY < 0 ? 1 : -1) * 0.1, [initialX, initialY]); + }); + canvas.addEventListener("scroll", (event) => { + event.preventDefault(); }); } @@ -1174,7 +1224,7 @@ this.connectionModel = connectionModel2; this.chatListModel = chatListModel2; this.fileTransferModel = fileTransferModel2; - this.BUILD = "Build 26.08.30.C"; + this.BUILD = "Build 26.08.30.D"; // CONTEXT this.contextStack = /* @__PURE__ */ new Map(); this.closeContext = (contextId, fromHistoryEvent = false) => { @@ -1858,7 +1908,10 @@ }; this.downloadFile = async () => { const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0]; - const backup = await this.coreViewModel.fileTransferModel.generateBackup(this.selectedPaths.value.values(), this.exportKey.value); + const backup = await this.coreViewModel.fileTransferModel.generateBackup( + this.selectedPaths.value.values(), + this.exportKey.value + ); const anchor = document.createElement("a"); anchor.href = window.URL.createObjectURL(backup); anchor.download = `comms-${date}.bak`; @@ -1888,7 +1941,10 @@ if (!fileString || !key) return; this.cannotDecryptImport.value = true; try { - await this.coreViewModel.fileTransferModel.handleBackupFile(fileString, key); + await this.coreViewModel.fileTransferModel.handleBackupFile( + fileString, + key + ); window.location.reload(); } catch (e) { console.error(e); @@ -4907,7 +4963,7 @@ const index = createPropertyValueIndexState(sortedStatuses, statusName); return StatusNameCell(coreViewModel2, statusName, index, boardViewModel); }; - const main = /* @__PURE__ */ createElement("div", { class: "status-page-content" }, /* @__PURE__ */ createElement( + return /* @__PURE__ */ createElement("div", { class: "status-page-content zoom" }, /* @__PURE__ */ createElement( "div", { class: "status-name-row", @@ -4941,8 +4997,6 @@ ); } )); - implementPinchZoom(main); - return main; } function StatusNameCell(coreViewModel2, statusName, index, boardViewModel) { const taskViewModelsWithMatchingStatus = new ListState(); @@ -5066,7 +5120,7 @@ // src/View/ChatPages/boardKanbanPage.tsx function BoardKanbanPage(coreViewModel2, boardViewModel) { - const main = PropertyValueList( + return PropertyValueList( "category", (taskViewModel) => taskViewModel.task, boardViewModel.filteredTaskViewModels, @@ -5086,14 +5140,12 @@ return /* @__PURE__ */ createElement( "div", { - class: "kanban-board-wrapper", + class: "kanban-board-wrapper zoom", "children:append": [categories, categoryNameConverter] } ); } ); - implementPinchZoom(main); - return main; } function Column(coreViewModel2, categoryName, index, boardViewModel) { return FilteredList( @@ -6800,9 +6852,7 @@ function toggle() { isSelected.value = !isSelected.value; } - return /* @__PURE__ */ createElement("button", { class: "tile", "toggle:selected": isSelected, "on:click": toggle }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", { class: "ellipsis" }, fileOption.label), /* @__PURE__ */ createElement("span", { class: "secondary ellipsis" }, StorageModel.pathComponentsToString( - ...fileOption.path - )))); + return /* @__PURE__ */ createElement("button", { class: "tile", "toggle:selected": isSelected, "on:click": toggle }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", { class: "ellipsis" }, fileOption.label), /* @__PURE__ */ createElement("span", { class: "secondary ellipsis" }, StorageModel.pathComponentsToString(...fileOption.path)))); } function DirectionSelectionModal(coreViewModel2, connectionViewModel2, fileTransferViewModel2) { const isPresented = createProxyState( @@ -6813,55 +6863,45 @@ [connectionViewModel2.isConnected], () => connectionViewModel2.isConnected.value == false ); - return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement( - "div", + return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement( + "button", { - class: "flex-column gap content-margin-bottom" + class: "tile", + "toggle:disabled": isDisconnected, + "on:click": fileTransferViewModel2.showFileSelectionModal }, - /* @__PURE__ */ createElement( - "button", - { - class: "tile", - "toggle:disabled": isDisconnected, - "on:click": fileTransferViewModel2.showFileSelectionModal - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "share_windows"), - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.dataTransferModal.fromThisDeviceButton)), - /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") - ), - /* @__PURE__ */ createElement( - "button", - { - class: "tile", - "toggle:disabled": isDisconnected, - "on:click": fileTransferViewModel2.showTransferDataInputModal - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "cloud_download"), - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.dataTransferModal.toThisDeviceButton)), - /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") - ), - /* @__PURE__ */ createElement("hr", null), - /* @__PURE__ */ createElement( - "button", - { - class: "tile", - "on:click": fileTransferViewModel2.showExportSelectionModal - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "file_save"), - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.dataTransferModal.exportButton)), - /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") - ), - /* @__PURE__ */ createElement( - "button", - { - class: "tile", - "on:click": fileTransferViewModel2.showImportModal - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "upload_file"), - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.dataTransferModal.importButton)), - /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") - ) - )), /* @__PURE__ */ createElement("button", { "on:click": fileTransferViewModel2.close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); + /* @__PURE__ */ createElement("span", { class: "icon" }, "share_windows"), + /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.dataTransferModal.fromThisDeviceButton)), + /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") + ), /* @__PURE__ */ createElement( + "button", + { + class: "tile", + "toggle:disabled": isDisconnected, + "on:click": fileTransferViewModel2.showTransferDataInputModal + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "cloud_download"), + /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.dataTransferModal.toThisDeviceButton)), + /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") + ), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( + "button", + { + class: "tile", + "on:click": fileTransferViewModel2.showExportSelectionModal + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "file_save"), + /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.dataTransferModal.exportButton)), + /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") + ), /* @__PURE__ */ createElement( + "button", + { + class: "tile", + "on:click": fileTransferViewModel2.showImportModal + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "upload_file"), + /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.dataTransferModal.importButton)), + /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") + ))), /* @__PURE__ */ createElement("button", { "on:click": fileTransferViewModel2.close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); } function FileSelectionModal(coreViewModel2, fileTransferViewModel2) { const OptionConverter = (fileOption) => { @@ -7118,7 +7158,14 @@ [fileTransferViewModel2.presentedModal], () => fileTransferViewModel2.presentedModal.value == 8 /* ImportSelection */ ); - return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.importHeadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement("input", { id: "file-transfer-input", type: "file", "on:change": fileTransferViewModel2.updateImportSelection }))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__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.importHeadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement( + "input", + { + id: "file-transfer-input", + type: "file", + "on:change": fileTransferViewModel2.updateImportSelection + } + ))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement( "button", { class: "flex", @@ -8031,4 +8078,5 @@ StorageModal(coreViewModel, storageViewModel), SettingsModal(coreViewModel, settingsViewModel) ); + implementPinchZoom(".zoom"); })(); diff --git a/src/Model/Global/fileTransferModel.ts b/src/Model/Global/fileTransferModel.ts index 1a24b637..f3143caa 100644 --- a/src/Model/Global/fileTransferModel.ts +++ b/src/Model/Global/fileTransferModel.ts @@ -149,14 +149,15 @@ export default class FileTransferModel { }); } const rawBackup = stringify(files); - console.log(rawBackup); + console.log(rawBackup); const encrypted = await encryptString(rawBackup, passphrase); - const blob = new Blob([encrypted], {type: "text/plain"}); - return blob; + const blob = new Blob([encrypted], { type: "text/plain" }); + return blob; }; readonly prepareFileForSending = (filePath: string[]): string => { - const fileContent: string | undefined = this.storageModel.read(filePath); + const fileContent: string | undefined = + this.storageModel.read(filePath); if (fileContent == null) return ""; const fileData: FileData = { diff --git a/src/Model/Utility/utility.ts b/src/Model/Utility/utility.ts index 8640a8c1..65e61c35 100644 --- a/src/Model/Utility/utility.ts +++ b/src/Model/Utility/utility.ts @@ -278,32 +278,95 @@ export function localeCompare(a: string, b: string): number { return a.localeCompare(b); } -// ui -export function implementPinchZoom(element: HTMLElement) { - let initialDistance = 0; +// ui +export function implementPinchZoom(targetSelector: string) { let pinching = false; - const distance = (e: TouchEvent) => Math.hypot(e.touches[0].pageX - e.touches[1].pageX, - e.touches[0].pageY - e.touches[1].pageY); - function apply(factor) { - element.style.transform = `scale(${(Math.floor(factor*100)/100).toString()})`; + let initialDistance = 0; + + let currentZoom = 1; + let initialZoom = 1; + + let currentX = 0; + let currentY = 0; + let initialX = 0; + let initialY = 0; + let initialTouchX = 0; + let initialTouchY = 0; + + const MIN = 0.25; + const MAX = 5; + + const element = (): HTMLElement => canvas.querySelector(targetSelector); + const parent = (): HTMLElement => element().parentElement; + const distance = (e: TouchEvent) => + Math.hypot( + e.touches[0].pageX - e.touches[1].pageX, + e.touches[0].pageY - e.touches[1].pageY, + ); + const point = (e: TouchEvent, direction: "x" | "y", i: number) => + e.touches[i][direction == "x" ? "clientX" : "clientY"]; + const midpoint = (e: TouchEvent, direction: "x" | "y") => + (point(e, direction, 0) + point(e, direction, 1)) / 2; + + function apply(factor: number, offset: [number, number]) { + if (factor < MIN) return apply(MIN, offset); + if (factor > MAX) return apply(5, offset); + if (!element) return; + + const [x, y] = offset; + element().style.transform = `scale(${(Math.floor(factor * 100) / 100).toString()}) translate(${x}px, ${y}px)`; + currentX = x; + currentY = y; + currentZoom = factor; } - element.addEventListener("touchstart", (event: TouchEvent) => { - if (event.touches.length != 2) return; - initialDistance = distance(event); + const canvas = document.body; + canvas.addEventListener("touchstart", (event: TouchEvent) => { + (document.activeElement as HTMLElement).blur(); + + initialZoom = currentZoom; + initialX = currentX; + initialY = currentY; + + if (event.touches.length != 2) { + pinching = false; + initialTouchX = event.touches[0].clientX; + initialTouchY = event.touches[0].clientY; + return; + } + pinching = true; - apply(1); + initialDistance = distance(event); + initialTouchX = midpoint(event, "x"); + initialTouchY = midpoint(event, "y"); }); - element.addEventListener("touchend", (event: TouchEvent) => { - if (event.touches.length == 2) return; - pinching = false; - }) - element.addEventListener("touchmove", (event: TouchEvent) => { - if (!pinching) return; + canvas.addEventListener("touchmove", (event: TouchEvent) => { + if (!pinching) { + apply(currentZoom, [ + initialX + (((event.touches[0].clientX - initialTouchX)/initialZoom)), + initialY + ((event.touches[0].clientY - initialTouchY)/initialZoom), + ]); + return; + } + if (event.touches.length < 2) return; event.preventDefault(); const currentDistance = distance(event); - const factor = currentDistance / initialDistance; - apply(factor); - }) -} + const midX = midpoint(event, "x"); + const midY = midpoint(event, "y"); + const ratio = currentDistance / initialDistance; + const difference = currentDistance - initialDistance; + apply(initialZoom * ratio, [initialX + (midX - difference - initialTouchX)/initialZoom, initialY + (midY - difference - initialTouchY)/initialZoom]); + }); + canvas.addEventListener("wheel", (event: WheelEvent) => { + event.preventDefault(); + if (!event.shiftKey) { + apply(currentZoom, [currentX - event.deltaX, currentY - event.deltaY]); + return; + }; + apply(currentZoom + (event.deltaY < 0 ? 1 : -1) * 0.1, [initialX, initialY]); + }); + canvas.addEventListener("scroll", (event: MouseEvent) => { + event.preventDefault(); + }); +} diff --git a/src/View/ChatPages/boardKanbanPage.tsx b/src/View/ChatPages/boardKanbanPage.tsx index ef2a7c42..bbf8f994 100644 --- a/src/View/ChatPages/boardKanbanPage.tsx +++ b/src/View/ChatPages/boardKanbanPage.tsx @@ -1,6 +1,4 @@ import * as React from "bloatless-react"; -import * as Utility from "../../Model/Utility/utility"; - import { ViewController } from "../viewController"; import { TaskViewModelToEntry } from "../Components/taskEntry"; import { @@ -12,12 +10,13 @@ import { TaskCategoryBulkChangeViewModel } from "../../ViewModel/Utility/taskPro import TaskViewModel from "../../ViewModel/Pages/taskViewModel"; import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; +import * as Utility from "../../Model/Utility/utility"; export function BoardKanbanPage( coreViewModel: CoreViewModel, boardViewModel: BoardViewModel, ) { - const main = PropertyValueList( + return PropertyValueList( "category", (taskViewModel: TaskViewModel) => taskViewModel.task, boardViewModel.filteredTaskViewModels, @@ -43,14 +42,12 @@ export function BoardKanbanPage( return (
); }, ); - Utility.implementPinchZoom(main); - return main; } function Column( diff --git a/src/View/ChatPages/boardPage.tsx b/src/View/ChatPages/boardPage.tsx index cf0b8f2b..8532e1d7 100644 --- a/src/View/ChatPages/boardPage.tsx +++ b/src/View/ChatPages/boardPage.tsx @@ -11,6 +11,7 @@ import BoardViewModel, { BoardPageTypes, } from "../../ViewModel/Pages/boardViewModel"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; +import * as Utility from "../../Model/Utility/utility"; export function BoardPage( coreViewModel: CoreViewModel, @@ -148,6 +149,7 @@ export function BoardPage( +
{BoardSettingsModal(coreViewModel, boardViewModel)} diff --git a/src/View/ChatPages/boardStatusGridPage.tsx b/src/View/ChatPages/boardStatusGridPage.tsx index 1e3cfe0c..264af3c6 100644 --- a/src/View/ChatPages/boardStatusGridPage.tsx +++ b/src/View/ChatPages/boardStatusGridPage.tsx @@ -1,6 +1,4 @@ import * as React from "bloatless-react"; -import * as Utility from "../../Model/Utility/utility"; - import { ViewController } from "../viewController"; import { TaskViewModelToEntry } from "../Components/taskEntry"; import { @@ -17,6 +15,7 @@ import { import TaskViewModel from "../../ViewModel/Pages/taskViewModel"; import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; +import * as Utility from "../../Model/Utility/utility"; export function BoardStatusGridPage( coreViewModel: CoreViewModel, @@ -42,8 +41,8 @@ export function BoardStatusGridPage( return StatusNameCell(coreViewModel, statusName, index, boardViewModel); }; - const main = ( -
+ return ( +
); - Utility.implementPinchZoom(main); - return main; } function StatusNameCell( diff --git a/src/View/ChatPages/taskPage.css b/src/View/ChatPages/taskPage.css index 4f6ec545..f85a3a45 100644 --- a/src/View/ChatPages/taskPage.css +++ b/src/View/ChatPages/taskPage.css @@ -3,6 +3,7 @@ max-width: 100%; padding: 0px; + overflow: hidden; } #chat-page @@ -24,16 +25,14 @@ padding: var(--content-padding); } -#chat-page > div > #main > #task-page .kanban-board-wrapper, -#chat-page > div > #main > #task-page .status-page-content { +#chat-page > div > #main > #task-page .zoom { display: flex; align-items: start; gap: var(--large-gap); - width: 100%; - height: 100%; + width: auto; + height: auto; - overflow: visible; transform-origin: top left; padding: var(--content-padding); diff --git a/src/View/Modals/dataTransferModal.tsx b/src/View/Modals/dataTransferModal.tsx index 76f54b5b..91acb5a2 100644 --- a/src/View/Modals/dataTransferModal.tsx +++ b/src/View/Modals/dataTransferModal.tsx @@ -24,14 +24,14 @@ export function DataTransferModalWrapper( )} {FileSelectionModal(coreViewModel, fileTransferViewModel)} {TransferDataDisplayModal(coreViewModel, fileTransferViewModel)} - {TransferDisplayModal(coreViewModel, fileTransferViewModel)} - {TransferDataInputModal(coreViewModel, fileTransferViewModel)} - {DataReceptionModal(coreViewModel, fileTransferViewModel)} - {ExportFileSelectionModal(coreViewModel, fileTransferViewModel)} - {ExportModal(coreViewModel, fileTransferViewModel)} - {ImportModal(coreViewModel, fileTransferViewModel)} - {ImportDecryptionDataModal(coreViewModel, fileTransferViewModel)} -
+ {TransferDisplayModal(coreViewModel, fileTransferViewModel)} + {TransferDataInputModal(coreViewModel, fileTransferViewModel)} + {DataReceptionModal(coreViewModel, fileTransferViewModel)} + {ExportFileSelectionModal(coreViewModel, fileTransferViewModel)} + {ExportModal(coreViewModel, fileTransferViewModel)} + {ImportModal(coreViewModel, fileTransferViewModel)} + {ImportDecryptionDataModal(coreViewModel, fileTransferViewModel)} +
); } @@ -42,30 +42,28 @@ function OptionEntry( ) { const isSelected = new React.State(false); if (fileTransferViewModel.selectedPaths.value.has(fileOption.path)) { - isSelected.value = true; + isSelected.value = true; } isSelected.subscribeSilent((isSelected) => { - if (isSelected == true) { - fileTransferViewModel.selectedPaths.add(fileOption.path); - } else { - fileTransferViewModel.selectedPaths.remove(fileOption.path); - } + if (isSelected == true) { + fileTransferViewModel.selectedPaths.add(fileOption.path); + } else { + fileTransferViewModel.selectedPaths.remove(fileOption.path); + } }); function toggle() { - isSelected.value = !isSelected.value; + isSelected.value = !isSelected.value; } return ( - + ); } @@ -76,110 +74,110 @@ function DirectionSelectionModal( fileTransferViewModel: FileTransferViewModel, ) { const isPresented = React.createProxyState( - [fileTransferViewModel.presentedModal], - () => - fileTransferViewModel.presentedModal.value == - FileTransferModals.DirectionSelection, + [fileTransferViewModel.presentedModal], + () => + fileTransferViewModel.presentedModal.value == + FileTransferModals.DirectionSelection, ); const isDisconnected = React.createProxyState( - [connectionViewModel.isConnected], - () => connectionViewModel.isConnected.value == false, + [connectionViewModel.isConnected], + () => connectionViewModel.isConnected.value == false, ); return ( -
-
-
-

- { - coreViewModel.translations.dataTransferModal - .transferDataHeadline - } -

+
+
+
+

+ { + coreViewModel.translations.dataTransferModal + .transferDataHeadline + } +

-
- - +
+ + -
+
- - -
-
- -
-
+ + +
+ + +
+
); } @@ -188,89 +186,89 @@ function FileSelectionModal( fileTransferViewModel: FileTransferViewModel, ) { const OptionConverter: React.StateItemConverter = ( - fileOption: FileTransferOption, + fileOption: FileTransferOption, ) => { - return OptionEntry(fileOption, fileTransferViewModel); + return OptionEntry(fileOption, fileTransferViewModel); }; // state const isPresented = React.createProxyState( - [fileTransferViewModel.presentedModal], - () => - fileTransferViewModel.presentedModal.value == - FileTransferModals.FileSelection, + [fileTransferViewModel.presentedModal], + () => + fileTransferViewModel.presentedModal.value == + FileTransferModals.FileSelection, ); return ( -
-
-
-

- { - coreViewModel.translations.dataTransferModal - .sendHeadline - } -

- - { - coreViewModel.translations.dataTransferModal - .selectionDescription - } - +
+
+
+

+ { + coreViewModel.translations.dataTransferModal + .sendHeadline + } +

+ + { + coreViewModel.translations.dataTransferModal + .selectionDescription + } + -
+
-

- { - coreViewModel.translations.dataTransferModal - .generalHeadline - } -

-
+

+ { + coreViewModel.translations.dataTransferModal + .generalHeadline + } +

+
-

- { - coreViewModel.translations.dataTransferModal - .chatsHeadline - } -

-
-
-
- - -
-
-
-); +

+ { + coreViewModel.translations.dataTransferModal + .chatsHeadline + } +

+
+
+
+ + +
+
+
+ ); } function TransferDataDisplayModal( @@ -278,82 +276,82 @@ function TransferDataDisplayModal( fileTransferViewModel: FileTransferViewModel, ) { const isPresented = React.createProxyState( - [fileTransferViewModel.presentedModal], - () => - fileTransferViewModel.presentedModal.value == - FileTransferModals.TransferDataDisplay, + [fileTransferViewModel.presentedModal], + () => + fileTransferViewModel.presentedModal.value == + FileTransferModals.TransferDataDisplay, ); return ( -
-
-
-

- { - coreViewModel.translations.dataTransferModal - .sendHeadline - } -

+
+
+
+

+ { + coreViewModel.translations.dataTransferModal + .sendHeadline + } +

- - { - coreViewModel.translations.dataTransferModal - .dataEntryDescription - } - + + { + coreViewModel.translations.dataTransferModal + .dataEntryDescription + } + -
+
-
-
- forum -
- - { - coreViewModel.translations - .dataTransferModal - .transferChannelHeadline - } - - -
-
-
- key -
- - { - coreViewModel.translations - .dataTransferModal - .transferKeyHeadline - } - - -
-
-
-
-
- - -
-
-
+
+
+ forum +
+ + { + coreViewModel.translations + .dataTransferModal + .transferChannelHeadline + } + + +
+
+
+ key +
+ + { + coreViewModel.translations + .dataTransferModal + .transferKeyHeadline + } + + +
+
+
+
+
+ + +
+
+
); } @@ -362,77 +360,77 @@ function TransferDisplayModal( fileTransferViewModel: FileTransferViewModel, ) { const isPresented = React.createProxyState( - [fileTransferViewModel.presentedModal], - () => - fileTransferViewModel.presentedModal.value == - FileTransferModals.TransferDisplay, + [fileTransferViewModel.presentedModal], + () => + fileTransferViewModel.presentedModal.value == + FileTransferModals.TransferDisplay, ); return ( -
-
-
-

- { - coreViewModel.translations.dataTransferModal - .transferDataHeadline - } -

+
+
+
+

+ { + coreViewModel.translations.dataTransferModal + .transferDataHeadline + } +

-

+

-

- { - coreViewModel.translations.dataTransferModal - .allFilesSent - } -

+

+ { + coreViewModel.translations.dataTransferModal + .allFilesSent + } +

-
+
-
-
-
- - -
-
-
-); +
+
+
+ + +
+
+
+ ); } function TransferDataInputModal( @@ -440,97 +438,97 @@ function TransferDataInputModal( fileTransferViewModel: FileTransferViewModel, ) { const isPresented = React.createProxyState( - [fileTransferViewModel.presentedModal], - () => - fileTransferViewModel.presentedModal.value == - FileTransferModals.TransferDataInput, + [fileTransferViewModel.presentedModal], + () => + fileTransferViewModel.presentedModal.value == + FileTransferModals.TransferDataInput, ); return ( -
-
-
-

- { - coreViewModel.translations.dataTransferModal - .receiveHeadline - } -

+
+
+
+

+ { + coreViewModel.translations.dataTransferModal + .receiveHeadline + } +

- - { - coreViewModel.translations.dataTransferModal - .dataEntryInputDescription - } - + + { + coreViewModel.translations.dataTransferModal + .dataEntryInputDescription + } + -
+
-
- - -
-
-
- - -
-
-
+
+ + +
+
+
+ + +
+
+
); } @@ -539,47 +537,47 @@ function DataReceptionModal( fileTransferViewModel: FileTransferViewModel, ) { const isPresented = React.createProxyState( - [fileTransferViewModel.presentedModal], - () => - fileTransferViewModel.presentedModal.value == - FileTransferModals.ReceptionDisplay, + [fileTransferViewModel.presentedModal], + () => + fileTransferViewModel.presentedModal.value == + FileTransferModals.ReceptionDisplay, ); return ( -
-
-
-

- { - coreViewModel.translations.dataTransferModal - .receiveHeadline - } -

+
+
+
+

+ { + coreViewModel.translations.dataTransferModal + .receiveHeadline + } +

-

+

-
+
-
-
- -
-
-); +
+
+ +
+
+ ); } function ExportFileSelectionModal( @@ -587,89 +585,89 @@ function ExportFileSelectionModal( fileTransferViewModel: FileTransferViewModel, ) { const OptionConverter: React.StateItemConverter = ( - fileOption: FileTransferOption, + fileOption: FileTransferOption, ) => { - return OptionEntry(fileOption, fileTransferViewModel); + return OptionEntry(fileOption, fileTransferViewModel); }; // state const isPresented = React.createProxyState( - [fileTransferViewModel.presentedModal], - () => - fileTransferViewModel.presentedModal.value == - FileTransferModals.ExportFileSelection, + [fileTransferViewModel.presentedModal], + () => + fileTransferViewModel.presentedModal.value == + FileTransferModals.ExportFileSelection, ); return ( -
-
-
-

- { - coreViewModel.translations.dataTransferModal - .exportHeadline - } -

- - { - coreViewModel.translations.dataTransferModal - .exportSelectionDescription - } - +
+
+
+

+ { + coreViewModel.translations.dataTransferModal + .exportHeadline + } +

+ + { + coreViewModel.translations.dataTransferModal + .exportSelectionDescription + } + -
+
-

- { - coreViewModel.translations.dataTransferModal - .generalHeadline - } -

-
+

+ { + coreViewModel.translations.dataTransferModal + .generalHeadline + } +

+
-

- { - coreViewModel.translations.dataTransferModal - .chatsHeadline - } -

-
-
-
- - -
-
-
-); +

+ { + coreViewModel.translations.dataTransferModal + .chatsHeadline + } +

+
+
+
+ + +
+
+
+ ); } function ExportModal( @@ -677,84 +675,82 @@ function ExportModal( fileTransferViewModel: FileTransferViewModel, ) { const isPresented = React.createProxyState( - [fileTransferViewModel.presentedModal], - () => - fileTransferViewModel.presentedModal.value == - FileTransferModals.Export, + [fileTransferViewModel.presentedModal], + () => + fileTransferViewModel.presentedModal.value == + FileTransferModals.Export, ); return ( -
-
-
-

- { - coreViewModel.translations.dataTransferModal - .exportHeadline - } -

+
+
+
+

+ { + coreViewModel.translations.dataTransferModal + .exportHeadline + } +

-
- - -
-
-
- - -
-
-
+
+ + +
+
+
+ + +
+
+
); } @@ -763,50 +759,57 @@ function ImportModal( fileTransferViewModel: FileTransferViewModel, ) { const isPresented = React.createProxyState( - [fileTransferViewModel.presentedModal], - () => - fileTransferViewModel.presentedModal.value == - FileTransferModals.ImportSelection, + [fileTransferViewModel.presentedModal], + () => + fileTransferViewModel.presentedModal.value == + FileTransferModals.ImportSelection, ); return ( -
-
-
-

- { - coreViewModel.translations.dataTransferModal - .importHeadline - } -

+
+
+
+

+ { + coreViewModel.translations.dataTransferModal + .importHeadline + } +

-
- -
-
+
+ +
+
-
- - -
-
-
+
+ + +
+ + ); } @@ -815,71 +818,73 @@ function ImportDecryptionDataModal( fileTransferViewModel: FileTransferViewModel, ) { const isPresented = React.createProxyState( - [fileTransferViewModel.presentedModal], - () => - fileTransferViewModel.presentedModal.value == - FileTransferModals.ImportDecryptData, + [fileTransferViewModel.presentedModal], + () => + fileTransferViewModel.presentedModal.value == + FileTransferModals.ImportDecryptData, ); return ( -
-
-
-

- { - coreViewModel.translations.dataTransferModal - .importHeadline - } -

+
+
+
+

+ { + coreViewModel.translations.dataTransferModal + .importHeadline + } +

-
- -
+
+ +
-

- {coreViewModel.translations.dataTransferModal.incorrectPassphraseError} -

-
-
- - -
-
-
+

+ { + coreViewModel.translations.dataTransferModal + .incorrectPassphraseError + } +

+
+
+ + +
+
+
); } diff --git a/src/View/translations.ts b/src/View/translations.ts index d32a7ccd..13fe9493 100644 --- a/src/View/translations.ts +++ b/src/View/translations.ts @@ -145,14 +145,14 @@ const englishTranslations = { filesReceivedCount: (count: number) => `Files processed: ${count}.`, - // - - exportKey: "Encryption key", - exportKeyConfirmation: "Confirm", - downloadFileButton: "Download", - importFileButton: "Import", - decryptImportButton: "Decrypt", - incorrectPassphraseError: "The encryption key is incorrect", + // + + exportKey: "Encryption key", + exportKeyConfirmation: "Confirm", + downloadFileButton: "Download", + importFileButton: "Import", + decryptImportButton: "Decrypt", + incorrectPassphraseError: "The encryption key is incorrect", }, storage: { diff --git a/src/ViewModel/Global/coreViewModel.ts b/src/ViewModel/Global/coreViewModel.ts index ffefdf32..7fc414e9 100644 --- a/src/ViewModel/Global/coreViewModel.ts +++ b/src/ViewModel/Global/coreViewModel.ts @@ -9,7 +9,7 @@ import ConnectionModel from "../../Model/Global/connectionModel"; import ChatListModel from "../../Model/Chat/chatListModel"; export default class CoreViewModel { - readonly BUILD = "Build 26.08.30.C"; + readonly BUILD = "Build 26.08.30.D"; translations: Translations; diff --git a/src/ViewModel/Global/fileTransferViewModel.ts b/src/ViewModel/Global/fileTransferViewModel.ts index cf9a1ba7..16c2d2d3 100644 --- a/src/ViewModel/Global/fileTransferViewModel.ts +++ b/src/ViewModel/Global/fileTransferViewModel.ts @@ -54,7 +54,7 @@ export default class FileTransferViewModel extends Context { exportKey: React.State = new React.State(""); exportKeyConfirmation: React.State = new React.State(""); - importedFile: React.State = new React.State(undefined); + importedFile: React.State = new React.State(undefined); importedFileString: React.State = new React.State(""); importKey: React.State = new React.State(""); isImportKeyCorrect: React.State = new React.State(true); @@ -71,21 +71,20 @@ export default class FileTransferViewModel extends Context { this.receivingTransferChannel.value == "" || this.receivingTransferKey.value == "", ); - + cannotExport: React.State = React.createProxyState( [this.exportKey, this.exportKeyConfirmation], () => - this.exportKey.value == "" || this.exportKey.value != this.exportKeyConfirmation.value + this.exportKey.value == "" || + this.exportKey.value != this.exportKeyConfirmation.value, ); cannotImport: React.State = React.createProxyState( [this.importedFile], - () => - this.importedFile.value == undefined + () => this.importedFile.value == undefined, ); cannotDecryptImport: React.State = React.createProxyState( [this.importKey], - () => - this.importKey.value == "" + () => this.importKey.value == "", ); // handlers @@ -136,142 +135,149 @@ export default class FileTransferViewModel extends Context { }; downloadFile = async (): Promise => { - const date = new Date().toISOString().split("T")[0]; + const date = new Date().toISOString().split("T")[0]; - const backup: Blob = await this.coreViewModel.fileTransferModel.generateBackup(this.selectedPaths.value.values(), this.exportKey.value); - const anchor = document.createElement("a"); - anchor.href = window.URL.createObjectURL(backup); - anchor.download = `comms-${date}.bak`; - anchor.click(); - } + const backup: Blob = + await this.coreViewModel.fileTransferModel.generateBackup( + this.selectedPaths.value.values(), + this.exportKey.value, + ); + const anchor = document.createElement("a"); + anchor.href = window.URL.createObjectURL(backup); + anchor.download = `comms-${date}.bak`; + anchor.click(); + }; updateImportSelection = (): void => { - const input = document.getElementById("file-transfer-input"); - if (!input) return; - if (!(input instanceof HTMLInputElement)) return; + const input = document.getElementById("file-transfer-input"); + if (!input) return; + if (!(input instanceof HTMLInputElement)) return; - const file = input.files[0]; - this.importedFile.value = file; - } + const file = input.files[0]; + this.importedFile.value = file; + }; importFile = (): void => { - const file = this.importedFile.value; - if (!file) return; + const file = this.importedFile.value; + if (!file) return; - const reader = new FileReader(); - reader.readAsText(file, "utf8"); - reader.onload = () => { - const result = reader.result; - this.importedFileString.value = result.toString(); - this.showImportDecryptDataModal(); - } - } + const reader = new FileReader(); + reader.readAsText(file, "utf8"); + reader.onload = () => { + const result = reader.result; + this.importedFileString.value = result.toString(); + this.showImportDecryptDataModal(); + }; + }; decryptImport = async (): Promise => { - const fileString = this.importedFileString.value; - const key = this.importKey.value; - if (!fileString || !key) return; + const fileString = this.importedFileString.value; + const key = this.importKey.value; + if (!fileString || !key) return; - this.cannotDecryptImport.value = true; - try { - await this.coreViewModel.fileTransferModel.handleBackupFile(fileString, key); - window.location.reload(); - } catch (e) { - console.error(e); - this.isImportKeyCorrect.value = false; - this.cannotDecryptImport.value = false; - } - } + this.cannotDecryptImport.value = true; + try { + await this.coreViewModel.fileTransferModel.handleBackupFile( + fileString, + key, + ); + window.location.reload(); + } catch (e) { + console.error(e); + this.isImportKeyCorrect.value = false; + this.cannotDecryptImport.value = false; + } + }; // view showDirectionSelectionModal = (): void => { - this.coreViewModel.context = this; - this.presentedModal.value = FileTransferModals.DirectionSelection; - this.getOptions(); + this.coreViewModel.context = this; + this.presentedModal.value = FileTransferModals.DirectionSelection; + this.getOptions(); }; showFileSelectionModal = (): void => { - this.presentedModal.value = FileTransferModals.FileSelection; + this.presentedModal.value = FileTransferModals.FileSelection; }; showTransferDataModal = (): void => { - this.presentedModal.value = FileTransferModals.TransferDataDisplay; - this.getTransferData(); - this.coreViewModel.fileTransferModel.prepareToSend(); + this.presentedModal.value = FileTransferModals.TransferDataDisplay; + this.getTransferData(); + this.coreViewModel.fileTransferModel.prepareToSend(); }; initiateTransfer = (): void => { - this.presentedModal.value = FileTransferModals.TransferDisplay; - this.didNotFinishSending.value = true; - this.filePathsSent.clear(); + this.presentedModal.value = FileTransferModals.TransferDisplay; + this.didNotFinishSending.value = true; + this.filePathsSent.clear(); - this.coreViewModel.fileTransferModel.sendFiles( - this.selectedPaths.value.values(), - (path: string) => { - this.filePathsSent.add(path); - }, - ); + this.coreViewModel.fileTransferModel.sendFiles( + this.selectedPaths.value.values(), + (path: string) => { + this.filePathsSent.add(path); + }, + ); - this.didNotFinishSending.value = false; + this.didNotFinishSending.value = false; }; showTransferDataInputModal = (): void => { - this.presentedModal.value = FileTransferModals.TransferDataInput; + this.presentedModal.value = FileTransferModals.TransferDataInput; }; showExportSelectionModal = (): void => { - this.presentedModal.value = FileTransferModals.ExportFileSelection; + this.presentedModal.value = FileTransferModals.ExportFileSelection; }; showExportModal = (): void => { - this.presentedModal.value = FileTransferModals.Export; + this.presentedModal.value = FileTransferModals.Export; }; showImportModal = (): void => { - this.presentedModal.value = FileTransferModals.ImportSelection; + this.presentedModal.value = FileTransferModals.ImportSelection; }; showImportDecryptDataModal = (): void => { - this.presentedModal.value = FileTransferModals.ImportDecryptData; + this.presentedModal.value = FileTransferModals.ImportDecryptData; }; prepareReceivingData = (): void => { - if (this.cannotPrepareToReceive.value == true) return; - this.presentedModal.value = FileTransferModals.ReceptionDisplay; - this.filePathsReceived.clear(); + if (this.cannotPrepareToReceive.value == true) return; + this.presentedModal.value = FileTransferModals.ReceptionDisplay; + this.filePathsReceived.clear(); - const transferData: TransferData = { - channel: this.receivingTransferChannel.value, - key: this.receivingTransferKey.value, - }; - this.coreViewModel.fileTransferModel.prepareToReceive(transferData); + const transferData: TransferData = { + channel: this.receivingTransferChannel.value, + key: this.receivingTransferKey.value, + }; + this.coreViewModel.fileTransferModel.prepareToReceive(transferData); }; // exit close = (): void => { - this.coreViewModel.closeContext(this.contextId); + this.coreViewModel.closeContext(this.contextId); }; handleContextClose = (): void => { - this.presentedModal.value = undefined; + this.presentedModal.value = undefined; }; // init constructor(public readonly coreViewModel: CoreViewModel) { - super("file-transfer"); + super("file-transfer"); - this.coreViewModel.fileTransferModel.fileHandlerManager.setHandler( - "file-transfer-view-model", - this.handleReceivedFile, - ); + this.coreViewModel.fileTransferModel.fileHandlerManager.setHandler( + "file-transfer-view-model", + this.handleReceivedFile, + ); - this.coreViewModel.fileTransferModel.readyToSendHandlerManager.setHandler( - "file-transfer-view-model", - () => this.initiateTransfer(), - ); + this.coreViewModel.fileTransferModel.readyToSendHandlerManager.setHandler( + "file-transfer-view-model", + () => this.initiateTransfer(), + ); - // keystrokes - this.registerKeyStroke(CommonKeys.CloseOrCancel, this.close); + // keystrokes + this.registerKeyStroke(CommonKeys.CloseOrCancel, this.close); } } @@ -283,18 +289,18 @@ export interface FileTransferOption { export enum FileTransferModals { DirectionSelection, - // sending - FileSelection, - TransferDataDisplay, - TransferDisplay, + // sending + FileSelection, + TransferDataDisplay, + TransferDisplay, - // receiving - TransferDataInput, - ReceptionDisplay, + // receiving + TransferDataInput, + ReceptionDisplay, - // export - ExportFileSelection, - Export, - ImportSelection, - ImportDecryptData, + // export + ExportFileSelection, + Export, + ImportSelection, + ImportDecryptData, } diff --git a/src/index.tsx b/src/index.tsx index bcd2f431..a44a0c5e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,7 +2,6 @@ import * as React from "bloatless-react"; import "./colors.css"; import "./coloredTile.css"; import "./base.css"; -import * as Utility from "./Model/Utility/utility"; import HomeViewModel from "./ViewModel/Pages/homeViewModel"; import StorageViewModel from "./ViewModel/Global/storageViewModel"; import SettingsViewModel from "./ViewModel/Global/settingsViewModel"; @@ -17,6 +16,7 @@ import { SettingsModal } from "./View/Modals/settingsModal"; import { DataTransferModalWrapper } from "./View/Modals/dataTransferModal"; import { ConnectionModal } from "./View/Modals/connectionModal"; import v1Upgrader from "./Upgrader/v1"; +import * as Utility from "./Model/Utility/utility"; import StorageModel from "./Model/Global/storageModel"; import SettingsModel from "./Model/Global/settingsModel"; import FileTransferModel from "./Model/Global/fileTransferModel"; @@ -100,3 +100,4 @@ document StorageModal(coreViewModel, storageViewModel), SettingsModal(coreViewModel, settingsViewModel), ); +Utility.implementPinchZoom(".zoom");