add progress bar to data transfer

This commit is contained in:
marlon-erler
2026-09-30 18:57:09 +02:00
parent c78c5747cf
commit 397a5f76e4
25 changed files with 1125 additions and 1048 deletions
+63 -53
View File
@@ -436,9 +436,8 @@
sendButton: "Send", sendButton: "Send",
sendAgainButton: "Send again", sendAgainButton: "Send again",
/// ///
filesSentCount: (count) => `Files sent: ${count}.`, filesSentProgress: (progress, total) => `Sent ${progress} of ${total} files`,
allFilesSent: "Done.", filesReceivedProgress: (progress, total) => `Received ${progress} of ${total} files`,
filesReceivedCount: (count) => `Files processed: ${count}.`,
// //
exportKey: "Encryption key", exportKey: "Encryption key",
exportKeyConfirmation: "Confirm", exportKeyConfirmation: "Confirm",
@@ -702,9 +701,8 @@
transferKeyHeadline: "Schl\xFCssel", transferKeyHeadline: "Schl\xFCssel",
sendButton: "Senden", sendButton: "Senden",
sendAgainButton: "Erneut senden", sendAgainButton: "Erneut senden",
filesSentCount: (count) => `Dateien gesendet: ${count}.`, filesSentProgress: (progress, total) => `${progress} von ${total} Dateien gesendet`,
allFilesSent: "Fertig.", filesReceivedProgress: (progress, total) => `${progress} von ${total} Dateien empfangen`,
filesReceivedCount: (count) => `Dateien empfangen: ${count}.`,
exportKey: "Schl\xFCssel", exportKey: "Schl\xFCssel",
exportKeyConfirmation: "Schl\xFCssel best\xE4tigen", exportKeyConfirmation: "Schl\xFCssel best\xE4tigen",
downloadFileButton: "Herunterladen", downloadFileButton: "Herunterladen",
@@ -954,9 +952,8 @@
transferKeyHeadline: "Clave de encriptaci\xF3n de transferencia", transferKeyHeadline: "Clave de encriptaci\xF3n de transferencia",
sendButton: "Enviar", sendButton: "Enviar",
sendAgainButton: "Enviar otra vez", sendAgainButton: "Enviar otra vez",
filesSentCount: (count) => `Archivos enviados: ${count}.`, filesSentProgress: (progress, total) => `${progress} de ${total} enviados`,
allFilesSent: "Hecho.", filesReceivedProgress: (progress, total) => `${progress} de ${total} recibidos`,
filesReceivedCount: (count) => `Archivos recibidos: ${count}.`,
exportKey: "Clave de encriptaci\xF3n", exportKey: "Clave de encriptaci\xF3n",
exportKeyConfirmation: "Confirmar clave", exportKeyConfirmation: "Confirmar clave",
downloadFileButton: "Descargar", downloadFileButton: "Descargar",
@@ -2290,27 +2287,13 @@
this.receivingTransferChannel = new State(""); this.receivingTransferChannel = new State("");
this.receivingTransferKey = new State(""); this.receivingTransferKey = new State("");
this.filePathsSent = new ListState(); this.filePathsSent = new ListState();
this.filesSentCount = createProxyState( this.fileTransferTotal = new State(0);
[this.filePathsSent], this.fileTransferProgress = new State(0);
() => this.filePathsSent.value.size
);
this.filesSentText = createProxyState(
[this.filesSentCount],
() => this.coreViewModel.translations.dataTransferModal.filesSentCount(
this.filesSentCount.value
)
);
this.filePathsReceived = new ListState(); this.filePathsReceived = new ListState();
this.filesReceivedCount = createProxyState( this.filesReceivedCount = createProxyState(
[this.filePathsReceived], [this.filePathsReceived],
() => this.filePathsReceived.value.size () => this.filePathsReceived.value.size
); );
this.filesReceivedText = createProxyState(
[this.filesReceivedCount],
() => this.coreViewModel.translations.dataTransferModal.filesReceivedCount(
this.filesReceivedCount.value
)
);
this.exportKey = new State(""); this.exportKey = new State("");
this.exportKeyConfirmation = new State(""); this.exportKeyConfirmation = new State("");
this.importedFile = new State(void 0); this.importedFile = new State(void 0);
@@ -2322,7 +2305,6 @@
[this.selectedPaths], [this.selectedPaths],
() => this.selectedPaths.value.size == 0 () => this.selectedPaths.value.size == 0
); );
this.didNotFinishSending = new State(true);
this.cannotPrepareToReceive = createProxyState( this.cannotPrepareToReceive = createProxyState(
[this.receivingTransferChannel, this.receivingTransferKey], [this.receivingTransferChannel, this.receivingTransferKey],
() => this.receivingTransferChannel.value == "" || this.receivingTransferKey.value == "" () => this.receivingTransferChannel.value == "" || this.receivingTransferKey.value == ""
@@ -2346,6 +2328,7 @@
// handlers // handlers
this.handleReceivedFile = (path) => { this.handleReceivedFile = (path) => {
this.filePathsReceived.add(path); this.filePathsReceived.add(path);
this.fileTransferProgress.value = this.filePathsReceived.value.size;
}; };
// methods // methods
this.getOptions = () => { this.getOptions = () => {
@@ -2454,7 +2437,6 @@
}; };
this.initiateTransfer = () => { this.initiateTransfer = () => {
this.presentedModal.value = 3 /* TransferDisplay */; this.presentedModal.value = 3 /* TransferDisplay */;
this.didNotFinishSending.value = true;
this.filePathsSent.clear(); this.filePathsSent.clear();
this.coreViewModel.fileTransferModel.sendFiles( this.coreViewModel.fileTransferModel.sendFiles(
this.selectedPaths.value.values(), this.selectedPaths.value.values(),
@@ -2462,7 +2444,6 @@
this.filePathsSent.add(path); this.filePathsSent.add(path);
} }
); );
this.didNotFinishSending.value = false;
}; };
this.showTransferDataInputModal = () => { this.showTransferDataInputModal = () => {
this.presentedModal.value = 4 /* TransferDataInput */; this.presentedModal.value = 4 /* TransferDataInput */;
@@ -2496,6 +2477,10 @@
this.handleContextClose = () => { this.handleContextClose = () => {
this.presentedModal.value = void 0; this.presentedModal.value = void 0;
}; };
this.coreViewModel.fileTransferModel.transferSizeHandlerManager.setHandler(
"file-transfer-view-model",
(count) => this.fileTransferTotal.value = count
);
this.coreViewModel.fileTransferModel.fileHandlerManager.setHandler( this.coreViewModel.fileTransferModel.fileHandlerManager.setHandler(
"file-transfer-view-model", "file-transfer-view-model",
this.handleReceivedFile this.handleReceivedFile
@@ -4742,7 +4727,6 @@
}; };
this.randomizeKey = () => { this.randomizeKey = () => {
this.encryptionKeyInput.value = random(24); this.encryptionKeyInput.value = random(24);
this.setEncryptionKey();
}; };
this.applyColor = () => { this.applyColor = () => {
this.chatViewModel.setColor(this.color.value); this.chatViewModel.setColor(this.color.value);
@@ -8165,6 +8149,21 @@
return /* @__PURE__ */ createElement("span", { class: "ellipsis" }, string); return /* @__PURE__ */ createElement("span", { class: "ellipsis" }, string);
}; };
// src/View/Components/progressBar.tsx
function ProgressBar(valueLabel, value, maximum) {
const style = createProxyState(
[value, maximum],
() => `width: ${100 * (value.value / maximum.value)}%`
);
return /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "width-100 surface-alt", style: "height: 18px" }, /* @__PURE__ */ createElement(
"div",
{
class: "height-100 background-primary",
"set:style": style
}
)), /* @__PURE__ */ createElement("span", { class: "secondary", "subscribe:innerText": valueLabel }));
}
// src/View/Modals/dataTransferModal.tsx // src/View/Modals/dataTransferModal.tsx
function DataTransferModalWrapper(coreViewModel2, connectionViewModel2, fileTransferViewModel2) { function DataTransferModalWrapper(coreViewModel2, connectionViewModel2, fileTransferViewModel2) {
return /* @__PURE__ */ createElement("div", null, DirectionSelectionModal( return /* @__PURE__ */ createElement("div", null, DirectionSelectionModal(
@@ -8320,20 +8319,8 @@
[fileTransferViewModel2.presentedModal], [fileTransferViewModel2.presentedModal],
() => fileTransferViewModel2.presentedModal.value == 3 /* TransferDisplay */ () => fileTransferViewModel2.presentedModal.value == 3 /* TransferDisplay */
); );
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( const progressLabel = createProxyState([fileTransferViewModel2.fileTransferProgress, fileTransferViewModel2.fileTransferTotal], () => coreViewModel2.translations.dataTransferModal.filesSentProgress(fileTransferViewModel2.fileTransferProgress.value, fileTransferViewModel2.fileTransferTotal.value));
"p", 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), ProgressBar(progressLabel, fileTransferViewModel2.fileTransferProgress, fileTransferViewModel2.fileTransferTotal), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.transferChannelHeadline), /* @__PURE__ */ createElement(
{
class: "secondary",
"subscribe:innerText": fileTransferViewModel2.filesSentText
}
), /* @__PURE__ */ createElement(
"p",
{
class: "secondary",
"toggle:hidden": fileTransferViewModel2.didNotFinishSending
},
coreViewModel2.translations.dataTransferModal.allFilesSent
), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.transferChannelHeadline), /* @__PURE__ */ createElement(
"b", "b",
{ {
"subscribe:innerText": fileTransferViewModel2.transferChannel "subscribe:innerText": fileTransferViewModel2.transferChannel
@@ -8364,8 +8351,7 @@
"button", "button",
{ {
class: "flex", class: "flex",
"on:click": fileTransferViewModel2.close, "on:click": fileTransferViewModel2.close
"toggle:disabled": fileTransferViewModel2.didNotFinishSending
}, },
coreViewModel2.translations.general.closeButton, coreViewModel2.translations.general.closeButton,
/* @__PURE__ */ createElement("span", { class: "icon" }, "close") /* @__PURE__ */ createElement("span", { class: "icon" }, "close")
@@ -8413,13 +8399,8 @@
[fileTransferViewModel2.presentedModal], [fileTransferViewModel2.presentedModal],
() => fileTransferViewModel2.presentedModal.value == 5 /* ReceptionDisplay */ () => fileTransferViewModel2.presentedModal.value == 5 /* ReceptionDisplay */
); );
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( const progressLabel = createProxyState([fileTransferViewModel2.fileTransferProgress, fileTransferViewModel2.fileTransferTotal], () => coreViewModel2.translations.dataTransferModal.filesReceivedProgress(fileTransferViewModel2.fileTransferProgress.value, fileTransferViewModel2.fileTransferTotal.value));
"p", 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), ProgressBar(progressLabel, fileTransferViewModel2.fileTransferProgress, fileTransferViewModel2.fileTransferTotal), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement(
{
class: "secondary",
"subscribe:innerText": fileTransferViewModel2.filesReceivedText
}
), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement(
"div", "div",
{ {
class: "tile flex-column align-start", class: "tile flex-column align-start",
@@ -8641,6 +8622,7 @@
this.direction = 0 /* Send */; this.direction = 0 /* Send */;
// handler managers // handler managers
this.fileHandlerManager = new HandlerManager(); this.fileHandlerManager = new HandlerManager();
this.transferSizeHandlerManager = new HandlerManager();
this.readyToSendHandlerManager = new HandlerManager(); this.readyToSendHandlerManager = new HandlerManager();
// general // general
this.generateTransferData = () => { this.generateTransferData = () => {
@@ -8700,6 +8682,11 @@
}; };
this.handleDecryptedFile = (data) => { this.handleDecryptedFile = (data) => {
const parsed = parse(data); const parsed = parse(data);
if (!parsed.type) return;
if (parsed.type == "transfer-size" && typeof parsed.count == "number") {
this.transferSizeHandlerManager.trigger(parsed.count);
return;
}
const isFileData = checkMatchesObjectStructure( const isFileData = checkMatchesObjectStructure(
parsed, parsed,
FileDataReference FileDataReference
@@ -8714,15 +8701,21 @@
}; };
// sending // sending
this.sendFiles = (directoryPaths, fileCallback) => { this.sendFiles = (directoryPaths, fileCallback) => {
const filesToSend = /* @__PURE__ */ new Set();
for (const directoryPath of directoryPaths) { for (const directoryPath of directoryPaths) {
this.storageModel.recurse(directoryPath, (filePath) => { this.storageModel.recurse(directoryPath, (filePath) => {
const stringifiedFileData = this.prepareFileForSending(filePath); const stringifiedFileData = this.prepareFileForSending(filePath);
filesToSend.add([stringifiedFileData, filePath]);
});
}
this.sendTransferSize(filesToSend.size);
for (const file of filesToSend) {
const [stringifiedFileData, filePath] = file;
this.sendFile(stringifiedFileData); this.sendFile(stringifiedFileData);
const pathString = StorageModel.pathComponentsToString( const pathString = StorageModel.pathComponentsToString(
...filePath ...filePath
); );
fileCallback(pathString); fileCallback(pathString);
});
} }
}; };
this.generateBackup = async (directoryPaths, passphrase) => { this.generateBackup = async (directoryPaths, passphrase) => {
@@ -8739,10 +8732,26 @@
const blob = new Blob([encrypted], { type: "text/plain" }); const blob = new Blob([encrypted], { type: "text/plain" });
return blob; return blob;
}; };
this.sendTransferSize = async (count) => {
if (!this.transferData) return;
const data = {
type: "transfer-size",
count
};
const encryptedData = await encryptString(
stringify(data),
this.transferData.key
);
this.connectionModel.sendPlainMessage(
this.transferData.channel,
encryptedData
);
};
this.prepareFileForSending = (filePath) => { this.prepareFileForSending = (filePath) => {
const fileContent = this.storageModel.read(filePath); const fileContent = this.storageModel.read(filePath);
if (fileContent == null) return ""; if (fileContent == null) return "";
const fileData = { const fileData = {
type: "file-data",
path: filePath, path: filePath,
body: fileContent body: fileContent
}; };
@@ -8771,6 +8780,7 @@
} }
}; };
var FileDataReference = { var FileDataReference = {
type: "file-data",
path: [""], path: [""],
body: "" body: ""
}; };
Vendored
+1 -1
View File
@@ -1,4 +1,4 @@
const VERSION = "2610 Beta 21.12" const VERSION = "2610 Beta 21.13"
function processUrl(event) { function processUrl(event) {
const url = event.request.url; const url = event.request.url;
+1 -1
View File
@@ -1 +1 @@
2610 Beta 21.12 2610 Beta 21.13
+40 -1
View File
@@ -24,6 +24,7 @@ export default class FileTransferModel {
// handler managers // handler managers
readonly fileHandlerManager = new HandlerManager<string>(); readonly fileHandlerManager = new HandlerManager<string>();
readonly transferSizeHandlerManager = new HandlerManager<number>();
readonly readyToSendHandlerManager = new HandlerManager<boolean>(); readonly readyToSendHandlerManager = new HandlerManager<boolean>();
// general // general
@@ -105,6 +106,12 @@ export default class FileTransferModel {
readonly handleDecryptedFile = (data: string): void => { readonly handleDecryptedFile = (data: string): void => {
const parsed: any = parse(data); const parsed: any = parse(data);
if (!parsed.type) return;
if (parsed.type == "transfer-size" && typeof parsed.count == "number") {
this.transferSizeHandlerManager.trigger(parsed.count);
return;
}
const isFileData: boolean = checkMatchesObjectStructure( const isFileData: boolean = checkMatchesObjectStructure(
parsed, parsed,
FileDataReference, FileDataReference,
@@ -126,16 +133,22 @@ export default class FileTransferModel {
directoryPaths: IteratorObject<string[]>, directoryPaths: IteratorObject<string[]>,
fileCallback: (path: string) => void, fileCallback: (path: string) => void,
): void => { ): void => {
const filesToSend = new Set<[string, string[]]>();
for (const directoryPath of directoryPaths) { for (const directoryPath of directoryPaths) {
this.storageModel.recurse(directoryPath, (filePath: string[]) => { this.storageModel.recurse(directoryPath, (filePath: string[]) => {
const stringifiedFileData = const stringifiedFileData =
this.prepareFileForSending(filePath); this.prepareFileForSending(filePath);
filesToSend.add([stringifiedFileData, filePath]);
});
}
this.sendTransferSize(filesToSend.size);
for (const file of filesToSend) {
const [stringifiedFileData, filePath] = file;
this.sendFile(stringifiedFileData); this.sendFile(stringifiedFileData);
const pathString: string = StorageModel.pathComponentsToString( const pathString: string = StorageModel.pathComponentsToString(
...filePath, ...filePath,
); );
fileCallback(pathString); fileCallback(pathString);
});
} }
}; };
@@ -158,11 +171,30 @@ export default class FileTransferModel {
return blob; return blob;
}; };
readonly sendTransferSize = async (count: number): Promise<void> => {
if (!this.transferData) return;
const data: TransferSize = {
type: "transfer-size",
count,
};
const encryptedData: string = await encryptString(
stringify(data),
this.transferData.key,
);
this.connectionModel.sendPlainMessage(
this.transferData.channel,
encryptedData,
);
};
readonly prepareFileForSending = (filePath: string[]): string => { readonly prepareFileForSending = (filePath: string[]): string => {
const fileContent: string | null = this.storageModel.read(filePath); const fileContent: string | null = this.storageModel.read(filePath);
if (fileContent == null) return ""; if (fileContent == null) return "";
const fileData: FileData = { const fileData: FileData = {
type: "file-data",
path: filePath, path: filePath,
body: fileContent, body: fileContent,
}; };
@@ -200,11 +232,18 @@ export interface TransferData {
} }
export interface FileData { export interface FileData {
type: "file-data";
path: string[]; path: string[];
body: string; body: string;
} }
export interface TransferSize {
type: "transfer-size";
count: number;
}
export const FileDataReference: FileData = { export const FileDataReference: FileData = {
type: "file-data",
path: [""], path: [""],
body: "", body: "",
}; };
+1 -1
View File
@@ -5,7 +5,7 @@ const ENCRYPTION_ALG = "AES-GCM";
// random // random
export function random(length: number): string { export function random(length: number): string {
return cryptoRandomString({length}) return cryptoRandomString({ length });
} }
// encrypt // encrypt
+5 -4
View File
@@ -140,11 +140,12 @@ export function SettingsPage(
<div class="flex-row width-input"> <div class="flex-row width-input">
<button <button
class="width-50" class="width-50"
on:click={ on:click={settingsPageViewModel.randomizeKey}
settingsPageViewModel.randomizeKey
}
> >
{coreViewModel.translations.chatPage.settings.randomizeKey} {
coreViewModel.translations.chatPage.settings
.randomizeKey
}
<span class="icon">shuffle</span> <span class="icon">shuffle</span>
</button> </button>
<button <button
+4 -4
View File
@@ -65,10 +65,10 @@ export function TaskPage(
class="no-outline" class="no-outline"
bind:value={taskPageViewModel.boardQuery} bind:value={taskPageViewModel.boardQuery}
on:enter={taskPageViewModel.createBoard} on:enter={taskPageViewModel.createBoard}
placeholder={ placeholder={coreViewModel.translations.general.filterOrCreateLabel(
coreViewModel.translations.general coreViewModel.translations.chatPage.task
.filterOrCreateLabel(coreViewModel.translations.chatPage.task.typeBoard) .typeBoard,
} )}
></input> ></input>
</div> </div>
</div> </div>
+24
View File
@@ -0,0 +1,24 @@
import * as React from "../../react";
export function ProgressBar(
valueLabel: React.State<string>,
value: React.State<number>,
maximum: React.State<number>,
) {
const style = React.createProxyState(
[value, maximum],
() => `width: ${100 * (value.value / maximum.value)}%`,
);
return (
<div>
<div class="width-100 surface-alt" style="height: 18px">
<div
class="height-100 background-primary"
set:style={style}
></div>
</div>
<span class="secondary" subscribe:innerText={valueLabel}></span>
</div>
);
}
+36 -31
View File
@@ -1,6 +1,7 @@
import { ViewController } from "../viewController"; import { ViewController } from "../viewController";
import { CommonKeys, getKeySymbol } from "../keystrokes"; import { CommonKeys, getKeySymbol } from "../keystrokes";
import { StringToTextSpan } from "../Components/textSpan"; import { StringToTextSpan } from "../Components/textSpan";
import { ProgressBar } from "../Components/progressBar";
import * as React from "../../react"; import * as React from "../../react";
import FileTransferViewModel, { import FileTransferViewModel, {
FileTransferModals, FileTransferModals,
@@ -370,6 +371,18 @@ function TransferDisplayModal(
FileTransferModals.TransferDisplay, FileTransferModals.TransferDisplay,
); );
const progressLabel = React.createProxyState(
[
fileTransferViewModel.fileTransferProgress,
fileTransferViewModel.fileTransferTotal,
],
() =>
coreViewModel.translations.dataTransferModal.filesSentProgress(
fileTransferViewModel.fileTransferProgress.value,
fileTransferViewModel.fileTransferTotal.value,
),
);
return ( return (
<div class="modal" toggle:open={isPresented}> <div class="modal" toggle:open={isPresented}>
<div> <div>
@@ -381,24 +394,11 @@ function TransferDisplayModal(
} }
</h2> </h2>
<p {ProgressBar(
class="secondary" progressLabel,
subscribe:innerText={ fileTransferViewModel.fileTransferProgress,
fileTransferViewModel.filesSentText fileTransferViewModel.fileTransferTotal,
} )}
></p>
<p
class="secondary"
toggle:hidden={
fileTransferViewModel.didNotFinishSending
}
>
{
coreViewModel.translations.dataTransferModal
.allFilesSent
}
</p>
<hr></hr> <hr></hr>
@@ -460,13 +460,7 @@ function TransferDisplayModal(
} }
<span class="icon">restart_alt</span> <span class="icon">restart_alt</span>
</button> </button>
<button <button class="flex" on:click={fileTransferViewModel.close}>
class="flex"
on:click={fileTransferViewModel.close}
toggle:disabled={
fileTransferViewModel.didNotFinishSending
}
>
{coreViewModel.translations.general.closeButton} {coreViewModel.translations.general.closeButton}
<span class="icon">close</span> <span class="icon">close</span>
</button> </button>
@@ -588,6 +582,18 @@ function DataReceptionModal(
FileTransferModals.ReceptionDisplay, FileTransferModals.ReceptionDisplay,
); );
const progressLabel = React.createProxyState(
[
fileTransferViewModel.fileTransferProgress,
fileTransferViewModel.fileTransferTotal,
],
() =>
coreViewModel.translations.dataTransferModal.filesReceivedProgress(
fileTransferViewModel.fileTransferProgress.value,
fileTransferViewModel.fileTransferTotal.value,
),
);
return ( return (
<div class="modal" toggle:open={isPresented}> <div class="modal" toggle:open={isPresented}>
<div> <div>
@@ -599,12 +605,11 @@ function DataReceptionModal(
} }
</h2> </h2>
<p {ProgressBar(
class="secondary" progressLabel,
subscribe:innerText={ fileTransferViewModel.fileTransferProgress,
fileTransferViewModel.filesReceivedText fileTransferViewModel.fileTransferTotal,
} )}
></p>
<hr></hr> <hr></hr>
+21 -12
View File
@@ -1,5 +1,6 @@
import "./homePage.css"; import "./homePage.css";
import { StringToOption } from "./Components/option"; import { StringToOption } from "./Components/option";
import { NewItemEntry } from "./Components/newItemEntry";
import { HomePageButton } from "./Components/homePageButton"; import { HomePageButton } from "./Components/homePageButton";
import { ChatViewModelToChatEntry } from "./Components/chatEntry"; import { ChatViewModelToChatEntry } from "./Components/chatEntry";
import * as React from "../react"; import * as React from "../react";
@@ -9,7 +10,6 @@ import FileTransferViewModel from "../ViewModel/Global/fileTransferViewModel";
import CoreViewModel from "../ViewModel/Global/coreViewModel"; import CoreViewModel from "../ViewModel/Global/coreViewModel";
import ConnectionViewModel from "../ViewModel/Global/connectionViewModel"; import ConnectionViewModel from "../ViewModel/Global/connectionViewModel";
import ChatListViewModel from "../ViewModel/Chat/chatListViewModel"; import ChatListViewModel from "../ViewModel/Chat/chatListViewModel";
import {NewItemEntry} from "./Components/newItemEntry";
export function HomePage( export function HomePage(
coreViewModel: CoreViewModel, coreViewModel: CoreViewModel,
@@ -19,9 +19,18 @@ export function HomePage(
fileTransferViewModel: FileTransferViewModel, fileTransferViewModel: FileTransferViewModel,
chatListViewModel: ChatListViewModel, chatListViewModel: ChatListViewModel,
) { ) {
const greeting = React.createProxyState([settingsViewModel.username], ()=>coreViewModel.translations.homePage.greeting(settingsViewModel.username.value)); const greeting = React.createProxyState([settingsViewModel.username], () =>
coreViewModel.translations.homePage.greeting(
settingsViewModel.username.value,
),
);
const isChatPlaceholderHidden = React.createProxyState([chatListViewModel.chatMatches, chatListViewModel.chatQuery], ()=>chatListViewModel.chatMatches.value.size > 0 || chatListViewModel.chatQuery.value != ""); const isChatPlaceholderHidden = React.createProxyState(
[chatListViewModel.chatMatches, chatListViewModel.chatQuery],
() =>
chatListViewModel.chatMatches.value.size > 0 ||
chatListViewModel.chatQuery.value != "",
);
// sections // sections
const overviewSection = ( const overviewSection = (
@@ -128,10 +137,9 @@ export function HomePage(
<div id="chat-section"> <div id="chat-section">
<div class="flex-row width-input"> <div class="flex-row width-input">
<input <input
placeholder={ placeholder={coreViewModel.translations.general.filterOrCreateLabel(
coreViewModel.translations.general coreViewModel.translations.homePage.typeChat,
.filterOrCreateLabel(coreViewModel.translations.homePage.typeChat) )}
}
bind:value={chatListViewModel.chatQuery} bind:value={chatListViewModel.chatQuery}
on:enter={chatListViewModel.createChat} on:enter={chatListViewModel.createChat}
></input> ></input>
@@ -139,12 +147,13 @@ export function HomePage(
<hr></hr> <hr></hr>
{NewItemEntry(coreViewModel, chatListViewModel.chatQuery, chatListViewModel.createChat)} {NewItemEntry(
coreViewModel,
chatListViewModel.chatQuery,
chatListViewModel.createChat,
)}
<span <span class="secondary" toggle:hidden={isChatPlaceholderHidden}>
class="secondary"
toggle:hidden={isChatPlaceholderHidden}
>
{coreViewModel.translations.homePage.noChatsPlaceholder} {coreViewModel.translations.homePage.noChatsPlaceholder}
</span> </span>
+14 -13
View File
@@ -181,10 +181,10 @@ const englishTranslations = {
/// ///
filesSentCount: (count: number) => `Files sent: ${count}.`, filesSentProgress: (progress: number, total: number) =>
allFilesSent: "Done.", `Sent ${progress} of ${total} files`,
filesReceivedProgress: (progress: number, total: number) =>
filesReceivedCount: (count: number) => `Files processed: ${count}.`, `Received ${progress} of ${total} files`,
// //
@@ -402,7 +402,8 @@ export const allTranslations: {
saveButton: "Speichern", saveButton: "Speichern",
setButton: "OK", setButton: "OK",
filterOrCreateLabel: (type: string) => `${type} suchen oder erstellen`, filterOrCreateLabel: (type: string) =>
`${type} suchen oder erstellen`,
createLabel: (query: string) => `"${query}" erstellen`, createLabel: (query: string) => `"${query}" erstellen`,
reloadAppButton: "Neu laden", reloadAppButton: "Neu laden",
@@ -550,10 +551,10 @@ export const allTranslations: {
sendButton: "Senden", sendButton: "Senden",
sendAgainButton: "Erneut senden", sendAgainButton: "Erneut senden",
filesSentCount: (count) => `Dateien gesendet: ${count}.`, filesSentProgress: (progress: number, total: number) =>
allFilesSent: "Fertig.", `${progress} von ${total} Dateien gesendet`,
filesReceivedProgress: (progress: number, total: number) =>
filesReceivedCount: (count) => `Dateien empfangen: ${count}.`, `${progress} von ${total} Dateien empfangen`,
exportKey: "Schlüssel", exportKey: "Schlüssel",
exportKeyConfirmation: "Schlüssel bestätigen", exportKeyConfirmation: "Schlüssel bestätigen",
@@ -894,10 +895,10 @@ export const allTranslations: {
sendButton: "Enviar", sendButton: "Enviar",
sendAgainButton: "Enviar otra vez", sendAgainButton: "Enviar otra vez",
filesSentCount: (count) => `Archivos enviados: ${count}.`, filesSentProgress: (progress: number, total: number) =>
allFilesSent: "Hecho.", `${progress} de ${total} enviados`,
filesReceivedProgress: (progress: number, total: number) =>
filesReceivedCount: (count) => `Archivos recibidos: ${count}.`, `${progress} de ${total} recibidos`,
exportKey: "Clave de encriptación", exportKey: "Clave de encriptación",
exportKeyConfirmation: "Confirmar clave", exportKeyConfirmation: "Confirmar clave",
+7 -4
View File
@@ -35,9 +35,7 @@ export default class ChatListViewModel {
// methods // methods
createChat = (): void => { createChat = (): void => {
const chatModel: ChatModel = const chatModel: ChatModel =
this.coreViewModel.chatListModel.createChat( this.coreViewModel.chatListModel.createChat(this.chatQuery.value);
this.chatQuery.value,
);
this.chatQuery.value = ""; this.chatQuery.value = "";
const chatViewModel: ChatViewModel = const chatViewModel: ChatViewModel =
@@ -109,7 +107,12 @@ export default class ChatListViewModel {
this.loadChats(); this.loadChats();
implementFilter(this.chatViewModels, this.chatMatches, this.chatQuery, chat => chat.chatModel.info.name) implementFilter(
this.chatViewModels,
this.chatMatches,
this.chatQuery,
(chat) => chat.chatModel.info.name,
);
} }
// utility // utility
+3 -2
View File
@@ -57,14 +57,15 @@ export default class CoreViewModel {
while (this.contexts.length > 0) { while (this.contexts.length > 0) {
const currentContext: Context | undefined = this.context; const currentContext: Context | undefined = this.context;
if (!currentContext) break; if (!currentContext) break;
const response = currentContext.handleContextClose(fromHistoryEvent); const response =
currentContext.handleContextClose(fromHistoryEvent);
if (response == false) break; if (response == false) break;
this.contextStack.delete(currentContext.contextId); this.contextStack.delete(currentContext.contextId);
if (response == true) { if (response == true) {
exitNext = true; exitNext = true;
continue; continue;
} else if (exitNext == true) { } else if (exitNext == true) {
break break;
} else if (currentContext.contextId == contextId) { } else if (currentContext.contextId == contextId) {
break; break;
} }
+9 -23
View File
@@ -11,7 +11,7 @@ import ChatModel from "../../Model/Chat/chatModel";
export default class FileTransferViewModel extends Context { export default class FileTransferViewModel extends Context {
// state // state
exitReception: () => void | undefined = undefined; exitReception: (() => void) | undefined = undefined;
presentedModal: React.State<FileTransferModals | undefined> = presentedModal: React.State<FileTransferModals | undefined> =
new React.State<any>(undefined); new React.State<any>(undefined);
@@ -29,30 +29,14 @@ export default class FileTransferViewModel extends Context {
receivingTransferKey: React.State<string> = new React.State(""); receivingTransferKey: React.State<string> = new React.State("");
filePathsSent: React.ListState<string> = new React.ListState(); filePathsSent: React.ListState<string> = new React.ListState();
filesSentCount: React.State<number> = React.createProxyState(
[this.filePathsSent],
() => this.filePathsSent.value.size,
);
filesSentText: React.State<string> = React.createProxyState(
[this.filesSentCount],
() =>
this.coreViewModel.translations.dataTransferModal.filesSentCount(
this.filesSentCount.value,
),
);
fileTransferTotal: React.State<number> = new React.State(0);
fileTransferProgress: React.State<number> = new React.State(0);
filePathsReceived: React.ListState<string> = new React.ListState(); filePathsReceived: React.ListState<string> = new React.ListState();
filesReceivedCount: React.State<number> = React.createProxyState( filesReceivedCount: React.State<number> = React.createProxyState(
[this.filePathsReceived], [this.filePathsReceived],
() => this.filePathsReceived.value.size, () => this.filePathsReceived.value.size,
); );
filesReceivedText: React.State<string> = React.createProxyState(
[this.filesReceivedCount],
() =>
this.coreViewModel.translations.dataTransferModal.filesReceivedCount(
this.filesReceivedCount.value,
),
);
exportKey: React.State<string> = new React.State(""); exportKey: React.State<string> = new React.State("");
exportKeyConfirmation: React.State<string> = new React.State(""); exportKeyConfirmation: React.State<string> = new React.State("");
@@ -69,7 +53,6 @@ export default class FileTransferViewModel extends Context {
[this.selectedPaths], [this.selectedPaths],
() => this.selectedPaths.value.size == 0, () => this.selectedPaths.value.size == 0,
); );
didNotFinishSending: React.State<boolean> = new React.State(true);
cannotPrepareToReceive: React.State<boolean> = React.createProxyState( cannotPrepareToReceive: React.State<boolean> = React.createProxyState(
[this.receivingTransferChannel, this.receivingTransferKey], [this.receivingTransferChannel, this.receivingTransferKey],
() => () =>
@@ -99,6 +82,7 @@ export default class FileTransferViewModel extends Context {
// handlers // handlers
handleReceivedFile = (path: string): void => { handleReceivedFile = (path: string): void => {
this.filePathsReceived.add(path); this.filePathsReceived.add(path);
this.fileTransferProgress.value = this.filePathsReceived.value.size;
}; };
// methods // methods
@@ -229,7 +213,6 @@ export default class FileTransferViewModel extends Context {
initiateTransfer = (): void => { initiateTransfer = (): void => {
this.presentedModal.value = FileTransferModals.TransferDisplay; this.presentedModal.value = FileTransferModals.TransferDisplay;
this.didNotFinishSending.value = true;
this.filePathsSent.clear(); this.filePathsSent.clear();
this.coreViewModel.fileTransferModel.sendFiles( this.coreViewModel.fileTransferModel.sendFiles(
@@ -238,8 +221,6 @@ export default class FileTransferViewModel extends Context {
this.filePathsSent.add(path); this.filePathsSent.add(path);
}, },
); );
this.didNotFinishSending.value = false;
}; };
showTransferDataInputModal = (): void => { showTransferDataInputModal = (): void => {
@@ -287,6 +268,11 @@ export default class FileTransferViewModel extends Context {
constructor(public readonly coreViewModel: CoreViewModel) { constructor(public readonly coreViewModel: CoreViewModel) {
super("file-transfer"); super("file-transfer");
this.coreViewModel.fileTransferModel.transferSizeHandlerManager.setHandler(
"file-transfer-view-model",
(count) => (this.fileTransferTotal.value = count),
);
this.coreViewModel.fileTransferModel.fileHandlerManager.setHandler( this.coreViewModel.fileTransferModel.fileHandlerManager.setHandler(
"file-transfer-view-model", "file-transfer-view-model",
this.handleReceivedFile, this.handleReceivedFile,
+2 -2
View File
@@ -204,7 +204,7 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
resetPinchZoom = (): void => { resetPinchZoom = (): void => {
this.pinchToZoomData.value = { x: 0, y: 0, zoom: 1 }; this.pinchToZoomData.value = { x: 0, y: 0, zoom: 1 };
} };
// load // load
preloadData = (): void => { preloadData = (): void => {
@@ -318,7 +318,7 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
this.searchViewModel.appliedQuery.subscribeSilent(() => { this.searchViewModel.appliedQuery.subscribeSilent(() => {
this.resetPinchZoom(); this.resetPinchZoom();
}) });
// handlers // handlers
boardsAndTasksModel.taskHandlerManager.setHandler( boardsAndTasksModel.taskHandlerManager.setHandler(
+1 -1
View File
@@ -224,7 +224,7 @@ export default class CalendarPageViewModel extends TaskContainingPageViewModel {
handleContextClose = (fromHistoryEvent: boolean): void | boolean => { handleContextClose = (fromHistoryEvent: boolean): void | boolean => {
if (!fromHistoryEvent) return; if (!fromHistoryEvent) return;
return true; return true;
} };
// init // init
constructor( constructor(
+1 -2
View File
@@ -1,10 +1,9 @@
import * as React from "../../react";
import StorageViewModel from "../Global/storageViewModel"; import StorageViewModel from "../Global/storageViewModel";
import SettingsViewModel from "../Global/settingsViewModel"; import SettingsViewModel from "../Global/settingsViewModel";
import FileTransferViewModel from "../Global/fileTransferViewModel"; import FileTransferViewModel from "../Global/fileTransferViewModel";
import CoreViewModel, { Context } from "../Global/coreViewModel"; import CoreViewModel, { Context } from "../Global/coreViewModel";
import ConnectionViewModel from "../Global/connectionViewModel"; import ConnectionViewModel from "../Global/connectionViewModel";
import * as React from "../../react";
import { CommonKeys } from "../../View/keystrokes"; import { CommonKeys } from "../../View/keystrokes";
export default class HomeViewModel extends Context { export default class HomeViewModel extends Context {
+2 -2
View File
@@ -197,14 +197,14 @@ export default class MessagePageViewModel extends Context {
}; };
// context // context
handleContextClose = (fromHistoryEvent: boolean):boolean=> { handleContextClose = (fromHistoryEvent: boolean): boolean | void => {
if (fromHistoryEvent) { if (fromHistoryEvent) {
if (this.replyViewSelectedMessage.value == undefined) return true; if (this.replyViewSelectedMessage.value == undefined) return true;
this.resetReplyView(); this.resetReplyView();
return false; return false;
} }
} };
// init // init
constructor( constructor(
+2 -3
View File
@@ -76,8 +76,7 @@ export default class SettingsPageViewModel extends Context {
randomizeKey = (): void => { randomizeKey = (): void => {
this.encryptionKeyInput.value = random(24); this.encryptionKeyInput.value = random(24);
this.setEncryptionKey(); };
}
applyColor = (): void => { applyColor = (): void => {
this.chatViewModel.setColor(this.color.value); this.chatViewModel.setColor(this.color.value);
@@ -125,7 +124,7 @@ export default class SettingsPageViewModel extends Context {
handleContextClose = (fromHistoryEvent: boolean): void | boolean => { handleContextClose = (fromHistoryEvent: boolean): void | boolean => {
if (!fromHistoryEvent) return; if (!fromHistoryEvent) return;
return true; return true;
} };
// init // init
constructor( constructor(
+1 -1
View File
@@ -170,7 +170,7 @@ export default class TaskPageViewModel extends ContextHost<string> {
handleContextClose = (fromHistoryEvent: boolean): void | boolean => { handleContextClose = (fromHistoryEvent: boolean): void | boolean => {
if (!fromHistoryEvent) return; if (!fromHistoryEvent) return;
return true; return true;
} };
// init // init
constructor( constructor(
+2 -2
View File
@@ -30,7 +30,7 @@ main {
z-index: -1; z-index: -1;
pointer-events: none; pointer-events: none;
opacity: .3; opacity: 0.3;
} }
#background-wrapper > div > div { #background-wrapper > div > div {
@@ -143,6 +143,6 @@ body[theme="dark"] #background-wrapper #grass-2 {
} }
body[keystroke-active] [ctkeystroke]::after { body[keystroke-active] [ctkeystroke]::after {
opacity: .7; opacity: 0.7;
transform: translateY(0px); transform: translateY(0px);
} }