From 551d2c3aba6f2300896576a85e50071655f3ba4f Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Wed, 1 Apr 2026 08:46:10 +0000 Subject: [PATCH] restore modal animation on desktop --- dist/index.css | 1200 ++-- dist/index.html | 56 +- dist/index.js | 14191 ++++++++++++++++++++++++------------------- dist/manifest.json | 16 +- dist/styles.css | 914 +-- dist/support.js | 43 +- dist/sw.js | 37 +- 7 files changed, 9267 insertions(+), 7190 deletions(-) diff --git a/dist/index.css b/dist/index.css index facc517d..b27412e6 100644 --- a/dist/index.css +++ b/dist/index.css @@ -3,266 +3,265 @@ #home-page #chat-section, #chat-page, #chat-page #main > div { - animation: slide-up var(--animation-duration) ease; + 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.3; + 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.3; } main { - z-index: 2; + 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); + 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)); + 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%); + --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%); + --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% + ); } @media (prefers-color-scheme: dark) { - #background-wrapper { - opacity: 0.2; - } - #background-wrapper #sky { - --color-1: hsl(188, 100%, 30%); - --color-2: hsl(194, 72%, 24%); - } - #background-wrapper #grass-1 { - --color-1: hsl(120, 30%, 30%); - --color-2: hsl(120, 30%, 24%); - } - #background-wrapper #grass-2 { - --color-1: hsl(110, 60%, 30%); - --color-2: hsl(110, 50%, 24%); - } + #background-wrapper { + opacity: 0.2; + } + #background-wrapper #sky { + --color-1: hsl(188, 100%, 30%); + --color-2: hsl(194, 72%, 24%); + } + #background-wrapper #grass-1 { + --color-1: hsl(120, 30%, 30%); + --color-2: hsl(120, 30%, 24%); + } + #background-wrapper #grass-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)); - --primary-hover: hsl(var(--hue), 95%, var(--accent-luma)); - --primary-active: hsl(var(--hue), 80%, var(--accent-luma)); - --text-on-color: hsl(0, 0%, var(--accent-text-luma)); - --link: hsl(var(--hue), 100%, var(--accent-luma)); - --surface: hsla(var(--hue), 80%, 30%, 5%); - --surface-hover: hsla(var(--hue), 100%, 5%, 10%); - --surface-active: hsla(var(--hue), 100%, 5%, 15%); +[color]:not([color="standard"]) { + --primary: hsl(var(--hue), 100%, var(--accent-luma)); + --primary-hover: hsl(var(--hue), 95%, var(--accent-luma)); + --primary-active: hsl(var(--hue), 80%, var(--accent-luma)); + --text-on-color: hsl(0, 0%, var(--accent-text-luma)); + --link: hsl(var(--hue), 100%, var(--accent-luma)); + --surface: hsla(var(--hue), 80%, 30%, 5%); + --surface-hover: hsla(var(--hue), 100%, 5%, 10%); + --surface-active: hsla(var(--hue), 100%, 5%, 15%); } @media (prefers-color-scheme: dark) { - [color]:not([color=standard]) { - --accent-luma: var(--accent-luma-dark); - --accent-text-luma: var(--accent-text-luma-dark); - --surface: hsla(var(--hue), 100%, 95%, 5%); - --surface-hover: hsla(var(--hue), 100%, 95%, 10%); - --surface-active: hsla(var(--hue), 100%, 95%, 15%); - } + [color]:not([color="standard"]) { + --accent-luma: var(--accent-luma-dark); + --accent-text-luma: var(--accent-text-luma-dark); + --surface: hsla(var(--hue), 100%, 95%, 5%); + --surface-hover: hsla(var(--hue), 100%, 95%, 10%); + --surface-active: hsla(var(--hue), 100%, 95%, 15%); + } } -[color]:not([color=standard]).tile { - --gradient-luma-1: 70%; - --gradient-luma-2: 90%; - --hover-luma: 75%; - --active-luma: 80%; - --text-luma: 20%; - --surface: - linear-gradient( - - 120deg, - hsl(var(--hue), 100%, var(--gradient-luma-1)), - hsla(var(--hue), 100%, var(--gradient-luma-2), 90%) ); - --surface-hover: hsl(var(--hue), 100%, var(--hover-luma)); - --surface-active: hsl(var(--hue), 100%, var(--active-luma)); - --text-normal: hsl(var(--hue), 100%, var(--text-luma)); +[color]:not([color="standard"]).tile { + --gradient-luma-1: 70%; + --gradient-luma-2: 90%; + --hover-luma: 75%; + --active-luma: 80%; + --text-luma: 20%; + --surface: linear-gradient( + 120deg, + hsl(var(--hue), 100%, var(--gradient-luma-1)), + hsla(var(--hue), 100%, var(--gradient-luma-2), 90%) + ); + --surface-hover: hsl(var(--hue), 100%, var(--hover-luma)); + --surface-active: hsl(var(--hue), 100%, var(--active-luma)); + --text-normal: hsl(var(--hue), 100%, var(--text-luma)); } @media (prefers-color-scheme: dark) { - [color]:not([color=standard]).tile { - --gradient-luma-1: 20%; - --gradient-luma-2: 15%; - --hover-luma: 25%; - --active-luma: 30%; - --text-luma: 90%; - } + [color]:not([color="standard"]).tile { + --gradient-luma-1: 20%; + --gradient-luma-2: 15%; + --hover-luma: 25%; + --active-luma: 30%; + --text-luma: 90%; + } } -[color]:not([color=standard]).fill-color { - color: white; - background-color: var(--primary); +[color]:not([color="standard"]).fill-color { + color: white; + background-color: var(--primary); } -[color]:not([color=standard])[selected].fill-color { - color: var(--primary); +[color]:not([color="standard"])[selected].fill-color { + color: var(--primary); } @media (min-width: 1024px) { - [color]:not([color=standard]).background { - --luma: var(--background-luma); - --sat: 80%; - background: hsl(var(--hue), var(--sat), var(--luma)); - } - @media (prefers-color-scheme: dark) { - [color]:not([color=standard]).background { - --luma: var(--background-luma-dark); - --sat: 40%; + [color]:not([color="standard"]).background { + --luma: var(--background-luma); + --sat: 80%; + background: hsl(var(--hue), var(--sat), var(--luma)); + } + @media (prefers-color-scheme: dark) { + [color]:not([color="standard"]).background { + --luma: var(--background-luma-dark); + --sat: 40%; + } + } + [color="standard"].background { + background: var(--surface); } - } - [color=standard].background { - background: var(--surface); - } } -[color]:not([color=standard]).subtle-background { - --luma: var(--subtle-luma); - background: hsl(var(--hue), 100%, var(--luma), 50%); +[color]:not([color="standard"]).subtle-background { + --luma: var(--subtle-luma); + background: hsl(var(--hue), 100%, var(--luma), 50%); } @media (prefers-color-scheme: dark) { - [color]:not([color=standard]).subtle-background { - --luma: var(--subtle-luma-dark); - } - @media (display-mode: standalone) { - [color]:not([color=standard]).subtle-background { - --luma: 0%; + [color]:not([color="standard"]).subtle-background { + --luma: var(--subtle-luma-dark); + } + @media (display-mode: standalone) { + [color]:not([color="standard"]).subtle-background { + --luma: 0%; + } } - } } -[color=coral] { - --hue: 20; - --accent-luma: 50%; - --accent-luma-dark: 70%; - --accent-text-luma: 100%; - --accent-text-luma-dark: 100%; - --background-luma: 90%; - --background-luma-dark: 11%; - --subtle-luma: 95%; - --subtle-luma-dark: 5%; +[color="coral"] { + --hue: 20; + --accent-luma: 50%; + --accent-luma-dark: 70%; + --accent-text-luma: 100%; + --accent-text-luma-dark: 100%; + --background-luma: 90%; + --background-luma-dark: 11%; + --subtle-luma: 95%; + --subtle-luma-dark: 5%; } -[color=yellow] { - --hue: 50; - --accent-luma: 50%; - --accent-luma-dark: 60%; - --accent-text-luma: 0%; - --accent-text-luma-dark: 0%; - --background-luma: 85%; - --background-luma-dark: 11%; - --subtle-luma: 95%; - --subtle-luma-dark: 5%; +[color="yellow"] { + --hue: 50; + --accent-luma: 50%; + --accent-luma-dark: 60%; + --accent-text-luma: 0%; + --accent-text-luma-dark: 0%; + --background-luma: 85%; + --background-luma-dark: 11%; + --subtle-luma: 95%; + --subtle-luma-dark: 5%; } -[color=mint] { - --hue: 150; - --accent-luma: 40%; - --accent-luma-dark: 60%; - --accent-text-luma: 100%; - --accent-text-luma-dark: 0%; - --background-luma: 85%; - --background-luma-dark: 12%; - --subtle-luma: 95%; - --subtle-luma-dark: 5%; +[color="mint"] { + --hue: 150; + --accent-luma: 40%; + --accent-luma-dark: 60%; + --accent-text-luma: 100%; + --accent-text-luma-dark: 0%; + --background-luma: 85%; + --background-luma-dark: 12%; + --subtle-luma: 95%; + --subtle-luma-dark: 5%; } -[color=lightblue] { - --hue: 180; - --accent-luma: 40%; - --accent-luma-dark: 60%; - --accent-text-luma: 0%; - --accent-text-luma-dark: 0%; - --background-luma: 85%; - --background-luma-dark: 12%; - --subtle-luma: 95%; - --subtle-luma-dark: 5%; +[color="lightblue"] { + --hue: 180; + --accent-luma: 40%; + --accent-luma-dark: 60%; + --accent-text-luma: 0%; + --accent-text-luma-dark: 0%; + --background-luma: 85%; + --background-luma-dark: 12%; + --subtle-luma: 95%; + --subtle-luma-dark: 5%; } -[color=blue] { - --hue: 220; - --accent-luma: 50%; - --accent-luma-dark: 70%; - --accent-text-luma: 100%; - --accent-text-luma-dark: 100%; - --background-luma: 90%; - --background-luma-dark: 12%; - --subtle-luma: 95%; - --subtle-luma-dark: 5%; +[color="blue"] { + --hue: 220; + --accent-luma: 50%; + --accent-luma-dark: 70%; + --accent-text-luma: 100%; + --accent-text-luma-dark: 100%; + --background-luma: 90%; + --background-luma-dark: 12%; + --subtle-luma: 95%; + --subtle-luma-dark: 5%; } -[color=purple] { - --hue: 280; - --accent-luma: 50%; - --accent-luma-dark: 70%; - --accent-text-luma: 100%; - --accent-text-luma-dark: 100%; - --background-luma: 90%; - --background-luma-dark: 11%; - --subtle-luma: 95%; - --subtle-luma-dark: 5%; +[color="purple"] { + --hue: 280; + --accent-luma: 50%; + --accent-luma-dark: 70%; + --accent-text-luma: 100%; + --accent-text-luma-dark: 100%; + --background-luma: 90%; + --background-luma-dark: 11%; + --subtle-luma: 95%; + --subtle-luma-dark: 5%; } /* src/coloredTile.css */ .colored-tile { - position: relative; - display: flex; - align-items: flex-end !important; - justify-content: start; - overflow: hidden; + position: relative; + display: flex; + align-items: flex-end !important; + justify-content: start; + overflow: hidden; } .colored-tile h2 { - margin: 0px; + margin: 0px; } .colored-tile .shadow { - position: absolute; - left: -15px; - bottom: -45px; - font-size: 7rem; - opacity: 0.1; - white-space: nowrap; + position: absolute; + left: -15px; + bottom: -45px; + font-size: 7rem; + opacity: 0.1; + white-space: nowrap; } /* src/View/chatPage.css */ #chat-page-wrapper { - display: none; + display: none; } body[showing-chat] #home-page { - display: none; + display: none; } body[showing-chat] #chat-page-wrapper { - display: flex; - position: absolute; - top: 0px; - left: 0px; - right: 0px; - bottom: 0px; - background-color: var(--background-glass); - z-index: 2; + display: flex; + position: absolute; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + background-color: var(--background-glass); + z-index: 2; } #chat-page { - --ribbon-size: var(--control-height); + --ribbon-size: var(--control-height); } #chat-page > div, #chat-page > div > #ribbon, @@ -273,569 +272,642 @@ body[showing-chat] #chat-page-wrapper { #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar, #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar > span, #chat-page > div > #main > div > .pane-wrapper > .pane > .content { - display: flex; + display: flex; } #chat-page > div { - padding: 0px; - overflow: hidden; + padding: 0px; + overflow: hidden; } #chat-page > div > #main { - width: calc(100% - var(--ribbon-size) - 1px); + width: calc(100% - var(--ribbon-size) - 1px); } #chat-page #main > div, #chat-page > div > #main > div > .pane-wrapper, #chat-page > div > #main > div > .pane-wrapper > .pane, #chat-page > div > #main > div > .pane-wrapper > .pane > .content { - width: 100%; - height: 100%; - flex: 1; + width: 100%; + height: 100%; + flex: 1; } #chat-page > div > #main > div > .pane-wrapper, #chat-page > div > #main > div > .pane-wrapper > .pane { - flex-direction: column; - scroll-snap-align: center; - min-width: 100%; - overflow: hidden; + flex-direction: column; + scroll-snap-align: center; + min-width: 100%; + overflow: hidden; } #chat-page > div > #main > div > .pane-wrapper > .pane:not(.side), #chat-page > div > #main > div > .pane-wrapper > .pane:not(.side) .content { - animation: slide-up var(--animation-duration); + animation: slide-up var(--animation-duration); } @media (min-width: 1024px) { - #chat-page > div > #main > div > .pane-wrapper { - min-width: unset; - } - #chat-page > div > #main > div > .pane-wrapper.side { - width: 18rem; - min-width: 18rem; - max-width: 18rem; - } + #chat-page > div > #main > div > .pane-wrapper { + min-width: unset; + } + #chat-page > div > #main > div > .pane-wrapper.side { + width: 18rem; + min-width: 18rem; + max-width: 18rem; + } } #chat-page > div > #main > div > .pane-wrapper > .pane > .content { - flex-direction: column; - overflow: auto; - padding: var(--content-padding); + flex-direction: column; + overflow: auto; + padding: var(--content-padding); } #chat-page #ribbon, #chat-page #main, #chat-page #main .content { - flex-direction: column; + flex-direction: column; } #chat-page > div > #ribbon, #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar { - align-items: center; + align-items: center; } #chat-page > div > #ribbon { - width: calc(var(--ribbon-size) + 1px); - border-right: 1px solid var(--border); - padding-bottom: var(--safe-area-bottom); + width: calc(var(--ribbon-size) + 1px); + border-right: 1px solid var(--border); + padding-bottom: var(--safe-area-bottom); } #chat-page > div > #ribbon > span, -#chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar > span.ribbon { - gap: var(--gap-between-tiles); - padding: var(--gap-between-tiles); +#chat-page + > div + > #main + > div + > .pane-wrapper + > .pane + > .toolbar + > span.ribbon { + gap: var(--gap-between-tiles); + padding: var(--gap-between-tiles); } #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar { - justify-content: start; - justify-content: space-between; + justify-content: start; + justify-content: space-between; } #chat-page > div > #ribbon > span { - height: 100%; - flex: 1; - overflow-y: auto; - flex-direction: column; - justify-content: end; + height: 100%; + flex: 1; + overflow-y: auto; + flex-direction: column; + justify-content: end; } #chat-page > div > #ribbon button, #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar button { - padding: 0px; - width: var(--control-height); - max-width: var(--control-height); - height: var(--control-height); - min-height: var(--control-height); - align-items: center; - justify-content: center; + padding: 0px; + width: var(--control-height); + max-width: var(--control-height); + height: var(--control-height); + min-height: var(--control-height); + align-items: center; + justify-content: center; } #chat-page > div > #ribbon button.ribbon-button, -#chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar button.ribbon-button { - min-width: calc(var(--control-height) - 2 * var(--gap-between-tiles)); - width: calc(var(--control-height) - 2 * var(--gap-between-tiles)); - max-width: calc(var(--control-height) - 2 * var(--gap-between-tiles)); - min-height: unset; - height: calc(var(--control-height) - 2 * var(--gap-between-tiles)); - color: var(--primary); +#chat-page + > div + > #main + > div + > .pane-wrapper + > .pane + > .toolbar + button.ribbon-button { + min-width: calc(var(--control-height) - 2 * var(--gap-between-tiles)); + width: calc(var(--control-height) - 2 * var(--gap-between-tiles)); + max-width: calc(var(--control-height) - 2 * var(--gap-between-tiles)); + min-height: unset; + height: calc(var(--control-height) - 2 * var(--gap-between-tiles)); + color: var(--primary); } #chat-page > div > #ribbon button > .icon, -#chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar button > .icon { - font-size: 1.1rem; +#chat-page + > div + > #main + > div + > .pane-wrapper + > .pane + > .toolbar + button + > .icon { + font-size: 1.1rem; } #chat-page > div > #ribbon > span { - width: 100%; + width: 100%; } #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar { - height: var(--ribbon-size); - overflow-x: auto; - padding: 0px; + height: var(--ribbon-size); + overflow-x: auto; + padding: 0px; } #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar > span { - display: flex; - align-items: center; + display: flex; + align-items: center; } #chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar > span.title { - padding-left: var(--content-padding); + padding-left: var(--content-padding); } -#chat-page > div > #main > div > .pane-wrapper > .pane > .toolbar :is(input, select, button) { - height: 100%; - flex: 1; +#chat-page + > div + > #main + > div + > .pane-wrapper + > .pane + > .toolbar + :is(input, select, button) { + height: 100%; + 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; - padding: 0px; + 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; + 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)); + 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)); + margin-top: var(--content-padding); + display: grid; + gap: var(--gap-between-tiles); + grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); } .chat-entry[unread] { - border: var(--outline-width) solid black; + border: var(--outline-width) solid black; } .chat-entry[unread]:not(:hover):not(:active) { - background: rgb(255, 217, 0) !important; - color: black; + background: rgb(255, 217, 0) !important; + color: black; } @media (min-width: 640px) { - #home-page > div { - overflow-x: hidden; - } - #home-page #overview-section { - width: 20rem; - min-width: 20rem; - } - #home-page #chat-section { - flex: 1; - min-width: unset; - } + #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); - } + #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 { + 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; +#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; + 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 + > .year-input { + width: 4.4rem; } -#chat-page > div > #main > #calendar-page > .pane-wrapper > .pane > .toolbar .input-wrapper > .month-input { - width: 3.1rem; +#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); - } + #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; + 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; + 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); + 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; + 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; + animation: slide-up var(--animation-duration); + overflow: hidden; + align-items: start; } .month-grid .tile[today] b { - color: var(--primary); + color: var(--primary); } .month-grid .tile > div { - width: 100%; - height: 100%; + width: 100%; + height: 100%; } .month-grid .tile .event-count { - display: none; - 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: 900px) { - .month-grid .tile { - padding: 0px; - } - .month-grid .tile > div { + 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 { +} +.month-grid .tile .event-count[has-events] { + background-color: var(--primary); + opacity: 1; +} +.month-grid .tile .event-list { display: flex; - } - .month-grid .tile .event-list { - display: none; - } + 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: 900px) { + .month-grid .tile { + padding: 0px; + } + .month-grid .tile > div { + align-items: center; + justify-content: center; + } + .month-grid .tile .event-count { + display: flex; + } + .month-grid .tile .event-list { + display: none; + } } /* src/View/ChatPages/messagePage.css */ #chat-page #main #message-page .content { - padding: 0px; - overflow: hidden; - display: grid; - grid-template-rows: 1fr var(--control-height) var(--safe-area-bottom); + padding: 0px; + overflow: hidden; + display: grid; + grid-template-rows: 1fr var(--control-height) var(--safe-area-bottom); } #message-page #message-container { - overflow: auto; + overflow: auto; } #message-page #composer { - display: flex; - justify-content: center; + display: flex; + justify-content: center; } #message-page #composer input { - margin: 0px; - width: 100%; - max-width: unset; - flex: 1; - border-top: 1px solid var(--border); - border-left: 1px solid var(--border); -} -@media (max-width: 700px) { - #message-page #composer div { + margin: 0px; width: 100%; max-width: unset; - display: flex; - } + flex: 1; + border-top: 1px solid var(--border); + border-left: 1px solid var(--border); +} +@media (max-width: 700px) { + #message-page #composer div { + width: 100%; + max-width: unset; + display: flex; + } } @media (min-width: 700px) { - #message-page #composer .content-width-constraint, - #message-page #composer .input-width-constraint { - display: flex; - justify-content: flex-end; - width: 100%; - max-width: var(--max-content-width); - } - #message-page #composer .content-width-constraint { - max-width: var(--max-content-width); - } - #message-page #composer .input-width-constraint { - max-width: var(--max-input-width); - } + #message-page #composer .content-width-constraint, + #message-page #composer .input-width-constraint { + display: flex; + justify-content: flex-end; + width: 100%; + max-width: var(--max-content-width); + } + #message-page #composer .content-width-constraint { + max-width: var(--max-content-width); + } + #message-page #composer .input-width-constraint { + max-width: var(--max-input-width); + } } #message-page #message-container { - display: flex; - flex-direction: column; - align-items: center; - padding: var(--content-padding); - gap: var(--gap-between-tiles); - height: auto; - max-height: unset; - scroll-behavior: smooth; + display: flex; + flex-direction: column; + align-items: center; + padding: var(--content-padding); + gap: var(--gap-between-tiles); + height: auto; + max-height: unset; + scroll-behavior: smooth; } .message-bubble { - display: flex; - width: 100%; - max-width: var(--max-content-width); + display: flex; + width: 100%; + max-width: var(--max-content-width); } .message-bubble .tile.main { - display: flex; - flex-direction: row; - align-items: start; - padding: 0px; - width: 100%; - max-width: var(--max-input-width); - overflow: hidden; + display: flex; + flex-direction: row; + align-items: start; + padding: 0px; + width: 100%; + max-width: var(--max-input-width); + overflow: hidden; } .message-bubble .tile.main .text-container { - display: flex; - flex-direction: column; - overflow: hidden; - width: 100%; - flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + width: 100%; + flex: 1; } .message-bubble .tile.main .button-container { - display: flex; - width: var(--control-height); - max-width: var(--control-height); + display: flex; + width: var(--control-height); + max-width: var(--control-height); } .message-bubble .tile.main button { - justify-content: center; - padding: 0px; - width: 100%; - --surface: transparent; + justify-content: center; + padding: 0px; + width: 100%; + --surface: transparent; } .message-bubble .tile.main .text-container { - padding: var(--control-padding); + padding: var(--control-padding); } .message-bubble .tile.main .timestamp, .message-bubble .tile.main .timestamp .icon, .message-bubble .tile.main .sender-name { - font-size: 0.8rem; - opacity: 0.6; + font-size: 0.8rem; + opacity: 0.6; } .message-bubble .tile.main .timestamp { - display: flex; - align-items: center; - gap: var(--gap-between-tiles); + display: flex; + align-items: center; + gap: var(--gap-between-tiles); } .message-bubble .tile.main .timestamp .icon { - display: none; + display: none; } .message-bubble .tile.main .sender-name { - font-weight: bold; + font-weight: bold; } .message-bubble .tile.main .body { - word-break: break-word; + word-break: break-word; } .message-bubble[sentbyuser] { - justify-content: flex-end; + justify-content: flex-end; } .message-bubble[sentbyuser] { - align-items: flex-end; + align-items: flex-end; } .message-bubble[sentbyuser] .tile.main, .message-bubble[sentbyuser] .tile.main .timestamp { - flex-direction: row-reverse; + flex-direction: row-reverse; } .message-bubble[sentbyuser] .tile.main .timestamp .icon { - display: flex; + display: flex; } .message-bubble[sentbyuser] .tile.main span { - text-align: right; + text-align: right; } /* src/View/ChatPages/taskPage.css */ #chat-page > div > #main > #task-page > .pane-wrapper > .pane > .main-content { - width: 100%; - max-width: 100%; - padding: 0px; + 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 + > .pane-wrapper + > .pane + > .main-content + > div { + animation: slide-up var(--animation-duration); } #chat-page > div > #main > #task-page .task-grid { - display: flex; - flex-direction: column; - gap: var(--gap-between-tiles); - padding: var(--content-padding); + 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; - gap: var(--large-gap); - width: 100%; - height: 100%; - overflow: auto; - padding: var(--content-padding); + display: flex; + 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; - gap: var(--gap-between-tiles); + display: flex; + flex-direction: column; + gap: var(--gap-between-tiles); } #chat-page > div > #main > #task-page .property-input-wrapper { - display: flex; - flex-direction: row; + display: flex; + flex-direction: row; } #chat-page > div > #main > #task-page .kanban-board-wrapper .kanban-column, #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; + width: 18rem; + min-width: 18rem; + max-width: 18rem; } #chat-page > div > #main > #task-page .status-name-row, #chat-page > div > #main > #task-page .status-grid-wrapper { - display: flex; - gap: var(--large-gap); + display: flex; + gap: var(--large-gap); } #chat-page > div > #main > #task-page .status-page-content, #chat-page > div > #main > #task-page .status-grid-wrapper { - flex-direction: column; + flex-direction: column; } #chat-page > div > #main > #task-page .status-name-row::before { - content: ""; - display: flex; + content: ""; + display: flex; } #chat-page > div > #main > #task-page .board-close-button { - display: none; + display: none; } #chat-page > div > #main > #task-page .board-toggle-button { - display: flex; + display: flex; } -#chat-page > div > #main > #task-page[isshowingboard][showingboardlist=false] #board-list { - display: none; +#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 > #task-page .task-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr)); + } } @media (max-width: 1024px) { - #chat-page > div > #main > #task-page #board-list { - display: false; - } - #chat-page > div > #main > #task-page #board-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; - } - #chat-page > div > #main > #task-page .board-close-button { - display: flex; - } - #chat-page > div > #main > #task-page .board-toggle-button { - display: none; - } + #chat-page > div > #main > #task-page #board-list { + display: false; + } + #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; + } + #chat-page > div > #main > #task-page .board-close-button { + display: flex; + } + #chat-page > div > #main > #task-page .board-toggle-button { + display: none; + } } /* src/View/Components/fileBrowser.css */ .file-browser { - width: 100%; - height: 100%; - display: flex; - overflow-x: scroll; - overflow-y: hidden; - scroll-behavior: smooth; - scroll-snap-type: x mandatory; + width: 100%; + height: 100%; + display: flex; + overflow-x: scroll; + overflow-y: hidden; + scroll-behavior: smooth; + scroll-snap-type: x mandatory; } .file-browser > div { - display: flex; - flex-direction: column; - width: 100%; - min-width: 100%; - flex: 0; - scroll-snap-align: center; + display: flex; + flex-direction: column; + width: 100%; + min-width: 100%; + flex: 0; + scroll-snap-align: center; } .file-browser .scroll-area { - flex: 1; - width: 100%; - overflow-y: auto; - padding: var(--content-padding); + flex: 1; + width: 100%; + overflow-y: auto; + padding: var(--content-padding); } .file-browser .detail-button-wrapper { - display: flex; - justify-content: end; - border-top: 1px solid var(--border); - padding: var(--content-padding); + display: flex; + justify-content: end; + border-top: 1px solid var(--border); + padding: var(--content-padding); } .file-browser .detail-button-wrapper button { - max-width: 100%; + max-width: 100%; } .file-browser code { - width: 100%; - height: 100%; - font-size: 0.9rem; + width: 100%; + height: 100%; + font-size: 0.9rem; } @media (min-width: 640px) { - .file-browser > div { - width: 50%; - min-width: 50%; - flex: 1; - } - .file-browser .detail-button-wrapper { - display: none; - } + .file-browser > div { + width: 50%; + min-width: 50%; + flex: 1; + } + .file-browser .detail-button-wrapper { + display: none; + } } diff --git a/dist/index.html b/dist/index.html index f3dd6a16..e9e941aa 100644 --- a/dist/index.html +++ b/dist/index.html @@ -1,35 +1,35 @@ - + - - Comms + + Comms - - + + - - + + - - - - - + + + + + - - + + - -
- + +
+ diff --git a/dist/index.js b/dist/index.js index de73d742..7a7cfb9b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,6149 +1,8140 @@ (() => { - // node_modules/uuid/dist/esm-browser/stringify.js - var byteToHex = []; - for (i = 0; i < 256; ++i) { - byteToHex.push((i + 256).toString(16).slice(1)); - } - var i; - function unsafeStringify(arr, offset = 0) { - return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); - } + // node_modules/uuid/dist/esm-browser/stringify.js + var byteToHex = []; + for (i = 0; i < 256; ++i) { + byteToHex.push((i + 256).toString(16).slice(1)); + } + var i; + function unsafeStringify(arr, offset = 0) { + return ( + byteToHex[arr[offset + 0]] + + byteToHex[arr[offset + 1]] + + byteToHex[arr[offset + 2]] + + byteToHex[arr[offset + 3]] + + "-" + + byteToHex[arr[offset + 4]] + + byteToHex[arr[offset + 5]] + + "-" + + byteToHex[arr[offset + 6]] + + byteToHex[arr[offset + 7]] + + "-" + + byteToHex[arr[offset + 8]] + + byteToHex[arr[offset + 9]] + + "-" + + byteToHex[arr[offset + 10]] + + byteToHex[arr[offset + 11]] + + byteToHex[arr[offset + 12]] + + byteToHex[arr[offset + 13]] + + byteToHex[arr[offset + 14]] + + byteToHex[arr[offset + 15]] + ).toLowerCase(); + } - // node_modules/uuid/dist/esm-browser/rng.js - var getRandomValues; - var rnds8 = new Uint8Array(16); - function rng() { - if (!getRandomValues) { - getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto); - if (!getRandomValues) { - throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported"); - } - } - return getRandomValues(rnds8); - } - - // node_modules/uuid/dist/esm-browser/native.js - var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto); - var native_default = { - randomUUID - }; - - // node_modules/uuid/dist/esm-browser/v4.js - function v4(options, buf, offset) { - if (native_default.randomUUID && !buf && !options) { - return native_default.randomUUID(); - } - options = options || {}; - var rnds = options.random || (options.rng || rng)(); - rnds[6] = rnds[6] & 15 | 64; - rnds[8] = rnds[8] & 63 | 128; - if (buf) { - offset = offset || 0; - for (var i = 0; i < 16; ++i) { - buf[offset + i] = rnds[i]; - } - return buf; - } - return unsafeStringify(rnds); - } - var v4_default = v4; - - // node_modules/bloatless-react/index.ts - var State = class { - _value; - _bindings = /* @__PURE__ */ new Set(); - // init - constructor(initialValue) { - this._value = initialValue; - } - // value - get value() { - return this._value; - } - set value(newValue) { - if (this._value == newValue) return; - this._value = newValue; - this.callSubscriptions(); - } - // subscriptions - callSubscriptions() { - this._bindings.forEach((fn) => fn(this._value)); - } - subscribe(fn) { - this._bindings.add(fn); - fn(this._value); - } - subscribeSilent(fn) { - this._bindings.add(fn); - } - // stringify - toString() { - return JSON.stringify(this._value); - } - }; - var ListState = class extends State { - additionHandlers = /* @__PURE__ */ new Set(); - removalHandlers = /* @__PURE__ */ new Map(); - // init - constructor(initialItems) { - super(new Set(initialItems)); - } - // list - add(...items) { - items.forEach((item) => { - this.value.add(item); - this.additionHandlers.forEach((handler) => handler(item)); - }); - this.callSubscriptions(); - } - remove(...items) { - items.forEach((item) => { - this.value.delete(item); - if (!this.removalHandlers.has(item)) return; - this.removalHandlers.get(item).forEach((handler) => handler(item)); - this.removalHandlers.delete(item); - }); - this.callSubscriptions(); - } - clear() { - this.remove(...this.value.values()); - } - // handlers - handleAddition(handler) { - this.additionHandlers.add(handler); - [...this.value.values()].forEach(handler); - } - handleRemoval(item, handler) { - if (!this.removalHandlers.has(item)) - this.removalHandlers.set(item, /* @__PURE__ */ new Set()); - this.removalHandlers.get(item).add(handler); - } - // stringification - toString() { - const array = [...this.value.values()]; - const json = JSON.stringify(array); - return json; - } - }; - var MapState = class extends State { - additionHandlers = /* @__PURE__ */ new Set(); - removalHandlers = /* @__PURE__ */ new Map(); - // init - constructor(initialItems) { - super(new Map(initialItems)); - } - // list - set(key, item) { - this.remove(key); - this.value.set(key, item); - this.additionHandlers.forEach((handler) => handler(item)); - this.callSubscriptions(); - } - remove(key) { - const item = this.value.get(key); - if (!item) return; - this.value.delete(key); - this.callSubscriptions(); - if (!this.removalHandlers.has(item)) return; - this.removalHandlers.get(item).forEach((handler) => handler(item)); - this.removalHandlers.delete(item); - } - clear() { - [...this.value.keys()].forEach((key) => this.remove(key)); - } - // handlers - handleAddition(handler) { - this.additionHandlers.add(handler); - [...this.value.values()].forEach(handler); - } - handleRemoval(item, handler) { - if (!this.removalHandlers.has(item)) - this.removalHandlers.set(item, /* @__PURE__ */ new Set()); - this.removalHandlers.get(item).add(handler); - } - // stringification - toString() { - const array = [...this.value.entries()]; - const json = JSON.stringify(array); - return json; - } - }; - function createProxyState(statesToSubscibe, fn) { - const proxyState = new State(fn()); - statesToSubscibe.forEach( - (state) => state.subscribe(() => proxyState.value = fn()) - ); - return proxyState; - } - function bulkSubscribe(statesToSubscibe, fn) { - statesToSubscibe.forEach((state) => state.subscribeSilent(fn)); - } - function createElement(tagName, attributes = {}, ...children) { - const element = document.createElement(tagName); - if (attributes != null) - Object.entries(attributes).forEach((entry) => { - const [attributename, value] = entry; - const [directiveKey, directiveValue] = attributename.split(":"); - switch (directiveKey) { - case "on": { - switch (directiveValue) { - case "enter": { - element.addEventListener("keydown", (e) => { - if (e.key != "Enter") return; - value(); - }); - break; - } - default: { - element.addEventListener(directiveValue, value); - } - } - break; - } - case "subscribe": { - const state = value; - state.subscribe( - (newValue) => element[directiveValue] = newValue - ); - break; - } - case "bind": { - const state = value; - state.subscribe( - (newValue) => element[directiveValue] = newValue - ); - element.addEventListener( - "input", - () => state.value = element[directiveValue] - ); - break; - } - case "toggle": { - if (value.subscribe) { - const state = value; - state.subscribe( - (newValue) => element.toggleAttribute(directiveValue, newValue) - ); - } else { - element.toggleAttribute(directiveValue, value); - } - break; - } - case "set": { - const state = value; - state.subscribe( - (newValue) => element.setAttribute(directiveValue, newValue) - ); - break; - } - case "children": { - switch (directiveValue) { - case "set": { - const state = value; - state.subscribe((newValue) => { - element.innerHTML = ""; - element.append(...[newValue].flat()); - }); - break; - } - case "append": - case "prepend": { - try { - const [listState, toElement] = value; - listState.handleAddition((newItem) => { - const child = toElement(newItem); - listState.handleRemoval( - newItem, - () => child.remove() - ); - if (directiveValue == "append") { - element.append(child); - } else if (directiveValue == "prepend") { - element.prepend(child); - } - }); - } catch (error) { - console.error(error); - throw `error: cannot process subscribe:children directive. - Usage: "children:append={[list, converter]}"; you can find a more detailed example in the documentation.`; - } - } - } - break; - } - default: - element.setAttribute(attributename, value); - } - }); - children.filter((x) => x).forEach((child) => element.append(child)); - 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); - return array.join(""); - } - function createTimestamp() { - return (/* @__PURE__ */ new Date()).toISOString(); - } - function checkDoesObjectMatchReference(reference, stringEntryObject) { - 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 (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 || stringEntryObjectValue == "") - 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 Set(); - // manage - this.addHandler = (handler) => { - this.handlers.add(handler); - }; - this.deleteHandler = (handler) => { - this.handlers.delete(handler); - }; - // trigger - this.trigger = (item) => { - for (const handler of this.handlers) { - 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"], - lastUsedPage: (id) => [ - ...filePaths.chat.chatBase(id), - "last-used-page" - ], - files: (id) => [...filePaths.chat.chatBase(id), "files"] - }, - settingsModel: { - username: ["user-name"], - firstDayOfWeek: ["first-day-of-week"] - } - }; - - // 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 = (year, month, defaultValueCreator) => { - const date = /* @__PURE__ */ new Date(); - 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 Color = /* @__PURE__ */ ((Color2) => { - Color2["Standard"] = "standard"; - Color2["Coral"] = "coral"; - Color2["Yellow"] = "yellow"; - Color2["Mint"] = "mint"; - Color2["LightBlue"] = "lightblue"; - Color2["Blue"] = "blue"; - Color2["purple"] = "purple"; - return Color2; - })(Color || {}); - - // 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("", 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("", 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); - }; - // methods - this.addFileContentAndSend = (fileContent) => { - this.handleFileContent(fileContent); - this.chatModel.sendMessage("", 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(); - // 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]; - }; - // handlers - this.handleMessage = (body) => { - const chatMessage = parseValidObject( - body, - ChatMessageReference - ); - if (chatMessage == null) return; - chatMessage.status = "received" /* Received */; - this.addMessage(chatMessage); - this.info.hasUnreadMessages = true; - this.storeInfo(); - }; - 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.sendMessage = async (body, fileContent) => { - 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("/"); - const chatMessage = await _ChatModel.createChatMessage( - combinedChannel, - senderName, - this.info.encryptionKey, - body, - 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.subscribe = () => { - this.connectionModel.addChannel(this.info.primaryChannel); - }; - this.markRead = () => { - this.info.hasUnreadMessages = false; - 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 - ); - chatMessages.push(chatMessage); - } - const sorted = chatMessages.sort( - (a, b) => a.dateSent.localeCompare(b.dateSent) - ); - return sorted; - } - static { - // utility - this.generateChatInfo = (primaryChannel) => { - return { - dataVersion: DATA_VERSION, - primaryChannel, - secondaryChannels: [], - encryptionKey: "", - hasUnreadMessages: false - }; - }; - } - static { - this.createChatMessage = async (channel, sender, encryptionKey, body, fileContent) => { - const chatMessage = { - dataVersion: DATA_VERSION, - id: v4_default(), - channel, - sender, - body, - dateSent: createTimestamp(), - 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; - }; - } - }; - var ChatInfoReference = { - dataVersion: DATA_VERSION, - primaryChannel: "", - secondaryChannels: [""], - encryptionKey: "", - hasUnreadMessages: true - }; - var ChatMessageReference = { - dataVersion: DATA_VERSION, - id: "", - channel: "", - sender: "", - body: "", - dateSent: "", - status: "", - stringifiedFile: "" - }; - - // 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.addHandler(this.messageHandler); - connectionModel2.messageSentHandlerManager.addHandler( - this.messageSentHandler - ); - } - }; - - // src/View/utility.ts - function allowDrop(event) { - event.preventDefault(); - } - function allowDrag(event) { - event.dataTransfer?.setData("text", ""); - } - function reload() { - window.location.reload(); - } - - // src/ViewModel/Pages/taskViewModel.ts - var TaskViewModel = class { - // init - constructor(coreViewModel, chatViewModel, boardsAndTasksModel, containingModel, taskFileContent) { - this.coreViewModel = coreViewModel; - this.chatViewModel = chatViewModel; - // paths - this.getFilePath = () => { - return this.boardsAndTasksModel.getTaskFilePath(this.task.fileId); - }; - // state - this.index = new State(0); - this.boardId = new State(""); - this.name = new State(""); - this.description = new State(""); - this.category = new State(""); - this.status = new State(""); - this.priority = new State(""); - this.date = new State(""); - this.time = new State(""); - this.selectedVersionId = new State(""); - this.versionIds = new ListState(); - // methods - this.dragStart = (event) => { - allowDrag(event); - this.coreViewModel.draggedObject.value = this; - }; - this.setCategoryAndStatus = (category, status) => { - if (category != void 0) this.category.value = category; - if (status != void 0) this.status.value = status; - this.save(); - }; - this.setBoardId = (boardId) => { - this.boardId.value = boardId; - this.save(); - }; - this.setDate = (dateISOString) => { - this.date.value = dateISOString; - this.save(); - }; - // view - this.open = () => { - this.containingModel.selectTask(this); - }; - this.close = () => { - this.containingModel.closeTask(); - }; - this.closeAndDiscard = () => { - this.close(); - this.loadTaskData(); - }; - this.closeAndSave = () => { - this.close(); - this.save(); - }; - this.updateIndex = () => { - const index = this.containingModel.taskIndexManager.getIndex(this); - this.index.value = index; - }; - this.updateSuggestions = () => { - if (this.coreViewModel.taskCategorySuggestions.value.has( - this.category.value - ) == false) { - this.coreViewModel.taskCategorySuggestions.add(this.category.value); - } - if (this.coreViewModel.taskStatusSuggestions.value.has( - this.status.value - ) == false) { - this.coreViewModel.taskStatusSuggestions.add(this.status.value); - } - }; - // settings - this.save = () => { - const newTaskFileContent = BoardsAndTasksModel.createTaskFileContent( - this.task.fileId, - this.name.value, - this.task.boardId - ); - newTaskFileContent.boardId = this.boardId.value; - newTaskFileContent.description = this.description.value; - newTaskFileContent.status = this.status.value; - newTaskFileContent.category = this.category.value; - newTaskFileContent.priority = this.priority.value; - newTaskFileContent.date = this.date.value; - newTaskFileContent.time = this.time.value; - this.boardsAndTasksModel.updateTaskAndSend(newTaskFileContent); - this.containingModel.showTask(newTaskFileContent); - this.containingModel.updateTaskIndices(); - this.updateSuggestions(); - }; - this.deleteTask = () => { - this.close(); - this.boardsAndTasksModel.deleteTask( - this.task.boardId, - this.task.fileId - ); - this.containingModel.removeTaskFromView(this.task); - }; - // load - this.loadVersionIds = () => { - const versionIds = this.boardsAndTasksModel.listTaskVersionIds(this.task.fileId); - const sortedVersionIds = versionIds.sort(localeCompare).reverse(); - this.versionIds.clear(); - this.versionIds.add(...sortedVersionIds); - }; - this.switchVersion = (versionId) => { - const taskFileContent = this.boardsAndTasksModel.getSpecificTaskFileContent( - this.task.fileId, - versionId - ); - if (taskFileContent == null) return; - this.task = taskFileContent; - this.loadTaskData(); - }; - this.loadAllData = () => { - this.loadTaskData(); - this.loadVersionIds(); - }; - this.loadTaskData = () => { - this.boardId.value = this.task.boardId; - this.name.value = this.task.name; - this.description.value = this.task.description ?? ""; - this.category.value = this.task.category ?? ""; - this.status.value = this.task.status ?? ""; - this.priority.value = this.task.priority ?? ""; - this.date.value = this.task.date ?? ""; - this.time.value = this.task.time ?? ""; - this.selectedVersionId.value = this.task.fileContentId; - this.updateSuggestions(); - }; - this.boardsAndTasksModel = boardsAndTasksModel; - this.containingModel = containingModel; - this.task = taskFileContent; - this.loadAllData(); - this.selectedVersionId.subscribeSilent((selectedVersionId) => { - this.switchVersion(selectedVersionId); - }); - } - get sortingString() { - const splitDate = this.date.value.split("-"); - const year = padZero(splitDate[0], 4); - const month = padZero(splitDate[1], 2); - const date = padZero(splitDate[2], 2); - const splitTime = this.time.value.split(":"); - const hour = padZero(splitTime[0], 2); - const minute = padZero(splitTime[1], 2); - const priorityNumber = parseInt(this.priority.value); - const invertedPriority = 100 - priorityNumber; - return year + month + date + hour + minute + invertedPriority + this.name.value; - } - static { - // utility - this.getStringsForFilter = (taskViewModel) => { - return [ - taskViewModel.task.name, - taskViewModel.task.description ?? "", - taskViewModel.task.category ?? "", - taskViewModel.task.status ?? "", - taskViewModel.task.priority ?? "", - taskViewModel.task.date ?? "", - taskViewModel.task.time ?? "" - ]; - }; - } - }; - - // src/ViewModel/Pages/taskContainingPageViewModel.ts - var TaskContainingPageViewModel = class { - // init - constructor(coreViewModel, chatViewModel, boardsAndTasksModel) { - this.coreViewModel = coreViewModel; - this.chatViewModel = chatViewModel; - // state - this.taskIndexManager = new IndexManager( - (taskViewModel) => taskViewModel.sortingString - ); - this.selectedTaskViewModel = new State(void 0); - this.taskViewModels = new MapState(); - // methods - this.createTaskFromBoardId = (boardId) => { - const taskFileContent = this.boardsAndTasksModel.createTask(boardId); - const taskViewModel = new TaskViewModel( - this.coreViewModel, - this.chatViewModel, - this.boardsAndTasksModel, - this, - taskFileContent - ); - this.selectTask(taskViewModel); - this.updateTaskIndices(); - }; - // view - this.showTask = (taskFileContent) => { - }; - this.removeTaskFromView = (taskFileContent) => { - }; - this.selectTask = (selectedTask) => { - this.selectedTaskViewModel.value = selectedTask; - }; - this.closeTask = () => { - this.selectedTaskViewModel.value = void 0; - }; - this.updateTaskIndices = () => { - this.taskIndexManager.update([...this.taskViewModels.value.values()]); - for (const boardViewModel of this.taskViewModels.value.values()) { - boardViewModel.updateIndex(); - } - }; - this.boardsAndTasksModel = boardsAndTasksModel; - } - }; - - // src/ViewModel/Pages/calendarPageViewModel.ts - var CALENDAR_EVENT_BOARD_ID = "events"; - var CalendarPageViewModel = class extends TaskContainingPageViewModel { - // init - constructor(coreViewModel, chatViewModel, storageModel2, calendarModel, boardsAndTasksModel) { - super(coreViewModel, chatViewModel, boardsAndTasksModel); - this.coreViewModel = coreViewModel; - this.chatViewModel = chatViewModel; - // paths - this.getBasePath = () => { - return [...this.calendarModel.getViewPath()]; - }; - // state - 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 - ); - this.selectTask(taskViewModel); - 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.removeTaskFromView = (taskFileContent) => { - this.taskViewModels.remove(taskFileContent.fileId); - }; - this.showToday = () => { - const today = /* @__PURE__ */ new Date(); - 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); - }; - // load - this.loadMonthTasks = () => { - this.monthGrid.value = this.calendarModel.generateMonthGrid( - this.selectedYear.value, - this.selectedMonth.value, - () => new MapState() - ); - const taskIds = this.calendarModel.listTaskIds( - this.monthString - ); - for (const taskId of taskIds) { - const taskFileContent = this.boardsAndTasksModel.getLatestTaskFileContent(taskId); - if (taskFileContent == null) continue; - this.showTask(taskFileContent); - } - this.updateTaskIndices(); - }; - this.loadData = () => { - this.loadMonthTasks(); - this.showToday(); - }; - this.storageModel = storageModel2; - this.calendarModel = calendarModel; - this.boardsAndTasksModel = boardsAndTasksModel; - this.chatViewModel = chatViewModel; - bulkSubscribe([this.selectedYear, this.selectedMonth], () => { - this.loadMonthTasks(); - }); - boardsAndTasksModel.taskHandlerManager.addHandler( - (taskFileContent) => { - this.showTask(taskFileContent); - } - ); - } - // data - get monthString() { - return CalendarModel.getMonthString( - this.selectedYear.value.toString(), - this.selectedMonth.value.toString() - ); - } - }; - - // src/ViewModel/Chat/chatMessageViewModel.ts - var ChatMessageViewModel = class { - // init - constructor(coreViewModel, messagePageViewModel, chatMessage, sentByUser) { - this.coreViewModel = coreViewModel; - this.body = new State(""); - this.status = new State( - void 0 - ); - // 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); - }; - // view - this.showInfoModal = () => { - this.isPresentingInfoModal.value = true; - }; - this.hideInfoModal = () => { - this.isPresentingInfoModal.value = false; - }; - // 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; - }; - this.messagePageViewModel = messagePageViewModel; - this.chatMessage = chatMessage; - this.sentByUser = sentByUser; - this.loadData(); - } - }; - - // src/ViewModel/Pages/messagePageViewModel.ts - var MessagePageViewModel = class { - // init - constructor(coreViewModel, chatViewModel) { - this.coreViewModel = coreViewModel; - // state - this.chatMessageViewModels = new MapState(); - this.composingMessage = new State(""); - // methods - this.sendMessage = () => { - if (this.cannotSendMessage.value == true) return; - this.sendMessageFromBody(this.composingMessage.value); - this.composingMessage.value = ""; - }; - this.sendMessageFromBody = (body) => { - this.chatViewModel.chatModel.sendMessage(body); - }; - this.decryptMessage = async (messageViewModel) => { - const chatMessage = messageViewModel.chatMessage; - await this.chatViewModel.chatModel.decryptMessage(chatMessage); - this.chatViewModel.chatModel.addMessage(chatMessage); - messageViewModel.loadData(); - }; - // 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 - ); - } - }; - // load - this.loadData = () => { - this.chatMessageViewModels.clear(); - for (const chatMessage of this.chatViewModel.chatModel.messages) { - this.showChatMessage(chatMessage); - } - }; - this.chatViewModel = chatViewModel; - this.cannotSendMessage = createProxyState( - [ - this.chatViewModel.settingsViewModel.username, - this.composingMessage - ], - () => this.chatViewModel.settingsViewModel.username.value == "" || this.composingMessage.value == "" - ); - } - }; - - // src/ViewModel/Pages/settingsPageViewModel.ts - var SettingsPageViewModel = class { - // init - constructor(coreViewModel, chatViewModel) { - this.coreViewModel = coreViewModel; - // 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.loadListRelevantData = () => { - 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.chatViewModel = chatViewModel; - this.loadListRelevantData(); - this.cannotSetEncryptionKey = createProxyState( - [this.encryptionKeyInput], - () => this.encryptionKeyInput.value == this.chatViewModel.chatModel.info.encryptionKey - ); - this.color.subscribe((newColor) => { - this.applyColor(newColor); - }); - } - }; - - // src/ViewModel/Utility/searchViewModel.ts - var SearchViewModel = class { - // init - constructor(allObjects, matchingObjects, getStringsOfObject, suggestions) { - // state - this.appliedQuery = new State(""); - this.searchInput = new State(""); - // guards - this.cannotApplySearch = createProxyState( - [this.searchInput, this.appliedQuery], - () => this.searchInput.value == this.appliedQuery.value - ); - // methods - this.search = (searchTerm) => { - this.searchInput.value = searchTerm; - this.applySearch(); - }; - this.applySearch = () => { - this.appliedQuery.value = this.searchInput.value; - this.matchingObjects.clear(); - for (const object of this.allObjects.value.values()) { - const doesMatch = this.checkDoesMatchSearch(object); - if (doesMatch == false) continue; - this.matchingObjects.add(object); - } - }; - // utility - this.checkDoesMatchSearch = (object) => { - return checkDoesObjectMatchSearch( - this.appliedQuery.value, - this.getStringsOfObject, - object - ); - }; - this.allObjects = allObjects; - this.matchingObjects = matchingObjects; - this.getStringsOfObject = getStringsOfObject; - this.suggestions = suggestions; - this.allObjects.handleAddition((newObject) => { - const doesMatch = this.checkDoesMatchSearch(newObject); - if (doesMatch == false) { - this.matchingObjects.remove(newObject); - } else { - if (this.matchingObjects.value.has(newObject)) return; - this.matchingObjects.add(newObject); - this.allObjects.handleRemoval(newObject, () => { - this.matchingObjects.remove(newObject); - }); - } - }); - } - }; - - // src/ViewModel/Pages/boardViewModel.ts - var BoardViewModel = class extends TaskContainingPageViewModel { - // init - constructor(coreViewModel, chatViewModel, storageModel2, boardsAndTasksModel, taskPageViewModel, boardInfo) { - super(coreViewModel, chatViewModel, boardsAndTasksModel); - this.coreViewModel = coreViewModel; - this.chatViewModel = chatViewModel; - // state - this.name = new State(""); - this.color = new State("standard" /* Standard */); - this.index = new State(0); - this.selectedPage = new State( - "list" /* List */ - ); - this.isPresentingSettingsModal = new State(false); - this.isPresentingFilterModal = new State(false); - this.filteredTaskViewModels = new ListState(); - // paths - this.getBasePath = () => { - return [ - ...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId) - ]; - }; - this.getLastUsedBoardPath = () => { - return [...this.getBasePath(), "last-used-view" /* LastUsedView */]; - }; - this.getPreviousSearchesPath = () => { - return [...this.getBasePath(), "previous-searches" /* PreviousSearches */]; - }; - this.getLastSearchPath = () => { - return [...this.getBasePath(), "last-search" /* LastSearch */]; - }; - // settings - this.saveSettings = () => { - const newBoardInfoFileContent = BoardsAndTasksModel.createBoardInfoFileContent( - this.boardInfo.fileId, - this.name.value, - this.color.value - ); - this.taskPageViewModel.updateBoard(newBoardInfoFileContent); - }; - this.applyColor = () => { - this.taskPageViewModel.chatViewModel.setDisplayedColor( - this.color.value - ); - }; - this.deleteBoard = () => { - this.taskPageViewModel.deleteBoard(this.boardInfo); - this.chatViewModel.taskBoardSuggestions.remove(this.boardInfo.fileId); - this.close(); - }; - // methods - this.createTask = () => { - this.createTaskFromBoardId(this.boardInfo.fileId); - }; - this.handleDropWithinBoard = (category, status) => { - const draggedObject = this.coreViewModel.draggedObject.value; - if (draggedObject instanceof TaskViewModel == false) return; - draggedObject.setCategoryAndStatus(category, status); - }; - this.handleDropBetweenBoards = () => { - const draggedObject = this.coreViewModel.draggedObject.value; - if (draggedObject instanceof TaskViewModel == false) return; - draggedObject.setBoardId(this.boardInfo.fileId); - }; - // storage - this.storeLastUsedView = () => { - const path = this.getLastUsedBoardPath(); - const lastUsedView = this.selectedPage.value; - this.storageModel.write(path, lastUsedView); - }; - this.restoreLastUsedView = () => { - const path = this.getLastUsedBoardPath(); - const lastUsedView = this.storageModel.read(path); - if (lastUsedView == null) return; - this.selectedPage.value = lastUsedView; - }; - this.handleNewSearch = (searchTerm) => { - const suggestionPath = [ - ...this.getPreviousSearchesPath(), - searchTerm - ]; - this.storageModel.write(suggestionPath, ""); - if (!this.coreViewModel.boardSearchSuggestions.value.has(searchTerm)) { - this.coreViewModel.boardSearchSuggestions.add(searchTerm); - } - const lastSearchPath = this.getLastSearchPath(); - this.storageModel.write(lastSearchPath, searchTerm); - }; - // view - this.showTask = (taskFileContent) => { - if (taskFileContent.boardId != this.boardInfo.fileId) { - this.boardsAndTasksModel.deleteTaskReference( - this.boardInfo.fileId, - taskFileContent.fileId - ); - this.removeTaskFromView(taskFileContent); - return; - } - const taskViewModel = new TaskViewModel( - this.coreViewModel, - this.chatViewModel, - this.boardsAndTasksModel, - this, - taskFileContent - ); - this.taskViewModels.set(taskFileContent.fileId, taskViewModel); - }; - this.removeTaskFromView = (taskFileContent) => { - this.taskViewModels.remove(taskFileContent.fileId); - this.updateIndex(); - }; - this.select = () => { - this.taskPageViewModel.selectBoard(this); - }; - this.close = () => { - this.taskPageViewModel.closeBoard(); - this.taskViewModels.clear(); - }; - this.showSettings = () => { - this.isPresentingSettingsModal.value = true; - }; - this.hideSettings = () => { - this.saveSettings(); - this.isPresentingSettingsModal.value = false; - }; - this.showFilterModal = () => { - this.isPresentingFilterModal.value = true; - }; - this.hideFilterModal = () => { - this.isPresentingFilterModal.value = false; - }; - this.updateIndex = () => { - const index = this.taskPageViewModel.boardIndexManager.getIndex(this); - this.index.value = index; - }; - // load - this.loadListRelevantData = () => { - this.name.value = this.boardInfo.name; - this.color.value = this.boardInfo.color; - }; - this.loadTasks = () => { - const taskIds = this.boardsAndTasksModel.listTaskIds( - this.boardInfo.fileId - ); - for (const taskId of taskIds) { - if (this.taskViewModels.value.has(taskId)) return; - const taskFileContent = this.boardsAndTasksModel.getLatestTaskFileContent(taskId); - if (taskFileContent == null) continue; - const taskViewModel = new TaskViewModel( - this.coreViewModel, - this.chatViewModel, - this.boardsAndTasksModel, - this, - taskFileContent - ); - this.taskViewModels.set(taskFileContent.fileId, taskViewModel); - } - this.updateTaskIndices(); - }; - this.loadSearchSuggestions = () => { - const dirPath = this.getPreviousSearchesPath(); - const searches = this.storageModel.list(dirPath); - this.coreViewModel.boardSearchSuggestions.add(...searches); - }; - this.restoreSearch = () => { - const lastSearchPath = this.getLastSearchPath(); - const lastSearch = this.storageModel.read(lastSearchPath); - if (lastSearch != null) { - this.searchViewModel.search(lastSearch); - } - }; - this.loadData = () => { - this.restoreLastUsedView(); - this.loadTasks(); - this.loadSearchSuggestions(); - }; - this.storageModel = storageModel2; - this.boardsAndTasksModel = boardsAndTasksModel; - this.taskPageViewModel = taskPageViewModel; - this.boardInfo = boardInfo; - this.loadListRelevantData(); - this.isSelected = createProxyState( - [this.taskPageViewModel.selectedBoardId], - () => this.taskPageViewModel.selectedBoardId.value == this.boardInfo.fileId - ); - this.color.subscribe(() => { - if (this.isSelected.value == false) return; - this.applyColor(); - }); - this.selectedPage.subscribeSilent(() => { - this.storeLastUsedView(); - }); - boardsAndTasksModel.taskHandlerManager.addHandler( - (taskFileContent) => { - if (taskFileContent.boardId != this.boardInfo.fileId) return; - this.showTask(taskFileContent); - this.updateTaskIndices(); - } - ); - this.searchViewModel = new SearchViewModel( - this.taskViewModels, - this.filteredTaskViewModels, - TaskViewModel.getStringsForFilter, - this.coreViewModel.boardSearchSuggestions - ); - this.searchViewModel.appliedQuery.subscribeSilent((newQuery) => { - this.handleNewSearch(newQuery); - }); - this.restoreSearch(); - } - }; - - // src/ViewModel/Pages/taskPageViewModel.ts - var TaskPageViewModel = class { - // init - constructor(coreViewModel, chatViewModel, storageModel2, boardModel) { - this.coreViewModel = coreViewModel; - this.chatViewModel = chatViewModel; - // data - this.boardIndexManager = new IndexManager( - (boardViewModel) => boardViewModel.name.value - ); - // paths - this.getBasePath = () => { - return [...this.boardsAndTasksModel.getViewPath()]; - }; - this.getBoardViewPath = (boardId) => { - return [...this.getBasePath(), boardId]; - }; - this.getLastUsedBoardPath = () => { - return [...this.getBasePath(), "last-used-board" /* LastUsedBoard */]; - }; - // state - this.newBoardNameInput = new State(""); - this.boardViewModels = new MapState(); - this.isShowingBoadList = new State(true); - this.selectedBoardId = new State( - void 0 - ); - // guards - this.cannotCreateBoard = createProxyState( - [this.newBoardNameInput], - () => this.newBoardNameInput.value == "" - ); - // methods - this.createBoard = () => { - if (this.cannotCreateBoard.value == true) return; - const boardInfoFileContent = this.boardsAndTasksModel.createBoard(this.newBoardNameInput.value); - this.newBoardNameInput.value = ""; - this.showBoardInList(boardInfoFileContent); - this.boardsAndTasksModel.updateBoardAndSend(boardInfoFileContent); - this.updateBoardIndices(); - }; - this.updateBoard = (boardInfoFileContent) => { - this.boardsAndTasksModel.updateBoardAndSend(boardInfoFileContent); - this.updateBoardIndices(); - }; - this.deleteBoard = (boardInfoFileContent) => { - this.boardsAndTasksModel.deleteBoard(boardInfoFileContent.fileId); - this.boardViewModels.remove(boardInfoFileContent.fileId); - this.updateBoardIndices(); - }; - // view - this.toggleBoardList = () => { - this.isShowingBoadList.value = !this.isShowingBoadList.value; - }; - this.showBoardInList = (boardInfo) => { - const boardViewModel = new BoardViewModel( - this.coreViewModel, - this.chatViewModel, - this.storageModel, - this.boardsAndTasksModel, - this, - boardInfo - ); - this.boardViewModels.set(boardInfo.fileId, boardViewModel); - this.chatViewModel.taskBoardSuggestions.set(boardInfo.fileId, [ - boardInfo.fileId, - this.boardsAndTasksModel.getBoardName(boardInfo.fileId) - ]); - }; - this.selectBoard = (boardViewModel) => { - this.selectedBoardId.value = boardViewModel.boardInfo.fileId; - this.chatViewModel.displayedColor.value = boardViewModel.color.value; - this.storeLastUsedBoard(); - }; - this.closeBoard = () => { - this.selectedBoardId.value = void 0; - this.chatViewModel.resetColor(); - this.storeLastUsedBoard(); - }; - this.updateBoardIndices = () => { - this.boardIndexManager.update([...this.boardViewModels.value.values()]); - for (const boardViewModel of this.boardViewModels.value.values()) { - boardViewModel.updateIndex(); - } - }; - // storage - this.storeLastUsedBoard = () => { - const path = this.getLastUsedBoardPath(); - const lastUsedBoardId = this.selectedBoardId.value ?? ""; - this.storageModel.write(path, lastUsedBoardId); - }; - this.openLastUsedBoard = () => { - const path = this.getLastUsedBoardPath(); - const lastUsedBoardId = this.storageModel.read(path); - if (lastUsedBoardId == null) return; - const boardViewModel = this.boardViewModels.value.get(lastUsedBoardId); - if (boardViewModel == void 0) return; - this.selectBoard(boardViewModel); - }; - // load - this.loadData = () => { - this.boardViewModels.clear(); - const boardIds = this.boardsAndTasksModel.listBoardIds(); - for (const boardId of boardIds) { - const boardInfo = this.boardsAndTasksModel.getBoardInfo(boardId); - if (boardInfo == null) continue; - this.showBoardInList(boardInfo); - } - this.updateBoardIndices(); - this.openLastUsedBoard(); - }; - this.storageModel = storageModel2; - this.boardsAndTasksModel = boardModel; - this.chatViewModel = chatViewModel; - boardModel.boardHandlerManager.addHandler( - (boardInfoFileContent) => { - this.showBoardInList(boardInfoFileContent); - this.updateBoardIndices(); - } - ); - } - }; - - // src/View/translations.ts - var englishTranslations = { - updater: { - migrated: "Migrated" - }, - general: { - deleteItemButtonAudioLabel: "delete item", - searchButtonAudioLabel: "search", - abortButton: "Abort", - cancelButton: "Cancel", - closeButton: "Close", - backButton: "Back", - continueButton: "Continue", - confirmButton: "Confirm", - saveButton: "Save", - setButton: "Set", - reloadAppButton: "Reload App", - fileVersionLabel: "Version", - searchLabel: "Search", - restoreConnection: "Restore connection" - }, - regional: { - weekdays: { - full: [ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday" - ], - abbreviated: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] - } - }, - homePage: { - appName: "Comms", - /// - overviewHeadline: "Overview", - serverAddress: "Server address", - serverAddressPlaceholder: "wss://192.168.0.69:3000", - connectAudioLabel: "connect to server", - disconnectAudioLabel: "disconnect from server", - manageConnectionsAudioLabel: "manage connections", - yourNameLabel: "Your name", - yourNamePlaceholder: "Jane Doe", - setNameButtonAudioLabel: "set name", - firstDayOfWeekLabel: "First day of week", - manageStorageButton: "Manage storage", - transferDataButton: "Data Transfer", - scrollToChatButton: "Chats", - /// - backToOverviewAudioLabel: "go back to overview", - chatsHeadline: "Chats", - addChatAudioLabel: "name of new chat", - addChatPlaceholder: "Add chat", - addChatButton: "Add chat" - }, - connectionModal: { - connectionModalHeadline: "Manage Connections", - /// - connectButtonAudioLabel: "connect" - }, - dataTransferModal: { - transferDataHeadline: "Data Transfer", - selectionDescription: "Select the data that you want to transfer.", - dataEntryDescription: "Enter this data on the other device.", - dataEntryInputDescription: "Enter the data displayed on the other device.", - readyToReceiveDescription: "Click 'send' on the other device.", - notConnectedError: "You are not connected to any server.", - /// - fromThisDeviceButton: "From this device", - toThisDeviceButton: "To this device", - /// - generalHeadline: "General", - connectionData: "Connection Data", - settingsData: "Settings Data", - chatsHeadline: "Chats", - /// - transferChannelHeadline: "Transfer Chanel", - transferKeyHeadline: "Transfer Encryption Key", - sendButton: "Send", - sendAgainButton: "Send again", - /// - filesSentCount: (count) => `Files sent: ${count}.`, - allFilesSent: "Done.", - filesReceivedCount: (count) => `Files received: ${count}.` - }, - storage: { - noItemSelected: "No item selected", - notAFile: "(not a file)", - contentEmpty: "(empty)", - path: "Path", - content: "Content", - deleteItem: "Delete item", - removeJunkButton: "Delete junk files" - }, - chatPage: { - closeChatAudioLabe: "close chat", - chatSettingsAudioLabel: "chat settings", - pages: { - settings: "Settings", - messages: "Messages", - tasks: "Tasks", - calendar: "Calendar" - }, - settings: { - settingsHeadline: "Settings", - primaryChannelLabel: "Primary channel", - setPrimaryChannelButtonAudioLabel: "set primary channel", - newSecondaryChannelPlaceholder: "Add secondary channel", - newSecondaryChannelAudioLabel: "name of new secondary channel", - addSecondaryChannelButtonAudioLabel: "add secondary channel", - encryptionKeyLabel: "Encryption key", - setEncryptionKeyButtonAudioLabel: "set encryption key", - showEncryptionKey: "Show encryption key", - deleteChatButton: "Delete entire chat" - }, - message: { - messagesHeadline: "Messages", - /// - composerInputPlaceholder: "Type a message...", - sendMessageButtonAudioLabel: "send message", - /// - showMessageInfoButtonAudioLabel: "show message info", - messageInfoHeadline: "Message Info", - sentBy: "Sent by", - timeSent: "Time sent", - channel: "Channel", - messageContent: "Message content", - copyMessageButton: "Copy message", - resendMessageButton: "Resend message", - decryptMessageButton: "Decrypt message", - deleteMessageButton: "Delete message" - }, - task: { - newBoardNamePlaceholder: "Create a board", - createBoardButtonAudioLabel: "create board", - /// - noBoardSelected: "No board selected", - boardNotFound: "Board not found", - /// - closeBoardButtonAudioLabel: "close board", - toggleBoardButtonAudioLabel: "toggle board list", - showBoardSettingsButtonAudioLabel: "show board settigns", - listViewButtonAudioLabel: "list view", - kanbanViewButtonAudioLabel: "kanban view", - statusViewButtonAudioLabel: "status grid view", - filterTasksButtonAudioLabel: "filter tasks", - createTaskButtonAudioLabel: "create new task", - /// - boardSettingsHeadline: "Board Settings", - boardNameInputLabel: "Board name", - deleteBoardButton: "Delete board and all tasks", - /// - taskSettingsHeadline: "Edit Task", - taskNameLabel: "Title", - taskBoardLabel: "Board", - taskCategoryLabel: "Category", - taskStatusLabel: "Status", - taskPriorityLabel: "Priority", - taskDescriptionLabel: "Description", - taskDateLabel: "Date", - taskTimeLabel: "Time", - deleteTaskButton: "Delete task", - /// - filterTasksHeadline: "Filter Tasks", - /// - renameCategoryInputPlaceholder: "Rename category," - }, - calendar: { - eventsBoard: "Events", - /// - todayButtonAudioLabel: "go to today", - previousMonthButtonAudioLabel: "previous month", - nextMonthButtonAudioLabel: "next month", - yearInputAudioLabel: "year", - monthInputAudioLabel: "month", - yearInputPlaceholder: "2000", - monthInputPlaceholder: "01", - /// - searchEventsHeadline: "Search Events", - /// - events: "Events", - noEvents: "No events" - } - } - }; - var allTranslations = { - en: englishTranslations, - de: { - updater: { - migrated: "Migriert" - }, - general: { - deleteItemButtonAudioLabel: "element l\xF6schen", - searchButtonAudioLabel: "suchen", - abortButton: "Abbrechen", - cancelButton: "Abbrechen", - closeButton: "Schlie\xDFen", - backButton: "Zur\xFCck", - continueButton: "Weiter", - confirmButton: "Best\xE4tigen", - saveButton: "Speichern", - setButton: "OK", - reloadAppButton: "Neu laden", - fileVersionLabel: "Version", - searchLabel: "Suche", - restoreConnection: "Verbindung wiederherstellen" - }, - regional: { - weekdays: { - full: [ - "Sonntag", - "Montag", - "Dienstag", - "Mittwoch", - "Donnerstag", - "Freitag", - "Samstag" - ], - abbreviated: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"] - } - }, - homePage: { - appName: "Comms", - overviewHeadline: "\xDCbersicht", - serverAddress: "Serveradresse", - serverAddressPlaceholder: "wss://192.168.0.69:3000", - connectAudioLabel: "mit Server verbinden", - disconnectAudioLabel: "vom Server trennen", - manageConnectionsAudioLabel: "Verbindungen verwalten", - yourNameLabel: "Dein Name", - yourNamePlaceholder: "Max Mustermann", - setNameButtonAudioLabel: "Name speichern", - firstDayOfWeekLabel: "Erster Wochentag", - manageStorageButton: "Daten verwalten", - transferDataButton: "Daten\xFCbertragung", - scrollToChatButton: "Chats", - backToOverviewAudioLabel: "zur\xFCck zur \xFCbersicht", - chatsHeadline: "Chats", - addChatAudioLabel: "Name des neuen Chats", - addChatPlaceholder: "Chat hinzuf\xFCgen", - addChatButton: "Chat hinzuf\xFCgen" - }, - connectionModal: { - connectionModalHeadline: "Verbindungen verwalten", - connectButtonAudioLabel: "verbinden" - }, - dataTransferModal: { - transferDataHeadline: "Daten\xFCbertragung", - selectionDescription: "W\xE4hle die Daten aus, die du \xFCbertragen m\xF6chtest.", - dataEntryDescription: "Gib diese Informationen auf dem anderen Ger\xE4t ein.", - dataEntryInputDescription: "Gib die auf dem anderen Ger\xE4t angezeigten Informationen ein.", - readyToReceiveDescription: "Klicke auf dem anderen Ger\xE4t auf 'Senden'.", - notConnectedError: "Du bist mit keinem Server verbunden.", - fromThisDeviceButton: "Von diesem Ger\xE4t", - toThisDeviceButton: "An dieses Ger\xE4t", - generalHeadline: "Allgemein", - connectionData: "Verbindungsdaten", - settingsData: "Einstellungen", - chatsHeadline: "Chats", - transferChannelHeadline: "\xDCbertragungskanal", - transferKeyHeadline: "Schl\xFCssel", - sendButton: "Senden", - sendAgainButton: "Erneut senden", - filesSentCount: (count) => `Dateien gesendet: ${count}.`, - allFilesSent: "Fertig.", - filesReceivedCount: (count) => `Dateien empfangen: ${count}.` - }, - storage: { - noItemSelected: "Kein Element ausgew\xE4hlt", - notAFile: "(keine Datei)", - contentEmpty: "(leer)", - path: "Pfad", - content: "Inhalt", - deleteItem: "Element l\xF6schen", - removeJunkButton: "Datenm\xFCll l\xF6schen" - }, - chatPage: { - closeChatAudioLabe: "Chat schlie\xDFen", - chatSettingsAudioLabel: "Chateinstellungen", - pages: { - settings: "Einstellungen", - messages: "Nachrichten", - tasks: "Aufgaben", - calendar: "Kalender" - }, - settings: { - settingsHeadline: "Einstellungen", - primaryChannelLabel: "Hauptkanal", - setPrimaryChannelButtonAudioLabel: "Hauptkanal festlegen", - newSecondaryChannelPlaceholder: "Sekund\xE4ren Kanal hinzuf\xFCgen", - newSecondaryChannelAudioLabel: "Name des neuen sekund\xE4ren Kanals", - addSecondaryChannelButtonAudioLabel: "Sekund\xE4ren Kanal hinzuf\xFCgen", - encryptionKeyLabel: "Schl\xFCssel", - setEncryptionKeyButtonAudioLabel: "Schl\xFCssel festlegen", - showEncryptionKey: "Schl\xFCssel anzeigen", - deleteChatButton: "Gesamten Chat l\xF6schen" - }, - message: { - messagesHeadline: "Nachrichten", - composerInputPlaceholder: "Schreib eine Nachricht...", - sendMessageButtonAudioLabel: "nachricht senden", - showMessageInfoButtonAudioLabel: "nachrichteninfo anzeigen", - messageInfoHeadline: "Nachrichteninfo", - sentBy: "Gesendet von", - timeSent: "Sendezeit", - channel: "Kanal", - messageContent: "Nachrichteninhalt", - copyMessageButton: "Nachricht kopieren", - resendMessageButton: "Nachricht erneut senden", - decryptMessageButton: "Nachricht entschl\xFCsseln", - deleteMessageButton: "Nachricht l\xF6schen" - }, - task: { - newBoardNamePlaceholder: "Board erstellen", - createBoardButtonAudioLabel: "board erstellen", - noBoardSelected: "Kein Board ausgew\xE4hlt", - boardNotFound: "Board nicht gefunden", - closeBoardButtonAudioLabel: "board schlie\xDFen", - toggleBoardButtonAudioLabel: "board-liste ein/ausblenden", - showBoardSettingsButtonAudioLabel: "Board-Einstellungen anzeigen", - listViewButtonAudioLabel: "Listenansicht", - kanbanViewButtonAudioLabel: "Kanban-Ansicht", - statusViewButtonAudioLabel: "Statusrasteransicht", - filterTasksButtonAudioLabel: "Aufgaben filtern", - createTaskButtonAudioLabel: "Neue Aufgabe erstellen", - boardSettingsHeadline: "Board-Einstellungen", - boardNameInputLabel: "Boardname", - deleteBoardButton: "Board und alle Aufgaben l\xF6schen", - taskSettingsHeadline: "Aufgabe bearbeiten", - taskNameLabel: "Titel", - taskBoardLabel: "Board", - taskCategoryLabel: "Kategorie", - taskStatusLabel: "Status", - taskPriorityLabel: "Priorit\xE4t", - taskDescriptionLabel: "Beschreibung", - taskDateLabel: "Datum", - taskTimeLabel: "Uhrzeit", - deleteTaskButton: "Aufgabe l\xF6schen", - filterTasksHeadline: "Aufgaben filtern", - renameCategoryInputPlaceholder: "Kategorie umbenennen" - }, - calendar: { - eventsBoard: "Ereignisse", - /// - todayButtonAudioLabel: "gehe zu heute", - previousMonthButtonAudioLabel: "vorheriger monat", - nextMonthButtonAudioLabel: "n\xE4chster monat", - yearInputAudioLabel: "Jahr", - monthInputAudioLabel: "Monat", - yearInputPlaceholder: "2000", - monthInputPlaceholder: "01", - searchEventsHeadline: "Ereignisse suchen", - events: "Ereignisse", - noEvents: "Keine Ereignisse" - } - } - }, - es: { - updater: { - migrated: "Migrado" - }, - general: { - deleteItemButtonAudioLabel: "eliminar elemento", - searchButtonAudioLabel: "buscar", - abortButton: "Abortar", - cancelButton: "Cancelar", - closeButton: "Cerrar", - backButton: "Atr\xE1s", - continueButton: "Continuar", - confirmButton: "Confirmar", - saveButton: "Guardar", - setButton: "OK", - reloadAppButton: "Recargar app", - fileVersionLabel: "Versi\xF3n", - searchLabel: "Buscar", - restoreConnection: "Conectar de nuevo" - }, - regional: { - weekdays: { - full: [ - "Domingo", - "Lunes", - "Martes", - "Mi\xE9rcoles", - "Jueves", - "Viernes", - "S\xE1bado" - ], - abbreviated: ["Dom", "Lun", "Mar", "Mi\xE9", "Jue", "Vie", "S\xE1b"] - } - }, - homePage: { - appName: "Comms", - overviewHeadline: "Resumen", - serverAddress: "Direcci\xF3n del servidor", - serverAddressPlaceholder: "wss://192.168.0.69:3000", - connectAudioLabel: "conectar al servidor", - disconnectAudioLabel: "desconectar del servidor", - manageConnectionsAudioLabel: "gestionar conexiones", - yourNameLabel: "Tu nombre", - yourNamePlaceholder: "Juan P\xE9rez", - setNameButtonAudioLabel: "establecer nombre", - firstDayOfWeekLabel: "Primer d\xEDa de la semana", - manageStorageButton: "Gestionar almacenamiento", - transferDataButton: "Transferencia de datos", - scrollToChatButton: "Chats", - backToOverviewAudioLabel: "volver al resumen", - chatsHeadline: "Chats", - addChatAudioLabel: "nombre del nuevo chat", - addChatPlaceholder: "A\xF1adir chat", - addChatButton: "A\xF1adir chat" - }, - connectionModal: { - connectionModalHeadline: "Gestionar Conexiones", - connectButtonAudioLabel: "conectar" - }, - dataTransferModal: { - transferDataHeadline: "Transferencia de Datos", - selectionDescription: "Selecciona los datos que quieres transferir.", - dataEntryDescription: "Introduce estos datos en el otro dispositivo.", - dataEntryInputDescription: "Introduce los datos mostrados en el otro dispositivo.", - readyToReceiveDescription: "Haz clic en 'enviar' en el otro dispositivo.", - notConnectedError: "No est\xE1s conectado a ning\xFAn servidor.", - fromThisDeviceButton: "Desde este dispositivo", - toThisDeviceButton: "A este dispositivo", - generalHeadline: "General", - connectionData: "Datos de Conexi\xF3n", - settingsData: "Datos de Configuraci\xF3n", - chatsHeadline: "Chats", - transferChannelHeadline: "Canal de Transferencia", - transferKeyHeadline: "Clave de Encriptaci\xF3n de Transferencia", - sendButton: "Enviar", - sendAgainButton: "Enviar otra vez", - filesSentCount: (count) => `Archivos enviados: ${count}.`, - allFilesSent: "Hecho.", - filesReceivedCount: (count) => `Archivos recibidos: ${count}.` - }, - storage: { - noItemSelected: "Ning\xFAn elemento seleccionado", - notAFile: "(no es un archivo)", - contentEmpty: "(vac\xEDo)", - path: "Ruta", - content: "Contenido", - deleteItem: "Eliminar elemento", - removeJunkButton: "Eliminar archivos basura" - }, - chatPage: { - closeChatAudioLabe: "cerrar chat", - chatSettingsAudioLabel: "configuraci\xF3n del chat", - pages: { - settings: "Configuraci\xF3n", - messages: "Mensajes", - tasks: "Tareas", - calendar: "Calendario" - }, - settings: { - settingsHeadline: "Configuraci\xF3n", - primaryChannelLabel: "Canal principal", - setPrimaryChannelButtonAudioLabel: "establecer canal principal", - newSecondaryChannelPlaceholder: "A\xF1adir canal secundario", - newSecondaryChannelAudioLabel: "nombre del nuevo canal secundario", - addSecondaryChannelButtonAudioLabel: "a\xF1adir canal secundario", - encryptionKeyLabel: "Clave de encriptaci\xF3n", - setEncryptionKeyButtonAudioLabel: "establecer clave de encriptaci\xF3n", - showEncryptionKey: "Mostrar clave de encriptaci\xF3n", - deleteChatButton: "Eliminar todo el chat" - }, - message: { - messagesHeadline: "Mensajes", - composerInputPlaceholder: "Escribe un mensaje...", - sendMessageButtonAudioLabel: "enviar mensaje", - showMessageInfoButtonAudioLabel: "mostrar informaci\xF3n del mensaje", - messageInfoHeadline: "Informaci\xF3n del Mensaje", - sentBy: "Enviado por", - timeSent: "Hora de env\xEDo", - channel: "Canal", - messageContent: "Contenido del mensaje", - copyMessageButton: "Copiar mensaje", - resendMessageButton: "Reenviar mensaje", - decryptMessageButton: "Desencriptar mensaje", - deleteMessageButton: "Eliminar mensaje" - }, - task: { - newBoardNamePlaceholder: "Crear un tablero", - createBoardButtonAudioLabel: "crear tablero", - noBoardSelected: "Ning\xFAn tablero seleccionado", - boardNotFound: "Tablero no encontrado", - closeBoardButtonAudioLabel: "cerrar tablero", - toggleBoardButtonAudioLabel: "mostrar o ocultar lista de tableros", - showBoardSettingsButtonAudioLabel: "mostrar configuraci\xF3n del tablero", - listViewButtonAudioLabel: "vista de lista", - kanbanViewButtonAudioLabel: "vista kanban", - statusViewButtonAudioLabel: "vista de cuadr\xEDcula de estado", - filterTasksButtonAudioLabel: "filtrar tareas", - createTaskButtonAudioLabel: "crear nueva tarea", - boardSettingsHeadline: "Configuraci\xF3n del Tablero", - boardNameInputLabel: "Nombre del tablero", - deleteBoardButton: "Eliminar tablero y todas las tareas", - taskSettingsHeadline: "Editar Tarea", - taskNameLabel: "T\xEDtulo", - taskBoardLabel: "Tablero", - taskCategoryLabel: "Categor\xEDa", - taskStatusLabel: "Estado", - taskPriorityLabel: "Prioridad", - taskDescriptionLabel: "Descripci\xF3n", - taskDateLabel: "Fecha", - taskTimeLabel: "Hora", - deleteTaskButton: "Eliminar tarea", - filterTasksHeadline: "Filtrar Tareas", - renameCategoryInputPlaceholder: "Renombrar categor\xEDa" - }, - calendar: { - eventsBoard: "Eventos", - /// - todayButtonAudioLabel: "ir a hoy", - previousMonthButtonAudioLabel: "mes anterior", - nextMonthButtonAudioLabel: "mes siguiente", - yearInputAudioLabel: "a\xF1o", - monthInputAudioLabel: "mes", - yearInputPlaceholder: "2000", - monthInputPlaceholder: "01", - searchEventsHeadline: "Buscar Eventos", - events: "Eventos", - noEvents: "No hay eventos" - } - } - } - }; - var language = navigator.language.substring(0, 2); - var translations = allTranslations[language] || allTranslations.en; - - // src/ViewModel/Chat/chatViewModel.ts - var ChatViewModel = class { - // init - constructor(coreViewModel, storageModel2, chatModel, settingsViewModel2, connectionViewModel2, chatListViewModel2) { - this.coreViewModel = coreViewModel; - // state - this.displayedColor = new State("standard" /* Standard */); - this.selectedPage = new State( - "messages" /* Messages */ - ); - this.index = new State(0); - this.hasUnreadMessages = new State(false); - this.taskBoardSuggestions = new MapState(); - // view - this.open = () => { - this.chatListViewModel.openChat(this); - this.markRead(); - }; - this.close = () => { - this.chatListViewModel.closeChat(); - }; - this.closeSubPages = () => { - }; - this.setColor = (color) => { - this.setDisplayedColor(color); - this.chatModel.setColor(color); - }; - this.setDisplayedColor = (color) => { - this.displayedColor.value = color; - }; - this.resetColor = () => { - this.displayedColor.value = this.settingsPageViewModel.color.value; - }; - this.updateIndex = () => { - const index = this.chatListViewModel.chatIndexManager.getIndex(this); - this.index.value = index; - }; - this.markRead = () => { - this.hasUnreadMessages.value = false; - this.chatModel.markRead(); - }; - // load - this.loadPageSelection = () => { - const path = StorageModel.getPath( - "chat" /* Chat */, - filePaths.chat.lastUsedPage(this.chatModel.id) - ); - const lastUsedPage = this.storageModel.read(path); - if (lastUsedPage != null) { - this.selectedPage.value = lastUsedPage; - } - this.selectedPage.subscribeSilent((newPage) => { - this.storageModel.write(path, newPage); - this.resetColor(); - }); - }; - this.loadInfo = () => { - this.hasUnreadMessages.value = this.chatModel.info.hasUnreadMessages; - this.taskBoardSuggestions.set(CALENDAR_EVENT_BOARD_ID, [ - CALENDAR_EVENT_BOARD_ID, - translations.chatPage.calendar.eventsBoard - ]); - }; - this.storageModel = storageModel2; - this.chatModel = chatModel; - this.settingsViewModel = settingsViewModel2; - this.connectionViewModel = connectionViewModel2; - this.chatListViewModel = chatListViewModel2; - this.calendarViewModel = new CalendarPageViewModel( - coreViewModel, - this, - this.storageModel, - this.chatModel.fileModel.boardsAndTasksModel.calendarModel, - this.chatModel.fileModel.boardsAndTasksModel - ); - this.taskPageViewModel = new TaskPageViewModel( - this.coreViewModel, - this, - this.storageModel, - this.chatModel.fileModel.boardsAndTasksModel - ); - this.messagePageViewModel = new MessagePageViewModel( - this.coreViewModel, - this - ); - this.settingsPageViewModel = new SettingsPageViewModel( - this.coreViewModel, - this - ); - chatModel.chatMessageHandlerManager.addHandler( - (chatMessage) => { - this.messagePageViewModel.showChatMessage(chatMessage); - this.markRead(); - } - ); - this.loadPageSelection(); - this.resetColor(); - this.loadInfo(); - } - }; - - // src/ViewModel/Chat/chatListViewModel.ts - var ChatListViewModel = class { - // init - constructor(coreViewModel, storageModel2, chatListModel2, settingsViewModel2, connectionViewModel2) { - this.coreViewModel = coreViewModel; - // data - this.chatIndexManager = new IndexManager( - (chatViewModel) => chatViewModel.settingsPageViewModel.primaryChannel.value - ); - // state - this.newChatPrimaryChannel = new State(""); - this.chatViewModels = new ListState(); - this.selectedChat = new State( - void 0 - ); - // guards - this.cannotCreateChat = createProxyState( - [this.newChatPrimaryChannel], - () => this.newChatPrimaryChannel.value == "" - ); - // methods - this.createChat = () => { - const chatModel = this.chatListModel.createChat( - this.newChatPrimaryChannel.value - ); - this.newChatPrimaryChannel.value = ""; - const chatViewModel = this.createChatViewModel(chatModel); - this.trackChat(chatViewModel); - this.updateIndices(); - }; - this.trackChat = (chatViewModel) => { - this.chatViewModels.add(chatViewModel); - }; - this.untrackChat = (chatViewModel) => { - this.chatListModel.untrackChat(chatViewModel.chatModel); - this.chatViewModels.remove(chatViewModel); - }; - this.createChatViewModel = (chatModel) => { - return new ChatViewModel( - this.coreViewModel, - this.storageModel, - chatModel, - this.settingsViewModel, - this.connectionViewModel, - this - ); - }; - this.updateIndices = () => { - this.chatIndexManager.update([...this.chatViewModels.value.values()]); - for (const chatViewModel of this.chatViewModels.value) { - chatViewModel.updateIndex(); - } - }; - // view - this.openChat = (chatViewModel) => { - this.selectedChat.value = chatViewModel; - }; - this.closeChat = () => { - this.selectedChat.value = void 0; - }; - // load - this.loadChats = () => { - this.chatViewModels.clear(); - for (const chatModel of this.chatListModel.chatModels.values()) { - const chatViewModel = this.createChatViewModel(chatModel); - this.trackChat(chatViewModel); - } - this.updateIndices(); - }; - this.storageModel = storageModel2; - this.chatListModel = chatListModel2; - this.settingsViewModel = settingsViewModel2; - this.connectionViewModel = connectionViewModel2; - this.loadChats(); - } - }; - - // src/View/Components/monthGrid.tsx - function MonthGrid2(monthGrid, selectedDate, handleDrop) { - const dayLabels = []; - let currentWeekday = monthGrid.firstDayOfWeek; - while (dayLabels.length < 7) { - dayLabels.push( - /* @__PURE__ */ createElement("span", { class: "secondary" }, 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 = monthGrid.isCurrentMonth == true && parseInt(date) == (/* @__PURE__ */ new Date()).getDate(); - 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": 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); - } - var StringToOption = (string) => { - return Option(string, string, false); - }; - var VersionIdToOption = (versionId) => { - const [date, rest] = versionId.split("T"); - const [time] = rest.split("."); - const readableName = `${date} ${time}`; - return Option(readableName, versionId, false); - }; - - // src/View/Components/dangerousActionButton.tsx - function DangerousActionButton(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 }, 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 - }, - translations.general.confirmButton, - /* @__PURE__ */ createElement("span", { class: "icon" }, "warning") - )); - } - - // src/View/Modals/taskSettingsModal.tsx - function TaskSettingsModal(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, 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, 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, translations.chatPage.task.taskNameLabel), /* @__PURE__ */ createElement("input", { "bind:value": taskViewModel.name }))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "category"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskBoardLabel), /* @__PURE__ */ createElement( - "select", - { - "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, 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, 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, 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, translations.chatPage.task.taskPriorityLabel), /* @__PURE__ */ createElement( - "input", - { - type: "number", - "bind:value": taskViewModel.priority - } - ))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "calendar_month"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskDateLabel), /* @__PURE__ */ createElement( - "input", - { - type: "date", - "bind:value": taskViewModel.date - } - ))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "schedule"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskTimeLabel), /* @__PURE__ */ createElement( - "input", - { - type: "time", - "bind:value": taskViewModel.time - } - ))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton( - translations.chatPage.task.deleteTaskButton, - "delete_forever", - taskViewModel.deleteTask - ))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement( - "button", - { - class: "flex", - "on:click": taskViewModel.closeAndDiscard - }, - translations.general.closeButton - ), /* @__PURE__ */ createElement( - "button", - { - class: "flex primary", - "on:click": taskViewModel.closeAndSave - }, - translations.general.saveButton, - /* @__PURE__ */ createElement("span", { class: "icon" }, "save") - )))); - } - - // src/View/Components/taskEntry.tsx - function TaskEntry(taskViewModel) { - const details = { - description: taskViewModel.description.value || "---", - priority_high: taskViewModel.priority.value || "---", - category: taskViewModel.category.value || "---", - clock_loader_40: taskViewModel.status.value || "---", - calendar_month: taskViewModel.date.value || "---", - schedule: taskViewModel.time.value || "---" - }; - const view = /* @__PURE__ */ createElement( - "button", - { - draggable: "true", - class: "tile flex-no", - style: "user-select: none; -webkit-user-select: none", - "on:click": taskViewModel.open, - "on:dragstart": taskViewModel.dragStart - }, - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement( - "b", - { - class: "ellipsis", - "subscribe:innerText": taskViewModel.name - } - ), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( - "div", - { - class: "grid secondary", - style: "grid-template-columns: repeat(2, 1fr); column-gap: 1rem; row-gap: .5rem" - }, - ...Object.entries(details).map((entry) => /* @__PURE__ */ createElement( - "span", - { - class: "flex-row align-center width-100 flex-no clip", - style: "gap: 1rem" - }, - /* @__PURE__ */ createElement("span", { class: "icon", style: "font-size: 1.1rem" }, entry[0]), - /* @__PURE__ */ createElement("span", { class: "ellipsis" }, entry[1]) - )) - )) - ); - taskViewModel.index.subscribe((newIndex) => { - view.style.order = newIndex; - }); - return view; - } - var TaskViewModelToEntry = (taskViewModel) => { - return TaskEntry(taskViewModel); - }; - - // src/View/ChatPages/calendarPage.tsx - function CalendarPage(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( - 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 /* @__PURE__ */ createElement("div", { class: "width-100 height-100 flex-column justify-center align-center" }, /* @__PURE__ */ createElement("span", { class: "secondary slide-up" }, translations.chatPage.calendar.noEvents)); - } else { - return /* @__PURE__ */ createElement( - "div", - { - class: "flex-column gap slide-up padding-bottom", - "children:append": [ - listState, - TaskViewModelToEntry - ] - } + // node_modules/uuid/dist/esm-browser/rng.js + var getRandomValues; + var rnds8 = new Uint8Array(16); + function rng() { + if (!getRandomValues) { + getRandomValues = + typeof crypto !== "undefined" && + crypto.getRandomValues && + crypto.getRandomValues.bind(crypto); + if (!getRandomValues) { + throw new Error( + "crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported", ); - } } - ); - return /* @__PURE__ */ createElement( + } + return getRandomValues(rnds8); + } + + // node_modules/uuid/dist/esm-browser/native.js + var randomUUID = + typeof crypto !== "undefined" && + crypto.randomUUID && + crypto.randomUUID.bind(crypto); + var native_default = { + randomUUID, + }; + + // node_modules/uuid/dist/esm-browser/v4.js + function v4(options, buf, offset) { + if (native_default.randomUUID && !buf && !options) { + return native_default.randomUUID(); + } + options = options || {}; + var rnds = options.random || (options.rng || rng)(); + rnds[6] = (rnds[6] & 15) | 64; + rnds[8] = (rnds[8] & 63) | 128; + if (buf) { + offset = offset || 0; + for (var i = 0; i < 16; ++i) { + buf[offset + i] = rnds[i]; + } + return buf; + } + return unsafeStringify(rnds); + } + var v4_default = v4; + + // node_modules/bloatless-react/index.ts + var State = class { + _value; + _bindings = /* @__PURE__ */ new Set(); + // init + constructor(initialValue) { + this._value = initialValue; + } + // value + get value() { + return this._value; + } + set value(newValue) { + if (this._value == newValue) return; + this._value = newValue; + this.callSubscriptions(); + } + // subscriptions + callSubscriptions() { + this._bindings.forEach((fn) => fn(this._value)); + } + subscribe(fn) { + this._bindings.add(fn); + fn(this._value); + } + subscribeSilent(fn) { + this._bindings.add(fn); + } + // stringify + toString() { + return JSON.stringify(this._value); + } + }; + var ListState = class extends State { + additionHandlers = /* @__PURE__ */ new Set(); + removalHandlers = /* @__PURE__ */ new Map(); + // init + constructor(initialItems) { + super(new Set(initialItems)); + } + // list + add(...items) { + items.forEach((item) => { + this.value.add(item); + this.additionHandlers.forEach((handler) => handler(item)); + }); + this.callSubscriptions(); + } + remove(...items) { + items.forEach((item) => { + this.value.delete(item); + if (!this.removalHandlers.has(item)) return; + this.removalHandlers + .get(item) + .forEach((handler) => handler(item)); + this.removalHandlers.delete(item); + }); + this.callSubscriptions(); + } + clear() { + this.remove(...this.value.values()); + } + // handlers + handleAddition(handler) { + this.additionHandlers.add(handler); + [...this.value.values()].forEach(handler); + } + handleRemoval(item, handler) { + if (!this.removalHandlers.has(item)) + this.removalHandlers.set(item, /* @__PURE__ */ new Set()); + this.removalHandlers.get(item).add(handler); + } + // stringification + toString() { + const array = [...this.value.values()]; + const json = JSON.stringify(array); + return json; + } + }; + var MapState = class extends State { + additionHandlers = /* @__PURE__ */ new Set(); + removalHandlers = /* @__PURE__ */ new Map(); + // init + constructor(initialItems) { + super(new Map(initialItems)); + } + // list + set(key, item) { + this.remove(key); + this.value.set(key, item); + this.additionHandlers.forEach((handler) => handler(item)); + this.callSubscriptions(); + } + remove(key) { + const item = this.value.get(key); + if (!item) return; + this.value.delete(key); + this.callSubscriptions(); + if (!this.removalHandlers.has(item)) return; + this.removalHandlers.get(item).forEach((handler) => handler(item)); + this.removalHandlers.delete(item); + } + clear() { + [...this.value.keys()].forEach((key) => this.remove(key)); + } + // handlers + handleAddition(handler) { + this.additionHandlers.add(handler); + [...this.value.values()].forEach(handler); + } + handleRemoval(item, handler) { + if (!this.removalHandlers.has(item)) + this.removalHandlers.set(item, /* @__PURE__ */ new Set()); + this.removalHandlers.get(item).add(handler); + } + // stringification + toString() { + const array = [...this.value.entries()]; + const json = JSON.stringify(array); + return json; + } + }; + function createProxyState(statesToSubscibe, fn) { + const proxyState = new State(fn()); + statesToSubscibe.forEach((state) => + state.subscribe(() => (proxyState.value = fn())), + ); + return proxyState; + } + function bulkSubscribe(statesToSubscibe, fn) { + statesToSubscibe.forEach((state) => state.subscribeSilent(fn)); + } + function createElement(tagName, attributes = {}, ...children) { + const element = document.createElement(tagName); + if (attributes != null) + Object.entries(attributes).forEach((entry) => { + const [attributename, value] = entry; + const [directiveKey, directiveValue] = attributename.split(":"); + switch (directiveKey) { + case "on": { + switch (directiveValue) { + case "enter": { + element.addEventListener("keydown", (e) => { + if (e.key != "Enter") return; + value(); + }); + break; + } + default: { + element.addEventListener(directiveValue, value); + } + } + break; + } + case "subscribe": { + const state = value; + state.subscribe( + (newValue) => (element[directiveValue] = newValue), + ); + break; + } + case "bind": { + const state = value; + state.subscribe( + (newValue) => (element[directiveValue] = newValue), + ); + element.addEventListener( + "input", + () => (state.value = element[directiveValue]), + ); + break; + } + case "toggle": { + if (value.subscribe) { + const state = value; + state.subscribe((newValue) => + element.toggleAttribute( + directiveValue, + newValue, + ), + ); + } else { + element.toggleAttribute(directiveValue, value); + } + break; + } + case "set": { + const state = value; + state.subscribe((newValue) => + element.setAttribute(directiveValue, newValue), + ); + break; + } + case "children": { + switch (directiveValue) { + case "set": { + const state = value; + state.subscribe((newValue) => { + element.innerHTML = ""; + element.append(...[newValue].flat()); + }); + break; + } + case "append": + case "prepend": { + try { + const [listState, toElement] = value; + listState.handleAddition((newItem) => { + const child = toElement(newItem); + listState.handleRemoval(newItem, () => + child.remove(), + ); + if (directiveValue == "append") { + element.append(child); + } else if ( + directiveValue == "prepend" + ) { + element.prepend(child); + } + }); + } catch (error) { + console.error(error); + throw `error: cannot process subscribe:children directive. + Usage: "children:append={[list, converter]}"; you can find a more detailed example in the documentation.`; + } + } + } + break; + } + default: + element.setAttribute(attributename, value); + } + }); + children.filter((x) => x).forEach((child) => element.append(child)); + 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); + return array.join(""); + } + function createTimestamp() { + return /* @__PURE__ */ new Date().toISOString(); + } + function checkDoesObjectMatchReference(reference, stringEntryObject) { + 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 ( + 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 || + stringEntryObjectValue == "" + ) + 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 Set(); + // manage + this.addHandler = (handler) => { + this.handlers.add(handler); + }; + this.deleteHandler = (handler) => { + this.handlers.delete(handler); + }; + // trigger + this.trigger = (item) => { + for (const handler of this.handlers) { + 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"], + lastUsedPage: (id) => [ + ...filePaths.chat.chatBase(id), + "last-used-page", + ], + files: (id) => [...filePaths.chat.chatBase(id), "files"], + }, + settingsModel: { + username: ["user-name"], + firstDayOfWeek: ["first-day-of-week"], + }, + }; + + // 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 = (year, month, defaultValueCreator) => { + const date = /* @__PURE__ */ new Date(); + 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 Color = /* @__PURE__ */ ((Color2) => { + Color2["Standard"] = "standard"; + Color2["Coral"] = "coral"; + Color2["Yellow"] = "yellow"; + Color2["Mint"] = "mint"; + Color2["LightBlue"] = "lightblue"; + Color2["Blue"] = "blue"; + Color2["purple"] = "purple"; + return Color2; + })(Color || {}); + + // 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("", 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("", 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); + }; + // methods + this.addFileContentAndSend = (fileContent) => { + this.handleFileContent(fileContent); + this.chatModel.sendMessage("", 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(); + // 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]; + }; + // handlers + this.handleMessage = (body) => { + const chatMessage = parseValidObject( + body, + ChatMessageReference, + ); + if (chatMessage == null) return; + chatMessage.status = "received" /* Received */; + this.addMessage(chatMessage); + this.info.hasUnreadMessages = true; + this.storeInfo(); + }; + 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.sendMessage = async (body, fileContent) => { + 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("/"); + const chatMessage = await _ChatModel.createChatMessage( + combinedChannel, + senderName, + this.info.encryptionKey, + body, + 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.subscribe = () => { + this.connectionModel.addChannel(this.info.primaryChannel); + }; + this.markRead = () => { + this.info.hasUnreadMessages = false; + 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, + ); + chatMessages.push(chatMessage); + } + const sorted = chatMessages.sort((a, b) => + a.dateSent.localeCompare(b.dateSent), + ); + return sorted; + } + static { + // utility + this.generateChatInfo = (primaryChannel) => { + return { + dataVersion: DATA_VERSION, + primaryChannel, + secondaryChannels: [], + encryptionKey: "", + hasUnreadMessages: false, + }; + }; + } + static { + this.createChatMessage = async ( + channel, + sender, + encryptionKey, + body, + fileContent, + ) => { + const chatMessage = { + dataVersion: DATA_VERSION, + id: v4_default(), + channel, + sender, + body, + dateSent: createTimestamp(), + 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; + }; + } + }; + var ChatInfoReference = { + dataVersion: DATA_VERSION, + primaryChannel: "", + secondaryChannels: [""], + encryptionKey: "", + hasUnreadMessages: true, + }; + var ChatMessageReference = { + dataVersion: DATA_VERSION, + id: "", + channel: "", + sender: "", + body: "", + dateSent: "", + status: "", + stringifiedFile: "", + }; + + // 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.addHandler( + this.messageHandler, + ); + connectionModel2.messageSentHandlerManager.addHandler( + this.messageSentHandler, + ); + } + }; + + // src/View/utility.ts + function allowDrop(event) { + event.preventDefault(); + } + function allowDrag(event) { + event.dataTransfer?.setData("text", ""); + } + function reload() { + window.location.reload(); + } + + // src/ViewModel/Pages/taskViewModel.ts + var TaskViewModel = class { + // init + constructor( + coreViewModel, + chatViewModel, + boardsAndTasksModel, + containingModel, + taskFileContent, + ) { + this.coreViewModel = coreViewModel; + this.chatViewModel = chatViewModel; + // paths + this.getFilePath = () => { + return this.boardsAndTasksModel.getTaskFilePath( + this.task.fileId, + ); + }; + // state + this.index = new State(0); + this.boardId = new State(""); + this.name = new State(""); + this.description = new State(""); + this.category = new State(""); + this.status = new State(""); + this.priority = new State(""); + this.date = new State(""); + this.time = new State(""); + this.selectedVersionId = new State(""); + this.versionIds = new ListState(); + // methods + this.dragStart = (event) => { + allowDrag(event); + this.coreViewModel.draggedObject.value = this; + }; + this.setCategoryAndStatus = (category, status) => { + if (category != void 0) this.category.value = category; + if (status != void 0) this.status.value = status; + this.save(); + }; + this.setBoardId = (boardId) => { + this.boardId.value = boardId; + this.save(); + }; + this.setDate = (dateISOString) => { + this.date.value = dateISOString; + this.save(); + }; + // view + this.open = () => { + this.containingModel.selectTask(this); + }; + this.close = () => { + this.containingModel.closeTask(); + }; + this.closeAndDiscard = () => { + this.close(); + this.loadTaskData(); + }; + this.closeAndSave = () => { + this.close(); + this.save(); + }; + this.updateIndex = () => { + const index = + this.containingModel.taskIndexManager.getIndex(this); + this.index.value = index; + }; + this.updateSuggestions = () => { + if ( + this.coreViewModel.taskCategorySuggestions.value.has( + this.category.value, + ) == false + ) { + this.coreViewModel.taskCategorySuggestions.add( + this.category.value, + ); + } + if ( + this.coreViewModel.taskStatusSuggestions.value.has( + this.status.value, + ) == false + ) { + this.coreViewModel.taskStatusSuggestions.add( + this.status.value, + ); + } + }; + // settings + this.save = () => { + const newTaskFileContent = + BoardsAndTasksModel.createTaskFileContent( + this.task.fileId, + this.name.value, + this.task.boardId, + ); + newTaskFileContent.boardId = this.boardId.value; + newTaskFileContent.description = this.description.value; + newTaskFileContent.status = this.status.value; + newTaskFileContent.category = this.category.value; + newTaskFileContent.priority = this.priority.value; + newTaskFileContent.date = this.date.value; + newTaskFileContent.time = this.time.value; + this.boardsAndTasksModel.updateTaskAndSend(newTaskFileContent); + this.containingModel.showTask(newTaskFileContent); + this.containingModel.updateTaskIndices(); + this.updateSuggestions(); + }; + this.deleteTask = () => { + this.close(); + this.boardsAndTasksModel.deleteTask( + this.task.boardId, + this.task.fileId, + ); + this.containingModel.removeTaskFromView(this.task); + }; + // load + this.loadVersionIds = () => { + const versionIds = this.boardsAndTasksModel.listTaskVersionIds( + this.task.fileId, + ); + const sortedVersionIds = versionIds + .sort(localeCompare) + .reverse(); + this.versionIds.clear(); + this.versionIds.add(...sortedVersionIds); + }; + this.switchVersion = (versionId) => { + const taskFileContent = + this.boardsAndTasksModel.getSpecificTaskFileContent( + this.task.fileId, + versionId, + ); + if (taskFileContent == null) return; + this.task = taskFileContent; + this.loadTaskData(); + }; + this.loadAllData = () => { + this.loadTaskData(); + this.loadVersionIds(); + }; + this.loadTaskData = () => { + this.boardId.value = this.task.boardId; + this.name.value = this.task.name; + this.description.value = this.task.description ?? ""; + this.category.value = this.task.category ?? ""; + this.status.value = this.task.status ?? ""; + this.priority.value = this.task.priority ?? ""; + this.date.value = this.task.date ?? ""; + this.time.value = this.task.time ?? ""; + this.selectedVersionId.value = this.task.fileContentId; + this.updateSuggestions(); + }; + this.boardsAndTasksModel = boardsAndTasksModel; + this.containingModel = containingModel; + this.task = taskFileContent; + this.loadAllData(); + this.selectedVersionId.subscribeSilent((selectedVersionId) => { + this.switchVersion(selectedVersionId); + }); + } + get sortingString() { + const splitDate = this.date.value.split("-"); + const year = padZero(splitDate[0], 4); + const month = padZero(splitDate[1], 2); + const date = padZero(splitDate[2], 2); + const splitTime = this.time.value.split(":"); + const hour = padZero(splitTime[0], 2); + const minute = padZero(splitTime[1], 2); + const priorityNumber = parseInt(this.priority.value); + const invertedPriority = 100 - priorityNumber; + return ( + year + + month + + date + + hour + + minute + + invertedPriority + + this.name.value + ); + } + static { + // utility + this.getStringsForFilter = (taskViewModel) => { + return [ + taskViewModel.task.name, + taskViewModel.task.description ?? "", + taskViewModel.task.category ?? "", + taskViewModel.task.status ?? "", + taskViewModel.task.priority ?? "", + taskViewModel.task.date ?? "", + taskViewModel.task.time ?? "", + ]; + }; + } + }; + + // src/ViewModel/Pages/taskContainingPageViewModel.ts + var TaskContainingPageViewModel = class { + // init + constructor(coreViewModel, chatViewModel, boardsAndTasksModel) { + this.coreViewModel = coreViewModel; + this.chatViewModel = chatViewModel; + // state + this.taskIndexManager = new IndexManager( + (taskViewModel) => taskViewModel.sortingString, + ); + this.selectedTaskViewModel = new State(void 0); + this.taskViewModels = new MapState(); + // methods + this.createTaskFromBoardId = (boardId) => { + const taskFileContent = + this.boardsAndTasksModel.createTask(boardId); + const taskViewModel = new TaskViewModel( + this.coreViewModel, + this.chatViewModel, + this.boardsAndTasksModel, + this, + taskFileContent, + ); + this.selectTask(taskViewModel); + this.updateTaskIndices(); + }; + // view + this.showTask = (taskFileContent) => {}; + this.removeTaskFromView = (taskFileContent) => {}; + this.selectTask = (selectedTask) => { + this.selectedTaskViewModel.value = selectedTask; + }; + this.closeTask = () => { + this.selectedTaskViewModel.value = void 0; + }; + this.updateTaskIndices = () => { + this.taskIndexManager.update([ + ...this.taskViewModels.value.values(), + ]); + for (const boardViewModel of this.taskViewModels.value.values()) { + boardViewModel.updateIndex(); + } + }; + this.boardsAndTasksModel = boardsAndTasksModel; + } + }; + + // src/ViewModel/Pages/calendarPageViewModel.ts + var CALENDAR_EVENT_BOARD_ID = "events"; + var CalendarPageViewModel = class extends TaskContainingPageViewModel { + // init + constructor( + coreViewModel, + chatViewModel, + storageModel2, + calendarModel, + boardsAndTasksModel, + ) { + super(coreViewModel, chatViewModel, boardsAndTasksModel); + this.coreViewModel = coreViewModel; + this.chatViewModel = chatViewModel; + // paths + this.getBasePath = () => { + return [...this.calendarModel.getViewPath()]; + }; + // state + 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, + ); + this.selectTask(taskViewModel); + 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.removeTaskFromView = (taskFileContent) => { + this.taskViewModels.remove(taskFileContent.fileId); + }; + this.showToday = () => { + const today = /* @__PURE__ */ new Date(); + 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); + }; + // load + this.loadMonthTasks = () => { + this.monthGrid.value = this.calendarModel.generateMonthGrid( + this.selectedYear.value, + this.selectedMonth.value, + () => new MapState(), + ); + const taskIds = this.calendarModel.listTaskIds( + this.monthString, + ); + for (const taskId of taskIds) { + const taskFileContent = + this.boardsAndTasksModel.getLatestTaskFileContent( + taskId, + ); + if (taskFileContent == null) continue; + this.showTask(taskFileContent); + } + this.updateTaskIndices(); + }; + this.loadData = () => { + this.loadMonthTasks(); + this.showToday(); + }; + this.storageModel = storageModel2; + this.calendarModel = calendarModel; + this.boardsAndTasksModel = boardsAndTasksModel; + this.chatViewModel = chatViewModel; + bulkSubscribe([this.selectedYear, this.selectedMonth], () => { + this.loadMonthTasks(); + }); + boardsAndTasksModel.taskHandlerManager.addHandler( + (taskFileContent) => { + this.showTask(taskFileContent); + }, + ); + } + // data + get monthString() { + return CalendarModel.getMonthString( + this.selectedYear.value.toString(), + this.selectedMonth.value.toString(), + ); + } + }; + + // src/ViewModel/Chat/chatMessageViewModel.ts + var ChatMessageViewModel = class { + // init + constructor( + coreViewModel, + messagePageViewModel, + chatMessage, + sentByUser, + ) { + this.coreViewModel = coreViewModel; + this.body = new State(""); + this.status = new State(void 0); + // 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); + }; + // view + this.showInfoModal = () => { + this.isPresentingInfoModal.value = true; + }; + this.hideInfoModal = () => { + this.isPresentingInfoModal.value = false; + }; + // 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; + }; + this.messagePageViewModel = messagePageViewModel; + this.chatMessage = chatMessage; + this.sentByUser = sentByUser; + this.loadData(); + } + }; + + // src/ViewModel/Pages/messagePageViewModel.ts + var MessagePageViewModel = class { + // init + constructor(coreViewModel, chatViewModel) { + this.coreViewModel = coreViewModel; + // state + this.chatMessageViewModels = new MapState(); + this.composingMessage = new State(""); + // methods + this.sendMessage = () => { + if (this.cannotSendMessage.value == true) return; + this.sendMessageFromBody(this.composingMessage.value); + this.composingMessage.value = ""; + }; + this.sendMessageFromBody = (body) => { + this.chatViewModel.chatModel.sendMessage(body); + }; + this.decryptMessage = async (messageViewModel) => { + const chatMessage = messageViewModel.chatMessage; + await this.chatViewModel.chatModel.decryptMessage(chatMessage); + this.chatViewModel.chatModel.addMessage(chatMessage); + messageViewModel.loadData(); + }; + // 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, + ); + } + }; + // load + this.loadData = () => { + this.chatMessageViewModels.clear(); + for (const chatMessage of this.chatViewModel.chatModel + .messages) { + this.showChatMessage(chatMessage); + } + }; + this.chatViewModel = chatViewModel; + this.cannotSendMessage = createProxyState( + [ + this.chatViewModel.settingsViewModel.username, + this.composingMessage, + ], + () => + this.chatViewModel.settingsViewModel.username.value == "" || + this.composingMessage.value == "", + ); + } + }; + + // src/ViewModel/Pages/settingsPageViewModel.ts + var SettingsPageViewModel = class { + // init + constructor(coreViewModel, chatViewModel) { + this.coreViewModel = coreViewModel; + // 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.loadListRelevantData = () => { + 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.chatViewModel = chatViewModel; + this.loadListRelevantData(); + this.cannotSetEncryptionKey = createProxyState( + [this.encryptionKeyInput], + () => + this.encryptionKeyInput.value == + this.chatViewModel.chatModel.info.encryptionKey, + ); + this.color.subscribe((newColor) => { + this.applyColor(newColor); + }); + } + }; + + // src/ViewModel/Utility/searchViewModel.ts + var SearchViewModel = class { + // init + constructor( + allObjects, + matchingObjects, + getStringsOfObject, + suggestions, + ) { + // state + this.appliedQuery = new State(""); + this.searchInput = new State(""); + // guards + this.cannotApplySearch = createProxyState( + [this.searchInput, this.appliedQuery], + () => this.searchInput.value == this.appliedQuery.value, + ); + // methods + this.search = (searchTerm) => { + this.searchInput.value = searchTerm; + this.applySearch(); + }; + this.applySearch = () => { + this.appliedQuery.value = this.searchInput.value; + this.matchingObjects.clear(); + for (const object of this.allObjects.value.values()) { + const doesMatch = this.checkDoesMatchSearch(object); + if (doesMatch == false) continue; + this.matchingObjects.add(object); + } + }; + // utility + this.checkDoesMatchSearch = (object) => { + return checkDoesObjectMatchSearch( + this.appliedQuery.value, + this.getStringsOfObject, + object, + ); + }; + this.allObjects = allObjects; + this.matchingObjects = matchingObjects; + this.getStringsOfObject = getStringsOfObject; + this.suggestions = suggestions; + this.allObjects.handleAddition((newObject) => { + const doesMatch = this.checkDoesMatchSearch(newObject); + if (doesMatch == false) { + this.matchingObjects.remove(newObject); + } else { + if (this.matchingObjects.value.has(newObject)) return; + this.matchingObjects.add(newObject); + this.allObjects.handleRemoval(newObject, () => { + this.matchingObjects.remove(newObject); + }); + } + }); + } + }; + + // src/ViewModel/Pages/boardViewModel.ts + var BoardViewModel = class extends TaskContainingPageViewModel { + // init + constructor( + coreViewModel, + chatViewModel, + storageModel2, + boardsAndTasksModel, + taskPageViewModel, + boardInfo, + ) { + super(coreViewModel, chatViewModel, boardsAndTasksModel); + this.coreViewModel = coreViewModel; + this.chatViewModel = chatViewModel; + // state + this.name = new State(""); + this.color = new State("standard" /* Standard */); + this.index = new State(0); + this.selectedPage = new State("list" /* List */); + this.isPresentingSettingsModal = new State(false); + this.isPresentingFilterModal = new State(false); + this.filteredTaskViewModels = new ListState(); + // paths + this.getBasePath = () => { + return [ + ...this.taskPageViewModel.getBoardViewPath( + this.boardInfo.fileId, + ), + ]; + }; + this.getLastUsedBoardPath = () => { + return [ + ...this.getBasePath(), + "last-used-view" /* LastUsedView */, + ]; + }; + this.getPreviousSearchesPath = () => { + return [ + ...this.getBasePath(), + "previous-searches" /* PreviousSearches */, + ]; + }; + this.getLastSearchPath = () => { + return [...this.getBasePath(), "last-search" /* LastSearch */]; + }; + // settings + this.saveSettings = () => { + const newBoardInfoFileContent = + BoardsAndTasksModel.createBoardInfoFileContent( + this.boardInfo.fileId, + this.name.value, + this.color.value, + ); + this.taskPageViewModel.updateBoard(newBoardInfoFileContent); + }; + this.applyColor = () => { + this.taskPageViewModel.chatViewModel.setDisplayedColor( + this.color.value, + ); + }; + this.deleteBoard = () => { + this.taskPageViewModel.deleteBoard(this.boardInfo); + this.chatViewModel.taskBoardSuggestions.remove( + this.boardInfo.fileId, + ); + this.close(); + }; + // methods + this.createTask = () => { + this.createTaskFromBoardId(this.boardInfo.fileId); + }; + this.handleDropWithinBoard = (category, status) => { + const draggedObject = this.coreViewModel.draggedObject.value; + if (draggedObject instanceof TaskViewModel == false) return; + draggedObject.setCategoryAndStatus(category, status); + }; + this.handleDropBetweenBoards = () => { + const draggedObject = this.coreViewModel.draggedObject.value; + if (draggedObject instanceof TaskViewModel == false) return; + draggedObject.setBoardId(this.boardInfo.fileId); + }; + // storage + this.storeLastUsedView = () => { + const path = this.getLastUsedBoardPath(); + const lastUsedView = this.selectedPage.value; + this.storageModel.write(path, lastUsedView); + }; + this.restoreLastUsedView = () => { + const path = this.getLastUsedBoardPath(); + const lastUsedView = this.storageModel.read(path); + if (lastUsedView == null) return; + this.selectedPage.value = lastUsedView; + }; + this.handleNewSearch = (searchTerm) => { + const suggestionPath = [ + ...this.getPreviousSearchesPath(), + searchTerm, + ]; + this.storageModel.write(suggestionPath, ""); + if ( + !this.coreViewModel.boardSearchSuggestions.value.has( + searchTerm, + ) + ) { + this.coreViewModel.boardSearchSuggestions.add(searchTerm); + } + const lastSearchPath = this.getLastSearchPath(); + this.storageModel.write(lastSearchPath, searchTerm); + }; + // view + this.showTask = (taskFileContent) => { + if (taskFileContent.boardId != this.boardInfo.fileId) { + this.boardsAndTasksModel.deleteTaskReference( + this.boardInfo.fileId, + taskFileContent.fileId, + ); + this.removeTaskFromView(taskFileContent); + return; + } + const taskViewModel = new TaskViewModel( + this.coreViewModel, + this.chatViewModel, + this.boardsAndTasksModel, + this, + taskFileContent, + ); + this.taskViewModels.set(taskFileContent.fileId, taskViewModel); + }; + this.removeTaskFromView = (taskFileContent) => { + this.taskViewModels.remove(taskFileContent.fileId); + this.updateIndex(); + }; + this.select = () => { + this.taskPageViewModel.selectBoard(this); + }; + this.close = () => { + this.taskPageViewModel.closeBoard(); + this.taskViewModels.clear(); + }; + this.showSettings = () => { + this.isPresentingSettingsModal.value = true; + }; + this.hideSettings = () => { + this.saveSettings(); + this.isPresentingSettingsModal.value = false; + }; + this.showFilterModal = () => { + this.isPresentingFilterModal.value = true; + }; + this.hideFilterModal = () => { + this.isPresentingFilterModal.value = false; + }; + this.updateIndex = () => { + const index = + this.taskPageViewModel.boardIndexManager.getIndex(this); + this.index.value = index; + }; + // load + this.loadListRelevantData = () => { + this.name.value = this.boardInfo.name; + this.color.value = this.boardInfo.color; + }; + this.loadTasks = () => { + const taskIds = this.boardsAndTasksModel.listTaskIds( + this.boardInfo.fileId, + ); + for (const taskId of taskIds) { + if (this.taskViewModels.value.has(taskId)) return; + const taskFileContent = + this.boardsAndTasksModel.getLatestTaskFileContent( + taskId, + ); + if (taskFileContent == null) continue; + const taskViewModel = new TaskViewModel( + this.coreViewModel, + this.chatViewModel, + this.boardsAndTasksModel, + this, + taskFileContent, + ); + this.taskViewModels.set( + taskFileContent.fileId, + taskViewModel, + ); + } + this.updateTaskIndices(); + }; + this.loadSearchSuggestions = () => { + const dirPath = this.getPreviousSearchesPath(); + const searches = this.storageModel.list(dirPath); + this.coreViewModel.boardSearchSuggestions.add(...searches); + }; + this.restoreSearch = () => { + const lastSearchPath = this.getLastSearchPath(); + const lastSearch = this.storageModel.read(lastSearchPath); + if (lastSearch != null) { + this.searchViewModel.search(lastSearch); + } + }; + this.loadData = () => { + this.restoreLastUsedView(); + this.loadTasks(); + this.loadSearchSuggestions(); + }; + this.storageModel = storageModel2; + this.boardsAndTasksModel = boardsAndTasksModel; + this.taskPageViewModel = taskPageViewModel; + this.boardInfo = boardInfo; + this.loadListRelevantData(); + this.isSelected = createProxyState( + [this.taskPageViewModel.selectedBoardId], + () => + this.taskPageViewModel.selectedBoardId.value == + this.boardInfo.fileId, + ); + this.color.subscribe(() => { + if (this.isSelected.value == false) return; + this.applyColor(); + }); + this.selectedPage.subscribeSilent(() => { + this.storeLastUsedView(); + }); + boardsAndTasksModel.taskHandlerManager.addHandler( + (taskFileContent) => { + if (taskFileContent.boardId != this.boardInfo.fileId) + return; + this.showTask(taskFileContent); + this.updateTaskIndices(); + }, + ); + this.searchViewModel = new SearchViewModel( + this.taskViewModels, + this.filteredTaskViewModels, + TaskViewModel.getStringsForFilter, + this.coreViewModel.boardSearchSuggestions, + ); + this.searchViewModel.appliedQuery.subscribeSilent((newQuery) => { + this.handleNewSearch(newQuery); + }); + this.restoreSearch(); + } + }; + + // src/ViewModel/Pages/taskPageViewModel.ts + var TaskPageViewModel = class { + // init + constructor(coreViewModel, chatViewModel, storageModel2, boardModel) { + this.coreViewModel = coreViewModel; + this.chatViewModel = chatViewModel; + // data + this.boardIndexManager = new IndexManager( + (boardViewModel) => boardViewModel.name.value, + ); + // paths + this.getBasePath = () => { + return [...this.boardsAndTasksModel.getViewPath()]; + }; + this.getBoardViewPath = (boardId) => { + return [...this.getBasePath(), boardId]; + }; + this.getLastUsedBoardPath = () => { + return [ + ...this.getBasePath(), + "last-used-board" /* LastUsedBoard */, + ]; + }; + // state + this.newBoardNameInput = new State(""); + this.boardViewModels = new MapState(); + this.isShowingBoadList = new State(true); + this.selectedBoardId = new State(void 0); + // guards + this.cannotCreateBoard = createProxyState( + [this.newBoardNameInput], + () => this.newBoardNameInput.value == "", + ); + // methods + this.createBoard = () => { + if (this.cannotCreateBoard.value == true) return; + const boardInfoFileContent = + this.boardsAndTasksModel.createBoard( + this.newBoardNameInput.value, + ); + this.newBoardNameInput.value = ""; + this.showBoardInList(boardInfoFileContent); + this.boardsAndTasksModel.updateBoardAndSend( + boardInfoFileContent, + ); + this.updateBoardIndices(); + }; + this.updateBoard = (boardInfoFileContent) => { + this.boardsAndTasksModel.updateBoardAndSend( + boardInfoFileContent, + ); + this.updateBoardIndices(); + }; + this.deleteBoard = (boardInfoFileContent) => { + this.boardsAndTasksModel.deleteBoard( + boardInfoFileContent.fileId, + ); + this.boardViewModels.remove(boardInfoFileContent.fileId); + this.updateBoardIndices(); + }; + // view + this.toggleBoardList = () => { + this.isShowingBoadList.value = !this.isShowingBoadList.value; + }; + this.showBoardInList = (boardInfo) => { + const boardViewModel = new BoardViewModel( + this.coreViewModel, + this.chatViewModel, + this.storageModel, + this.boardsAndTasksModel, + this, + boardInfo, + ); + this.boardViewModels.set(boardInfo.fileId, boardViewModel); + this.chatViewModel.taskBoardSuggestions.set(boardInfo.fileId, [ + boardInfo.fileId, + this.boardsAndTasksModel.getBoardName(boardInfo.fileId), + ]); + }; + this.selectBoard = (boardViewModel) => { + this.selectedBoardId.value = boardViewModel.boardInfo.fileId; + this.chatViewModel.displayedColor.value = + boardViewModel.color.value; + this.storeLastUsedBoard(); + }; + this.closeBoard = () => { + this.selectedBoardId.value = void 0; + this.chatViewModel.resetColor(); + this.storeLastUsedBoard(); + }; + this.updateBoardIndices = () => { + this.boardIndexManager.update([ + ...this.boardViewModels.value.values(), + ]); + for (const boardViewModel of this.boardViewModels.value.values()) { + boardViewModel.updateIndex(); + } + }; + // storage + this.storeLastUsedBoard = () => { + const path = this.getLastUsedBoardPath(); + const lastUsedBoardId = this.selectedBoardId.value ?? ""; + this.storageModel.write(path, lastUsedBoardId); + }; + this.openLastUsedBoard = () => { + const path = this.getLastUsedBoardPath(); + const lastUsedBoardId = this.storageModel.read(path); + if (lastUsedBoardId == null) return; + const boardViewModel = + this.boardViewModels.value.get(lastUsedBoardId); + if (boardViewModel == void 0) return; + this.selectBoard(boardViewModel); + }; + // load + this.loadData = () => { + this.boardViewModels.clear(); + const boardIds = this.boardsAndTasksModel.listBoardIds(); + for (const boardId of boardIds) { + const boardInfo = + this.boardsAndTasksModel.getBoardInfo(boardId); + if (boardInfo == null) continue; + this.showBoardInList(boardInfo); + } + this.updateBoardIndices(); + this.openLastUsedBoard(); + }; + this.storageModel = storageModel2; + this.boardsAndTasksModel = boardModel; + this.chatViewModel = chatViewModel; + boardModel.boardHandlerManager.addHandler( + (boardInfoFileContent) => { + this.showBoardInList(boardInfoFileContent); + this.updateBoardIndices(); + }, + ); + } + }; + + // src/View/translations.ts + var englishTranslations = { + updater: { + migrated: "Migrated", + }, + general: { + deleteItemButtonAudioLabel: "delete item", + searchButtonAudioLabel: "search", + abortButton: "Abort", + cancelButton: "Cancel", + closeButton: "Close", + backButton: "Back", + continueButton: "Continue", + confirmButton: "Confirm", + saveButton: "Save", + setButton: "Set", + reloadAppButton: "Reload App", + fileVersionLabel: "Version", + searchLabel: "Search", + restoreConnection: "Restore connection", + }, + regional: { + weekdays: { + full: [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ], + abbreviated: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + }, + }, + homePage: { + appName: "Comms", + /// + overviewHeadline: "Overview", + serverAddress: "Server address", + serverAddressPlaceholder: "wss://192.168.0.69:3000", + connectAudioLabel: "connect to server", + disconnectAudioLabel: "disconnect from server", + manageConnectionsAudioLabel: "manage connections", + yourNameLabel: "Your name", + yourNamePlaceholder: "Jane Doe", + setNameButtonAudioLabel: "set name", + firstDayOfWeekLabel: "First day of week", + manageStorageButton: "Manage storage", + transferDataButton: "Data Transfer", + scrollToChatButton: "Chats", + /// + backToOverviewAudioLabel: "go back to overview", + chatsHeadline: "Chats", + addChatAudioLabel: "name of new chat", + addChatPlaceholder: "Add chat", + addChatButton: "Add chat", + }, + connectionModal: { + connectionModalHeadline: "Manage Connections", + /// + connectButtonAudioLabel: "connect", + }, + dataTransferModal: { + transferDataHeadline: "Data Transfer", + selectionDescription: "Select the data that you want to transfer.", + dataEntryDescription: "Enter this data on the other device.", + dataEntryInputDescription: + "Enter the data displayed on the other device.", + readyToReceiveDescription: "Click 'send' on the other device.", + notConnectedError: "You are not connected to any server.", + /// + fromThisDeviceButton: "From this device", + toThisDeviceButton: "To this device", + /// + generalHeadline: "General", + connectionData: "Connection Data", + settingsData: "Settings Data", + chatsHeadline: "Chats", + /// + transferChannelHeadline: "Transfer Chanel", + transferKeyHeadline: "Transfer Encryption Key", + sendButton: "Send", + sendAgainButton: "Send again", + /// + filesSentCount: (count) => `Files sent: ${count}.`, + allFilesSent: "Done.", + filesReceivedCount: (count) => `Files received: ${count}.`, + }, + storage: { + noItemSelected: "No item selected", + notAFile: "(not a file)", + contentEmpty: "(empty)", + path: "Path", + content: "Content", + deleteItem: "Delete item", + removeJunkButton: "Delete junk files", + }, + chatPage: { + closeChatAudioLabe: "close chat", + chatSettingsAudioLabel: "chat settings", + pages: { + settings: "Settings", + messages: "Messages", + tasks: "Tasks", + calendar: "Calendar", + }, + settings: { + settingsHeadline: "Settings", + primaryChannelLabel: "Primary channel", + setPrimaryChannelButtonAudioLabel: "set primary channel", + newSecondaryChannelPlaceholder: "Add secondary channel", + newSecondaryChannelAudioLabel: "name of new secondary channel", + addSecondaryChannelButtonAudioLabel: "add secondary channel", + encryptionKeyLabel: "Encryption key", + setEncryptionKeyButtonAudioLabel: "set encryption key", + showEncryptionKey: "Show encryption key", + deleteChatButton: "Delete entire chat", + }, + message: { + messagesHeadline: "Messages", + /// + composerInputPlaceholder: "Type a message...", + sendMessageButtonAudioLabel: "send message", + /// + showMessageInfoButtonAudioLabel: "show message info", + messageInfoHeadline: "Message Info", + sentBy: "Sent by", + timeSent: "Time sent", + channel: "Channel", + messageContent: "Message content", + copyMessageButton: "Copy message", + resendMessageButton: "Resend message", + decryptMessageButton: "Decrypt message", + deleteMessageButton: "Delete message", + }, + task: { + newBoardNamePlaceholder: "Create a board", + createBoardButtonAudioLabel: "create board", + /// + noBoardSelected: "No board selected", + boardNotFound: "Board not found", + /// + closeBoardButtonAudioLabel: "close board", + toggleBoardButtonAudioLabel: "toggle board list", + showBoardSettingsButtonAudioLabel: "show board settigns", + listViewButtonAudioLabel: "list view", + kanbanViewButtonAudioLabel: "kanban view", + statusViewButtonAudioLabel: "status grid view", + filterTasksButtonAudioLabel: "filter tasks", + createTaskButtonAudioLabel: "create new task", + /// + boardSettingsHeadline: "Board Settings", + boardNameInputLabel: "Board name", + deleteBoardButton: "Delete board and all tasks", + /// + taskSettingsHeadline: "Edit Task", + taskNameLabel: "Title", + taskBoardLabel: "Board", + taskCategoryLabel: "Category", + taskStatusLabel: "Status", + taskPriorityLabel: "Priority", + taskDescriptionLabel: "Description", + taskDateLabel: "Date", + taskTimeLabel: "Time", + deleteTaskButton: "Delete task", + /// + filterTasksHeadline: "Filter Tasks", + /// + renameCategoryInputPlaceholder: "Rename category,", + }, + calendar: { + eventsBoard: "Events", + /// + todayButtonAudioLabel: "go to today", + previousMonthButtonAudioLabel: "previous month", + nextMonthButtonAudioLabel: "next month", + yearInputAudioLabel: "year", + monthInputAudioLabel: "month", + yearInputPlaceholder: "2000", + monthInputPlaceholder: "01", + /// + searchEventsHeadline: "Search Events", + /// + events: "Events", + noEvents: "No events", + }, + }, + }; + var allTranslations = { + en: englishTranslations, + de: { + updater: { + migrated: "Migriert", + }, + general: { + deleteItemButtonAudioLabel: "element l\xF6schen", + searchButtonAudioLabel: "suchen", + abortButton: "Abbrechen", + cancelButton: "Abbrechen", + closeButton: "Schlie\xDFen", + backButton: "Zur\xFCck", + continueButton: "Weiter", + confirmButton: "Best\xE4tigen", + saveButton: "Speichern", + setButton: "OK", + reloadAppButton: "Neu laden", + fileVersionLabel: "Version", + searchLabel: "Suche", + restoreConnection: "Verbindung wiederherstellen", + }, + regional: { + weekdays: { + full: [ + "Sonntag", + "Montag", + "Dienstag", + "Mittwoch", + "Donnerstag", + "Freitag", + "Samstag", + ], + abbreviated: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"], + }, + }, + homePage: { + appName: "Comms", + overviewHeadline: "\xDCbersicht", + serverAddress: "Serveradresse", + serverAddressPlaceholder: "wss://192.168.0.69:3000", + connectAudioLabel: "mit Server verbinden", + disconnectAudioLabel: "vom Server trennen", + manageConnectionsAudioLabel: "Verbindungen verwalten", + yourNameLabel: "Dein Name", + yourNamePlaceholder: "Max Mustermann", + setNameButtonAudioLabel: "Name speichern", + firstDayOfWeekLabel: "Erster Wochentag", + manageStorageButton: "Daten verwalten", + transferDataButton: "Daten\xFCbertragung", + scrollToChatButton: "Chats", + backToOverviewAudioLabel: "zur\xFCck zur \xFCbersicht", + chatsHeadline: "Chats", + addChatAudioLabel: "Name des neuen Chats", + addChatPlaceholder: "Chat hinzuf\xFCgen", + addChatButton: "Chat hinzuf\xFCgen", + }, + connectionModal: { + connectionModalHeadline: "Verbindungen verwalten", + connectButtonAudioLabel: "verbinden", + }, + dataTransferModal: { + transferDataHeadline: "Daten\xFCbertragung", + selectionDescription: + "W\xE4hle die Daten aus, die du \xFCbertragen m\xF6chtest.", + dataEntryDescription: + "Gib diese Informationen auf dem anderen Ger\xE4t ein.", + dataEntryInputDescription: + "Gib die auf dem anderen Ger\xE4t angezeigten Informationen ein.", + readyToReceiveDescription: + "Klicke auf dem anderen Ger\xE4t auf 'Senden'.", + notConnectedError: "Du bist mit keinem Server verbunden.", + fromThisDeviceButton: "Von diesem Ger\xE4t", + toThisDeviceButton: "An dieses Ger\xE4t", + generalHeadline: "Allgemein", + connectionData: "Verbindungsdaten", + settingsData: "Einstellungen", + chatsHeadline: "Chats", + transferChannelHeadline: "\xDCbertragungskanal", + transferKeyHeadline: "Schl\xFCssel", + sendButton: "Senden", + sendAgainButton: "Erneut senden", + filesSentCount: (count) => `Dateien gesendet: ${count}.`, + allFilesSent: "Fertig.", + filesReceivedCount: (count) => `Dateien empfangen: ${count}.`, + }, + storage: { + noItemSelected: "Kein Element ausgew\xE4hlt", + notAFile: "(keine Datei)", + contentEmpty: "(leer)", + path: "Pfad", + content: "Inhalt", + deleteItem: "Element l\xF6schen", + removeJunkButton: "Datenm\xFCll l\xF6schen", + }, + chatPage: { + closeChatAudioLabe: "Chat schlie\xDFen", + chatSettingsAudioLabel: "Chateinstellungen", + pages: { + settings: "Einstellungen", + messages: "Nachrichten", + tasks: "Aufgaben", + calendar: "Kalender", + }, + settings: { + settingsHeadline: "Einstellungen", + primaryChannelLabel: "Hauptkanal", + setPrimaryChannelButtonAudioLabel: "Hauptkanal festlegen", + newSecondaryChannelPlaceholder: + "Sekund\xE4ren Kanal hinzuf\xFCgen", + newSecondaryChannelAudioLabel: + "Name des neuen sekund\xE4ren Kanals", + addSecondaryChannelButtonAudioLabel: + "Sekund\xE4ren Kanal hinzuf\xFCgen", + encryptionKeyLabel: "Schl\xFCssel", + setEncryptionKeyButtonAudioLabel: "Schl\xFCssel festlegen", + showEncryptionKey: "Schl\xFCssel anzeigen", + deleteChatButton: "Gesamten Chat l\xF6schen", + }, + message: { + messagesHeadline: "Nachrichten", + composerInputPlaceholder: "Schreib eine Nachricht...", + sendMessageButtonAudioLabel: "nachricht senden", + showMessageInfoButtonAudioLabel: "nachrichteninfo anzeigen", + messageInfoHeadline: "Nachrichteninfo", + sentBy: "Gesendet von", + timeSent: "Sendezeit", + channel: "Kanal", + messageContent: "Nachrichteninhalt", + copyMessageButton: "Nachricht kopieren", + resendMessageButton: "Nachricht erneut senden", + decryptMessageButton: "Nachricht entschl\xFCsseln", + deleteMessageButton: "Nachricht l\xF6schen", + }, + task: { + newBoardNamePlaceholder: "Board erstellen", + createBoardButtonAudioLabel: "board erstellen", + noBoardSelected: "Kein Board ausgew\xE4hlt", + boardNotFound: "Board nicht gefunden", + closeBoardButtonAudioLabel: "board schlie\xDFen", + toggleBoardButtonAudioLabel: "board-liste ein/ausblenden", + showBoardSettingsButtonAudioLabel: + "Board-Einstellungen anzeigen", + listViewButtonAudioLabel: "Listenansicht", + kanbanViewButtonAudioLabel: "Kanban-Ansicht", + statusViewButtonAudioLabel: "Statusrasteransicht", + filterTasksButtonAudioLabel: "Aufgaben filtern", + createTaskButtonAudioLabel: "Neue Aufgabe erstellen", + boardSettingsHeadline: "Board-Einstellungen", + boardNameInputLabel: "Boardname", + deleteBoardButton: "Board und alle Aufgaben l\xF6schen", + taskSettingsHeadline: "Aufgabe bearbeiten", + taskNameLabel: "Titel", + taskBoardLabel: "Board", + taskCategoryLabel: "Kategorie", + taskStatusLabel: "Status", + taskPriorityLabel: "Priorit\xE4t", + taskDescriptionLabel: "Beschreibung", + taskDateLabel: "Datum", + taskTimeLabel: "Uhrzeit", + deleteTaskButton: "Aufgabe l\xF6schen", + filterTasksHeadline: "Aufgaben filtern", + renameCategoryInputPlaceholder: "Kategorie umbenennen", + }, + calendar: { + eventsBoard: "Ereignisse", + /// + todayButtonAudioLabel: "gehe zu heute", + previousMonthButtonAudioLabel: "vorheriger monat", + nextMonthButtonAudioLabel: "n\xE4chster monat", + yearInputAudioLabel: "Jahr", + monthInputAudioLabel: "Monat", + yearInputPlaceholder: "2000", + monthInputPlaceholder: "01", + searchEventsHeadline: "Ereignisse suchen", + events: "Ereignisse", + noEvents: "Keine Ereignisse", + }, + }, + }, + es: { + updater: { + migrated: "Migrado", + }, + general: { + deleteItemButtonAudioLabel: "eliminar elemento", + searchButtonAudioLabel: "buscar", + abortButton: "Abortar", + cancelButton: "Cancelar", + closeButton: "Cerrar", + backButton: "Atr\xE1s", + continueButton: "Continuar", + confirmButton: "Confirmar", + saveButton: "Guardar", + setButton: "OK", + reloadAppButton: "Recargar app", + fileVersionLabel: "Versi\xF3n", + searchLabel: "Buscar", + restoreConnection: "Conectar de nuevo", + }, + regional: { + weekdays: { + full: [ + "Domingo", + "Lunes", + "Martes", + "Mi\xE9rcoles", + "Jueves", + "Viernes", + "S\xE1bado", + ], + abbreviated: [ + "Dom", + "Lun", + "Mar", + "Mi\xE9", + "Jue", + "Vie", + "S\xE1b", + ], + }, + }, + homePage: { + appName: "Comms", + overviewHeadline: "Resumen", + serverAddress: "Direcci\xF3n del servidor", + serverAddressPlaceholder: "wss://192.168.0.69:3000", + connectAudioLabel: "conectar al servidor", + disconnectAudioLabel: "desconectar del servidor", + manageConnectionsAudioLabel: "gestionar conexiones", + yourNameLabel: "Tu nombre", + yourNamePlaceholder: "Juan P\xE9rez", + setNameButtonAudioLabel: "establecer nombre", + firstDayOfWeekLabel: "Primer d\xEDa de la semana", + manageStorageButton: "Gestionar almacenamiento", + transferDataButton: "Transferencia de datos", + scrollToChatButton: "Chats", + backToOverviewAudioLabel: "volver al resumen", + chatsHeadline: "Chats", + addChatAudioLabel: "nombre del nuevo chat", + addChatPlaceholder: "A\xF1adir chat", + addChatButton: "A\xF1adir chat", + }, + connectionModal: { + connectionModalHeadline: "Gestionar Conexiones", + connectButtonAudioLabel: "conectar", + }, + dataTransferModal: { + transferDataHeadline: "Transferencia de Datos", + selectionDescription: + "Selecciona los datos que quieres transferir.", + dataEntryDescription: + "Introduce estos datos en el otro dispositivo.", + dataEntryInputDescription: + "Introduce los datos mostrados en el otro dispositivo.", + readyToReceiveDescription: + "Haz clic en 'enviar' en el otro dispositivo.", + notConnectedError: + "No est\xE1s conectado a ning\xFAn servidor.", + fromThisDeviceButton: "Desde este dispositivo", + toThisDeviceButton: "A este dispositivo", + generalHeadline: "General", + connectionData: "Datos de Conexi\xF3n", + settingsData: "Datos de Configuraci\xF3n", + chatsHeadline: "Chats", + transferChannelHeadline: "Canal de Transferencia", + transferKeyHeadline: + "Clave de Encriptaci\xF3n de Transferencia", + sendButton: "Enviar", + sendAgainButton: "Enviar otra vez", + filesSentCount: (count) => `Archivos enviados: ${count}.`, + allFilesSent: "Hecho.", + filesReceivedCount: (count) => `Archivos recibidos: ${count}.`, + }, + storage: { + noItemSelected: "Ning\xFAn elemento seleccionado", + notAFile: "(no es un archivo)", + contentEmpty: "(vac\xEDo)", + path: "Ruta", + content: "Contenido", + deleteItem: "Eliminar elemento", + removeJunkButton: "Eliminar archivos basura", + }, + chatPage: { + closeChatAudioLabe: "cerrar chat", + chatSettingsAudioLabel: "configuraci\xF3n del chat", + pages: { + settings: "Configuraci\xF3n", + messages: "Mensajes", + tasks: "Tareas", + calendar: "Calendario", + }, + settings: { + settingsHeadline: "Configuraci\xF3n", + primaryChannelLabel: "Canal principal", + setPrimaryChannelButtonAudioLabel: + "establecer canal principal", + newSecondaryChannelPlaceholder: + "A\xF1adir canal secundario", + newSecondaryChannelAudioLabel: + "nombre del nuevo canal secundario", + addSecondaryChannelButtonAudioLabel: + "a\xF1adir canal secundario", + encryptionKeyLabel: "Clave de encriptaci\xF3n", + setEncryptionKeyButtonAudioLabel: + "establecer clave de encriptaci\xF3n", + showEncryptionKey: "Mostrar clave de encriptaci\xF3n", + deleteChatButton: "Eliminar todo el chat", + }, + message: { + messagesHeadline: "Mensajes", + composerInputPlaceholder: "Escribe un mensaje...", + sendMessageButtonAudioLabel: "enviar mensaje", + showMessageInfoButtonAudioLabel: + "mostrar informaci\xF3n del mensaje", + messageInfoHeadline: "Informaci\xF3n del Mensaje", + sentBy: "Enviado por", + timeSent: "Hora de env\xEDo", + channel: "Canal", + messageContent: "Contenido del mensaje", + copyMessageButton: "Copiar mensaje", + resendMessageButton: "Reenviar mensaje", + decryptMessageButton: "Desencriptar mensaje", + deleteMessageButton: "Eliminar mensaje", + }, + task: { + newBoardNamePlaceholder: "Crear un tablero", + createBoardButtonAudioLabel: "crear tablero", + noBoardSelected: "Ning\xFAn tablero seleccionado", + boardNotFound: "Tablero no encontrado", + closeBoardButtonAudioLabel: "cerrar tablero", + toggleBoardButtonAudioLabel: + "mostrar o ocultar lista de tableros", + showBoardSettingsButtonAudioLabel: + "mostrar configuraci\xF3n del tablero", + listViewButtonAudioLabel: "vista de lista", + kanbanViewButtonAudioLabel: "vista kanban", + statusViewButtonAudioLabel: + "vista de cuadr\xEDcula de estado", + filterTasksButtonAudioLabel: "filtrar tareas", + createTaskButtonAudioLabel: "crear nueva tarea", + boardSettingsHeadline: "Configuraci\xF3n del Tablero", + boardNameInputLabel: "Nombre del tablero", + deleteBoardButton: "Eliminar tablero y todas las tareas", + taskSettingsHeadline: "Editar Tarea", + taskNameLabel: "T\xEDtulo", + taskBoardLabel: "Tablero", + taskCategoryLabel: "Categor\xEDa", + taskStatusLabel: "Estado", + taskPriorityLabel: "Prioridad", + taskDescriptionLabel: "Descripci\xF3n", + taskDateLabel: "Fecha", + taskTimeLabel: "Hora", + deleteTaskButton: "Eliminar tarea", + filterTasksHeadline: "Filtrar Tareas", + renameCategoryInputPlaceholder: "Renombrar categor\xEDa", + }, + calendar: { + eventsBoard: "Eventos", + /// + todayButtonAudioLabel: "ir a hoy", + previousMonthButtonAudioLabel: "mes anterior", + nextMonthButtonAudioLabel: "mes siguiente", + yearInputAudioLabel: "a\xF1o", + monthInputAudioLabel: "mes", + yearInputPlaceholder: "2000", + monthInputPlaceholder: "01", + searchEventsHeadline: "Buscar Eventos", + events: "Eventos", + noEvents: "No hay eventos", + }, + }, + }, + }; + var language = navigator.language.substring(0, 2); + var translations = allTranslations[language] || allTranslations.en; + + // src/ViewModel/Chat/chatViewModel.ts + var ChatViewModel = class { + // init + constructor( + coreViewModel, + storageModel2, + chatModel, + settingsViewModel2, + connectionViewModel2, + chatListViewModel2, + ) { + this.coreViewModel = coreViewModel; + // state + this.displayedColor = new State("standard" /* Standard */); + this.selectedPage = new State("messages" /* Messages */); + this.index = new State(0); + this.hasUnreadMessages = new State(false); + this.taskBoardSuggestions = new MapState(); + // view + this.open = () => { + this.chatListViewModel.openChat(this); + this.markRead(); + }; + this.close = () => { + this.chatListViewModel.closeChat(); + }; + this.closeSubPages = () => {}; + this.setColor = (color) => { + this.setDisplayedColor(color); + this.chatModel.setColor(color); + }; + this.setDisplayedColor = (color) => { + this.displayedColor.value = color; + }; + this.resetColor = () => { + this.displayedColor.value = + this.settingsPageViewModel.color.value; + }; + this.updateIndex = () => { + const index = + this.chatListViewModel.chatIndexManager.getIndex(this); + this.index.value = index; + }; + this.markRead = () => { + this.hasUnreadMessages.value = false; + this.chatModel.markRead(); + }; + // load + this.loadPageSelection = () => { + const path = StorageModel.getPath( + "chat" /* Chat */, + filePaths.chat.lastUsedPage(this.chatModel.id), + ); + const lastUsedPage = this.storageModel.read(path); + if (lastUsedPage != null) { + this.selectedPage.value = lastUsedPage; + } + this.selectedPage.subscribeSilent((newPage) => { + this.storageModel.write(path, newPage); + this.resetColor(); + }); + }; + this.loadInfo = () => { + this.hasUnreadMessages.value = + this.chatModel.info.hasUnreadMessages; + this.taskBoardSuggestions.set(CALENDAR_EVENT_BOARD_ID, [ + CALENDAR_EVENT_BOARD_ID, + translations.chatPage.calendar.eventsBoard, + ]); + }; + this.storageModel = storageModel2; + this.chatModel = chatModel; + this.settingsViewModel = settingsViewModel2; + this.connectionViewModel = connectionViewModel2; + this.chatListViewModel = chatListViewModel2; + this.calendarViewModel = new CalendarPageViewModel( + coreViewModel, + this, + this.storageModel, + this.chatModel.fileModel.boardsAndTasksModel.calendarModel, + this.chatModel.fileModel.boardsAndTasksModel, + ); + this.taskPageViewModel = new TaskPageViewModel( + this.coreViewModel, + this, + this.storageModel, + this.chatModel.fileModel.boardsAndTasksModel, + ); + this.messagePageViewModel = new MessagePageViewModel( + this.coreViewModel, + this, + ); + this.settingsPageViewModel = new SettingsPageViewModel( + this.coreViewModel, + this, + ); + chatModel.chatMessageHandlerManager.addHandler((chatMessage) => { + this.messagePageViewModel.showChatMessage(chatMessage); + this.markRead(); + }); + this.loadPageSelection(); + this.resetColor(); + this.loadInfo(); + } + }; + + // src/ViewModel/Chat/chatListViewModel.ts + var ChatListViewModel = class { + // init + constructor( + coreViewModel, + storageModel2, + chatListModel2, + settingsViewModel2, + connectionViewModel2, + ) { + this.coreViewModel = coreViewModel; + // data + this.chatIndexManager = new IndexManager( + (chatViewModel) => + chatViewModel.settingsPageViewModel.primaryChannel.value, + ); + // state + this.newChatPrimaryChannel = new State(""); + this.chatViewModels = new ListState(); + this.selectedChat = new State(void 0); + // guards + this.cannotCreateChat = createProxyState( + [this.newChatPrimaryChannel], + () => this.newChatPrimaryChannel.value == "", + ); + // methods + this.createChat = () => { + const chatModel = this.chatListModel.createChat( + this.newChatPrimaryChannel.value, + ); + this.newChatPrimaryChannel.value = ""; + const chatViewModel = this.createChatViewModel(chatModel); + this.trackChat(chatViewModel); + this.updateIndices(); + }; + this.trackChat = (chatViewModel) => { + this.chatViewModels.add(chatViewModel); + }; + this.untrackChat = (chatViewModel) => { + this.chatListModel.untrackChat(chatViewModel.chatModel); + this.chatViewModels.remove(chatViewModel); + }; + this.createChatViewModel = (chatModel) => { + return new ChatViewModel( + this.coreViewModel, + this.storageModel, + chatModel, + this.settingsViewModel, + this.connectionViewModel, + this, + ); + }; + this.updateIndices = () => { + this.chatIndexManager.update([ + ...this.chatViewModels.value.values(), + ]); + for (const chatViewModel of this.chatViewModels.value) { + chatViewModel.updateIndex(); + } + }; + // view + this.openChat = (chatViewModel) => { + this.selectedChat.value = chatViewModel; + }; + this.closeChat = () => { + this.selectedChat.value = void 0; + }; + // load + this.loadChats = () => { + this.chatViewModels.clear(); + for (const chatModel of this.chatListModel.chatModels.values()) { + const chatViewModel = this.createChatViewModel(chatModel); + this.trackChat(chatViewModel); + } + this.updateIndices(); + }; + this.storageModel = storageModel2; + this.chatListModel = chatListModel2; + this.settingsViewModel = settingsViewModel2; + this.connectionViewModel = connectionViewModel2; + this.loadChats(); + } + }; + + // src/View/Components/monthGrid.tsx + function MonthGrid2(monthGrid, selectedDate, handleDrop) { + const dayLabels = []; + let currentWeekday = monthGrid.firstDayOfWeek; + while (dayLabels.length < 7) { + dayLabels.push( + /* @__PURE__ */ createElement( + "span", + { class: "secondary" }, + 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 = + monthGrid.isCurrentMonth == true && + parseInt(date) == + /* @__PURE__ */ new Date().getDate(); + 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": 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, + ); + } + var StringToOption = (string) => { + return Option(string, string, false); + }; + var VersionIdToOption = (versionId) => { + const [date, rest] = versionId.split("T"); + const [time] = rest.split("."); + const readableName = `${date} ${time}`; + return Option(readableName, versionId, false); + }; + + // src/View/Components/dangerousActionButton.tsx + function DangerousActionButton(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, + }, + 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, + }, + translations.general.confirmButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "warning", + ), + ), + ); + } + + // src/View/Modals/taskSettingsModal.tsx + function TaskSettingsModal(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, + 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, + 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, + translations.chatPage.task.taskNameLabel, + ), + /* @__PURE__ */ createElement("input", { + "bind:value": taskViewModel.name, + }), + ), + ), + /* @__PURE__ */ createElement( + "label", + { class: "tile flex-no" }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "category", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + null, + translations.chatPage.task.taskBoardLabel, + ), + /* @__PURE__ */ createElement("select", { + "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, + 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, + 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, + 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, + translations.chatPage.task.taskPriorityLabel, + ), + /* @__PURE__ */ createElement("input", { + type: "number", + "bind:value": taskViewModel.priority, + }), + ), + ), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement( + "label", + { class: "tile flex-no" }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "calendar_month", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + null, + translations.chatPage.task.taskDateLabel, + ), + /* @__PURE__ */ createElement("input", { + type: "date", + "bind:value": taskViewModel.date, + }), + ), + ), + /* @__PURE__ */ createElement( + "label", + { class: "tile flex-no" }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "schedule", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + null, + translations.chatPage.task.taskTimeLabel, + ), + /* @__PURE__ */ createElement("input", { + type: "time", + "bind:value": taskViewModel.time, + }), + ), + ), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement( + "div", + { class: "width-input" }, + DangerousActionButton( + translations.chatPage.task.deleteTaskButton, + "delete_forever", + taskViewModel.deleteTask, + ), + ), + ), + /* @__PURE__ */ createElement( + "div", + { class: "flex-row width-100" }, + /* @__PURE__ */ createElement( + "button", + { + class: "flex", + "on:click": taskViewModel.closeAndDiscard, + }, + translations.general.closeButton, + ), + /* @__PURE__ */ createElement( + "button", + { + class: "flex primary", + "on:click": taskViewModel.closeAndSave, + }, + translations.general.saveButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "save", + ), + ), + ), + ), + ); + } + + // src/View/Components/taskEntry.tsx + function TaskEntry(taskViewModel) { + const details = { + description: taskViewModel.description.value || "---", + priority_high: taskViewModel.priority.value || "---", + category: taskViewModel.category.value || "---", + clock_loader_40: taskViewModel.status.value || "---", + calendar_month: taskViewModel.date.value || "---", + schedule: taskViewModel.time.value || "---", + }; + const view = /* @__PURE__ */ createElement( + "button", + { + draggable: "true", + class: "tile flex-no", + style: "user-select: none; -webkit-user-select: none", + "on:click": taskViewModel.open, + "on:dragstart": taskViewModel.dragStart, + }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement("b", { + class: "ellipsis", + "subscribe:innerText": taskViewModel.name, + }), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement( + "div", + { + class: "grid secondary", + style: "grid-template-columns: repeat(2, 1fr); column-gap: 1rem; row-gap: .5rem", + }, + ...Object.entries(details).map((entry) => + /* @__PURE__ */ createElement( + "span", + { + class: "flex-row align-center width-100 flex-no clip", + style: "gap: 1rem", + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon", style: "font-size: 1.1rem" }, + entry[0], + ), + /* @__PURE__ */ createElement( + "span", + { class: "ellipsis" }, + entry[1], + ), + ), + ), + ), + ), + ); + taskViewModel.index.subscribe((newIndex) => { + view.style.order = newIndex; + }); + return view; + } + var TaskViewModelToEntry = (taskViewModel) => { + return TaskEntry(taskViewModel); + }; + + // src/View/ChatPages/calendarPage.tsx + function CalendarPage(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( + 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 /* @__PURE__ */ createElement( + "div", + { + class: "width-100 height-100 flex-column justify-center align-center", + }, + /* @__PURE__ */ createElement( + "span", + { class: "secondary slide-up" }, + translations.chatPage.calendar.noEvents, + ), + ); + } else { + return /* @__PURE__ */ createElement("div", { + class: "flex-column gap slide-up padding-bottom", + "children:append": [ + listState, + TaskViewModelToEntry, + ], + }); + } + }, + ); + return /* @__PURE__ */ createElement("div", { + 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 { + return TaskSettingsModal( + 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": + translations.chatPage.calendar + .todayButtonAudioLabel, + "on:click": calendarPageViewModel.showToday, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "today", + ), + ), + ), + /* @__PURE__ */ createElement( + "span", + null, + /* @__PURE__ */ createElement( + "button", + { + class: "ghost", + "aria-label": + translations.chatPage.calendar + .previousMonthButtonAudioLabel, + "on:click": + calendarPageViewModel.showPreviousMonth, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "arrow_back", + ), + ), + /* @__PURE__ */ createElement( + "span", + { class: "input-wrapper" }, + /* @__PURE__ */ createElement("input", { + class: "year-input", + type: "number", + "aria-label": + translations.chatPage.calendar + .yearInputAudioLabel, + placeholder: + translations.chatPage.calendar + .yearInputPlaceholder, + "bind:value": + calendarPageViewModel.selectedYear, + }), + /* @__PURE__ */ createElement("input", { + class: "month-input", + type: "number", + "aria-label": + translations.chatPage.calendar + .monthInputAudioLabel, + placeholder: + translations.chatPage.calendar + .monthInputPlaceholder, + "bind:value": + calendarPageViewModel.selectedMonth, + }), + ), + /* @__PURE__ */ createElement( + "button", + { + class: "ghost", + "aria-label": + translations.chatPage.calendar + .nextMonthButtonAudioLabel, + "on:click": + calendarPageViewModel.showNextMonth, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "arrow_forward", + ), + ), + ), + /* @__PURE__ */ createElement( + "span", + null, + /* @__PURE__ */ createElement( + "button", + { + class: "ghost", + "aria-label": + translations.chatPage.task + .createTaskButtonAudioLabel, + "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) { + return /* @__PURE__ */ createElement( + "button", + { + class: "ribbon-button", + "aria-label": label, + "toggle:selected": isSelected, + "on:click": select, + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, icon), + ); + } + + // src/View/Components/chatViewToggleButton.tsx + function ChatViewToggleButton(label, icon, page, chatViewModel) { + function select() { + chatViewModel.selectedPage.value = page; + } + const isSelected = createProxyState( + [chatViewModel.selectedPage], + () => chatViewModel.selectedPage.value == page, + ); + return RibbonButton(label, icon, isSelected, select); + } + + // src/View/Modals/chatMessageInfoModal.tsx + function ChatMessageInfoModal(chatMessageViewModel) { + return /* @__PURE__ */ createElement( "div", { - class: "width-100 height-100", - "children:set": sidePaneContent - } - ); - } - } - ); - const taskSettingsModal = createProxyState( - [calendarPageViewModel.selectedTaskViewModel], - () => { - if (calendarPageViewModel.selectedTaskViewModel.value == void 0) { - return /* @__PURE__ */ createElement("div", null); - } else { - return TaskSettingsModal( - 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": translations.chatPage.calendar.todayButtonAudioLabel, - "on:click": calendarPageViewModel.showToday - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "today") - )), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement( - "button", - { - class: "ghost", - "aria-label": translations.chatPage.calendar.previousMonthButtonAudioLabel, - "on:click": calendarPageViewModel.showPreviousMonth - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_back") - ), /* @__PURE__ */ createElement("span", { class: "input-wrapper" }, /* @__PURE__ */ createElement( - "input", - { - class: "year-input", - type: "number", - "aria-label": translations.chatPage.calendar.yearInputAudioLabel, - placeholder: translations.chatPage.calendar.yearInputPlaceholder, - "bind:value": calendarPageViewModel.selectedYear - } - ), /* @__PURE__ */ createElement( - "input", - { - class: "month-input", - type: "number", - "aria-label": translations.chatPage.calendar.monthInputAudioLabel, - placeholder: translations.chatPage.calendar.monthInputPlaceholder, - "bind:value": calendarPageViewModel.selectedMonth - } - )), /* @__PURE__ */ createElement( - "button", - { - class: "ghost", - "aria-label": translations.chatPage.calendar.nextMonthButtonAudioLabel, - "on:click": calendarPageViewModel.showNextMonth - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") - )), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement( - "button", - { - class: "ghost", - "aria-label": translations.chatPage.task.createTaskButtonAudioLabel, - "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) { - return /* @__PURE__ */ createElement( - "button", - { - class: "ribbon-button", - "aria-label": label, - "toggle:selected": isSelected, - "on:click": select - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, icon) - ); - } - - // src/View/Components/chatViewToggleButton.tsx - function ChatViewToggleButton(label, icon, page, chatViewModel) { - function select() { - chatViewModel.selectedPage.value = page; + class: "modal", + "toggle:open": chatMessageViewModel.isPresentingInfoModal, + }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "main", + null, + /* @__PURE__ */ createElement( + "h2", + null, + translations.chatPage.message.messageInfoHeadline, + ), + /* @__PURE__ */ createElement( + "div", + { class: "flex-column gap" }, + /* @__PURE__ */ createElement( + "div", + { class: "tile" }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "account_circle", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + null, + translations.chatPage.message.sentBy, + ), + /* @__PURE__ */ createElement( + "b", + { class: "break-word" }, + chatMessageViewModel.sender, + ), + ), + ), + /* @__PURE__ */ createElement( + "div", + { class: "tile" }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "schedule", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + null, + translations.chatPage.message.timeSent, + ), + /* @__PURE__ */ createElement( + "b", + { class: "break-word" }, + chatMessageViewModel.dateSent, + ), + ), + ), + /* @__PURE__ */ createElement( + "div", + { class: "tile" }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "forum", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + null, + translations.chatPage.message.channel, + ), + /* @__PURE__ */ createElement( + "b", + { class: "break-word" }, + chatMessageViewModel.channel, + ), + ), + ), + /* @__PURE__ */ createElement( + "div", + { class: "tile" }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "description", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + null, + translations.chatPage.message + .messageContent, + ), + /* @__PURE__ */ createElement("b", { + class: "break-word", + "subscribe:innerText": + chatMessageViewModel.body, + }), + ), + ), + ), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement( + "div", + { class: "flex-column gap" }, + /* @__PURE__ */ createElement( + "button", + { "on:click": chatMessageViewModel.copyMessage }, + translations.chatPage.message.copyMessageButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "content_copy", + ), + ), + /* @__PURE__ */ createElement( + "button", + { "on:click": chatMessageViewModel.resendMessage }, + translations.chatPage.message.resendMessageButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "redo", + ), + ), + /* @__PURE__ */ createElement( + "button", + { "on:click": chatMessageViewModel.decryptMessage }, + translations.chatPage.message.decryptMessageButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "key", + ), + ), + ), + ), + /* @__PURE__ */ createElement( + "button", + { "on:click": chatMessageViewModel.hideInfoModal }, + translations.general.closeButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "close", + ), + ), + ), + ); } - const isSelected = createProxyState( - [chatViewModel.selectedPage], - () => chatViewModel.selectedPage.value == page - ); - return RibbonButton(label, icon, isSelected, select); - } - // src/View/Modals/chatMessageInfoModal.tsx - function ChatMessageInfoModal(chatMessageViewModel) { - return /* @__PURE__ */ createElement( - "div", - { - class: "modal", - "toggle:open": chatMessageViewModel.isPresentingInfoModal - }, - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.chatPage.message.messageInfoHeadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "account_circle"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.sentBy), /* @__PURE__ */ createElement("b", { class: "break-word" }, chatMessageViewModel.sender))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "schedule"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.timeSent), /* @__PURE__ */ createElement("b", { class: "break-word" }, chatMessageViewModel.dateSent))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.channel), /* @__PURE__ */ createElement("b", { class: "break-word" }, chatMessageViewModel.channel))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "description"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.messageContent), /* @__PURE__ */ createElement( - "b", - { - class: "break-word", - "subscribe:innerText": chatMessageViewModel.body - } - )))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.copyMessage }, translations.chatPage.message.copyMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "content_copy")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.resendMessage }, translations.chatPage.message.resendMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "redo")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.decryptMessage }, translations.chatPage.message.decryptMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "key")))), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.hideInfoModal }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))) - ); - } - - // src/View/Components/chatMessage.tsx - function ChatMessage2(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", - "toggle:sentbyuser": chatMessageViewModel.sentByUser - }, - /* @__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": translations.chatPage.message.showMessageInfoButtonAudioLabel - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "info") - ))), - ChatMessageInfoModal(chatMessageViewModel) - ); - } - var ChatMessageViewModelToView = (chatMessageViewModel) => { - return ChatMessage2(chatMessageViewModel); - }; - - // src/View/ChatPages/messagePage.tsx - function MessagePage(messagePageViewModel) { - messagePageViewModel.loadData(); - const messageContainer = /* @__PURE__ */ createElement( - "div", - { - id: "message-container", - "children:append": [ - messagePageViewModel.chatMessageViewModels, - ChatMessageViewModelToView - ] - } - ); - function scrollDown() { - messageContainer.scrollTop = messageContainer.scrollHeight; + // src/View/Components/chatMessage.tsx + function ChatMessage2(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", + "toggle:sentbyuser": chatMessageViewModel.sentByUser, + }, + /* @__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": + translations.chatPage.message + .showMessageInfoButtonAudioLabel, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "info", + ), + ), + ), + ), + ChatMessageInfoModal(chatMessageViewModel), + ); } - function scrollDownIfApplicable() { - const scrollFromBottom = messageContainer.scrollHeight - (messageContainer.scrollTop + messageContainer.offsetHeight); - if (scrollFromBottom > 400) return; - scrollDown(); - } - messagePageViewModel.chatMessageViewModels.subscribeSilent( - scrollDownIfApplicable - ); - setTimeout(() => scrollDown(), 100); - 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" }, translations.chatPage.message.messagesHeadline)), /* @__PURE__ */ createElement("div", { class: "content" }, messageContainer, /* @__PURE__ */ createElement("div", { id: "composer" }, /* @__PURE__ */ createElement("div", { class: "content-width-constraint" }, /* @__PURE__ */ createElement("div", { class: "input-width-constraint" }, /* @__PURE__ */ createElement( - "input", - { - "bind:value": messagePageViewModel.composingMessage, - "on:enter": messagePageViewModel.sendMessage, - placeholder: translations.chatPage.message.composerInputPlaceholder - } - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary", - "aria-label": translations.chatPage.message.sendMessageButtonAudioLabel, - "on:click": messagePageViewModel.sendMessage, - "toggle:disabled": messagePageViewModel.cannotSendMessage - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "send") - )))))))); - } - - // src/View/Components/colorPicker.tsx - function ColorPicker(selectedColor) { - return /* @__PURE__ */ createElement("div", { class: "flex-row gap width-input" }, ...Object.values(Color).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(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": translations.general.deleteItemButtonAudioLabel, - "on:click": ondelete - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "delete") - ))); - } - - // src/View/ChatPages/settingsPage.tsx - function SettingsPage(settingsPageViewModel) { - settingsPageViewModel.loadData(); - const secondaryChannelConverter = (secondaryChannel) => { - return DeletableListItem(secondaryChannel, /* @__PURE__ */ createElement("span", null), () => { - settingsPageViewModel.removeSecondaryChannel(secondaryChannel); - }); + var ChatMessageViewModelToView = (chatMessageViewModel) => { + return ChatMessage2(chatMessageViewModel); }; - 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" }, 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, 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": translations.chatPage.settings.setPrimaryChannelButtonAudioLabel, - "on:click": settingsPageViewModel.setPrimaryChannel, - "toggle:disabled": settingsPageViewModel.cannotSetPrimaryChannel - }, - 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": translations.chatPage.settings.newSecondaryChannelAudioLabel, - placeholder: translations.chatPage.settings.newSecondaryChannelPlaceholder, - "bind:value": settingsPageViewModel.newSecondaryChannelInput, - "on:enter": settingsPageViewModel.addSecondaryChannel - } - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary", - "aria-label": 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, 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": translations.chatPage.settings.setEncryptionKeyButtonAudioLabel, - "on:click": settingsPageViewModel.setEncryptionKey, - "toggle:disabled": settingsPageViewModel.cannotSetEncryptionKey - }, - translations.general.setButton, - /* @__PURE__ */ createElement("span", { class: "icon" }, "check") - )), /* @__PURE__ */ createElement("label", { class: "inline" }, /* @__PURE__ */ createElement( - "input", - { - type: "checkbox", - "bind:checked": settingsPageViewModel.shouldShowEncryptionKey - } - ), translations.chatPage.settings.showEncryptionKey), /* @__PURE__ */ createElement("hr", null), ColorPicker(settingsPageViewModel.color), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton( - translations.chatPage.settings.deleteChatButton, - "chat_error", - settingsPageViewModel.remove - )))))); - } - // src/View/Components/propertyValueList.tsx - function PropertyValueList(propertyKey, stringEntryObjectConverter, objects, viewBuilder) { - const propertyValues = new ListState(); - const sortedPropertyValues = createSortedPropertyValueState(propertyValues); - objects.subscribe(() => { - collectPropertyValuesToState( + // src/View/ChatPages/messagePage.tsx + function MessagePage(messagePageViewModel) { + messagePageViewModel.loadData(); + const messageContainer = /* @__PURE__ */ createElement("div", { + id: "message-container", + "children:append": [ + messagePageViewModel.chatMessageViewModels, + ChatMessageViewModelToView, + ], + }); + function scrollDown() { + messageContainer.scrollTop = messageContainer.scrollHeight; + } + function scrollDownIfApplicable() { + const scrollFromBottom = + messageContainer.scrollHeight - + (messageContainer.scrollTop + messageContainer.offsetHeight); + if (scrollFromBottom > 400) return; + scrollDown(); + } + messagePageViewModel.chatMessageViewModels.subscribeSilent( + scrollDownIfApplicable, + ); + setTimeout(() => scrollDown(), 100); + 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" }, + translations.chatPage.message.messagesHeadline, + ), + ), + /* @__PURE__ */ createElement( + "div", + { class: "content" }, + messageContainer, + /* @__PURE__ */ createElement( + "div", + { id: "composer" }, + /* @__PURE__ */ createElement( + "div", + { class: "content-width-constraint" }, + /* @__PURE__ */ createElement( + "div", + { class: "input-width-constraint" }, + /* @__PURE__ */ createElement("input", { + "bind:value": + messagePageViewModel.composingMessage, + "on:enter": + messagePageViewModel.sendMessage, + placeholder: + translations.chatPage.message + .composerInputPlaceholder, + }), + /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "aria-label": + translations.chatPage.message + .sendMessageButtonAudioLabel, + "on:click": + messagePageViewModel.sendMessage, + "toggle:disabled": + messagePageViewModel.cannotSendMessage, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "send", + ), + ), + ), + ), + ), + ), + ), + ), + ); + } + + // src/View/Components/colorPicker.tsx + function ColorPicker(selectedColor) { + return /* @__PURE__ */ createElement( + "div", + { class: "flex-row gap width-input" }, + ...Object.values(Color).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(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": + translations.general.deleteItemButtonAudioLabel, + "on:click": ondelete, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "delete", + ), + ), + ), + ); + } + + // src/View/ChatPages/settingsPage.tsx + function SettingsPage(settingsPageViewModel) { + settingsPageViewModel.loadData(); + const secondaryChannelConverter = (secondaryChannel) => { + return DeletableListItem( + 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" }, + 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, + 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": + translations.chatPage.settings + .setPrimaryChannelButtonAudioLabel, + "on:click": + settingsPageViewModel.setPrimaryChannel, + "toggle:disabled": + settingsPageViewModel.cannotSetPrimaryChannel, + }, + 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": + translations.chatPage.settings + .newSecondaryChannelAudioLabel, + placeholder: + translations.chatPage.settings + .newSecondaryChannelPlaceholder, + "bind:value": + settingsPageViewModel.newSecondaryChannelInput, + "on:enter": + settingsPageViewModel.addSecondaryChannel, + }), + /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "aria-label": + 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, + 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": + translations.chatPage.settings + .setEncryptionKeyButtonAudioLabel, + "on:click": + settingsPageViewModel.setEncryptionKey, + "toggle:disabled": + settingsPageViewModel.cannotSetEncryptionKey, + }, + translations.general.setButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "check", + ), + ), + ), + /* @__PURE__ */ createElement( + "label", + { class: "inline" }, + /* @__PURE__ */ createElement("input", { + type: "checkbox", + "bind:checked": + settingsPageViewModel.shouldShowEncryptionKey, + }), + translations.chatPage.settings.showEncryptionKey, + ), + /* @__PURE__ */ createElement("hr", null), + ColorPicker(settingsPageViewModel.color), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement( + "div", + { class: "width-input" }, + DangerousActionButton( + translations.chatPage.settings.deleteChatButton, + "chat_error", + settingsPageViewModel.remove, + ), + ), + ), + ), + ), + ); + } + + // src/View/Components/propertyValueList.tsx + function PropertyValueList( propertyKey, stringEntryObjectConverter, objects, - propertyValues - ); - }); - return viewBuilder(propertyValues, sortedPropertyValues); - } - function collectPropertyValuesToState(propertyKey, stringEntryObjectConverter, objects, propertyValues) { - const values = collectObjectValuesForKey( - propertyKey, - stringEntryObjectConverter, - [...objects.value.values()] - ); - for (const existingValue of values) { - if (propertyValues.value.has(existingValue)) continue; - propertyValues.add(existingValue); - } - for (const displayedValue of propertyValues.value.values()) { - if (values.includes(displayedValue) == false) { - propertyValues.remove(displayedValue); - } - } - } - function createSortedPropertyValueState(propertyValues) { - return createProxyState( - [propertyValues], - () => [...propertyValues.value.values()].sort(localeCompare) - ); - } - function createPropertyValueIndexState(sortedKeys, key) { - return createProxyState( - [sortedKeys], - () => sortedKeys.value.indexOf(key) - ); - } - - // src/View/Components/filteredList.tsx - function FilteredList(reference, stringEntryObjectConverter, objects, viewBuilder) { - const matchingObjects = new ListState(); - objects.handleAddition((newObject) => { - const doesMatch = checkDoesObjectMatchReference( - reference, - stringEntryObjectConverter(newObject) - ); - if (doesMatch == false) return; - matchingObjects.add(newObject); - objects.handleRemoval(newObject, () => { - matchingObjects.remove(newObject); - }); - }); - return viewBuilder(matchingObjects); - } - - // src/ViewModel/Utility/taskPropertyBulkChangeViewModel.ts - var TaskPropertyBulkChangeViewModel = class { - // init - constructor(taskViewModels, valueSetter, initialValue) { - // state - this.inputValue = new State(""); - // methods - this.set = () => { - if (this.cannotSet.value == true) return; - this.taskViewModels.value.forEach((taskViewModel) => { - this.setValue(this.inputValue.value, taskViewModel); + viewBuilder, + ) { + const propertyValues = new ListState(); + const sortedPropertyValues = + createSortedPropertyValueState(propertyValues); + objects.subscribe(() => { + collectPropertyValuesToState( + propertyKey, + stringEntryObjectConverter, + objects, + propertyValues, + ); }); - }; - this.taskViewModels = taskViewModels; - this.inputValue.value = initialValue; - this.setValue = valueSetter; - this.cannotSet = createProxyState( - [this.inputValue], - () => this.inputValue.value == "" || this.inputValue.value == initialValue - ); + return viewBuilder(propertyValues, sortedPropertyValues); } - }; - var TaskCategoryBulkChangeViewModel = class extends TaskPropertyBulkChangeViewModel { - constructor(taskViewModels, initialValue) { - super( - taskViewModels, - (newCategory, taskViewModel) => { - taskViewModel.category.value = newCategory; - taskViewModel.save(); - }, - initialValue - ); - } - }; - var TaskStatusBulkChangeViewModel = class extends TaskPropertyBulkChangeViewModel { - constructor(taskViewModels, initialValue) { - super( - taskViewModels, - (newStatus, taskViewModel) => { - taskViewModel.status.value = newStatus; - taskViewModel.save(); - }, - initialValue - ); - } - }; - - // src/View/ChatPages/boardKanbanPage.tsx - function BoardKanbanPage(boardViewModel) { - return PropertyValueList( - "category", - (taskViewModel) => taskViewModel.task, - boardViewModel.taskViewModels, - (categories, sortedCategories) => { - const categoryNameConverter = (categoryName) => { - const index = createPropertyValueIndexState( - sortedCategories, - categoryName - ); - return KanbanBoard(categoryName, index, boardViewModel); - }; - return /* @__PURE__ */ createElement( - "div", - { - class: "kanban-board-wrapper", - "children:append": [categories, categoryNameConverter] - } + function collectPropertyValuesToState( + propertyKey, + stringEntryObjectConverter, + objects, + propertyValues, + ) { + const values = collectObjectValuesForKey( + propertyKey, + stringEntryObjectConverter, + [...objects.value.values()], ); - } - ); - } - function KanbanBoard(categoryName, index, boardViewModel) { - return FilteredList( - { category: categoryName }, - (taskViewModel) => taskViewModel.task, - boardViewModel.filteredTaskViewModels, - (taskViewModels) => { - const viewModel = new TaskCategoryBulkChangeViewModel( - taskViewModels, - categoryName - ); - function drop() { - boardViewModel.handleDropWithinBoard(categoryName); + for (const existingValue of values) { + if (propertyValues.value.has(existingValue)) continue; + propertyValues.add(existingValue); } - const view = /* @__PURE__ */ createElement( - "div", - { - class: "flex-column flex-no", - "on:dragover": allowDrop, - "on:drop": drop - }, - /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( - "input", - { - placeholder: translations.chatPage.task.renameCategoryInputPlaceholder, - "bind:value": viewModel.inputValue, - "on:enter": viewModel.set + for (const displayedValue of propertyValues.value.values()) { + if (values.includes(displayedValue) == false) { + propertyValues.remove(displayedValue); } - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary", - "on:click": viewModel.set, - "toggle:disabled": viewModel.cannotSet + } + } + function createSortedPropertyValueState(propertyValues) { + return createProxyState([propertyValues], () => + [...propertyValues.value.values()].sort(localeCompare), + ); + } + function createPropertyValueIndexState(sortedKeys, key) { + return createProxyState([sortedKeys], () => + sortedKeys.value.indexOf(key), + ); + } + + // src/View/Components/filteredList.tsx + function FilteredList( + reference, + stringEntryObjectConverter, + objects, + viewBuilder, + ) { + const matchingObjects = new ListState(); + objects.handleAddition((newObject) => { + const doesMatch = checkDoesObjectMatchReference( + reference, + stringEntryObjectConverter(newObject), + ); + if (doesMatch == false) return; + matchingObjects.add(newObject); + objects.handleRemoval(newObject, () => { + matchingObjects.remove(newObject); + }); + }); + return viewBuilder(matchingObjects); + } + + // src/ViewModel/Utility/taskPropertyBulkChangeViewModel.ts + var TaskPropertyBulkChangeViewModel = class { + // init + constructor(taskViewModels, valueSetter, initialValue) { + // state + this.inputValue = new State(""); + // methods + this.set = () => { + if (this.cannotSet.value == true) return; + this.taskViewModels.value.forEach((taskViewModel) => { + this.setValue(this.inputValue.value, taskViewModel); + }); + }; + this.taskViewModels = taskViewModels; + this.inputValue.value = initialValue; + this.setValue = valueSetter; + this.cannotSet = createProxyState( + [this.inputValue], + () => + this.inputValue.value == "" || + this.inputValue.value == initialValue, + ); + } + }; + var TaskCategoryBulkChangeViewModel = class extends TaskPropertyBulkChangeViewModel { + constructor(taskViewModels, initialValue) { + super( + taskViewModels, + (newCategory, taskViewModel) => { + taskViewModel.category.value = newCategory; + taskViewModel.save(); + }, + initialValue, + ); + } + }; + var TaskStatusBulkChangeViewModel = class extends TaskPropertyBulkChangeViewModel { + constructor(taskViewModels, initialValue) { + super( + taskViewModels, + (newStatus, taskViewModel) => { + taskViewModel.status.value = newStatus; + taskViewModel.save(); + }, + initialValue, + ); + } + }; + + // src/View/ChatPages/boardKanbanPage.tsx + function BoardKanbanPage(boardViewModel) { + return PropertyValueList( + "category", + (taskViewModel) => taskViewModel.task, + boardViewModel.taskViewModels, + (categories, sortedCategories) => { + const categoryNameConverter = (categoryName) => { + const index = createPropertyValueIndexState( + sortedCategories, + categoryName, + ); + return KanbanBoard(categoryName, index, boardViewModel); + }; + return /* @__PURE__ */ createElement("div", { + class: "kanban-board-wrapper", + "children:append": [categories, categoryNameConverter], + }); }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "check") - )), - /* @__PURE__ */ createElement("hr", null), - /* @__PURE__ */ createElement( + ); + } + function KanbanBoard(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": allowDrop, + "on:drop": drop, + }, + /* @__PURE__ */ createElement( + "div", + { class: "flex-row width-input" }, + /* @__PURE__ */ createElement("input", { + placeholder: + translations.chatPage.task + .renameCategoryInputPlaceholder, + "bind:value": viewModel.inputValue, + "on:enter": viewModel.set, + }), + /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "on:click": viewModel.set, + "toggle:disabled": viewModel.cannotSet, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "check", + ), + ), + ), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement("div", { + class: "kanban-column", + "children:append": [ + taskViewModels, + TaskViewModelToEntry, + ], + }), + ); + index.subscribe((newIndex) => { + view.style.order = newIndex; + }); + return view; + }, + ); + } + + // src/View/Modals/boardSettingsModal.tsx + function BoardSettingsModal(boardViewModel) { + return /* @__PURE__ */ createElement( "div", { - class: "kanban-column", - "children:append": [taskViewModels, TaskViewModelToEntry] - } - ) + class: "modal", + "toggle:open": boardViewModel.isPresentingSettingsModal, + }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "main", + null, + /* @__PURE__ */ createElement( + "h2", + null, + translations.chatPage.task.boardSettingsHeadline, + ), + /* @__PURE__ */ createElement( + "label", + { class: "tile flex-no" }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "label", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + null, + translations.chatPage.task.boardNameInputLabel, + ), + /* @__PURE__ */ createElement("input", { + "on:enter": boardViewModel.saveSettings, + "bind:value": boardViewModel.name, + }), + ), + ), + /* @__PURE__ */ createElement("hr", null), + ColorPicker(boardViewModel.color), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement( + "div", + { class: "width-input" }, + DangerousActionButton( + translations.chatPage.task.deleteBoardButton, + "delete_forever", + boardViewModel.deleteBoard, + ), + ), + ), + /* @__PURE__ */ createElement( + "button", + { "on:click": boardViewModel.hideSettings }, + translations.general.closeButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "close", + ), + ), + ), ); - index.subscribe((newIndex) => { - view.style.order = newIndex; + } + + // src/View/ChatPages/boardStatusGridPage.tsx + function BoardStatusGridPage(boardViewModel) { + const statuses = new ListState(); + const sortedStatuses = createSortedPropertyValueState(statuses); + boardViewModel.taskViewModels.subscribe(() => { + collectPropertyValuesToState( + "status", + (taskViewModel) => taskViewModel.task, + boardViewModel.taskViewModels, + statuses, + ); }); - return view; - } - ); - } - - // src/View/Modals/boardSettingsModal.tsx - function BoardSettingsModal(boardViewModel) { - return /* @__PURE__ */ createElement( - "div", - { - class: "modal", - "toggle:open": boardViewModel.isPresentingSettingsModal - }, - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.chatPage.task.boardSettingsHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.boardNameInputLabel), /* @__PURE__ */ createElement( - "input", - { - "on:enter": boardViewModel.saveSettings, - "bind:value": boardViewModel.name - } - ))), /* @__PURE__ */ createElement("hr", null), ColorPicker(boardViewModel.color), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton( - translations.chatPage.task.deleteBoardButton, - "delete_forever", - boardViewModel.deleteBoard - ))), /* @__PURE__ */ createElement("button", { "on:click": boardViewModel.hideSettings }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))) - ); - } - - // src/View/ChatPages/boardStatusGridPage.tsx - function BoardStatusGridPage(boardViewModel) { - const statuses = new ListState(); - const sortedStatuses = createSortedPropertyValueState(statuses); - boardViewModel.taskViewModels.subscribe(() => { - collectPropertyValuesToState( - "status", - (taskViewModel) => taskViewModel.task, - boardViewModel.taskViewModels, - statuses - ); - }); - const statusNameCellConverter = (statusName) => { - const index = createPropertyValueIndexState(sortedStatuses, statusName); - return StatusNameCell(statusName, index, boardViewModel); - }; - return /* @__PURE__ */ createElement("div", { class: "status-page-content" }, /* @__PURE__ */ createElement( - "div", - { - class: "status-name-row", - "children:append": [statuses, statusNameCellConverter] - } - ), PropertyValueList( - "category", - (taskViewModel) => taskViewModel.task, - boardViewModel.filteredTaskViewModels, - (categories, sortedCategories) => { - const categoryRowConverter = (categoryName) => { - const index = createPropertyValueIndexState( - sortedCategories, - categoryName - ); - return CategoryRow( - categoryName, - index, - statuses, - sortedStatuses, - boardViewModel - ); + const statusNameCellConverter = (statusName) => { + const index = createPropertyValueIndexState( + sortedStatuses, + statusName, + ); + return StatusNameCell(statusName, index, boardViewModel); }; return /* @__PURE__ */ createElement( - "div", - { - class: "status-grid-wrapper", - "children:append": [categories, categoryRowConverter] - } + "div", + { class: "status-page-content" }, + /* @__PURE__ */ createElement("div", { + class: "status-name-row", + "children:append": [statuses, statusNameCellConverter], + }), + PropertyValueList( + "category", + (taskViewModel) => taskViewModel.task, + boardViewModel.filteredTaskViewModels, + (categories, sortedCategories) => { + const categoryRowConverter = (categoryName) => { + const index = createPropertyValueIndexState( + sortedCategories, + categoryName, + ); + return CategoryRow( + categoryName, + index, + statuses, + sortedStatuses, + boardViewModel, + ); + }; + return /* @__PURE__ */ createElement("div", { + class: "status-grid-wrapper", + "children:append": [categories, categoryRowConverter], + }); + }, + ), ); - } - )); - } - function StatusNameCell(statusName, index, boardViewModel) { - const taskViewModelsWithMatchingStatus = new ListState(); - boardViewModel.filteredTaskViewModels.handleAddition( - (taskViewModel) => { - const doesMatchStatus = taskViewModel.task.status == statusName; - if (doesMatchStatus == false) return; - taskViewModelsWithMatchingStatus.add(taskViewModel); - boardViewModel.filteredTaskViewModels.handleRemoval( - taskViewModel, - () => { - taskViewModelsWithMatchingStatus.remove(taskViewModel); - } + } + function StatusNameCell(statusName, index, boardViewModel) { + const taskViewModelsWithMatchingStatus = new ListState(); + boardViewModel.filteredTaskViewModels.handleAddition( + (taskViewModel) => { + const doesMatchStatus = taskViewModel.task.status == statusName; + if (doesMatchStatus == false) return; + taskViewModelsWithMatchingStatus.add(taskViewModel); + boardViewModel.filteredTaskViewModels.handleRemoval( + taskViewModel, + () => { + taskViewModelsWithMatchingStatus.remove(taskViewModel); + }, + ); + }, ); - } - ); - const viewModel = new TaskStatusBulkChangeViewModel( - taskViewModelsWithMatchingStatus, - statusName - ); - const view = /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement("div", { class: "property-input-wrapper" }, /* @__PURE__ */ createElement( - "input", - { - placeholder: translations.chatPage.task.renameCategoryInputPlaceholder, - "bind:value": viewModel.inputValue, - "on:enter": viewModel.set - } - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary", - "on:click": viewModel.set, - "toggle:disabled": viewModel.cannotSet - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "check") - ))); - index.subscribe((newIndex) => { - view.style.order = newIndex; - }); - return view; - } - function CategoryRow(categoryName, index, allStatuses, sortedStatuses, boardViewModel) { - return FilteredList( - { category: categoryName }, - (taskViewModel) => taskViewModel.task, - boardViewModel.filteredTaskViewModels, - (taskViewModels) => { - const statusNameConverter = (statusName) => { - const index2 = createPropertyValueIndexState( - sortedStatuses, - statusName - ); - return CategoryStatusColumn( - categoryName, + const viewModel = new TaskStatusBulkChangeViewModel( + taskViewModelsWithMatchingStatus, statusName, - index2, - boardViewModel, - taskViewModels - ); - }; - const viewModel = new TaskCategoryBulkChangeViewModel( - taskViewModels, - categoryName ); - const view = /* @__PURE__ */ createElement("div", { class: "flex-row flex-no large-gap" }, /* @__PURE__ */ createElement("div", { class: "property-input-wrapper" }, /* @__PURE__ */ createElement( - "input", - { - placeholder: translations.chatPage.task.renameCategoryInputPlaceholder, - "bind:value": viewModel.inputValue, - "on:enter": viewModel.set - } - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary", - "on:click": viewModel.set, - "toggle:disabled": viewModel.cannotSet - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "check") - )), /* @__PURE__ */ createElement( - "div", - { - class: "flex-row large-gap padding-right", - "children:append": [allStatuses, statusNameConverter] - } - )); + const view = /* @__PURE__ */ createElement( + "div", + { class: "flex-row" }, + /* @__PURE__ */ createElement( + "div", + { class: "property-input-wrapper" }, + /* @__PURE__ */ createElement("input", { + placeholder: + translations.chatPage.task + .renameCategoryInputPlaceholder, + "bind:value": viewModel.inputValue, + "on:enter": viewModel.set, + }), + /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "on:click": viewModel.set, + "toggle:disabled": viewModel.cannotSet, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "check", + ), + ), + ), + ); index.subscribe((newIndex) => { - view.style.order = newIndex; + view.style.order = newIndex; }); return view; - } - ); - } - function CategoryStatusColumn(categoryName, statusName, index, boardViewModel, taskViewModelsWithMatchingCategory) { - const taskViewModels = new ListState(); - taskViewModelsWithMatchingCategory.handleAddition((taskViewModel) => { - const doesMatchStatus = taskViewModel.status.value == statusName; - if (doesMatchStatus == false) return; - taskViewModels.add(taskViewModel); - taskViewModelsWithMatchingCategory.handleRemoval(taskViewModel, () => { - taskViewModels.remove(taskViewModel); - }); - }); - function drop() { - boardViewModel.handleDropWithinBoard(categoryName, statusName); } - const view = /* @__PURE__ */ createElement( - "div", - { - class: "status-column gap", - "on:dragover": allowDrop, - "on:drop": drop, - "children:append": [taskViewModels, TaskViewModelToEntry] - } - ); - index.subscribe((newIndex) => { - view.style.order = newIndex; - }); - return view; - } - - // src/View/Components/boardViewToggleButton.tsx - function BoardViewToggleButton(label, icon, page, boardViewModel) { - function select() { - boardViewModel.selectedPage.value = page; + function CategoryRow( + categoryName, + index, + allStatuses, + sortedStatuses, + boardViewModel, + ) { + return FilteredList( + { category: categoryName }, + (taskViewModel) => taskViewModel.task, + boardViewModel.filteredTaskViewModels, + (taskViewModels) => { + const statusNameConverter = (statusName) => { + const index2 = createPropertyValueIndexState( + sortedStatuses, + statusName, + ); + return CategoryStatusColumn( + categoryName, + statusName, + index2, + boardViewModel, + taskViewModels, + ); + }; + const viewModel = new TaskCategoryBulkChangeViewModel( + taskViewModels, + categoryName, + ); + const view = /* @__PURE__ */ createElement( + "div", + { class: "flex-row flex-no large-gap" }, + /* @__PURE__ */ createElement( + "div", + { class: "property-input-wrapper" }, + /* @__PURE__ */ createElement("input", { + placeholder: + translations.chatPage.task + .renameCategoryInputPlaceholder, + "bind:value": viewModel.inputValue, + "on:enter": viewModel.set, + }), + /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "on:click": viewModel.set, + "toggle:disabled": viewModel.cannotSet, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "check", + ), + ), + ), + /* @__PURE__ */ createElement("div", { + class: "flex-row large-gap padding-right", + "children:append": [allStatuses, statusNameConverter], + }), + ); + index.subscribe((newIndex) => { + view.style.order = newIndex; + }); + return view; + }, + ); } - const isSelected = createProxyState( - [boardViewModel.selectedPage], - () => boardViewModel.selectedPage.value == page - ); - return RibbonButton(label, icon, isSelected, select); - } - - // src/View/Modals/searchModal.tsx - function SearchModal(searchViewModel, headline, converter, isOpen) { - function close() { - isOpen.value = false; - } - const suggestionId = v4_default(); - return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isOpen }, /* @__PURE__ */ createElement("div", { style: "max-width: 64rem" }, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, headline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( - "input", - { - placeholder: translations.general.searchLabel, - "bind:value": searchViewModel.searchInput, - "on:enter": searchViewModel.applySearch, - list: suggestionId - } - ), /* @__PURE__ */ createElement( - "datalist", - { - hidden: true, - id: suggestionId, - "children:append": [ - searchViewModel.suggestions, - StringToOption - ] - } - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary", - "aria-label": translations.general.searchButtonAudioLabel, - "on:click": searchViewModel.applySearch, - "toggle:disabled": searchViewModel.cannotApplySearch - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "search") - )), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( - "div", - { - class: "grid gap", - style: "grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr))", - "children:append": [ - searchViewModel.matchingObjects, - converter - ] - } - )), /* @__PURE__ */ createElement("button", { "on:click": close }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); - } - - // src/View/ChatPages/boardPage.tsx - function BoardPage(boardViewModel) { - boardViewModel.loadData(); - const mainContent = createProxyState( - [boardViewModel.selectedPage], - () => { - switch (boardViewModel.selectedPage.value) { - case "kanban" /* Kanban */: { - return BoardKanbanPage(boardViewModel); - } - case "status-grid" /* StatusGrid */: { - return BoardStatusGridPage(boardViewModel); - } - default: { - return /* @__PURE__ */ createElement( - "div", - { - class: "task-grid", - "children:append": [ - boardViewModel.filteredTaskViewModels, - TaskViewModelToEntry - ] - } + function CategoryStatusColumn( + categoryName, + statusName, + index, + boardViewModel, + taskViewModelsWithMatchingCategory, + ) { + const taskViewModels = new ListState(); + taskViewModelsWithMatchingCategory.handleAddition((taskViewModel) => { + const doesMatchStatus = taskViewModel.status.value == statusName; + if (doesMatchStatus == false) return; + taskViewModels.add(taskViewModel); + taskViewModelsWithMatchingCategory.handleRemoval( + taskViewModel, + () => { + taskViewModels.remove(taskViewModel); + }, ); - } - } - } - ); - const taskSettingsModal = createProxyState( - [boardViewModel.selectedTaskViewModel], - () => { - if (boardViewModel.selectedTaskViewModel.value == void 0) { - return /* @__PURE__ */ createElement("div", null); - } else { - return TaskSettingsModal( - boardViewModel.selectedTaskViewModel.value - ); - } - } - ); - return /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement( - "button", - { - class: "ghost board-close-button", - "aria-label": translations.chatPage.task.closeBoardButtonAudioLabel, - "on:click": boardViewModel.close - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_back") - ), /* @__PURE__ */ createElement( - "button", - { - class: "ghost board-toggle-button inset-outline", - "aria-label": translations.chatPage.task.toggleBoardButtonAudioLabel, - "on:click": boardViewModel.taskPageViewModel.toggleBoardList, - "toggle:selected": boardViewModel.taskPageViewModel.isShowingBoadList - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "dock_to_right") - ), /* @__PURE__ */ createElement( - "button", - { - class: "ghost", - "aria-label": translations.chatPage.task.boardSettingsHeadline, - "on:click": boardViewModel.showSettings - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "settings") - )), /* @__PURE__ */ createElement("span", { class: "scroll-h ribbon" }, BoardViewToggleButton( - translations.chatPage.task.listViewButtonAudioLabel, - "view_list", - "list" /* List */, - boardViewModel - ), BoardViewToggleButton( - translations.chatPage.task.kanbanViewButtonAudioLabel, - "view_kanban", - "kanban" /* Kanban */, - boardViewModel - ), BoardViewToggleButton( - translations.chatPage.task.statusViewButtonAudioLabel, - "grid_view", - "status-grid" /* StatusGrid */, - boardViewModel - )), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement( - "button", - { - class: "ghost", - "aria-label": translations.chatPage.task.filterTasksButtonAudioLabel, - "on:click": boardViewModel.showFilterModal - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "filter_alt") - ), /* @__PURE__ */ createElement( - "button", - { - class: "ghost", - "aria-label": translations.chatPage.task.createTaskButtonAudioLabel, - "on:click": boardViewModel.createTask - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "add") - ))), /* @__PURE__ */ createElement("div", { class: "content main-content", "children:set": mainContent }), BoardSettingsModal(boardViewModel), SearchModal( - boardViewModel.searchViewModel, - translations.chatPage.task.filterTasksHeadline, - TaskViewModelToEntry, - boardViewModel.isPresentingFilterModal - ), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal })); - } - - // src/View/Components/boardEntry.tsx - function BoardEntry(boardViewModel) { - const view = /* @__PURE__ */ createElement( - "button", - { - "set:color": boardViewModel.color, - class: "tile colored-tile", - "toggle:selected": boardViewModel.isSelected, - "on:click": boardViewModel.select, - "on:dragover": allowDrop, - "on:drop": boardViewModel.handleDropBetweenBoards - }, - /* @__PURE__ */ createElement( - "span", - { - class: "shadow", - "subscribe:innerText": boardViewModel.name - } - ), - /* @__PURE__ */ createElement("b", { "subscribe:innerText": boardViewModel.name }) - ); - boardViewModel.index.subscribe((newIndex) => { - view.style.order = newIndex; - }); - return view; - } - var BoardViewModelToEntry = (boardViewModel) => { - return BoardEntry(boardViewModel); - }; - - // src/View/ChatPages/taskPage.tsx - function TaskPage(taskPageViewModel) { - taskPageViewModel.loadData(); - const isShowingBoard = createProxyState( - [taskPageViewModel.selectedBoardId], - () => taskPageViewModel.selectedBoardId.value != void 0 - ); - const paneContent = createProxyState( - [taskPageViewModel.selectedBoardId], - () => { - const selectedBoardId = taskPageViewModel.selectedBoardId.value; - if (selectedBoardId == void 0) { - return /* @__PURE__ */ createElement("div", { class: "pane align-center justify-center" }, /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.chatPage.task.noBoardSelected)); - } - const selectedBoard = taskPageViewModel.boardViewModels.value.get(selectedBoardId); - if (selectedBoard == void 0) { - return /* @__PURE__ */ createElement("div", { class: "pane align-center justify-center" }, /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.chatPage.task.boardNotFound)); - } - return BoardPage(selectedBoard); - } - ); - return /* @__PURE__ */ createElement( - "div", - { - id: "task-page", - "toggle:isshowingboard": isShowingBoard, - "set:showingboardlist": taskPageViewModel.isShowingBoadList - }, - /* @__PURE__ */ createElement( - "div", - { - id: "board-list", - class: "pane-wrapper side background", - "set:color": taskPageViewModel.chatViewModel.displayedColor - }, - /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( - "input", - { - class: "no-outline", - "bind:value": taskPageViewModel.newBoardNameInput, - "on:enter": taskPageViewModel.createBoard, - placeholder: translations.chatPage.task.newBoardNamePlaceholder - } - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary", - "aria-label": translations.chatPage.task.createBoardButtonAudioLabel, - "on:click": taskPageViewModel.createBoard, - "toggle:disabled": taskPageViewModel.cannotCreateBoard - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "add") - ))), /* @__PURE__ */ createElement("div", { class: "content" }, /* @__PURE__ */ createElement( - "div", - { - class: "grid gap", - style: "grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr))", - "children:append": [ - taskPageViewModel.boardViewModels, - BoardViewModelToEntry - ] - } - ))) - ), - /* @__PURE__ */ createElement( - "div", - { - id: "board-content", - class: "pane-wrapper", - "children:set": paneContent - } - ) - ); - } - - // src/View/chatPage.tsx - function ChatPage(chatViewModel) { - const mainContent = createProxyState( - [chatViewModel.selectedPage], - () => { - chatViewModel.closeSubPages(); - switch (chatViewModel.selectedPage.value) { - case "settings" /* Settings */: { - return SettingsPage(chatViewModel.settingsPageViewModel); - } - case "tasks" /* Tasks */: { - return TaskPage(chatViewModel.taskPageViewModel); - } - case "calendar" /* Calendar */: { - return CalendarPage(chatViewModel.calendarViewModel); - } - default: { - return MessagePage(chatViewModel.messagePageViewModel); - } - } - } - ); - return /* @__PURE__ */ createElement( - "article", - { - id: "chat-page", - "set:color": chatViewModel.displayedColor, - class: "subtle-background" - }, - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { id: "ribbon" }, /* @__PURE__ */ createElement( - "button", - { - class: "ghost", - "aria-label": translations.chatPage.closeChatAudioLabe, - "on:click": chatViewModel.close - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "close") - ), /* @__PURE__ */ createElement( - "button", - { - class: "danger", - "aria-label": translations.general.restoreConnection, - "on:click": chatViewModel.connectionViewModel.showConnectionModal, - "toggle:hidden": chatViewModel.connectionViewModel.isConnected - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "signal_disconnected") - ), /* @__PURE__ */ createElement("span", null, ChatViewToggleButton( - translations.chatPage.pages.calendar, - "calendar_month", - "calendar" /* Calendar */, - chatViewModel - ), ChatViewToggleButton( - translations.chatPage.pages.tasks, - "task_alt", - "tasks" /* Tasks */, - chatViewModel - ), ChatViewToggleButton( - translations.chatPage.pages.messages, - "forum", - "messages" /* Messages */, - chatViewModel - ), ChatViewToggleButton( - translations.chatPage.pages.settings, - "settings", - "settings" /* Settings */, - chatViewModel - ))), /* @__PURE__ */ createElement("div", { id: "main", "children:set": mainContent })) - ); - } - - // src/View/chatPageWrapper.tsx - function ChatPageWrapper(chatListViewModel2) { - const chatPageContent = createProxyState( - [chatListViewModel2.selectedChat], - () => { - if (chatListViewModel2.selectedChat.value == void 0) { - return /* @__PURE__ */ createElement("div", null); - } else { - return ChatPage(chatListViewModel2.selectedChat.value); - } - } - ); - return /* @__PURE__ */ createElement("div", { id: "chat-page-wrapper", "children:set": chatPageContent }); - } - - // src/View/Modals/connectionModal.tsx - function ConnectionModal(connectionViewModel2) { - const previousAddressConverter = (address) => { - function connnect() { - connectionViewModel2.connectToAddress(address); - } - const cannotConnect = createProxyState( - [connectionViewModel2.isConnected], - () => connectionViewModel2.isConnected.value == true && connectionViewModel2.connectionModel.address == address - ); - return DeletableListItem( - address, - /* @__PURE__ */ createElement( - "button", - { - class: "primary", - "on:click": connnect, - "toggle:disabled": cannotConnect, - "aria-label": translations.connectionModal.connectButtonAudioLabel - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "link") - ), - () => { - connectionViewModel2.removePreviousAddress(address); - } - ); - }; - return /* @__PURE__ */ createElement( - "div", - { - class: "modal", - "toggle:open": connectionViewModel2.isShowingConnectionModal - }, - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.connectionModal.connectionModalHeadline), /* @__PURE__ */ createElement( - "div", - { - class: "flex-column gap", - "children:append": [ - connectionViewModel2.previousAddresses, - previousAddressConverter - ] - } - )), /* @__PURE__ */ createElement("button", { "on:click": connectionViewModel2.hideConnectionModal }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))) - ); - } - - // ../ui-base/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); - } + function drop() { + boardViewModel.handleDropWithinBoard(categoryName, statusName); + } + const view = /* @__PURE__ */ createElement("div", { + class: "status-column gap", + "on:dragover": allowDrop, + "on:drop": drop, + "children:append": [taskViewModels, TaskViewModelToEntry], + }); + index.subscribe((newIndex) => { + view.style.order = newIndex; + }); + return view; } - 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.isConnected == false) { - if (this.shouldAttemptReconnect == false) return; - this.reconnectInterval = setInterval(() => { - if (this.isConnected == true) { - clearInterval(this.reconnectInterval); - return; - } - this.reconnect(); - }, 5e3); - return; + // src/View/Components/boardViewToggleButton.tsx + function BoardViewToggleButton(label, icon, page, boardViewModel) { + function select() { + boardViewModel.selectedPage.value = page; } - if (this.address == void 0) return; - this.storeAddress(this.address); - this.sendSubscriptionRequest(); - this.sendMessagesInOutbox(); - this.shouldAttemptReconnect = true; - }; - // connection - this.connect = (address) => { - this.udn.connect(address); - }; - this.disconnect = () => { - this.udn.disconnect(); - const reconnectAddressPath = StorageModel.getPath( - "connection" /* ConnectionModel */, - filePaths.connectionModel.reconnectAddress + const isSelected = createProxyState( + [boardViewModel.selectedPage], + () => boardViewModel.selectedPage.value == page, ); - this.storageModel.remove(reconnectAddressPath); - this.shouldAttemptReconnect = false; - }; - this.reconnect = () => { - const reconnectAddressPath = this.getReconnectAddressPath(); - const reconnectAddress = this.storageModel.read(reconnectAddressPath); - if (reconnectAddress == null) return; - console.log("reconnecting..."); - this.connect(reconnectAddress); - }; - // mailbox - this.getMailboxPath = (address) => { - 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}`); - }; - this.reconnect(); + return RibbonButton(label, icon, isSelected, select); } - // 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(coreViewModel, connectionModel2) { - this.coreViewModel = coreViewModel; - // 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.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.connectionModel.isConnected; - if (this.connectionModel.isConnected == false) return; - if (this.connectionModel.address == void 0) return; - this.serverAddressInput.value = this.connectionModel.address; - if (!this.previousAddresses.value.has(this.connectionModel.address)) { - this.previousAddresses.add(this.connectionModel.address); + // src/View/Modals/searchModal.tsx + function SearchModal(searchViewModel, headline, converter, isOpen) { + function close() { + isOpen.value = false; } - }; - // methods - this.connect = () => { - this.connectToAddress(this.serverAddressInput.value); - }; - this.connectToAddress = (address) => { - this.connectionModel.connect(address); - }; - this.disconnect = () => { - this.connectionModel.disconnect(); - }; - this.removePreviousAddress = (address) => { - this.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.connectionModel.addresses); - }; - this.connectionModel = connectionModel2; - this.updatePreviousAddresses(); - connectionModel2.connectionChangeHandlerManager.addHandler( - this.connectionChangeHandler - ); - } - }; - - // src/ViewModel/Global/coreViewModel.ts - var CoreViewModel = class { - constructor() { - // DRAG & DROP - this.draggedObject = new State(void 0); - // SUGGESTIONS - // boards & tasks - this.boardSearchSuggestions = new ListState(); - this.taskCategorySuggestions = new ListState(); - this.taskStatusSuggestions = new ListState(); - } - }; - - // src/ViewModel/Global/fileTransferViewModel.ts - var FileTransferViewModel = class { - // init - constructor(fileTransferModel2, chatListModel2) { - // 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], - () => translations.dataTransferModal.filesSentCount( - this.filesSentCount.value - ) - ); - this.filePathsReceived = new ListState(); - this.filesReceivedCount = createProxyState( - [this.filePathsReceived], - () => this.filePathsReceived.value.size - ); - this.filesReceivedText = createProxyState( - [this.filesReceivedCount], - () => 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: translations.dataTransferModal.connectionData, - path: StorageModel.getPath( - "connection" /* ConnectionModel */, - filePaths.connectionModel.previousAddresses - ) - }, - { - label: translations.dataTransferModal.settingsData, - path: StorageModel.getPath( - "settings" /* SettingsModel */, - [] - ) - } - ); - const chatModels = this.chatListModel.chatModels; - for (const chatModel of chatModels) { - this.chatFileOptions.add({ - label: chatModel.info.primaryChannel, - path: chatModel.getBasePath() - }); - } - }; - this.getTransferData = () => { - const transferData = this.fileTransferModel.generateTransferData(); - this.transferChannel.value = transferData.channel; - this.transferKey.value = transferData.key; - }; - // view - this.showDirectionSelectionModal = () => { - this.presentedModal.value = 0 /* DirectionSelection */; - this.getOptions(); - }; - this.showFileSelectionModal = () => { - this.presentedModal.value = 1 /* FileSelection */; - }; - this.showTransferDataModal = () => { - this.presentedModal.value = 2 /* TransferDataDisplay */; - this.getTransferData(); - }; - this.initiateTransfer = () => { - this.presentedModal.value = 3 /* TransferDisplay */; - this.didNotFinishSending.value = true; - this.filePathsSent.clear(); - this.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 = () => { - this.presentedModal.value = 5 /* ReceptionDisplay */; - this.filePathsReceived.clear(); - const transferData = { - channel: this.receivingTransferChannel.value, - key: this.receivingTransferKey.value - }; - this.fileTransferModel.prepareToReceive(transferData); - }; - this.hideModal = () => { - this.presentedModal.value = void 0; - }; - this.fileTransferModel = fileTransferModel2; - this.chatListModel = chatListModel2; - this.fileTransferModel.fileHandlerManager.addHandler( - this.handleReceivedFile - ); - } - }; - - // src/View/Components/textSpan.tsx - var StringToTextSpan = (string) => { - return /* @__PURE__ */ createElement("span", { class: "ellipsis" }, string); - }; - - // src/View/Modals/dataTransferModal.tsx - function DataTransferModalWrapper(connectionViewModel2, fileTransferViewModel2) { - return /* @__PURE__ */ createElement("div", null, DirectionSelectionModal( - connectionViewModel2, - fileTransferViewModel2 - ), FileSelectionModal(fileTransferViewModel2), TransferDataDisplayModal(fileTransferViewModel2), TransferDisplayModal(fileTransferViewModel2), TransferDataInputModal(fileTransferViewModel2), DataReceptionModal(fileTransferViewModel2)); - } - function DirectionSelectionModal(connectionViewModel2, fileTransferViewModel2) { - const isPresented = createProxyState( - [fileTransferViewModel2.presentedModal], - () => fileTransferViewModel2.presentedModal.value == 0 /* DirectionSelection */ - ); - const isDisconnected = createProxyState( - [connectionViewModel2.isConnected], - () => connectionViewModel2.isConnected.value == false - ); - return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement( - "p", - { - class: "error", - "toggle:hidden": connectionViewModel2.isConnected - }, - translations.dataTransferModal.notConnectedError - ), /* @__PURE__ */ createElement( - "div", - { - class: "flex-column gap content-margin-bottom", - "toggle:hidden": isDisconnected - }, - /* @__PURE__ */ createElement( - "button", - { - class: "tile", - "on:click": fileTransferViewModel2.showFileSelectionModal - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "upload"), - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, translations.dataTransferModal.fromThisDeviceButton)), - /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") - ), - /* @__PURE__ */ createElement( - "button", - { - class: "tile", - "on:click": fileTransferViewModel2.showTransferDataInputModal - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "download"), - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, translations.dataTransferModal.toThisDeviceButton)), - /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") - ) - )), /* @__PURE__ */ createElement("button", { "on:click": fileTransferViewModel2.hideModal }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); - } - function FileSelectionModal(fileTransferViewModel2) { - const OptionConverter = (fileOption) => { - return OptionEntry(fileOption, fileTransferViewModel2); - }; - function OptionEntry(fileOption, fileTransferViewModel3) { - const isSelected = new State(false); - if (fileTransferViewModel3.selectedPaths.value.has(fileOption.path)) { - isSelected.value = true; - } - isSelected.subscribeSilent((isSelected2) => { - if (isSelected2 == true) { - fileTransferViewModel3.selectedPaths.add(fileOption.path); - } else { - fileTransferViewModel3.selectedPaths.remove(fileOption.path); - } - }); - function toggle() { - isSelected.value = !isSelected.value; - } - return /* @__PURE__ */ createElement("button", { class: "tile", "toggle:selected": isSelected, "on:click": toggle }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", { class: "ellipsis" }, fileOption.label), /* @__PURE__ */ createElement("span", { class: "secondary ellipsis" }, StorageModel.pathComponentsToString( - ...fileOption.path - )))); - } - const isPresented = createProxyState( - [fileTransferViewModel2.presentedModal], - () => fileTransferViewModel2.presentedModal.value == 1 /* FileSelection */ - ); - return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.dataTransferModal.selectionDescription), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("h3", null, translations.dataTransferModal.generalHeadline), /* @__PURE__ */ createElement( - "div", - { - class: "flex-column gap content-margin-bottom", - "children:append": [ - fileTransferViewModel2.generalFileOptions, - OptionConverter - ] - } - ), /* @__PURE__ */ createElement("h3", null, translations.dataTransferModal.chatsHeadline), /* @__PURE__ */ createElement( - "div", - { - class: "flex-column gap", - "children:append": [ - fileTransferViewModel2.chatFileOptions, - OptionConverter - ] - } - )), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement( - "button", - { - class: "flex", - "on:click": fileTransferViewModel2.showDirectionSelectionModal - }, - translations.general.backButton - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary flex", - "on:click": fileTransferViewModel2.showTransferDataModal, - "toggle:disabled": fileTransferViewModel2.hasNoPathsSelected - }, - translations.general.continueButton, - /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") - )))); - } - function TransferDataDisplayModal(fileTransferViewModel2) { - const isPresented = createProxyState( - [fileTransferViewModel2.presentedModal], - () => fileTransferViewModel2.presentedModal.value == 2 /* TransferDataDisplay */ - ); - return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.dataTransferModal.dataEntryDescription), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.dataTransferModal.transferChannelHeadline), /* @__PURE__ */ createElement( - "b", - { - "subscribe:innerText": fileTransferViewModel2.transferChannel - } - ))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.dataTransferModal.transferKeyHeadline), /* @__PURE__ */ createElement( - "b", - { - "subscribe:innerText": fileTransferViewModel2.transferKey - } - ))))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement( - "button", - { - class: "flex", - "on:click": fileTransferViewModel2.showFileSelectionModal - }, - translations.general.backButton - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary flex", - "on:click": fileTransferViewModel2.initiateTransfer - }, - translations.dataTransferModal.sendButton, - /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") - )))); - } - function TransferDisplayModal(fileTransferViewModel2) { - const isPresented = createProxyState( - [fileTransferViewModel2.presentedModal], - () => fileTransferViewModel2.presentedModal.value == 3 /* TransferDisplay */ - ); - return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement( - "p", - { - class: "secondary", - "subscribe:innerText": fileTransferViewModel2.filesSentText - } - ), /* @__PURE__ */ createElement( - "p", - { - class: "secondary", - "toggle:hidden": fileTransferViewModel2.didNotFinishSending - }, - translations.dataTransferModal.allFilesSent - ), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( - "div", - { - class: "tile flex-column align-start", - "children:append": [ - fileTransferViewModel2.filePathsSent, - StringToTextSpan - ] - } - )), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement( - "button", - { - class: "flex", - "on:click": fileTransferViewModel2.initiateTransfer - }, - translations.dataTransferModal.sendAgainButton, - /* @__PURE__ */ createElement("span", { class: "icon" }, "restart_alt") - ), /* @__PURE__ */ createElement( - "button", - { - class: "flex", - "on:click": fileTransferViewModel2.hideModal, - "toggle:disabled": fileTransferViewModel2.didNotFinishSending - }, - translations.general.closeButton, - /* @__PURE__ */ createElement("span", { class: "icon" }, "close") - )))); - } - function TransferDataInputModal(fileTransferViewModel2) { - const isPresented = createProxyState( - [fileTransferViewModel2.presentedModal], - () => fileTransferViewModel2.presentedModal.value == 4 /* TransferDataInput */ - ); - return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.dataTransferModal.dataEntryInputDescription), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.dataTransferModal.transferChannelHeadline), /* @__PURE__ */ createElement( - "input", - { - "bind:value": fileTransferViewModel2.receivingTransferChannel - } - ))), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.dataTransferModal.transferKeyHeadline), /* @__PURE__ */ createElement( - "input", - { - "bind:value": fileTransferViewModel2.receivingTransferKey - } - ))))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement( - "button", - { - class: "flex", - "on:click": fileTransferViewModel2.showDirectionSelectionModal - }, - translations.general.backButton - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary flex", - "on:click": fileTransferViewModel2.prepareReceivingData, - "toggle:disabled": fileTransferViewModel2.cannotPrepareToReceive - }, - translations.general.continueButton, - /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward") - )))); - } - function DataReceptionModal(fileTransferViewModel2) { - const isPresented = createProxyState( - [fileTransferViewModel2.presentedModal], - () => fileTransferViewModel2.presentedModal.value == 5 /* ReceptionDisplay */ - ); - return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement("p", { class: "secondary" }, translations.dataTransferModal.readyToReceiveDescription), /* @__PURE__ */ createElement( - "p", - { - class: "secondary", - "subscribe:innerText": fileTransferViewModel2.filesReceivedText - } - ), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( - "div", - { - class: "tile flex-column align-start", - "children:append": [ - fileTransferViewModel2.filePathsReceived, - StringToTextSpan - ] - } - )), /* @__PURE__ */ createElement("button", { "on:click": reload }, translations.general.reloadAppButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "refresh")))); - } - - // src/Model/Global/fileTransferModel.ts - var FileTransferModel = class { - // init - constructor(storageModel2, connectionModel2) { - this.fileHandlerManager = new HandlerManager(); - // general - this.generateTransferData = () => { - const transferData = { - channel: generateRandomToken(2).substring(0, 4), - key: generateRandomToken(3).substring(0, 6) - }; - this.transferData = transferData; - return transferData; - }; - this.prepareToReceive = (transferData) => { - this.connectionModel.addChannel(transferData.channel); - this.transferData = transferData; - }; - // handlers - this.handleMessage = (data) => { - if (this.transferData == void 0) return; - if (data.messageBody == void 0) return; - if (data.messageChannel != this.transferData.channel) return; - this.handleFile(data.messageBody); - }; - this.handleFile = async (encryptedFileData) => { - if (this.transferData == void 0) return; - const decrypted = await decryptString( - encryptedFileData, - this.transferData.key - ); - console.log(decrypted); - 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.addHandler( - this.handleMessage - ); - } - }; - var FileDataReference = { - path: [""], - body: "" - }; - - // src/View/Components/chatEntry.tsx - function ChatEntry(chatViewModel) { - const view = /* @__PURE__ */ createElement( - "button", - { - class: "tile colored-tile chat-entry", - "set:color": chatViewModel.settingsPageViewModel.color, - style: "height: 8rem", - "on:click": chatViewModel.open, - "toggle:unread": 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/homePage.tsx - function HomePage(storageViewModel2, settingsViewModel2, connectionViewModel2, fileTransferViewModel2, chatListViewModel2) { - const overviewSection = /* @__PURE__ */ createElement("div", { id: "overview-section" }, /* @__PURE__ */ createElement("h2", null, 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, translations.homePage.serverAddress), /* @__PURE__ */ createElement( - "input", - { - list: "previous-connection-list", - placeholder: 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": 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": 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": 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, translations.homePage.yourNameLabel), /* @__PURE__ */ createElement( - "input", - { - placeholder: 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": translations.homePage.setNameButtonAudioLabel - }, - translations.general.setButton, - /* @__PURE__ */ createElement("span", { class: "icon" }, "check") - )), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "calendar_month"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.homePage.firstDayOfWeekLabel), /* @__PURE__ */ createElement("select", { "bind:value": settingsViewModel2.firstDayOfWeekInput }, ...translations.regional.weekdays.full.map( - (weekdayName, i) => Option( - weekdayName, - i.toString(), - i.toString() == settingsViewModel2.firstDayOfWeekInput.value - ) - )), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( - "button", - { - class: "tile flex-no", - "on:click": fileTransferViewModel2.showDirectionSelectionModal - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "sync_alt"), - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.homePage.transferDataButton)) - ), /* @__PURE__ */ createElement( - "button", - { - class: "tile flex-no", - "on:click": storageViewModel2.showStorageModal - }, - /* @__PURE__ */ createElement("span", { class: "icon" }, "hard_drive_2"), - /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.homePage.manageStorageButton)) - ), /* @__PURE__ */ createElement("div", { class: "mobile-only" }, /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, /* @__PURE__ */ createElement("button", { class: "ghost width-50", "on:click": scrollToChat }, translations.homePage.scrollToChatButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward"))))); - const chatSection = /* @__PURE__ */ createElement("div", { id: "chat-section" }, /* @__PURE__ */ createElement("h2", null, translations.homePage.chatsHeadline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( - "input", - { - placeholder: translations.homePage.addChatPlaceholder, - "aria-label": translations.homePage.addChatAudioLabel, - "bind:value": chatListViewModel2.newChatPrimaryChannel, - "on:enter": chatListViewModel2.createChat - } - ), /* @__PURE__ */ createElement( - "button", - { - class: "primary", - "aria-label": 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 { - // storage - setName(newValue) { - this.username = newValue; - const path = StorageModel.getPath( - "settings" /* SettingsModel */, - filePaths.settingsModel.username - ); - this.storageModel.write(path, newValue); - } - setFirstDayOfWeek(newValue) { - this.firstDayOfWeek = newValue; - const path = StorageModel.getPath( - "settings" /* SettingsModel */, - filePaths.settingsModel.firstDayOfWeek - ); - this.storageModel.write(path, newValue); - } - // load - loadUsernam() { - const path = StorageModel.getPath( - "settings" /* SettingsModel */, - filePaths.settingsModel.username - ); - const content = this.storageModel.read(path); - this.username = content ?? ""; - } - loadFirstDayofWeek() { - const path = StorageModel.getPath( - "settings" /* SettingsModel */, - filePaths.settingsModel.firstDayOfWeek - ); - const content = this.storageModel.read(path); - this.firstDayOfWeek = content ?? "0"; - } - // init - constructor(storageModel2) { - this.storageModel = storageModel2; - this.loadUsernam(); - this.loadFirstDayofWeek(); - } - }; - - // src/ViewModel/Global/settingsViewModel.ts - var SettingsViewModel = class { - // init - constructor(coreViewModel, settingsModel2) { - this.coreViewModel = coreViewModel; - // state - this.username = new State(""); - this.usernameInput = new State(""); - this.firstDayOfWeekInput = new State("0"); - // guards - this.cannotSetName = createProxyState( - [this.usernameInput], - () => this.usernameInput.value == "" || this.usernameInput.value == this.settingsModel.username - ); - // methods - this.setName = () => { - this.settingsModel.setName(this.usernameInput.value); - this.username.value = this.settingsModel.username; - this.usernameInput.callSubscriptions(); - }; - this.setFirstDayofWeek = () => { - this.settingsModel.setFirstDayOfWeek(this.firstDayOfWeekInput.value); - }; - this.settingsModel = settingsModel2; - this.username.value = settingsModel2.username; - this.usernameInput.value = settingsModel2.username; - this.firstDayOfWeekInput.value = settingsModel2.firstDayOfWeek; - this.firstDayOfWeekInput.subscribe(this.setFirstDayofWeek); - } - }; - - // 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.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/Components/fileBrowser.tsx - function FileBrowser(storageViewModel2) { - const detailView = createProxyState( - [storageViewModel2.selectedPath], - () => { - if (storageViewModel2.selectedPath.value == PATH_COMPONENT_SEPARATOR) - return /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.storage.noItemSelected), /* @__PURE__ */ createElement("hr", null), DangerousActionButton( - 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, 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, translations.storage.content), /* @__PURE__ */ createElement( - "code", - { - "subscribe:innerText": storageViewModel2.selectedFileContent - } - ))), DangerousActionButton( - translations.storage.deleteItem, - "delete_forever", - storageViewModel2.deleteSelectedItem - )); - } - ); - const view = /* @__PURE__ */ createElement("div", { class: "file-browser" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "scroll-area" }, DirectoryItemList(storageViewModel2)), /* @__PURE__ */ createElement("div", { class: "detail-button-wrapper" }, /* @__PURE__ */ createElement("button", { class: "ghost", "on:click": scrollToDetails }, /* @__PURE__ */ createElement( - "span", - { - class: "ellipsis", - "subscribe:innerText": storageViewModel2.selectedFileName - } - ), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")))), /* @__PURE__ */ createElement("div", { class: "scroll-area", "children:set": detailView })); - function scrollToDetails() { - view.scrollLeft = view.scrollWidth; - } - return view; - } - - // src/View/Modals/storageModal.tsx - function StorageModal(storageViewModel2) { - return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": storageViewModel2.isShowingStorageModal }, /* @__PURE__ */ createElement("div", { style: "max-width: 64rem" }, /* @__PURE__ */ createElement("main", { class: "padding-0" }, FileBrowser(storageViewModel2)), /* @__PURE__ */ createElement("button", { "on:click": storageViewModel2.hideStorageModal }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); - } - - // src/ViewModel/Global/storageViewModel.ts - var StorageViewModel = class { - // init - constructor(coreViewModel, storageModel2) { - this.coreViewModel = coreViewModel; - // 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.storageModel.read(path); - return (content ?? translations.storage.notAFile) || translations.storage.contentEmpty; - }; - this.deleteSelectedItem = () => { - const path = StorageModel.stringToPathComponents( - this.selectedPath.value - ); - this.lastDeletedItemPath.value = this.selectedPath.value; - this.storageModel.removeRecursively(path); - this.didMakeChanges.value = true; - }; - this.removeJunk = () => { - this.storageModel.removeJunk(); - this.selectedPath.value = PATH_COMPONENT_SEPARATOR; - }; - // view - this.showStorageModal = () => { - this.isShowingStorageModal.value = true; - }; - this.hideStorageModal = () => { - if (this.didMakeChanges.value == true) { - window.location.reload(); - return; - } - this.isShowingStorageModal.value = false; - }; - this.storageModel = storageModel2; - this.selectedFileName = createProxyState( - [this.selectedPath], - () => StorageModel.getFileNameFromString(this.selectedPath.value) - ); - this.selectedFileContent = createProxyState( - [this.selectedPath], - () => this.getSelectedItemContent() - ); - } - }; - - // src/Upgrader/v1.ts - var v1Upgrader = class { - // init - constructor(settingsModel2, connectionModel2, chatListModel2) { - this.settingsModel = settingsModel2; - this.connectionModel = connectionModel2; - this.chatListModel = chatListModel2; - // general - this.migrateSettings = () => { - const name = getLocalStorageItemAndClear("sender-name"); - if (name != null) { - const parsedName = parseOrFallback(name); - this.settingsModel.setName(parsedName); - } - const firstDayOfWeek = getLocalStorageItemAndClear("first-day-of-week"); - if (firstDayOfWeek != null) { - const parsedFirstDayOfWeek = parseOrFallback(firstDayOfWeek); - this.settingsModel.setFirstDayOfWeek(parsedFirstDayOfWeek); - } - }; - this.migrateConnections = () => { - const previousAddressString = getLocalStorageItemAndClear("previous-addresses"); - if (previousAddressString == null) return; - const previousAddresses = parseArray(previousAddressString); - for (const address of previousAddresses) { - if (typeof address != "string") continue; - this.connectionModel.storeAddress(address); - } - }; - // chats - this.migrateChats = () => { - const chatIdString = getLocalStorageItemAndClear("chat-ids"); - if (chatIdString == null) return; - const chatIds = parseArray(chatIdString); - for (const chatId of chatIds) { - if (typeof chatId != "string") continue; - this.migrateChatById(chatId); - } - }; - this.migrateChatById = (id) => { - const primaryChannel = getLocalStorageItemAndClear( - storageKeys.primaryChannel(id) - ); - if (primaryChannel == null) return; - const parsedPriamryChannel = parseOrFallback(primaryChannel); - const chatModel = this.chatListModel.createChat(parsedPriamryChannel); - const secondaryChannelString = getLocalStorageItemAndClear(storageKeys.secondaryChannels(id)); - if (secondaryChannelString != null) { - const potentialSecondaryChannels = parseArray( - secondaryChannelString - ); - const confirmedSecondaryChannels = []; - for (const secondaryChannel of potentialSecondaryChannels) { - if (typeof secondaryChannel != "string") continue; - confirmedSecondaryChannels.push(secondaryChannel); - } - chatModel.setSecondaryChannels(confirmedSecondaryChannels); - } - const encryptionKey = getLocalStorageItemAndClear( - storageKeys.encyptionKey(id) - ); - if (encryptionKey != null) { - const parsedEncryptionKey = parseOrFallback(encryptionKey); - chatModel.setEncryptionKey(parsedEncryptionKey); - } - const messagesString = getLocalStorageItemAndClear(storageKeys.messages(id)) ?? ""; - const messageOutboxString = getLocalStorageItemAndClear(storageKeys.outbox(id)) ?? ""; - const potentialMessages = parseArray(messagesString); - const potentialMessagesInOutbox = parseArray(messageOutboxString); - const addMessages = (potentialMessages2, status) => { - for (const potentialMessage of potentialMessages2) { - const isV1ChatMessage = checkMatchesObjectStructure( - potentialMessage, - V1ChatMessageReference - ); - if (isV1ChatMessage == false) continue; - const v1ChatMessage = potentialMessage; - const convertedChatMessage = { - dataVersion: "v2", - id: v4_default(), - channel: v1ChatMessage.channel, - sender: v1ChatMessage.sender, - body: v1ChatMessage.body, - dateSent: v1ChatMessage.isoDate, - status, - stringifiedFile: "" - }; - chatModel.addMessage(convertedChatMessage); - if (status == "outbox" /* Outbox */) { - this.connectionModel.sendMessageOrStore( - convertedChatMessage - ); - } - } - }; - addMessages(potentialMessages, "received" /* Received */); - addMessages(potentialMessagesInOutbox, "outbox" /* Outbox */); - const objectsString = getLocalStorageItemAndClear(storageKeys.objects(id)) ?? ""; - const objectOutboxString = getLocalStorageItemAndClear(storageKeys.itemOutbox(id)) ?? ""; - const potentialObjects = parseArray(objectsString); - const potentialObjectsInOutbox = parseArray(objectOutboxString); - const addObjects = (potentialObjects2) => { - const board = chatModel.fileModel.boardsAndTasksModel.createBoard( - translations.updater.migrated - ); - chatModel.fileModel.boardsAndTasksModel.updateBoard(board); - for (const potentialObjectEntry of potentialObjects2) { - const potentialObject = potentialObjectEntry[1]; - const isV1MessageObject = checkIsV1MessageObject(potentialObject); - if (isV1MessageObject == false) continue; - const objectId = potentialObject.id; - const objectName = potentialObject.title; - const contentVersions = Object.values( - potentialObject.contentVersions - ); - for (const potentialVersion of contentVersions) { - const isV1MessageObjectContent = checkIsV1MessageObjectContent(potentialVersion); - if (isV1MessageObjectContent == false) continue; - const convertedTaskFileContent = { - dataVersion: "v2", - fileId: objectId, - fileContentId: FileModel2.generateFileContentId( - potentialVersion.isoDateVersionCreated + const suggestionId = v4_default(); + return /* @__PURE__ */ createElement( + "div", + { class: "modal", "toggle:open": isOpen }, + /* @__PURE__ */ createElement( + "div", + { style: "max-width: 64rem" }, + /* @__PURE__ */ createElement( + "main", + null, + /* @__PURE__ */ createElement("h2", null, headline), + /* @__PURE__ */ createElement( + "div", + { class: "flex-row width-input" }, + /* @__PURE__ */ createElement("input", { + placeholder: translations.general.searchLabel, + "bind:value": searchViewModel.searchInput, + "on:enter": searchViewModel.applySearch, + list: suggestionId, + }), + /* @__PURE__ */ createElement("datalist", { + hidden: true, + id: suggestionId, + "children:append": [ + searchViewModel.suggestions, + StringToOption, + ], + }), + /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "aria-label": + translations.general.searchButtonAudioLabel, + "on:click": searchViewModel.applySearch, + "toggle:disabled": + searchViewModel.cannotApplySearch, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "search", + ), + ), + ), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement("div", { + class: "grid gap", + style: "grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr))", + "children:append": [ + searchViewModel.matchingObjects, + converter, + ], + }), ), - creationDate: potentialVersion.isoDateVersionCreated, - type: "task", - name: objectName ?? "", - boardId: board.fileId ?? "", - description: potentialVersion.noteContent ?? "", - category: potentialVersion.categoryName ?? "", - status: potentialVersion.status ?? "", - priority: potentialVersion.priority ?? "", - date: potentialVersion.date ?? "", - time: potentialVersion.time ?? "" - }; - console.log(convertedTaskFileContent); - chatModel.fileModel.handleFileContent( - convertedTaskFileContent - ); - } - } - }; - addObjects([...potentialObjects, ...potentialObjectsInOutbox]); - }; - this.migrateSettings(); - this.migrateConnections(); - this.migrateChats(); + /* @__PURE__ */ createElement( + "button", + { "on:click": close }, + translations.general.closeButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "close", + ), + ), + ), + ); } - }; - var storageKeys = { - viewType(id) { - return id + "view-type"; - }, - hasUnread(id) { - return id + "has-unread-messages"; - }, - primaryChannel(id) { - return id + "primary-channel"; - }, - secondaryChannels(id) { - return id + "secondary-channels"; - }, - encyptionKey(id) { - return id + "encryption-key"; - }, - messages(id) { - return id + "messages"; - }, - objects(id) { - return id + "items"; - }, - outbox(id) { - return id + "outbox"; - }, - itemOutbox(id) { - return id + "item-outbox"; - }, - composingMessage(id) { - return id + "composing-message"; - } - }; - var V1ChatMessageReference = { - channel: "", - sender: "", - body: "", - isoDate: "" - }; - function checkIsV1MessageObject(object) { - if (object.id == void 0) return false; - if (object.title == void 0) return false; - if (object.contentVersions == void 0) return false; - return true; - } - function checkIsV1MessageObjectContent(object) { - if (object.id == void 0) return false; - if (object.isoDateVersionCreated == void 0) return false; - return true; - } - // src/index.tsx - var storageModel = new StorageModel(); - var settingsModel = new SettingsModel(storageModel); - var connectionModel = new ConnectionModel(storageModel); - var chatListModel = new ChatListModel( - storageModel, - settingsModel, - connectionModel - ); - var fileTransferModel = new FileTransferModel(storageModel, connectionModel); - new v1Upgrader(settingsModel, connectionModel, chatListModel); - var coreVieWModel = new CoreViewModel(); - var storageViewModel = new StorageViewModel(coreVieWModel, storageModel); - var settingsViewModel = new SettingsViewModel(coreVieWModel, settingsModel); - var connectionViewModel = new ConnectionViewModel( - coreVieWModel, - connectionModel - ); - var chatListViewModel = new ChatListViewModel( - coreVieWModel, - storageModel, - chatListModel, - settingsViewModel, - connectionViewModel - ); - var fileTransferViewModel = new FileTransferViewModel( - fileTransferModel, - chatListModel - ); - chatListViewModel.selectedChat.subscribe(() => { - document.body.toggleAttribute( - "showing-chat", - chatListViewModel.selectedChat.value != void 0 + // src/View/ChatPages/boardPage.tsx + function BoardPage(boardViewModel) { + boardViewModel.loadData(); + const mainContent = createProxyState( + [boardViewModel.selectedPage], + () => { + switch (boardViewModel.selectedPage.value) { + case "kanban" /* Kanban */: { + return BoardKanbanPage(boardViewModel); + } + case "status-grid" /* StatusGrid */: { + return BoardStatusGridPage(boardViewModel); + } + default: { + return /* @__PURE__ */ createElement("div", { + class: "task-grid", + "children:append": [ + boardViewModel.filteredTaskViewModels, + TaskViewModelToEntry, + ], + }); + } + } + }, + ); + const taskSettingsModal = createProxyState( + [boardViewModel.selectedTaskViewModel], + () => { + if (boardViewModel.selectedTaskViewModel.value == void 0) { + return /* @__PURE__ */ createElement("div", null); + } else { + return TaskSettingsModal( + boardViewModel.selectedTaskViewModel.value, + ); + } + }, + ); + return /* @__PURE__ */ createElement( + "div", + { class: "pane" }, + /* @__PURE__ */ createElement( + "div", + { class: "toolbar" }, + /* @__PURE__ */ createElement( + "span", + null, + /* @__PURE__ */ createElement( + "button", + { + class: "ghost board-close-button", + "aria-label": + translations.chatPage.task + .closeBoardButtonAudioLabel, + "on:click": boardViewModel.close, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "arrow_back", + ), + ), + /* @__PURE__ */ createElement( + "button", + { + class: "ghost board-toggle-button inset-outline", + "aria-label": + translations.chatPage.task + .toggleBoardButtonAudioLabel, + "on:click": + boardViewModel.taskPageViewModel + .toggleBoardList, + "toggle:selected": + boardViewModel.taskPageViewModel + .isShowingBoadList, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "dock_to_right", + ), + ), + /* @__PURE__ */ createElement( + "button", + { + class: "ghost", + "aria-label": + translations.chatPage.task + .boardSettingsHeadline, + "on:click": boardViewModel.showSettings, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "settings", + ), + ), + ), + /* @__PURE__ */ createElement( + "span", + { class: "scroll-h ribbon" }, + BoardViewToggleButton( + translations.chatPage.task.listViewButtonAudioLabel, + "view_list", + "list" /* List */, + boardViewModel, + ), + BoardViewToggleButton( + translations.chatPage.task.kanbanViewButtonAudioLabel, + "view_kanban", + "kanban" /* Kanban */, + boardViewModel, + ), + BoardViewToggleButton( + translations.chatPage.task.statusViewButtonAudioLabel, + "grid_view", + "status-grid" /* StatusGrid */, + boardViewModel, + ), + ), + /* @__PURE__ */ createElement( + "span", + null, + /* @__PURE__ */ createElement( + "button", + { + class: "ghost", + "aria-label": + translations.chatPage.task + .filterTasksButtonAudioLabel, + "on:click": boardViewModel.showFilterModal, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "filter_alt", + ), + ), + /* @__PURE__ */ createElement( + "button", + { + class: "ghost", + "aria-label": + translations.chatPage.task + .createTaskButtonAudioLabel, + "on:click": boardViewModel.createTask, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "add", + ), + ), + ), + ), + /* @__PURE__ */ createElement("div", { + class: "content main-content", + "children:set": mainContent, + }), + BoardSettingsModal(boardViewModel), + SearchModal( + boardViewModel.searchViewModel, + translations.chatPage.task.filterTasksHeadline, + TaskViewModelToEntry, + boardViewModel.isPresentingFilterModal, + ), + /* @__PURE__ */ createElement("div", { + "children:set": taskSettingsModal, + }), + ); + } + + // src/View/Components/boardEntry.tsx + function BoardEntry(boardViewModel) { + const view = /* @__PURE__ */ createElement( + "button", + { + "set:color": boardViewModel.color, + class: "tile colored-tile", + "toggle:selected": boardViewModel.isSelected, + "on:click": boardViewModel.select, + "on:dragover": allowDrop, + "on:drop": boardViewModel.handleDropBetweenBoards, + }, + /* @__PURE__ */ createElement("span", { + class: "shadow", + "subscribe:innerText": boardViewModel.name, + }), + /* @__PURE__ */ createElement("b", { + "subscribe:innerText": boardViewModel.name, + }), + ); + boardViewModel.index.subscribe((newIndex) => { + view.style.order = newIndex; + }); + return view; + } + var BoardViewModelToEntry = (boardViewModel) => { + return BoardEntry(boardViewModel); + }; + + // src/View/ChatPages/taskPage.tsx + function TaskPage(taskPageViewModel) { + taskPageViewModel.loadData(); + const isShowingBoard = createProxyState( + [taskPageViewModel.selectedBoardId], + () => taskPageViewModel.selectedBoardId.value != void 0, + ); + const paneContent = createProxyState( + [taskPageViewModel.selectedBoardId], + () => { + const selectedBoardId = taskPageViewModel.selectedBoardId.value; + if (selectedBoardId == void 0) { + return /* @__PURE__ */ createElement( + "div", + { class: "pane align-center justify-center" }, + /* @__PURE__ */ createElement( + "span", + { class: "secondary" }, + translations.chatPage.task.noBoardSelected, + ), + ); + } + const selectedBoard = + taskPageViewModel.boardViewModels.value.get( + selectedBoardId, + ); + if (selectedBoard == void 0) { + return /* @__PURE__ */ createElement( + "div", + { class: "pane align-center justify-center" }, + /* @__PURE__ */ createElement( + "span", + { class: "secondary" }, + translations.chatPage.task.boardNotFound, + ), + ); + } + return BoardPage(selectedBoard); + }, + ); + return /* @__PURE__ */ createElement( + "div", + { + id: "task-page", + "toggle:isshowingboard": isShowingBoard, + "set:showingboardlist": taskPageViewModel.isShowingBoadList, + }, + /* @__PURE__ */ createElement( + "div", + { + id: "board-list", + class: "pane-wrapper side background", + "set:color": taskPageViewModel.chatViewModel.displayedColor, + }, + /* @__PURE__ */ createElement( + "div", + { class: "pane" }, + /* @__PURE__ */ createElement( + "div", + { class: "toolbar" }, + /* @__PURE__ */ createElement( + "div", + { class: "flex-row width-input" }, + /* @__PURE__ */ createElement("input", { + class: "no-outline", + "bind:value": + taskPageViewModel.newBoardNameInput, + "on:enter": taskPageViewModel.createBoard, + placeholder: + translations.chatPage.task + .newBoardNamePlaceholder, + }), + /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "aria-label": + translations.chatPage.task + .createBoardButtonAudioLabel, + "on:click": taskPageViewModel.createBoard, + "toggle:disabled": + taskPageViewModel.cannotCreateBoard, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "add", + ), + ), + ), + ), + /* @__PURE__ */ createElement( + "div", + { class: "content" }, + /* @__PURE__ */ createElement("div", { + class: "grid gap", + style: "grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr))", + "children:append": [ + taskPageViewModel.boardViewModels, + BoardViewModelToEntry, + ], + }), + ), + ), + ), + /* @__PURE__ */ createElement("div", { + id: "board-content", + class: "pane-wrapper", + "children:set": paneContent, + }), + ); + } + + // src/View/chatPage.tsx + function ChatPage(chatViewModel) { + const mainContent = createProxyState( + [chatViewModel.selectedPage], + () => { + chatViewModel.closeSubPages(); + switch (chatViewModel.selectedPage.value) { + case "settings" /* Settings */: { + return SettingsPage( + chatViewModel.settingsPageViewModel, + ); + } + case "tasks" /* Tasks */: { + return TaskPage(chatViewModel.taskPageViewModel); + } + case "calendar" /* Calendar */: { + return CalendarPage(chatViewModel.calendarViewModel); + } + default: { + return MessagePage(chatViewModel.messagePageViewModel); + } + } + }, + ); + return /* @__PURE__ */ createElement( + "article", + { + id: "chat-page", + "set:color": chatViewModel.displayedColor, + class: "subtle-background", + }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "div", + { id: "ribbon" }, + /* @__PURE__ */ createElement( + "button", + { + class: "ghost", + "aria-label": + translations.chatPage.closeChatAudioLabe, + "on:click": chatViewModel.close, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "close", + ), + ), + /* @__PURE__ */ createElement( + "button", + { + class: "danger", + "aria-label": + translations.general.restoreConnection, + "on:click": + chatViewModel.connectionViewModel + .showConnectionModal, + "toggle:hidden": + chatViewModel.connectionViewModel.isConnected, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "signal_disconnected", + ), + ), + /* @__PURE__ */ createElement( + "span", + null, + ChatViewToggleButton( + translations.chatPage.pages.calendar, + "calendar_month", + "calendar" /* Calendar */, + chatViewModel, + ), + ChatViewToggleButton( + translations.chatPage.pages.tasks, + "task_alt", + "tasks" /* Tasks */, + chatViewModel, + ), + ChatViewToggleButton( + translations.chatPage.pages.messages, + "forum", + "messages" /* Messages */, + chatViewModel, + ), + ChatViewToggleButton( + translations.chatPage.pages.settings, + "settings", + "settings" /* Settings */, + chatViewModel, + ), + ), + ), + /* @__PURE__ */ createElement("div", { + id: "main", + "children:set": mainContent, + }), + ), + ); + } + + // src/View/chatPageWrapper.tsx + function ChatPageWrapper(chatListViewModel2) { + const chatPageContent = createProxyState( + [chatListViewModel2.selectedChat], + () => { + if (chatListViewModel2.selectedChat.value == void 0) { + return /* @__PURE__ */ createElement("div", null); + } else { + return ChatPage(chatListViewModel2.selectedChat.value); + } + }, + ); + return /* @__PURE__ */ createElement("div", { + id: "chat-page-wrapper", + "children:set": chatPageContent, + }); + } + + // src/View/Modals/connectionModal.tsx + function ConnectionModal(connectionViewModel2) { + const previousAddressConverter = (address) => { + function connnect() { + connectionViewModel2.connectToAddress(address); + } + const cannotConnect = createProxyState( + [connectionViewModel2.isConnected], + () => + connectionViewModel2.isConnected.value == true && + connectionViewModel2.connectionModel.address == address, + ); + return DeletableListItem( + address, + /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "on:click": connnect, + "toggle:disabled": cannotConnect, + "aria-label": + translations.connectionModal + .connectButtonAudioLabel, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "link", + ), + ), + () => { + connectionViewModel2.removePreviousAddress(address); + }, + ); + }; + return /* @__PURE__ */ createElement( + "div", + { + class: "modal", + "toggle:open": connectionViewModel2.isShowingConnectionModal, + }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "main", + null, + /* @__PURE__ */ createElement( + "h2", + null, + translations.connectionModal.connectionModalHeadline, + ), + /* @__PURE__ */ createElement("div", { + class: "flex-column gap", + "children:append": [ + connectionViewModel2.previousAddresses, + previousAddressConverter, + ], + }), + ), + /* @__PURE__ */ createElement( + "button", + { "on:click": connectionViewModel2.hideConnectionModal }, + translations.general.closeButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "close", + ), + ), + ), + ); + } + + // ../ui-base/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.isConnected == false) { + if (this.shouldAttemptReconnect == false) return; + this.reconnectInterval = setInterval(() => { + if (this.isConnected == true) { + clearInterval(this.reconnectInterval); + return; + } + this.reconnect(); + }, 5e3); + return; + } + if (this.address == void 0) return; + this.storeAddress(this.address); + this.sendSubscriptionRequest(); + this.sendMessagesInOutbox(); + this.shouldAttemptReconnect = true; + }; + // connection + this.connect = (address) => { + this.udn.connect(address); + }; + this.disconnect = () => { + this.udn.disconnect(); + const reconnectAddressPath = StorageModel.getPath( + "connection" /* ConnectionModel */, + filePaths.connectionModel.reconnectAddress, + ); + this.storageModel.remove(reconnectAddressPath); + this.shouldAttemptReconnect = false; + }; + this.reconnect = () => { + const reconnectAddressPath = this.getReconnectAddressPath(); + const reconnectAddress = + this.storageModel.read(reconnectAddressPath); + if (reconnectAddress == null) return; + console.log("reconnecting..."); + this.connect(reconnectAddress); + }; + // mailbox + this.getMailboxPath = (address) => { + 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}`); + }; + 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(coreViewModel, connectionModel2) { + this.coreViewModel = coreViewModel; + // 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.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.connectionModel.isConnected; + if (this.connectionModel.isConnected == false) return; + if (this.connectionModel.address == void 0) return; + this.serverAddressInput.value = this.connectionModel.address; + if ( + !this.previousAddresses.value.has( + this.connectionModel.address, + ) + ) { + this.previousAddresses.add(this.connectionModel.address); + } + }; + // methods + this.connect = () => { + this.connectToAddress(this.serverAddressInput.value); + }; + this.connectToAddress = (address) => { + this.connectionModel.connect(address); + }; + this.disconnect = () => { + this.connectionModel.disconnect(); + }; + this.removePreviousAddress = (address) => { + this.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.connectionModel.addresses); + }; + this.connectionModel = connectionModel2; + this.updatePreviousAddresses(); + connectionModel2.connectionChangeHandlerManager.addHandler( + this.connectionChangeHandler, + ); + } + }; + + // src/ViewModel/Global/coreViewModel.ts + var CoreViewModel = class { + constructor() { + // DRAG & DROP + this.draggedObject = new State(void 0); + // SUGGESTIONS + // boards & tasks + this.boardSearchSuggestions = new ListState(); + this.taskCategorySuggestions = new ListState(); + this.taskStatusSuggestions = new ListState(); + } + }; + + // src/ViewModel/Global/fileTransferViewModel.ts + var FileTransferViewModel = class { + // init + constructor(fileTransferModel2, chatListModel2) { + // 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], () => + translations.dataTransferModal.filesSentCount( + this.filesSentCount.value, + ), + ); + this.filePathsReceived = new ListState(); + this.filesReceivedCount = createProxyState( + [this.filePathsReceived], + () => this.filePathsReceived.value.size, + ); + this.filesReceivedText = createProxyState( + [this.filesReceivedCount], + () => + 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: translations.dataTransferModal.connectionData, + path: StorageModel.getPath( + "connection" /* ConnectionModel */, + filePaths.connectionModel.previousAddresses, + ), + }, + { + label: translations.dataTransferModal.settingsData, + path: StorageModel.getPath( + "settings" /* SettingsModel */, + [], + ), + }, + ); + const chatModels = this.chatListModel.chatModels; + for (const chatModel of chatModels) { + this.chatFileOptions.add({ + label: chatModel.info.primaryChannel, + path: chatModel.getBasePath(), + }); + } + }; + this.getTransferData = () => { + const transferData = + this.fileTransferModel.generateTransferData(); + this.transferChannel.value = transferData.channel; + this.transferKey.value = transferData.key; + }; + // view + this.showDirectionSelectionModal = () => { + this.presentedModal.value = 0 /* DirectionSelection */; + this.getOptions(); + }; + this.showFileSelectionModal = () => { + this.presentedModal.value = 1 /* FileSelection */; + }; + this.showTransferDataModal = () => { + this.presentedModal.value = 2 /* TransferDataDisplay */; + this.getTransferData(); + }; + this.initiateTransfer = () => { + this.presentedModal.value = 3 /* TransferDisplay */; + this.didNotFinishSending.value = true; + this.filePathsSent.clear(); + this.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 = () => { + this.presentedModal.value = 5 /* ReceptionDisplay */; + this.filePathsReceived.clear(); + const transferData = { + channel: this.receivingTransferChannel.value, + key: this.receivingTransferKey.value, + }; + this.fileTransferModel.prepareToReceive(transferData); + }; + this.hideModal = () => { + this.presentedModal.value = void 0; + }; + this.fileTransferModel = fileTransferModel2; + this.chatListModel = chatListModel2; + this.fileTransferModel.fileHandlerManager.addHandler( + this.handleReceivedFile, + ); + } + }; + + // src/View/Components/textSpan.tsx + var StringToTextSpan = (string) => { + return /* @__PURE__ */ createElement( + "span", + { class: "ellipsis" }, + string, + ); + }; + + // src/View/Modals/dataTransferModal.tsx + function DataTransferModalWrapper( + connectionViewModel2, + fileTransferViewModel2, + ) { + return /* @__PURE__ */ createElement( + "div", + null, + DirectionSelectionModal( + connectionViewModel2, + fileTransferViewModel2, + ), + FileSelectionModal(fileTransferViewModel2), + TransferDataDisplayModal(fileTransferViewModel2), + TransferDisplayModal(fileTransferViewModel2), + TransferDataInputModal(fileTransferViewModel2), + DataReceptionModal(fileTransferViewModel2), + ); + } + function DirectionSelectionModal( + connectionViewModel2, + fileTransferViewModel2, + ) { + const isPresented = createProxyState( + [fileTransferViewModel2.presentedModal], + () => + fileTransferViewModel2.presentedModal.value == + 0 /* DirectionSelection */, + ); + const isDisconnected = createProxyState( + [connectionViewModel2.isConnected], + () => connectionViewModel2.isConnected.value == false, + ); + return /* @__PURE__ */ createElement( + "div", + { class: "modal", "toggle:open": isPresented }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "main", + null, + /* @__PURE__ */ createElement( + "h2", + null, + translations.dataTransferModal.transferDataHeadline, + ), + /* @__PURE__ */ createElement( + "p", + { + class: "error", + "toggle:hidden": connectionViewModel2.isConnected, + }, + translations.dataTransferModal.notConnectedError, + ), + /* @__PURE__ */ createElement( + "div", + { + class: "flex-column gap content-margin-bottom", + "toggle:hidden": isDisconnected, + }, + /* @__PURE__ */ createElement( + "button", + { + class: "tile", + "on:click": + fileTransferViewModel2.showFileSelectionModal, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "upload", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "b", + null, + translations.dataTransferModal + .fromThisDeviceButton, + ), + ), + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "arrow_forward", + ), + ), + /* @__PURE__ */ createElement( + "button", + { + class: "tile", + "on:click": + fileTransferViewModel2.showTransferDataInputModal, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "download", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "b", + null, + translations.dataTransferModal + .toThisDeviceButton, + ), + ), + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "arrow_forward", + ), + ), + ), + ), + /* @__PURE__ */ createElement( + "button", + { "on:click": fileTransferViewModel2.hideModal }, + translations.general.closeButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "close", + ), + ), + ), + ); + } + function FileSelectionModal(fileTransferViewModel2) { + const OptionConverter = (fileOption) => { + return OptionEntry(fileOption, fileTransferViewModel2); + }; + function OptionEntry(fileOption, fileTransferViewModel3) { + const isSelected = new State(false); + if ( + fileTransferViewModel3.selectedPaths.value.has(fileOption.path) + ) { + isSelected.value = true; + } + isSelected.subscribeSilent((isSelected2) => { + if (isSelected2 == true) { + fileTransferViewModel3.selectedPaths.add(fileOption.path); + } else { + fileTransferViewModel3.selectedPaths.remove( + fileOption.path, + ); + } + }); + function toggle() { + isSelected.value = !isSelected.value; + } + return /* @__PURE__ */ createElement( + "button", + { + class: "tile", + "toggle:selected": isSelected, + "on:click": toggle, + }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "b", + { class: "ellipsis" }, + fileOption.label, + ), + /* @__PURE__ */ createElement( + "span", + { class: "secondary ellipsis" }, + StorageModel.pathComponentsToString(...fileOption.path), + ), + ), + ); + } + const isPresented = createProxyState( + [fileTransferViewModel2.presentedModal], + () => + fileTransferViewModel2.presentedModal.value == + 1 /* FileSelection */, + ); + return /* @__PURE__ */ createElement( + "div", + { class: "modal", "toggle:open": isPresented }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "main", + null, + /* @__PURE__ */ createElement( + "h2", + null, + translations.dataTransferModal.transferDataHeadline, + ), + /* @__PURE__ */ createElement( + "span", + { class: "secondary" }, + translations.dataTransferModal.selectionDescription, + ), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement( + "h3", + null, + translations.dataTransferModal.generalHeadline, + ), + /* @__PURE__ */ createElement("div", { + class: "flex-column gap content-margin-bottom", + "children:append": [ + fileTransferViewModel2.generalFileOptions, + OptionConverter, + ], + }), + /* @__PURE__ */ createElement( + "h3", + null, + translations.dataTransferModal.chatsHeadline, + ), + /* @__PURE__ */ createElement("div", { + class: "flex-column gap", + "children:append": [ + fileTransferViewModel2.chatFileOptions, + OptionConverter, + ], + }), + ), + /* @__PURE__ */ createElement( + "div", + { class: "flex-row width-100" }, + /* @__PURE__ */ createElement( + "button", + { + class: "flex", + "on:click": + fileTransferViewModel2.showDirectionSelectionModal, + }, + translations.general.backButton, + ), + /* @__PURE__ */ createElement( + "button", + { + class: "primary flex", + "on:click": + fileTransferViewModel2.showTransferDataModal, + "toggle:disabled": + fileTransferViewModel2.hasNoPathsSelected, + }, + translations.general.continueButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "arrow_forward", + ), + ), + ), + ), + ); + } + function TransferDataDisplayModal(fileTransferViewModel2) { + const isPresented = createProxyState( + [fileTransferViewModel2.presentedModal], + () => + fileTransferViewModel2.presentedModal.value == + 2 /* TransferDataDisplay */, + ); + return /* @__PURE__ */ createElement( + "div", + { class: "modal", "toggle:open": isPresented }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "main", + null, + /* @__PURE__ */ createElement( + "h2", + null, + translations.dataTransferModal.transferDataHeadline, + ), + /* @__PURE__ */ createElement( + "span", + { class: "secondary" }, + translations.dataTransferModal.dataEntryDescription, + ), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement( + "div", + { class: "flex-column gap content-margin-bottom" }, + /* @__PURE__ */ createElement( + "div", + { class: "tile" }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "forum", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + { class: "secondary" }, + translations.dataTransferModal + .transferChannelHeadline, + ), + /* @__PURE__ */ createElement("b", { + "subscribe:innerText": + fileTransferViewModel2.transferChannel, + }), + ), + ), + /* @__PURE__ */ createElement( + "div", + { class: "tile" }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "key", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + { class: "secondary" }, + translations.dataTransferModal + .transferKeyHeadline, + ), + /* @__PURE__ */ createElement("b", { + "subscribe:innerText": + fileTransferViewModel2.transferKey, + }), + ), + ), + ), + ), + /* @__PURE__ */ createElement( + "div", + { class: "flex-row width-100" }, + /* @__PURE__ */ createElement( + "button", + { + class: "flex", + "on:click": + fileTransferViewModel2.showFileSelectionModal, + }, + translations.general.backButton, + ), + /* @__PURE__ */ createElement( + "button", + { + class: "primary flex", + "on:click": fileTransferViewModel2.initiateTransfer, + }, + translations.dataTransferModal.sendButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "arrow_forward", + ), + ), + ), + ), + ); + } + function TransferDisplayModal(fileTransferViewModel2) { + const isPresented = createProxyState( + [fileTransferViewModel2.presentedModal], + () => + fileTransferViewModel2.presentedModal.value == + 3 /* TransferDisplay */, + ); + return /* @__PURE__ */ createElement( + "div", + { class: "modal", "toggle:open": isPresented }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "main", + null, + /* @__PURE__ */ createElement( + "h2", + null, + translations.dataTransferModal.transferDataHeadline, + ), + /* @__PURE__ */ createElement("p", { + class: "secondary", + "subscribe:innerText": + fileTransferViewModel2.filesSentText, + }), + /* @__PURE__ */ createElement( + "p", + { + class: "secondary", + "toggle:hidden": + fileTransferViewModel2.didNotFinishSending, + }, + translations.dataTransferModal.allFilesSent, + ), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement("div", { + class: "tile flex-column align-start", + "children:append": [ + fileTransferViewModel2.filePathsSent, + StringToTextSpan, + ], + }), + ), + /* @__PURE__ */ createElement( + "div", + { class: "flex-row width-100" }, + /* @__PURE__ */ createElement( + "button", + { + class: "flex", + "on:click": fileTransferViewModel2.initiateTransfer, + }, + translations.dataTransferModal.sendAgainButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "restart_alt", + ), + ), + /* @__PURE__ */ createElement( + "button", + { + class: "flex", + "on:click": fileTransferViewModel2.hideModal, + "toggle:disabled": + fileTransferViewModel2.didNotFinishSending, + }, + translations.general.closeButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "close", + ), + ), + ), + ), + ); + } + function TransferDataInputModal(fileTransferViewModel2) { + const isPresented = createProxyState( + [fileTransferViewModel2.presentedModal], + () => + fileTransferViewModel2.presentedModal.value == + 4 /* TransferDataInput */, + ); + return /* @__PURE__ */ createElement( + "div", + { class: "modal", "toggle:open": isPresented }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "main", + null, + /* @__PURE__ */ createElement( + "h2", + null, + translations.dataTransferModal.transferDataHeadline, + ), + /* @__PURE__ */ createElement( + "span", + { class: "secondary" }, + translations.dataTransferModal + .dataEntryInputDescription, + ), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement( + "div", + { class: "flex-column gap content-margin-bottom" }, + /* @__PURE__ */ createElement( + "label", + { class: "tile" }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "forum", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + { class: "secondary" }, + translations.dataTransferModal + .transferChannelHeadline, + ), + /* @__PURE__ */ createElement("input", { + "bind:value": + fileTransferViewModel2.receivingTransferChannel, + }), + ), + ), + /* @__PURE__ */ createElement( + "label", + { class: "tile" }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "key", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + { class: "secondary" }, + translations.dataTransferModal + .transferKeyHeadline, + ), + /* @__PURE__ */ createElement("input", { + "bind:value": + fileTransferViewModel2.receivingTransferKey, + }), + ), + ), + ), + ), + /* @__PURE__ */ createElement( + "div", + { class: "flex-row width-100" }, + /* @__PURE__ */ createElement( + "button", + { + class: "flex", + "on:click": + fileTransferViewModel2.showDirectionSelectionModal, + }, + translations.general.backButton, + ), + /* @__PURE__ */ createElement( + "button", + { + class: "primary flex", + "on:click": + fileTransferViewModel2.prepareReceivingData, + "toggle:disabled": + fileTransferViewModel2.cannotPrepareToReceive, + }, + translations.general.continueButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "arrow_forward", + ), + ), + ), + ), + ); + } + function DataReceptionModal(fileTransferViewModel2) { + const isPresented = createProxyState( + [fileTransferViewModel2.presentedModal], + () => + fileTransferViewModel2.presentedModal.value == + 5 /* ReceptionDisplay */, + ); + return /* @__PURE__ */ createElement( + "div", + { class: "modal", "toggle:open": isPresented }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "main", + null, + /* @__PURE__ */ createElement( + "h2", + null, + translations.dataTransferModal.transferDataHeadline, + ), + /* @__PURE__ */ createElement( + "p", + { class: "secondary" }, + translations.dataTransferModal + .readyToReceiveDescription, + ), + /* @__PURE__ */ createElement("p", { + class: "secondary", + "subscribe:innerText": + fileTransferViewModel2.filesReceivedText, + }), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement("div", { + class: "tile flex-column align-start", + "children:append": [ + fileTransferViewModel2.filePathsReceived, + StringToTextSpan, + ], + }), + ), + /* @__PURE__ */ createElement( + "button", + { "on:click": reload }, + translations.general.reloadAppButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "refresh", + ), + ), + ), + ); + } + + // src/Model/Global/fileTransferModel.ts + var FileTransferModel = class { + // init + constructor(storageModel2, connectionModel2) { + this.fileHandlerManager = new HandlerManager(); + // general + this.generateTransferData = () => { + const transferData = { + channel: generateRandomToken(2).substring(0, 4), + key: generateRandomToken(3).substring(0, 6), + }; + this.transferData = transferData; + return transferData; + }; + this.prepareToReceive = (transferData) => { + this.connectionModel.addChannel(transferData.channel); + this.transferData = transferData; + }; + // handlers + this.handleMessage = (data) => { + if (this.transferData == void 0) return; + if (data.messageBody == void 0) return; + if (data.messageChannel != this.transferData.channel) return; + this.handleFile(data.messageBody); + }; + this.handleFile = async (encryptedFileData) => { + if (this.transferData == void 0) return; + const decrypted = await decryptString( + encryptedFileData, + this.transferData.key, + ); + console.log(decrypted); + 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.addHandler( + this.handleMessage, + ); + } + }; + var FileDataReference = { + path: [""], + body: "", + }; + + // src/View/Components/chatEntry.tsx + function ChatEntry(chatViewModel) { + const view = /* @__PURE__ */ createElement( + "button", + { + class: "tile colored-tile chat-entry", + "set:color": chatViewModel.settingsPageViewModel.color, + style: "height: 8rem", + "on:click": chatViewModel.open, + "toggle:unread": 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/homePage.tsx + function HomePage( + storageViewModel2, + settingsViewModel2, + connectionViewModel2, + fileTransferViewModel2, + chatListViewModel2, + ) { + const overviewSection = /* @__PURE__ */ createElement( + "div", + { id: "overview-section" }, + /* @__PURE__ */ createElement( + "h2", + null, + 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, + translations.homePage.serverAddress, + ), + /* @__PURE__ */ createElement("input", { + list: "previous-connection-list", + placeholder: + 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": + 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": + 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": 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, + translations.homePage.yourNameLabel, + ), + /* @__PURE__ */ createElement("input", { + placeholder: 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": + translations.homePage.setNameButtonAudioLabel, + }, + translations.general.setButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "check", + ), + ), + ), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement( + "label", + { class: "tile flex-no" }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "calendar_month", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + null, + translations.homePage.firstDayOfWeekLabel, + ), + /* @__PURE__ */ createElement( + "select", + { + "bind:value": + settingsViewModel2.firstDayOfWeekInput, + }, + ...translations.regional.weekdays.full.map( + (weekdayName, i) => + Option( + weekdayName, + i.toString(), + i.toString() == + settingsViewModel2.firstDayOfWeekInput + .value, + ), + ), + ), + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "arrow_drop_down", + ), + ), + ), + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement( + "button", + { + class: "tile flex-no", + "on:click": + fileTransferViewModel2.showDirectionSelectionModal, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "sync_alt", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + null, + translations.homePage.transferDataButton, + ), + ), + ), + /* @__PURE__ */ createElement( + "button", + { + class: "tile flex-no", + "on:click": storageViewModel2.showStorageModal, + }, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "hard_drive_2", + ), + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + null, + translations.homePage.manageStorageButton, + ), + ), + ), + /* @__PURE__ */ createElement( + "div", + { class: "mobile-only" }, + /* @__PURE__ */ createElement("hr", null), + /* @__PURE__ */ createElement( + "div", + { class: "flex-row justify-end" }, + /* @__PURE__ */ createElement( + "button", + { class: "ghost width-50", "on:click": scrollToChat }, + translations.homePage.scrollToChatButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "arrow_forward", + ), + ), + ), + ), + ); + const chatSection = /* @__PURE__ */ createElement( + "div", + { id: "chat-section" }, + /* @__PURE__ */ createElement( + "h2", + null, + translations.homePage.chatsHeadline, + ), + /* @__PURE__ */ createElement( + "div", + { class: "flex-row width-input" }, + /* @__PURE__ */ createElement("input", { + placeholder: translations.homePage.addChatPlaceholder, + "aria-label": translations.homePage.addChatAudioLabel, + "bind:value": chatListViewModel2.newChatPrimaryChannel, + "on:enter": chatListViewModel2.createChat, + }), + /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "aria-label": 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 { + // storage + setName(newValue) { + this.username = newValue; + const path = StorageModel.getPath( + "settings" /* SettingsModel */, + filePaths.settingsModel.username, + ); + this.storageModel.write(path, newValue); + } + setFirstDayOfWeek(newValue) { + this.firstDayOfWeek = newValue; + const path = StorageModel.getPath( + "settings" /* SettingsModel */, + filePaths.settingsModel.firstDayOfWeek, + ); + this.storageModel.write(path, newValue); + } + // load + loadUsernam() { + const path = StorageModel.getPath( + "settings" /* SettingsModel */, + filePaths.settingsModel.username, + ); + const content = this.storageModel.read(path); + this.username = content ?? ""; + } + loadFirstDayofWeek() { + const path = StorageModel.getPath( + "settings" /* SettingsModel */, + filePaths.settingsModel.firstDayOfWeek, + ); + const content = this.storageModel.read(path); + this.firstDayOfWeek = content ?? "0"; + } + // init + constructor(storageModel2) { + this.storageModel = storageModel2; + this.loadUsernam(); + this.loadFirstDayofWeek(); + } + }; + + // src/ViewModel/Global/settingsViewModel.ts + var SettingsViewModel = class { + // init + constructor(coreViewModel, settingsModel2) { + this.coreViewModel = coreViewModel; + // state + this.username = new State(""); + this.usernameInput = new State(""); + this.firstDayOfWeekInput = new State("0"); + // guards + this.cannotSetName = createProxyState( + [this.usernameInput], + () => + this.usernameInput.value == "" || + this.usernameInput.value == this.settingsModel.username, + ); + // methods + this.setName = () => { + this.settingsModel.setName(this.usernameInput.value); + this.username.value = this.settingsModel.username; + this.usernameInput.callSubscriptions(); + }; + this.setFirstDayofWeek = () => { + this.settingsModel.setFirstDayOfWeek( + this.firstDayOfWeekInput.value, + ); + }; + this.settingsModel = settingsModel2; + this.username.value = settingsModel2.username; + this.usernameInput.value = settingsModel2.username; + this.firstDayOfWeekInput.value = settingsModel2.firstDayOfWeek; + this.firstDayOfWeekInput.subscribe(this.setFirstDayofWeek); + } + }; + + // 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.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/Components/fileBrowser.tsx + function FileBrowser(storageViewModel2) { + const detailView = createProxyState( + [storageViewModel2.selectedPath], + () => { + if ( + storageViewModel2.selectedPath.value == + PATH_COMPONENT_SEPARATOR + ) + return /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "span", + { class: "secondary" }, + translations.storage.noItemSelected, + ), + /* @__PURE__ */ createElement("hr", null), + DangerousActionButton( + 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, + 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, + translations.storage.content, + ), + /* @__PURE__ */ createElement("code", { + "subscribe:innerText": + storageViewModel2.selectedFileContent, + }), + ), + ), + DangerousActionButton( + translations.storage.deleteItem, + "delete_forever", + storageViewModel2.deleteSelectedItem, + ), + ); + }, + ); + const view = /* @__PURE__ */ createElement( + "div", + { class: "file-browser" }, + /* @__PURE__ */ createElement( + "div", + null, + /* @__PURE__ */ createElement( + "div", + { class: "scroll-area" }, + DirectoryItemList(storageViewModel2), + ), + /* @__PURE__ */ createElement( + "div", + { class: "detail-button-wrapper" }, + /* @__PURE__ */ createElement( + "button", + { class: "ghost", "on:click": scrollToDetails }, + /* @__PURE__ */ createElement("span", { + class: "ellipsis", + "subscribe:innerText": + storageViewModel2.selectedFileName, + }), + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "arrow_forward", + ), + ), + ), + ), + /* @__PURE__ */ createElement("div", { + class: "scroll-area", + "children:set": detailView, + }), + ); + function scrollToDetails() { + view.scrollLeft = view.scrollWidth; + } + return view; + } + + // src/View/Modals/storageModal.tsx + function StorageModal(storageViewModel2) { + return /* @__PURE__ */ createElement( + "div", + { + class: "modal", + "toggle:open": storageViewModel2.isShowingStorageModal, + }, + /* @__PURE__ */ createElement( + "div", + { style: "max-width: 64rem" }, + /* @__PURE__ */ createElement( + "main", + { class: "padding-0" }, + FileBrowser(storageViewModel2), + ), + /* @__PURE__ */ createElement( + "button", + { "on:click": storageViewModel2.hideStorageModal }, + translations.general.closeButton, + /* @__PURE__ */ createElement( + "span", + { class: "icon" }, + "close", + ), + ), + ), + ); + } + + // src/ViewModel/Global/storageViewModel.ts + var StorageViewModel = class { + // init + constructor(coreViewModel, storageModel2) { + this.coreViewModel = coreViewModel; + // 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.storageModel.read(path); + return ( + (content ?? translations.storage.notAFile) || + translations.storage.contentEmpty + ); + }; + this.deleteSelectedItem = () => { + const path = StorageModel.stringToPathComponents( + this.selectedPath.value, + ); + this.lastDeletedItemPath.value = this.selectedPath.value; + this.storageModel.removeRecursively(path); + this.didMakeChanges.value = true; + }; + this.removeJunk = () => { + this.storageModel.removeJunk(); + this.selectedPath.value = PATH_COMPONENT_SEPARATOR; + }; + // view + this.showStorageModal = () => { + this.isShowingStorageModal.value = true; + }; + this.hideStorageModal = () => { + if (this.didMakeChanges.value == true) { + window.location.reload(); + return; + } + this.isShowingStorageModal.value = false; + }; + this.storageModel = storageModel2; + this.selectedFileName = createProxyState([this.selectedPath], () => + StorageModel.getFileNameFromString(this.selectedPath.value), + ); + this.selectedFileContent = createProxyState( + [this.selectedPath], + () => this.getSelectedItemContent(), + ); + } + }; + + // src/Upgrader/v1.ts + var v1Upgrader = class { + // init + constructor(settingsModel2, connectionModel2, chatListModel2) { + this.settingsModel = settingsModel2; + this.connectionModel = connectionModel2; + this.chatListModel = chatListModel2; + // general + this.migrateSettings = () => { + const name = getLocalStorageItemAndClear("sender-name"); + if (name != null) { + const parsedName = parseOrFallback(name); + this.settingsModel.setName(parsedName); + } + const firstDayOfWeek = + getLocalStorageItemAndClear("first-day-of-week"); + if (firstDayOfWeek != null) { + const parsedFirstDayOfWeek = + parseOrFallback(firstDayOfWeek); + this.settingsModel.setFirstDayOfWeek(parsedFirstDayOfWeek); + } + }; + this.migrateConnections = () => { + const previousAddressString = + getLocalStorageItemAndClear("previous-addresses"); + if (previousAddressString == null) return; + const previousAddresses = parseArray(previousAddressString); + for (const address of previousAddresses) { + if (typeof address != "string") continue; + this.connectionModel.storeAddress(address); + } + }; + // chats + this.migrateChats = () => { + const chatIdString = getLocalStorageItemAndClear("chat-ids"); + if (chatIdString == null) return; + const chatIds = parseArray(chatIdString); + for (const chatId of chatIds) { + if (typeof chatId != "string") continue; + this.migrateChatById(chatId); + } + }; + this.migrateChatById = (id) => { + const primaryChannel = getLocalStorageItemAndClear( + storageKeys.primaryChannel(id), + ); + if (primaryChannel == null) return; + const parsedPriamryChannel = parseOrFallback(primaryChannel); + const chatModel = + this.chatListModel.createChat(parsedPriamryChannel); + const secondaryChannelString = getLocalStorageItemAndClear( + storageKeys.secondaryChannels(id), + ); + if (secondaryChannelString != null) { + const potentialSecondaryChannels = parseArray( + secondaryChannelString, + ); + const confirmedSecondaryChannels = []; + for (const secondaryChannel of potentialSecondaryChannels) { + if (typeof secondaryChannel != "string") continue; + confirmedSecondaryChannels.push(secondaryChannel); + } + chatModel.setSecondaryChannels(confirmedSecondaryChannels); + } + const encryptionKey = getLocalStorageItemAndClear( + storageKeys.encyptionKey(id), + ); + if (encryptionKey != null) { + const parsedEncryptionKey = parseOrFallback(encryptionKey); + chatModel.setEncryptionKey(parsedEncryptionKey); + } + const messagesString = + getLocalStorageItemAndClear(storageKeys.messages(id)) ?? ""; + const messageOutboxString = + getLocalStorageItemAndClear(storageKeys.outbox(id)) ?? ""; + const potentialMessages = parseArray(messagesString); + const potentialMessagesInOutbox = + parseArray(messageOutboxString); + const addMessages = (potentialMessages2, status) => { + for (const potentialMessage of potentialMessages2) { + const isV1ChatMessage = checkMatchesObjectStructure( + potentialMessage, + V1ChatMessageReference, + ); + if (isV1ChatMessage == false) continue; + const v1ChatMessage = potentialMessage; + const convertedChatMessage = { + dataVersion: "v2", + id: v4_default(), + channel: v1ChatMessage.channel, + sender: v1ChatMessage.sender, + body: v1ChatMessage.body, + dateSent: v1ChatMessage.isoDate, + status, + stringifiedFile: "", + }; + chatModel.addMessage(convertedChatMessage); + if (status == "outbox" /* Outbox */) { + this.connectionModel.sendMessageOrStore( + convertedChatMessage, + ); + } + } + }; + addMessages(potentialMessages, "received" /* Received */); + addMessages(potentialMessagesInOutbox, "outbox" /* Outbox */); + const objectsString = + getLocalStorageItemAndClear(storageKeys.objects(id)) ?? ""; + const objectOutboxString = + getLocalStorageItemAndClear(storageKeys.itemOutbox(id)) ?? + ""; + const potentialObjects = parseArray(objectsString); + const potentialObjectsInOutbox = parseArray(objectOutboxString); + const addObjects = (potentialObjects2) => { + const board = + chatModel.fileModel.boardsAndTasksModel.createBoard( + translations.updater.migrated, + ); + chatModel.fileModel.boardsAndTasksModel.updateBoard(board); + for (const potentialObjectEntry of potentialObjects2) { + const potentialObject = potentialObjectEntry[1]; + const isV1MessageObject = + checkIsV1MessageObject(potentialObject); + if (isV1MessageObject == false) continue; + const objectId = potentialObject.id; + const objectName = potentialObject.title; + const contentVersions = Object.values( + potentialObject.contentVersions, + ); + for (const potentialVersion of contentVersions) { + const isV1MessageObjectContent = + checkIsV1MessageObjectContent(potentialVersion); + if (isV1MessageObjectContent == false) continue; + const convertedTaskFileContent = { + dataVersion: "v2", + fileId: objectId, + fileContentId: FileModel2.generateFileContentId( + potentialVersion.isoDateVersionCreated, + ), + creationDate: + potentialVersion.isoDateVersionCreated, + type: "task", + name: objectName ?? "", + boardId: board.fileId ?? "", + description: potentialVersion.noteContent ?? "", + category: potentialVersion.categoryName ?? "", + status: potentialVersion.status ?? "", + priority: potentialVersion.priority ?? "", + date: potentialVersion.date ?? "", + time: potentialVersion.time ?? "", + }; + console.log(convertedTaskFileContent); + chatModel.fileModel.handleFileContent( + convertedTaskFileContent, + ); + } + } + }; + addObjects([...potentialObjects, ...potentialObjectsInOutbox]); + }; + this.migrateSettings(); + this.migrateConnections(); + this.migrateChats(); + } + }; + var storageKeys = { + viewType(id) { + return id + "view-type"; + }, + hasUnread(id) { + return id + "has-unread-messages"; + }, + primaryChannel(id) { + return id + "primary-channel"; + }, + secondaryChannels(id) { + return id + "secondary-channels"; + }, + encyptionKey(id) { + return id + "encryption-key"; + }, + messages(id) { + return id + "messages"; + }, + objects(id) { + return id + "items"; + }, + outbox(id) { + return id + "outbox"; + }, + itemOutbox(id) { + return id + "item-outbox"; + }, + composingMessage(id) { + return id + "composing-message"; + }, + }; + var V1ChatMessageReference = { + channel: "", + sender: "", + body: "", + isoDate: "", + }; + function checkIsV1MessageObject(object) { + if (object.id == void 0) return false; + if (object.title == void 0) return false; + if (object.contentVersions == void 0) return false; + return true; + } + function checkIsV1MessageObjectContent(object) { + if (object.id == void 0) return false; + if (object.isoDateVersionCreated == void 0) return false; + return true; + } + + // src/index.tsx + var storageModel = new StorageModel(); + var settingsModel = new SettingsModel(storageModel); + var connectionModel = new ConnectionModel(storageModel); + var chatListModel = new ChatListModel( + storageModel, + settingsModel, + connectionModel, ); - }); - document.body.append( - /* @__PURE__ */ createElement("div", { id: "background-wrapper" }, /* @__PURE__ */ createElement("div", { id: "sky" }), /* @__PURE__ */ createElement("div", { id: "grass-1" }), /* @__PURE__ */ createElement("div", { id: "grass-2" })) - ); - document.querySelector("main").append( - HomePage( - storageViewModel, - settingsViewModel, - connectionViewModel, - fileTransferViewModel, - chatListViewModel - ), - ChatPageWrapper(chatListViewModel), - ConnectionModal(connectionViewModel), - DataTransferModalWrapper(connectionViewModel, fileTransferViewModel), - StorageModal(storageViewModel) - ); + var fileTransferModel = new FileTransferModel( + storageModel, + connectionModel, + ); + new v1Upgrader(settingsModel, connectionModel, chatListModel); + var coreVieWModel = new CoreViewModel(); + var storageViewModel = new StorageViewModel(coreVieWModel, storageModel); + var settingsViewModel = new SettingsViewModel(coreVieWModel, settingsModel); + var connectionViewModel = new ConnectionViewModel( + coreVieWModel, + connectionModel, + ); + var chatListViewModel = new ChatListViewModel( + coreVieWModel, + storageModel, + chatListModel, + settingsViewModel, + connectionViewModel, + ); + var fileTransferViewModel = new FileTransferViewModel( + fileTransferModel, + chatListModel, + ); + chatListViewModel.selectedChat.subscribe(() => { + document.body.toggleAttribute( + "showing-chat", + chatListViewModel.selectedChat.value != void 0, + ); + }); + document.body.append( + /* @__PURE__ */ createElement( + "div", + { id: "background-wrapper" }, + /* @__PURE__ */ createElement("div", { id: "sky" }), + /* @__PURE__ */ createElement("div", { id: "grass-1" }), + /* @__PURE__ */ createElement("div", { id: "grass-2" }), + ), + ); + document + .querySelector("main") + .append( + HomePage( + storageViewModel, + settingsViewModel, + connectionViewModel, + fileTransferViewModel, + chatListViewModel, + ), + ChatPageWrapper(chatListViewModel), + ConnectionModal(connectionViewModel), + DataTransferModalWrapper( + connectionViewModel, + fileTransferViewModel, + ), + StorageModal(storageViewModel), + ); })(); diff --git a/dist/manifest.json b/dist/manifest.json index 6bfe9035..a7ad1283 100644 --- a/dist/manifest.json +++ b/dist/manifest.json @@ -1,10 +1,10 @@ { - "name": "Comms", - "display": "fullscreen", - "icons": [ - { - "src": "icon.png", - "sizes": "any" - } - ] + "name": "Comms", + "display": "fullscreen", + "icons": [ + { + "src": "icon.png", + "sizes": "any" + } + ] } diff --git a/dist/styles.css b/dist/styles.css index 14d1d412..9b64c708 100644 --- a/dist/styles.css +++ b/dist/styles.css @@ -2,34 +2,35 @@ BASIC */ * { - box-sizing: border-box; - text-align: left; - border: none; - outline: none; - margin: 0px; - padding: 0px; + box-sizing: border-box; + text-align: left; + border: none; + outline: none; + margin: 0px; + padding: 0px; } html { - -webkit-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; } /* ANIMATION */ @keyframes slide-up { - 0% { - opacity: 0; - transform: translateY(2rem); - } + 0% { + opacity: 0; + transform: translateY(2rem); + } } :root { - --animation-duration: 0.5s; + --animation-duration: 0.4s; } +.modal > div, .slide-up { - animation: slide-up var(--animation-duration); + animation: slide-up var(--animation-duration); } /* @@ -37,128 +38,128 @@ COLORS */ /* definition */ :root { - --background: hsla(0, 0%, 100%, 100%); - --background-glass: hsla(0, 0%, 100%, 5%); - --background-transparent: hsla(0, 0%, 0%, 5%); - --background-hover: hsla(0, 0%, 55%, 12%); - --background-active: hsla(0, 0%, 55%, 12%); + --background: hsla(0, 0%, 100%, 100%); + --background-glass: hsla(0, 0%, 100%, 5%); + --background-transparent: hsla(0, 0%, 0%, 5%); + --background-hover: hsla(0, 0%, 55%, 12%); + --background-active: hsla(0, 0%, 55%, 12%); - --layer: hsla(0, 0%, 96%, 60%); + --layer: hsla(0, 0%, 96%, 60%); - --surface: hsla(0, 0%, 0%, 5%); - --surface-hover: hsla(0, 0%, 5%, 10%); - --surface-active: hsla(0, 0%, 5%, 15%); + --surface: hsla(0, 0%, 0%, 5%); + --surface-hover: hsla(0, 0%, 5%, 10%); + --surface-active: hsla(0, 0%, 5%, 15%); - --border: hsla(0, 0%, 0%, 10%); - --surface-border: #8d8d8d; + --border: hsla(0, 0%, 0%, 10%); + --surface-border: #8d8d8d; - --text-normal: #161616; - --text-placeholder: hsla(0, 0%, 0%, 30%); - --text-on-color: #ffffff; + --text-normal: #161616; + --text-placeholder: hsla(0, 0%, 0%, 30%); + --text-on-color: #ffffff; - --text-success: #24a148; - --text-warning: #f1c21b; - --text-error: #da1e28; - --text-info: #0043ce; + --text-success: #24a148; + --text-warning: #f1c21b; + --text-error: #da1e28; + --text-info: #0043ce; - --link: #0f62fe; - --link-hover: #0043ce; + --link: #0f62fe; + --link-hover: #0043ce; - --primary: #0f62fe; - --primary-hover: #0353e9; - --primary-active: #002d9c; + --primary: #0f62fe; + --primary-hover: #0353e9; + --primary-active: #002d9c; - --danger: #da1e28; - --danger-hover: #ba1b23; - --danger-active: #750e13; + --danger: #da1e28; + --danger-hover: #ba1b23; + --danger-active: #750e13; } @media (prefers-color-scheme: dark) { - :root { - --background: hsla(0, 0%, 9%, 100%); - --background-glass: hsla(0, 0%, 9%, 20%); - --background-transparent: hsla(0, 0%, 100%, 5%); - --background-hover: hsla(0, 0%, 55%, 16%); - --background-active: hsla(0, 0%, 55%, 40%); - - --layer: hsla(0, 0%, 15%, 60%); - - --surface: hsla(0, 0%, 100%, 4%); - --surface-hover: hsla(0, 0%, 100%, 10%); - --surface-active: hsla(0, 0%, 100%, 15%); - - --border: hsla(0, 0%, 100%, 10%); - --surface-border: #6f6f6f; - - --text-normal: #f4f4f4; - --text-placeholder: hsla(0, 0%, 100%, 30%); - - --text-success: #42be65; - --text-warning: #f1c21b; - --text-error: #fa4d56; - --text-info: #4589ff; - - --link: #78a9ff; - --link-hover: #a6c8ff; - } - - @media (display-mode: standalone) { :root { - --background: hsla(0, 0%, 0%, 100%); - --background-transparent: hsla(0, 0%, 0%, 60%); - --layer: hsla(0, 0%, 0%, 60%); + --background: hsla(0, 0%, 9%, 100%); + --background-glass: hsla(0, 0%, 9%, 20%); + --background-transparent: hsla(0, 0%, 100%, 5%); + --background-hover: hsla(0, 0%, 55%, 16%); + --background-active: hsla(0, 0%, 55%, 40%); + + --layer: hsla(0, 0%, 15%, 60%); + + --surface: hsla(0, 0%, 100%, 4%); + --surface-hover: hsla(0, 0%, 100%, 10%); + --surface-active: hsla(0, 0%, 100%, 15%); + + --border: hsla(0, 0%, 100%, 10%); + --surface-border: #6f6f6f; + + --text-normal: #f4f4f4; + --text-placeholder: hsla(0, 0%, 100%, 30%); + + --text-success: #42be65; + --text-warning: #f1c21b; + --text-error: #fa4d56; + --text-info: #4589ff; + + --link: #78a9ff; + --link-hover: #a6c8ff; + } + + @media (display-mode: standalone) { + :root { + --background: hsla(0, 0%, 0%, 100%); + --background-transparent: hsla(0, 0%, 0%, 60%); + --layer: hsla(0, 0%, 0%, 60%); + } } - } } /* basic */ html { - background: var(--background); - color: var(--text-normal); + background: var(--background); + color: var(--text-normal); } .tile { - color: var(--text-normal); + color: var(--text-normal); } ::placeholder { - color: var(--text-placeholder); + color: var(--text-placeholder); } .modal > div { - background: var(--background-glass); + background: var(--background-glass); } menu, header { - background: var(--background-transparent); + background: var(--background-transparent); } * { - color: inherit; - accent-color: var(--primary); + color: inherit; + accent-color: var(--primary); } a { - color: var(--link); + color: var(--link); } /* text */ .success { - color: var(--text-success); + color: var(--text-success); } .warning { - color: var(--text-warning); + color: var(--text-warning); } .error { - color: var(--text-error); + color: var(--text-error); } .info { - color: var(--text-info); + color: var(--text-info); } /* controls */ @@ -169,129 +170,143 @@ textarea:not(.tile *), select:not(.tile *), .surface, .tile { - background: var(--surface); -} -[selected] { - background: var(--background); -} -@media (hover: hover) { - button:hover, - select:hover, - button.tile.ghost:hover { - background: var(--surface-hover); - } - [selected]:hover { background: var(--surface); - } } + +[selected] { + background: var(--background); +} + +@media (hover: hover) { + button:hover, + select:hover, + button.tile.ghost:hover { + background: var(--surface-hover); + } + + [selected]:hover { + background: var(--surface); + } +} + button:active, select:active, button.tile.ghost:active { - background: var(--surface-active); + background: var(--surface-active); } + [selected]:active { - background: var(--surface-hover); + background: var(--surface-hover); } + .tile *, header button { - background: transparent; + background: transparent; } input:checked { - background: var(--primary); + background: var(--primary); } /* primary button */ button.primary { - background: var(--primary); - color: var(--text-on-color); + background: var(--primary); + color: var(--text-on-color); } + @media (hover: hover) { - button.primary:hover { - background: var(--primary-hover); - } + button.primary:hover { + background: var(--primary-hover); + } } + button.primary:active { - background: var(--primary-active); + background: var(--primary-active); } /* ghost button */ button.ghost:not(.tile) { - background: transparent; + background: transparent; } + button.ghost { - color: var(--link); + color: var(--link); } + .tab-link[active] { - color: var(--primary); + color: var(--primary); } + @media (hover: hover) { - button.ghost:hover { - background: var(--background-hover); - } + button.ghost:hover { + background: var(--background-hover); + } } + button.ghost:active { - background: var(--background-active); + background: var(--background-active); } /* danger button */ button.danger { - background: var(--danger); - color: white; + background: var(--danger); + color: white; } + @media (hover: hover) { - button.danger:hover { - background: var(--danger-hover); - } + button.danger:hover { + background: var(--danger-hover); + } } + button.danger:active { - background: var(--danger-active); + background: var(--danger-active); } /* SIZES */ :root { - /* gap */ - --gap-between-tiles: 4px; - --gap-in-tab-links: 1rem; - --gap-in-controls: 1.2rem; - --large-gap: 2rem; + /* gap */ + --gap-between-tiles: 4px; + --gap-in-tab-links: 1rem; + --gap-in-controls: 1.2rem; + --large-gap: 2rem; - /* outline */ - --outline-width: var(--gap-between-tiles); + /* outline */ + --outline-width: var(--gap-between-tiles); - /* margin */ - --label-top-padding: 0.25rem; - --label-tile-top-padding: 0.5rem; - --label-margin-bottom: 1rem; - --content-margin: 0.75rem; + /* margin */ + --label-top-padding: 0.25rem; + --label-tile-top-padding: 0.5rem; + --label-margin-bottom: 1rem; + --content-margin: 0.75rem; - /* padding */ - --control-padding: 1rem; - --control-marker-padding: 0.7rem; - --content-padding: 1rem; - --modal-padding: 1.25rem; - --horizontal-tab-link-padding: 0.8rem; + /* padding */ + --control-padding: 1rem; + --control-marker-padding: 0.7rem; + --content-padding: 1rem; + --modal-padding: 1.25rem; + --horizontal-tab-link-padding: 0.8rem; - /* size */ - --checkbox-size: 16px; + /* size */ + --checkbox-size: 16px; - --menu-height-mobile: calc(var(--header-height) + var(--safe-area-bottom)); - --menu-width-desktop: 200px; - --split-pane-width-desktop: 300px; + --menu-height-mobile: calc(var(--header-height) + var(--safe-area-bottom)); + --menu-width-desktop: 200px; + --split-pane-width-desktop: 300px; - --control-height: 42px; - --header-height: 48px; + --control-height: 42px; + --header-height: 48px; - --max-input-width: 24rem; - --max-content-width: 38rem; + --max-input-width: 24rem; + --max-content-width: 38rem; - /* safe */ - --safe-area-top: env(safe-area-inset-top); - --safe-area-left: env(safe-area-inset-left); - --safe-area-right: env(safe-area-inset-right); - --safe-area-bottom: env(safe-area-inset-bottom); + /* safe */ + --safe-area-top: env(safe-area-inset-top); + --safe-area-left: env(safe-area-inset-left); + --safe-area-right: env(safe-area-inset-right); + --safe-area-bottom: env(safe-area-inset-bottom); } /* @@ -299,51 +314,44 @@ TYPE */ @font-face { - font-family: IBM Plex Sans; - src: url("Fonts/IBMPlexSans-Regular.ttf"); + font-family: IBM Plex Sans; + src: url("Fonts/IBMPlexSans-Regular.ttf"); } @font-face { - font-family: IBM Plex Sans; - src: url("Fonts/IBMPlexSans-Bold.ttf"); - font-weight: bold; + font-family: IBM Plex Sans; + src: url("Fonts/IBMPlexSans-Bold.ttf"); + font-weight: bold; } @font-face { - font-family: IBM Plex Mono; - src: url("Fonts/IBMPlexMono-Regular.ttf"); + font-family: IBM Plex Mono; + src: url("Fonts/IBMPlexMono-Regular.ttf"); } @font-face { - font-family: IBM Plex Serif; - src: url("Fonts/IBMPlexSerif-Regular.ttf"); + font-family: IBM Plex Serif; + src: url("Fonts/IBMPlexSerif-Regular.ttf"); } /* MAIN */ -/* animation */ -@media (max-width: 640px) { - .modal > div { - animation: slide-up var(--animation-duration); - } -} - /* appearance */ input[type="checkbox"], input[type="radio"], select { - appearance: none; - --webkit-appearance: none; - --mpz-appearance: none; + appearance: none; + --webkit-appearance: none; + --mpz-appearance: none; } /* backdrop-filter */ header, menu, .modal > div { - backdrop-filter: blur(70px); - -webkit-backdrop-filter: blur(70px); + backdrop-filter: blur(70px); + -webkit-backdrop-filter: blur(70px); } /* border, outline, etc */ @@ -351,63 +359,63 @@ button, input, select, textarea { - border: none; + border: none; } footer { - border-top: 1px solid var(--surface-border); + border-top: 1px solid var(--surface-border); } input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not( - label.tile * - ), + label.tile * + ), label.tile, select:not(label.tile *), textarea:not(label.tile *) { - border-bottom: 1px solid var(--surface-border); + border-bottom: 1px solid var(--surface-border); } hr { - border-bottom: 1px solid var(--border); + border-bottom: 1px solid var(--border); } .border-bottom { - border-bottom: 1px solid var(--border) !important; + border-bottom: 1px solid var(--border) !important; } input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):focus:not( - label.tile * - ):not(.no-outline), + label.tile * + ):not(.no-outline), label.tile:has(input:focus), label.tile:has(textarea:focus), textarea:focus:not(label.tile *), [selected] { - outline: var(--outline-width) solid var(--primary); + outline: var(--outline-width) solid var(--primary); } .inset-outline[selected] { - outline: none; - box-shadow: var(--primary) 0 0 0 var(--outline-width) inset; + outline: none; + box-shadow: var(--primary) 0 0 0 var(--outline-width) inset; } /* border-radius */ * { - border-radius: 0px; + border-radius: 0px; } input[type="radio"] { - border-radius: 100%; + border-radius: 100%; } /* cursor */ a, .tab-link { - cursor: pointer; + cursor: pointer; } /* display etc */ .align-start { - align-items: start !important; + align-items: start !important; } button:not(.tile), @@ -419,20 +427,20 @@ label.inline, select, textarea, .tile:not(label) { - align-items: center; + align-items: center; } .align-center { - align-items: center !important; + align-items: center !important; } .align-end { - align-items: end !important; + align-items: end !important; } label, label * { - display: block; + display: block; } footer, @@ -445,97 +453,97 @@ main, .modal > div, .tile, .tile > div { - display: flex; + display: flex; } header > span, .flex-column, .flex-row { - display: flex; + display: flex; } .grid { - display: grid !important; + display: grid !important; } button:not(.tile), input, select, textarea { - display: inline-flex; + display: inline-flex; } .modal { - display: none; + display: none; } [hidden] { - display: none !important; + display: none !important; } main, .tile, .tile > div { - flex: 1; + flex: 1; } .flex { - flex: 1 !important; + flex: 1 !important; } .flex-no { - flex: 0 !important; + flex: 0 !important; } .modal > div, .modal main, .tile > div { - flex-direction: column; + flex-direction: column; } .flex-column { - flex-direction: column !important; + flex-direction: column !important; } .flex-row { - flex-direction: row !important; + flex-direction: row !important; } .flex-wrap { - flex-wrap: wrap !important; + flex-wrap: wrap !important; } button, label.inline, .tile, .control-gap { - gap: var(--gap-in-controls); + gap: var(--gap-in-controls); } header > span, .gap { - gap: var(--gap-between-tiles) !important; + gap: var(--gap-between-tiles) !important; } .large-gap { - gap: var(--large-gap) !important; + gap: var(--large-gap) !important; } header, button { - justify-content: space-between; + justify-content: space-between; } .justify-apart { - justify-content: space-between !important; + justify-content: space-between !important; } .tile { - justify-content: start; + justify-content: start; } .justify-start { - justify-content: start !important; + justify-content: start !important; } footer, @@ -543,176 +551,177 @@ header button, input[type="checkbox"], input[type="radio"], .modal { - justify-content: center; + justify-content: center; } .justify-center { - justify-content: safe center !important; + justify-content: safe center !important; } .justify-end { - justify-content: end !important; + justify-content: end !important; } /* opacity */ [disabled] { - opacity: 0.5; + opacity: 0.5; } label span, .helper-text, .secondary, .tab-link:not([active]) { - opacity: 0.6; + opacity: 0.6; } /* padding, margin, etc */ h1, h2, h3 { - margin: var(--control-padding) 0px; + margin: var(--control-padding) 0px; } main, p { - margin: 0px; + margin: 0px; } .margin-0 { - margin: 0px !important; + margin: 0px !important; } hr { - margin: var(--content-margin) 0px; + margin: var(--content-margin) 0px; } :is(code, p):not(:last-child) { - margin-bottom: var(--content-margin); + margin-bottom: var(--content-margin); } .content-margin-bottom { - margin-bottom: var(--content-margin) !important; + margin-bottom: var(--content-margin) !important; } label:not(.tile) { - margin-bottom: var(--label-margin-bottom); + margin-bottom: var(--label-margin-bottom); } .margin-bottom { - margin-bottom: var(--gap-between-tiles) !important; + margin-bottom: var(--gap-between-tiles) !important; } label.tile { - margin-bottom: var(--gap-between-tiles); + margin-bottom: var(--gap-between-tiles); } label:not(.tile) span:first-child { - margin-top: var(--label-top-padding); + margin-top: var(--label-top-padding); } label.tile span:first-child { - margin-top: var(--label-tile-top-padding); + margin-top: var(--label-tile-top-padding); } input[type="color"], input[type="checkbox"], input[type="radio"] { - padding: 0px; + padding: 0px; } .padding-0 { - padding: 0 !important; + padding: 0 !important; } article > div { - padding: var(--content-padding); + padding: var(--content-padding); } button:not(.tile):not(header *), input, select { - padding: 0px var(--control-padding); + padding: 0px var(--control-padding); } .padding-h { - padding: 0px var(--control-padding) !important; + padding: 0px var(--control-padding) !important; } label.inline, textarea, .tile:not(label) { - padding: var(--control-padding); + padding: var(--control-padding); } .padding { - padding: var(--control-padding) !important; + padding: var(--control-padding) !important; } header > span { - padding: var(--gap-between-tiles); + padding: var(--gap-between-tiles); } .padding-sm { - padding: var(--gap-between-tiles) !important; + padding: var(--gap-between-tiles) !important; } .modal main { - padding: var(--modal-padding); + padding: var(--modal-padding); } .padding-top { - padding-top: var(--control-padding) !important; + padding-top: var(--control-padding) !important; } article > div { - padding-top: calc(var(--header-height) + var(--content-margin)); + padding-top: calc(var(--header-height) + var(--content-margin)); } header { - padding-left: var(--content-padding); + padding-left: var(--content-padding); } label.tile:has(> span.icon) { - padding-left: var(--control-padding); + padding-left: var(--control-padding); } .padding-left { - padding-left: var(--control-padding) !important; + padding-left: var(--control-padding) !important; } label.tile:has(> span.icon) :is(input, select, textarea) { - padding-left: 0px; + padding-left: 0px; } select { - padding-right: 2.5rem; + padding-right: 2.5rem; } .padding-right { - padding-right: var(--control-padding) !important; + padding-right: var(--control-padding) !important; } .padding-bottom { - padding-bottom: var(--control-padding) !important; + padding-bottom: var(--control-padding) !important; } article > div { - padding-bottom: calc( - var(--menu-height-mobile) + var(--control-height) + var(--content-padding) - ); + padding-bottom: calc( + var(--menu-height-mobile) + var(--control-height) + + var(--content-padding) + ); } label:not(.inline) { - text-indent: var(--control-padding); + text-indent: var(--control-padding); } label.tile:has(> span.icon) { - text-indent: 0px; + text-indent: 0px; } /* pointer-events */ [disabled] { - pointer-events: none; + pointer-events: none; } /* position etc */ @@ -720,88 +729,88 @@ article > div, footer, header, select + .icon { - position: absolute; + position: absolute; } .absolute { - position: absolute !important; + position: absolute !important; } .modal { - position: fixed; + position: fixed; } .fixed { - position: fixed !important; + position: fixed !important; } article, main, input, label { - position: relative; + position: relative; } .relative { - position: relative !important; + position: relative !important; } .sticky { - position: sticky !important; + position: sticky !important; } article > div, header, .modal { - top: 0; + top: 0; } .top-0 { - top: 0 !important; + top: 0 !important; } article > div, footer, header, .modal { - left: 0px; + left: 0px; } .left-0 { - left: 0px !important; + left: 0px !important; } article > div, footer, header, .modal { - right: 0px; + right: 0px; } .right-0 { - right: 0px !important; + right: 0px !important; } select + .icon { - right: var(--control-marker-padding); + right: var(--control-marker-padding); } footer { - bottom: var(--safe-area-bottom); + bottom: var(--safe-area-bottom); } body:has(> menu) footer { - bottom: var(--menu-height-mobile); + bottom: var(--menu-height-mobile); } article > div, select + .icon, .modal { - bottom: 0px; + bottom: 0px; } .bottom-0 { - bottom: 0px !important; + bottom: 0px !important; } /* size */ @@ -810,15 +819,15 @@ main, select + .icon, .modal > div, .modal main { - height: 100%; + height: 100%; } .height-100 { - height: 100% !important; + height: 100% !important; } .height-auto { - height: auto !important; + height: auto !important; } button:not(.tile):not(header *), @@ -826,19 +835,19 @@ footer, input:not([type="checkbox"]):not([type="radio"]), select, select + .icon { - height: var(--control-height); + height: var(--control-height); } header { - height: var(--header-height); + height: var(--header-height); } header button { - height: calc(var(--header-height) - var(--gap-between-tiles) * 2); + height: calc(var(--header-height) - var(--gap-between-tiles) * 2); } .max-height-100 { - max-height: 100% !important; + max-height: 100% !important; } button:not(.tile):not(header *), @@ -846,19 +855,19 @@ footer, input:not([type="checkbox"]):not([type="radio"]), select, .modal > div > :is(button, div.flex-row) { - min-height: var(--control-height); + min-height: var(--control-height); } .ellipsis { - max-width: 100%; + max-width: 100%; } article { - min-width: 100%; + min-width: 100%; } .width-50 { - width: 50% !important; + width: 50% !important; } article, @@ -871,51 +880,51 @@ textarea, .modal > div, .tile, .tile > div { - width: 100%; + width: 100%; } .width-content, .width-input, .width-100 { - width: 100% !important; + width: 100% !important; } header button { - width: var(--header-height); + width: var(--header-height); } input[type="checkbox"], input[type="radio"] { - width: 16px; - height: 16px; + width: 16px; + height: 16px; } label.tile textarea { - resize: vertical; + resize: vertical; } /* text */ * { - font-family: IBM Plex Sans; - font-size: 1rem; + font-family: IBM Plex Sans; + font-size: 1rem; } h1 { - font-size: 2rem; + font-size: 2rem; } h2 { - font-size: 1.6rem; + font-size: 1.6rem; } h3 { - font-size: 1.4rem; + font-size: 1.4rem; } code { - font-family: IBM Plex Mono; + font-family: IBM Plex Mono; } .secondary, @@ -925,63 +934,63 @@ code { .warning, .success, label.tile > div > span:not(.icon) { - font-size: 0.9rem; + font-size: 0.9rem; } /* text-decoration */ a { - text-decoration: none; + text-decoration: none; } /* z-index */ footer, header, menu { - z-index: 1; + z-index: 1; } .modal { - z-index: 2; + z-index: 2; } .modal > div { - z-index: 2; + z-index: 2; } /* PSEUDO */ input[type="checkbox"]:checked::before { - content: "check"; - font-size: 70% !important; - color: var(--text-on-color); + content: "check"; + font-size: 70% !important; + color: var(--text-on-color); } input[type="radio"]:checked::before { - content: ""; + content: ""; - background: var(--text-on-color); - border-radius: 100%; + background: var(--text-on-color); + border-radius: 100%; - --inset: 4px; - position: absolute; - top: var(--inset); - left: var(--inset); - right: var(--inset); - bottom: var(--inset); + --inset: 4px; + position: absolute; + top: var(--inset); + left: var(--inset); + right: var(--inset); + bottom: var(--inset); } .modal::before { - content: ""; + content: ""; - position: absolute; - top: 0px; - left: 0px; - right: 0px; - bottom: 0px; + position: absolute; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; - background: var(--background); - opacity: 0.6; + background: var(--background); + opacity: 0.6; } /* @@ -990,65 +999,66 @@ LAYOUT /* basic */ body, html { - display: flex; - flex-direction: column-reverse; + display: flex; + flex-direction: column-reverse; - margin: 0px; - width: 100%; - height: 100%; + margin: 0px; + width: 100%; + height: 100%; } body, .modal { - padding-left: var(--safe-area-left); - padding-right: var(--safe-area-right); - padding-top: var(--safe-area-top); + padding-left: var(--safe-area-left); + padding-right: var(--safe-area-right); + padding-top: var(--safe-area-top); } .modal { - padding-bottom: var(--safe-area-bottom); + padding-bottom: var(--safe-area-bottom); } menu { - width: 100%; - height: var(--menu-height-mobile); + width: 100%; + height: var(--menu-height-mobile); - position: absolute; - bottom: 0px; - left: 0px; - right: 0px; + position: absolute; + bottom: 0px; + left: 0px; + right: 0px; - display: flex; - flex-direction: row; - justify-content: space-around; + display: flex; + flex-direction: row; + justify-content: space-around; - padding-top: 0px; - padding-bottom: var(--safe-area-bottom); + padding-top: 0px; + padding-bottom: var(--safe-area-bottom); } .tab-link { - width: 100%; - height: var(--header-height); + width: 100%; + height: var(--header-height); - padding: var(--horizontal-tab-link-padding) var(--control-padding); + padding: var(--horizontal-tab-link-padding) var(--control-padding); - display: flex; - flex-direction: column; - align-items: center; - gap: 0px; + display: flex; + flex-direction: column; + align-items: center; + gap: 0px; - font-size: 0.5rem; + font-size: 0.5rem; } article > div { - padding-bottom: calc( - var(--menu-height-mobile) + var(--header-height) + var(--content-padding) - ); + padding-bottom: calc( + var(--menu-height-mobile) + var(--header-height) + + var(--content-padding) + ); } /* overflow */ menu { - overflow: auto; + overflow: auto; } article, @@ -1056,161 +1066,161 @@ body, main, .clip, .ellipsis { - overflow: hidden; + overflow: hidden; } .scroll-no { - overflow: hidden !important; + overflow: hidden !important; } article > div, .modal main { - overflow: auto; + overflow: auto; } main { - overflow-x: auto; + overflow-x: auto; } .scroll-h { - overflow-x: auto !important; + overflow-x: auto !important; } .scroll-v { - overflow-y: auto !important; + overflow-y: auto !important; } .ellipsis { - text-overflow: ellipsis; + text-overflow: ellipsis; } .tile { - word-break: normal; + word-break: normal; } .break-all { - word-break: break-all !important; + word-break: break-all !important; } .break-word { - word-break: break-word !important; + word-break: break-word !important; } .ellipsis { - white-space: nowrap; + white-space: nowrap; } code { - white-space: pre-wrap; - word-break: break-all; + white-space: pre-wrap; + word-break: break-all; } /* scrolling */ main { - scrollbar-width: none; + scrollbar-width: none; } main::-webkit-scrollbar { - display: none; + display: none; } main, article > div { - scroll-behavior: smooth; + scroll-behavior: smooth; } article { - scroll-snap-align: start; + scroll-snap-align: start; } main { - scroll-snap-type: x mandatory; + scroll-snap-type: x mandatory; } /* tablet & up */ @media (min-width: 640px) { - /* basic */ - .mobile-only { - display: none; - } + /* basic */ + .mobile-only { + display: none; + } - /* border, outline, etc */ - .modal > div { - border: 1px solid var(--surface-border); - } + /* border, outline, etc */ + .modal > div { + border: 1px solid var(--surface-border); + } - menu, - main.split > article:first-of-type { - border-right: 1px solid var(--border); - } + menu, + main.split > article:first-of-type { + border-right: 1px solid var(--border); + } - /* size */ - .modal > div { - height: auto; - max-height: 100%; - } + /* size */ + .modal > div { + height: auto; + max-height: 100%; + } - p, - .modal > div { - max-width: var(--max-content-width); - } + p, + .modal > div { + max-width: var(--max-content-width); + } - .width-content { - max-width: var(--max-content-width) !important; - } + .width-content { + max-width: var(--max-content-width) !important; + } - input, - label, - select, - textarea { - max-width: var(--max-input-width); - } + input, + label, + select, + textarea { + max-width: var(--max-input-width); + } - .width-input { - max-width: var(--max-input-width) !important; - } + .width-input { + max-width: var(--max-input-width) !important; + } - /* layout */ - body { - flex-direction: row; - } + /* layout */ + body { + flex-direction: row; + } - main.split { - display: grid; - grid-template-columns: var(--split-pane-width-desktop) 1fr; - } + main.split { + display: grid; + grid-template-columns: var(--split-pane-width-desktop) 1fr; + } - article > div { - padding-bottom: calc(var(--content-padding) + var(--control-height)); - } + article > div { + padding-bottom: calc(var(--content-padding) + var(--control-height)); + } - menu { - position: static; + menu { + position: static; - width: var(--menu-width-desktop); - height: 100%; + width: var(--menu-width-desktop); + height: 100%; - flex-direction: column; - justify-content: start; - padding-top: 20px; - } + flex-direction: column; + justify-content: start; + padding-top: 20px; + } - .tab-link { - width: 100%; - height: var(--control-height); + .tab-link { + width: 100%; + height: var(--control-height); - flex-direction: row; - gap: var(--gap-in-tab-links); + flex-direction: row; + gap: var(--gap-in-tab-links); - font-size: 1rem; - } + font-size: 1rem; + } } /* desktop */ @media (min-width: 1024px) { - :root { - --split-pane-width-desktop: 400px; - } + :root { + --split-pane-width-desktop: 400px; + } } /* @@ -1219,26 +1229,26 @@ ICONS input[type="checkbox"], .tile.arrow-right::after, .icon { - font-family: "Material Symbols Sharp"; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-flex; - align-items: center; - justify-content: center; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; + font-family: "Material Symbols Sharp"; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-flex; + align-items: center; + justify-content: center; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; } @font-face { - font-family: "Material Symbols Sharp"; - font-style: normal; - font-weight: 300; - src: url("Icons/material-icons.woff") format("woff"); + font-family: "Material Symbols Sharp"; + font-style: normal; + font-weight: 300; + src: url("Icons/material-icons.woff") format("woff"); } diff --git a/dist/support.js b/dist/support.js index 39110646..8d839ed5 100644 --- a/dist/support.js +++ b/dist/support.js @@ -1,32 +1,35 @@ // TABS document.querySelector("main").addEventListener("scroll", () => { - const links = document.querySelectorAll(".tab-link"); - links.forEach((element) => element.removeAttribute("active")); + const links = document.querySelectorAll(".tab-link"); + links.forEach((element) => element.removeAttribute("active")); - const pageWidth = document.querySelector("main > article").offsetWidth; - const scroll = document.querySelector("main").scrollLeft; - const currentPage = Math.round(scroll / pageWidth); + const pageWidth = document.querySelector("main > article").offsetWidth; + const scroll = document.querySelector("main").scrollLeft; + const currentPage = Math.round(scroll / pageWidth); - if (links[currentPage]) links[currentPage].setAttribute("active", ""); + if (links[currentPage]) links[currentPage].setAttribute("active", ""); }); // SERVICE WORKER async function registerServiceWorker() { - if ("serviceWorker" in navigator) { - try { - const registration = await navigator.serviceWorker.register("/sw.js", { - scope: "/", - }); - if (registration.installing) { - setTimeout(() => window.location.reload(), 500); - } else if (registration.active) { - console.log("Service worker active"); - registration.update(); - } - } catch (error) { - console.error(`Could not install service worker: ${error}`); + if ("serviceWorker" in navigator) { + try { + const registration = await navigator.serviceWorker.register( + "/sw.js", + { + scope: "/", + }, + ); + if (registration.installing) { + setTimeout(() => window.location.reload(), 500); + } else if (registration.active) { + console.log("Service worker active"); + registration.update(); + } + } catch (error) { + console.error(`Could not install service worker: ${error}`); + } } - } } registerServiceWorker(); diff --git a/dist/sw.js b/dist/sw.js index fac4171d..ceefd51c 100644 --- a/dist/sw.js +++ b/dist/sw.js @@ -1,31 +1,32 @@ const CACHE_VERSION = "v2"; self.addEventListener("fetch", (event) => { - event.respondWith(handleRequest(event)); + event.respondWith(handleRequest(event)); }); async function handleRequest(event) { - const response = await getFromCache(event); - if (response) { - fetchAndCache(event); // Call this but don't await, allowing the cache to update in the background - return response; - } - return fetchAndCache(event); + const response = await getFromCache(event); + if (response) { + fetchAndCache(event); // Call this but don't await, allowing the cache to update in the background + return response; + } + return fetchAndCache(event); } async function getFromCache(event) { - const request = event.request; - const cache = await caches.open(CACHE_VERSION); - const response = await cache.match(request); - return response; + const request = event.request; + const cache = await caches.open(CACHE_VERSION); + const response = await cache.match(request); + return response; } async function fetchAndCache(event) { - const request = event.request; - const response = await fetch(request); - if (response.status === 200) { // Only cache successful responses - const cache = await caches.open(CACHE_VERSION); - cache.put(request, response.clone()); - } - return response; + const request = event.request; + const response = await fetch(request); + if (response.status === 200) { + // Only cache successful responses + const cache = await caches.open(CACHE_VERSION); + cache.put(request, response.clone()); + } + return response; }