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
+88 -87
View File
@@ -9,98 +9,99 @@ import SettingsViewModel from "../Global/settingsViewModel";
import StorageModel from "../../Model/Global/storageModel";
export default class ChatListViewModel {
storageModel: StorageModel;
chatListModel: ChatListModel;
settingsViewModel: SettingsViewModel;
storageModel: StorageModel;
chatListModel: ChatListModel;
settingsViewModel: SettingsViewModel;
// data
chatIndexManager: IndexManager<ChatViewModel> = new IndexManager(
(chatViewModel: ChatViewModel) =>
chatViewModel.settingsPageViewModel.primaryChannel.value
);
// state
newChatPrimaryChannel: React.State<string> = new React.State("");
chatViewModels: React.ListState<ChatViewModel> = new React.ListState();
selectedChat: React.State<ChatViewModel | undefined> = new React.State<any>(
undefined
);
// guards
cannotCreateChat = React.createProxyState(
[this.newChatPrimaryChannel],
() => this.newChatPrimaryChannel.value == ""
);
// methods
createChat = (): void => {
const chatModel: ChatModel = this.chatListModel.createChat(
this.newChatPrimaryChannel.value
// data
chatIndexManager: IndexManager<ChatViewModel> = new IndexManager(
(chatViewModel: ChatViewModel) =>
chatViewModel.settingsPageViewModel.primaryChannel.value,
);
this.newChatPrimaryChannel.value = "";
const chatViewModel: ChatViewModel = this.createChatViewModel(chatModel);
this.trackChat(chatViewModel);
this.updateIndices();
};
// state
newChatPrimaryChannel: React.State<string> = new React.State("");
chatViewModels: React.ListState<ChatViewModel> = new React.ListState();
trackChat = (chatViewModel: ChatViewModel): void => {
this.chatViewModels.add(chatViewModel);
};
untrackChat = (chatViewModel: ChatViewModel): void => {
this.chatListModel.untrackChat(chatViewModel.chatModel);
this.chatViewModels.remove(chatViewModel);
};
createChatViewModel = (chatModel: ChatModel): ChatViewModel => {
return new ChatViewModel(
this.coreViewModel,
this.storageModel,
chatModel,
this.settingsViewModel,
this
selectedChat: React.State<ChatViewModel | undefined> = new React.State<any>(
undefined,
);
};
updateIndices = (): void => {
this.chatIndexManager.update([...this.chatViewModels.value.values()]);
for (const chatViewModel of this.chatViewModels.value) {
chatViewModel.updateIndex();
// guards
cannotCreateChat = React.createProxyState(
[this.newChatPrimaryChannel],
() => this.newChatPrimaryChannel.value == "",
);
// methods
createChat = (): void => {
const chatModel: ChatModel = this.chatListModel.createChat(
this.newChatPrimaryChannel.value,
);
this.newChatPrimaryChannel.value = "";
const chatViewModel: ChatViewModel =
this.createChatViewModel(chatModel);
this.trackChat(chatViewModel);
this.updateIndices();
};
trackChat = (chatViewModel: ChatViewModel): void => {
this.chatViewModels.add(chatViewModel);
};
untrackChat = (chatViewModel: ChatViewModel): void => {
this.chatListModel.untrackChat(chatViewModel.chatModel);
this.chatViewModels.remove(chatViewModel);
};
createChatViewModel = (chatModel: ChatModel): ChatViewModel => {
return new ChatViewModel(
this.coreViewModel,
this.storageModel,
chatModel,
this.settingsViewModel,
this,
);
};
updateIndices = (): void => {
this.chatIndexManager.update([...this.chatViewModels.value.values()]);
for (const chatViewModel of this.chatViewModels.value) {
chatViewModel.updateIndex();
}
};
// view
openChat = (chatViewModel: ChatViewModel): void => {
this.selectedChat.value = chatViewModel;
};
closeChat = (): void => {
this.selectedChat.value = undefined;
};
// load
loadChats = (): void => {
this.chatViewModels.clear();
for (const chatModel of this.chatListModel.chatModels.values()) {
const chatViewModel = this.createChatViewModel(chatModel);
this.trackChat(chatViewModel);
}
this.updateIndices();
};
// init
constructor(
public coreViewModel: CoreViewModel,
storageModel: StorageModel,
chatListModel: ChatListModel,
settingsViewModel: SettingsViewModel,
) {
this.storageModel = storageModel;
this.chatListModel = chatListModel;
this.settingsViewModel = settingsViewModel;
this.loadChats();
}
};
// view
openChat = (chatViewModel: ChatViewModel): void => {
this.selectedChat.value = chatViewModel;
};
closeChat = (): void => {
this.selectedChat.value = undefined;
};
// load
loadChats = (): void => {
this.chatViewModels.clear();
for (const chatModel of this.chatListModel.chatModels.values()) {
const chatViewModel = this.createChatViewModel(chatModel);
this.trackChat(chatViewModel);
}
this.updateIndices();
};
// init
constructor(
public coreViewModel: CoreViewModel,
storageModel: StorageModel,
chatListModel: ChatListModel,
settingsViewModel: SettingsViewModel
) {
this.storageModel = storageModel;
this.chatListModel = chatListModel;
this.settingsViewModel = settingsViewModel;
this.loadChats();
}
}
+50 -50
View File
@@ -6,62 +6,62 @@ import CoreViewModel from "../Global/coreViewModel";
import MessagePageViewModel from "../Pages/messagePageViewModel";
export default class ChatMessageViewModel {
messagePageViewModel: MessagePageViewModel;
messagePageViewModel: MessagePageViewModel;
// data
chatMessage: ChatMessage;
channel: string;
sender: string;
dateSent: string;
body: React.State<string> = new React.State("");
status: React.State<ChatMessageStatus | any> = new React.State<any>(
undefined
);
sentByUser: boolean;
// data
chatMessage: ChatMessage;
channel: string;
sender: string;
dateSent: string;
body: React.State<string> = new React.State("");
status: React.State<ChatMessageStatus | any> = new React.State<any>(
undefined,
);
sentByUser: boolean;
// state
isPresentingInfoModal: React.State<boolean> = new React.State(false);
// state
isPresentingInfoModal: React.State<boolean> = new React.State(false);
// methods
copyMessage = (): void => {
navigator.clipboard.writeText(this.body.value);
};
resendMessage = (): void => {
this.messagePageViewModel.sendMessageFromBody(this.body.value);
};
decryptMessage = (): void => {
this.messagePageViewModel.decryptMessage(this);
};
// methods
copyMessage = (): void => {
navigator.clipboard.writeText(this.body.value);
};
resendMessage = (): void => {
this.messagePageViewModel.sendMessageFromBody(this.body.value);
};
decryptMessage = (): void => {
this.messagePageViewModel.decryptMessage(this);
};
// view
showInfoModal = (): void => {
this.isPresentingInfoModal.value = true;
};
// view
showInfoModal = (): void => {
this.isPresentingInfoModal.value = true;
};
hideInfoModal = (): void => {
this.isPresentingInfoModal.value = false;
};
hideInfoModal = (): void => {
this.isPresentingInfoModal.value = false;
};
// load
loadData = (): void => {
this.channel = this.chatMessage.channel;
this.sender = this.chatMessage.sender;
this.dateSent = new Date(this.chatMessage.dateSent).toLocaleString();
this.body.value = this.chatMessage.body;
this.status.value = this.chatMessage.status;
};
// load
loadData = (): void => {
this.channel = this.chatMessage.channel;
this.sender = this.chatMessage.sender;
this.dateSent = new Date(this.chatMessage.dateSent).toLocaleString();
this.body.value = this.chatMessage.body;
this.status.value = this.chatMessage.status;
};
// init
constructor(
public coreViewModel: CoreViewModel,
messagePageViewModel: MessagePageViewModel,
chatMessage: ChatMessage,
sentByUser: boolean
) {
this.messagePageViewModel = messagePageViewModel;
// init
constructor(
public coreViewModel: CoreViewModel,
messagePageViewModel: MessagePageViewModel,
chatMessage: ChatMessage,
sentByUser: boolean,
) {
this.messagePageViewModel = messagePageViewModel;
this.chatMessage = chatMessage;
this.sentByUser = sentByUser;
this.loadData();
}
this.chatMessage = chatMessage;
this.sentByUser = sentByUser;
this.loadData();
}
}
+133 -133
View File
@@ -1,12 +1,12 @@
import * as React from "bloatless-react";
import CalendarPageViewModel, {
CALENDAR_EVENT_BOARD_ID,
CALENDAR_EVENT_BOARD_ID,
} from "../Pages/calendarPageViewModel";
import ChatModel, { ChatMessage } from "../../Model/Chat/chatModel";
import StorageModel, {
StorageModelSubPath,
filePaths,
StorageModelSubPath,
filePaths,
} from "../../Model/Global/storageModel";
import ChatListViewModel from "./chatListViewModel";
@@ -20,145 +20,145 @@ import TaskPageViewModel from "../Pages/taskPageViewModel";
import { translations } from "../../View/translations";
export default class ChatViewModel {
chatModel: ChatModel;
storageModel: StorageModel;
settingsViewModel: SettingsViewModel;
chatListViewModel: ChatListViewModel;
chatModel: ChatModel;
storageModel: StorageModel;
settingsViewModel: SettingsViewModel;
chatListViewModel: ChatListViewModel;
calendarViewModel: CalendarPageViewModel;
taskPageViewModel: TaskPageViewModel;
messagePageViewModel: MessagePageViewModel;
settingsPageViewModel: SettingsPageViewModel;
calendarViewModel: CalendarPageViewModel;
taskPageViewModel: TaskPageViewModel;
messagePageViewModel: MessagePageViewModel;
settingsPageViewModel: SettingsPageViewModel;
// state
displayedColor: React.State<Color> = new React.State<any>(Color.Standard);
selectedPage: React.State<ChatPageType> = new React.State<any>(
ChatPageType.Messages
);
index: React.State<number> = new React.State(0);
hasUnreadMessages: React.State<boolean> = new React.State(false);
taskBoardSuggestions: React.MapState<Entry> = new React.MapState();
// view
open = (): void => {
this.chatListViewModel.openChat(this);
this.markRead();
};
close = (): void => {
this.chatListViewModel.closeChat();
};
closeSubPages = (): void => {};
setColor = (color: Color): void => {
this.setDisplayedColor(color);
this.chatModel.setColor(color);
};
setDisplayedColor = (color: Color): void => {
this.displayedColor.value = color;
};
resetColor = (): void => {
this.displayedColor.value = this.settingsPageViewModel.color.value;
};
updateIndex = (): void => {
const index: number =
this.chatListViewModel.chatIndexManager.getIndex(this);
this.index.value = index;
};
markRead = (): void => {
this.hasUnreadMessages.value = false;
this.chatModel.markRead();
};
// load
loadPageSelection = (): void => {
const path: string[] = StorageModel.getPath(
StorageModelSubPath.Chat,
filePaths.chat.lastUsedPage(this.chatModel.id)
);
const lastUsedPage: string | null = this.storageModel.read(path);
if (lastUsedPage != null) {
this.selectedPage.value = lastUsedPage as any;
}
this.selectedPage.subscribeSilent((newPage) => {
this.storageModel.write(path, newPage);
this.resetColor();
});
};
loadInfo = (): void => {
this.hasUnreadMessages.value = this.chatModel.info.hasUnreadMessages;
this.taskBoardSuggestions.set(CALENDAR_EVENT_BOARD_ID, [
CALENDAR_EVENT_BOARD_ID,
translations.chatPage.calendar.eventsBoard,
]);
};
// init
constructor(
public coreViewModel: CoreViewModel,
storageModel: StorageModel,
chatModel: ChatModel,
settingsViewModel: SettingsViewModel,
chatListViewModel: ChatListViewModel
) {
// models
this.storageModel = storageModel;
this.chatModel = chatModel;
this.settingsViewModel = settingsViewModel;
this.chatListViewModel = chatListViewModel;
// page viewModels
this.calendarViewModel = new CalendarPageViewModel(
coreViewModel,
this,
this.storageModel,
this.chatModel.fileModel.boardsAndTasksModel.calendarModel,
this.chatModel.fileModel.boardsAndTasksModel
);
this.taskPageViewModel = new TaskPageViewModel(
this.coreViewModel,
this,
this.storageModel,
this.chatModel.fileModel.boardsAndTasksModel
);
this.messagePageViewModel = new MessagePageViewModel(
this.coreViewModel,
this
);
this.settingsPageViewModel = new SettingsPageViewModel(
this.coreViewModel,
this
// state
displayedColor: React.State<Color> = new React.State<any>(Color.Standard);
selectedPage: React.State<ChatPageType> = new React.State<any>(
ChatPageType.Messages,
);
// handlers
chatModel.chatMessageHandlerManager.addHandler(
(chatMessage: ChatMessage) => {
this.messagePageViewModel.showChatMessage(chatMessage);
index: React.State<number> = new React.State(0);
hasUnreadMessages: React.State<boolean> = new React.State(false);
taskBoardSuggestions: React.MapState<Entry> = new React.MapState();
// view
open = (): void => {
this.chatListViewModel.openChat(this);
this.markRead();
}
);
};
close = (): void => {
this.chatListViewModel.closeChat();
};
closeSubPages = (): void => {};
setColor = (color: Color): void => {
this.setDisplayedColor(color);
this.chatModel.setColor(color);
};
setDisplayedColor = (color: Color): void => {
this.displayedColor.value = color;
};
resetColor = (): void => {
this.displayedColor.value = this.settingsPageViewModel.color.value;
};
updateIndex = (): void => {
const index: number =
this.chatListViewModel.chatIndexManager.getIndex(this);
this.index.value = index;
};
markRead = (): void => {
this.hasUnreadMessages.value = false;
this.chatModel.markRead();
};
// load
this.loadPageSelection();
this.resetColor();
this.loadInfo();
}
loadPageSelection = (): void => {
const path: string[] = StorageModel.getPath(
StorageModelSubPath.Chat,
filePaths.chat.lastUsedPage(this.chatModel.id),
);
const lastUsedPage: string | null = this.storageModel.read(path);
if (lastUsedPage != null) {
this.selectedPage.value = lastUsedPage as any;
}
this.selectedPage.subscribeSilent((newPage) => {
this.storageModel.write(path, newPage);
this.resetColor();
});
};
loadInfo = (): void => {
this.hasUnreadMessages.value = this.chatModel.info.hasUnreadMessages;
this.taskBoardSuggestions.set(CALENDAR_EVENT_BOARD_ID, [
CALENDAR_EVENT_BOARD_ID,
translations.chatPage.calendar.eventsBoard,
]);
};
// init
constructor(
public coreViewModel: CoreViewModel,
storageModel: StorageModel,
chatModel: ChatModel,
settingsViewModel: SettingsViewModel,
chatListViewModel: ChatListViewModel,
) {
// models
this.storageModel = storageModel;
this.chatModel = chatModel;
this.settingsViewModel = settingsViewModel;
this.chatListViewModel = chatListViewModel;
// page viewModels
this.calendarViewModel = new CalendarPageViewModel(
coreViewModel,
this,
this.storageModel,
this.chatModel.fileModel.boardsAndTasksModel.calendarModel,
this.chatModel.fileModel.boardsAndTasksModel,
);
this.taskPageViewModel = new TaskPageViewModel(
this.coreViewModel,
this,
this.storageModel,
this.chatModel.fileModel.boardsAndTasksModel,
);
this.messagePageViewModel = new MessagePageViewModel(
this.coreViewModel,
this,
);
this.settingsPageViewModel = new SettingsPageViewModel(
this.coreViewModel,
this,
);
// handlers
chatModel.chatMessageHandlerManager.addHandler(
(chatMessage: ChatMessage) => {
this.messagePageViewModel.showChatMessage(chatMessage);
this.markRead();
},
);
// load
this.loadPageSelection();
this.resetColor();
this.loadInfo();
}
}
// types
export enum ChatPageType {
Settings = "settings",
Messages = "messages",
Tasks = "tasks",
Calendar = "calendar",
Settings = "settings",
Messages = "messages",
Tasks = "tasks",
Calendar = "calendar",
}
+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()
);
}
}
+258 -252
View File
@@ -1,8 +1,8 @@
import * as React from "bloatless-react";
import BoardsAndTasksModel, {
BoardInfoFileContent,
TaskFileContent,
BoardInfoFileContent,
TaskFileContent,
} from "../../Model/Files/boardsAndTasksModel";
import ChatViewModel from "../Chat/chatViewModel";
@@ -15,293 +15,299 @@ import TaskPageViewModel from "./taskPageViewModel";
import TaskViewModel from "./taskViewModel";
export default class BoardViewModel extends TaskContainingPageViewModel {
storageModel: StorageModel;
boardsAndTasksModel: BoardsAndTasksModel;
storageModel: StorageModel;
boardsAndTasksModel: BoardsAndTasksModel;
taskPageViewModel: TaskPageViewModel;
taskPageViewModel: TaskPageViewModel;
// data
boardInfo: BoardInfoFileContent;
// data
boardInfo: BoardInfoFileContent;
// state
name: React.State<string> = new React.State("");
color: React.State<Color> = new React.State<any>(Color.Standard);
// state
name: React.State<string> = new React.State("");
color: React.State<Color> = new React.State<any>(Color.Standard);
index: React.State<number> = new React.State(0);
index: React.State<number> = new React.State(0);
selectedPage: React.State<BoardPageType> = new React.State<any>(
BoardPageType.List
);
isSelected: React.State<boolean>;
isPresentingSettingsModal: React.State<boolean> = new React.State(false);
isPresentingFilterModal: React.State<boolean> = new React.State(false);
searchViewModel: SearchViewModel<TaskViewModel>;
filteredTaskViewModels: React.ListState<TaskViewModel> =
new React.ListState();
// paths
getBasePath = (): string[] => {
return [...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId)];
};
getLastUsedBoardPath = (): string[] => {
return [...this.getBasePath(), BoardViewModelSubPath.LastUsedView];
};
getPreviousSearchesPath = (): string[] => {
return [...this.getBasePath(), BoardViewModelSubPath.PreviousSearches];
};
getLastSearchPath = (): string[] => {
return [...this.getBasePath(), BoardViewModelSubPath.LastSearch];
};
// settings
saveSettings = (): void => {
const newBoardInfoFileContent: BoardInfoFileContent =
BoardsAndTasksModel.createBoardInfoFileContent(
this.boardInfo.fileId,
this.name.value,
this.color.value
);
this.taskPageViewModel.updateBoard(newBoardInfoFileContent);
};
applyColor = (): void => {
this.taskPageViewModel.chatViewModel.setDisplayedColor(this.color.value);
};
deleteBoard = (): void => {
this.taskPageViewModel.deleteBoard(this.boardInfo);
this.chatViewModel.taskBoardSuggestions.remove(this.boardInfo.fileId);
this.close();
};
// methods
createTask = (): void => {
this.createTaskFromBoardId(this.boardInfo.fileId);
};
handleDropWithinBoard = (category?: string, status?: string): void => {
const draggedObject: any = this.coreViewModel.draggedObject.value;
if (draggedObject instanceof TaskViewModel == false) return;
draggedObject.setCategoryAndStatus(category, status);
};
handleDropBetweenBoards = (): void => {
const draggedObject: any = this.coreViewModel.draggedObject.value;
if (draggedObject instanceof TaskViewModel == false) return;
draggedObject.setBoardId(this.boardInfo.fileId);
};
// storage
storeLastUsedView = (): void => {
const path: string[] = this.getLastUsedBoardPath();
const lastUsedView: string = this.selectedPage.value;
this.storageModel.write(path, lastUsedView);
};
restoreLastUsedView = (): void => {
const path: string[] = this.getLastUsedBoardPath();
const lastUsedView: string | null = this.storageModel.read(path);
if (lastUsedView == null) return;
this.selectedPage.value = lastUsedView as BoardPageType;
};
handleNewSearch = (searchTerm: string): void => {
const suggestionPath: string[] = [
...this.getPreviousSearchesPath(),
searchTerm,
];
this.storageModel.write(suggestionPath, "");
if (!this.coreViewModel.boardSearchSuggestions.value.has(searchTerm)) {
this.coreViewModel.boardSearchSuggestions.add(searchTerm);
}
const lastSearchPath: string[] = this.getLastSearchPath();
this.storageModel.write(lastSearchPath, searchTerm);
};
// view
showTask = (taskFileContent: TaskFileContent): void => {
if (taskFileContent.boardId != this.boardInfo.fileId) {
// remove task that was moved to different board
this.boardsAndTasksModel.deleteTaskReference(
this.boardInfo.fileId,
taskFileContent.fileId
);
this.removeTaskFromView(taskFileContent);
return;
}
const taskViewModel: TaskViewModel = new TaskViewModel(
this.coreViewModel,
this.chatViewModel,
this.boardsAndTasksModel,
this,
taskFileContent
selectedPage: React.State<BoardPageType> = new React.State<any>(
BoardPageType.List,
);
this.taskViewModels.set(taskFileContent.fileId, taskViewModel);
};
removeTaskFromView = (taskFileContent: TaskFileContent): void => {
this.taskViewModels.remove(taskFileContent.fileId);
this.updateIndex();
};
isSelected: React.State<boolean>;
isPresentingSettingsModal: React.State<boolean> = new React.State(false);
isPresentingFilterModal: React.State<boolean> = new React.State(false);
select = (): void => {
this.taskPageViewModel.selectBoard(this);
};
searchViewModel: SearchViewModel<TaskViewModel>;
filteredTaskViewModels: React.ListState<TaskViewModel> =
new React.ListState();
close = (): void => {
this.taskPageViewModel.closeBoard();
this.taskViewModels.clear();
};
// paths
getBasePath = (): string[] => {
return [
...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId),
];
};
showSettings = (): void => {
this.isPresentingSettingsModal.value = true;
};
getLastUsedBoardPath = (): string[] => {
return [...this.getBasePath(), BoardViewModelSubPath.LastUsedView];
};
hideSettings = (): void => {
this.saveSettings();
this.isPresentingSettingsModal.value = false;
};
getPreviousSearchesPath = (): string[] => {
return [...this.getBasePath(), BoardViewModelSubPath.PreviousSearches];
};
showFilterModal = (): void => {
this.isPresentingFilterModal.value = true;
};
getLastSearchPath = (): string[] => {
return [...this.getBasePath(), BoardViewModelSubPath.LastSearch];
};
hideFilterModal = (): void => {
this.isPresentingFilterModal.value = false;
};
// settings
saveSettings = (): void => {
const newBoardInfoFileContent: BoardInfoFileContent =
BoardsAndTasksModel.createBoardInfoFileContent(
this.boardInfo.fileId,
this.name.value,
this.color.value,
);
this.taskPageViewModel.updateBoard(newBoardInfoFileContent);
};
updateIndex = (): void => {
const index: number =
this.taskPageViewModel.boardIndexManager.getIndex(this);
this.index.value = index;
};
applyColor = (): void => {
this.taskPageViewModel.chatViewModel.setDisplayedColor(
this.color.value,
);
};
// load
loadListRelevantData = (): void => {
this.name.value = this.boardInfo.name;
this.color.value = this.boardInfo.color;
};
deleteBoard = (): void => {
this.taskPageViewModel.deleteBoard(this.boardInfo);
this.chatViewModel.taskBoardSuggestions.remove(this.boardInfo.fileId);
this.close();
};
loadTasks = (): void => {
const taskIds: string[] = this.boardsAndTasksModel.listTaskIds(
this.boardInfo.fileId
);
for (const taskId of taskIds) {
if (this.taskViewModels.value.has(taskId)) return;
// methods
createTask = (): void => {
this.createTaskFromBoardId(this.boardInfo.fileId);
};
const taskFileContent: TaskFileContent | null =
this.boardsAndTasksModel.getLatestTaskFileContent(taskId);
if (taskFileContent == null) continue;
handleDropWithinBoard = (category?: string, status?: string): void => {
const draggedObject: any = this.coreViewModel.draggedObject.value;
if (draggedObject instanceof TaskViewModel == false) return;
draggedObject.setCategoryAndStatus(category, status);
};
const taskViewModel: TaskViewModel = new TaskViewModel(
this.coreViewModel,
this.chatViewModel,
this.boardsAndTasksModel,
this,
taskFileContent
);
this.taskViewModels.set(taskFileContent.fileId, taskViewModel);
}
handleDropBetweenBoards = (): void => {
const draggedObject: any = this.coreViewModel.draggedObject.value;
if (draggedObject instanceof TaskViewModel == false) return;
draggedObject.setBoardId(this.boardInfo.fileId);
};
this.updateTaskIndices();
};
// storage
storeLastUsedView = (): void => {
const path: string[] = this.getLastUsedBoardPath();
const lastUsedView: string = this.selectedPage.value;
this.storageModel.write(path, lastUsedView);
};
loadSearchSuggestions = (): void => {
const dirPath: string[] = this.getPreviousSearchesPath();
const searches: string[] = this.storageModel.list(dirPath);
this.coreViewModel.boardSearchSuggestions.add(...searches);
};
restoreLastUsedView = (): void => {
const path: string[] = this.getLastUsedBoardPath();
const lastUsedView: string | null = this.storageModel.read(path);
if (lastUsedView == null) return;
restoreSearch = (): void => {
const lastSearchPath: string[] = this.getLastSearchPath();
const lastSearch: string | null = this.storageModel.read(lastSearchPath);
if (lastSearch != null) {
this.searchViewModel.search(lastSearch);
}
};
this.selectedPage.value = lastUsedView as BoardPageType;
};
loadData = (): void => {
this.restoreLastUsedView();
this.loadTasks();
this.loadSearchSuggestions();
};
handleNewSearch = (searchTerm: string): void => {
const suggestionPath: string[] = [
...this.getPreviousSearchesPath(),
searchTerm,
];
this.storageModel.write(suggestionPath, "");
if (!this.coreViewModel.boardSearchSuggestions.value.has(searchTerm)) {
this.coreViewModel.boardSearchSuggestions.add(searchTerm);
}
// init
constructor(
public coreViewModel: CoreViewModel,
public chatViewModel: ChatViewModel,
storageModel: StorageModel,
boardsAndTasksModel: BoardsAndTasksModel,
taskPageViewModel: TaskPageViewModel,
boardInfo: BoardInfoFileContent
) {
super(coreViewModel, chatViewModel, boardsAndTasksModel);
const lastSearchPath: string[] = this.getLastSearchPath();
this.storageModel.write(lastSearchPath, searchTerm);
};
// set
this.storageModel = storageModel;
this.boardsAndTasksModel = boardsAndTasksModel;
this.taskPageViewModel = taskPageViewModel;
this.boardInfo = boardInfo;
// view
showTask = (taskFileContent: TaskFileContent): void => {
if (taskFileContent.boardId != this.boardInfo.fileId) {
// remove task that was moved to different board
this.boardsAndTasksModel.deleteTaskReference(
this.boardInfo.fileId,
taskFileContent.fileId,
);
this.removeTaskFromView(taskFileContent);
return;
}
const taskViewModel: TaskViewModel = new TaskViewModel(
this.coreViewModel,
this.chatViewModel,
this.boardsAndTasksModel,
this,
taskFileContent,
);
this.taskViewModels.set(taskFileContent.fileId, taskViewModel);
};
removeTaskFromView = (taskFileContent: TaskFileContent): void => {
this.taskViewModels.remove(taskFileContent.fileId);
this.updateIndex();
};
select = (): void => {
this.taskPageViewModel.selectBoard(this);
};
close = (): void => {
this.taskPageViewModel.closeBoard();
this.taskViewModels.clear();
};
showSettings = (): void => {
this.isPresentingSettingsModal.value = true;
};
hideSettings = (): void => {
this.saveSettings();
this.isPresentingSettingsModal.value = false;
};
showFilterModal = (): void => {
this.isPresentingFilterModal.value = true;
};
hideFilterModal = (): void => {
this.isPresentingFilterModal.value = false;
};
updateIndex = (): void => {
const index: number =
this.taskPageViewModel.boardIndexManager.getIndex(this);
this.index.value = index;
};
// load
this.loadListRelevantData();
loadListRelevantData = (): void => {
this.name.value = this.boardInfo.name;
this.color.value = this.boardInfo.color;
};
// subscriptions
this.isSelected = React.createProxyState(
[this.taskPageViewModel.selectedBoardId],
() =>
this.taskPageViewModel.selectedBoardId.value == this.boardInfo.fileId
);
loadTasks = (): void => {
const taskIds: string[] = this.boardsAndTasksModel.listTaskIds(
this.boardInfo.fileId,
);
for (const taskId of taskIds) {
if (this.taskViewModels.value.has(taskId)) return;
this.color.subscribe(() => {
if (this.isSelected.value == false) return;
this.applyColor();
});
const taskFileContent: TaskFileContent | null =
this.boardsAndTasksModel.getLatestTaskFileContent(taskId);
if (taskFileContent == null) continue;
this.selectedPage.subscribeSilent(() => {
this.storeLastUsedView();
});
const taskViewModel: TaskViewModel = new TaskViewModel(
this.coreViewModel,
this.chatViewModel,
this.boardsAndTasksModel,
this,
taskFileContent,
);
this.taskViewModels.set(taskFileContent.fileId, taskViewModel);
}
// handlers
boardsAndTasksModel.taskHandlerManager.addHandler(
(taskFileContent: TaskFileContent) => {
if (taskFileContent.boardId != this.boardInfo.fileId) return;
this.showTask(taskFileContent);
this.updateTaskIndices();
}
);
};
// search
this.searchViewModel = new SearchViewModel(
this.taskViewModels,
this.filteredTaskViewModels,
TaskViewModel.getStringsForFilter,
this.coreViewModel.boardSearchSuggestions
);
this.searchViewModel.appliedQuery.subscribeSilent((newQuery) => {
this.handleNewSearch(newQuery);
});
this.restoreSearch();
}
loadSearchSuggestions = (): void => {
const dirPath: string[] = this.getPreviousSearchesPath();
const searches: string[] = this.storageModel.list(dirPath);
this.coreViewModel.boardSearchSuggestions.add(...searches);
};
restoreSearch = (): void => {
const lastSearchPath: string[] = this.getLastSearchPath();
const lastSearch: string | null =
this.storageModel.read(lastSearchPath);
if (lastSearch != null) {
this.searchViewModel.search(lastSearch);
}
};
loadData = (): void => {
this.restoreLastUsedView();
this.loadTasks();
this.loadSearchSuggestions();
};
// init
constructor(
public coreViewModel: CoreViewModel,
public chatViewModel: ChatViewModel,
storageModel: StorageModel,
boardsAndTasksModel: BoardsAndTasksModel,
taskPageViewModel: TaskPageViewModel,
boardInfo: BoardInfoFileContent,
) {
super(coreViewModel, chatViewModel, boardsAndTasksModel);
// set
this.storageModel = storageModel;
this.boardsAndTasksModel = boardsAndTasksModel;
this.taskPageViewModel = taskPageViewModel;
this.boardInfo = boardInfo;
// load
this.loadListRelevantData();
// subscriptions
this.isSelected = React.createProxyState(
[this.taskPageViewModel.selectedBoardId],
() =>
this.taskPageViewModel.selectedBoardId.value ==
this.boardInfo.fileId,
);
this.color.subscribe(() => {
if (this.isSelected.value == false) return;
this.applyColor();
});
this.selectedPage.subscribeSilent(() => {
this.storeLastUsedView();
});
// handlers
boardsAndTasksModel.taskHandlerManager.addHandler(
(taskFileContent: TaskFileContent) => {
if (taskFileContent.boardId != this.boardInfo.fileId) return;
this.showTask(taskFileContent);
this.updateTaskIndices();
},
);
// search
this.searchViewModel = new SearchViewModel(
this.taskViewModels,
this.filteredTaskViewModels,
TaskViewModel.getStringsForFilter,
this.coreViewModel.boardSearchSuggestions,
);
this.searchViewModel.appliedQuery.subscribeSilent((newQuery) => {
this.handleNewSearch(newQuery);
});
this.restoreSearch();
}
}
export enum BoardViewModelSubPath {
LastUsedView = "last-used-view",
PreviousSearches = "previous-searches",
LastSearch = "last-search",
LastUsedView = "last-used-view",
PreviousSearches = "previous-searches",
LastSearch = "last-search",
}
// types
export enum BoardPageType {
List = "list",
Kanban = "kanban",
StatusGrid = "status-grid",
List = "list",
Kanban = "kanban",
StatusGrid = "status-grid",
}
+195 -188
View File
@@ -1,7 +1,7 @@
import * as React from "bloatless-react";
import BoardsAndTasksModel, {
TaskFileContent,
TaskFileContent,
} from "../../Model/Files/boardsAndTasksModel";
import CalendarModel, { MonthGrid } from "../../Model/Files/calendarModel";
@@ -14,201 +14,208 @@ import TaskViewModel from "./taskViewModel";
export const CALENDAR_EVENT_BOARD_ID = "events";
export default class CalendarPageViewModel extends TaskContainingPageViewModel {
storageModel: StorageModel;
calendarModel: CalendarModel;
boardsAndTasksModel: BoardsAndTasksModel;
storageModel: StorageModel;
calendarModel: CalendarModel;
boardsAndTasksModel: BoardsAndTasksModel;
// data
get monthString(): string {
return CalendarModel.getMonthString(
this.selectedYear.value.toString(),
this.selectedMonth.value.toString()
);
}
// paths
getBasePath = (): string[] => {
return [...this.calendarModel.getViewPath()];
};
// state
selectedYear: React.State<number> = new React.State(0);
selectedMonth: React.State<number> = new React.State(0);
selectedDate: React.State<number> = new React.State(0);
monthGrid: React.State<MonthGrid<React.MapState<TaskViewModel>> | undefined> =
new React.State<any>(undefined);
// methods
createEvent = (): void => {
const taskFileContent: TaskFileContent =
this.boardsAndTasksModel.createTask(CALENDAR_EVENT_BOARD_ID);
taskFileContent.date = CalendarModel.getISODateString(
this.selectedYear.value.toString(),
this.selectedMonth.value.toString(),
this.selectedDate.value.toString()
);
const taskViewModel: TaskViewModel = new TaskViewModel(
this.coreViewModel,
this.chatViewModel,
this.boardsAndTasksModel,
this,
taskFileContent
);
this.selectTask(taskViewModel);
this.updateTaskIndices();
};
getEventsForDate = (): React.MapState<TaskViewModel> | undefined => {
const paddedDate: string = CalendarModel.padDateOrMonth(
this.selectedDate.toString()
);
if (this.monthGrid.value == undefined) {
return undefined;
// data
get monthString(): string {
return CalendarModel.getMonthString(
this.selectedYear.value.toString(),
this.selectedMonth.value.toString(),
);
}
return this.monthGrid.value.days[paddedDate];
};
// paths
getBasePath = (): string[] => {
return [...this.calendarModel.getViewPath()];
};
// view
getTaskMapState = (
taskFileContent: TaskFileContent
): React.MapState<TaskViewModel> | null => {
if (this.monthGrid.value == null) return null;
// state
selectedYear: React.State<number> = new React.State(0);
selectedMonth: React.State<number> = new React.State(0);
selectedDate: React.State<number> = new React.State(0);
const date: string = CalendarModel.isoToDateString(
taskFileContent.date ?? ""
);
return this.monthGrid.value.days[date];
};
monthGrid: React.State<
MonthGrid<React.MapState<TaskViewModel>> | undefined
> = new React.State<any>(undefined);
showTask = (taskFileContent: TaskFileContent): void => {
const monthString: string | undefined = CalendarModel.isoToMonthString(
taskFileContent.date ?? ""
);
if (monthString == undefined || monthString != this.monthString) {
this.removeTaskFromView(taskFileContent);
this.calendarModel.deleteTaskReference(
this.monthString,
taskFileContent.fileId
);
return;
// methods
createEvent = (): void => {
const taskFileContent: TaskFileContent =
this.boardsAndTasksModel.createTask(CALENDAR_EVENT_BOARD_ID);
taskFileContent.date = CalendarModel.getISODateString(
this.selectedYear.value.toString(),
this.selectedMonth.value.toString(),
this.selectedDate.value.toString(),
);
const taskViewModel: TaskViewModel = new TaskViewModel(
this.coreViewModel,
this.chatViewModel,
this.boardsAndTasksModel,
this,
taskFileContent,
);
this.selectTask(taskViewModel);
this.updateTaskIndices();
};
getEventsForDate = (): React.MapState<TaskViewModel> | undefined => {
const paddedDate: string = CalendarModel.padDateOrMonth(
this.selectedDate.toString(),
);
if (this.monthGrid.value == undefined) {
return undefined;
}
return this.monthGrid.value.days[paddedDate];
};
// view
getTaskMapState = (
taskFileContent: TaskFileContent,
): React.MapState<TaskViewModel> | null => {
if (this.monthGrid.value == null) return null;
const date: string = CalendarModel.isoToDateString(
taskFileContent.date ?? "",
);
return this.monthGrid.value.days[date];
};
showTask = (taskFileContent: TaskFileContent): void => {
const monthString: string | undefined = CalendarModel.isoToMonthString(
taskFileContent.date ?? "",
);
if (monthString == undefined || monthString != this.monthString) {
this.removeTaskFromView(taskFileContent);
this.calendarModel.deleteTaskReference(
this.monthString,
taskFileContent.fileId,
);
return;
}
const taskViewModel: TaskViewModel = new TaskViewModel(
this.coreViewModel,
this.chatViewModel,
this.boardsAndTasksModel,
this,
taskFileContent,
);
const mapState: React.MapState<TaskViewModel> | null =
this.getTaskMapState(taskFileContent);
this.taskViewModels.handleRemoval(taskViewModel, () => {
mapState?.remove(taskFileContent.fileId);
});
this.taskViewModels.remove(taskFileContent.fileId);
this.taskViewModels.set(taskFileContent.fileId, taskViewModel);
mapState?.set(taskFileContent.fileId, taskViewModel);
};
removeTaskFromView = (taskFileContent: TaskFileContent): void => {
this.taskViewModels.remove(taskFileContent.fileId);
};
showToday = (): void => {
const today: Date = new Date();
this.selectedYear.value = today.getFullYear();
this.selectedMonth.value = today.getMonth() + 1;
this.selectedDate.value = today.getDate();
};
showPreviousMonth = (): void => {
this.selectedMonth.value -= 1;
if (this.selectedMonth.value <= 0) {
this.selectedYear.value -= 1;
this.selectedMonth.value = 12;
}
};
showNextMonth = (): void => {
this.selectedMonth.value += 1;
if (this.selectedMonth.value >= 13) {
this.selectedYear.value += 1;
this.selectedMonth.value = 1;
}
};
handleDrop = (year: string, month: string, date: string): void => {
const ISOString: string = CalendarModel.getISODateString(
year,
month,
date,
);
const draggedObject: any = this.coreViewModel.draggedObject.value;
if (draggedObject instanceof TaskViewModel == false) return;
draggedObject.setDate(ISOString);
};
// load
loadMonthTasks = (): void => {
this.monthGrid.value = this.calendarModel.generateMonthGrid(
this.selectedYear.value,
this.selectedMonth.value,
() => new React.MapState(),
);
const taskIds: string[] = this.calendarModel.listTaskIds(
this.monthString,
);
for (const taskId of taskIds) {
const taskFileContent: TaskFileContent | null =
this.boardsAndTasksModel.getLatestTaskFileContent(taskId);
if (taskFileContent == null) continue;
this.showTask(taskFileContent);
}
this.updateTaskIndices();
};
loadData = (): void => {
this.loadMonthTasks();
this.showToday();
};
// init
constructor(
public coreViewModel: CoreViewModel,
public chatViewModel: ChatViewModel,
storageModel: StorageModel,
calendarModel: CalendarModel,
boardsAndTasksModel: BoardsAndTasksModel,
) {
super(coreViewModel, chatViewModel, boardsAndTasksModel);
this.storageModel = storageModel;
this.calendarModel = calendarModel;
this.boardsAndTasksModel = boardsAndTasksModel;
this.chatViewModel = chatViewModel;
React.bulkSubscribe([this.selectedYear, this.selectedMonth], () => {
this.loadMonthTasks();
});
// handlers
boardsAndTasksModel.taskHandlerManager.addHandler(
(taskFileContent: TaskFileContent) => {
this.showTask(taskFileContent);
},
);
}
const taskViewModel: TaskViewModel = new TaskViewModel(
this.coreViewModel,
this.chatViewModel,
this.boardsAndTasksModel,
this,
taskFileContent
);
const mapState: React.MapState<TaskViewModel> | null =
this.getTaskMapState(taskFileContent);
this.taskViewModels.handleRemoval(taskViewModel, () => {
mapState?.remove(taskFileContent.fileId);
});
this.taskViewModels.remove(taskFileContent.fileId);
this.taskViewModels.set(taskFileContent.fileId, taskViewModel);
mapState?.set(taskFileContent.fileId, taskViewModel);
};
removeTaskFromView = (taskFileContent: TaskFileContent): void => {
this.taskViewModels.remove(taskFileContent.fileId);
};
showToday = (): void => {
const today: Date = new Date();
this.selectedYear.value = today.getFullYear();
this.selectedMonth.value = today.getMonth() + 1;
this.selectedDate.value = today.getDate();
};
showPreviousMonth = (): void => {
this.selectedMonth.value -= 1;
if (this.selectedMonth.value <= 0) {
this.selectedYear.value -= 1;
this.selectedMonth.value = 12;
}
};
showNextMonth = (): void => {
this.selectedMonth.value += 1;
if (this.selectedMonth.value >= 13) {
this.selectedYear.value += 1;
this.selectedMonth.value = 1;
}
};
handleDrop = (year: string, month: string, date: string): void => {
const ISOString: string = CalendarModel.getISODateString(year, month, date);
const draggedObject: any = this.coreViewModel.draggedObject.value;
if (draggedObject instanceof TaskViewModel == false) return;
draggedObject.setDate(ISOString);
};
// load
loadMonthTasks = (): void => {
this.monthGrid.value = this.calendarModel.generateMonthGrid(
this.selectedYear.value,
this.selectedMonth.value,
() => new React.MapState()
);
const taskIds: string[] = this.calendarModel.listTaskIds(this.monthString);
for (const taskId of taskIds) {
const taskFileContent: TaskFileContent | null =
this.boardsAndTasksModel.getLatestTaskFileContent(taskId);
if (taskFileContent == null) continue;
this.showTask(taskFileContent);
}
this.updateTaskIndices();
};
loadData = (): void => {
this.loadMonthTasks();
this.showToday();
};
// init
constructor(
public coreViewModel: CoreViewModel,
public chatViewModel: ChatViewModel,
storageModel: StorageModel,
calendarModel: CalendarModel,
boardsAndTasksModel: BoardsAndTasksModel
) {
super(coreViewModel, chatViewModel, boardsAndTasksModel);
this.storageModel = storageModel;
this.calendarModel = calendarModel;
this.boardsAndTasksModel = boardsAndTasksModel;
this.chatViewModel = chatViewModel;
React.bulkSubscribe([this.selectedYear, this.selectedMonth], () => {
this.loadMonthTasks();
});
// handlers
boardsAndTasksModel.taskHandlerManager.addHandler(
(taskFileContent: TaskFileContent) => {
this.showTask(taskFileContent);
}
);
}
}
export enum TaskPageViewModelSubPath {
LastUsedBoard = "last-used-board",
LastUsedBoard = "last-used-board",
}
+70 -63
View File
@@ -6,76 +6,83 @@ import ChatViewModel from "../Chat/chatViewModel";
import CoreViewModel from "../Global/coreViewModel";
export default class MessagePageViewModel {
chatViewModel: ChatViewModel;
chatViewModel: ChatViewModel;
// state
chatMessageViewModels: React.MapState<ChatMessageViewModel> =
new React.MapState();
composingMessage: React.State<string> = new React.State("");
// state
chatMessageViewModels: React.MapState<ChatMessageViewModel> =
new React.MapState();
composingMessage: React.State<string> = new React.State("");
// guards
cannotSendMessage: React.State<boolean>;
// guards
cannotSendMessage: React.State<boolean>;
// methods
sendMessage = (): void => {
if (this.cannotSendMessage.value == true) return;
// methods
sendMessage = (): void => {
if (this.cannotSendMessage.value == true) return;
this.sendMessageFromBody(this.composingMessage.value);
this.composingMessage.value = "";
};
this.sendMessageFromBody(this.composingMessage.value);
this.composingMessage.value = "";
};
sendMessageFromBody = (body: string): void => {
this.chatViewModel.chatModel.sendMessage(body);
};
sendMessageFromBody = (body: string): void => {
this.chatViewModel.chatModel.sendMessage(body);
};
decryptMessage = async (
messageViewModel: ChatMessageViewModel
): Promise<void> => {
const chatMessage: ChatMessage = messageViewModel.chatMessage;
await this.chatViewModel.chatModel.decryptMessage(chatMessage);
this.chatViewModel.chatModel.addMessage(chatMessage);
messageViewModel.loadData();
};
decryptMessage = async (
messageViewModel: ChatMessageViewModel,
): Promise<void> => {
const chatMessage: ChatMessage = messageViewModel.chatMessage;
await this.chatViewModel.chatModel.decryptMessage(chatMessage);
this.chatViewModel.chatModel.addMessage(chatMessage);
messageViewModel.loadData();
};
// view
showChatMessage = (chatMessage: ChatMessage): void => {
const chatMessageViewModel = new ChatMessageViewModel(
this.coreViewModel,
this,
chatMessage,
chatMessage.sender == this.chatViewModel.settingsViewModel.username.value
);
// view
showChatMessage = (chatMessage: ChatMessage): void => {
const chatMessageViewModel = new ChatMessageViewModel(
this.coreViewModel,
this,
chatMessage,
chatMessage.sender ==
this.chatViewModel.settingsViewModel.username.value,
);
const existingChatMessageViewModel: ChatMessageViewModel | undefined =
this.chatMessageViewModels.value.get(chatMessage.id);
if (existingChatMessageViewModel != undefined) {
existingChatMessageViewModel.body.value = chatMessage.body;
existingChatMessageViewModel.status.value = chatMessage.status;
} else {
this.chatMessageViewModels.set(chatMessage.id, chatMessageViewModel);
const existingChatMessageViewModel: ChatMessageViewModel | undefined =
this.chatMessageViewModels.value.get(chatMessage.id);
if (existingChatMessageViewModel != undefined) {
existingChatMessageViewModel.body.value = chatMessage.body;
existingChatMessageViewModel.status.value = chatMessage.status;
} else {
this.chatMessageViewModels.set(
chatMessage.id,
chatMessageViewModel,
);
}
};
// load
loadData = (): void => {
this.chatMessageViewModels.clear();
for (const chatMessage of this.chatViewModel.chatModel.messages) {
this.showChatMessage(chatMessage);
}
};
// init
constructor(
public coreViewModel: CoreViewModel,
chatViewModel: ChatViewModel,
) {
this.chatViewModel = chatViewModel;
this.cannotSendMessage = React.createProxyState(
[
this.chatViewModel.settingsViewModel.username,
this.composingMessage,
],
() =>
this.chatViewModel.settingsViewModel.username.value == "" ||
this.composingMessage.value == "",
);
}
};
// load
loadData = (): void => {
this.chatMessageViewModels.clear();
for (const chatMessage of this.chatViewModel.chatModel.messages) {
this.showChatMessage(chatMessage);
}
};
// init
constructor(
public coreViewModel: CoreViewModel,
chatViewModel: ChatViewModel
) {
this.chatViewModel = chatViewModel;
this.cannotSendMessage = React.createProxyState(
[this.chatViewModel.settingsViewModel.username, this.composingMessage],
() =>
this.chatViewModel.settingsViewModel.username.value == "" ||
this.composingMessage.value == ""
);
}
}
+104 -104
View File
@@ -5,128 +5,128 @@ import { Color } from "../../colors";
import CoreViewModel from "../Global/coreViewModel";
export default class SettingsPageViewModel {
chatViewModel: ChatViewModel;
chatViewModel: ChatViewModel;
// state
primaryChannel: React.State<string> = new React.State("");
primaryChannelInput: React.State<string> = new React.State("");
// state
primaryChannel: React.State<string> = new React.State("");
primaryChannelInput: React.State<string> = new React.State("");
secondaryChannels: React.ListState<string> = new React.ListState();
newSecondaryChannelInput: React.State<string> = new React.State("");
secondaryChannels: React.ListState<string> = new React.ListState();
newSecondaryChannelInput: React.State<string> = new React.State("");
encryptionKeyInput: React.State<string> = new React.State("");
shouldShowEncryptionKey: React.State<boolean> = new React.State(false);
encryptionKeyInputType: React.State<"text" | "password"> =
React.createProxyState([this.shouldShowEncryptionKey], () =>
this.shouldShowEncryptionKey.value == true ? "text" : "password"
encryptionKeyInput: React.State<string> = new React.State("");
shouldShowEncryptionKey: React.State<boolean> = new React.State(false);
encryptionKeyInputType: React.State<"text" | "password"> =
React.createProxyState([this.shouldShowEncryptionKey], () =>
this.shouldShowEncryptionKey.value == true ? "text" : "password",
);
color: React.State<Color> = new React.State<any>(Color.Standard);
// guards
cannotSetPrimaryChannel: React.State<boolean> = React.createProxyState(
[this.primaryChannel, this.primaryChannelInput],
() =>
this.primaryChannelInput.value == "" ||
this.primaryChannelInput.value == this.primaryChannel.value,
);
color: React.State<Color> = new React.State<any>(Color.Standard);
// guards
cannotSetPrimaryChannel: React.State<boolean> = React.createProxyState(
[this.primaryChannel, this.primaryChannelInput],
() =>
this.primaryChannelInput.value == "" ||
this.primaryChannelInput.value == this.primaryChannel.value
);
cannotAddSecondaryChannel: React.State<boolean> = React.createProxyState(
[this.newSecondaryChannelInput],
() => this.newSecondaryChannelInput.value == ""
);
cannotSetEncryptionKey: React.State<boolean>;
// methods
setPrimaryChannel = (): void => {
this.chatViewModel.chatModel.setPrimaryChannel(
this.primaryChannelInput.value
cannotAddSecondaryChannel: React.State<boolean> = React.createProxyState(
[this.newSecondaryChannelInput],
() => this.newSecondaryChannelInput.value == "",
);
this.primaryChannel.value =
this.chatViewModel.chatModel.info.primaryChannel;
cannotSetEncryptionKey: React.State<boolean>;
this.chatViewModel.chatListViewModel.updateIndices();
};
// methods
setPrimaryChannel = (): void => {
this.chatViewModel.chatModel.setPrimaryChannel(
this.primaryChannelInput.value,
);
this.primaryChannel.value =
this.chatViewModel.chatModel.info.primaryChannel;
addSecondaryChannel = (): void => {
this.secondaryChannels.add(this.newSecondaryChannelInput.value);
this.newSecondaryChannelInput.value = "";
this.storeSecondaryChannels();
this.loadSecondaryChannels();
};
this.chatViewModel.chatListViewModel.updateIndices();
};
removeSecondaryChannel = (secondaryChannel: string): void => {
this.secondaryChannels.remove(secondaryChannel);
this.storeSecondaryChannels();
};
addSecondaryChannel = (): void => {
this.secondaryChannels.add(this.newSecondaryChannelInput.value);
this.newSecondaryChannelInput.value = "";
this.storeSecondaryChannels();
this.loadSecondaryChannels();
};
storeSecondaryChannels = (): void => {
this.chatViewModel.chatModel.setSecondaryChannels([
...this.secondaryChannels.value.values(),
]);
};
removeSecondaryChannel = (secondaryChannel: string): void => {
this.secondaryChannels.remove(secondaryChannel);
this.storeSecondaryChannels();
};
setEncryptionKey = (): void => {
this.chatViewModel.chatModel.setEncryptionKey(
this.encryptionKeyInput.value
);
storeSecondaryChannels = (): void => {
this.chatViewModel.chatModel.setSecondaryChannels([
...this.secondaryChannels.value.values(),
]);
};
// disable button
this.encryptionKeyInput.callSubscriptions();
};
setEncryptionKey = (): void => {
this.chatViewModel.chatModel.setEncryptionKey(
this.encryptionKeyInput.value,
);
applyColor = (newColor: Color): void => {
this.chatViewModel.setColor(newColor);
};
// disable button
this.encryptionKeyInput.callSubscriptions();
};
remove = (): void => {
this.chatViewModel.close();
this.chatViewModel.chatModel.delete();
this.chatViewModel.chatListViewModel.untrackChat(this.chatViewModel);
};
applyColor = (newColor: Color): void => {
this.chatViewModel.setColor(newColor);
};
// load
loadListRelevantData = (): void => {
this.primaryChannel.value =
this.chatViewModel.chatModel.info.primaryChannel;
remove = (): void => {
this.chatViewModel.close();
this.chatViewModel.chatModel.delete();
this.chatViewModel.chatListViewModel.untrackChat(this.chatViewModel);
};
this.color.value = this.chatViewModel.chatModel.color;
};
// load
loadListRelevantData = (): void => {
this.primaryChannel.value =
this.chatViewModel.chatModel.info.primaryChannel;
loadData = (): void => {
this.primaryChannelInput.value =
this.chatViewModel.chatModel.info.primaryChannel;
this.color.value = this.chatViewModel.chatModel.color;
};
this.loadSecondaryChannels();
loadData = (): void => {
this.primaryChannelInput.value =
this.chatViewModel.chatModel.info.primaryChannel;
this.encryptionKeyInput.value =
this.chatViewModel.chatModel.info.encryptionKey;
};
this.loadSecondaryChannels();
loadSecondaryChannels = (): void => {
this.secondaryChannels.clear();
for (const secondaryChannel of this.chatViewModel.chatModel
.secondaryChannels) {
this.secondaryChannels.add(secondaryChannel);
this.encryptionKeyInput.value =
this.chatViewModel.chatModel.info.encryptionKey;
};
loadSecondaryChannels = (): void => {
this.secondaryChannels.clear();
for (const secondaryChannel of this.chatViewModel.chatModel
.secondaryChannels) {
this.secondaryChannels.add(secondaryChannel);
}
};
// init
constructor(
public coreViewModel: CoreViewModel,
chatViewModel: ChatViewModel,
) {
this.chatViewModel = chatViewModel;
this.loadListRelevantData();
this.cannotSetEncryptionKey = React.createProxyState(
[this.encryptionKeyInput],
() =>
this.encryptionKeyInput.value ==
this.chatViewModel.chatModel.info.encryptionKey,
);
this.color.subscribe((newColor) => {
this.applyColor(newColor);
});
}
};
// init
constructor(
public coreViewModel: CoreViewModel,
chatViewModel: ChatViewModel
) {
this.chatViewModel = chatViewModel;
this.loadListRelevantData();
this.cannotSetEncryptionKey = React.createProxyState(
[this.encryptionKeyInput],
() =>
this.encryptionKeyInput.value ==
this.chatViewModel.chatModel.info.encryptionKey
);
this.color.subscribe((newColor) => {
this.applyColor(newColor);
});
}
}
@@ -1,7 +1,7 @@
import * as React from "bloatless-react";
import BoardsAndTasksModel, {
TaskFileContent,
TaskFileContent,
} from "../../Model/Files/boardsAndTasksModel";
import ChatViewModel from "../Chat/chatViewModel";
@@ -10,58 +10,58 @@ import { IndexManager } from "../../Model/Utility/utility";
import TaskViewModel from "./taskViewModel";
export default class TaskContainingPageViewModel {
boardsAndTasksModel: BoardsAndTasksModel;
boardsAndTasksModel: BoardsAndTasksModel;
// state
taskIndexManager: IndexManager<TaskViewModel> = new IndexManager(
(taskViewModel: TaskViewModel) => taskViewModel.sortingString
);
selectedTaskViewModel: React.State<TaskViewModel | undefined> =
new React.State<any>(undefined);
taskViewModels: React.MapState<TaskViewModel> = new React.MapState();
// methods
createTaskFromBoardId = (boardId: string): void => {
const taskFileContent: TaskFileContent =
this.boardsAndTasksModel.createTask(boardId);
const taskViewModel: TaskViewModel = new TaskViewModel(
this.coreViewModel,
this.chatViewModel,
this.boardsAndTasksModel,
this,
taskFileContent
// state
taskIndexManager: IndexManager<TaskViewModel> = new IndexManager(
(taskViewModel: TaskViewModel) => taskViewModel.sortingString,
);
this.selectTask(taskViewModel);
this.updateTaskIndices();
};
// view
showTask = (taskFileContent: TaskFileContent): void => {};
removeTaskFromView = (taskFileContent: TaskFileContent): void => {};
selectedTaskViewModel: React.State<TaskViewModel | undefined> =
new React.State<any>(undefined);
selectTask = (selectedTask: TaskViewModel): void => {
this.selectedTaskViewModel.value = selectedTask;
};
taskViewModels: React.MapState<TaskViewModel> = new React.MapState();
closeTask = () => {
this.selectedTaskViewModel.value = undefined;
};
// methods
createTaskFromBoardId = (boardId: string): void => {
const taskFileContent: TaskFileContent =
this.boardsAndTasksModel.createTask(boardId);
const taskViewModel: TaskViewModel = new TaskViewModel(
this.coreViewModel,
this.chatViewModel,
this.boardsAndTasksModel,
this,
taskFileContent,
);
this.selectTask(taskViewModel);
this.updateTaskIndices();
};
updateTaskIndices = (): void => {
this.taskIndexManager.update([...this.taskViewModels.value.values()]);
for (const boardViewModel of this.taskViewModels.value.values()) {
boardViewModel.updateIndex();
// view
showTask = (taskFileContent: TaskFileContent): void => {};
removeTaskFromView = (taskFileContent: TaskFileContent): void => {};
selectTask = (selectedTask: TaskViewModel): void => {
this.selectedTaskViewModel.value = selectedTask;
};
closeTask = () => {
this.selectedTaskViewModel.value = undefined;
};
updateTaskIndices = (): void => {
this.taskIndexManager.update([...this.taskViewModels.value.values()]);
for (const boardViewModel of this.taskViewModels.value.values()) {
boardViewModel.updateIndex();
}
};
// init
constructor(
public coreViewModel: CoreViewModel,
public chatViewModel: ChatViewModel,
boardsAndTasksModel: BoardsAndTasksModel,
) {
this.boardsAndTasksModel = boardsAndTasksModel;
}
};
// init
constructor(
public coreViewModel: CoreViewModel,
public chatViewModel: ChatViewModel,
boardsAndTasksModel: BoardsAndTasksModel
) {
this.boardsAndTasksModel = boardsAndTasksModel;
}
}
+148 -148
View File
@@ -1,7 +1,7 @@
import * as React from "bloatless-react";
import BoardsAndTasksModel, {
BoardInfoFileContent,
BoardInfoFileContent,
} from "../../Model/Files/boardsAndTasksModel";
import BoardViewModel from "./boardViewModel";
@@ -11,169 +11,169 @@ import { IndexManager } from "../../Model/Utility/utility";
import StorageModel from "../../Model/Global/storageModel";
export default class TaskPageViewModel {
storageModel: StorageModel;
boardsAndTasksModel: BoardsAndTasksModel;
storageModel: StorageModel;
boardsAndTasksModel: BoardsAndTasksModel;
// data
boardIndexManager: IndexManager<BoardViewModel> = new IndexManager(
(boardViewModel: BoardViewModel) => boardViewModel.name.value
);
// paths
getBasePath = (): string[] => {
return [...this.boardsAndTasksModel.getViewPath()];
};
getBoardViewPath = (boardId): string[] => {
return [...this.getBasePath(), boardId];
};
getLastUsedBoardPath = (): string[] => {
return [...this.getBasePath(), TaskPageViewModelSubPath.LastUsedBoard];
};
// state
newBoardNameInput: React.State<string> = new React.State("");
boardViewModels: React.MapState<BoardViewModel> = new React.MapState();
isShowingBoadList: React.State<boolean> = new React.State(true);
selectedBoardId: React.State<string | undefined> = new React.State<any>(
undefined
);
// guards
cannotCreateBoard: React.State<boolean> = React.createProxyState(
[this.newBoardNameInput],
() => this.newBoardNameInput.value == ""
);
// methods
createBoard = (): void => {
if (this.cannotCreateBoard.value == true) return;
const boardInfoFileContent: BoardInfoFileContent =
this.boardsAndTasksModel.createBoard(this.newBoardNameInput.value);
this.newBoardNameInput.value = "";
this.showBoardInList(boardInfoFileContent);
this.boardsAndTasksModel.updateBoardAndSend(boardInfoFileContent);
this.updateBoardIndices();
};
updateBoard = (boardInfoFileContent: BoardInfoFileContent): void => {
this.boardsAndTasksModel.updateBoardAndSend(boardInfoFileContent);
this.updateBoardIndices();
};
deleteBoard = (boardInfoFileContent: BoardInfoFileContent): void => {
this.boardsAndTasksModel.deleteBoard(boardInfoFileContent.fileId);
this.boardViewModels.remove(boardInfoFileContent.fileId);
this.updateBoardIndices();
};
// view
toggleBoardList = (): void => {
this.isShowingBoadList.value = !this.isShowingBoadList.value;
};
showBoardInList = (boardInfo: BoardInfoFileContent): void => {
const boardViewModel: BoardViewModel = new BoardViewModel(
this.coreViewModel,
this.chatViewModel,
this.storageModel,
this.boardsAndTasksModel,
this,
boardInfo
// data
boardIndexManager: IndexManager<BoardViewModel> = new IndexManager(
(boardViewModel: BoardViewModel) => boardViewModel.name.value,
);
this.boardViewModels.set(boardInfo.fileId, boardViewModel);
this.chatViewModel.taskBoardSuggestions.set(boardInfo.fileId, [
boardInfo.fileId,
this.boardsAndTasksModel.getBoardName(boardInfo.fileId),
]);
};
// paths
getBasePath = (): string[] => {
return [...this.boardsAndTasksModel.getViewPath()];
};
selectBoard = (boardViewModel: BoardViewModel): void => {
this.selectedBoardId.value = boardViewModel.boardInfo.fileId;
this.chatViewModel.displayedColor.value = boardViewModel.color.value;
getBoardViewPath = (boardId): string[] => {
return [...this.getBasePath(), boardId];
};
this.storeLastUsedBoard();
};
getLastUsedBoardPath = (): string[] => {
return [...this.getBasePath(), TaskPageViewModelSubPath.LastUsedBoard];
};
closeBoard = (): void => {
this.selectedBoardId.value = undefined;
this.chatViewModel.resetColor();
// state
newBoardNameInput: React.State<string> = new React.State("");
this.storeLastUsedBoard();
};
boardViewModels: React.MapState<BoardViewModel> = new React.MapState();
updateBoardIndices = (): void => {
this.boardIndexManager.update([...this.boardViewModels.value.values()]);
for (const boardViewModel of this.boardViewModels.value.values()) {
boardViewModel.updateIndex();
}
};
isShowingBoadList: React.State<boolean> = new React.State(true);
// storage
storeLastUsedBoard = (): void => {
const path: string[] = this.getLastUsedBoardPath();
const lastUsedBoardId: string = this.selectedBoardId.value ?? "";
this.storageModel.write(path, lastUsedBoardId);
};
selectedBoardId: React.State<string | undefined> = new React.State<any>(
undefined,
);
openLastUsedBoard = (): void => {
const path: string[] = this.getLastUsedBoardPath();
const lastUsedBoardId: string | null = this.storageModel.read(path);
if (lastUsedBoardId == null) return;
// guards
cannotCreateBoard: React.State<boolean> = React.createProxyState(
[this.newBoardNameInput],
() => this.newBoardNameInput.value == "",
);
const boardViewModel: BoardViewModel | undefined =
this.boardViewModels.value.get(lastUsedBoardId);
if (boardViewModel == undefined) return;
// methods
createBoard = (): void => {
if (this.cannotCreateBoard.value == true) return;
this.selectBoard(boardViewModel);
};
const boardInfoFileContent: BoardInfoFileContent =
this.boardsAndTasksModel.createBoard(this.newBoardNameInput.value);
this.newBoardNameInput.value = "";
// load
loadData = (): void => {
this.boardViewModels.clear();
const boardIds: string[] = this.boardsAndTasksModel.listBoardIds();
for (const boardId of boardIds) {
const boardInfo: BoardInfoFileContent | null =
this.boardsAndTasksModel.getBoardInfo(boardId);
if (boardInfo == null) continue;
this.showBoardInList(boardInfo);
}
this.updateBoardIndices();
this.openLastUsedBoard();
};
// init
constructor(
public coreViewModel: CoreViewModel,
public chatViewModel: ChatViewModel,
storageModel: StorageModel,
boardModel: BoardsAndTasksModel
) {
this.storageModel = storageModel;
this.boardsAndTasksModel = boardModel;
this.chatViewModel = chatViewModel;
// handlers
boardModel.boardHandlerManager.addHandler(
(boardInfoFileContent: BoardInfoFileContent) => {
this.showBoardInList(boardInfoFileContent);
this.boardsAndTasksModel.updateBoardAndSend(boardInfoFileContent);
this.updateBoardIndices();
}
);
}
};
updateBoard = (boardInfoFileContent: BoardInfoFileContent): void => {
this.boardsAndTasksModel.updateBoardAndSend(boardInfoFileContent);
this.updateBoardIndices();
};
deleteBoard = (boardInfoFileContent: BoardInfoFileContent): void => {
this.boardsAndTasksModel.deleteBoard(boardInfoFileContent.fileId);
this.boardViewModels.remove(boardInfoFileContent.fileId);
this.updateBoardIndices();
};
// view
toggleBoardList = (): void => {
this.isShowingBoadList.value = !this.isShowingBoadList.value;
};
showBoardInList = (boardInfo: BoardInfoFileContent): void => {
const boardViewModel: BoardViewModel = new BoardViewModel(
this.coreViewModel,
this.chatViewModel,
this.storageModel,
this.boardsAndTasksModel,
this,
boardInfo,
);
this.boardViewModels.set(boardInfo.fileId, boardViewModel);
this.chatViewModel.taskBoardSuggestions.set(boardInfo.fileId, [
boardInfo.fileId,
this.boardsAndTasksModel.getBoardName(boardInfo.fileId),
]);
};
selectBoard = (boardViewModel: BoardViewModel): void => {
this.selectedBoardId.value = boardViewModel.boardInfo.fileId;
this.chatViewModel.displayedColor.value = boardViewModel.color.value;
this.storeLastUsedBoard();
};
closeBoard = (): void => {
this.selectedBoardId.value = undefined;
this.chatViewModel.resetColor();
this.storeLastUsedBoard();
};
updateBoardIndices = (): void => {
this.boardIndexManager.update([...this.boardViewModels.value.values()]);
for (const boardViewModel of this.boardViewModels.value.values()) {
boardViewModel.updateIndex();
}
};
// storage
storeLastUsedBoard = (): void => {
const path: string[] = this.getLastUsedBoardPath();
const lastUsedBoardId: string = this.selectedBoardId.value ?? "";
this.storageModel.write(path, lastUsedBoardId);
};
openLastUsedBoard = (): void => {
const path: string[] = this.getLastUsedBoardPath();
const lastUsedBoardId: string | null = this.storageModel.read(path);
if (lastUsedBoardId == null) return;
const boardViewModel: BoardViewModel | undefined =
this.boardViewModels.value.get(lastUsedBoardId);
if (boardViewModel == undefined) return;
this.selectBoard(boardViewModel);
};
// load
loadData = (): void => {
this.boardViewModels.clear();
const boardIds: string[] = this.boardsAndTasksModel.listBoardIds();
for (const boardId of boardIds) {
const boardInfo: BoardInfoFileContent | null =
this.boardsAndTasksModel.getBoardInfo(boardId);
if (boardInfo == null) continue;
this.showBoardInList(boardInfo);
}
this.updateBoardIndices();
this.openLastUsedBoard();
};
// init
constructor(
public coreViewModel: CoreViewModel,
public chatViewModel: ChatViewModel,
storageModel: StorageModel,
boardModel: BoardsAndTasksModel,
) {
this.storageModel = storageModel;
this.boardsAndTasksModel = boardModel;
this.chatViewModel = chatViewModel;
// handlers
boardModel.boardHandlerManager.addHandler(
(boardInfoFileContent: BoardInfoFileContent) => {
this.showBoardInList(boardInfoFileContent);
this.updateBoardIndices();
},
);
}
}
export enum TaskPageViewModelSubPath {
LastUsedBoard = "last-used-board",
LastUsedBoard = "last-used-board",
}
+202 -192
View File
@@ -1,7 +1,7 @@
import * as React from "bloatless-react";
import BoardsAndTasksModel, {
TaskFileContent,
TaskFileContent,
} from "../../Model/Files/boardsAndTasksModel";
import { localeCompare, padZero } from "../../Model/Utility/utility";
@@ -11,225 +11,235 @@ import TaskContainingPageViewModel from "./taskContainingPageViewModel";
import { allowDrag } from "../../View/utility";
export default class TaskViewModel {
boardsAndTasksModel: BoardsAndTasksModel;
boardsAndTasksModel: BoardsAndTasksModel;
containingModel: TaskContainingPageViewModel;
containingModel: TaskContainingPageViewModel;
// data
task: TaskFileContent;
// data
task: TaskFileContent;
get sortingString(): string {
const splitDate: string[] = this.date.value.split("-");
const year = padZero(splitDate[0], 4);
const month = padZero(splitDate[1], 2);
const date = padZero(splitDate[2], 2);
get sortingString(): string {
const splitDate: string[] = this.date.value.split("-");
const year = padZero(splitDate[0], 4);
const month = padZero(splitDate[1], 2);
const date = padZero(splitDate[2], 2);
const splitTime: string[] = this.time.value.split(":");
const hour = padZero(splitTime[0], 2);
const minute = padZero(splitTime[1], 2);
const splitTime: string[] = this.time.value.split(":");
const hour = padZero(splitTime[0], 2);
const minute = padZero(splitTime[1], 2);
const priorityNumber = parseInt(this.priority.value);
const invertedPriority = 100 - priorityNumber;
const priorityNumber = parseInt(this.priority.value);
const invertedPriority = 100 - priorityNumber;
return (
year + month + date + hour + minute + invertedPriority + this.name.value
);
}
// paths
getFilePath = (): string[] => {
return this.boardsAndTasksModel.getTaskFilePath(this.task.fileId);
};
// state
index: React.State<number> = new React.State(0);
boardId: React.State<string> = new React.State("");
name: React.State<string> = new React.State("");
description: React.State<string> = new React.State("");
category: React.State<string> = new React.State("");
status: React.State<string> = new React.State("");
priority: React.State<string> = new React.State("");
date: React.State<string> = new React.State("");
time: React.State<string> = new React.State("");
selectedVersionId: React.State<string> = new React.State("");
versionIds: React.ListState<string> = new React.ListState();
// methods
dragStart = (event: DragEvent): void => {
allowDrag(event);
this.coreViewModel.draggedObject.value = this;
};
setCategoryAndStatus = (category?: string, status?: string): void => {
if (category != undefined) this.category.value = category;
if (status != undefined) this.status.value = status;
this.save();
};
setBoardId = (boardId: string): void => {
this.boardId.value = boardId;
this.save();
};
setDate = (dateISOString: string): void => {
this.date.value = dateISOString;
this.save();
};
// view
open = (): void => {
this.containingModel.selectTask(this);
};
close = (): void => {
this.containingModel.closeTask();
};
closeAndDiscard = (): void => {
this.close();
this.loadTaskData();
}
closeAndSave = (): void => {
this.close();
this.save();
};
updateIndex = (): void => {
const index: number = this.containingModel.taskIndexManager.getIndex(this);
this.index.value = index;
};
updateSuggestions = (): void => {
if (
this.coreViewModel.taskCategorySuggestions.value.has(
this.category.value
) == false
) {
this.coreViewModel.taskCategorySuggestions.add(this.category.value);
return (
year +
month +
date +
hour +
minute +
invertedPriority +
this.name.value
);
}
if (
this.coreViewModel.taskStatusSuggestions.value.has(this.status.value) ==
false
) {
this.coreViewModel.taskStatusSuggestions.add(this.status.value);
}
};
// paths
getFilePath = (): string[] => {
return this.boardsAndTasksModel.getTaskFilePath(this.task.fileId);
};
// settings
save = (): void => {
const newTaskFileContent: TaskFileContent =
BoardsAndTasksModel.createTaskFileContent(
this.task.fileId,
this.name.value,
this.task.boardId
);
// state
index: React.State<number> = new React.State(0);
newTaskFileContent.boardId = this.boardId.value;
newTaskFileContent.description = this.description.value;
newTaskFileContent.status = this.status.value;
newTaskFileContent.category = this.category.value;
newTaskFileContent.priority = this.priority.value;
newTaskFileContent.date = this.date.value;
newTaskFileContent.time = this.time.value;
boardId: React.State<string> = new React.State("");
this.boardsAndTasksModel.updateTaskAndSend(newTaskFileContent);
name: React.State<string> = new React.State("");
description: React.State<string> = new React.State("");
this.containingModel.showTask(newTaskFileContent);
this.containingModel.updateTaskIndices();
category: React.State<string> = new React.State("");
status: React.State<string> = new React.State("");
priority: React.State<string> = new React.State("");
this.updateSuggestions();
};
date: React.State<string> = new React.State("");
time: React.State<string> = new React.State("");
deleteTask = (): void => {
this.close();
this.boardsAndTasksModel.deleteTask(this.task.boardId, this.task.fileId);
this.containingModel.removeTaskFromView(this.task);
};
selectedVersionId: React.State<string> = new React.State("");
versionIds: React.ListState<string> = new React.ListState();
// load
loadVersionIds = (): void => {
const versionIds: string[] = this.boardsAndTasksModel.listTaskVersionIds(
this.task.fileId
);
const sortedVersionIds = versionIds.sort(localeCompare).reverse();
this.versionIds.clear();
this.versionIds.add(...sortedVersionIds);
};
// methods
dragStart = (event: DragEvent): void => {
allowDrag(event);
this.coreViewModel.draggedObject.value = this;
};
switchVersion = (versionId: string): void => {
const taskFileContent: TaskFileContent | null =
this.boardsAndTasksModel.getSpecificTaskFileContent(
this.task.fileId,
versionId
);
if (taskFileContent == null) return;
setCategoryAndStatus = (category?: string, status?: string): void => {
if (category != undefined) this.category.value = category;
if (status != undefined) this.status.value = status;
this.save();
};
this.task = taskFileContent;
this.loadTaskData();
};
setBoardId = (boardId: string): void => {
this.boardId.value = boardId;
this.save();
};
loadAllData = (): void => {
this.loadTaskData();
this.loadVersionIds();
};
setDate = (dateISOString: string): void => {
this.date.value = dateISOString;
this.save();
};
loadTaskData = (): void => {
this.boardId.value = this.task.boardId;
// view
open = (): void => {
this.containingModel.selectTask(this);
};
this.name.value = this.task.name;
this.description.value = this.task.description ?? "";
close = (): void => {
this.containingModel.closeTask();
};
this.category.value = this.task.category ?? "";
this.status.value = this.task.status ?? "";
this.priority.value = this.task.priority ?? "";
closeAndDiscard = (): void => {
this.close();
this.loadTaskData();
};
this.date.value = this.task.date ?? "";
this.time.value = this.task.time ?? "";
closeAndSave = (): void => {
this.close();
this.save();
};
this.selectedVersionId.value = this.task.fileContentId;
updateIndex = (): void => {
const index: number =
this.containingModel.taskIndexManager.getIndex(this);
this.index.value = index;
};
this.updateSuggestions();
};
updateSuggestions = (): void => {
if (
this.coreViewModel.taskCategorySuggestions.value.has(
this.category.value,
) == false
) {
this.coreViewModel.taskCategorySuggestions.add(this.category.value);
}
// init
constructor(
public coreViewModel: CoreViewModel,
public chatViewModel: ChatViewModel,
boardsAndTasksModel: BoardsAndTasksModel,
containingModel: TaskContainingPageViewModel,
taskFileContent: TaskFileContent
) {
this.boardsAndTasksModel = boardsAndTasksModel;
this.containingModel = containingModel;
if (
this.coreViewModel.taskStatusSuggestions.value.has(
this.status.value,
) == false
) {
this.coreViewModel.taskStatusSuggestions.add(this.status.value);
}
};
this.task = taskFileContent;
// settings
save = (): void => {
const newTaskFileContent: TaskFileContent =
BoardsAndTasksModel.createTaskFileContent(
this.task.fileId,
this.name.value,
this.task.boardId,
);
newTaskFileContent.boardId = this.boardId.value;
newTaskFileContent.description = this.description.value;
newTaskFileContent.status = this.status.value;
newTaskFileContent.category = this.category.value;
newTaskFileContent.priority = this.priority.value;
newTaskFileContent.date = this.date.value;
newTaskFileContent.time = this.time.value;
this.boardsAndTasksModel.updateTaskAndSend(newTaskFileContent);
this.containingModel.showTask(newTaskFileContent);
this.containingModel.updateTaskIndices();
this.updateSuggestions();
};
deleteTask = (): void => {
this.close();
this.boardsAndTasksModel.deleteTask(
this.task.boardId,
this.task.fileId,
);
this.containingModel.removeTaskFromView(this.task);
};
// load
this.loadAllData();
loadVersionIds = (): void => {
const versionIds: string[] =
this.boardsAndTasksModel.listTaskVersionIds(this.task.fileId);
const sortedVersionIds = versionIds.sort(localeCompare).reverse();
this.versionIds.clear();
this.versionIds.add(...sortedVersionIds);
};
// subscriptions
this.selectedVersionId.subscribeSilent((selectedVersionId) => {
this.switchVersion(selectedVersionId);
});
}
switchVersion = (versionId: string): void => {
const taskFileContent: TaskFileContent | null =
this.boardsAndTasksModel.getSpecificTaskFileContent(
this.task.fileId,
versionId,
);
if (taskFileContent == null) return;
// utility
static getStringsForFilter = (taskViewModel: TaskViewModel): string[] => {
return [
taskViewModel.task.name,
taskViewModel.task.description ?? "",
taskViewModel.task.category ?? "",
taskViewModel.task.status ?? "",
taskViewModel.task.priority ?? "",
taskViewModel.task.date ?? "",
taskViewModel.task.time ?? "",
];
};
this.task = taskFileContent;
this.loadTaskData();
};
loadAllData = (): void => {
this.loadTaskData();
this.loadVersionIds();
};
loadTaskData = (): void => {
this.boardId.value = this.task.boardId;
this.name.value = this.task.name;
this.description.value = this.task.description ?? "";
this.category.value = this.task.category ?? "";
this.status.value = this.task.status ?? "";
this.priority.value = this.task.priority ?? "";
this.date.value = this.task.date ?? "";
this.time.value = this.task.time ?? "";
this.selectedVersionId.value = this.task.fileContentId;
this.updateSuggestions();
};
// init
constructor(
public coreViewModel: CoreViewModel,
public chatViewModel: ChatViewModel,
boardsAndTasksModel: BoardsAndTasksModel,
containingModel: TaskContainingPageViewModel,
taskFileContent: TaskFileContent,
) {
this.boardsAndTasksModel = boardsAndTasksModel;
this.containingModel = containingModel;
this.task = taskFileContent;
// load
this.loadAllData();
// subscriptions
this.selectedVersionId.subscribeSilent((selectedVersionId) => {
this.switchVersion(selectedVersionId);
});
}
// utility
static getStringsForFilter = (taskViewModel: TaskViewModel): string[] => {
return [
taskViewModel.task.name,
taskViewModel.task.description ?? "",
taskViewModel.task.category ?? "",
taskViewModel.task.status ?? "",
taskViewModel.task.priority ?? "",
taskViewModel.task.date ?? "",
taskViewModel.task.time ?? "",
];
};
}
+66 -66
View File
@@ -3,74 +3,74 @@ import * as React from "bloatless-react";
import { checkDoesObjectMatchSearch } from "../../Model/Utility/utility";
export default class SearchViewModel<T> {
// data
allObjects: React.ListState<T> | React.MapState<T>;
getStringsOfObject: (object: T) => string[];
// data
allObjects: React.ListState<T> | React.MapState<T>;
getStringsOfObject: (object: T) => string[];
// state
appliedQuery: React.State<string> = new React.State("");
searchInput: React.State<string> = new React.State("");
matchingObjects: React.ListState<T>;
// state
appliedQuery: React.State<string> = new React.State("");
searchInput: React.State<string> = new React.State("");
matchingObjects: React.ListState<T>;
suggestions: React.ListState<string>;
suggestions: React.ListState<string>;
// guards
cannotApplySearch: React.State<boolean> = React.createProxyState(
[this.searchInput, this.appliedQuery],
() => this.searchInput.value == this.appliedQuery.value
);
// methods
search = (searchTerm: string): void => {
this.searchInput.value = searchTerm;
this.applySearch();
};
applySearch = (): void => {
this.appliedQuery.value = this.searchInput.value;
this.matchingObjects.clear();
for (const object of this.allObjects.value.values()) {
const doesMatch: boolean = this.checkDoesMatchSearch(object);
if (doesMatch == false) continue;
this.matchingObjects.add(object);
}
};
// init
constructor(
allObjects: React.ListState<T> | React.MapState<T>,
matchingObjects: React.ListState<T>,
getStringsOfObject: (object: T) => string[],
suggestions: React.ListState<string>
) {
this.allObjects = allObjects;
this.matchingObjects = matchingObjects;
this.getStringsOfObject = getStringsOfObject;
this.suggestions = suggestions;
// handle new objects
this.allObjects.handleAddition((newObject: T) => {
const doesMatch: boolean = this.checkDoesMatchSearch(newObject);
if (doesMatch == false) {
this.matchingObjects.remove(newObject);
} else {
if (this.matchingObjects.value.has(newObject)) return;
this.matchingObjects.add(newObject);
this.allObjects.handleRemoval(newObject, () => {
this.matchingObjects.remove(newObject);
});
}
});
}
// utility
checkDoesMatchSearch = (object: T): boolean => {
return checkDoesObjectMatchSearch(
this.appliedQuery.value,
this.getStringsOfObject,
object
// guards
cannotApplySearch: React.State<boolean> = React.createProxyState(
[this.searchInput, this.appliedQuery],
() => this.searchInput.value == this.appliedQuery.value,
);
};
// methods
search = (searchTerm: string): void => {
this.searchInput.value = searchTerm;
this.applySearch();
};
applySearch = (): void => {
this.appliedQuery.value = this.searchInput.value;
this.matchingObjects.clear();
for (const object of this.allObjects.value.values()) {
const doesMatch: boolean = this.checkDoesMatchSearch(object);
if (doesMatch == false) continue;
this.matchingObjects.add(object);
}
};
// init
constructor(
allObjects: React.ListState<T> | React.MapState<T>,
matchingObjects: React.ListState<T>,
getStringsOfObject: (object: T) => string[],
suggestions: React.ListState<string>,
) {
this.allObjects = allObjects;
this.matchingObjects = matchingObjects;
this.getStringsOfObject = getStringsOfObject;
this.suggestions = suggestions;
// handle new objects
this.allObjects.handleAddition((newObject: T) => {
const doesMatch: boolean = this.checkDoesMatchSearch(newObject);
if (doesMatch == false) {
this.matchingObjects.remove(newObject);
} else {
if (this.matchingObjects.value.has(newObject)) return;
this.matchingObjects.add(newObject);
this.allObjects.handleRemoval(newObject, () => {
this.matchingObjects.remove(newObject);
});
}
});
}
// utility
checkDoesMatchSearch = (object: T): boolean => {
return checkDoesObjectMatchSearch(
this.appliedQuery.value,
this.getStringsOfObject,
object,
);
};
}
@@ -3,70 +3,72 @@ import * as React from "bloatless-react";
import TaskViewModel from "../Pages/taskViewModel";
export default class TaskPropertyBulkChangeViewModel {
taskViewModels: React.ListState<TaskViewModel>;
taskViewModels: React.ListState<TaskViewModel>;
// state
inputValue: React.State<string> = new React.State("");
// state
inputValue: React.State<string> = new React.State("");
// guards
cannotSet: React.State<boolean>;
// guards
cannotSet: React.State<boolean>;
// methods
set = (): void => {
if (this.cannotSet.value == true) return;
this.taskViewModels.value.forEach((taskViewModel: TaskViewModel) => {
this.setValue(this.inputValue.value, taskViewModel);
});
};
// methods
set = (): void => {
if (this.cannotSet.value == true) return;
this.taskViewModels.value.forEach((taskViewModel: TaskViewModel) => {
this.setValue(this.inputValue.value, taskViewModel);
});
};
setValue: (newValue: string, taskViewModel: TaskViewModel) => void;
setValue: (newValue: string, taskViewModel: TaskViewModel) => void;
// init
constructor(
taskViewModels: React.ListState<TaskViewModel>,
valueSetter: (newValue: string, taskViewModel: TaskViewModel) => void,
initialValue: string
) {
this.taskViewModels = taskViewModels;
this.inputValue.value = initialValue;
// init
constructor(
taskViewModels: React.ListState<TaskViewModel>,
valueSetter: (newValue: string, taskViewModel: TaskViewModel) => void,
initialValue: string,
) {
this.taskViewModels = taskViewModels;
this.inputValue.value = initialValue;
this.setValue = valueSetter;
this.setValue = valueSetter;
this.cannotSet = React.createProxyState(
[this.inputValue],
() => this.inputValue.value == "" || this.inputValue.value == initialValue
);
}
this.cannotSet = React.createProxyState(
[this.inputValue],
() =>
this.inputValue.value == "" ||
this.inputValue.value == initialValue,
);
}
}
export class TaskCategoryBulkChangeViewModel extends TaskPropertyBulkChangeViewModel {
constructor(
taskViewModels: React.ListState<TaskViewModel>,
initialValue: string
) {
super(
taskViewModels,
(newCategory: string, taskViewModel: TaskViewModel) => {
taskViewModel.category.value = newCategory;
taskViewModel.save();
},
initialValue
);
}
constructor(
taskViewModels: React.ListState<TaskViewModel>,
initialValue: string,
) {
super(
taskViewModels,
(newCategory: string, taskViewModel: TaskViewModel) => {
taskViewModel.category.value = newCategory;
taskViewModel.save();
},
initialValue,
);
}
}
export class TaskStatusBulkChangeViewModel extends TaskPropertyBulkChangeViewModel {
constructor(
taskViewModels: React.ListState<TaskViewModel>,
initialValue: string
) {
super(
taskViewModels,
(newStatus: string, taskViewModel: TaskViewModel) => {
taskViewModel.status.value = newStatus;
taskViewModel.save();
},
initialValue
);
}
constructor(
taskViewModels: React.ListState<TaskViewModel>,
initialValue: string,
) {
super(
taskViewModels,
(newStatus: string, taskViewModel: TaskViewModel) => {
taskViewModel.status.value = newStatus;
taskViewModel.save();
},
initialValue,
);
}
}