implement auto reconnect

This commit is contained in:
marlon-erler
2026-04-01 08:29:39 +00:00
parent c52d7b7685
commit 9d400a9929
82 changed files with 8340 additions and 7641 deletions
+81 -80
View File
@@ -4,89 +4,90 @@ import ConnectionModel from "../../Model/Global/connectionModel";
import CoreViewModel from "./coreViewModel";
export default class ConnectionViewModel {
connectionModel: ConnectionModel;
connectionModel: ConnectionModel;
// state
serverAddressInput: React.State<string> = new React.State("");
isConnected: React.State<boolean> = new React.State(false);
// state
serverAddressInput: React.State<string> = new React.State("");
isConnected: React.State<boolean> = new React.State(false);
isShowingConnectionModal: React.State<boolean> = new React.State(false);
isShowingConnectionModal: React.State<boolean> = new React.State(false);
previousAddresses: React.ListState<string> = new React.ListState();
previousAddresses: React.ListState<string> = new React.ListState();
// guards
cannotConnect: React.State<boolean> = React.createProxyState(
[this.serverAddressInput, this.isConnected],
() =>
(this.isConnected.value == true &&
this.serverAddressInput.value == this.connectionModel.address) ||
this.serverAddressInput.value == ""
);
cannotDisonnect: React.State<boolean> = React.createProxyState(
[this.isConnected],
() => this.isConnected.value == false
);
hasNoPreviousConnections: React.State<boolean> = React.createProxyState(
[this.previousAddresses],
() => this.previousAddresses.value.size == 0
);
// handlers
connectionChangeHandler = (): void => {
this.isConnected.value = this.connectionModel.isConnected;
if (this.connectionModel.isConnected == false) return;
if (this.connectionModel.address == undefined) return;
this.serverAddressInput.value = this.connectionModel.address;
if (!this.previousAddresses.value.has(this.connectionModel.address)) {
this.previousAddresses.add(this.connectionModel.address);
}
};
// methods
connect = (): void => {
this.connectToAddress(this.serverAddressInput.value);
};
connectToAddress = (address: string): void => {
this.connectionModel.connect(address);
};
disconnect = (): void => {
this.connectionModel.disconnect();
};
removePreviousAddress = (address: string): void => {
this.connectionModel.removeAddress(address);
this.updatePreviousAddresses();
};
// view
showConnectionModal = (): void => {
this.isShowingConnectionModal.value = true;
};
hideConnectionModal = (): void => {
this.isShowingConnectionModal.value = false;
};
updatePreviousAddresses = (): void => {
this.previousAddresses.clear();
this.previousAddresses.add(...this.connectionModel.addresses);
};
// init
constructor(
public coreViewModel: CoreViewModel,
connectionModel: ConnectionModel
) {
this.connectionModel = connectionModel;
this.updatePreviousAddresses();
connectionModel.connectionChangeHandlerManager.addHandler(
this.connectionChangeHandler
// guards
cannotConnect: React.State<boolean> = React.createProxyState(
[this.serverAddressInput, this.isConnected],
() =>
(this.isConnected.value == true &&
this.serverAddressInput.value ==
this.connectionModel.address) ||
this.serverAddressInput.value == "",
);
}
cannotDisonnect: React.State<boolean> = React.createProxyState(
[this.isConnected],
() => this.isConnected.value == false,
);
hasNoPreviousConnections: React.State<boolean> = React.createProxyState(
[this.previousAddresses],
() => this.previousAddresses.value.size == 0,
);
// handlers
connectionChangeHandler = (): void => {
this.isConnected.value = this.connectionModel.isConnected;
if (this.connectionModel.isConnected == false) return;
if (this.connectionModel.address == undefined) return;
this.serverAddressInput.value = this.connectionModel.address;
if (!this.previousAddresses.value.has(this.connectionModel.address)) {
this.previousAddresses.add(this.connectionModel.address);
}
};
// methods
connect = (): void => {
this.connectToAddress(this.serverAddressInput.value);
};
connectToAddress = (address: string): void => {
this.connectionModel.connect(address);
};
disconnect = (): void => {
this.connectionModel.disconnect();
};
removePreviousAddress = (address: string): void => {
this.connectionModel.removeAddress(address);
this.updatePreviousAddresses();
};
// view
showConnectionModal = (): void => {
this.isShowingConnectionModal.value = true;
};
hideConnectionModal = (): void => {
this.isShowingConnectionModal.value = false;
};
updatePreviousAddresses = (): void => {
this.previousAddresses.clear();
this.previousAddresses.add(...this.connectionModel.addresses);
};
// init
constructor(
public coreViewModel: CoreViewModel,
connectionModel: ConnectionModel,
) {
this.connectionModel = connectionModel;
this.updatePreviousAddresses();
connectionModel.connectionChangeHandlerManager.addHandler(
this.connectionChangeHandler,
);
}
}
+8 -8
View File
@@ -1,13 +1,13 @@
import * as React from "bloatless-react";
export default class CoreViewModel {
// DRAG & DROP
draggedObject: React.State<any> = new React.State<any>(undefined);
// DRAG & DROP
draggedObject: React.State<any> = new React.State<any>(undefined);
// SUGGESTIONS
// boards & tasks
boardSearchSuggestions: React.ListState<string> = new React.ListState();
taskCategorySuggestions: React.ListState<string> = new React.ListState();
taskStatusSuggestions: React.ListState<string> = new React.ListState();
// SUGGESTIONS
// boards & tasks
boardSearchSuggestions: React.ListState<string> = new React.ListState();
taskCategorySuggestions: React.ListState<string> = new React.ListState();
taskStatusSuggestions: React.ListState<string> = new React.ListState();
}
+163 -157
View File
@@ -1,11 +1,11 @@
import * as React from "bloatless-react";
import FileTransferModel, {
TransferData,
TransferData,
} from "../../Model/Global/fileTransferModel";
import StorageModel, {
StorageModelSubPath,
filePaths,
StorageModelSubPath,
filePaths,
} from "../../Model/Global/storageModel";
import ChatListModel from "../../Model/Chat/chatListModel";
@@ -13,180 +13,186 @@ import ChatModel from "../../Model/Chat/chatModel";
import { translations } from "../../View/translations";
export default class FileTransferViewModel {
fileTransferModel: FileTransferModel;
chatListModel: ChatListModel;
fileTransferModel: FileTransferModel;
chatListModel: ChatListModel;
// state
presentedModal: React.State<FileTransferModal | undefined> =
new React.State<any>(undefined);
// state
presentedModal: React.State<FileTransferModal | undefined> =
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();
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("");
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("");
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],
() =>
translations.dataTransferModal.filesSentCount(this.filesSentCount.value)
);
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],
() =>
translations.dataTransferModal.filesReceivedCount(
this.filesReceivedCount.value
)
);
// 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 == ""
);
// handlers
handleReceivedFile = (path: string): void => {
this.filePathsReceived.add(path);
};
// methods
getOptions = (): void => {
this.generalFileOptions.clear();
this.chatFileOptions.clear();
this.selectedPaths.clear();
this.generalFileOptions.add(
{
label: translations.dataTransferModal.connectionData,
path: StorageModel.getPath(
StorageModelSubPath.ConnectionModel,
filePaths.connectionModel.previousAddresses
),
},
{
label: translations.dataTransferModal.settingsData,
path: StorageModel.getPath(StorageModelSubPath.SettingsModel, []),
}
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],
() =>
translations.dataTransferModal.filesSentCount(
this.filesSentCount.value,
),
);
const chatModels: Set<ChatModel> = this.chatListModel.chatModels;
for (const chatModel of chatModels) {
this.chatFileOptions.add({
label: chatModel.info.primaryChannel,
path: chatModel.getBasePath(),
});
}
};
getTransferData = (): void => {
const transferData: TransferData =
this.fileTransferModel.generateTransferData();
this.transferChannel.value = transferData.channel;
this.transferKey.value = transferData.key;
};
// view
showDirectionSelectionModal = (): void => {
this.presentedModal.value = FileTransferModal.DirectionSelection;
this.getOptions();
};
showFileSelectionModal = (): void => {
this.presentedModal.value = FileTransferModal.FileSelection;
};
showTransferDataModal = (): void => {
this.presentedModal.value = FileTransferModal.TransferDataDisplay;
this.getTransferData();
};
initiateTransfer = (): void => {
this.presentedModal.value = FileTransferModal.TransferDisplay;
this.didNotFinishSending.value = true;
this.filePathsSent.clear();
this.fileTransferModel.sendFiles(
this.selectedPaths.value.values(),
(path: string) => {
console.log(path);
this.filePathsSent.add(path);
}
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],
() =>
translations.dataTransferModal.filesReceivedCount(
this.filesReceivedCount.value,
),
);
this.didNotFinishSending.value = false;
};
// 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 == "",
);
showTransferDataInputModal = (): void => {
this.presentedModal.value = FileTransferModal.TransferDataInput;
};
prepareReceivingData = (): void => {
this.presentedModal.value = FileTransferModal.ReceptionDisplay;
this.filePathsReceived.clear();
const transferData: TransferData = {
channel: this.receivingTransferChannel.value,
key: this.receivingTransferKey.value,
// handlers
handleReceivedFile = (path: string): void => {
this.filePathsReceived.add(path);
};
this.fileTransferModel.prepareToReceive(transferData);
};
hideModal = (): void => {
this.presentedModal.value = undefined;
};
// methods
getOptions = (): void => {
this.generalFileOptions.clear();
this.chatFileOptions.clear();
this.selectedPaths.clear();
// init
constructor(
fileTransferModel: FileTransferModel,
chatListModel: ChatListModel
) {
this.fileTransferModel = fileTransferModel;
this.chatListModel = chatListModel;
this.generalFileOptions.add(
{
label: translations.dataTransferModal.connectionData,
path: StorageModel.getPath(
StorageModelSubPath.ConnectionModel,
filePaths.connectionModel.previousAddresses,
),
},
{
label: translations.dataTransferModal.settingsData,
path: StorageModel.getPath(
StorageModelSubPath.SettingsModel,
[],
),
},
);
this.fileTransferModel.fileHandlerManager.addHandler(
this.handleReceivedFile
);
}
const chatModels: Set<ChatModel> = this.chatListModel.chatModels;
for (const chatModel of chatModels) {
this.chatFileOptions.add({
label: chatModel.info.primaryChannel,
path: chatModel.getBasePath(),
});
}
};
getTransferData = (): void => {
const transferData: TransferData =
this.fileTransferModel.generateTransferData();
this.transferChannel.value = transferData.channel;
this.transferKey.value = transferData.key;
};
// view
showDirectionSelectionModal = (): void => {
this.presentedModal.value = FileTransferModal.DirectionSelection;
this.getOptions();
};
showFileSelectionModal = (): void => {
this.presentedModal.value = FileTransferModal.FileSelection;
};
showTransferDataModal = (): void => {
this.presentedModal.value = FileTransferModal.TransferDataDisplay;
this.getTransferData();
};
initiateTransfer = (): void => {
this.presentedModal.value = FileTransferModal.TransferDisplay;
this.didNotFinishSending.value = true;
this.filePathsSent.clear();
this.fileTransferModel.sendFiles(
this.selectedPaths.value.values(),
(path: string) => {
console.log(path);
this.filePathsSent.add(path);
},
);
this.didNotFinishSending.value = false;
};
showTransferDataInputModal = (): void => {
this.presentedModal.value = FileTransferModal.TransferDataInput;
};
prepareReceivingData = (): void => {
this.presentedModal.value = FileTransferModal.ReceptionDisplay;
this.filePathsReceived.clear();
const transferData: TransferData = {
channel: this.receivingTransferChannel.value,
key: this.receivingTransferKey.value,
};
this.fileTransferModel.prepareToReceive(transferData);
};
hideModal = (): void => {
this.presentedModal.value = undefined;
};
// init
constructor(
fileTransferModel: FileTransferModel,
chatListModel: ChatListModel,
) {
this.fileTransferModel = fileTransferModel;
this.chatListModel = chatListModel;
this.fileTransferModel.fileHandlerManager.addHandler(
this.handleReceivedFile,
);
}
}
export interface FileTransferOption {
label: string;
path: string[];
label: string;
path: string[];
}
export enum FileTransferModal {
DirectionSelection,
DirectionSelection,
// sending
FileSelection,
TransferDataDisplay,
TransferDisplay,
// sending
FileSelection,
TransferDataDisplay,
TransferDisplay,
// receiving
TransferDataInput,
ReceptionDisplay,
// receiving
TransferDataInput,
ReceptionDisplay,
}
+33 -33
View File
@@ -4,44 +4,44 @@ import CoreViewModel from "./coreViewModel";
import SettingsModel from "../../Model/Global/settingsModel";
export default class SettingsViewModel {
settingsModel: SettingsModel;
settingsModel: SettingsModel;
// state
username: React.State<string> = new React.State("");
usernameInput: React.State<string> = new React.State("");
firstDayOfWeekInput: React.State<string> = new React.State("0");
// state
username: React.State<string> = new React.State("");
usernameInput: React.State<string> = new React.State("");
firstDayOfWeekInput: React.State<string> = new React.State("0");
// guards
cannotSetName: React.State<boolean> = React.createProxyState(
[this.usernameInput],
() =>
this.usernameInput.value == "" ||
this.usernameInput.value == this.settingsModel.username
);
// guards
cannotSetName: React.State<boolean> = React.createProxyState(
[this.usernameInput],
() =>
this.usernameInput.value == "" ||
this.usernameInput.value == this.settingsModel.username,
);
// methods
setName = (): void => {
this.settingsModel.setName(this.usernameInput.value);
this.username.value = this.settingsModel.username;
this.usernameInput.callSubscriptions();
};
// methods
setName = (): void => {
this.settingsModel.setName(this.usernameInput.value);
this.username.value = this.settingsModel.username;
this.usernameInput.callSubscriptions();
};
setFirstDayofWeek = (): void => {
this.settingsModel.setFirstDayOfWeek(this.firstDayOfWeekInput.value);
};
setFirstDayofWeek = (): void => {
this.settingsModel.setFirstDayOfWeek(this.firstDayOfWeekInput.value);
};
// init
constructor(
public coreViewModel: CoreViewModel,
settingsModel: SettingsModel
) {
this.settingsModel = settingsModel;
// init
constructor(
public coreViewModel: CoreViewModel,
settingsModel: SettingsModel,
) {
this.settingsModel = settingsModel;
this.username.value = settingsModel.username;
this.usernameInput.value = settingsModel.username;
this.firstDayOfWeekInput.value = settingsModel.firstDayOfWeek;
this.username.value = settingsModel.username;
this.usernameInput.value = settingsModel.username;
this.firstDayOfWeekInput.value = settingsModel.firstDayOfWeek;
// subscriptions
this.firstDayOfWeekInput.subscribe(this.setFirstDayofWeek);
}
// subscriptions
this.firstDayOfWeekInput.subscribe(this.setFirstDayofWeek);
}
}
+63 -52
View File
@@ -1,70 +1,81 @@
import * as React from "bloatless-react";
import StorageModel, {
PATH_COMPONENT_SEPARATOR,
PATH_COMPONENT_SEPARATOR,
} from "../../Model/Global/storageModel";
import CoreViewModel from "./coreViewModel";
import { translations } from "../../View/translations";
export default class StorageViewModel {
storageModel: StorageModel;
storageModel: StorageModel;
// state
isShowingStorageModal: React.State<boolean> = new React.State(false);
selectedPath: React.State<string> = new React.State(PATH_COMPONENT_SEPARATOR);
didMakeChanges: React.State<boolean> = new React.State(false);
selectedFileName: React.State<string>;
selectedFileContent: React.State<string>;
lastDeletedItemPath: React.State<string> = new React.State("");
// methods
getSelectedItemContent = (): string => {
const path = StorageModel.stringToPathComponents(this.selectedPath.value);
const content = this.storageModel.read(path);
return (
(content ?? translations.storage.notAFile) ||
translations.storage.contentEmpty
// state
isShowingStorageModal: React.State<boolean> = new React.State(false);
selectedPath: React.State<string> = new React.State(
PATH_COMPONENT_SEPARATOR,
);
};
didMakeChanges: React.State<boolean> = new React.State(false);
deleteSelectedItem = (): void => {
const path = StorageModel.stringToPathComponents(this.selectedPath.value);
this.lastDeletedItemPath.value = this.selectedPath.value;
selectedFileName: React.State<string>;
selectedFileContent: React.State<string>;
this.storageModel.removeRecursively(path);
this.didMakeChanges.value = true;
};
lastDeletedItemPath: React.State<string> = new React.State("");
removeJunk = (): void => {
this.storageModel.removeJunk();
this.selectedPath.value = PATH_COMPONENT_SEPARATOR;
};
// methods
getSelectedItemContent = (): string => {
const path = StorageModel.stringToPathComponents(
this.selectedPath.value,
);
const content = this.storageModel.read(path);
return (
(content ?? translations.storage.notAFile) ||
translations.storage.contentEmpty
);
};
// view
showStorageModal = (): void => {
this.isShowingStorageModal.value = true;
};
deleteSelectedItem = (): void => {
const path = StorageModel.stringToPathComponents(
this.selectedPath.value,
);
this.lastDeletedItemPath.value = this.selectedPath.value;
hideStorageModal = (): void => {
if (this.didMakeChanges.value == true) {
window.location.reload();
return;
this.storageModel.removeRecursively(path);
this.didMakeChanges.value = true;
};
removeJunk = (): void => {
this.storageModel.removeJunk();
this.selectedPath.value = PATH_COMPONENT_SEPARATOR;
};
// view
showStorageModal = (): void => {
this.isShowingStorageModal.value = true;
};
hideStorageModal = (): void => {
if (this.didMakeChanges.value == true) {
window.location.reload();
return;
}
this.isShowingStorageModal.value = false;
};
// init
constructor(
public coreViewModel: CoreViewModel,
storageModel: StorageModel,
) {
this.storageModel = storageModel;
this.selectedFileName = React.createProxyState(
[this.selectedPath],
() => StorageModel.getFileNameFromString(this.selectedPath.value),
);
this.selectedFileContent = React.createProxyState(
[this.selectedPath],
() => this.getSelectedItemContent(),
);
}
this.isShowingStorageModal.value = false;
};
// init
constructor(public coreViewModel: CoreViewModel, storageModel: StorageModel) {
this.storageModel = storageModel;
this.selectedFileName = React.createProxyState([this.selectedPath], () =>
StorageModel.getFileNameFromString(this.selectedPath.value)
);
this.selectedFileContent = React.createProxyState([this.selectedPath], () =>
this.getSelectedItemContent()
);
}
}