From 2ae00924c8f58964a01b638732f9a9baf9974b72 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Sat, 18 Apr 2026 11:55:01 +0000 Subject: [PATCH] clean model (A) --- .prettierrc.yaml | 9 + bun.lockb | Bin 12416 -> 13639 bytes dist/index.css | 608 +- dist/index.js | 8571 +++++++++-------- package-lock.json | 75 +- package.json | 3 + src/Model/Chat/chatListModel.ts | 94 +- src/Model/Chat/chatModel.ts | 90 +- src/Model/Files/boardsAndTasksModel.ts | 73 +- src/Model/Files/calendarModel.ts | 31 +- src/Model/Files/fileModel.ts | 53 +- src/Model/Global/connectionModel.ts | 60 +- src/Model/Global/fileTransferModel.ts | 31 +- src/Model/Global/settingsModel.ts | 24 +- src/Model/Global/storageModel.ts | 32 +- src/Model/Utility/crypto.ts | 22 +- src/Upgrader/v1.ts | 19 +- src/View/ChatPages/boardKanbanPage.tsx | 8 +- src/View/ChatPages/boardPage.tsx | 18 +- src/View/ChatPages/boardStatusGridPage.tsx | 10 +- src/View/ChatPages/calendarPage.tsx | 12 +- src/View/ChatPages/messagePage.tsx | 12 +- src/View/ChatPages/settingsPage.tsx | 6 +- src/View/ChatPages/taskPage.tsx | 9 +- src/View/Components/boardEntry.tsx | 3 +- src/View/Components/boardViewToggleButton.tsx | 4 +- src/View/Components/chatEntry.tsx | 1 - src/View/Components/chatMessage.tsx | 10 +- src/View/Components/chatViewToggleButton.tsx | 4 +- src/View/Components/colorPicker.tsx | 1 - src/View/Components/directoryItemList.tsx | 4 +- src/View/Components/filteredList.tsx | 1 - src/View/Components/inlineReply.tsx | 4 +- src/View/Components/messageReactionButton.tsx | 7 +- .../Components/messageReactionButtonRow.tsx | 4 +- src/View/Components/messageReactionEntry.tsx | 2 - .../messageReactionFilterButton.tsx | 7 +- src/View/Components/monthGrid.tsx | 15 +- src/View/Components/propertyValueList.tsx | 1 - src/View/Components/replyPreview.tsx | 3 +- src/View/Components/ribbonButton.tsx | 4 - src/View/Components/taskEntry.tsx | 1 - src/View/Modals/boardSettingsModal.tsx | 9 +- src/View/Modals/chatMessageInfoModal.tsx | 5 +- src/View/Modals/connectionModal.tsx | 3 +- src/View/Modals/dataTransferModal.tsx | 8 +- src/View/Modals/messageFilterModal.tsx | 9 +- src/View/Modals/searchModal.tsx | 9 +- src/View/Modals/settingsModal.tsx | 13 +- src/View/Modals/splitModal.tsx | 5 +- src/View/Modals/storageModal.tsx | 8 +- src/View/Modals/taskSettingsModal.tsx | 4 +- src/View/chatPage.tsx | 19 +- src/View/chatPageWrapper.tsx | 6 +- src/View/homePage.tsx | 21 +- src/ViewModel/Chat/chatListViewModel.ts | 9 +- src/ViewModel/Chat/chatMessageViewModel.ts | 6 +- src/ViewModel/Chat/chatViewModel.ts | 33 +- src/ViewModel/Global/connectionViewModel.ts | 2 - src/ViewModel/Global/coreViewModel.ts | 11 +- src/ViewModel/Global/fileTransferViewModel.ts | 10 +- src/ViewModel/Global/notificationViewModel.ts | 4 +- src/ViewModel/Global/settingsViewModel.ts | 3 +- src/ViewModel/Global/storageViewModel.ts | 6 +- src/ViewModel/Pages/boardViewModel.ts | 18 +- src/ViewModel/Pages/calendarPageViewModel.ts | 14 +- src/ViewModel/Pages/homeViewModel.ts | 10 +- src/ViewModel/Pages/messagePageViewModel.ts | 12 +- src/ViewModel/Pages/settingsPageViewModel.ts | 3 +- .../Pages/taskContainingPageViewModel.ts | 10 +- src/ViewModel/Pages/taskPageViewModel.ts | 12 +- src/ViewModel/Pages/taskViewModel.ts | 14 +- src/ViewModel/Utility/searchViewModel.ts | 1 - .../taskPropertyBulkChangeViewModel.ts | 1 - src/index.tsx | 42 +- 75 files changed, 5139 insertions(+), 5147 deletions(-) create mode 100755 .prettierrc.yaml diff --git a/.prettierrc.yaml b/.prettierrc.yaml new file mode 100755 index 00000000..6bf624fd --- /dev/null +++ b/.prettierrc.yaml @@ -0,0 +1,9 @@ +trailingComma: "all" +tabWidth: 4 +semi: true +singleQuote: false +plugins: + - "prettier-plugin-sort-imports" +sortingMethod: "alphabetical" +sortingOrder: "ascending" +stripNewlines: true diff --git a/bun.lockb b/bun.lockb index d05422a5e2adf717631e4fd7517de14c42313a9a..4925831d69a09c22265297f48e897d821828bcd0 100755 GIT binary patch delta 2617 zcmcgueNa+~`k!91`+IwP zyLY#DZ}<6&Pdfv~w9Ec?3Of63M<(hmElye4jyv}Ba;7V7cf-|%ef@#H^B-p+uBmJ1 zCHuyt1Ox8kDGY(@Mz<48sJD6mjx&X zRPs2K#{mZVeW3V(JOG~K@i>ordE5pV3;MM@F5|I~#~FYlK%c{7Dvxp=MLg9oHvBB`Yq$Zp33mfu!|^j5mt!W}$}A#0e18~1Gw^ot6s8~7 zLhl5u8e(NOaJ+-#qwu5%D^tYrJsj`lcs4!{{cAYhN_co=Btm(ducz_vkyfUToS&<^2y zDTq0(4gyGgiTQ47D+b@`K4L@dLNOltNAnhs5aD(`;)zJelDS#_jc~`23I*qf2cbRI zi1712j!<(uBu5z<5jHe5!H@(ap=yqaJ}C7uXizo?26PxvhKU*)0@Jr_EH3FWB`&%o zw-zlv^mlcWeW2@>Zq?S!wm%AfsW%;Xcl*|HI}LfUN1JZmD1S5RlBl_7x}&E4cFiUY z_@jQ6!Pz(-)>d}wQ+Y#fZooM`d}_Z{U+}q4zuT!!Pg>X2S|RzYy7SAz=KGeBySmPH z3^Ok{xaGp(^*?1_RUfk6Vb6WIuOcZT#IY*Waj#xBv*Sfqar=Vyxh->UuQ0s!WaZsC z^YM7qs+z;jJ!#fz$Ni`?GT-+Xe*HT3=M9O4bFZ1om*-!e9Fafbfs8$&gH#e%Ck~U) z#55Uq$LS(Z!W<+!H5VuJF`?=n9FvfthaAE+ibNnGrpYS~Y=?t6yoaXd!h{pySQ(F{ zFYxI;kb)5e0a_oA z(%dRMMa447*rs}EgbFlDAiWkCw6%JCO*O_s9!W;0$vcuM$~5K}Gc&oVNL2s)YyMlW z&U@~z2s9=m{E!Z(sLf0^o~Jg8L=4`nj#tnM;%Ld;n>D2J$1uiZ9A`FWB9w|xs96iG zIH#`H?v0JJ-WBw;CS}iT|7d63k=25x5J#u53R=UO_EkH3BefR<{aQRJg=Mzk1u%+M zz%voE-pIRI*(!`Wj<=?;7Fsb|QLi<%M>J8;)0*13t~@XE^~e!uJkugvGU~oW zmMrfV^hz9~VVNwPu3;6lgzr$8j$Q02eO?%4!wWU6g_iv2xT!rm4y-sW=;=qn`*{wV z`zhVqf~FCF0G{bb#JHH3=XM{PT_Wh)aJR-hf!<eoSBftogX@< zC&vyqF%6H_PRU1JPbnv#yeHr7t`Epd97{7upX|F`xxGotk9j>FyL*M*ZM&#^Wp;V~ zz*)F*i(gQL;~;AswMr%7?2 zR#8Y-{$Haq3>p8YHU#ge(Oa?FyUJzvl)4=*Z*bMYmEzy@icCQT?gYV*4N))?TVg?{ XF_t<##EPXIp-V8sD`Cc8W$gb450@VG delta 1778 zcmc&#TSyd97(Qp*wcV-I)va}R$JX1fTDI=G8cJZ^V5A^Y5qs#eiUmnML>Y8-BM2=Y zls~#sX^9C*Mh4jpsfV;ks2)NpDv639qN2>Cw*GVW3{%jnbe8}7_|JD{{{3gp`DVB= zRM+m!YQ4Do>YKij9W@o9YdZ6qAH>1_?@SFo_)+Ae7HJlUCqwqNTOp? z(}o6?IWT4s5?Z&VzG=h82GTDPV#E9x@?7M7f;S1S6jhT}E*9)Xjz?V)EDN5*2H5-?!84I#G1nTzlo)U_1nqPZ zI;9mf0G}k4Hb7yNO8a3i;vKk)Xa$Qwr9qB;949%JgWsqc+YN*)h0&-o`UGwnRhkNw z(JF1^c!}c_$5I%_`2!puadZGRvHLRY;rO0oE~J=Mx{c#Cj$w|aP$H{zKeQsgfQPbb zj*|%~z!qfak`^}`u_G32{K<)!C-K>7Wqg^_TnOJRna8W>%+g5Xk^29mbN-i-MEz-b z)c@{092;hW7z4qWqcWV8tl*0>n6+*0ffPrEsTDY=G^H^HQ?v|r>X>B^ z+^mG{#W;*;3=0N3m39mT15Ycr9X#!11_s&>ablo-5EBO437L&y#$aWGDF`^mcu7iN zbNWu%lp2||x*?oC-^QB$KDF{hOKr_JiLUYcy!azCVYNFz9nj$pD4IV!e?D~beD2Lc z1NC`x171HNlLTJ66Z17gd3$*4=dL9a*YyRO<=lGR(VJjXzv+q>7JBSX&7ypz$Jbto zj+J!18tOcDx*m4oD$U?bmZAlN^w52MRU7ns?D?JesF{N%PEGl%?k!rU>zZZWCLdJK zJE>S#M&Ku2$vqaYoFAVVpa)-d>AL2^^>criV_nt>T`|D247*cv?U9L)Ygcy-=z2CZ zXV~*Km#->%us}(&oYQsfJ22Z;{_fO;ZNs{<2}beG+IM4P@XW8r9al?q{TR?pI~uPu hGa%QwBX6!a=?-09$1xCA;ams-rp}z8>s*Q diff --git a/dist/index.css b/dist/index.css index 576bc4f2..3acdf06b 100644 --- a/dist/index.css +++ b/dist/index.css @@ -1,78 +1,3 @@ -/* src/base.css */ -#home-page, -#home-page #chat-section, -#chat-page, -#chat-page #main > div { - animation: slide-up var(--animation-duration) ease; -} -#background-wrapper { - position: fixed; - top: 0px; - left: var(--safe-area-left); - right: var(--safe-area-right); - bottom: 0px; - z-index: 1; - pointer-events: none; - overflow: hidden; - opacity: 0.4; -} -main { - z-index: 2; -} -#background-wrapper div { - content: ""; - position: absolute; - top: -80px; - left: -80px; - right: -80px; - bottom: -80px; - z-index: -1; - pointer-events: none; - filter: blur(40px); -} -#background-wrapper #sky { - 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)); -} -#background-wrapper #grass-1 { - --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-wrapper #grass-2 { - --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%); -} -body[theme=dark] #background-wrapper { - opacity: 0.5; -} -body[theme=dark] #background-wrapper #sky { - --color-1: hsl(188, 100%, 30%); - --color-2: hsl(194, 72%, 24%); -} -body[theme=dark] #background-wrapper #grass-1 { - --color-1: hsl(120, 30%, 30%); - --color-2: hsl(120, 30%, 24%); -} -body[theme=dark] #background-wrapper #grass-2 { - --color-1: hsl(110, 60%, 30%); - --color-2: hsl(110, 50%, 24%); -} - /* src/colors.css */ [color]:not([color=standard]) { --primary: hsl(var(--hue), 100%, var(--accent-luma)); @@ -238,6 +163,143 @@ body[theme=dark] [color]:not([color=standard]).subtle-background { white-space: nowrap; } +/* src/base.css */ +#home-page, +#home-page #chat-section, +#chat-page, +#chat-page #main > div { + animation: slide-up var(--animation-duration) ease; +} +#background-wrapper { + position: fixed; + top: 0px; + left: var(--safe-area-left); + right: var(--safe-area-right); + bottom: 0px; + z-index: 1; + pointer-events: none; + overflow: hidden; + opacity: 0.4; +} +main { + z-index: 2; +} +#background-wrapper div { + content: ""; + position: absolute; + top: -80px; + left: -80px; + right: -80px; + bottom: -80px; + z-index: -1; + pointer-events: none; + filter: blur(40px); +} +#background-wrapper #sky { + 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)); +} +#background-wrapper #grass-1 { + --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-wrapper #grass-2 { + --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%); +} +body[theme=dark] #background-wrapper { + opacity: 0.5; +} +body[theme=dark] #background-wrapper #sky { + --color-1: hsl(188, 100%, 30%); + --color-2: hsl(194, 72%, 24%); +} +body[theme=dark] #background-wrapper #grass-1 { + --color-1: hsl(120, 30%, 30%); + --color-2: hsl(120, 30%, 24%); +} +body[theme=dark] #background-wrapper #grass-2 { + --color-1: hsl(110, 60%, 30%); + --color-2: hsl(110, 50%, 24%); +} + +/* src/View/homePage.css */ +#home-page > div { + display: flex; + flex-direction: row; + gap: var(--large-gap); + scroll-snap-type: x mandatory; + scroll-behavior: smooth; + overflow: hidden; + overflow-x: scroll; + padding: 0px; +} +#home-page > div > div { + 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)); + width: 100%; + min-width: 100%; + height: 100%; + overflow-y: auto; + 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)); +} +#home-page #chat-grid { + margin-top: var(--content-padding); + display: grid; + gap: var(--gap-between-tiles); + grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); +} +@media (min-width: 640px) { + #home-page > div { + overflow-x: hidden; + } + #home-page #overview-section { + width: 20rem; + min-width: 20rem; + } + #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 #chat-grid { + margin-top: 3rem; + } + #home-page #overview-section { + width: var(--max-input-width); + min-width: var(--max-input-width); + } +} + /* src/View/chatPage.css */ #chat-page-wrapper { display: none; @@ -408,179 +470,95 @@ body[showing-chat] #chat-page-wrapper { flex: 1; } -/* src/View/homePage.css */ -#home-page > div { - display: flex; - flex-direction: row; - gap: var(--large-gap); - scroll-snap-type: x mandatory; - scroll-behavior: smooth; - overflow: hidden; - overflow-x: scroll; +/* src/View/ChatPages/taskPage.css */ +#chat-page > div > #main > #task-page > .pane-wrapper > .pane > .main-content { + width: 100%; + max-width: 100%; padding: 0px; } -#home-page > div > div { +#chat-page > 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; - justify-content: start; gap: var(--gap-between-tiles); padding: var(--content-padding); - padding-top: calc(var(--header-height) + var(--content-margin)); +} +#chat-page > div > #main > #task-page .kanban-board-wrapper, +#chat-page > div > #main > #task-page .status-page-content { + display: flex; + align-items: start; + gap: var(--large-gap); width: 100%; - min-width: 100%; - height: 100%; - overflow-y: auto; - 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)); -} -#home-page #chat-grid { - margin-top: var(--content-padding); - display: grid; - gap: var(--gap-between-tiles); - grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); -} -@media (min-width: 640px) { - #home-page > div { - overflow-x: hidden; - } - #home-page #overview-section { - width: 20rem; - min-width: 20rem; - } - #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 #chat-grid { - margin-top: 3rem; - } - #home-page #overview-section { - width: var(--max-input-width); - min-width: var(--max-input-width); - } -} - -/* src/View/ChatPages/calendarPage.css */ -#chat-page > div > #main > #calendar-page > .pane-wrapper > .pane > .toolbar .input-wrapper { - display: flex; - padding: var(--gap-between-tiles); - gap: var(--gap-between-tiles); -} -#chat-page > div > #main > #calendar-page > .pane-wrapper > .pane > .toolbar .input-wrapper > input { - min-height: unset; - height: calc(var(--control-height) - 2 * var(--gap-between-tiles)); - appearance: textfield; - -moz-appearance: textfield; - 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; - margin: 0px; -} -#chat-page > div > #main > #calendar-page > .pane-wrapper > .pane > .toolbar .input-wrapper > .year-input { - width: 4.4rem; -} -#chat-page > div > #main > #calendar-page > .pane-wrapper > .pane > .toolbar .input-wrapper > .month-input { - width: 3.1rem; -} -@media (max-width: 1024px) { - #chat-page > div > #main > #calendar-page { - flex-direction: column; - } - #chat-page > div > #main > #calendar-page .grid-pane-wrapper { - border-bottom: 1px solid var(--border); - } -} - -/* src/View/Components/monthGrid.css */ -.month-grid-wrapper { - display: flex; - flex-direction: column; - gap: var(--gap-between-tiles); height: 100%; overflow: auto; -} -.month-grid-wrapper .day-labels { - flex: 0; 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)); - animation: slide-up var(--animation-duration); -} -.month-grid { - overflow: visible; - padding: var(--content-padding); - padding-top: 0px; - flex: 1; - grid-auto-rows: 1fr; -} -.month-grid .tile { - animation: slide-up var(--animation-duration); - overflow: hidden; - align-items: start; -} -.month-grid .tile[today] b { - color: var(--primary); -} -.month-grid .tile > div { - width: 100%; - height: 100%; -} -.month-grid .tile .event-count { - display: none; - background-color: var(--text-normal); - color: var(--background); - opacity: 0.6; - border-radius: 100%; - width: 1rem; - height: 1rem; - font-size: 0.6rem; - font-weight: bold; - align-items: center; - justify-content: center; -} -.month-grid .tile .event-count[has-events] { - background-color: var(--primary); - opacity: 1; -} -.month-grid .tile .event-list { +#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; - height: 100%; - width: 100%; gap: var(--gap-between-tiles); } -.month-grid .tile .event-list span { - animation: slide-up var(--animation-duration); +#chat-page > div > #main > #task-page .property-input-wrapper { + display: flex; + flex-direction: row; } -@media (max-width: 640px) and (max-height: 1200px) { - .month-grid .tile { - padding: 0px; +#chat-page > div > #main > #task-page .kanban-board-wrapper .kanban-column, +#chat-page > div > #main > #task-page .property-input-wrapper, +#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; +} +#chat-page > div > #main > #task-page .status-name-row, +#chat-page > div > #main > #task-page .status-grid-wrapper { + 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; +} +#chat-page > div > #main > #task-page .status-name-row::before { + content: ""; + display: flex; +} +#chat-page > div > #main > #task-page .board-close-button { + display: none; +} +#chat-page > div > #main > #task-page .board-toggle-button { + display: flex; +} +#chat-page > 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)); } - .month-grid .tile > div { - align-items: center; - justify-content: center; +} +@media (max-width: 1024px) { + #chat-page > div > #main > #task-page #board-list { + display: false; } - .month-grid .tile .event-count { + #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-content { display: flex; } - .month-grid .tile .event-list { + #chat-page > div > #main > #task-page .board-close-button { + display: flex; + } + #chat-page > div > #main > #task-page .board-toggle-button { display: none; } } @@ -810,95 +788,117 @@ body[showing-chat] #chat-page-wrapper { grid-template-columns: repeat(3, 1fr); } -/* src/View/ChatPages/taskPage.css */ -#chat-page > div > #main > #task-page > .pane-wrapper > .pane > .main-content { - width: 100%; - max-width: 100%; - padding: 0px; -} -#chat-page > div > #main > #task-page > .pane-wrapper > .pane > .main-content > div { - animation: slide-up var(--animation-duration); -} -#chat-page > div > #main > #task-page .task-grid { +/* src/View/ChatPages/calendarPage.css */ +#chat-page > div > #main > #calendar-page > .pane-wrapper > .pane > .toolbar .input-wrapper { display: flex; - flex-direction: column; - gap: var(--gap-between-tiles); - padding: var(--content-padding); -} -#chat-page > div > #main > #task-page .kanban-board-wrapper, -#chat-page > div > #main > #task-page .status-page-content { - display: flex; - align-items: start; - gap: var(--large-gap); - width: 100%; - height: 100%; - overflow: auto; - 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; + padding: var(--gap-between-tiles); gap: var(--gap-between-tiles); } -#chat-page > div > #main > #task-page .property-input-wrapper { - display: flex; - flex-direction: row; +#chat-page > div > #main > #calendar-page > .pane-wrapper > .pane > .toolbar .input-wrapper > input { + min-height: unset; + height: calc(var(--control-height) - 2 * var(--gap-between-tiles)); + appearance: textfield; + -moz-appearance: textfield; + padding-right: 0px; } -#chat-page > div > #main > #task-page .kanban-board-wrapper .kanban-column, -#chat-page > div > #main > #task-page .property-input-wrapper, -#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; +#calendar-page .input-wrapper > input::-webkit-outer-spin-button, +#calendar-page .input-wrapper > input::-webkit-innner-spin-button { + appearance: none; + -webkit-appearance: none; + margin: 0px; } -#chat-page > div > #main > #task-page .status-name-row, -#chat-page > div > #main > #task-page .status-grid-wrapper { - display: flex; - gap: var(--large-gap); +#chat-page > div > #main > #calendar-page > .pane-wrapper > .pane > .toolbar .input-wrapper > .year-input { + width: 4.4rem; } -#chat-page > div > #main > #task-page .status-page-content, -#chat-page > div > #main > #task-page .status-grid-wrapper { - flex-direction: column; -} -#chat-page > div > #main > #task-page .status-name-row::before { - content: ""; - display: flex; -} -#chat-page > div > #main > #task-page .board-close-button { - display: none; -} -#chat-page > div > #main > #task-page .board-toggle-button { - display: flex; -} -#chat-page > 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 > #calendar-page > .pane-wrapper > .pane > .toolbar .input-wrapper > .month-input { + width: 3.1rem; } @media (max-width: 1024px) { - #chat-page > div > #main > #task-page #board-list { - display: false; + #chat-page > div > #main > #calendar-page { + flex-direction: column; } - #chat-page > div > #main > #task-page #board-content { - display: none; + #chat-page > div > #main > #calendar-page .grid-pane-wrapper { + border-bottom: 1px solid var(--border); } - #chat-page > div > #main > #task-page[isshowingboard] #board-list { - display: none; +} + +/* src/View/Components/monthGrid.css */ +.month-grid-wrapper { + display: flex; + flex-direction: column; + gap: var(--gap-between-tiles); + height: 100%; + overflow: auto; +} +.month-grid-wrapper .day-labels { + flex: 0; + 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)); + animation: slide-up var(--animation-duration); +} +.month-grid { + overflow: visible; + padding: var(--content-padding); + padding-top: 0px; + flex: 1; + grid-auto-rows: 1fr; +} +.month-grid .tile { + animation: slide-up var(--animation-duration); + overflow: hidden; + align-items: start; +} +.month-grid .tile[today] b { + color: var(--primary); +} +.month-grid .tile > div { + width: 100%; + height: 100%; +} +.month-grid .tile .event-count { + display: none; + background-color: var(--text-normal); + color: var(--background); + opacity: 0.6; + border-radius: 100%; + width: 1rem; + height: 1rem; + font-size: 0.6rem; + font-weight: bold; + align-items: center; + justify-content: center; +} +.month-grid .tile .event-count[has-events] { + background-color: var(--primary); + opacity: 1; +} +.month-grid .tile .event-list { + display: flex; + flex-direction: column; + height: 100%; + width: 100%; + gap: var(--gap-between-tiles); +} +.month-grid .tile .event-list span { + animation: slide-up var(--animation-duration); +} +@media (max-width: 640px) and (max-height: 1200px) { + .month-grid .tile { + padding: 0px; } - #chat-page > div > #main > #task-page[isshowingboard] #board-content { + .month-grid .tile > div { + align-items: center; + justify-content: center; + } + .month-grid .tile .event-count { display: flex; } - #chat-page > div > #main > #task-page .board-close-button { - display: flex; - } - #chat-page > div > #main > #task-page .board-toggle-button { + .month-grid .tile .event-list { display: none; } } diff --git a/dist/index.js b/dist/index.js index 713f345d..0503a9e3 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,9 +1,10 @@ (() => { - // ../../bloatless-react/index.ts + // node_modules/bloatless-react/index.ts var State = class { + _value; + _bindings = /* @__PURE__ */ new Set(); // init constructor(initialValue) { - this._bindings = /* @__PURE__ */ new Set(); this._value = initialValue; } // value @@ -31,16 +32,15 @@ } }; var ListState = class extends State { + additionHandlers = /* @__PURE__ */ new Set(); + removalHandlers = /* @__PURE__ */ new Map(); // init constructor(initialItems) { super(new Set(initialItems)); - this.additionHandlers = /* @__PURE__ */ new Set(); - this.removalHandlers = /* @__PURE__ */ new Map(); } // list add(...items) { items.forEach((item) => { - if (this.value.has(item)) return; this.value.add(item); this.additionHandlers.forEach((handler) => handler(item)); }); @@ -76,11 +76,11 @@ } }; var MapState = class extends State { + additionHandlers = /* @__PURE__ */ new Set(); + removalHandlers = /* @__PURE__ */ new Map(); // init constructor(initialItems) { super(new Map(initialItems)); - this.additionHandlers = /* @__PURE__ */ new Set(); - this.removalHandlers = /* @__PURE__ */ new Map(); } // list set(key, item) { @@ -237,510 +237,6 @@ return element; } - // src/Model/Utility/typeSafety.ts - var DATA_VERSION = "v2"; - function checkIsValidObject(object) { - return object.dataVersion == DATA_VERSION; - } - function checkMatchesObjectStructure(objectToCheck, reference) { - 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) { - if (objectToCheck[key].length == 0) continue; - if (objectToCheck[key].size == 0) continue; - const [firstOfObjectToCheck] = objectToCheck[key]; - const [fisrtOfReference] = reference[key]; - const doesFirstItemMatch = checkMatchesObjectStructure( - firstOfObjectToCheck, - fisrtOfReference - ); - if (doesFirstItemMatch == false) return false; - } else if (requiredType == "object") { - const doesNestedObjectMatch = checkMatchesObjectStructure( - objectToCheck[key], - reference[key] - ); - if (doesNestedObjectMatch == false) return false; - } - } - return true; - } - - // src/Model/Utility/utility.ts - function generateRandomToken(length) { - const array = new Uint8Array(length); - crypto.getRandomValues(array); - const string = array.join(""); - return string.substring(0, length); - } - function createTimestamp() { - return (/* @__PURE__ */ new Date()).toISOString(); - } - function checkDoesObjectMatchReference(reference, stringEntryObject, explicitEmptyValue = false) { - reference_entry_loop: for (const referenceEntry of Object.entries( - reference - )) { - const [referenceKey, referenceValue] = referenceEntry; - const stringEntryObjectValue = stringEntryObject[referenceKey]; - if (referenceValue == void 0) return false; - if (referenceValue[0] == "-") { - const strippedReferenceValue = referenceValue.toString().substring(1); - if (strippedReferenceValue == "" && stringEntryObjectValue != void 0 && stringEntryObjectValue != "") { - return false; - } - if (stringEntryObjectValue == strippedReferenceValue) { - return false; - } - } else { - if (explicitEmptyValue == false) { - if (referenceValue == "" && (stringEntryObjectValue == void 0 || stringEntryObjectValue == "")) { - return false; - } else if (referenceValue == "") { - continue reference_entry_loop; - } - } - if (stringEntryObjectValue != referenceValue) { - return false; - } - } - } - return true; - } - function collectObjectValuesForKey(key, converter, objects) { - const values = /* @__PURE__ */ new Set(); - for (const object of objects) { - const stringEntryObject = converter(object); - const stringEntryObjectValue = stringEntryObject[key]; - if (stringEntryObjectValue == void 0) continue; - values.add(stringEntryObjectValue.toString()); - } - return [...values.values()]; - } - function checkDoesObjectMatchSearch(query, getStringsOfObject, object) { - if (query == "") return true; - const stringsInObject = getStringsOfObject(object); - const wordsInObject = []; - 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] == "-") { - const wordContent = queryWord.substring(1); - if (wordsInObject.includes(wordContent)) { - return false; - } - } else { - if (wordsInObject.includes(queryWord) == false) { - return false; - } - } - } - return true; - } - var HandlerManager = class { - constructor() { - this.handlers = /* @__PURE__ */ new Map(); - // manage - this.setHandler = (id, handler) => { - this.handlers.set(id, handler); - }; - this.deleteHandler = (id) => { - this.handlers.delete(id); - }; - // trigger - this.trigger = (item) => { - [...this.handlers.values()].forEach((handler) => handler(item)); - }; - } - }; - var IndexManager = class { - // init - constructor(itemToString) { - this.sortedStrings = []; - // methods - this.update = (items) => { - this.sortedStrings = []; - let strings = []; - for (const item of items) { - const string = this.itemToString(item); - strings.push(string); - } - this.sortedStrings = strings.sort(localeCompare); - }; - this.getIndex = (item) => { - const string = this.itemToString(item); - const index = this.sortedStrings.indexOf(string); - return index; - }; - this.itemToString = itemToString; - } - }; - function getLocalStorageItemAndClear(key) { - const value = localStorage.getItem(key); - localStorage.removeItem(key); - if (value != null) localStorage.setItem(`_${key}`, value); - return value; - } - function stringify(data) { - return JSON.stringify(data, null, 4); - } - function padZero(string, length) { - return (string ?? "").padStart(length, "0"); - } - function parse(string) { - try { - return JSON.parse(string); - } catch { - return {}; - } - } - function parseValidObject(string, reference) { - const parsed = parse(string); - if (checkIsValidObject(parsed) == false) return null; - const doesMatchReference = checkMatchesObjectStructure( - parsed, - reference - ); - if (doesMatchReference == false) return null; - return parsed; - } - function parseOrFallback(inputString) { - try { - return JSON.parse(inputString); - } catch { - return inputString; - } - } - function parseArray(inputString) { - const parsed = parseOrFallback(inputString); - if (Array.isArray(parsed) == false) return []; - return parsed; - } - function localeCompare(a, b) { - return a.localeCompare(b); - } - - // src/Model/Global/storageModel.ts - var PATH_COMPONENT_SEPARATOR = "\\"; - var StorageModel = class _StorageModel { - // init - constructor() { - this.storageEntryTree = {}; - // read - this.read = (pathComponents) => { - const pathString = _StorageModel.pathComponentsToString( - ...pathComponents - ); - return localStorage.getItem(pathString); - }; - this.list = (pathComponents) => { - let currentParent = this.storageEntryTree; - for (const component of pathComponents) { - const nextParent = currentParent[component]; - if (nextParent == void 0) return []; - currentParent = nextParent; - } - return [...Object.keys(currentParent).sort(localeCompare)]; - }; - // write - this.write = (pathComponents, value) => { - const pathString = _StorageModel.pathComponentsToString( - ...pathComponents - ); - localStorage.setItem(pathString, value); - this.updateTree(...pathComponents); - }; - this.remove = (pathComponents, shouldInitialize = true) => { - const pathString = _StorageModel.pathComponentsToString( - ...pathComponents - ); - localStorage.removeItem(pathString); - if (shouldInitialize == true) { - this.initializeTree(); - } - }; - this.rename = (sourcePathComponents, destinationPathComponents, shouldInitialize = true) => { - const content = this.read(sourcePathComponents); - if (content == null) return false; - this.write(destinationPathComponents, content); - this.remove(sourcePathComponents); - if (shouldInitialize == true) { - this.initializeTree(); - } - return true; - }; - // recursion - this.recurse = (rootDirectory, fn) => { - loop_over_files: for (const key of Object.keys(localStorage)) { - const pathComponentsOfCurrentEntity = _StorageModel.stringToPathComponents(key); - 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; - } - fn(pathComponentsOfCurrentEntity); - } - this.initializeTree(); - }; - this.removeRecursively = (pathComponents) => { - this.recurse( - pathComponents, - (path) => this.remove(path, false) - ); - this.initializeTree(); - }; - this.renameRecursively = (sourcePathComponents, destinationPathComponents) => { - this.recurse(sourcePathComponents, (path) => { - const relativePathOfCurrentEntity = path.slice( - sourcePathComponents.length - ); - const destinationPathComponentsOfCurrentEntity = [ - ...destinationPathComponents, - ...relativePathOfCurrentEntity - ]; - this.rename(path, destinationPathComponentsOfCurrentEntity, false); - }); - this.initializeTree(); - }; - // stringifiable - this.writeStringifiable = (pathComponents, value) => { - const valueString = stringify(value); - this.write(pathComponents, valueString); - }; - this.readStringifiable = (pathComponents, reference) => { - const valueString = this.read(pathComponents); - if (!valueString) return null; - const object = parseValidObject(valueString, reference); - if (object == null) return null; - return object; - }; - // cleaning - this.removeJunk = () => { - this.recurse([], (path) => { - if (path[0] == DATA_VERSION) return; - this.remove(path); - }); - }; - // tree - this.initializeTree = () => { - console.log("initializing tree"); - this.storageEntryTree = {}; - for (const key of Object.keys(localStorage)) { - const components = _StorageModel.stringToPathComponents(key); - this.updateTree(...components); - } - }; - this.updateTree = (...pathComponents) => { - let currentParent = this.storageEntryTree; - for (const pathPart of pathComponents) { - if (!currentParent[pathPart]) { - currentParent[pathPart] = {}; - } - currentParent = currentParent[pathPart]; - } - }; - this.printTree = () => { - return stringify(this.storageEntryTree); - }; - this.initializeTree(); - } - static { - // utility - this.getFileName = (pathComponents) => { - return pathComponents[pathComponents.length - 1] || "\\"; - }; - } - static { - this.getFileNameFromString = (pathString) => { - const pathComponents = this.stringToPathComponents(pathString); - return pathComponents[pathComponents.length - 1] || "\\"; - }; - } - static { - this.pathComponentsToString = (...pathComponents) => { - return pathComponents.filter((x) => x != "").join(PATH_COMPONENT_SEPARATOR); - }; - } - static { - this.stringToPathComponents = (string) => { - return string.split(PATH_COMPONENT_SEPARATOR).filter((x) => x != ""); - }; - } - static { - this.join = (...items) => { - let allComponents = []; - for (const item of items) { - const parts = this.stringToPathComponents(item); - allComponents.push(...parts); - } - return _StorageModel.pathComponentsToString(...allComponents); - }; - } - static getPath(locationName, filePath) { - return [DATA_VERSION, locationName, ...filePath]; - } - }; - var filePaths = { - connectionModel: { - socketAddress: ["socket-address"], - reconnectAddress: ["reconnect-address"], - outbox: ["outbox"], - mailboxes: ["mailboxes"], - previousAddresses: ["previous-addresses"] - }, - chat: { - base: [], - chatBase: (id) => [id], - info: (id) => [...filePaths.chat.chatBase(id), "info"], - color: (id) => [...filePaths.chat.chatBase(id), "color"], - messages: (id) => [...filePaths.chat.chatBase(id), "messages"], - reactions: (id) => [ - ...filePaths.chat.chatBase(id), - "reactions" - ], - lastUsedPage: (id) => [ - ...filePaths.chat.chatBase(id), - "last-used-page" - ], - files: (id) => [...filePaths.chat.chatBase(id), "files"] - }, - notificationModel: { - base: [] - }, - settingsModel: { - username: ["user-name"], - firstDayOfWeek: ["first-day-of-week"], - language: ["language"], - theme: ["theme"] - } - }; - - // src/Model/Files/calendarModel.ts - var CalendarModel = class _CalendarModel { - // init - constructor(storageModel2, settingsModel2, fileModel) { - // paths - this.getBasePath = () => { - return this.fileModel.getModelContainerPath( - "calendar" /* ModelCalendar */ - ); - }; - this.getViewPath = () => { - return [...this.getBasePath(), "view" /* ModelView */]; - }; - this.getMonthContainerPath = () => { - return [...this.getBasePath(), "months" /* Months */]; - }; - this.getMonthPath = (monthString) => { - return [...this.getMonthContainerPath(), monthString]; - }; - // main - this.storeTaskReference = (taskFileContent) => { - if (taskFileContent.date == void 0) return; - const monthString = _CalendarModel.isoToMonthString( - taskFileContent.date - ); - const monthPath = this.getMonthPath(monthString); - const referencePath = [...monthPath, taskFileContent.fileId]; - this.storageModel.write(referencePath, ""); - }; - this.deleteTaskReference = (monthString, taskId) => { - const monthPath = this.getMonthPath(monthString); - const referencePath = [...monthPath, taskId]; - this.storageModel.write(referencePath, ""); - }; - this.listTaskIds = (monthString) => { - const monthPath = this.getMonthPath(monthString); - return this.storageModel.list(monthPath); - }; - this.generateMonthGrid = (coreViewModel2, year, month, defaultValueCreator) => { - const date = coreViewModel2.unwrappedTodayDate; - const isCurrentMonth = year == date.getFullYear() && month == date.getMonth() + 1; - date.setDate(1); - date.setMonth(month - 1); - date.setFullYear(year); - const firstWeekdayOfMonth = date.getDay(); - const firstDayOfWeekSetting = parseInt( - this.settingsModel.firstDayOfWeek - ); - const offset = firstWeekdayOfMonth < firstDayOfWeekSetting ? 7 - firstDayOfWeekSetting : firstWeekdayOfMonth - firstDayOfWeekSetting; - date.setMonth(month); - date.setDate(-1); - const daysInMonth = date.getDate() + 1; - const grid = { - offset, - firstDayOfWeek: parseInt(this.settingsModel.firstDayOfWeek), - isCurrentMonth, - year, - month, - days: {} - }; - for (let i = 0; i < daysInMonth; i++) { - const paddedDate = _CalendarModel.padDateOrMonth( - (i + 1).toString() - ); - grid.days[paddedDate] = defaultValueCreator(); - } - return grid; - }; - this.storageModel = storageModel2; - this.settingsModel = settingsModel2; - this.fileModel = fileModel; - } - static { - // utility - this.isoToMonthString = (dateISOString) => { - const [year, month, _] = dateISOString.split("-"); - return _CalendarModel.getMonthString(year, month); - }; - } - static { - this.isoToDateString = (dateISOString) => { - const [year, month, date, _] = dateISOString.split("-"); - const paddedDate = _CalendarModel.padDateOrMonth(date ?? ""); - return paddedDate; - }; - } - static { - this.getMonthString = (year = "", month = "") => { - const paddedYear = year.padStart(4, "0"); - const paddedMonth = _CalendarModel.padDateOrMonth(month); - return `${paddedYear}-${paddedMonth}`; - }; - } - static { - this.getISODateString = (year, month, date) => { - const monthString = _CalendarModel.getMonthString(year, month); - const paddedDate = _CalendarModel.padDateOrMonth(date); - return `${monthString}-${paddedDate}`; - }; - } - static { - this.padDateOrMonth = (input) => { - return input.padStart(2, "0"); - }; - } - }; - - // src/colors.ts - var Colors = /* @__PURE__ */ ((Colors2) => { - Colors2["Standard"] = "standard"; - Colors2["Coral"] = "coral"; - Colors2["Yellow"] = "yellow"; - Colors2["Green"] = "green"; - Colors2["LightBlue"] = "lightblue"; - Colors2["Blue"] = "blue"; - Colors2["purple"] = "purple"; - return Colors2; - })(Colors || {}); - // node_modules/uuid/dist/esm-browser/stringify.js var byteToHex = []; for (i = 0; i < 256; ++i) { @@ -790,846 +286,6 @@ } var v4_default = v4; - // src/Model/Files/boardsAndTasksModel.ts - var BoardsAndTasksModel = class _BoardsAndTasksModel { - // init - constructor(storageModel2, settingsModel2, chatModel, fileModel) { - // data - this.boardHandlerManager = new HandlerManager(); - this.taskHandlerManager = new HandlerManager(); - // paths - this.getBasePath = () => { - return this.fileModel.getModelContainerPath("tasks" /* ModelTask */); - }; - this.getViewPath = () => { - return [...this.getBasePath(), "view" /* ModelView */]; - }; - this.getBoardFilePath = (boardId) => { - return [...this.fileModel.getFilePath(boardId)]; - }; - this.getTaskFilePath = (taskId) => { - return [...this.fileModel.getFilePath(taskId)]; - }; - this.getBoardContainerPath = () => { - return [...this.getBasePath(), "boards" /* Boards */]; - }; - this.getBoardDirectoryPath = (boardId) => { - return [...this.getBoardContainerPath(), boardId]; - }; - this.getTaskContainerPath = (boardId) => { - return [ - ...this.getBoardDirectoryPath(boardId), - "tasks" /* BoardTasks */ - ]; - }; - this.getTaskReferencePath = (boardId, fileId) => { - return [...this.getTaskContainerPath(boardId), fileId]; - }; - // handlers - this.handleFileContent = (fileContent) => { - if (checkMatchesObjectStructure( - fileContent, - BoardInfoFileContentReference - ) == true) { - this.handleBoard(fileContent); - } else if (checkMatchesObjectStructure( - fileContent, - TaskFileContentReference - ) == true) { - this.handleTask(fileContent); - } - }; - this.handleBoard = (boardInfoFileContent) => { - this.updateBoard(boardInfoFileContent); - }; - this.handleTask = (taskFileContent) => { - this.updateTask(taskFileContent); - }; - // boards - this.createBoard = (name) => { - const boardInfoFileContent = _BoardsAndTasksModel.createBoardInfoFileContent( - v4_default(), - name, - "standard" /* Standard */ - ); - return boardInfoFileContent; - }; - this.updateBoard = (boardInfoFileContent) => { - this.storeBoard(boardInfoFileContent); - this.boardHandlerManager.trigger(boardInfoFileContent); - }; - this.updateBoardAndSend = (boardInfoFileContent) => { - this.updateBoard(boardInfoFileContent); - this.chatModel.sendMessage("", void 0, boardInfoFileContent); - }; - this.storeBoard = (boardInfoFileContent) => { - this.fileModel.storeFileContent(boardInfoFileContent); - const boardDirectoryPath = this.getBoardDirectoryPath( - boardInfoFileContent.fileId - ); - this.storageModel.write(boardDirectoryPath, ""); - }; - this.deleteBoard = (boardId) => { - const boardFilePath = this.getBoardFilePath(boardId); - const boardDirectoryPath = this.getBoardDirectoryPath(boardId); - this.storageModel.removeRecursively(boardFilePath); - this.storageModel.removeRecursively(boardDirectoryPath); - }; - this.listBoardIds = () => { - const boardContainerPath = this.getBoardContainerPath(); - const boardIds = this.storageModel.list(boardContainerPath); - return boardIds; - }; - this.getBoardInfo = (fileId) => { - const boardInfoFileContentOrNull = this.fileModel.getLatestFileContent( - fileId, - BoardInfoFileContentReference - ); - return boardInfoFileContentOrNull; - }; - this.getBoardName = (boardId) => { - const boardInfo = this.getBoardInfo(boardId); - if (boardInfo == null) return ""; - return boardInfo.name; - }; - //tasks - this.createTask = (boardId) => { - const taskFileContent = _BoardsAndTasksModel.createTaskFileContent(v4_default(), "", boardId); - return taskFileContent; - }; - this.updateTask = (taskFileContent) => { - this.storeTask(taskFileContent); - this.taskHandlerManager.trigger(taskFileContent); - }; - this.updateTaskAndSend = (taskFileContent) => { - this.updateTask(taskFileContent); - this.chatModel.sendMessage("", void 0, taskFileContent); - }; - this.storeTask = (taskFileContent) => { - this.fileModel.storeFileContent(taskFileContent); - const taskReferencePath = this.getTaskReferencePath( - taskFileContent.boardId, - taskFileContent.fileId - ); - this.storageModel.write(taskReferencePath, ""); - this.calendarModel.storeTaskReference(taskFileContent); - }; - this.listTaskIds = (boardId) => { - const taskContainerPath = this.getTaskContainerPath(boardId); - const fileIds = this.storageModel.list(taskContainerPath); - return fileIds; - }; - this.listTaskVersionIds = (taskId) => { - const versionIds = this.fileModel.listFileContentIds(taskId); - return versionIds; - }; - this.getLatestTaskFileContent = (taskId) => { - const taskFileContentOrNull = this.fileModel.getLatestFileContent( - taskId, - TaskFileContentReference - ); - return taskFileContentOrNull; - }; - this.getSpecificTaskFileContent = (taskId, versionId) => { - const taskFileContentOrNull = this.fileModel.getFileContent( - taskId, - versionId, - TaskFileContentReference - ); - return taskFileContentOrNull; - }; - this.deleteTask = (boardId, taskId) => { - const taskFilePath = this.getTaskFilePath(taskId); - this.storageModel.removeRecursively(taskFilePath); - this.deleteTaskReference(boardId, taskId); - }; - this.deleteTaskReference = (boardId, taskId) => { - const taskReferencePath = this.getTaskReferencePath( - boardId, - taskId - ); - this.storageModel.removeRecursively(taskReferencePath); - }; - this.storageModel = storageModel2; - this.settingsModel = settingsModel2; - this.chatModel = chatModel; - this.fileModel = fileModel; - this.calendarModel = new CalendarModel( - this.storageModel, - this.settingsModel, - this.fileModel - ); - } - static { - // utility - this.createBoardInfoFileContent = (fileId, name, color) => { - const fileContent = FileModel2.createFileContent(fileId, "board-info"); - return { - ...fileContent, - name, - color - }; - }; - } - static { - this.createTaskFileContent = (fileId, name, boardId) => { - const fileContent = FileModel2.createFileContent( - fileId, - "task" - ); - return { - ...fileContent, - name, - boardId - }; - }; - } - }; - var BoardInfoFileContentReference = { - dataVersion: DATA_VERSION, - fileId: "string", - fileContentId: "", - creationDate: "", - type: "board-info", - name: "", - color: "" - }; - var TaskFileContentReference = { - dataVersion: DATA_VERSION, - fileId: "string", - fileContentId: "", - creationDate: "", - type: "task", - name: "", - boardId: "" - }; - - // src/Model/Files/fileModel.ts - var FileModel2 = class _FileModel { - // init - constructor(storageModel2, settingsModel2, chatModel) { - // paths - this.getBasePath = () => { - return StorageModel.getPath( - "chat" /* Chat */, - filePaths.chat.files(this.chatModel.id) - ); - }; - this.getFileContainerPath = () => { - return [...this.getBasePath(), "data" /* Data */]; - }; - this.getModelContainerPath = (modelName) => { - return [...this.getBasePath(), "model" /* Model */, modelName]; - }; - this.getFilePath = (fileId) => { - return [...this.getFileContainerPath(), fileId]; - }; - this.getFileContentPath = (fileId, fileContentId) => { - const filePath = this.getFilePath(fileId); - return [...filePath, fileContentId]; - }; - // handlers - this.handleStringifiedFileContent = (stringifiedFileContent) => { - const fileContent = parseValidObject( - stringifiedFileContent, - FileContentReference - ); - if (fileContent == null) return; - this.handleFileContent(fileContent); - }; - this.handleFileContent = (fileContent) => { - const didStore = this.storeFileContent(fileContent); - if (didStore == false) return; - this.boardsAndTasksModel.handleFileContent(fileContent); - this.chatModel.handleReaction(fileContent); - }; - // methods - this.addFileContentAndSend = (fileContent) => { - this.handleFileContent(fileContent); - this.chatModel.sendMessage("", void 0, fileContent); - }; - // storage - this.storeFileContent = (fileContent) => { - const fileContentPath = this.getFileContentPath( - fileContent.fileId, - fileContent.fileContentId - ); - const existingFileContent = this.storageModel.read(fileContentPath); - if (existingFileContent != null) return false; - const stringifiedContent = stringify(fileContent); - this.storageModel.write(fileContentPath, stringifiedContent); - return true; - }; - this.listFileIds = () => { - return this.storageModel.list(this.getBasePath()); - }; - this.listFileContentIds = (fileId) => { - const filePath = this.getFilePath(fileId); - return this.storageModel.list(filePath); - }; - this.selectLatestFileContentId = (fileContentIds) => { - return fileContentIds[fileContentIds.length - 1]; - }; - this.getFileContent = (fileId, fileContentName, reference) => { - const filePath = this.getFileContentPath( - fileId, - fileContentName - ); - const fileContentOrNull = this.storageModel.readStringifiable( - filePath, - reference - ); - return fileContentOrNull; - }; - this.getLatestFileContent = (fileId, reference) => { - const fileContentsIds = this.listFileContentIds(fileId); - const latestFileContentId = this.selectLatestFileContentId(fileContentsIds); - if (latestFileContentId == void 0) return null; - const fileContent = this.getFileContent( - fileId, - latestFileContentId, - reference - ); - return fileContent; - }; - this.chatModel = chatModel; - this.settingsModel = settingsModel2; - this.storageModel = storageModel2; - this.boardsAndTasksModel = new BoardsAndTasksModel( - this.storageModel, - this.settingsModel, - chatModel, - this - ); - } - static { - // utility - this.generateFileContentId = (creationDate) => { - return creationDate + v4_default(); - }; - } - static { - this.createFileContent = (fileId, type) => { - const creationDate = createTimestamp(); - const fileContentId = _FileModel.generateFileContentId(creationDate); - return { - dataVersion: DATA_VERSION, - fileId, - fileContentId, - creationDate, - type - }; - }; - } - }; - var FileContentReference = { - dataVersion: DATA_VERSION, - fileId: "", - fileContentId: "", - creationDate: "", - type: "" - }; - - // src/Model/Utility/crypto.ts - var IV_SIZE = 12; - var ENCRYPTION_ALG = "AES-GCM"; - async function encryptString(plaintext, passphrase) { - if (!window.crypto.subtle) return 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)); - } - async function decryptString(cyphertext, passphrase) { - 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; - } - } - function encode(string) { - return new TextEncoder().encode(string); - } - function decode(array) { - return new TextDecoder("utf-8").decode(array); - } - async function encrypt(iv, key, message) { - const arrayBuffer = await window.crypto.subtle.encrypt( - { name: ENCRYPTION_ALG, iv }, - key, - encode(message) - ); - return new Uint8Array(arrayBuffer); - } - async function decrypt(iv, key, cyphertext) { - const arrayBuffer = await crypto.subtle.decrypt( - { name: ENCRYPTION_ALG, iv }, - key, - cyphertext - ); - return arrayBufferToString(arrayBuffer); - } - async function hash(encoded) { - return await crypto.subtle.digest("SHA-256", encoded); - } - function generateIV() { - return crypto.getRandomValues(new Uint8Array(IV_SIZE)); - } - async function importKey(passphrase, purpose) { - return await crypto.subtle.importKey( - "raw", - await hash(encode(passphrase)), - { name: ENCRYPTION_ALG }, - false, - [purpose] - ); - } - function arrayBufferToString(arrayBuffer) { - const uInt8Array = new Uint8Array(arrayBuffer); - return decode(uInt8Array); - } - function uInt8ToArray(uInt8Array) { - return Array.from(uInt8Array); - } - function arrayToUint8(array) { - return new Uint8Array(array); - } - - // src/Model/Chat/chatModel.ts - var ChatModel = class _ChatModel { - // init - constructor(storageModel2, connectionModel2, settingsModel2, chatListModel2, chatId) { - this.chatMessageHandlerManager = new HandlerManager(); - this.reactionHandlerManager = new HandlerManager(); - // paths - this.getBasePath = () => { - return StorageModel.getPath( - "chat" /* Chat */, - filePaths.chat.chatBase(this.id) - ); - }; - this.getInfoPath = () => { - return StorageModel.getPath( - "chat" /* Chat */, - filePaths.chat.info(this.id) - ); - }; - this.getColorPath = () => { - return StorageModel.getPath( - "chat" /* Chat */, - filePaths.chat.color(this.id) - ); - }; - this.getMessageDirPath = () => { - return StorageModel.getPath( - "chat" /* Chat */, - filePaths.chat.messages(this.id) - ); - }; - this.getMessagePath = (id) => { - return [...this.getMessageDirPath(), id]; - }; - this.getReactionDirPath = () => { - return StorageModel.getPath( - "chat" /* Chat */, - filePaths.chat.reactions(this.id) - ); - }; - this.getReactionPath = (id) => { - return [...this.getReactionDirPath(), id]; - }; - // handlers - this.handleMessage = (body) => { - const chatMessage = parseValidObject( - body, - ChatMessageReference - ); - if (chatMessage == null) return; - chatMessage.status = "received" /* Received */; - this.addMessage(chatMessage); - if (chatMessage.stringifiedFile) return; - this.setReadStatus(true); - }; - this.handleReaction = (reaction) => { - if (!checkMatchesObjectStructure(reaction, ChatMessageReactionReference)) - return; - const reactionPath = this.getReactionPath(reaction.fileId); - if (reaction.isDeleting == true) { - this.storageModel.remove(reactionPath); - } else { - this.storageModel.writeStringifiable(reactionPath, reaction); - } - this.reactionHandlerManager.trigger(reaction); - }; - this.handleMessageSent = (chatMessage) => { - chatMessage.status = "sent" /* Sent */; - this.addMessage(chatMessage); - }; - // settings - this.setPrimaryChannel = (primaryChannel) => { - this.info.primaryChannel = primaryChannel; - this.storeInfo(); - this.subscribe(); - }; - this.setSecondaryChannels = (secondaryChannels) => { - this.info.secondaryChannels = secondaryChannels; - this.storeInfo(); - }; - this.setEncryptionKey = (key) => { - this.info.encryptionKey = key; - this.storeInfo(); - }; - this.setColor = (color) => { - this.color = color; - this.storeColor(); - }; - // messaging - this.addMessage = async (chatMessage) => { - await this.decryptMessage(chatMessage); - if (chatMessage.body != "") { - const messagePath = this.getMessagePath(chatMessage.id); - this.storageModel.writeStringifiable(messagePath, chatMessage); - this.chatMessageHandlerManager.trigger(chatMessage); - } - this.fileModel.handleStringifiedFileContent( - chatMessage.stringifiedFile - ); - }; - this.getNameAndChannel = () => { - 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 = allChannels.join("/"); - return [senderName, combinedChannel]; - }; - this.sendMessage = async (body, inlineReplyId, fileContent) => { - const nameAndChannel = this.getNameAndChannel(); - if (nameAndChannel == false) return; - const [senderName, combinedChannel] = nameAndChannel; - const chatMessage = await _ChatModel.createChatMessage( - combinedChannel, - senderName, - this.info.encryptionKey, - body, - inlineReplyId, - fileContent - ); - this.addMessage(chatMessage); - this.connectionModel.sendMessageOrStore(chatMessage); - return true; - }; - this.decryptMessage = async (chatMessage) => { - const decryptedBody = await decryptString( - chatMessage.body, - this.info.encryptionKey - ); - const decryptedFile = await decryptString( - chatMessage.stringifiedFile ?? "", - this.info.encryptionKey - ); - chatMessage.body = decryptedBody; - chatMessage.stringifiedFile = decryptedFile; - }; - this.sendReaction = async (messageId, content, isDeleting) => { - const nameAndChannel = this.getNameAndChannel(); - if (nameAndChannel == false) return; - const [senderName, combinedChannel] = nameAndChannel; - const reaction = _ChatModel.createMessageReaction( - messageId, - senderName, - content, - isDeleting - ); - this.sendMessage("", void 0, reaction); - this.handleReaction(reaction); - }; - this.subscribe = () => { - this.connectionModel.addChannel(this.info.primaryChannel); - }; - this.setReadStatus = (hasUnreadMessages) => { - this.info.hasUnreadMessages = hasUnreadMessages; - this.storeInfo(); - }; - // storage - this.storeInfo = () => { - this.storageModel.writeStringifiable(this.getInfoPath(), this.info); - }; - this.storeColor = () => { - this.storageModel.write(this.getColorPath(), this.color); - }; - this.delete = () => { - this.chatListModel.untrackChat(this); - const dirPath = this.getBasePath(); - this.storageModel.removeRecursively(dirPath); - }; - // load - this.loadInfo = () => { - const info = this.storageModel.readStringifiable( - this.getInfoPath(), - ChatInfoReference - ); - if (info != null) { - this.info = info; - } else { - this.info = _ChatModel.generateChatInfo("0"); - } - }; - this.loadColor = () => { - const path = this.getColorPath(); - const color = this.storageModel.read(path); - if (!color) { - this.color = "standard" /* Standard */; - } else { - this.color = color; - } - }; - this.id = chatId; - this.connectionModel = connectionModel2; - this.settingsModel = settingsModel2; - this.storageModel = storageModel2; - this.chatListModel = chatListModel2; - this.loadInfo(); - this.loadColor(); - this.subscribe(); - this.fileModel = new FileModel2( - this.storageModel, - this.settingsModel, - this - ); - } - get secondaryChannels() { - return this.info.secondaryChannels.sort(localeCompare); - } - get messages() { - const messageIds = this.storageModel.list( - this.getMessageDirPath() - ); - if (!Array.isArray(messageIds)) return []; - const chatMessages = []; - for (const messageId of messageIds) { - const messagePath = this.getMessagePath(messageId); - const chatMessage = this.storageModel.readStringifiable( - messagePath, - ChatMessageReference - ); - if (chatMessage == null) continue; - chatMessages.push(chatMessage); - } - const sorted = chatMessages.sort( - (a, b) => a.dateSent.localeCompare(b.dateSent) - ); - return sorted; - } - get reactions() { - const reactionIds = this.storageModel.list( - this.getReactionDirPath() - ); - if (!Array.isArray(reactionIds)) return []; - const reactions = []; - for (const reactionId of reactionIds) { - const reactionPath = this.getReactionPath(reactionId); - const reaction = this.storageModel.readStringifiable( - reactionPath, - ChatMessageReactionReference - ); - if (reaction == null) continue; - reactions.push(reaction); - } - return reactions; - } - // utility - static splitChannel(channelString) { - return channelString.split("/"); - } - static { - this.generateChatInfo = (primaryChannel) => { - return { - dataVersion: DATA_VERSION, - primaryChannel, - secondaryChannels: [], - encryptionKey: "", - hasUnreadMessages: false - }; - }; - } - static { - this.createChatMessage = async (channel, sender, encryptionKey, body, inlineReplyId, fileContent) => { - const chatMessage = { - dataVersion: DATA_VERSION, - id: v4_default(), - channel, - sender, - body, - dateSent: createTimestamp(), - inlineReplyId, - status: "outbox" /* Outbox */, - stringifiedFile: "" - }; - if (fileContent != void 0) { - 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; - }; - } - static { - this.createMessageReaction = (messageId, sender, content, isDeleting) => { - const fileContent = FileModel2.createFileContent(v4_default(), "reaction"); - const reaction = { - ...fileContent, - fileId: _ChatModel.createMessageReactionId(messageId, sender), - messageId, - sender, - content, - isDeleting - }; - return reaction; - }; - } - static { - this.createMessageReactionId = (messageId, sender) => { - return messageId + sender; - }; - } - }; - var ChatInfoReference = { - dataVersion: DATA_VERSION, - primaryChannel: "", - secondaryChannels: [""], - encryptionKey: "", - hasUnreadMessages: true - }; - var ChatMessageReference = { - dataVersion: DATA_VERSION, - id: "", - channel: "", - sender: "", - body: "", - dateSent: "", - status: "", - stringifiedFile: "" - }; - var ChatMessageReactionReference = { - dataVersion: DATA_VERSION, - fileId: "", - fileContentId: "", - creationDate: "", - type: "reaction", - messageId: "", - sender: "", - content: "", - isDeleting: false - }; - - // src/Model/Chat/chatListModel.ts - var ChatListModel = class { - // init - constructor(storageModel2, settingsModel2, connectionModel2) { - // data - this.chatModels = /* @__PURE__ */ new Set(); - // handlers - this.messageHandler = (data) => { - const channel = data.messageChannel; - const body = data.messageBody; - if (channel == void 0) return; - if (body == void 0) return; - this.routeMessageToCorrectChatModel( - channel, - (chatModel) => chatModel.handleMessage(body) - ); - }; - this.messageSentHandler = (chatMessage) => { - const channel = chatMessage.channel; - this.routeMessageToCorrectChatModel( - channel, - (chatModel) => chatModel.handleMessageSent(chatMessage) - ); - }; - // methods - this.routeMessageToCorrectChatModel = (channel, fn) => { - const allChannels = channel.split("/"); - for (const chatModel of this.chatModels) { - for (const channel2 of allChannels) { - if (channel2 != chatModel.info.primaryChannel) continue; - fn(chatModel); - break; - } - } - }; - // storage - this.addChatModel = (chatModel) => { - this.chatModels.add(chatModel); - }; - this.createChat = (primaryChannel) => { - const id = v4_default(); - const chatModel = new ChatModel( - this.storageModel, - this.connectionModel, - this.settingsModel, - this, - id - ); - chatModel.setPrimaryChannel(primaryChannel); - this.addChatModel(chatModel); - return chatModel; - }; - this.untrackChat = (chat) => { - this.chatModels.delete(chat); - }; - // load - this.loadChats = () => { - const chatDir = StorageModel.getPath( - "chat" /* 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); - } - }; - this.storageModel = storageModel2; - this.settingsModel = settingsModel2; - this.connectionModel = connectionModel2; - this.loadChats(); - connectionModel2.messageHandlerManager.setHandler( - "chat-list", - this.messageHandler - ); - connectionModel2.messageSentHandlerManager.setHandler( - "chat-list", - this.messageSentHandler - ); - } - }; - // src/View/translations.ts var englishTranslations = { updater: { @@ -2257,6 +913,196 @@ es: "Espa\xF1ol" }; + // src/Model/Utility/typeSafety.ts + var DATA_VERSION = "v2"; + function checkIsValidObject(object) { + return object.dataVersion == DATA_VERSION; + } + function checkMatchesObjectStructure(objectToCheck, reference) { + 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) { + if (objectToCheck[key].length == 0) continue; + if (objectToCheck[key].size == 0) continue; + const [firstOfObjectToCheck] = objectToCheck[key]; + const [fisrtOfReference] = reference[key]; + const doesFirstItemMatch = checkMatchesObjectStructure( + firstOfObjectToCheck, + fisrtOfReference + ); + if (doesFirstItemMatch == false) return false; + } else if (requiredType == "object") { + const doesNestedObjectMatch = checkMatchesObjectStructure( + objectToCheck[key], + reference[key] + ); + if (doesNestedObjectMatch == false) return false; + } + } + return true; + } + + // src/Model/Utility/utility.ts + function generateRandomToken(length) { + const array = new Uint8Array(length); + crypto.getRandomValues(array); + const string = array.join(""); + return string.substring(0, length); + } + function createTimestamp() { + return (/* @__PURE__ */ new Date()).toISOString(); + } + function checkDoesObjectMatchReference(reference, stringEntryObject, explicitEmptyValue = false) { + reference_entry_loop: for (const referenceEntry of Object.entries( + reference + )) { + const [referenceKey, referenceValue] = referenceEntry; + const stringEntryObjectValue = stringEntryObject[referenceKey]; + if (referenceValue == void 0) return false; + if (referenceValue[0] == "-") { + const strippedReferenceValue = referenceValue.toString().substring(1); + if (strippedReferenceValue == "" && stringEntryObjectValue != void 0 && stringEntryObjectValue != "") { + return false; + } + if (stringEntryObjectValue == strippedReferenceValue) { + return false; + } + } else { + if (explicitEmptyValue == false) { + if (referenceValue == "" && (stringEntryObjectValue == void 0 || stringEntryObjectValue == "")) { + return false; + } else if (referenceValue == "") { + continue reference_entry_loop; + } + } + if (stringEntryObjectValue != referenceValue) { + return false; + } + } + } + return true; + } + function collectObjectValuesForKey(key, converter, objects) { + const values = /* @__PURE__ */ new Set(); + for (const object of objects) { + const stringEntryObject = converter(object); + const stringEntryObjectValue = stringEntryObject[key]; + if (stringEntryObjectValue == void 0) continue; + values.add(stringEntryObjectValue.toString()); + } + return [...values.values()]; + } + function checkDoesObjectMatchSearch(query, getStringsOfObject, object) { + if (query == "") return true; + const stringsInObject = getStringsOfObject(object); + const wordsInObject = []; + 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] == "-") { + const wordContent = queryWord.substring(1); + if (wordsInObject.includes(wordContent)) { + return false; + } + } else { + if (wordsInObject.includes(queryWord) == false) { + return false; + } + } + } + return true; + } + var HandlerManager = class { + constructor() { + this.handlers = /* @__PURE__ */ new Map(); + // manage + this.setHandler = (id, handler) => { + this.handlers.set(id, handler); + }; + this.deleteHandler = (id) => { + this.handlers.delete(id); + }; + // trigger + this.trigger = (item) => { + [...this.handlers.values()].forEach((handler) => handler(item)); + }; + } + }; + var IndexManager = class { + // init + constructor(itemToString) { + this.sortedStrings = []; + // methods + this.update = (items) => { + this.sortedStrings = []; + let strings = []; + for (const item of items) { + const string = this.itemToString(item); + strings.push(string); + } + this.sortedStrings = strings.sort(localeCompare); + }; + this.getIndex = (item) => { + const string = this.itemToString(item); + const index = this.sortedStrings.indexOf(string); + return index; + }; + this.itemToString = itemToString; + } + }; + function getLocalStorageItemAndClear(key) { + const value = localStorage.getItem(key); + localStorage.removeItem(key); + if (value != null) localStorage.setItem(`_${key}`, value); + return value; + } + function stringify(data) { + return JSON.stringify(data, null, 4); + } + function padZero(string, length) { + return (string ?? "").padStart(length, "0"); + } + function parse(string) { + try { + return JSON.parse(string); + } catch { + return {}; + } + } + function parseValidObject(string, reference) { + const parsed = parse(string); + if (checkIsValidObject(parsed) == false) return null; + const doesMatchReference = checkMatchesObjectStructure( + parsed, + reference + ); + if (doesMatchReference == false) return null; + return parsed; + } + function parseOrFallback(inputString) { + try { + return JSON.parse(inputString); + } catch { + return inputString; + } + } + function parseArray(inputString) { + const parsed = parseOrFallback(inputString); + if (Array.isArray(parsed) == false) return []; + return parsed; + } + function localeCompare(a, b) { + return a.localeCompare(b); + } + // src/ViewModel/Global/coreViewModel.ts var CoreViewModel = class _CoreViewModel { // init @@ -2266,7 +1112,7 @@ this.connectionModel = connectionModel2; this.chatListModel = chatListModel2; this.fileTransferModel = fileTransferModel2; - this.BUILD = "Build 26.04.15.B"; + this.BUILD = "Build 26.04.16.A"; // CONTEXT this.contextStack = /* @__PURE__ */ new Map(); this.closeContext = (contextId, fromHistoryEvent = false) => { @@ -2298,7 +1144,8 @@ }; this.handleChron = () => { const newDate = /* @__PURE__ */ new Date(); - if (this.unwrappedTodayDate.toDateString() == newDate.toDateString()) return; + if (this.unwrappedTodayDate.toDateString() == newDate.toDateString()) + return; this.todayDate.value = /* @__PURE__ */ new Date(); }; // DRAG & DROP @@ -2397,6 +1244,670 @@ } }; + // src/ViewModel/Pages/homeViewModel.ts + var HomeViewModel = class extends Context { + constructor(coreViewModel2, settingsViewModel2, fileTransferViewModel2, storageViewModel2, connectionViewModel2) { + super("home"); + this.coreViewModel = coreViewModel2; + this.settingsViewModel = settingsViewModel2; + this.fileTransferViewModel = fileTransferViewModel2; + this.storageViewModel = storageViewModel2; + this.connectionViewModel = connectionViewModel2; + this.coreViewModel.context = this; + this.registerKeyStroke( + "," /* Settings */, + this.settingsViewModel.showSettingsModal + ); + this.registerKeyStroke( + "t", + this.fileTransferViewModel.showDirectionSelectionModal + ); + this.registerKeyStroke("e", this.storageViewModel.showStorageModal); + this.registerKeyStroke("x", this.connectionViewModel.disconnect); + this.registerKeyStroke("c", this.connectionViewModel.connect); + } + }; + + // src/Model/Global/storageModel.ts + var PATH_COMPONENT_SEPARATOR = "\\"; + var StorageModel = class _StorageModel { + // init + constructor() { + this.storageEntryTree = {}; + // read + this.read = (pathComponents) => { + const pathString = _StorageModel.pathComponentsToString( + ...pathComponents + ); + return localStorage.getItem(pathString); + }; + this.list = (pathComponents) => { + let currentParent = this.storageEntryTree; + for (const component of pathComponents) { + const nextParent = currentParent[component]; + if (nextParent == void 0) return []; + currentParent = nextParent; + } + return [...Object.keys(currentParent).sort(localeCompare)]; + }; + // write + this.write = (pathComponents, value) => { + const pathString = _StorageModel.pathComponentsToString( + ...pathComponents + ); + localStorage.setItem(pathString, value); + this.updateTree(...pathComponents); + }; + this.remove = (pathComponents, shouldInitialize = true) => { + const pathString = _StorageModel.pathComponentsToString( + ...pathComponents + ); + localStorage.removeItem(pathString); + if (shouldInitialize == true) { + this.initializeTree(); + } + }; + this.rename = (sourcePathComponents, destinationPathComponents, shouldInitialize = true) => { + const content = this.read(sourcePathComponents); + if (content == null) return false; + this.write(destinationPathComponents, content); + this.remove(sourcePathComponents); + if (shouldInitialize == true) { + this.initializeTree(); + } + return true; + }; + // recursion + this.recurse = (rootDirectory, fn) => { + loop_over_files: for (const key of Object.keys(localStorage)) { + const pathComponentsOfCurrentEntity = _StorageModel.stringToPathComponents(key); + 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; + } + fn(pathComponentsOfCurrentEntity); + } + this.initializeTree(); + }; + this.removeRecursively = (pathComponents) => { + this.recurse( + pathComponents, + (path) => this.remove(path, false) + ); + this.initializeTree(); + }; + this.renameRecursively = (sourcePathComponents, destinationPathComponents) => { + this.recurse(sourcePathComponents, (path) => { + const relativePathOfCurrentEntity = path.slice( + sourcePathComponents.length + ); + const destinationPathComponentsOfCurrentEntity = [ + ...destinationPathComponents, + ...relativePathOfCurrentEntity + ]; + this.rename(path, destinationPathComponentsOfCurrentEntity, false); + }); + this.initializeTree(); + }; + // stringifiable + this.writeStringifiable = (pathComponents, value) => { + const valueString = stringify(value); + this.write(pathComponents, valueString); + }; + this.readStringifiable = (pathComponents, reference) => { + const valueString = this.read(pathComponents); + if (!valueString) return null; + const object = parseValidObject(valueString, reference); + if (object == null) return null; + return object; + }; + // cleaning + this.removeJunk = () => { + this.recurse([], (path) => { + if (path[0] == DATA_VERSION) return; + this.remove(path); + }); + }; + // tree + this.initializeTree = () => { + console.log("initializing tree"); + this.storageEntryTree = {}; + for (const key of Object.keys(localStorage)) { + const components = _StorageModel.stringToPathComponents(key); + this.updateTree(...components); + } + }; + this.updateTree = (...pathComponents) => { + let currentParent = this.storageEntryTree; + for (const pathPart of pathComponents) { + if (!currentParent[pathPart]) { + currentParent[pathPart] = {}; + } + currentParent = currentParent[pathPart]; + } + }; + this.printTree = () => { + return stringify(this.storageEntryTree); + }; + this.initializeTree(); + } + static { + // utility + this.getFileName = (pathComponents) => { + return pathComponents[pathComponents.length - 1] || "\\"; + }; + } + static { + this.getFileNameFromString = (pathString) => { + const pathComponents = this.stringToPathComponents(pathString); + return pathComponents[pathComponents.length - 1] || "\\"; + }; + } + static { + this.pathComponentsToString = (...pathComponents) => { + return pathComponents.filter((x) => x != "").join(PATH_COMPONENT_SEPARATOR); + }; + } + static { + this.stringToPathComponents = (string) => { + return string.split(PATH_COMPONENT_SEPARATOR).filter((x) => x != ""); + }; + } + static { + this.join = (...items) => { + let allComponents = []; + for (const item of items) { + const parts = this.stringToPathComponents(item); + allComponents.push(...parts); + } + return _StorageModel.pathComponentsToString(...allComponents); + }; + } + static getPath(locationName, filePath) { + return [DATA_VERSION, locationName, ...filePath]; + } + }; + var filePaths = { + connectionModel: { + socketAddress: ["socket-address"], + reconnectAddress: ["reconnect-address"], + outbox: ["outbox"], + mailboxes: ["mailboxes"], + previousAddresses: ["previous-addresses"] + }, + chat: { + base: [], + chatBase: (id) => [id], + info: (id) => [...filePaths.chat.chatBase(id), "info"], + color: (id) => [...filePaths.chat.chatBase(id), "color"], + messages: (id) => [...filePaths.chat.chatBase(id), "messages"], + reactions: (id) => [ + ...filePaths.chat.chatBase(id), + "reactions" + ], + lastUsedPage: (id) => [ + ...filePaths.chat.chatBase(id), + "last-used-page" + ], + files: (id) => [...filePaths.chat.chatBase(id), "files"] + }, + notificationModel: { + base: [] + }, + settingsModel: { + username: ["user-name"], + firstDayOfWeek: ["first-day-of-week"], + language: ["language"], + theme: ["theme"] + } + }; + + // src/ViewModel/Global/storageViewModel.ts + var StorageViewModel = class extends Context { + // init + constructor(coreViewModel2) { + super("storage"); + this.coreViewModel = coreViewModel2; + // state + this.isShowingStorageModal = new State(false); + 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 content = this.coreViewModel.storageModel.read(path); + return (content ?? this.coreViewModel.translations.storage.notAFile) || this.coreViewModel.translations.storage.contentEmpty; + }; + this.deleteSelectedItem = () => { + const path = StorageModel.stringToPathComponents( + this.selectedPath.value + ); + this.lastDeletedItemPath.value = this.selectedPath.value; + this.coreViewModel.storageModel.removeRecursively(path); + this.didMakeChanges.value = true; + }; + this.removeJunk = () => { + this.coreViewModel.storageModel.removeJunk(); + this.selectedPath.value = PATH_COMPONENT_SEPARATOR; + }; + // view + this.showStorageModal = () => { + this.coreViewModel.context = this; + this.isShowingStorageModal.value = true; + }; + // exit + this.close = () => { + this.coreViewModel.closeContext(this.contextId); + }; + this.handleContextClose = () => { + if (this.didMakeChanges.value == true) { + window.location.reload(); + return; + } + this.isShowingStorageModal.value = false; + }; + this.selectedFileName = createProxyState( + [this.selectedPath], + () => StorageModel.getFileNameFromString(this.selectedPath.value) + ); + this.selectedFileContent = createProxyState( + [this.selectedPath], + () => this.getSelectedItemContent() + ); + this.registerKeyStroke("backspace" /* CloseOrCancel */, this.close); + } + }; + + // src/Model/Global/settingsModel.ts + var SettingsModel = class _SettingsModel { + // init + constructor(storageModel2) { + // storage + this.storeSetting = (pathName, value) => { + const path = StorageModel.getPath( + "settings" /* SettingsModel */, + filePaths.settingsModel[pathName] + ); + this.storageModel.write(path, value); + }; + this.setName = (newValue) => { + this.username = newValue; + this.storeSetting("username", newValue); + }; + this.setFirstDayOfWeek = (newValue) => { + this.firstDayOfWeek = newValue; + this.storeSetting("firstDayOfWeek", newValue); + }; + this.setLanguage = (newValue) => { + this.language = newValue; + this.storeSetting("language", newValue); + }; + this.setTheme = (newValue) => { + this.theme = newValue; + this.storeSetting("theme", newValue); + }; + // load + this.readSetting = (pathName) => { + const path = StorageModel.getPath( + "settings" /* SettingsModel */, + filePaths.settingsModel[pathName] + ); + return this.storageModel.read(path); + }; + this.loadUsername = () => { + const content = this.readSetting("username"); + this.username = content ?? ""; + }; + this.loadFirstDayofWeek = () => { + const content = this.readSetting("firstDayOfWeek"); + this.firstDayOfWeek = content ?? "0"; + }; + this.loadLanguage = () => { + const content = this.readSetting("language"); + this.language = content ?? _SettingsModel.getSystemLanguage(); + }; + this.loadTheme = () => { + const content = this.readSetting("theme"); + this.theme = content ?? "system" /* System */; + }; + this.storageModel = storageModel2; + this.loadUsername(); + this.loadFirstDayofWeek(); + this.loadLanguage(); + this.loadTheme(); + } + static getSystemLanguage() { + switch (navigator.language.substring(0, 2)) { + case "de": + return "de" /* German */; + case "es": + return "es" /* Spanish */; + default: + return "en" /* English */; + } + } + }; + var Languages = /* @__PURE__ */ ((Languages2) => { + Languages2["English"] = "en"; + Languages2["German"] = "de"; + Languages2["Spanish"] = "es"; + return Languages2; + })(Languages || {}); + + // src/ViewModel/Global/settingsViewModel.ts + var SettingsViewModel = class _SettingsViewModel extends Context { + // init + constructor(coreViewModel2) { + super("settings"); + this.coreViewModel = coreViewModel2; + // state + this.username = new State(""); + this.usernameInput = new State(""); + this.isShowingSettingsModal = new State(false); + this.selectedModalPage = new State(void 0); + this.requiresReload = new State(false); + this.firstDayOfWeek = new State("0"); + this.language = new State( + "en" /* English */ + ); + this.theme = new State( + "system" /* System */ + ); + // guards + this.cannotSetName = createProxyState( + [this.usernameInput], + () => this.usernameInput.value == "" || this.usernameInput.value == this.coreViewModel.settingsModel.username + ); + // methods + this.setName = () => { + this.coreViewModel.settingsModel.setName(this.usernameInput.value); + this.username.value = this.coreViewModel.settingsModel.username; + this.usernameInput.callSubscriptions(); + }; + this.setFirstDayofWeek = () => { + this.coreViewModel.settingsModel.setFirstDayOfWeek( + this.firstDayOfWeek.value + ); + }; + this.showSettingsModal = () => { + this.coreViewModel.context = this; + this.isShowingSettingsModal.value = true; + }; + this.showModalPage = (page) => { + this.selectedModalPage.value = page; + }; + // view + this.applyTheme = () => { + let theme = this.theme.value; + if (theme == "system" /* System */) { + theme = _SettingsViewModel.getSystemTheme(); + } + document.body.setAttribute("theme", theme); + }; + // exit + this.close = () => { + this.coreViewModel.closeContext(this.contextId); + }; + this.handleContextClose = () => { + this.isShowingSettingsModal.value = false; + if (this.requiresReload.value == true) { + window.location.reload(); + } + }; + this.username.value = coreViewModel2.settingsModel.username; + this.usernameInput.value = coreViewModel2.settingsModel.username; + this.firstDayOfWeek.value = coreViewModel2.settingsModel.firstDayOfWeek; + this.language.value = coreViewModel2.settingsModel.language; + this.theme.value = coreViewModel2.settingsModel.theme; + this.firstDayOfWeek.subscribe(this.setFirstDayofWeek); + this.language.subscribeSilent((newValue) => { + this.coreViewModel.settingsModel.setLanguage(newValue); + this.requiresReload.value = true; + }); + this.theme.subscribeSilent((newValue) => { + this.coreViewModel.settingsModel.setTheme(newValue); + }); + this.theme.subscribe(() => { + this.applyTheme(); + }); + _SettingsViewModel.generateThemeMedia().addEventListener( + "change", + () => this.applyTheme() + ); + this.registerKeyStroke("backspace" /* CloseOrCancel */, this.close); + } + static generateThemeMedia() { + return window.matchMedia("(prefers-color-scheme: dark)"); + } + static getSystemTheme() { + const media = _SettingsViewModel.generateThemeMedia(); + return media.matches == true ? "dark" /* Dark */ : "light" /* Light */; + } + }; + + // src/ViewModel/Global/fileTransferViewModel.ts + var FileTransferViewModel = class extends Context { + // init + constructor(coreViewModel2) { + super("file-transfer"); + this.coreViewModel = coreViewModel2; + // state + this.presentedModal = new State(void 0); + this.generalFileOptions = new ListState(); + this.chatFileOptions = new ListState(); + this.selectedPaths = new ListState(); + this.transferChannel = new State(""); + this.transferKey = new State(""); + this.receivingTransferChannel = new State(""); + this.receivingTransferKey = new State(""); + this.filePathsSent = new ListState(); + this.filesSentCount = createProxyState( + [this.filePathsSent], + () => this.filePathsSent.value.size + ); + this.filesSentText = createProxyState( + [this.filesSentCount], + () => this.coreViewModel.translations.dataTransferModal.filesSentCount( + this.filesSentCount.value + ) + ); + this.filePathsReceived = new ListState(); + this.filesReceivedCount = createProxyState( + [this.filePathsReceived], + () => this.filePathsReceived.value.size + ); + this.filesReceivedText = createProxyState( + [this.filesReceivedCount], + () => this.coreViewModel.translations.dataTransferModal.filesReceivedCount( + this.filesReceivedCount.value + ) + ); + // guards + this.hasNoPathsSelected = createProxyState( + [this.selectedPaths], + () => this.selectedPaths.value.size == 0 + ); + this.didNotFinishSending = new State(true); + this.cannotPrepareToReceive = createProxyState( + [this.receivingTransferChannel, this.receivingTransferKey], + () => this.receivingTransferChannel.value == "" || this.receivingTransferKey.value == "" + ); + // handlers + this.handleReceivedFile = (path) => { + this.filePathsReceived.add(path); + }; + // methods + this.getOptions = () => { + this.generalFileOptions.clear(); + this.chatFileOptions.clear(); + this.selectedPaths.clear(); + this.generalFileOptions.add( + { + label: this.coreViewModel.translations.dataTransferModal.connectionData, + path: StorageModel.getPath( + "connection" /* ConnectionModel */, + filePaths.connectionModel.previousAddresses + ) + }, + { + label: this.coreViewModel.translations.dataTransferModal.settingsData, + path: StorageModel.getPath( + "settings" /* SettingsModel */, + [] + ) + } + ); + const chatModels = this.coreViewModel.chatListModel.chatModels; + for (const chatModel of chatModels) { + this.chatFileOptions.add({ + label: chatModel.info.primaryChannel, + path: chatModel.getBasePath() + }); + } + }; + this.getTransferData = () => { + const transferData = this.coreViewModel.fileTransferModel.generateTransferData(); + this.transferChannel.value = transferData.channel; + this.transferKey.value = transferData.key; + }; + // view + this.showDirectionSelectionModal = () => { + this.coreViewModel.context = this; + this.presentedModal.value = 0 /* DirectionSelection */; + this.getOptions(); + }; + this.showFileSelectionModal = () => { + this.presentedModal.value = 1 /* FileSelection */; + }; + this.showTransferDataModal = () => { + this.presentedModal.value = 2 /* TransferDataDisplay */; + this.getTransferData(); + this.coreViewModel.fileTransferModel.prepareToSend(); + }; + this.initiateTransfer = () => { + this.presentedModal.value = 3 /* TransferDisplay */; + this.didNotFinishSending.value = true; + this.filePathsSent.clear(); + this.coreViewModel.fileTransferModel.sendFiles( + this.selectedPaths.value.values(), + (path) => { + console.log(path); + this.filePathsSent.add(path); + } + ); + this.didNotFinishSending.value = false; + }; + this.showTransferDataInputModal = () => { + this.presentedModal.value = 4 /* TransferDataInput */; + }; + this.prepareReceivingData = () => { + if (this.cannotPrepareToReceive.value == true) return; + this.presentedModal.value = 5 /* ReceptionDisplay */; + this.filePathsReceived.clear(); + const transferData = { + channel: this.receivingTransferChannel.value, + key: this.receivingTransferKey.value + }; + this.coreViewModel.fileTransferModel.prepareToReceive(transferData); + }; + // exit + this.close = () => { + this.coreViewModel.closeContext(this.contextId); + }; + this.handleContextClose = () => { + this.presentedModal.value = void 0; + }; + this.coreViewModel.fileTransferModel.fileHandlerManager.setHandler( + "file-transfer-view-model", + this.handleReceivedFile + ); + this.coreViewModel.fileTransferModel.readyToSendHandlerManager.setHandler( + "file-transfer-view-model", + () => this.initiateTransfer() + ); + this.registerKeyStroke("backspace" /* CloseOrCancel */, this.close); + } + }; + + // src/ViewModel/Global/connectionViewModel.ts + var ConnectionViewModel = class { + // init + constructor(coreViewModel2) { + this.coreViewModel = coreViewModel2; + // state + this.serverAddressInput = new State(""); + this.isConnected = new State(false); + this.isShowingConnectionModal = new State(false); + this.previousAddresses = new ListState(); + // guards + this.cannotConnect = createProxyState( + [this.serverAddressInput, this.isConnected], + () => this.isConnected.value == true && this.serverAddressInput.value == this.coreViewModel.connectionModel.address || this.serverAddressInput.value == "" + ); + this.cannotDisonnect = createProxyState( + [this.isConnected], + () => this.isConnected.value == false + ); + this.hasNoPreviousConnections = createProxyState( + [this.previousAddresses], + () => this.previousAddresses.value.size == 0 + ); + // handlers + this.connectionChangeHandler = () => { + this.isConnected.value = this.coreViewModel.connectionModel.isConnected; + if (this.coreViewModel.connectionModel.isConnected == false) return; + if (this.coreViewModel.connectionModel.address == void 0) return; + this.serverAddressInput.value = this.coreViewModel.connectionModel.address; + if (!this.previousAddresses.value.has( + this.coreViewModel.connectionModel.address + )) { + this.previousAddresses.add( + this.coreViewModel.connectionModel.address + ); + } + }; + // methods + this.connect = () => { + this.connectToAddress(this.serverAddressInput.value); + }; + this.connectToAddress = (address) => { + console.log(address); + this.coreViewModel.connectionModel.connect(address); + }; + this.disconnect = () => { + this.coreViewModel.connectionModel.disconnect(); + }; + this.removePreviousAddress = (address) => { + this.coreViewModel.connectionModel.removeAddress(address); + this.updatePreviousAddresses(); + }; + // view + this.showConnectionModal = () => { + this.isShowingConnectionModal.value = true; + }; + this.hideConnectionModal = () => { + this.isShowingConnectionModal.value = false; + }; + this.updatePreviousAddresses = () => { + this.previousAddresses.clear(); + this.previousAddresses.add( + ...this.coreViewModel.connectionModel.addresses + ); + }; + this.updatePreviousAddresses(); + coreViewModel2.connectionModel.connectionChangeHandlerManager.setHandler( + "connection-view-model", + this.connectionChangeHandler + ); + } + }; + // src/View/viewController.ts var Tracker = class { }; @@ -2476,6 +1987,467 @@ } }; + // src/Model/Files/fileModel.ts + var FileModel = class _FileModel { + // init + constructor(storageModel2, settingsModel2, chatModel) { + this.getFileContainerPath = () => { + return [...this.basePath, "data" /* Data */]; + }; + this.getModelContainerPath = (modelName) => { + return [...this.basePath, "model" /* Model */, modelName]; + }; + this.getFilePath = (fileId) => { + return [...this.getFileContainerPath(), fileId]; + }; + this.getFileContentPath = (fileId, fileContentId) => { + const filePath = this.getFilePath(fileId); + return [...filePath, fileContentId]; + }; + // handlers + this.handleStringifiedFileContent = (stringifiedFileContent) => { + const fileContent = parseValidObject( + stringifiedFileContent, + FileContentReference + ); + if (fileContent == null) return; + this.handleFileContent(fileContent); + }; + this.handleFileContent = (fileContent) => { + const didStore = this.storeFileContent(fileContent); + if (didStore == false) return; + this.boardsAndTasksModel.handleFileContent(fileContent); + this.chatModel.handleReaction(fileContent); + }; + // methods + this.addFileContentAndSend = (fileContent) => { + this.handleFileContent(fileContent); + this.chatModel.sendMessage("", void 0, fileContent); + }; + // storage + this.storeFileContent = (fileContent) => { + const fileContentPath = this.getFileContentPath( + fileContent.fileId, + fileContent.fileContentId + ); + const existingFileContent = this.storageModel.read(fileContentPath); + if (existingFileContent != null) return false; + const stringifiedContent = stringify(fileContent); + this.storageModel.write(fileContentPath, stringifiedContent); + return true; + }; + this.listFileIds = () => { + return this.storageModel.list(this.basePath); + }; + this.listFileContentIds = (fileId) => { + const filePath = this.getFilePath(fileId); + return this.storageModel.list(filePath); + }; + this.selectLatestFileContentId = (fileContentIds) => { + return fileContentIds[fileContentIds.length - 1]; + }; + this.getFileContent = (fileId, fileContentName, reference) => { + const filePath = this.getFileContentPath( + fileId, + fileContentName + ); + const fileContentOrNull = this.storageModel.readStringifiable( + filePath, + reference + ); + return fileContentOrNull; + }; + this.getLatestFileContent = (fileId, reference) => { + const fileContentsIds = this.listFileContentIds(fileId); + const latestFileContentId = this.selectLatestFileContentId(fileContentsIds); + if (latestFileContentId == void 0) return null; + const fileContent = this.getFileContent( + fileId, + latestFileContentId, + reference + ); + return fileContent; + }; + this.chatModel = chatModel; + this.settingsModel = settingsModel2; + this.storageModel = storageModel2; + this.boardsAndTasksModel = new BoardsAndTasksModel( + this.storageModel, + this.settingsModel, + chatModel, + this + ); + } + // paths + get basePath() { + return StorageModel.getPath( + "chat" /* Chat */, + filePaths.chat.files(this.chatModel.id) + ); + } + static { + // utility + this.generateFileContentId = (creationDate) => { + return creationDate + v4_default(); + }; + } + static { + this.createFileContent = (fileId, type) => { + const creationDate = createTimestamp(); + const fileContentId = _FileModel.generateFileContentId(creationDate); + return { + dataVersion: DATA_VERSION, + fileId, + fileContentId, + creationDate, + type + }; + }; + } + }; + var FileContentReference = { + dataVersion: DATA_VERSION, + fileId: "", + fileContentId: "", + creationDate: "", + type: "" + }; + + // src/Model/Files/calendarModel.ts + var CalendarModel = class _CalendarModel { + // init + constructor(storageModel2, settingsModel2, fileModel) { + this.getViewPath = () => { + return [...this.basePath, "view" /* ModelView */]; + }; + this.getMonthContainerPath = () => { + return [...this.basePath, "months" /* Months */]; + }; + this.getMonthPath = (monthString) => { + return [...this.getMonthContainerPath(), monthString]; + }; + // task references + this.storeTaskReference = (taskFileContent) => { + if (taskFileContent.date == void 0) return; + const monthString = _CalendarModel.isoToMonthString( + taskFileContent.date + ); + const monthPath = this.getMonthPath(monthString); + const referencePath = [...monthPath, taskFileContent.fileId]; + this.storageModel.write(referencePath, ""); + }; + this.deleteTaskReference = (monthString, taskId) => { + const monthPath = this.getMonthPath(monthString); + const referencePath = [...monthPath, taskId]; + this.storageModel.write(referencePath, ""); + }; + // data + this.listTaskIds = (monthString) => { + const monthPath = this.getMonthPath(monthString); + return this.storageModel.list(monthPath); + }; + // util + this.generateMonthGrid = (coreViewModel2, year, month, defaultValueCreator) => { + const date = coreViewModel2.unwrappedTodayDate; + const isCurrentMonth = year == date.getFullYear() && month == date.getMonth() + 1; + date.setDate(1); + date.setMonth(month - 1); + date.setFullYear(year); + const firstWeekdayOfMonth = date.getDay(); + const firstDayOfWeekSetting = parseInt( + this.settingsModel.firstDayOfWeek + ); + const offset = firstWeekdayOfMonth < firstDayOfWeekSetting ? 7 - firstDayOfWeekSetting : firstWeekdayOfMonth - firstDayOfWeekSetting; + date.setMonth(month); + date.setDate(-1); + const daysInMonth = date.getDate() + 1; + const grid = { + offset, + firstDayOfWeek: parseInt(this.settingsModel.firstDayOfWeek), + isCurrentMonth, + year, + month, + days: {} + }; + for (let i = 0; i < daysInMonth; i++) { + const paddedDate = _CalendarModel.padDateOrMonth( + (i + 1).toString() + ); + grid.days[paddedDate] = defaultValueCreator(); + } + return grid; + }; + this.storageModel = storageModel2; + this.settingsModel = settingsModel2; + this.fileModel = fileModel; + } + // paths + get basePath() { + return this.fileModel.getModelContainerPath( + "calendar" /* ModelCalendar */ + ); + } + static { + // utility + this.isoToMonthString = (dateISOString) => { + const [year, month, _] = dateISOString.split("-"); + return _CalendarModel.getMonthString(year, month); + }; + } + static { + this.isoToDateString = (dateISOString) => { + const [year, month, date, _] = dateISOString.split("-"); + const paddedDate = _CalendarModel.padDateOrMonth(date ?? ""); + return paddedDate; + }; + } + static { + this.getMonthString = (year = "", month = "") => { + const paddedYear = year.padStart(4, "0"); + const paddedMonth = _CalendarModel.padDateOrMonth(month); + return `${paddedYear}-${paddedMonth}`; + }; + } + static { + this.getISODateString = (year, month, date) => { + const monthString = _CalendarModel.getMonthString(year, month); + const paddedDate = _CalendarModel.padDateOrMonth(date); + return `${monthString}-${paddedDate}`; + }; + } + static { + this.padDateOrMonth = (input) => { + return input.padStart(2, "0"); + }; + } + }; + + // src/colors.ts + var Colors = /* @__PURE__ */ ((Colors2) => { + Colors2["Standard"] = "standard"; + Colors2["Coral"] = "coral"; + Colors2["Yellow"] = "yellow"; + Colors2["Green"] = "green"; + Colors2["LightBlue"] = "lightblue"; + Colors2["Blue"] = "blue"; + Colors2["purple"] = "purple"; + return Colors2; + })(Colors || {}); + + // src/Model/Files/boardsAndTasksModel.ts + var BoardsAndTasksModel = class _BoardsAndTasksModel { + // init + constructor(storageModel2, settingsModel2, chatModel, fileModel) { + // data + this.boardHandlerManager = new HandlerManager(); + this.taskHandlerManager = new HandlerManager(); + // paths + this.getBasePath = () => { + return this.fileModel.getModelContainerPath("tasks" /* ModelTask */); + }; + this.getViewPath = () => { + return [...this.getBasePath(), "view" /* ModelView */]; + }; + this.getBoardFilePath = (boardId) => { + return [...this.fileModel.getFilePath(boardId)]; + }; + this.getTaskFilePath = (taskId) => { + return [...this.fileModel.getFilePath(taskId)]; + }; + this.getBoardContainerPath = () => { + return [...this.getBasePath(), "boards" /* Boards */]; + }; + this.getBoardDirectoryPath = (boardId) => { + return [...this.getBoardContainerPath(), boardId]; + }; + this.getTaskContainerPath = (boardId) => { + return [ + ...this.getBoardDirectoryPath(boardId), + "tasks" /* BoardTasks */ + ]; + }; + this.getTaskReferencePath = (boardId, fileId) => { + return [...this.getTaskContainerPath(boardId), fileId]; + }; + // handlers + this.handleFileContent = (fileContent) => { + if (checkMatchesObjectStructure( + fileContent, + BoardInfoFileContentReference + ) == true) { + this.handleBoard(fileContent); + } else if (checkMatchesObjectStructure( + fileContent, + TaskFileContentReference + ) == true) { + this.handleTask(fileContent); + } + }; + this.handleBoard = (boardInfoFileContent) => { + this.updateBoard(boardInfoFileContent); + }; + this.handleTask = (taskFileContent) => { + this.updateTask(taskFileContent); + }; + // boards + this.createBoard = (name) => { + const boardInfoFileContent = _BoardsAndTasksModel.createBoardInfoFileContent( + v4_default(), + name, + "standard" /* Standard */ + ); + return boardInfoFileContent; + }; + this.updateBoard = (boardInfoFileContent) => { + this.storeBoard(boardInfoFileContent); + this.boardHandlerManager.trigger(boardInfoFileContent); + }; + this.updateBoardAndSend = (boardInfoFileContent) => { + this.updateBoard(boardInfoFileContent); + this.chatModel.sendMessage("", void 0, boardInfoFileContent); + }; + this.storeBoard = (boardInfoFileContent) => { + this.fileModel.storeFileContent(boardInfoFileContent); + const boardDirectoryPath = this.getBoardDirectoryPath( + boardInfoFileContent.fileId + ); + this.storageModel.write(boardDirectoryPath, ""); + }; + this.deleteBoard = (boardId) => { + const boardFilePath = this.getBoardFilePath(boardId); + const boardDirectoryPath = this.getBoardDirectoryPath(boardId); + this.storageModel.removeRecursively(boardFilePath); + this.storageModel.removeRecursively(boardDirectoryPath); + }; + this.listBoardIds = () => { + const boardContainerPath = this.getBoardContainerPath(); + const boardIds = this.storageModel.list(boardContainerPath); + return boardIds; + }; + this.getBoardInfo = (fileId) => { + const boardInfoFileContentOrNull = this.fileModel.getLatestFileContent( + fileId, + BoardInfoFileContentReference + ); + return boardInfoFileContentOrNull; + }; + this.getBoardName = (boardId) => { + const boardInfo = this.getBoardInfo(boardId); + if (boardInfo == null) return ""; + return boardInfo.name; + }; + //tasks + this.createTask = (boardId) => { + const taskFileContent = _BoardsAndTasksModel.createTaskFileContent(v4_default(), "", boardId); + return taskFileContent; + }; + this.updateTask = (taskFileContent) => { + this.storeTask(taskFileContent); + this.taskHandlerManager.trigger(taskFileContent); + }; + this.updateTaskAndSend = (taskFileContent) => { + this.updateTask(taskFileContent); + this.chatModel.sendMessage("", void 0, taskFileContent); + }; + this.storeTask = (taskFileContent) => { + this.fileModel.storeFileContent(taskFileContent); + const taskReferencePath = this.getTaskReferencePath( + taskFileContent.boardId, + taskFileContent.fileId + ); + this.storageModel.write(taskReferencePath, ""); + this.calendarModel.storeTaskReference(taskFileContent); + }; + this.listTaskIds = (boardId) => { + const taskContainerPath = this.getTaskContainerPath(boardId); + const fileIds = this.storageModel.list(taskContainerPath); + return fileIds; + }; + this.listTaskVersionIds = (taskId) => { + const versionIds = this.fileModel.listFileContentIds(taskId); + return versionIds; + }; + this.getLatestTaskFileContent = (taskId) => { + const taskFileContentOrNull = this.fileModel.getLatestFileContent( + taskId, + TaskFileContentReference + ); + return taskFileContentOrNull; + }; + this.getSpecificTaskFileContent = (taskId, versionId) => { + const taskFileContentOrNull = this.fileModel.getFileContent( + taskId, + versionId, + TaskFileContentReference + ); + return taskFileContentOrNull; + }; + this.deleteTask = (boardId, taskId) => { + const taskFilePath = this.getTaskFilePath(taskId); + this.storageModel.removeRecursively(taskFilePath); + this.deleteTaskReference(boardId, taskId); + }; + this.deleteTaskReference = (boardId, taskId) => { + const taskReferencePath = this.getTaskReferencePath( + boardId, + taskId + ); + this.storageModel.removeRecursively(taskReferencePath); + }; + this.storageModel = storageModel2; + this.settingsModel = settingsModel2; + this.chatModel = chatModel; + this.fileModel = fileModel; + this.calendarModel = new CalendarModel( + this.storageModel, + this.settingsModel, + this.fileModel + ); + } + static { + // utility + this.createBoardInfoFileContent = (fileId, name, color) => { + const fileContent = FileModel.createFileContent(fileId, "board-info"); + return { + ...fileContent, + name, + color + }; + }; + } + static { + this.createTaskFileContent = (fileId, name, boardId) => { + const fileContent = FileModel.createFileContent( + fileId, + "task" + ); + return { + ...fileContent, + name, + boardId + }; + }; + } + }; + var BoardInfoFileContentReference = { + dataVersion: DATA_VERSION, + fileId: "string", + fileContentId: "", + creationDate: "", + type: "board-info", + name: "", + color: "" + }; + var TaskFileContentReference = { + dataVersion: DATA_VERSION, + fileId: "string", + fileContentId: "", + creationDate: "", + type: "task", + name: "", + boardId: "" + }; + // src/ViewModel/Pages/taskViewModel.ts var TaskViewModel = class extends Context { // init @@ -2696,326 +2668,6 @@ } }; - // src/ViewModel/Pages/calendarPageViewModel.ts - var CALENDAR_EVENT_BOARD_ID = "events"; - var CalendarPageViewModel = class extends TaskContainingPageViewModel { - // init - constructor(coreViewModel2, chatViewModel, calendarModel, boardsAndTasksModel) { - super(coreViewModel2, chatViewModel, boardsAndTasksModel, "calendar"); - this.coreViewModel = coreViewModel2; - this.chatViewModel = chatViewModel; - this.calendarModel = calendarModel; - this.boardsAndTasksModel = boardsAndTasksModel; - // paths - this.getBasePath = () => { - return [...this.calendarModel.getViewPath()]; - }; - // state - this.currentTodayDate = void 0; - this.selectedYear = new State(0); - this.selectedMonth = new State(0); - this.selectedDate = new State(0); - this.monthGrid = new State(void 0); - // methods - this.createEvent = () => { - const 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 = new TaskViewModel( - this.coreViewModel, - this.chatViewModel, - this.boardsAndTasksModel, - this, - taskFileContent - ); - taskViewModel.open(); - this.updateTaskIndices(); - }; - this.getEventsForDate = () => { - const paddedDate = CalendarModel.padDateOrMonth( - this.selectedDate.toString() - ); - if (this.monthGrid.value == void 0) { - return void 0; - } - return this.monthGrid.value.days[paddedDate]; - }; - // view - this.getTaskMapState = (taskFileContent) => { - if (this.monthGrid.value == null) return null; - const date = CalendarModel.isoToDateString( - taskFileContent.date ?? "" - ); - return this.monthGrid.value.days[date]; - }; - this.showTask = (taskFileContent) => { - const monthString = CalendarModel.isoToMonthString( - taskFileContent.date ?? "" - ); - if (monthString == void 0 || monthString != this.monthString) { - this.removeTaskFromView(taskFileContent); - this.calendarModel.deleteTaskReference( - this.monthString, - taskFileContent.fileId - ); - return; - } - const taskViewModel = new TaskViewModel( - this.coreViewModel, - this.chatViewModel, - this.boardsAndTasksModel, - this, - taskFileContent - ); - const mapState = 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); - this.updateTaskIndices(); - }; - this.removeTaskFromView = (taskFileContent) => { - this.taskViewModels.remove(taskFileContent.fileId); - }; - this.showToday = () => { - const today = this.coreViewModel.todayDate.value; - this.selectedYear.value = today.getFullYear(); - this.selectedMonth.value = today.getMonth() + 1; - this.selectedDate.value = today.getDate(); - }; - this.showPreviousMonth = () => { - this.selectedMonth.value -= 1; - if (this.selectedMonth.value <= 0) { - this.selectedYear.value -= 1; - this.selectedMonth.value = 12; - } - }; - this.showNextMonth = () => { - this.selectedMonth.value += 1; - if (this.selectedMonth.value >= 13) { - this.selectedYear.value += 1; - this.selectedMonth.value = 1; - } - }; - this.handleDrop = (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); - }; - this.updateMonthGrid = () => { - if (this.currentTodayDate == this.coreViewModel.todayDate.value.toISOString()) - return; - this.loadMonthTasks(); - this.currentTodayDate = this.coreViewModel.todayDate.value.toISOString(); - }; - // load - this.loadMonthTasks = () => { - this.monthGrid.value = this.calendarModel.generateMonthGrid( - this.coreViewModel, - this.selectedYear.value, - this.selectedMonth.value, - () => new MapState() - ); - const taskIds = this.calendarModel.listTaskIds( - this.monthString - ); - for (const taskId of taskIds) { - const taskFileContent = this.boardsAndTasksModel.getLatestTaskFileContent(taskId); - if (taskFileContent == null) continue; - this.showTask(taskFileContent); - } - }; - this.loadData = () => { - this.loadMonthTasks(); - }; - this.calendarModel = calendarModel; - this.boardsAndTasksModel = boardsAndTasksModel; - this.chatViewModel = chatViewModel; - bulkSubscribe([this.selectedYear, this.selectedMonth], () => { - this.loadMonthTasks(); - }); - boardsAndTasksModel.taskHandlerManager.setHandler( - "calendar" + this.chatViewModel.chatModel.id, - (taskFileContent) => { - this.showTask(taskFileContent); - } - ); - this.coreViewModel.chronHandlerManager.setHandler( - `calendar-${this.chatViewModel.chatModel.id}`, - this.updateMonthGrid - ); - this.showToday(); - this.registerKeyStroke("-" /* Reset */, this.showToday); - this.registerKeyStroke("k", this.showPreviousMonth); - this.registerKeyStroke("l", this.showNextMonth); - this.registerKeyStroke(";" /* Create */, this.createEvent); - this.chatViewModel.registerContext("calendar" /* Calendar */, this); - } - // data - get monthString() { - return CalendarModel.getMonthString( - this.selectedYear.value.toString(), - this.selectedMonth.value.toString() - ); - } - }; - - // src/ViewModel/Chat/chatMessageViewModel.ts - var ChatMessageViewModel = class { - // init - constructor(coreViewModel2, messagePageViewModel, chatMessage, sentByUser) { - this.coreViewModel = coreViewModel2; - this.messagePageViewModel = messagePageViewModel; - this.body = new State(""); - this.inlineReply = void 0; - this.status = new State( - void 0 - ); - this.isHidden = new State(false); - this.allReactions = new MapState(); - this.userReaction = new State( - void 0 - ); - this.reactionsThumbsUp = MapState; - this.reactionsCheck = MapState; - this.reactionsStop = MapState; - this.reactionsAttention = MapState; - this.reactionsDoubleAttention = MapState; - this.reactionsQuestion = MapState; - this.generateReactionCountProxyState = (content) => { - return createProxyState( - [this.allReactions], - () => [...this.allReactions.value.values()].filter( - (x) => x.content == content - ).length - ); - }; - this.reactionsThumbsUpCount = this.generateReactionCountProxyState( - "\u{1F44D}" /* ThumbsUp */ - ); - this.reactionsCheckCount = this.generateReactionCountProxyState( - "\u2705" /* Check */ - ); - this.reactionsStopCount = this.generateReactionCountProxyState( - "\u{1F6D1}" /* Stop */ - ); - this.reactionsAttentionCount = this.generateReactionCountProxyState( - "\u2757\uFE0F" /* Attention */ - ); - this.reactionsDoubleAttentionCount = this.generateReactionCountProxyState( - "\u203C\uFE0F" /* DoubleAttention */ - ); - this.reactionsQuestionCount = this.generateReactionCountProxyState( - "\u2753" /* Question */ - ); - // state - this.isPresentingInfoModal = new State(false); - // methods - this.copyMessage = () => { - navigator.clipboard.writeText(this.body.value); - }; - this.resendMessage = () => { - this.messagePageViewModel.sendMessageFromBody(this.body.value); - }; - this.decryptMessage = () => { - this.messagePageViewModel.decryptMessage(this); - }; - this.reply = () => { - this.messagePageViewModel.setReply(this); - this.hideInfoModal(); - }; - this.cancelReply = () => { - if (this.messagePageViewModel.replyingMessage.value != this) return; - this.messagePageViewModel.setReply(void 0); - }; - // view - this.showInfoModal = () => { - this.isPresentingInfoModal.value = true; - }; - this.hideInfoModal = () => { - this.isPresentingInfoModal.value = false; - }; - // reactions - this.handleReaction = (reaction) => { - if (reaction.isDeleting) { - this.allReactions.remove(reaction.sender); - } else { - this.allReactions.set(reaction.sender, reaction); - } - if (reaction.sender != this.coreViewModel.settingsModel.username) - return; - this.userReaction.value = reaction.isDeleting ? void 0 : reaction.content; - }; - this.sendReaction = (content, isDeleting) => { - this.messagePageViewModel.sendReaction( - this.chatMessage.id, - content, - isDeleting - ); - }; - // load - this.loadData = () => { - 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; - if (this.chatMessage.inlineReplyId) { - this.inlineReply = this.messagePageViewModel.chatMessageViewModels.value.get( - this.chatMessage.inlineReplyId - ); - } - }; - this.chatMessage = chatMessage; - this.sentByUser = sentByUser; - this.loadData(); - this.messagePageViewModel.reactionFilter.subscribe((content) => { - if (content == void 0) { - this.isHidden.value = false; - return; - } - let count = 0; - switch (content) { - case "\u{1F44D}" /* ThumbsUp */: { - count = this.reactionsThumbsUpCount.value; - break; - } - case "\u2705" /* Check */: { - count = this.reactionsCheckCount.value; - break; - } - case "\u{1F6D1}" /* Stop */: { - count = this.reactionsStopCount.value; - break; - } - case "\u2757\uFE0F" /* Attention */: { - count = this.reactionsAttentionCount.value; - break; - } - case "\u203C\uFE0F" /* DoubleAttention */: { - count = this.reactionsDoubleAttentionCount.value; - break; - } - case "\u2753" /* Question */: { - count = this.reactionsQuestionCount.value; - break; - } - } - this.isHidden.value = count == 0; - }); - } - }; - // src/ViewModel/Utility/searchViewModel.ts var SearchViewModel = class { // init @@ -3069,235 +2721,6 @@ } }; - // src/ViewModel/Pages/messagePageViewModel.ts - var MessagePageViewModel = class extends Context { - // init - constructor(coreViewModel2, chatViewModel) { - super("message-page"); - this.coreViewModel = coreViewModel2; - this.chatViewModel = chatViewModel; - // state - this.chatMessageViewModels = new MapState(); - this.filteredMessageViewModels = new ListState(); - this.isFilterModalOpen = new State(false); - this.reactionFilter = new State(void 0); - this.replyingMessage = new State( - void 0 - ); - this.composingMessage = new State(""); - this.focusSetter = new State(null); - // methods - this.sendMessage = () => { - if (this.cannotSendMessage.value == true) return; - this.sendMessageFromBody(this.composingMessage.value); - this.composingMessage.value = ""; - }; - this.sendMessageFromBody = (body) => { - let replyId = void 0; - if (this.replyingMessage.value) { - replyId = this.replyingMessage.value.chatMessage.id; - } - this.chatViewModel.chatModel.sendMessage(body, replyId); - this.replyingMessage.value = void 0; - }; - this.decryptMessage = async (messageViewModel) => { - const chatMessage = messageViewModel.chatMessage; - await this.chatViewModel.chatModel.decryptMessage(chatMessage); - this.chatViewModel.chatModel.addMessage(chatMessage); - messageViewModel.loadData(); - }; - this.sendReaction = (messageId, content, isDeleting) => { - this.chatViewModel.chatModel.sendReaction( - messageId, - content, - 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( - this.coreViewModel, - this, - chatMessage, - chatMessage.sender == this.chatViewModel.settingsViewModel.username.value - ); - const existingChatMessageViewModel = this.chatMessageViewModels.value.get(chatMessage.id); - if (existingChatMessageViewModel != void 0) { - existingChatMessageViewModel.body.value = chatMessage.body; - existingChatMessageViewModel.status.value = chatMessage.status; - } else { - this.chatMessageViewModels.set( - chatMessage.id, - chatMessageViewModel - ); - } - }; - this.handleReaction = (reaction) => { - const messageViewModel = this.chatMessageViewModels.value.get(reaction.messageId); - if (messageViewModel == void 0) return; - messageViewModel.handleReaction(reaction); - }; - this.showFilterModal = () => { - this.isFilterModalOpen.value = true; - }; - this.hideFilterModal = () => { - this.isFilterModalOpen.value = false; - }; - this.revokeReactionFilter = () => { - this.reactionFilter.value = void 0; - }; - this.setReactionFilter = (content) => { - this.reactionFilter.value = content; - }; - this.resetFilter = () => { - this.revokeReactionFilter(); - this.searchViewModel.search(""); - }; - this.setFocus = () => { - this.focusSetter.callSubscriptions(); - }; - // load - this.loadData = () => { - this.chatMessageViewModels.clear(); - for (const chatMessage of this.chatViewModel.chatModel.messages) { - this.showChatMessage(chatMessage); - } - for (const reaction of this.chatViewModel.chatModel.reactions) { - this.handleReaction(reaction); - } - }; - this.cannotSendMessage = createProxyState( - [ - this.chatViewModel.settingsViewModel.username, - this.composingMessage - ], - () => this.chatViewModel.settingsViewModel.username.value == "" || this.composingMessage.value == "" - ); - this.searchViewModel = new SearchViewModel( - this.chatMessageViewModels, - this.filteredMessageViewModels, - (chatMessageViewModel) => [chatMessageViewModel.body.value], - new ListState() - ); - this.isFilterActive = createProxyState( - [this.searchViewModel.appliedQuery, this.reactionFilter], - () => this.searchViewModel.appliedQuery.value != "" || this.reactionFilter.value != void 0 - ); - this.registerKeyStroke("=" /* Filter */, this.showFilterModal); - 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); - } - }; - - // src/ViewModel/Pages/settingsPageViewModel.ts - var SettingsPageViewModel = class extends Context { - // init - constructor(coreViewModel2, chatViewModel) { - super("settings"); - this.coreViewModel = coreViewModel2; - this.chatViewModel = chatViewModel; - // state - this.primaryChannel = new State(""); - this.primaryChannelInput = new State(""); - this.secondaryChannels = new ListState(); - this.newSecondaryChannelInput = new State(""); - this.encryptionKeyInput = new State(""); - this.shouldShowEncryptionKey = new State(false); - this.encryptionKeyInputType = createProxyState( - [this.shouldShowEncryptionKey], - () => this.shouldShowEncryptionKey.value == true ? "text" : "password" - ); - this.color = new State("standard" /* Standard */); - // guards - this.cannotSetPrimaryChannel = createProxyState( - [this.primaryChannel, this.primaryChannelInput], - () => this.primaryChannelInput.value == "" || this.primaryChannelInput.value == this.primaryChannel.value - ); - this.cannotAddSecondaryChannel = createProxyState( - [this.newSecondaryChannelInput], - () => this.newSecondaryChannelInput.value == "" - ); - // methods - this.setPrimaryChannel = () => { - this.chatViewModel.chatModel.setPrimaryChannel( - this.primaryChannelInput.value - ); - this.primaryChannel.value = this.chatViewModel.chatModel.info.primaryChannel; - this.chatViewModel.chatListViewModel.updateIndices(); - }; - this.addSecondaryChannel = () => { - this.secondaryChannels.add(this.newSecondaryChannelInput.value); - this.newSecondaryChannelInput.value = ""; - this.storeSecondaryChannels(); - this.loadSecondaryChannels(); - }; - this.removeSecondaryChannel = (secondaryChannel) => { - this.secondaryChannels.remove(secondaryChannel); - this.storeSecondaryChannels(); - }; - this.storeSecondaryChannels = () => { - this.chatViewModel.chatModel.setSecondaryChannels([ - ...this.secondaryChannels.value.values() - ]); - }; - this.setEncryptionKey = () => { - this.chatViewModel.chatModel.setEncryptionKey( - this.encryptionKeyInput.value - ); - this.encryptionKeyInput.callSubscriptions(); - }; - this.applyColor = (newColor) => { - this.chatViewModel.setColor(newColor); - }; - this.remove = () => { - this.chatViewModel.close(); - this.chatViewModel.chatModel.delete(); - this.chatViewModel.chatListViewModel.untrackChat(this.chatViewModel); - }; - // load - this.preloadData = () => { - this.primaryChannel.value = this.chatViewModel.chatModel.info.primaryChannel; - this.color.value = this.chatViewModel.chatModel.color; - }; - this.loadData = () => { - this.primaryChannelInput.value = this.chatViewModel.chatModel.info.primaryChannel; - this.loadSecondaryChannels(); - this.encryptionKeyInput.value = this.chatViewModel.chatModel.info.encryptionKey; - }; - this.loadSecondaryChannels = () => { - this.secondaryChannels.clear(); - for (const secondaryChannel of this.chatViewModel.chatModel.secondaryChannels) { - this.secondaryChannels.add(secondaryChannel); - } - }; - this.preloadData(); - this.cannotSetEncryptionKey = createProxyState( - [this.encryptionKeyInput], - () => this.encryptionKeyInput.value == this.chatViewModel.chatModel.info.encryptionKey - ); - this.color.subscribe((newColor) => { - this.applyColor(newColor); - }); - this.chatViewModel.registerContext("settings" /* Settings */, this); - } - }; - // src/ViewModel/Pages/boardViewModel.ts var BoardViewModel = class extends TaskContainingPageViewModel { // init @@ -3698,6 +3121,968 @@ } }; + // src/ViewModel/Pages/settingsPageViewModel.ts + var SettingsPageViewModel = class extends Context { + // init + constructor(coreViewModel2, chatViewModel) { + super("settings"); + this.coreViewModel = coreViewModel2; + this.chatViewModel = chatViewModel; + // state + this.primaryChannel = new State(""); + this.primaryChannelInput = new State(""); + this.secondaryChannels = new ListState(); + this.newSecondaryChannelInput = new State(""); + this.encryptionKeyInput = new State(""); + this.shouldShowEncryptionKey = new State(false); + this.encryptionKeyInputType = createProxyState( + [this.shouldShowEncryptionKey], + () => this.shouldShowEncryptionKey.value == true ? "text" : "password" + ); + this.color = new State("standard" /* Standard */); + // guards + this.cannotSetPrimaryChannel = createProxyState( + [this.primaryChannel, this.primaryChannelInput], + () => this.primaryChannelInput.value == "" || this.primaryChannelInput.value == this.primaryChannel.value + ); + this.cannotAddSecondaryChannel = createProxyState( + [this.newSecondaryChannelInput], + () => this.newSecondaryChannelInput.value == "" + ); + // methods + this.setPrimaryChannel = () => { + this.chatViewModel.chatModel.setPrimaryChannel( + this.primaryChannelInput.value + ); + this.primaryChannel.value = this.chatViewModel.chatModel.info.primaryChannel; + this.chatViewModel.chatListViewModel.updateIndices(); + }; + this.addSecondaryChannel = () => { + this.secondaryChannels.add(this.newSecondaryChannelInput.value); + this.newSecondaryChannelInput.value = ""; + this.storeSecondaryChannels(); + this.loadSecondaryChannels(); + }; + this.removeSecondaryChannel = (secondaryChannel) => { + this.secondaryChannels.remove(secondaryChannel); + this.storeSecondaryChannels(); + }; + this.storeSecondaryChannels = () => { + this.chatViewModel.chatModel.setSecondaryChannels([ + ...this.secondaryChannels.value.values() + ]); + }; + this.setEncryptionKey = () => { + this.chatViewModel.chatModel.setEncryptionKey( + this.encryptionKeyInput.value + ); + this.encryptionKeyInput.callSubscriptions(); + }; + this.applyColor = (newColor) => { + this.chatViewModel.setColor(newColor); + }; + this.remove = () => { + this.chatViewModel.close(); + this.chatViewModel.chatModel.delete(); + this.chatViewModel.chatListViewModel.untrackChat(this.chatViewModel); + }; + // load + this.preloadData = () => { + this.primaryChannel.value = this.chatViewModel.chatModel.info.primaryChannel; + this.color.value = this.chatViewModel.chatModel.color; + }; + this.loadData = () => { + this.primaryChannelInput.value = this.chatViewModel.chatModel.info.primaryChannel; + this.loadSecondaryChannels(); + this.encryptionKeyInput.value = this.chatViewModel.chatModel.info.encryptionKey; + }; + this.loadSecondaryChannels = () => { + this.secondaryChannels.clear(); + for (const secondaryChannel of this.chatViewModel.chatModel.secondaryChannels) { + this.secondaryChannels.add(secondaryChannel); + } + }; + this.preloadData(); + this.cannotSetEncryptionKey = createProxyState( + [this.encryptionKeyInput], + () => this.encryptionKeyInput.value == this.chatViewModel.chatModel.info.encryptionKey + ); + this.color.subscribe((newColor) => { + this.applyColor(newColor); + }); + this.chatViewModel.registerContext("settings" /* Settings */, this); + } + }; + + // src/Model/Utility/crypto.ts + var IV_SIZE = 12; + var ENCRYPTION_ALG = "AES-GCM"; + async function encryptString(plaintext, passphrase) { + if (!window.crypto.subtle) return 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)); + } + async function decryptString(cyphertext, passphrase) { + 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; + } + } + function encode(string) { + return new TextEncoder().encode(string); + } + function decode(array) { + return new TextDecoder("utf-8").decode(array); + } + async function encrypt(iv, key, message) { + const arrayBuffer = await window.crypto.subtle.encrypt( + { name: ENCRYPTION_ALG, iv }, + key, + encode(message) + ); + return new Uint8Array(arrayBuffer); + } + async function decrypt(iv, key, cyphertext) { + const arrayBuffer = await crypto.subtle.decrypt( + { name: ENCRYPTION_ALG, iv }, + key, + cyphertext + ); + return arrayBufferToString(arrayBuffer); + } + async function hash(encoded) { + return await crypto.subtle.digest("SHA-256", encoded); + } + function generateIV() { + return crypto.getRandomValues(new Uint8Array(IV_SIZE)); + } + async function importKey(passphrase, purpose) { + return await crypto.subtle.importKey( + "raw", + await hash(encode(passphrase)), + { name: ENCRYPTION_ALG }, + false, + [purpose] + ); + } + function arrayBufferToString(arrayBuffer) { + const uInt8Array = new Uint8Array(arrayBuffer); + return decode(uInt8Array); + } + function uInt8ToArray(uInt8Array) { + return Array.from(uInt8Array); + } + function arrayToUint8(array) { + return new Uint8Array(array); + } + + // src/Model/Chat/chatModel.ts + var ChatModel = class _ChatModel { + // init + constructor(storageModel2, connectionModel2, settingsModel2, chatListModel2, chatId) { + // handler managers + this.chatMessageHandlerManager = new HandlerManager(); + this.reactionHandlerManager = new HandlerManager(); + // paths + this.getBasePath = () => { + return StorageModel.getPath( + "chat" /* Chat */, + filePaths.chat.chatBase(this.id) + ); + }; + this.getInfoPath = () => { + return StorageModel.getPath( + "chat" /* Chat */, + filePaths.chat.info(this.id) + ); + }; + this.getColorPath = () => { + return StorageModel.getPath( + "chat" /* Chat */, + filePaths.chat.color(this.id) + ); + }; + this.getMessageDirPath = () => { + return StorageModel.getPath( + "chat" /* Chat */, + filePaths.chat.messages(this.id) + ); + }; + this.getMessagePath = (id) => { + return [...this.getMessageDirPath(), id]; + }; + this.getReactionDirPath = () => { + return StorageModel.getPath( + "chat" /* Chat */, + filePaths.chat.reactions(this.id) + ); + }; + this.getReactionPath = (id) => { + return [...this.getReactionDirPath(), id]; + }; + // handlers + this.handleMessage = (body) => { + const chatMessage = parseValidObject( + body, + ChatMessageReference + ); + if (chatMessage == null) return; + chatMessage.status = "received" /* Received */; + this.addMessage(chatMessage); + if (chatMessage.stringifiedFile) return; + this.setReadStatus(true); + }; + this.handleReaction = (reaction) => { + if (!checkMatchesObjectStructure(reaction, ChatMessageReactionReference)) + return; + const reactionPath = this.getReactionPath(reaction.fileId); + if (reaction.isDeleting == true) { + this.storageModel.remove(reactionPath); + } else { + this.storageModel.writeStringifiable(reactionPath, reaction); + } + this.reactionHandlerManager.trigger(reaction); + }; + this.handleMessageSent = (chatMessage) => { + chatMessage.status = "sent" /* Sent */; + this.addMessage(chatMessage); + }; + // settings + this.setPrimaryChannel = (primaryChannel) => { + this.info.primaryChannel = primaryChannel; + this.storeInfo(); + this.subscribe(); + }; + this.setSecondaryChannels = (secondaryChannels) => { + this.info.secondaryChannels = secondaryChannels; + this.storeInfo(); + }; + this.setEncryptionKey = (key) => { + this.info.encryptionKey = key; + this.storeInfo(); + }; + this.setColor = (color) => { + this.color = color; + this.storeColor(); + }; + // messaging + this.addMessage = async (chatMessage) => { + await this.decryptMessage(chatMessage); + if (chatMessage.body != "") { + const messagePath = this.getMessagePath(chatMessage.id); + this.storageModel.writeStringifiable(messagePath, chatMessage); + this.chatMessageHandlerManager.trigger(chatMessage); + } + this.fileModel.handleStringifiedFileContent( + chatMessage.stringifiedFile + ); + }; + this.getNameAndChannel = () => { + 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 = allChannels.join("/"); + return [senderName, combinedChannel]; + }; + this.sendMessage = async (body, inlineReplyId, fileContent) => { + const nameAndChannel = this.getNameAndChannel(); + if (nameAndChannel == false) return; + const [senderName, combinedChannel] = nameAndChannel; + const chatMessage = await _ChatModel.createChatMessage( + combinedChannel, + senderName, + this.info.encryptionKey, + body, + inlineReplyId, + fileContent + ); + this.addMessage(chatMessage); + this.connectionModel.sendMessageOrStore(chatMessage); + return true; + }; + this.decryptMessage = async (chatMessage) => { + const decryptedBody = await decryptString( + chatMessage.body, + this.info.encryptionKey + ); + const decryptedFile = await decryptString( + chatMessage.stringifiedFile ?? "", + this.info.encryptionKey + ); + chatMessage.body = decryptedBody; + chatMessage.stringifiedFile = decryptedFile; + }; + this.sendReaction = async (messageId, content, isDeleting) => { + const nameAndChannel = this.getNameAndChannel(); + if (nameAndChannel == false) return; + const [senderName, combinedChannel] = nameAndChannel; + const reaction = _ChatModel.createMessageReaction( + messageId, + senderName, + content, + isDeleting + ); + this.sendMessage("", void 0, reaction); + this.handleReaction(reaction); + }; + this.subscribe = () => { + this.connectionModel.addChannel(this.info.primaryChannel); + }; + this.setReadStatus = (hasUnreadMessages) => { + this.info.hasUnreadMessages = hasUnreadMessages; + this.storeInfo(); + }; + // storage + this.storeInfo = () => { + this.storageModel.writeStringifiable(this.getInfoPath(), this.info); + }; + this.storeColor = () => { + this.storageModel.write(this.getColorPath(), this.color); + }; + this.delete = () => { + this.chatListModel.untrackChat(this); + const dirPath = this.getBasePath(); + this.storageModel.removeRecursively(dirPath); + }; + // load + this.loadInfo = () => { + const info = this.storageModel.readStringifiable( + this.getInfoPath(), + ChatInfoReference + ); + if (info != null) { + this.info = info; + } else { + this.info = _ChatModel.generateChatInfo("0"); + } + }; + this.loadColor = () => { + const path = this.getColorPath(); + const color = this.storageModel.read(path); + if (!color) { + this.color = "standard" /* Standard */; + } else { + this.color = color; + } + }; + this.id = chatId; + this.connectionModel = connectionModel2; + this.settingsModel = settingsModel2; + this.storageModel = storageModel2; + this.chatListModel = chatListModel2; + this.loadInfo(); + this.loadColor(); + this.subscribe(); + this.fileModel = new FileModel( + this.storageModel, + this.settingsModel, + this + ); + } + get secondaryChannels() { + return this.info.secondaryChannels.sort(localeCompare); + } + get messages() { + const messageIds = this.storageModel.list( + this.getMessageDirPath() + ); + if (!Array.isArray(messageIds)) return []; + const chatMessages = []; + for (const messageId of messageIds) { + const messagePath = this.getMessagePath(messageId); + const chatMessage = this.storageModel.readStringifiable( + messagePath, + ChatMessageReference + ); + if (chatMessage == null) continue; + chatMessages.push(chatMessage); + } + const sorted = chatMessages.sort( + (a, b) => a.dateSent.localeCompare(b.dateSent) + ); + return sorted; + } + get reactions() { + const reactionIds = this.storageModel.list( + this.getReactionDirPath() + ); + if (!Array.isArray(reactionIds)) return []; + const reactions = []; + for (const reactionId of reactionIds) { + const reactionPath = this.getReactionPath(reactionId); + const reaction = this.storageModel.readStringifiable( + reactionPath, + ChatMessageReactionReference + ); + if (reaction == null) continue; + reactions.push(reaction); + } + return reactions; + } + // utility + static splitChannel(channelString) { + return channelString.split("/"); + } + static { + this.generateChatInfo = (primaryChannel) => { + return { + dataVersion: DATA_VERSION, + primaryChannel, + secondaryChannels: [], + encryptionKey: "", + hasUnreadMessages: false + }; + }; + } + static { + this.createChatMessage = async (channel, sender, encryptionKey, body, inlineReplyId, fileContent) => { + const chatMessage = { + dataVersion: DATA_VERSION, + id: v4_default(), + channel, + sender, + body, + dateSent: createTimestamp(), + inlineReplyId, + status: "outbox" /* Outbox */, + stringifiedFile: "" + }; + if (fileContent != void 0) { + 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; + }; + } + static { + this.createMessageReaction = (messageId, sender, content, isDeleting) => { + const fileContent = FileModel.createFileContent(v4_default(), "reaction"); + const reaction = { + ...fileContent, + fileId: _ChatModel.createMessageReactionId(messageId, sender), + messageId, + sender, + content, + isDeleting + }; + return reaction; + }; + } + static { + this.createMessageReactionId = (messageId, sender) => { + return messageId + sender; + }; + } + }; + var ChatInfoReference = { + dataVersion: DATA_VERSION, + primaryChannel: "", + secondaryChannels: [""], + encryptionKey: "", + hasUnreadMessages: true + }; + var ChatMessageReference = { + dataVersion: DATA_VERSION, + id: "", + channel: "", + sender: "", + body: "", + dateSent: "", + status: "", + stringifiedFile: "" + }; + var ChatMessageReactionReference = { + dataVersion: DATA_VERSION, + fileId: "", + fileContentId: "", + creationDate: "", + type: "reaction", + messageId: "", + sender: "", + content: "", + isDeleting: false + }; + + // src/ViewModel/Chat/chatMessageViewModel.ts + var ChatMessageViewModel = class { + // init + constructor(coreViewModel2, messagePageViewModel, chatMessage, sentByUser) { + this.coreViewModel = coreViewModel2; + this.messagePageViewModel = messagePageViewModel; + this.body = new State(""); + this.inlineReply = void 0; + this.status = new State( + void 0 + ); + this.isHidden = new State(false); + this.allReactions = new MapState(); + this.userReaction = new State( + void 0 + ); + this.reactionsThumbsUp = MapState; + this.reactionsCheck = MapState; + this.reactionsStop = MapState; + this.reactionsAttention = MapState; + this.reactionsDoubleAttention = MapState; + this.reactionsQuestion = MapState; + this.generateReactionCountProxyState = (content) => { + return createProxyState( + [this.allReactions], + () => [...this.allReactions.value.values()].filter( + (x) => x.content == content + ).length + ); + }; + this.reactionsThumbsUpCount = this.generateReactionCountProxyState( + "\u{1F44D}" /* ThumbsUp */ + ); + this.reactionsCheckCount = this.generateReactionCountProxyState( + "\u2705" /* Check */ + ); + this.reactionsStopCount = this.generateReactionCountProxyState( + "\u{1F6D1}" /* Stop */ + ); + this.reactionsAttentionCount = this.generateReactionCountProxyState( + "\u2757\uFE0F" /* Attention */ + ); + this.reactionsDoubleAttentionCount = this.generateReactionCountProxyState( + "\u203C\uFE0F" /* DoubleAttention */ + ); + this.reactionsQuestionCount = this.generateReactionCountProxyState( + "\u2753" /* Question */ + ); + // state + this.isPresentingInfoModal = new State(false); + // methods + this.copyMessage = () => { + navigator.clipboard.writeText(this.body.value); + }; + this.resendMessage = () => { + this.messagePageViewModel.sendMessageFromBody(this.body.value); + }; + this.decryptMessage = () => { + this.messagePageViewModel.decryptMessage(this); + }; + this.reply = () => { + this.messagePageViewModel.setReply(this); + this.hideInfoModal(); + }; + this.cancelReply = () => { + if (this.messagePageViewModel.replyingMessage.value != this) return; + this.messagePageViewModel.setReply(void 0); + }; + // view + this.showInfoModal = () => { + this.isPresentingInfoModal.value = true; + }; + this.hideInfoModal = () => { + this.isPresentingInfoModal.value = false; + }; + // reactions + this.handleReaction = (reaction) => { + if (reaction.isDeleting) { + this.allReactions.remove(reaction.sender); + } else { + this.allReactions.set(reaction.sender, reaction); + } + if (reaction.sender != this.coreViewModel.settingsModel.username) + return; + this.userReaction.value = reaction.isDeleting ? void 0 : reaction.content; + }; + this.sendReaction = (content, isDeleting) => { + this.messagePageViewModel.sendReaction( + this.chatMessage.id, + content, + isDeleting + ); + }; + // load + this.loadData = () => { + 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; + if (this.chatMessage.inlineReplyId) { + this.inlineReply = this.messagePageViewModel.chatMessageViewModels.value.get( + this.chatMessage.inlineReplyId + ); + } + }; + this.chatMessage = chatMessage; + this.sentByUser = sentByUser; + this.loadData(); + this.messagePageViewModel.reactionFilter.subscribe((content) => { + if (content == void 0) { + this.isHidden.value = false; + return; + } + let count = 0; + switch (content) { + case "\u{1F44D}" /* ThumbsUp */: { + count = this.reactionsThumbsUpCount.value; + break; + } + case "\u2705" /* Check */: { + count = this.reactionsCheckCount.value; + break; + } + case "\u{1F6D1}" /* Stop */: { + count = this.reactionsStopCount.value; + break; + } + case "\u2757\uFE0F" /* Attention */: { + count = this.reactionsAttentionCount.value; + break; + } + case "\u203C\uFE0F" /* DoubleAttention */: { + count = this.reactionsDoubleAttentionCount.value; + break; + } + case "\u2753" /* Question */: { + count = this.reactionsQuestionCount.value; + break; + } + } + this.isHidden.value = count == 0; + }); + } + }; + + // src/ViewModel/Pages/messagePageViewModel.ts + var MessagePageViewModel = class extends Context { + // init + constructor(coreViewModel2, chatViewModel) { + super("message-page"); + this.coreViewModel = coreViewModel2; + this.chatViewModel = chatViewModel; + // state + this.chatMessageViewModels = new MapState(); + this.filteredMessageViewModels = new ListState(); + this.isFilterModalOpen = new State(false); + this.reactionFilter = new State(void 0); + this.replyingMessage = new State( + void 0 + ); + this.composingMessage = new State(""); + this.focusSetter = new State(null); + // methods + this.sendMessage = () => { + if (this.cannotSendMessage.value == true) return; + this.sendMessageFromBody(this.composingMessage.value); + this.composingMessage.value = ""; + }; + this.sendMessageFromBody = (body) => { + let replyId = void 0; + if (this.replyingMessage.value) { + replyId = this.replyingMessage.value.chatMessage.id; + } + this.chatViewModel.chatModel.sendMessage(body, replyId); + this.replyingMessage.value = void 0; + }; + this.decryptMessage = async (messageViewModel) => { + const chatMessage = messageViewModel.chatMessage; + await this.chatViewModel.chatModel.decryptMessage(chatMessage); + this.chatViewModel.chatModel.addMessage(chatMessage); + messageViewModel.loadData(); + }; + this.sendReaction = (messageId, content, isDeleting) => { + this.chatViewModel.chatModel.sendReaction( + messageId, + content, + 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( + this.coreViewModel, + this, + chatMessage, + chatMessage.sender == this.chatViewModel.settingsViewModel.username.value + ); + const existingChatMessageViewModel = this.chatMessageViewModels.value.get(chatMessage.id); + if (existingChatMessageViewModel != void 0) { + existingChatMessageViewModel.body.value = chatMessage.body; + existingChatMessageViewModel.status.value = chatMessage.status; + } else { + this.chatMessageViewModels.set( + chatMessage.id, + chatMessageViewModel + ); + } + }; + this.handleReaction = (reaction) => { + const messageViewModel = this.chatMessageViewModels.value.get(reaction.messageId); + if (messageViewModel == void 0) return; + messageViewModel.handleReaction(reaction); + }; + this.showFilterModal = () => { + this.isFilterModalOpen.value = true; + }; + this.hideFilterModal = () => { + this.isFilterModalOpen.value = false; + }; + this.revokeReactionFilter = () => { + this.reactionFilter.value = void 0; + }; + this.setReactionFilter = (content) => { + this.reactionFilter.value = content; + }; + this.resetFilter = () => { + this.revokeReactionFilter(); + this.searchViewModel.search(""); + }; + this.setFocus = () => { + this.focusSetter.callSubscriptions(); + }; + // load + this.loadData = () => { + this.chatMessageViewModels.clear(); + for (const chatMessage of this.chatViewModel.chatModel.messages) { + this.showChatMessage(chatMessage); + } + for (const reaction of this.chatViewModel.chatModel.reactions) { + this.handleReaction(reaction); + } + }; + this.cannotSendMessage = createProxyState( + [ + this.chatViewModel.settingsViewModel.username, + this.composingMessage + ], + () => this.chatViewModel.settingsViewModel.username.value == "" || this.composingMessage.value == "" + ); + this.searchViewModel = new SearchViewModel( + this.chatMessageViewModels, + this.filteredMessageViewModels, + (chatMessageViewModel) => [chatMessageViewModel.body.value], + new ListState() + ); + this.isFilterActive = createProxyState( + [this.searchViewModel.appliedQuery, this.reactionFilter], + () => this.searchViewModel.appliedQuery.value != "" || this.reactionFilter.value != void 0 + ); + this.registerKeyStroke("=" /* Filter */, this.showFilterModal); + 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); + } + }; + + // src/ViewModel/Pages/calendarPageViewModel.ts + var CALENDAR_EVENT_BOARD_ID = "events"; + var CalendarPageViewModel = class extends TaskContainingPageViewModel { + // init + constructor(coreViewModel2, chatViewModel, calendarModel, boardsAndTasksModel) { + super(coreViewModel2, chatViewModel, boardsAndTasksModel, "calendar"); + this.coreViewModel = coreViewModel2; + this.chatViewModel = chatViewModel; + this.calendarModel = calendarModel; + this.boardsAndTasksModel = boardsAndTasksModel; + // paths + this.getBasePath = () => { + return [...this.calendarModel.getViewPath()]; + }; + // state + this.currentTodayDate = void 0; + this.selectedYear = new State(0); + this.selectedMonth = new State(0); + this.selectedDate = new State(0); + this.monthGrid = new State(void 0); + // methods + this.createEvent = () => { + const 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 = new TaskViewModel( + this.coreViewModel, + this.chatViewModel, + this.boardsAndTasksModel, + this, + taskFileContent + ); + taskViewModel.open(); + this.updateTaskIndices(); + }; + this.getEventsForDate = () => { + const paddedDate = CalendarModel.padDateOrMonth( + this.selectedDate.toString() + ); + if (this.monthGrid.value == void 0) { + return void 0; + } + return this.monthGrid.value.days[paddedDate]; + }; + // view + this.getTaskMapState = (taskFileContent) => { + if (this.monthGrid.value == null) return null; + const date = CalendarModel.isoToDateString( + taskFileContent.date ?? "" + ); + return this.monthGrid.value.days[date]; + }; + this.showTask = (taskFileContent) => { + const monthString = CalendarModel.isoToMonthString( + taskFileContent.date ?? "" + ); + if (monthString == void 0 || monthString != this.monthString) { + this.removeTaskFromView(taskFileContent); + this.calendarModel.deleteTaskReference( + this.monthString, + taskFileContent.fileId + ); + return; + } + const taskViewModel = new TaskViewModel( + this.coreViewModel, + this.chatViewModel, + this.boardsAndTasksModel, + this, + taskFileContent + ); + const mapState = 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); + this.updateTaskIndices(); + }; + this.removeTaskFromView = (taskFileContent) => { + this.taskViewModels.remove(taskFileContent.fileId); + }; + this.showToday = () => { + const today = this.coreViewModel.todayDate.value; + this.selectedYear.value = today.getFullYear(); + this.selectedMonth.value = today.getMonth() + 1; + this.selectedDate.value = today.getDate(); + }; + this.showPreviousMonth = () => { + this.selectedMonth.value -= 1; + if (this.selectedMonth.value <= 0) { + this.selectedYear.value -= 1; + this.selectedMonth.value = 12; + } + }; + this.showNextMonth = () => { + this.selectedMonth.value += 1; + if (this.selectedMonth.value >= 13) { + this.selectedYear.value += 1; + this.selectedMonth.value = 1; + } + }; + this.handleDrop = (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); + }; + this.updateMonthGrid = () => { + if (this.currentTodayDate == this.coreViewModel.todayDate.value.toISOString()) + return; + this.loadMonthTasks(); + this.currentTodayDate = this.coreViewModel.todayDate.value.toISOString(); + }; + // load + this.loadMonthTasks = () => { + this.monthGrid.value = this.calendarModel.generateMonthGrid( + this.coreViewModel, + this.selectedYear.value, + this.selectedMonth.value, + () => new MapState() + ); + const taskIds = this.calendarModel.listTaskIds( + this.monthString + ); + for (const taskId of taskIds) { + const taskFileContent = this.boardsAndTasksModel.getLatestTaskFileContent(taskId); + if (taskFileContent == null) continue; + this.showTask(taskFileContent); + } + }; + this.loadData = () => { + this.loadMonthTasks(); + }; + this.calendarModel = calendarModel; + this.boardsAndTasksModel = boardsAndTasksModel; + this.chatViewModel = chatViewModel; + bulkSubscribe([this.selectedYear, this.selectedMonth], () => { + this.loadMonthTasks(); + }); + boardsAndTasksModel.taskHandlerManager.setHandler( + "calendar" + this.chatViewModel.chatModel.id, + (taskFileContent) => { + this.showTask(taskFileContent); + } + ); + this.coreViewModel.chronHandlerManager.setHandler( + `calendar-${this.chatViewModel.chatModel.id}`, + this.updateMonthGrid + ); + this.showToday(); + this.registerKeyStroke("-" /* Reset */, this.showToday); + this.registerKeyStroke("k", this.showPreviousMonth); + this.registerKeyStroke("l", this.showNextMonth); + this.registerKeyStroke(";" /* Create */, this.createEvent); + this.chatViewModel.registerContext("calendar" /* Calendar */, this); + } + // data + get monthString() { + return CalendarModel.getMonthString( + this.selectedYear.value.toString(), + this.selectedMonth.value.toString() + ); + } + }; + // src/ViewModel/Chat/chatViewModel.ts var ChatViewModel6 = class extends ContextHost { // init @@ -4003,82 +4388,6 @@ } }; - // src/View/Components/monthGrid.tsx - function MonthGrid2(coreViewModel2, monthGrid, selectedDate, handleDrop) { - const dayLabels = []; - let currentWeekday = monthGrid.firstDayOfWeek; - while (dayLabels.length < 7) { - dayLabels.push( - /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.regional.weekdays.abbreviated[currentWeekday]) - ); - currentWeekday++; - if (currentWeekday == 7) currentWeekday = 0; - } - const offsetElements = []; - for (let i = 0; i < monthGrid.offset; i++) { - offsetElements.push(/* @__PURE__ */ createElement("div", null)); - } - const converter = (taskViewModel) => { - const view = /* @__PURE__ */ createElement("span", { class: "ellipsis secondary" }, taskViewModel.task.name); - taskViewModel.index.subscribe((newIndex) => { - view.style.order = newIndex; - }); - return view; - }; - return /* @__PURE__ */ createElement("div", { class: "month-grid-wrapper" }, /* @__PURE__ */ createElement("div", { class: "day-labels" }, ...dayLabels), /* @__PURE__ */ createElement("div", { class: "month-grid" }, ...offsetElements, ...Object.entries(monthGrid.days).sort((a, b) => localeCompare(a[0], b[0])).map((entry) => { - const [date, mapState] = entry; - const isSelected = createProxyState( - [selectedDate], - () => selectedDate.value == parseInt(date) - ); - const isToday = createProxyState( - [coreViewModel2.todayDate], - () => { - return monthGrid.isCurrentMonth == true && date == coreViewModel2.unwrappedTodayDate.getDate().toString(); - } - ); - const eventCount = createProxyState( - [mapState], - () => mapState.value.size - ); - const hasEvents = createProxyState( - [eventCount], - () => eventCount.value != 0 - ); - function select() { - selectedDate.value = parseInt(date); - } - function drop() { - handleDrop(date); - } - return /* @__PURE__ */ createElement( - "button", - { - class: "tile", - "on:click": select, - "toggle:selected": isSelected, - "toggle:today": isToday, - "on:dragover": ViewController.allowDrop, - "on:drop": drop - }, - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, date), /* @__PURE__ */ createElement( - "span", - { - class: "event-count", - "toggle:has-events": hasEvents, - "subscribe:innerText": eventCount - } - ), /* @__PURE__ */ createElement( - "div", - { - class: "event-list", - "children:append": [mapState, converter] - } - )) - ); - }))); - } - // src/View/Components/option.tsx function Option(text, value, selectedOnCreate) { return /* @__PURE__ */ createElement("option", { value, "toggle:selected": selectedOnCreate }, text); @@ -4093,149 +4402,186 @@ return Option(readableName, versionId, false); }; - // src/View/Components/dangerousActionButton.tsx - function DangerousActionButton(coreViewModel2, label, icon, action) { - const isActionRequested = new State(false); - const cannotConfirm = createProxyState( - [isActionRequested], - () => isActionRequested.value == false - ); - function requestAction() { - isActionRequested.value = true; - } - function abort() { - isActionRequested.value = false; - } - return /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement("button", { class: "flex", "on:click": abort, "toggle:hidden": cannotConfirm }, coreViewModel2.translations.general.abortButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "undo")), /* @__PURE__ */ createElement( - "button", - { - class: "danger flex", - "on:click": requestAction, - "toggle:hidden": isActionRequested - }, - label, - /* @__PURE__ */ createElement("span", { class: "icon" }, icon) - ), /* @__PURE__ */ createElement( - "button", - { - class: "danger flex", - "on:click": action, - "toggle:hidden": cannotConfirm - }, - coreViewModel2.translations.general.confirmButton, - /* @__PURE__ */ createElement("span", { class: "icon" }, "warning") - )); + // src/View/Components/homePageButton.tsx + function HomePageButton(action, label, icon) { + return /* @__PURE__ */ createElement("button", { class: "tile flex-no", "on:click": action }, /* @__PURE__ */ createElement("span", { class: "icon" }, icon), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, label))); } - // src/View/Modals/taskSettingsModal.tsx - function TaskSettingsModal(coreViewModel2, taskViewModel) { - const categorySuggestionId = v4_default(); - const statusSuggestionId = v4_default(); - const BoardOptionConverter = (entry) => { - const isSelected = entry[0] == taskViewModel.task.boardId; - return Option(entry[1], entry[0], isSelected); - }; - return /* @__PURE__ */ createElement("div", { class: "modal", open: true }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.chatPage.task.taskSettingsHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "history"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.general.fileVersionLabel), /* @__PURE__ */ createElement( - "select", - { - "bind:value": taskViewModel.selectedVersionId, - "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, coreViewModel2.translations.chatPage.task.taskNameLabel), /* @__PURE__ */ createElement( - "input", - { - "bind:value": taskViewModel.name, - id: "focused" - } - ))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "category"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskBoardLabel), /* @__PURE__ */ createElement( - "select", - { - "bind:value": taskViewModel.boardId, - "children:append": [ - taskViewModel.chatViewModel.taskBoardSuggestions, - BoardOptionConverter - ] - } - ), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "description"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskDescriptionLabel), /* @__PURE__ */ createElement( - "textarea", - { - rows: "10", - "bind:value": taskViewModel.description - } - ))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "category"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskCategoryLabel), /* @__PURE__ */ createElement( - "input", - { - "bind:value": taskViewModel.category, - list: categorySuggestionId - } - ))), /* @__PURE__ */ createElement( - "datalist", - { - hidden: true, - id: categorySuggestionId, - "children:append": [ - taskViewModel.coreViewModel.taskCategorySuggestions, - StringToOption - ] - } - ), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "clock_loader_40"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskStatusLabel), /* @__PURE__ */ createElement( - "input", - { - "bind:value": taskViewModel.status, - list: statusSuggestionId - } - ))), /* @__PURE__ */ createElement( - "datalist", - { - hidden: true, - id: statusSuggestionId, - "children:append": [ - taskViewModel.coreViewModel.taskStatusSuggestions, - StringToOption - ] - } - ), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "priority_high"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.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, coreViewModel2.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, coreViewModel2.translations.chatPage.task.taskTimeLabel), /* @__PURE__ */ createElement( - "input", - { - type: "time", - "bind:value": taskViewModel.time - } - ))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton( - coreViewModel2, - coreViewModel2.translations.chatPage.task.deleteTaskButton, - "delete_forever", - taskViewModel.deleteTask - ))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement( + // src/View/Components/chatEntry.tsx + function ChatEntry(chatViewModel) { + const view = /* @__PURE__ */ createElement( "button", { - class: "flex", - "on:click": taskViewModel.closeAndDiscard + class: "tile colored-tile chat-entry animate-highlight", + "set:color": chatViewModel.settingsPageViewModel.color, + style: "height: 8rem", + "on:click": chatViewModel.open, + "toggle:highlight": chatViewModel.hasUnreadMessages }, - coreViewModel2.translations.general.closeButton + /* @__PURE__ */ createElement( + "span", + { + class: "shadow", + "subscribe:innerText": chatViewModel.settingsPageViewModel.primaryChannel + } + ), + /* @__PURE__ */ createElement( + "h2", + { + "subscribe:innerText": chatViewModel.settingsPageViewModel.primaryChannel + } + ) + ); + chatViewModel.index.subscribe((newIndex) => { + view.style.order = newIndex; + }); + return view; + } + var ChatViewModelToChatEntry = (chatViewModel) => { + return ChatEntry(chatViewModel); + }; + + // src/View/homePage.tsx + function HomePage(coreViewModel2, storageViewModel2, settingsViewModel2, connectionViewModel2, fileTransferViewModel2, chatListViewModel2) { + const overviewSection = /* @__PURE__ */ createElement("div", { id: "overview-section" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.overviewHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "cell_tower"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.homePage.serverAddress), /* @__PURE__ */ createElement( + "input", + { + list: "previous-connection-list", + placeholder: coreViewModel2.translations.homePage.serverAddressPlaceholder, + "bind:value": connectionViewModel2.serverAddressInput, + "on:enter": connectionViewModel2.connect + } + ), /* @__PURE__ */ createElement( + "datalist", + { + hidden: true, + id: "previous-connection-list", + "children:append": [ + connectionViewModel2.previousAddresses, + StringToOption + ] + } + ))), /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement( + "button", + { + class: "danger flex justify-center", + "aria-label": coreViewModel2.translations.homePage.disconnectAudioLabel, + "on:click": connectionViewModel2.disconnect, + "toggle:disabled": connectionViewModel2.cannotDisonnect + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "link_off") ), /* @__PURE__ */ createElement( "button", { - class: "flex primary", - "on:click": taskViewModel.closeAndSave + class: "flex justify-center", + "aria-label": coreViewModel2.translations.homePage.manageConnectionsAudioLabel, + "on:click": connectionViewModel2.showConnectionModal, + "toggle:disabled": connectionViewModel2.hasNoPreviousConnections }, - coreViewModel2.translations.general.saveButton, - /* @__PURE__ */ createElement("span", { class: "icon" }, "save") - )))); + /* @__PURE__ */ createElement("span", { class: "icon" }, "history") + ), /* @__PURE__ */ createElement( + "button", + { + class: "primary flex justify-center", + "aria-label": coreViewModel2.translations.homePage.connectAudioLabel, + "on:click": connectionViewModel2.connect, + "toggle:disabled": connectionViewModel2.cannotConnect + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "link") + )), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "account_circle"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.homePage.yourNameLabel), /* @__PURE__ */ createElement( + "input", + { + placeholder: coreViewModel2.translations.homePage.yourNamePlaceholder, + "bind:value": settingsViewModel2.usernameInput, + "on:enter": settingsViewModel2.setName + } + ))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, /* @__PURE__ */ createElement( + "button", + { + class: "width-50", + "on:click": settingsViewModel2.setName, + "toggle:disabled": settingsViewModel2.cannotSetName, + "aria-label": coreViewModel2.translations.homePage.setNameButtonAudioLabel + }, + coreViewModel2.translations.general.setButton, + /* @__PURE__ */ createElement("span", { class: "icon" }, "check") + )), /* @__PURE__ */ createElement("hr", null), HomePageButton( + settingsViewModel2.showSettingsModal, + coreViewModel2.translations.homePage.settingsButton, + "settings" + ), HomePageButton( + fileTransferViewModel2.showDirectionSelectionModal, + coreViewModel2.translations.homePage.transferDataButton, + "sync_alt" + ), HomePageButton( + storageViewModel2.showStorageModal, + coreViewModel2.translations.homePage.manageStorageButton, + "hard_drive" + ), /* @__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 }, coreViewModel2.translations.homePage.scrollToChatButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward"))))); + const chatSection = /* @__PURE__ */ createElement("div", { id: "chat-section" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.chatsHeadline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( + "input", + { + placeholder: coreViewModel2.translations.homePage.addChatPlaceholder, + "aria-label": coreViewModel2.translations.homePage.addChatAudioLabel, + "bind:value": chatListViewModel2.newChatPrimaryChannel, + "on:enter": chatListViewModel2.createChat + } + ), /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "aria-label": coreViewModel2.translations.homePage.addChatButton, + "on:click": chatListViewModel2.createChat, + "toggle:disabled": chatListViewModel2.cannotCreateChat + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "add") + )), /* @__PURE__ */ createElement( + "div", + { + id: "chat-grid", + "children:append": [ + chatListViewModel2.chatViewModels, + ChatViewModelToChatEntry + ] + } + )); + function scrollToChat() { + chatSection.scrollIntoView(); + } + return /* @__PURE__ */ createElement("article", { id: "home-page" }, /* @__PURE__ */ createElement("div", null, overviewSection, chatSection)); + } + + // src/View/Components/ribbonButton.tsx + function RibbonButton(label, icon, isSelected, select, isHighlighted = new State(false)) { + return /* @__PURE__ */ createElement( + "button", + { + class: "ribbon-button animate-highlight", + "aria-label": label, + "toggle:selected": isSelected, + "toggle:highlight": isHighlighted, + "on:click": select + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, icon) + ); + } + + // src/View/Components/chatViewToggleButton.tsx + function ChatViewToggleButton(label, icon, page, chatViewModel) { + function select() { + chatViewModel.openPage(page); + } + const isSelected = createProxyState( + [chatViewModel.selectedPage], + () => chatViewModel.selectedPage.value == page + ); + const isHighlighted = new State(false); + if (page == "messages" /* Messages */) { + chatViewModel.hasUnreadMessages.subscribe( + (hasUnreadMessages) => isHighlighted.value = hasUnreadMessages + ); + } + return RibbonButton(label, icon, isSelected, select, isHighlighted); } // src/View/Components/taskEntry.tsx @@ -4289,719 +4635,6 @@ return TaskEntry(taskViewModel); }; - // src/View/Components/placeholderView.tsx - function PlaceholderView(text) { - return /* @__PURE__ */ createElement("div", { class: "width-100 height-100 flex-column justify-center align-center" }, /* @__PURE__ */ createElement("span", { class: "secondary slide-up" }, text)); - } - - // src/View/ChatPages/calendarPage.tsx - function CalendarPage(coreViewModel2, calendarPageViewModel) { - calendarPageViewModel.loadData(); - const mainContent = createProxyState( - [calendarPageViewModel.monthGrid], - () => { - const monthGrid = calendarPageViewModel.monthGrid.value; - if (monthGrid == void 0) { - return /* @__PURE__ */ createElement("div", null); - } else { - let drop = function(date) { - calendarPageViewModel.handleDrop( - monthGrid.year.toString(), - monthGrid.month.toString(), - date - ); - }; - return MonthGrid2( - coreViewModel2, - monthGrid, - calendarPageViewModel.selectedDate, - drop - ); - } - } - ); - const sidePaneContentWrapper = createProxyState( - [ - calendarPageViewModel.selectedYear, - calendarPageViewModel.selectedMonth, - calendarPageViewModel.selectedDate - ], - () => { - const listState = calendarPageViewModel.getEventsForDate(); - if (listState == void 0) { - return /* @__PURE__ */ createElement("div", null); - } else { - const sidePaneContent = createProxyState( - [listState], - () => { - if (listState.value.size == 0) { - return PlaceholderView( - coreViewModel2.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", - { - class: "width-100 height-100", - "children:set": sidePaneContent - } - ); - } - } - ); - const taskSettingsModal = createProxyState( - [calendarPageViewModel.selectedTaskViewModel], - () => { - if (calendarPageViewModel.selectedTaskViewModel.value == void 0) { - return /* @__PURE__ */ createElement("div", null); - } else { - ViewController.setFocusWithDelay(); - return TaskSettingsModal( - coreViewModel2, - calendarPageViewModel.selectedTaskViewModel.value - ); - } - } - ); - return /* @__PURE__ */ createElement("div", { id: "calendar-page" }, /* @__PURE__ */ createElement("div", { class: "pane-wrapper grid-pane-wrapper" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement( - "button", - { - class: "ghost", - "aria-label": coreViewModel2.translations.chatPage.calendar.todayButtonAudioLabel, - "on:click": calendarPageViewModel.showToday - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "today") - )), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement( - "button", - { - class: "ghost", - "aria-label": coreViewModel2.translations.chatPage.calendar.previousMonthButtonAudioLabel, - "on:click": calendarPageViewModel.showPreviousMonth - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_back") - ), /* @__PURE__ */ createElement("span", { class: "input-wrapper" }, /* @__PURE__ */ createElement( - "input", - { - class: "year-input", - type: "number", - "aria-label": coreViewModel2.translations.chatPage.calendar.yearInputAudioLabel, - placeholder: coreViewModel2.translations.chatPage.calendar.yearInputPlaceholder, - "bind:value": calendarPageViewModel.selectedYear - } - ), /* @__PURE__ */ createElement( - "input", - { - class: "month-input", - type: "number", - "aria-label": coreViewModel2.translations.chatPage.calendar.monthInputAudioLabel, - placeholder: coreViewModel2.translations.chatPage.calendar.monthInputPlaceholder, - "bind:value": calendarPageViewModel.selectedMonth - } - )), /* @__PURE__ */ createElement( - "button", - { - class: "ghost", - "aria-label": coreViewModel2.translations.chatPage.calendar.nextMonthButtonAudioLabel, - "on:click": calendarPageViewModel.showNextMonth - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") - )), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement( - "button", - { - class: "ghost", - "aria-label": coreViewModel2.translations.chatPage.task.createTaskButtonAudioLabel, - "on:click": calendarPageViewModel.createEvent - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "add") - ))), /* @__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", { "children:set": taskSettingsModal })); - } - - // src/View/Components/ribbonButton.tsx - function RibbonButton(label, icon, isSelected, select, isHighlighted = new State(false)) { - return /* @__PURE__ */ createElement( - "button", - { - class: "ribbon-button animate-highlight", - "aria-label": label, - "toggle:selected": isSelected, - "toggle:highlight": isHighlighted, - "on:click": select - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, icon) - ); - } - - // src/View/Components/chatViewToggleButton.tsx - function ChatViewToggleButton(label, icon, page, chatViewModel) { - function select() { - chatViewModel.openPage(page); - } - const isSelected = createProxyState( - [chatViewModel.selectedPage], - () => chatViewModel.selectedPage.value == page - ); - const isHighlighted = new State(false); - if (page == "messages" /* Messages */) { - chatViewModel.hasUnreadMessages.subscribe( - (hasUnreadMessages) => isHighlighted.value = hasUnreadMessages - ); - } - return RibbonButton(label, icon, isSelected, select, isHighlighted); - } - - // src/View/Components/messageReactionButton.tsx - function MessageReactionButton(coreViewModel2, chatMessageViewModel, content) { - let audioLabel; - let count; - let isActive = createProxyState( - [chatMessageViewModel.userReaction], - () => chatMessageViewModel.userReaction.value == content - ); - function sendReaction() { - chatMessageViewModel.sendReaction(content, isActive.value); - } - switch (content) { - case "\u{1F44D}" /* ThumbsUp */: { - audioLabel = coreViewModel2.translations.chatPage.message.thumbsUpReaction; - count = chatMessageViewModel.reactionsThumbsUpCount; - break; - } - case "\u2705" /* Check */: { - audioLabel = coreViewModel2.translations.chatPage.message.checkReaction; - count = chatMessageViewModel.reactionsCheckCount; - break; - } - case "\u{1F6D1}" /* Stop */: { - audioLabel = coreViewModel2.translations.chatPage.message.stopReaction; - count = chatMessageViewModel.reactionsStopCount; - break; - } - case "\u2757\uFE0F" /* Attention */: { - audioLabel = coreViewModel2.translations.chatPage.message.attentionReaction; - count = chatMessageViewModel.reactionsAttentionCount; - break; - } - case "\u203C\uFE0F" /* DoubleAttention */: { - audioLabel = coreViewModel2.translations.chatPage.message.doubleAttentionReaction; - count = chatMessageViewModel.reactionsDoubleAttentionCount; - break; - } - case "\u2753" /* Question */: { - audioLabel = coreViewModel2.translations.chatPage.message.questionReaction; - count = chatMessageViewModel.reactionsQuestionCount; - break; - } - } - return /* @__PURE__ */ createElement( - "button", - { - class: "flex", - "on:click": sendReaction, - "aria-label": audioLabel, - "toggle:selected": isActive, - "set:count": count - }, - content, - /* @__PURE__ */ createElement("span", { "subscribe:innerText": count }) - ); - } - - // src/View/Components/messageReactionButtonRow.tsx - function MessageReactionButtonRow(coreViewModel2, chatMessageViewModel) { - return /* @__PURE__ */ createElement("div", { class: "grid gap width-100 message-reaction-row" }, MessageReactionButton( - coreViewModel2, - chatMessageViewModel, - "\u{1F44D}" /* ThumbsUp */ - ), MessageReactionButton( - coreViewModel2, - chatMessageViewModel, - "\u2705" /* Check */ - ), MessageReactionButton( - coreViewModel2, - chatMessageViewModel, - "\u{1F6D1}" /* Stop */ - ), MessageReactionButton( - coreViewModel2, - chatMessageViewModel, - "\u2757\uFE0F" /* Attention */ - ), MessageReactionButton( - coreViewModel2, - chatMessageViewModel, - "\u203C\uFE0F" /* DoubleAttention */ - ), MessageReactionButton( - coreViewModel2, - chatMessageViewModel, - "\u2753" /* Question */ - )); - } - - // src/View/Components/infoTile.tsx - function InfoTile(icon, label, content) { - let contentState; - if (typeof content == "string") { - contentState = new State(content); - } else { - contentState = content; - } - return /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, icon), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, label), /* @__PURE__ */ createElement("b", { class: "break-word", "subscribe:innerText": contentState }))); - } - - // src/View/Components/messageReactionEntry.tsx - function MessageReactionEntry(reaction) { - return /* @__PURE__ */ createElement("div", { class: "tile flex-row" }, /* @__PURE__ */ createElement("span", { class: "flex width-100" }, reaction.sender), /* @__PURE__ */ createElement("span", null, reaction.content)); - } - - // src/View/Modals/chatMessageInfoModal.tsx - function ChatMessageInfoModal(coreViewModel2, chatMessageViewModel) { - return /* @__PURE__ */ createElement( - "div", - { - class: "modal", - "toggle:open": chatMessageViewModel.isPresentingInfoModal - }, - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.chatPage.message.messageInfoHeadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, InfoTile( - "account_circle", - coreViewModel2.translations.chatPage.message.sentBy, - chatMessageViewModel.sender - ), InfoTile( - "schedule", - coreViewModel2.translations.chatPage.message.timeSent, - chatMessageViewModel.dateSent - ), InfoTile( - "forum", - coreViewModel2.translations.chatPage.message.channel, - chatMessageViewModel.channel - ), InfoTile( - "description", - coreViewModel2.translations.chatPage.message.messageContent, - chatMessageViewModel.body - )), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.copyMessage }, coreViewModel2.translations.chatPage.message.copyMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "content_copy")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.resendMessage }, coreViewModel2.translations.chatPage.message.resendMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "redo")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.decryptMessage }, coreViewModel2.translations.chatPage.message.decryptMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "key")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.reply }, coreViewModel2.translations.chatPage.message.replyToMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "reply"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, MessageReactionButtonRow( - coreViewModel2, - chatMessageViewModel - ), /* @__PURE__ */ createElement( - "div", - { - class: "flex-column gap", - "children:append": [ - chatMessageViewModel.allReactions, - MessageReactionEntry - ] - } - ))), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.hideInfoModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))) - ); - } - - // src/View/Components/inlineReply.tsx - function InlineReply(chatMessageViewModel) { - const reply = chatMessageViewModel.inlineReply; - if (reply == void 0) return /* @__PURE__ */ createElement("div", null); - const scroll = () => { - ViewController.scrollToView(reply.chatMessage.id); - }; - return /* @__PURE__ */ createElement("div", { class: "inline-reply", "on:click": scroll }, /* @__PURE__ */ createElement("span", null, reply.sender), /* @__PURE__ */ createElement("b", { class: "ellipsis", "subscribe:innerText": reply.body })); - } - - // src/View/Components/chatMessage.tsx - function ChatMessage4(coreViewModel2, chatMessageViewModel) { - 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", - { - class: "message-bubble", - id: chatMessageViewModel.chatMessage.id, - "toggle:sentbyuser": chatMessageViewModel.sentByUser, - "toggle:hidden": chatMessageViewModel.isHidden - }, - InlineReply(chatMessageViewModel), - /* @__PURE__ */ createElement("div", { class: "main tile" }, /* @__PURE__ */ createElement("div", { class: "text-container" }, /* @__PURE__ */ createElement("span", { class: "sender-name ellipsis" }, chatMessageViewModel.sender), /* @__PURE__ */ createElement( - "span", - { - 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( - "button", - { - "on:click": chatMessageViewModel.showInfoModal, - "aria-label": coreViewModel2.translations.chatPage.message.showMessageInfoButtonAudioLabel - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "info") - ), /* @__PURE__ */ createElement( - "button", - { - class: "reply-button", - "on:click": chatMessageViewModel.reply, - "aria-label": coreViewModel2.translations.chatPage.message.replyToMessageButton - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "reply") - ))), - MessageReactionButtonRow(coreViewModel2, chatMessageViewModel), - ChatMessageInfoModal(coreViewModel2, chatMessageViewModel) - ); - } - - // src/View/Components/messageReactionFilterButton.tsx - function MessageReactionFilterButton(messagePageViewModel, content) { - const select = () => { - messagePageViewModel.setReactionFilter(content); - }; - const isSelected = createProxyState( - [messagePageViewModel.reactionFilter], - () => messagePageViewModel.reactionFilter.value == content - ); - return /* @__PURE__ */ createElement( - "button", - { - class: "flex justify-center", - "on:click": select, - "toggle:selected": isSelected - }, - content - ); - } - - // src/View/Modals/messageFilterModal.tsx - function MessageFilterModal(coreViewModel2, messagePageViewModel, converter) { - const noFilter = createProxyState( - [messagePageViewModel.reactionFilter], - () => messagePageViewModel.reactionFilter.value == void 0 - ); - 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", - { - id: "focused", - placeholder: coreViewModel2.translations.general.searchLabel, - "bind:value": messagePageViewModel.searchViewModel.searchInput, - "on:enter": messagePageViewModel.searchViewModel.applySearch - } - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary", - "aria-label": coreViewModel2.translations.general.searchButtonAudioLabel, - "on:click": messagePageViewModel.searchViewModel.applySearch, - "toggle:disabled": messagePageViewModel.searchViewModel.cannotApplySearch - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "search") - )), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.chatPage.message.messageFilterReactionsHadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement( - "button", - { - "toggle:selected": noFilter, - "on:click": messagePageViewModel.revokeReactionFilter - }, - coreViewModel2.translations.chatPage.message.messageFilterAllReactionsButton - ), /* @__PURE__ */ createElement("div", { class: "grid gap message-reaction-filter" }, MessageReactionFilterButton( - messagePageViewModel, - "\u{1F44D}" /* ThumbsUp */ - ), MessageReactionFilterButton( - messagePageViewModel, - "\u2705" /* Check */ - ), MessageReactionFilterButton( - messagePageViewModel, - "\u{1F6D1}" /* Stop */ - ), MessageReactionFilterButton( - messagePageViewModel, - "\u2757\uFE0F" /* Attention */ - ), MessageReactionFilterButton( - messagePageViewModel, - "\u203C\uFE0F" /* DoubleAttention */ - ), MessageReactionFilterButton( - messagePageViewModel, - "\u2753" /* Question */ - )))), /* @__PURE__ */ createElement("button", { "on:click": messagePageViewModel.hideFilterModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); - } - - // 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( - "button", - { - class: "standard square blur", - "on:click": chatMessageViewModel.cancelReply, - "aria-label": coreViewModel2.translations.chatPage.message.cancelReplyAudioLabel - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "close") - )); - } - - // src/View/ChatPages/messagePage.tsx - function MessagePage(coreViewModel2, messagePageViewModel) { - messagePageViewModel.loadData(); - const ChatMessageViewModelToView = (chatMessageViewModel) => { - return ChatMessage4(coreViewModel2, chatMessageViewModel); - }; - const messageContainer = /* @__PURE__ */ createElement( - "div", - { - id: "message-container", - "children:append": [ - messagePageViewModel.filteredMessageViewModels, - ChatMessageViewModelToView - ] - } - ); - const persistenceId = messagePageViewModel.chatViewModel.chatModel.id; - const replyPreview = ViewController.inlineReplies.setState( - persistenceId, - () => createProxyState( - [messagePageViewModel.replyingMessage], - () => { - if (messagePageViewModel.replyingMessage.value == void 0) - return /* @__PURE__ */ createElement("div", null); - return ReplyPreview( - coreViewModel2, - messagePageViewModel.replyingMessage.value - ); - } - ) - ); - function scrollDown(hard = false) { - if (hard) { - messageContainer.setAttribute("scroll-hard", ""); - } - messageContainer.scrollTop = messageContainer.scrollHeight; - messageContainer.removeAttribute("scroll-hard"); - } - function scrollDownIfApplicable() { - const scrollFromBottom = messageContainer.scrollHeight - (messageContainer.scrollTop + messageContainer.offsetHeight); - if (scrollFromBottom > 400) return; - scrollDown(); - } - messagePageViewModel.filteredMessageViewModels.subscribeSilent( - scrollDownIfApplicable - ); - setTimeout(() => scrollDown(true), 100); - messagePageViewModel.focusSetter.subscribeSilent(() => { - ViewController.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", - { - class: "ghost inset-outline", - "on:click": messagePageViewModel.showFilterModal, - "aria-label": coreViewModel2.translations.chatPage.message.filterMessagesButtonAudioLabel, - "toggle:selected": messagePageViewModel.isFilterActive - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "filter_alt") - ))), /* @__PURE__ */ createElement("div", { class: "content" }, messageContainer, /* @__PURE__ */ createElement("div", { id: "composer" }, /* @__PURE__ */ createElement("div", { class: "content-width-constraint" }, /* @__PURE__ */ createElement( - "div", - { - class: "reply-preview-wrapper", - "children:set": replyPreview - } - ), /* @__PURE__ */ createElement("div", { class: "input-width-constraint" }, /* @__PURE__ */ createElement( - "input", - { - id: "focused", - class: "blur", - "bind:value": messagePageViewModel.composingMessage, - "on:enter": messagePageViewModel.sendMessage, - placeholder: coreViewModel2.translations.chatPage.message.composerInputPlaceholder - } - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary blur", - "aria-label": coreViewModel2.translations.chatPage.message.sendMessageButtonAudioLabel, - "on:click": messagePageViewModel.sendMessage, - "toggle:disabled": messagePageViewModel.cannotSendMessage - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "send") - ))))))), MessageFilterModal( - coreViewModel2, - messagePageViewModel, - ChatMessageViewModelToView - )); - } - - // src/View/Components/colorPicker.tsx - function ColorPicker(selectedColor) { - return /* @__PURE__ */ createElement("div", { class: "flex-row gap width-input" }, ...Object.values(Colors).map((color) => { - const isSelected = createProxyState( - [selectedColor], - () => selectedColor.value == color - ); - function setColor() { - selectedColor.value = color; - } - return /* @__PURE__ */ createElement( - "button", - { - color, - class: "fill-color width-100 flex", - style: "height: 2rem", - "toggle:selected": isSelected, - "on:click": setColor - } - ); - })); - } - - // src/View/Components/deletableListItem.tsx - function DeletableListItem(coreViewModel2, text, primaryButton, ondelete) { - return /* @__PURE__ */ createElement("div", { class: "tile flex-row justify-apart align-center padding-0" }, /* @__PURE__ */ createElement("span", { class: "padding-h ellipsis" }, text), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, primaryButton, /* @__PURE__ */ createElement( - "button", - { - class: "danger", - "aria-label": coreViewModel2.translations.general.deleteItemButtonAudioLabel, - "on:click": ondelete - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "delete") - ))); - } - - // src/View/ChatPages/settingsPage.tsx - function SettingsPage(coreViewModel2, settingsPageViewModel) { - settingsPageViewModel.loadData(); - const secondaryChannelConverter = (secondaryChannel) => { - return DeletableListItem( - coreViewModel2, - secondaryChannel, - /* @__PURE__ */ createElement("span", null), - () => { - settingsPageViewModel.removeSecondaryChannel(secondaryChannel); - } - ); - }; - return /* @__PURE__ */ createElement("div", { id: "settings-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.settings.settingsHeadline)), /* @__PURE__ */ createElement("div", { class: "content" }, /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.settings.primaryChannelLabel), /* @__PURE__ */ createElement( - "input", - { - "bind:value": settingsPageViewModel.primaryChannelInput, - "on:enter": settingsPageViewModel.setPrimaryChannel - } - ))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end width-input" }, /* @__PURE__ */ createElement( - "button", - { - class: "width-50", - "aria-label": coreViewModel2.translations.chatPage.settings.setPrimaryChannelButtonAudioLabel, - "on:click": settingsPageViewModel.setPrimaryChannel, - "toggle:disabled": settingsPageViewModel.cannotSetPrimaryChannel - }, - coreViewModel2.translations.general.setButton, - /* @__PURE__ */ createElement("span", { class: "icon" }, "check") - )), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-row width-input margin-bottom" }, /* @__PURE__ */ createElement( - "input", - { - "aria-label": coreViewModel2.translations.chatPage.settings.newSecondaryChannelAudioLabel, - placeholder: coreViewModel2.translations.chatPage.settings.newSecondaryChannelPlaceholder, - "bind:value": settingsPageViewModel.newSecondaryChannelInput, - "on:enter": settingsPageViewModel.addSecondaryChannel - } - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary", - "aria-label": coreViewModel2.translations.chatPage.settings.addSecondaryChannelButtonAudioLabel, - "on:click": settingsPageViewModel.addSecondaryChannel, - "toggle:disabled": settingsPageViewModel.cannotAddSecondaryChannel - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "add") - )), /* @__PURE__ */ createElement( - "div", - { - class: "flex-column gap width-input", - "children:append": [ - settingsPageViewModel.secondaryChannels, - secondaryChannelConverter - ] - } - ), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.settings.encryptionKeyLabel), /* @__PURE__ */ createElement( - "input", - { - "bind:value": settingsPageViewModel.encryptionKeyInput, - "on:enter": settingsPageViewModel.setEncryptionKey, - "set:type": settingsPageViewModel.encryptionKeyInputType - } - ))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end width-input" }, /* @__PURE__ */ createElement( - "button", - { - class: "width-50", - "aria-label": coreViewModel2.translations.chatPage.settings.setEncryptionKeyButtonAudioLabel, - "on:click": settingsPageViewModel.setEncryptionKey, - "toggle:disabled": settingsPageViewModel.cannotSetEncryptionKey - }, - coreViewModel2.translations.general.setButton, - /* @__PURE__ */ createElement("span", { class: "icon" }, "check") - )), /* @__PURE__ */ createElement("label", { class: "inline" }, /* @__PURE__ */ createElement( - "input", - { - type: "checkbox", - "bind:checked": settingsPageViewModel.shouldShowEncryptionKey - } - ), coreViewModel2.translations.chatPage.settings.showEncryptionKey), /* @__PURE__ */ createElement("hr", null), ColorPicker(settingsPageViewModel.color), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton( - coreViewModel2, - coreViewModel2.translations.chatPage.settings.deleteChatButton, - "chat_error", - settingsPageViewModel.remove - )))))); - } - // src/View/Components/propertyValueList.tsx function PropertyValueList(propertyKey, stringEntryObjectConverter, objects, viewBuilder) { const propertyValues = new ListState(); @@ -5109,115 +4742,6 @@ } }; - // src/View/ChatPages/boardKanbanPage.tsx - function BoardKanbanPage(coreViewModel2, boardViewModel) { - return PropertyValueList( - "category", - (taskViewModel) => taskViewModel.task, - boardViewModel.filteredTaskViewModels, - (categories, sortedCategories) => { - const categoryNameConverter = (categoryName) => { - const index = createPropertyValueIndexState( - sortedCategories, - categoryName - ); - return Column( - coreViewModel2, - categoryName, - index, - boardViewModel - ); - }; - return /* @__PURE__ */ createElement( - "div", - { - class: "kanban-board-wrapper", - "children:append": [categories, categoryNameConverter] - } - ); - } - ); - } - function Column(coreViewModel2, categoryName, index, boardViewModel) { - return FilteredList( - { category: categoryName }, - (taskViewModel) => taskViewModel.task, - boardViewModel.filteredTaskViewModels, - (taskViewModels) => { - const viewModel = new TaskCategoryBulkChangeViewModel( - taskViewModels, - categoryName - ); - function drop() { - boardViewModel.handleDropWithinBoard(categoryName); - } - const view = /* @__PURE__ */ createElement( - "div", - { - class: "flex-column flex-no", - "on:dragover": ViewController.allowDrop, - "on:drop": drop - }, - /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( - "input", - { - placeholder: coreViewModel2.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; - }); - return view; - } - ); - } - - // src/View/Modals/boardSettingsModal.tsx - function BoardSettingsModal(coreViewModel2, boardViewModel) { - boardViewModel.isPresentingSettingsModal.subscribe( - ViewController.setFocusWithDelay - ); - return /* @__PURE__ */ createElement( - "div", - { - class: "modal", - "toggle:open": boardViewModel.isPresentingSettingsModal - }, - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.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, coreViewModel2.translations.chatPage.task.boardNameInputLabel), /* @__PURE__ */ createElement( - "input", - { - id: "focused", - "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( - coreViewModel2, - coreViewModel2.translations.chatPage.task.deleteBoardButton, - "delete_forever", - boardViewModel.deleteBoard - ))), /* @__PURE__ */ createElement("button", { "on:click": boardViewModel.hideSettings }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))) - ); - } - // src/View/ChatPages/boardStatusGridPage.tsx function BoardStatusGridPage(coreViewModel2, boardViewModel) { const statuses = new ListState(); @@ -5389,16 +4913,231 @@ return view; } - // src/View/Components/boardViewToggleButton.tsx - function BoardViewToggleButton(label, icon, page, boardViewModel) { - function select() { - boardViewModel.selectedPage.value = page; - } - const isSelected = createProxyState( - [boardViewModel.selectedPage], - () => boardViewModel.selectedPage.value == page + // src/View/ChatPages/boardKanbanPage.tsx + function BoardKanbanPage(coreViewModel2, boardViewModel) { + return PropertyValueList( + "category", + (taskViewModel) => taskViewModel.task, + boardViewModel.filteredTaskViewModels, + (categories, sortedCategories) => { + const categoryNameConverter = (categoryName) => { + const index = createPropertyValueIndexState( + sortedCategories, + categoryName + ); + return Column( + coreViewModel2, + categoryName, + index, + boardViewModel + ); + }; + return /* @__PURE__ */ createElement( + "div", + { + class: "kanban-board-wrapper", + "children:append": [categories, categoryNameConverter] + } + ); + } ); - return RibbonButton(label, icon, isSelected, select); + } + function Column(coreViewModel2, categoryName, index, boardViewModel) { + return FilteredList( + { category: categoryName }, + (taskViewModel) => taskViewModel.task, + boardViewModel.filteredTaskViewModels, + (taskViewModels) => { + const viewModel = new TaskCategoryBulkChangeViewModel( + taskViewModels, + categoryName + ); + function drop() { + boardViewModel.handleDropWithinBoard(categoryName); + } + const view = /* @__PURE__ */ createElement( + "div", + { + class: "flex-column flex-no", + "on:dragover": ViewController.allowDrop, + "on:drop": drop + }, + /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( + "input", + { + placeholder: coreViewModel2.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; + }); + return view; + } + ); + } + + // src/View/Components/dangerousActionButton.tsx + function DangerousActionButton(coreViewModel2, label, icon, action) { + const isActionRequested = new State(false); + const cannotConfirm = createProxyState( + [isActionRequested], + () => isActionRequested.value == false + ); + function requestAction() { + isActionRequested.value = true; + } + function abort() { + isActionRequested.value = false; + } + return /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement("button", { class: "flex", "on:click": abort, "toggle:hidden": cannotConfirm }, coreViewModel2.translations.general.abortButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "undo")), /* @__PURE__ */ createElement( + "button", + { + class: "danger flex", + "on:click": requestAction, + "toggle:hidden": isActionRequested + }, + label, + /* @__PURE__ */ createElement("span", { class: "icon" }, icon) + ), /* @__PURE__ */ createElement( + "button", + { + class: "danger flex", + "on:click": action, + "toggle:hidden": cannotConfirm + }, + coreViewModel2.translations.general.confirmButton, + /* @__PURE__ */ createElement("span", { class: "icon" }, "warning") + )); + } + + // src/View/Modals/taskSettingsModal.tsx + function TaskSettingsModal(coreViewModel2, taskViewModel) { + const categorySuggestionId = v4_default(); + const statusSuggestionId = v4_default(); + const BoardOptionConverter = (entry) => { + const isSelected = entry[0] == taskViewModel.task.boardId; + return Option(entry[1], entry[0], isSelected); + }; + return /* @__PURE__ */ createElement("div", { class: "modal", open: true }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.chatPage.task.taskSettingsHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "history"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.general.fileVersionLabel), /* @__PURE__ */ createElement( + "select", + { + "bind:value": taskViewModel.selectedVersionId, + "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, coreViewModel2.translations.chatPage.task.taskNameLabel), /* @__PURE__ */ createElement( + "input", + { + "bind:value": taskViewModel.name, + id: "focused" + } + ))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "category"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskBoardLabel), /* @__PURE__ */ createElement( + "select", + { + "bind:value": taskViewModel.boardId, + "children:append": [ + taskViewModel.chatViewModel.taskBoardSuggestions, + BoardOptionConverter + ] + } + ), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "description"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskDescriptionLabel), /* @__PURE__ */ createElement( + "textarea", + { + rows: "10", + "bind:value": taskViewModel.description + } + ))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "category"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskCategoryLabel), /* @__PURE__ */ createElement( + "input", + { + "bind:value": taskViewModel.category, + list: categorySuggestionId + } + ))), /* @__PURE__ */ createElement( + "datalist", + { + hidden: true, + id: categorySuggestionId, + "children:append": [ + taskViewModel.coreViewModel.taskCategorySuggestions, + StringToOption + ] + } + ), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "clock_loader_40"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskStatusLabel), /* @__PURE__ */ createElement( + "input", + { + "bind:value": taskViewModel.status, + list: statusSuggestionId + } + ))), /* @__PURE__ */ createElement( + "datalist", + { + hidden: true, + id: statusSuggestionId, + "children:append": [ + taskViewModel.coreViewModel.taskStatusSuggestions, + StringToOption + ] + } + ), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "priority_high"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.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, coreViewModel2.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, coreViewModel2.translations.chatPage.task.taskTimeLabel), /* @__PURE__ */ createElement( + "input", + { + type: "time", + "bind:value": taskViewModel.time + } + ))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton( + coreViewModel2, + coreViewModel2.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 + }, + coreViewModel2.translations.general.closeButton + ), /* @__PURE__ */ createElement( + "button", + { + class: "flex primary", + "on:click": taskViewModel.closeAndSave + }, + coreViewModel2.translations.general.saveButton, + /* @__PURE__ */ createElement("span", { class: "icon" }, "save") + )))); } // src/View/Modals/searchModal.tsx @@ -5452,6 +5191,68 @@ )), /* @__PURE__ */ createElement("button", { "on:click": close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); } + // src/View/Components/colorPicker.tsx + function ColorPicker(selectedColor) { + return /* @__PURE__ */ createElement("div", { class: "flex-row gap width-input" }, ...Object.values(Colors).map((color) => { + const isSelected = createProxyState( + [selectedColor], + () => selectedColor.value == color + ); + function setColor() { + selectedColor.value = color; + } + return /* @__PURE__ */ createElement( + "button", + { + color, + class: "fill-color width-100 flex", + style: "height: 2rem", + "toggle:selected": isSelected, + "on:click": setColor + } + ); + })); + } + + // src/View/Modals/boardSettingsModal.tsx + function BoardSettingsModal(coreViewModel2, boardViewModel) { + boardViewModel.isPresentingSettingsModal.subscribe( + ViewController.setFocusWithDelay + ); + return /* @__PURE__ */ createElement( + "div", + { + class: "modal", + "toggle:open": boardViewModel.isPresentingSettingsModal + }, + /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.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, coreViewModel2.translations.chatPage.task.boardNameInputLabel), /* @__PURE__ */ createElement( + "input", + { + id: "focused", + "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( + coreViewModel2, + coreViewModel2.translations.chatPage.task.deleteBoardButton, + "delete_forever", + boardViewModel.deleteBoard + ))), /* @__PURE__ */ createElement("button", { "on:click": boardViewModel.hideSettings }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))) + ); + } + + // src/View/Components/boardViewToggleButton.tsx + function BoardViewToggleButton(label, icon, page, boardViewModel) { + function select() { + boardViewModel.selectedPage.value = page; + } + const isSelected = createProxyState( + [boardViewModel.selectedPage], + () => boardViewModel.selectedPage.value == page + ); + return RibbonButton(label, icon, isSelected, select); + } + // src/View/ChatPages/boardPage.tsx function BoardPage(coreViewModel2, boardViewModel) { boardViewModel.loadData(); @@ -5561,6 +5362,11 @@ ), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal })); } + // src/View/Components/placeholderView.tsx + function PlaceholderView(text) { + return /* @__PURE__ */ createElement("div", { class: "width-100 height-100 flex-column justify-center align-center" }, /* @__PURE__ */ createElement("span", { class: "secondary slide-up" }, text)); + } + // src/View/Components/boardEntry.tsx function BoardEntry(boardViewModel) { const view = /* @__PURE__ */ createElement( @@ -5669,6 +5475,734 @@ ); } + // src/View/Components/deletableListItem.tsx + function DeletableListItem(coreViewModel2, text, primaryButton, ondelete) { + return /* @__PURE__ */ createElement("div", { class: "tile flex-row justify-apart align-center padding-0" }, /* @__PURE__ */ createElement("span", { class: "padding-h ellipsis" }, text), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, primaryButton, /* @__PURE__ */ createElement( + "button", + { + class: "danger", + "aria-label": coreViewModel2.translations.general.deleteItemButtonAudioLabel, + "on:click": ondelete + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "delete") + ))); + } + + // src/View/ChatPages/settingsPage.tsx + function SettingsPage(coreViewModel2, settingsPageViewModel) { + settingsPageViewModel.loadData(); + const secondaryChannelConverter = (secondaryChannel) => { + return DeletableListItem( + coreViewModel2, + secondaryChannel, + /* @__PURE__ */ createElement("span", null), + () => { + settingsPageViewModel.removeSecondaryChannel(secondaryChannel); + } + ); + }; + return /* @__PURE__ */ createElement("div", { id: "settings-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.settings.settingsHeadline)), /* @__PURE__ */ createElement("div", { class: "content" }, /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.settings.primaryChannelLabel), /* @__PURE__ */ createElement( + "input", + { + "bind:value": settingsPageViewModel.primaryChannelInput, + "on:enter": settingsPageViewModel.setPrimaryChannel + } + ))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end width-input" }, /* @__PURE__ */ createElement( + "button", + { + class: "width-50", + "aria-label": coreViewModel2.translations.chatPage.settings.setPrimaryChannelButtonAudioLabel, + "on:click": settingsPageViewModel.setPrimaryChannel, + "toggle:disabled": settingsPageViewModel.cannotSetPrimaryChannel + }, + coreViewModel2.translations.general.setButton, + /* @__PURE__ */ createElement("span", { class: "icon" }, "check") + )), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-row width-input margin-bottom" }, /* @__PURE__ */ createElement( + "input", + { + "aria-label": coreViewModel2.translations.chatPage.settings.newSecondaryChannelAudioLabel, + placeholder: coreViewModel2.translations.chatPage.settings.newSecondaryChannelPlaceholder, + "bind:value": settingsPageViewModel.newSecondaryChannelInput, + "on:enter": settingsPageViewModel.addSecondaryChannel + } + ), /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "aria-label": coreViewModel2.translations.chatPage.settings.addSecondaryChannelButtonAudioLabel, + "on:click": settingsPageViewModel.addSecondaryChannel, + "toggle:disabled": settingsPageViewModel.cannotAddSecondaryChannel + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "add") + )), /* @__PURE__ */ createElement( + "div", + { + class: "flex-column gap width-input", + "children:append": [ + settingsPageViewModel.secondaryChannels, + secondaryChannelConverter + ] + } + ), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.settings.encryptionKeyLabel), /* @__PURE__ */ createElement( + "input", + { + "bind:value": settingsPageViewModel.encryptionKeyInput, + "on:enter": settingsPageViewModel.setEncryptionKey, + "set:type": settingsPageViewModel.encryptionKeyInputType + } + ))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end width-input" }, /* @__PURE__ */ createElement( + "button", + { + class: "width-50", + "aria-label": coreViewModel2.translations.chatPage.settings.setEncryptionKeyButtonAudioLabel, + "on:click": settingsPageViewModel.setEncryptionKey, + "toggle:disabled": settingsPageViewModel.cannotSetEncryptionKey + }, + coreViewModel2.translations.general.setButton, + /* @__PURE__ */ createElement("span", { class: "icon" }, "check") + )), /* @__PURE__ */ createElement("label", { class: "inline" }, /* @__PURE__ */ createElement( + "input", + { + type: "checkbox", + "bind:checked": settingsPageViewModel.shouldShowEncryptionKey + } + ), coreViewModel2.translations.chatPage.settings.showEncryptionKey), /* @__PURE__ */ createElement("hr", null), ColorPicker(settingsPageViewModel.color), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton( + coreViewModel2, + coreViewModel2.translations.chatPage.settings.deleteChatButton, + "chat_error", + settingsPageViewModel.remove + )))))); + } + + // src/View/Components/messageReactionFilterButton.tsx + function MessageReactionFilterButton(messagePageViewModel, content) { + const select = () => { + messagePageViewModel.setReactionFilter(content); + }; + const isSelected = createProxyState( + [messagePageViewModel.reactionFilter], + () => messagePageViewModel.reactionFilter.value == content + ); + return /* @__PURE__ */ createElement( + "button", + { + class: "flex justify-center", + "on:click": select, + "toggle:selected": isSelected + }, + content + ); + } + + // src/View/Modals/messageFilterModal.tsx + function MessageFilterModal(coreViewModel2, messagePageViewModel, converter) { + const noFilter = createProxyState( + [messagePageViewModel.reactionFilter], + () => messagePageViewModel.reactionFilter.value == void 0 + ); + 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", + { + id: "focused", + placeholder: coreViewModel2.translations.general.searchLabel, + "bind:value": messagePageViewModel.searchViewModel.searchInput, + "on:enter": messagePageViewModel.searchViewModel.applySearch + } + ), /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "aria-label": coreViewModel2.translations.general.searchButtonAudioLabel, + "on:click": messagePageViewModel.searchViewModel.applySearch, + "toggle:disabled": messagePageViewModel.searchViewModel.cannotApplySearch + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "search") + )), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.chatPage.message.messageFilterReactionsHadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement( + "button", + { + "toggle:selected": noFilter, + "on:click": messagePageViewModel.revokeReactionFilter + }, + coreViewModel2.translations.chatPage.message.messageFilterAllReactionsButton + ), /* @__PURE__ */ createElement("div", { class: "grid gap message-reaction-filter" }, MessageReactionFilterButton( + messagePageViewModel, + "\u{1F44D}" /* ThumbsUp */ + ), MessageReactionFilterButton( + messagePageViewModel, + "\u2705" /* Check */ + ), MessageReactionFilterButton( + messagePageViewModel, + "\u{1F6D1}" /* Stop */ + ), MessageReactionFilterButton( + messagePageViewModel, + "\u2757\uFE0F" /* Attention */ + ), MessageReactionFilterButton( + messagePageViewModel, + "\u203C\uFE0F" /* DoubleAttention */ + ), MessageReactionFilterButton( + messagePageViewModel, + "\u2753" /* Question */ + )))), /* @__PURE__ */ createElement("button", { "on:click": messagePageViewModel.hideFilterModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); + } + + // 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( + "button", + { + class: "standard square blur", + "on:click": chatMessageViewModel.cancelReply, + "aria-label": coreViewModel2.translations.chatPage.message.cancelReplyAudioLabel + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "close") + )); + } + + // src/View/Components/messageReactionButton.tsx + function MessageReactionButton(coreViewModel2, chatMessageViewModel, content) { + let audioLabel; + let count; + let isActive = createProxyState( + [chatMessageViewModel.userReaction], + () => chatMessageViewModel.userReaction.value == content + ); + function sendReaction() { + chatMessageViewModel.sendReaction(content, isActive.value); + } + switch (content) { + case "\u{1F44D}" /* ThumbsUp */: { + audioLabel = coreViewModel2.translations.chatPage.message.thumbsUpReaction; + count = chatMessageViewModel.reactionsThumbsUpCount; + break; + } + case "\u2705" /* Check */: { + audioLabel = coreViewModel2.translations.chatPage.message.checkReaction; + count = chatMessageViewModel.reactionsCheckCount; + break; + } + case "\u{1F6D1}" /* Stop */: { + audioLabel = coreViewModel2.translations.chatPage.message.stopReaction; + count = chatMessageViewModel.reactionsStopCount; + break; + } + case "\u2757\uFE0F" /* Attention */: { + audioLabel = coreViewModel2.translations.chatPage.message.attentionReaction; + count = chatMessageViewModel.reactionsAttentionCount; + break; + } + case "\u203C\uFE0F" /* DoubleAttention */: { + audioLabel = coreViewModel2.translations.chatPage.message.doubleAttentionReaction; + count = chatMessageViewModel.reactionsDoubleAttentionCount; + break; + } + case "\u2753" /* Question */: { + audioLabel = coreViewModel2.translations.chatPage.message.questionReaction; + count = chatMessageViewModel.reactionsQuestionCount; + break; + } + } + return /* @__PURE__ */ createElement( + "button", + { + class: "flex", + "on:click": sendReaction, + "aria-label": audioLabel, + "toggle:selected": isActive, + "set:count": count + }, + content, + /* @__PURE__ */ createElement("span", { "subscribe:innerText": count }) + ); + } + + // src/View/Components/messageReactionButtonRow.tsx + function MessageReactionButtonRow(coreViewModel2, chatMessageViewModel) { + return /* @__PURE__ */ createElement("div", { class: "grid gap width-100 message-reaction-row" }, MessageReactionButton( + coreViewModel2, + chatMessageViewModel, + "\u{1F44D}" /* ThumbsUp */ + ), MessageReactionButton( + coreViewModel2, + chatMessageViewModel, + "\u2705" /* Check */ + ), MessageReactionButton( + coreViewModel2, + chatMessageViewModel, + "\u{1F6D1}" /* Stop */ + ), MessageReactionButton( + coreViewModel2, + chatMessageViewModel, + "\u2757\uFE0F" /* Attention */ + ), MessageReactionButton( + coreViewModel2, + chatMessageViewModel, + "\u203C\uFE0F" /* DoubleAttention */ + ), MessageReactionButton( + coreViewModel2, + chatMessageViewModel, + "\u2753" /* Question */ + )); + } + + // src/View/Components/inlineReply.tsx + function InlineReply(chatMessageViewModel) { + const reply = chatMessageViewModel.inlineReply; + if (reply == void 0) return /* @__PURE__ */ createElement("div", null); + const scroll = () => { + ViewController.scrollToView(reply.chatMessage.id); + }; + return /* @__PURE__ */ createElement("div", { class: "inline-reply", "on:click": scroll }, /* @__PURE__ */ createElement("span", null, reply.sender), /* @__PURE__ */ createElement("b", { class: "ellipsis", "subscribe:innerText": reply.body })); + } + + // src/View/Components/messageReactionEntry.tsx + function MessageReactionEntry(reaction) { + return /* @__PURE__ */ createElement("div", { class: "tile flex-row" }, /* @__PURE__ */ createElement("span", { class: "flex width-100" }, reaction.sender), /* @__PURE__ */ createElement("span", null, reaction.content)); + } + + // src/View/Components/infoTile.tsx + function InfoTile(icon, label, content) { + let contentState; + if (typeof content == "string") { + contentState = new State(content); + } else { + contentState = content; + } + return /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, icon), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, label), /* @__PURE__ */ createElement("b", { class: "break-word", "subscribe:innerText": contentState }))); + } + + // src/View/Modals/chatMessageInfoModal.tsx + function ChatMessageInfoModal(coreViewModel2, chatMessageViewModel) { + return /* @__PURE__ */ createElement( + "div", + { + class: "modal", + "toggle:open": chatMessageViewModel.isPresentingInfoModal + }, + /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.chatPage.message.messageInfoHeadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, InfoTile( + "account_circle", + coreViewModel2.translations.chatPage.message.sentBy, + chatMessageViewModel.sender + ), InfoTile( + "schedule", + coreViewModel2.translations.chatPage.message.timeSent, + chatMessageViewModel.dateSent + ), InfoTile( + "forum", + coreViewModel2.translations.chatPage.message.channel, + chatMessageViewModel.channel + ), InfoTile( + "description", + coreViewModel2.translations.chatPage.message.messageContent, + chatMessageViewModel.body + )), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.copyMessage }, coreViewModel2.translations.chatPage.message.copyMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "content_copy")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.resendMessage }, coreViewModel2.translations.chatPage.message.resendMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "redo")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.decryptMessage }, coreViewModel2.translations.chatPage.message.decryptMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "key")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.reply }, coreViewModel2.translations.chatPage.message.replyToMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "reply"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, MessageReactionButtonRow( + coreViewModel2, + chatMessageViewModel + ), /* @__PURE__ */ createElement( + "div", + { + class: "flex-column gap", + "children:append": [ + chatMessageViewModel.allReactions, + MessageReactionEntry + ] + } + ))), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.hideInfoModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))) + ); + } + + // src/View/Components/chatMessage.tsx + function ChatMessage3(coreViewModel2, chatMessageViewModel) { + 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", + { + class: "message-bubble", + id: chatMessageViewModel.chatMessage.id, + "toggle:sentbyuser": chatMessageViewModel.sentByUser, + "toggle:hidden": chatMessageViewModel.isHidden + }, + InlineReply(chatMessageViewModel), + /* @__PURE__ */ createElement("div", { class: "main tile" }, /* @__PURE__ */ createElement("div", { class: "text-container" }, /* @__PURE__ */ createElement("span", { class: "sender-name ellipsis" }, chatMessageViewModel.sender), /* @__PURE__ */ createElement( + "span", + { + 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( + "button", + { + "on:click": chatMessageViewModel.showInfoModal, + "aria-label": coreViewModel2.translations.chatPage.message.showMessageInfoButtonAudioLabel + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "info") + ), /* @__PURE__ */ createElement( + "button", + { + class: "reply-button", + "on:click": chatMessageViewModel.reply, + "aria-label": coreViewModel2.translations.chatPage.message.replyToMessageButton + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "reply") + ))), + MessageReactionButtonRow(coreViewModel2, chatMessageViewModel), + ChatMessageInfoModal(coreViewModel2, chatMessageViewModel) + ); + } + + // src/View/ChatPages/messagePage.tsx + function MessagePage(coreViewModel2, messagePageViewModel) { + messagePageViewModel.loadData(); + const ChatMessageViewModelToView = (chatMessageViewModel) => { + return ChatMessage3(coreViewModel2, chatMessageViewModel); + }; + const messageContainer = /* @__PURE__ */ createElement( + "div", + { + id: "message-container", + "children:append": [ + messagePageViewModel.filteredMessageViewModels, + ChatMessageViewModelToView + ] + } + ); + const persistenceId = messagePageViewModel.chatViewModel.chatModel.id; + const replyPreview = ViewController.inlineReplies.setState( + persistenceId, + () => createProxyState( + [messagePageViewModel.replyingMessage], + () => { + if (messagePageViewModel.replyingMessage.value == void 0) + return /* @__PURE__ */ createElement("div", null); + return ReplyPreview( + coreViewModel2, + messagePageViewModel.replyingMessage.value + ); + } + ) + ); + function scrollDown(hard = false) { + if (hard) { + messageContainer.setAttribute("scroll-hard", ""); + } + messageContainer.scrollTop = messageContainer.scrollHeight; + messageContainer.removeAttribute("scroll-hard"); + } + function scrollDownIfApplicable() { + const scrollFromBottom = messageContainer.scrollHeight - (messageContainer.scrollTop + messageContainer.offsetHeight); + if (scrollFromBottom > 400) return; + scrollDown(); + } + messagePageViewModel.filteredMessageViewModels.subscribeSilent( + scrollDownIfApplicable + ); + setTimeout(() => scrollDown(true), 100); + messagePageViewModel.focusSetter.subscribeSilent(() => { + ViewController.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", + { + class: "ghost inset-outline", + "on:click": messagePageViewModel.showFilterModal, + "aria-label": coreViewModel2.translations.chatPage.message.filterMessagesButtonAudioLabel, + "toggle:selected": messagePageViewModel.isFilterActive + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "filter_alt") + ))), /* @__PURE__ */ createElement("div", { class: "content" }, messageContainer, /* @__PURE__ */ createElement("div", { id: "composer" }, /* @__PURE__ */ createElement("div", { class: "content-width-constraint" }, /* @__PURE__ */ createElement( + "div", + { + class: "reply-preview-wrapper", + "children:set": replyPreview + } + ), /* @__PURE__ */ createElement("div", { class: "input-width-constraint" }, /* @__PURE__ */ createElement( + "input", + { + id: "focused", + class: "blur", + "bind:value": messagePageViewModel.composingMessage, + "on:enter": messagePageViewModel.sendMessage, + placeholder: coreViewModel2.translations.chatPage.message.composerInputPlaceholder + } + ), /* @__PURE__ */ createElement( + "button", + { + class: "primary blur", + "aria-label": coreViewModel2.translations.chatPage.message.sendMessageButtonAudioLabel, + "on:click": messagePageViewModel.sendMessage, + "toggle:disabled": messagePageViewModel.cannotSendMessage + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "send") + ))))))), MessageFilterModal( + coreViewModel2, + messagePageViewModel, + ChatMessageViewModelToView + )); + } + + // src/View/Components/monthGrid.tsx + function MonthGrid2(coreViewModel2, monthGrid, selectedDate, handleDrop) { + const dayLabels = []; + let currentWeekday = monthGrid.firstDayOfWeek; + while (dayLabels.length < 7) { + dayLabels.push( + /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.regional.weekdays.abbreviated[currentWeekday]) + ); + currentWeekday++; + if (currentWeekday == 7) currentWeekday = 0; + } + const offsetElements = []; + for (let i = 0; i < monthGrid.offset; i++) { + offsetElements.push(/* @__PURE__ */ createElement("div", null)); + } + const converter = (taskViewModel) => { + const view = /* @__PURE__ */ createElement("span", { class: "ellipsis secondary" }, taskViewModel.task.name); + taskViewModel.index.subscribe((newIndex) => { + view.style.order = newIndex; + }); + return view; + }; + return /* @__PURE__ */ createElement("div", { class: "month-grid-wrapper" }, /* @__PURE__ */ createElement("div", { class: "day-labels" }, ...dayLabels), /* @__PURE__ */ createElement("div", { class: "month-grid" }, ...offsetElements, ...Object.entries(monthGrid.days).sort((a, b) => localeCompare(a[0], b[0])).map((entry) => { + const [date, mapState] = entry; + const isSelected = createProxyState( + [selectedDate], + () => selectedDate.value == parseInt(date) + ); + const isToday = createProxyState( + [coreViewModel2.todayDate], + () => { + return monthGrid.isCurrentMonth == true && date == coreViewModel2.unwrappedTodayDate.getDate().toString(); + } + ); + const eventCount = createProxyState( + [mapState], + () => mapState.value.size + ); + const hasEvents = createProxyState( + [eventCount], + () => eventCount.value != 0 + ); + function select() { + selectedDate.value = parseInt(date); + } + function drop() { + handleDrop(date); + } + return /* @__PURE__ */ createElement( + "button", + { + class: "tile", + "on:click": select, + "toggle:selected": isSelected, + "toggle:today": isToday, + "on:dragover": ViewController.allowDrop, + "on:drop": drop + }, + /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, date), /* @__PURE__ */ createElement( + "span", + { + class: "event-count", + "toggle:has-events": hasEvents, + "subscribe:innerText": eventCount + } + ), /* @__PURE__ */ createElement( + "div", + { + class: "event-list", + "children:append": [mapState, converter] + } + )) + ); + }))); + } + + // src/View/ChatPages/calendarPage.tsx + function CalendarPage(coreViewModel2, calendarPageViewModel) { + calendarPageViewModel.loadData(); + const mainContent = createProxyState( + [calendarPageViewModel.monthGrid], + () => { + const monthGrid = calendarPageViewModel.monthGrid.value; + if (monthGrid == void 0) { + return /* @__PURE__ */ createElement("div", null); + } else { + let drop = function(date) { + calendarPageViewModel.handleDrop( + monthGrid.year.toString(), + monthGrid.month.toString(), + date + ); + }; + return MonthGrid2( + coreViewModel2, + monthGrid, + calendarPageViewModel.selectedDate, + drop + ); + } + } + ); + const sidePaneContentWrapper = createProxyState( + [ + calendarPageViewModel.selectedYear, + calendarPageViewModel.selectedMonth, + calendarPageViewModel.selectedDate + ], + () => { + const listState = calendarPageViewModel.getEventsForDate(); + if (listState == void 0) { + return /* @__PURE__ */ createElement("div", null); + } else { + const sidePaneContent = createProxyState( + [listState], + () => { + if (listState.value.size == 0) { + return PlaceholderView( + coreViewModel2.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", + { + class: "width-100 height-100", + "children:set": sidePaneContent + } + ); + } + } + ); + const taskSettingsModal = createProxyState( + [calendarPageViewModel.selectedTaskViewModel], + () => { + if (calendarPageViewModel.selectedTaskViewModel.value == void 0) { + return /* @__PURE__ */ createElement("div", null); + } else { + ViewController.setFocusWithDelay(); + return TaskSettingsModal( + coreViewModel2, + calendarPageViewModel.selectedTaskViewModel.value + ); + } + } + ); + return /* @__PURE__ */ createElement("div", { id: "calendar-page" }, /* @__PURE__ */ createElement("div", { class: "pane-wrapper grid-pane-wrapper" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement( + "button", + { + class: "ghost", + "aria-label": coreViewModel2.translations.chatPage.calendar.todayButtonAudioLabel, + "on:click": calendarPageViewModel.showToday + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "today") + )), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement( + "button", + { + class: "ghost", + "aria-label": coreViewModel2.translations.chatPage.calendar.previousMonthButtonAudioLabel, + "on:click": calendarPageViewModel.showPreviousMonth + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_back") + ), /* @__PURE__ */ createElement("span", { class: "input-wrapper" }, /* @__PURE__ */ createElement( + "input", + { + class: "year-input", + type: "number", + "aria-label": coreViewModel2.translations.chatPage.calendar.yearInputAudioLabel, + placeholder: coreViewModel2.translations.chatPage.calendar.yearInputPlaceholder, + "bind:value": calendarPageViewModel.selectedYear + } + ), /* @__PURE__ */ createElement( + "input", + { + class: "month-input", + type: "number", + "aria-label": coreViewModel2.translations.chatPage.calendar.monthInputAudioLabel, + placeholder: coreViewModel2.translations.chatPage.calendar.monthInputPlaceholder, + "bind:value": calendarPageViewModel.selectedMonth + } + )), /* @__PURE__ */ createElement( + "button", + { + class: "ghost", + "aria-label": coreViewModel2.translations.chatPage.calendar.nextMonthButtonAudioLabel, + "on:click": calendarPageViewModel.showNextMonth + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") + )), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement( + "button", + { + class: "ghost", + "aria-label": coreViewModel2.translations.chatPage.task.createTaskButtonAudioLabel, + "on:click": calendarPageViewModel.createEvent + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "add") + ))), /* @__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", { "children:set": taskSettingsModal })); + } + // src/View/chatPage.tsx function ChatPage(coreViewModel2, chatViewModel) { ViewController.chatPages.state = createProxyState( @@ -5790,590 +6324,283 @@ return /* @__PURE__ */ createElement("div", { id: "chat-page-wrapper", "children:set": chatPageContent }); } - // src/View/Modals/connectionModal.tsx - function ConnectionModal(coreViewModel2, connectionViewModel2) { - const previousAddressConverter = (address) => { - function connnect() { - connectionViewModel2.connectToAddress(address); - } - const cannotConnect = createProxyState( - [connectionViewModel2.isConnected], - () => connectionViewModel2.isConnected.value == true && connectionViewModel2.coreViewModel.connectionModel.address == address - ); - return DeletableListItem( - coreViewModel2, - address, + // src/View/Modals/splitModal.tsx + function SplitModal(coreViewModel2, leftView, rightView, extendedStyle = false, navigationState) { + function closePage() { + navigationState.value = void 0; + } + const hasPageOpen = new State(false); + navigationState?.subscribe( + (newValue) => hasPageOpen.value = newValue != void 0 + ); + const view = /* @__PURE__ */ createElement( + "div", + { + class: "split-modal", + "toggle:extended": extendedStyle, + "toggle:navigation": navigationState != void 0, + "toggle:page-open": hasPageOpen + }, + /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "scroll-area", "children:set": leftView })), + /* @__PURE__ */ createElement("div", { class: "scroll-area slide-up" }, /* @__PURE__ */ createElement( + "div", + { + class: "flex-row width-100 mobile-only", + "toggle:hidden": navigationState == void 0 + }, /* @__PURE__ */ createElement( "button", { - class: "primary", - "on:click": connnect, - "toggle:disabled": cannotConnect, - "aria-label": coreViewModel2.translations.connectionModal.connectButtonAudioLabel + class: "ghost square", + "aria-label": coreViewModel2.translations.general.backButton, + "on:click": closePage }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "link") - ), - () => { - connectionViewModel2.removePreviousAddress(address); + /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_back") + ) + ), /* @__PURE__ */ createElement("div", { "children:set": rightView })) + ); + return view; + } + + // src/View/Components/directoryItemList.tsx + function DirectoryItemList(storageViewModel2, pathString = PATH_COMPONENT_SEPARATOR) { + const StringToDirectoryItemList = (pathString2) => DirectoryItemList(storageViewModel2, pathString2); + const path = StorageModel.stringToPathComponents(pathString); + const fileName = StorageModel.getFileName(path); + const items = new ListState(); + const style = `text-indent: ${path.length}rem`; + function loadItems() { + items.clear(); + const directoryItems = storageViewModel2.coreViewModel.storageModel.list(path); + for (const directoryItem of directoryItems) { + const itemPath = [...path, directoryItem]; + const pathString2 = StorageModel.pathComponentsToString(...itemPath); + items.add(pathString2); + } + } + function select() { + storageViewModel2.selectedPath.value = pathString; + } + storageViewModel2.lastDeletedItemPath.subscribe((lastDeletedItemPath) => { + if (!items.value.has(lastDeletedItemPath)) return; + select(); + setTimeout(() => loadItems(), 50); + }); + const isSelected = createProxyState( + [storageViewModel2.selectedPath], + () => storageViewModel2.selectedPath.value == pathString + ); + isSelected.subscribe(() => { + if (isSelected.value == false) return; + loadItems(); + }); + return /* @__PURE__ */ createElement("div", { class: "flex-column" }, /* @__PURE__ */ createElement( + "button", + { + class: "width-100 flex-1 clip", + "toggle:selected": isSelected, + "on:click": select + }, + /* @__PURE__ */ createElement("span", { class: "ellipsis width-100 flex-1", style }, fileName) + ), /* @__PURE__ */ createElement( + "div", + { + class: "flex-column", + "children:append": [items, StringToDirectoryItemList] + } + )); + } + + // src/View/Modals/storageModal.tsx + function StorageModal(coreViewModel2, storageViewModel2) { + const detailView = createProxyState( + [storageViewModel2.selectedPath], + () => { + if (storageViewModel2.selectedPath.value == PATH_COMPONENT_SEPARATOR) + return /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.storage.noItemSelected), /* @__PURE__ */ createElement("hr", null), DangerousActionButton( + coreViewModel2, + coreViewModel2.translations.storage.removeJunkButton, + "delete_forever", + storageViewModel2.removeJunk + )); + return /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("div", { class: "tile flex-no" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.storage.path), /* @__PURE__ */ createElement( + "span", + { + class: "break-all", + "subscribe:innerText": storageViewModel2.selectedPath + } + ))), /* @__PURE__ */ createElement("div", { class: "tile flex-no" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.storage.content), /* @__PURE__ */ createElement( + "code", + { + "subscribe:innerText": storageViewModel2.selectedFileContent + } + ))), DangerousActionButton( + coreViewModel2, + coreViewModel2.translations.storage.deleteItem, + "delete_forever", + storageViewModel2.deleteSelectedItem + )); + } + ); + return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": storageViewModel2.isShowingStorageModal }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", { class: "padding-0" }, SplitModal( + coreViewModel2, + new State(DirectoryItemList(storageViewModel2)), + detailView, + true + )), /* @__PURE__ */ createElement("button", { "on:click": storageViewModel2.close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); + } + + // src/View/Components/optionButton.tsx + function OptionButton(text, value, selection) { + function select() { + selection.value = value; + } + const isSelected = createProxyState( + [selection], + () => selection.value == value + ); + return /* @__PURE__ */ createElement("button", { class: "standard", "toggle:selected": isSelected, "on:click": select }, text); + } + + // src/View/Components/optionButtonList.tsx + function OptionButtonList(options, selection) { + function OptionToView(option) { + const [text, value] = option; + return OptionButton(text, value, selection); + } + return /* @__PURE__ */ createElement( + "div", + { + class: "flex-column gap", + "children:append": [options, OptionToView] + } + ); + } + + // src/View/Components/navigationButton.tsx + function NavigationButton(coreViewModel2, label, navigationState, page, arrowMobileOnly) { + const isSelected = createProxyState( + [navigationState], + () => navigationState.value == page + ); + function open() { + navigationState.value = page; + } + const iconClass = `icon ${arrowMobileOnly == true ? "mobile-only" : ""}`; + return /* @__PURE__ */ createElement("button", { "toggle:selected": isSelected, "on:click": open }, label, /* @__PURE__ */ createElement("span", { class: iconClass }, "arrow_forward")); + } + + // src/View/Modals/settingsModal.tsx + function SettingsModal(coreViewModel2, settingsViewModel2) { + const detailView = createProxyState( + [settingsViewModel2.selectedModalPage], + () => { + switch (settingsViewModel2.selectedModalPage.value) { + case void 0: { + return PlaceholderView( + coreViewModel2.translations.general.noPageSelected + ); + } + case 0 /* Appearance */: + return SettingsAppearancePane( + coreViewModel2, + settingsViewModel2 + ); + case 1 /* Regional */: + return SettingsRegionalPane( + coreViewModel2, + settingsViewModel2 + ); + case 2 /* Info */: + return SettingsInfoPane(coreViewModel2, settingsViewModel2); } - ); - }; + } + ); return /* @__PURE__ */ createElement( "div", { class: "modal", - "toggle:open": connectionViewModel2.isShowingConnectionModal + "toggle:open": settingsViewModel2.isShowingSettingsModal }, - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.connectionModal.connectionModalHeadline), /* @__PURE__ */ createElement( - "div", - { - class: "flex-column gap", - "children:append": [ - connectionViewModel2.previousAddresses, - previousAddressConverter - ] - } - )), /* @__PURE__ */ createElement("button", { "on:click": connectionViewModel2.hideConnectionModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))) + /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", { class: "padding-0" }, SplitModal( + coreViewModel2, + new State( + SettingsLeftPane(coreViewModel2, settingsViewModel2) + ), + detailView, + true, + settingsViewModel2.selectedModalPage + )), /* @__PURE__ */ createElement("button", { "on:click": settingsViewModel2.close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))) ); } - - // node_modules/udn-frontend/index.ts - var UDNFrontend = class { - ws; - // HANDLERS - connectionHandler = () => { - }; - disconnectionHandler = () => { - }; - messageHandler = (data) => { - }; - mailboxHandler = (mailboxId) => { - }; - mailboxConnectionHandler = (mailboxId) => { - }; - mailboxDeleteHandler = (mailboxId) => { - }; - // INIT - set onconnect(handler) { - this.connectionHandler = handler; - } - set ondisconnect(handler) { - this.disconnectionHandler = handler; - } - set onmessage(handler) { - this.messageHandler = handler; - } - set onmailboxcreate(handler) { - this.mailboxHandler = handler; - } - set onmailboxconnect(handler) { - this.mailboxConnectionHandler = handler; - } - set onmailboxdelete(handler) { - this.mailboxDeleteHandler = handler; - } - // UTILITY METHODS - send(messageObject) { - if (this.ws == void 0) return false; - if (this.ws.readyState != 1) return false; - const messageString = JSON.stringify(messageObject); - this.ws.send(messageString); - return true; - } - // PUBLIC METHODS - // connection - connect(address) { - try { - this.disconnect(); - this.ws = new WebSocket(address); - this.ws.addEventListener("open", this.connectionHandler); - this.ws.addEventListener("close", this.disconnectionHandler); - this.ws.addEventListener("message", (message) => { - const dataString = message.data.toString(); - const data = JSON.parse(dataString); - if (data.assignedMailboxId) { - return this.mailboxHandler(data.assignedMailboxId); - } else if (data.connectedMailboxId) { - return this.mailboxConnectionHandler(data.connectedMailboxId); - } else if (data.deletedMailbox) { - return this.mailboxDeleteHandler(data.deletedMailbox); - } else { - this.send({ uuid: data.uuid, confirmingMessageReceived: true }); - this.messageHandler(data); - } - }); - } catch (error) { - console.error(error); - } - } - disconnect() { - this.ws?.close(); - } - // message - sendMessage(channel, body) { - const messageObject = { - messageChannel: channel, - messageBody: body - }; - return this.send(messageObject); - } - // subscription - subscribe(channel) { - const messageObject = { - subscribeChannel: channel - }; - return this.send(messageObject); - } - unsubscribe(channel) { - const messageObject = { - unsubscribeChannel: channel - }; - return this.send(messageObject); - } - // mailbox - requestMailbox() { - const messageObject = { - requestingMailboxSetup: true - }; - return this.send(messageObject); - } - connectMailbox(mailboxId) { - const messageObject = { - requestedMailbox: mailboxId - }; - return this.send(messageObject); - } - deleteMailbox(mailboxId) { - const messageObject = { - deletingMailbox: mailboxId - }; - return this.send(messageObject); - } - }; - - // src/Model/Global/connectionModel.ts - 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(); - this.channelsToSubscribe = /* @__PURE__ */ new Set(); - // handlers - this.handleMessage = (data) => { - this.messageHandlerManager.trigger(data); - }; - this.handleConnectionChange = () => { - console.log("connection status:", this.isConnected, this.address); - this.connectionChangeHandlerManager.trigger(); - if (this.address == void 0) return; - this.storeAddress(this.address); - this.sendSubscriptionRequest(); - this.sendMessagesInOutbox(); - }; - // connection - this.connect = (address) => { - console.log("connecting...", address); - this.shouldAttemptReconnect = true; - this.udn.connect(address); - }; - this.disconnect = () => { - this.shouldAttemptReconnect = false; - this.udn.disconnect(); - const reconnectAddressPath = StorageModel.getPath( - "connection" /* ConnectionModel */, - filePaths.connectionModel.reconnectAddress - ); - this.storageModel.remove(reconnectAddressPath); - }; - 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) => { - const mailboxDirPath = StorageModel.getPath( - "connection" /* ConnectionModel */, - filePaths.connectionModel.mailboxes - ); - const mailboxFilePath = [...mailboxDirPath, address]; - return mailboxFilePath; - }; - this.requestNewMailbox = () => { - console.log("requesting new mailbox"); - this.udn.requestMailbox(); - }; - this.connectMailbox = () => { - if (this.address == void 0) 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); - }; - this.storeMailbox = (mailboxId) => { - if (this.address == void 0) return; - this.storageModel.write(this.getMailboxPath(this.address), mailboxId); - }; - // subscription - this.addChannel = (channel) => { - this.channelsToSubscribe.add(channel); - this.sendSubscriptionRequest(); - }; - this.sendSubscriptionRequest = () => { - if (this.isConnected == false) return; - for (const channel of this.channelsToSubscribe) { - this.udn.subscribe(channel); - } - this.connectMailbox(); - }; - // outbox - this.getOutboxPath = () => { - return StorageModel.getPath( - "connection" /* ConnectionModel */, - filePaths.connectionModel.outbox - ); - }; - this.getOutboxMessags = () => { - const outboxPath = this.getOutboxPath(); - const messageIds = this.storageModel.list(outboxPath); - let chatMessages = []; - for (const messageId of messageIds) { - const chatMessage = this.storageModel.readStringifiable( - [...outboxPath, messageId], - ChatMessageReference - ); - if (chatMessage == null) continue; - chatMessages.push(chatMessage); - } - return chatMessages; - }; - this.addToOutbox = (chatMessage) => { - const messagePath = [...this.getOutboxPath(), chatMessage.id]; - this.storageModel.writeStringifiable(messagePath, chatMessage); - }; - this.removeFromOutbox = (chatMessage) => { - const messagePath = [...this.getOutboxPath(), chatMessage.id]; - this.storageModel.remove(messagePath); - }; - this.sendMessagesInOutbox = () => { - const messages = this.getOutboxMessags(); - for (const message of messages) { - const isSent = this.tryToSendMessage(message); - if (isSent == false) return; - this.removeFromOutbox(message); - } - }; - // messaging - this.sendMessageOrStore = (chatMessage) => { - const isSent = this.tryToSendMessage(chatMessage); - if (isSent == true) return; - this.addToOutbox(chatMessage); - }; - this.tryToSendMessage = (chatMessage) => { - const stringifiedBody = stringify(chatMessage); - const isSent = this.sendPlainMessage( - chatMessage.channel, - stringifiedBody - ); - if (isSent) this.messageSentHandlerManager.trigger(chatMessage); - return isSent; - }; - this.sendPlainMessage = (channel, body) => { - return this.udn.sendMessage(channel, body); - }; - // storage - this.getPreviousAddressPath = () => { - return StorageModel.getPath( - "connection" /* ConnectionModel */, - filePaths.connectionModel.previousAddresses - ); - }; - this.getAddressPath = (address) => { - const dirPath = this.getPreviousAddressPath(); - return [...dirPath, address]; - }; - this.getReconnectAddressPath = () => { - return StorageModel.getPath( - "connection" /* ConnectionModel */, - filePaths.connectionModel.reconnectAddress - ); - }; - this.storeAddress = (address) => { - const addressPath = this.getAddressPath(address); - this.storageModel.write(addressPath, ""); - const reconnectAddressPath = this.getReconnectAddressPath(); - this.storageModel.write(reconnectAddressPath, address); - }; - this.removeAddress = (address) => { - const addressPath = this.getAddressPath(address); - this.storageModel.remove(addressPath); - }; - this.udn = new UDNFrontend(); - this.storageModel = storageModel2; - this.udn.onmessage = (data) => { - this.handleMessage(data); - }; - this.udn.onconnect = () => { - this.handleConnectionChange(); - }; - this.udn.ondisconnect = () => { - this.handleConnectionChange(); - }; - this.udn.onmailboxcreate = (mailboxId) => { - console.log("created mailbox", mailboxId); - this.storeMailbox(mailboxId); - this.connectMailbox(); - }; - this.udn.onmailboxdelete = (mailboxId) => { - console.log(`mailbox ${mailboxId} deleted`); - this.requestNewMailbox(); - }; - this.udn.onmailboxconnect = (mailboxId) => { - console.log(`using mailbox ${mailboxId}`); - }; - setInterval(() => { - if (this.isConnected == true) return; - if (this.shouldAttemptReconnect == false) return; - this.reconnect(); - }, 5e3); - this.reconnect(); - } - // data - get isConnected() { - return this.udn.ws != void 0 && this.udn.ws.readyState == 1; - } - get address() { - return this.udn.ws?.url; - } - get addresses() { - const dirPath = this.getPreviousAddressPath(); - return this.storageModel.list(dirPath); - } - }; - - // src/ViewModel/Global/connectionViewModel.ts - var ConnectionViewModel = class { - // init - constructor(coreViewModel2) { - this.coreViewModel = coreViewModel2; - // state - this.serverAddressInput = new State(""); - this.isConnected = new State(false); - this.isShowingConnectionModal = new State(false); - this.previousAddresses = new ListState(); - // guards - this.cannotConnect = createProxyState( - [this.serverAddressInput, this.isConnected], - () => this.isConnected.value == true && this.serverAddressInput.value == this.coreViewModel.connectionModel.address || this.serverAddressInput.value == "" - ); - this.cannotDisonnect = createProxyState( - [this.isConnected], - () => this.isConnected.value == false - ); - this.hasNoPreviousConnections = createProxyState( - [this.previousAddresses], - () => this.previousAddresses.value.size == 0 - ); - // handlers - this.connectionChangeHandler = () => { - this.isConnected.value = this.coreViewModel.connectionModel.isConnected; - if (this.coreViewModel.connectionModel.isConnected == false) return; - if (this.coreViewModel.connectionModel.address == void 0) return; - this.serverAddressInput.value = this.coreViewModel.connectionModel.address; - if (!this.previousAddresses.value.has( - this.coreViewModel.connectionModel.address - )) { - this.previousAddresses.add( - this.coreViewModel.connectionModel.address - ); - } - }; - // methods - this.connect = () => { - this.connectToAddress(this.serverAddressInput.value); - }; - this.connectToAddress = (address) => { - console.log(address); - this.coreViewModel.connectionModel.connect(address); - }; - this.disconnect = () => { - this.coreViewModel.connectionModel.disconnect(); - }; - this.removePreviousAddress = (address) => { - this.coreViewModel.connectionModel.removeAddress(address); - this.updatePreviousAddresses(); - }; - // view - this.showConnectionModal = () => { - this.isShowingConnectionModal.value = true; - }; - this.hideConnectionModal = () => { - this.isShowingConnectionModal.value = false; - }; - this.updatePreviousAddresses = () => { - this.previousAddresses.clear(); - this.previousAddresses.add( - ...this.coreViewModel.connectionModel.addresses - ); - }; - this.updatePreviousAddresses(); - coreViewModel2.connectionModel.connectionChangeHandlerManager.setHandler( - "connection-view-model", - this.connectionChangeHandler - ); - } - }; - - // src/ViewModel/Global/fileTransferViewModel.ts - var FileTransferViewModel = class extends Context { - // init - constructor(coreViewModel2) { - super("file-transfer"); - this.coreViewModel = coreViewModel2; - // state - this.presentedModal = new State(void 0); - this.generalFileOptions = new ListState(); - this.chatFileOptions = new ListState(); - this.selectedPaths = new ListState(); - this.transferChannel = new State(""); - this.transferKey = new State(""); - this.receivingTransferChannel = new State(""); - this.receivingTransferKey = new State(""); - this.filePathsSent = new ListState(); - this.filesSentCount = createProxyState( - [this.filePathsSent], - () => this.filePathsSent.value.size - ); - this.filesSentText = createProxyState( - [this.filesSentCount], - () => this.coreViewModel.translations.dataTransferModal.filesSentCount( - this.filesSentCount.value + function SettingsLeftPane(coreViewModel2, settingsViewModel2) { + return /* @__PURE__ */ createElement("div", { class: "flex-column gap slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.settingsButton), SettingsPaneButton( + coreViewModel2, + settingsViewModel2, + 0 /* Appearance */, + coreViewModel2.translations.settings.pages.appearance + ), SettingsPaneButton( + coreViewModel2, + settingsViewModel2, + 1 /* Regional */, + coreViewModel2.translations.settings.pages.regional + ), SettingsPaneButton( + coreViewModel2, + settingsViewModel2, + 2 /* Info */, + coreViewModel2.translations.settings.pages.info + )); + } + function SettingsPaneButton(coreViewModel2, settingsViewModel2, page, label) { + return NavigationButton( + coreViewModel2, + label, + settingsViewModel2.selectedModalPage, + page, + true + ); + } + function SettingsInfoPane(coreViewModel2, settingsViewModel2) { + return /* @__PURE__ */ createElement("div", { class: "slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.appName), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, InfoTile( + "build", + coreViewModel2.translations.settings.version, + settingsViewModel2.coreViewModel.BUILD + ))); + } + function SettingsRegionalPane(coreViewModel2, settingsViewModel2) { + return /* @__PURE__ */ createElement("div", { class: "slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.settings.pages.regional), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.settings.language), OptionButtonList( + new ListState( + Object.values(Languages).map((x) => [languageNames[x], x]) + ), + settingsViewModel2.language + ), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.settings.firstDayOfWeekLabel), OptionButtonList( + new ListState( + coreViewModel2.translations.regional.weekdays.full.map( + (x, i) => [x, i.toString()] ) - ); - this.filePathsReceived = new ListState(); - this.filesReceivedCount = createProxyState( - [this.filePathsReceived], - () => this.filePathsReceived.value.size - ); - this.filesReceivedText = createProxyState( - [this.filesReceivedCount], - () => this.coreViewModel.translations.dataTransferModal.filesReceivedCount( - this.filesReceivedCount.value - ) - ); - // guards - this.hasNoPathsSelected = createProxyState( - [this.selectedPaths], - () => this.selectedPaths.value.size == 0 - ); - this.didNotFinishSending = new State(true); - this.cannotPrepareToReceive = createProxyState( - [this.receivingTransferChannel, this.receivingTransferKey], - () => this.receivingTransferChannel.value == "" || this.receivingTransferKey.value == "" - ); - // handlers - this.handleReceivedFile = (path) => { - this.filePathsReceived.add(path); - }; - // methods - this.getOptions = () => { - this.generalFileOptions.clear(); - this.chatFileOptions.clear(); - this.selectedPaths.clear(); - this.generalFileOptions.add( - { - label: this.coreViewModel.translations.dataTransferModal.connectionData, - path: StorageModel.getPath( - "connection" /* ConnectionModel */, - filePaths.connectionModel.previousAddresses - ) - }, - { - label: this.coreViewModel.translations.dataTransferModal.settingsData, - path: StorageModel.getPath( - "settings" /* SettingsModel */, - [] - ) - } - ); - const chatModels = this.coreViewModel.chatListModel.chatModels; - for (const chatModel of chatModels) { - this.chatFileOptions.add({ - label: chatModel.info.primaryChannel, - path: chatModel.getBasePath() - }); - } - }; - this.getTransferData = () => { - const transferData = this.coreViewModel.fileTransferModel.generateTransferData(); - this.transferChannel.value = transferData.channel; - this.transferKey.value = transferData.key; - }; - // view - this.showDirectionSelectionModal = () => { - this.coreViewModel.context = this; - this.presentedModal.value = 0 /* DirectionSelection */; - this.getOptions(); - }; - this.showFileSelectionModal = () => { - this.presentedModal.value = 1 /* FileSelection */; - }; - this.showTransferDataModal = () => { - this.presentedModal.value = 2 /* TransferDataDisplay */; - this.getTransferData(); - this.coreViewModel.fileTransferModel.prepareToSend(); - }; - this.initiateTransfer = () => { - this.presentedModal.value = 3 /* TransferDisplay */; - this.didNotFinishSending.value = true; - this.filePathsSent.clear(); - this.coreViewModel.fileTransferModel.sendFiles( - this.selectedPaths.value.values(), - (path) => { - console.log(path); - this.filePathsSent.add(path); - } - ); - this.didNotFinishSending.value = false; - }; - this.showTransferDataInputModal = () => { - this.presentedModal.value = 4 /* TransferDataInput */; - }; - this.prepareReceivingData = () => { - if (this.cannotPrepareToReceive.value == true) return; - this.presentedModal.value = 5 /* ReceptionDisplay */; - this.filePathsReceived.clear(); - const transferData = { - channel: this.receivingTransferChannel.value, - key: this.receivingTransferKey.value - }; - this.coreViewModel.fileTransferModel.prepareToReceive(transferData); - }; - // exit - this.close = () => { - this.coreViewModel.closeContext(this.contextId); - }; - this.handleContextClose = () => { - this.presentedModal.value = void 0; - }; - this.coreViewModel.fileTransferModel.fileHandlerManager.setHandler( - "file-transfer-view-model", - this.handleReceivedFile - ); - this.coreViewModel.fileTransferModel.readyToSendHandlerManager.setHandler( - "file-transfer-view-model", - () => this.initiateTransfer() - ); - this.registerKeyStroke("backspace" /* CloseOrCancel */, this.close); - } - }; + ), + settingsViewModel2.firstDayOfWeek + )); + } + function SettingsAppearancePane(coreViewModel2, settingsViewModel2) { + return /* @__PURE__ */ createElement("div", { class: "slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.settings.pages.appearance), /* @__PURE__ */ createElement("hr", null), OptionButtonList( + new ListState([ + [ + coreViewModel2.translations.settings.themes.dark, + "dark" /* Dark */ + ], + [ + coreViewModel2.translations.settings.themes.light, + "light" /* Light */ + ], + [ + coreViewModel2.translations.settings.themes.system, + "system" /* System */ + ] + ]), + settingsViewModel2.theme + )); + } // src/View/Components/textSpan.tsx var StringToTextSpan = (string) => { @@ -6623,613 +6850,52 @@ )), /* @__PURE__ */ createElement("button", { "on:click": ViewController.reload }, coreViewModel2.translations.general.reloadAppButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "refresh")))); } - // src/Model/Global/fileTransferModel.ts - var FileTransferModel = class _FileTransferModel { - // init - constructor(storageModel2, connectionModel2) { - this.fileHandlerManager = new HandlerManager(); - this.readyToSendHandlerManager = new HandlerManager(); - this.direction = 0 /* Send */; - // general - this.generateTransferData = () => { - const transferData = { - channel: generateRandomToken(4), - key: generateRandomToken(6) - }; - this.transferData = transferData; - return transferData; - }; - this.prepareToSend = () => { - this.direction = 0 /* Send */; - this.connectionModel.addChannel(this.transferData.channel); - }; - this.prepareToReceive = (transferData) => { - this.direction = 1 /* Receive */; - this.connectionModel.addChannel(transferData.channel); - this.transferData = transferData; - this.connectionModel.sendPlainMessage( - transferData.channel, - _FileTransferModel.READY_MESSAGE - ); - }; - // handlers - this.handleMessage = (data) => { - if (this.transferData == void 0) return; - if (data.messageChannel != this.transferData.channel) return; - if (data.messageBody == _FileTransferModel.READY_MESSAGE && this.direction == 0 /* Send */) { - this.readyToSendHandlerManager.trigger(true); - } - if (this.transferData == void 0) return; - if (data.messageBody == void 0) return; - this.handleFile(data.messageBody); - }; - this.handleFile = async (encryptedFileData) => { - if (this.transferData == void 0) return; - const decrypted = await decryptString( - encryptedFileData, - this.transferData.key - ); - const parsed = parse(decrypted); - const isFileData = checkMatchesObjectStructure( - parsed, - FileDataReference - ); - if (isFileData == false) return; - const fileData = parsed; - this.storageModel.write(fileData.path, fileData.body); - const pathString = StorageModel.pathComponentsToString( - ...fileData.path - ); - this.fileHandlerManager.trigger(pathString); - }; - // sending - this.sendFiles = (directoryPaths, callback) => { - for (const directoryPath of directoryPaths) { - this.storageModel.recurse(directoryPath, (filePath) => { - this.sendFile(filePath); - const pathString = StorageModel.pathComponentsToString( - ...filePath - ); - callback(pathString); - }); - } - }; - this.sendFile = async (filePath) => { - if (this.transferData == void 0) return; - const fileContent = this.storageModel.read(filePath); - if (fileContent == null) return; - const fileData = { - path: filePath, - body: fileContent - }; - const stringifiedFileData = stringify(fileData); - const encryptedFileData = await encryptString( - stringifiedFileData, - this.transferData.key - ); - this.connectionModel.sendPlainMessage( - this.transferData.channel, - encryptedFileData - ); - }; - this.storageModel = storageModel2; - this.connectionModel = connectionModel2; - this.connectionModel.messageHandlerManager.setHandler( - "file-transfer", - this.handleMessage - ); - } - static { - this.READY_MESSAGE = "ready"; - } - }; - var FileDataReference = { - path: [""], - body: "" - }; - - // src/View/Components/chatEntry.tsx - function ChatEntry(chatViewModel) { - const view = /* @__PURE__ */ createElement( - "button", - { - class: "tile colored-tile chat-entry animate-highlight", - "set:color": chatViewModel.settingsPageViewModel.color, - style: "height: 8rem", - "on:click": chatViewModel.open, - "toggle:highlight": chatViewModel.hasUnreadMessages - }, - /* @__PURE__ */ createElement( - "span", - { - class: "shadow", - "subscribe:innerText": chatViewModel.settingsPageViewModel.primaryChannel - } - ), - /* @__PURE__ */ createElement( - "h2", - { - "subscribe:innerText": chatViewModel.settingsPageViewModel.primaryChannel - } - ) - ); - chatViewModel.index.subscribe((newIndex) => { - view.style.order = newIndex; - }); - return view; - } - var ChatViewModelToChatEntry = (chatViewModel) => { - return ChatEntry(chatViewModel); - }; - - // src/View/Components/homePageButton.tsx - function HomePageButton(action, label, icon) { - return /* @__PURE__ */ createElement("button", { class: "tile flex-no", "on:click": action }, /* @__PURE__ */ createElement("span", { class: "icon" }, icon), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, label))); - } - - // src/View/homePage.tsx - function HomePage(coreViewModel2, storageViewModel2, settingsViewModel2, connectionViewModel2, fileTransferViewModel2, chatListViewModel2) { - const overviewSection = /* @__PURE__ */ createElement("div", { id: "overview-section" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.overviewHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "cell_tower"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.homePage.serverAddress), /* @__PURE__ */ createElement( - "input", - { - list: "previous-connection-list", - placeholder: coreViewModel2.translations.homePage.serverAddressPlaceholder, - "bind:value": connectionViewModel2.serverAddressInput, - "on:enter": connectionViewModel2.connect + // src/View/Modals/connectionModal.tsx + function ConnectionModal(coreViewModel2, connectionViewModel2) { + const previousAddressConverter = (address) => { + function connnect() { + connectionViewModel2.connectToAddress(address); } - ), /* @__PURE__ */ createElement( - "datalist", - { - hidden: true, - id: "previous-connection-list", - "children:append": [ - connectionViewModel2.previousAddresses, - StringToOption - ] - } - ))), /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement( - "button", - { - class: "danger flex justify-center", - "aria-label": coreViewModel2.translations.homePage.disconnectAudioLabel, - "on:click": connectionViewModel2.disconnect, - "toggle:disabled": connectionViewModel2.cannotDisonnect - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "link_off") - ), /* @__PURE__ */ createElement( - "button", - { - class: "flex justify-center", - "aria-label": coreViewModel2.translations.homePage.manageConnectionsAudioLabel, - "on:click": connectionViewModel2.showConnectionModal, - "toggle:disabled": connectionViewModel2.hasNoPreviousConnections - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "history") - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary flex justify-center", - "aria-label": coreViewModel2.translations.homePage.connectAudioLabel, - "on:click": connectionViewModel2.connect, - "toggle:disabled": connectionViewModel2.cannotConnect - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "link") - )), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "account_circle"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.homePage.yourNameLabel), /* @__PURE__ */ createElement( - "input", - { - placeholder: coreViewModel2.translations.homePage.yourNamePlaceholder, - "bind:value": settingsViewModel2.usernameInput, - "on:enter": settingsViewModel2.setName - } - ))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, /* @__PURE__ */ createElement( - "button", - { - class: "width-50", - "on:click": settingsViewModel2.setName, - "toggle:disabled": settingsViewModel2.cannotSetName, - "aria-label": coreViewModel2.translations.homePage.setNameButtonAudioLabel - }, - coreViewModel2.translations.general.setButton, - /* @__PURE__ */ createElement("span", { class: "icon" }, "check") - )), /* @__PURE__ */ createElement("hr", null), HomePageButton( - settingsViewModel2.showSettingsModal, - coreViewModel2.translations.homePage.settingsButton, - "settings" - ), HomePageButton( - fileTransferViewModel2.showDirectionSelectionModal, - coreViewModel2.translations.homePage.transferDataButton, - "sync_alt" - ), HomePageButton( - storageViewModel2.showStorageModal, - coreViewModel2.translations.homePage.manageStorageButton, - "hard_drive" - ), /* @__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 }, coreViewModel2.translations.homePage.scrollToChatButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward"))))); - const chatSection = /* @__PURE__ */ createElement("div", { id: "chat-section" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.chatsHeadline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( - "input", - { - placeholder: coreViewModel2.translations.homePage.addChatPlaceholder, - "aria-label": coreViewModel2.translations.homePage.addChatAudioLabel, - "bind:value": chatListViewModel2.newChatPrimaryChannel, - "on:enter": chatListViewModel2.createChat - } - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary", - "aria-label": coreViewModel2.translations.homePage.addChatButton, - "on:click": chatListViewModel2.createChat, - "toggle:disabled": chatListViewModel2.cannotCreateChat - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "add") - )), /* @__PURE__ */ createElement( - "div", - { - id: "chat-grid", - "children:append": [ - chatListViewModel2.chatViewModels, - ChatViewModelToChatEntry - ] - } - )); - function scrollToChat() { - chatSection.scrollIntoView(); - } - return /* @__PURE__ */ createElement("article", { id: "home-page" }, /* @__PURE__ */ createElement("div", null, overviewSection, chatSection)); - } - - // src/Model/Global/settingsModel.ts - var SettingsModel = class _SettingsModel { - // init - constructor(storageModel2) { - this.setName = (newValue) => { - this.username = newValue; - this.storeSetting("username", newValue); - }; - this.setFirstDayOfWeek = (newValue) => { - this.firstDayOfWeek = newValue; - this.storeSetting("firstDayOfWeek", newValue); - }; - this.setLanguage = (newValue) => { - this.language = newValue; - this.storeSetting("language", newValue); - }; - this.setTheme = (newValue) => { - this.theme = newValue; - this.storeSetting("theme", newValue); - }; - // load - this.readSetting = (pathName) => { - const path = StorageModel.getPath( - "settings" /* SettingsModel */, - filePaths.settingsModel[pathName] - ); - return this.storageModel.read(path); - }; - this.loadUsername = () => { - const content = this.readSetting("username"); - this.username = content ?? ""; - }; - this.loadFirstDayofWeek = () => { - const content = this.readSetting("firstDayOfWeek"); - this.firstDayOfWeek = content ?? "0"; - }; - this.loadLanguage = () => { - const content = this.readSetting("language"); - this.language = content ?? _SettingsModel.getSystemLanguage(); - }; - this.loadTheme = () => { - const content = this.readSetting("theme"); - this.theme = content ?? "system" /* System */; - }; - this.storageModel = storageModel2; - this.loadUsername(); - this.loadFirstDayofWeek(); - this.loadLanguage(); - this.loadTheme(); - } - // storage - storeSetting(pathName, value) { - const path = StorageModel.getPath( - "settings" /* SettingsModel */, - filePaths.settingsModel[pathName] + const cannotConnect = createProxyState( + [connectionViewModel2.isConnected], + () => connectionViewModel2.isConnected.value == true && connectionViewModel2.coreViewModel.connectionModel.address == address ); - this.storageModel.write(path, value); - } - static getSystemLanguage() { - switch (navigator.language.substring(0, 2)) { - case "de": - return "de" /* German */; - case "es": - return "es" /* Spanish */; - default: - return "en" /* English */; - } - } - }; - var Languages = /* @__PURE__ */ ((Languages2) => { - Languages2["English"] = "en"; - Languages2["German"] = "de"; - Languages2["Spanish"] = "es"; - return Languages2; - })(Languages || {}); - - // src/ViewModel/Global/settingsViewModel.ts - var SettingsViewModel = class _SettingsViewModel extends Context { - // init - constructor(coreViewModel2) { - super("settings"); - this.coreViewModel = coreViewModel2; - // state - this.username = new State(""); - this.usernameInput = new State(""); - this.isShowingSettingsModal = new State(false); - this.selectedModalPage = new State(void 0); - this.requiresReload = new State(false); - this.firstDayOfWeek = new State("0"); - this.language = new State( - "en" /* English */ - ); - this.theme = new State( - "system" /* System */ - ); - // guards - this.cannotSetName = createProxyState( - [this.usernameInput], - () => this.usernameInput.value == "" || this.usernameInput.value == this.coreViewModel.settingsModel.username - ); - // methods - this.setName = () => { - this.coreViewModel.settingsModel.setName(this.usernameInput.value); - this.username.value = this.coreViewModel.settingsModel.username; - this.usernameInput.callSubscriptions(); - }; - this.setFirstDayofWeek = () => { - this.coreViewModel.settingsModel.setFirstDayOfWeek( - this.firstDayOfWeek.value - ); - }; - this.showSettingsModal = () => { - this.coreViewModel.context = this; - this.isShowingSettingsModal.value = true; - }; - this.showModalPage = (page) => { - this.selectedModalPage.value = page; - }; - // view - this.applyTheme = () => { - let theme = this.theme.value; - if (theme == "system" /* System */) { - theme = _SettingsViewModel.getSystemTheme(); - } - document.body.setAttribute("theme", theme); - }; - // exit - this.close = () => { - this.coreViewModel.closeContext(this.contextId); - }; - this.handleContextClose = () => { - this.isShowingSettingsModal.value = false; - if (this.requiresReload.value == true) { - window.location.reload(); - } - }; - this.username.value = coreViewModel2.settingsModel.username; - this.usernameInput.value = coreViewModel2.settingsModel.username; - this.firstDayOfWeek.value = coreViewModel2.settingsModel.firstDayOfWeek; - this.language.value = coreViewModel2.settingsModel.language; - this.theme.value = coreViewModel2.settingsModel.theme; - this.firstDayOfWeek.subscribe(this.setFirstDayofWeek); - this.language.subscribeSilent((newValue) => { - this.coreViewModel.settingsModel.setLanguage(newValue); - this.requiresReload.value = true; - }); - this.theme.subscribeSilent((newValue) => { - this.coreViewModel.settingsModel.setTheme(newValue); - }); - this.theme.subscribe(() => { - this.applyTheme(); - }); - _SettingsViewModel.generateThemeMedia().addEventListener( - "change", - () => this.applyTheme() - ); - this.registerKeyStroke("backspace" /* CloseOrCancel */, this.close); - } - static generateThemeMedia() { - return window.matchMedia("(prefers-color-scheme: dark)"); - } - static getSystemTheme() { - const media = _SettingsViewModel.generateThemeMedia(); - return media.matches == true ? "dark" /* Dark */ : "light" /* Light */; - } - }; - - // src/View/Modals/splitModal.tsx - function SplitModal(coreViewModel2, leftView, rightView, extendedStyle = false, navigationState) { - function closePage() { - navigationState.value = void 0; - } - const hasPageOpen = new State(false); - navigationState?.subscribe( - (newValue) => hasPageOpen.value = newValue != void 0 - ); - const view = /* @__PURE__ */ createElement( - "div", - { - class: "split-modal", - "toggle:extended": extendedStyle, - "toggle:navigation": navigationState != void 0, - "toggle:page-open": hasPageOpen - }, - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "scroll-area", "children:set": leftView })), - /* @__PURE__ */ createElement("div", { class: "scroll-area slide-up" }, /* @__PURE__ */ createElement( - "div", - { - class: "flex-row width-100 mobile-only", - "toggle:hidden": navigationState == void 0 - }, + return DeletableListItem( + coreViewModel2, + address, /* @__PURE__ */ createElement( "button", { - class: "ghost square", - "aria-label": coreViewModel2.translations.general.backButton, - "on:click": closePage + class: "primary", + "on:click": connnect, + "toggle:disabled": cannotConnect, + "aria-label": coreViewModel2.translations.connectionModal.connectButtonAudioLabel }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_back") - ) - ), /* @__PURE__ */ createElement("div", { "children:set": rightView })) - ); - return view; - } - - // src/View/Components/directoryItemList.tsx - function DirectoryItemList(storageViewModel2, pathString = PATH_COMPONENT_SEPARATOR) { - const StringToDirectoryItemList = (pathString2) => DirectoryItemList(storageViewModel2, pathString2); - const path = StorageModel.stringToPathComponents(pathString); - const fileName = StorageModel.getFileName(path); - const items = new ListState(); - const style = `text-indent: ${path.length}rem`; - function loadItems() { - items.clear(); - const directoryItems = storageViewModel2.coreViewModel.storageModel.list(path); - for (const directoryItem of directoryItems) { - const itemPath = [...path, directoryItem]; - const pathString2 = StorageModel.pathComponentsToString(...itemPath); - items.add(pathString2); - } - } - function select() { - storageViewModel2.selectedPath.value = pathString; - } - storageViewModel2.lastDeletedItemPath.subscribe((lastDeletedItemPath) => { - if (!items.value.has(lastDeletedItemPath)) return; - select(); - setTimeout(() => loadItems(), 50); - }); - const isSelected = createProxyState( - [storageViewModel2.selectedPath], - () => storageViewModel2.selectedPath.value == pathString - ); - isSelected.subscribe(() => { - if (isSelected.value == false) return; - loadItems(); - }); - return /* @__PURE__ */ createElement("div", { class: "flex-column" }, /* @__PURE__ */ createElement( - "button", - { - class: "width-100 flex-1 clip", - "toggle:selected": isSelected, - "on:click": select - }, - /* @__PURE__ */ createElement("span", { class: "ellipsis width-100 flex-1", style }, fileName) - ), /* @__PURE__ */ createElement( + /* @__PURE__ */ createElement("span", { class: "icon" }, "link") + ), + () => { + connectionViewModel2.removePreviousAddress(address); + } + ); + }; + return /* @__PURE__ */ createElement( "div", { - class: "flex-column", - "children:append": [items, StringToDirectoryItemList] - } - )); - } - - // src/View/Modals/storageModal.tsx - function StorageModal(coreViewModel2, storageViewModel2) { - const detailView = createProxyState( - [storageViewModel2.selectedPath], - () => { - if (storageViewModel2.selectedPath.value == PATH_COMPONENT_SEPARATOR) - return /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.storage.noItemSelected), /* @__PURE__ */ createElement("hr", null), DangerousActionButton( - coreViewModel2, - coreViewModel2.translations.storage.removeJunkButton, - "delete_forever", - storageViewModel2.removeJunk - )); - return /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("div", { class: "tile flex-no" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.storage.path), /* @__PURE__ */ createElement( - "span", - { - class: "break-all", - "subscribe:innerText": storageViewModel2.selectedPath - } - ))), /* @__PURE__ */ createElement("div", { class: "tile flex-no" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.storage.content), /* @__PURE__ */ createElement( - "code", - { - "subscribe:innerText": storageViewModel2.selectedFileContent - } - ))), DangerousActionButton( - coreViewModel2, - coreViewModel2.translations.storage.deleteItem, - "delete_forever", - storageViewModel2.deleteSelectedItem - )); - } - ); - return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": storageViewModel2.isShowingStorageModal }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", { class: "padding-0" }, SplitModal( - coreViewModel2, - new State(DirectoryItemList(storageViewModel2)), - detailView, - true - )), /* @__PURE__ */ createElement("button", { "on:click": storageViewModel2.close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); - } - - // src/ViewModel/Global/storageViewModel.ts - var StorageViewModel = class extends Context { - // init - constructor(coreViewModel2) { - super("storage"); - this.coreViewModel = coreViewModel2; - // state - this.isShowingStorageModal = new State(false); - 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 content = this.coreViewModel.storageModel.read(path); - return (content ?? this.coreViewModel.translations.storage.notAFile) || this.coreViewModel.translations.storage.contentEmpty; - }; - this.deleteSelectedItem = () => { - const path = StorageModel.stringToPathComponents( - this.selectedPath.value - ); - this.lastDeletedItemPath.value = this.selectedPath.value; - this.coreViewModel.storageModel.removeRecursively(path); - this.didMakeChanges.value = true; - }; - this.removeJunk = () => { - this.coreViewModel.storageModel.removeJunk(); - this.selectedPath.value = PATH_COMPONENT_SEPARATOR; - }; - // view - this.showStorageModal = () => { - this.coreViewModel.context = this; - this.isShowingStorageModal.value = true; - }; - // exit - this.close = () => { - this.coreViewModel.closeContext(this.contextId); - }; - this.handleContextClose = () => { - if (this.didMakeChanges.value == true) { - window.location.reload(); - return; + class: "modal", + "toggle:open": connectionViewModel2.isShowingConnectionModal + }, + /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.connectionModal.connectionModalHeadline), /* @__PURE__ */ createElement( + "div", + { + class: "flex-column gap", + "children:append": [ + connectionViewModel2.previousAddresses, + previousAddressConverter + ] } - this.isShowingStorageModal.value = false; - }; - this.selectedFileName = createProxyState( - [this.selectedPath], - () => StorageModel.getFileNameFromString(this.selectedPath.value) - ); - this.selectedFileContent = createProxyState( - [this.selectedPath], - () => this.getSelectedItemContent() - ); - this.registerKeyStroke("backspace" /* CloseOrCancel */, this.close); - } - }; + )), /* @__PURE__ */ createElement("button", { "on:click": connectionViewModel2.hideConnectionModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))) + ); + } // src/Upgrader/v1.ts var v1Upgrader = class { @@ -7352,7 +7018,7 @@ const convertedTaskFileContent = { dataVersion: "v2", fileId: objectId, - fileContentId: FileModel2.generateFileContentId( + fileContentId: FileModel.generateFileContentId( potentialVersion.isoDateVersionCreated ), creationDate: potentialVersion.isoDateVersionCreated, @@ -7430,179 +7096,520 @@ return true; } - // src/View/Components/optionButton.tsx - function OptionButton(text, value, selection) { - function select() { - selection.value = value; - } - const isSelected = createProxyState( - [selection], - () => selection.value == value - ); - return /* @__PURE__ */ createElement("button", { class: "standard", "toggle:selected": isSelected, "on:click": select }, text); - } - - // src/View/Components/optionButtonList.tsx - function OptionButtonList(options, selection) { - function OptionToView(option) { - const [text, value] = option; - return OptionButton(text, value, selection); - } - return /* @__PURE__ */ createElement( - "div", - { - class: "flex-column gap", - "children:append": [options, OptionToView] - } - ); - } - - // src/View/Components/navigationButton.tsx - function NavigationButton(coreViewModel2, label, navigationState, page, arrowMobileOnly) { - const isSelected = createProxyState( - [navigationState], - () => navigationState.value == page - ); - function open() { - navigationState.value = page; - } - const iconClass = `icon ${arrowMobileOnly == true ? "mobile-only" : ""}`; - return /* @__PURE__ */ createElement("button", { "toggle:selected": isSelected, "on:click": open }, label, /* @__PURE__ */ createElement("span", { class: iconClass }, "arrow_forward")); - } - - // src/View/Modals/settingsModal.tsx - function SettingsModal(coreViewModel2, settingsViewModel2) { - const detailView = createProxyState( - [settingsViewModel2.selectedModalPage], - () => { - switch (settingsViewModel2.selectedModalPage.value) { - case void 0: { - return PlaceholderView( - coreViewModel2.translations.general.noPageSelected - ); - } - case 0 /* Appearance */: - return SettingsAppearancePane( - coreViewModel2, - settingsViewModel2 - ); - case 1 /* Regional */: - return SettingsRegionalPane( - coreViewModel2, - settingsViewModel2 - ); - case 2 /* Info */: - return SettingsInfoPane(coreViewModel2, settingsViewModel2); + // src/Model/Global/fileTransferModel.ts + var FileTransferModel = class _FileTransferModel { + // init + constructor(storageModel2, connectionModel2) { + this.direction = 0 /* Send */; + // handler managers + this.fileHandlerManager = new HandlerManager(); + this.readyToSendHandlerManager = new HandlerManager(); + // general + this.generateTransferData = () => { + const transferData = { + channel: generateRandomToken(4), + key: generateRandomToken(6) + }; + this.transferData = transferData; + return transferData; + }; + this.prepareToSend = () => { + this.direction = 0 /* Send */; + this.connectionModel.addChannel(this.transferData.channel); + }; + this.prepareToReceive = (transferData) => { + this.direction = 1 /* Receive */; + this.connectionModel.addChannel(transferData.channel); + this.transferData = transferData; + this.connectionModel.sendPlainMessage( + transferData.channel, + _FileTransferModel.READY_MESSAGE + ); + }; + // handlers + this.handleMessage = (data) => { + if (this.transferData == void 0) return; + if (data.messageChannel != this.transferData.channel) return; + if (data.messageBody == _FileTransferModel.READY_MESSAGE && this.direction == 0 /* Send */) { + this.readyToSendHandlerManager.trigger(true); } - } - ); - return /* @__PURE__ */ createElement( - "div", - { - class: "modal", - "toggle:open": settingsViewModel2.isShowingSettingsModal - }, - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", { class: "padding-0" }, SplitModal( - coreViewModel2, - new State( - SettingsLeftPane(coreViewModel2, settingsViewModel2) - ), - detailView, - true, - settingsViewModel2.selectedModalPage - )), /* @__PURE__ */ createElement("button", { "on:click": settingsViewModel2.close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))) - ); - } - function SettingsLeftPane(coreViewModel2, settingsViewModel2) { - return /* @__PURE__ */ createElement("div", { class: "flex-column gap slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.settingsButton), SettingsPaneButton( - coreViewModel2, - settingsViewModel2, - 0 /* Appearance */, - coreViewModel2.translations.settings.pages.appearance - ), SettingsPaneButton( - coreViewModel2, - settingsViewModel2, - 1 /* Regional */, - coreViewModel2.translations.settings.pages.regional - ), SettingsPaneButton( - coreViewModel2, - settingsViewModel2, - 2 /* Info */, - coreViewModel2.translations.settings.pages.info - )); - } - function SettingsPaneButton(coreViewModel2, settingsViewModel2, page, label) { - return NavigationButton( - coreViewModel2, - label, - settingsViewModel2.selectedModalPage, - page, - true - ); - } - function SettingsInfoPane(coreViewModel2, settingsViewModel2) { - return /* @__PURE__ */ createElement("div", { class: "slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.appName), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, InfoTile( - "build", - coreViewModel2.translations.settings.version, - settingsViewModel2.coreViewModel.BUILD - ))); - } - function SettingsRegionalPane(coreViewModel2, settingsViewModel2) { - return /* @__PURE__ */ createElement("div", { class: "slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.settings.pages.regional), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.settings.language), OptionButtonList( - new ListState( - Object.values(Languages).map((x) => [languageNames[x], x]) - ), - settingsViewModel2.language - ), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.settings.firstDayOfWeekLabel), OptionButtonList( - new ListState( - coreViewModel2.translations.regional.weekdays.full.map( - (x, i) => [x, i.toString()] - ) - ), - settingsViewModel2.firstDayOfWeek - )); - } - function SettingsAppearancePane(coreViewModel2, settingsViewModel2) { - return /* @__PURE__ */ createElement("div", { class: "slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.settings.pages.appearance), /* @__PURE__ */ createElement("hr", null), OptionButtonList( - new ListState([ - [ - coreViewModel2.translations.settings.themes.dark, - "dark" /* Dark */ - ], - [ - coreViewModel2.translations.settings.themes.light, - "light" /* Light */ - ], - [ - coreViewModel2.translations.settings.themes.system, - "system" /* System */ - ] - ]), - settingsViewModel2.theme - )); - } + if (this.transferData == void 0) return; + if (data.messageBody == void 0) return; + this.handleFile(data.messageBody); + }; + this.handleFile = async (encryptedFileData) => { + if (this.transferData == void 0) return; + const decrypted = await decryptString( + encryptedFileData, + this.transferData.key + ); + const parsed = parse(decrypted); + const isFileData = checkMatchesObjectStructure( + parsed, + FileDataReference + ); + if (isFileData == false) return; + const fileData = parsed; + this.storageModel.write(fileData.path, fileData.body); + const pathString = StorageModel.pathComponentsToString( + ...fileData.path + ); + this.fileHandlerManager.trigger(pathString); + }; + // sending + this.sendFiles = (directoryPaths, callback) => { + for (const directoryPath of directoryPaths) { + this.storageModel.recurse(directoryPath, (filePath) => { + this.sendFile(filePath); + const pathString = StorageModel.pathComponentsToString( + ...filePath + ); + callback(pathString); + }); + } + }; + this.sendFile = async (filePath) => { + if (this.transferData == void 0) return; + const fileContent = this.storageModel.read(filePath); + if (fileContent == null) return; + const fileData = { + path: filePath, + body: fileContent + }; + const stringifiedFileData = stringify(fileData); + const encryptedFileData = await encryptString( + stringifiedFileData, + this.transferData.key + ); + this.connectionModel.sendPlainMessage( + this.transferData.channel, + encryptedFileData + ); + }; + this.storageModel = storageModel2; + this.connectionModel = connectionModel2; + this.connectionModel.messageHandlerManager.setHandler( + "file-transfer", + this.handleMessage + ); + } + static { + this.READY_MESSAGE = "ready"; + } + }; + var FileDataReference = { + path: [""], + body: "" + }; - // src/ViewModel/Pages/homeViewModel.ts - var HomeViewModel = class extends Context { - constructor(coreViewModel2, settingsViewModel2, fileTransferViewModel2, storageViewModel2, connectionViewModel2) { - super("home"); - this.coreViewModel = coreViewModel2; - this.settingsViewModel = settingsViewModel2; - this.fileTransferViewModel = fileTransferViewModel2; - this.storageViewModel = storageViewModel2; - this.connectionViewModel = connectionViewModel2; - this.coreViewModel.context = this; - this.registerKeyStroke( - "," /* Settings */, - this.settingsViewModel.showSettingsModal + // node_modules/udn-frontend/index.ts + var UDNFrontend = class { + ws; + // HANDLERS + connectionHandler = () => { + }; + disconnectionHandler = () => { + }; + messageHandler = (data) => { + }; + mailboxHandler = (mailboxId) => { + }; + mailboxConnectionHandler = (mailboxId) => { + }; + mailboxDeleteHandler = (mailboxId) => { + }; + // INIT + set onconnect(handler) { + this.connectionHandler = handler; + } + set ondisconnect(handler) { + this.disconnectionHandler = handler; + } + set onmessage(handler) { + this.messageHandler = handler; + } + set onmailboxcreate(handler) { + this.mailboxHandler = handler; + } + set onmailboxconnect(handler) { + this.mailboxConnectionHandler = handler; + } + set onmailboxdelete(handler) { + this.mailboxDeleteHandler = handler; + } + // UTILITY METHODS + send(messageObject) { + if (this.ws == void 0) return false; + if (this.ws.readyState != 1) return false; + const messageString = JSON.stringify(messageObject); + this.ws.send(messageString); + return true; + } + // PUBLIC METHODS + // connection + connect(address) { + try { + this.disconnect(); + this.ws = new WebSocket(address); + this.ws.addEventListener("open", this.connectionHandler); + this.ws.addEventListener("close", this.disconnectionHandler); + this.ws.addEventListener("message", (message) => { + const dataString = message.data.toString(); + const data = JSON.parse(dataString); + if (data.assignedMailboxId) { + return this.mailboxHandler(data.assignedMailboxId); + } else if (data.connectedMailboxId) { + return this.mailboxConnectionHandler(data.connectedMailboxId); + } else if (data.deletedMailbox) { + return this.mailboxDeleteHandler(data.deletedMailbox); + } else { + this.send({ uuid: data.uuid, confirmingMessageReceived: true }); + this.messageHandler(data); + } + }); + } catch (error) { + console.error(error); + } + } + disconnect() { + this.ws?.close(); + } + // message + sendMessage(channel, body) { + const messageObject = { + messageChannel: channel, + messageBody: body + }; + return this.send(messageObject); + } + // subscription + subscribe(channel) { + const messageObject = { + subscribeChannel: channel + }; + return this.send(messageObject); + } + unsubscribe(channel) { + const messageObject = { + unsubscribeChannel: channel + }; + return this.send(messageObject); + } + // mailbox + requestMailbox() { + const messageObject = { + requestingMailboxSetup: true + }; + return this.send(messageObject); + } + connectMailbox(mailboxId) { + const messageObject = { + requestedMailbox: mailboxId + }; + return this.send(messageObject); + } + deleteMailbox(mailboxId) { + const messageObject = { + deletingMailbox: mailboxId + }; + return this.send(messageObject); + } + }; + + // src/Model/Global/connectionModel.ts + var ConnectionModel = class { + // init + constructor(storageModel2) { + // config + this.reconnectInterval = void 0; + this.shouldAttemptReconnect = false; + // handler managers + this.connectionChangeHandlerManager = new HandlerManager(); + this.messageHandlerManager = new HandlerManager(); + this.messageSentHandlerManager = new HandlerManager(); + this.channelsToSubscribe = /* @__PURE__ */ new Set(); + // handlers + this.handleMessage = (data) => { + this.messageHandlerManager.trigger(data); + }; + this.handleConnectionChange = () => { + console.log("connection status:", this.isConnected, this.address); + this.connectionChangeHandlerManager.trigger(); + if (this.address == void 0) return; + this.storeAddress(this.address); + this.sendSubscriptionRequest(); + this.sendMessagesInOutbox(); + }; + // connection + this.connect = (address) => { + console.log("connecting...", address); + this.shouldAttemptReconnect = true; + this.udn.connect(address); + }; + this.disconnect = () => { + this.shouldAttemptReconnect = false; + this.udn.disconnect(); + const reconnectAddressPath = StorageModel.getPath( + "connection" /* ConnectionModel */, + filePaths.connectionModel.reconnectAddress + ); + this.storageModel.remove(reconnectAddressPath); + }; + 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) => { + const mailboxDirPath = StorageModel.getPath( + "connection" /* ConnectionModel */, + filePaths.connectionModel.mailboxes + ); + const mailboxFilePath = [...mailboxDirPath, address]; + return mailboxFilePath; + }; + this.requestNewMailbox = () => { + console.log("requesting new mailbox"); + this.udn.requestMailbox(); + }; + this.connectMailbox = () => { + if (this.address == void 0) 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); + }; + this.storeMailbox = (mailboxId) => { + if (this.address == void 0) return; + this.storageModel.write(this.getMailboxPath(this.address), mailboxId); + }; + // subscription + this.addChannel = (channel) => { + this.channelsToSubscribe.add(channel); + this.sendSubscriptionRequest(); + }; + this.sendSubscriptionRequest = () => { + if (this.isConnected == false) return; + for (const channel of this.channelsToSubscribe) { + this.udn.subscribe(channel); + } + this.connectMailbox(); + }; + // outbox + this.getOutboxPath = () => { + return StorageModel.getPath( + "connection" /* ConnectionModel */, + filePaths.connectionModel.outbox + ); + }; + this.getOutboxMessags = () => { + const outboxPath = this.getOutboxPath(); + const messageIds = this.storageModel.list(outboxPath); + let chatMessages = []; + for (const messageId of messageIds) { + const chatMessage = this.storageModel.readStringifiable( + [...outboxPath, messageId], + ChatMessageReference + ); + if (chatMessage == null) continue; + chatMessages.push(chatMessage); + } + return chatMessages; + }; + this.addToOutbox = (chatMessage) => { + const messagePath = [...this.getOutboxPath(), chatMessage.id]; + this.storageModel.writeStringifiable(messagePath, chatMessage); + }; + this.removeFromOutbox = (chatMessage) => { + const messagePath = [...this.getOutboxPath(), chatMessage.id]; + this.storageModel.remove(messagePath); + }; + this.sendMessagesInOutbox = () => { + const messages = this.getOutboxMessags(); + for (const message of messages) { + const isSent = this.tryToSendMessage(message); + if (isSent == false) return; + this.removeFromOutbox(message); + } + }; + // messaging + this.sendMessageOrStore = (chatMessage) => { + const isSent = this.tryToSendMessage(chatMessage); + if (isSent == true) return; + this.addToOutbox(chatMessage); + }; + this.tryToSendMessage = (chatMessage) => { + const stringifiedBody = stringify(chatMessage); + const isSent = this.sendPlainMessage( + chatMessage.channel, + stringifiedBody + ); + if (isSent) this.messageSentHandlerManager.trigger(chatMessage); + return isSent; + }; + this.sendPlainMessage = (channel, body) => { + return this.udn.sendMessage(channel, body); + }; + // storage + this.getPreviousAddressPath = () => { + return StorageModel.getPath( + "connection" /* ConnectionModel */, + filePaths.connectionModel.previousAddresses + ); + }; + this.getAddressPath = (address) => { + const dirPath = this.getPreviousAddressPath(); + return [...dirPath, address]; + }; + this.getReconnectAddressPath = () => { + return StorageModel.getPath( + "connection" /* ConnectionModel */, + filePaths.connectionModel.reconnectAddress + ); + }; + this.storeAddress = (address) => { + const addressPath = this.getAddressPath(address); + this.storageModel.write(addressPath, ""); + const reconnectAddressPath = this.getReconnectAddressPath(); + this.storageModel.write(reconnectAddressPath, address); + }; + this.removeAddress = (address) => { + const addressPath = this.getAddressPath(address); + this.storageModel.remove(addressPath); + }; + this.udn = new UDNFrontend(); + this.storageModel = storageModel2; + this.udn.onmessage = (data) => { + this.handleMessage(data); + }; + this.udn.onconnect = () => { + this.handleConnectionChange(); + }; + this.udn.ondisconnect = () => { + this.handleConnectionChange(); + }; + this.udn.onmailboxcreate = (mailboxId) => { + console.log("created mailbox", mailboxId); + this.storeMailbox(mailboxId); + this.connectMailbox(); + }; + this.udn.onmailboxdelete = (mailboxId) => { + console.log(`mailbox ${mailboxId} deleted`); + this.requestNewMailbox(); + }; + this.udn.onmailboxconnect = (mailboxId) => { + console.log(`using mailbox ${mailboxId}`); + }; + setInterval(() => { + if (this.isConnected == true) return; + if (this.shouldAttemptReconnect == false) return; + this.reconnect(); + }, 5e3); + this.reconnect(); + } + // data + get isConnected() { + return this.udn.ws != void 0 && this.udn.ws.readyState == 1; + } + get address() { + return this.udn.ws?.url; + } + get addresses() { + const dirPath = this.getPreviousAddressPath(); + return this.storageModel.list(dirPath); + } + }; + + // src/Model/Chat/chatListModel.ts + var ChatListModel = class { + // init + constructor(storageModel2, settingsModel2, connectionModel2) { + // data + this.chatModels = /* @__PURE__ */ new Set(); + // chat handling + this.addChatModel = (chatModel) => { + this.chatModels.add(chatModel); + }; + this.createChat = (primaryChannel) => { + const id = v4_default(); + const chatModel = new ChatModel( + this.storageModel, + this.connectionModel, + this.settingsModel, + this, + id + ); + chatModel.setPrimaryChannel(primaryChannel); + this.addChatModel(chatModel); + return chatModel; + }; + this.untrackChat = (chat) => { + this.chatModels.delete(chat); + }; + // message handlers + this.messageHandler = (data) => { + const channel = data.messageChannel; + const body = data.messageBody; + if (channel == void 0) return; + if (body == void 0) return; + this.routeMessageToCorrectChatModel( + channel, + (chatModel) => chatModel.handleMessage(body) + ); + }; + this.messageSentHandler = (chatMessage) => { + const channel = chatMessage.channel; + this.routeMessageToCorrectChatModel( + channel, + (chatModel) => chatModel.handleMessageSent(chatMessage) + ); + }; + // util + this.routeMessageToCorrectChatModel = (channel, fn) => { + const allChannels = channel.split("/"); + for (const chatModel of this.chatModels) { + for (const channel2 of allChannels) { + if (channel2 != chatModel.info.primaryChannel) continue; + fn(chatModel); + break; + } + } + }; + // load + this.loadChats = () => { + const chatDir = StorageModel.getPath( + "chat" /* 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); + } + }; + this.storageModel = storageModel2; + this.settingsModel = settingsModel2; + this.connectionModel = connectionModel2; + this.loadChats(); + connectionModel2.messageHandlerManager.setHandler( + "chat-list", + this.messageHandler ); - this.registerKeyStroke( - "t", - this.fileTransferViewModel.showDirectionSelectionModal + connectionModel2.messageSentHandlerManager.setHandler( + "chat-list", + this.messageSentHandler ); - this.registerKeyStroke("e", this.storageViewModel.showStorageModal); - this.registerKeyStroke("x", this.connectionViewModel.disconnect); - this.registerKeyStroke("c", this.connectionViewModel.connect); } }; diff --git a/package-lock.json b/package-lock.json index 910c5d16..1035eb3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,10 +6,14 @@ "": { "dependencies": { "@types/uuid": "^10.0.0", - "bloatless-react": "^1.3.11", - "esbuild": "^0.23.0", + "bloatless-react": "1.3.14", + "esbuild": "^0.23.1", "udn-frontend": "^1.0.9", "uuid": "^10.0.0" + }, + "devDependencies": { + "prettier-plugin-organize-imports": "^4.3.0", + "prettier-plugin-sort-imports": "^1.8.11" } }, "node_modules/@esbuild/aix-ppc64": { @@ -403,9 +407,9 @@ "license": "MIT" }, "node_modules/bloatless-react": { - "version": "1.3.12", - "resolved": "https://registry.npmjs.org/bloatless-react/-/bloatless-react-1.3.12.tgz", - "integrity": "sha512-JEyAA8JKRVuv6swl7TUzOX3vJ9QdV25cQvTqjLyaQlNKfDVPAtLVR8HMfGdpiOC2mr866FghT9OotWKfWd665w==", + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/bloatless-react/-/bloatless-react-1.3.14.tgz", + "integrity": "sha512-Y4v8RnZeggUozvqHsG6QkZI6AvwxtumaJ4NLPQRbCB7/MuuxA2cc6U+Cw4SiXPY1y9TAarI75xOWh8CNK6wijA==", "license": "SEE LICENSE IN LICENSE", "dependencies": { "uuid": "^13.0.0" @@ -463,6 +467,67 @@ "@esbuild/win32-x64": "0.23.1" } }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-organize-imports": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.3.0.tgz", + "integrity": "sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": ">=2.0", + "typescript": ">=2.9", + "vue-tsc": "^2.1.0 || 3" + }, + "peerDependenciesMeta": { + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/prettier-plugin-sort-imports": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-1.8.11.tgz", + "integrity": "sha512-ApmZkEmNh2Fi6TAcYNgUR15rsP1+omX43+8neY+MXfNZ7JQwrqSdjzKhLUYTtaLo52aaC9gCs+lJaYSU8oSJJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier": "^3.1.1" + }, + "peerDependencies": { + "typescript": ">4.0.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/udn-frontend": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/udn-frontend/-/udn-frontend-1.0.9.tgz", diff --git a/package.json b/package.json index 455f2629..48a87a17 100644 --- a/package.json +++ b/package.json @@ -8,5 +8,8 @@ }, "scripts": { "build": "esbuild src/index.tsx --bundle --outdir=dist" + }, + "devDependencies": { + "prettier-plugin-sort-imports": "^1.8.11" } } diff --git a/src/Model/Chat/chatListModel.ts b/src/Model/Chat/chatListModel.ts index 44c415bc..2400b0e7 100644 --- a/src/Model/Chat/chatListModel.ts +++ b/src/Model/Chat/chatListModel.ts @@ -1,64 +1,30 @@ +// cleanup: Phase A + +import { v4 } from "uuid"; +import { Message } from "udn-frontend"; import ChatModel, { ChatMessage } from "./chatModel"; import StorageModel, { StorageModelSubPaths, filePaths, } from "../Global/storageModel"; - -import ConnectionModel from "../Global/connectionModel"; -import { Message } from "udn-frontend"; import SettingsModel from "../Global/settingsModel"; -import { v4 } from "uuid"; +import ConnectionModel from "../Global/connectionModel"; export default class ChatListModel { + // models readonly storageModel: StorageModel; readonly settingsModel: SettingsModel; readonly connectionModel: ConnectionModel; // data - chatModels = new Set(); + readonly 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; - - this.routeMessageToCorrectChatModel(channel, (chatModel: ChatModel) => - chatModel.handleMessage(body), - ); - }; - - messageSentHandler = (chatMessage: ChatMessage): void => { - const channel: string = chatMessage.channel; - - this.routeMessageToCorrectChatModel(channel, (chatModel: ChatModel) => - chatModel.handleMessageSent(chatMessage), - ); - }; - - // methods - routeMessageToCorrectChatModel = ( - channel: string, - fn: (chatModel: ChatModel) => void, - ): void => { - const allChannels: string[] = channel.split("/"); - - for (const chatModel of this.chatModels) { - for (const channel of allChannels) { - if (channel != chatModel.info.primaryChannel) continue; - fn(chatModel); - break; - } - } - }; - - // storage - addChatModel = (chatModel: ChatModel) => { + // chat handling + readonly addChatModel = (chatModel: ChatModel) => { this.chatModels.add(chatModel); }; - createChat = (primaryChannel: string): ChatModel => { + readonly createChat = (primaryChannel: string): ChatModel => { const id: string = v4(); const chatModel = new ChatModel( @@ -74,12 +40,48 @@ export default class ChatListModel { return chatModel; }; - untrackChat = (chat: ChatModel): void => { + readonly untrackChat = (chat: ChatModel): void => { this.chatModels.delete(chat); }; + // message handlers + readonly 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), + ); + }; + + readonly messageSentHandler = (chatMessage: ChatMessage): void => { + const channel: string = chatMessage.channel; + + this.routeMessageToCorrectChatModel(channel, (chatModel: ChatModel) => + chatModel.handleMessageSent(chatMessage), + ); + }; + + // util + readonly routeMessageToCorrectChatModel = ( + channel: string, + fn: (chatModel: ChatModel) => void, + ): void => { + const allChannels: string[] = channel.split("/"); + + for (const chatModel of this.chatModels) { + for (const channel of allChannels) { + if (channel != chatModel.info.primaryChannel) continue; + fn(chatModel); + break; + } + } + }; + // load - loadChats = (): void => { + readonly loadChats = (): void => { const chatDir = StorageModel.getPath( StorageModelSubPaths.Chat, filePaths.chat.base, diff --git a/src/Model/Chat/chatModel.ts b/src/Model/Chat/chatModel.ts index 8e461db6..e06a0787 100644 --- a/src/Model/Chat/chatModel.ts +++ b/src/Model/Chat/chatModel.ts @@ -1,11 +1,7 @@ -// this file is responsible for managing chats. +// cleanup: Phase A -import { - checkMatchesObjectStructure, - DATA_VERSION, - ValidObject, -} from "../Utility/typeSafety"; -import FileModel, { FileContent } from "../Files/fileModel"; +import { v4 } from "uuid"; +import ChatListModel from "./chatListModel"; import { HandlerManager, createTimestamp, @@ -13,24 +9,27 @@ import { parseValidObject, stringify, } from "../Utility/utility"; +import { + DATA_VERSION, + ValidObject, + checkMatchesObjectStructure, +} from "../Utility/typeSafety"; +import { decryptString, encryptString } from "../Utility/crypto"; import StorageModel, { StorageModelSubPaths, filePaths, } from "../Global/storageModel"; -import { decryptString, encryptString } from "../Utility/crypto"; - -import ChatListModel from "./chatListModel"; -import { Colors } from "../../colors"; -import ConnectionModel from "../Global/connectionModel"; import SettingsModel from "../Global/settingsModel"; -import { v4 } from "uuid"; +import ConnectionModel from "../Global/connectionModel"; +import FileModel, { FileContent } from "../Files/fileModel"; +import { Colors } from "../../colors"; export default class ChatModel { + // models readonly connectionModel: ConnectionModel; readonly storageModel: StorageModel; readonly settingsModel: SettingsModel; readonly chatListModel: ChatListModel; - readonly fileModel: FileModel; // data @@ -38,61 +37,62 @@ export default class ChatModel { info: ChatInfo; color: Colors; + get secondaryChannels(): string[] { + return this.info.secondaryChannels.sort(localeCompare); + } + + // handler managers chatMessageHandlerManager: HandlerManager = new HandlerManager(); readonly reactionHandlerManager: HandlerManager = new HandlerManager(); - get secondaryChannels(): string[] { - return this.info.secondaryChannels.sort(localeCompare); - } - // paths - getBasePath = (): string[] => { + readonly getBasePath = (): string[] => { return StorageModel.getPath( StorageModelSubPaths.Chat, filePaths.chat.chatBase(this.id), ); }; - getInfoPath = (): string[] => { + readonly getInfoPath = (): string[] => { return StorageModel.getPath( StorageModelSubPaths.Chat, filePaths.chat.info(this.id), ); }; - getColorPath = (): string[] => { + readonly getColorPath = (): string[] => { return StorageModel.getPath( StorageModelSubPaths.Chat, filePaths.chat.color(this.id), ); }; - getMessageDirPath = (): string[] => { + readonly getMessageDirPath = (): string[] => { return StorageModel.getPath( StorageModelSubPaths.Chat, filePaths.chat.messages(this.id), ); }; - getMessagePath = (id: string): string[] => { + readonly getMessagePath = (id: string): string[] => { return [...this.getMessageDirPath(), id]; }; - getReactionDirPath = (): string[] => { + readonly getReactionDirPath = (): string[] => { return StorageModel.getPath( StorageModelSubPaths.Chat, filePaths.chat.reactions(this.id), ); }; - getReactionPath = (id: string): string[] => { + readonly getReactionPath = (id: string): string[] => { return [...this.getReactionDirPath(), id]; }; // handlers - handleMessage = (body: string): void => { + readonly handleMessage = (body: string): void => { const chatMessage: ChatMessage | null = parseValidObject( body, ChatMessageReference, @@ -107,7 +107,7 @@ export default class ChatModel { this.setReadStatus(true); }; - handleReaction = (reaction: ChatMessageReaction | any): void => { + readonly handleReaction = (reaction: ChatMessageReaction | any): void => { if ( !checkMatchesObjectStructure(reaction, ChatMessageReactionReference) ) @@ -122,35 +122,35 @@ export default class ChatModel { this.reactionHandlerManager.trigger(reaction); }; - handleMessageSent = (chatMessage: ChatMessage): void => { + readonly handleMessageSent = (chatMessage: ChatMessage): void => { chatMessage.status = ChatMessageStatuses.Sent; this.addMessage(chatMessage); }; // settings - setPrimaryChannel = (primaryChannel: string): void => { + readonly setPrimaryChannel = (primaryChannel: string): void => { this.info.primaryChannel = primaryChannel; this.storeInfo(); this.subscribe(); }; - setSecondaryChannels = (secondaryChannels: string[]): void => { + readonly setSecondaryChannels = (secondaryChannels: string[]): void => { this.info.secondaryChannels = secondaryChannels; this.storeInfo(); }; - setEncryptionKey = (key: string): void => { + readonly setEncryptionKey = (key: string): void => { this.info.encryptionKey = key; this.storeInfo(); }; - setColor = (color: Colors): void => { + readonly setColor = (color: Colors): void => { this.color = color; this.storeColor(); }; // messaging - addMessage = async (chatMessage: ChatMessage): Promise => { + readonly addMessage = async (chatMessage: ChatMessage): Promise => { await this.decryptMessage(chatMessage); // message @@ -166,7 +166,7 @@ export default class ChatModel { ); }; - getNameAndChannel = (): [string, string] | false => { + readonly getNameAndChannel = (): [string, string] | false => { const senderName: string = this.settingsModel.username; if (senderName == "") return false; @@ -180,7 +180,7 @@ export default class ChatModel { return [senderName, combinedChannel]; }; - sendMessage = async ( + readonly sendMessage = async ( body: string, inlineReplyId?: string, fileContent?: FileContent, @@ -203,7 +203,9 @@ export default class ChatModel { return true; }; - decryptMessage = async (chatMessage: ChatMessage): Promise => { + readonly decryptMessage = async ( + chatMessage: ChatMessage, + ): Promise => { const decryptedBody: string = await decryptString( chatMessage.body, this.info.encryptionKey, @@ -216,7 +218,7 @@ export default class ChatModel { chatMessage.stringifiedFile = decryptedFile; }; - sendReaction = async ( + readonly sendReaction = async ( messageId: string, content: ReactionSymbols, isDeleting?: boolean, @@ -235,25 +237,25 @@ export default class ChatModel { this.handleReaction(reaction); }; - subscribe = (): void => { + readonly subscribe = (): void => { this.connectionModel.addChannel(this.info.primaryChannel); }; - setReadStatus = (hasUnreadMessages: boolean): void => { + readonly setReadStatus = (hasUnreadMessages: boolean): void => { this.info.hasUnreadMessages = hasUnreadMessages; this.storeInfo(); }; // storage - storeInfo = (): void => { + readonly storeInfo = (): void => { this.storageModel.writeStringifiable(this.getInfoPath(), this.info); }; - storeColor = (): void => { + readonly storeColor = (): void => { this.storageModel.write(this.getColorPath(), this.color); }; - delete = () => { + readonly delete = () => { // untrack this.chatListModel.untrackChat(this); @@ -263,7 +265,7 @@ export default class ChatModel { }; // load - loadInfo = (): void => { + readonly loadInfo = (): void => { const info: ChatInfo | null = this.storageModel.readStringifiable( this.getInfoPath(), ChatInfoReference, @@ -275,7 +277,7 @@ export default class ChatModel { } }; - loadColor = (): void => { + readonly loadColor = (): void => { const path: string[] = this.getColorPath(); const color: string | null = this.storageModel.read(path); if (!color) { diff --git a/src/Model/Files/boardsAndTasksModel.ts b/src/Model/Files/boardsAndTasksModel.ts index a5983cb9..19869f2d 100644 --- a/src/Model/Files/boardsAndTasksModel.ts +++ b/src/Model/Files/boardsAndTasksModel.ts @@ -1,18 +1,17 @@ -// this file is responsible for managing boards within chats and tasks within boards. +// cleanup: Phase A +import { v4 } from "uuid"; +import FileModel, { FileContent, FileModelSubPath } from "./fileModel"; +import CalendarModel from "./calendarModel"; +import { HandlerManager } from "../Utility/utility"; import { DATA_VERSION, checkMatchesObjectStructure, } from "../Utility/typeSafety"; -import FileModel, { FileContent, FileModelSubPath } from "./fileModel"; - -import CalendarModel from "./calendarModel"; +import StorageModel from "../Global/storageModel"; +import SettingsModel from "../Global/settingsModel"; import ChatModel from "../Chat/chatModel"; import { Colors } from "../../colors"; -import { HandlerManager } from "../Utility/utility"; -import SettingsModel from "../Global/settingsModel"; -import StorageModel from "../Global/storageModel"; -import { v4 } from "uuid"; export default class BoardsAndTasksModel { readonly storageModel: StorageModel; @@ -27,43 +26,43 @@ export default class BoardsAndTasksModel { readonly taskHandlerManager = new HandlerManager(); // paths - getBasePath = (): string[] => { + readonly getBasePath = (): string[] => { return this.fileModel.getModelContainerPath(FileModelSubPath.ModelTask); }; - getViewPath = (): string[] => { + readonly getViewPath = (): string[] => { return [...this.getBasePath(), FileModelSubPath.ModelView]; }; - getBoardFilePath = (boardId: string): string[] => { + readonly getBoardFilePath = (boardId: string): string[] => { return [...this.fileModel.getFilePath(boardId)]; }; - getTaskFilePath = (taskId: string): string[] => { + readonly getTaskFilePath = (taskId: string): string[] => { return [...this.fileModel.getFilePath(taskId)]; }; - getBoardContainerPath = (): string[] => { + readonly getBoardContainerPath = (): string[] => { return [...this.getBasePath(), TaskModelSubPaths.Boards]; }; - getBoardDirectoryPath = (boardId: string): string[] => { + readonly getBoardDirectoryPath = (boardId: string): string[] => { return [...this.getBoardContainerPath(), boardId]; }; - getTaskContainerPath = (boardId: string): string[] => { + readonly getTaskContainerPath = (boardId: string): string[] => { return [ ...this.getBoardDirectoryPath(boardId), TaskModelSubPaths.BoardTasks, ]; }; - getTaskReferencePath = (boardId: string, fileId: string): string[] => { + readonly getTaskReferencePath = (boardId: string, fileId: string): string[] => { return [...this.getTaskContainerPath(boardId), fileId]; }; // handlers - handleFileContent = (fileContent: FileContent): void => { + readonly handleFileContent = (fileContent: FileContent): void => { if ( checkMatchesObjectStructure( fileContent, @@ -81,16 +80,16 @@ export default class BoardsAndTasksModel { } }; - handleBoard = (boardInfoFileContent: BoardInfoFileContent) => { + readonly handleBoard = (boardInfoFileContent: BoardInfoFileContent) => { this.updateBoard(boardInfoFileContent); }; - handleTask = (taskFileContent: TaskFileContent) => { + readonly handleTask = (taskFileContent: TaskFileContent) => { this.updateTask(taskFileContent); }; // boards - createBoard = (name: string): BoardInfoFileContent => { + readonly createBoard = (name: string): BoardInfoFileContent => { const boardInfoFileContent: BoardInfoFileContent = BoardsAndTasksModel.createBoardInfoFileContent( v4(), @@ -100,17 +99,17 @@ export default class BoardsAndTasksModel { return boardInfoFileContent; }; - updateBoard = (boardInfoFileContent: BoardInfoFileContent): void => { + readonly updateBoard = (boardInfoFileContent: BoardInfoFileContent): void => { this.storeBoard(boardInfoFileContent); this.boardHandlerManager.trigger(boardInfoFileContent); }; - updateBoardAndSend = (boardInfoFileContent: BoardInfoFileContent): void => { + readonly updateBoardAndSend = (boardInfoFileContent: BoardInfoFileContent): void => { this.updateBoard(boardInfoFileContent); this.chatModel.sendMessage("", undefined, boardInfoFileContent); }; - storeBoard = (boardInfoFileContent: BoardInfoFileContent): void => { + readonly storeBoard = (boardInfoFileContent: BoardInfoFileContent): void => { // store info this.fileModel.storeFileContent(boardInfoFileContent); @@ -121,7 +120,7 @@ export default class BoardsAndTasksModel { this.storageModel.write(boardDirectoryPath, ""); }; - deleteBoard = (boardId: string): void => { + readonly deleteBoard = (boardId: string): void => { const boardFilePath: string[] = this.getBoardFilePath(boardId); const boardDirectoryPath: string[] = this.getBoardDirectoryPath(boardId); @@ -130,13 +129,13 @@ export default class BoardsAndTasksModel { this.storageModel.removeRecursively(boardDirectoryPath); }; - listBoardIds = (): string[] => { + readonly listBoardIds = (): string[] => { const boardContainerPath: string[] = this.getBoardContainerPath(); const boardIds: string[] = this.storageModel.list(boardContainerPath); return boardIds; }; - getBoardInfo = (fileId: string): BoardInfoFileContent | null => { + readonly getBoardInfo = (fileId: string): BoardInfoFileContent | null => { const boardInfoFileContentOrNull: BoardInfoFileContent | null = this.fileModel.getLatestFileContent( fileId, @@ -145,7 +144,7 @@ export default class BoardsAndTasksModel { return boardInfoFileContentOrNull; }; - getBoardName = (boardId: string): string => { + readonly getBoardName = (boardId: string): string => { const boardInfo: BoardInfoFileContent | null = this.getBoardInfo(boardId); if (boardInfo == null) return ""; @@ -153,23 +152,23 @@ export default class BoardsAndTasksModel { }; //tasks - createTask = (boardId: string): TaskFileContent => { + readonly createTask = (boardId: string): TaskFileContent => { const taskFileContent: TaskFileContent = BoardsAndTasksModel.createTaskFileContent(v4(), "", boardId); return taskFileContent; }; - updateTask = (taskFileContent: TaskFileContent): void => { + readonly updateTask = (taskFileContent: TaskFileContent): void => { this.storeTask(taskFileContent); this.taskHandlerManager.trigger(taskFileContent); }; - updateTaskAndSend = (taskFileContent: TaskFileContent): void => { + readonly updateTaskAndSend = (taskFileContent: TaskFileContent): void => { this.updateTask(taskFileContent); this.chatModel.sendMessage("", undefined, taskFileContent); }; - storeTask = (taskFileContent: TaskFileContent): void => { + readonly storeTask = (taskFileContent: TaskFileContent): void => { // store info this.fileModel.storeFileContent(taskFileContent); @@ -184,18 +183,18 @@ export default class BoardsAndTasksModel { this.calendarModel.storeTaskReference(taskFileContent); }; - listTaskIds = (boardId: string): string[] => { + readonly listTaskIds = (boardId: string): string[] => { const taskContainerPath: string[] = this.getTaskContainerPath(boardId); const fileIds: string[] = this.storageModel.list(taskContainerPath); return fileIds; }; - listTaskVersionIds = (taskId: string): string[] => { + readonly listTaskVersionIds = (taskId: string): string[] => { const versionIds: string[] = this.fileModel.listFileContentIds(taskId); return versionIds; }; - getLatestTaskFileContent = (taskId: string): TaskFileContent | null => { + readonly getLatestTaskFileContent = (taskId: string): TaskFileContent | null => { const taskFileContentOrNull: TaskFileContent | null = this.fileModel.getLatestFileContent( taskId, @@ -204,7 +203,7 @@ export default class BoardsAndTasksModel { return taskFileContentOrNull; }; - getSpecificTaskFileContent = ( + readonly getSpecificTaskFileContent = ( taskId: string, versionId: string, ): TaskFileContent | null => { @@ -217,14 +216,14 @@ export default class BoardsAndTasksModel { return taskFileContentOrNull; }; - deleteTask = (boardId: string, taskId: string): void => { + readonly 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 => { + readonly deleteTaskReference = (boardId: string, taskId: string): void => { const taskReferencePath: string[] = this.getTaskReferencePath( boardId, taskId, diff --git a/src/Model/Files/calendarModel.ts b/src/Model/Files/calendarModel.ts index 88e16b2d..f0319d71 100644 --- a/src/Model/Files/calendarModel.ts +++ b/src/Model/Files/calendarModel.ts @@ -1,10 +1,9 @@ -// it is responsible for managing calendar-realted reference files for tasks. +// cleanup: Phase A import FileModel, { FileModelSubPath } from "./fileModel"; - -import SettingsModel from "../Global/settingsModel"; -import StorageModel from "../Global/storageModel"; import { TaskFileContent } from "./boardsAndTasksModel"; +import StorageModel from "../Global/storageModel"; +import SettingsModel from "../Global/settingsModel"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; export default class CalendarModel { @@ -13,26 +12,26 @@ export default class CalendarModel { readonly settingsModel: SettingsModel; // paths - getBasePath = (): string[] => { + get basePath (): string[] { return this.fileModel.getModelContainerPath( FileModelSubPath.ModelCalendar, ); }; - getViewPath = (): string[] => { - return [...this.getBasePath(), FileModelSubPath.ModelView]; + readonly getViewPath = (): string[] => { + return [...this.basePath, FileModelSubPath.ModelView]; }; - getMonthContainerPath = (): string[] => { - return [...this.getBasePath(), CalendarModelSubPaths.Months]; + readonly getMonthContainerPath = (): string[] => { + return [...this.basePath, CalendarModelSubPaths.Months]; }; - getMonthPath = (monthString: string): string[] => { + readonly getMonthPath = (monthString: string): string[] => { return [...this.getMonthContainerPath(), monthString]; }; - // main - storeTaskReference = (taskFileContent: TaskFileContent): void => { + // task references + readonly storeTaskReference = (taskFileContent: TaskFileContent): void => { if (taskFileContent.date == undefined) return; const monthString: string = CalendarModel.isoToMonthString( @@ -45,19 +44,21 @@ export default class CalendarModel { this.storageModel.write(referencePath, ""); }; - deleteTaskReference = (monthString: string, taskId: string): void => { + readonly 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[] => { + // data + readonly listTaskIds = (monthString: string): string[] => { const monthPath: string[] = this.getMonthPath(monthString); return this.storageModel.list(monthPath); }; - generateMonthGrid = ( + // util + readonly generateMonthGrid = ( coreViewModel: CoreViewModel, year: number, month: number, diff --git a/src/Model/Files/fileModel.ts b/src/Model/Files/fileModel.ts index 0a8cfcae..ddeea041 100644 --- a/src/Model/Files/fileModel.ts +++ b/src/Model/Files/fileModel.ts @@ -1,21 +1,20 @@ -// this file is responsible for managing files within chats. +// cleanup: Phase A -import { DATA_VERSION, ValidObject } from "../Utility/typeSafety"; -import StorageModel, { - StorageModelSubPaths, - filePaths, -} from "../Global/storageModel"; +import { v4 } from "uuid"; +import BoardsAndTasksModel from "./boardsAndTasksModel"; import { StringEntryObject, createTimestamp, parseValidObject, stringify, } from "../Utility/utility"; - -import BoardsAndTasksModel from "./boardsAndTasksModel"; -import ChatModel from "../Chat/chatModel"; +import { DATA_VERSION, ValidObject } from "../Utility/typeSafety"; +import StorageModel, { + StorageModelSubPaths, + filePaths, +} from "../Global/storageModel"; import SettingsModel from "../Global/settingsModel"; -import { v4 } from "uuid"; +import ChatModel from "../Chat/chatModel"; export default class FileModel { readonly storageModel: StorageModel; @@ -25,32 +24,32 @@ export default class FileModel { readonly boardsAndTasksModel: BoardsAndTasksModel; // paths - getBasePath = (): string[] => { + get basePath(): string[] { return StorageModel.getPath( StorageModelSubPaths.Chat, filePaths.chat.files(this.chatModel.id), ); }; - getFileContainerPath = (): string[] => { - return [...this.getBasePath(), FileModelSubPath.Data]; + readonly getFileContainerPath = (): string[] => { + return [...this.basePath, FileModelSubPath.Data]; }; - getModelContainerPath = (modelName: FileModelSubPath): string[] => { - return [...this.getBasePath(), FileModelSubPath.Model, modelName]; + readonly getModelContainerPath = (modelName: FileModelSubPath): string[] => { + return [...this.basePath, FileModelSubPath.Model, modelName]; }; - getFilePath = (fileId: string): string[] => { + readonly getFilePath = (fileId: string): string[] => { return [...this.getFileContainerPath(), fileId]; }; - getFileContentPath = (fileId: string, fileContentId: string): string[] => { + readonly getFileContentPath = (fileId: string, fileContentId: string): string[] => { const filePath: string[] = this.getFilePath(fileId); return [...filePath, fileContentId]; }; // handlers - handleStringifiedFileContent = (stringifiedFileContent: string): void => { + readonly handleStringifiedFileContent = (stringifiedFileContent: string): void => { const fileContent: FileContent | null = parseValidObject( stringifiedFileContent, FileContentReference, @@ -60,7 +59,7 @@ export default class FileModel { this.handleFileContent(fileContent); }; - handleFileContent = (fileContent: FileContent): void => { + readonly handleFileContent = (fileContent: FileContent): void => { const didStore: boolean = this.storeFileContent(fileContent); if (didStore == false) return; @@ -69,13 +68,13 @@ export default class FileModel { }; // methods - addFileContentAndSend = (fileContent: FileContent): void => { + readonly addFileContentAndSend = (fileContent: FileContent): void => { this.handleFileContent(fileContent); this.chatModel.sendMessage("", undefined, fileContent); }; // storage - storeFileContent = (fileContent: FileContent): boolean => { + readonly storeFileContent = (fileContent: FileContent): boolean => { const fileContentPath: string[] = this.getFileContentPath( fileContent.fileId, fileContent.fileContentId, @@ -91,22 +90,22 @@ export default class FileModel { return true; }; - listFileIds = (): string[] => { - return this.storageModel.list(this.getBasePath()); + readonly listFileIds = (): string[] => { + return this.storageModel.list(this.basePath); }; - listFileContentIds = (fileId: string): string[] => { + readonly listFileContentIds = (fileId: string): string[] => { const filePath: string[] = this.getFilePath(fileId); return this.storageModel.list(filePath); }; - selectLatestFileContentId = ( + readonly selectLatestFileContentId = ( fileContentIds: string[], ): string | undefined => { return fileContentIds[fileContentIds.length - 1]; }; - getFileContent = >( + readonly getFileContent = >( fileId: string, fileContentName: string, reference: T, @@ -122,7 +121,7 @@ export default class FileModel { return fileContentOrNull; }; - getLatestFileContent = >( + readonly getLatestFileContent = >( fileId: string, reference: T, ): T | null => { diff --git a/src/Model/Global/connectionModel.ts b/src/Model/Global/connectionModel.ts index e31780e1..74ac3b6e 100644 --- a/src/Model/Global/connectionModel.ts +++ b/src/Model/Global/connectionModel.ts @@ -1,13 +1,16 @@ -// this file is responsible for managing UDN connections. +// cleanup: Phase A -import { ChatMessage, ChatMessageReference } from "../Chat/chatModel"; -import { HandlerManager, stringify } from "../Utility/utility"; -import StorageModel, { StorageModelSubPaths, filePaths } from "./storageModel"; import UDNFrontend, { Message } from "udn-frontend"; +import StorageModel, { StorageModelSubPaths, filePaths } from "./storageModel"; +import { HandlerManager, stringify } from "../Utility/utility"; +import { ChatMessage, ChatMessageReference } from "../Chat/chatModel"; export default class ConnectionModel { + // models etc readonly udn: UDNFrontend; readonly storageModel: StorageModel; + + // config reconnectInterval: number | undefined = undefined; shouldAttemptReconnect: boolean = false; @@ -20,17 +23,18 @@ export default class ConnectionModel { return this.udn.ws?.url; } + // handler managers readonly connectionChangeHandlerManager = new HandlerManager(); readonly messageHandlerManager = new HandlerManager(); readonly messageSentHandlerManager = new HandlerManager(); readonly channelsToSubscribe = new Set(); // handlers - handleMessage = (data: Message): void => { + readonly handleMessage = (data: Message): void => { this.messageHandlerManager.trigger(data); }; - handleConnectionChange = (): void => { + readonly handleConnectionChange = (): void => { console.log("connection status:", this.isConnected, this.address); this.connectionChangeHandlerManager.trigger(); if (this.address == undefined) return; @@ -41,13 +45,13 @@ export default class ConnectionModel { }; // connection - connect = (address: string): void => { + readonly connect = (address: string): void => { console.log("connecting...", address); this.shouldAttemptReconnect = true; this.udn.connect(address); }; - disconnect = (): void => { + readonly disconnect = (): void => { this.shouldAttemptReconnect = false; this.udn.disconnect(); @@ -59,7 +63,7 @@ export default class ConnectionModel { this.storageModel.remove(reconnectAddressPath); }; - reconnect = (): void => { + readonly reconnect = (): void => { const reconnectAddressPath: string[] = this.getReconnectAddressPath(); const reconnectAddress: string | null = this.storageModel.read(reconnectAddressPath); @@ -70,7 +74,7 @@ export default class ConnectionModel { }; // mailbox - getMailboxPath = (address: string): string[] => { + readonly getMailboxPath = (address: string): string[] => { const mailboxDirPath = StorageModel.getPath( StorageModelSubPaths.ConnectionModel, filePaths.connectionModel.mailboxes, @@ -79,12 +83,12 @@ export default class ConnectionModel { return mailboxFilePath; }; - requestNewMailbox = (): void => { + readonly requestNewMailbox = (): void => { console.log("requesting new mailbox"); this.udn.requestMailbox(); }; - connectMailbox = (): void => { + readonly connectMailbox = (): void => { if (this.address == undefined) return; const mailboxId = this.storageModel.read( @@ -96,18 +100,18 @@ export default class ConnectionModel { this.udn.connectMailbox(mailboxId); }; - storeMailbox = (mailboxId: string): void => { + readonly storeMailbox = (mailboxId: string): void => { if (this.address == undefined) return; this.storageModel.write(this.getMailboxPath(this.address), mailboxId); }; // subscription - addChannel = (channel: string): void => { + readonly addChannel = (channel: string): void => { this.channelsToSubscribe.add(channel); this.sendSubscriptionRequest(); }; - sendSubscriptionRequest = (): void => { + readonly sendSubscriptionRequest = (): void => { if (this.isConnected == false) return; for (const channel of this.channelsToSubscribe) { @@ -118,14 +122,14 @@ export default class ConnectionModel { }; // outbox - getOutboxPath = (): string[] => { + readonly getOutboxPath = (): string[] => { return StorageModel.getPath( StorageModelSubPaths.ConnectionModel, filePaths.connectionModel.outbox, ); }; - getOutboxMessags = (): ChatMessage[] => { + readonly getOutboxMessags = (): ChatMessage[] => { const outboxPath: string[] = this.getOutboxPath(); const messageIds: string[] = this.storageModel.list(outboxPath); @@ -144,17 +148,17 @@ export default class ConnectionModel { return chatMessages; }; - addToOutbox = (chatMessage: ChatMessage): void => { + readonly addToOutbox = (chatMessage: ChatMessage): void => { const messagePath: string[] = [...this.getOutboxPath(), chatMessage.id]; this.storageModel.writeStringifiable(messagePath, chatMessage); }; - removeFromOutbox = (chatMessage: ChatMessage): void => { + readonly removeFromOutbox = (chatMessage: ChatMessage): void => { const messagePath: string[] = [...this.getOutboxPath(), chatMessage.id]; this.storageModel.remove(messagePath); }; - sendMessagesInOutbox = (): void => { + readonly sendMessagesInOutbox = (): void => { const messages: ChatMessage[] = this.getOutboxMessags(); for (const message of messages) { @@ -166,14 +170,14 @@ export default class ConnectionModel { }; // messaging - sendMessageOrStore = (chatMessage: ChatMessage): void => { + readonly sendMessageOrStore = (chatMessage: ChatMessage): void => { const isSent = this.tryToSendMessage(chatMessage); if (isSent == true) return; this.addToOutbox(chatMessage); }; - tryToSendMessage = (chatMessage: ChatMessage): boolean => { + readonly tryToSendMessage = (chatMessage: ChatMessage): boolean => { const stringifiedBody: string = stringify(chatMessage); const isSent: boolean = this.sendPlainMessage( chatMessage.channel, @@ -183,31 +187,31 @@ export default class ConnectionModel { return isSent; }; - sendPlainMessage = (channel: string, body: string): boolean => { + readonly sendPlainMessage = (channel: string, body: string): boolean => { return this.udn.sendMessage(channel, body); }; // storage - getPreviousAddressPath = (): string[] => { + readonly getPreviousAddressPath = (): string[] => { return StorageModel.getPath( StorageModelSubPaths.ConnectionModel, filePaths.connectionModel.previousAddresses, ); }; - getAddressPath = (address: string): string[] => { + readonly getAddressPath = (address: string): string[] => { const dirPath = this.getPreviousAddressPath(); return [...dirPath, address]; }; - getReconnectAddressPath = (): string[] => { + readonly getReconnectAddressPath = (): string[] => { return StorageModel.getPath( StorageModelSubPaths.ConnectionModel, filePaths.connectionModel.reconnectAddress, ); }; - storeAddress = (address: string): void => { + readonly storeAddress = (address: string): void => { // history const addressPath = this.getAddressPath(address); this.storageModel.write(addressPath, ""); @@ -217,7 +221,7 @@ export default class ConnectionModel { this.storageModel.write(reconnectAddressPath, address); }; - removeAddress = (address: string): void => { + readonly removeAddress = (address: string): void => { const addressPath = this.getAddressPath(address); this.storageModel.remove(addressPath); }; diff --git a/src/Model/Global/fileTransferModel.ts b/src/Model/Global/fileTransferModel.ts index d2a266e2..7fe843e7 100644 --- a/src/Model/Global/fileTransferModel.ts +++ b/src/Model/Global/fileTransferModel.ts @@ -1,17 +1,16 @@ -// this file is responsible for sending and handling files. +// cleanup: Phase A +import { Message } from "udn-frontend"; +import StorageModel from "./storageModel"; +import ConnectionModel from "./connectionModel"; import { HandlerManager, generateRandomToken, parse, stringify, } from "../Utility/utility"; -import { decryptString, encryptString } from "../Utility/crypto"; - -import ConnectionModel from "./connectionModel"; -import { Message } from "udn-frontend"; -import StorageModel from "./storageModel"; import { checkMatchesObjectStructure } from "../Utility/typeSafety"; +import { decryptString, encryptString } from "../Utility/crypto"; export default class FileTransferModel { readonly storageModel: StorageModel; @@ -21,12 +20,14 @@ export default class FileTransferModel { // data transferData: TransferData | undefined; - readonly fileHandlerManager = new HandlerManager(); - readonly readyToSendHandlerManager = new HandlerManager(); direction: TransferDirections = TransferDirections.Send; + // handler managers + readonly fileHandlerManager = new HandlerManager(); + readonly readyToSendHandlerManager = new HandlerManager(); + // general - generateTransferData = (): TransferData => { + readonly generateTransferData = (): TransferData => { const transferData: TransferData = { channel: generateRandomToken(4), key: generateRandomToken(6), @@ -35,12 +36,12 @@ export default class FileTransferModel { return transferData; }; - prepareToSend = (): void => { + readonly prepareToSend = (): void => { this.direction = TransferDirections.Send; this.connectionModel.addChannel(this.transferData.channel); }; - prepareToReceive = (transferData: TransferData): void => { + readonly prepareToReceive = (transferData: TransferData): void => { this.direction = TransferDirections.Receive; this.connectionModel.addChannel(transferData.channel); @@ -52,7 +53,7 @@ export default class FileTransferModel { }; // handlers - handleMessage = (data: Message): void => { + readonly handleMessage = (data: Message): void => { if (this.transferData == undefined) return; if (data.messageChannel != this.transferData.channel) return; if ( @@ -68,7 +69,7 @@ export default class FileTransferModel { this.handleFile(data.messageBody); }; - handleFile = async (encryptedFileData: string): Promise => { + readonly handleFile = async (encryptedFileData: string): Promise => { if (this.transferData == undefined) return; const decrypted: string = await decryptString( @@ -93,7 +94,7 @@ export default class FileTransferModel { }; // sending - sendFiles = ( + readonly sendFiles = ( directoryPaths: IteratorObject, callback: (path: string) => void, ): void => { @@ -108,7 +109,7 @@ export default class FileTransferModel { } }; - sendFile = async (filePath: string[]): Promise => { + readonly sendFile = async (filePath: string[]): Promise => { if (this.transferData == undefined) return; const fileContent: string | null = this.storageModel.read(filePath); if (fileContent == null) return; diff --git a/src/Model/Global/settingsModel.ts b/src/Model/Global/settingsModel.ts index 85d16c49..e5ff93cf 100644 --- a/src/Model/Global/settingsModel.ts +++ b/src/Model/Global/settingsModel.ts @@ -1,4 +1,4 @@ -// this file is responsible for all settings. +// cleanup: Phase A import StorageModel, { StorageModelSubPaths, filePaths } from "./storageModel"; @@ -12,10 +12,10 @@ export default class SettingsModel { theme: string; // storage - private storeSetting( + private readonly storeSetting = ( pathName: keyof typeof filePaths.settingsModel, value: string, - ): void { + ): void => { const path = StorageModel.getPath( StorageModelSubPaths.SettingsModel, filePaths.settingsModel[pathName], @@ -23,28 +23,28 @@ export default class SettingsModel { this.storageModel.write(path, value); } - setName = (newValue: string): void => { + readonly setName = (newValue: string): void => { this.username = newValue; this.storeSetting("username", newValue); }; - setFirstDayOfWeek = (newValue: string): void => { + readonly setFirstDayOfWeek = (newValue: string): void => { this.firstDayOfWeek = newValue; this.storeSetting("firstDayOfWeek", newValue); }; - setLanguage = (newValue: string): void => { + readonly setLanguage = (newValue: string): void => { this.language = newValue; this.storeSetting("language", newValue); }; - setTheme = (newValue: string): void => { + readonly setTheme = (newValue: string): void => { this.theme = newValue; this.storeSetting("theme", newValue); }; // load - private readSetting = ( + private readonly readSetting = ( pathName: keyof typeof filePaths.settingsModel, ): string => { const path = StorageModel.getPath( @@ -54,22 +54,22 @@ export default class SettingsModel { return this.storageModel.read(path); }; - loadUsername = (): void => { + readonly loadUsername = (): void => { const content: string | null = this.readSetting("username"); this.username = content ?? ""; }; - loadFirstDayofWeek = (): void => { + readonly loadFirstDayofWeek = (): void => { const content: string | null = this.readSetting("firstDayOfWeek"); this.firstDayOfWeek = content ?? "0"; }; - loadLanguage = (): void => { + readonly loadLanguage = (): void => { const content: string | null = this.readSetting("language"); this.language = content ?? SettingsModel.getSystemLanguage(); }; - loadTheme = (): void => { + readonly loadTheme = (): void => { const content: string | null = this.readSetting("theme"); this.theme = content ?? ThemeSettings.System; }; diff --git a/src/Model/Global/storageModel.ts b/src/Model/Global/storageModel.ts index 38f83cc3..f2380d5d 100644 --- a/src/Model/Global/storageModel.ts +++ b/src/Model/Global/storageModel.ts @@ -1,7 +1,7 @@ -// this file is responsible for reading and writing persistent data. +// cleanup: Phase A -import { DATA_VERSION, ValidObject } from "../Utility/typeSafety"; import { localeCompare, parseValidObject, stringify } from "../Utility/utility"; +import { DATA_VERSION, ValidObject } from "../Utility/typeSafety"; export const PATH_COMPONENT_SEPARATOR = "\\"; @@ -9,14 +9,14 @@ export default class StorageModel { storageEntryTree: StorageEntry = {}; // read - read = (pathComponents: string[]): string | null => { + readonly read = (pathComponents: string[]): string | null => { const pathString: string = StorageModel.pathComponentsToString( ...pathComponents, ); return localStorage.getItem(pathString); }; - list = (pathComponents: string[]): string[] => { + readonly list = (pathComponents: string[]): string[] => { let currentParent: StorageEntry = this.storageEntryTree; for (const component of pathComponents) { const nextParent: StorageEntry | undefined = @@ -29,7 +29,7 @@ export default class StorageModel { }; // write - write = (pathComponents: string[], value: string): void => { + readonly write = (pathComponents: string[], value: string): void => { const pathString: string = StorageModel.pathComponentsToString( ...pathComponents, ); @@ -37,7 +37,7 @@ export default class StorageModel { this.updateTree(...pathComponents); }; - remove = ( + readonly remove = ( pathComponents: string[], shouldInitialize: boolean = true, ): void => { @@ -51,7 +51,7 @@ export default class StorageModel { } }; - rename = ( + readonly rename = ( sourcePathComponents: string[], destinationPathComponents: string[], shouldInitialize: boolean = true, @@ -69,7 +69,7 @@ export default class StorageModel { }; // recursion - recurse = (rootDirectory: string[], fn: (path: string[]) => void): void => { + readonly 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[] = @@ -92,14 +92,14 @@ export default class StorageModel { this.initializeTree(); }; - removeRecursively = (pathComponents: string[]): void => { + readonly removeRecursively = (pathComponents: string[]): void => { this.recurse(pathComponents, (path: string[]) => this.remove(path, false), ); this.initializeTree(); }; - renameRecursively = ( + readonly renameRecursively = ( sourcePathComponents: string[], destinationPathComponents: string[], ): void => { @@ -120,7 +120,7 @@ export default class StorageModel { }; // stringifiable - writeStringifiable = ( + readonly writeStringifiable = ( pathComponents: string[], value: ValidObject, ): void => { @@ -128,7 +128,7 @@ export default class StorageModel { this.write(pathComponents, valueString); }; - readStringifiable = ( + readonly readStringifiable = ( pathComponents: string[], reference: T, ): T | null => { @@ -142,7 +142,7 @@ export default class StorageModel { }; // cleaning - removeJunk = (): void => { + readonly removeJunk = (): void => { this.recurse([], (path: string[]) => { if (path[0] == DATA_VERSION) return; this.remove(path); @@ -150,7 +150,7 @@ export default class StorageModel { }; // tree - initializeTree = (): void => { + readonly initializeTree = (): void => { console.log("initializing tree"); this.storageEntryTree = {}; @@ -161,7 +161,7 @@ export default class StorageModel { } }; - updateTree = (...pathComponents: string[]): void => { + readonly updateTree = (...pathComponents: string[]): void => { let currentParent: StorageEntry = this.storageEntryTree; for (const pathPart of pathComponents) { if (!currentParent[pathPart]) { @@ -172,7 +172,7 @@ export default class StorageModel { } }; - printTree = (): string => { + readonly printTree = (): string => { return stringify(this.storageEntryTree); }; diff --git a/src/Model/Utility/crypto.ts b/src/Model/Utility/crypto.ts index 5345f62a..50b61a65 100644 --- a/src/Model/Utility/crypto.ts +++ b/src/Model/Utility/crypto.ts @@ -36,15 +36,15 @@ export async function decryptString( } // process -export function encode(string: string): Uint8Array { +function encode(string: string): Uint8Array { return new TextEncoder().encode(string); } -export function decode(array: Uint8Array): string { +function decode(array: Uint8Array): string { return new TextDecoder("utf-8").decode(array); } -export async function encrypt( +async function encrypt( iv: Uint8Array, key: CryptoKey, message: string, @@ -57,7 +57,7 @@ export async function encrypt( return new Uint8Array(arrayBuffer); } -export async function decrypt( +async function decrypt( iv: Uint8Array, key: CryptoKey, cyphertext: Uint8Array, @@ -70,16 +70,16 @@ export async function decrypt( return arrayBufferToString(arrayBuffer); } -export async function hash(encoded: Uint8Array): Promise { +async function hash(encoded: Uint8Array): Promise { return await crypto.subtle.digest("SHA-256", encoded); } // iv -export function generateIV(): Uint8Array { +function generateIV(): Uint8Array { return crypto.getRandomValues(new Uint8Array(IV_SIZE)); } -export function splitMergedString(mergedString: string): [Uint8Array, string] { +function splitMergedString(mergedString: string): [Uint8Array, string] { const iv = mergedString.slice(0, IV_SIZE); const encryptedString = mergedString.slice(IV_SIZE); @@ -87,7 +87,7 @@ export function splitMergedString(mergedString: string): [Uint8Array, string] { } // key -export async function importKey( +async function importKey( passphrase: string, purpose: "encrypt" | "decrypt", ): Promise { @@ -101,15 +101,15 @@ export async function importKey( } // conversion -export function arrayBufferToString(arrayBuffer: ArrayBuffer): string { +function arrayBufferToString(arrayBuffer: ArrayBuffer): string { const uInt8Array = new Uint8Array(arrayBuffer); return decode(uInt8Array); } -export function uInt8ToArray(uInt8Array: Uint8Array): number[] { +function uInt8ToArray(uInt8Array: Uint8Array): number[] { return Array.from(uInt8Array); } -export function arrayToUint8(array: number[]): Uint8Array { +function arrayToUint8(array: number[]): Uint8Array { return new Uint8Array(array); } diff --git a/src/Upgrader/v1.ts b/src/Upgrader/v1.ts index 698a8f84..1ca9b76c 100644 --- a/src/Upgrader/v1.ts +++ b/src/Upgrader/v1.ts @@ -1,5 +1,14 @@ // this file is responsible for migrating data from v1 to v2. +import { v4 } from "uuid"; +import CoreViewModel from "../ViewModel/Global/coreViewModel"; +import { + getLocalStorageItemAndClear, + parseArray, + parseOrFallback, +} from "../Model/Utility/utility"; +import { checkMatchesObjectStructure } from "../Model/Utility/typeSafety"; +import FileModel from "../Model/Files/fileModel"; import { BoardInfoFileContent, TaskFileContent, @@ -8,16 +17,6 @@ import ChatModel, { ChatMessage, ChatMessageStatuses, } from "../Model/Chat/chatModel"; -import { - getLocalStorageItemAndClear, - parseArray, - parseOrFallback, -} from "../Model/Utility/utility"; - -import FileModel from "../Model/Files/fileModel"; -import { checkMatchesObjectStructure } from "../Model/Utility/typeSafety"; -import { v4 } from "uuid"; -import CoreViewModel from "../ViewModel/Global/coreViewModel"; export default class v1Upgrader { // general diff --git a/src/View/ChatPages/boardKanbanPage.tsx b/src/View/ChatPages/boardKanbanPage.tsx index 6e9d9cc9..c4ddbe24 100644 --- a/src/View/ChatPages/boardKanbanPage.tsx +++ b/src/View/ChatPages/boardKanbanPage.tsx @@ -1,17 +1,15 @@ import * as React from "bloatless-react"; - +import { ViewController } from "../viewController"; +import { TaskViewModelToEntry } from "../Components/taskEntry"; import { PropertyValueList, createPropertyValueIndexState, } from "../Components/propertyValueList"; - -import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; import { FilteredList } from "../Components/filteredList"; import { TaskCategoryBulkChangeViewModel } from "../../ViewModel/Utility/taskPropertyBulkChangeViewModel"; import TaskViewModel from "../../ViewModel/Pages/taskViewModel"; -import { TaskViewModelToEntry } from "../Components/taskEntry"; +import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; -import { ViewController } from "../viewController"; export function BoardKanbanPage( coreViewModel: CoreViewModel, diff --git a/src/View/ChatPages/boardPage.tsx b/src/View/ChatPages/boardPage.tsx index 3dbaa6c3..cf0b8f2b 100644 --- a/src/View/ChatPages/boardPage.tsx +++ b/src/View/ChatPages/boardPage.tsx @@ -1,18 +1,16 @@ import * as React from "bloatless-react"; - +import { BoardStatusGridPage } from "./boardStatusGridPage"; +import { BoardKanbanPage } from "./boardKanbanPage"; +import { ViewController } from "../viewController"; +import { TaskSettingsModal } from "../Modals/taskSettingsModal"; +import { SearchModal } from "../Modals/searchModal"; +import { BoardSettingsModal } from "../Modals/boardSettingsModal"; +import { TaskViewModelToEntry } from "../Components/taskEntry"; +import { BoardViewToggleButton } from "../Components/boardViewToggleButton"; import BoardViewModel, { BoardPageTypes, } from "../../ViewModel/Pages/boardViewModel"; - -import { BoardKanbanPage } from "./boardKanbanPage"; -import { BoardSettingsModal } from "../Modals/boardSettingsModal"; -import { BoardStatusGridPage } from "./boardStatusGridPage"; -import { BoardViewToggleButton } from "../Components/boardViewToggleButton"; -import { SearchModal } from "../Modals/searchModal"; -import { TaskSettingsModal } from "../Modals/taskSettingsModal"; -import { TaskViewModelToEntry } from "../Components/taskEntry"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; -import { ViewController } from "../viewController"; export function BoardPage( coreViewModel: CoreViewModel, diff --git a/src/View/ChatPages/boardStatusGridPage.tsx b/src/View/ChatPages/boardStatusGridPage.tsx index 095fc87c..5ca1e8cc 100644 --- a/src/View/ChatPages/boardStatusGridPage.tsx +++ b/src/View/ChatPages/boardStatusGridPage.tsx @@ -1,22 +1,20 @@ import * as React from "bloatless-react"; - +import { ViewController } from "../viewController"; +import { TaskViewModelToEntry } from "../Components/taskEntry"; import { PropertyValueList, collectPropertyValuesToState, createPropertyValueIndexState, createSortedPropertyValueState, } from "../Components/propertyValueList"; +import { FilteredList } from "../Components/filteredList"; import { TaskCategoryBulkChangeViewModel, TaskStatusBulkChangeViewModel, } from "../../ViewModel/Utility/taskPropertyBulkChangeViewModel"; - -import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; -import { FilteredList } from "../Components/filteredList"; import TaskViewModel from "../../ViewModel/Pages/taskViewModel"; -import { TaskViewModelToEntry } from "../Components/taskEntry"; +import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; -import { ViewController } from "../viewController"; export function BoardStatusGridPage( coreViewModel: CoreViewModel, diff --git a/src/View/ChatPages/calendarPage.tsx b/src/View/ChatPages/calendarPage.tsx index 7fd86703..4ca44f03 100644 --- a/src/View/ChatPages/calendarPage.tsx +++ b/src/View/ChatPages/calendarPage.tsx @@ -1,14 +1,12 @@ -import "./calendarPage.css"; - import * as React from "bloatless-react"; - -import CalendarPageViewModel from "../../ViewModel/Pages/calendarPageViewModel"; -import { MonthGrid } from "../Components/monthGrid"; +import "./calendarPage.css"; +import { ViewController } from "../viewController"; import { TaskSettingsModal } from "../Modals/taskSettingsModal"; import { TaskViewModelToEntry } from "../Components/taskEntry"; -import CoreViewModel from "../../ViewModel/Global/coreViewModel"; import { PlaceholderView } from "../Components/placeholderView"; -import { ViewController } from "../viewController"; +import { MonthGrid } from "../Components/monthGrid"; +import CalendarPageViewModel from "../../ViewModel/Pages/calendarPageViewModel"; +import CoreViewModel from "../../ViewModel/Global/coreViewModel"; export function CalendarPage( coreViewModel: CoreViewModel, diff --git a/src/View/ChatPages/messagePage.tsx b/src/View/ChatPages/messagePage.tsx index fd1b643e..5285d3c1 100644 --- a/src/View/ChatPages/messagePage.tsx +++ b/src/View/ChatPages/messagePage.tsx @@ -1,14 +1,12 @@ -import "./messagePage.css"; - import * as React from "bloatless-react"; - +import "./messagePage.css"; +import { ViewController } from "../viewController"; +import { MessageFilterModal } from "../Modals/messageFilterModal"; +import { ReplyPreview } from "../Components/replyPreview"; +import { ChatMessage } from "../Components/chatMessage"; import MessagePageViewModel from "../../ViewModel/Pages/messagePageViewModel"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; -import { ChatMessage } from "../Components/chatMessage"; -import { MessageFilterModal } from "../Modals/messageFilterModal"; -import { ViewController } from "../viewController"; -import { ReplyPreview } from "../Components/replyPreview"; export function MessagePage( coreViewModel: CoreViewModel, diff --git a/src/View/ChatPages/settingsPage.tsx b/src/View/ChatPages/settingsPage.tsx index 5d2e9a72..45852940 100644 --- a/src/View/ChatPages/settingsPage.tsx +++ b/src/View/ChatPages/settingsPage.tsx @@ -1,9 +1,7 @@ import * as React from "bloatless-react"; - -import { Colors } from "../../colors"; -import { ColorPicker } from "../Components/colorPicker"; -import { DangerousActionButton } from "../Components/dangerousActionButton"; import { DeletableListItem } from "../Components/deletableListItem"; +import { DangerousActionButton } from "../Components/dangerousActionButton"; +import { ColorPicker } from "../Components/colorPicker"; import SettingsPageViewModel from "../../ViewModel/Pages/settingsPageViewModel"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; diff --git a/src/View/ChatPages/taskPage.tsx b/src/View/ChatPages/taskPage.tsx index 90fc2681..fc0e1d93 100644 --- a/src/View/ChatPages/taskPage.tsx +++ b/src/View/ChatPages/taskPage.tsx @@ -1,14 +1,11 @@ -import "./taskPage.css"; - import * as React from "bloatless-react"; - +import "./taskPage.css"; import { BoardPage } from "./boardPage"; +import { ViewController } from "../viewController"; +import { PlaceholderView } from "../Components/placeholderView"; import { BoardViewModelToEntry } from "../Components/boardEntry"; import TaskPageViewModel from "../../ViewModel/Pages/taskPageViewModel"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; -import { PlaceholderView } from "../Components/placeholderView"; -import { v4 } from "uuid"; -import { ViewController } from "../viewController"; export function TaskPage( coreViewModel: CoreViewModel, diff --git a/src/View/Components/boardEntry.tsx b/src/View/Components/boardEntry.tsx index 26a754e8..558a67b8 100644 --- a/src/View/Components/boardEntry.tsx +++ b/src/View/Components/boardEntry.tsx @@ -1,7 +1,6 @@ import * as React from "bloatless-react"; - -import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; import { ViewController } from "../viewController"; +import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; export function BoardEntry(boardViewModel: BoardViewModel) { const view = ( diff --git a/src/View/Components/boardViewToggleButton.tsx b/src/View/Components/boardViewToggleButton.tsx index 02b40255..e9661b96 100644 --- a/src/View/Components/boardViewToggleButton.tsx +++ b/src/View/Components/boardViewToggleButton.tsx @@ -1,11 +1,9 @@ import * as React from "bloatless-react"; - +import { RibbonButton } from "./ribbonButton"; import BoardViewModel, { BoardPageTypes, } from "../../ViewModel/Pages/boardViewModel"; -import { RibbonButton } from "./ribbonButton"; - export function BoardViewToggleButton( label: string, icon: string, diff --git a/src/View/Components/chatEntry.tsx b/src/View/Components/chatEntry.tsx index 60da0ca3..9102d683 100644 --- a/src/View/Components/chatEntry.tsx +++ b/src/View/Components/chatEntry.tsx @@ -1,5 +1,4 @@ import * as React from "bloatless-react"; - import ChatViewModel from "../../ViewModel/Chat/chatViewModel"; export function ChatEntry(chatViewModel: ChatViewModel) { diff --git a/src/View/Components/chatMessage.tsx b/src/View/Components/chatMessage.tsx index ba70d68f..96091d23 100644 --- a/src/View/Components/chatMessage.tsx +++ b/src/View/Components/chatMessage.tsx @@ -1,12 +1,10 @@ import * as React from "bloatless-react"; - -import { ChatMessageInfoModal } from "../Modals/chatMessageInfoModal"; -import { ChatMessageStatuses } from "../../Model/Chat/chatModel"; -import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; import { MessageReactionButtonRow } from "./messageReactionButtonRow"; -import CoreViewModel from "../../ViewModel/Global/coreViewModel"; import { InlineReply } from "./inlineReply"; -import { ViewController } from "../viewController"; +import { ChatMessageInfoModal } from "../Modals/chatMessageInfoModal"; +import CoreViewModel from "../../ViewModel/Global/coreViewModel"; +import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; +import { ChatMessageStatuses } from "../../Model/Chat/chatModel"; export function ChatMessage( coreViewModel: CoreViewModel, diff --git a/src/View/Components/chatViewToggleButton.tsx b/src/View/Components/chatViewToggleButton.tsx index 1557cb9f..b3c7c02d 100644 --- a/src/View/Components/chatViewToggleButton.tsx +++ b/src/View/Components/chatViewToggleButton.tsx @@ -1,11 +1,9 @@ import * as React from "bloatless-react"; - +import { RibbonButton } from "./ribbonButton"; import ChatViewModel, { ChatPageTypes, } from "../../ViewModel/Chat/chatViewModel"; -import { RibbonButton } from "./ribbonButton"; - export function ChatViewToggleButton( label: string, icon: string, diff --git a/src/View/Components/colorPicker.tsx b/src/View/Components/colorPicker.tsx index e189d88c..27939f6c 100644 --- a/src/View/Components/colorPicker.tsx +++ b/src/View/Components/colorPicker.tsx @@ -1,5 +1,4 @@ import * as React from "bloatless-react"; - import { Colors } from "../../colors"; export function ColorPicker(selectedColor: React.State) { diff --git a/src/View/Components/directoryItemList.tsx b/src/View/Components/directoryItemList.tsx index 51a7b8a2..b5d29aca 100644 --- a/src/View/Components/directoryItemList.tsx +++ b/src/View/Components/directoryItemList.tsx @@ -1,11 +1,9 @@ import * as React from "bloatless-react"; - +import StorageViewModel from "../../ViewModel/Global/storageViewModel"; import StorageModel, { PATH_COMPONENT_SEPARATOR, } from "../../Model/Global/storageModel"; -import StorageViewModel from "../../ViewModel/Global/storageViewModel"; - export function DirectoryItemList( storageViewModel: StorageViewModel, pathString: string = PATH_COMPONENT_SEPARATOR, diff --git a/src/View/Components/filteredList.tsx b/src/View/Components/filteredList.tsx index 02a0d5f5..0dc92913 100644 --- a/src/View/Components/filteredList.tsx +++ b/src/View/Components/filteredList.tsx @@ -1,5 +1,4 @@ import * as React from "bloatless-react"; - import { StringEntryObject, checkDoesObjectMatchReference, diff --git a/src/View/Components/inlineReply.tsx b/src/View/Components/inlineReply.tsx index a1241b0f..82c726d8 100644 --- a/src/View/Components/inlineReply.tsx +++ b/src/View/Components/inlineReply.tsx @@ -1,8 +1,6 @@ import * as React from "bloatless-react"; - -import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; -import CoreViewModel from "../../ViewModel/Global/coreViewModel"; import { ViewController } from "../viewController"; +import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; export function InlineReply(chatMessageViewModel: ChatMessageViewModel) { const reply = chatMessageViewModel.inlineReply; diff --git a/src/View/Components/messageReactionButton.tsx b/src/View/Components/messageReactionButton.tsx index 73ca0904..15ef9217 100644 --- a/src/View/Components/messageReactionButton.tsx +++ b/src/View/Components/messageReactionButton.tsx @@ -1,10 +1,7 @@ import * as React from "bloatless-react"; -import { - ChatMessageReaction, - ReactionSymbols, -} from "../../Model/Chat/chatModel"; -import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; +import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; +import { ReactionSymbols } from "../../Model/Chat/chatModel"; export function MessageReactionButton( coreViewModel: CoreViewModel, diff --git a/src/View/Components/messageReactionButtonRow.tsx b/src/View/Components/messageReactionButtonRow.tsx index 65f24e48..923f8bcc 100644 --- a/src/View/Components/messageReactionButtonRow.tsx +++ b/src/View/Components/messageReactionButtonRow.tsx @@ -1,8 +1,8 @@ import * as React from "bloatless-react"; -import { ReactionSymbols } from "../../Model/Chat/chatModel"; -import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; import { MessageReactionButton } from "./messageReactionButton"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; +import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; +import { ReactionSymbols } from "../../Model/Chat/chatModel"; export function MessageReactionButtonRow( coreViewModel: CoreViewModel, diff --git a/src/View/Components/messageReactionEntry.tsx b/src/View/Components/messageReactionEntry.tsx index eac7a440..49a5fb04 100644 --- a/src/View/Components/messageReactionEntry.tsx +++ b/src/View/Components/messageReactionEntry.tsx @@ -1,6 +1,4 @@ import * as React from "bloatless-react"; -import CoreViewModel from "../../ViewModel/Global/coreViewModel"; -import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; import { ChatMessageReaction } from "../../Model/Chat/chatModel"; export function MessageReactionEntry(reaction: ChatMessageReaction) { diff --git a/src/View/Components/messageReactionFilterButton.tsx b/src/View/Components/messageReactionFilterButton.tsx index 2bad888c..88bcdd76 100644 --- a/src/View/Components/messageReactionFilterButton.tsx +++ b/src/View/Components/messageReactionFilterButton.tsx @@ -1,11 +1,6 @@ import * as React from "bloatless-react"; -import { - ChatMessageReaction, - ReactionSymbols, -} from "../../Model/Chat/chatModel"; -import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; -import CoreViewModel from "../../ViewModel/Global/coreViewModel"; import MessagePageViewModel from "../../ViewModel/Pages/messagePageViewModel"; +import { ReactionSymbols } from "../../Model/Chat/chatModel"; export function MessageReactionFilterButton( messagePageViewModel: MessagePageViewModel, diff --git a/src/View/Components/monthGrid.tsx b/src/View/Components/monthGrid.tsx index b103be2b..efcc682f 100644 --- a/src/View/Components/monthGrid.tsx +++ b/src/View/Components/monthGrid.tsx @@ -1,11 +1,10 @@ -import "./monthGrid.css"; - import * as React from "bloatless-react"; -import { MonthGrid } from "../../Model/Files/calendarModel"; -import TaskViewModel from "../../ViewModel/Pages/taskViewModel"; -import { localeCompare } from "../../Model/Utility/utility"; -import CoreViewModel from "../../ViewModel/Global/coreViewModel"; +import "./monthGrid.css"; import { ViewController } from "../viewController"; +import TaskViewModel from "../../ViewModel/Pages/taskViewModel"; +import CoreViewModel from "../../ViewModel/Global/coreViewModel"; +import { localeCompare } from "../../Model/Utility/utility"; +import { MonthGrid } from "../../Model/Files/calendarModel"; export function MonthGrid( coreViewModel: CoreViewModel, @@ -69,7 +68,9 @@ export function MonthGrid( return ( monthGrid.isCurrentMonth == true && date == - coreViewModel.unwrappedTodayDate.getDate().toString() + coreViewModel.unwrappedTodayDate + .getDate() + .toString() ); }, ); diff --git a/src/View/Components/propertyValueList.tsx b/src/View/Components/propertyValueList.tsx index 605a939c..63a4cdce 100644 --- a/src/View/Components/propertyValueList.tsx +++ b/src/View/Components/propertyValueList.tsx @@ -1,5 +1,4 @@ import * as React from "bloatless-react"; - import { StringEntryObject, collectObjectValuesForKey, diff --git a/src/View/Components/replyPreview.tsx b/src/View/Components/replyPreview.tsx index 2b6948ca..4064caa4 100644 --- a/src/View/Components/replyPreview.tsx +++ b/src/View/Components/replyPreview.tsx @@ -1,7 +1,6 @@ import * as React from "bloatless-react"; - -import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; +import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; export function ReplyPreview( coreViewModel: CoreViewModel, diff --git a/src/View/Components/ribbonButton.tsx b/src/View/Components/ribbonButton.tsx index 557b3196..66d735fa 100644 --- a/src/View/Components/ribbonButton.tsx +++ b/src/View/Components/ribbonButton.tsx @@ -1,9 +1,5 @@ import * as React from "bloatless-react"; -import BoardViewModel, { - BoardPageTypes, -} from "../../ViewModel/Pages/boardViewModel"; - export function RibbonButton( label: string, icon: string, diff --git a/src/View/Components/taskEntry.tsx b/src/View/Components/taskEntry.tsx index 95d526dc..407d78fd 100644 --- a/src/View/Components/taskEntry.tsx +++ b/src/View/Components/taskEntry.tsx @@ -1,5 +1,4 @@ import * as React from "bloatless-react"; - import TaskViewModel from "../../ViewModel/Pages/taskViewModel"; export function TaskEntry(taskViewModel: TaskViewModel) { diff --git a/src/View/Modals/boardSettingsModal.tsx b/src/View/Modals/boardSettingsModal.tsx index 0e3237c6..07fd6716 100644 --- a/src/View/Modals/boardSettingsModal.tsx +++ b/src/View/Modals/boardSettingsModal.tsx @@ -1,10 +1,9 @@ import * as React from "bloatless-react"; - -import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; -import { ColorPicker } from "../Components/colorPicker"; -import { DangerousActionButton } from "../Components/dangerousActionButton"; -import CoreViewModel from "../../ViewModel/Global/coreViewModel"; import { ViewController } from "../viewController"; +import { DangerousActionButton } from "../Components/dangerousActionButton"; +import { ColorPicker } from "../Components/colorPicker"; +import BoardViewModel from "../../ViewModel/Pages/boardViewModel"; +import CoreViewModel from "../../ViewModel/Global/coreViewModel"; export function BoardSettingsModal( coreViewModel: CoreViewModel, diff --git a/src/View/Modals/chatMessageInfoModal.tsx b/src/View/Modals/chatMessageInfoModal.tsx index 67f6e6c2..1dc46521 100644 --- a/src/View/Modals/chatMessageInfoModal.tsx +++ b/src/View/Modals/chatMessageInfoModal.tsx @@ -1,10 +1,9 @@ import * as React from "bloatless-react"; - -import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; +import { MessageReactionEntry } from "../Components/messageReactionEntry"; import { MessageReactionButtonRow } from "../Components/messageReactionButtonRow"; import { InfoTile } from "../Components/infoTile"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; -import { MessageReactionEntry } from "../Components/messageReactionEntry"; +import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel"; export function ChatMessageInfoModal( coreViewModel: CoreViewModel, diff --git a/src/View/Modals/connectionModal.tsx b/src/View/Modals/connectionModal.tsx index 6a6fba76..e49d1cd5 100644 --- a/src/View/Modals/connectionModal.tsx +++ b/src/View/Modals/connectionModal.tsx @@ -1,8 +1,7 @@ import * as React from "bloatless-react"; - -import ConnectionViewModel from "../../ViewModel/Global/connectionViewModel"; import { DeletableListItem } from "../Components/deletableListItem"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; +import ConnectionViewModel from "../../ViewModel/Global/connectionViewModel"; export function ConnectionModal( coreViewModel: CoreViewModel, diff --git a/src/View/Modals/dataTransferModal.tsx b/src/View/Modals/dataTransferModal.tsx index 514a73f9..de25a3b4 100644 --- a/src/View/Modals/dataTransferModal.tsx +++ b/src/View/Modals/dataTransferModal.tsx @@ -1,15 +1,13 @@ import * as React from "bloatless-react"; - +import { ViewController } from "../viewController"; +import { StringToTextSpan } from "../Components/textSpan"; import FileTransferViewModel, { FileTransferModals, FileTransferOption, } from "../../ViewModel/Global/fileTransferViewModel"; - +import CoreViewModel from "../../ViewModel/Global/coreViewModel"; import ConnectionViewModel from "../../ViewModel/Global/connectionViewModel"; import StorageModel from "../../Model/Global/storageModel"; -import { StringToTextSpan } from "../Components/textSpan"; -import CoreViewModel from "../../ViewModel/Global/coreViewModel"; -import { ViewController } from "../viewController"; export function DataTransferModalWrapper( coreViewModel: CoreViewModel, diff --git a/src/View/Modals/messageFilterModal.tsx b/src/View/Modals/messageFilterModal.tsx index e58973bf..42c23098 100644 --- a/src/View/Modals/messageFilterModal.tsx +++ b/src/View/Modals/messageFilterModal.tsx @@ -1,10 +1,9 @@ import * as React from "bloatless-react"; - -import CoreViewModel from "../../ViewModel/Global/coreViewModel"; -import MessagePageViewModel from "../../ViewModel/Pages/messagePageViewModel"; -import { MessageReactionFilterButton } from "../Components/messageReactionFilterButton"; -import { ReactionSymbols } from "../../Model/Chat/chatModel"; import { ViewController } from "../viewController"; +import { MessageReactionFilterButton } from "../Components/messageReactionFilterButton"; +import MessagePageViewModel from "../../ViewModel/Pages/messagePageViewModel"; +import CoreViewModel from "../../ViewModel/Global/coreViewModel"; +import { ReactionSymbols } from "../../Model/Chat/chatModel"; export function MessageFilterModal( coreViewModel: CoreViewModel, diff --git a/src/View/Modals/searchModal.tsx b/src/View/Modals/searchModal.tsx index 38bf3512..b79c0c8c 100644 --- a/src/View/Modals/searchModal.tsx +++ b/src/View/Modals/searchModal.tsx @@ -1,10 +1,9 @@ -import * as React from "bloatless-react"; - -import SearchViewModel from "../../ViewModel/Utility/searchViewModel"; -import { StringToOption } from "../Components/option"; import { v4 } from "uuid"; -import CoreViewModel from "../../ViewModel/Global/coreViewModel"; +import * as React from "bloatless-react"; import { ViewController } from "../viewController"; +import { StringToOption } from "../Components/option"; +import SearchViewModel from "../../ViewModel/Utility/searchViewModel"; +import CoreViewModel from "../../ViewModel/Global/coreViewModel"; export function SearchModal( coreViewModel: CoreViewModel, diff --git a/src/View/Modals/settingsModal.tsx b/src/View/Modals/settingsModal.tsx index f4950ac7..a37455d6 100644 --- a/src/View/Modals/settingsModal.tsx +++ b/src/View/Modals/settingsModal.tsx @@ -1,18 +1,15 @@ import * as React from "bloatless-react"; - +import { SplitModal } from "./splitModal"; import { languageNames } from "../translations"; +import { PlaceholderView } from "../Components/placeholderView"; +import { OptionButtonList } from "../Components/optionButtonList"; +import { NavigationButton } from "../Components/navigationButton"; +import { InfoTile } from "../Components/infoTile"; import SettingsViewModel, { SettingsModalPages, } from "../../ViewModel/Global/settingsViewModel"; -import { SplitModal } from "./splitModal"; -import { InfoTile } from "../Components/infoTile"; -import { Option } from "../Components/option"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; import { Languages, ThemeSettings } from "../../Model/Global/settingsModel"; -import { OptionButtonList } from "../Components/optionButtonList"; -import { stringify } from "../../Model/Utility/utility"; -import { PlaceholderView } from "../Components/placeholderView"; -import { NavigationButton } from "../Components/navigationButton"; export function SettingsModal( coreViewModel: CoreViewModel, diff --git a/src/View/Modals/splitModal.tsx b/src/View/Modals/splitModal.tsx index 0910ddbb..925fb957 100644 --- a/src/View/Modals/splitModal.tsx +++ b/src/View/Modals/splitModal.tsx @@ -1,7 +1,6 @@ -import CoreViewModel from "../../ViewModel/Global/coreViewModel"; -import "./splitModal.css"; - import * as React from "bloatless-react"; +import "./splitModal.css"; +import CoreViewModel from "../../ViewModel/Global/coreViewModel"; export function SplitModal( coreViewModel: CoreViewModel, diff --git a/src/View/Modals/storageModal.tsx b/src/View/Modals/storageModal.tsx index 7c285d72..7eec9266 100644 --- a/src/View/Modals/storageModal.tsx +++ b/src/View/Modals/storageModal.tsx @@ -1,12 +1,10 @@ import * as React from "bloatless-react"; - -import { PATH_COMPONENT_SEPARATOR } from "../../Model/Global/storageModel"; -import StorageViewModel from "../../ViewModel/Global/storageViewModel"; - import { SplitModal } from "./splitModal"; -import { DangerousActionButton } from "../Components/dangerousActionButton"; import { DirectoryItemList } from "../Components/directoryItemList"; +import { DangerousActionButton } from "../Components/dangerousActionButton"; +import StorageViewModel from "../../ViewModel/Global/storageViewModel"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; +import { PATH_COMPONENT_SEPARATOR } from "../../Model/Global/storageModel"; export function StorageModal( coreViewModel: CoreViewModel, diff --git a/src/View/Modals/taskSettingsModal.tsx b/src/View/Modals/taskSettingsModal.tsx index 9ec1217f..5985f8ac 100644 --- a/src/View/Modals/taskSettingsModal.tsx +++ b/src/View/Modals/taskSettingsModal.tsx @@ -1,15 +1,13 @@ +import { v4 } from "uuid"; import * as React from "bloatless-react"; - import { Entry, Option, StringToOption, VersionIdToOption, } from "../Components/option"; - import { DangerousActionButton } from "../Components/dangerousActionButton"; import TaskViewModel from "../../ViewModel/Pages/taskViewModel"; -import { v4 } from "uuid"; import CoreViewModel from "../../ViewModel/Global/coreViewModel"; export function TaskSettingsModal( diff --git a/src/View/chatPage.tsx b/src/View/chatPage.tsx index ad1074e3..3970f8d2 100644 --- a/src/View/chatPage.tsx +++ b/src/View/chatPage.tsx @@ -1,16 +1,13 @@ -import "./homePage.css"; - import * as React from "bloatless-react"; - -import ChatViewModel, { ChatPageTypes } from "../ViewModel/Chat/chatViewModel"; - -import { CalendarPage } from "./ChatPages/calendarPage"; -import { ChatViewToggleButton } from "./Components/chatViewToggleButton"; -import { MessagePage } from "./ChatPages/messagePage"; -import { SettingsPage } from "./ChatPages/settingsPage"; -import { TaskPage } from "./ChatPages/taskPage"; -import CoreViewModel from "../ViewModel/Global/coreViewModel"; import { ViewController } from "./viewController"; +import "./homePage.css"; +import { ChatViewToggleButton } from "./Components/chatViewToggleButton"; +import { TaskPage } from "./ChatPages/taskPage"; +import { SettingsPage } from "./ChatPages/settingsPage"; +import { MessagePage } from "./ChatPages/messagePage"; +import { CalendarPage } from "./ChatPages/calendarPage"; +import CoreViewModel from "../ViewModel/Global/coreViewModel"; +import ChatViewModel, { ChatPageTypes } from "../ViewModel/Chat/chatViewModel"; export function ChatPage( coreViewModel: CoreViewModel, diff --git a/src/View/chatPageWrapper.tsx b/src/View/chatPageWrapper.tsx index b204e6de..14ad9dcf 100644 --- a/src/View/chatPageWrapper.tsx +++ b/src/View/chatPageWrapper.tsx @@ -1,10 +1,8 @@ -import "./chatPage.css"; - import * as React from "bloatless-react"; - -import ChatListViewModel from "../ViewModel/Chat/chatListViewModel"; +import "./chatPage.css"; import { ChatPage } from "./chatPage"; import CoreViewModel from "../ViewModel/Global/coreViewModel"; +import ChatListViewModel from "../ViewModel/Chat/chatListViewModel"; export function ChatPageWrapper( coreViewModel: CoreViewModel, diff --git a/src/View/homePage.tsx b/src/View/homePage.tsx index 5d671874..c45d69f3 100644 --- a/src/View/homePage.tsx +++ b/src/View/homePage.tsx @@ -1,17 +1,14 @@ -import "./homePage.css"; - import * as React from "bloatless-react"; - -import { Option, StringToOption } from "./Components/option"; - -import ChatListViewModel from "../ViewModel/Chat/chatListViewModel"; -import { ChatViewModelToChatEntry } from "./Components/chatEntry"; -import ConnectionViewModel from "../ViewModel/Global/connectionViewModel"; -import FileTransferViewModel from "../ViewModel/Global/fileTransferViewModel"; -import SettingsViewModel from "../ViewModel/Global/settingsViewModel"; -import StorageViewModel from "../ViewModel/Global/storageViewModel"; -import CoreViewModel from "../ViewModel/Global/coreViewModel"; +import "./homePage.css"; +import { StringToOption } from "./Components/option"; import { HomePageButton } from "./Components/homePageButton"; +import { ChatViewModelToChatEntry } from "./Components/chatEntry"; +import StorageViewModel from "../ViewModel/Global/storageViewModel"; +import SettingsViewModel from "../ViewModel/Global/settingsViewModel"; +import FileTransferViewModel from "../ViewModel/Global/fileTransferViewModel"; +import CoreViewModel from "../ViewModel/Global/coreViewModel"; +import ConnectionViewModel from "../ViewModel/Global/connectionViewModel"; +import ChatListViewModel from "../ViewModel/Chat/chatListViewModel"; export function HomePage( coreViewModel: CoreViewModel, diff --git a/src/ViewModel/Chat/chatListViewModel.ts b/src/ViewModel/Chat/chatListViewModel.ts index eef94501..0c1ebcbc 100644 --- a/src/ViewModel/Chat/chatListViewModel.ts +++ b/src/ViewModel/Chat/chatListViewModel.ts @@ -1,12 +1,11 @@ import * as React from "bloatless-react"; - -import ChatModel from "../../Model/Chat/chatModel"; import ChatViewModel from "./chatViewModel"; -import CoreViewModel from "../Global/coreViewModel"; -import { IndexManager } from "../../Model/Utility/utility"; import SettingsViewModel from "../Global/settingsViewModel"; -import ConnectionViewModel from "../Global/connectionViewModel"; import NotificationViewModel from "../Global/notificationViewModel"; +import CoreViewModel from "../Global/coreViewModel"; +import ConnectionViewModel from "../Global/connectionViewModel"; +import { IndexManager } from "../../Model/Utility/utility"; +import ChatModel from "../../Model/Chat/chatModel"; export default class ChatListViewModel { notificationViewModel: NotificationViewModel; diff --git a/src/ViewModel/Chat/chatMessageViewModel.ts b/src/ViewModel/Chat/chatMessageViewModel.ts index aba237ca..3bf940da 100644 --- a/src/ViewModel/Chat/chatMessageViewModel.ts +++ b/src/ViewModel/Chat/chatMessageViewModel.ts @@ -1,5 +1,6 @@ import * as React from "bloatless-react"; - +import MessagePageViewModel from "../Pages/messagePageViewModel"; +import CoreViewModel from "../Global/coreViewModel"; import { ChatMessage, ChatMessageReaction, @@ -7,9 +8,6 @@ import { ReactionSymbols, } from "../../Model/Chat/chatModel"; -import CoreViewModel from "../Global/coreViewModel"; -import MessagePageViewModel from "../Pages/messagePageViewModel"; - export default class ChatMessageViewModel { // data chatMessage: ChatMessage; diff --git a/src/ViewModel/Chat/chatViewModel.ts b/src/ViewModel/Chat/chatViewModel.ts index 2f4c531b..eb37c335 100644 --- a/src/ViewModel/Chat/chatViewModel.ts +++ b/src/ViewModel/Chat/chatViewModel.ts @@ -1,29 +1,26 @@ import * as React from "bloatless-react"; - +import ChatListViewModel from "./chatListViewModel"; +import TaskPageViewModel from "../Pages/taskPageViewModel"; +import SettingsPageViewModel from "../Pages/settingsPageViewModel"; +import MessagePageViewModel from "../Pages/messagePageViewModel"; import CalendarPageViewModel, { CALENDAR_EVENT_BOARD_ID, } from "../Pages/calendarPageViewModel"; -import ChatModel, { - ChatMessage, - ChatMessageReaction, -} from "../../Model/Chat/chatModel"; +import SettingsViewModel from "../Global/settingsViewModel"; +import NotificationViewModel from "../Global/notificationViewModel"; +import CoreViewModel, { Context, ContextHost } from "../Global/coreViewModel"; +import ConnectionViewModel from "../Global/connectionViewModel"; +import { Colors } from "../../colors"; +import { CommonKeys } from "../../View/keystrokes"; +import { Entry } from "../../View/Components/option"; import StorageModel, { StorageModelSubPaths, filePaths, } from "../../Model/Global/storageModel"; - -import ChatListViewModel from "./chatListViewModel"; -import { Colors } from "../../colors"; -import CoreViewModel, { Context, ContextHost } from "../Global/coreViewModel"; -import { Entry } from "../../View/Components/option"; -import MessagePageViewModel from "../Pages/messagePageViewModel"; -import SettingsPageViewModel from "../Pages/settingsPageViewModel"; -import SettingsViewModel from "../Global/settingsViewModel"; -import TaskPageViewModel from "../Pages/taskPageViewModel"; -import ConnectionViewModel from "../Global/connectionViewModel"; -import NotificationViewModel from "../Global/notificationViewModel"; -import { v4 } from "uuid"; -import { CommonKeys } from "../../View/keystrokes"; +import ChatModel, { + ChatMessage, + ChatMessageReaction, +} from "../../Model/Chat/chatModel"; export default class ChatViewModel extends ContextHost { calendarViewModel: CalendarPageViewModel; diff --git a/src/ViewModel/Global/connectionViewModel.ts b/src/ViewModel/Global/connectionViewModel.ts index 8e3f5d80..2c93c84f 100644 --- a/src/ViewModel/Global/connectionViewModel.ts +++ b/src/ViewModel/Global/connectionViewModel.ts @@ -1,6 +1,4 @@ import * as React from "bloatless-react"; - -import ConnectionModel from "../../Model/Global/connectionModel"; import CoreViewModel from "./coreViewModel"; export default class ConnectionViewModel { diff --git a/src/ViewModel/Global/coreViewModel.ts b/src/ViewModel/Global/coreViewModel.ts index 7344a36e..39201ec9 100644 --- a/src/ViewModel/Global/coreViewModel.ts +++ b/src/ViewModel/Global/coreViewModel.ts @@ -1,12 +1,12 @@ +import { v4 } from "uuid"; import * as React from "bloatless-react"; -import SettingsModel from "../../Model/Global/settingsModel"; import { allTranslations, Translations } from "../../View/translations"; +import { HandlerManager } from "../../Model/Utility/utility"; import StorageModel from "../../Model/Global/storageModel"; +import SettingsModel from "../../Model/Global/settingsModel"; +import FileTransferModel from "../../Model/Global/fileTransferModel"; import ConnectionModel from "../../Model/Global/connectionModel"; import ChatListModel from "../../Model/Chat/chatListModel"; -import FileTransferModel from "../../Model/Global/fileTransferModel"; -import { v4 } from "uuid"; -import { HandlerManager } from "../../Model/Utility/utility"; export default class CoreViewModel { readonly BUILD = "Build 26.04.16.A"; @@ -72,7 +72,8 @@ export default class CoreViewModel { handleChron = (): void => { const newDate = new Date(); - if (this.unwrappedTodayDate.toDateString() == newDate.toDateString()) return; + if (this.unwrappedTodayDate.toDateString() == newDate.toDateString()) + return; this.todayDate.value = new Date(); }; diff --git a/src/ViewModel/Global/fileTransferViewModel.ts b/src/ViewModel/Global/fileTransferViewModel.ts index 40a9b503..ec71c765 100644 --- a/src/ViewModel/Global/fileTransferViewModel.ts +++ b/src/ViewModel/Global/fileTransferViewModel.ts @@ -1,16 +1,12 @@ import * as React from "bloatless-react"; - -import FileTransferModel, { - TransferData, -} from "../../Model/Global/fileTransferModel"; +import CoreViewModel, { Context } from "./coreViewModel"; +import { CommonKeys } from "../../View/keystrokes"; import StorageModel, { StorageModelSubPaths, filePaths, } from "../../Model/Global/storageModel"; - +import { TransferData } from "../../Model/Global/fileTransferModel"; import ChatModel from "../../Model/Chat/chatModel"; -import CoreViewModel, { Context } from "./coreViewModel"; -import { CommonKeys } from "../../View/keystrokes"; export default class FileTransferViewModel extends Context { // state diff --git a/src/ViewModel/Global/notificationViewModel.ts b/src/ViewModel/Global/notificationViewModel.ts index 3abee98a..cb71a5d3 100644 --- a/src/ViewModel/Global/notificationViewModel.ts +++ b/src/ViewModel/Global/notificationViewModel.ts @@ -1,7 +1,7 @@ import * as React from "bloatless-react"; -import ChatModel, { ChatMessage } from "../../Model/Chat/chatModel"; -import ChatListViewModel from "../Chat/chatListViewModel"; import { ChatPageTypes } from "../Chat/chatViewModel"; +import ChatListViewModel from "../Chat/chatListViewModel"; +import ChatModel, { ChatMessage } from "../../Model/Chat/chatModel"; export default class NotificationViewModel { // data diff --git a/src/ViewModel/Global/settingsViewModel.ts b/src/ViewModel/Global/settingsViewModel.ts index 00e6fccf..335d579c 100644 --- a/src/ViewModel/Global/settingsViewModel.ts +++ b/src/ViewModel/Global/settingsViewModel.ts @@ -1,8 +1,7 @@ import * as React from "bloatless-react"; - import CoreViewModel, { Context } from "./coreViewModel"; -import { Languages, ThemeSettings } from "../../Model/Global/settingsModel"; import { CommonKeys } from "../../View/keystrokes"; +import { Languages, ThemeSettings } from "../../Model/Global/settingsModel"; export default class SettingsViewModel extends Context { // state diff --git a/src/ViewModel/Global/storageViewModel.ts b/src/ViewModel/Global/storageViewModel.ts index cb1848a2..4fb362e9 100644 --- a/src/ViewModel/Global/storageViewModel.ts +++ b/src/ViewModel/Global/storageViewModel.ts @@ -1,12 +1,10 @@ import * as React from "bloatless-react"; - +import CoreViewModel, { Context } from "./coreViewModel"; +import { CommonKeys } from "../../View/keystrokes"; import StorageModel, { PATH_COMPONENT_SEPARATOR, } from "../../Model/Global/storageModel"; -import CoreViewModel, { Context } from "./coreViewModel"; -import { CommonKeys } from "../../View/keystrokes"; - export default class StorageViewModel extends Context { // state isShowingStorageModal: React.State = new React.State(false); diff --git a/src/ViewModel/Pages/boardViewModel.ts b/src/ViewModel/Pages/boardViewModel.ts index a3a4bb92..eb53e2b1 100644 --- a/src/ViewModel/Pages/boardViewModel.ts +++ b/src/ViewModel/Pages/boardViewModel.ts @@ -1,19 +1,17 @@ import * as React from "bloatless-react"; - +import TaskViewModel from "./taskViewModel"; +import TaskPageViewModel from "./taskPageViewModel"; +import TaskContainingPageViewModel from "./taskContainingPageViewModel"; +import SearchViewModel from "../Utility/searchViewModel"; +import CoreViewModel from "../Global/coreViewModel"; +import ChatViewModel, { ChatPageTypes } from "../Chat/chatViewModel"; +import { Colors } from "../../colors"; +import { CommonKeys } from "../../View/keystrokes"; import BoardsAndTasksModel, { BoardInfoFileContent, TaskFileContent, } from "../../Model/Files/boardsAndTasksModel"; -import ChatViewModel, { ChatPageTypes } from "../Chat/chatViewModel"; -import { Colors } from "../../colors"; -import CoreViewModel from "../Global/coreViewModel"; -import SearchViewModel from "../Utility/searchViewModel"; -import TaskContainingPageViewModel from "./taskContainingPageViewModel"; -import TaskPageViewModel from "./taskPageViewModel"; -import TaskViewModel from "./taskViewModel"; -import { CommonKeys } from "../../View/keystrokes"; - export default class BoardViewModel extends TaskContainingPageViewModel { // state name: React.State = new React.State(""); diff --git a/src/ViewModel/Pages/calendarPageViewModel.ts b/src/ViewModel/Pages/calendarPageViewModel.ts index fd722bae..56d15136 100644 --- a/src/ViewModel/Pages/calendarPageViewModel.ts +++ b/src/ViewModel/Pages/calendarPageViewModel.ts @@ -1,15 +1,13 @@ import * as React from "bloatless-react"; - +import TaskViewModel from "./taskViewModel"; +import TaskContainingPageViewModel from "./taskContainingPageViewModel"; +import CoreViewModel from "../Global/coreViewModel"; +import ChatViewModel, { ChatPageTypes } from "../Chat/chatViewModel"; +import { CommonKeys } from "../../View/keystrokes"; +import CalendarModel, { MonthGrid } from "../../Model/Files/calendarModel"; import BoardsAndTasksModel, { TaskFileContent, } from "../../Model/Files/boardsAndTasksModel"; -import CalendarModel, { MonthGrid } from "../../Model/Files/calendarModel"; - -import ChatViewModel, { ChatPageTypes } from "../Chat/chatViewModel"; -import CoreViewModel from "../Global/coreViewModel"; -import TaskContainingPageViewModel from "./taskContainingPageViewModel"; -import TaskViewModel from "./taskViewModel"; -import { CommonKeys } from "../../View/keystrokes"; export const CALENDAR_EVENT_BOARD_ID = "events"; diff --git a/src/ViewModel/Pages/homeViewModel.ts b/src/ViewModel/Pages/homeViewModel.ts index 424c67b6..720a0277 100644 --- a/src/ViewModel/Pages/homeViewModel.ts +++ b/src/ViewModel/Pages/homeViewModel.ts @@ -1,9 +1,9 @@ -import { CommonKeys } from "../../View/keystrokes"; -import ConnectionViewModel from "../Global/connectionViewModel"; -import CoreViewModel, { Context } from "../Global/coreViewModel"; -import FileTransferViewModel from "../Global/fileTransferViewModel"; -import SettingsViewModel from "../Global/settingsViewModel"; import StorageViewModel from "../Global/storageViewModel"; +import SettingsViewModel from "../Global/settingsViewModel"; +import FileTransferViewModel from "../Global/fileTransferViewModel"; +import CoreViewModel, { Context } from "../Global/coreViewModel"; +import ConnectionViewModel from "../Global/connectionViewModel"; +import { CommonKeys } from "../../View/keystrokes"; export default class HomeViewModel extends Context { constructor( diff --git a/src/ViewModel/Pages/messagePageViewModel.ts b/src/ViewModel/Pages/messagePageViewModel.ts index dcc2fc86..53727277 100644 --- a/src/ViewModel/Pages/messagePageViewModel.ts +++ b/src/ViewModel/Pages/messagePageViewModel.ts @@ -1,16 +1,14 @@ import * as React from "bloatless-react"; - +import SearchViewModel from "../Utility/searchViewModel"; +import CoreViewModel, { Context } from "../Global/coreViewModel"; +import ChatViewModel, { ChatPageTypes } from "../Chat/chatViewModel"; +import ChatMessageViewModel from "../Chat/chatMessageViewModel"; +import { CommonKeys } from "../../View/keystrokes"; import { ChatMessage, ChatMessageReaction, ReactionSymbols, } from "../../Model/Chat/chatModel"; -import ChatMessageViewModel from "../Chat/chatMessageViewModel"; -import ChatViewModel, { ChatPageTypes } from "../Chat/chatViewModel"; -import CoreViewModel, { Context } from "../Global/coreViewModel"; -import SearchViewModel from "../Utility/searchViewModel"; -import { v4 } from "uuid"; -import { CommonKeys } from "../../View/keystrokes"; export default class MessagePageViewModel extends Context { // state diff --git a/src/ViewModel/Pages/settingsPageViewModel.ts b/src/ViewModel/Pages/settingsPageViewModel.ts index 2c7c6c07..e4cec776 100644 --- a/src/ViewModel/Pages/settingsPageViewModel.ts +++ b/src/ViewModel/Pages/settingsPageViewModel.ts @@ -1,8 +1,7 @@ import * as React from "bloatless-react"; - +import CoreViewModel, { Context } from "../Global/coreViewModel"; import ChatViewModel, { ChatPageTypes } from "../Chat/chatViewModel"; import { Colors } from "../../colors"; -import CoreViewModel, { Context } from "../Global/coreViewModel"; export default class SettingsPageViewModel extends Context { // state diff --git a/src/ViewModel/Pages/taskContainingPageViewModel.ts b/src/ViewModel/Pages/taskContainingPageViewModel.ts index bdc810aa..5fc5e676 100644 --- a/src/ViewModel/Pages/taskContainingPageViewModel.ts +++ b/src/ViewModel/Pages/taskContainingPageViewModel.ts @@ -1,14 +1,12 @@ import * as React from "bloatless-react"; - +import TaskViewModel from "./taskViewModel"; +import CoreViewModel, { Context } from "../Global/coreViewModel"; +import ChatViewModel from "../Chat/chatViewModel"; +import { IndexManager } from "../../Model/Utility/utility"; import BoardsAndTasksModel, { TaskFileContent, } from "../../Model/Files/boardsAndTasksModel"; -import ChatViewModel from "../Chat/chatViewModel"; -import CoreViewModel, { Context } from "../Global/coreViewModel"; -import { IndexManager } from "../../Model/Utility/utility"; -import TaskViewModel from "./taskViewModel"; - export default class TaskContainingPageViewModel extends Context { // state readonly taskIndexManager = new IndexManager( diff --git a/src/ViewModel/Pages/taskPageViewModel.ts b/src/ViewModel/Pages/taskPageViewModel.ts index 8944b8da..5c7ae43c 100644 --- a/src/ViewModel/Pages/taskPageViewModel.ts +++ b/src/ViewModel/Pages/taskPageViewModel.ts @@ -1,15 +1,13 @@ import * as React from "bloatless-react"; - +import BoardViewModel from "./boardViewModel"; +import CoreViewModel, { ContextHost } from "../Global/coreViewModel"; +import ChatViewModel, { ChatPageTypes } from "../Chat/chatViewModel"; +import { CommonKeys } from "../../View/keystrokes"; +import { IndexManager } from "../../Model/Utility/utility"; import BoardsAndTasksModel, { BoardInfoFileContent, } from "../../Model/Files/boardsAndTasksModel"; -import BoardViewModel, { BoardPageTypes } from "./boardViewModel"; -import ChatViewModel, { ChatPageTypes } from "../Chat/chatViewModel"; -import CoreViewModel, { Context, ContextHost } from "../Global/coreViewModel"; -import { IndexManager } from "../../Model/Utility/utility"; -import { CommonKeys } from "../../View/keystrokes"; - export default class TaskPageViewModel extends ContextHost { // data readonly boardIndexManager = new IndexManager( diff --git a/src/ViewModel/Pages/taskViewModel.ts b/src/ViewModel/Pages/taskViewModel.ts index a2402165..6578a7b2 100644 --- a/src/ViewModel/Pages/taskViewModel.ts +++ b/src/ViewModel/Pages/taskViewModel.ts @@ -1,15 +1,13 @@ import * as React from "bloatless-react"; - +import TaskContainingPageViewModel from "./taskContainingPageViewModel"; +import CoreViewModel, { Context } from "../Global/coreViewModel"; +import ChatViewModel from "../Chat/chatViewModel"; +import { ViewController } from "../../View/viewController"; +import { CommonKeys } from "../../View/keystrokes"; +import { localeCompare, padZero } from "../../Model/Utility/utility"; import BoardsAndTasksModel, { TaskFileContent, } from "../../Model/Files/boardsAndTasksModel"; -import { localeCompare, padZero } from "../../Model/Utility/utility"; - -import ChatViewModel from "../Chat/chatViewModel"; -import CoreViewModel, { Context } from "../Global/coreViewModel"; -import TaskContainingPageViewModel from "./taskContainingPageViewModel"; -import { CommonKeys } from "../../View/keystrokes"; -import { ViewController } from "../../View/viewController"; export default class TaskViewModel extends Context { // util diff --git a/src/ViewModel/Utility/searchViewModel.ts b/src/ViewModel/Utility/searchViewModel.ts index f29e4cbc..b3d13533 100644 --- a/src/ViewModel/Utility/searchViewModel.ts +++ b/src/ViewModel/Utility/searchViewModel.ts @@ -1,5 +1,4 @@ import * as React from "bloatless-react"; - import { checkDoesObjectMatchSearch } from "../../Model/Utility/utility"; export default class SearchViewModel { diff --git a/src/ViewModel/Utility/taskPropertyBulkChangeViewModel.ts b/src/ViewModel/Utility/taskPropertyBulkChangeViewModel.ts index 3729159c..ffd60ca2 100644 --- a/src/ViewModel/Utility/taskPropertyBulkChangeViewModel.ts +++ b/src/ViewModel/Utility/taskPropertyBulkChangeViewModel.ts @@ -1,5 +1,4 @@ import * as React from "bloatless-react"; - import TaskViewModel from "../Pages/taskViewModel"; export default class TaskPropertyBulkChangeViewModel { diff --git a/src/index.tsx b/src/index.tsx index f90abc02..7efc731f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,28 +1,26 @@ -import "./base.css"; +import * as React from "bloatless-react"; import "./colors.css"; import "./coloredTile.css"; - -import * as React from "bloatless-react"; - -import ChatListModel from "./Model/Chat/chatListModel"; -import ChatListViewModel from "./ViewModel/Chat/chatListViewModel"; -import { ChatPageWrapper } from "./View/chatPageWrapper"; -import { ConnectionModal } from "./View/Modals/connectionModal"; -import ConnectionModel from "./Model/Global/connectionModel"; -import ConnectionViewModel from "./ViewModel/Global/connectionViewModel"; -import CoreViewModel from "./ViewModel/Global/coreViewModel"; -import { DataTransferModalWrapper } from "./View/Modals/dataTransferModal"; -import FileTransferModel from "./Model/Global/fileTransferModel"; -import FileTransferViewModel from "./ViewModel/Global/fileTransferViewModel"; -import { HomePage } from "./View/homePage"; -import SettingsModel from "./Model/Global/settingsModel"; -import SettingsViewModel from "./ViewModel/Global/settingsViewModel"; -import { StorageModal } from "./View/Modals/storageModal"; -import StorageModel from "./Model/Global/storageModel"; -import StorageViewModel from "./ViewModel/Global/storageViewModel"; -import v1Upgrader from "./Upgrader/v1"; -import { SettingsModal } from "./View/Modals/settingsModal"; +import "./base.css"; import HomeViewModel from "./ViewModel/Pages/homeViewModel"; +import StorageViewModel from "./ViewModel/Global/storageViewModel"; +import SettingsViewModel from "./ViewModel/Global/settingsViewModel"; +import FileTransferViewModel from "./ViewModel/Global/fileTransferViewModel"; +import CoreViewModel from "./ViewModel/Global/coreViewModel"; +import ConnectionViewModel from "./ViewModel/Global/connectionViewModel"; +import ChatListViewModel from "./ViewModel/Chat/chatListViewModel"; +import { HomePage } from "./View/homePage"; +import { ChatPageWrapper } from "./View/chatPageWrapper"; +import { StorageModal } from "./View/Modals/storageModal"; +import { SettingsModal } from "./View/Modals/settingsModal"; +import { DataTransferModalWrapper } from "./View/Modals/dataTransferModal"; +import { ConnectionModal } from "./View/Modals/connectionModal"; +import v1Upgrader from "./Upgrader/v1"; +import StorageModel from "./Model/Global/storageModel"; +import SettingsModel from "./Model/Global/settingsModel"; +import FileTransferModel from "./Model/Global/fileTransferModel"; +import ConnectionModel from "./Model/Global/connectionModel"; +import ChatListModel from "./Model/Chat/chatListModel"; // models const storageModel = new StorageModel();