From bccc15dd1d8c7e075ba7c6a14e07fb39e62a41b4 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Fri, 26 Jul 2024 03:14:55 +0200 Subject: [PATCH] add month grid --- dist/index.css | 50 +++ dist/index.js | 437 ++++++++++++++----- dist/styles.css | 4 + src/Model/Chat/chatModel.ts | 27 +- src/Model/Files/boardsAndTasksModel.ts | 20 +- src/Model/Files/calendarModel.ts | 77 +++- src/Model/Files/fileModel.ts | 40 +- src/View/ChatPages/boardPage.tsx | 2 +- src/View/ChatPages/calendarPage.css | 61 +++ src/View/ChatPages/calendarPage.tsx | 94 +++- src/View/Components/monthGrid.css | 25 ++ src/View/Components/monthGrid.tsx | 39 ++ src/View/Components/textSpan.tsx | 7 + src/View/chatPage.css | 5 + src/View/translations.ts | 13 + src/ViewModel/Chat/chatListViewModel.ts | 6 +- src/ViewModel/Chat/chatMessageViewModel.ts | 12 +- src/ViewModel/Chat/chatViewModel.ts | 6 +- src/ViewModel/Pages/boardViewModel.ts | 19 +- src/ViewModel/Pages/calendarPageViewModel.ts | 95 +++- src/ViewModel/Pages/settingsPageViewModel.ts | 2 +- src/ViewModel/Pages/taskPageViewModel.ts | 6 +- src/ViewModel/Pages/taskViewModel.ts | 13 + 23 files changed, 860 insertions(+), 200 deletions(-) create mode 100644 src/View/ChatPages/calendarPage.css create mode 100644 src/View/Components/monthGrid.css create mode 100644 src/View/Components/monthGrid.tsx create mode 100644 src/View/Components/textSpan.tsx diff --git a/dist/index.css b/dist/index.css index 954a0a24..3fa7bc90 100644 --- a/dist/index.css +++ b/dist/index.css @@ -380,6 +380,10 @@ body[showing-chat] #chat-page-wrapper { overflow-x: auto; padding: 0px; } +#chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar > span { + display: flex; + align-items: center; +} #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar > span.title { padding-left: var(--content-padding); } @@ -447,6 +451,52 @@ body[showing-chat] #chat-page-wrapper { } } +/* src/View/ChatPages/calendarPage.css */ +#chat-page > div > #main > #calendar-page > .pane-wrapper > .pane > .toolbar .input-wrapper { + display: flex; + padding: var(--gap-between-tiles); + gap: var(--gap-between-tiles); +} +#chat-page > div > #main > #calendar-page > .pane-wrapper > .pane > .toolbar .input-wrapper > input { + min-height: unset; + height: calc(var(--control-height) - 2 * var(--gap-between-tiles)); + appearance: textfield; + -moz-appearance: textfield; +} +#calendar-page .input-wrapper > input::-webkit-outer-spin-button, +#calendar-page .input-wrapper > input::-webkit-innner-spin-button { + appearance: none; + -webkit-appearance: none; + margin: 0px; +} +#chat-page > div > #main > #calendar-page > .pane-wrapper > .pane > .toolbar .input-wrapper > .year-input { + width: 4.4rem; +} +#chat-page > div > #main > #calendar-page > .pane-wrapper > .pane > .toolbar .input-wrapper > .month-input { + width: 3.1rem; +} + +/* src/View/Components/monthGrid.css */ +.month-grid-wrapper { + display: grid; + gap: var(--gap-between-tiles); + height: 100%; + animation: slide-up var(--animation-duration); + grid-template-columns: repeat(7, minmax(0px, 1fr)); + grid-auto-rows: 1fr; +} +.month-grid-wrapper .tile > div { + display: flex; + flex-direction: column; + height: 100%; + width: 100%; +} +@media (max-width: 640px) { + .month-grid-wrapper .tile { + padding: 4px; + } +} + /* src/View/ChatPages/messagePage.css */ #chat-page #main #message-page .content { padding: 0px; diff --git a/dist/index.js b/dist/index.js index 801bdafd..e96fc4bd 100644 --- a/dist/index.js +++ b/dist/index.js @@ -175,6 +175,9 @@ ); return proxyState; } + function bulkSubscribe(statesToSubscibe, fn) { + statesToSubscibe.forEach((state) => state.subscribeSilent(fn)); + } function createElement(tagName, attributes = {}, ...children) { const element = document.createElement(tagName); if (attributes != null) @@ -613,6 +616,78 @@ } }; + // src/Model/Files/calendarModel.ts + var CalendarModel = class _CalendarModel { + storageModel; + fileModel; + settingsModel; + // paths + getBasePath = () => { + return this.fileModel.getModelContainerPath("calendar" /* ModelCalendar */); + }; + getViewPath = () => { + return [...this.getBasePath(), "view" /* ModelView */]; + }; + getMonthContainerPath = () => { + return [...this.getBasePath(), "months" /* Months */]; + }; + getMonthPath = (monthString) => { + return [...this.getMonthContainerPath(), monthString]; + }; + // main + storeTaskReference = (taskFileContent) => { + if (taskFileContent.date == void 0) return; + const monthString = _CalendarModel.isoToMonthString( + taskFileContent.date + ); + const monthPath = this.getMonthPath(monthString); + const referencePath = [...monthPath, taskFileContent.fileId]; + this.storageModel.write(referencePath, ""); + }; + listTaskIds = (monthString) => { + const monthPath = this.getMonthPath(monthString); + return this.storageModel.list(monthPath); + }; + generateMonthGrid = (year, month, defaultValueCreator) => { + const date = /* @__PURE__ */ new Date(); + date.setFullYear(year); + date.setMonth(month - 1); + date.setDate(1); + const offset = date.getDay() - parseInt(this.settingsModel.firstDayOfWeek); + date.setMonth(month); + date.setDate(-1); + const daysInMonth = date.getDate() + 1; + const grid = { + offset, + days: {} + }; + for (let i = 0; i < daysInMonth; i++) { + grid.days[i + 1] = defaultValueCreator(); + } + return grid; + }; + // init + constructor(storageModel2, settingsModel2, fileModel) { + this.storageModel = storageModel2; + this.settingsModel = settingsModel2; + this.fileModel = fileModel; + } + // utility + static isoToMonthString = (dateISOString) => { + const [year, month, _] = dateISOString.split("-"); + return _CalendarModel.getMonthString(year, month); + }; + static isoToDateString = (dateISOString) => { + const [year, month, date, _] = dateISOString.split("-"); + return date; + }; + static getMonthString = (year = "", month = "") => { + const paddedYear = year.padStart(4, "0"); + const paddedMonth = month.padStart(2, "0"); + return `${paddedYear}-${paddedMonth}`; + }; + }; + // src/colors.ts var Color = /* @__PURE__ */ ((Color2) => { Color2["Standard"] = "standard"; @@ -628,8 +703,10 @@ // src/Model/Files/boardsAndTasksModel.ts var BoardsAndTasksModel = class _BoardsAndTasksModel { storageModel; + settingsModel; chatModel; fileModel; + calendarModel; // data boardHandlerManager = new HandlerManager(); taskHandlerManager = new HandlerManager(); @@ -731,13 +808,13 @@ this.chatModel.sendMessage("", taskFileContent); }; storeTask = (taskFileContent) => { - console.log(taskFileContent); this.fileModel.storeFileContent(taskFileContent); const taskReferencePath = this.getTaskReferencePath( taskFileContent.boardId, taskFileContent.fileId ); this.storageModel.write(taskReferencePath, ""); + this.calendarModel.storeTaskReference(taskFileContent); }; listTaskIds = (boardId) => { const taskContainerPath = this.getTaskContainerPath(boardId); @@ -773,14 +850,20 @@ this.storageModel.removeRecursively(taskReferencePath); }; // init - constructor(storageModel2, chatModel, fileModel) { + constructor(storageModel2, settingsModel2, chatModel, fileModel) { + this.storageModel = storageModel2; + this.settingsModel = settingsModel2; this.chatModel = chatModel; this.fileModel = fileModel; - this.storageModel = storageModel2; + this.calendarModel = new CalendarModel( + this.storageModel, + this.settingsModel, + this.fileModel + ); } // utility static createBoardInfoFileContent = (fileId, name, color) => { - const fileContent = FileModel.createFileContent( + const fileContent = FileModel2.createFileContent( fileId, "board-info" ); @@ -791,7 +874,7 @@ }; }; static createTaskFileContent = (fileId, name, boardId) => { - const fileContent = FileModel.createFileContent( + const fileContent = FileModel2.createFileContent( fileId, "task" ); @@ -821,60 +904,12 @@ boardId: "" }; - // src/Model/Files/calendarModel.ts - var CalendarModel = class _CalendarModel { - storageModel; - fileModel; - // paths - getBasePath = () => { - return this.fileModel.getModelContainerPath("calendar" /* ModelCalendar */); - }; - getViewPath = () => { - return [...this.getBasePath(), "view" /* ModelView */]; - }; - getMonthContainerPath = () => { - return [...this.getBasePath(), "months" /* Months */]; - }; - getMonthPath = (monthString) => { - return [...this.getMonthContainerPath(), monthString]; - }; - // handlers - handleFileContent = (fileContent) => { - if (checkMatchesObjectStructure(fileContent, TaskFileContentReference) == false) - return; - }; - // main - storeTaskReference = (taskFileContent) => { - if (taskFileContent.date == void 0) return; - const monthString = _CalendarModel.isoToMonthString( - taskFileContent.date - ); - const monthPath = this.getMonthPath(monthString); - const referencePath = [...monthPath, taskFileContent.fileId]; - this.storageModel.write(referencePath, ""); - }; - listTaskIds = (monthString) => { - const monthPath = this.getMonthPath(monthString); - return this.storageModel.list(monthPath); - }; - // init - constructor(storageModel2, fileModel) { - this.storageModel = storageModel2; - this.fileModel = fileModel; - } - // utility - static isoToMonthString = (dateISOString) => { - const [year, month, _] = dateISOString.split("-"); - return `${year}-${month}`; - }; - }; - // src/Model/Files/fileModel.ts - var FileModel = class _FileModel { - chatModel; + var FileModel2 = class _FileModel { storageModel; + settingsModel; + chatModel; boardsAndTasksModel; - calendarModel; // paths getBasePath = () => { return StorageModel.getPath( @@ -908,7 +943,6 @@ const didStore = this.storeFileContent(fileContent); if (didStore == false) return; this.boardsAndTasksModel.handleFileContent(fileContent); - this.calendarModel.handleFileContent(fileContent); }; // methods addFileContentAndSend = (fileContent) => { @@ -957,11 +991,16 @@ return fileContent; }; // init - constructor(chatModel, storageModel2) { + constructor(storageModel2, settingsModel2, chatModel) { this.chatModel = chatModel; + this.settingsModel = settingsModel2; this.storageModel = storageModel2; - this.boardsAndTasksModel = new BoardsAndTasksModel(this.storageModel, chatModel, this); - this.calendarModel = new CalendarModel(this.storageModel, this); + this.boardsAndTasksModel = new BoardsAndTasksModel( + this.storageModel, + this.settingsModel, + chatModel, + this + ); } // utility static generateFileContentId = (creationDate) => { @@ -1077,16 +1116,28 @@ } // paths getBasePath = () => { - return StorageModel.getPath("chat" /* Chat */, filePaths.chat.chatBase(this.id)); + return StorageModel.getPath( + "chat" /* Chat */, + filePaths.chat.chatBase(this.id) + ); }; getInfoPath = () => { - return StorageModel.getPath("chat" /* Chat */, filePaths.chat.info(this.id)); + return StorageModel.getPath( + "chat" /* Chat */, + filePaths.chat.info(this.id) + ); }; getColorPath = () => { - return StorageModel.getPath("chat" /* Chat */, filePaths.chat.color(this.id)); + return StorageModel.getPath( + "chat" /* Chat */, + filePaths.chat.color(this.id) + ); }; getMessageDirPath = () => { - return StorageModel.getPath("chat" /* Chat */, filePaths.chat.messages(this.id)); + return StorageModel.getPath( + "chat" /* Chat */, + filePaths.chat.messages(this.id) + ); }; getMessagePath = (id) => { return [...this.getMessageDirPath(), id]; @@ -1226,7 +1277,7 @@ this.loadInfo(); this.loadColor(); this.subscribe(); - this.fileModel = new FileModel(this, this.storageModel); + this.fileModel = new FileModel2(this.storageModel, this.settingsModel, this); } // utility static generateChatInfo = (primaryChannel) => { @@ -1514,6 +1565,18 @@ this.time.value = this.task.time ?? ""; this.selectedVersionId.value = this.task.fileContentId; }; + // utility + static getStringsForFilter = (taskViewModel) => { + return [ + taskViewModel.task.name, + taskViewModel.task.description ?? "", + taskViewModel.task.category ?? "", + taskViewModel.task.status ?? "", + taskViewModel.task.priority ?? "", + taskViewModel.task.date ?? "", + taskViewModel.task.time ?? "" + ]; + }; }; // src/ViewModel/Pages/calendarPageViewModel.ts @@ -1523,31 +1586,90 @@ this.coreViewModel = coreViewModel; this.storageModel = storageModel2; this.calendarModel = calendarModel; - this.boardAndTasksModel = boardAndTasksModel; + this.boardsAndTasksModel = boardAndTasksModel; + bulkSubscribe([this.selectedYear, this.selectedMonth], () => { + this.loadTasks(); + }); } storageModel; calendarModel; - boardAndTasksModel; + boardsAndTasksModel; + // data + get monthString() { + return CalendarModel.getMonthString( + this.selectedYear.value.toString(), + this.selectedMonth.value.toString() + ); + } // paths getBasePath = () => { return [...this.calendarModel.getViewPath()]; }; // state - taskViewModels = new MapState(); + selectedYear = new State(0); + selectedMonth = new State(0); + monthGrid = new State(void 0); // view + getTaskMapState = (taskFileContent) => { + if (this.monthGrid.value == null) return null; + const dateString = CalendarModel.isoToDateString( + taskFileContent.date ?? "" + ); + if (dateString == void 0) return null; + return this.monthGrid.value.days[dateString]; + }; showTask = (taskFileContent) => { const taskViewModel = new TaskViewModel( this.coreViewModel, - this.boardAndTasksModel, + this.boardsAndTasksModel, null, this, taskFileContent ); - this.taskViewModels.set(taskFileContent.fileId, taskViewModel); + const mapState = this.getTaskMapState(taskFileContent); + mapState?.set(taskFileContent.fileId, taskViewModel); + }; + removeTaskFromList = (taskFileContent) => { + const mapState = this.getTaskMapState(taskFileContent); + mapState?.remove(taskFileContent.fileId); + }; + showToday = () => { + const today = /* @__PURE__ */ new Date(); + this.selectedYear.value = today.getFullYear(); + this.selectedMonth.value = today.getMonth() + 1; + }; + showPreviousMonth = () => { + this.selectedMonth.value -= 1; + if (this.selectedMonth.value <= 0) { + this.selectedYear.value -= 1; + this.selectedMonth.value = 12; + } + }; + showNextMonth = () => { + this.selectedMonth.value += 1; + if (this.selectedMonth.value >= 13) { + this.selectedYear.value += 1; + this.selectedMonth.value = 1; + } }; // storage // load + loadTasks = () => { + this.monthGrid.value = this.calendarModel.generateMonthGrid( + this.selectedYear.value, + this.selectedMonth.value, + () => new MapState() + ); + const taskIds = this.calendarModel.listTaskIds(this.monthString); + for (const taskId of taskIds) { + const taskFileContent = this.boardsAndTasksModel.getLatestTaskFileContent(taskId); + if (taskFileContent == null) continue; + this.showTask(taskFileContent); + } + }; loadData = () => { + this.loadTasks(); + this.showToday(); }; }; @@ -1568,7 +1690,9 @@ sender; dateSent; body = new State(""); - status = new State(void 0); + status = new State( + void 0 + ); sentByUser; // state isPresentingInfoModal = new State(false); @@ -1915,18 +2039,6 @@ boardViewModel.updateIndex(); } }; - // filter - getStringsFromTaskViewModel = (taskViewModel) => { - return [ - taskViewModel.task.name, - taskViewModel.task.description ?? "", - taskViewModel.task.category ?? "", - taskViewModel.task.status ?? "", - taskViewModel.task.priority ?? "", - taskViewModel.task.date ?? "", - taskViewModel.task.time ?? "" - ]; - }; // load loadListRelevantData = () => { this.name.value = this.boardInfo.name; @@ -1992,7 +2104,9 @@ // state newBoardNameInput = new State(""); boardViewModels = new MapState(); - selectedBoardId = new State(void 0); + selectedBoardId = new State( + void 0 + ); // guards cannotCreateBoard = createProxyState( [this.newBoardNameInput], @@ -2083,7 +2197,7 @@ this.calendarViewModel = new CalendarPageViewModel( coreViewModel, this.storageModel, - this.chatModel.fileModel.calendarModel, + this.chatModel.fileModel.boardsAndTasksModel.calendarModel, this.chatModel.fileModel.boardsAndTasksModel ); this.taskPageViewModel = new TaskPageViewModel( @@ -2182,7 +2296,9 @@ // state newChatPrimaryChannel = new State(""); chatViewModels = new ListState(); - selectedChat = new State(void 0); + selectedChat = new State( + void 0 + ); // guards cannotCreateChat = createProxyState( [this.newChatPrimaryChannel], @@ -2238,36 +2354,25 @@ }; }; - // src/View/ChatPages/calendarPage.tsx - function CalendarPage(calendarPageViewModel) { - calendarPageViewModel.loadData(); - return /* @__PURE__ */ createElement("div", { id: "calendar-page" }); - } - - // src/View/Components/ribbonButton.tsx - function RibbonButton(label, icon, isSelected, select) { - return /* @__PURE__ */ createElement( - "button", - { - class: "ribbon-button", - "aria-label": label, - "toggle:selected": isSelected, - "on:click": select - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, icon) - ); - } - - // src/View/Components/chatViewToggleButton.tsx - function ChatViewToggleButton(label, icon, page, chatViewModel) { - function select() { - chatViewModel.selectedPage.value = page; + // src/View/Components/monthGrid.tsx + function MonthGrid2(monthGrid) { + const offsetElements = []; + for (let i = 0; i < monthGrid.offset; i++) { + offsetElements.push(/* @__PURE__ */ createElement("div", null)); } - const isSelected = createProxyState( - [chatViewModel.selectedPage], - () => chatViewModel.selectedPage.value == page - ); - return RibbonButton(label, icon, isSelected, select); + const converter = (taskViewModel) => { + return /* @__PURE__ */ createElement("span", { class: "ellipsis secondary" }, taskViewModel.task.name); + }; + return /* @__PURE__ */ createElement("div", { class: "month-grid-wrapper" }, ...offsetElements, ...Object.entries(monthGrid.days).map((entry) => { + const [date, mapState] = entry; + return /* @__PURE__ */ createElement("button", { class: "tile" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, date), /* @__PURE__ */ createElement( + "div", + { + class: "flex-column gap clip", + "children:append": [mapState, converter] + } + ))); + })); } // src/View/translations.ts @@ -2402,6 +2507,15 @@ filterTasksHeadline: "Filter Tasks", /// renameCategoryInputPlaceholder: "Rename category," + }, + calendar: { + todayButtonAudioLabel: "go to today", + previousMonthButtonAudioLabel: "previous month", + nextMonthButtonAudioLabel: "next month", + yearInputAudioLabel: "year", + monthInputAudioLabel: "month", + yearInputPlaceholder: "2000", + monthInputPlaceholder: "01" } } }; @@ -2411,6 +2525,97 @@ var language = navigator.language.substring(0, 2); var translations = allTranslations[language] || allTranslations.en; + // src/View/ChatPages/calendarPage.tsx + function CalendarPage(calendarPageViewModel) { + calendarPageViewModel.loadData(); + const mainContent = createProxyState( + [calendarPageViewModel.monthGrid], + () => { + if (calendarPageViewModel.monthGrid.value == void 0) { + return /* @__PURE__ */ createElement("div", null); + } else { + return MonthGrid2(calendarPageViewModel.monthGrid.value); + } + } + ); + return /* @__PURE__ */ createElement("div", { id: "calendar-page" }, /* @__PURE__ */ createElement("div", { class: "pane-wrapper" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement( + "button", + { + class: "ghost", + "aria-label": translations.chatPage.calendar.todayButtonAudioLabel, + "on:click": calendarPageViewModel.showToday + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "today") + )), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement( + "button", + { + class: "ghost", + "aria-label": translations.chatPage.calendar.previousMonthButtonAudioLabel, + "on:click": calendarPageViewModel.showPreviousMonth + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_back") + ), /* @__PURE__ */ createElement("span", { class: "input-wrapper" }, /* @__PURE__ */ createElement( + "input", + { + class: "year-input", + type: "number", + "aria-label": translations.chatPage.calendar.yearInputAudioLabel, + placeholder: translations.chatPage.calendar.yearInputPlaceholder, + "bind:value": calendarPageViewModel.selectedYear + } + ), /* @__PURE__ */ createElement( + "input", + { + class: "month-input", + type: "number", + "aria-label": translations.chatPage.calendar.monthInputAudioLabel, + placeholder: translations.chatPage.calendar.monthInputPlaceholder, + "bind:value": calendarPageViewModel.selectedMonth + } + )), /* @__PURE__ */ createElement( + "button", + { + class: "ghost", + "aria-label": translations.chatPage.calendar.nextMonthButtonAudioLabel, + "on:click": calendarPageViewModel.showNextMonth + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") + )), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement( + "button", + { + class: "ghost", + "aria-label": translations.chatPage.task.createTaskButtonAudioLabel + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "add") + ))), /* @__PURE__ */ createElement("div", { class: "content", "children:set": mainContent })))); + } + + // src/View/Components/ribbonButton.tsx + function RibbonButton(label, icon, isSelected, select) { + return /* @__PURE__ */ createElement( + "button", + { + class: "ribbon-button", + "aria-label": label, + "toggle:selected": isSelected, + "on:click": select + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, icon) + ); + } + + // src/View/Components/chatViewToggleButton.tsx + function ChatViewToggleButton(label, icon, page, chatViewModel) { + function select() { + chatViewModel.selectedPage.value = page; + } + const isSelected = createProxyState( + [chatViewModel.selectedPage], + () => chatViewModel.selectedPage.value == page + ); + return RibbonButton(label, icon, isSelected, select); + } + // src/View/Modals/chatMessageInfoModal.tsx function ChatMessageInfoModal(chatMessageViewModel) { return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": chatMessageViewModel.isPresentingInfoModal }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.chatPage.message.messageInfoHeadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "account_circle"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.sentBy), /* @__PURE__ */ createElement("b", { class: "break-word" }, chatMessageViewModel.sender))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "schedule"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.timeSent), /* @__PURE__ */ createElement("b", { class: "break-word" }, chatMessageViewModel.dateSent))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.channel), /* @__PURE__ */ createElement("b", { class: "break-word" }, chatMessageViewModel.channel))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "description"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.messageContent), /* @__PURE__ */ createElement( @@ -3281,7 +3486,7 @@ boardViewModel.taskViewModels, boardViewModel.filteredTaskViewModels, TaskViewModelToEntry, - boardViewModel.getStringsFromTaskViewModel, + TaskViewModel.getStringsForFilter, boardViewModel.isPresentingFilterModal ), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal })); } diff --git a/dist/styles.css b/dist/styles.css index 22bdc72a..8e4c0c27 100644 --- a/dist/styles.css +++ b/dist/styles.css @@ -28,6 +28,10 @@ ANIMATION --animation-duration: 0.4s; } +.slide-up { + animation: slide-up var(--animation-duration) +} + /* COLORS */ diff --git a/src/Model/Chat/chatModel.ts b/src/Model/Chat/chatModel.ts index 9a079547..5ca178a2 100644 --- a/src/Model/Chat/chatModel.ts +++ b/src/Model/Chat/chatModel.ts @@ -9,7 +9,10 @@ import { parseValidObject, stringify, } from "../Utility/utility"; -import StorageModel, { StorageModelSubPath, filePaths } from "../Global/storageModel"; +import StorageModel, { + StorageModelSubPath, + filePaths, +} from "../Global/storageModel"; import { decryptString, encryptString } from "../Utility/crypto"; import ChatListModel from "./chatListModel"; @@ -39,19 +42,31 @@ export default class ChatModel { // paths getBasePath = (): string[] => { - return StorageModel.getPath(StorageModelSubPath.Chat, filePaths.chat.chatBase(this.id)); + return StorageModel.getPath( + StorageModelSubPath.Chat, + filePaths.chat.chatBase(this.id) + ); }; getInfoPath = (): string[] => { - return StorageModel.getPath(StorageModelSubPath.Chat, filePaths.chat.info(this.id)); + return StorageModel.getPath( + StorageModelSubPath.Chat, + filePaths.chat.info(this.id) + ); }; getColorPath = (): string[] => { - return StorageModel.getPath(StorageModelSubPath.Chat, filePaths.chat.color(this.id)); + return StorageModel.getPath( + StorageModelSubPath.Chat, + filePaths.chat.color(this.id) + ); }; getMessageDirPath = (): string[] => { - return StorageModel.getPath(StorageModelSubPath.Chat, filePaths.chat.messages(this.id)); + return StorageModel.getPath( + StorageModelSubPath.Chat, + filePaths.chat.messages(this.id) + ); }; getMessagePath = (id: string): string[] => { @@ -236,7 +251,7 @@ export default class ChatModel { this.loadColor(); this.subscribe(); - this.fileModel = new FileModel(this, this.storageModel); + this.fileModel = new FileModel(this.storageModel, this.settingsModel, this); } // utility diff --git a/src/Model/Files/boardsAndTasksModel.ts b/src/Model/Files/boardsAndTasksModel.ts index 25bb8387..a40c9639 100644 --- a/src/Model/Files/boardsAndTasksModel.ts +++ b/src/Model/Files/boardsAndTasksModel.ts @@ -6,17 +6,22 @@ import { } from "../Utility/typeSafety"; import FileModel, { FileContent, FileModelSubPath } from "./fileModel"; +import CalendarModel from "./calendarModel"; import ChatModel from "../Chat/chatModel"; import { Color } from "../../colors"; import { HandlerManager } from "../Utility/utility"; +import SettingsModel from "../Global/settingsModel"; import StorageModel from "../Global/storageModel"; import { v4 } from "uuid"; export default class BoardsAndTasksModel { storageModel: StorageModel; + settingsModel: SettingsModel; chatModel: ChatModel; fileModel: FileModel; + calendarModel: CalendarModel; + // data boardHandlerManager: HandlerManager = new HandlerManager(); @@ -153,8 +158,6 @@ export default class BoardsAndTasksModel { }; storeTask = (taskFileContent: TaskFileContent): void => { - console.log(taskFileContent); - // store info this.fileModel.storeFileContent(taskFileContent); @@ -164,6 +167,9 @@ export default class BoardsAndTasksModel { taskFileContent.fileId ); this.storageModel.write(taskReferencePath, ""); + + // add to calendar + this.calendarModel.storeTaskReference(taskFileContent); }; listTaskIds = (boardId: string): string[] => { @@ -214,12 +220,20 @@ export default class BoardsAndTasksModel { // init constructor( storageModel: StorageModel, + settingsModel: SettingsModel, chatModel: ChatModel, fileModel: FileModel ) { + this.storageModel = storageModel; + this.settingsModel = settingsModel; this.chatModel = chatModel; this.fileModel = fileModel; - this.storageModel = storageModel; + + this.calendarModel = new CalendarModel( + this.storageModel, + this.settingsModel, + this.fileModel + ); } // utility diff --git a/src/Model/Files/calendarModel.ts b/src/Model/Files/calendarModel.ts index 937cfaf5..8767831a 100644 --- a/src/Model/Files/calendarModel.ts +++ b/src/Model/Files/calendarModel.ts @@ -1,18 +1,15 @@ -// this file extends the BoardsAndTasksModel regarding calendar features. // it is responsible for managing calendar-realted reference files for tasks. -import BoardsAndTasksModel, { - TaskFileContent, - TaskFileContentReference, -} from "./boardsAndTasksModel"; -import FileModel, { FileContent, FileModelSubPath } from "./fileModel"; +import FileModel, { FileModelSubPath } from "./fileModel"; +import SettingsModel from "../Global/settingsModel"; import StorageModel from "../Global/storageModel"; -import { checkMatchesObjectStructure } from "../Utility/typeSafety"; +import { TaskFileContent } from "./boardsAndTasksModel"; export default class CalendarModel { storageModel: StorageModel; fileModel: FileModel; + settingsModel: SettingsModel; // paths getBasePath = (): string[] => { @@ -31,18 +28,10 @@ export default class CalendarModel { return [...this.getMonthContainerPath(), monthString]; }; - // handlers - handleFileContent = (fileContent: FileContent): void => { - if ( - checkMatchesObjectStructure(fileContent, TaskFileContentReference) == - false - ) - return; - }; - // main storeTaskReference = (taskFileContent: TaskFileContent): void => { if (taskFileContent.date == undefined) return; + const monthString: string = CalendarModel.isoToMonthString( taskFileContent.date ); @@ -58,21 +47,73 @@ export default class CalendarModel { return this.storageModel.list(monthPath); }; + generateMonthGrid = ( + year: number, + month: number, + defaultValueCreator: () => T + ): MonthGrid => { + // get first day of month + const date: Date = new Date(); + date.setFullYear(year); + date.setMonth(month - 1); + date.setDate(1); + + // get offset + const offset: number = + date.getDay() - parseInt(this.settingsModel.firstDayOfWeek); + + // get total day count + date.setMonth(month); + date.setDate(-1); + const daysInMonth: number = date.getDate() + 1; + + const grid: MonthGrid = { + offset, + days: {}, + }; + + for (let i = 0; i < daysInMonth; i++) { + grid.days[i + 1] = defaultValueCreator(); + } + + return grid; + }; + // init constructor( storageModel: StorageModel, - fileModel: FileModel, ) { + settingsModel: SettingsModel, + fileModel: FileModel + ) { this.storageModel = storageModel; + this.settingsModel = settingsModel; this.fileModel = fileModel; } // utility static isoToMonthString = (dateISOString: string): string => { const [year, month, _] = dateISOString.split("-"); - return `${year}-${month}`; + return CalendarModel.getMonthString(year, month); + }; + + static isoToDateString = (dateISOString: string): string | undefined => { + const [year, month, date, _] = dateISOString.split("-"); + return date; + }; + + static getMonthString = (year: string = "", month: string = ""): string => { + const paddedYear: string = year.padStart(4, "0"); + const paddedMonth: string = month.padStart(2, "0"); + return `${paddedYear}-${paddedMonth}`; }; } export enum CalendarModelSubPaths { Months = "months", } + +// types +export interface MonthGrid { + offset: number; + days: { [date: string]: T }; +} diff --git a/src/Model/Files/fileModel.ts b/src/Model/Files/fileModel.ts index eb3bbac7..cf463b4a 100644 --- a/src/Model/Files/fileModel.ts +++ b/src/Model/Files/fileModel.ts @@ -1,26 +1,28 @@ // this file is responsible for managing files within chats. import { DATA_VERSION, ValidObject } from "../Utility/typeSafety"; +import StorageModel, { + StorageModelSubPath, + filePaths, +} from "../Global/storageModel"; import { - HandlerManager, StringEntryObject, createTimestamp, parseValidObject, stringify, } from "../Utility/utility"; -import StorageModel, { StorageModelSubPath, filePaths } from "../Global/storageModel"; import BoardsAndTasksModel from "./boardsAndTasksModel"; -import CalendarModel from "./calendarModel"; import ChatModel from "../Chat/chatModel"; +import SettingsModel from "../Global/settingsModel"; import { v4 } from "uuid"; export default class FileModel { - chatModel: ChatModel; storageModel: StorageModel; + settingsModel: SettingsModel; + chatModel: ChatModel; boardsAndTasksModel: BoardsAndTasksModel; - calendarModel: CalendarModel; // paths getBasePath = (): string[] => { @@ -34,9 +36,7 @@ export default class FileModel { return [...this.getBasePath(), FileModelSubPath.Data]; }; - getModelContainerPath = ( - modelName: FileModelSubPath - ): string[] => { + getModelContainerPath = (modelName: FileModelSubPath): string[] => { return [...this.getBasePath(), FileModelSubPath.Model, modelName]; }; @@ -65,7 +65,6 @@ export default class FileModel { if (didStore == false) return; this.boardsAndTasksModel.handleFileContent(fileContent); - this.calendarModel.handleFileContent(fileContent); }; // methods @@ -135,14 +134,23 @@ export default class FileModel { ); return fileContent; }; - + // init - constructor(chatModel: ChatModel, storageModel: StorageModel) { + constructor( + storageModel: StorageModel, + settingsModel: SettingsModel, + chatModel: ChatModel + ) { this.chatModel = chatModel; + this.settingsModel = settingsModel; this.storageModel = storageModel; - this.boardsAndTasksModel = new BoardsAndTasksModel(this.storageModel, chatModel, this); - this.calendarModel = new CalendarModel(this.storageModel, this); + this.boardsAndTasksModel = new BoardsAndTasksModel( + this.storageModel, + this.settingsModel, + chatModel, + this + ); } // utility @@ -175,10 +183,12 @@ export enum FileModelSubPath { ModelView = "view", ModelTask = "tasks", ModelCalendar = "calendar", -}; +} // types -export interface FileContent extends ValidObject, StringEntryObject { +export interface FileContent + extends ValidObject, + StringEntryObject { fileId: string; fileContentId: string; creationDate: string; diff --git a/src/View/ChatPages/boardPage.tsx b/src/View/ChatPages/boardPage.tsx index ef2c82ae..08c925a3 100644 --- a/src/View/ChatPages/boardPage.tsx +++ b/src/View/ChatPages/boardPage.tsx @@ -119,7 +119,7 @@ export function BoardPage(boardViewModel: BoardViewModel) { boardViewModel.taskViewModels, boardViewModel.filteredTaskViewModels, TaskViewModelToEntry, - boardViewModel.getStringsFromTaskViewModel, + TaskViewModel.getStringsForFilter, boardViewModel.isPresentingFilterModal )}
diff --git a/src/View/ChatPages/calendarPage.css b/src/View/ChatPages/calendarPage.css new file mode 100644 index 00000000..b593ae61 --- /dev/null +++ b/src/View/ChatPages/calendarPage.css @@ -0,0 +1,61 @@ +#chat-page + > div + > #main + > #calendar-page + > .pane-wrapper + > .pane + > .toolbar + .input-wrapper { + display: flex; + + padding: var(--gap-between-tiles); + gap: var(--gap-between-tiles); +} + +#chat-page + > div + > #main + > #calendar-page + > .pane-wrapper + > .pane + > .toolbar + .input-wrapper + > input { + min-height: unset; + height: calc(var(--control-height) - 2 * var(--gap-between-tiles)); + + appearance: textfield; + -moz-appearance: textfield; +} + +#calendar-page .input-wrapper > input::-webkit-outer-spin-button, +#calendar-page .input-wrapper > input::-webkit-innner-spin-button { + appearance: none; + -webkit-appearance: none; + + margin: 0px; +} + +#chat-page + > div + > #main + > #calendar-page + > .pane-wrapper + > .pane + > .toolbar + .input-wrapper + > .year-input { + width: 4.4rem; +} + +#chat-page + > div + > #main + > #calendar-page + > .pane-wrapper + > .pane + > .toolbar + .input-wrapper + > .month-input { + width: 3.1rem; +} diff --git a/src/View/ChatPages/calendarPage.tsx b/src/View/ChatPages/calendarPage.tsx index 4071f663..9d1468f4 100644 --- a/src/View/ChatPages/calendarPage.tsx +++ b/src/View/ChatPages/calendarPage.tsx @@ -1,9 +1,101 @@ +import "./calendarPage.css"; + import * as React from "bloatless-react"; import CalendarPageViewModel from "../../ViewModel/Pages/calendarPageViewModel"; +import { MonthGrid } from "../Components/monthGrid"; +import { TaskViewModelToEntry } from "../Components/taskEntry"; +import { createPropertyValueIndexState } from "../Components/propertyValueList"; +import { translations } from "../translations"; export function CalendarPage(calendarPageViewModel: CalendarPageViewModel) { calendarPageViewModel.loadData(); - return
; + const mainContent = React.createProxyState( + [calendarPageViewModel.monthGrid], + () => { + if (calendarPageViewModel.monthGrid.value == undefined) { + return
; + } else { + return MonthGrid(calendarPageViewModel.monthGrid.value); + } + } + ); + + return ( +
+
+
+
+ + + + + + + + + + + + + + +
+
+
+
+
+ ); } diff --git a/src/View/Components/monthGrid.css b/src/View/Components/monthGrid.css new file mode 100644 index 00000000..52979bff --- /dev/null +++ b/src/View/Components/monthGrid.css @@ -0,0 +1,25 @@ +.month-grid-wrapper { + display: grid; + gap: var(--gap-between-tiles); + + height: 100%; + + animation: slide-up var(--animation-duration); + + grid-template-columns: repeat(7, minmax(0px, 1fr)); + grid-auto-rows: 1fr; +} + +.month-grid-wrapper .tile > div { + display: flex; + flex-direction: column; + + height: 100%; + width: 100%; +} + +@media (max-width: 640px) { + .month-grid-wrapper .tile { + padding: 4px; + } +} diff --git a/src/View/Components/monthGrid.tsx b/src/View/Components/monthGrid.tsx new file mode 100644 index 00000000..cafb1ed7 --- /dev/null +++ b/src/View/Components/monthGrid.tsx @@ -0,0 +1,39 @@ +import "./monthGrid.css"; + +import * as React from "bloatless-react"; +import { MonthGrid } from "../../Model/Files/calendarModel"; +import TaskViewModel from "../../ViewModel/Pages/taskViewModel"; + +export function MonthGrid(monthGrid: MonthGrid>) { + const offsetElements: HTMLElement[] = []; + for (let i = 0; i < monthGrid.offset; i++) { + offsetElements.push(
); + } + + const converter: React.StateItemConverter = ( + taskViewModel + ) => { + return {taskViewModel.task.name}; + }; + + return ( +
+ {...offsetElements} + {...Object.entries(monthGrid.days).map((entry) => { + const [date, mapState] = entry; + + return ( + + ); + })} +
+ ); +} diff --git a/src/View/Components/textSpan.tsx b/src/View/Components/textSpan.tsx new file mode 100644 index 00000000..fecbd1da --- /dev/null +++ b/src/View/Components/textSpan.tsx @@ -0,0 +1,7 @@ +import * as React from "bloatless-react"; + +export const StringToTextSpan: React.StateItemConverter = ( + string: string +) => { + return {string} +}; diff --git a/src/View/chatPage.css b/src/View/chatPage.css index 9ab06dcf..cc8ea827 100644 --- a/src/View/chatPage.css +++ b/src/View/chatPage.css @@ -195,6 +195,11 @@ body[showing-chat] #chat-page-wrapper { padding: 0px; } +#chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar > span { + display: flex; + align-items: center; +} + #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar > span.title { padding-left: var(--content-padding); } diff --git a/src/View/translations.ts b/src/View/translations.ts index 9d8833eb..7a7f6a42 100644 --- a/src/View/translations.ts +++ b/src/View/translations.ts @@ -185,6 +185,19 @@ const englishTranslations = { renameCategoryInputPlaceholder: "Rename category," }, + + calendar: { + todayButtonAudioLabel: "go to today", + + previousMonthButtonAudioLabel: "previous month", + nextMonthButtonAudioLabel: "next month", + + yearInputAudioLabel: "year", + monthInputAudioLabel: "month", + + yearInputPlaceholder: "2000", + monthInputPlaceholder: "01", + } }, }; diff --git a/src/ViewModel/Chat/chatListViewModel.ts b/src/ViewModel/Chat/chatListViewModel.ts index 8eaa52bb..69f44840 100644 --- a/src/ViewModel/Chat/chatListViewModel.ts +++ b/src/ViewModel/Chat/chatListViewModel.ts @@ -23,9 +23,9 @@ export default class ChatListViewModel { newChatPrimaryChannel: React.State = new React.State(""); chatViewModels: React.ListState = new React.ListState(); - selectedChat: React.State = new React.State< - ChatViewModel | undefined - >(undefined); + selectedChat: React.State = new React.State( + undefined + ); // guards cannotCreateChat = React.createProxyState( diff --git a/src/ViewModel/Chat/chatMessageViewModel.ts b/src/ViewModel/Chat/chatMessageViewModel.ts index d47c02e2..8911f29a 100644 --- a/src/ViewModel/Chat/chatMessageViewModel.ts +++ b/src/ViewModel/Chat/chatMessageViewModel.ts @@ -14,11 +14,11 @@ export default class ChatMessageViewModel { sender: string; dateSent: string; body: React.State = new React.State(""); - status: React.State = new React.State< - ChatMessageStatus | undefined - >(undefined); + status: React.State = new React.State( + undefined + ); sentByUser: boolean; - + // state isPresentingInfoModal: React.State = new React.State(false); @@ -36,11 +36,11 @@ export default class ChatMessageViewModel { // view showInfoModal = (): void => { this.isPresentingInfoModal.value = true; - } + }; hideInfoModal = (): void => { this.isPresentingInfoModal.value = false; - } + }; // load loadData = (): void => { diff --git a/src/ViewModel/Chat/chatViewModel.ts b/src/ViewModel/Chat/chatViewModel.ts index 33c59d9e..d15c6729 100644 --- a/src/ViewModel/Chat/chatViewModel.ts +++ b/src/ViewModel/Chat/chatViewModel.ts @@ -27,8 +27,8 @@ export default class ChatViewModel { settingsPageViewModel: SettingsPageViewModel; // state - displayedColor: React.State = new React.State(Color.Standard); - selectedPage: React.State = new React.State( + displayedColor: React.State = new React.State(Color.Standard); + selectedPage: React.State = new React.State( ChatPageType.Messages ); @@ -99,7 +99,7 @@ export default class ChatViewModel { this.calendarViewModel = new CalendarPageViewModel( coreViewModel, this.storageModel, - this.chatModel.fileModel.calendarModel, + this.chatModel.fileModel.boardsAndTasksModel.calendarModel, this.chatModel.fileModel.boardsAndTasksModel ); this.taskPageViewModel = new TaskPageViewModel( diff --git a/src/ViewModel/Pages/boardViewModel.ts b/src/ViewModel/Pages/boardViewModel.ts index 57a3cfec..3c1adf34 100644 --- a/src/ViewModel/Pages/boardViewModel.ts +++ b/src/ViewModel/Pages/boardViewModel.ts @@ -27,15 +27,15 @@ export default class BoardViewModel { // state name: React.State = new React.State(""); - color: React.State = new React.State(Color.Standard); + color: React.State = new React.State(Color.Standard); index: React.State = new React.State(0); - selectedPage: React.State = new React.State( + selectedPage: React.State = new React.State( BoardPageType.List ); selectedTaskViewModel: React.State = - new React.State(undefined); + new React.State(undefined); isSelected: React.State; isPresentingSettingsModal: React.State = new React.State(false); @@ -190,19 +190,6 @@ export default class BoardViewModel { } }; - // filter - getStringsFromTaskViewModel = (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 ?? "", - ]; - }; - // load loadListRelevantData = (): void => { this.name.value = this.boardInfo.name; diff --git a/src/ViewModel/Pages/calendarPageViewModel.ts b/src/ViewModel/Pages/calendarPageViewModel.ts index aacc434e..5401b434 100644 --- a/src/ViewModel/Pages/calendarPageViewModel.ts +++ b/src/ViewModel/Pages/calendarPageViewModel.ts @@ -3,8 +3,8 @@ import * as React from "bloatless-react"; import BoardsAndTasksModel, { TaskFileContent, } from "../../Model/Files/boardsAndTasksModel"; +import CalendarModel, { MonthGrid } from "../../Model/Files/calendarModel"; -import CalendarModel from "../../Model/Files/calendarModel"; import CoreViewModel from "../Global/coreViewModel"; import StorageModel from "../../Model/Global/storageModel"; import TaskViewModel from "./taskViewModel"; @@ -12,7 +12,15 @@ import TaskViewModel from "./taskViewModel"; export default class CalendarPageViewModel { storageModel: StorageModel; calendarModel: CalendarModel; - boardAndTasksModel: BoardsAndTasksModel; + boardsAndTasksModel: BoardsAndTasksModel; + + // data + get monthString(): string { + return CalendarModel.getMonthString( + this.selectedYear.value.toString(), + this.selectedMonth.value.toString() + ); + } // paths getBasePath = (): string[] => { @@ -20,35 +28,106 @@ export default class CalendarPageViewModel { }; // state - taskViewModels: React.MapState = new React.MapState(); + selectedYear: React.State = new React.State(0); + selectedMonth: React.State = new React.State(0); + + monthGrid: React.State> | undefined> = + new React.State(undefined); // view + getTaskMapState = ( + taskFileContent: TaskFileContent + ): React.MapState | null => { + if (this.monthGrid.value == null) return null; + + const dateString: string | undefined = CalendarModel.isoToDateString( + taskFileContent.date ?? "" + ); + if (dateString == undefined) return null; + return this.monthGrid.value.days[dateString]; + }; + showTask = (taskFileContent: TaskFileContent): void => { const taskViewModel: TaskViewModel = new TaskViewModel( this.coreViewModel, - this.boardAndTasksModel, + this.boardsAndTasksModel, null, this, taskFileContent ); - this.taskViewModels.set(taskFileContent.fileId, taskViewModel); + const mapState: React.MapState | null = + this.getTaskMapState(taskFileContent); + mapState?.set(taskFileContent.fileId, taskViewModel); + }; + + removeTaskFromList = (taskFileContent: TaskFileContent): void => { + const mapState: React.MapState | null = + this.getTaskMapState(taskFileContent); + mapState?.remove(taskFileContent.fileId); + }; + + showToday = (): void => { + const today: Date = new Date(); + this.selectedYear.value = today.getFullYear(); + this.selectedMonth.value = today.getMonth() + 1; + }; + + 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; + } }; // storage // load - loadData = (): void => {} + loadTasks = (): 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); + } + }; + + loadData = (): void => { + this.loadTasks(); + this.showToday(); + }; // init constructor( public coreViewModel: CoreViewModel, storageModel: StorageModel, calendarModel: CalendarModel, - boardAndTasksModel: BoardsAndTasksModel, + boardAndTasksModel: BoardsAndTasksModel ) { this.storageModel = storageModel; this.calendarModel = calendarModel; - this.boardAndTasksModel = boardAndTasksModel; + this.boardsAndTasksModel = boardAndTasksModel; + + React.bulkSubscribe([this.selectedYear, this.selectedMonth], () => { + this.loadTasks(); + }); } } diff --git a/src/ViewModel/Pages/settingsPageViewModel.ts b/src/ViewModel/Pages/settingsPageViewModel.ts index 6c576d7e..1392255a 100644 --- a/src/ViewModel/Pages/settingsPageViewModel.ts +++ b/src/ViewModel/Pages/settingsPageViewModel.ts @@ -21,7 +21,7 @@ export default class SettingsPageViewModel { this.shouldShowEncryptionKey.value == true ? "text" : "password" ); - color: React.State = new React.State(Color.Standard); + color: React.State = new React.State(Color.Standard); // guards cannotSetPrimaryChannel: React.State = React.createProxyState( diff --git a/src/ViewModel/Pages/taskPageViewModel.ts b/src/ViewModel/Pages/taskPageViewModel.ts index ccb52f37..4e005789 100644 --- a/src/ViewModel/Pages/taskPageViewModel.ts +++ b/src/ViewModel/Pages/taskPageViewModel.ts @@ -39,9 +39,9 @@ export default class TaskPageViewModel { boardViewModels: React.MapState = new React.MapState(); - selectedBoardId: React.State = new React.State< - string | undefined - >(undefined); + selectedBoardId: React.State = new React.State( + undefined + ); // guards cannotCreateBoard: React.State = React.createProxyState( diff --git a/src/ViewModel/Pages/taskViewModel.ts b/src/ViewModel/Pages/taskViewModel.ts index 76538f85..70f877aa 100644 --- a/src/ViewModel/Pages/taskViewModel.ts +++ b/src/ViewModel/Pages/taskViewModel.ts @@ -200,4 +200,17 @@ export default class TaskViewModel { 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 ?? "", + ]; + }; }