diff --git a/dist/index.css b/dist/index.css index 321b420d..506572c6 100644 --- a/dist/index.css +++ b/dist/index.css @@ -1,130 +1,26 @@ -/* src/styles.css */ -[unread] { - border-left: 10px solid var(--text-warning); -} -:root { - --split-thread-height: 8rem; - --split-thread-width: 20rem; -} -.object-entry-wide { - width: 320px; - min-width: 320px; -} -#message-tab { - flex-direction: column; -} -.chat-object-view { - display: none; -} -[showingobjects] header { - border-bottom: 1px solid var(--border); -} -[showingobjects] footer { - display: none; -} -[showingobjects] .chat-object-view { - display: flex; - top: var(--header-height); -} -[showingobjects] .thread-view { - display: none; -} -[showingobjects][chatvisible] .chat-object-view { - right: var(--split-thread-width); - border-right: 1px solid var(--border); -} -[showingobjects][chatvisible] .thread-view { - top: var(--header-height); - padding-top: var(--content-padding); -} -[showingobjects][chatvisible] :is(.thread-view, footer) { - display: flex; - right: 0px; - left: unset; - width: var(--split-thread-width); -} -main:has([showingobjects][chatopen]) { - display: flex; -} -main:has([showingobjects][chatopen]) #settings-tab { - display: none; -} -@media (max-width: 640px) { - [showingobjects][chatvisible][chatopen] .chat-object-view { - width: 100%; - bottom: calc(var(--split-thread-height) + var(--control-height)); - border-right: none; - border-bottom: 1px solid var(--border); - } - [showingobjects][chatvisible][chatopen] .thread-view { - top: unset; - bottom: calc(var(--control-height) + var(--safe-area-bottom)); - width: 100%; - height: var(--split-thread-height); - padding: 0px; - } - [showingobjects][chatvisible][chatopen] footer { - width: 100%; - } -} -:root { - --day-view-width: 400px; -} -.calendar-wrapper { +/* src/View/homePage.css */ +#home-page > div { display: flex; flex-direction: column; - width: 100%; - height: 100%; + gap: var(--large-gap); } -.calendar-wrapper .month-grid { - border-bottom: 1px solid var(--border); -} -.calendar-wrapper > * { +#home-page > div > div { + display: flex; + flex-direction: column; + justify-content: start; + gap: var(--gap-between-tiles); width: 100%; - height: 100%; flex: 1; } -.day-miniature[today] h3 { - color: var(--primary); +#home-page #overview-section { + max-width: var(--max-input-width); } -.day-miniature:has(> div:not(:empty)):not([selected]) { - border: var(--gap-between-tiles) solid var(--text-warning); -} -@media (min-width: 750px) { - .calendar-wrapper:not([chatvisible] *) { +@media (min-width: 1000px) { + #home-page > div { flex-direction: row; + gap: 8rem; } - .calendar-wrapper:not([chatvisible] *) .month-grid { - border-bottom: none; - border-right: 1px solid var(--border); - } - .calendar-wrapper:not([chatvisible] *) .day-view { - max-width: var(--day-view-width); - } -} -@media (min-width: 1200px) { - .calendar-wrapper { - flex-direction: row; - } - .calendar-wrapper .month-grid { - border-bottom: none; - border-right: 1px solid var(--border); - } - .calendar-wrapper .day-view { - max-width: var(--day-view-width); - } -} -@media (max-width: 600px) or ((min-width: 750px) and (max-width: 1100px)) { - .day-miniature { - padding: 0px !important; - align-items: center !important; - justify-content: center; - } - .day-miniature h3 { - font-size: 1rem; - text-align: center; - } - .day-miniature > div { - display: none; + #home-page #overview-section { + width: var(--max-input-width); } } diff --git a/dist/index.js b/dist/index.js index b354a1db..dc7117c7 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,150 +1,4 @@ (() => { - // 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); - } - }; - function persistState(localStorageKey, state) { - state.subscribe(() => { - const stringifiedValue = state.toString(); - localStorage.setItem(localStorageKey, stringifiedValue); - }); - } - function restoreState(localStorageKey, initialStateValue) { - const storedString = localStorage.getItem(localStorageKey) ?? JSON.stringify(initialStateValue); - const convertedValue = JSON.parse(storedString); - const state = new State(convertedValue); - persistState(localStorageKey, state); - return state; - } - 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 "appendandscroll": - case "prepend": { - element.style.scrollBehavior = "smooth"; - 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 { - throw `error: cannot process subscribe:children directive because StateItemConverter is not defined. Usage: "subscribe:children={[list, converter]}"; you can find a more detailed example in the documentation`; - } - } - } - } - default: - element.setAttribute(attributename, value); - } - }); - children.filter((x) => x).forEach((child) => element.append(child)); - return element; - } - // node_modules/udn-frontend/index.ts var UDNFrontend = class { ws; @@ -257,7 +111,7 @@ } }; - // src/utility.ts + // src/Model/Utility/utility.ts function stringify(data) { return JSON.stringify(data); } @@ -308,30 +162,164 @@ } }; - // src/View/maintenance.tsx - var addressInput = restoreState("maintenance-address", ""); - var connectedAddress = new State(""); - var isConnected = new State(false); - var connectionModel = new ConnectionModel({ - connectionChangeHandler() { - isConnected.value = connectionModel.isConnected; - connectedAddress.value = connectionModel.address ?? "---"; - }, - messageHandler(data) { - console.log(data); - } - }); - function connect() { - connectionModel.connect(addressInput.value); + // node_modules/bloatless-react/index.ts + 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 "appendandscroll": + case "prepend": { + element.style.scrollBehavior = "smooth"; + 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 { + throw `error: cannot process subscribe:children directive because StateItemConverter is not defined. Usage: "subscribe:children={[list, converter]}"; you can find a more detailed example in the documentation`; + } + } + } + } + default: + element.setAttribute(attributename, value); + } + }); + children.filter((x) => x).forEach((child) => element.append(child)); + return element; } - function disconnect() { - connectionModel.disconnect(); - } - document.querySelector("main").append( - /* @__PURE__ */ createElement("div", { class: "flex-column" }, /* @__PURE__ */ createElement("input", { "bind:value": addressInput }), /* @__PURE__ */ createElement("button", { "on:click": connect, "toggle:disabled": isConnected }, "Connect"), /* @__PURE__ */ createElement("button", { "on:click": disconnect }, "Disconnect"), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("span", { "subscribe:innerText": connectedAddress })) - ); - // src/typeSafety.ts + // src/View/translations.ts + var englishTranslations = { + general: { + closeButton: "close" + }, + homePage: { + appName: "Comms", + /// + overviewHeadline: "Overview", + statusHeadline: "Status", + settingsButton: "Settings", + /// + chatsHeadline: "Chats", + addChatAudioLabel: "name of new chat", + addChatPlaceholder: "Add chat", + addChatButton: "Add chat" + }, + settings: { + settingsHeadline: "Settings" + /// + } + }; + var allTranslations = { + en: englishTranslations + }; + var language = navigator.language.substring(0, 2); + var translations = allTranslations[language] || allTranslations.en; + + // src/View/homePage.tsx + function HomePage() { + return /* @__PURE__ */ createElement("article", { id: "home-page" }, /* @__PURE__ */ createElement("header", null, /* @__PURE__ */ createElement("span", null, translations.homePage.appName)), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { id: "overview-section" }, /* @__PURE__ */ createElement("h2", null, translations.homePage.overviewHeadline), /* @__PURE__ */ createElement("div", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "cell_tower"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("h3", null, translations.homePage.statusHeadline))), /* @__PURE__ */ createElement("button", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "settings"), /* @__PURE__ */ createElement("div", null, translations.homePage.settingsButton))), /* @__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 + } + ), /* @__PURE__ */ createElement( + "button", + { + class: "primary", + "aria-label": translations.homePage.addChatButton + }, + /* @__PURE__ */ createElement("span", { class: "icon" }, "add") + ))))); + } + + // src/View/mainPage.tsx + function MainPage() { + return /* @__PURE__ */ createElement("article", { id: "home-page" }, /* @__PURE__ */ createElement("header", null, /* @__PURE__ */ createElement("span", null, translations.homePage.appName)), /* @__PURE__ */ createElement("div", null)); + } + + // src/View/setingsModal.tsx + function SetttingsModal() { + return /* @__PURE__ */ createElement("div", { class: "modal" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.settings.settingsHeadline)), /* @__PURE__ */ createElement("button", null, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); + } + + // src/Model/Utility/typeSafety.ts var DATA_VERSION = "v2"; // src/Model/storageModel.ts @@ -421,5 +409,11 @@ // src/index.tsx var storageModel = new StorageModel(); - console.log(storageModel.list("z")); + var connectionModel = new ConnectionModel({ + connectionChangeHandler() { + }, + messageHandler(data) { + } + }); + document.querySelector("main").append(HomePage(), MainPage(), SetttingsModal()); })(); diff --git a/src/typeSafety.ts b/src/Model/Utility/typeSafety.ts similarity index 100% rename from src/typeSafety.ts rename to src/Model/Utility/typeSafety.ts diff --git a/src/utility.ts b/src/Model/Utility/utility.ts similarity index 100% rename from src/utility.ts rename to src/Model/Utility/utility.ts diff --git a/src/Model/chatModel.ts b/src/Model/chatModel.ts index 6ddb30c2..8d738cd2 100644 --- a/src/Model/chatModel.ts +++ b/src/Model/chatModel.ts @@ -2,8 +2,8 @@ import StorageModel, { storageKeys } from "./storageModel"; -import { checkIsValidObject } from "../typeSafety"; -import { createTimestamp } from "../utility"; +import { checkIsValidObject } from "./Utility/typeSafety"; +import { createTimestamp } from "./Utility/utility"; import { v4 } from "uuid"; export class ChatModel { diff --git a/src/Model/connectionModel.ts b/src/Model/connectionModel.ts index 26aa19cd..22f2245a 100644 --- a/src/Model/connectionModel.ts +++ b/src/Model/connectionModel.ts @@ -3,7 +3,7 @@ import UDNFrontend, { Message } from "udn-frontend"; import { ChatMessage } from "./chatModel"; -import { stringify } from "../utility"; +import { stringify } from "./Utility/utility"; export default class ConnectionModel { // basic diff --git a/src/Model/storageModel.ts b/src/Model/storageModel.ts index e0f563c4..f9a237ac 100644 --- a/src/Model/storageModel.ts +++ b/src/Model/storageModel.ts @@ -1,8 +1,8 @@ // this file is responsible for reading and writing persistent data; all storage shall be handled by this file. -import { parse, stringify } from "../utility"; +import { parse, stringify } from "./Utility/utility"; -import { DATA_VERSION } from "../typeSafety"; +import { DATA_VERSION } from "./Utility/typeSafety"; export default class StorageModel { storageEntryTree: StorageEntry = {}; diff --git a/src/View/homePage.css b/src/View/homePage.css new file mode 100644 index 00000000..72b31675 --- /dev/null +++ b/src/View/homePage.css @@ -0,0 +1,33 @@ +#home-page > div { + display: flex; + flex-direction: column; + + gap: var(--large-gap); +} + +#home-page > div > div { + display: flex; + flex-direction: column; + justify-content: start; + + gap: var(--gap-between-tiles); + + width: 100%; + flex: 1; +} + +#home-page #overview-section { + max-width: var(--max-input-width); +} + +@media (min-width: 1000px) { + #home-page > div { + flex-direction: row; + + gap: 8rem; + } + + #home-page #overview-section { + width: var(--max-input-width); + } +} diff --git a/src/View/homePage.tsx b/src/View/homePage.tsx new file mode 100644 index 00000000..014adf0f --- /dev/null +++ b/src/View/homePage.tsx @@ -0,0 +1,48 @@ +import "./homePage.css"; + +import * as React from "bloatless-react"; + +import { translations } from "./translations"; + +export function HomePage() { + return ( +
+
+ {translations.homePage.appName} +
+
+
+

