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
+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 ?? "",
];
};
}