diff --git a/dist/index.css b/dist/index.css index 3d402325..facc517d 100644 --- a/dist/index.css +++ b/dist/index.css @@ -827,7 +827,7 @@ body[showing-chat] #chat-page-wrapper { .file-browser code { width: 100%; height: 100%; - font-size: .9rem; + font-size: 0.9rem; } @media (min-width: 640px) { .file-browser > div { diff --git a/dist/index.js b/dist/index.js index 54d9cfdd..2d781a5c 100644 --- a/dist/index.js +++ b/dist/index.js @@ -532,7 +532,10 @@ this.initializeTree(); }; this.removeRecursively = (pathComponents) => { - this.recurse(pathComponents, (path) => this.remove(path, false)); + this.recurse( + pathComponents, + (path) => this.remove(path, false) + ); this.initializeTree(); }; this.renameRecursively = (sourcePathComponents, destinationPathComponents) => { @@ -658,7 +661,9 @@ constructor(storageModel2, settingsModel2, fileModel) { // paths this.getBasePath = () => { - return this.fileModel.getModelContainerPath("calendar" /* ModelCalendar */); + return this.fileModel.getModelContainerPath( + "calendar" /* ModelCalendar */ + ); }; this.getViewPath = () => { return [...this.getBasePath(), "view" /* ModelView */]; @@ -806,9 +811,15 @@ }; // handlers this.handleFileContent = (fileContent) => { - if (checkMatchesObjectStructure(fileContent, BoardInfoFileContentReference) == true) { + if (checkMatchesObjectStructure( + fileContent, + BoardInfoFileContentReference + ) == true) { this.handleBoard(fileContent); - } else if (checkMatchesObjectStructure(fileContent, TaskFileContentReference) == true) { + } else if (checkMatchesObjectStructure( + fileContent, + TaskFileContentReference + ) == true) { this.handleTask(fileContent); } }; @@ -897,7 +908,10 @@ return versionIds; }; this.getLatestTaskFileContent = (taskId) => { - const taskFileContentOrNull = this.fileModel.getLatestFileContent(taskId, TaskFileContentReference); + const taskFileContentOrNull = this.fileModel.getLatestFileContent( + taskId, + TaskFileContentReference + ); return taskFileContentOrNull; }; this.getSpecificTaskFileContent = (taskId, versionId) => { @@ -933,10 +947,7 @@ static { // utility this.createBoardInfoFileContent = (fileId, name, color) => { - const fileContent = FileModel2.createFileContent( - fileId, - "board-info" - ); + const fileContent = FileModel2.createFileContent(fileId, "board-info"); return { ...fileContent, name, @@ -1043,7 +1054,10 @@ return fileContentIds[fileContentIds.length - 1]; }; this.getFileContent = (fileId, fileContentName, reference) => { - const filePath = this.getFileContentPath(fileId, fileContentName); + const filePath = this.getFileContentPath( + fileId, + fileContentName + ); const fileContentOrNull = this.storageModel.readStringifiable( filePath, reference @@ -1247,7 +1261,9 @@ this.storageModel.writeStringifiable(messagePath, chatMessage); this.chatMessageHandlerManager.trigger(chatMessage); } - this.fileModel.handleStringifiedFileContent(chatMessage.stringifiedFile); + this.fileModel.handleStringifiedFileContent( + chatMessage.stringifiedFile + ); }; this.sendMessage = async (body, fileContent) => { const senderName = this.settingsModel.username; @@ -1328,7 +1344,11 @@ this.loadInfo(); this.loadColor(); this.subscribe(); - this.fileModel = new FileModel2(this.storageModel, this.settingsModel, this); + this.fileModel = new FileModel2( + this.storageModel, + this.settingsModel, + this + ); } get secondaryChannels() { return this.info.secondaryChannels.sort(localeCompare); @@ -1341,7 +1361,10 @@ const chatMessages = []; for (const messageId of messageIds) { const messagePath = this.getMessagePath(messageId); - const chatMessage = this.storageModel.readStringifiable(messagePath, ChatMessageReference); + const chatMessage = this.storageModel.readStringifiable( + messagePath, + ChatMessageReference + ); chatMessages.push(chatMessage); } const sorted = chatMessages.sort( @@ -1378,7 +1401,10 @@ chatMessage.stringifiedFile = stringifiedFile; } if (encryptionKey != "") { - chatMessage.body = await encryptString(chatMessage.body, encryptionKey); + chatMessage.body = await encryptString( + chatMessage.body, + encryptionKey + ); chatMessage.stringifiedFile = await encryptString( chatMessage.stringifiedFile, encryptionKey @@ -1463,7 +1489,10 @@ }; // load this.loadChats = () => { - const chatDir = StorageModel.getPath("chat" /* Chat */, filePaths.chat.base); + const chatDir = StorageModel.getPath( + "chat" /* Chat */, + filePaths.chat.base + ); const chatIds = this.storageModel.list(chatDir); for (const chatId of chatIds) { const chatModel = new ChatModel( @@ -1563,7 +1592,9 @@ ) == false) { this.coreViewModel.taskCategorySuggestions.add(this.category.value); } - if (this.coreViewModel.taskStatusSuggestions.value.has(this.status.value) == false) { + if (this.coreViewModel.taskStatusSuggestions.value.has( + this.status.value + ) == false) { this.coreViewModel.taskStatusSuggestions.add(this.status.value); } }; @@ -1588,14 +1619,15 @@ }; this.deleteTask = () => { this.close(); - this.boardsAndTasksModel.deleteTask(this.task.boardId, this.task.fileId); + this.boardsAndTasksModel.deleteTask( + this.task.boardId, + this.task.fileId + ); this.containingModel.removeTaskFromView(this.task); }; // load this.loadVersionIds = () => { - const versionIds = this.boardsAndTasksModel.listTaskVersionIds( - this.task.fileId - ); + const versionIds = this.boardsAndTasksModel.listTaskVersionIds(this.task.fileId); const sortedVersionIds = versionIds.sort(localeCompare).reverse(); this.versionIds.clear(); this.versionIds.add(...sortedVersionIds); @@ -1810,7 +1842,11 @@ } }; this.handleDrop = (year, month, date) => { - const ISOString = CalendarModel.getISODateString(year, month, date); + const ISOString = CalendarModel.getISODateString( + year, + month, + date + ); const draggedObject = this.coreViewModel.draggedObject.value; if (draggedObject instanceof TaskViewModel == false) return; draggedObject.setDate(ISOString); @@ -1822,7 +1858,9 @@ this.selectedMonth.value, () => new MapState() ); - const taskIds = this.calendarModel.listTaskIds(this.monthString); + const taskIds = this.calendarModel.listTaskIds( + this.monthString + ); for (const taskId of taskIds) { const taskFileContent = this.boardsAndTasksModel.getLatestTaskFileContent(taskId); if (taskFileContent == null) continue; @@ -1935,7 +1973,10 @@ existingChatMessageViewModel.body.value = chatMessage.body; existingChatMessageViewModel.status.value = chatMessage.status; } else { - this.chatMessageViewModels.set(chatMessage.id, chatMessageViewModel); + this.chatMessageViewModels.set( + chatMessage.id, + chatMessageViewModel + ); } }; // load @@ -1947,7 +1988,10 @@ }; this.chatViewModel = chatViewModel; this.cannotSendMessage = createProxyState( - [this.chatViewModel.settingsViewModel.username, this.composingMessage], + [ + this.chatViewModel.settingsViewModel.username, + this.composingMessage + ], () => this.chatViewModel.settingsViewModel.username.value == "" || this.composingMessage.value == "" ); } @@ -2116,7 +2160,9 @@ this.filteredTaskViewModels = new ListState(); // paths this.getBasePath = () => { - return [...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId)]; + return [ + ...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId) + ]; }; this.getLastUsedBoardPath = () => { return [...this.getBasePath(), "last-used-view" /* LastUsedView */]; @@ -2137,7 +2183,9 @@ this.taskPageViewModel.updateBoard(newBoardInfoFileContent); }; this.applyColor = () => { - this.taskPageViewModel.chatViewModel.setDisplayedColor(this.color.value); + this.taskPageViewModel.chatViewModel.setDisplayedColor( + this.color.value + ); }; this.deleteBoard = () => { this.taskPageViewModel.deleteBoard(this.boardInfo); @@ -3266,7 +3314,10 @@ "select", { "bind:value": taskViewModel.selectedVersionId, - "children:append": [taskViewModel.versionIds, VersionIdToOption] + "children:append": [ + taskViewModel.versionIds, + VersionIdToOption + ] } ), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskNameLabel), /* @__PURE__ */ createElement("input", { "bind:value": taskViewModel.name }))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "category"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskBoardLabel), /* @__PURE__ */ createElement( "select", @@ -3315,11 +3366,44 @@ StringToOption ] } - ), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "priority_high"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskPriorityLabel), /* @__PURE__ */ createElement("input", { type: "number", "bind:value": taskViewModel.priority }))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "calendar_month"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskDateLabel), /* @__PURE__ */ createElement("input", { type: "date", "bind:value": taskViewModel.date }))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "schedule"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskTimeLabel), /* @__PURE__ */ createElement("input", { type: "time", "bind:value": taskViewModel.time }))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton( + ), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "priority_high"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskPriorityLabel), /* @__PURE__ */ createElement( + "input", + { + type: "number", + "bind:value": taskViewModel.priority + } + ))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "calendar_month"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskDateLabel), /* @__PURE__ */ createElement( + "input", + { + type: "date", + "bind:value": taskViewModel.date + } + ))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "schedule"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskTimeLabel), /* @__PURE__ */ createElement( + "input", + { + type: "time", + "bind:value": taskViewModel.time + } + ))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton( translations.chatPage.task.deleteTaskButton, "delete_forever", taskViewModel.deleteTask - ))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement("button", { class: "flex", "on:click": taskViewModel.closeAndDiscard }, translations.general.closeButton), /* @__PURE__ */ createElement("button", { class: "flex primary", "on:click": taskViewModel.closeAndSave }, translations.general.saveButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "save"))))); + ))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement( + "button", + { + class: "flex", + "on:click": taskViewModel.closeAndDiscard + }, + translations.general.closeButton + ), /* @__PURE__ */ createElement( + "button", + { + class: "flex primary", + "on:click": taskViewModel.closeAndSave + }, + translations.general.saveButton, + /* @__PURE__ */ createElement("span", { class: "icon" }, "save") + )))); } // src/View/Components/taskEntry.tsx @@ -3341,7 +3425,13 @@ "on:click": taskViewModel.open, "on:dragstart": taskViewModel.dragStart }, - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", { class: "ellipsis", "subscribe:innerText": taskViewModel.name }), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( + /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement( + "b", + { + class: "ellipsis", + "subscribe:innerText": taskViewModel.name + } + ), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( "div", { class: "grid secondary", @@ -3384,7 +3474,11 @@ date ); }; - return MonthGrid2(monthGrid, calendarPageViewModel.selectedDate, drop); + return MonthGrid2( + monthGrid, + calendarPageViewModel.selectedDate, + drop + ); } } ); @@ -3399,19 +3493,25 @@ if (listState == void 0) { return /* @__PURE__ */ createElement("div", null); } else { - const sidePaneContent = createProxyState([listState], () => { - if (listState.value.size == 0) { - return /* @__PURE__ */ createElement("div", { class: "width-100 height-100 flex-column justify-center align-center" }, /* @__PURE__ */ createElement("span", { class: "secondary slide-up" }, translations.chatPage.calendar.noEvents)); - } else { - return /* @__PURE__ */ createElement( - "div", - { - class: "flex-column gap slide-up padding-bottom", - "children:append": [listState, TaskViewModelToEntry] - } - ); + const sidePaneContent = createProxyState( + [listState], + () => { + if (listState.value.size == 0) { + return /* @__PURE__ */ createElement("div", { class: "width-100 height-100 flex-column justify-center align-center" }, /* @__PURE__ */ createElement("span", { class: "secondary slide-up" }, translations.chatPage.calendar.noEvents)); + } else { + return /* @__PURE__ */ createElement( + "div", + { + class: "flex-column gap slide-up padding-bottom", + "children:append": [ + listState, + TaskViewModelToEntry + ] + } + ); + } } - }); + ); return /* @__PURE__ */ createElement( "div", { @@ -3484,13 +3584,25 @@ "on:click": calendarPageViewModel.createEvent }, /* @__PURE__ */ createElement("span", { class: "icon" }, "add") - ))), /* @__PURE__ */ createElement("div", { class: "content padding-0", "children:set": mainContent }))), /* @__PURE__ */ createElement( + ))), /* @__PURE__ */ createElement( + "div", + { + class: "content padding-0", + "children:set": mainContent + } + ))), /* @__PURE__ */ createElement( "div", { class: "pane-wrapper side background", "set:color": calendarPageViewModel.chatViewModel.displayedColor }, - /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "content", "children:set": sidePaneContentWrapper })) + /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement( + "div", + { + class: "content", + "children:set": sidePaneContentWrapper + } + )) ), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal })); } @@ -3522,13 +3634,20 @@ // 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( - "b", + return /* @__PURE__ */ createElement( + "div", { - class: "break-word", - "subscribe:innerText": chatMessageViewModel.body - } - )))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.copyMessage }, translations.chatPage.message.copyMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "content_copy")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.resendMessage }, translations.chatPage.message.resendMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "redo")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.decryptMessage }, translations.chatPage.message.decryptMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "key")))), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.hideInfoModal }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); + 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( + "b", + { + class: "break-word", + "subscribe:innerText": chatMessageViewModel.body + } + )))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.copyMessage }, translations.chatPage.message.copyMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "content_copy")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.resendMessage }, translations.chatPage.message.resendMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "redo")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.decryptMessage }, translations.chatPage.message.decryptMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "key")))), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.hideInfoModal }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))) + ); } // src/View/Components/chatMessage.tsx @@ -3560,7 +3679,13 @@ class: "body", "subscribe:innerText": chatMessageViewModel.body } - ), /* @__PURE__ */ createElement("span", { class: "timestamp ellipsis" }, /* @__PURE__ */ createElement("span", { class: "icon", "subscribe:innerText": statusIcon }), chatMessageViewModel.dateSent)), /* @__PURE__ */ createElement("div", { class: "button-container" }, /* @__PURE__ */ createElement( + ), /* @__PURE__ */ createElement("span", { class: "timestamp ellipsis" }, /* @__PURE__ */ createElement( + "span", + { + class: "icon", + "subscribe:innerText": statusIcon + } + ), chatMessageViewModel.dateSent)), /* @__PURE__ */ createElement("div", { class: "button-container" }, /* @__PURE__ */ createElement( "button", { "on:click": chatMessageViewModel.showInfoModal, @@ -3872,32 +3997,45 @@ (taskViewModel) => taskViewModel.task, boardViewModel.filteredTaskViewModels, (taskViewModels) => { - const viewModel = new TaskCategoryBulkChangeViewModel(taskViewModels, categoryName); + const viewModel = new TaskCategoryBulkChangeViewModel( + taskViewModels, + categoryName + ); function drop() { boardViewModel.handleDropWithinBoard(categoryName); } - const view = /* @__PURE__ */ createElement("div", { class: "flex-column flex-no", "on:dragover": allowDrop, "on:drop": drop }, /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( - "input", - { - placeholder: translations.chatPage.task.renameCategoryInputPlaceholder, - "bind:value": viewModel.inputValue, - "on:enter": viewModel.set - } - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary", - "on:click": viewModel.set, - "toggle:disabled": viewModel.cannotSet - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "check") - )), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( + const view = /* @__PURE__ */ createElement( "div", { - class: "kanban-column", - "children:append": [taskViewModels, TaskViewModelToEntry] - } - )); + class: "flex-column flex-no", + "on:dragover": allowDrop, + "on:drop": drop + }, + /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( + "input", + { + placeholder: translations.chatPage.task.renameCategoryInputPlaceholder, + "bind:value": viewModel.inputValue, + "on:enter": viewModel.set + } + ), /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "on:click": viewModel.set, + "toggle:disabled": viewModel.cannotSet + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "check") + )), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement( + "div", + { + class: "kanban-column", + "children:append": [taskViewModels, TaskViewModelToEntry] + } + ) + ); index.subscribe((newIndex) => { view.style.order = newIndex; }); @@ -3908,17 +4046,24 @@ // src/View/Modals/boardSettingsModal.tsx function BoardSettingsModal(boardViewModel) { - return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": boardViewModel.isPresentingSettingsModal }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.chatPage.task.boardSettingsHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.boardNameInputLabel), /* @__PURE__ */ createElement( - "input", + return /* @__PURE__ */ createElement( + "div", { - "on:enter": boardViewModel.saveSettings, - "bind:value": boardViewModel.name - } - ))), /* @__PURE__ */ createElement("hr", null), ColorPicker(boardViewModel.color), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton( - translations.chatPage.task.deleteBoardButton, - "delete_forever", - boardViewModel.deleteBoard - ))), /* @__PURE__ */ createElement("button", { "on:click": boardViewModel.hideSettings }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); + class: "modal", + "toggle:open": boardViewModel.isPresentingSettingsModal + }, + /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.chatPage.task.boardSettingsHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.boardNameInputLabel), /* @__PURE__ */ createElement( + "input", + { + "on:enter": boardViewModel.saveSettings, + "bind:value": boardViewModel.name + } + ))), /* @__PURE__ */ createElement("hr", null), ColorPicker(boardViewModel.color), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton( + translations.chatPage.task.deleteBoardButton, + "delete_forever", + boardViewModel.deleteBoard + ))), /* @__PURE__ */ createElement("button", { "on:click": boardViewModel.hideSettings }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))) + ); } // src/View/ChatPages/boardStatusGridPage.tsx @@ -3978,9 +4123,12 @@ const doesMatchStatus = taskViewModel.task.status == statusName; if (doesMatchStatus == false) return; taskViewModelsWithMatchingStatus.add(taskViewModel); - boardViewModel.filteredTaskViewModels.handleRemoval(taskViewModel, () => { - taskViewModelsWithMatchingStatus.remove(taskViewModel); - }); + boardViewModel.filteredTaskViewModels.handleRemoval( + taskViewModel, + () => { + taskViewModelsWithMatchingStatus.remove(taskViewModel); + } + ); } ); const viewModel = new TaskStatusBulkChangeViewModel( @@ -4015,7 +4163,10 @@ boardViewModel.filteredTaskViewModels, (taskViewModels) => { const statusNameConverter = (statusName) => { - const index2 = createPropertyValueIndexState(sortedStatuses, statusName); + const index2 = createPropertyValueIndexState( + sortedStatuses, + statusName + ); return CategoryStatusColumn( categoryName, statusName, @@ -4024,7 +4175,10 @@ taskViewModels ); }; - const viewModel = new TaskCategoryBulkChangeViewModel(taskViewModels, categoryName); + const viewModel = new TaskCategoryBulkChangeViewModel( + taskViewModels, + categoryName + ); const view = /* @__PURE__ */ createElement("div", { class: "flex-row flex-no large-gap" }, /* @__PURE__ */ createElement("div", { class: "property-input-wrapper" }, /* @__PURE__ */ createElement( "input", { @@ -4113,7 +4267,10 @@ { hidden: true, id: suggestionId, - "children:append": [searchViewModel.suggestions, StringToOption] + "children:append": [ + searchViewModel.suggestions, + StringToOption + ] } ), /* @__PURE__ */ createElement( "button", @@ -4129,7 +4286,10 @@ { class: "grid gap", style: "grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr))", - "children:append": [searchViewModel.matchingObjects, converter] + "children:append": [ + searchViewModel.matchingObjects, + converter + ] } )), /* @__PURE__ */ createElement("button", { "on:click": close }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); } @@ -4168,7 +4328,9 @@ if (boardViewModel.selectedTaskViewModel.value == void 0) { return /* @__PURE__ */ createElement("div", null); } else { - return TaskSettingsModal(boardViewModel.selectedTaskViewModel.value); + return TaskSettingsModal( + boardViewModel.selectedTaskViewModel.value + ); } } ); @@ -4248,7 +4410,13 @@ "on:dragover": allowDrop, "on:drop": boardViewModel.handleDropBetweenBoards }, - /* @__PURE__ */ createElement("span", { class: "shadow", "subscribe:innerText": boardViewModel.name }), + /* @__PURE__ */ createElement( + "span", + { + class: "shadow", + "subscribe:innerText": boardViewModel.name + } + ), /* @__PURE__ */ createElement("b", { "subscribe:innerText": boardViewModel.name }) ); boardViewModel.index.subscribe((newIndex) => { @@ -4281,49 +4449,58 @@ return BoardPage(selectedBoard); } ); - return /* @__PURE__ */ createElement("div", { id: "task-page", "toggle:isshowingboard": isShowingBoard, "set:showingboardlist": taskPageViewModel.isShowingBoadList }, /* @__PURE__ */ createElement( + return /* @__PURE__ */ createElement( "div", { - id: "board-list", - class: "pane-wrapper side background", - "set:color": taskPageViewModel.chatViewModel.displayedColor + id: "task-page", + "toggle:isshowingboard": isShowingBoard, + "set:showingboardlist": taskPageViewModel.isShowingBoadList }, - /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( - "input", - { - class: "no-outline", - "bind:value": taskPageViewModel.newBoardNameInput, - "on:enter": taskPageViewModel.createBoard, - placeholder: translations.chatPage.task.newBoardNamePlaceholder - } - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary", - "aria-label": translations.chatPage.task.createBoardButtonAudioLabel, - "on:click": taskPageViewModel.createBoard, - "toggle:disabled": taskPageViewModel.cannotCreateBoard - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "add") - ))), /* @__PURE__ */ createElement("div", { class: "content" }, /* @__PURE__ */ createElement( + /* @__PURE__ */ createElement( "div", { - class: "grid gap", - style: "grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr))", - "children:append": [ - taskPageViewModel.boardViewModels, - BoardViewModelToEntry - ] + id: "board-list", + class: "pane-wrapper side background", + "set:color": taskPageViewModel.chatViewModel.displayedColor + }, + /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( + "input", + { + class: "no-outline", + "bind:value": taskPageViewModel.newBoardNameInput, + "on:enter": taskPageViewModel.createBoard, + placeholder: translations.chatPage.task.newBoardNamePlaceholder + } + ), /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "aria-label": translations.chatPage.task.createBoardButtonAudioLabel, + "on:click": taskPageViewModel.createBoard, + "toggle:disabled": taskPageViewModel.cannotCreateBoard + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "add") + ))), /* @__PURE__ */ createElement("div", { class: "content" }, /* @__PURE__ */ createElement( + "div", + { + class: "grid gap", + style: "grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr))", + "children:append": [ + taskPageViewModel.boardViewModels, + BoardViewModelToEntry + ] + } + ))) + ), + /* @__PURE__ */ createElement( + "div", + { + id: "board-content", + class: "pane-wrapper", + "children:set": paneContent } - ))) - ), /* @__PURE__ */ createElement( - "div", - { - id: "board-content", - class: "pane-wrapper", - "children:set": paneContent - } - )); + ) + ); } // src/View/chatPage.tsx @@ -4399,13 +4576,7 @@ } } ); - return /* @__PURE__ */ createElement( - "div", - { - id: "chat-page-wrapper", - "children:set": chatPageContent - } - ); + return /* @__PURE__ */ createElement("div", { id: "chat-page-wrapper", "children:set": chatPageContent }); } // src/View/Modals/connectionModal.tsx @@ -4572,6 +4743,8 @@ var ConnectionModel = class { // init constructor(storageModel2) { + this.reconnectInterval = void 0; + this.shouldAttemptReconnect = false; this.connectionChangeHandlerManager = new HandlerManager(); this.messageHandlerManager = new HandlerManager(); this.messageSentHandlerManager = new HandlerManager(); @@ -4583,11 +4756,22 @@ this.handleConnectionChange = () => { console.log("connection status:", this.isConnected, this.address); this.connectionChangeHandlerManager.trigger(); - if (this.isConnected == false) return; + if (this.isConnected == false) { + if (this.shouldAttemptReconnect == false) return; + this.reconnectInterval = setInterval(() => { + if (this.isConnected == true) { + clearInterval(this.reconnectInterval); + return; + } + this.reconnect(); + }, 5e3); + return; + } if (this.address == void 0) return; this.storeAddress(this.address); this.sendSubscriptionRequest(); this.sendMessagesInOutbox(); + this.shouldAttemptReconnect = true; }; // connection this.connect = (address) => { @@ -4600,6 +4784,14 @@ filePaths.connectionModel.reconnectAddress ); this.storageModel.remove(reconnectAddressPath); + this.shouldAttemptReconnect = false; + }; + this.reconnect = () => { + const reconnectAddressPath = this.getReconnectAddressPath(); + const reconnectAddress = this.storageModel.read(reconnectAddressPath); + if (reconnectAddress == null) return; + console.log("reconnecting..."); + this.connect(reconnectAddress); }; // mailbox this.getMailboxPath = (address) => { @@ -4616,7 +4808,9 @@ }; this.connectMailbox = () => { if (this.address == void 0) return; - const mailboxId = this.storageModel.read(this.getMailboxPath(this.address)); + const mailboxId = this.storageModel.read( + this.getMailboxPath(this.address) + ); console.log("connecting mailbox", mailboxId); if (mailboxId == null) return this.requestNewMailbox(); this.udn.connectMailbox(mailboxId); @@ -4742,11 +4936,7 @@ this.udn.onmailboxconnect = (mailboxId) => { console.log(`using mailbox ${mailboxId}`); }; - const reconnectAddressPath = this.getReconnectAddressPath(); - const reconnectAddress = storageModel2.read(reconnectAddressPath); - if (reconnectAddress != null) { - this.connect(reconnectAddress); - } + this.reconnect(); } // data get isConnected() { @@ -4860,7 +5050,9 @@ ); this.filesSentText = createProxyState( [this.filesSentCount], - () => translations.dataTransferModal.filesSentCount(this.filesSentCount.value) + () => translations.dataTransferModal.filesSentCount( + this.filesSentCount.value + ) ); this.filePathsReceived = new ListState(); this.filesReceivedCount = createProxyState( @@ -4902,7 +5094,10 @@ }, { label: translations.dataTransferModal.settingsData, - path: StorageModel.getPath("settings" /* SettingsModel */, []) + path: StorageModel.getPath( + "settings" /* SettingsModel */, + [] + ) } ); const chatModels = this.chatListModel.chatModels; @@ -4973,7 +5168,10 @@ // src/View/Modals/dataTransferModal.tsx function DataTransferModalWrapper(connectionViewModel2, fileTransferViewModel2) { - return /* @__PURE__ */ createElement("div", null, DirectionSelectionModal(connectionViewModel2, fileTransferViewModel2), FileSelectionModal(fileTransferViewModel2), TransferDataDisplayModal(fileTransferViewModel2), TransferDisplayModal(fileTransferViewModel2), TransferDataInputModal(fileTransferViewModel2), DataReceptionModal(fileTransferViewModel2)); + return /* @__PURE__ */ createElement("div", null, DirectionSelectionModal( + connectionViewModel2, + fileTransferViewModel2 + ), FileSelectionModal(fileTransferViewModel2), TransferDataDisplayModal(fileTransferViewModel2), TransferDisplayModal(fileTransferViewModel2), TransferDataInputModal(fileTransferViewModel2), DataReceptionModal(fileTransferViewModel2)); } function DirectionSelectionModal(connectionViewModel2, fileTransferViewModel2) { const isPresented = createProxyState( @@ -4984,7 +5182,14 @@ [connectionViewModel2.isConnected], () => connectionViewModel2.isConnected.value == false ); - return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement("p", { class: "error", "toggle:hidden": connectionViewModel2.isConnected }, translations.dataTransferModal.notConnectedError), /* @__PURE__ */ createElement( + return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement( + "p", + { + class: "error", + "toggle:hidden": connectionViewModel2.isConnected + }, + translations.dataTransferModal.notConnectedError + ), /* @__PURE__ */ createElement( "div", { class: "flex-column gap content-margin-bottom", @@ -5031,7 +5236,9 @@ function toggle() { isSelected.value = !isSelected.value; } - return /* @__PURE__ */ createElement("button", { class: "tile", "toggle:selected": isSelected, "on:click": toggle }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", { class: "ellipsis" }, fileOption.label), /* @__PURE__ */ createElement("span", { class: "secondary ellipsis" }, StorageModel.pathComponentsToString(...fileOption.path)))); + return /* @__PURE__ */ createElement("button", { class: "tile", "toggle:selected": isSelected, "on:click": toggle }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", { class: "ellipsis" }, fileOption.label), /* @__PURE__ */ createElement("span", { class: "secondary ellipsis" }, StorageModel.pathComponentsToString( + ...fileOption.path + )))); } const isPresented = createProxyState( [fileTransferViewModel2.presentedModal], @@ -5083,7 +5290,12 @@ { "subscribe:innerText": fileTransferViewModel2.transferChannel } - ))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.dataTransferModal.transferKeyHeadline), /* @__PURE__ */ createElement("b", { "subscribe:innerText": fileTransferViewModel2.transferKey }))))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement( + ))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.dataTransferModal.transferKeyHeadline), /* @__PURE__ */ createElement( + "b", + { + "subscribe:innerText": fileTransferViewModel2.transferKey + } + ))))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement( "button", { class: "flex", @@ -5279,7 +5491,9 @@ }; this.storageModel = storageModel2; this.connectionModel = connectionModel2; - this.connectionModel.messageHandlerManager.addHandler(this.handleMessage); + this.connectionModel.messageHandlerManager.addHandler( + this.handleMessage + ); } }; var FileDataReference = { @@ -5399,7 +5613,15 @@ }, /* @__PURE__ */ createElement("span", { class: "icon" }, "sync_alt"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.homePage.transferDataButton)) - ), /* @__PURE__ */ createElement("button", { class: "tile flex-no", "on:click": storageViewModel2.showStorageModal }, /* @__PURE__ */ createElement("span", { class: "icon" }, "hard_drive_2"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.homePage.manageStorageButton))), /* @__PURE__ */ createElement("div", { class: "mobile-only" }, /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, /* @__PURE__ */ createElement("button", { class: "ghost width-50", "on:click": scrollToChat }, translations.homePage.scrollToChatButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward"))))); + ), /* @__PURE__ */ createElement( + "button", + { + class: "tile flex-no", + "on:click": storageViewModel2.showStorageModal + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "hard_drive_2"), + /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.homePage.manageStorageButton)) + ), /* @__PURE__ */ createElement("div", { class: "mobile-only" }, /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, /* @__PURE__ */ createElement("button", { class: "ghost width-50", "on:click": scrollToChat }, translations.homePage.scrollToChatButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward"))))); const chatSection = /* @__PURE__ */ createElement("div", { id: "chat-section" }, /* @__PURE__ */ createElement("h2", null, translations.homePage.chatsHeadline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( "input", { @@ -5611,17 +5833,23 @@ this.coreViewModel = coreViewModel; // state this.isShowingStorageModal = new State(false); - this.selectedPath = new State(PATH_COMPONENT_SEPARATOR); + this.selectedPath = new State( + PATH_COMPONENT_SEPARATOR + ); this.didMakeChanges = new State(false); this.lastDeletedItemPath = new State(""); // methods this.getSelectedItemContent = () => { - const path = StorageModel.stringToPathComponents(this.selectedPath.value); + const path = StorageModel.stringToPathComponents( + this.selectedPath.value + ); const content = this.storageModel.read(path); return (content ?? translations.storage.notAFile) || translations.storage.contentEmpty; }; this.deleteSelectedItem = () => { - const path = StorageModel.stringToPathComponents(this.selectedPath.value); + const path = StorageModel.stringToPathComponents( + this.selectedPath.value + ); this.lastDeletedItemPath.value = this.selectedPath.value; this.storageModel.removeRecursively(path); this.didMakeChanges.value = true; @@ -5699,9 +5927,7 @@ if (primaryChannel == null) return; const parsedPriamryChannel = parseOrFallback(primaryChannel); const chatModel = this.chatListModel.createChat(parsedPriamryChannel); - const secondaryChannelString = getLocalStorageItemAndClear( - storageKeys.secondaryChannels(id) - ); + const secondaryChannelString = getLocalStorageItemAndClear(storageKeys.secondaryChannels(id)); if (secondaryChannelString != null) { const potentialSecondaryChannels = parseArray( secondaryChannelString @@ -5744,7 +5970,9 @@ }; chatModel.addMessage(convertedChatMessage); if (status == "outbox" /* Outbox */) { - this.connectionModel.sendMessageOrStore(convertedChatMessage); + this.connectionModel.sendMessageOrStore( + convertedChatMessage + ); } } }; @@ -5789,7 +6017,9 @@ time: potentialVersion.time ?? "" }; console.log(convertedTaskFileContent); - chatModel.fileModel.handleFileContent(convertedTaskFileContent); + chatModel.fileModel.handleFileContent( + convertedTaskFileContent + ); } } }; diff --git a/dist/manifest.json b/dist/manifest.json index 5450fd66..6bfe9035 100644 --- a/dist/manifest.json +++ b/dist/manifest.json @@ -1,6 +1,6 @@ { "name": "Comms", - "display": "standalone", + "display": "fullscreen", "icons": [ { "src": "icon.png", diff --git a/src/Model/Chat/chatListModel.ts b/src/Model/Chat/chatListModel.ts index 7645592f..a2f38a26 100644 --- a/src/Model/Chat/chatListModel.ts +++ b/src/Model/Chat/chatListModel.ts @@ -1,5 +1,8 @@ import ChatModel, { ChatMessage } from "./chatModel"; -import StorageModel, { StorageModelSubPath, filePaths } from "../Global/storageModel"; +import StorageModel, { + StorageModelSubPath, + filePaths, +} from "../Global/storageModel"; import ConnectionModel from "../Global/connectionModel"; import { Message } from "udn-frontend"; @@ -7,104 +10,107 @@ import SettingsModel from "../Global/settingsModel"; import { v4 } from "uuid"; export default class ChatListModel { - storageModel: StorageModel; - settingsModel: SettingsModel; - connectionModel: ConnectionModel; + storageModel: StorageModel; + settingsModel: SettingsModel; + connectionModel: ConnectionModel; - // data - chatModels = new Set(); + // data + chatModels = new Set(); - // handlers - messageHandler = (data: Message): void => { - const channel: string | undefined = data.messageChannel; - const body: string | undefined = data.messageBody; - if (channel == undefined) return; - if (body == undefined) return; + // handlers + messageHandler = (data: Message): void => { + const channel: string | undefined = data.messageChannel; + const body: string | undefined = data.messageBody; + if (channel == undefined) return; + if (body == undefined) return; - this.routeMessageToCorrectChatModel(channel, (chatModel: ChatModel) => - chatModel.handleMessage(body) - ); - }; + this.routeMessageToCorrectChatModel(channel, (chatModel: ChatModel) => + chatModel.handleMessage(body), + ); + }; - messageSentHandler = (chatMessage: ChatMessage): void => { - const channel: string = chatMessage.channel; + messageSentHandler = (chatMessage: ChatMessage): void => { + const channel: string = chatMessage.channel; - this.routeMessageToCorrectChatModel(channel, (chatModel: ChatModel) => - chatModel.handleMessageSent(chatMessage) - ); - }; + this.routeMessageToCorrectChatModel(channel, (chatModel: ChatModel) => + chatModel.handleMessageSent(chatMessage), + ); + }; - // methods - routeMessageToCorrectChatModel = ( - channel: string, - fn: (chatModel: ChatModel) => void - ): void => { - const allChannels = channel.split("/"); + // methods + routeMessageToCorrectChatModel = ( + channel: string, + fn: (chatModel: ChatModel) => void, + ): void => { + const allChannels = channel.split("/"); - for (const chatModel of this.chatModels) { - for (const channel of allChannels) { - if (channel != chatModel.info.primaryChannel) continue; - fn(chatModel); - break; - } + for (const chatModel of this.chatModels) { + for (const channel of allChannels) { + if (channel != chatModel.info.primaryChannel) continue; + fn(chatModel); + break; + } + } + }; + + // storage + addChatModel = (chatModel: ChatModel) => { + this.chatModels.add(chatModel); + }; + + createChat = (primaryChannel: string): ChatModel => { + const id: string = v4(); + + const chatModel = new ChatModel( + this.storageModel, + this.connectionModel, + this.settingsModel, + this, + id, + ); + chatModel.setPrimaryChannel(primaryChannel); + + this.addChatModel(chatModel); + return chatModel; + }; + + untrackChat = (chat: ChatModel): void => { + this.chatModels.delete(chat); + }; + + // load + loadChats = (): void => { + const chatDir = StorageModel.getPath( + StorageModelSubPath.Chat, + filePaths.chat.base, + ); + const chatIds = this.storageModel.list(chatDir); + for (const chatId of chatIds) { + const chatModel = new ChatModel( + this.storageModel, + this.connectionModel, + this.settingsModel, + this, + chatId, + ); + this.addChatModel(chatModel); + } + }; + + // init + constructor( + storageModel: StorageModel, + settingsModel: SettingsModel, + connectionModel: ConnectionModel, + ) { + this.storageModel = storageModel; + this.settingsModel = settingsModel; + this.connectionModel = connectionModel; + this.loadChats(); + + connectionModel.messageHandlerManager.addHandler(this.messageHandler); + connectionModel.messageSentHandlerManager.addHandler( + this.messageSentHandler, + ); } - }; - - // storage - addChatModel = (chatModel: ChatModel) => { - this.chatModels.add(chatModel); - }; - - createChat = (primaryChannel: string): ChatModel => { - const id: string = v4(); - - const chatModel = new ChatModel( - this.storageModel, - this.connectionModel, - this.settingsModel, - this, - id - ); - chatModel.setPrimaryChannel(primaryChannel); - - this.addChatModel(chatModel); - return chatModel; - }; - - untrackChat = (chat: ChatModel): void => { - this.chatModels.delete(chat); - }; - - // load - loadChats = (): void => { - const chatDir = StorageModel.getPath(StorageModelSubPath.Chat, filePaths.chat.base); - const chatIds = this.storageModel.list(chatDir); - for (const chatId of chatIds) { - const chatModel = new ChatModel( - this.storageModel, - this.connectionModel, - this.settingsModel, - this, - chatId - ); - this.addChatModel(chatModel); - } - }; - - // init - constructor( - storageModel: StorageModel, - settingsModel: SettingsModel, - connectionModel: ConnectionModel - ) { - this.storageModel = storageModel; - this.settingsModel = settingsModel; - this.connectionModel = connectionModel; - this.loadChats(); - - connectionModel.messageHandlerManager.addHandler(this.messageHandler); - connectionModel.messageSentHandlerManager.addHandler( - this.messageSentHandler - ); - } } diff --git a/src/Model/Chat/chatModel.ts b/src/Model/Chat/chatModel.ts index 42aa555d..ec4abf46 100644 --- a/src/Model/Chat/chatModel.ts +++ b/src/Model/Chat/chatModel.ts @@ -3,15 +3,15 @@ import { DATA_VERSION, ValidObject } from "../Utility/typeSafety"; import FileModel, { FileContent } from "../Files/fileModel"; import { - HandlerManager, - createTimestamp, - localeCompare, - parseValidObject, - stringify, + HandlerManager, + createTimestamp, + localeCompare, + parseValidObject, + stringify, } from "../Utility/utility"; import StorageModel, { - StorageModelSubPath, - filePaths, + StorageModelSubPath, + filePaths, } from "../Global/storageModel"; import { decryptString, encryptString } from "../Utility/crypto"; @@ -22,345 +22,358 @@ import SettingsModel from "../Global/settingsModel"; import { v4 } from "uuid"; export default class ChatModel { - connectionModel: ConnectionModel; - storageModel: StorageModel; - settingsModel: SettingsModel; - chatListModel: ChatListModel; + connectionModel: ConnectionModel; + storageModel: StorageModel; + settingsModel: SettingsModel; + chatListModel: ChatListModel; - fileModel: FileModel; + fileModel: FileModel; - // data - id: string; - info: ChatInfo; - color: Color; + // data + id: string; + info: ChatInfo; + color: Color; - chatMessageHandlerManager: HandlerManager = new HandlerManager(); + chatMessageHandlerManager: HandlerManager = + new HandlerManager(); - get secondaryChannels(): string[] { - return this.info.secondaryChannels.sort(localeCompare); - } - - // paths - getBasePath = (): string[] => { - return StorageModel.getPath( - StorageModelSubPath.Chat, - filePaths.chat.chatBase(this.id) - ); - }; - - getInfoPath = (): string[] => { - return StorageModel.getPath( - StorageModelSubPath.Chat, - filePaths.chat.info(this.id) - ); - }; - - getColorPath = (): string[] => { - return StorageModel.getPath( - StorageModelSubPath.Chat, - filePaths.chat.color(this.id) - ); - }; - - getMessageDirPath = (): string[] => { - return StorageModel.getPath( - StorageModelSubPath.Chat, - filePaths.chat.messages(this.id) - ); - }; - - getMessagePath = (id: string): string[] => { - return [...this.getMessageDirPath(), id]; - }; - - // handlers - handleMessage = (body: string): void => { - const chatMessage: ChatMessage | null = parseValidObject( - body, - ChatMessageReference - ); - if (chatMessage == null) return; - - chatMessage.status = ChatMessageStatus.Received; - this.addMessage(chatMessage); - - this.info.hasUnreadMessages = true; - this.storeInfo(); - }; - - handleMessageSent = (chatMessage: ChatMessage): void => { - chatMessage.status = ChatMessageStatus.Sent; - this.addMessage(chatMessage); - }; - - // settings - setPrimaryChannel = (primaryChannel: string): void => { - this.info.primaryChannel = primaryChannel; - this.storeInfo(); - this.subscribe(); - }; - - setSecondaryChannels = (secondaryChannels: string[]): void => { - this.info.secondaryChannels = secondaryChannels; - this.storeInfo(); - }; - - setEncryptionKey = (key: string): void => { - this.info.encryptionKey = key; - this.storeInfo(); - }; - - setColor = (color: Color): void => { - this.color = color; - this.storeColor(); - }; - - // messaging - addMessage = async (chatMessage: ChatMessage): Promise => { - await this.decryptMessage(chatMessage); - - // message - if (chatMessage.body != "") { - const messagePath: string[] = this.getMessagePath(chatMessage.id); - this.storageModel.writeStringifiable(messagePath, chatMessage); - this.chatMessageHandlerManager.trigger(chatMessage); + get secondaryChannels(): string[] { + return this.info.secondaryChannels.sort(localeCompare); } - // file - this.fileModel.handleStringifiedFileContent(chatMessage.stringifiedFile); - }; - - sendMessage = async ( - body: string, - fileContent?: FileContent - ): Promise => { - const senderName = this.settingsModel.username; - if (senderName == "") return false; - - const allChannels = [this.info.primaryChannel]; - for (const secondaryChannel of this.info.secondaryChannels) { - allChannels.push(secondaryChannel); - } - - const combinedChannel: string = allChannels.join("/"); - - const chatMessage: ChatMessage = await ChatModel.createChatMessage( - combinedChannel, - senderName, - this.info.encryptionKey, - body, - fileContent - ); - - this.addMessage(chatMessage); - this.connectionModel.sendMessageOrStore(chatMessage); - return true; - }; - - decryptMessage = async (chatMessage: ChatMessage): Promise => { - const decryptedBody: string = await decryptString( - chatMessage.body, - this.info.encryptionKey - ); - const decryptedFile: string = await decryptString( - chatMessage.stringifiedFile ?? "", - this.info.encryptionKey - ); - chatMessage.body = decryptedBody; - chatMessage.stringifiedFile = decryptedFile; - }; - - subscribe = (): void => { - this.connectionModel.addChannel(this.info.primaryChannel); - }; - - markRead = (): void => { - this.info.hasUnreadMessages = false; - this.storeInfo(); - } - - // storage - storeInfo = (): void => { - this.storageModel.writeStringifiable(this.getInfoPath(), this.info); - }; - - storeColor = (): void => { - this.storageModel.write(this.getColorPath(), this.color); - }; - - delete = () => { - // untrack - this.chatListModel.untrackChat(this); - - // delete - const dirPath: string[] = this.getBasePath(); - this.storageModel.removeRecursively(dirPath); - }; - - // load - loadInfo = (): void => { - const info: ChatInfo | null = this.storageModel.readStringifiable( - this.getInfoPath(), - ChatInfoReference - ); - if (info != null) { - this.info = info; - } else { - this.info = ChatModel.generateChatInfo("0"); - } - }; - - loadColor = (): void => { - const path: string[] = this.getColorPath(); - const color: string | null = this.storageModel.read(path); - if (!color) { - this.color = Color.Standard; - } else { - this.color = color as any; - } - }; - - get messages(): ChatMessage[] { - const messageIds: string[] = this.storageModel.list( - this.getMessageDirPath() - ); - if (!Array.isArray(messageIds)) return []; - - const chatMessages: ChatMessage[] = []; - for (const messageId of messageIds) { - const messagePath: string[] = this.getMessagePath(messageId); - const chatMessage: ChatMessage | any = - this.storageModel.readStringifiable(messagePath, ChatMessageReference); - chatMessages.push(chatMessage); - } - - const sorted = chatMessages.sort((a, b) => - a.dateSent.localeCompare(b.dateSent) - ); - return sorted; - } - - // init - constructor( - storageModel: StorageModel, - connectionModel: ConnectionModel, - settingsModel: SettingsModel, - chatListModel: ChatListModel, - chatId: string - ) { - this.id = chatId; - this.connectionModel = connectionModel; - this.settingsModel = settingsModel; - this.storageModel = storageModel; - this.chatListModel = chatListModel; - - this.loadInfo(); - this.loadColor(); - this.subscribe(); - - this.fileModel = new FileModel(this.storageModel, this.settingsModel, this); - } - - // utility - static generateChatInfo = (primaryChannel: string): ChatInfo => { - return { - dataVersion: DATA_VERSION, - - primaryChannel, - secondaryChannels: [], - encryptionKey: "", - hasUnreadMessages: false, + // paths + getBasePath = (): string[] => { + return StorageModel.getPath( + StorageModelSubPath.Chat, + filePaths.chat.chatBase(this.id), + ); }; - }; - static createChatMessage = async ( - channel: string, - sender: string, - encryptionKey: string, - body: string, - fileContent?: FileContent - ): Promise => { - const chatMessage: ChatMessage = { - dataVersion: DATA_VERSION, - - id: v4(), - - channel, - sender, - body, - dateSent: createTimestamp(), - - status: ChatMessageStatus.Outbox, - stringifiedFile: "", + getInfoPath = (): string[] => { + return StorageModel.getPath( + StorageModelSubPath.Chat, + filePaths.chat.info(this.id), + ); }; - if (fileContent != undefined) { - const stringifiedFile = stringify(fileContent); - chatMessage.stringifiedFile = stringifiedFile; + + getColorPath = (): string[] => { + return StorageModel.getPath( + StorageModelSubPath.Chat, + filePaths.chat.color(this.id), + ); + }; + + getMessageDirPath = (): string[] => { + return StorageModel.getPath( + StorageModelSubPath.Chat, + filePaths.chat.messages(this.id), + ); + }; + + getMessagePath = (id: string): string[] => { + return [...this.getMessageDirPath(), id]; + }; + + // handlers + handleMessage = (body: string): void => { + const chatMessage: ChatMessage | null = parseValidObject( + body, + ChatMessageReference, + ); + if (chatMessage == null) return; + + chatMessage.status = ChatMessageStatus.Received; + this.addMessage(chatMessage); + + this.info.hasUnreadMessages = true; + this.storeInfo(); + }; + + handleMessageSent = (chatMessage: ChatMessage): void => { + chatMessage.status = ChatMessageStatus.Sent; + this.addMessage(chatMessage); + }; + + // settings + setPrimaryChannel = (primaryChannel: string): void => { + this.info.primaryChannel = primaryChannel; + this.storeInfo(); + this.subscribe(); + }; + + setSecondaryChannels = (secondaryChannels: string[]): void => { + this.info.secondaryChannels = secondaryChannels; + this.storeInfo(); + }; + + setEncryptionKey = (key: string): void => { + this.info.encryptionKey = key; + this.storeInfo(); + }; + + setColor = (color: Color): void => { + this.color = color; + this.storeColor(); + }; + + // messaging + addMessage = async (chatMessage: ChatMessage): Promise => { + await this.decryptMessage(chatMessage); + + // message + if (chatMessage.body != "") { + const messagePath: string[] = this.getMessagePath(chatMessage.id); + this.storageModel.writeStringifiable(messagePath, chatMessage); + this.chatMessageHandlerManager.trigger(chatMessage); + } + + // file + this.fileModel.handleStringifiedFileContent( + chatMessage.stringifiedFile, + ); + }; + + sendMessage = async ( + body: string, + fileContent?: FileContent, + ): Promise => { + const senderName = this.settingsModel.username; + if (senderName == "") return false; + + const allChannels = [this.info.primaryChannel]; + for (const secondaryChannel of this.info.secondaryChannels) { + allChannels.push(secondaryChannel); + } + + const combinedChannel: string = allChannels.join("/"); + + const chatMessage: ChatMessage = await ChatModel.createChatMessage( + combinedChannel, + senderName, + this.info.encryptionKey, + body, + fileContent, + ); + + this.addMessage(chatMessage); + this.connectionModel.sendMessageOrStore(chatMessage); + return true; + }; + + decryptMessage = async (chatMessage: ChatMessage): Promise => { + const decryptedBody: string = await decryptString( + chatMessage.body, + this.info.encryptionKey, + ); + const decryptedFile: string = await decryptString( + chatMessage.stringifiedFile ?? "", + this.info.encryptionKey, + ); + chatMessage.body = decryptedBody; + chatMessage.stringifiedFile = decryptedFile; + }; + + subscribe = (): void => { + this.connectionModel.addChannel(this.info.primaryChannel); + }; + + markRead = (): void => { + this.info.hasUnreadMessages = false; + this.storeInfo(); + }; + + // storage + storeInfo = (): void => { + this.storageModel.writeStringifiable(this.getInfoPath(), this.info); + }; + + storeColor = (): void => { + this.storageModel.write(this.getColorPath(), this.color); + }; + + delete = () => { + // untrack + this.chatListModel.untrackChat(this); + + // delete + const dirPath: string[] = this.getBasePath(); + this.storageModel.removeRecursively(dirPath); + }; + + // load + loadInfo = (): void => { + const info: ChatInfo | null = this.storageModel.readStringifiable( + this.getInfoPath(), + ChatInfoReference, + ); + if (info != null) { + this.info = info; + } else { + this.info = ChatModel.generateChatInfo("0"); + } + }; + + loadColor = (): void => { + const path: string[] = this.getColorPath(); + const color: string | null = this.storageModel.read(path); + if (!color) { + this.color = Color.Standard; + } else { + this.color = color as any; + } + }; + + get messages(): ChatMessage[] { + const messageIds: string[] = this.storageModel.list( + this.getMessageDirPath(), + ); + if (!Array.isArray(messageIds)) return []; + + const chatMessages: ChatMessage[] = []; + for (const messageId of messageIds) { + const messagePath: string[] = this.getMessagePath(messageId); + const chatMessage: ChatMessage | any = + this.storageModel.readStringifiable( + messagePath, + ChatMessageReference, + ); + chatMessages.push(chatMessage); + } + + const sorted = chatMessages.sort((a, b) => + a.dateSent.localeCompare(b.dateSent), + ); + return sorted; } - if (encryptionKey != "") { - chatMessage.body = await encryptString(chatMessage.body, encryptionKey); - chatMessage.stringifiedFile = await encryptString( - chatMessage.stringifiedFile, - encryptionKey - ); + // init + constructor( + storageModel: StorageModel, + connectionModel: ConnectionModel, + settingsModel: SettingsModel, + chatListModel: ChatListModel, + chatId: string, + ) { + this.id = chatId; + this.connectionModel = connectionModel; + this.settingsModel = settingsModel; + this.storageModel = storageModel; + this.chatListModel = chatListModel; + + this.loadInfo(); + this.loadColor(); + this.subscribe(); + + this.fileModel = new FileModel( + this.storageModel, + this.settingsModel, + this, + ); } - return chatMessage; - }; + // utility + static generateChatInfo = (primaryChannel: string): ChatInfo => { + return { + dataVersion: DATA_VERSION, + + primaryChannel, + secondaryChannels: [], + encryptionKey: "", + hasUnreadMessages: false, + }; + }; + + static createChatMessage = async ( + channel: string, + sender: string, + encryptionKey: string, + body: string, + fileContent?: FileContent, + ): Promise => { + const chatMessage: ChatMessage = { + dataVersion: DATA_VERSION, + + id: v4(), + + channel, + sender, + body, + dateSent: createTimestamp(), + + status: ChatMessageStatus.Outbox, + stringifiedFile: "", + }; + if (fileContent != undefined) { + const stringifiedFile = stringify(fileContent); + chatMessage.stringifiedFile = stringifiedFile; + } + + if (encryptionKey != "") { + chatMessage.body = await encryptString( + chatMessage.body, + encryptionKey, + ); + chatMessage.stringifiedFile = await encryptString( + chatMessage.stringifiedFile, + encryptionKey, + ); + } + + return chatMessage; + }; } // types export interface ChatInfo extends ValidObject { - primaryChannel: string; - secondaryChannels: string[]; - encryptionKey: string; + primaryChannel: string; + secondaryChannels: string[]; + encryptionKey: string; - hasUnreadMessages: boolean; + hasUnreadMessages: boolean; } export enum ChatMessageStatus { - Outbox = "outbox", - Sent = "sent", - Received = "received", - Other = "other", + Outbox = "outbox", + Sent = "sent", + Received = "received", + Other = "other", } export interface ChatMessage extends ValidObject { - id: string; + id: string; - channel: string; - sender: string; - body: string; - dateSent: string; + channel: string; + sender: string; + body: string; + dateSent: string; - status: ChatMessageStatus; + status: ChatMessageStatus; - stringifiedFile: string; + stringifiedFile: string; } // references export const ChatInfoReference: ChatInfo = { - dataVersion: DATA_VERSION, + dataVersion: DATA_VERSION, - primaryChannel: "", - secondaryChannels: [""], - encryptionKey: "", + primaryChannel: "", + secondaryChannels: [""], + encryptionKey: "", - hasUnreadMessages: true, + hasUnreadMessages: true, }; export const ChatMessageReference: ChatMessage = { - dataVersion: DATA_VERSION, + dataVersion: DATA_VERSION, - id: "", + id: "", - channel: "", - sender: "", - body: "", - dateSent: "", + channel: "", + sender: "", + body: "", + dateSent: "", - status: "" as ChatMessageStatus, + status: "" as ChatMessageStatus, - stringifiedFile: "", + stringifiedFile: "", }; diff --git a/src/Model/Files/boardsAndTasksModel.ts b/src/Model/Files/boardsAndTasksModel.ts index 65774d9f..5dfee8fa 100644 --- a/src/Model/Files/boardsAndTasksModel.ts +++ b/src/Model/Files/boardsAndTasksModel.ts @@ -1,8 +1,8 @@ // this file is responsible for managing boards within chats and tasks within boards. import { - DATA_VERSION, - checkMatchesObjectStructure, + DATA_VERSION, + checkMatchesObjectStructure, } from "../Utility/typeSafety"; import FileModel, { FileContent, FileModelSubPath } from "./fileModel"; @@ -15,316 +15,324 @@ import StorageModel from "../Global/storageModel"; import { v4 } from "uuid"; export default class BoardsAndTasksModel { - storageModel: StorageModel; - settingsModel: SettingsModel; - chatModel: ChatModel; - fileModel: FileModel; + storageModel: StorageModel; + settingsModel: SettingsModel; + chatModel: ChatModel; + fileModel: FileModel; - calendarModel: CalendarModel; + calendarModel: CalendarModel; - // data - boardHandlerManager: HandlerManager = - new HandlerManager(); - taskHandlerManager: HandlerManager = new HandlerManager(); + // data + boardHandlerManager: HandlerManager = + new HandlerManager(); + taskHandlerManager: HandlerManager = new HandlerManager(); - // paths - getBasePath = (): string[] => { - return this.fileModel.getModelContainerPath(FileModelSubPath.ModelTask); - }; + // paths + getBasePath = (): string[] => { + return this.fileModel.getModelContainerPath(FileModelSubPath.ModelTask); + }; - getViewPath = (): string[] => { - return [...this.getBasePath(), FileModelSubPath.ModelView]; - }; + getViewPath = (): string[] => { + return [...this.getBasePath(), FileModelSubPath.ModelView]; + }; - getBoardFilePath = (boardId: string): string[] => { - return [...this.fileModel.getFilePath(boardId)]; - }; + getBoardFilePath = (boardId: string): string[] => { + return [...this.fileModel.getFilePath(boardId)]; + }; - getTaskFilePath = (taskId: string): string[] => { - return [...this.fileModel.getFilePath(taskId)]; - }; + getTaskFilePath = (taskId: string): string[] => { + return [...this.fileModel.getFilePath(taskId)]; + }; - getBoardContainerPath = (): string[] => { - return [...this.getBasePath(), TaskModelSubPaths.Boards]; - }; + getBoardContainerPath = (): string[] => { + return [...this.getBasePath(), TaskModelSubPaths.Boards]; + }; - getBoardDirectoryPath = (boardId: string): string[] => { - return [...this.getBoardContainerPath(), boardId]; - }; + getBoardDirectoryPath = (boardId: string): string[] => { + return [...this.getBoardContainerPath(), boardId]; + }; - getTaskContainerPath = (boardId: string): string[] => { - return [ - ...this.getBoardDirectoryPath(boardId), - TaskModelSubPaths.BoardTasks, - ]; - }; + getTaskContainerPath = (boardId: string): string[] => { + return [ + ...this.getBoardDirectoryPath(boardId), + TaskModelSubPaths.BoardTasks, + ]; + }; - getTaskReferencePath = (boardId: string, fileId: string): string[] => { - return [...this.getTaskContainerPath(boardId), fileId]; - }; + getTaskReferencePath = (boardId: string, fileId: string): string[] => { + return [...this.getTaskContainerPath(boardId), fileId]; + }; - // handlers - handleFileContent = (fileContent: FileContent): void => { - if ( - checkMatchesObjectStructure(fileContent, BoardInfoFileContentReference) == - true + // handlers + handleFileContent = (fileContent: FileContent): void => { + if ( + checkMatchesObjectStructure( + fileContent, + BoardInfoFileContentReference, + ) == true + ) { + this.handleBoard(fileContent as BoardInfoFileContent); + } else if ( + checkMatchesObjectStructure( + fileContent, + TaskFileContentReference, + ) == true + ) { + this.handleTask(fileContent as TaskFileContent); + } + }; + + handleBoard = (boardInfoFileContent: BoardInfoFileContent) => { + this.updateBoard(boardInfoFileContent); + }; + + handleTask = (taskFileContent: TaskFileContent) => { + this.updateTask(taskFileContent); + }; + + // boards + createBoard = (name: string): BoardInfoFileContent => { + const boardInfoFileContent: BoardInfoFileContent = + BoardsAndTasksModel.createBoardInfoFileContent( + v4(), + name, + Color.Standard, + ); + return boardInfoFileContent; + }; + + updateBoard = (boardInfoFileContent: BoardInfoFileContent): void => { + this.storeBoard(boardInfoFileContent); + this.boardHandlerManager.trigger(boardInfoFileContent); + }; + + updateBoardAndSend = (boardInfoFileContent: BoardInfoFileContent): void => { + this.updateBoard(boardInfoFileContent); + this.chatModel.sendMessage("", boardInfoFileContent); + }; + + storeBoard = (boardInfoFileContent: BoardInfoFileContent): void => { + // store info + this.fileModel.storeFileContent(boardInfoFileContent); + + // add to list + const boardDirectoryPath: string[] = this.getBoardDirectoryPath( + boardInfoFileContent.fileId, + ); + this.storageModel.write(boardDirectoryPath, ""); + }; + + deleteBoard = (boardId: string): void => { + const boardFilePath: string[] = this.getBoardFilePath(boardId); + const boardDirectoryPath: string[] = + this.getBoardDirectoryPath(boardId); + + this.storageModel.removeRecursively(boardFilePath); + this.storageModel.removeRecursively(boardDirectoryPath); + }; + + listBoardIds = (): string[] => { + const boardContainerPath: string[] = this.getBoardContainerPath(); + const boardIds: string[] = this.storageModel.list(boardContainerPath); + return boardIds; + }; + + getBoardInfo = (fileId: string): BoardInfoFileContent | null => { + const boardInfoFileContentOrNull: BoardInfoFileContent | null = + this.fileModel.getLatestFileContent( + fileId, + BoardInfoFileContentReference, + ); + return boardInfoFileContentOrNull; + }; + + getBoardName = (boardId: string): string => { + const boardInfo: BoardInfoFileContent | null = + this.getBoardInfo(boardId); + if (boardInfo == null) return ""; + return boardInfo.name; + }; + + //tasks + createTask = (boardId: string): TaskFileContent => { + const taskFileContent: TaskFileContent = + BoardsAndTasksModel.createTaskFileContent(v4(), "", boardId); + return taskFileContent; + }; + + updateTask = (taskFileContent: TaskFileContent): void => { + this.storeTask(taskFileContent); + this.taskHandlerManager.trigger(taskFileContent); + }; + + updateTaskAndSend = (taskFileContent: TaskFileContent): void => { + this.updateTask(taskFileContent); + this.chatModel.sendMessage("", taskFileContent); + }; + + storeTask = (taskFileContent: TaskFileContent): void => { + // store info + this.fileModel.storeFileContent(taskFileContent); + + // add to board + const taskReferencePath: string[] = this.getTaskReferencePath( + taskFileContent.boardId, + taskFileContent.fileId, + ); + this.storageModel.write(taskReferencePath, ""); + + // add to calendar + this.calendarModel.storeTaskReference(taskFileContent); + }; + + listTaskIds = (boardId: string): string[] => { + const taskContainerPath: string[] = this.getTaskContainerPath(boardId); + const fileIds: string[] = this.storageModel.list(taskContainerPath); + return fileIds; + }; + + listTaskVersionIds = (taskId: string): string[] => { + const versionIds: string[] = this.fileModel.listFileContentIds(taskId); + return versionIds; + }; + + getLatestTaskFileContent = (taskId: string): TaskFileContent | null => { + const taskFileContentOrNull: TaskFileContent | null = + this.fileModel.getLatestFileContent( + taskId, + TaskFileContentReference, + ); + return taskFileContentOrNull; + }; + + getSpecificTaskFileContent = ( + taskId: string, + versionId: string, + ): TaskFileContent | null => { + const taskFileContentOrNull: TaskFileContent | null = + this.fileModel.getFileContent( + taskId, + versionId, + TaskFileContentReference, + ); + return taskFileContentOrNull; + }; + + deleteTask = (boardId: string, taskId: string): void => { + const taskFilePath: string[] = this.getTaskFilePath(taskId); + this.storageModel.removeRecursively(taskFilePath); + + this.deleteTaskReference(boardId, taskId); + }; + + deleteTaskReference = (boardId: string, taskId: string): void => { + const taskReferencePath: string[] = this.getTaskReferencePath( + boardId, + taskId, + ); + this.storageModel.removeRecursively(taskReferencePath); + }; + + // init + constructor( + storageModel: StorageModel, + settingsModel: SettingsModel, + chatModel: ChatModel, + fileModel: FileModel, ) { - this.handleBoard(fileContent as BoardInfoFileContent); - } else if ( - checkMatchesObjectStructure(fileContent, TaskFileContentReference) == true - ) { - this.handleTask(fileContent as TaskFileContent); + this.storageModel = storageModel; + this.settingsModel = settingsModel; + this.chatModel = chatModel; + this.fileModel = fileModel; + + this.calendarModel = new CalendarModel( + this.storageModel, + this.settingsModel, + this.fileModel, + ); } - }; - handleBoard = (boardInfoFileContent: BoardInfoFileContent) => { - this.updateBoard(boardInfoFileContent); - }; + // utility + static createBoardInfoFileContent = ( + fileId: string, + name: string, + color: Color, + ): BoardInfoFileContent => { + const fileContent: FileContent<"board-info"> = + FileModel.createFileContent(fileId, "board-info"); + return { + ...fileContent, - handleTask = (taskFileContent: TaskFileContent) => { - this.updateTask(taskFileContent); - }; - - // boards - createBoard = (name: string): BoardInfoFileContent => { - const boardInfoFileContent: BoardInfoFileContent = - BoardsAndTasksModel.createBoardInfoFileContent( - v4(), - name, - Color.Standard - ); - return boardInfoFileContent; - }; - - updateBoard = (boardInfoFileContent: BoardInfoFileContent): void => { - this.storeBoard(boardInfoFileContent); - this.boardHandlerManager.trigger(boardInfoFileContent); - }; - - updateBoardAndSend = (boardInfoFileContent: BoardInfoFileContent): void => { - this.updateBoard(boardInfoFileContent); - this.chatModel.sendMessage("", boardInfoFileContent); - }; - - storeBoard = (boardInfoFileContent: BoardInfoFileContent): void => { - // store info - this.fileModel.storeFileContent(boardInfoFileContent); - - // add to list - const boardDirectoryPath: string[] = this.getBoardDirectoryPath( - boardInfoFileContent.fileId - ); - this.storageModel.write(boardDirectoryPath, ""); - }; - - deleteBoard = (boardId: string): void => { - const boardFilePath: string[] = this.getBoardFilePath(boardId); - const boardDirectoryPath: string[] = this.getBoardDirectoryPath(boardId); - - this.storageModel.removeRecursively(boardFilePath); - this.storageModel.removeRecursively(boardDirectoryPath); - }; - - listBoardIds = (): string[] => { - const boardContainerPath: string[] = this.getBoardContainerPath(); - const boardIds: string[] = this.storageModel.list(boardContainerPath); - return boardIds; - }; - - getBoardInfo = (fileId: string): BoardInfoFileContent | null => { - const boardInfoFileContentOrNull: BoardInfoFileContent | null = - this.fileModel.getLatestFileContent( - fileId, - BoardInfoFileContentReference - ); - return boardInfoFileContentOrNull; - }; - - getBoardName = (boardId: string): string => { - const boardInfo: BoardInfoFileContent|null = this.getBoardInfo(boardId); - if (boardInfo == null) return ""; - return boardInfo.name; - } - - //tasks - createTask = (boardId: string): TaskFileContent => { - const taskFileContent: TaskFileContent = - BoardsAndTasksModel.createTaskFileContent(v4(), "", boardId); - return taskFileContent; - }; - - updateTask = (taskFileContent: TaskFileContent): void => { - this.storeTask(taskFileContent); - this.taskHandlerManager.trigger(taskFileContent); - }; - - updateTaskAndSend = (taskFileContent: TaskFileContent): void => { - this.updateTask(taskFileContent); - this.chatModel.sendMessage("", taskFileContent); - }; - - storeTask = (taskFileContent: TaskFileContent): void => { - // store info - this.fileModel.storeFileContent(taskFileContent); - - // add to board - const taskReferencePath: string[] = this.getTaskReferencePath( - taskFileContent.boardId, - taskFileContent.fileId - ); - this.storageModel.write(taskReferencePath, ""); - - // add to calendar - this.calendarModel.storeTaskReference(taskFileContent); - }; - - listTaskIds = (boardId: string): string[] => { - const taskContainerPath: string[] = this.getTaskContainerPath(boardId); - const fileIds: string[] = this.storageModel.list(taskContainerPath); - return fileIds; - }; - - listTaskVersionIds = (taskId: string): string[] => { - const versionIds: string[] = this.fileModel.listFileContentIds(taskId); - return versionIds; - }; - - getLatestTaskFileContent = (taskId: string): TaskFileContent | null => { - const taskFileContentOrNull: TaskFileContent | null = - this.fileModel.getLatestFileContent(taskId, TaskFileContentReference); - return taskFileContentOrNull; - }; - - getSpecificTaskFileContent = ( - taskId: string, - versionId: string - ): TaskFileContent | null => { - const taskFileContentOrNull: TaskFileContent | null = - this.fileModel.getFileContent( - taskId, - versionId, - TaskFileContentReference - ); - return taskFileContentOrNull; - }; - - deleteTask = (boardId: string, taskId: string): void => { - const taskFilePath: string[] = this.getTaskFilePath(taskId); - this.storageModel.removeRecursively(taskFilePath); - - this.deleteTaskReference(boardId, taskId); - }; - - deleteTaskReference = (boardId: string, taskId: string): void => { - const taskReferencePath: string[] = this.getTaskReferencePath( - boardId, - taskId - ); - this.storageModel.removeRecursively(taskReferencePath); - }; - - // init - constructor( - storageModel: StorageModel, - settingsModel: SettingsModel, - chatModel: ChatModel, - fileModel: FileModel - ) { - this.storageModel = storageModel; - this.settingsModel = settingsModel; - this.chatModel = chatModel; - this.fileModel = fileModel; - - this.calendarModel = new CalendarModel( - this.storageModel, - this.settingsModel, - this.fileModel - ); - } - - // utility - static createBoardInfoFileContent = ( - fileId: string, - name: string, - color: Color - ): BoardInfoFileContent => { - const fileContent: FileContent<"board-info"> = FileModel.createFileContent( - fileId, - "board-info" - ); - return { - ...fileContent, - - name, - color, + name, + color, + }; }; - }; - static createTaskFileContent = ( - fileId: string, - name: string, - boardId: string - ): TaskFileContent => { - const fileContent: FileContent<"task"> = FileModel.createFileContent( - fileId, - "task" - ); - return { - ...fileContent, + static createTaskFileContent = ( + fileId: string, + name: string, + boardId: string, + ): TaskFileContent => { + const fileContent: FileContent<"task"> = FileModel.createFileContent( + fileId, + "task", + ); + return { + ...fileContent, - name, - boardId, + name, + boardId, + }; }; - }; } export enum TaskModelSubPaths { - Boards = "boards", - BoardTasks = "tasks", + Boards = "boards", + BoardTasks = "tasks", } // types export interface BoardInfoFileContent extends FileContent<"board-info"> { - name: string; - color: Color; + name: string; + color: Color; } export interface TaskFileContent extends FileContent<"task"> { - name: string; - boardId: string; + name: string; + boardId: string; - description?: string; + description?: string; - category?: string; - status?: string; - priority?: string; + category?: string; + status?: string; + priority?: string; - date?: string; - time?: string; + date?: string; + time?: string; } // reference export const BoardInfoFileContentReference: BoardInfoFileContent = { - dataVersion: DATA_VERSION, + dataVersion: DATA_VERSION, - fileId: "string", - fileContentId: "", - creationDate: "", + fileId: "string", + fileContentId: "", + creationDate: "", - type: "board-info", + type: "board-info", - name: "", - color: "" as Color, + name: "", + color: "" as Color, }; export const TaskFileContentReference: TaskFileContent = { - dataVersion: DATA_VERSION, + dataVersion: DATA_VERSION, - fileId: "string", - fileContentId: "", - creationDate: "", - type: "task", + fileId: "string", + fileContentId: "", + creationDate: "", + type: "task", - name: "", - boardId: "", + name: "", + boardId: "", }; diff --git a/src/Model/Files/calendarModel.ts b/src/Model/Files/calendarModel.ts index e2dbf550..1432be16 100644 --- a/src/Model/Files/calendarModel.ts +++ b/src/Model/Files/calendarModel.ts @@ -7,158 +7,160 @@ import StorageModel from "../Global/storageModel"; import { TaskFileContent } from "./boardsAndTasksModel"; export default class CalendarModel { - storageModel: StorageModel; - fileModel: FileModel; - settingsModel: SettingsModel; + storageModel: StorageModel; + fileModel: FileModel; + settingsModel: SettingsModel; - // paths - getBasePath = (): string[] => { - return this.fileModel.getModelContainerPath(FileModelSubPath.ModelCalendar); - }; - - getViewPath = (): string[] => { - return [...this.getBasePath(), FileModelSubPath.ModelView]; - }; - - getMonthContainerPath = (): string[] => { - return [...this.getBasePath(), CalendarModelSubPaths.Months]; - }; - - getMonthPath = (monthString: string): string[] => { - return [...this.getMonthContainerPath(), monthString]; - }; - - // main - storeTaskReference = (taskFileContent: TaskFileContent): void => { - if (taskFileContent.date == undefined) return; - - const monthString: string = CalendarModel.isoToMonthString( - taskFileContent.date - ); - - const monthPath: string[] = this.getMonthPath(monthString); - const referencePath: string[] = [...monthPath, taskFileContent.fileId]; - - this.storageModel.write(referencePath, ""); - }; - - deleteTaskReference = (monthString: string, taskId: string): void => { - const monthPath: string[] = this.getMonthPath(monthString); - const referencePath: string[] = [...monthPath, taskId]; - - this.storageModel.write(referencePath, ""); - }; - - listTaskIds = (monthString: string): string[] => { - const monthPath: string[] = this.getMonthPath(monthString); - return this.storageModel.list(monthPath); - }; - - generateMonthGrid = ( - year: number, - month: number, - defaultValueCreator: () => T - ): MonthGrid => { - // get today - const date: Date = new Date(); - const isCurrentMonth: boolean = - year == date.getFullYear() && month == date.getMonth() + 1; - - // get data for offset - date.setDate(1); - date.setMonth(month - 1); - date.setFullYear(year); - - const firstWeekdayOfMonth = date.getDay(); - const firstDayOfWeekSetting: number = parseInt( - this.settingsModel.firstDayOfWeek - ); - - const offset: number = - firstWeekdayOfMonth < firstDayOfWeekSetting - ? 7 - firstDayOfWeekSetting - : firstWeekdayOfMonth - firstDayOfWeekSetting; - - // get total day count - date.setMonth(month); - date.setDate(-1); - const daysInMonth: number = date.getDate() + 1; - - const grid: MonthGrid = { - offset, - firstDayOfWeek: parseInt(this.settingsModel.firstDayOfWeek), - isCurrentMonth, - year, - month, - days: {}, + // paths + getBasePath = (): string[] => { + return this.fileModel.getModelContainerPath( + FileModelSubPath.ModelCalendar, + ); }; - for (let i = 0; i < daysInMonth; i++) { - const paddedDate: string = CalendarModel.padDateOrMonth( - (i + 1).toString() - ); - grid.days[paddedDate] = defaultValueCreator(); + getViewPath = (): string[] => { + return [...this.getBasePath(), FileModelSubPath.ModelView]; + }; + + getMonthContainerPath = (): string[] => { + return [...this.getBasePath(), CalendarModelSubPaths.Months]; + }; + + getMonthPath = (monthString: string): string[] => { + return [...this.getMonthContainerPath(), monthString]; + }; + + // main + storeTaskReference = (taskFileContent: TaskFileContent): void => { + if (taskFileContent.date == undefined) return; + + const monthString: string = CalendarModel.isoToMonthString( + taskFileContent.date, + ); + + const monthPath: string[] = this.getMonthPath(monthString); + const referencePath: string[] = [...monthPath, taskFileContent.fileId]; + + this.storageModel.write(referencePath, ""); + }; + + deleteTaskReference = (monthString: string, taskId: string): void => { + const monthPath: string[] = this.getMonthPath(monthString); + const referencePath: string[] = [...monthPath, taskId]; + + this.storageModel.write(referencePath, ""); + }; + + listTaskIds = (monthString: string): string[] => { + const monthPath: string[] = this.getMonthPath(monthString); + return this.storageModel.list(monthPath); + }; + + generateMonthGrid = ( + year: number, + month: number, + defaultValueCreator: () => T, + ): MonthGrid => { + // get today + const date: Date = new Date(); + const isCurrentMonth: boolean = + year == date.getFullYear() && month == date.getMonth() + 1; + + // get data for offset + date.setDate(1); + date.setMonth(month - 1); + date.setFullYear(year); + + const firstWeekdayOfMonth = date.getDay(); + const firstDayOfWeekSetting: number = parseInt( + this.settingsModel.firstDayOfWeek, + ); + + const offset: number = + firstWeekdayOfMonth < firstDayOfWeekSetting + ? 7 - firstDayOfWeekSetting + : firstWeekdayOfMonth - firstDayOfWeekSetting; + + // get total day count + date.setMonth(month); + date.setDate(-1); + const daysInMonth: number = date.getDate() + 1; + + const grid: MonthGrid = { + offset, + firstDayOfWeek: parseInt(this.settingsModel.firstDayOfWeek), + isCurrentMonth, + year, + month, + days: {}, + }; + + for (let i = 0; i < daysInMonth; i++) { + const paddedDate: string = CalendarModel.padDateOrMonth( + (i + 1).toString(), + ); + grid.days[paddedDate] = defaultValueCreator(); + } + + return grid; + }; + + // init + constructor( + storageModel: StorageModel, + settingsModel: SettingsModel, + fileModel: FileModel, + ) { + this.storageModel = storageModel; + this.settingsModel = settingsModel; + this.fileModel = fileModel; } - return grid; - }; + // utility + static isoToMonthString = (dateISOString: string): string => { + const [year, month, _] = dateISOString.split("-"); + return CalendarModel.getMonthString(year, month); + }; - // init - constructor( - storageModel: StorageModel, - settingsModel: SettingsModel, - fileModel: FileModel - ) { - this.storageModel = storageModel; - this.settingsModel = settingsModel; - this.fileModel = fileModel; - } + static isoToDateString = (dateISOString: string): string => { + const [year, month, date, _] = dateISOString.split("-"); + const paddedDate = CalendarModel.padDateOrMonth(date ?? ""); + return paddedDate; + }; - // utility - static isoToMonthString = (dateISOString: string): string => { - const [year, month, _] = dateISOString.split("-"); - return CalendarModel.getMonthString(year, month); - }; + static getMonthString = (year: string = "", month: string = ""): string => { + const paddedYear: string = year.padStart(4, "0"); + const paddedMonth: string = CalendarModel.padDateOrMonth(month); + return `${paddedYear}-${paddedMonth}`; + }; - static isoToDateString = (dateISOString: string): string => { - const [year, month, date, _] = dateISOString.split("-"); - const paddedDate = CalendarModel.padDateOrMonth(date ?? ""); - return paddedDate; - }; + static getISODateString = ( + year: string, + month: string, + date: string, + ): string => { + const monthString: string = CalendarModel.getMonthString(year, month); + const paddedDate: string = CalendarModel.padDateOrMonth(date); + return `${monthString}-${paddedDate}`; + }; - static getMonthString = (year: string = "", month: string = ""): string => { - const paddedYear: string = year.padStart(4, "0"); - const paddedMonth: string = CalendarModel.padDateOrMonth(month); - return `${paddedYear}-${paddedMonth}`; - }; - - static getISODateString = ( - year: string, - month: string, - date: string - ): string => { - const monthString: string = CalendarModel.getMonthString(year, month); - const paddedDate: string = CalendarModel.padDateOrMonth(date); - return `${monthString}-${paddedDate}`; - }; - - static padDateOrMonth = (input: string): string => { - return input.padStart(2, "0"); - }; + static padDateOrMonth = (input: string): string => { + return input.padStart(2, "0"); + }; } export enum CalendarModelSubPaths { - Months = "months", + Months = "months", } // types export interface MonthGrid { - offset: number; - firstDayOfWeek: number; - isCurrentMonth: boolean; + offset: number; + firstDayOfWeek: number; + isCurrentMonth: boolean; - year: number; - month: number; + year: number; + month: number; - days: { [date: string]: T }; + days: { [date: string]: T }; } diff --git a/src/Model/Files/fileModel.ts b/src/Model/Files/fileModel.ts index feda16d4..89ee5ff1 100644 --- a/src/Model/Files/fileModel.ts +++ b/src/Model/Files/fileModel.ts @@ -2,14 +2,14 @@ import { DATA_VERSION, ValidObject } from "../Utility/typeSafety"; import StorageModel, { - StorageModelSubPath, - filePaths, + StorageModelSubPath, + filePaths, } from "../Global/storageModel"; import { - StringEntryObject, - createTimestamp, - parseValidObject, - stringify, + StringEntryObject, + createTimestamp, + parseValidObject, + stringify, } from "../Utility/utility"; import BoardsAndTasksModel from "./boardsAndTasksModel"; @@ -18,191 +18,194 @@ import SettingsModel from "../Global/settingsModel"; import { v4 } from "uuid"; export default class FileModel { - storageModel: StorageModel; - settingsModel: SettingsModel; + storageModel: StorageModel; + settingsModel: SettingsModel; - chatModel: ChatModel; - boardsAndTasksModel: BoardsAndTasksModel; + chatModel: ChatModel; + boardsAndTasksModel: BoardsAndTasksModel; - // paths - getBasePath = (): string[] => { - return StorageModel.getPath( - StorageModelSubPath.Chat, - filePaths.chat.files(this.chatModel.id) - ); - }; - - getFileContainerPath = (): string[] => { - return [...this.getBasePath(), FileModelSubPath.Data]; - }; - - getModelContainerPath = (modelName: FileModelSubPath): string[] => { - return [...this.getBasePath(), FileModelSubPath.Model, modelName]; - }; - - getFilePath = (fileId: string): string[] => { - return [...this.getFileContainerPath(), fileId]; - }; - - getFileContentPath = (fileId: string, fileContentId: string): string[] => { - const filePath: string[] = this.getFilePath(fileId); - return [...filePath, fileContentId]; - }; - - // handlers - handleStringifiedFileContent = (stringifiedFileContent: string): void => { - const fileContent: FileContent | null = parseValidObject( - stringifiedFileContent, - FileContentReference - ); - if (fileContent == null) return; - - this.handleFileContent(fileContent); - }; - - handleFileContent = (fileContent: FileContent): void => { - const didStore: boolean = this.storeFileContent(fileContent); - if (didStore == false) return; - - this.boardsAndTasksModel.handleFileContent(fileContent); - }; - - // methods - addFileContentAndSend = (fileContent: FileContent): void => { - this.handleFileContent(fileContent); - this.chatModel.sendMessage("", fileContent); - }; - - // storage - storeFileContent = (fileContent: FileContent): boolean => { - const fileContentPath: string[] = this.getFileContentPath( - fileContent.fileId, - fileContent.fileContentId - ); - - // check if fileContent already exists - const existingFileContent: string | null = - this.storageModel.read(fileContentPath); - if (existingFileContent != null) return false; - - const stringifiedContent: string = stringify(fileContent); - this.storageModel.write(fileContentPath, stringifiedContent); - return true; - }; - - listFileIds = (): string[] => { - return this.storageModel.list(this.getBasePath()); - }; - - listFileContentIds = (fileId: string): string[] => { - const filePath: string[] = this.getFilePath(fileId); - return this.storageModel.list(filePath); - }; - - selectLatestFileContentId = ( - fileContentIds: string[] - ): string | undefined => { - return fileContentIds[fileContentIds.length - 1]; - }; - - getFileContent = >( - fileId: string, - fileContentName: string, - reference: T - ): T | null => { - const filePath: string[] = this.getFileContentPath(fileId, fileContentName); - const fileContentOrNull: T | null = this.storageModel.readStringifiable( - filePath, - reference - ); - return fileContentOrNull; - }; - - getLatestFileContent = >( - fileId: string, - reference: T - ): T | null => { - const fileContentsIds: string[] = this.listFileContentIds(fileId); - const latestFileContentId: string | undefined = - this.selectLatestFileContentId(fileContentsIds); - if (latestFileContentId == undefined) return null; - - const fileContent: T | null = this.getFileContent( - fileId, - latestFileContentId, - reference - ); - return fileContent; - }; - - // init - constructor( - storageModel: StorageModel, - settingsModel: SettingsModel, - chatModel: ChatModel - ) { - this.chatModel = chatModel; - this.settingsModel = settingsModel; - this.storageModel = storageModel; - - this.boardsAndTasksModel = new BoardsAndTasksModel( - this.storageModel, - this.settingsModel, - chatModel, - this - ); - } - - // utility - static generateFileContentId = (creationDate: string): string => { - return creationDate + v4(); - }; - - static createFileContent = ( - fileId: string, - type: T - ): FileContent => { - const creationDate: string = createTimestamp(); - const fileContentId: string = FileModel.generateFileContentId(creationDate); - - return { - dataVersion: DATA_VERSION, - - fileId, - fileContentId, - creationDate, - type, + // paths + getBasePath = (): string[] => { + return StorageModel.getPath( + StorageModelSubPath.Chat, + filePaths.chat.files(this.chatModel.id), + ); + }; + + getFileContainerPath = (): string[] => { + return [...this.getBasePath(), FileModelSubPath.Data]; + }; + + getModelContainerPath = (modelName: FileModelSubPath): string[] => { + return [...this.getBasePath(), FileModelSubPath.Model, modelName]; + }; + + getFilePath = (fileId: string): string[] => { + return [...this.getFileContainerPath(), fileId]; + }; + + getFileContentPath = (fileId: string, fileContentId: string): string[] => { + const filePath: string[] = this.getFilePath(fileId); + return [...filePath, fileContentId]; + }; + + // handlers + handleStringifiedFileContent = (stringifiedFileContent: string): void => { + const fileContent: FileContent | null = parseValidObject( + stringifiedFileContent, + FileContentReference, + ); + if (fileContent == null) return; + + this.handleFileContent(fileContent); + }; + + handleFileContent = (fileContent: FileContent): void => { + const didStore: boolean = this.storeFileContent(fileContent); + if (didStore == false) return; + + this.boardsAndTasksModel.handleFileContent(fileContent); + }; + + // methods + addFileContentAndSend = (fileContent: FileContent): void => { + this.handleFileContent(fileContent); + this.chatModel.sendMessage("", fileContent); + }; + + // storage + storeFileContent = (fileContent: FileContent): boolean => { + const fileContentPath: string[] = this.getFileContentPath( + fileContent.fileId, + fileContent.fileContentId, + ); + + // check if fileContent already exists + const existingFileContent: string | null = + this.storageModel.read(fileContentPath); + if (existingFileContent != null) return false; + + const stringifiedContent: string = stringify(fileContent); + this.storageModel.write(fileContentPath, stringifiedContent); + return true; + }; + + listFileIds = (): string[] => { + return this.storageModel.list(this.getBasePath()); + }; + + listFileContentIds = (fileId: string): string[] => { + const filePath: string[] = this.getFilePath(fileId); + return this.storageModel.list(filePath); + }; + + selectLatestFileContentId = ( + fileContentIds: string[], + ): string | undefined => { + return fileContentIds[fileContentIds.length - 1]; + }; + + getFileContent = >( + fileId: string, + fileContentName: string, + reference: T, + ): T | null => { + const filePath: string[] = this.getFileContentPath( + fileId, + fileContentName, + ); + const fileContentOrNull: T | null = this.storageModel.readStringifiable( + filePath, + reference, + ); + return fileContentOrNull; + }; + + getLatestFileContent = >( + fileId: string, + reference: T, + ): T | null => { + const fileContentsIds: string[] = this.listFileContentIds(fileId); + const latestFileContentId: string | undefined = + this.selectLatestFileContentId(fileContentsIds); + if (latestFileContentId == undefined) return null; + + const fileContent: T | null = this.getFileContent( + fileId, + latestFileContentId, + reference, + ); + return fileContent; + }; + + // init + constructor( + storageModel: StorageModel, + settingsModel: SettingsModel, + chatModel: ChatModel, + ) { + this.chatModel = chatModel; + this.settingsModel = settingsModel; + this.storageModel = storageModel; + + this.boardsAndTasksModel = new BoardsAndTasksModel( + this.storageModel, + this.settingsModel, + chatModel, + this, + ); + } + + // utility + static generateFileContentId = (creationDate: string): string => { + return creationDate + v4(); + }; + + static createFileContent = ( + fileId: string, + type: T, + ): FileContent => { + const creationDate: string = createTimestamp(); + const fileContentId: string = + FileModel.generateFileContentId(creationDate); + + return { + dataVersion: DATA_VERSION, + + fileId, + fileContentId, + creationDate, + type, + }; }; - }; } // paths export enum FileModelSubPath { - Data = "data", - Model = "model", - ModelView = "view", - ModelTask = "tasks", - ModelCalendar = "calendar", + Data = "data", + Model = "model", + ModelView = "view", + ModelTask = "tasks", + ModelCalendar = "calendar", } // types export interface FileContent - extends ValidObject, - StringEntryObject { - fileId: string; - fileContentId: string; - creationDate: string; + extends ValidObject, StringEntryObject { + fileId: string; + fileContentId: string; + creationDate: string; - type: T; + type: T; } // references export const FileContentReference: FileContent = { - dataVersion: DATA_VERSION, + dataVersion: DATA_VERSION, - fileId: "", - fileContentId: "", - creationDate: "", + fileId: "", + fileContentId: "", + creationDate: "", - type: "", + type: "", }; diff --git a/src/Model/Global/connectionModel.ts b/src/Model/Global/connectionModel.ts index de8ebf9c..ad9a3623 100644 --- a/src/Model/Global/connectionModel.ts +++ b/src/Model/Global/connectionModel.ts @@ -6,255 +6,278 @@ import StorageModel, { StorageModelSubPath, filePaths } from "./storageModel"; import UDNFrontend, { Message } from "udn-frontend"; export default class ConnectionModel { - udn: UDNFrontend; - storageModel: StorageModel; + udn: UDNFrontend; + storageModel: StorageModel; + reconnectInterval: number | undefined = undefined; + shouldAttemptReconnect: boolean = false; - // data - get isConnected(): boolean { - return this.udn.ws != undefined && this.udn.ws.readyState == 1; - } - - get address(): string | undefined { - return this.udn.ws?.url; - } - - connectionChangeHandlerManager: HandlerManager = new HandlerManager(); - messageHandlerManager: HandlerManager = new HandlerManager(); - messageSentHandlerManager: HandlerManager = new HandlerManager(); - - channelsToSubscribe: Set = new Set(); - - // handlers - handleMessage = (data: Message): void => { - this.messageHandlerManager.trigger(data); - }; - - handleConnectionChange = (): void => { - console.log("connection status:", this.isConnected, this.address); - this.connectionChangeHandlerManager.trigger(); - - if (this.isConnected == false) return; - if (this.address == undefined) return; - - this.storeAddress(this.address); - this.sendSubscriptionRequest(); - this.sendMessagesInOutbox(); - }; - - // connection - connect = (address: string): void => { - this.udn.connect(address); - }; - - disconnect = (): void => { - this.udn.disconnect(); - - // do not reconnect - const reconnectAddressPath: string[] = StorageModel.getPath( - StorageModelSubPath.ConnectionModel, - filePaths.connectionModel.reconnectAddress - ); - this.storageModel.remove(reconnectAddressPath); - }; - - // mailbox - getMailboxPath = (address: string): string[] => { - const mailboxDirPath = StorageModel.getPath( - StorageModelSubPath.ConnectionModel, - filePaths.connectionModel.mailboxes - ); - const mailboxFilePath = [...mailboxDirPath, address]; - return mailboxFilePath; - }; - - requestNewMailbox = (): void => { - console.log("requesting new mailbox"); - this.udn.requestMailbox(); - }; - - connectMailbox = (): void => { - if (this.address == undefined) return; - - const mailboxId = this.storageModel.read(this.getMailboxPath(this.address)); - console.log("connecting mailbox", mailboxId); - if (mailboxId == null) return this.requestNewMailbox(); - - this.udn.connectMailbox(mailboxId); - }; - - storeMailbox = (mailboxId: string): void => { - if (this.address == undefined) return; - this.storageModel.write(this.getMailboxPath(this.address), mailboxId); - }; - - // subscription - addChannel = (channel: string): void => { - this.channelsToSubscribe.add(channel); - this.sendSubscriptionRequest(); - }; - - sendSubscriptionRequest = (): void => { - if (this.isConnected == false) return; - - for (const channel of this.channelsToSubscribe) { - this.udn.subscribe(channel); + // data + get isConnected(): boolean { + return this.udn.ws != undefined && this.udn.ws.readyState == 1; } - // update mailbox - this.connectMailbox(); - }; - // outbox - getOutboxPath = (): string[] => { - return StorageModel.getPath( - StorageModelSubPath.ConnectionModel, - filePaths.connectionModel.outbox - ); - }; + get address(): string | undefined { + return this.udn.ws?.url; + } - getOutboxMessags = (): ChatMessage[] => { - const outboxPath: string[] = this.getOutboxPath(); - const messageIds: string[] = this.storageModel.list(outboxPath); + connectionChangeHandlerManager: HandlerManager = new HandlerManager(); + messageHandlerManager: HandlerManager = new HandlerManager(); + messageSentHandlerManager: HandlerManager = + new HandlerManager(); - let chatMessages: ChatMessage[] = []; - for (const messageId of messageIds) { - const chatMessage: ChatMessage | null = - this.storageModel.readStringifiable( - [...outboxPath, messageId], - ChatMessageReference + channelsToSubscribe: Set = new Set(); + + // handlers + handleMessage = (data: Message): void => { + this.messageHandlerManager.trigger(data); + }; + + handleConnectionChange = (): void => { + console.log("connection status:", this.isConnected, this.address); + this.connectionChangeHandlerManager.trigger(); + + if (this.isConnected == false) { + if (this.shouldAttemptReconnect == false) return; + + this.reconnectInterval = setInterval(() => { + if (this.isConnected == true) { + clearInterval(this.reconnectInterval); + return; + } + this.reconnect(); + }, 5000); + + return; + } + if (this.address == undefined) return; + + this.storeAddress(this.address); + this.sendSubscriptionRequest(); + this.sendMessagesInOutbox(); + this.shouldAttemptReconnect = true; + }; + + // connection + connect = (address: string): void => { + this.udn.connect(address); + }; + + disconnect = (): void => { + this.udn.disconnect(); + + // do not reconnect + const reconnectAddressPath: string[] = StorageModel.getPath( + StorageModelSubPath.ConnectionModel, + filePaths.connectionModel.reconnectAddress, ); + this.storageModel.remove(reconnectAddressPath); + this.shouldAttemptReconnect = false; + }; - if (chatMessage == null) continue; - chatMessages.push(chatMessage); + reconnect = (): void => { + const reconnectAddressPath: string[] = this.getReconnectAddressPath(); + const reconnectAddress: string | null = + this.storageModel.read(reconnectAddressPath); + if (reconnectAddress == null) return; + + console.log("reconnecting..."); + this.connect(reconnectAddress); + }; + + // mailbox + getMailboxPath = (address: string): string[] => { + const mailboxDirPath = StorageModel.getPath( + StorageModelSubPath.ConnectionModel, + filePaths.connectionModel.mailboxes, + ); + const mailboxFilePath = [...mailboxDirPath, address]; + return mailboxFilePath; + }; + + requestNewMailbox = (): void => { + console.log("requesting new mailbox"); + this.udn.requestMailbox(); + }; + + connectMailbox = (): void => { + if (this.address == undefined) return; + + const mailboxId = this.storageModel.read( + this.getMailboxPath(this.address), + ); + console.log("connecting mailbox", mailboxId); + if (mailboxId == null) return this.requestNewMailbox(); + + this.udn.connectMailbox(mailboxId); + }; + + storeMailbox = (mailboxId: string): void => { + if (this.address == undefined) return; + this.storageModel.write(this.getMailboxPath(this.address), mailboxId); + }; + + // subscription + addChannel = (channel: string): void => { + this.channelsToSubscribe.add(channel); + this.sendSubscriptionRequest(); + }; + + sendSubscriptionRequest = (): void => { + if (this.isConnected == false) return; + + for (const channel of this.channelsToSubscribe) { + this.udn.subscribe(channel); + } + // update mailbox + this.connectMailbox(); + }; + + // outbox + getOutboxPath = (): string[] => { + return StorageModel.getPath( + StorageModelSubPath.ConnectionModel, + filePaths.connectionModel.outbox, + ); + }; + + getOutboxMessags = (): ChatMessage[] => { + const outboxPath: string[] = this.getOutboxPath(); + const messageIds: string[] = this.storageModel.list(outboxPath); + + let chatMessages: ChatMessage[] = []; + for (const messageId of messageIds) { + const chatMessage: ChatMessage | null = + this.storageModel.readStringifiable( + [...outboxPath, messageId], + ChatMessageReference, + ); + + if (chatMessage == null) continue; + chatMessages.push(chatMessage); + } + + return chatMessages; + }; + + addToOutbox = (chatMessage: ChatMessage): void => { + const messagePath: string[] = [...this.getOutboxPath(), chatMessage.id]; + this.storageModel.writeStringifiable(messagePath, chatMessage); + }; + + removeFromOutbox = (chatMessage: ChatMessage): void => { + const messagePath: string[] = [...this.getOutboxPath(), chatMessage.id]; + this.storageModel.remove(messagePath); + }; + + sendMessagesInOutbox = (): void => { + const messages: ChatMessage[] = this.getOutboxMessags(); + + for (const message of messages) { + const isSent: boolean = this.tryToSendMessage(message); + if (isSent == false) return; + + this.removeFromOutbox(message); + } + }; + + // messaging + sendMessageOrStore = (chatMessage: ChatMessage): void => { + const isSent = this.tryToSendMessage(chatMessage); + if (isSent == true) return; + + this.addToOutbox(chatMessage); + }; + + tryToSendMessage = (chatMessage: ChatMessage): boolean => { + const stringifiedBody: string = stringify(chatMessage); + const isSent: boolean = this.sendPlainMessage( + chatMessage.channel, + stringifiedBody, + ); + if (isSent) this.messageSentHandlerManager.trigger(chatMessage); + return isSent; + }; + + sendPlainMessage = (channel: string, body: string): boolean => { + return this.udn.sendMessage(channel, body); + }; + + // storage + getPreviousAddressPath = (): string[] => { + return StorageModel.getPath( + StorageModelSubPath.ConnectionModel, + filePaths.connectionModel.previousAddresses, + ); + }; + + getAddressPath = (address: string): string[] => { + const dirPath = this.getPreviousAddressPath(); + return [...dirPath, address]; + }; + + getReconnectAddressPath = (): string[] => { + return StorageModel.getPath( + StorageModelSubPath.ConnectionModel, + filePaths.connectionModel.reconnectAddress, + ); + }; + + storeAddress = (address: string): void => { + // history + const addressPath = this.getAddressPath(address); + this.storageModel.write(addressPath, ""); + + // reconnect + const reconnectAddressPath: string[] = this.getReconnectAddressPath(); + this.storageModel.write(reconnectAddressPath, address); + }; + + removeAddress = (address: string): void => { + const addressPath = this.getAddressPath(address); + this.storageModel.remove(addressPath); + }; + + get addresses(): string[] { + const dirPath = this.getPreviousAddressPath(); + return this.storageModel.list(dirPath); } - return chatMessages; - }; + // init + constructor(storageModel: StorageModel) { + // create frontend + this.udn = new UDNFrontend(); + this.storageModel = storageModel; - addToOutbox = (chatMessage: ChatMessage): void => { - const messagePath: string[] = [...this.getOutboxPath(), chatMessage.id]; - this.storageModel.writeStringifiable(messagePath, chatMessage); - }; + // setup handlers + this.udn.onmessage = (data: Message) => { + this.handleMessage(data); + }; + this.udn.onconnect = () => { + this.handleConnectionChange(); + }; + this.udn.ondisconnect = () => { + this.handleConnectionChange(); + }; - removeFromOutbox = (chatMessage: ChatMessage): void => { - const messagePath: string[] = [...this.getOutboxPath(), chatMessage.id]; - this.storageModel.remove(messagePath); - }; + this.udn.onmailboxcreate = (mailboxId: string) => { + console.log("created mailbox", mailboxId); + this.storeMailbox(mailboxId); + this.connectMailbox(); + }; + this.udn.onmailboxdelete = (mailboxId: string) => { + console.log(`mailbox ${mailboxId} deleted`); + this.requestNewMailbox(); + }; + this.udn.onmailboxconnect = (mailboxId: string) => { + console.log(`using mailbox ${mailboxId}`); + }; - sendMessagesInOutbox = (): void => { - const messages: ChatMessage[] = this.getOutboxMessags(); - - for (const message of messages) { - const isSent: boolean = this.tryToSendMessage(message); - if (isSent == false) return; - - this.removeFromOutbox(message); + this.reconnect(); } - }; - - // messaging - sendMessageOrStore = (chatMessage: ChatMessage): void => { - const isSent = this.tryToSendMessage(chatMessage); - if (isSent == true) return; - - this.addToOutbox(chatMessage); - }; - - tryToSendMessage = (chatMessage: ChatMessage): boolean => { - const stringifiedBody: string = stringify(chatMessage); - const isSent: boolean = this.sendPlainMessage( - chatMessage.channel, - stringifiedBody - ); - if (isSent) this.messageSentHandlerManager.trigger(chatMessage); - return isSent; - }; - - sendPlainMessage = (channel: string, body: string): boolean => { - return this.udn.sendMessage(channel, body); - }; - - // storage - getPreviousAddressPath = (): string[] => { - return StorageModel.getPath( - StorageModelSubPath.ConnectionModel, - filePaths.connectionModel.previousAddresses - ); - }; - - getAddressPath = (address: string): string[] => { - const dirPath = this.getPreviousAddressPath(); - return [...dirPath, address]; - }; - - getReconnectAddressPath = (): string[] => { - return StorageModel.getPath( - StorageModelSubPath.ConnectionModel, - filePaths.connectionModel.reconnectAddress - ); - }; - - storeAddress = (address: string): void => { - // history - const addressPath = this.getAddressPath(address); - this.storageModel.write(addressPath, ""); - - // reconnect - const reconnectAddressPath: string[] = this.getReconnectAddressPath(); - this.storageModel.write(reconnectAddressPath, address); - }; - - removeAddress = (address: string): void => { - const addressPath = this.getAddressPath(address); - this.storageModel.remove(addressPath); - }; - - get addresses(): string[] { - const dirPath = this.getPreviousAddressPath(); - return this.storageModel.list(dirPath); - } - - // init - constructor(storageModel: StorageModel) { - // create frontend - this.udn = new UDNFrontend(); - this.storageModel = storageModel; - - // setup handlers - this.udn.onmessage = (data: Message) => { - this.handleMessage(data); - }; - this.udn.onconnect = () => { - this.handleConnectionChange(); - }; - this.udn.ondisconnect = () => { - this.handleConnectionChange(); - }; - - this.udn.onmailboxcreate = (mailboxId: string) => { - console.log("created mailbox", mailboxId); - this.storeMailbox(mailboxId); - this.connectMailbox(); - }; - this.udn.onmailboxdelete = (mailboxId: string) => { - console.log(`mailbox ${mailboxId} deleted`); - this.requestNewMailbox(); - }; - this.udn.onmailboxconnect = (mailboxId: string) => { - console.log(`using mailbox ${mailboxId}`); - }; - - // reconnect - const reconnectAddressPath: string[] = this.getReconnectAddressPath(); - const reconnectAddress: string | null = - storageModel.read(reconnectAddressPath); - if (reconnectAddress != null) { - this.connect(reconnectAddress); - } - } } // paths export enum settingsModelStorageFiles { - username = "user-name", - firstDayOfWeek = "first-day-of-week", + username = "user-name", + firstDayOfWeek = "first-day-of-week", } diff --git a/src/Model/Global/fileTransferModel.ts b/src/Model/Global/fileTransferModel.ts index d4528863..64d1e6aa 100644 --- a/src/Model/Global/fileTransferModel.ts +++ b/src/Model/Global/fileTransferModel.ts @@ -1,10 +1,10 @@ // this file is responsible for sending and handling files. import { - HandlerManager, - generateRandomToken, - parse, - stringify, + HandlerManager, + generateRandomToken, + parse, + stringify, } from "../Utility/utility"; import { decryptString, encryptString } from "../Utility/crypto"; @@ -14,118 +14,120 @@ import StorageModel from "./storageModel"; import { checkMatchesObjectStructure } from "../Utility/typeSafety"; export default class FileTransferModel { - storageModel: StorageModel; - connectionModel: ConnectionModel; + storageModel: StorageModel; + connectionModel: ConnectionModel; - // data - transferData: TransferData | undefined; - fileHandlerManager: HandlerManager = new HandlerManager(); + // data + transferData: TransferData | undefined; + fileHandlerManager: HandlerManager = new HandlerManager(); - // general - generateTransferData = (): TransferData => { - const transferData: TransferData = { - channel: generateRandomToken(2).substring(0, 4), - key: generateRandomToken(3).substring(0, 6), + // general + generateTransferData = (): TransferData => { + const transferData: TransferData = { + channel: generateRandomToken(2).substring(0, 4), + key: generateRandomToken(3).substring(0, 6), + }; + this.transferData = transferData; + return transferData; }; - this.transferData = transferData; - return transferData; - }; - prepareToReceive = (transferData: TransferData) => { - this.connectionModel.addChannel(transferData.channel); - this.transferData = transferData; - }; + prepareToReceive = (transferData: TransferData) => { + this.connectionModel.addChannel(transferData.channel); + this.transferData = transferData; + }; - // handlers - handleMessage = (data: Message): void => { - if (this.transferData == undefined) return; - if (data.messageBody == undefined) return; - if (data.messageChannel != this.transferData.channel) return; + // handlers + handleMessage = (data: Message): void => { + if (this.transferData == undefined) return; + if (data.messageBody == undefined) return; + if (data.messageChannel != this.transferData.channel) return; - this.handleFile(data.messageBody); - }; + this.handleFile(data.messageBody); + }; - handleFile = async (encryptedFileData: string): Promise => { - if (this.transferData == undefined) return; + handleFile = async (encryptedFileData: string): Promise => { + if (this.transferData == undefined) return; - const decrypted: string = await decryptString( - encryptedFileData, - this.transferData.key - ); - console.log(decrypted); - - const parsed: any = parse(decrypted); - const isFileData: boolean = checkMatchesObjectStructure( - parsed, - FileDataReference - ); - if (isFileData == false) return; - - const fileData: FileData = parsed; - this.storageModel.write(fileData.path, fileData.body); - - const pathString: string = StorageModel.pathComponentsToString( - ...fileData.path - ); - this.fileHandlerManager.trigger(pathString); - }; - - // sending - sendFiles = ( - directoryPaths: IteratorObject, - callback: (path: string) => void - ): void => { - for (const directoryPath of directoryPaths) { - this.storageModel.recurse(directoryPath, (filePath: string[]) => { - this.sendFile(filePath); - const pathString: string = StorageModel.pathComponentsToString( - ...filePath + const decrypted: string = await decryptString( + encryptedFileData, + this.transferData.key, ); - callback(pathString); - }); - } - }; + console.log(decrypted); - sendFile = async (filePath: string[]): Promise => { - if (this.transferData == undefined) return; - const fileContent: string | null = this.storageModel.read(filePath); - if (fileContent == null) return; + const parsed: any = parse(decrypted); + const isFileData: boolean = checkMatchesObjectStructure( + parsed, + FileDataReference, + ); + if (isFileData == false) return; - const fileData: FileData = { - path: filePath, - body: fileContent, + const fileData: FileData = parsed; + this.storageModel.write(fileData.path, fileData.body); + + const pathString: string = StorageModel.pathComponentsToString( + ...fileData.path, + ); + this.fileHandlerManager.trigger(pathString); }; - const stringifiedFileData: string = stringify(fileData); - const encryptedFileData: string = await encryptString( - stringifiedFileData, - this.transferData.key - ); - this.connectionModel.sendPlainMessage( - this.transferData.channel, - encryptedFileData - ); - }; - // init - constructor(storageModel: StorageModel, connectionModel: ConnectionModel) { - this.storageModel = storageModel; - this.connectionModel = connectionModel; + // sending + sendFiles = ( + directoryPaths: IteratorObject, + callback: (path: string) => void, + ): void => { + for (const directoryPath of directoryPaths) { + this.storageModel.recurse(directoryPath, (filePath: string[]) => { + this.sendFile(filePath); + const pathString: string = StorageModel.pathComponentsToString( + ...filePath, + ); + callback(pathString); + }); + } + }; - this.connectionModel.messageHandlerManager.addHandler(this.handleMessage); - } + sendFile = async (filePath: string[]): Promise => { + if (this.transferData == undefined) return; + const fileContent: string | null = this.storageModel.read(filePath); + if (fileContent == null) return; + + const fileData: FileData = { + path: filePath, + body: fileContent, + }; + const stringifiedFileData: string = stringify(fileData); + const encryptedFileData: string = await encryptString( + stringifiedFileData, + this.transferData.key, + ); + this.connectionModel.sendPlainMessage( + this.transferData.channel, + encryptedFileData, + ); + }; + + // init + constructor(storageModel: StorageModel, connectionModel: ConnectionModel) { + this.storageModel = storageModel; + this.connectionModel = connectionModel; + + this.connectionModel.messageHandlerManager.addHandler( + this.handleMessage, + ); + } } export interface TransferData { - channel: string; - key: string; + channel: string; + key: string; } export interface FileData { - path: string[]; - body: string; + path: string[]; + body: string; } export const FileDataReference: FileData = { - path: [""], - body: "", + path: [""], + body: "", }; diff --git a/src/Model/Global/settingsModel.ts b/src/Model/Global/settingsModel.ts index 0a40c604..30d57451 100644 --- a/src/Model/Global/settingsModel.ts +++ b/src/Model/Global/settingsModel.ts @@ -3,55 +3,55 @@ import StorageModel, { StorageModelSubPath, filePaths } from "./storageModel"; export default class SettingsModel { - storageModel: StorageModel; + storageModel: StorageModel; - // data - username: string; - firstDayOfWeek: string; + // data + username: string; + firstDayOfWeek: string; - // storage - setName(newValue: string): void { - this.username = newValue; - const path = StorageModel.getPath( - StorageModelSubPath.SettingsModel, - filePaths.settingsModel.username - ); - this.storageModel.write(path, newValue); - } + // storage + setName(newValue: string): void { + this.username = newValue; + const path = StorageModel.getPath( + StorageModelSubPath.SettingsModel, + filePaths.settingsModel.username, + ); + this.storageModel.write(path, newValue); + } - setFirstDayOfWeek(newValue: string): void { - this.firstDayOfWeek = newValue; - const path = StorageModel.getPath( - StorageModelSubPath.SettingsModel, - filePaths.settingsModel.firstDayOfWeek - ); - this.storageModel.write(path, newValue); - } + setFirstDayOfWeek(newValue: string): void { + this.firstDayOfWeek = newValue; + const path = StorageModel.getPath( + StorageModelSubPath.SettingsModel, + filePaths.settingsModel.firstDayOfWeek, + ); + this.storageModel.write(path, newValue); + } - // load - loadUsernam(): void { - const path = StorageModel.getPath( - StorageModelSubPath.SettingsModel, - filePaths.settingsModel.username - ); - const content = this.storageModel.read(path); - this.username = content ?? ""; - } + // load + loadUsernam(): void { + const path = StorageModel.getPath( + StorageModelSubPath.SettingsModel, + filePaths.settingsModel.username, + ); + const content = this.storageModel.read(path); + this.username = content ?? ""; + } - loadFirstDayofWeek(): void { - const path = StorageModel.getPath( - StorageModelSubPath.SettingsModel, - filePaths.settingsModel.firstDayOfWeek - ); - const content = this.storageModel.read(path); - this.firstDayOfWeek = content ?? "0"; - } + loadFirstDayofWeek(): void { + const path = StorageModel.getPath( + StorageModelSubPath.SettingsModel, + filePaths.settingsModel.firstDayOfWeek, + ); + const content = this.storageModel.read(path); + this.firstDayOfWeek = content ?? "0"; + } - // init - constructor(storageModel: StorageModel) { - this.storageModel = storageModel; + // init + constructor(storageModel: StorageModel) { + this.storageModel = storageModel; - this.loadUsernam(); - this.loadFirstDayofWeek(); - } + this.loadUsernam(); + this.loadFirstDayofWeek(); + } } diff --git a/src/Model/Global/storageModel.ts b/src/Model/Global/storageModel.ts index 334e4ff8..ea1d3571 100644 --- a/src/Model/Global/storageModel.ts +++ b/src/Model/Global/storageModel.ts @@ -6,207 +6,217 @@ import { localeCompare, parseValidObject, stringify } from "../Utility/utility"; export const PATH_COMPONENT_SEPARATOR = "\\"; export default class StorageModel { - storageEntryTree: StorageEntry = {}; + storageEntryTree: StorageEntry = {}; - // read - read = (pathComponents: string[]): string | null => { - const pathString: string = StorageModel.pathComponentsToString( - ...pathComponents - ); - return localStorage.getItem(pathString); - }; + // read + read = (pathComponents: string[]): string | null => { + const pathString: string = StorageModel.pathComponentsToString( + ...pathComponents, + ); + return localStorage.getItem(pathString); + }; - list = (pathComponents: string[]): string[] => { - let currentParent: StorageEntry = this.storageEntryTree; - for (const component of pathComponents) { - const nextParent: StorageEntry | undefined = currentParent[component]; - if (nextParent == undefined) return []; - currentParent = nextParent; + list = (pathComponents: string[]): string[] => { + let currentParent: StorageEntry = this.storageEntryTree; + for (const component of pathComponents) { + const nextParent: StorageEntry | undefined = + currentParent[component]; + if (nextParent == undefined) return []; + currentParent = nextParent; + } + + return [...Object.keys(currentParent).sort(localeCompare)]; + }; + + // write + write = (pathComponents: string[], value: string): void => { + const pathString: string = StorageModel.pathComponentsToString( + ...pathComponents, + ); + localStorage.setItem(pathString, value); + this.updateTree(...pathComponents); + }; + + remove = ( + pathComponents: string[], + shouldInitialize: boolean = true, + ): void => { + const pathString: string = StorageModel.pathComponentsToString( + ...pathComponents, + ); + localStorage.removeItem(pathString); + + if (shouldInitialize == true) { + this.initializeTree(); + } + }; + + rename = ( + sourcePathComponents: string[], + destinationPathComponents: string[], + shouldInitialize: boolean = true, + ): boolean => { + const content: string | null = this.read(sourcePathComponents); + if (content == null) return false; + + this.write(destinationPathComponents, content); + this.remove(sourcePathComponents); + + if (shouldInitialize == true) { + this.initializeTree(); + } + return true; + }; + + // recursion + recurse = (rootDirectory: string[], fn: (path: string[]) => void): void => { + loop_over_files: for (const key of Object.keys(localStorage)) { + // get path of current entity + const pathComponentsOfCurrentEntity: string[] = + StorageModel.stringToPathComponents(key); + + // exit if entity does not match + loop_over_path_components: for ( + let i = 0; + i < rootDirectory.length; + i++ + ) { + if (!pathComponentsOfCurrentEntity[i]) continue loop_over_files; + if (pathComponentsOfCurrentEntity[i] != rootDirectory[i]) + continue loop_over_files; + } + + // execute + fn(pathComponentsOfCurrentEntity); + } + this.initializeTree(); + }; + + removeRecursively = (pathComponents: string[]): void => { + this.recurse(pathComponents, (path: string[]) => + this.remove(path, false), + ); + this.initializeTree(); + }; + + renameRecursively = ( + sourcePathComponents: string[], + destinationPathComponents: string[], + ): void => { + this.recurse(sourcePathComponents, (path: string[]) => { + // assemble destinationPath + const relativePathOfCurrentEntity = path.slice( + sourcePathComponents.length, + ); + const destinationPathComponentsOfCurrentEntity = [ + ...destinationPathComponents, + ...relativePathOfCurrentEntity, + ]; + + // rename + this.rename(path, destinationPathComponentsOfCurrentEntity, false); + }); + this.initializeTree(); + }; + + // stringifiable + writeStringifiable = ( + pathComponents: string[], + value: ValidObject, + ): void => { + const valueString: string = stringify(value); + this.write(pathComponents, valueString); + }; + + readStringifiable = ( + pathComponents: string[], + reference: T, + ): T | null => { + const valueString: string | null = this.read(pathComponents); + if (!valueString) return null; + + const object: any | null = parseValidObject(valueString, reference); + if (object == null) return null; + + return object; + }; + + // cleaning + removeJunk = (): void => { + this.recurse([], (path: string[]) => { + if (path[0] == DATA_VERSION) return; + this.remove(path); + }); + }; + + // tree + initializeTree = (): void => { + console.log("initializing tree"); + + this.storageEntryTree = {}; + for (const key of Object.keys(localStorage)) { + const components: string[] = + StorageModel.stringToPathComponents(key); + this.updateTree(...components); + } + }; + + updateTree = (...pathComponents: string[]): void => { + let currentParent: StorageEntry = this.storageEntryTree; + for (const pathPart of pathComponents) { + if (!currentParent[pathPart]) { + currentParent[pathPart] = {}; + } + + currentParent = currentParent[pathPart]; + } + }; + + printTree = (): string => { + return stringify(this.storageEntryTree); + }; + + // init + constructor() { + this.initializeTree(); } - return [...Object.keys(currentParent).sort(localeCompare)]; - }; + // utility + static getFileName = (pathComponents: string[]): string => { + return pathComponents[pathComponents.length - 1] || "\\"; + }; - // write - write = (pathComponents: string[], value: string): void => { - const pathString: string = StorageModel.pathComponentsToString( - ...pathComponents - ); - localStorage.setItem(pathString, value); - this.updateTree(...pathComponents); - }; + static getFileNameFromString = (pathString: string): string => { + const pathComponents: string[] = + this.stringToPathComponents(pathString); + return pathComponents[pathComponents.length - 1] || "\\"; + }; - remove = ( - pathComponents: string[], - shouldInitialize: boolean = true - ): void => { - const pathString: string = StorageModel.pathComponentsToString( - ...pathComponents - ); - localStorage.removeItem(pathString); + static pathComponentsToString = (...pathComponents: string[]): string => { + return pathComponents + .filter((x) => x != "") + .join(PATH_COMPONENT_SEPARATOR); + }; - if (shouldInitialize == true) { - this.initializeTree(); + static stringToPathComponents = (string: string): string[] => { + return string.split(PATH_COMPONENT_SEPARATOR).filter((x) => x != ""); + }; + + static join = (...items: string[]): string => { + let allComponents: string[] = []; + for (const item of items) { + const parts = this.stringToPathComponents(item); + allComponents.push(...parts); + } + return StorageModel.pathComponentsToString(...allComponents); + }; + + static getPath( + locationName: StorageModelSubPath, + filePath: string[], + ): string[] { + return [DATA_VERSION, locationName, ...filePath]; } - }; - - rename = ( - sourcePathComponents: string[], - destinationPathComponents: string[], - shouldInitialize: boolean = true - ): boolean => { - const content: string | null = this.read(sourcePathComponents); - if (content == null) return false; - - this.write(destinationPathComponents, content); - this.remove(sourcePathComponents); - - if (shouldInitialize == true) { - this.initializeTree(); - } - return true; - }; - - // recursion - recurse = (rootDirectory: string[], fn: (path: string[]) => void): void => { - loop_over_files: for (const key of Object.keys(localStorage)) { - // get path of current entity - const pathComponentsOfCurrentEntity: string[] = - StorageModel.stringToPathComponents(key); - - // exit if entity does not match - loop_over_path_components: for ( - let i = 0; - i < rootDirectory.length; - i++ - ) { - if (!pathComponentsOfCurrentEntity[i]) continue loop_over_files; - if (pathComponentsOfCurrentEntity[i] != rootDirectory[i]) - continue loop_over_files; - } - - // execute - fn(pathComponentsOfCurrentEntity); - } - this.initializeTree(); - }; - - removeRecursively = (pathComponents: string[]): void => { - this.recurse(pathComponents, (path: string[]) => this.remove(path, false)); - this.initializeTree(); - }; - - renameRecursively = ( - sourcePathComponents: string[], - destinationPathComponents: string[] - ): void => { - this.recurse(sourcePathComponents, (path: string[]) => { - // assemble destinationPath - const relativePathOfCurrentEntity = path.slice( - sourcePathComponents.length - ); - const destinationPathComponentsOfCurrentEntity = [ - ...destinationPathComponents, - ...relativePathOfCurrentEntity, - ]; - - // rename - this.rename(path, destinationPathComponentsOfCurrentEntity, false); - }); - this.initializeTree(); - }; - - // stringifiable - writeStringifiable = (pathComponents: string[], value: ValidObject): void => { - const valueString: string = stringify(value); - this.write(pathComponents, valueString); - }; - - readStringifiable = ( - pathComponents: string[], - reference: T - ): T | null => { - const valueString: string | null = this.read(pathComponents); - if (!valueString) return null; - - const object: any | null = parseValidObject(valueString, reference); - if (object == null) return null; - - return object; - }; - - // cleaning - removeJunk = (): void => { - this.recurse([], (path: string[]) => { - if (path[0] == DATA_VERSION) return; - this.remove(path); - }) - } - - // tree - initializeTree = (): void => { - console.log("initializing tree"); - - this.storageEntryTree = {}; - for (const key of Object.keys(localStorage)) { - const components: string[] = StorageModel.stringToPathComponents(key); - this.updateTree(...components); - } - }; - - updateTree = (...pathComponents: string[]): void => { - let currentParent: StorageEntry = this.storageEntryTree; - for (const pathPart of pathComponents) { - if (!currentParent[pathPart]) { - currentParent[pathPart] = {}; - } - - currentParent = currentParent[pathPart]; - } - }; - - printTree = (): string => { - return stringify(this.storageEntryTree); - }; - - // init - constructor() { - this.initializeTree(); - } - - // utility - static getFileName = (pathComponents: string[]): string => { - return pathComponents[pathComponents.length - 1] || "\\"; - }; - - static getFileNameFromString = (pathString: string): string => { - const pathComponents: string[] = this.stringToPathComponents(pathString); - return pathComponents[pathComponents.length - 1] || "\\"; - }; - - static pathComponentsToString = (...pathComponents: string[]): string => { - return pathComponents.filter((x) => x != "").join(PATH_COMPONENT_SEPARATOR); - }; - - static stringToPathComponents = (string: string): string[] => { - return string.split(PATH_COMPONENT_SEPARATOR).filter((x) => x != ""); - }; - - static join = (...items: string[]): string => { - let allComponents: string[] = []; - for (const item of items) { - const parts = this.stringToPathComponents(item); - allComponents.push(...parts); - } - return StorageModel.pathComponentsToString(...allComponents); - }; - - static getPath( - locationName: StorageModelSubPath, - filePath: string[] - ): string[] { - return [DATA_VERSION, locationName, ...filePath]; - } } // types @@ -214,37 +224,37 @@ export type StorageEntry = { [key: string]: StorageEntry }; // locations export enum StorageModelSubPath { - Chat = "chat", + Chat = "chat", - ConnectionModel = "connection", - SettingsModel = "settings", + ConnectionModel = "connection", + SettingsModel = "settings", } export const filePaths = { - connectionModel: { - socketAddress: ["socket-address"], - reconnectAddress: ["reconnect-address"], - outbox: ["outbox"], - mailboxes: ["mailboxes"], + connectionModel: { + socketAddress: ["socket-address"], + reconnectAddress: ["reconnect-address"], + outbox: ["outbox"], + mailboxes: ["mailboxes"], - previousAddresses: ["previous-addresses"], - }, + previousAddresses: ["previous-addresses"], + }, - chat: { - base: [], - chatBase: (id: string) => [id], - info: (id: string) => [...filePaths.chat.chatBase(id), "info"], - color: (id: string) => [...filePaths.chat.chatBase(id), "color"], - messages: (id: string) => [...filePaths.chat.chatBase(id), "messages"], - lastUsedPage: (id: string) => [ - ...filePaths.chat.chatBase(id), - "last-used-page", - ], - files: (id: string) => [...filePaths.chat.chatBase(id), "files"], - }, + chat: { + base: [], + chatBase: (id: string) => [id], + info: (id: string) => [...filePaths.chat.chatBase(id), "info"], + color: (id: string) => [...filePaths.chat.chatBase(id), "color"], + messages: (id: string) => [...filePaths.chat.chatBase(id), "messages"], + lastUsedPage: (id: string) => [ + ...filePaths.chat.chatBase(id), + "last-used-page", + ], + files: (id: string) => [...filePaths.chat.chatBase(id), "files"], + }, - settingsModel: { - username: ["user-name"], - firstDayOfWeek: ["first-day-of-week"], - }, + settingsModel: { + username: ["user-name"], + firstDayOfWeek: ["first-day-of-week"], + }, }; diff --git a/src/Model/Utility/crypto.ts b/src/Model/Utility/crypto.ts index f839e0a3..5345f62a 100644 --- a/src/Model/Utility/crypto.ts +++ b/src/Model/Utility/crypto.ts @@ -3,113 +3,113 @@ const ENCRYPTION_ALG = "AES-GCM"; // minimal export async function encryptString( - plaintext: string, - passphrase: string + plaintext: string, + passphrase: string, ): Promise { - if (!window.crypto.subtle) return plaintext; + if (!window.crypto.subtle) return plaintext; - const iv = generateIV(); - const key = await importKey(passphrase, "encrypt"); - const encryptedArray = await encrypt(iv, key, plaintext); + const iv = generateIV(); + const key = await importKey(passphrase, "encrypt"); + const encryptedArray = await encrypt(iv, key, plaintext); - const encryptionData = { - iv: uInt8ToArray(iv), - encryptedArray: uInt8ToArray(encryptedArray), - }; - return btoa(JSON.stringify(encryptionData)); + const encryptionData = { + iv: uInt8ToArray(iv), + encryptedArray: uInt8ToArray(encryptedArray), + }; + return btoa(JSON.stringify(encryptionData)); } export async function decryptString( - cyphertext: string, - passphrase: string + cyphertext: string, + passphrase: string, ): Promise { - try { - const encrypionData = JSON.parse(atob(cyphertext)); - const iv = arrayToUint8(encrypionData.iv); - const encryptedArray = arrayToUint8(encrypionData.encryptedArray); + try { + const encrypionData = JSON.parse(atob(cyphertext)); + const iv = arrayToUint8(encrypionData.iv); + const encryptedArray = arrayToUint8(encrypionData.encryptedArray); - const key = await importKey(passphrase, "decrypt"); - return await decrypt(iv, key, encryptedArray); - } catch { - return cyphertext; - } + const key = await importKey(passphrase, "decrypt"); + return await decrypt(iv, key, encryptedArray); + } catch { + return cyphertext; + } } // process export function encode(string: string): Uint8Array { - return new TextEncoder().encode(string); + return new TextEncoder().encode(string); } export function decode(array: Uint8Array): string { - return new TextDecoder("utf-8").decode(array); + return new TextDecoder("utf-8").decode(array); } export async function encrypt( - iv: Uint8Array, - key: CryptoKey, - message: string + iv: Uint8Array, + key: CryptoKey, + message: string, ): Promise { - const arrayBuffer = await window.crypto.subtle.encrypt( - { name: ENCRYPTION_ALG, iv }, - key, - encode(message) - ); - return new Uint8Array(arrayBuffer); + const arrayBuffer = await window.crypto.subtle.encrypt( + { name: ENCRYPTION_ALG, iv }, + key, + encode(message), + ); + return new Uint8Array(arrayBuffer); } export async function decrypt( - iv: Uint8Array, - key: CryptoKey, - cyphertext: Uint8Array + iv: Uint8Array, + key: CryptoKey, + cyphertext: Uint8Array, ): Promise { - const arrayBuffer = await crypto.subtle.decrypt( - { name: ENCRYPTION_ALG, iv }, - key, - cyphertext - ); - return arrayBufferToString(arrayBuffer); + const arrayBuffer = await crypto.subtle.decrypt( + { name: ENCRYPTION_ALG, iv }, + key, + cyphertext, + ); + return arrayBufferToString(arrayBuffer); } export async function hash(encoded: Uint8Array): Promise { - return await crypto.subtle.digest("SHA-256", encoded); + return await crypto.subtle.digest("SHA-256", encoded); } // iv export function generateIV(): Uint8Array { - return crypto.getRandomValues(new Uint8Array(IV_SIZE)); + return crypto.getRandomValues(new Uint8Array(IV_SIZE)); } export function splitMergedString(mergedString: string): [Uint8Array, string] { - const iv = mergedString.slice(0, IV_SIZE); - const encryptedString = mergedString.slice(IV_SIZE); + const iv = mergedString.slice(0, IV_SIZE); + const encryptedString = mergedString.slice(IV_SIZE); - return [JSON.parse(iv), encryptedString]; + return [JSON.parse(iv), encryptedString]; } // key export async function importKey( - passphrase: string, - purpose: "encrypt" | "decrypt" + passphrase: string, + purpose: "encrypt" | "decrypt", ): Promise { - return await crypto.subtle.importKey( - "raw", - await hash(encode(passphrase)), - { name: ENCRYPTION_ALG }, - false, - [purpose] - ); + return await crypto.subtle.importKey( + "raw", + await hash(encode(passphrase)), + { name: ENCRYPTION_ALG }, + false, + [purpose], + ); } // conversion export function arrayBufferToString(arrayBuffer: ArrayBuffer): string { - const uInt8Array = new Uint8Array(arrayBuffer); - return decode(uInt8Array); + const uInt8Array = new Uint8Array(arrayBuffer); + return decode(uInt8Array); } export function uInt8ToArray(uInt8Array: Uint8Array): number[] { - return Array.from(uInt8Array); + return Array.from(uInt8Array); } export function arrayToUint8(array: number[]): Uint8Array { - return new Uint8Array(array); + return new Uint8Array(array); } diff --git a/src/Model/Utility/typeSafety.ts b/src/Model/Utility/typeSafety.ts index f425e1c3..9f61ab17 100644 --- a/src/Model/Utility/typeSafety.ts +++ b/src/Model/Utility/typeSafety.ts @@ -1,48 +1,48 @@ export const DATA_VERSION = "v2"; export function checkIsValidObject(object: any): boolean { - return object.dataVersion == DATA_VERSION; + return object.dataVersion == DATA_VERSION; } export function checkMatchesObjectStructure( - objectToCheck: any, - reference: any + objectToCheck: any, + reference: any, ): boolean { - if (typeof reference != "object") { - return typeof objectToCheck == typeof reference; - } - - for (const key of Object.keys(reference)) { - const requiredType = typeof reference[key]; - const actualType = typeof objectToCheck[key]; - if (requiredType != actualType) return false; - - if (Array.isArray(reference[key]) || reference[key] instanceof Set) { - // only check if array is not empty - if (objectToCheck[key].length == 0) continue; - if (objectToCheck[key].size == 0) continue; - - // check first item of array - const [firstOfObjectToCheck] = objectToCheck[key]; - const [fisrtOfReference] = reference[key]; - const doesFirstItemMatch = checkMatchesObjectStructure( - firstOfObjectToCheck, - fisrtOfReference - ); - if (doesFirstItemMatch == false) return false; - } else if (requiredType == "object") { - // recurse into objects - const doesNestedObjectMatch = checkMatchesObjectStructure( - objectToCheck[key], - reference[key] - ); - if (doesNestedObjectMatch == false) return false; + if (typeof reference != "object") { + return typeof objectToCheck == typeof reference; } - } - return true; + for (const key of Object.keys(reference)) { + const requiredType = typeof reference[key]; + const actualType = typeof objectToCheck[key]; + if (requiredType != actualType) return false; + + if (Array.isArray(reference[key]) || reference[key] instanceof Set) { + // only check if array is not empty + if (objectToCheck[key].length == 0) continue; + if (objectToCheck[key].size == 0) continue; + + // check first item of array + const [firstOfObjectToCheck] = objectToCheck[key]; + const [fisrtOfReference] = reference[key]; + const doesFirstItemMatch = checkMatchesObjectStructure( + firstOfObjectToCheck, + fisrtOfReference, + ); + if (doesFirstItemMatch == false) return false; + } else if (requiredType == "object") { + // recurse into objects + const doesNestedObjectMatch = checkMatchesObjectStructure( + objectToCheck[key], + reference[key], + ); + if (doesNestedObjectMatch == false) return false; + } + } + + return true; } export interface ValidObject { - dataVersion: "v2"; + dataVersion: "v2"; } diff --git a/src/Model/Utility/utility.ts b/src/Model/Utility/utility.ts index b10b6b55..64448c98 100644 --- a/src/Model/Utility/utility.ts +++ b/src/Model/Utility/utility.ts @@ -1,277 +1,278 @@ import { checkIsValidObject, checkMatchesObjectStructure } from "./typeSafety"; export interface Stringifiable { - toString(): string; + toString(): string; } // crypto export function generateRandomToken(length: number): string { - const array = new Uint8Array(length); - crypto.getRandomValues(array); - return array.join(""); + const array = new Uint8Array(length); + crypto.getRandomValues(array); + return array.join(""); } // date export function createTimestamp(): string { - return new Date().toISOString(); + return new Date().toISOString(); } // filters etc export type StringEntryObject = { [key: string]: Stringifiable | undefined }; export function filterObjectsByStringEntries( - reference: StringEntryObject, - converter: (object: T) => StringEntryObject, - objects: T[] + reference: StringEntryObject, + converter: (object: T) => StringEntryObject, + objects: T[], ): Set { - const matches: Set = new Set(); + const matches: Set = new Set(); - object_loop: for (const object of objects) { - const doesMatch: boolean = checkDoesObjectMatchReference( - reference, - converter(object) - ); - if (doesMatch) matches.add(object); - } + object_loop: for (const object of objects) { + const doesMatch: boolean = checkDoesObjectMatchReference( + reference, + converter(object), + ); + if (doesMatch) matches.add(object); + } - return matches; + return matches; } export function checkDoesObjectMatchReference( - reference: StringEntryObject, - stringEntryObject: StringEntryObject + reference: StringEntryObject, + stringEntryObject: StringEntryObject, ): boolean { - reference_entry_loop: for (const referenceEntry of Object.entries( - reference - )) { - const [referenceKey, referenceValue] = referenceEntry; - const stringEntryObjectValue: Stringifiable | undefined = - stringEntryObject[referenceKey]; + reference_entry_loop: for (const referenceEntry of Object.entries( + reference, + )) { + const [referenceKey, referenceValue] = referenceEntry; + const stringEntryObjectValue: Stringifiable | undefined = + stringEntryObject[referenceKey]; - if (referenceValue == undefined) return false; + if (referenceValue == undefined) return false; - if (referenceValue[0] == "-") { - const strippedReferenceValue: string = referenceValue - .toString() - .substring(1); - // property may not exist - if ( - strippedReferenceValue == "" && - stringEntryObjectValue != undefined && - stringEntryObjectValue != "" - ) { - return false; - } + if (referenceValue[0] == "-") { + const strippedReferenceValue: string = referenceValue + .toString() + .substring(1); + // property may not exist + if ( + strippedReferenceValue == "" && + stringEntryObjectValue != undefined && + stringEntryObjectValue != "" + ) { + return false; + } - // property may not match - if (stringEntryObjectValue == strippedReferenceValue) { - return false; - } - } else { - // property must exist but be anything - if ( - referenceValue == "" && - (stringEntryObjectValue == undefined || stringEntryObjectValue == "") - ) { - return false; - } else if (referenceValue == "") { - continue reference_entry_loop; - } + // property may not match + if (stringEntryObjectValue == strippedReferenceValue) { + return false; + } + } else { + // property must exist but be anything + if ( + referenceValue == "" && + (stringEntryObjectValue == undefined || + stringEntryObjectValue == "") + ) { + return false; + } else if (referenceValue == "") { + continue reference_entry_loop; + } - // property must match - if (stringEntryObjectValue != referenceValue) { - return false; - } + // property must match + if (stringEntryObjectValue != referenceValue) { + return false; + } + } } - } - return true; + return true; } export function collectObjectValuesForKey( - key: string, - converter: (object: T) => StringEntryObject, - objects: T[] + key: string, + converter: (object: T) => StringEntryObject, + objects: T[], ): string[] { - const values: Set = new Set(); + const values: Set = new Set(); - for (const object of objects) { - const stringEntryObject: StringEntryObject = converter(object); - const stringEntryObjectValue: Stringifiable | undefined = - stringEntryObject[key]; - if (stringEntryObjectValue == undefined || stringEntryObjectValue == "") - continue; + for (const object of objects) { + const stringEntryObject: StringEntryObject = converter(object); + const stringEntryObjectValue: Stringifiable | undefined = + stringEntryObject[key]; + if (stringEntryObjectValue == undefined || stringEntryObjectValue == "") + continue; - values.add(stringEntryObjectValue.toString()); - } + values.add(stringEntryObjectValue.toString()); + } - return [...values.values()]; + return [...values.values()]; } export function filterObjectsByWords( - query: string, - getStringsOfObject: (object: T) => string[], - objects: T[] + query: string, + getStringsOfObject: (object: T) => string[], + objects: T[], ): Set { - const matches: Set = new Set(); + const matches: Set = new Set(); - object_loop: for (const object of objects) { - const doesMatch: boolean = checkDoesObjectMatchSearch( - query, - getStringsOfObject, - object - ); - if (doesMatch) matches.add(object); - } + object_loop: for (const object of objects) { + const doesMatch: boolean = checkDoesObjectMatchSearch( + query, + getStringsOfObject, + object, + ); + if (doesMatch) matches.add(object); + } - return matches; + return matches; } export function checkDoesObjectMatchSearch( - query: string, - getStringsOfObject: (object: T) => string[], - object: T + query: string, + getStringsOfObject: (object: T) => string[], + object: T, ): boolean { - if (query == "") return true; + if (query == "") return true; - const stringsInObject: string[] = getStringsOfObject(object); - const wordsInObject: string[] = []; - for (const string of stringsInObject) { - const lowercaseWordsInString = string - .toString() - .toLowerCase() - .split(" ") - .filter((word) => word != ""); - wordsInObject.push(...lowercaseWordsInString); - } - - const lowercaseWordsInQuery = query - .toLowerCase() - .split(" ") - .filter((word) => word != ""); - for (const queryWord of lowercaseWordsInQuery) { - if (queryWord[0] == "-") { - // exclusion - const wordContent = queryWord.substring(1); - if (wordsInObject.includes(wordContent)) { - return false; - } - } else { - if (wordsInObject.includes(queryWord) == false) { - return false; - } + const stringsInObject: string[] = getStringsOfObject(object); + const wordsInObject: string[] = []; + for (const string of stringsInObject) { + const lowercaseWordsInString = string + .toString() + .toLowerCase() + .split(" ") + .filter((word) => word != ""); + wordsInObject.push(...lowercaseWordsInString); } - } - return true; + const lowercaseWordsInQuery = query + .toLowerCase() + .split(" ") + .filter((word) => word != ""); + for (const queryWord of lowercaseWordsInQuery) { + if (queryWord[0] == "-") { + // exclusion + const wordContent = queryWord.substring(1); + if (wordsInObject.includes(wordContent)) { + return false; + } + } else { + if (wordsInObject.includes(queryWord) == false) { + return false; + } + } + } + + return true; } // handlers export type Handler = (item: T) => void; export class HandlerManager { - handlers: Set> = new Set(); + handlers: Set> = new Set(); - // manage - addHandler = (handler: Handler): void => { - this.handlers.add(handler); - }; + // manage + addHandler = (handler: Handler): void => { + this.handlers.add(handler); + }; - deleteHandler = (handler: Handler): void => { - this.handlers.delete(handler); - }; + deleteHandler = (handler: Handler): void => { + this.handlers.delete(handler); + }; - // trigger - trigger = (item: T): void => { - for (const handler of this.handlers) { - handler(item); - } - }; + // trigger + trigger = (item: T): void => { + for (const handler of this.handlers) { + handler(item); + } + }; } // sorting export class IndexManager { - private itemToString: (item: T) => string; + private itemToString: (item: T) => string; - sortedStrings: string[] = []; + sortedStrings: string[] = []; - // methods - update = (items: T[]): void => { - this.sortedStrings = []; + // methods + update = (items: T[]): void => { + this.sortedStrings = []; - let strings: string[] = []; - for (const item of items) { - const string: string = this.itemToString(item); - strings.push(string); + let strings: string[] = []; + for (const item of items) { + const string: string = this.itemToString(item); + strings.push(string); + } + + this.sortedStrings = strings.sort(localeCompare); + }; + + getIndex = (item: T): number => { + const string: string = this.itemToString(item); + const index: number = this.sortedStrings.indexOf(string); + return index; + }; + + // init + constructor(itemToString: (item: T) => string) { + this.itemToString = itemToString; } - - this.sortedStrings = strings.sort(localeCompare); - }; - - getIndex = (item: T): number => { - const string: string = this.itemToString(item); - const index: number = this.sortedStrings.indexOf(string); - return index; - }; - - // init - constructor(itemToString: (item: T) => string) { - this.itemToString = itemToString; - } } // storage export function getLocalStorageItemAndClear(key: string): string | null { - const value: string | null = localStorage.getItem(key); - localStorage.removeItem(key); - if (value != null) localStorage.setItem(`_${key}`, value); - return value; + const value: string | null = localStorage.getItem(key); + localStorage.removeItem(key); + if (value != null) localStorage.setItem(`_${key}`, value); + return value; } // string & parsing export function stringify(data: any): string { - return JSON.stringify(data, null, 4); + return JSON.stringify(data, null, 4); } export function padZero(string: string | undefined, length: number): string { - return (string ?? "").padStart(length, "0"); + return (string ?? "").padStart(length, "0"); } export function parse(string: string): any { - try { - return JSON.parse(string); - } catch { - return {}; - } + try { + return JSON.parse(string); + } catch { + return {}; + } } export function parseValidObject(string: string, reference: T): T | null { - const parsed: any = parse(string); - if (checkIsValidObject(parsed) == false) return null; + const parsed: any = parse(string); + if (checkIsValidObject(parsed) == false) return null; - const doesMatchReference: boolean = checkMatchesObjectStructure( - parsed, - reference - ); - if (doesMatchReference == false) return null; + const doesMatchReference: boolean = checkMatchesObjectStructure( + parsed, + reference, + ); + if (doesMatchReference == false) return null; - return parsed; + return parsed; } export function parseOrFallback(inputString: string): any { - try { - return JSON.parse(inputString); - } catch { - return inputString; - } + try { + return JSON.parse(inputString); + } catch { + return inputString; + } } export function parseArray(inputString: string): any[] { - const parsed: any = parseOrFallback(inputString); - if (Array.isArray(parsed) == false) return []; - return parsed; + const parsed: any = parseOrFallback(inputString); + if (Array.isArray(parsed) == false) return []; + return parsed; } // sort export function localeCompare(a: string, b: string): number { - return a.localeCompare(b); + return a.localeCompare(b); } diff --git a/src/Upgrader/v1.ts b/src/Upgrader/v1.ts index 2e0694ca..563a5235 100644 --- a/src/Upgrader/v1.ts +++ b/src/Upgrader/v1.ts @@ -1,14 +1,18 @@ // this file is responsible for migrating data from v1 to v2. import { - BoardInfoFileContent, - TaskFileContent, + BoardInfoFileContent, + TaskFileContent, } from "../Model/Files/boardsAndTasksModel"; import ChatModel, { - ChatMessage, - ChatMessageStatus, + ChatMessage, + ChatMessageStatus, } from "../Model/Chat/chatModel"; -import { getLocalStorageItemAndClear, parseArray, parseOrFallback } from "../Model/Utility/utility"; +import { + getLocalStorageItemAndClear, + parseArray, + parseOrFallback, +} from "../Model/Utility/utility"; import ChatListModel from "../Model/Chat/chatListModel"; import ConnectionModel from "../Model/Global/connectionModel"; @@ -19,263 +23,270 @@ import { translations } from "../View/translations"; import { v4 } from "uuid"; export default class v1Upgrader { - // general - migrateSettings = (): void => { - const name: string | null = getLocalStorageItemAndClear("sender-name"); - if (name != null) { - const parsedName: string = parseOrFallback(name); - this.settingsModel.setName(parsedName); - } + // general + migrateSettings = (): void => { + const name: string | null = getLocalStorageItemAndClear("sender-name"); + if (name != null) { + const parsedName: string = parseOrFallback(name); + this.settingsModel.setName(parsedName); + } - const firstDayOfWeek: string | null = - getLocalStorageItemAndClear("first-day-of-week"); - if (firstDayOfWeek != null) { - const parsedFirstDayOfWeek: string = parseOrFallback(firstDayOfWeek); - this.settingsModel.setFirstDayOfWeek(parsedFirstDayOfWeek); - } - }; + const firstDayOfWeek: string | null = + getLocalStorageItemAndClear("first-day-of-week"); + if (firstDayOfWeek != null) { + const parsedFirstDayOfWeek: string = + parseOrFallback(firstDayOfWeek); + this.settingsModel.setFirstDayOfWeek(parsedFirstDayOfWeek); + } + }; - migrateConnections = (): void => { - const previousAddressString: string | null = - getLocalStorageItemAndClear("previous-addresses"); - if (previousAddressString == null) return; + migrateConnections = (): void => { + const previousAddressString: string | null = + getLocalStorageItemAndClear("previous-addresses"); + if (previousAddressString == null) return; - const previousAddresses: any[] = parseArray(previousAddressString); - for (const address of previousAddresses) { - if (typeof address != "string") continue; - this.connectionModel.storeAddress(address); - } - }; + const previousAddresses: any[] = parseArray(previousAddressString); + for (const address of previousAddresses) { + if (typeof address != "string") continue; + this.connectionModel.storeAddress(address); + } + }; - // chats - migrateChats = (): void => { - const chatIdString: string | null = getLocalStorageItemAndClear("chat-ids"); - if (chatIdString == null) return; + // chats + migrateChats = (): void => { + const chatIdString: string | null = + getLocalStorageItemAndClear("chat-ids"); + if (chatIdString == null) return; - const chatIds: any[] = parseArray(chatIdString); - for (const chatId of chatIds) { - if (typeof chatId != "string") continue; - this.migrateChatById(chatId); - } - }; + const chatIds: any[] = parseArray(chatIdString); + for (const chatId of chatIds) { + if (typeof chatId != "string") continue; + this.migrateChatById(chatId); + } + }; - migrateChatById = (id: string): void => { - // get primary channel - const primaryChannel: string | null = getLocalStorageItemAndClear( - storageKeys.primaryChannel(id) - ); - if (primaryChannel == null) return; - const parsedPriamryChannel: string = parseOrFallback(primaryChannel); - - // create chat - const chatModel: ChatModel = - this.chatListModel.createChat(parsedPriamryChannel); - - // secondary channels - const secondaryChannelString: string | null = getLocalStorageItemAndClear( - storageKeys.secondaryChannels(id) - ); - if (secondaryChannelString != null) { - const potentialSecondaryChannels: any[] = parseArray( - secondaryChannelString - ); - - const confirmedSecondaryChannels: string[] = []; - for (const secondaryChannel of potentialSecondaryChannels) { - if (typeof secondaryChannel != "string") continue; - confirmedSecondaryChannels.push(secondaryChannel); - } - - chatModel.setSecondaryChannels(confirmedSecondaryChannels); - } - - // encryption key - const encryptionKey: string | null = getLocalStorageItemAndClear( - storageKeys.encyptionKey(id) - ); - if (encryptionKey != null) { - const parsedEncryptionKey: string = parseOrFallback(encryptionKey); - chatModel.setEncryptionKey(parsedEncryptionKey); - } - - // messages - const messagesString: string = - getLocalStorageItemAndClear(storageKeys.messages(id)) ?? ""; - const messageOutboxString: string = - getLocalStorageItemAndClear(storageKeys.outbox(id)) ?? ""; - const potentialMessages: any[] = parseArray(messagesString); - const potentialMessagesInOutbox: any[] = parseArray(messageOutboxString); - - const addMessages = ( - potentialMessages: any[], - status: ChatMessageStatus - ) => { - for (const potentialMessage of potentialMessages) { - const isV1ChatMessage: boolean = checkMatchesObjectStructure( - potentialMessage, - V1ChatMessageReference + migrateChatById = (id: string): void => { + // get primary channel + const primaryChannel: string | null = getLocalStorageItemAndClear( + storageKeys.primaryChannel(id), ); - if (isV1ChatMessage == false) continue; - const v1ChatMessage: typeof V1ChatMessageReference = potentialMessage; + if (primaryChannel == null) return; + const parsedPriamryChannel: string = parseOrFallback(primaryChannel); - const convertedChatMessage: ChatMessage = { - dataVersion: "v2", + // create chat + const chatModel: ChatModel = + this.chatListModel.createChat(parsedPriamryChannel); - id: v4(), + // secondary channels + const secondaryChannelString: string | null = + getLocalStorageItemAndClear(storageKeys.secondaryChannels(id)); + if (secondaryChannelString != null) { + const potentialSecondaryChannels: any[] = parseArray( + secondaryChannelString, + ); - channel: v1ChatMessage.channel, - sender: v1ChatMessage.sender, - body: v1ChatMessage.body, - dateSent: v1ChatMessage.isoDate, + const confirmedSecondaryChannels: string[] = []; + for (const secondaryChannel of potentialSecondaryChannels) { + if (typeof secondaryChannel != "string") continue; + confirmedSecondaryChannels.push(secondaryChannel); + } - status, - stringifiedFile: "", + chatModel.setSecondaryChannels(confirmedSecondaryChannels); + } + + // encryption key + const encryptionKey: string | null = getLocalStorageItemAndClear( + storageKeys.encyptionKey(id), + ); + if (encryptionKey != null) { + const parsedEncryptionKey: string = parseOrFallback(encryptionKey); + chatModel.setEncryptionKey(parsedEncryptionKey); + } + + // messages + const messagesString: string = + getLocalStorageItemAndClear(storageKeys.messages(id)) ?? ""; + const messageOutboxString: string = + getLocalStorageItemAndClear(storageKeys.outbox(id)) ?? ""; + const potentialMessages: any[] = parseArray(messagesString); + const potentialMessagesInOutbox: any[] = + parseArray(messageOutboxString); + + const addMessages = ( + potentialMessages: any[], + status: ChatMessageStatus, + ) => { + for (const potentialMessage of potentialMessages) { + const isV1ChatMessage: boolean = checkMatchesObjectStructure( + potentialMessage, + V1ChatMessageReference, + ); + if (isV1ChatMessage == false) continue; + const v1ChatMessage: typeof V1ChatMessageReference = + potentialMessage; + + const convertedChatMessage: ChatMessage = { + dataVersion: "v2", + + id: v4(), + + channel: v1ChatMessage.channel, + sender: v1ChatMessage.sender, + body: v1ChatMessage.body, + dateSent: v1ChatMessage.isoDate, + + status, + stringifiedFile: "", + }; + chatModel.addMessage(convertedChatMessage); + if (status == ChatMessageStatus.Outbox) { + this.connectionModel.sendMessageOrStore( + convertedChatMessage, + ); + } + } }; - chatModel.addMessage(convertedChatMessage); - if (status == ChatMessageStatus.Outbox) { - this.connectionModel.sendMessageOrStore(convertedChatMessage); - } - } + + addMessages(potentialMessages, ChatMessageStatus.Received); + addMessages(potentialMessagesInOutbox, ChatMessageStatus.Outbox); + + // objects + const objectsString: string = + getLocalStorageItemAndClear(storageKeys.objects(id)) ?? ""; + const objectOutboxString: string = + getLocalStorageItemAndClear(storageKeys.itemOutbox(id)) ?? ""; + const potentialObjects: any[] = parseArray(objectsString); + const potentialObjectsInOutbox: any[] = parseArray(objectOutboxString); + + const addObjects = (potentialObjects: any[]): void => { + const board: BoardInfoFileContent = + chatModel.fileModel.boardsAndTasksModel.createBoard( + translations.updater.migrated, + ); + chatModel.fileModel.boardsAndTasksModel.updateBoard(board); + + for (const potentialObjectEntry of potentialObjects) { + const potentialObject = potentialObjectEntry[1]; + const isV1MessageObject: boolean = + checkIsV1MessageObject(potentialObject); + if (isV1MessageObject == false) continue; + + const objectId: string = potentialObject.id; + const objectName: string = potentialObject.title; + + const contentVersions: any[] = Object.values( + potentialObject.contentVersions, + ); + for (const potentialVersion of contentVersions) { + const isV1MessageObjectContent: boolean = + checkIsV1MessageObjectContent(potentialVersion); + if (isV1MessageObjectContent == false) continue; + + const convertedTaskFileContent: TaskFileContent = { + dataVersion: "v2", + + fileId: objectId, + fileContentId: FileModel.generateFileContentId( + potentialVersion.isoDateVersionCreated, + ), + creationDate: potentialVersion.isoDateVersionCreated, + type: "task", + + name: objectName ?? "", + boardId: board.fileId ?? "", + + description: potentialVersion.noteContent ?? "", + + category: potentialVersion.categoryName ?? "", + status: potentialVersion.status ?? "", + priority: potentialVersion.priority ?? "", + + date: potentialVersion.date ?? "", + time: potentialVersion.time ?? "", + }; + console.log(convertedTaskFileContent); + chatModel.fileModel.handleFileContent( + convertedTaskFileContent, + ); + } + } + }; + + addObjects([...potentialObjects, ...potentialObjectsInOutbox]); }; - addMessages(potentialMessages, ChatMessageStatus.Received); - addMessages(potentialMessagesInOutbox, ChatMessageStatus.Outbox); - - // objects - const objectsString: string = - getLocalStorageItemAndClear(storageKeys.objects(id)) ?? ""; - const objectOutboxString: string = - getLocalStorageItemAndClear(storageKeys.itemOutbox(id)) ?? ""; - const potentialObjects: any[] = parseArray(objectsString); - const potentialObjectsInOutbox: any[] = parseArray(objectOutboxString); - - const addObjects = (potentialObjects: any[]): void => { - const board: BoardInfoFileContent = - chatModel.fileModel.boardsAndTasksModel.createBoard( - translations.updater.migrated - ); - chatModel.fileModel.boardsAndTasksModel.updateBoard(board); - - for (const potentialObjectEntry of potentialObjects) { - const potentialObject = potentialObjectEntry[1]; - const isV1MessageObject: boolean = - checkIsV1MessageObject(potentialObject); - if (isV1MessageObject == false) continue; - - const objectId: string = potentialObject.id; - const objectName: string = potentialObject.title; - - const contentVersions: any[] = Object.values( - potentialObject.contentVersions - ); - for (const potentialVersion of contentVersions) { - const isV1MessageObjectContent: boolean = - checkIsV1MessageObjectContent(potentialVersion); - if (isV1MessageObjectContent == false) continue; - - const convertedTaskFileContent: TaskFileContent = { - dataVersion: "v2", - - fileId: objectId, - fileContentId: FileModel.generateFileContentId( - potentialVersion.isoDateVersionCreated - ), - creationDate: potentialVersion.isoDateVersionCreated, - type: "task", - - name: objectName ?? "", - boardId: board.fileId ?? "", - - description: potentialVersion.noteContent ?? "", - - category: potentialVersion.categoryName ?? "", - status: potentialVersion.status ?? "", - priority: potentialVersion.priority ?? "", - - date: potentialVersion.date ?? "", - time: potentialVersion.time ?? "", - }; - console.log(convertedTaskFileContent); - chatModel.fileModel.handleFileContent(convertedTaskFileContent); - } - } - }; - - addObjects([...potentialObjects, ...potentialObjectsInOutbox]); - }; - - // init - constructor( - public settingsModel: SettingsModel, - public connectionModel: ConnectionModel, - public chatListModel: ChatListModel - ) { - this.migrateSettings(); - this.migrateConnections(); - this.migrateChats(); - } + // init + constructor( + public settingsModel: SettingsModel, + public connectionModel: ConnectionModel, + public chatListModel: ChatListModel, + ) { + this.migrateSettings(); + this.migrateConnections(); + this.migrateChats(); + } } const storageKeys = { - viewType(id: string): string { - return id + "view-type"; - }, + viewType(id: string): string { + return id + "view-type"; + }, - hasUnread(id: string): string { - return id + "has-unread-messages"; - }, + hasUnread(id: string): string { + return id + "has-unread-messages"; + }, - primaryChannel(id: string): string { - return id + "primary-channel"; - }, + primaryChannel(id: string): string { + return id + "primary-channel"; + }, - secondaryChannels(id: string): string { - return id + "secondary-channels"; - }, + secondaryChannels(id: string): string { + return id + "secondary-channels"; + }, - encyptionKey(id: string): string { - return id + "encryption-key"; - }, + encyptionKey(id: string): string { + return id + "encryption-key"; + }, - messages(id: string): string { - return id + "messages"; - }, + messages(id: string): string { + return id + "messages"; + }, - objects(id: string): string { - return id + "items"; - }, + objects(id: string): string { + return id + "items"; + }, - outbox(id: string): string { - return id + "outbox"; - }, + outbox(id: string): string { + return id + "outbox"; + }, - itemOutbox(id: string): string { - return id + "item-outbox"; - }, + itemOutbox(id: string): string { + return id + "item-outbox"; + }, - composingMessage(id: string): string { - return id + "composing-message"; - }, + composingMessage(id: string): string { + return id + "composing-message"; + }, }; const V1ChatMessageReference = { - channel: "", - sender: "", - body: "", - isoDate: "", + channel: "", + sender: "", + body: "", + isoDate: "", }; function checkIsV1MessageObject(object: any): boolean { - if (object.id == undefined) return false; - if (object.title == undefined) return false; - if (object.contentVersions == undefined) return false; + if (object.id == undefined) return false; + if (object.title == undefined) return false; + if (object.contentVersions == undefined) return false; - return true; + return true; } function checkIsV1MessageObjectContent(object: any): boolean { - if (object.id == undefined) return false; - if (object.isoDateVersionCreated == undefined) return false; + if (object.id == undefined) return false; + if (object.isoDateVersionCreated == undefined) return false; - return true; -} \ No newline at end of file + return true; +} diff --git a/src/View/ChatPages/boardKanbanPage.tsx b/src/View/ChatPages/boardKanbanPage.tsx index 1a313076..56b1a981 100644 --- a/src/View/ChatPages/boardKanbanPage.tsx +++ b/src/View/ChatPages/boardKanbanPage.tsx @@ -1,8 +1,8 @@ import * as React from "bloatless-react"; import { - PropertyValueList, - createPropertyValueIndexState, + PropertyValueList, + createPropertyValueIndexState, } from "../Components/propertyValueList"; import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; @@ -14,84 +14,93 @@ import { allowDrop } from "../utility"; import { translations } from "../translations"; export function BoardKanbanPage(boardViewModel: BoardViewModel) { - return PropertyValueList( - "category", - (taskViewModel: TaskViewModel) => taskViewModel.task, - boardViewModel.taskViewModels, - ( - categories: React.ListState, - sortedCategories: React.State - ) => { - const categoryNameConverter: React.StateItemConverter = ( - categoryName: string - ) => { - const index: React.State = createPropertyValueIndexState( - sortedCategories, - categoryName - ); - return KanbanBoard(categoryName, index, boardViewModel); - }; + return PropertyValueList( + "category", + (taskViewModel: TaskViewModel) => taskViewModel.task, + boardViewModel.taskViewModels, + ( + categories: React.ListState, + sortedCategories: React.State, + ) => { + const categoryNameConverter: React.StateItemConverter = ( + categoryName: string, + ) => { + const index: React.State = + createPropertyValueIndexState( + sortedCategories, + categoryName, + ); + return KanbanBoard(categoryName, index, boardViewModel); + }; - return ( -
- ); - } - ); + return ( +
+ ); + }, + ); } function KanbanBoard( - categoryName: string, - index: React.State, - boardViewModel: BoardViewModel + categoryName: string, + index: React.State, + boardViewModel: BoardViewModel, ) { - return FilteredList( - { category: categoryName }, - (taskViewModel: TaskViewModel) => taskViewModel.task, - boardViewModel.filteredTaskViewModels, - (taskViewModels: React.ListState) => { - const viewModel: TaskCategoryBulkChangeViewModel = - new TaskCategoryBulkChangeViewModel(taskViewModels, categoryName); + return FilteredList( + { category: categoryName }, + (taskViewModel: TaskViewModel) => taskViewModel.task, + boardViewModel.filteredTaskViewModels, + (taskViewModels: React.ListState) => { + const viewModel: TaskCategoryBulkChangeViewModel = + new TaskCategoryBulkChangeViewModel( + taskViewModels, + categoryName, + ); - function drop() { - boardViewModel.handleDropWithinBoard(categoryName); - } + function drop() { + boardViewModel.handleDropWithinBoard(categoryName); + } - const view = ( -
-
- - -
+ const view = ( +
+
+ + +
-
+
-
-
- ); +
+
+ ); - index.subscribe((newIndex) => { - view.style.order = newIndex; - }); - - return view; - } - ); + index.subscribe((newIndex) => { + view.style.order = newIndex; + }); + + return view; + }, + ); } diff --git a/src/View/ChatPages/boardPage.tsx b/src/View/ChatPages/boardPage.tsx index addc954e..91c4bae6 100644 --- a/src/View/ChatPages/boardPage.tsx +++ b/src/View/ChatPages/boardPage.tsx @@ -1,7 +1,7 @@ import * as React from "bloatless-react"; import BoardViewModel, { - BoardPageType, + BoardPageType, } from "../../ViewModel/Pages/boardViewModel"; import { BoardKanbanPage } from "./boardKanbanPage"; @@ -14,120 +14,140 @@ import { TaskViewModelToEntry } from "../Components/taskEntry"; import { translations } from "../translations"; export function BoardPage(boardViewModel: BoardViewModel) { - boardViewModel.loadData(); + boardViewModel.loadData(); - // main content - const mainContent = React.createProxyState( - [boardViewModel.selectedPage], - () => { - switch (boardViewModel.selectedPage.value) { - case BoardPageType.Kanban: { - return BoardKanbanPage(boardViewModel); - } - case BoardPageType.StatusGrid: { - return BoardStatusGridPage(boardViewModel); - } - default: { - return ( -
{ + switch (boardViewModel.selectedPage.value) { + case BoardPageType.Kanban: { + return BoardKanbanPage(boardViewModel); + } + case BoardPageType.StatusGrid: { + return BoardStatusGridPage(boardViewModel); + } + default: { + return ( +
+ ); + } + } + }, + ); + + // task modal + const taskSettingsModal = React.createProxyState( + [boardViewModel.selectedTaskViewModel], + () => { + if (boardViewModel.selectedTaskViewModel.value == undefined) { + return
; + } else { + return TaskSettingsModal( + boardViewModel.selectedTaskViewModel.value, + ); + } + }, + ); + + return ( +
+
+ + + + + + + {BoardViewToggleButton( + translations.chatPage.task.listViewButtonAudioLabel, + "view_list", + BoardPageType.List, + boardViewModel, + )} + {BoardViewToggleButton( + translations.chatPage.task.kanbanViewButtonAudioLabel, + "view_kanban", + BoardPageType.Kanban, + boardViewModel, + )} + {BoardViewToggleButton( + translations.chatPage.task.statusViewButtonAudioLabel, + "grid_view", + BoardPageType.StatusGrid, + boardViewModel, + )} + + + + + +
+
+ + {BoardSettingsModal(boardViewModel)} + {SearchModal( + boardViewModel.searchViewModel, + translations.chatPage.task.filterTasksHeadline, TaskViewModelToEntry, - ]} - >
- ); - } - } - } - ); - - // task modal - const taskSettingsModal = React.createProxyState( - [boardViewModel.selectedTaskViewModel], - () => { - if (boardViewModel.selectedTaskViewModel.value == undefined) { - return
; - } else { - return TaskSettingsModal(boardViewModel.selectedTaskViewModel.value); - } - } - ); - - return ( -
-
- - - - - - - {BoardViewToggleButton( - translations.chatPage.task.listViewButtonAudioLabel, - "view_list", - BoardPageType.List, - boardViewModel - )} - {BoardViewToggleButton( - translations.chatPage.task.kanbanViewButtonAudioLabel, - "view_kanban", - BoardPageType.Kanban, - boardViewModel - )} - {BoardViewToggleButton( - translations.chatPage.task.statusViewButtonAudioLabel, - "grid_view", - BoardPageType.StatusGrid, - boardViewModel - )} - - - - - -
-
- - {BoardSettingsModal(boardViewModel)} - {SearchModal( - boardViewModel.searchViewModel, - translations.chatPage.task.filterTasksHeadline, - TaskViewModelToEntry, - boardViewModel.isPresentingFilterModal - )} -
-
- ); + boardViewModel.isPresentingFilterModal, + )} +
+
+ ); } diff --git a/src/View/ChatPages/boardStatusGridPage.tsx b/src/View/ChatPages/boardStatusGridPage.tsx index 82d3bde8..69fb8d18 100644 --- a/src/View/ChatPages/boardStatusGridPage.tsx +++ b/src/View/ChatPages/boardStatusGridPage.tsx @@ -1,14 +1,14 @@ import * as React from "bloatless-react"; import { - PropertyValueList, - collectPropertyValuesToState, - createPropertyValueIndexState, - createSortedPropertyValueState, + PropertyValueList, + collectPropertyValuesToState, + createPropertyValueIndexState, + createSortedPropertyValueState, } from "../Components/propertyValueList"; import { - TaskCategoryBulkChangeViewModel, - TaskStatusBulkChangeViewModel, + TaskCategoryBulkChangeViewModel, + TaskStatusBulkChangeViewModel, } from "../../ViewModel/Utility/taskPropertyBulkChangeViewModel"; import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; @@ -19,221 +19,236 @@ import { allowDrop } from "../utility"; import { translations } from "../translations"; export function BoardStatusGridPage(boardViewModel: BoardViewModel) { - const statuses: React.ListState = new React.ListState(); - const sortedStatuses: React.State = - createSortedPropertyValueState(statuses); + const statuses: React.ListState = new React.ListState(); + const sortedStatuses: React.State = + createSortedPropertyValueState(statuses); - boardViewModel.taskViewModels.subscribe(() => { - collectPropertyValuesToState( - "status", - (taskViewModel: TaskViewModel) => taskViewModel.task, - boardViewModel.taskViewModels, - statuses - ); - }); + boardViewModel.taskViewModels.subscribe(() => { + collectPropertyValuesToState( + "status", + (taskViewModel: TaskViewModel) => taskViewModel.task, + boardViewModel.taskViewModels, + statuses, + ); + }); - const statusNameCellConverter: React.StateItemConverter = ( - statusName: string - ) => { - const index = createPropertyValueIndexState(sortedStatuses, statusName); - return StatusNameCell(statusName, index, boardViewModel); - }; + const statusNameCellConverter: React.StateItemConverter = ( + statusName: string, + ) => { + const index = createPropertyValueIndexState(sortedStatuses, statusName); + return StatusNameCell(statusName, index, boardViewModel); + }; - return ( -
-
- {PropertyValueList( - "category", - (taskViewModel: TaskViewModel) => taskViewModel.task, - boardViewModel.filteredTaskViewModels, - ( - categories: React.ListState, - sortedCategories: React.State - ) => { - const categoryRowConverter: React.StateItemConverter = ( - categoryName: string - ) => { - const index: React.State = createPropertyValueIndexState( - sortedCategories, - categoryName - ); - - return CategoryRow( - categoryName, - index, - statuses, - sortedStatuses, - boardViewModel - ); - }; - - return ( + return ( +
- ); - } - )} -
- ); + {PropertyValueList( + "category", + (taskViewModel: TaskViewModel) => taskViewModel.task, + boardViewModel.filteredTaskViewModels, + ( + categories: React.ListState, + sortedCategories: React.State, + ) => { + const categoryRowConverter: React.StateItemConverter< + string + > = (categoryName: string) => { + const index: React.State = + createPropertyValueIndexState( + sortedCategories, + categoryName, + ); + + return CategoryRow( + categoryName, + index, + statuses, + sortedStatuses, + boardViewModel, + ); + }; + + return ( +
+ ); + }, + )} +
+ ); } function StatusNameCell( - statusName: string, - index: React.State, - boardViewModel: BoardViewModel + statusName: string, + index: React.State, + boardViewModel: BoardViewModel, ) { - const taskViewModelsWithMatchingStatus: React.ListState = - new React.ListState(); + const taskViewModelsWithMatchingStatus: React.ListState = + new React.ListState(); - boardViewModel.filteredTaskViewModels.handleAddition( - (taskViewModel: TaskViewModel) => { - const doesMatchStatus: boolean = taskViewModel.task.status == statusName; - if (doesMatchStatus == false) return; + boardViewModel.filteredTaskViewModels.handleAddition( + (taskViewModel: TaskViewModel) => { + const doesMatchStatus: boolean = + taskViewModel.task.status == statusName; + if (doesMatchStatus == false) return; - taskViewModelsWithMatchingStatus.add(taskViewModel); - boardViewModel.filteredTaskViewModels.handleRemoval(taskViewModel, () => { - taskViewModelsWithMatchingStatus.remove(taskViewModel); - }); - } - ); - - const viewModel: TaskStatusBulkChangeViewModel = - new TaskStatusBulkChangeViewModel( - taskViewModelsWithMatchingStatus, - statusName + taskViewModelsWithMatchingStatus.add(taskViewModel); + boardViewModel.filteredTaskViewModels.handleRemoval( + taskViewModel, + () => { + taskViewModelsWithMatchingStatus.remove(taskViewModel); + }, + ); + }, ); - const view = ( -
-
- - -
-
- ); + const viewModel: TaskStatusBulkChangeViewModel = + new TaskStatusBulkChangeViewModel( + taskViewModelsWithMatchingStatus, + statusName, + ); - index.subscribe((newIndex) => { - view.style.order = newIndex; - }); + const view = ( +
+
+ + +
+
+ ); - return view; + index.subscribe((newIndex) => { + view.style.order = newIndex; + }); + + return view; } function CategoryRow( - categoryName: string, - index: React.State, - allStatuses: React.ListState, - sortedStatuses: React.State, - boardViewModel: BoardViewModel + categoryName: string, + index: React.State, + allStatuses: React.ListState, + sortedStatuses: React.State, + boardViewModel: BoardViewModel, ) { - return FilteredList( - { category: categoryName }, - (taskViewModel: TaskViewModel) => taskViewModel.task, - boardViewModel.filteredTaskViewModels, - (taskViewModels: React.ListState) => { - const statusNameConverter: React.StateItemConverter = ( - statusName: string - ) => { - const index = createPropertyValueIndexState(sortedStatuses, statusName); - return CategoryStatusColumn( - categoryName, - statusName, - index, - boardViewModel, - taskViewModels - ); - }; + return FilteredList( + { category: categoryName }, + (taskViewModel: TaskViewModel) => taskViewModel.task, + boardViewModel.filteredTaskViewModels, + (taskViewModels: React.ListState) => { + const statusNameConverter: React.StateItemConverter = ( + statusName: string, + ) => { + const index = createPropertyValueIndexState( + sortedStatuses, + statusName, + ); + return CategoryStatusColumn( + categoryName, + statusName, + index, + boardViewModel, + taskViewModels, + ); + }; - const viewModel: TaskCategoryBulkChangeViewModel = - new TaskCategoryBulkChangeViewModel(taskViewModels, categoryName); + const viewModel: TaskCategoryBulkChangeViewModel = + new TaskCategoryBulkChangeViewModel( + taskViewModels, + categoryName, + ); - const view = ( -
-
- - -
+ const view = ( +
+
+ + +
-
-
- ); +
+
+ ); - index.subscribe((newIndex) => { - view.style.order = newIndex; - }); + index.subscribe((newIndex) => { + view.style.order = newIndex; + }); - return view; - } - ); + return view; + }, + ); } function CategoryStatusColumn( - categoryName: string, - statusName: string, - index: React.State, - boardViewModel: BoardViewModel, - taskViewModelsWithMatchingCategory: React.ListState + categoryName: string, + statusName: string, + index: React.State, + boardViewModel: BoardViewModel, + taskViewModelsWithMatchingCategory: React.ListState, ) { - const taskViewModels: React.ListState = new React.ListState(); + const taskViewModels: React.ListState = + new React.ListState(); - taskViewModelsWithMatchingCategory.handleAddition((taskViewModel) => { - const doesMatchStatus: boolean = taskViewModel.status.value == statusName; - if (doesMatchStatus == false) return; + taskViewModelsWithMatchingCategory.handleAddition((taskViewModel) => { + const doesMatchStatus: boolean = + taskViewModel.status.value == statusName; + if (doesMatchStatus == false) return; - taskViewModels.add(taskViewModel); - taskViewModelsWithMatchingCategory.handleRemoval(taskViewModel, () => { - taskViewModels.remove(taskViewModel); + taskViewModels.add(taskViewModel); + taskViewModelsWithMatchingCategory.handleRemoval(taskViewModel, () => { + taskViewModels.remove(taskViewModel); + }); }); - }); - function drop() { - boardViewModel.handleDropWithinBoard(categoryName, statusName); - } + function drop() { + boardViewModel.handleDropWithinBoard(categoryName, statusName); + } - const view = ( -
- ); + const view = ( +
+ ); - index.subscribe((newIndex) => { - view.style.order = newIndex; - }); + index.subscribe((newIndex) => { + view.style.order = newIndex; + }); - return view; + return view; } diff --git a/src/View/ChatPages/calendarPage.css b/src/View/ChatPages/calendarPage.css index a0ded5fa..05d88fb4 100644 --- a/src/View/ChatPages/calendarPage.css +++ b/src/View/ChatPages/calendarPage.css @@ -1,74 +1,73 @@ #chat-page - > div - > #main - > #calendar-page - > .pane-wrapper - > .pane - > .toolbar - .input-wrapper { - display: flex; + > div + > #main + > #calendar-page + > .pane-wrapper + > .pane + > .toolbar + .input-wrapper { + display: flex; - padding: var(--gap-between-tiles); - gap: var(--gap-between-tiles); + 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)); + > 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; + appearance: textfield; + -moz-appearance: textfield; - padding-right: 0px; + padding-right: 0px; } #calendar-page .input-wrapper > input::-webkit-outer-spin-button, #calendar-page .input-wrapper > input::-webkit-innner-spin-button { - appearance: none; - -webkit-appearance: none; + appearance: none; + -webkit-appearance: none; - margin: 0px; + margin: 0px; } #chat-page - > div - > #main - > #calendar-page - > .pane-wrapper - > .pane - > .toolbar - .input-wrapper - > .year-input { - width: 4.4rem; + > 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; + > div + > #main + > #calendar-page + > .pane-wrapper + > .pane + > .toolbar + .input-wrapper + > .month-input { + width: 3.1rem; } - @media (max-width: 1024px) { - #chat-page > div > #main > #calendar-page { - flex-direction: column; - } + #chat-page > div > #main > #calendar-page { + flex-direction: column; + } - #chat-page > div > #main > #calendar-page .grid-pane-wrapper { - border-bottom: 1px solid var(--border); - } + #chat-page > div > #main > #calendar-page .grid-pane-wrapper { + border-bottom: 1px solid var(--border); + } } diff --git a/src/View/ChatPages/calendarPage.tsx b/src/View/ChatPages/calendarPage.tsx index 27192117..15c3acd8 100644 --- a/src/View/ChatPages/calendarPage.tsx +++ b/src/View/ChatPages/calendarPage.tsx @@ -10,168 +10,203 @@ import { TaskViewModelToEntry } from "../Components/taskEntry"; import { translations } from "../translations"; export function CalendarPage(calendarPageViewModel: CalendarPageViewModel) { - calendarPageViewModel.loadData(); + calendarPageViewModel.loadData(); - const mainContent = React.createProxyState( - [calendarPageViewModel.monthGrid], - () => { - const monthGrid = calendarPageViewModel.monthGrid.value; + const mainContent = React.createProxyState( + [calendarPageViewModel.monthGrid], + () => { + const monthGrid = calendarPageViewModel.monthGrid.value; - if (monthGrid == undefined) { - return
; - } else { - function drop(date: string) { - calendarPageViewModel.handleDrop( - monthGrid!.year.toString(), - monthGrid!.month.toString(), - date - ); - } - - return MonthGrid(monthGrid, calendarPageViewModel.selectedDate, drop); - } - } - ); - - const sidePaneContentWrapper = React.createProxyState( - [ - calendarPageViewModel.selectedYear, - calendarPageViewModel.selectedMonth, - calendarPageViewModel.selectedDate, - ], - () => { - const listState = calendarPageViewModel.getEventsForDate(); - - if (listState == undefined) { - return
; - } else { - const sidePaneContent = React.createProxyState([listState], () => { - if (listState.value.size == 0) { - return ( -
- - {translations.chatPage.calendar.noEvents} - -
- ); - } else { - return ( -
- ); - } - }); - - return ( -
- ); - } - } - ); - - const taskSettingsModal = React.createProxyState( - [calendarPageViewModel.selectedTaskViewModel], - () => { - if (calendarPageViewModel.selectedTaskViewModel.value == undefined) { - return
; - } else { - return TaskSettingsModal( - calendarPageViewModel.selectedTaskViewModel.value - ); - } - } - ); - - return ( -
-
-
-
- -
; + } else { + function drop(date: string) { + calendarPageViewModel.handleDrop( + monthGrid!.year.toString(), + monthGrid!.month.toString(), + date, + ); } - on:click={calendarPageViewModel.showToday} - > - today - - - - - - - - - - - - - -
-
+ + return MonthGrid( + monthGrid, + calendarPageViewModel.selectedDate, + drop, + ); + } + }, + ); + + const sidePaneContentWrapper = React.createProxyState( + [ + calendarPageViewModel.selectedYear, + calendarPageViewModel.selectedMonth, + calendarPageViewModel.selectedDate, + ], + () => { + const listState = calendarPageViewModel.getEventsForDate(); + + if (listState == undefined) { + return
; + } else { + const sidePaneContent = React.createProxyState( + [listState], + () => { + if (listState.value.size == 0) { + return ( +
+ + { + translations.chatPage.calendar + .noEvents + } + +
+ ); + } else { + return ( +
+ ); + } + }, + ); + + return ( +
+ ); + } + }, + ); + + const taskSettingsModal = React.createProxyState( + [calendarPageViewModel.selectedTaskViewModel], + () => { + if ( + calendarPageViewModel.selectedTaskViewModel.value == undefined + ) { + return
; + } else { + return TaskSettingsModal( + calendarPageViewModel.selectedTaskViewModel.value, + ); + } + }, + ); + + return ( +
+
+
+
+ + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+ +
-
-
-
-
-
-
- -
-
- ); + ); } diff --git a/src/View/ChatPages/messagePage.css b/src/View/ChatPages/messagePage.css index 5af6590b..cf532dee 100644 --- a/src/View/ChatPages/messagePage.css +++ b/src/View/ChatPages/messagePage.css @@ -1,168 +1,168 @@ /* layout */ #chat-page #main #message-page .content { - padding: 0px; + padding: 0px; - overflow: hidden; + overflow: hidden; - display: grid; - grid-template-rows: 1fr var(--control-height) var(--safe-area-bottom); + display: grid; + grid-template-rows: 1fr var(--control-height) var(--safe-area-bottom); } #message-page #message-container { - overflow: auto; + overflow: auto; } /* composer */ #message-page #composer { - display: flex; - justify-content: center; + display: flex; + justify-content: center; } #message-page #composer input { - margin: 0px; + margin: 0px; - width: 100%; - max-width: unset; - - flex: 1; - - border-top: 1px solid var(--border); - border-left: 1px solid var(--border); -} - -@media (max-width: 700px) { - #message-page #composer div { width: 100%; max-width: unset; - display: flex; - } + flex: 1; + + border-top: 1px solid var(--border); + border-left: 1px solid var(--border); +} + +@media (max-width: 700px) { + #message-page #composer div { + width: 100%; + max-width: unset; + + display: flex; + } } @media (min-width: 700px) { - #message-page #composer .content-width-constraint, - #message-page #composer .input-width-constraint { - display: flex; - justify-content: flex-end; + #message-page #composer .content-width-constraint, + #message-page #composer .input-width-constraint { + display: flex; + justify-content: flex-end; - width: 100%; - max-width: var(--max-content-width); - } + width: 100%; + max-width: var(--max-content-width); + } - #message-page #composer .content-width-constraint { - max-width: var(--max-content-width); - } + #message-page #composer .content-width-constraint { + max-width: var(--max-content-width); + } - #message-page #composer .input-width-constraint { - max-width: var(--max-input-width); - } + #message-page #composer .input-width-constraint { + max-width: var(--max-input-width); + } } /* message */ #message-page #message-container { - display: flex; - flex-direction: column; - align-items: center; + display: flex; + flex-direction: column; + align-items: center; - padding: var(--content-padding); - gap: var(--gap-between-tiles); + padding: var(--content-padding); + gap: var(--gap-between-tiles); - height: auto; - max-height: unset; + height: auto; + max-height: unset; - scroll-behavior: smooth; + scroll-behavior: smooth; } .message-bubble { - display: flex; - width: 100%; - max-width: var(--max-content-width); + display: flex; + width: 100%; + max-width: var(--max-content-width); } .message-bubble .tile.main { - display: flex; - flex-direction: row; - align-items: start; + display: flex; + flex-direction: row; + align-items: start; - padding: 0px; + padding: 0px; - width: 100%; - max-width: var(--max-input-width); - overflow: hidden; + width: 100%; + max-width: var(--max-input-width); + overflow: hidden; } .message-bubble .tile.main .text-container { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; - overflow: hidden; + overflow: hidden; - width: 100%; - flex: 1; + width: 100%; + flex: 1; } .message-bubble .tile.main .button-container { - display: flex; + display: flex; - width: var(--control-height); - max-width: var(--control-height); + width: var(--control-height); + max-width: var(--control-height); } .message-bubble .tile.main button { - justify-content: center; + justify-content: center; - padding: 0px; - width: 100%; + padding: 0px; + width: 100%; - --surface: transparent; + --surface: transparent; } .message-bubble .tile.main .text-container { - padding: var(--control-padding); + padding: var(--control-padding); } .message-bubble .tile.main .timestamp, .message-bubble .tile.main .timestamp .icon, .message-bubble .tile.main .sender-name { - font-size: 0.8rem; - opacity: 0.6; + font-size: 0.8rem; + opacity: 0.6; } .message-bubble .tile.main .timestamp { - display: flex; - align-items: center; - gap: var(--gap-between-tiles); + display: flex; + align-items: center; + gap: var(--gap-between-tiles); } .message-bubble .tile.main .timestamp .icon { - display: none; + display: none; } .message-bubble .tile.main .sender-name { - font-weight: bold; + font-weight: bold; } .message-bubble .tile.main .body { - word-break: break-word; + word-break: break-word; } .message-bubble[sentbyuser] { - justify-content: flex-end; + justify-content: flex-end; } .message-bubble[sentbyuser] { - align-items: flex-end; + align-items: flex-end; } .message-bubble[sentbyuser] .tile.main, .message-bubble[sentbyuser] .tile.main .timestamp { - flex-direction: row-reverse; + flex-direction: row-reverse; } .message-bubble[sentbyuser] .tile.main .timestamp .icon { - display: flex; + display: flex; } .message-bubble[sentbyuser] .tile.main span { - text-align: right; + text-align: right; } diff --git a/src/View/ChatPages/messagePage.tsx b/src/View/ChatPages/messagePage.tsx index 6e467ed1..0b5d5f4f 100644 --- a/src/View/ChatPages/messagePage.tsx +++ b/src/View/ChatPages/messagePage.tsx @@ -7,69 +7,81 @@ import MessagePageViewModel from "../../ViewModel/Pages/messagePageViewModel"; import { translations } from "../translations"; export function MessagePage(messagePageViewModel: MessagePageViewModel) { - messagePageViewModel.loadData(); + messagePageViewModel.loadData(); - const messageContainer = ( -
- ); + const messageContainer = ( +
+ ); - function scrollDown() { - messageContainer.scrollTop = messageContainer.scrollHeight; - } - function scrollDownIfApplicable() { - const scrollFromBottom = - messageContainer.scrollHeight - - (messageContainer.scrollTop + messageContainer.offsetHeight); - if (scrollFromBottom > 400) return; + function scrollDown() { + messageContainer.scrollTop = messageContainer.scrollHeight; + } + function scrollDownIfApplicable() { + const scrollFromBottom = + messageContainer.scrollHeight - + (messageContainer.scrollTop + messageContainer.offsetHeight); + if (scrollFromBottom > 400) return; - scrollDown(); - } - messagePageViewModel.chatMessageViewModels.subscribeSilent( - scrollDownIfApplicable - ); - setTimeout(() => scrollDown(), 100); + scrollDown(); + } + messagePageViewModel.chatMessageViewModels.subscribeSilent( + scrollDownIfApplicable, + ); + setTimeout(() => scrollDown(), 100); - return ( -
-
-
-
- {translations.chatPage.message.messagesHeadline} -
-
- {messageContainer} -
-
-
- - + return ( +
+
+
+
+ + {translations.chatPage.message.messagesHeadline} + +
+
+ {messageContainer} +
+
+
+ + +
+
+
+
-
-
-
-
- ); + ); } diff --git a/src/View/ChatPages/settingsPage.tsx b/src/View/ChatPages/settingsPage.tsx index 62a83065..ebd95959 100644 --- a/src/View/ChatPages/settingsPage.tsx +++ b/src/View/ChatPages/settingsPage.tsx @@ -8,141 +8,177 @@ import SettingsPageViewModel from "../../ViewModel/Pages/settingsPageViewModel"; import { translations } from "../translations"; export function SettingsPage(settingsPageViewModel: SettingsPageViewModel) { - settingsPageViewModel.loadData(); + settingsPageViewModel.loadData(); - const secondaryChannelConverter: React.StateItemConverter = ( - secondaryChannel: string - ) => { - return DeletableListItem(secondaryChannel, , () => { - settingsPageViewModel.removeSecondaryChannel(secondaryChannel); - }); - }; + const secondaryChannelConverter: React.StateItemConverter = ( + secondaryChannel: string, + ) => { + return DeletableListItem(secondaryChannel, , () => { + settingsPageViewModel.removeSecondaryChannel(secondaryChannel); + }); + }; - return ( -
-
-
-
- - {translations.chatPage.settings.settingsHeadline} - -
-
- -
- + return ( +
+
+
+
+ + {translations.chatPage.settings.settingsHeadline} + +
+
+ +
+ +
+ +
+ +
+ + +
+ +
+ +
+ + +
+ +
+ + + +
+ + {ColorPicker(settingsPageViewModel.color)} + +
+ +
+ {DangerousActionButton( + translations.chatPage.settings.deleteChatButton, + "chat_error", + settingsPageViewModel.remove, + )} +
+
+
- -
- -
- - -
- -
- -
- - -
- -
- - - -
- - {ColorPicker(settingsPageViewModel.color)} - -
- -
- {DangerousActionButton( - translations.chatPage.settings.deleteChatButton, - "chat_error", - settingsPageViewModel.remove - )} -
-
-
-
- ); + ); } diff --git a/src/View/ChatPages/taskPage.css b/src/View/ChatPages/taskPage.css index 521ba098..dd191613 100644 --- a/src/View/ChatPages/taskPage.css +++ b/src/View/ChatPages/taskPage.css @@ -1,53 +1,53 @@ #chat-page > div > #main > #task-page > .pane-wrapper > .pane > .main-content { - width: 100%; - max-width: 100%; + width: 100%; + max-width: 100%; - padding: 0px; + padding: 0px; } #chat-page - > div - > #main - > #task-page - > .pane-wrapper - > .pane - > .main-content - > div { - animation: slide-up var(--animation-duration); + > div + > #main + > #task-page + > .pane-wrapper + > .pane + > .main-content + > div { + animation: slide-up var(--animation-duration); } #chat-page > div > #main > #task-page .task-grid { - display: flex; - flex-direction: column; - gap: var(--gap-between-tiles); + display: flex; + flex-direction: column; + gap: var(--gap-between-tiles); - padding: var(--content-padding); + padding: var(--content-padding); } #chat-page > div > #main > #task-page .kanban-board-wrapper, #chat-page > div > #main > #task-page .status-page-content { - display: flex; - gap: var(--large-gap); + display: flex; + gap: var(--large-gap); - width: 100%; - height: 100%; + width: 100%; + height: 100%; - overflow: auto; + overflow: auto; - padding: var(--content-padding); + padding: var(--content-padding); } #chat-page > div > #main > #task-page .kanban-board-wrapper .kanban-column, #chat-page > div > #main > #task-page .status-grid-wrapper .status-column { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; - gap: var(--gap-between-tiles); + gap: var(--gap-between-tiles); } #chat-page > div > #main > #task-page .property-input-wrapper { - display: flex; - flex-direction: row; + display: flex; + flex-direction: row; } #chat-page > div > #main > #task-page .kanban-board-wrapper .kanban-column, @@ -55,72 +55,72 @@ #chat-page > div > #main > #task-page .status-grid-wrapper .status-column, #chat-page > div > #main > #task-page .status-name-row > *, #chat-page > div > #main > #task-page .status-name-row::before { - width: 18rem; - min-width: 18rem; - max-width: 18rem; + width: 18rem; + min-width: 18rem; + max-width: 18rem; } #chat-page > div > #main > #task-page .status-name-row, #chat-page > div > #main > #task-page .status-grid-wrapper { - display: flex; - gap: var(--large-gap); + display: flex; + gap: var(--large-gap); } #chat-page > div > #main > #task-page .status-page-content, #chat-page > div > #main > #task-page .status-grid-wrapper { - flex-direction: column; + flex-direction: column; } #chat-page > div > #main > #task-page .status-name-row::before { - content: ""; - display: flex; + content: ""; + display: flex; } #chat-page > div > #main > #task-page .board-close-button { - display: none; + display: none; } #chat-page > div > #main > #task-page .board-toggle-button { - display: flex; + display: flex; } #chat-page - > div - > #main - > #task-page[isshowingboard][showingboardlist="false"] - #board-list { - display: none; + > div + > #main + > #task-page[isshowingboard][showingboardlist="false"] + #board-list { + display: none; } @media (min-width: 640px) { - #chat-page > div > #main > #task-page .task-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr)); - } + #chat-page > div > #main > #task-page .task-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr)); + } } @media (max-width: 1024px) { - #chat-page > div > #main > #task-page #board-list { - display: false; - } + #chat-page > div > #main > #task-page #board-list { + display: false; + } - #chat-page > div > #main > #task-page #board-content { - display: none; - } + #chat-page > div > #main > #task-page #board-content { + display: none; + } - #chat-page > div > #main > #task-page[isshowingboard] #board-list { - display: none; - } + #chat-page > div > #main > #task-page[isshowingboard] #board-list { + display: none; + } - #chat-page > div > #main > #task-page[isshowingboard] #board-content { - display: flex; - } + #chat-page > div > #main > #task-page[isshowingboard] #board-content { + display: flex; + } - #chat-page > div > #main > #task-page .board-close-button { - display: flex; - } + #chat-page > div > #main > #task-page .board-close-button { + display: flex; + } - #chat-page > div > #main > #task-page .board-toggle-button { - display: none; - } + #chat-page > div > #main > #task-page .board-toggle-button { + display: none; + } } diff --git a/src/View/ChatPages/taskPage.tsx b/src/View/ChatPages/taskPage.tsx index 4e79a609..af6342a0 100644 --- a/src/View/ChatPages/taskPage.tsx +++ b/src/View/ChatPages/taskPage.tsx @@ -8,87 +8,97 @@ import TaskPageViewModel from "../../ViewModel/Pages/taskPageViewModel"; import { translations } from "../translations"; export function TaskPage(taskPageViewModel: TaskPageViewModel) { - taskPageViewModel.loadData(); + taskPageViewModel.loadData(); - const isShowingBoard = React.createProxyState( - [taskPageViewModel.selectedBoardId], - () => taskPageViewModel.selectedBoardId.value != undefined - ); + const isShowingBoard = React.createProxyState( + [taskPageViewModel.selectedBoardId], + () => taskPageViewModel.selectedBoardId.value != undefined, + ); - const paneContent = React.createProxyState( - [taskPageViewModel.selectedBoardId], - () => { - const selectedBoardId = taskPageViewModel.selectedBoardId.value; - if (selectedBoardId == undefined) { - return ( -
- - {translations.chatPage.task.noBoardSelected} - -
- ); - } - const selectedBoard = - taskPageViewModel.boardViewModels.value.get(selectedBoardId); - if (selectedBoard == undefined) { - return ( -
- - {translations.chatPage.task.boardNotFound} - -
- ); - } + const paneContent = React.createProxyState( + [taskPageViewModel.selectedBoardId], + () => { + const selectedBoardId = taskPageViewModel.selectedBoardId.value; + if (selectedBoardId == undefined) { + return ( +
+ + {translations.chatPage.task.noBoardSelected} + +
+ ); + } + const selectedBoard = + taskPageViewModel.boardViewModels.value.get(selectedBoardId); + if (selectedBoard == undefined) { + return ( +
+ + {translations.chatPage.task.boardNotFound} + +
+ ); + } - return BoardPage(selectedBoard); - } - ); + return BoardPage(selectedBoard); + }, + ); - return ( -
-
-
-
-
- - -
-
-
+ return ( +
+
+
+
+ + +
+
+
+
+
+
+
+
-
-
-
-
- ); + ); } diff --git a/src/View/Components/boardEntry.tsx b/src/View/Components/boardEntry.tsx index 6cc03387..37be95cc 100644 --- a/src/View/Components/boardEntry.tsx +++ b/src/View/Components/boardEntry.tsx @@ -4,29 +4,32 @@ import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; import { allowDrop } from "../utility"; export function BoardEntry(boardViewModel: BoardViewModel) { - const view = ( - - ); + const view = ( + + ); - boardViewModel.index.subscribe((newIndex) => { - view.style.order = newIndex; - }); + boardViewModel.index.subscribe((newIndex) => { + view.style.order = newIndex; + }); - return view; + return view; } export const BoardViewModelToEntry: React.StateItemConverter = ( - boardViewModel: BoardViewModel + boardViewModel: BoardViewModel, ) => { - return BoardEntry(boardViewModel); + return BoardEntry(boardViewModel); }; diff --git a/src/View/Components/boardViewToggleButton.tsx b/src/View/Components/boardViewToggleButton.tsx index 778337e6..6b998a99 100644 --- a/src/View/Components/boardViewToggleButton.tsx +++ b/src/View/Components/boardViewToggleButton.tsx @@ -1,25 +1,25 @@ import * as React from "bloatless-react"; import BoardViewModel, { - BoardPageType, + BoardPageType, } from "../../ViewModel/Pages/boardViewModel"; import { RibbonButton } from "./ribbonButton"; export function BoardViewToggleButton( - label: string, - icon: string, - page: BoardPageType, - boardViewModel: BoardViewModel + label: string, + icon: string, + page: BoardPageType, + boardViewModel: BoardViewModel, ) { - function select() { - boardViewModel.selectedPage.value = page; - } + function select() { + boardViewModel.selectedPage.value = page; + } - const isSelected = React.createProxyState( - [boardViewModel.selectedPage], - () => boardViewModel.selectedPage.value == page - ); + const isSelected = React.createProxyState( + [boardViewModel.selectedPage], + () => boardViewModel.selectedPage.value == page, + ); - return RibbonButton(label, icon, isSelected, select); + return RibbonButton(label, icon, isSelected, select); } diff --git a/src/View/Components/chatEntry.tsx b/src/View/Components/chatEntry.tsx index 94116c84..e444371b 100644 --- a/src/View/Components/chatEntry.tsx +++ b/src/View/Components/chatEntry.tsx @@ -3,33 +3,37 @@ import * as React from "bloatless-react"; import ChatViewModel from "../../ViewModel/Chat/chatViewModel"; export function ChatEntry(chatViewModel: ChatViewModel) { - const view = ( - - ); + const view = ( + + ); - chatViewModel.index.subscribe((newIndex) => { - view.style.order = newIndex; - }); + chatViewModel.index.subscribe((newIndex) => { + view.style.order = newIndex; + }); - return view; + return view; } export const ChatViewModelToChatEntry: React.StateItemConverter< - ChatViewModel + ChatViewModel > = (chatViewModel: ChatViewModel) => { - return ChatEntry(chatViewModel); + return ChatEntry(chatViewModel); }; diff --git a/src/View/Components/chatMessage.tsx b/src/View/Components/chatMessage.tsx index 13602fda..76e737fd 100644 --- a/src/View/Components/chatMessage.tsx +++ b/src/View/Components/chatMessage.tsx @@ -6,61 +6,65 @@ import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; import { translations } from "../translations"; export function ChatMessage(chatMessageViewModel: ChatMessageViewModel) { - const statusIcon = React.createProxyState( - [chatMessageViewModel.status], - () => { - switch (chatMessageViewModel.status.value) { - case ChatMessageStatus.Outbox: - return "hourglass_top"; - case ChatMessageStatus.Sent: - return "check"; - case ChatMessageStatus.Received: - return "done_all"; - default: - return "warning"; - } - } - ); - - return ( -
-
-
- - {chatMessageViewModel.sender} - - - - - {chatMessageViewModel.dateSent} - -
- -
- -
-
+ }, + ); - {ChatMessageInfoModal(chatMessageViewModel)} -
- ); + return ( +
+
+
+ + {chatMessageViewModel.sender} + + + + + {chatMessageViewModel.dateSent} + +
+ +
+ +
+
+ + {ChatMessageInfoModal(chatMessageViewModel)} +
+ ); } export const ChatMessageViewModelToView: React.StateItemConverter< - ChatMessageViewModel + ChatMessageViewModel > = (chatMessageViewModel: ChatMessageViewModel) => { - return ChatMessage(chatMessageViewModel); + return ChatMessage(chatMessageViewModel); }; diff --git a/src/View/Components/chatViewToggleButton.tsx b/src/View/Components/chatViewToggleButton.tsx index 6b91568b..f84a37b7 100644 --- a/src/View/Components/chatViewToggleButton.tsx +++ b/src/View/Components/chatViewToggleButton.tsx @@ -1,25 +1,25 @@ import * as React from "bloatless-react"; import ChatViewModel, { - ChatPageType, + ChatPageType, } from "../../ViewModel/Chat/chatViewModel"; import { RibbonButton } from "./ribbonButton"; export function ChatViewToggleButton( - label: string, - icon: string, - page: ChatPageType, - chatViewModel: ChatViewModel + label: string, + icon: string, + page: ChatPageType, + chatViewModel: ChatViewModel, ) { - function select() { - chatViewModel.selectedPage.value = page; - } + function select() { + chatViewModel.selectedPage.value = page; + } - const isSelected = React.createProxyState( - [chatViewModel.selectedPage], - () => chatViewModel.selectedPage.value == page - ); + const isSelected = React.createProxyState( + [chatViewModel.selectedPage], + () => chatViewModel.selectedPage.value == page, + ); - return RibbonButton(label, icon, isSelected, select); + return RibbonButton(label, icon, isSelected, select); } diff --git a/src/View/Components/colorPicker.tsx b/src/View/Components/colorPicker.tsx index 53b10fb7..e160a68e 100644 --- a/src/View/Components/colorPicker.tsx +++ b/src/View/Components/colorPicker.tsx @@ -3,28 +3,28 @@ import * as React from "bloatless-react"; import { Color } from "../../colors"; export function ColorPicker(selectedColor: React.State) { - return ( -
- {...Object.values(Color).map((color) => { - const isSelected = React.createProxyState( - [selectedColor], - () => selectedColor.value == color - ); + return ( +
+ {...Object.values(Color).map((color) => { + const isSelected = React.createProxyState( + [selectedColor], + () => selectedColor.value == color, + ); - function setColor() { - selectedColor.value = color; - } + function setColor() { + selectedColor.value = color; + } - return ( - - ); - })} -
- ); + return ( + + ); + })} +
+ ); } diff --git a/src/View/Components/dangerousActionButton.tsx b/src/View/Components/dangerousActionButton.tsx index 2a9a06c6..fe39cc12 100644 --- a/src/View/Components/dangerousActionButton.tsx +++ b/src/View/Components/dangerousActionButton.tsx @@ -3,45 +3,45 @@ import * as React from "bloatless-react"; import { translations } from "../translations"; export function DangerousActionButton( - label: string, - icon: string, - action: () => void + label: string, + icon: string, + action: () => void, ) { - const isActionRequested = new React.State(false); - const cannotConfirm = React.createProxyState( - [isActionRequested], - () => isActionRequested.value == false - ); + const isActionRequested = new React.State(false); + const cannotConfirm = React.createProxyState( + [isActionRequested], + () => isActionRequested.value == false, + ); - function requestAction() { - isActionRequested.value = true; - } - function abort() { - isActionRequested.value = false; - } + function requestAction() { + isActionRequested.value = true; + } + function abort() { + isActionRequested.value = false; + } - return ( -
- - - -
- ); + return ( +
+ + + +
+ ); } diff --git a/src/View/Components/deletableListItem.tsx b/src/View/Components/deletableListItem.tsx index b45d2012..109fe855 100644 --- a/src/View/Components/deletableListItem.tsx +++ b/src/View/Components/deletableListItem.tsx @@ -3,25 +3,25 @@ import * as React from "bloatless-react"; import { translations } from "../translations"; export function DeletableListItem( - text: string, - primaryButton: HTMLElement, - ondelete: () => void + text: string, + primaryButton: HTMLElement, + ondelete: () => void, ) { - return ( -
- {text} + return ( +
+ {text} -
- {primaryButton} +
+ {primaryButton} - -
-
- ); + +
+
+ ); } diff --git a/src/View/Components/directoryItemList.tsx b/src/View/Components/directoryItemList.tsx index 2f292388..07cbc786 100644 --- a/src/View/Components/directoryItemList.tsx +++ b/src/View/Components/directoryItemList.tsx @@ -1,79 +1,79 @@ import * as React from "bloatless-react"; import StorageModel, { - PATH_COMPONENT_SEPARATOR, + PATH_COMPONENT_SEPARATOR, } from "../../Model/Global/storageModel"; import StorageViewModel from "../../ViewModel/Global/storageViewModel"; export function DirectoryItemList( - storageViewModel: StorageViewModel, - pathString: string = PATH_COMPONENT_SEPARATOR + storageViewModel: StorageViewModel, + pathString: string = PATH_COMPONENT_SEPARATOR, ) { - // converter - const StringToDirectoryItemList: React.StateItemConverter = ( - pathString: string - ) => DirectoryItemList(storageViewModel, pathString); + // converter + const StringToDirectoryItemList: React.StateItemConverter = ( + pathString: string, + ) => DirectoryItemList(storageViewModel, pathString); - // data - const path = StorageModel.stringToPathComponents(pathString); - const fileName = StorageModel.getFileName(path); - const items = new React.ListState(); + // data + const path = StorageModel.stringToPathComponents(pathString); + const fileName = StorageModel.getFileName(path); + const items = new React.ListState(); - const style = `text-indent: ${path.length}rem`; + const style = `text-indent: ${path.length}rem`; - // methods - function loadItems() { - items.clear(); + // methods + function loadItems() { + items.clear(); - const directoryItems = storageViewModel.storageModel.list(path); + const directoryItems = storageViewModel.storageModel.list(path); - for (const directoryItem of directoryItems) { - const itemPath = [...path, directoryItem]; - const pathString = StorageModel.pathComponentsToString(...itemPath); - items.add(pathString); + for (const directoryItem of directoryItems) { + const itemPath = [...path, directoryItem]; + const pathString = StorageModel.pathComponentsToString(...itemPath); + items.add(pathString); + } } - } - function select() { - storageViewModel.selectedPath.value = pathString; - } + function select() { + storageViewModel.selectedPath.value = pathString; + } - // handlers - storageViewModel.lastDeletedItemPath.subscribe((lastDeletedItemPath) => { - if (!items.value.has(lastDeletedItemPath)) return; + // handlers + storageViewModel.lastDeletedItemPath.subscribe((lastDeletedItemPath) => { + if (!items.value.has(lastDeletedItemPath)) return; - select(); - setTimeout(() => loadItems(), 50); - }); + select(); + setTimeout(() => loadItems(), 50); + }); - // state - const isSelected = React.createProxyState( - [storageViewModel.selectedPath], - () => storageViewModel.selectedPath.value == pathString - ); + // state + const isSelected = React.createProxyState( + [storageViewModel.selectedPath], + () => storageViewModel.selectedPath.value == pathString, + ); - isSelected.subscribe(() => { - if (isSelected.value == false) return; - loadItems(); - }); + isSelected.subscribe(() => { + if (isSelected.value == false) return; + loadItems(); + }); - return ( -
- + return ( +
+ -
-
- ); +
+
+ ); } diff --git a/src/View/Components/fileBrowser.css b/src/View/Components/fileBrowser.css index e3b32b91..367c5beb 100644 --- a/src/View/Components/fileBrowser.css +++ b/src/View/Components/fileBrowser.css @@ -1,62 +1,62 @@ .file-browser { - width: 100%; - height: 100%; + width: 100%; + height: 100%; - display: flex; + display: flex; - overflow-x: scroll; - overflow-y: hidden; - scroll-behavior: smooth; - scroll-snap-type: x mandatory; + overflow-x: scroll; + overflow-y: hidden; + scroll-behavior: smooth; + scroll-snap-type: x mandatory; } .file-browser > div { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; - width: 100%; - min-width: 100%; - flex: 0; + width: 100%; + min-width: 100%; + flex: 0; - scroll-snap-align: center; + scroll-snap-align: center; } .file-browser .scroll-area { - flex: 1; - width: 100%; - overflow-y: auto; + flex: 1; + width: 100%; + overflow-y: auto; - padding: var(--content-padding); + padding: var(--content-padding); } .file-browser .detail-button-wrapper { - display: flex; - justify-content: end; + display: flex; + justify-content: end; - border-top: 1px solid var(--border); - padding: var(--content-padding); + border-top: 1px solid var(--border); + padding: var(--content-padding); } .file-browser .detail-button-wrapper button { - max-width: 100%; + max-width: 100%; } .file-browser code { - width: 100%; - height: 100%; + width: 100%; + height: 100%; - font-size: .9rem; + font-size: 0.9rem; } @media (min-width: 640px) { - .file-browser > div { - width: 50%; - min-width: 50%; + .file-browser > div { + width: 50%; + min-width: 50%; - flex: 1; - } + flex: 1; + } - .file-browser .detail-button-wrapper { - display: none; - } + .file-browser .detail-button-wrapper { + display: none; + } } diff --git a/src/View/Components/fileBrowser.tsx b/src/View/Components/fileBrowser.tsx index f71ad00e..0db341aa 100644 --- a/src/View/Components/fileBrowser.tsx +++ b/src/View/Components/fileBrowser.tsx @@ -9,75 +9,85 @@ import StorageViewModel from "../../ViewModel/Global/storageViewModel"; import { translations } from "../translations"; export function FileBrowser(storageViewModel: StorageViewModel) { - const detailView = React.createProxyState( - [storageViewModel.selectedPath], - () => { - if (storageViewModel.selectedPath.value == PATH_COMPONENT_SEPARATOR) - return ( -
- {translations.storage.noItemSelected} + const detailView = React.createProxyState( + [storageViewModel.selectedPath], + () => { + if (storageViewModel.selectedPath.value == PATH_COMPONENT_SEPARATOR) + return ( +
+ + {translations.storage.noItemSelected} + -
+
- {DangerousActionButton( - translations.storage.removeJunkButton, - "delete_forever", - storageViewModel.removeJunk - )} -
- ); + {DangerousActionButton( + translations.storage.removeJunkButton, + "delete_forever", + storageViewModel.removeJunk, + )} +
+ ); - return ( -
-
+ return ( +
+
+
+ {translations.storage.path} + +
+
+
+
+ {translations.storage.content} + +
+
+ + {DangerousActionButton( + translations.storage.deleteItem, + "delete_forever", + storageViewModel.deleteSelectedItem, + )} +
+ ); + }, + ); + + const view = ( +
- {translations.storage.path} - +
+ {DirectoryItemList(storageViewModel)} +
+
+ +
-
-
-
- {translations.storage.content} - -
-
- - {DangerousActionButton( - translations.storage.deleteItem, - "delete_forever", - storageViewModel.deleteSelectedItem - )} +
- ); + ); + + function scrollToDetails() { + view.scrollLeft = view.scrollWidth; } - ); - const view = ( -
-
-
{DirectoryItemList(storageViewModel)}
-
- -
-
-
-
- ); - - function scrollToDetails() { - view.scrollLeft = view.scrollWidth; - } - - return view; + return view; } diff --git a/src/View/Components/filteredList.tsx b/src/View/Components/filteredList.tsx index e74b7238..12866be6 100644 --- a/src/View/Components/filteredList.tsx +++ b/src/View/Components/filteredList.tsx @@ -1,30 +1,30 @@ import * as React from "bloatless-react"; import { - StringEntryObject, - checkDoesObjectMatchReference, + StringEntryObject, + checkDoesObjectMatchReference, } from "../../Model/Utility/utility"; export function FilteredList( - reference: StringEntryObject, - stringEntryObjectConverter: (object: T) => StringEntryObject, - objects: React.ListState | React.MapState, - viewBuilder: (matchingObjects: React.ListState) => HTMLElement + reference: StringEntryObject, + stringEntryObjectConverter: (object: T) => StringEntryObject, + objects: React.ListState | React.MapState, + viewBuilder: (matchingObjects: React.ListState) => HTMLElement, ) { - const matchingObjects: React.ListState = new React.ListState(); + const matchingObjects: React.ListState = new React.ListState(); - objects.handleAddition((newObject) => { - const doesMatch: boolean = checkDoesObjectMatchReference( - reference, - stringEntryObjectConverter(newObject) - ); - if (doesMatch == false) return; + objects.handleAddition((newObject) => { + const doesMatch: boolean = checkDoesObjectMatchReference( + reference, + stringEntryObjectConverter(newObject), + ); + if (doesMatch == false) return; - matchingObjects.add(newObject); - objects.handleRemoval(newObject, () => { - matchingObjects.remove(newObject); + matchingObjects.add(newObject); + objects.handleRemoval(newObject, () => { + matchingObjects.remove(newObject); + }); }); - }); - return viewBuilder(matchingObjects); + return viewBuilder(matchingObjects); } diff --git a/src/View/Components/monthGrid.css b/src/View/Components/monthGrid.css index 24326def..b21a7d65 100644 --- a/src/View/Components/monthGrid.css +++ b/src/View/Components/monthGrid.css @@ -1,106 +1,106 @@ .month-grid-wrapper { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; - gap: var(--gap-between-tiles); + gap: var(--gap-between-tiles); - height: 100%; - overflow: auto; + height: 100%; + overflow: auto; } .month-grid-wrapper .day-labels { - flex: 0; + flex: 0; - padding: var(--content-padding); - padding-bottom: 0px; + padding: var(--content-padding); + padding-bottom: 0px; } .month-grid-wrapper > div { - display: grid; - gap: var(--gap-between-tiles); - grid-template-columns: repeat(7, minmax(0px, 1fr)); + display: grid; + gap: var(--gap-between-tiles); + grid-template-columns: repeat(7, minmax(0px, 1fr)); - animation: slide-up var(--animation-duration); + animation: slide-up var(--animation-duration); } .month-grid { - overflow: visible; + overflow: visible; - padding: var(--content-padding); - padding-top: 0px; + padding: var(--content-padding); + padding-top: 0px; - flex: 1; + flex: 1; - grid-auto-rows: 1fr; + grid-auto-rows: 1fr; } .month-grid .tile { - animation: slide-up var(--animation-duration); - overflow: hidden; + animation: slide-up var(--animation-duration); + overflow: hidden; - align-items: start; + align-items: start; } .month-grid .tile[today] b { - color: var(--primary); + color: var(--primary); } .month-grid .tile > div { - width: 100%; - height: 100%; + width: 100%; + height: 100%; } .month-grid .tile .event-count { - display: none; + display: none; - background-color: var(--text-normal); - color: var(--background); + background-color: var(--text-normal); + color: var(--background); - opacity: 0.6; - border-radius: 100%; + opacity: 0.6; + border-radius: 100%; - width: 1rem; - height: 1rem; - font-size: 0.6rem; - font-weight: bold; + width: 1rem; + height: 1rem; + font-size: 0.6rem; + font-weight: bold; - align-items: center; - justify-content: center; + align-items: center; + justify-content: center; } .month-grid .tile .event-count[has-events] { - background-color: var(--primary); - opacity: 1; + background-color: var(--primary); + opacity: 1; } .month-grid .tile .event-list { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; - height: 100%; - width: 100%; + height: 100%; + width: 100%; - gap: var(--gap-between-tiles); + gap: var(--gap-between-tiles); } .month-grid .tile .event-list span { - animation: slide-up var(--animation-duration); + animation: slide-up var(--animation-duration); } @media (max-width: 640px) and (max-height: 900px) { - .month-grid .tile { - padding: 0px; - } + .month-grid .tile { + padding: 0px; + } - .month-grid .tile > div { - align-items: center; - justify-content: center; - } + .month-grid .tile > div { + align-items: center; + justify-content: center; + } - .month-grid .tile .event-count { - display: flex; - } + .month-grid .tile .event-count { + display: flex; + } - .month-grid .tile .event-list { - display: none; - } + .month-grid .tile .event-list { + display: none; + } } diff --git a/src/View/Components/monthGrid.tsx b/src/View/Components/monthGrid.tsx index 42586535..530889c2 100644 --- a/src/View/Components/monthGrid.tsx +++ b/src/View/Components/monthGrid.tsx @@ -8,103 +8,105 @@ import { allowDrop } from "../utility"; import { localeCompare } from "../../Model/Utility/utility"; export function MonthGrid( - monthGrid: MonthGrid>, - selectedDate: React.State, - handleDrop: (date: string) => void + monthGrid: MonthGrid>, + selectedDate: React.State, + handleDrop: (date: string) => void, ) { - const dayLabels: HTMLElement[] = []; - let currentWeekday = monthGrid.firstDayOfWeek; - while (dayLabels.length < 7) { - dayLabels.push( - - {translations.regional.weekdays.abbreviated[currentWeekday]} - + const dayLabels: HTMLElement[] = []; + let currentWeekday = monthGrid.firstDayOfWeek; + while (dayLabels.length < 7) { + dayLabels.push( + + {translations.regional.weekdays.abbreviated[currentWeekday]} + , + ); + currentWeekday++; + if (currentWeekday == 7) currentWeekday = 0; + } + + const offsetElements: HTMLElement[] = []; + for (let i = 0; i < monthGrid.offset; i++) { + offsetElements.push(
); + } + + const converter: React.StateItemConverter = ( + taskViewModel, + ) => { + const view = ( + {taskViewModel.task.name} + ); + + taskViewModel.index.subscribe((newIndex) => { + view.style.order = newIndex; + }); + + return view; + }; + + return ( +
+
{...dayLabels}
+
+ {...offsetElements} + {...Object.entries(monthGrid.days) + .sort((a, b) => localeCompare(a[0], b[0])) + .map((entry) => { + const [date, mapState] = entry; + + const isSelected = React.createProxyState( + [selectedDate], + () => selectedDate.value == parseInt(date), + ); + + const isToday: boolean = + monthGrid.isCurrentMonth == true && + parseInt(date) == new Date().getDate(); + + const eventCount: React.State = + React.createProxyState( + [mapState], + () => mapState.value.size, + ); + + const hasEvents: React.State = + React.createProxyState( + [eventCount], + () => eventCount.value != 0, + ); + + function select() { + selectedDate.value = parseInt(date); + } + + function drop() { + handleDrop(date); + } + + return ( + + ); + })} +
+
); - currentWeekday++; - if (currentWeekday == 7) currentWeekday = 0; - } - - const offsetElements: HTMLElement[] = []; - for (let i = 0; i < monthGrid.offset; i++) { - offsetElements.push(
); - } - - const converter: React.StateItemConverter = ( - taskViewModel - ) => { - const view = ( - {taskViewModel.task.name} - ); - - taskViewModel.index.subscribe((newIndex) => { - view.style.order = newIndex; - }); - - return view; - }; - - return ( -
-
{...dayLabels}
-
- {...offsetElements} - {...Object.entries(monthGrid.days) - .sort((a, b) => localeCompare(a[0], b[0])) - .map((entry) => { - const [date, mapState] = entry; - - const isSelected = React.createProxyState( - [selectedDate], - () => selectedDate.value == parseInt(date) - ); - - const isToday: boolean = - monthGrid.isCurrentMonth == true && - parseInt(date) == new Date().getDate(); - - const eventCount: React.State = React.createProxyState( - [mapState], - () => mapState.value.size - ); - - const hasEvents: React.State = React.createProxyState( - [eventCount], - () => eventCount.value != 0 - ); - - function select() { - selectedDate.value = parseInt(date); - } - - function drop() { - handleDrop(date); - } - - return ( - - ); - })} -
-
- ); } diff --git a/src/View/Components/option.tsx b/src/View/Components/option.tsx index 659b6a93..16a00cbd 100644 --- a/src/View/Components/option.tsx +++ b/src/View/Components/option.tsx @@ -1,32 +1,32 @@ import * as React from "bloatless-react"; export function Option(text: string, value: string, selectedOnCreate: boolean) { - return ( - - ); + return ( + + ); } export const StringToOption: React.StateItemConverter = ( - string: string + string: string, ) => { - return Option(string, string, false); + return Option(string, string, false); }; export const EntryToOption: React.StateItemConverter = ( - entry: Entry + entry: Entry, ) => { - return Option(entry[1], entry[0], false); + return Option(entry[1], entry[0], false); }; export const VersionIdToOption: React.StateItemConverter = ( - versionId: string + versionId: string, ) => { - const [date, rest] = versionId.split("T"); - const [time] = rest.split("."); - const readableName = `${date} ${time}`; - return Option(readableName, versionId, false); + const [date, rest] = versionId.split("T"); + const [time] = rest.split("."); + const readableName = `${date} ${time}`; + return Option(readableName, versionId, false); }; export type Entry = [string, string]; diff --git a/src/View/Components/propertyValueList.tsx b/src/View/Components/propertyValueList.tsx index 7b2a6037..58672815 100644 --- a/src/View/Components/propertyValueList.tsx +++ b/src/View/Components/propertyValueList.tsx @@ -1,70 +1,71 @@ import * as React from "bloatless-react"; import { - StringEntryObject, - collectObjectValuesForKey, - localeCompare, + StringEntryObject, + collectObjectValuesForKey, + localeCompare, } from "../../Model/Utility/utility"; export function PropertyValueList( - propertyKey: string, - stringEntryObjectConverter: (object: T) => StringEntryObject, - objects: React.ListState | React.MapState, - viewBuilder: ( - keys: React.ListState, - sortedKeys: React.State - ) => HTMLElement + propertyKey: string, + stringEntryObjectConverter: (object: T) => StringEntryObject, + objects: React.ListState | React.MapState, + viewBuilder: ( + keys: React.ListState, + sortedKeys: React.State, + ) => HTMLElement, ) { - const propertyValues: React.ListState = new React.ListState(); - const sortedPropertyValues: React.State = createSortedPropertyValueState(propertyValues) - objects.subscribe(() => { - collectPropertyValuesToState( - propertyKey, - stringEntryObjectConverter, - objects, - propertyValues - ); - }); + const propertyValues: React.ListState = new React.ListState(); + const sortedPropertyValues: React.State = + createSortedPropertyValueState(propertyValues); + objects.subscribe(() => { + collectPropertyValuesToState( + propertyKey, + stringEntryObjectConverter, + objects, + propertyValues, + ); + }); - return viewBuilder(propertyValues, sortedPropertyValues); + return viewBuilder(propertyValues, sortedPropertyValues); } export function collectPropertyValuesToState( - propertyKey: string, - stringEntryObjectConverter: (object: T) => StringEntryObject, - objects: React.ListState | React.MapState, - propertyValues: React.ListState + propertyKey: string, + stringEntryObjectConverter: (object: T) => StringEntryObject, + objects: React.ListState | React.MapState, + propertyValues: React.ListState, ) { - const values: string[] = collectObjectValuesForKey( - propertyKey, - stringEntryObjectConverter, - [...objects.value.values()] - ); - for (const existingValue of values) { - if (propertyValues.value.has(existingValue)) continue; - propertyValues.add(existingValue); - } - - for (const displayedValue of propertyValues.value.values()) { - if (values.includes(displayedValue) == false) { - propertyValues.remove(displayedValue); + const values: string[] = collectObjectValuesForKey( + propertyKey, + stringEntryObjectConverter, + [...objects.value.values()], + ); + for (const existingValue of values) { + if (propertyValues.value.has(existingValue)) continue; + propertyValues.add(existingValue); + } + + for (const displayedValue of propertyValues.value.values()) { + if (values.includes(displayedValue) == false) { + propertyValues.remove(displayedValue); + } } - } } export function createSortedPropertyValueState( - propertyValues: React.ListState + propertyValues: React.ListState, ): React.State { - return React.createProxyState([propertyValues], () => - [...propertyValues.value.values()].sort(localeCompare) - ); + return React.createProxyState([propertyValues], () => + [...propertyValues.value.values()].sort(localeCompare), + ); } export function createPropertyValueIndexState( - sortedKeys: React.State, - key: string + sortedKeys: React.State, + key: string, ): React.State { - return React.createProxyState([sortedKeys], () => - sortedKeys.value.indexOf(key) - ); + return React.createProxyState([sortedKeys], () => + sortedKeys.value.indexOf(key), + ); } diff --git a/src/View/Components/ribbonButton.tsx b/src/View/Components/ribbonButton.tsx index 0f23de65..c714d4d8 100644 --- a/src/View/Components/ribbonButton.tsx +++ b/src/View/Components/ribbonButton.tsx @@ -1,23 +1,23 @@ import * as React from "bloatless-react"; import BoardViewModel, { - BoardPageType, + BoardPageType, } from "../../ViewModel/Pages/boardViewModel"; export function RibbonButton( - label: string, - icon: string, - isSelected: React.State, - select: () => void + label: string, + icon: string, + isSelected: React.State, + select: () => void, ) { - return ( - - ); + return ( + + ); } diff --git a/src/View/Components/taskEntry.tsx b/src/View/Components/taskEntry.tsx index 30093800..95d526dc 100644 --- a/src/View/Components/taskEntry.tsx +++ b/src/View/Components/taskEntry.tsx @@ -3,55 +3,58 @@ import * as React from "bloatless-react"; import TaskViewModel from "../../ViewModel/Pages/taskViewModel"; export function TaskEntry(taskViewModel: TaskViewModel) { - const details: { [icon: string]: string } = { - description: taskViewModel.description.value || "---", - priority_high: taskViewModel.priority.value || "---", - category: taskViewModel.category.value || "---", - clock_loader_40: taskViewModel.status.value || "---", - calendar_month: taskViewModel.date.value || "---", - schedule: taskViewModel.time.value || "---", - }; + const details: { [icon: string]: string } = { + description: taskViewModel.description.value || "---", + priority_high: taskViewModel.priority.value || "---", + category: taskViewModel.category.value || "---", + clock_loader_40: taskViewModel.status.value || "---", + calendar_month: taskViewModel.date.value || "---", + schedule: taskViewModel.time.value || "---", + }; - const view = ( - - ); +
+ +
+
+ {...Object.entries(details).map((entry) => ( + + + {entry[0]} + + {entry[1]} + + ))} +
+
+ + ); - taskViewModel.index.subscribe((newIndex) => { - view.style.order = newIndex; - }); + taskViewModel.index.subscribe((newIndex) => { + view.style.order = newIndex; + }); - return view; + return view; } export const TaskViewModelToEntry: React.StateItemConverter = ( - taskViewModel: TaskViewModel + taskViewModel: TaskViewModel, ) => { - return TaskEntry(taskViewModel); + return TaskEntry(taskViewModel); }; diff --git a/src/View/Components/textSpan.tsx b/src/View/Components/textSpan.tsx index fd944c89..e7d88ca0 100644 --- a/src/View/Components/textSpan.tsx +++ b/src/View/Components/textSpan.tsx @@ -1,7 +1,7 @@ import * as React from "bloatless-react"; export const StringToTextSpan: React.StateItemConverter = ( - string: string + string: string, ) => { - return {string} + return {string}; }; diff --git a/src/View/Modals/boardSettingsModal.tsx b/src/View/Modals/boardSettingsModal.tsx index 26a2ec60..c00e914f 100644 --- a/src/View/Modals/boardSettingsModal.tsx +++ b/src/View/Modals/boardSettingsModal.tsx @@ -6,42 +6,47 @@ import { DangerousActionButton } from "../Components/dangerousActionButton"; import { translations } from "../translations"; export function BoardSettingsModal(boardViewModel: BoardViewModel) { - return ( - - ); +
+ ); } diff --git a/src/View/Modals/chatMessageInfoModal.tsx b/src/View/Modals/chatMessageInfoModal.tsx index d15db4c7..6d2c9722 100644 --- a/src/View/Modals/chatMessageInfoModal.tsx +++ b/src/View/Modals/chatMessageInfoModal.tsx @@ -4,73 +4,95 @@ import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; import { translations } from "../translations"; export function ChatMessageInfoModal( - chatMessageViewModel: ChatMessageViewModel + chatMessageViewModel: ChatMessageViewModel, ) { - return ( - - ); +
+ ); } diff --git a/src/View/Modals/connectionModal.tsx b/src/View/Modals/connectionModal.tsx index 99aa81bf..ba1a8fd1 100644 --- a/src/View/Modals/connectionModal.tsx +++ b/src/View/Modals/connectionModal.tsx @@ -5,58 +5,62 @@ import { DeletableListItem } from "../Components/deletableListItem"; import { translations } from "../translations"; export function ConnectionModal(connectionViewModel: ConnectionViewModel) { - const previousAddressConverter: React.StateItemConverter = ( - address: string - ) => { - function connnect() { - connectionViewModel.connectToAddress(address); - } + const previousAddressConverter: React.StateItemConverter = ( + address: string, + ) => { + function connnect() { + connectionViewModel.connectToAddress(address); + } - const cannotConnect = React.createProxyState( - [connectionViewModel.isConnected], - () => - connectionViewModel.isConnected.value == true && - connectionViewModel.connectionModel.address == address + const cannotConnect = React.createProxyState( + [connectionViewModel.isConnected], + () => + connectionViewModel.isConnected.value == true && + connectionViewModel.connectionModel.address == address, + ); + + return DeletableListItem( + address, + , + () => { + connectionViewModel.removePreviousAddress(address); + }, + ); + }; + + return ( + ); - - return DeletableListItem( - address, - , - () => { - connectionViewModel.removePreviousAddress(address); - } - ); - }; - - return ( - - ); } diff --git a/src/View/Modals/dataTransferModal.tsx b/src/View/Modals/dataTransferModal.tsx index 4150fc6c..adaf5c06 100644 --- a/src/View/Modals/dataTransferModal.tsx +++ b/src/View/Modals/dataTransferModal.tsx @@ -1,8 +1,8 @@ import * as React from "bloatless-react"; import FileTransferViewModel, { - FileTransferModal, - FileTransferOption, + FileTransferModal, + FileTransferOption, } from "../../ViewModel/Global/fileTransferViewModel"; import ConnectionViewModel from "../../ViewModel/Global/connectionViewModel"; @@ -12,406 +12,484 @@ import { reload } from "../utility"; import { translations } from "../translations"; export function DataTransferModalWrapper( - connectionViewModel: ConnectionViewModel, - fileTransferViewModel: FileTransferViewModel + connectionViewModel: ConnectionViewModel, + fileTransferViewModel: FileTransferViewModel, ) { - return ( -
- {DirectionSelectionModal(connectionViewModel, fileTransferViewModel)} - {FileSelectionModal(fileTransferViewModel)} - {TransferDataDisplayModal(fileTransferViewModel)} - {TransferDisplayModal(fileTransferViewModel)} - {TransferDataInputModal(fileTransferViewModel)} - {DataReceptionModal(fileTransferViewModel)} -
- ); + return ( +
+ {DirectionSelectionModal( + connectionViewModel, + fileTransferViewModel, + )} + {FileSelectionModal(fileTransferViewModel)} + {TransferDataDisplayModal(fileTransferViewModel)} + {TransferDisplayModal(fileTransferViewModel)} + {TransferDataInputModal(fileTransferViewModel)} + {DataReceptionModal(fileTransferViewModel)} +
+ ); } function DirectionSelectionModal( - connectionViewModel: ConnectionViewModel, - fileTransferViewModel: FileTransferViewModel + connectionViewModel: ConnectionViewModel, + fileTransferViewModel: FileTransferViewModel, ) { - const isPresented = React.createProxyState( - [fileTransferViewModel.presentedModal], - () => - fileTransferViewModel.presentedModal.value == - FileTransferModal.DirectionSelection - ); + const isPresented = React.createProxyState( + [fileTransferViewModel.presentedModal], + () => + fileTransferViewModel.presentedModal.value == + FileTransferModal.DirectionSelection, + ); - const isDisconnected = React.createProxyState( - [connectionViewModel.isConnected], - () => connectionViewModel.isConnected.value == false - ); + const isDisconnected = React.createProxyState( + [connectionViewModel.isConnected], + () => connectionViewModel.isConnected.value == false, + ); - return ( - + ); } function FileSelectionModal(fileTransferViewModel: FileTransferViewModel) { - // option - const OptionConverter: React.StateItemConverter = ( - fileOption: FileTransferOption - ) => { - return OptionEntry(fileOption, fileTransferViewModel); - }; + // option + const OptionConverter: React.StateItemConverter = ( + fileOption: FileTransferOption, + ) => { + return OptionEntry(fileOption, fileTransferViewModel); + }; - function OptionEntry( - fileOption: FileTransferOption, - fileTransferViewModel: FileTransferViewModel - ) { - const isSelected = new React.State(false); - if (fileTransferViewModel.selectedPaths.value.has(fileOption.path)) { - isSelected.value = true; - } - isSelected.subscribeSilent((isSelected) => { - if (isSelected == true) { - fileTransferViewModel.selectedPaths.add(fileOption.path); - } else { - fileTransferViewModel.selectedPaths.remove(fileOption.path); - } - }); - function toggle() { - isSelected.value = !isSelected.value; + function OptionEntry( + fileOption: FileTransferOption, + fileTransferViewModel: FileTransferViewModel, + ) { + const isSelected = new React.State(false); + if (fileTransferViewModel.selectedPaths.value.has(fileOption.path)) { + isSelected.value = true; + } + isSelected.subscribeSilent((isSelected) => { + if (isSelected == true) { + fileTransferViewModel.selectedPaths.add(fileOption.path); + } else { + fileTransferViewModel.selectedPaths.remove(fileOption.path); + } + }); + function toggle() { + isSelected.value = !isSelected.value; + } + + return ( + + ); } + // state + const isPresented = React.createProxyState( + [fileTransferViewModel.presentedModal], + () => + fileTransferViewModel.presentedModal.value == + FileTransferModal.FileSelection, + ); + return ( - + +
+
- ); - } - - // state - const isPresented = React.createProxyState( - [fileTransferViewModel.presentedModal], - () => - fileTransferViewModel.presentedModal.value == - FileTransferModal.FileSelection - ); - - return ( - - ); } function TransferDataDisplayModal( - fileTransferViewModel: FileTransferViewModel + fileTransferViewModel: FileTransferViewModel, ) { - const isPresented = React.createProxyState( - [fileTransferViewModel.presentedModal], - () => - fileTransferViewModel.presentedModal.value == - FileTransferModal.TransferDataDisplay - ); + const isPresented = React.createProxyState( + [fileTransferViewModel.presentedModal], + () => + fileTransferViewModel.presentedModal.value == + FileTransferModal.TransferDataDisplay, + ); - return ( - - ); + ); } function TransferDisplayModal(fileTransferViewModel: FileTransferViewModel) { - const isPresented = React.createProxyState( - [fileTransferViewModel.presentedModal], - () => - fileTransferViewModel.presentedModal.value == - FileTransferModal.TransferDisplay - ); + const isPresented = React.createProxyState( + [fileTransferViewModel.presentedModal], + () => + fileTransferViewModel.presentedModal.value == + FileTransferModal.TransferDisplay, + ); - return ( - -
-
- ); + ); } function TransferDataInputModal(fileTransferViewModel: FileTransferViewModel) { - const isPresented = React.createProxyState( - [fileTransferViewModel.presentedModal], - () => - fileTransferViewModel.presentedModal.value == - FileTransferModal.TransferDataInput - ); + const isPresented = React.createProxyState( + [fileTransferViewModel.presentedModal], + () => + fileTransferViewModel.presentedModal.value == + FileTransferModal.TransferDataInput, + ); - return ( - - - - ); + ); } function DataReceptionModal(fileTransferViewModel: FileTransferViewModel) { - const isPresented = React.createProxyState( - [fileTransferViewModel.presentedModal], - () => - fileTransferViewModel.presentedModal.value == - FileTransferModal.ReceptionDisplay - ); + const isPresented = React.createProxyState( + [fileTransferViewModel.presentedModal], + () => + fileTransferViewModel.presentedModal.value == + FileTransferModal.ReceptionDisplay, + ); - return ( - + ); } diff --git a/src/View/Modals/searchModal.tsx b/src/View/Modals/searchModal.tsx index 1d3f6e22..491d4d62 100644 --- a/src/View/Modals/searchModal.tsx +++ b/src/View/Modals/searchModal.tsx @@ -6,57 +6,65 @@ import { translations } from "../translations"; import { v4 } from "uuid"; export function SearchModal( - searchViewModel: SearchViewModel, - headline: string, - converter: React.StateItemConverter, - isOpen: React.State + searchViewModel: SearchViewModel, + headline: string, + converter: React.StateItemConverter, + isOpen: React.State, ) { - function close() { - isOpen.value = false; - } + function close() { + isOpen.value = false; + } - const suggestionId = v4(); + const suggestionId = v4(); - return ( - + ); } diff --git a/src/View/Modals/storageModal.tsx b/src/View/Modals/storageModal.tsx index e6413aa6..1ac981fa 100644 --- a/src/View/Modals/storageModal.tsx +++ b/src/View/Modals/storageModal.tsx @@ -5,15 +5,15 @@ import StorageViewModel from "../../ViewModel/Global/storageViewModel"; import { translations } from "../translations"; export function StorageModal(storageViewModel: StorageViewModel) { - return ( - - ); + return ( + + ); } diff --git a/src/View/Modals/taskSettingsModal.tsx b/src/View/Modals/taskSettingsModal.tsx index 83a24a49..dc604697 100644 --- a/src/View/Modals/taskSettingsModal.tsx +++ b/src/View/Modals/taskSettingsModal.tsx @@ -1,11 +1,11 @@ import * as React from "bloatless-react"; import { - Entry, - EntryToOption, - Option, - StringToOption, - VersionIdToOption, + Entry, + EntryToOption, + Option, + StringToOption, + VersionIdToOption, } from "../Components/option"; import { DangerousActionButton } from "../Components/dangerousActionButton"; @@ -14,156 +14,194 @@ import { translations } from "../translations"; import { v4 } from "uuid"; export function TaskSettingsModal(taskViewModel: TaskViewModel) { - const categorySuggestionId = v4(); - const statusSuggestionId = v4(); + const categorySuggestionId = v4(); + const statusSuggestionId = v4(); - const BoardOptionConverter: React.StateItemConverter = ( - entry: Entry - ) => { - const isSelected = entry[0] == taskViewModel.task.boardId; - return Option(entry[1], entry[0], isSelected); - }; + const BoardOptionConverter: React.StateItemConverter = ( + entry: Entry, + ) => { + const isSelected = entry[0] == taskViewModel.task.boardId; + return Option(entry[1], entry[0], isSelected); + }; - return ( - - ); + ); } diff --git a/src/View/chatPage.css b/src/View/chatPage.css index b73f3118..a29982bb 100644 --- a/src/View/chatPage.css +++ b/src/View/chatPage.css @@ -1,29 +1,29 @@ /* show/hide */ #chat-page-wrapper { - display: none; + display: none; } body[showing-chat] #home-page { - display: none; + display: none; } body[showing-chat] #chat-page-wrapper { - display: flex; + display: flex; - position: absolute; - top: 0px; - left: 0px; - right: 0px; - bottom: 0px; + position: absolute; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; - background-color: var(--background-glass); + background-color: var(--background-glass); - z-index: 2; + z-index: 2; } /* layout */ #chat-page { - --ribbon-size: var(--control-height); + --ribbon-size: var(--control-height); } #chat-page > div, @@ -35,179 +35,179 @@ body[showing-chat] #chat-page-wrapper { #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar, #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar > span, #chat-page > div > #main > div > .pane-wrapper > .pane > .content { - display: flex; + display: flex; } #chat-page > div { - padding: 0px; - overflow: hidden; + padding: 0px; + overflow: hidden; } #chat-page > div > #main { - width: calc(100% - var(--ribbon-size) - 1px); + width: calc(100% - var(--ribbon-size) - 1px); } #chat-page #main > div, #chat-page > div > #main > div > .pane-wrapper, #chat-page > div > #main > div > .pane-wrapper > .pane, #chat-page > div > #main > div > .pane-wrapper > .pane > .content { - width: 100%; - height: 100%; - flex: 1; + width: 100%; + height: 100%; + flex: 1; } #chat-page > div > #main > div > .pane-wrapper, #chat-page > div > #main > div > .pane-wrapper > .pane { - flex-direction: column; - scroll-snap-align: center; + flex-direction: column; + scroll-snap-align: center; - min-width: 100%; - overflow: hidden; + min-width: 100%; + overflow: hidden; } #chat-page > div > #main > div > .pane-wrapper > .pane:not(.side), #chat-page > div > #main > div > .pane-wrapper > .pane:not(.side) .content { - animation: slide-up var(--animation-duration); + animation: slide-up var(--animation-duration); } @media (min-width: 1024px) { - #chat-page > div > #main > div > .pane-wrapper { - min-width: unset; - } + #chat-page > div > #main > div > .pane-wrapper { + min-width: unset; + } - #chat-page > div > #main > div > .pane-wrapper.side { - width: 18rem; - min-width: 18rem; - max-width: 18rem; - } + #chat-page > div > #main > div > .pane-wrapper.side { + width: 18rem; + min-width: 18rem; + max-width: 18rem; + } } #chat-page > div > #main > div > .pane-wrapper > .pane > .content { - flex-direction: column; - overflow: auto; - padding: var(--content-padding); + flex-direction: column; + overflow: auto; + padding: var(--content-padding); } #chat-page #ribbon, #chat-page #main, #chat-page #main .content { - flex-direction: column; + flex-direction: column; } /* ribbon, toolbar */ #chat-page > div > #ribbon, #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar { - align-items: center; + align-items: center; } #chat-page > div > #ribbon { - width: calc(var(--ribbon-size) + 1px); - border-right: 1px solid var(--border); - padding-bottom: var(--safe-area-bottom); + width: calc(var(--ribbon-size) + 1px); + border-right: 1px solid var(--border); + padding-bottom: var(--safe-area-bottom); } #chat-page > div > #ribbon > span, #chat-page - > div - > #main - > div - > .pane-wrapper - > .pane - > .toolbar - > span.ribbon { - gap: var(--gap-between-tiles); - padding: var(--gap-between-tiles); + > div + > #main + > div + > .pane-wrapper + > .pane + > .toolbar + > span.ribbon { + gap: var(--gap-between-tiles); + padding: var(--gap-between-tiles); } #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar { - justify-content: start; - justify-content: space-between; + justify-content: start; + justify-content: space-between; } #chat-page > div > #ribbon > span { - height: 100%; + height: 100%; - flex: 1; - overflow-y: auto; + flex: 1; + overflow-y: auto; - flex-direction: column; - justify-content: end; + flex-direction: column; + justify-content: end; } #chat-page > div > #ribbon button, #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar button { - padding: 0px; + padding: 0px; - width: var(--control-height); - max-width: var(--control-height); - height: var(--control-height); - min-height: var(--control-height); + width: var(--control-height); + max-width: var(--control-height); + height: var(--control-height); + min-height: var(--control-height); - align-items: center; - justify-content: center; + align-items: center; + justify-content: center; } #chat-page > div > #ribbon button.ribbon-button, #chat-page - > div - > #main - > div - > .pane-wrapper - > .pane - > .toolbar - button.ribbon-button { - min-width: calc(var(--control-height) - 2 * var(--gap-between-tiles)); - width: calc(var(--control-height) - 2 * var(--gap-between-tiles)); - max-width: calc(var(--control-height) - 2 * var(--gap-between-tiles)); + > div + > #main + > div + > .pane-wrapper + > .pane + > .toolbar + button.ribbon-button { + min-width: calc(var(--control-height) - 2 * var(--gap-between-tiles)); + width: calc(var(--control-height) - 2 * var(--gap-between-tiles)); + max-width: calc(var(--control-height) - 2 * var(--gap-between-tiles)); - min-height: unset; - height: calc(var(--control-height) - 2 * var(--gap-between-tiles)); + min-height: unset; + height: calc(var(--control-height) - 2 * var(--gap-between-tiles)); - color: var(--primary); + color: var(--primary); } #chat-page > div > #ribbon button > .icon, #chat-page - > div - > #main - > div - > .pane-wrapper - > .pane - > .toolbar - button - > .icon { - font-size: 1.1rem; + > div + > #main + > div + > .pane-wrapper + > .pane + > .toolbar + button + > .icon { + font-size: 1.1rem; } #chat-page > div > #ribbon > span { - width: 100%; + width: 100%; } #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar { - height: var(--ribbon-size); + height: var(--ribbon-size); - overflow-x: auto; + overflow-x: auto; - padding: 0px; + padding: 0px; } #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar > span { - display: flex; - align-items: center; + display: flex; + align-items: center; } #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar > span.title { - padding-left: var(--content-padding); + padding-left: var(--content-padding); } #chat-page - > div - > #main - > div - > .pane-wrapper - > .pane - > .toolbar - :is(input, select, button) { - height: 100%; - flex: 1; + > div + > #main + > div + > .pane-wrapper + > .pane + > .toolbar + :is(input, select, button) { + height: 100%; + flex: 1; } diff --git a/src/View/chatPage.tsx b/src/View/chatPage.tsx index e84b62ec..9f928e4a 100644 --- a/src/View/chatPage.tsx +++ b/src/View/chatPage.tsx @@ -12,73 +12,73 @@ import { TaskPage } from "./ChatPages/taskPage"; import { translations } from "./translations"; export function ChatPage(chatViewModel: ChatViewModel) { - const mainContent = React.createProxyState( - [chatViewModel.selectedPage], - () => { - chatViewModel.closeSubPages(); + const mainContent = React.createProxyState( + [chatViewModel.selectedPage], + () => { + chatViewModel.closeSubPages(); - switch (chatViewModel.selectedPage.value) { - case ChatPageType.Settings: { - return SettingsPage(chatViewModel.settingsPageViewModel); - } - case ChatPageType.Tasks: { - return TaskPage(chatViewModel.taskPageViewModel); - } - case ChatPageType.Calendar: { - return CalendarPage(chatViewModel.calendarViewModel) - } - default: { - return MessagePage(chatViewModel.messagePageViewModel); - } - } - } - ); + switch (chatViewModel.selectedPage.value) { + case ChatPageType.Settings: { + return SettingsPage(chatViewModel.settingsPageViewModel); + } + case ChatPageType.Tasks: { + return TaskPage(chatViewModel.taskPageViewModel); + } + case ChatPageType.Calendar: { + return CalendarPage(chatViewModel.calendarViewModel); + } + default: { + return MessagePage(chatViewModel.messagePageViewModel); + } + } + }, + ); - return ( -
-
-
- + return ( +
+
+
+ - - {ChatViewToggleButton( - translations.chatPage.pages.calendar, - "calendar_month", - ChatPageType.Calendar, - chatViewModel - )} - {ChatViewToggleButton( - translations.chatPage.pages.tasks, - "task_alt", - ChatPageType.Tasks, - chatViewModel - )} - {ChatViewToggleButton( - translations.chatPage.pages.messages, - "forum", - ChatPageType.Messages, - chatViewModel - )} - {ChatViewToggleButton( - translations.chatPage.pages.settings, - "settings", - ChatPageType.Settings, - chatViewModel - )} - -
-
-
-
- ); + + {ChatViewToggleButton( + translations.chatPage.pages.calendar, + "calendar_month", + ChatPageType.Calendar, + chatViewModel, + )} + {ChatViewToggleButton( + translations.chatPage.pages.tasks, + "task_alt", + ChatPageType.Tasks, + chatViewModel, + )} + {ChatViewToggleButton( + translations.chatPage.pages.messages, + "forum", + ChatPageType.Messages, + chatViewModel, + )} + {ChatViewToggleButton( + translations.chatPage.pages.settings, + "settings", + ChatPageType.Settings, + chatViewModel, + )} + +
+
+
+
+ ); } diff --git a/src/View/chatPageWrapper.tsx b/src/View/chatPageWrapper.tsx index ac732b07..3777e5c7 100644 --- a/src/View/chatPageWrapper.tsx +++ b/src/View/chatPageWrapper.tsx @@ -6,21 +6,16 @@ import ChatListViewModel from "../ViewModel/Chat/chatListViewModel"; import { ChatPage } from "./chatPage"; export function ChatPageWrapper(chatListViewModel: ChatListViewModel) { - const chatPageContent = React.createProxyState( - [chatListViewModel.selectedChat], - () => { - if (chatListViewModel.selectedChat.value == undefined) { - return
; - } else { - return ChatPage(chatListViewModel.selectedChat.value); - } - } - ); + const chatPageContent = React.createProxyState( + [chatListViewModel.selectedChat], + () => { + if (chatListViewModel.selectedChat.value == undefined) { + return
; + } else { + return ChatPage(chatListViewModel.selectedChat.value); + } + }, + ); - return ( -
- ); + return
; } diff --git a/src/View/homePage.css b/src/View/homePage.css index 48f4f658..b2de65b0 100644 --- a/src/View/homePage.css +++ b/src/View/homePage.css @@ -1,88 +1,88 @@ #home-page > div { - display: flex; - flex-direction: row; + display: flex; + flex-direction: row; - gap: var(--large-gap); + gap: var(--large-gap); - scroll-snap-type: x mandatory; - scroll-behavior: smooth; - overflow: hidden; - overflow-x: scroll; + scroll-snap-type: x mandatory; + scroll-behavior: smooth; + overflow: hidden; + overflow-x: scroll; - padding: 0px; + padding: 0px; } #home-page > div > div { - display: flex; - flex-direction: column; - justify-content: start; + display: flex; + flex-direction: column; + justify-content: start; - gap: var(--gap-between-tiles); - padding: var(--content-padding); - padding-top: calc(var(--header-height) + var(--content-margin)); + gap: var(--gap-between-tiles); + padding: var(--content-padding); + padding-top: calc(var(--header-height) + var(--content-margin)); - width: 100%; - min-width: 100%; - height: 100%; - overflow-y: auto; + width: 100%; + min-width: 100%; + height: 100%; + overflow-y: auto; - scroll-snap-align: center; + scroll-snap-align: center; } #home-page #overview-section, #home-page #chat-section { - padding-bottom: calc( - var(--content-padding) + var(--header-height) + var(--safe-area-bottom) - ); + padding-bottom: calc( + var(--content-padding) + var(--header-height) + var(--safe-area-bottom) + ); } #home-page #chat-grid { - margin-top: var(--content-padding); + margin-top: var(--content-padding); - display: grid; - gap: var(--gap-between-tiles); + display: grid; + gap: var(--gap-between-tiles); - grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); } .chat-entry[unread] { - border: var(--outline-width) solid black; + border: var(--outline-width) solid black; } .chat-entry[unread]:not(:hover):not(:active) { - background: rgb(255, 217, 0) !important; - color: black; + background: rgb(255, 217, 0) !important; + color: black; } @media (min-width: 640px) { - #home-page > div { - overflow-x: hidden; - } + #home-page > div { + overflow-x: hidden; + } - #home-page #overview-section { - width: 20rem; - min-width: 20rem; - } + #home-page #overview-section { + width: 20rem; + min-width: 20rem; + } - #home-page #chat-section { - flex: 1; - min-width: unset; - } + #home-page #chat-section { + flex: 1; + min-width: unset; + } } @media (min-width: 1200px) { - #home-page #overview-section, - #home-page #chat-section { - padding-left: 3rem; - padding-right: 3rem; - } + #home-page #overview-section, + #home-page #chat-section { + padding-left: 3rem; + padding-right: 3rem; + } - #home-page #chat-grid { - margin-top: 3rem; - } + #home-page #chat-grid { + margin-top: 3rem; + } - #home-page #overview-section { - width: var(--max-input-width); - min-width: var(--max-input-width); - } + #home-page #overview-section { + width: var(--max-input-width); + min-width: var(--max-input-width); + } } diff --git a/src/View/homePage.tsx b/src/View/homePage.tsx index 782cab9f..24370222 100644 --- a/src/View/homePage.tsx +++ b/src/View/homePage.tsx @@ -13,184 +13,196 @@ import StorageViewModel from "../ViewModel/Global/storageViewModel"; import { translations } from "./translations"; export function HomePage( - storageViewModel: StorageViewModel, - settingsViewModel: SettingsViewModel, - connectionViewModel: ConnectionViewModel, - fileTransferViewModel: FileTransferViewModel, - chatListViewModel: ChatListViewModel + storageViewModel: StorageViewModel, + settingsViewModel: SettingsViewModel, + connectionViewModel: ConnectionViewModel, + fileTransferViewModel: FileTransferViewModel, + chatListViewModel: ChatListViewModel, ) { - // sections - const overviewSection = ( -
-

{translations.homePage.overviewHeadline}

+ // sections + const overviewSection = ( +
+

{translations.homePage.overviewHeadline}

- + ); -
- - - -
+ const chatSection = ( +
+

{translations.homePage.chatsHeadline}

-
+
+ + +
- -
- -
+ ); -
+ // methods + function scrollToChat() { + chatSection.scrollIntoView(); + } - - -
- - - - - -
-
- -
- -
-
-
- ); - - const chatSection = ( -
-

{translations.homePage.chatsHeadline}

- -
- - -
- -
-
- ); - - // methods - function scrollToChat() { - chatSection.scrollIntoView(); - } - - // final - return ( -
-
- {overviewSection} - {chatSection} -
-
- ); + // final + return ( +
+
+ {overviewSection} + {chatSection} +
+
+ ); } diff --git a/src/View/mainPage.tsx b/src/View/mainPage.tsx index c51f5476..7cf5d8cd 100644 --- a/src/View/mainPage.tsx +++ b/src/View/mainPage.tsx @@ -3,12 +3,12 @@ import * as React from "bloatless-react"; import { translations } from "./translations"; export function MainPage() { - return ( -
-
- {translations.homePage.appName} -
-
-
- ); + return ( +
+
+ {translations.homePage.appName} +
+
+
+ ); } diff --git a/src/View/translations.ts b/src/View/translations.ts index 2ae88a60..9553dbdf 100644 --- a/src/View/translations.ts +++ b/src/View/translations.ts @@ -1,749 +1,763 @@ const englishTranslations = { - updater: { - migrated: "Migrated", - }, - - general: { - deleteItemButtonAudioLabel: "delete item", - searchButtonAudioLabel: "search", - - abortButton: "Abort", - cancelButton: "Cancel", - closeButton: "Close", - backButton: "Back", - - continueButton: "Continue", - confirmButton: "Confirm", - saveButton: "Save", - setButton: "Set", - - reloadAppButton: "Reload App", - - fileVersionLabel: "Version", - searchLabel: "Search", - }, - - regional: { - weekdays: { - full: [ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - ], - abbreviated: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], - }, - }, - - homePage: { - appName: "Comms", - - /// - - overviewHeadline: "Overview", - - serverAddress: "Server address", - serverAddressPlaceholder: "wss://192.168.0.69:3000", - connectAudioLabel: "connect to server", - disconnectAudioLabel: "disconnect from server", - manageConnectionsAudioLabel: "manage connections", - - yourNameLabel: "Your name", - yourNamePlaceholder: "Jane Doe", - setNameButtonAudioLabel: "set name", - - firstDayOfWeekLabel: "First day of week", - - manageStorageButton: "Manage storage", - - transferDataButton: "Data Transfer", - - scrollToChatButton: "Chats", - - /// - - backToOverviewAudioLabel: "go back to overview", - chatsHeadline: "Chats", - - addChatAudioLabel: "name of new chat", - addChatPlaceholder: "Add chat", - addChatButton: "Add chat", - }, - - connectionModal: { - connectionModalHeadline: "Manage Connections", - - /// - - connectButtonAudioLabel: "connect", - }, - - dataTransferModal: { - transferDataHeadline: "Data Transfer", - selectionDescription: "Select the data that you want to transfer.", - dataEntryDescription: "Enter this data on the other device.", - dataEntryInputDescription: "Enter the data displayed on the other device.", - readyToReceiveDescription: "Click 'send' on the other device.", - - notConnectedError: "You are not connected to any server.", - - /// - - fromThisDeviceButton: "From this device", - toThisDeviceButton: "To this device", - - /// - - generalHeadline: "General", - - connectionData: "Connection Data", - settingsData: "Settings Data", - - chatsHeadline: "Chats", - - /// - - transferChannelHeadline: "Transfer Chanel", - transferKeyHeadline: "Transfer Encryption Key", - - sendButton: "Send", - sendAgainButton: "Send again", - - /// - - filesSentCount: (count: number) => `Files sent: ${count}.`, - allFilesSent: "Done.", - - filesReceivedCount: (count: number) => `Files received: ${count}.`, - }, - - storage: { - noItemSelected: "No item selected", - notAFile: "(not a file)", - contentEmpty: "(empty)", - - path: "Path", - content: "Content", - - deleteItem: "Delete item", - - removeJunkButton: "Delete junk files", - }, - - chatPage: { - closeChatAudioLabe: "close chat", - chatSettingsAudioLabel: "chat settings", - - pages: { - settings: "Settings", - messages: "Messages", - tasks: "Tasks", - calendar: "Calendar", + updater: { + migrated: "Migrated", }, - settings: { - settingsHeadline: "Settings", + general: { + deleteItemButtonAudioLabel: "delete item", + searchButtonAudioLabel: "search", - primaryChannelLabel: "Primary channel", - setPrimaryChannelButtonAudioLabel: "set primary channel", + abortButton: "Abort", + cancelButton: "Cancel", + closeButton: "Close", + backButton: "Back", - newSecondaryChannelPlaceholder: "Add secondary channel", - newSecondaryChannelAudioLabel: "name of new secondary channel", - addSecondaryChannelButtonAudioLabel: "add secondary channel", + continueButton: "Continue", + confirmButton: "Confirm", + saveButton: "Save", + setButton: "Set", - encryptionKeyLabel: "Encryption key", - setEncryptionKeyButtonAudioLabel: "set encryption key", - showEncryptionKey: "Show encryption key", + reloadAppButton: "Reload App", - deleteChatButton: "Delete entire chat", + fileVersionLabel: "Version", + searchLabel: "Search", }, - message: { - messagesHeadline: "Messages", - - /// - - composerInputPlaceholder: "Type a message...", - sendMessageButtonAudioLabel: "send message", - - /// - - showMessageInfoButtonAudioLabel: "show message info", - messageInfoHeadline: "Message Info", - - sentBy: "Sent by", - timeSent: "Time sent", - channel: "Channel", - messageContent: "Message content", - - copyMessageButton: "Copy message", - resendMessageButton: "Resend message", - decryptMessageButton: "Decrypt message", - deleteMessageButton: "Delete message", + regional: { + weekdays: { + full: [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ], + abbreviated: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + }, }, - task: { - newBoardNamePlaceholder: "Create a board", - createBoardButtonAudioLabel: "create board", + homePage: { + appName: "Comms", - /// + /// - noBoardSelected: "No board selected", - boardNotFound: "Board not found", + overviewHeadline: "Overview", - /// + serverAddress: "Server address", + serverAddressPlaceholder: "wss://192.168.0.69:3000", + connectAudioLabel: "connect to server", + disconnectAudioLabel: "disconnect from server", + manageConnectionsAudioLabel: "manage connections", - closeBoardButtonAudioLabel: "close board", - toggleBoardButtonAudioLabel: "toggle board list", - showBoardSettingsButtonAudioLabel: "show board settigns", + yourNameLabel: "Your name", + yourNamePlaceholder: "Jane Doe", + setNameButtonAudioLabel: "set name", - listViewButtonAudioLabel: "list view", - kanbanViewButtonAudioLabel: "kanban view", - statusViewButtonAudioLabel: "status grid view", + firstDayOfWeekLabel: "First day of week", - filterTasksButtonAudioLabel: "filter tasks", - createTaskButtonAudioLabel: "create new task", + manageStorageButton: "Manage storage", - /// + transferDataButton: "Data Transfer", - boardSettingsHeadline: "Board Settings", - boardNameInputLabel: "Board name", - deleteBoardButton: "Delete board and all tasks", + scrollToChatButton: "Chats", - /// + /// - taskSettingsHeadline: "Edit Task", + backToOverviewAudioLabel: "go back to overview", + chatsHeadline: "Chats", - taskNameLabel: "Title", - taskBoardLabel: "Board", - - taskCategoryLabel: "Category", - taskStatusLabel: "Status", - taskPriorityLabel: "Priority", - - taskDescriptionLabel: "Description", - - taskDateLabel: "Date", - taskTimeLabel: "Time", - - deleteTaskButton: "Delete task", - - /// - - filterTasksHeadline: "Filter Tasks", - - /// - - renameCategoryInputPlaceholder: "Rename category,", + addChatAudioLabel: "name of new chat", + addChatPlaceholder: "Add chat", + addChatButton: "Add chat", }, - calendar: { - eventsBoard: "Events", + connectionModal: { + connectionModalHeadline: "Manage Connections", - /// + /// - todayButtonAudioLabel: "go to today", - - previousMonthButtonAudioLabel: "previous month", - nextMonthButtonAudioLabel: "next month", - - yearInputAudioLabel: "year", - monthInputAudioLabel: "month", - - yearInputPlaceholder: "2000", - monthInputPlaceholder: "01", - - /// - - searchEventsHeadline: "Search Events", - - /// - - events: "Events", - noEvents: "No events", + connectButtonAudioLabel: "connect", + }, + + dataTransferModal: { + transferDataHeadline: "Data Transfer", + selectionDescription: "Select the data that you want to transfer.", + dataEntryDescription: "Enter this data on the other device.", + dataEntryInputDescription: + "Enter the data displayed on the other device.", + readyToReceiveDescription: "Click 'send' on the other device.", + + notConnectedError: "You are not connected to any server.", + + /// + + fromThisDeviceButton: "From this device", + toThisDeviceButton: "To this device", + + /// + + generalHeadline: "General", + + connectionData: "Connection Data", + settingsData: "Settings Data", + + chatsHeadline: "Chats", + + /// + + transferChannelHeadline: "Transfer Chanel", + transferKeyHeadline: "Transfer Encryption Key", + + sendButton: "Send", + sendAgainButton: "Send again", + + /// + + filesSentCount: (count: number) => `Files sent: ${count}.`, + allFilesSent: "Done.", + + filesReceivedCount: (count: number) => `Files received: ${count}.`, + }, + + storage: { + noItemSelected: "No item selected", + notAFile: "(not a file)", + contentEmpty: "(empty)", + + path: "Path", + content: "Content", + + deleteItem: "Delete item", + + removeJunkButton: "Delete junk files", + }, + + chatPage: { + closeChatAudioLabe: "close chat", + chatSettingsAudioLabel: "chat settings", + + pages: { + settings: "Settings", + messages: "Messages", + tasks: "Tasks", + calendar: "Calendar", + }, + + settings: { + settingsHeadline: "Settings", + + primaryChannelLabel: "Primary channel", + setPrimaryChannelButtonAudioLabel: "set primary channel", + + newSecondaryChannelPlaceholder: "Add secondary channel", + newSecondaryChannelAudioLabel: "name of new secondary channel", + addSecondaryChannelButtonAudioLabel: "add secondary channel", + + encryptionKeyLabel: "Encryption key", + setEncryptionKeyButtonAudioLabel: "set encryption key", + showEncryptionKey: "Show encryption key", + + deleteChatButton: "Delete entire chat", + }, + + message: { + messagesHeadline: "Messages", + + /// + + composerInputPlaceholder: "Type a message...", + sendMessageButtonAudioLabel: "send message", + + /// + + showMessageInfoButtonAudioLabel: "show message info", + messageInfoHeadline: "Message Info", + + sentBy: "Sent by", + timeSent: "Time sent", + channel: "Channel", + messageContent: "Message content", + + copyMessageButton: "Copy message", + resendMessageButton: "Resend message", + decryptMessageButton: "Decrypt message", + deleteMessageButton: "Delete message", + }, + + task: { + newBoardNamePlaceholder: "Create a board", + createBoardButtonAudioLabel: "create board", + + /// + + noBoardSelected: "No board selected", + boardNotFound: "Board not found", + + /// + + closeBoardButtonAudioLabel: "close board", + toggleBoardButtonAudioLabel: "toggle board list", + showBoardSettingsButtonAudioLabel: "show board settigns", + + listViewButtonAudioLabel: "list view", + kanbanViewButtonAudioLabel: "kanban view", + statusViewButtonAudioLabel: "status grid view", + + filterTasksButtonAudioLabel: "filter tasks", + createTaskButtonAudioLabel: "create new task", + + /// + + boardSettingsHeadline: "Board Settings", + boardNameInputLabel: "Board name", + deleteBoardButton: "Delete board and all tasks", + + /// + + taskSettingsHeadline: "Edit Task", + + taskNameLabel: "Title", + taskBoardLabel: "Board", + + taskCategoryLabel: "Category", + taskStatusLabel: "Status", + taskPriorityLabel: "Priority", + + taskDescriptionLabel: "Description", + + taskDateLabel: "Date", + taskTimeLabel: "Time", + + deleteTaskButton: "Delete task", + + /// + + filterTasksHeadline: "Filter Tasks", + + /// + + renameCategoryInputPlaceholder: "Rename category,", + }, + + calendar: { + eventsBoard: "Events", + + /// + + todayButtonAudioLabel: "go to today", + + previousMonthButtonAudioLabel: "previous month", + nextMonthButtonAudioLabel: "next month", + + yearInputAudioLabel: "year", + monthInputAudioLabel: "month", + + yearInputPlaceholder: "2000", + monthInputPlaceholder: "01", + + /// + + searchEventsHeadline: "Search Events", + + /// + + events: "Events", + noEvents: "No events", + }, }, - }, }; const allTranslations: { [language: string]: typeof englishTranslations } = { - en: englishTranslations, + en: englishTranslations, - de: { - updater: { - migrated: "Migriert", + de: { + updater: { + migrated: "Migriert", + }, + + general: { + deleteItemButtonAudioLabel: "element löschen", + searchButtonAudioLabel: "suchen", + + abortButton: "Abbrechen", + cancelButton: "Abbrechen", + closeButton: "Schließen", + backButton: "Zurück", + + continueButton: "Weiter", + confirmButton: "Bestätigen", + saveButton: "Speichern", + setButton: "OK", + + reloadAppButton: "Neu laden", + + fileVersionLabel: "Version", + searchLabel: "Suche", + }, + + regional: { + weekdays: { + full: [ + "Sonntag", + "Montag", + "Dienstag", + "Mittwoch", + "Donnerstag", + "Freitag", + "Samstag", + ], + abbreviated: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"], + }, + }, + + homePage: { + appName: "Comms", + + overviewHeadline: "Übersicht", + + serverAddress: "Serveradresse", + serverAddressPlaceholder: "wss://192.168.0.69:3000", + connectAudioLabel: "mit Server verbinden", + disconnectAudioLabel: "vom Server trennen", + manageConnectionsAudioLabel: "Verbindungen verwalten", + + yourNameLabel: "Dein Name", + yourNamePlaceholder: "Max Mustermann", + setNameButtonAudioLabel: "Name speichern", + + firstDayOfWeekLabel: "Erster Wochentag", + + manageStorageButton: "Daten verwalten", + + transferDataButton: "Datenübertragung", + + scrollToChatButton: "Chats", + + backToOverviewAudioLabel: "zurück zur übersicht", + chatsHeadline: "Chats", + + addChatAudioLabel: "Name des neuen Chats", + addChatPlaceholder: "Chat hinzufügen", + addChatButton: "Chat hinzufügen", + }, + + connectionModal: { + connectionModalHeadline: "Verbindungen verwalten", + + connectButtonAudioLabel: "verbinden", + }, + + dataTransferModal: { + transferDataHeadline: "Datenübertragung", + selectionDescription: + "Wähle die Daten aus, die du übertragen möchtest.", + dataEntryDescription: + "Gib diese Informationen auf dem anderen Gerät ein.", + dataEntryInputDescription: + "Gib die auf dem anderen Gerät angezeigten Informationen ein.", + readyToReceiveDescription: + "Klicke auf dem anderen Gerät auf 'Senden'.", + + notConnectedError: "Du bist mit keinem Server verbunden.", + + fromThisDeviceButton: "Von diesem Gerät", + toThisDeviceButton: "An dieses Gerät", + + generalHeadline: "Allgemein", + + connectionData: "Verbindungsdaten", + settingsData: "Einstellungen", + + chatsHeadline: "Chats", + + transferChannelHeadline: "Übertragungskanal", + transferKeyHeadline: "Schlüssel", + + sendButton: "Senden", + sendAgainButton: "Erneut senden", + + filesSentCount: (count) => `Dateien gesendet: ${count}.`, + allFilesSent: "Fertig.", + + filesReceivedCount: (count) => `Dateien empfangen: ${count}.`, + }, + + storage: { + noItemSelected: "Kein Element ausgewählt", + notAFile: "(keine Datei)", + contentEmpty: "(leer)", + + path: "Pfad", + content: "Inhalt", + + deleteItem: "Element löschen", + + removeJunkButton: "Datenmüll löschen", + }, + + chatPage: { + closeChatAudioLabe: "Chat schließen", + chatSettingsAudioLabel: "Chateinstellungen", + + pages: { + settings: "Einstellungen", + messages: "Nachrichten", + tasks: "Aufgaben", + calendar: "Kalender", + }, + + settings: { + settingsHeadline: "Einstellungen", + + primaryChannelLabel: "Hauptkanal", + setPrimaryChannelButtonAudioLabel: "Hauptkanal festlegen", + + newSecondaryChannelPlaceholder: "Sekundären Kanal hinzufügen", + newSecondaryChannelAudioLabel: + "Name des neuen sekundären Kanals", + addSecondaryChannelButtonAudioLabel: + "Sekundären Kanal hinzufügen", + + encryptionKeyLabel: "Schlüssel", + setEncryptionKeyButtonAudioLabel: "Schlüssel festlegen", + showEncryptionKey: "Schlüssel anzeigen", + + deleteChatButton: "Gesamten Chat löschen", + }, + + message: { + messagesHeadline: "Nachrichten", + + composerInputPlaceholder: "Schreib eine Nachricht...", + sendMessageButtonAudioLabel: "nachricht senden", + + showMessageInfoButtonAudioLabel: "nachrichteninfo anzeigen", + messageInfoHeadline: "Nachrichteninfo", + + sentBy: "Gesendet von", + timeSent: "Sendezeit", + channel: "Kanal", + messageContent: "Nachrichteninhalt", + + copyMessageButton: "Nachricht kopieren", + resendMessageButton: "Nachricht erneut senden", + decryptMessageButton: "Nachricht entschlüsseln", + deleteMessageButton: "Nachricht löschen", + }, + + task: { + newBoardNamePlaceholder: "Board erstellen", + createBoardButtonAudioLabel: "board erstellen", + + noBoardSelected: "Kein Board ausgewählt", + boardNotFound: "Board nicht gefunden", + + closeBoardButtonAudioLabel: "board schließen", + toggleBoardButtonAudioLabel: "board-liste ein/ausblenden", + showBoardSettingsButtonAudioLabel: + "Board-Einstellungen anzeigen", + + listViewButtonAudioLabel: "Listenansicht", + kanbanViewButtonAudioLabel: "Kanban-Ansicht", + statusViewButtonAudioLabel: "Statusrasteransicht", + + filterTasksButtonAudioLabel: "Aufgaben filtern", + createTaskButtonAudioLabel: "Neue Aufgabe erstellen", + + boardSettingsHeadline: "Board-Einstellungen", + boardNameInputLabel: "Boardname", + deleteBoardButton: "Board und alle Aufgaben löschen", + + taskSettingsHeadline: "Aufgabe bearbeiten", + + taskNameLabel: "Titel", + taskBoardLabel: "Board", + + taskCategoryLabel: "Kategorie", + taskStatusLabel: "Status", + taskPriorityLabel: "Priorität", + + taskDescriptionLabel: "Beschreibung", + + taskDateLabel: "Datum", + taskTimeLabel: "Uhrzeit", + + deleteTaskButton: "Aufgabe löschen", + + filterTasksHeadline: "Aufgaben filtern", + + renameCategoryInputPlaceholder: "Kategorie umbenennen", + }, + + calendar: { + eventsBoard: "Ereignisse", + + /// + + todayButtonAudioLabel: "gehe zu heute", + + previousMonthButtonAudioLabel: "vorheriger monat", + nextMonthButtonAudioLabel: "nächster monat", + + yearInputAudioLabel: "Jahr", + monthInputAudioLabel: "Monat", + + yearInputPlaceholder: "2000", + monthInputPlaceholder: "01", + + searchEventsHeadline: "Ereignisse suchen", + + events: "Ereignisse", + noEvents: "Keine Ereignisse", + }, + }, }, + es: { + updater: { + migrated: "Migrado", + }, - general: { - deleteItemButtonAudioLabel: "element löschen", - searchButtonAudioLabel: "suchen", + general: { + deleteItemButtonAudioLabel: "eliminar elemento", + searchButtonAudioLabel: "buscar", - abortButton: "Abbrechen", - cancelButton: "Abbrechen", - closeButton: "Schließen", - backButton: "Zurück", + abortButton: "Abortar", + cancelButton: "Cancelar", + closeButton: "Cerrar", + backButton: "Atrás", - continueButton: "Weiter", - confirmButton: "Bestätigen", - saveButton: "Speichern", - setButton: "OK", + continueButton: "Continuar", + confirmButton: "Confirmar", + saveButton: "Guardar", + setButton: "OK", - reloadAppButton: "Neu laden", + reloadAppButton: "Recargar app", - fileVersionLabel: "Version", - searchLabel: "Suche", + fileVersionLabel: "Versión", + searchLabel: "Buscar", + }, + + regional: { + weekdays: { + full: [ + "Domingo", + "Lunes", + "Martes", + "Miércoles", + "Jueves", + "Viernes", + "Sábado", + ], + abbreviated: ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb"], + }, + }, + + homePage: { + appName: "Comms", + + overviewHeadline: "Resumen", + + serverAddress: "Dirección del servidor", + serverAddressPlaceholder: "wss://192.168.0.69:3000", + connectAudioLabel: "conectar al servidor", + disconnectAudioLabel: "desconectar del servidor", + manageConnectionsAudioLabel: "gestionar conexiones", + + yourNameLabel: "Tu nombre", + yourNamePlaceholder: "Juan Pérez", + setNameButtonAudioLabel: "establecer nombre", + + firstDayOfWeekLabel: "Primer día de la semana", + + manageStorageButton: "Gestionar almacenamiento", + + transferDataButton: "Transferencia de datos", + + scrollToChatButton: "Chats", + + backToOverviewAudioLabel: "volver al resumen", + chatsHeadline: "Chats", + + addChatAudioLabel: "nombre del nuevo chat", + addChatPlaceholder: "Añadir chat", + addChatButton: "Añadir chat", + }, + + connectionModal: { + connectionModalHeadline: "Gestionar Conexiones", + + connectButtonAudioLabel: "conectar", + }, + + dataTransferModal: { + transferDataHeadline: "Transferencia de Datos", + selectionDescription: + "Selecciona los datos que quieres transferir.", + dataEntryDescription: + "Introduce estos datos en el otro dispositivo.", + dataEntryInputDescription: + "Introduce los datos mostrados en el otro dispositivo.", + readyToReceiveDescription: + "Haz clic en 'enviar' en el otro dispositivo.", + + notConnectedError: "No estás conectado a ningún servidor.", + + fromThisDeviceButton: "Desde este dispositivo", + toThisDeviceButton: "A este dispositivo", + + generalHeadline: "General", + + connectionData: "Datos de Conexión", + settingsData: "Datos de Configuración", + + chatsHeadline: "Chats", + + transferChannelHeadline: "Canal de Transferencia", + transferKeyHeadline: "Clave de Encriptación de Transferencia", + + sendButton: "Enviar", + sendAgainButton: "Enviar otra vez", + + filesSentCount: (count) => `Archivos enviados: ${count}.`, + allFilesSent: "Hecho.", + + filesReceivedCount: (count) => `Archivos recibidos: ${count}.`, + }, + + storage: { + noItemSelected: "Ningún elemento seleccionado", + notAFile: "(no es un archivo)", + contentEmpty: "(vacío)", + + path: "Ruta", + content: "Contenido", + + deleteItem: "Eliminar elemento", + + removeJunkButton: "Eliminar archivos basura", + }, + + chatPage: { + closeChatAudioLabe: "cerrar chat", + chatSettingsAudioLabel: "configuración del chat", + + pages: { + settings: "Configuración", + messages: "Mensajes", + tasks: "Tareas", + calendar: "Calendario", + }, + + settings: { + settingsHeadline: "Configuración", + + primaryChannelLabel: "Canal principal", + setPrimaryChannelButtonAudioLabel: "establecer canal principal", + + newSecondaryChannelPlaceholder: "Añadir canal secundario", + newSecondaryChannelAudioLabel: + "nombre del nuevo canal secundario", + addSecondaryChannelButtonAudioLabel: "añadir canal secundario", + + encryptionKeyLabel: "Clave de encriptación", + setEncryptionKeyButtonAudioLabel: + "establecer clave de encriptación", + showEncryptionKey: "Mostrar clave de encriptación", + + deleteChatButton: "Eliminar todo el chat", + }, + + message: { + messagesHeadline: "Mensajes", + + composerInputPlaceholder: "Escribe un mensaje...", + sendMessageButtonAudioLabel: "enviar mensaje", + + showMessageInfoButtonAudioLabel: + "mostrar información del mensaje", + messageInfoHeadline: "Información del Mensaje", + + sentBy: "Enviado por", + timeSent: "Hora de envío", + channel: "Canal", + messageContent: "Contenido del mensaje", + + copyMessageButton: "Copiar mensaje", + resendMessageButton: "Reenviar mensaje", + decryptMessageButton: "Desencriptar mensaje", + deleteMessageButton: "Eliminar mensaje", + }, + + task: { + newBoardNamePlaceholder: "Crear un tablero", + createBoardButtonAudioLabel: "crear tablero", + + noBoardSelected: "Ningún tablero seleccionado", + boardNotFound: "Tablero no encontrado", + + closeBoardButtonAudioLabel: "cerrar tablero", + toggleBoardButtonAudioLabel: + "mostrar o ocultar lista de tableros", + showBoardSettingsButtonAudioLabel: + "mostrar configuración del tablero", + + listViewButtonAudioLabel: "vista de lista", + kanbanViewButtonAudioLabel: "vista kanban", + statusViewButtonAudioLabel: "vista de cuadrícula de estado", + + filterTasksButtonAudioLabel: "filtrar tareas", + createTaskButtonAudioLabel: "crear nueva tarea", + + boardSettingsHeadline: "Configuración del Tablero", + boardNameInputLabel: "Nombre del tablero", + deleteBoardButton: "Eliminar tablero y todas las tareas", + + taskSettingsHeadline: "Editar Tarea", + + taskNameLabel: "Título", + taskBoardLabel: "Tablero", + + taskCategoryLabel: "Categoría", + taskStatusLabel: "Estado", + taskPriorityLabel: "Prioridad", + + taskDescriptionLabel: "Descripción", + + taskDateLabel: "Fecha", + taskTimeLabel: "Hora", + + deleteTaskButton: "Eliminar tarea", + + filterTasksHeadline: "Filtrar Tareas", + + renameCategoryInputPlaceholder: "Renombrar categoría", + }, + + calendar: { + eventsBoard: "Eventos", + + /// + + todayButtonAudioLabel: "ir a hoy", + + previousMonthButtonAudioLabel: "mes anterior", + nextMonthButtonAudioLabel: "mes siguiente", + + yearInputAudioLabel: "año", + monthInputAudioLabel: "mes", + + yearInputPlaceholder: "2000", + monthInputPlaceholder: "01", + + searchEventsHeadline: "Buscar Eventos", + + events: "Eventos", + noEvents: "No hay eventos", + }, + }, }, - - regional: { - weekdays: { - full: [ - "Sonntag", - "Montag", - "Dienstag", - "Mittwoch", - "Donnerstag", - "Freitag", - "Samstag", - ], - abbreviated: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"], - }, - }, - - homePage: { - appName: "Comms", - - overviewHeadline: "Übersicht", - - serverAddress: "Serveradresse", - serverAddressPlaceholder: "wss://192.168.0.69:3000", - connectAudioLabel: "mit Server verbinden", - disconnectAudioLabel: "vom Server trennen", - manageConnectionsAudioLabel: "Verbindungen verwalten", - - yourNameLabel: "Dein Name", - yourNamePlaceholder: "Max Mustermann", - setNameButtonAudioLabel: "Name speichern", - - firstDayOfWeekLabel: "Erster Wochentag", - - manageStorageButton: "Daten verwalten", - - transferDataButton: "Datenübertragung", - - scrollToChatButton: "Chats", - - backToOverviewAudioLabel: "zurück zur übersicht", - chatsHeadline: "Chats", - - addChatAudioLabel: "Name des neuen Chats", - addChatPlaceholder: "Chat hinzufügen", - addChatButton: "Chat hinzufügen", - }, - - connectionModal: { - connectionModalHeadline: "Verbindungen verwalten", - - connectButtonAudioLabel: "verbinden", - }, - - dataTransferModal: { - transferDataHeadline: "Datenübertragung", - selectionDescription: "Wähle die Daten aus, die du übertragen möchtest.", - dataEntryDescription: - "Gib diese Informationen auf dem anderen Gerät ein.", - dataEntryInputDescription: - "Gib die auf dem anderen Gerät angezeigten Informationen ein.", - readyToReceiveDescription: "Klicke auf dem anderen Gerät auf 'Senden'.", - - notConnectedError: "Du bist mit keinem Server verbunden.", - - fromThisDeviceButton: "Von diesem Gerät", - toThisDeviceButton: "An dieses Gerät", - - generalHeadline: "Allgemein", - - connectionData: "Verbindungsdaten", - settingsData: "Einstellungen", - - chatsHeadline: "Chats", - - transferChannelHeadline: "Übertragungskanal", - transferKeyHeadline: "Schlüssel", - - sendButton: "Senden", - sendAgainButton: "Erneut senden", - - filesSentCount: (count) => `Dateien gesendet: ${count}.`, - allFilesSent: "Fertig.", - - filesReceivedCount: (count) => `Dateien empfangen: ${count}.`, - }, - - storage: { - noItemSelected: "Kein Element ausgewählt", - notAFile: "(keine Datei)", - contentEmpty: "(leer)", - - path: "Pfad", - content: "Inhalt", - - deleteItem: "Element löschen", - - removeJunkButton: "Datenmüll löschen", - }, - - chatPage: { - closeChatAudioLabe: "Chat schließen", - chatSettingsAudioLabel: "Chateinstellungen", - - pages: { - settings: "Einstellungen", - messages: "Nachrichten", - tasks: "Aufgaben", - calendar: "Kalender", - }, - - settings: { - settingsHeadline: "Einstellungen", - - primaryChannelLabel: "Hauptkanal", - setPrimaryChannelButtonAudioLabel: "Hauptkanal festlegen", - - newSecondaryChannelPlaceholder: "Sekundären Kanal hinzufügen", - newSecondaryChannelAudioLabel: "Name des neuen sekundären Kanals", - addSecondaryChannelButtonAudioLabel: "Sekundären Kanal hinzufügen", - - encryptionKeyLabel: "Schlüssel", - setEncryptionKeyButtonAudioLabel: "Schlüssel festlegen", - showEncryptionKey: "Schlüssel anzeigen", - - deleteChatButton: "Gesamten Chat löschen", - }, - - message: { - messagesHeadline: "Nachrichten", - - composerInputPlaceholder: "Schreib eine Nachricht...", - sendMessageButtonAudioLabel: "nachricht senden", - - showMessageInfoButtonAudioLabel: "nachrichteninfo anzeigen", - messageInfoHeadline: "Nachrichteninfo", - - sentBy: "Gesendet von", - timeSent: "Sendezeit", - channel: "Kanal", - messageContent: "Nachrichteninhalt", - - copyMessageButton: "Nachricht kopieren", - resendMessageButton: "Nachricht erneut senden", - decryptMessageButton: "Nachricht entschlüsseln", - deleteMessageButton: "Nachricht löschen", - }, - - task: { - newBoardNamePlaceholder: "Board erstellen", - createBoardButtonAudioLabel: "board erstellen", - - noBoardSelected: "Kein Board ausgewählt", - boardNotFound: "Board nicht gefunden", - - closeBoardButtonAudioLabel: "board schließen", - toggleBoardButtonAudioLabel: "board-liste ein/ausblenden", - showBoardSettingsButtonAudioLabel: "Board-Einstellungen anzeigen", - - listViewButtonAudioLabel: "Listenansicht", - kanbanViewButtonAudioLabel: "Kanban-Ansicht", - statusViewButtonAudioLabel: "Statusrasteransicht", - - filterTasksButtonAudioLabel: "Aufgaben filtern", - createTaskButtonAudioLabel: "Neue Aufgabe erstellen", - - boardSettingsHeadline: "Board-Einstellungen", - boardNameInputLabel: "Boardname", - deleteBoardButton: "Board und alle Aufgaben löschen", - - taskSettingsHeadline: "Aufgabe bearbeiten", - - taskNameLabel: "Titel", - taskBoardLabel: "Board", - - taskCategoryLabel: "Kategorie", - taskStatusLabel: "Status", - taskPriorityLabel: "Priorität", - - taskDescriptionLabel: "Beschreibung", - - taskDateLabel: "Datum", - taskTimeLabel: "Uhrzeit", - - deleteTaskButton: "Aufgabe löschen", - - filterTasksHeadline: "Aufgaben filtern", - - renameCategoryInputPlaceholder: "Kategorie umbenennen", - }, - - calendar: { - eventsBoard: "Ereignisse", - - /// - - todayButtonAudioLabel: "gehe zu heute", - - previousMonthButtonAudioLabel: "vorheriger monat", - nextMonthButtonAudioLabel: "nächster monat", - - yearInputAudioLabel: "Jahr", - monthInputAudioLabel: "Monat", - - yearInputPlaceholder: "2000", - monthInputPlaceholder: "01", - - searchEventsHeadline: "Ereignisse suchen", - - events: "Ereignisse", - noEvents: "Keine Ereignisse", - }, - }, - }, - es: { - updater: { - migrated: "Migrado", - }, - - general: { - deleteItemButtonAudioLabel: "eliminar elemento", - searchButtonAudioLabel: "buscar", - - abortButton: "Abortar", - cancelButton: "Cancelar", - closeButton: "Cerrar", - backButton: "Atrás", - - continueButton: "Continuar", - confirmButton: "Confirmar", - saveButton: "Guardar", - setButton: "OK", - - reloadAppButton: "Recargar app", - - fileVersionLabel: "Versión", - searchLabel: "Buscar", - }, - - regional: { - weekdays: { - full: [ - "Domingo", - "Lunes", - "Martes", - "Miércoles", - "Jueves", - "Viernes", - "Sábado", - ], - abbreviated: ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb"], - }, - }, - - homePage: { - appName: "Comms", - - overviewHeadline: "Resumen", - - serverAddress: "Dirección del servidor", - serverAddressPlaceholder: "wss://192.168.0.69:3000", - connectAudioLabel: "conectar al servidor", - disconnectAudioLabel: "desconectar del servidor", - manageConnectionsAudioLabel: "gestionar conexiones", - - yourNameLabel: "Tu nombre", - yourNamePlaceholder: "Juan Pérez", - setNameButtonAudioLabel: "establecer nombre", - - firstDayOfWeekLabel: "Primer día de la semana", - - manageStorageButton: "Gestionar almacenamiento", - - transferDataButton: "Transferencia de datos", - - scrollToChatButton: "Chats", - - backToOverviewAudioLabel: "volver al resumen", - chatsHeadline: "Chats", - - addChatAudioLabel: "nombre del nuevo chat", - addChatPlaceholder: "Añadir chat", - addChatButton: "Añadir chat", - }, - - connectionModal: { - connectionModalHeadline: "Gestionar Conexiones", - - connectButtonAudioLabel: "conectar", - }, - - dataTransferModal: { - transferDataHeadline: "Transferencia de Datos", - selectionDescription: "Selecciona los datos que quieres transferir.", - dataEntryDescription: "Introduce estos datos en el otro dispositivo.", - dataEntryInputDescription: - "Introduce los datos mostrados en el otro dispositivo.", - readyToReceiveDescription: "Haz clic en 'enviar' en el otro dispositivo.", - - notConnectedError: "No estás conectado a ningún servidor.", - - fromThisDeviceButton: "Desde este dispositivo", - toThisDeviceButton: "A este dispositivo", - - generalHeadline: "General", - - connectionData: "Datos de Conexión", - settingsData: "Datos de Configuración", - - chatsHeadline: "Chats", - - transferChannelHeadline: "Canal de Transferencia", - transferKeyHeadline: "Clave de Encriptación de Transferencia", - - sendButton: "Enviar", - sendAgainButton: "Enviar otra vez", - - filesSentCount: (count) => `Archivos enviados: ${count}.`, - allFilesSent: "Hecho.", - - filesReceivedCount: (count) => `Archivos recibidos: ${count}.`, - }, - - storage: { - noItemSelected: "Ningún elemento seleccionado", - notAFile: "(no es un archivo)", - contentEmpty: "(vacío)", - - path: "Ruta", - content: "Contenido", - - deleteItem: "Eliminar elemento", - - removeJunkButton: "Eliminar archivos basura", - }, - - chatPage: { - closeChatAudioLabe: "cerrar chat", - chatSettingsAudioLabel: "configuración del chat", - - pages: { - settings: "Configuración", - messages: "Mensajes", - tasks: "Tareas", - calendar: "Calendario", - }, - - settings: { - settingsHeadline: "Configuración", - - primaryChannelLabel: "Canal principal", - setPrimaryChannelButtonAudioLabel: "establecer canal principal", - - newSecondaryChannelPlaceholder: "Añadir canal secundario", - newSecondaryChannelAudioLabel: "nombre del nuevo canal secundario", - addSecondaryChannelButtonAudioLabel: "añadir canal secundario", - - encryptionKeyLabel: "Clave de encriptación", - setEncryptionKeyButtonAudioLabel: "establecer clave de encriptación", - showEncryptionKey: "Mostrar clave de encriptación", - - deleteChatButton: "Eliminar todo el chat", - }, - - message: { - messagesHeadline: "Mensajes", - - composerInputPlaceholder: "Escribe un mensaje...", - sendMessageButtonAudioLabel: "enviar mensaje", - - showMessageInfoButtonAudioLabel: "mostrar información del mensaje", - messageInfoHeadline: "Información del Mensaje", - - sentBy: "Enviado por", - timeSent: "Hora de envío", - channel: "Canal", - messageContent: "Contenido del mensaje", - - copyMessageButton: "Copiar mensaje", - resendMessageButton: "Reenviar mensaje", - decryptMessageButton: "Desencriptar mensaje", - deleteMessageButton: "Eliminar mensaje", - }, - - task: { - newBoardNamePlaceholder: "Crear un tablero", - createBoardButtonAudioLabel: "crear tablero", - - noBoardSelected: "Ningún tablero seleccionado", - boardNotFound: "Tablero no encontrado", - - closeBoardButtonAudioLabel: "cerrar tablero", - toggleBoardButtonAudioLabel: "mostrar o ocultar lista de tableros", - showBoardSettingsButtonAudioLabel: "mostrar configuración del tablero", - - listViewButtonAudioLabel: "vista de lista", - kanbanViewButtonAudioLabel: "vista kanban", - statusViewButtonAudioLabel: "vista de cuadrícula de estado", - - filterTasksButtonAudioLabel: "filtrar tareas", - createTaskButtonAudioLabel: "crear nueva tarea", - - boardSettingsHeadline: "Configuración del Tablero", - boardNameInputLabel: "Nombre del tablero", - deleteBoardButton: "Eliminar tablero y todas las tareas", - - taskSettingsHeadline: "Editar Tarea", - - taskNameLabel: "Título", - taskBoardLabel: "Tablero", - - taskCategoryLabel: "Categoría", - taskStatusLabel: "Estado", - taskPriorityLabel: "Prioridad", - - taskDescriptionLabel: "Descripción", - - taskDateLabel: "Fecha", - taskTimeLabel: "Hora", - - deleteTaskButton: "Eliminar tarea", - - filterTasksHeadline: "Filtrar Tareas", - - renameCategoryInputPlaceholder: "Renombrar categoría", - }, - - calendar: { - eventsBoard: "Eventos", - - /// - - todayButtonAudioLabel: "ir a hoy", - - previousMonthButtonAudioLabel: "mes anterior", - nextMonthButtonAudioLabel: "mes siguiente", - - yearInputAudioLabel: "año", - monthInputAudioLabel: "mes", - - yearInputPlaceholder: "2000", - monthInputPlaceholder: "01", - - searchEventsHeadline: "Buscar Eventos", - - events: "Eventos", - noEvents: "No hay eventos", - }, - }, - }, }; const language = navigator.language.substring(0, 2); diff --git a/src/View/utility.ts b/src/View/utility.ts index f7ba929e..b16cc650 100644 --- a/src/View/utility.ts +++ b/src/View/utility.ts @@ -1,11 +1,11 @@ export function allowDrop(event: DragEvent) { - event.preventDefault(); + event.preventDefault(); } export function allowDrag(event: DragEvent) { - event.dataTransfer?.setData("text", "") + event.dataTransfer?.setData("text", ""); } export function reload() { - window.location.reload(); -} \ No newline at end of file + window.location.reload(); +} diff --git a/src/ViewModel/Chat/chatListViewModel.ts b/src/ViewModel/Chat/chatListViewModel.ts index 69f44840..6e254876 100644 --- a/src/ViewModel/Chat/chatListViewModel.ts +++ b/src/ViewModel/Chat/chatListViewModel.ts @@ -9,98 +9,99 @@ import SettingsViewModel from "../Global/settingsViewModel"; import StorageModel from "../../Model/Global/storageModel"; export default class ChatListViewModel { - storageModel: StorageModel; - chatListModel: ChatListModel; - settingsViewModel: SettingsViewModel; + storageModel: StorageModel; + chatListModel: ChatListModel; + settingsViewModel: SettingsViewModel; - // data - chatIndexManager: IndexManager = new IndexManager( - (chatViewModel: ChatViewModel) => - chatViewModel.settingsPageViewModel.primaryChannel.value - ); - - // state - newChatPrimaryChannel: React.State = new React.State(""); - chatViewModels: React.ListState = new React.ListState(); - - selectedChat: React.State = new React.State( - undefined - ); - - // guards - cannotCreateChat = React.createProxyState( - [this.newChatPrimaryChannel], - () => this.newChatPrimaryChannel.value == "" - ); - - // methods - createChat = (): void => { - const chatModel: ChatModel = this.chatListModel.createChat( - this.newChatPrimaryChannel.value + // data + chatIndexManager: IndexManager = new IndexManager( + (chatViewModel: ChatViewModel) => + chatViewModel.settingsPageViewModel.primaryChannel.value, ); - this.newChatPrimaryChannel.value = ""; - const chatViewModel: ChatViewModel = this.createChatViewModel(chatModel); - this.trackChat(chatViewModel); - this.updateIndices(); - }; + // state + newChatPrimaryChannel: React.State = new React.State(""); + chatViewModels: React.ListState = new React.ListState(); - trackChat = (chatViewModel: ChatViewModel): void => { - this.chatViewModels.add(chatViewModel); - }; - - untrackChat = (chatViewModel: ChatViewModel): void => { - this.chatListModel.untrackChat(chatViewModel.chatModel); - this.chatViewModels.remove(chatViewModel); - }; - - createChatViewModel = (chatModel: ChatModel): ChatViewModel => { - return new ChatViewModel( - this.coreViewModel, - this.storageModel, - chatModel, - this.settingsViewModel, - this + selectedChat: React.State = new React.State( + undefined, ); - }; - updateIndices = (): void => { - this.chatIndexManager.update([...this.chatViewModels.value.values()]); - for (const chatViewModel of this.chatViewModels.value) { - chatViewModel.updateIndex(); + // guards + cannotCreateChat = React.createProxyState( + [this.newChatPrimaryChannel], + () => this.newChatPrimaryChannel.value == "", + ); + + // methods + createChat = (): void => { + const chatModel: ChatModel = this.chatListModel.createChat( + this.newChatPrimaryChannel.value, + ); + this.newChatPrimaryChannel.value = ""; + + const chatViewModel: ChatViewModel = + this.createChatViewModel(chatModel); + this.trackChat(chatViewModel); + this.updateIndices(); + }; + + trackChat = (chatViewModel: ChatViewModel): void => { + this.chatViewModels.add(chatViewModel); + }; + + untrackChat = (chatViewModel: ChatViewModel): void => { + this.chatListModel.untrackChat(chatViewModel.chatModel); + this.chatViewModels.remove(chatViewModel); + }; + + createChatViewModel = (chatModel: ChatModel): ChatViewModel => { + return new ChatViewModel( + this.coreViewModel, + this.storageModel, + chatModel, + this.settingsViewModel, + this, + ); + }; + + updateIndices = (): void => { + this.chatIndexManager.update([...this.chatViewModels.value.values()]); + for (const chatViewModel of this.chatViewModels.value) { + chatViewModel.updateIndex(); + } + }; + + // view + openChat = (chatViewModel: ChatViewModel): void => { + this.selectedChat.value = chatViewModel; + }; + + closeChat = (): void => { + this.selectedChat.value = undefined; + }; + + // load + loadChats = (): void => { + this.chatViewModels.clear(); + for (const chatModel of this.chatListModel.chatModels.values()) { + const chatViewModel = this.createChatViewModel(chatModel); + this.trackChat(chatViewModel); + } + this.updateIndices(); + }; + + // init + constructor( + public coreViewModel: CoreViewModel, + storageModel: StorageModel, + chatListModel: ChatListModel, + settingsViewModel: SettingsViewModel, + ) { + this.storageModel = storageModel; + this.chatListModel = chatListModel; + this.settingsViewModel = settingsViewModel; + + this.loadChats(); } - }; - - // view - openChat = (chatViewModel: ChatViewModel): void => { - this.selectedChat.value = chatViewModel; - }; - - closeChat = (): void => { - this.selectedChat.value = undefined; - }; - - // load - loadChats = (): void => { - this.chatViewModels.clear(); - for (const chatModel of this.chatListModel.chatModels.values()) { - const chatViewModel = this.createChatViewModel(chatModel); - this.trackChat(chatViewModel); - } - this.updateIndices(); - }; - - // init - constructor( - public coreViewModel: CoreViewModel, - storageModel: StorageModel, - chatListModel: ChatListModel, - settingsViewModel: SettingsViewModel - ) { - this.storageModel = storageModel; - this.chatListModel = chatListModel; - this.settingsViewModel = settingsViewModel; - - this.loadChats(); - } } diff --git a/src/ViewModel/Chat/chatMessageViewModel.ts b/src/ViewModel/Chat/chatMessageViewModel.ts index 8911f29a..3a9a79f5 100644 --- a/src/ViewModel/Chat/chatMessageViewModel.ts +++ b/src/ViewModel/Chat/chatMessageViewModel.ts @@ -6,62 +6,62 @@ import CoreViewModel from "../Global/coreViewModel"; import MessagePageViewModel from "../Pages/messagePageViewModel"; export default class ChatMessageViewModel { - messagePageViewModel: MessagePageViewModel; + messagePageViewModel: MessagePageViewModel; - // data - chatMessage: ChatMessage; - channel: string; - sender: string; - dateSent: string; - body: React.State = new React.State(""); - status: React.State = new React.State( - undefined - ); - sentByUser: boolean; + // data + chatMessage: ChatMessage; + channel: string; + sender: string; + dateSent: string; + body: React.State = new React.State(""); + status: React.State = new React.State( + undefined, + ); + sentByUser: boolean; - // state - isPresentingInfoModal: React.State = new React.State(false); + // state + isPresentingInfoModal: React.State = new React.State(false); - // methods - copyMessage = (): void => { - navigator.clipboard.writeText(this.body.value); - }; - resendMessage = (): void => { - this.messagePageViewModel.sendMessageFromBody(this.body.value); - }; - decryptMessage = (): void => { - this.messagePageViewModel.decryptMessage(this); - }; + // methods + copyMessage = (): void => { + navigator.clipboard.writeText(this.body.value); + }; + resendMessage = (): void => { + this.messagePageViewModel.sendMessageFromBody(this.body.value); + }; + decryptMessage = (): void => { + this.messagePageViewModel.decryptMessage(this); + }; - // view - showInfoModal = (): void => { - this.isPresentingInfoModal.value = true; - }; + // view + showInfoModal = (): void => { + this.isPresentingInfoModal.value = true; + }; - hideInfoModal = (): void => { - this.isPresentingInfoModal.value = false; - }; + hideInfoModal = (): void => { + this.isPresentingInfoModal.value = false; + }; - // load - loadData = (): void => { - this.channel = this.chatMessage.channel; - this.sender = this.chatMessage.sender; - this.dateSent = new Date(this.chatMessage.dateSent).toLocaleString(); - this.body.value = this.chatMessage.body; - this.status.value = this.chatMessage.status; - }; + // load + loadData = (): void => { + this.channel = this.chatMessage.channel; + this.sender = this.chatMessage.sender; + this.dateSent = new Date(this.chatMessage.dateSent).toLocaleString(); + this.body.value = this.chatMessage.body; + this.status.value = this.chatMessage.status; + }; - // init - constructor( - public coreViewModel: CoreViewModel, - messagePageViewModel: MessagePageViewModel, - chatMessage: ChatMessage, - sentByUser: boolean - ) { - this.messagePageViewModel = messagePageViewModel; + // init + constructor( + public coreViewModel: CoreViewModel, + messagePageViewModel: MessagePageViewModel, + chatMessage: ChatMessage, + sentByUser: boolean, + ) { + this.messagePageViewModel = messagePageViewModel; - this.chatMessage = chatMessage; - this.sentByUser = sentByUser; - this.loadData(); - } + this.chatMessage = chatMessage; + this.sentByUser = sentByUser; + this.loadData(); + } } diff --git a/src/ViewModel/Chat/chatViewModel.ts b/src/ViewModel/Chat/chatViewModel.ts index 7291acc3..ff182275 100644 --- a/src/ViewModel/Chat/chatViewModel.ts +++ b/src/ViewModel/Chat/chatViewModel.ts @@ -1,12 +1,12 @@ import * as React from "bloatless-react"; import CalendarPageViewModel, { - CALENDAR_EVENT_BOARD_ID, + CALENDAR_EVENT_BOARD_ID, } from "../Pages/calendarPageViewModel"; import ChatModel, { ChatMessage } from "../../Model/Chat/chatModel"; import StorageModel, { - StorageModelSubPath, - filePaths, + StorageModelSubPath, + filePaths, } from "../../Model/Global/storageModel"; import ChatListViewModel from "./chatListViewModel"; @@ -20,145 +20,145 @@ import TaskPageViewModel from "../Pages/taskPageViewModel"; import { translations } from "../../View/translations"; export default class ChatViewModel { - chatModel: ChatModel; - storageModel: StorageModel; - settingsViewModel: SettingsViewModel; - chatListViewModel: ChatListViewModel; + chatModel: ChatModel; + storageModel: StorageModel; + settingsViewModel: SettingsViewModel; + chatListViewModel: ChatListViewModel; - calendarViewModel: CalendarPageViewModel; - taskPageViewModel: TaskPageViewModel; - messagePageViewModel: MessagePageViewModel; - settingsPageViewModel: SettingsPageViewModel; + calendarViewModel: CalendarPageViewModel; + taskPageViewModel: TaskPageViewModel; + messagePageViewModel: MessagePageViewModel; + settingsPageViewModel: SettingsPageViewModel; - // state - displayedColor: React.State = new React.State(Color.Standard); - selectedPage: React.State = new React.State( - ChatPageType.Messages - ); - - index: React.State = new React.State(0); - hasUnreadMessages: React.State = new React.State(false); - - taskBoardSuggestions: React.MapState = new React.MapState(); - - // view - open = (): void => { - this.chatListViewModel.openChat(this); - this.markRead(); - }; - - close = (): void => { - this.chatListViewModel.closeChat(); - }; - - closeSubPages = (): void => {}; - - setColor = (color: Color): void => { - this.setDisplayedColor(color); - this.chatModel.setColor(color); - }; - - setDisplayedColor = (color: Color): void => { - this.displayedColor.value = color; - }; - - resetColor = (): void => { - this.displayedColor.value = this.settingsPageViewModel.color.value; - }; - - updateIndex = (): void => { - const index: number = - this.chatListViewModel.chatIndexManager.getIndex(this); - this.index.value = index; - }; - - markRead = (): void => { - this.hasUnreadMessages.value = false; - this.chatModel.markRead(); - }; - - // load - loadPageSelection = (): void => { - const path: string[] = StorageModel.getPath( - StorageModelSubPath.Chat, - filePaths.chat.lastUsedPage(this.chatModel.id) - ); - const lastUsedPage: string | null = this.storageModel.read(path); - if (lastUsedPage != null) { - this.selectedPage.value = lastUsedPage as any; - } - - this.selectedPage.subscribeSilent((newPage) => { - this.storageModel.write(path, newPage); - this.resetColor(); - }); - }; - - loadInfo = (): void => { - this.hasUnreadMessages.value = this.chatModel.info.hasUnreadMessages; - - this.taskBoardSuggestions.set(CALENDAR_EVENT_BOARD_ID, [ - CALENDAR_EVENT_BOARD_ID, - translations.chatPage.calendar.eventsBoard, - ]); - }; - - // init - constructor( - public coreViewModel: CoreViewModel, - storageModel: StorageModel, - chatModel: ChatModel, - settingsViewModel: SettingsViewModel, - chatListViewModel: ChatListViewModel - ) { - // models - this.storageModel = storageModel; - this.chatModel = chatModel; - this.settingsViewModel = settingsViewModel; - this.chatListViewModel = chatListViewModel; - - // page viewModels - this.calendarViewModel = new CalendarPageViewModel( - coreViewModel, - this, - this.storageModel, - this.chatModel.fileModel.boardsAndTasksModel.calendarModel, - this.chatModel.fileModel.boardsAndTasksModel - ); - this.taskPageViewModel = new TaskPageViewModel( - this.coreViewModel, - this, - this.storageModel, - this.chatModel.fileModel.boardsAndTasksModel - ); - this.messagePageViewModel = new MessagePageViewModel( - this.coreViewModel, - this - ); - this.settingsPageViewModel = new SettingsPageViewModel( - this.coreViewModel, - this + // state + displayedColor: React.State = new React.State(Color.Standard); + selectedPage: React.State = new React.State( + ChatPageType.Messages, ); - // handlers - chatModel.chatMessageHandlerManager.addHandler( - (chatMessage: ChatMessage) => { - this.messagePageViewModel.showChatMessage(chatMessage); + index: React.State = new React.State(0); + hasUnreadMessages: React.State = new React.State(false); + + taskBoardSuggestions: React.MapState = new React.MapState(); + + // view + open = (): void => { + this.chatListViewModel.openChat(this); this.markRead(); - } - ); + }; + + close = (): void => { + this.chatListViewModel.closeChat(); + }; + + closeSubPages = (): void => {}; + + setColor = (color: Color): void => { + this.setDisplayedColor(color); + this.chatModel.setColor(color); + }; + + setDisplayedColor = (color: Color): void => { + this.displayedColor.value = color; + }; + + resetColor = (): void => { + this.displayedColor.value = this.settingsPageViewModel.color.value; + }; + + updateIndex = (): void => { + const index: number = + this.chatListViewModel.chatIndexManager.getIndex(this); + this.index.value = index; + }; + + markRead = (): void => { + this.hasUnreadMessages.value = false; + this.chatModel.markRead(); + }; // load - this.loadPageSelection(); - this.resetColor(); - this.loadInfo(); - } + loadPageSelection = (): void => { + const path: string[] = StorageModel.getPath( + StorageModelSubPath.Chat, + filePaths.chat.lastUsedPage(this.chatModel.id), + ); + const lastUsedPage: string | null = this.storageModel.read(path); + if (lastUsedPage != null) { + this.selectedPage.value = lastUsedPage as any; + } + + this.selectedPage.subscribeSilent((newPage) => { + this.storageModel.write(path, newPage); + this.resetColor(); + }); + }; + + loadInfo = (): void => { + this.hasUnreadMessages.value = this.chatModel.info.hasUnreadMessages; + + this.taskBoardSuggestions.set(CALENDAR_EVENT_BOARD_ID, [ + CALENDAR_EVENT_BOARD_ID, + translations.chatPage.calendar.eventsBoard, + ]); + }; + + // init + constructor( + public coreViewModel: CoreViewModel, + storageModel: StorageModel, + chatModel: ChatModel, + settingsViewModel: SettingsViewModel, + chatListViewModel: ChatListViewModel, + ) { + // models + this.storageModel = storageModel; + this.chatModel = chatModel; + this.settingsViewModel = settingsViewModel; + this.chatListViewModel = chatListViewModel; + + // page viewModels + this.calendarViewModel = new CalendarPageViewModel( + coreViewModel, + this, + this.storageModel, + this.chatModel.fileModel.boardsAndTasksModel.calendarModel, + this.chatModel.fileModel.boardsAndTasksModel, + ); + this.taskPageViewModel = new TaskPageViewModel( + this.coreViewModel, + this, + this.storageModel, + this.chatModel.fileModel.boardsAndTasksModel, + ); + this.messagePageViewModel = new MessagePageViewModel( + this.coreViewModel, + this, + ); + this.settingsPageViewModel = new SettingsPageViewModel( + this.coreViewModel, + this, + ); + + // handlers + chatModel.chatMessageHandlerManager.addHandler( + (chatMessage: ChatMessage) => { + this.messagePageViewModel.showChatMessage(chatMessage); + this.markRead(); + }, + ); + + // load + this.loadPageSelection(); + this.resetColor(); + this.loadInfo(); + } } // types export enum ChatPageType { - Settings = "settings", - Messages = "messages", - Tasks = "tasks", - Calendar = "calendar", + Settings = "settings", + Messages = "messages", + Tasks = "tasks", + Calendar = "calendar", } diff --git a/src/ViewModel/Global/connectionViewModel.ts b/src/ViewModel/Global/connectionViewModel.ts index 8895b330..cc5a8cd0 100644 --- a/src/ViewModel/Global/connectionViewModel.ts +++ b/src/ViewModel/Global/connectionViewModel.ts @@ -4,89 +4,90 @@ import ConnectionModel from "../../Model/Global/connectionModel"; import CoreViewModel from "./coreViewModel"; export default class ConnectionViewModel { - connectionModel: ConnectionModel; + connectionModel: ConnectionModel; - // state - serverAddressInput: React.State = new React.State(""); - isConnected: React.State = new React.State(false); + // state + serverAddressInput: React.State = new React.State(""); + isConnected: React.State = new React.State(false); - isShowingConnectionModal: React.State = new React.State(false); + isShowingConnectionModal: React.State = new React.State(false); - previousAddresses: React.ListState = new React.ListState(); + previousAddresses: React.ListState = new React.ListState(); - // guards - cannotConnect: React.State = React.createProxyState( - [this.serverAddressInput, this.isConnected], - () => - (this.isConnected.value == true && - this.serverAddressInput.value == this.connectionModel.address) || - this.serverAddressInput.value == "" - ); - cannotDisonnect: React.State = React.createProxyState( - [this.isConnected], - () => this.isConnected.value == false - ); - hasNoPreviousConnections: React.State = React.createProxyState( - [this.previousAddresses], - () => this.previousAddresses.value.size == 0 - ); - - // handlers - connectionChangeHandler = (): void => { - this.isConnected.value = this.connectionModel.isConnected; - - if (this.connectionModel.isConnected == false) return; - if (this.connectionModel.address == undefined) return; - - this.serverAddressInput.value = this.connectionModel.address; - - if (!this.previousAddresses.value.has(this.connectionModel.address)) { - this.previousAddresses.add(this.connectionModel.address); - } - }; - - // methods - connect = (): void => { - this.connectToAddress(this.serverAddressInput.value); - }; - - connectToAddress = (address: string): void => { - this.connectionModel.connect(address); - }; - - disconnect = (): void => { - this.connectionModel.disconnect(); - }; - - removePreviousAddress = (address: string): void => { - this.connectionModel.removeAddress(address); - this.updatePreviousAddresses(); - }; - - // view - showConnectionModal = (): void => { - this.isShowingConnectionModal.value = true; - }; - - hideConnectionModal = (): void => { - this.isShowingConnectionModal.value = false; - }; - - updatePreviousAddresses = (): void => { - this.previousAddresses.clear(); - this.previousAddresses.add(...this.connectionModel.addresses); - }; - - // init - constructor( - public coreViewModel: CoreViewModel, - connectionModel: ConnectionModel - ) { - this.connectionModel = connectionModel; - this.updatePreviousAddresses(); - - connectionModel.connectionChangeHandlerManager.addHandler( - this.connectionChangeHandler + // guards + cannotConnect: React.State = React.createProxyState( + [this.serverAddressInput, this.isConnected], + () => + (this.isConnected.value == true && + this.serverAddressInput.value == + this.connectionModel.address) || + this.serverAddressInput.value == "", ); - } + cannotDisonnect: React.State = React.createProxyState( + [this.isConnected], + () => this.isConnected.value == false, + ); + hasNoPreviousConnections: React.State = React.createProxyState( + [this.previousAddresses], + () => this.previousAddresses.value.size == 0, + ); + + // handlers + connectionChangeHandler = (): void => { + this.isConnected.value = this.connectionModel.isConnected; + + if (this.connectionModel.isConnected == false) return; + if (this.connectionModel.address == undefined) return; + + this.serverAddressInput.value = this.connectionModel.address; + + if (!this.previousAddresses.value.has(this.connectionModel.address)) { + this.previousAddresses.add(this.connectionModel.address); + } + }; + + // methods + connect = (): void => { + this.connectToAddress(this.serverAddressInput.value); + }; + + connectToAddress = (address: string): void => { + this.connectionModel.connect(address); + }; + + disconnect = (): void => { + this.connectionModel.disconnect(); + }; + + removePreviousAddress = (address: string): void => { + this.connectionModel.removeAddress(address); + this.updatePreviousAddresses(); + }; + + // view + showConnectionModal = (): void => { + this.isShowingConnectionModal.value = true; + }; + + hideConnectionModal = (): void => { + this.isShowingConnectionModal.value = false; + }; + + updatePreviousAddresses = (): void => { + this.previousAddresses.clear(); + this.previousAddresses.add(...this.connectionModel.addresses); + }; + + // init + constructor( + public coreViewModel: CoreViewModel, + connectionModel: ConnectionModel, + ) { + this.connectionModel = connectionModel; + this.updatePreviousAddresses(); + + connectionModel.connectionChangeHandlerManager.addHandler( + this.connectionChangeHandler, + ); + } } diff --git a/src/ViewModel/Global/coreViewModel.ts b/src/ViewModel/Global/coreViewModel.ts index 414b20d7..df160207 100644 --- a/src/ViewModel/Global/coreViewModel.ts +++ b/src/ViewModel/Global/coreViewModel.ts @@ -1,13 +1,13 @@ import * as React from "bloatless-react"; export default class CoreViewModel { - // DRAG & DROP - draggedObject: React.State = new React.State(undefined); + // DRAG & DROP + draggedObject: React.State = new React.State(undefined); - // SUGGESTIONS - // boards & tasks - boardSearchSuggestions: React.ListState = new React.ListState(); - - taskCategorySuggestions: React.ListState = new React.ListState(); - taskStatusSuggestions: React.ListState = new React.ListState(); + // SUGGESTIONS + // boards & tasks + boardSearchSuggestions: React.ListState = new React.ListState(); + + taskCategorySuggestions: React.ListState = new React.ListState(); + taskStatusSuggestions: React.ListState = new React.ListState(); } diff --git a/src/ViewModel/Global/fileTransferViewModel.ts b/src/ViewModel/Global/fileTransferViewModel.ts index 14cec045..aaa34d91 100644 --- a/src/ViewModel/Global/fileTransferViewModel.ts +++ b/src/ViewModel/Global/fileTransferViewModel.ts @@ -1,11 +1,11 @@ import * as React from "bloatless-react"; import FileTransferModel, { - TransferData, + TransferData, } from "../../Model/Global/fileTransferModel"; import StorageModel, { - StorageModelSubPath, - filePaths, + StorageModelSubPath, + filePaths, } from "../../Model/Global/storageModel"; import ChatListModel from "../../Model/Chat/chatListModel"; @@ -13,180 +13,186 @@ import ChatModel from "../../Model/Chat/chatModel"; import { translations } from "../../View/translations"; export default class FileTransferViewModel { - fileTransferModel: FileTransferModel; - chatListModel: ChatListModel; + fileTransferModel: FileTransferModel; + chatListModel: ChatListModel; - // state - presentedModal: React.State = - new React.State(undefined); + // state + presentedModal: React.State = + new React.State(undefined); - generalFileOptions: React.ListState = - new React.ListState(); - chatFileOptions: React.ListState = new React.ListState(); - selectedPaths: React.ListState = new React.ListState(); + generalFileOptions: React.ListState = + new React.ListState(); + chatFileOptions: React.ListState = + new React.ListState(); + selectedPaths: React.ListState = new React.ListState(); - transferChannel: React.State = new React.State(""); - transferKey: React.State = new React.State(""); + transferChannel: React.State = new React.State(""); + transferKey: React.State = new React.State(""); - receivingTransferChannel: React.State = new React.State(""); - receivingTransferKey: React.State = new React.State(""); + receivingTransferChannel: React.State = new React.State(""); + receivingTransferKey: React.State = new React.State(""); - filePathsSent: React.ListState = new React.ListState(); - filesSentCount: React.State = React.createProxyState( - [this.filePathsSent], - () => this.filePathsSent.value.size - ); - filesSentText: React.State = React.createProxyState( - [this.filesSentCount], - () => - translations.dataTransferModal.filesSentCount(this.filesSentCount.value) - ); - - filePathsReceived: React.ListState = new React.ListState(); - filesReceivedCount: React.State = React.createProxyState( - [this.filePathsReceived], - () => this.filePathsReceived.value.size - ); - filesReceivedText: React.State = React.createProxyState( - [this.filesReceivedCount], - () => - translations.dataTransferModal.filesReceivedCount( - this.filesReceivedCount.value - ) - ); - - // guards - hasNoPathsSelected: React.State = React.createProxyState( - [this.selectedPaths], - () => this.selectedPaths.value.size == 0 - ); - didNotFinishSending: React.State = new React.State(true); - cannotPrepareToReceive: React.State = React.createProxyState( - [this.receivingTransferChannel, this.receivingTransferKey], - () => - this.receivingTransferChannel.value == "" || - this.receivingTransferKey.value == "" - ); - - // handlers - handleReceivedFile = (path: string): void => { - this.filePathsReceived.add(path); - }; - - // methods - getOptions = (): void => { - this.generalFileOptions.clear(); - this.chatFileOptions.clear(); - this.selectedPaths.clear(); - - this.generalFileOptions.add( - { - label: translations.dataTransferModal.connectionData, - path: StorageModel.getPath( - StorageModelSubPath.ConnectionModel, - filePaths.connectionModel.previousAddresses - ), - }, - { - label: translations.dataTransferModal.settingsData, - path: StorageModel.getPath(StorageModelSubPath.SettingsModel, []), - } + filePathsSent: React.ListState = new React.ListState(); + filesSentCount: React.State = React.createProxyState( + [this.filePathsSent], + () => this.filePathsSent.value.size, + ); + filesSentText: React.State = React.createProxyState( + [this.filesSentCount], + () => + translations.dataTransferModal.filesSentCount( + this.filesSentCount.value, + ), ); - const chatModels: Set = this.chatListModel.chatModels; - for (const chatModel of chatModels) { - this.chatFileOptions.add({ - label: chatModel.info.primaryChannel, - path: chatModel.getBasePath(), - }); - } - }; - - getTransferData = (): void => { - const transferData: TransferData = - this.fileTransferModel.generateTransferData(); - this.transferChannel.value = transferData.channel; - this.transferKey.value = transferData.key; - }; - - // view - showDirectionSelectionModal = (): void => { - this.presentedModal.value = FileTransferModal.DirectionSelection; - this.getOptions(); - }; - - showFileSelectionModal = (): void => { - this.presentedModal.value = FileTransferModal.FileSelection; - }; - - showTransferDataModal = (): void => { - this.presentedModal.value = FileTransferModal.TransferDataDisplay; - this.getTransferData(); - }; - - initiateTransfer = (): void => { - this.presentedModal.value = FileTransferModal.TransferDisplay; - this.didNotFinishSending.value = true; - this.filePathsSent.clear(); - - this.fileTransferModel.sendFiles( - this.selectedPaths.value.values(), - (path: string) => { - console.log(path); - this.filePathsSent.add(path); - } + filePathsReceived: React.ListState = new React.ListState(); + filesReceivedCount: React.State = React.createProxyState( + [this.filePathsReceived], + () => this.filePathsReceived.value.size, + ); + filesReceivedText: React.State = React.createProxyState( + [this.filesReceivedCount], + () => + translations.dataTransferModal.filesReceivedCount( + this.filesReceivedCount.value, + ), ); - this.didNotFinishSending.value = false; - }; + // guards + hasNoPathsSelected: React.State = React.createProxyState( + [this.selectedPaths], + () => this.selectedPaths.value.size == 0, + ); + didNotFinishSending: React.State = new React.State(true); + cannotPrepareToReceive: React.State = React.createProxyState( + [this.receivingTransferChannel, this.receivingTransferKey], + () => + this.receivingTransferChannel.value == "" || + this.receivingTransferKey.value == "", + ); - showTransferDataInputModal = (): void => { - this.presentedModal.value = FileTransferModal.TransferDataInput; - }; - - prepareReceivingData = (): void => { - this.presentedModal.value = FileTransferModal.ReceptionDisplay; - this.filePathsReceived.clear(); - - const transferData: TransferData = { - channel: this.receivingTransferChannel.value, - key: this.receivingTransferKey.value, + // handlers + handleReceivedFile = (path: string): void => { + this.filePathsReceived.add(path); }; - this.fileTransferModel.prepareToReceive(transferData); - }; - hideModal = (): void => { - this.presentedModal.value = undefined; - }; + // methods + getOptions = (): void => { + this.generalFileOptions.clear(); + this.chatFileOptions.clear(); + this.selectedPaths.clear(); - // init - constructor( - fileTransferModel: FileTransferModel, - chatListModel: ChatListModel - ) { - this.fileTransferModel = fileTransferModel; - this.chatListModel = chatListModel; + this.generalFileOptions.add( + { + label: translations.dataTransferModal.connectionData, + path: StorageModel.getPath( + StorageModelSubPath.ConnectionModel, + filePaths.connectionModel.previousAddresses, + ), + }, + { + label: translations.dataTransferModal.settingsData, + path: StorageModel.getPath( + StorageModelSubPath.SettingsModel, + [], + ), + }, + ); - this.fileTransferModel.fileHandlerManager.addHandler( - this.handleReceivedFile - ); - } + const chatModels: Set = this.chatListModel.chatModels; + for (const chatModel of chatModels) { + this.chatFileOptions.add({ + label: chatModel.info.primaryChannel, + path: chatModel.getBasePath(), + }); + } + }; + + getTransferData = (): void => { + const transferData: TransferData = + this.fileTransferModel.generateTransferData(); + this.transferChannel.value = transferData.channel; + this.transferKey.value = transferData.key; + }; + + // view + showDirectionSelectionModal = (): void => { + this.presentedModal.value = FileTransferModal.DirectionSelection; + this.getOptions(); + }; + + showFileSelectionModal = (): void => { + this.presentedModal.value = FileTransferModal.FileSelection; + }; + + showTransferDataModal = (): void => { + this.presentedModal.value = FileTransferModal.TransferDataDisplay; + this.getTransferData(); + }; + + initiateTransfer = (): void => { + this.presentedModal.value = FileTransferModal.TransferDisplay; + this.didNotFinishSending.value = true; + this.filePathsSent.clear(); + + this.fileTransferModel.sendFiles( + this.selectedPaths.value.values(), + (path: string) => { + console.log(path); + this.filePathsSent.add(path); + }, + ); + + this.didNotFinishSending.value = false; + }; + + showTransferDataInputModal = (): void => { + this.presentedModal.value = FileTransferModal.TransferDataInput; + }; + + prepareReceivingData = (): void => { + this.presentedModal.value = FileTransferModal.ReceptionDisplay; + this.filePathsReceived.clear(); + + const transferData: TransferData = { + channel: this.receivingTransferChannel.value, + key: this.receivingTransferKey.value, + }; + this.fileTransferModel.prepareToReceive(transferData); + }; + + hideModal = (): void => { + this.presentedModal.value = undefined; + }; + + // init + constructor( + fileTransferModel: FileTransferModel, + chatListModel: ChatListModel, + ) { + this.fileTransferModel = fileTransferModel; + this.chatListModel = chatListModel; + + this.fileTransferModel.fileHandlerManager.addHandler( + this.handleReceivedFile, + ); + } } export interface FileTransferOption { - label: string; - path: string[]; + label: string; + path: string[]; } export enum FileTransferModal { - DirectionSelection, + DirectionSelection, - // sending - FileSelection, - TransferDataDisplay, - TransferDisplay, + // sending + FileSelection, + TransferDataDisplay, + TransferDisplay, - // receiving - TransferDataInput, - ReceptionDisplay, + // receiving + TransferDataInput, + ReceptionDisplay, } diff --git a/src/ViewModel/Global/settingsViewModel.ts b/src/ViewModel/Global/settingsViewModel.ts index 86f8ae81..4fbe94c7 100644 --- a/src/ViewModel/Global/settingsViewModel.ts +++ b/src/ViewModel/Global/settingsViewModel.ts @@ -4,44 +4,44 @@ import CoreViewModel from "./coreViewModel"; import SettingsModel from "../../Model/Global/settingsModel"; export default class SettingsViewModel { - settingsModel: SettingsModel; + settingsModel: SettingsModel; - // state - username: React.State = new React.State(""); - usernameInput: React.State = new React.State(""); - firstDayOfWeekInput: React.State = new React.State("0"); + // state + username: React.State = new React.State(""); + usernameInput: React.State = new React.State(""); + firstDayOfWeekInput: React.State = new React.State("0"); - // guards - cannotSetName: React.State = React.createProxyState( - [this.usernameInput], - () => - this.usernameInput.value == "" || - this.usernameInput.value == this.settingsModel.username - ); + // guards + cannotSetName: React.State = React.createProxyState( + [this.usernameInput], + () => + this.usernameInput.value == "" || + this.usernameInput.value == this.settingsModel.username, + ); - // methods - setName = (): void => { - this.settingsModel.setName(this.usernameInput.value); - this.username.value = this.settingsModel.username; - this.usernameInput.callSubscriptions(); - }; + // methods + setName = (): void => { + this.settingsModel.setName(this.usernameInput.value); + this.username.value = this.settingsModel.username; + this.usernameInput.callSubscriptions(); + }; - setFirstDayofWeek = (): void => { - this.settingsModel.setFirstDayOfWeek(this.firstDayOfWeekInput.value); - }; + setFirstDayofWeek = (): void => { + this.settingsModel.setFirstDayOfWeek(this.firstDayOfWeekInput.value); + }; - // init - constructor( - public coreViewModel: CoreViewModel, - settingsModel: SettingsModel - ) { - this.settingsModel = settingsModel; + // init + constructor( + public coreViewModel: CoreViewModel, + settingsModel: SettingsModel, + ) { + this.settingsModel = settingsModel; - this.username.value = settingsModel.username; - this.usernameInput.value = settingsModel.username; - this.firstDayOfWeekInput.value = settingsModel.firstDayOfWeek; + this.username.value = settingsModel.username; + this.usernameInput.value = settingsModel.username; + this.firstDayOfWeekInput.value = settingsModel.firstDayOfWeek; - // subscriptions - this.firstDayOfWeekInput.subscribe(this.setFirstDayofWeek); - } + // subscriptions + this.firstDayOfWeekInput.subscribe(this.setFirstDayofWeek); + } } diff --git a/src/ViewModel/Global/storageViewModel.ts b/src/ViewModel/Global/storageViewModel.ts index e4ffca36..fd9ffc84 100644 --- a/src/ViewModel/Global/storageViewModel.ts +++ b/src/ViewModel/Global/storageViewModel.ts @@ -1,70 +1,81 @@ import * as React from "bloatless-react"; import StorageModel, { - PATH_COMPONENT_SEPARATOR, + PATH_COMPONENT_SEPARATOR, } from "../../Model/Global/storageModel"; import CoreViewModel from "./coreViewModel"; import { translations } from "../../View/translations"; export default class StorageViewModel { - storageModel: StorageModel; + storageModel: StorageModel; - // state - isShowingStorageModal: React.State = new React.State(false); - selectedPath: React.State = new React.State(PATH_COMPONENT_SEPARATOR); - didMakeChanges: React.State = new React.State(false); - - selectedFileName: React.State; - selectedFileContent: React.State; - - lastDeletedItemPath: React.State = new React.State(""); - - // methods - getSelectedItemContent = (): string => { - const path = StorageModel.stringToPathComponents(this.selectedPath.value); - const content = this.storageModel.read(path); - return ( - (content ?? translations.storage.notAFile) || - translations.storage.contentEmpty + // state + isShowingStorageModal: React.State = new React.State(false); + selectedPath: React.State = new React.State( + PATH_COMPONENT_SEPARATOR, ); - }; + didMakeChanges: React.State = new React.State(false); - deleteSelectedItem = (): void => { - const path = StorageModel.stringToPathComponents(this.selectedPath.value); - this.lastDeletedItemPath.value = this.selectedPath.value; + selectedFileName: React.State; + selectedFileContent: React.State; - this.storageModel.removeRecursively(path); - this.didMakeChanges.value = true; - }; + lastDeletedItemPath: React.State = new React.State(""); - removeJunk = (): void => { - this.storageModel.removeJunk(); - this.selectedPath.value = PATH_COMPONENT_SEPARATOR; - }; + // methods + getSelectedItemContent = (): string => { + const path = StorageModel.stringToPathComponents( + this.selectedPath.value, + ); + const content = this.storageModel.read(path); + return ( + (content ?? translations.storage.notAFile) || + translations.storage.contentEmpty + ); + }; - // view - showStorageModal = (): void => { - this.isShowingStorageModal.value = true; - }; + deleteSelectedItem = (): void => { + const path = StorageModel.stringToPathComponents( + this.selectedPath.value, + ); + this.lastDeletedItemPath.value = this.selectedPath.value; - hideStorageModal = (): void => { - if (this.didMakeChanges.value == true) { - window.location.reload(); - return; + this.storageModel.removeRecursively(path); + this.didMakeChanges.value = true; + }; + + removeJunk = (): void => { + this.storageModel.removeJunk(); + this.selectedPath.value = PATH_COMPONENT_SEPARATOR; + }; + + // view + showStorageModal = (): void => { + this.isShowingStorageModal.value = true; + }; + + hideStorageModal = (): void => { + if (this.didMakeChanges.value == true) { + window.location.reload(); + return; + } + this.isShowingStorageModal.value = false; + }; + + // init + constructor( + public coreViewModel: CoreViewModel, + storageModel: StorageModel, + ) { + this.storageModel = storageModel; + + this.selectedFileName = React.createProxyState( + [this.selectedPath], + () => StorageModel.getFileNameFromString(this.selectedPath.value), + ); + this.selectedFileContent = React.createProxyState( + [this.selectedPath], + () => this.getSelectedItemContent(), + ); } - this.isShowingStorageModal.value = false; - }; - - // init - constructor(public coreViewModel: CoreViewModel, storageModel: StorageModel) { - this.storageModel = storageModel; - - this.selectedFileName = React.createProxyState([this.selectedPath], () => - StorageModel.getFileNameFromString(this.selectedPath.value) - ); - this.selectedFileContent = React.createProxyState([this.selectedPath], () => - this.getSelectedItemContent() - ); - } } diff --git a/src/ViewModel/Pages/boardViewModel.ts b/src/ViewModel/Pages/boardViewModel.ts index bf41efe3..0a26294f 100644 --- a/src/ViewModel/Pages/boardViewModel.ts +++ b/src/ViewModel/Pages/boardViewModel.ts @@ -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 = new React.State(""); - color: React.State = new React.State(Color.Standard); + // state + name: React.State = new React.State(""); + color: React.State = new React.State(Color.Standard); - index: React.State = new React.State(0); + index: React.State = new React.State(0); - selectedPage: React.State = new React.State( - BoardPageType.List - ); - - isSelected: React.State; - isPresentingSettingsModal: React.State = new React.State(false); - isPresentingFilterModal: React.State = new React.State(false); - - searchViewModel: SearchViewModel; - filteredTaskViewModels: React.ListState = - 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 = new React.State( + BoardPageType.List, ); - this.taskViewModels.set(taskFileContent.fileId, taskViewModel); - }; - removeTaskFromView = (taskFileContent: TaskFileContent): void => { - this.taskViewModels.remove(taskFileContent.fileId); - this.updateIndex(); - }; + isSelected: React.State; + isPresentingSettingsModal: React.State = new React.State(false); + isPresentingFilterModal: React.State = new React.State(false); - select = (): void => { - this.taskPageViewModel.selectBoard(this); - }; + searchViewModel: SearchViewModel; + filteredTaskViewModels: React.ListState = + 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", } diff --git a/src/ViewModel/Pages/calendarPageViewModel.ts b/src/ViewModel/Pages/calendarPageViewModel.ts index 69d7c3b5..455a7a5d 100644 --- a/src/ViewModel/Pages/calendarPageViewModel.ts +++ b/src/ViewModel/Pages/calendarPageViewModel.ts @@ -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 = new React.State(0); - selectedMonth: React.State = new React.State(0); - selectedDate: React.State = new React.State(0); - - monthGrid: React.State> | undefined> = - new React.State(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 | 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 | null => { - if (this.monthGrid.value == null) return null; + // state + selectedYear: React.State = new React.State(0); + selectedMonth: React.State = new React.State(0); + selectedDate: React.State = new React.State(0); - const date: string = CalendarModel.isoToDateString( - taskFileContent.date ?? "" - ); - return this.monthGrid.value.days[date]; - }; + monthGrid: React.State< + MonthGrid> | undefined + > = new React.State(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 | 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 | 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 | 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 | 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", } diff --git a/src/ViewModel/Pages/messagePageViewModel.ts b/src/ViewModel/Pages/messagePageViewModel.ts index 77c47892..9b024a1b 100644 --- a/src/ViewModel/Pages/messagePageViewModel.ts +++ b/src/ViewModel/Pages/messagePageViewModel.ts @@ -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 = - new React.MapState(); - composingMessage: React.State = new React.State(""); + // state + chatMessageViewModels: React.MapState = + new React.MapState(); + composingMessage: React.State = new React.State(""); - // guards - cannotSendMessage: React.State; + // guards + cannotSendMessage: React.State; - // 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 => { - const chatMessage: ChatMessage = messageViewModel.chatMessage; - await this.chatViewModel.chatModel.decryptMessage(chatMessage); - this.chatViewModel.chatModel.addMessage(chatMessage); - messageViewModel.loadData(); - }; + decryptMessage = async ( + messageViewModel: ChatMessageViewModel, + ): Promise => { + 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 == "" - ); - } } diff --git a/src/ViewModel/Pages/settingsPageViewModel.ts b/src/ViewModel/Pages/settingsPageViewModel.ts index 1392255a..c49afb98 100644 --- a/src/ViewModel/Pages/settingsPageViewModel.ts +++ b/src/ViewModel/Pages/settingsPageViewModel.ts @@ -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 = new React.State(""); - primaryChannelInput: React.State = new React.State(""); + // state + primaryChannel: React.State = new React.State(""); + primaryChannelInput: React.State = new React.State(""); - secondaryChannels: React.ListState = new React.ListState(); - newSecondaryChannelInput: React.State = new React.State(""); + secondaryChannels: React.ListState = new React.ListState(); + newSecondaryChannelInput: React.State = new React.State(""); - encryptionKeyInput: React.State = new React.State(""); - shouldShowEncryptionKey: React.State = new React.State(false); - encryptionKeyInputType: React.State<"text" | "password"> = - React.createProxyState([this.shouldShowEncryptionKey], () => - this.shouldShowEncryptionKey.value == true ? "text" : "password" + encryptionKeyInput: React.State = new React.State(""); + shouldShowEncryptionKey: React.State = new React.State(false); + encryptionKeyInputType: React.State<"text" | "password"> = + React.createProxyState([this.shouldShowEncryptionKey], () => + this.shouldShowEncryptionKey.value == true ? "text" : "password", + ); + + color: React.State = new React.State(Color.Standard); + + // guards + cannotSetPrimaryChannel: React.State = React.createProxyState( + [this.primaryChannel, this.primaryChannelInput], + () => + this.primaryChannelInput.value == "" || + this.primaryChannelInput.value == this.primaryChannel.value, ); - - color: React.State = new React.State(Color.Standard); - - // guards - cannotSetPrimaryChannel: React.State = React.createProxyState( - [this.primaryChannel, this.primaryChannelInput], - () => - this.primaryChannelInput.value == "" || - this.primaryChannelInput.value == this.primaryChannel.value - ); - cannotAddSecondaryChannel: React.State = React.createProxyState( - [this.newSecondaryChannelInput], - () => this.newSecondaryChannelInput.value == "" - ); - cannotSetEncryptionKey: React.State; - - // methods - setPrimaryChannel = (): void => { - this.chatViewModel.chatModel.setPrimaryChannel( - this.primaryChannelInput.value + cannotAddSecondaryChannel: React.State = React.createProxyState( + [this.newSecondaryChannelInput], + () => this.newSecondaryChannelInput.value == "", ); - this.primaryChannel.value = - this.chatViewModel.chatModel.info.primaryChannel; + cannotSetEncryptionKey: React.State; - 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); - }); - } } diff --git a/src/ViewModel/Pages/taskContainingPageViewModel.ts b/src/ViewModel/Pages/taskContainingPageViewModel.ts index 971a4abd..65e0708f 100644 --- a/src/ViewModel/Pages/taskContainingPageViewModel.ts +++ b/src/ViewModel/Pages/taskContainingPageViewModel.ts @@ -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 = new IndexManager( - (taskViewModel: TaskViewModel) => taskViewModel.sortingString - ); - - selectedTaskViewModel: React.State = - new React.State(undefined); - - taskViewModels: React.MapState = 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 = new IndexManager( + (taskViewModel: TaskViewModel) => taskViewModel.sortingString, ); - this.selectTask(taskViewModel); - this.updateTaskIndices(); - }; - // view - showTask = (taskFileContent: TaskFileContent): void => {}; - removeTaskFromView = (taskFileContent: TaskFileContent): void => {}; + selectedTaskViewModel: React.State = + new React.State(undefined); - selectTask = (selectedTask: TaskViewModel): void => { - this.selectedTaskViewModel.value = selectedTask; - }; + taskViewModels: React.MapState = 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; - } } diff --git a/src/ViewModel/Pages/taskPageViewModel.ts b/src/ViewModel/Pages/taskPageViewModel.ts index 19494687..2a07908a 100644 --- a/src/ViewModel/Pages/taskPageViewModel.ts +++ b/src/ViewModel/Pages/taskPageViewModel.ts @@ -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 = 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 = new React.State(""); - - boardViewModels: React.MapState = new React.MapState(); - - isShowingBoadList: React.State = new React.State(true); - - selectedBoardId: React.State = new React.State( - undefined - ); - - // guards - cannotCreateBoard: React.State = 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 = 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 = new React.State(""); - this.storeLastUsedBoard(); - }; + boardViewModels: React.MapState = 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 = 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 = new React.State( + undefined, + ); - openLastUsedBoard = (): void => { - const path: string[] = this.getLastUsedBoardPath(); - const lastUsedBoardId: string | null = this.storageModel.read(path); - if (lastUsedBoardId == null) return; + // guards + cannotCreateBoard: React.State = 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", } diff --git a/src/ViewModel/Pages/taskViewModel.ts b/src/ViewModel/Pages/taskViewModel.ts index 0bfc735c..e6d47d3e 100644 --- a/src/ViewModel/Pages/taskViewModel.ts +++ b/src/ViewModel/Pages/taskViewModel.ts @@ -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 = new React.State(0); - - boardId: React.State = new React.State(""); - - name: React.State = new React.State(""); - description: React.State = new React.State(""); - - category: React.State = new React.State(""); - status: React.State = new React.State(""); - priority: React.State = new React.State(""); - - date: React.State = new React.State(""); - time: React.State = new React.State(""); - - selectedVersionId: React.State = new React.State(""); - versionIds: React.ListState = 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 = 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 = new React.State(""); - this.boardsAndTasksModel.updateTaskAndSend(newTaskFileContent); + name: React.State = new React.State(""); + description: React.State = new React.State(""); - this.containingModel.showTask(newTaskFileContent); - this.containingModel.updateTaskIndices(); + category: React.State = new React.State(""); + status: React.State = new React.State(""); + priority: React.State = new React.State(""); - this.updateSuggestions(); - }; + date: React.State = new React.State(""); + time: React.State = new React.State(""); - deleteTask = (): void => { - this.close(); - this.boardsAndTasksModel.deleteTask(this.task.boardId, this.task.fileId); - this.containingModel.removeTaskFromView(this.task); - }; + selectedVersionId: React.State = new React.State(""); + versionIds: React.ListState = 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 ?? "", + ]; + }; } diff --git a/src/ViewModel/Utility/searchViewModel.ts b/src/ViewModel/Utility/searchViewModel.ts index d9085a2c..ec3bc920 100644 --- a/src/ViewModel/Utility/searchViewModel.ts +++ b/src/ViewModel/Utility/searchViewModel.ts @@ -3,74 +3,74 @@ import * as React from "bloatless-react"; import { checkDoesObjectMatchSearch } from "../../Model/Utility/utility"; export default class SearchViewModel { - // data - allObjects: React.ListState | React.MapState; - getStringsOfObject: (object: T) => string[]; + // data + allObjects: React.ListState | React.MapState; + getStringsOfObject: (object: T) => string[]; - // state - appliedQuery: React.State = new React.State(""); - searchInput: React.State = new React.State(""); - matchingObjects: React.ListState; + // state + appliedQuery: React.State = new React.State(""); + searchInput: React.State = new React.State(""); + matchingObjects: React.ListState; - suggestions: React.ListState; + suggestions: React.ListState; - // guards - cannotApplySearch: React.State = React.createProxyState( - [this.searchInput, this.appliedQuery], - () => this.searchInput.value == this.appliedQuery.value - ); - - // methods - search = (searchTerm: string): void => { - this.searchInput.value = searchTerm; - this.applySearch(); - }; - - applySearch = (): void => { - this.appliedQuery.value = this.searchInput.value; - - this.matchingObjects.clear(); - for (const object of this.allObjects.value.values()) { - const doesMatch: boolean = this.checkDoesMatchSearch(object); - if (doesMatch == false) continue; - - this.matchingObjects.add(object); - } - }; - - // init - constructor( - allObjects: React.ListState | React.MapState, - matchingObjects: React.ListState, - getStringsOfObject: (object: T) => string[], - suggestions: React.ListState - ) { - this.allObjects = allObjects; - this.matchingObjects = matchingObjects; - this.getStringsOfObject = getStringsOfObject; - this.suggestions = suggestions; - - // handle new objects - this.allObjects.handleAddition((newObject: T) => { - const doesMatch: boolean = this.checkDoesMatchSearch(newObject); - if (doesMatch == false) { - this.matchingObjects.remove(newObject); - } else { - if (this.matchingObjects.value.has(newObject)) return; - this.matchingObjects.add(newObject); - this.allObjects.handleRemoval(newObject, () => { - this.matchingObjects.remove(newObject); - }); - } - }); - } - - // utility - checkDoesMatchSearch = (object: T): boolean => { - return checkDoesObjectMatchSearch( - this.appliedQuery.value, - this.getStringsOfObject, - object + // guards + cannotApplySearch: React.State = React.createProxyState( + [this.searchInput, this.appliedQuery], + () => this.searchInput.value == this.appliedQuery.value, ); - }; + + // methods + search = (searchTerm: string): void => { + this.searchInput.value = searchTerm; + this.applySearch(); + }; + + applySearch = (): void => { + this.appliedQuery.value = this.searchInput.value; + + this.matchingObjects.clear(); + for (const object of this.allObjects.value.values()) { + const doesMatch: boolean = this.checkDoesMatchSearch(object); + if (doesMatch == false) continue; + + this.matchingObjects.add(object); + } + }; + + // init + constructor( + allObjects: React.ListState | React.MapState, + matchingObjects: React.ListState, + getStringsOfObject: (object: T) => string[], + suggestions: React.ListState, + ) { + this.allObjects = allObjects; + this.matchingObjects = matchingObjects; + this.getStringsOfObject = getStringsOfObject; + this.suggestions = suggestions; + + // handle new objects + this.allObjects.handleAddition((newObject: T) => { + const doesMatch: boolean = this.checkDoesMatchSearch(newObject); + if (doesMatch == false) { + this.matchingObjects.remove(newObject); + } else { + if (this.matchingObjects.value.has(newObject)) return; + this.matchingObjects.add(newObject); + this.allObjects.handleRemoval(newObject, () => { + this.matchingObjects.remove(newObject); + }); + } + }); + } + + // utility + checkDoesMatchSearch = (object: T): boolean => { + return checkDoesObjectMatchSearch( + this.appliedQuery.value, + this.getStringsOfObject, + object, + ); + }; } diff --git a/src/ViewModel/Utility/taskPropertyBulkChangeViewModel.ts b/src/ViewModel/Utility/taskPropertyBulkChangeViewModel.ts index 5e84fb88..3729159c 100644 --- a/src/ViewModel/Utility/taskPropertyBulkChangeViewModel.ts +++ b/src/ViewModel/Utility/taskPropertyBulkChangeViewModel.ts @@ -3,70 +3,72 @@ import * as React from "bloatless-react"; import TaskViewModel from "../Pages/taskViewModel"; export default class TaskPropertyBulkChangeViewModel { - taskViewModels: React.ListState; + taskViewModels: React.ListState; - // state - inputValue: React.State = new React.State(""); + // state + inputValue: React.State = new React.State(""); - // guards - cannotSet: React.State; + // guards + cannotSet: React.State; - // methods - set = (): void => { - if (this.cannotSet.value == true) return; - this.taskViewModels.value.forEach((taskViewModel: TaskViewModel) => { - this.setValue(this.inputValue.value, taskViewModel); - }); - }; + // methods + set = (): void => { + if (this.cannotSet.value == true) return; + this.taskViewModels.value.forEach((taskViewModel: TaskViewModel) => { + this.setValue(this.inputValue.value, taskViewModel); + }); + }; - setValue: (newValue: string, taskViewModel: TaskViewModel) => void; + setValue: (newValue: string, taskViewModel: TaskViewModel) => void; - // init - constructor( - taskViewModels: React.ListState, - valueSetter: (newValue: string, taskViewModel: TaskViewModel) => void, - initialValue: string - ) { - this.taskViewModels = taskViewModels; - this.inputValue.value = initialValue; + // init + constructor( + taskViewModels: React.ListState, + valueSetter: (newValue: string, taskViewModel: TaskViewModel) => void, + initialValue: string, + ) { + this.taskViewModels = taskViewModels; + this.inputValue.value = initialValue; - this.setValue = valueSetter; + this.setValue = valueSetter; - this.cannotSet = React.createProxyState( - [this.inputValue], - () => this.inputValue.value == "" || this.inputValue.value == initialValue - ); - } + this.cannotSet = React.createProxyState( + [this.inputValue], + () => + this.inputValue.value == "" || + this.inputValue.value == initialValue, + ); + } } export class TaskCategoryBulkChangeViewModel extends TaskPropertyBulkChangeViewModel { - constructor( - taskViewModels: React.ListState, - initialValue: string - ) { - super( - taskViewModels, - (newCategory: string, taskViewModel: TaskViewModel) => { - taskViewModel.category.value = newCategory; - taskViewModel.save(); - }, - initialValue - ); - } + constructor( + taskViewModels: React.ListState, + initialValue: string, + ) { + super( + taskViewModels, + (newCategory: string, taskViewModel: TaskViewModel) => { + taskViewModel.category.value = newCategory; + taskViewModel.save(); + }, + initialValue, + ); + } } export class TaskStatusBulkChangeViewModel extends TaskPropertyBulkChangeViewModel { - constructor( - taskViewModels: React.ListState, - initialValue: string - ) { - super( - taskViewModels, - (newStatus: string, taskViewModel: TaskViewModel) => { - taskViewModel.status.value = newStatus; - taskViewModel.save(); - }, - initialValue - ); - } + constructor( + taskViewModels: React.ListState, + initialValue: string, + ) { + super( + taskViewModels, + (newStatus: string, taskViewModel: TaskViewModel) => { + taskViewModel.status.value = newStatus; + taskViewModel.save(); + }, + initialValue, + ); + } } diff --git a/src/base.css b/src/base.css index f338105e..1efaa3e1 100644 --- a/src/base.css +++ b/src/base.css @@ -3,96 +3,96 @@ #home-page #chat-section, #chat-page, #chat-page #main > div { - animation: slide-up var(--animation-duration) ease; + animation: slide-up var(--animation-duration) ease; } /* Gradient */ #background-wrapper { - position: fixed; + position: fixed; - top: 0px; - left: var(--safe-area-left); - right: var(--safe-area-right); - bottom: 0px; + top: 0px; + left: var(--safe-area-left); + right: var(--safe-area-right); + bottom: 0px; - z-index: 1; - pointer-events: none; + z-index: 1; + pointer-events: none; - overflow: hidden; + overflow: hidden; - opacity: 0.3; + opacity: 0.3; } main { - z-index: 2; + z-index: 2; } #background-wrapper div { - content: ""; + content: ""; - position: absolute; - top: -80px; - left: -80px; - right: -80px; - bottom: -80px; + position: absolute; + top: -80px; + left: -80px; + right: -80px; + bottom: -80px; - z-index: -1; - pointer-events: none; + z-index: -1; + pointer-events: none; - filter: blur(40px); + filter: blur(40px); } #background-wrapper #sky { - left: -100%; - bottom: -50%; + left: -100%; + bottom: -50%; - --color-1: hsl(188, 100%, 83%); - --color-2: hsl(194, 72%, 56%); - background-image: radial-gradient(var(--color-1), var(--color-2)); + --color-1: hsl(188, 100%, 83%); + --color-2: hsl(194, 72%, 56%); + background-image: radial-gradient(var(--color-1), var(--color-2)); } #background-wrapper #grass-1 { - --color-1: hsl(150, 100%, 30%); - --color-2: hsl(150, 100%, 25%); + --color-1: hsl(150, 100%, 30%); + --color-2: hsl(150, 100%, 25%); - background-image: linear-gradient( - 350deg, - var(--color-1), - var(--color-2) 30%, - transparent 40% - ); + background-image: linear-gradient( + 350deg, + var(--color-1), + var(--color-2) 30%, + transparent 40% + ); } #background-wrapper #grass-2 { - --color-1: hsl(110, 100%, 40%); - --color-2: hsl(110, 100%, 35%); + --color-1: hsl(110, 100%, 40%); + --color-2: hsl(110, 100%, 35%); - background-image: linear-gradient( - 9deg, - var(--color-1), - var(--color-2) 30%, - transparent 35% - ); + background-image: linear-gradient( + 9deg, + var(--color-1), + var(--color-2) 30%, + transparent 35% + ); } @media (prefers-color-scheme: dark) { - #background-wrapper { - opacity: 0.2; - } + #background-wrapper { + opacity: 0.2; + } - #background-wrapper #sky { - --color-1: hsl(188, 100%, 30%); - --color-2: hsl(194, 72%, 24%); - } + #background-wrapper #sky { + --color-1: hsl(188, 100%, 30%); + --color-2: hsl(194, 72%, 24%); + } - #background-wrapper #grass-1 { - --color-1: hsl(120, 30%, 30%); - --color-2: hsl(120, 30%, 24%); - } + #background-wrapper #grass-1 { + --color-1: hsl(120, 30%, 30%); + --color-2: hsl(120, 30%, 24%); + } - #background-wrapper #grass-2 { - --color-1: hsl(110, 60%, 30%); - --color-2: hsl(110, 50%, 24%); - } + #background-wrapper #grass-2 { + --color-1: hsl(110, 60%, 30%); + --color-2: hsl(110, 50%, 24%); + } } diff --git a/src/coloredTile.css b/src/coloredTile.css index 2d64aa70..07c18066 100644 --- a/src/coloredTile.css +++ b/src/coloredTile.css @@ -23,4 +23,4 @@ opacity: 0.1; white-space: nowrap; -} \ No newline at end of file +} diff --git a/src/colors.css b/src/colors.css index b82bf5da..1b22cfb6 100644 --- a/src/colors.css +++ b/src/colors.css @@ -1,202 +1,202 @@ /* basic */ [color]:not([color="standard"]) { - --primary: hsl(var(--hue), 100%, var(--accent-luma)); - --primary-hover: hsl(var(--hue), 95%, var(--accent-luma)); - --primary-active: hsl(var(--hue), 80%, var(--accent-luma)); + --primary: hsl(var(--hue), 100%, var(--accent-luma)); + --primary-hover: hsl(var(--hue), 95%, var(--accent-luma)); + --primary-active: hsl(var(--hue), 80%, var(--accent-luma)); - --text-on-color: hsl(0, 0%, var(--accent-text-luma)); - --link: hsl(var(--hue), 100%, var(--accent-luma)); + --text-on-color: hsl(0, 0%, var(--accent-text-luma)); + --link: hsl(var(--hue), 100%, var(--accent-luma)); - --surface: hsla(var(--hue), 80%, 30%, 5%); - --surface-hover: hsla(var(--hue), 100%, 5%, 10%); - --surface-active: hsla(var(--hue), 100%, 5%, 15%); + --surface: hsla(var(--hue), 80%, 30%, 5%); + --surface-hover: hsla(var(--hue), 100%, 5%, 10%); + --surface-active: hsla(var(--hue), 100%, 5%, 15%); } @media (prefers-color-scheme: dark) { - [color]:not([color="standard"]) { - --accent-luma: var(--accent-luma-dark); - --accent-text-luma: var(--accent-text-luma-dark); + [color]:not([color="standard"]) { + --accent-luma: var(--accent-luma-dark); + --accent-text-luma: var(--accent-text-luma-dark); - --surface: hsla(var(--hue), 100%, 95%, 5%); - --surface-hover: hsla(var(--hue), 100%, 95%, 10%); - --surface-active: hsla(var(--hue), 100%, 95%, 15%); - } + --surface: hsla(var(--hue), 100%, 95%, 5%); + --surface-hover: hsla(var(--hue), 100%, 95%, 10%); + --surface-active: hsla(var(--hue), 100%, 95%, 15%); + } } /* tile */ [color]:not([color="standard"]).tile { - --gradient-luma-1: 70%; - --gradient-luma-2: 90%; + --gradient-luma-1: 70%; + --gradient-luma-2: 90%; - --hover-luma: 75%; - --active-luma: 80%; + --hover-luma: 75%; + --active-luma: 80%; - --text-luma: 20%; + --text-luma: 20%; - --surface: linear-gradient( - 120deg, - hsl(var(--hue), 100%, var(--gradient-luma-1)), - hsla(var(--hue), 100%, var(--gradient-luma-2), 90%) - ); - --surface-hover: hsl(var(--hue), 100%, var(--hover-luma)); - --surface-active: hsl(var(--hue), 100%, var(--active-luma)); + --surface: linear-gradient( + 120deg, + hsl(var(--hue), 100%, var(--gradient-luma-1)), + hsla(var(--hue), 100%, var(--gradient-luma-2), 90%) + ); + --surface-hover: hsl(var(--hue), 100%, var(--hover-luma)); + --surface-active: hsl(var(--hue), 100%, var(--active-luma)); - --text-normal: hsl(var(--hue), 100%, var(--text-luma)); + --text-normal: hsl(var(--hue), 100%, var(--text-luma)); } @media (prefers-color-scheme: dark) { - [color]:not([color="standard"]).tile { - --gradient-luma-1: 20%; - --gradient-luma-2: 15%; + [color]:not([color="standard"]).tile { + --gradient-luma-1: 20%; + --gradient-luma-2: 15%; - --hover-luma: 25%; - --active-luma: 30%; + --hover-luma: 25%; + --active-luma: 30%; - --text-luma: 90%; - } + --text-luma: 90%; + } } /* fill */ [color]:not([color="standard"]).fill-color { - color: white; - background-color: var(--primary); + color: white; + background-color: var(--primary); } [color]:not([color="standard"])[selected].fill-color { - color: var(--primary); + color: var(--primary); } /* background */ @media (min-width: 1024px) { - [color]:not([color="standard"]).background { - --luma: var(--background-luma); - --sat: 80%; - background: hsl(var(--hue), var(--sat), var(--luma)); - } - - @media (prefers-color-scheme: dark) { [color]:not([color="standard"]).background { - --luma: var(--background-luma-dark); - --sat: 40%; + --luma: var(--background-luma); + --sat: 80%; + background: hsl(var(--hue), var(--sat), var(--luma)); } - } - [color="standard"].background { - background: var(--surface); - } + @media (prefers-color-scheme: dark) { + [color]:not([color="standard"]).background { + --luma: var(--background-luma-dark); + --sat: 40%; + } + } + + [color="standard"].background { + background: var(--surface); + } } /* subtle */ [color]:not([color="standard"]).subtle-background { - --luma: var(--subtle-luma); - background: hsl(var(--hue), 100%, var(--luma), 50%); + --luma: var(--subtle-luma); + background: hsl(var(--hue), 100%, var(--luma), 50%); } @media (prefers-color-scheme: dark) { - [color]:not([color="standard"]).subtle-background { - --luma: var(--subtle-luma-dark); - } - - @media (display-mode: standalone) { [color]:not([color="standard"]).subtle-background { - --luma: 0%; + --luma: var(--subtle-luma-dark); + } + + @media (display-mode: standalone) { + [color]:not([color="standard"]).subtle-background { + --luma: 0%; + } } - } } /* definitions */ [color="coral"] { - --hue: 20; + --hue: 20; - --accent-luma: 50%; - --accent-luma-dark: 70%; + --accent-luma: 50%; + --accent-luma-dark: 70%; - --accent-text-luma: 100%; - --accent-text-luma-dark: 100%; + --accent-text-luma: 100%; + --accent-text-luma-dark: 100%; - --background-luma: 90%; - --background-luma-dark: 11%; + --background-luma: 90%; + --background-luma-dark: 11%; - --subtle-luma: 95%; - --subtle-luma-dark: 5%; + --subtle-luma: 95%; + --subtle-luma-dark: 5%; } [color="yellow"] { - --hue: 50; + --hue: 50; - --accent-luma: 50%; - --accent-luma-dark: 60%; + --accent-luma: 50%; + --accent-luma-dark: 60%; - --accent-text-luma: 0%; - --accent-text-luma-dark: 0%; + --accent-text-luma: 0%; + --accent-text-luma-dark: 0%; - --background-luma: 85%; - --background-luma-dark: 11%; + --background-luma: 85%; + --background-luma-dark: 11%; - --subtle-luma: 95%; - --subtle-luma-dark: 5%; + --subtle-luma: 95%; + --subtle-luma-dark: 5%; } [color="mint"] { - --hue: 150; + --hue: 150; - --accent-luma: 40%; - --accent-luma-dark: 60%; + --accent-luma: 40%; + --accent-luma-dark: 60%; - --accent-text-luma: 100%; - --accent-text-luma-dark: 0%; + --accent-text-luma: 100%; + --accent-text-luma-dark: 0%; - --background-luma: 85%; - --background-luma-dark: 12%; + --background-luma: 85%; + --background-luma-dark: 12%; - --subtle-luma: 95%; - --subtle-luma-dark: 5%; + --subtle-luma: 95%; + --subtle-luma-dark: 5%; } [color="lightblue"] { - --hue: 180; + --hue: 180; - --accent-luma: 40%; - --accent-luma-dark: 60%; + --accent-luma: 40%; + --accent-luma-dark: 60%; - --accent-text-luma: 0%; - --accent-text-luma-dark: 0%; + --accent-text-luma: 0%; + --accent-text-luma-dark: 0%; - --background-luma: 85%; - --background-luma-dark: 12%; + --background-luma: 85%; + --background-luma-dark: 12%; - --subtle-luma: 95%; - --subtle-luma-dark: 5%; + --subtle-luma: 95%; + --subtle-luma-dark: 5%; } [color="blue"] { - --hue: 220; + --hue: 220; - --accent-luma: 50%; - --accent-luma-dark: 70%; + --accent-luma: 50%; + --accent-luma-dark: 70%; - --accent-text-luma: 100%; - --accent-text-luma-dark: 100%; + --accent-text-luma: 100%; + --accent-text-luma-dark: 100%; - --background-luma: 90%; - --background-luma-dark: 12%; + --background-luma: 90%; + --background-luma-dark: 12%; - --subtle-luma: 95%; - --subtle-luma-dark: 5%; + --subtle-luma: 95%; + --subtle-luma-dark: 5%; } [color="purple"] { - --hue: 280; + --hue: 280; - --accent-luma: 50%; - --accent-luma-dark: 70%; + --accent-luma: 50%; + --accent-luma-dark: 70%; - --accent-text-luma: 100%; - --accent-text-luma-dark: 100%; + --accent-text-luma: 100%; + --accent-text-luma-dark: 100%; - --background-luma: 90%; - --background-luma-dark: 11%; + --background-luma: 90%; + --background-luma-dark: 11%; - --subtle-luma: 95%; - --subtle-luma-dark: 5%; + --subtle-luma: 95%; + --subtle-luma-dark: 5%; } diff --git a/src/colors.ts b/src/colors.ts index eadde144..54067363 100644 --- a/src/colors.ts +++ b/src/colors.ts @@ -1,9 +1,9 @@ export enum Color { - Standard = "standard", - Coral = "coral", - Yellow = "yellow", - Mint = "mint", - LightBlue = "lightblue", - Blue = "blue", - purple = "purple", + Standard = "standard", + Coral = "coral", + Yellow = "yellow", + Mint = "mint", + LightBlue = "lightblue", + Blue = "blue", + purple = "purple", } diff --git a/src/index.tsx b/src/index.tsx index bd0ae352..546f50d9 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -27,9 +27,9 @@ const storageModel = new StorageModel(); const settingsModel = new SettingsModel(storageModel); const connectionModel = new ConnectionModel(storageModel); const chatListModel = new ChatListModel( - storageModel, - settingsModel, - connectionModel + storageModel, + settingsModel, + connectionModel, ); const fileTransferModel = new FileTransferModel(storageModel, connectionModel); @@ -42,47 +42,47 @@ const coreVieWModel = new CoreViewModel(); const storageViewModel = new StorageViewModel(coreVieWModel, storageModel); const settingsViewModel = new SettingsViewModel(coreVieWModel, settingsModel); const connectionViewModel = new ConnectionViewModel( - coreVieWModel, - connectionModel + coreVieWModel, + connectionModel, ); const chatListViewModel = new ChatListViewModel( - coreVieWModel, - storageModel, - chatListModel, - settingsViewModel + coreVieWModel, + storageModel, + chatListModel, + settingsViewModel, ); const fileTransferViewModel = new FileTransferViewModel( - fileTransferModel, - chatListModel + fileTransferModel, + chatListModel, ); // view chatListViewModel.selectedChat.subscribe(() => { - document.body.toggleAttribute( - "showing-chat", - chatListViewModel.selectedChat.value != undefined - ); + document.body.toggleAttribute( + "showing-chat", + chatListViewModel.selectedChat.value != undefined, + ); }); document.body.append( -
-
-
-
-
+
+
+
+
+
, ); document - .querySelector("main")! - .append( - HomePage( - storageViewModel, - settingsViewModel, - connectionViewModel, - fileTransferViewModel, - chatListViewModel - ), - ChatPageWrapper(chatListViewModel), - ConnectionModal(connectionViewModel), - DataTransferModalWrapper(connectionViewModel, fileTransferViewModel), - StorageModal(storageViewModel) - ); + .querySelector("main")! + .append( + HomePage( + storageViewModel, + settingsViewModel, + connectionViewModel, + fileTransferViewModel, + chatListViewModel, + ), + ChatPageWrapper(chatListViewModel), + ConnectionModal(connectionViewModel), + DataTransferModalWrapper(connectionViewModel, fileTransferViewModel), + StorageModal(storageViewModel), + );