diff --git a/dist/index.css b/dist/index.css index 105a429e..576bc4f2 100644 --- a/dist/index.css +++ b/dist/index.css @@ -597,7 +597,7 @@ body[showing-chat] #chat-page-wrapper { padding-bottom: calc(var(--message-container-padding) + var(--content-padding)) !important; } #chat-page #main #message-page .content:has(.reply-preview-wrapper .reply-preview) #message-container { - --message-container-padding: calc(2 * var(--control-height) + var(--gap-between-tiles)); + --message-container-padding: calc( 2 * var(--control-height) + var(--gap-between-tiles) ); } #message-page #message-container { overflow: auto; diff --git a/dist/index.js b/dist/index.js index 7036bca0..b919682b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2378,26 +2378,84 @@ } }; - // src/View/utility.ts - function allowDrop(event) { - event.preventDefault(); - } - function allowDrag(event) { - event.dataTransfer?.setData("text", ""); - } - function reload() { - window.location.reload(); - } - function setFocus() { - const focusedInModal = document.querySelector( - ".modal[open] #focused" - ); - if (focusedInModal) return focusedInModal.focus(); - document.getElementById("focused")?.focus(); - } - function setFocusWithDelay() { - setTimeout(setFocus, 100); - } + // src/View/viewController.ts + var Tracker = class { + }; + var TrackerMap = class { + constructor() { + this.trackers = /* @__PURE__ */ new Map(); + this.register = (key) => { + if (this.trackers.has(key)) { + return this.trackers.get(key); + } + const tracker = new Tracker(); + this.trackers.set(key, tracker); + return tracker; + }; + this.setState = (key, stateBuilder) => { + const tracker = this.register(key); + if (tracker.state != void 0) { + return tracker.state; + } else { + tracker.state = stateBuilder(); + return tracker.state; + } + }; + } + }; + var ViewController = class { + static { + this.chatPages = new Tracker(); + } + static { + this.taskPages = new TrackerMap(); + } + static { + this.boardPages = new TrackerMap(); + } + static { + this.inlineReplies = new TrackerMap(); + } + static { + this.scrollToView = (id) => { + const element = document.getElementById(id); + if (!element) return; + element.scrollIntoView(); + element.setAttribute("scrolled", ""); + setTimeout(() => element.removeAttribute("scrolled"), 1e3); + }; + } + static { + this.allowDrop = (event) => { + event.preventDefault(); + }; + } + static { + this.allowDrag = (event) => { + event.dataTransfer?.setData("text", ""); + }; + } + static { + this.reload = () => { + window.location.reload(); + }; + } + static { + this.setFocus = () => { + const focusedInModal = document.querySelector( + ".modal[open] #focused" + ); + if (focusedInModal) return focusedInModal.focus(); + document.getElementById("focused")?.focus(); + }; + } + static { + this.setFocusWithDelay = () => { + console.trace("focus"); + setTimeout(this.setFocus, 100); + }; + } + }; // src/ViewModel/Pages/taskViewModel.ts var TaskViewModel = class extends Context { @@ -2427,7 +2485,7 @@ this.versionIds = new ListState(); // methods this.dragStart = (event) => { - allowDrag(event); + ViewController.allowDrag(event); this.coreViewModel.draggedObject.value = this; }; this.setCategoryAndStatus = (category, status) => { @@ -2842,12 +2900,12 @@ this.messagePageViewModel.decryptMessage(this); }; this.reply = () => { - this.messagePageViewModel.replyingMessage.value = this; + this.messagePageViewModel.setReply(this); this.hideInfoModal(); }; this.cancelReply = () => { if (this.messagePageViewModel.replyingMessage.value != this) return; - this.messagePageViewModel.replyingMessage.value = void 0; + this.messagePageViewModel.setReply(void 0); }; // view this.showInfoModal = () => { @@ -2882,7 +2940,9 @@ this.body.value = this.chatMessage.body; this.status.value = this.chatMessage.status; if (this.chatMessage.inlineReplyId) { - this.inlineReply = this.messagePageViewModel.chatMessageViewModels.value.get(this.chatMessage.inlineReplyId); + this.inlineReply = this.messagePageViewModel.chatMessageViewModels.value.get( + this.chatMessage.inlineReplyId + ); } }; this.chatMessage = chatMessage; @@ -2994,6 +3054,7 @@ void 0 ); this.composingMessage = new State(""); + this.focusSetter = new State(null); // methods this.sendMessage = () => { if (this.cannotSendMessage.value == true) return; @@ -3005,10 +3066,7 @@ if (this.replyingMessage.value) { replyId = this.replyingMessage.value.chatMessage.id; } - this.chatViewModel.chatModel.sendMessage( - body, - replyId - ); + this.chatViewModel.chatModel.sendMessage(body, replyId); this.replyingMessage.value = void 0; }; this.decryptMessage = async (messageViewModel) => { @@ -3024,6 +3082,14 @@ isDeleting ); }; + this.setReply = (chatMessageViewModel) => { + this.replyingMessage.value = chatMessageViewModel; + this.setFocus(); + }; + this.resetReply = () => { + this.replyingMessage.value = void 0; + this.setFocus(); + }; // view this.showChatMessage = (chatMessage) => { const chatMessageViewModel = new ChatMessageViewModel( @@ -3064,6 +3130,9 @@ this.revokeReactionFilter(); this.searchViewModel.search(""); }; + this.setFocus = () => { + this.focusSetter.callSubscriptions(); + }; // load this.loadData = () => { this.chatMessageViewModels.clear(); @@ -3092,8 +3161,15 @@ () => this.searchViewModel.appliedQuery.value != "" || this.reactionFilter.value != void 0 ); this.registerKeyStroke("=" /* Filter */, this.showFilterModal); - this.registerKeyStroke("backspace" /* CloseOrCancel */, this.hideFilterModal); + this.registerKeyStroke("backspace" /* CloseOrCancel */, () => { + if (this.isFilterModalOpen.value == true) { + this.hideFilterModal(); + } else { + this.replyingMessage.value = void 0; + } + }); this.registerKeyStroke("-" /* Reset */, this.resetFilter); + this.registerKeyStroke(";" /* Create */, this.setFocus); this.chatViewModel.registerContext("messages" /* Messages */, this); } }; @@ -3526,7 +3602,8 @@ this.closeCurrentContext(); }; this.handleBoardClosed = (boardViewModel) => { - if (boardViewModel.boardInfo.fileId != this.selectedBoardId.value) return; + if (boardViewModel.boardInfo.fileId != this.selectedBoardId.value) + return; this.selectedBoardId.value = void 0; this.chatViewModel.resetColor(); }; @@ -3945,7 +4022,7 @@ "on:click": select, "toggle:selected": isSelected, "toggle:today": isToday, - "on:dragover": allowDrop, + "on:dragover": ViewController.allowDrop, "on:drop": drop }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, date), /* @__PURE__ */ createElement( @@ -4255,7 +4332,7 @@ if (calendarPageViewModel.selectedTaskViewModel.value == void 0) { return /* @__PURE__ */ createElement("div", null); } else { - setFocusWithDelay(); + ViewController.setFocusWithDelay(); return TaskSettingsModal( coreViewModel2, calendarPageViewModel.selectedTaskViewModel.value @@ -4510,56 +4587,6 @@ ); } - // src/View/viewController.ts - var ItemTracker = class { - }; - var ViewTrackerMap = class { - constructor() { - this.trackers = /* @__PURE__ */ new Map(); - this.register = (key) => { - if (this.trackers.has(key)) { - return this.trackers.get(key); - } - const tracker = new ItemTracker(); - this.trackers.set(key, tracker); - return tracker; - }; - this.setItems = (key, views) => { - const tracker = this.register(key); - if (tracker.items != void 0) { - return tracker.items; - } else { - tracker.items = views(); - return tracker.items; - } - }; - } - }; - var ViewController = class { - static { - this.chatPages = new ItemTracker(); - } - static { - this.taskPages = new ViewTrackerMap(); - } - static { - this.boardPages = new ViewTrackerMap(); - } - static { - this.inlineReplies = new ViewTrackerMap(); - } - static { - this.messageIcons = new ViewTrackerMap(); - } - static scrollToView(id) { - const element = document.getElementById(id); - if (!element) return; - element.scrollIntoView(); - element.setAttribute("scrolled", ""); - setTimeout(() => element.removeAttribute("scrolled"), 1e3); - } - }; - // src/View/Components/inlineReply.tsx function InlineReply(chatMessageViewModel) { const reply = chatMessageViewModel.inlineReply; @@ -4572,22 +4599,18 @@ // src/View/Components/chatMessage.tsx function ChatMessage4(coreViewModel2, chatMessageViewModel) { - const persistenceId = chatMessageViewModel.chatMessage.id; - const statusIcon = ViewController.messageIcons.setItems(persistenceId, () => createProxyState( - [chatMessageViewModel.status], - () => { - switch (chatMessageViewModel.status.value) { - case "outbox" /* Outbox */: - return "hourglass_top"; - case "sent" /* Sent */: - return "check"; - case "received" /* Received */: - return "done_all"; - default: - return "warning"; - } + const statusIcon = createProxyState([chatMessageViewModel.status], () => { + switch (chatMessageViewModel.status.value) { + case "outbox" /* Outbox */: + return "hourglass_top"; + case "sent" /* Sent */: + return "check"; + case "received" /* Received */: + return "done_all"; + default: + return "warning"; } - )); + }); return /* @__PURE__ */ createElement( "div", { @@ -4656,7 +4679,18 @@ [messagePageViewModel.reactionFilter], () => messagePageViewModel.reactionFilter.value == void 0 ); - messagePageViewModel.isFilterModalOpen.subscribe(setFocusWithDelay); + messagePageViewModel.isFilterModalOpen.subscribe((isOpen) => { + if (isOpen == false) return; + ViewController.setFocusWithDelay(); + }); + createProxyState( + [messagePageViewModel.isFilterModalOpen], + () => { + if (messagePageViewModel.isFilterModalOpen.value == false) + return; + ViewController.setFocusWithDelay(); + } + ); return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": messagePageViewModel.isFilterModalOpen }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.chatPage.message.messageFilterHeadline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( "input", { @@ -4704,7 +4738,13 @@ // src/View/Components/replyPreview.tsx function ReplyPreview(coreViewModel2, chatMessageViewModel) { - return /* @__PURE__ */ createElement("div", { class: "reply-preview" }, /* @__PURE__ */ createElement("div", { class: "surface blur" }, /* @__PURE__ */ createElement("span", { class: "secondary" }, chatMessageViewModel.sender), /* @__PURE__ */ createElement("b", { class: "ellipsis", "subscribe:innerText": chatMessageViewModel.body })), /* @__PURE__ */ createElement( + return /* @__PURE__ */ createElement("div", { class: "reply-preview" }, /* @__PURE__ */ createElement("div", { class: "surface blur" }, /* @__PURE__ */ createElement("span", { class: "secondary" }, chatMessageViewModel.sender), /* @__PURE__ */ createElement( + "b", + { + class: "ellipsis", + "subscribe:innerText": chatMessageViewModel.body + } + )), /* @__PURE__ */ createElement( "button", { class: "standard square blur", @@ -4732,7 +4772,7 @@ } ); const persistenceId = messagePageViewModel.chatViewModel.chatModel.id; - const replyPreview = ViewController.inlineReplies.setItems( + const replyPreview = ViewController.inlineReplies.setState( persistenceId, () => createProxyState( [messagePageViewModel.replyingMessage], @@ -4762,10 +4802,9 @@ scrollDownIfApplicable ); setTimeout(() => scrollDown(true), 100); - messagePageViewModel.replyingMessage.subscribeSilent(() => { - setFocusWithDelay(); + messagePageViewModel.focusSetter.subscribeSilent(() => { + ViewController.setFocusWithDelay(); }); - setFocusWithDelay(); return /* @__PURE__ */ createElement("div", { id: "message-page" }, /* @__PURE__ */ createElement("div", { class: "pane-wrapper" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", { class: "title" }, coreViewModel2.translations.chatPage.message.messagesHeadline), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement( "button", { @@ -5081,7 +5120,7 @@ "div", { class: "flex-column flex-no", - "on:dragover": allowDrop, + "on:dragover": ViewController.allowDrop, "on:drop": drop }, /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( @@ -5119,7 +5158,7 @@ // src/View/Modals/boardSettingsModal.tsx function BoardSettingsModal(coreViewModel2, boardViewModel) { - boardViewModel.isPresentingSettingsModal.subscribe(setFocusWithDelay); + boardViewModel.isPresentingSettingsModal.subscribe(ViewController.setFocusWithDelay); return /* @__PURE__ */ createElement( "div", { @@ -5302,7 +5341,7 @@ "div", { class: "status-column gap", - "on:dragover": allowDrop, + "on:dragover": ViewController.allowDrop, "on:drop": drop, "children:append": [taskViewModels, TaskViewModelToEntry] } @@ -5331,7 +5370,10 @@ isOpen.value = false; } const suggestionId = v4_default(); - isOpen.subscribe(setFocusWithDelay); + isOpen.subscribe((isOpen2) => { + if (!isOpen2) return; + ViewController.setFocusWithDelay(); + }); return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isOpen, extended: true }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, headline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( "input", { @@ -5377,7 +5419,7 @@ function BoardPage(coreViewModel2, boardViewModel) { boardViewModel.loadData(); const persistenceId = boardViewModel.boardInfo.fileId; - const pages = ViewController.boardPages.setItems( + const pages = ViewController.boardPages.setState( persistenceId, () => createProxyState([boardViewModel.selectedPage], () => { switch (boardViewModel.selectedPage.value) { @@ -5408,7 +5450,7 @@ if (boardViewModel.selectedTaskViewModel.value == void 0) { return /* @__PURE__ */ createElement("div", null); } else { - setFocusWithDelay(); + ViewController.setFocusWithDelay(); return TaskSettingsModal( coreViewModel2, boardViewModel.selectedTaskViewModel.value @@ -5491,7 +5533,7 @@ class: "tile colored-tile", "toggle:selected": boardViewModel.isSelected, "on:click": boardViewModel.select, - "on:dragover": allowDrop, + "on:dragover": ViewController.allowDrop, "on:drop": boardViewModel.handleDropBetweenBoards }, /* @__PURE__ */ createElement( @@ -5520,7 +5562,7 @@ () => taskPageViewModel.selectedBoardId.value != void 0 ); const persistenceId = taskPageViewModel.chatViewModel.chatModel.id; - const pages = ViewController.taskPages.setItems( + const pages = ViewController.taskPages.setState( persistenceId, () => createProxyState([taskPageViewModel.selectedBoardId], () => { const selectedBoardId = taskPageViewModel.selectedBoardId.value; @@ -5592,7 +5634,7 @@ // src/View/chatPage.tsx function ChatPage(coreViewModel2, chatViewModel) { - ViewController.chatPages.items = createProxyState( + ViewController.chatPages.state = createProxyState( [chatViewModel.selectedPage], () => { switch (chatViewModel.selectedPage.value) { @@ -5687,7 +5729,7 @@ "div", { id: "main", - "children:set": ViewController.chatPages.items + "children:set": ViewController.chatPages.state } )) ); @@ -6541,7 +6583,7 @@ StringToTextSpan ] } - )), /* @__PURE__ */ createElement("button", { "on:click": reload }, coreViewModel2.translations.general.reloadAppButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "refresh")))); + )), /* @__PURE__ */ createElement("button", { "on:click": ViewController.reload }, coreViewModel2.translations.general.reloadAppButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "refresh")))); } // src/Model/Global/fileTransferModel.ts @@ -6954,10 +6996,7 @@ "change", () => this.applyTheme() ); - this.registerKeyStroke( - "backspace" /* CloseOrCancel */, - this.close - ); + this.registerKeyStroke("backspace" /* CloseOrCancel */, this.close); } static generateThemeMedia() { return window.matchMedia("(prefers-color-scheme: dark)"); diff --git a/src/View/ChatPages/boardKanbanPage.tsx b/src/View/ChatPages/boardKanbanPage.tsx index a82a65cc..6e9d9cc9 100644 --- a/src/View/ChatPages/boardKanbanPage.tsx +++ b/src/View/ChatPages/boardKanbanPage.tsx @@ -10,8 +10,8 @@ import { FilteredList } from "../Components/filteredList"; import { TaskCategoryBulkChangeViewModel } from "../../ViewModel/Utility/taskPropertyBulkChangeViewModel"; import TaskViewModel from "../../ViewModel/Pages/taskViewModel"; import { TaskViewModelToEntry } from "../Components/taskEntry"; -import { allowDrop } from "../utility"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; +import { ViewController } from "../viewController"; export function BoardKanbanPage( coreViewModel: CoreViewModel, @@ -75,7 +75,7 @@ function Column( const view = (
diff --git a/src/View/ChatPages/boardPage.tsx b/src/View/ChatPages/boardPage.tsx index 1fc50326..3dbaa6c3 100644 --- a/src/View/ChatPages/boardPage.tsx +++ b/src/View/ChatPages/boardPage.tsx @@ -11,7 +11,6 @@ import { BoardViewToggleButton } from "../Components/boardViewToggleButton"; import { SearchModal } from "../Modals/searchModal"; import { TaskSettingsModal } from "../Modals/taskSettingsModal"; import { TaskViewModelToEntry } from "../Components/taskEntry"; -import { setFocusWithDelay } from "../utility"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; import { ViewController } from "../viewController"; @@ -23,7 +22,7 @@ export function BoardPage( // main content const persistenceId = boardViewModel.boardInfo.fileId; - const pages = ViewController.boardPages.setItems(persistenceId, () => + const pages = ViewController.boardPages.setState(persistenceId, () => React.createProxyState([boardViewModel.selectedPage], () => { switch (boardViewModel.selectedPage.value) { case BoardPageTypes.Kanban: { @@ -54,7 +53,7 @@ export function BoardPage( if (boardViewModel.selectedTaskViewModel.value == undefined) { return
; } else { - setFocusWithDelay(); + ViewController.setFocusWithDelay(); return TaskSettingsModal( coreViewModel, diff --git a/src/View/ChatPages/boardStatusGridPage.tsx b/src/View/ChatPages/boardStatusGridPage.tsx index ed723470..095fc87c 100644 --- a/src/View/ChatPages/boardStatusGridPage.tsx +++ b/src/View/ChatPages/boardStatusGridPage.tsx @@ -15,8 +15,8 @@ import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; import { FilteredList } from "../Components/filteredList"; import TaskViewModel from "../../ViewModel/Pages/taskViewModel"; import { TaskViewModelToEntry } from "../Components/taskEntry"; -import { allowDrop } from "../utility"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; +import { ViewController } from "../viewController"; export function BoardStatusGridPage( coreViewModel: CoreViewModel, @@ -246,7 +246,7 @@ function StatusColumn( const view = (
diff --git a/src/View/ChatPages/calendarPage.tsx b/src/View/ChatPages/calendarPage.tsx index c112b46b..7fd86703 100644 --- a/src/View/ChatPages/calendarPage.tsx +++ b/src/View/ChatPages/calendarPage.tsx @@ -2,14 +2,13 @@ import "./calendarPage.css"; import * as React from "bloatless-react"; -import CalendarModel from "../../Model/Files/calendarModel"; import CalendarPageViewModel from "../../ViewModel/Pages/calendarPageViewModel"; import { MonthGrid } from "../Components/monthGrid"; import { TaskSettingsModal } from "../Modals/taskSettingsModal"; import { TaskViewModelToEntry } from "../Components/taskEntry"; -import { setFocusWithDelay } from "../utility"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; import { PlaceholderView } from "../Components/placeholderView"; +import { ViewController } from "../viewController"; export function CalendarPage( coreViewModel: CoreViewModel, @@ -95,7 +94,7 @@ export function CalendarPage( ) { return
; } else { - setFocusWithDelay(); + ViewController.setFocusWithDelay(); return TaskSettingsModal( coreViewModel, diff --git a/src/View/ChatPages/messagePage.css b/src/View/ChatPages/messagePage.css index 2057ab34..7b82cd5e 100644 --- a/src/View/ChatPages/messagePage.css +++ b/src/View/ChatPages/messagePage.css @@ -9,11 +9,19 @@ #chat-page #main #message-page .content #message-container { --message-container-padding: var(--control-height); - padding-bottom: calc(var(--message-container-padding) + var(--content-padding)) !important; + padding-bottom: calc( + var(--message-container-padding) + var(--content-padding) + ) !important; } -#chat-page #main #message-page .content:has(.reply-preview-wrapper .reply-preview) #message-container { - --message-container-padding: calc(2 * var(--control-height) + var(--gap-between-tiles)); +#chat-page + #main + #message-page + .content:has(.reply-preview-wrapper .reply-preview) + #message-container { + --message-container-padding: calc( + 2 * var(--control-height) + var(--gap-between-tiles) + ); } #message-page #message-container { @@ -62,7 +70,7 @@ gap: var(--gap-between-tiles); } -#message-page #composer .reply-preview-wrapper .reply-preview>div { +#message-page #composer .reply-preview-wrapper .reply-preview > div { display: flex; align-items: center; gap: var(--text-gap); @@ -81,7 +89,6 @@ } @media (min-width: 700px) { - #message-page #composer .content-width-constraint, #message-page #composer .input-width-constraint { display: flex; @@ -227,7 +234,7 @@ text-align: right; } -.message-bubble>.inline-reply { +.message-bubble > .inline-reply { cursor: pointer; display: flex; @@ -246,7 +253,7 @@ border-left: var(--outline-width) solid var(--primary); } -.message-bubble>.message-reaction-row { +.message-bubble > .message-reaction-row { max-width: var(--max-input-width); width: 100%; @@ -257,11 +264,11 @@ overflow: hidden; } -.message-bubble>.message-reaction-row:has(button:not([count="0"])) { +.message-bubble > .message-reaction-row:has(button:not([count="0"])) { display: grid !important; } -.message-bubble>.message-reaction-row button { +.message-bubble > .message-reaction-row button { min-height: unset; height: 100%; width: 100%; @@ -271,7 +278,7 @@ animation: slide-up var(--animation-duration); } -.message-bubble>.message-reaction-row button[count="0"] { +.message-bubble > .message-reaction-row button[count="0"] { display: none; } @@ -285,7 +292,6 @@ } } - .grid.message-reaction-filter { grid-template-columns: repeat(3, 1fr); -} \ No newline at end of file +} diff --git a/src/View/ChatPages/messagePage.tsx b/src/View/ChatPages/messagePage.tsx index c383cb64..fd1b643e 100644 --- a/src/View/ChatPages/messagePage.tsx +++ b/src/View/ChatPages/messagePage.tsx @@ -7,7 +7,6 @@ import CoreViewModel from "../../ViewModel/Global/coreViewModel"; import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; import { ChatMessage } from "../Components/chatMessage"; import { MessageFilterModal } from "../Modals/messageFilterModal"; -import { setFocusWithDelay } from "../utility"; import { ViewController } from "../viewController"; import { ReplyPreview } from "../Components/replyPreview"; @@ -34,7 +33,7 @@ export function MessagePage( ); const persistenceId = messagePageViewModel.chatViewModel.chatModel.id; - const replyPreview = ViewController.inlineReplies.setItems( + const replyPreview = ViewController.inlineReplies.setState( persistenceId, () => React.createProxyState( @@ -70,10 +69,9 @@ export function MessagePage( ); setTimeout(() => scrollDown(true), 100); - messagePageViewModel.replyingMessage.subscribeSilent(() => { - setFocusWithDelay(); - }) - setFocusWithDelay(); + messagePageViewModel.focusSetter.subscribeSilent(() => { + ViewController.setFocusWithDelay(); + }); return (
diff --git a/src/View/ChatPages/taskPage.tsx b/src/View/ChatPages/taskPage.tsx index 94bcb7b9..90fc2681 100644 --- a/src/View/ChatPages/taskPage.tsx +++ b/src/View/ChatPages/taskPage.tsx @@ -22,7 +22,7 @@ export function TaskPage( ); const persistenceId = taskPageViewModel.chatViewModel.chatModel.id; - const pages = ViewController.taskPages.setItems(persistenceId, () => + const pages = ViewController.taskPages.setState(persistenceId, () => React.createProxyState([taskPageViewModel.selectedBoardId], () => { const selectedBoardId = taskPageViewModel.selectedBoardId.value; if (selectedBoardId == undefined) { diff --git a/src/View/Components/boardEntry.tsx b/src/View/Components/boardEntry.tsx index 37be95cc..26a754e8 100644 --- a/src/View/Components/boardEntry.tsx +++ b/src/View/Components/boardEntry.tsx @@ -1,7 +1,7 @@ import * as React from "bloatless-react"; import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; -import { allowDrop } from "../utility"; +import { ViewController } from "../viewController"; export function BoardEntry(boardViewModel: BoardViewModel) { const view = ( @@ -10,7 +10,7 @@ export function BoardEntry(boardViewModel: BoardViewModel) { class="tile colored-tile" toggle:selected={boardViewModel.isSelected} on:click={boardViewModel.select} - on:dragover={allowDrop} + on:dragover={ViewController.allowDrop} on:drop={boardViewModel.handleDropBetweenBoards} > React.createProxyState( + const statusIcon = React.createProxyState( [chatMessageViewModel.status], () => { switch (chatMessageViewModel.status.value) { @@ -26,8 +25,8 @@ export function ChatMessage( default: return "warning"; } - } - )); + }, + ); return (
+ if (reply == undefined) return
; const scroll = () => { ViewController.scrollToView(reply.chatMessage.id); - } + }; return (
diff --git a/src/View/Components/monthGrid.tsx b/src/View/Components/monthGrid.tsx index 8c793e93..761536ad 100644 --- a/src/View/Components/monthGrid.tsx +++ b/src/View/Components/monthGrid.tsx @@ -3,9 +3,9 @@ import "./monthGrid.css"; import * as React from "bloatless-react"; import { MonthGrid } from "../../Model/Files/calendarModel"; import TaskViewModel from "../../ViewModel/Pages/taskViewModel"; -import { allowDrop } from "../utility"; import { localeCompare } from "../../Model/Utility/utility"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; +import { ViewController } from "../viewController"; export function MonthGrid( coreViewModel: CoreViewModel, @@ -93,7 +93,7 @@ export function MonthGrid( on:click={select} toggle:selected={isSelected} toggle:today={isToday} - on:dragover={allowDrop} + on:dragover={ViewController.allowDrop} on:drop={drop} >
diff --git a/src/View/Components/replyPreview.tsx b/src/View/Components/replyPreview.tsx index 82e9ebdf..2b6948ca 100644 --- a/src/View/Components/replyPreview.tsx +++ b/src/View/Components/replyPreview.tsx @@ -11,12 +11,18 @@ export function ReplyPreview(
{chatMessageViewModel.sender} - +
diff --git a/src/View/Modals/boardSettingsModal.tsx b/src/View/Modals/boardSettingsModal.tsx index 08abf05e..0e3237c6 100644 --- a/src/View/Modals/boardSettingsModal.tsx +++ b/src/View/Modals/boardSettingsModal.tsx @@ -4,13 +4,15 @@ import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; import { ColorPicker } from "../Components/colorPicker"; import { DangerousActionButton } from "../Components/dangerousActionButton"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; -import { setFocusWithDelay } from "../utility"; +import { ViewController } from "../viewController"; export function BoardSettingsModal( coreViewModel: CoreViewModel, boardViewModel: BoardViewModel, ) { - boardViewModel.isPresentingSettingsModal.subscribe(setFocusWithDelay); + boardViewModel.isPresentingSettingsModal.subscribe( + ViewController.setFocusWithDelay, + ); return (
- diff --git a/src/View/Modals/messageFilterModal.tsx b/src/View/Modals/messageFilterModal.tsx index 748f057f..e58973bf 100644 --- a/src/View/Modals/messageFilterModal.tsx +++ b/src/View/Modals/messageFilterModal.tsx @@ -4,7 +4,7 @@ import CoreViewModel from "../../ViewModel/Global/coreViewModel"; import MessagePageViewModel from "../../ViewModel/Pages/messagePageViewModel"; import { MessageReactionFilterButton } from "../Components/messageReactionFilterButton"; import { ReactionSymbols } from "../../Model/Chat/chatModel"; -import { setFocusWithDelay } from "../utility"; +import { ViewController } from "../viewController"; export function MessageFilterModal( coreViewModel: CoreViewModel, @@ -16,7 +16,14 @@ export function MessageFilterModal( () => messagePageViewModel.reactionFilter.value == undefined, ); - messagePageViewModel.isFilterModalOpen.subscribe(setFocusWithDelay); + messagePageViewModel.isFilterModalOpen.subscribe((isOpen) => { + if (isOpen == false) return; + ViewController.setFocusWithDelay(); + }); + React.createProxyState([messagePageViewModel.isFilterModalOpen], () => { + if (messagePageViewModel.isFilterModalOpen.value == false) return; + ViewController.setFocusWithDelay(); + }); return ( diff --git a/src/View/utility.ts b/src/View/utility.ts deleted file mode 100644 index 727cda3f..00000000 --- a/src/View/utility.ts +++ /dev/null @@ -1,23 +0,0 @@ -export function allowDrop(event: DragEvent) { - event.preventDefault(); -} - -export function allowDrag(event: DragEvent) { - event.dataTransfer?.setData("text", ""); -} - -export function reload() { - window.location.reload(); -} - -export function setFocus() { - const focusedInModal = document.querySelector( - ".modal[open] #focused", - ) as HTMLElement; - if (focusedInModal) return focusedInModal.focus(); - document.getElementById("focused")?.focus(); -} - -export function setFocusWithDelay() { - setTimeout(setFocus, 100); -} diff --git a/src/View/viewController.ts b/src/View/viewController.ts index 992f0c9c..9f64ca43 100644 --- a/src/View/viewController.ts +++ b/src/View/viewController.ts @@ -1,48 +1,72 @@ import * as React from "bloatless-react"; -class ItemTracker { - items: React.State | undefined; +class Tracker { + state: React.State | undefined; } -class ViewTrackerMap { - trackers = new Map>(); +class TrackerMap { + trackers = new Map>(); - private register = (key: string): ItemTracker => { + private register = (key: string): Tracker => { if (this.trackers.has(key)) { return this.trackers.get(key); } - const tracker = new ItemTracker(); + const tracker = new Tracker(); this.trackers.set(key, tracker); return tracker; }; - setItems = ( + setState = ( key: string, - views: () => React.State, + stateBuilder: () => React.State, ): React.State | undefined => { - const tracker: ItemTracker = this.register(key); + const tracker: Tracker = this.register(key); - if (tracker.items != undefined) { - return tracker.items; + if (tracker.state != undefined) { + return tracker.state; } else { - tracker.items = views(); - return tracker.items; + tracker.state = stateBuilder(); + return tracker.state; } }; } export class ViewController { - static readonly chatPages = new ItemTracker(); - static readonly taskPages = new ViewTrackerMap(); - static readonly boardPages = new ViewTrackerMap(); - static readonly inlineReplies = new ViewTrackerMap(); - static readonly messageIcons = new ViewTrackerMap(); + static readonly chatPages = new Tracker(); + static readonly taskPages = new TrackerMap(); + static readonly boardPages = new TrackerMap(); + static readonly inlineReplies = new TrackerMap(); - static scrollToView(id: string) { + static readonly scrollToView = (id: string) => { const element = document.getElementById(id); - if (!element) return + if (!element) return; element.scrollIntoView(); element.setAttribute("scrolled", ""); - setTimeout(()=>element.removeAttribute("scrolled"), 1000); - } + setTimeout(() => element.removeAttribute("scrolled"), 1000); + }; + + static readonly allowDrop = (event: DragEvent) => { + event.preventDefault(); + }; + + static readonly allowDrag = (event: DragEvent) => { + event.dataTransfer?.setData("text", ""); + }; + + static readonly reload = () => { + window.location.reload(); + }; + + static readonly setFocus = () => { + const focusedInModal = document.querySelector( + ".modal[open] #focused", + ) as HTMLElement; + if (focusedInModal) return focusedInModal.focus(); + document.getElementById("focused")?.focus(); + }; + + static readonly setFocusWithDelay = () => { + console.trace("focus"); + setTimeout(this.setFocus, 100); + }; } diff --git a/src/ViewModel/Chat/chatMessageViewModel.ts b/src/ViewModel/Chat/chatMessageViewModel.ts index 5a1119ee..aba237ca 100644 --- a/src/ViewModel/Chat/chatMessageViewModel.ts +++ b/src/ViewModel/Chat/chatMessageViewModel.ts @@ -82,12 +82,12 @@ export default class ChatMessageViewModel { }; reply = (): void => { - this.messagePageViewModel.replyingMessage.value = this; + this.messagePageViewModel.setReply(this); this.hideInfoModal(); }; cancelReply = (): void => { if (this.messagePageViewModel.replyingMessage.value != this) return; - this.messagePageViewModel.replyingMessage.value = undefined; + this.messagePageViewModel.setReply(undefined); }; // view @@ -131,7 +131,10 @@ export default class ChatMessageViewModel { this.status.value = this.chatMessage.status; if (this.chatMessage.inlineReplyId) { - this.inlineReply = this.messagePageViewModel.chatMessageViewModels.value.get(this.chatMessage.inlineReplyId); + this.inlineReply = + this.messagePageViewModel.chatMessageViewModels.value.get( + this.chatMessage.inlineReplyId, + ); } }; diff --git a/src/ViewModel/Chat/chatViewModel.ts b/src/ViewModel/Chat/chatViewModel.ts index b54ccb82..2f4c531b 100644 --- a/src/ViewModel/Chat/chatViewModel.ts +++ b/src/ViewModel/Chat/chatViewModel.ts @@ -58,7 +58,7 @@ export default class ChatViewModel extends ContextHost { this.chatListViewModel.openChat(this); //this.selectedPage.callSubscriptions(); }; - + openPage = (page: ChatPageTypes): void => { this.closeCurrentContext(); this.selectedPage.value = page; @@ -142,7 +142,7 @@ export default class ChatViewModel extends ContextHost { handleContextClose = (): void => { this.chatListViewModel.closeChat(); - } + }; // init constructor( diff --git a/src/ViewModel/Global/coreViewModel.ts b/src/ViewModel/Global/coreViewModel.ts index 6a079234..6cf03e45 100644 --- a/src/ViewModel/Global/coreViewModel.ts +++ b/src/ViewModel/Global/coreViewModel.ts @@ -27,7 +27,10 @@ export default class CoreViewModel { history.pushState({ id: context.contextId }, ""); } - closeContext = (contextId: string, fromHistoryEvent: boolean = false): void => { + closeContext = ( + contextId: string, + fromHistoryEvent: boolean = false, + ): void => { if ( !this.contexts .map((context) => context.contextId) @@ -81,7 +84,7 @@ export default class CoreViewModel { window.onpopstate = () => { this.closeContext(this.context.contextId, true); - } + }; } // util @@ -110,7 +113,7 @@ export class Context { this.keystrokes.set(key, fn); }; - constructor(public contextDebugDescription: string) { } + constructor(public contextDebugDescription: string) {} } export class ContextHost extends Context { diff --git a/src/ViewModel/Global/fileTransferViewModel.ts b/src/ViewModel/Global/fileTransferViewModel.ts index ed90df2c..40a9b503 100644 --- a/src/ViewModel/Global/fileTransferViewModel.ts +++ b/src/ViewModel/Global/fileTransferViewModel.ts @@ -171,7 +171,7 @@ export default class FileTransferViewModel extends Context { handleContextClose = (): void => { this.presentedModal.value = undefined; - } + }; // init constructor(public readonly coreViewModel: CoreViewModel) { diff --git a/src/ViewModel/Global/settingsViewModel.ts b/src/ViewModel/Global/settingsViewModel.ts index e678428f..00e6fccf 100644 --- a/src/ViewModel/Global/settingsViewModel.ts +++ b/src/ViewModel/Global/settingsViewModel.ts @@ -71,7 +71,7 @@ export default class SettingsViewModel extends Context { if (this.requiresReload.value == true) { window.location.reload(); } - } + }; // init constructor(public readonly coreViewModel: CoreViewModel) { @@ -102,10 +102,7 @@ export default class SettingsViewModel extends Context { ); // keystrokes - this.registerKeyStroke( - CommonKeys.CloseOrCancel, - this.close, - ); + this.registerKeyStroke(CommonKeys.CloseOrCancel, this.close); } static generateThemeMedia(): MediaQueryList { diff --git a/src/ViewModel/Global/storageViewModel.ts b/src/ViewModel/Global/storageViewModel.ts index 4f4baab5..cb1848a2 100644 --- a/src/ViewModel/Global/storageViewModel.ts +++ b/src/ViewModel/Global/storageViewModel.ts @@ -65,7 +65,7 @@ export default class StorageViewModel extends Context { return; } this.isShowingStorageModal.value = false; - } + }; // init constructor(public readonly coreViewModel: CoreViewModel) { diff --git a/src/ViewModel/Pages/boardViewModel.ts b/src/ViewModel/Pages/boardViewModel.ts index b45515ba..a3a4bb92 100644 --- a/src/ViewModel/Pages/boardViewModel.ts +++ b/src/ViewModel/Pages/boardViewModel.ts @@ -252,7 +252,7 @@ export default class BoardViewModel extends TaskContainingPageViewModel { if (!fromHistoryEvent) return; this.taskPageViewModel.storeLastUsedBoard(); - } + }; // init constructor( diff --git a/src/ViewModel/Pages/messagePageViewModel.ts b/src/ViewModel/Pages/messagePageViewModel.ts index 7f2731c0..dcc2fc86 100644 --- a/src/ViewModel/Pages/messagePageViewModel.ts +++ b/src/ViewModel/Pages/messagePageViewModel.ts @@ -27,6 +27,7 @@ export default class MessagePageViewModel extends Context { undefined, ); composingMessage = new React.State(""); + focusSetter = new React.State(null); // guards cannotSendMessage: React.State; @@ -40,14 +41,11 @@ export default class MessagePageViewModel extends Context { }; sendMessageFromBody = (body: string): void => { - let replyId: string|undefined = undefined; + let replyId: string | undefined = undefined; if (this.replyingMessage.value) { replyId = this.replyingMessage.value.chatMessage.id; } - this.chatViewModel.chatModel.sendMessage( - body, - replyId, - ); + this.chatViewModel.chatModel.sendMessage(body, replyId); this.replyingMessage.value = undefined; }; @@ -72,6 +70,16 @@ export default class MessagePageViewModel extends Context { ); }; + setReply = (chatMessageViewModel: ChatMessageViewModel): void => { + this.replyingMessage.value = chatMessageViewModel; + this.setFocus(); + }; + + resetReply = (): void => { + this.replyingMessage.value = undefined; + this.setFocus(); + }; + // view showChatMessage = (chatMessage: ChatMessage): void => { const chatMessageViewModel = new ChatMessageViewModel( @@ -123,6 +131,10 @@ export default class MessagePageViewModel extends Context { this.searchViewModel.search(""); }; + setFocus = (): void => { + this.focusSetter.callSubscriptions(); + }; + // load loadData = (): void => { this.chatMessageViewModels.clear(); @@ -168,8 +180,15 @@ export default class MessagePageViewModel extends Context { // keystrokes this.registerKeyStroke(CommonKeys.Filter, this.showFilterModal); - this.registerKeyStroke(CommonKeys.CloseOrCancel, this.hideFilterModal); + this.registerKeyStroke(CommonKeys.CloseOrCancel, () => { + if (this.isFilterModalOpen.value == true) { + this.hideFilterModal(); + } else { + this.replyingMessage.value = undefined; + } + }); this.registerKeyStroke(CommonKeys.Reset, this.resetFilter); + this.registerKeyStroke(CommonKeys.Create, this.setFocus); this.chatViewModel.registerContext(ChatPageTypes.Messages, this); } diff --git a/src/ViewModel/Pages/taskPageViewModel.ts b/src/ViewModel/Pages/taskPageViewModel.ts index 27708acf..8944b8da 100644 --- a/src/ViewModel/Pages/taskPageViewModel.ts +++ b/src/ViewModel/Pages/taskPageViewModel.ts @@ -114,10 +114,11 @@ export default class TaskPageViewModel extends ContextHost { closeBoard = (): void => { this.closeCurrentContext(); - } + }; handleBoardClosed = (boardViewModel: BoardViewModel): void => { - if (boardViewModel.boardInfo.fileId != this.selectedBoardId.value) return; + if (boardViewModel.boardInfo.fileId != this.selectedBoardId.value) + return; this.selectedBoardId.value = undefined; this.chatViewModel.resetColor(); diff --git a/src/ViewModel/Pages/taskViewModel.ts b/src/ViewModel/Pages/taskViewModel.ts index 78c44a64..a2402165 100644 --- a/src/ViewModel/Pages/taskViewModel.ts +++ b/src/ViewModel/Pages/taskViewModel.ts @@ -8,9 +8,8 @@ import { localeCompare, padZero } from "../../Model/Utility/utility"; import ChatViewModel from "../Chat/chatViewModel"; import CoreViewModel, { Context } from "../Global/coreViewModel"; import TaskContainingPageViewModel from "./taskContainingPageViewModel"; -import { allowDrag } from "../../View/utility"; -import { v4 } from "uuid"; import { CommonKeys } from "../../View/keystrokes"; +import { ViewController } from "../../View/viewController"; export default class TaskViewModel extends Context { // util @@ -63,7 +62,7 @@ export default class TaskViewModel extends Context { // methods dragStart = (event: DragEvent): void => { - allowDrag(event); + ViewController.allowDrag(event); this.coreViewModel.draggedObject.value = this; };