{translations.homePage.overviewHeadline}

+ +
+ cell_tower +
+

{translations.homePage.statusHeadline}

+
+
+ +
+ +
+

{translations.homePage.chatsHeadline}

+ +
+ + +
+
+
+
+ ); +} diff --git a/src/View/mainPage.tsx b/src/View/mainPage.tsx new file mode 100644 index 00000000..c51f5476 --- /dev/null +++ b/src/View/mainPage.tsx @@ -0,0 +1,14 @@ +import * as React from "bloatless-react"; + +import { translations } from "./translations"; + +export function MainPage() { + return ( +
+
+ {translations.homePage.appName} +
+
+
+ ); +} diff --git a/src/View/setingsModal.tsx b/src/View/setingsModal.tsx new file mode 100644 index 00000000..0adde0b7 --- /dev/null +++ b/src/View/setingsModal.tsx @@ -0,0 +1,19 @@ +import * as React from "bloatless-react"; + +import { translations } from "./translations"; + +export function SetttingsModal() { + return ( + + ); +} diff --git a/src/View/translations.ts b/src/View/translations.ts new file mode 100644 index 00000000..1858faf9 --- /dev/null +++ b/src/View/translations.ts @@ -0,0 +1,39 @@ +const englishTranslations = { + general: { + closeButton: "close", + }, + + homePage: { + appName: "Comms", + + /// + + overviewHeadline: "Overview", + + statusHeadline: "Status", + settingsButton: "Settings", + + /// + + chatsHeadline: "Chats", + + addChatAudioLabel: "name of new chat", + addChatPlaceholder: "Add chat", + addChatButton: "Add chat", + }, + + settings: { + settingsHeadline: "Settings", + + /// + + + } +}; + +const allTranslations: { [language: string]: typeof englishTranslations } = { + en: englishTranslations, +}; + +const language = navigator.language.substring(0, 2); +export const translations = allTranslations[language] || allTranslations.en; diff --git a/src/index.tsx b/src/index.tsx index f39b5b00..2d7508d0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,12 +1,17 @@ import ConnectionModel from "./Model/connectionModel"; +import { HomePage } from "./View/homePage"; +import { MainPage } from "./View/mainPage"; +import { SetttingsModal } from "./View/setingsModal"; import StorageModel from "./Model/storageModel"; +// models const storageModel = new StorageModel(); const connectionModel = new ConnectionModel({ - connectionChangeHandler() { - - }, - messageHandler(data) { - - }, -}) \ No newline at end of file + connectionChangeHandler() {}, + messageHandler(data) {}, +}); + +// view +document + .querySelector("main")! + .append(HomePage(), MainPage(), SetttingsModal());