2024-07-27 19:13:59 +02:00
|
|
|
import * as React from "bloatless-react";
|
2026-04-18 11:55:01 +00:00
|
|
|
import CoreViewModel, { Context } from "./coreViewModel";
|
|
|
|
|
import { CommonKeys } from "../../View/keystrokes";
|
2024-07-27 19:13:59 +02:00
|
|
|
import StorageModel, {
|
2026-04-08 14:02:12 +00:00
|
|
|
StorageModelSubPaths,
|
2026-04-01 08:29:39 +00:00
|
|
|
filePaths,
|
2024-07-27 19:13:59 +02:00
|
|
|
} from "../../Model/Global/storageModel";
|
2026-04-18 11:55:01 +00:00
|
|
|
import { TransferData } from "../../Model/Global/fileTransferModel";
|
2024-07-27 19:13:59 +02:00
|
|
|
import ChatModel from "../../Model/Chat/chatModel";
|
2026-04-12 19:20:37 +00:00
|
|
|
|
|
|
|
|
export default class FileTransferViewModel extends Context {
|
2026-04-01 08:29:39 +00:00
|
|
|
// state
|
2026-04-08 14:02:12 +00:00
|
|
|
presentedModal: React.State<FileTransferModals | undefined> =
|
2026-04-01 08:29:39 +00:00
|
|
|
new React.State<any>(undefined);
|
|
|
|
|
|
|
|
|
|
generalFileOptions: React.ListState<FileTransferOption> =
|
|
|
|
|
new React.ListState();
|
|
|
|
|
chatFileOptions: React.ListState<FileTransferOption> =
|
|
|
|
|
new React.ListState();
|
|
|
|
|
selectedPaths: React.ListState<string[]> = new React.ListState();
|
|
|
|
|
|
|
|
|
|
transferChannel: React.State<string> = new React.State("");
|
|
|
|
|
transferKey: React.State<string> = new React.State("");
|
|
|
|
|
|
|
|
|
|
receivingTransferChannel: React.State<string> = new React.State("");
|
|
|
|
|
receivingTransferKey: React.State<string> = new React.State("");
|
|
|
|
|
|
|
|
|
|
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],
|
|
|
|
|
() =>
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.translations.dataTransferModal.filesSentCount(
|
2026-04-01 08:29:39 +00:00
|
|
|
this.filesSentCount.value,
|
|
|
|
|
),
|
2024-07-27 19:13:59 +02:00
|
|
|
);
|
|
|
|
|
|
2026-04-01 08:29:39 +00:00
|
|
|
filePathsReceived: React.ListState<string> = new React.ListState();
|
|
|
|
|
filesReceivedCount: React.State<number> = React.createProxyState(
|
|
|
|
|
[this.filePathsReceived],
|
|
|
|
|
() => this.filePathsReceived.value.size,
|
|
|
|
|
);
|
|
|
|
|
filesReceivedText: React.State<string> = React.createProxyState(
|
|
|
|
|
[this.filesReceivedCount],
|
|
|
|
|
() =>
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.translations.dataTransferModal.filesReceivedCount(
|
2026-04-01 08:29:39 +00:00
|
|
|
this.filesReceivedCount.value,
|
|
|
|
|
),
|
2024-07-28 02:27:09 +02:00
|
|
|
);
|
|
|
|
|
|
2026-08-29 23:51:38 +02:00
|
|
|
exportKey: React.State<string> = new React.State("");
|
|
|
|
|
exportKeyConfirmation: React.State<string> = new React.State("");
|
|
|
|
|
|
2026-08-30 22:24:55 +02:00
|
|
|
importedFile: React.State<File | undefined> = new React.State(undefined);
|
2026-08-30 14:39:36 +02:00
|
|
|
importedFileString: React.State<string> = new React.State("");
|
|
|
|
|
importKey: React.State<string> = new React.State("");
|
|
|
|
|
isImportKeyCorrect: React.State<boolean> = new React.State(true);
|
|
|
|
|
|
2026-04-01 08:29:39 +00:00
|
|
|
// guards
|
|
|
|
|
hasNoPathsSelected: React.State<boolean> = React.createProxyState(
|
|
|
|
|
[this.selectedPaths],
|
|
|
|
|
() => this.selectedPaths.value.size == 0,
|
|
|
|
|
);
|
|
|
|
|
didNotFinishSending: React.State<boolean> = new React.State(true);
|
|
|
|
|
cannotPrepareToReceive: React.State<boolean> = React.createProxyState(
|
|
|
|
|
[this.receivingTransferChannel, this.receivingTransferKey],
|
|
|
|
|
() =>
|
|
|
|
|
this.receivingTransferChannel.value == "" ||
|
|
|
|
|
this.receivingTransferKey.value == "",
|
|
|
|
|
);
|
2026-09-05 02:26:10 +02:00
|
|
|
cannotExitReception: React.State<boolean> = React.createProxyState(
|
|
|
|
|
[this.filePathsReceived],
|
|
|
|
|
() => this.filePathsReceived.value.size > 0,
|
|
|
|
|
);
|
2026-08-30 22:24:55 +02:00
|
|
|
|
2026-08-29 23:51:38 +02:00
|
|
|
cannotExport: React.State<boolean> = React.createProxyState(
|
|
|
|
|
[this.exportKey, this.exportKeyConfirmation],
|
|
|
|
|
() =>
|
2026-08-30 22:24:55 +02:00
|
|
|
this.exportKey.value == "" ||
|
|
|
|
|
this.exportKey.value != this.exportKeyConfirmation.value,
|
2026-08-29 23:51:38 +02:00
|
|
|
);
|
2026-08-30 14:39:36 +02:00
|
|
|
cannotImport: React.State<boolean> = React.createProxyState(
|
|
|
|
|
[this.importedFile],
|
2026-08-30 22:24:55 +02:00
|
|
|
() => this.importedFile.value == undefined,
|
2026-08-30 14:39:36 +02:00
|
|
|
);
|
2026-09-05 02:32:27 +02:00
|
|
|
importDecryptSuccessful: React.State<boolean> = React.createProxyState(
|
2026-08-30 14:39:36 +02:00
|
|
|
[this.importKey],
|
2026-08-30 22:24:55 +02:00
|
|
|
() => this.importKey.value == "",
|
2026-08-30 14:39:36 +02:00
|
|
|
);
|
2026-04-01 08:29:39 +00:00
|
|
|
|
|
|
|
|
// handlers
|
|
|
|
|
handleReceivedFile = (path: string): void => {
|
|
|
|
|
this.filePathsReceived.add(path);
|
|
|
|
|
};
|
2024-07-28 02:27:09 +02:00
|
|
|
|
2026-04-01 08:29:39 +00:00
|
|
|
// methods
|
|
|
|
|
getOptions = (): void => {
|
|
|
|
|
this.generalFileOptions.clear();
|
|
|
|
|
this.chatFileOptions.clear();
|
|
|
|
|
this.selectedPaths.clear();
|
|
|
|
|
|
|
|
|
|
this.generalFileOptions.add(
|
|
|
|
|
{
|
2026-04-09 16:48:22 +00:00
|
|
|
label: this.coreViewModel.translations.dataTransferModal
|
|
|
|
|
.connectionData,
|
2026-04-01 08:29:39 +00:00
|
|
|
path: StorageModel.getPath(
|
2026-04-08 14:02:12 +00:00
|
|
|
StorageModelSubPaths.ConnectionModel,
|
2026-04-01 08:29:39 +00:00
|
|
|
filePaths.connectionModel.previousAddresses,
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
{
|
2026-04-09 16:48:22 +00:00
|
|
|
label: this.coreViewModel.translations.dataTransferModal
|
|
|
|
|
.settingsData,
|
2026-04-01 08:29:39 +00:00
|
|
|
path: StorageModel.getPath(
|
2026-04-08 14:02:12 +00:00
|
|
|
StorageModelSubPaths.SettingsModel,
|
2026-04-01 08:29:39 +00:00
|
|
|
[],
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2026-04-09 16:48:22 +00:00
|
|
|
const chatModels: Set<ChatModel> =
|
|
|
|
|
this.coreViewModel.chatListModel.chatModels;
|
2026-04-01 08:29:39 +00:00
|
|
|
for (const chatModel of chatModels) {
|
|
|
|
|
this.chatFileOptions.add({
|
2026-05-06 16:34:29 +00:00
|
|
|
label: chatModel.unwrappedPrimaryChannel,
|
2026-04-01 08:29:39 +00:00
|
|
|
path: chatModel.getBasePath(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-07-28 03:46:08 +02:00
|
|
|
|
2026-04-01 08:29:39 +00:00
|
|
|
getTransferData = (): void => {
|
|
|
|
|
const transferData: TransferData =
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.fileTransferModel.generateTransferData();
|
2026-04-01 08:29:39 +00:00
|
|
|
this.transferChannel.value = transferData.channel;
|
|
|
|
|
this.transferKey.value = transferData.key;
|
|
|
|
|
};
|
2024-07-28 03:46:08 +02:00
|
|
|
|
2026-08-30 00:15:30 +02:00
|
|
|
downloadFile = async (): Promise<void> => {
|
2026-08-30 22:24:55 +02:00
|
|
|
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();
|
|
|
|
|
};
|
2026-08-30 00:15:30 +02:00
|
|
|
|
2026-08-30 14:39:36 +02:00
|
|
|
updateImportSelection = (): void => {
|
2026-08-30 22:24:55 +02:00
|
|
|
const input = document.getElementById("file-transfer-input");
|
|
|
|
|
if (!input) return;
|
|
|
|
|
if (!(input instanceof HTMLInputElement)) return;
|
2026-08-30 14:39:36 +02:00
|
|
|
|
2026-08-30 22:24:55 +02:00
|
|
|
const file = input.files[0];
|
|
|
|
|
this.importedFile.value = file;
|
|
|
|
|
};
|
2026-08-30 14:39:36 +02:00
|
|
|
|
|
|
|
|
importFile = (): void => {
|
2026-08-30 22:24:55 +02:00
|
|
|
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();
|
|
|
|
|
};
|
|
|
|
|
};
|
2026-08-30 14:39:36 +02:00
|
|
|
|
|
|
|
|
decryptImport = async (): Promise<void> => {
|
2026-08-30 22:24:55 +02:00
|
|
|
const fileString = this.importedFileString.value;
|
|
|
|
|
const key = this.importKey.value;
|
|
|
|
|
if (!fileString || !key) return;
|
|
|
|
|
|
2026-09-05 02:32:27 +02:00
|
|
|
this.importDecryptSuccessful.value = true;
|
2026-08-30 22:24:55 +02:00
|
|
|
try {
|
|
|
|
|
await this.coreViewModel.fileTransferModel.handleBackupFile(
|
|
|
|
|
fileString,
|
|
|
|
|
key,
|
|
|
|
|
);
|
|
|
|
|
window.location.reload();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e);
|
|
|
|
|
this.isImportKeyCorrect.value = false;
|
2026-09-05 02:32:27 +02:00
|
|
|
this.importDecryptSuccessful.value = false;
|
2026-08-30 22:24:55 +02:00
|
|
|
}
|
|
|
|
|
};
|
2026-08-30 14:39:36 +02:00
|
|
|
|
2026-04-01 08:29:39 +00:00
|
|
|
// view
|
|
|
|
|
showDirectionSelectionModal = (): void => {
|
2026-08-30 22:24:55 +02:00
|
|
|
this.coreViewModel.context = this;
|
|
|
|
|
this.presentedModal.value = FileTransferModals.DirectionSelection;
|
|
|
|
|
this.getOptions();
|
2024-07-28 03:46:08 +02:00
|
|
|
};
|
2026-04-01 08:29:39 +00:00
|
|
|
|
|
|
|
|
showFileSelectionModal = (): void => {
|
2026-08-30 22:24:55 +02:00
|
|
|
this.presentedModal.value = FileTransferModals.FileSelection;
|
2026-04-01 08:29:39 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
showTransferDataModal = (): void => {
|
2026-08-30 22:24:55 +02:00
|
|
|
this.presentedModal.value = FileTransferModals.TransferDataDisplay;
|
|
|
|
|
this.getTransferData();
|
|
|
|
|
this.coreViewModel.fileTransferModel.prepareToSend();
|
2026-04-01 08:29:39 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
initiateTransfer = (): void => {
|
2026-08-30 22:24:55 +02:00
|
|
|
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.didNotFinishSending.value = false;
|
2026-04-01 08:29:39 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
showTransferDataInputModal = (): void => {
|
2026-08-30 22:24:55 +02:00
|
|
|
this.presentedModal.value = FileTransferModals.TransferDataInput;
|
2026-04-01 08:29:39 +00:00
|
|
|
};
|
|
|
|
|
|
2026-08-30 00:03:16 +02:00
|
|
|
showExportSelectionModal = (): void => {
|
2026-08-30 22:24:55 +02:00
|
|
|
this.presentedModal.value = FileTransferModals.ExportFileSelection;
|
2026-08-30 00:03:16 +02:00
|
|
|
};
|
|
|
|
|
|
2026-08-29 23:44:00 +02:00
|
|
|
showExportModal = (): void => {
|
2026-08-30 22:24:55 +02:00
|
|
|
this.presentedModal.value = FileTransferModals.Export;
|
2026-08-29 23:44:00 +02:00
|
|
|
};
|
|
|
|
|
|
2026-08-29 23:31:51 +02:00
|
|
|
showImportModal = (): void => {
|
2026-08-30 22:24:55 +02:00
|
|
|
this.presentedModal.value = FileTransferModals.ImportSelection;
|
2026-08-30 14:39:36 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
showImportDecryptDataModal = (): void => {
|
2026-08-30 22:24:55 +02:00
|
|
|
this.presentedModal.value = FileTransferModals.ImportDecryptData;
|
2026-08-29 23:31:51 +02:00
|
|
|
};
|
|
|
|
|
|
2026-04-01 08:29:39 +00:00
|
|
|
prepareReceivingData = (): void => {
|
2026-08-30 22:24:55 +02:00
|
|
|
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);
|
2026-04-01 08:29:39 +00:00
|
|
|
};
|
|
|
|
|
|
2026-04-14 17:19:35 +00:00
|
|
|
// exit
|
|
|
|
|
close = (): void => {
|
2026-08-30 22:24:55 +02:00
|
|
|
this.coreViewModel.closeContext(this.contextId);
|
2026-04-01 08:29:39 +00:00
|
|
|
};
|
|
|
|
|
|
2026-04-14 17:19:35 +00:00
|
|
|
handleContextClose = (): void => {
|
2026-08-30 22:24:55 +02:00
|
|
|
this.presentedModal.value = undefined;
|
2026-04-15 12:47:10 +00:00
|
|
|
};
|
2026-04-14 17:19:35 +00:00
|
|
|
|
2026-04-01 08:29:39 +00:00
|
|
|
// init
|
2026-04-13 10:58:21 +00:00
|
|
|
constructor(public readonly coreViewModel: CoreViewModel) {
|
2026-08-30 22:24:55 +02:00
|
|
|
super("file-transfer");
|
2026-04-12 19:20:37 +00:00
|
|
|
|
2026-08-30 22:24:55 +02:00
|
|
|
this.coreViewModel.fileTransferModel.fileHandlerManager.setHandler(
|
|
|
|
|
"file-transfer-view-model",
|
|
|
|
|
this.handleReceivedFile,
|
|
|
|
|
);
|
2026-04-06 15:50:13 +00:00
|
|
|
|
2026-08-30 22:24:55 +02:00
|
|
|
this.coreViewModel.fileTransferModel.readyToSendHandlerManager.setHandler(
|
|
|
|
|
"file-transfer-view-model",
|
|
|
|
|
() => this.initiateTransfer(),
|
|
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
|
2026-08-30 22:24:55 +02:00
|
|
|
// keystrokes
|
|
|
|
|
this.registerKeyStroke(CommonKeys.CloseOrCancel, this.close);
|
2026-04-01 08:29:39 +00:00
|
|
|
}
|
2024-07-27 19:13:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface FileTransferOption {
|
2026-04-01 08:29:39 +00:00
|
|
|
label: string;
|
|
|
|
|
path: string[];
|
2024-07-27 19:13:59 +02:00
|
|
|
}
|
2024-07-27 23:45:36 +02:00
|
|
|
|
2026-04-08 14:02:12 +00:00
|
|
|
export enum FileTransferModals {
|
2026-04-01 08:29:39 +00:00
|
|
|
DirectionSelection,
|
2024-07-27 23:45:36 +02:00
|
|
|
|
2026-08-30 22:24:55 +02:00
|
|
|
// sending
|
|
|
|
|
FileSelection,
|
|
|
|
|
TransferDataDisplay,
|
|
|
|
|
TransferDisplay,
|
2024-07-27 23:45:36 +02:00
|
|
|
|
2026-08-30 22:24:55 +02:00
|
|
|
// receiving
|
|
|
|
|
TransferDataInput,
|
|
|
|
|
ReceptionDisplay,
|
2026-08-29 23:31:51 +02:00
|
|
|
|
2026-08-30 22:24:55 +02:00
|
|
|
// export
|
|
|
|
|
ExportFileSelection,
|
|
|
|
|
Export,
|
|
|
|
|
ImportSelection,
|
|
|
|
|
ImportDecryptData,
|
2024-07-28 00:02:17 +02:00
|
|
|
}
|