2024-07-07 13:43:18 +02:00
|
|
|
(() => {
|
2024-07-22 01:40:53 +02:00
|
|
|
// 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 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) {
|
2024-07-23 00:05:48 +02:00
|
|
|
console.error(error);
|
2024-07-22 01:40:53 +02:00
|
|
|
throw `error: cannot process subscribe:children directive.
|
2024-07-23 00:05:48 +02:00
|
|
|
Usage: "children:append={[list, converter]}"; you can find a more detailed example in the documentation.`;
|
2024-07-22 01:40:53 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
element.setAttribute(attributename, value);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
children.filter((x) => x).forEach((child) => element.append(child));
|
|
|
|
|
return element;
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-21 22:42:32 +02:00
|
|
|
// src/Model/Utility/typeSafety.ts
|
|
|
|
|
var DATA_VERSION = "v2";
|
|
|
|
|
function checkIsValidObject(object) {
|
|
|
|
|
return object.dataVersion == DATA_VERSION;
|
|
|
|
|
}
|
2024-07-22 01:40:53 +02:00
|
|
|
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;
|
2024-07-22 11:49:05 +02:00
|
|
|
if (Array.isArray(reference[key]) || reference[key] instanceof Set) {
|
2024-07-22 01:40:53 +02:00
|
|
|
if (objectToCheck[key].length == 0) continue;
|
2024-07-22 11:49:05 +02:00
|
|
|
if (objectToCheck[key].size == 0) continue;
|
|
|
|
|
const [firstOfObjectToCheck] = objectToCheck[key];
|
|
|
|
|
const [fisrtOfReference] = reference[key];
|
2024-07-22 01:40:53 +02:00
|
|
|
const doesFirstItemMatch = checkMatchesObjectStructure(
|
2024-07-22 11:49:05 +02:00
|
|
|
firstOfObjectToCheck,
|
|
|
|
|
fisrtOfReference
|
2024-07-22 01:40:53 +02:00
|
|
|
);
|
|
|
|
|
if (doesFirstItemMatch == false) return false;
|
|
|
|
|
} else if (requiredType == "object") {
|
|
|
|
|
const doesNestedObjectMatch = checkMatchesObjectStructure(
|
|
|
|
|
objectToCheck[key],
|
|
|
|
|
reference[key]
|
|
|
|
|
);
|
|
|
|
|
if (doesNestedObjectMatch == false) return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2024-07-21 22:42:32 +02:00
|
|
|
|
2024-07-20 05:10:50 +02:00
|
|
|
// src/Model/Utility/utility.ts
|
2024-07-20 21:10:06 +02:00
|
|
|
function createTimestamp() {
|
|
|
|
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
|
|
|
}
|
2024-07-20 05:10:50 +02:00
|
|
|
function stringify(data) {
|
2024-07-20 20:42:43 +02:00
|
|
|
return JSON.stringify(data, null, 4);
|
2024-07-20 05:10:50 +02:00
|
|
|
}
|
|
|
|
|
function parse(string) {
|
2024-07-21 22:42:32 +02:00
|
|
|
try {
|
|
|
|
|
return JSON.parse(string);
|
|
|
|
|
} catch {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-22 01:40:53 +02:00
|
|
|
function parseValidObject(string, reference) {
|
2024-07-21 22:42:32 +02:00
|
|
|
const parsed = parse(string);
|
|
|
|
|
if (checkIsValidObject(parsed) == false) return null;
|
2024-07-22 01:40:53 +02:00
|
|
|
const doesMatchReference = checkMatchesObjectStructure(
|
|
|
|
|
parsed,
|
|
|
|
|
reference
|
|
|
|
|
);
|
|
|
|
|
if (doesMatchReference == false) return null;
|
2024-07-21 22:42:32 +02:00
|
|
|
return parsed;
|
2024-07-20 05:10:50 +02:00
|
|
|
}
|
2024-07-20 18:58:25 +02:00
|
|
|
function localeCompare(a, b) {
|
|
|
|
|
return a.localeCompare(b);
|
|
|
|
|
}
|
2024-07-20 05:10:50 +02:00
|
|
|
|
2024-07-22 02:05:27 +02:00
|
|
|
// src/Model/Global/storageModel.ts
|
2024-07-20 05:10:50 +02:00
|
|
|
var PATH_COMPONENT_SEPARATOR = "\\";
|
|
|
|
|
var StorageModel = class _StorageModel {
|
|
|
|
|
storageEntryTree = {};
|
2024-07-22 15:06:01 +02:00
|
|
|
// read
|
|
|
|
|
read = (pathComponents) => {
|
2024-07-21 17:55:53 +02:00
|
|
|
const pathString = _StorageModel.pathComponentsToString(
|
|
|
|
|
...pathComponents
|
|
|
|
|
);
|
2024-07-22 15:06:01 +02:00
|
|
|
return localStorage.getItem(pathString);
|
2024-07-20 05:10:50 +02:00
|
|
|
};
|
2024-07-22 15:06:01 +02:00
|
|
|
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
|
|
|
|
|
write = (pathComponents, value) => {
|
2024-07-21 17:55:53 +02:00
|
|
|
const pathString = _StorageModel.pathComponentsToString(
|
|
|
|
|
...pathComponents
|
|
|
|
|
);
|
2024-07-22 15:06:01 +02:00
|
|
|
localStorage.setItem(pathString, value);
|
|
|
|
|
this.updateTree(...pathComponents);
|
2024-07-20 05:10:50 +02:00
|
|
|
};
|
|
|
|
|
remove = (pathComponents, shouldInitialize = true) => {
|
2024-07-21 17:55:53 +02:00
|
|
|
const pathString = _StorageModel.pathComponentsToString(
|
|
|
|
|
...pathComponents
|
|
|
|
|
);
|
|
|
|
|
localStorage.removeItem(pathString);
|
2024-07-20 05:10:50 +02:00
|
|
|
if (shouldInitialize == true) {
|
|
|
|
|
this.initializeTree();
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-07-22 15:06:01 +02:00
|
|
|
removeRecursively = (pathComponents) => {
|
|
|
|
|
loop_over_files: for (const key of Object.keys(localStorage)) {
|
2024-07-21 17:55:53 +02:00
|
|
|
const pathComponentsOfCurrentEntity = _StorageModel.stringToPathComponents(key);
|
2024-07-22 15:06:01 +02:00
|
|
|
loop_over_path_components: for (let i = 0; i < pathComponents.length; i++) {
|
|
|
|
|
if (!pathComponentsOfCurrentEntity[i]) continue loop_over_files;
|
|
|
|
|
if (pathComponentsOfCurrentEntity[i] != pathComponents[i])
|
|
|
|
|
continue loop_over_files;
|
2024-07-20 05:10:50 +02:00
|
|
|
}
|
|
|
|
|
this.remove(pathComponentsOfCurrentEntity, false);
|
|
|
|
|
}
|
|
|
|
|
this.initializeTree();
|
|
|
|
|
};
|
2024-07-22 15:06:01 +02:00
|
|
|
rename = (sourcePathComponents, destinationPathComponents, shouldInitialize = true) => {
|
2024-07-22 13:09:33 +02:00
|
|
|
const content = this.read(sourcePathComponents);
|
|
|
|
|
if (content == null) return false;
|
|
|
|
|
this.write(destinationPathComponents, content);
|
|
|
|
|
this.remove(sourcePathComponents);
|
2024-07-22 15:06:01 +02:00
|
|
|
if (shouldInitialize == true) {
|
|
|
|
|
this.initializeTree();
|
|
|
|
|
}
|
2024-07-22 13:09:33 +02:00
|
|
|
return true;
|
|
|
|
|
};
|
2024-07-22 15:06:01 +02:00
|
|
|
renameRecursively = (sourcePathComponents, destinationPathComponents) => {
|
|
|
|
|
loop_over_files: for (const key of Object.keys(localStorage)) {
|
|
|
|
|
const originalPathComponentsOfCurrentEntity = _StorageModel.stringToPathComponents(key);
|
|
|
|
|
loop_over_path_components: for (let i = 0; i < sourcePathComponents.length; i++) {
|
|
|
|
|
if (!originalPathComponentsOfCurrentEntity[i]) continue loop_over_files;
|
|
|
|
|
if (originalPathComponentsOfCurrentEntity[i] != sourcePathComponents[i])
|
|
|
|
|
continue loop_over_files;
|
|
|
|
|
}
|
|
|
|
|
const relativePathOfCurrentEntity = originalPathComponentsOfCurrentEntity.slice(
|
|
|
|
|
sourcePathComponents.length
|
|
|
|
|
);
|
|
|
|
|
const destinationPathComponentsOfCurrentEntity = [
|
|
|
|
|
...destinationPathComponents,
|
|
|
|
|
...relativePathOfCurrentEntity
|
|
|
|
|
];
|
|
|
|
|
this.rename(
|
|
|
|
|
originalPathComponentsOfCurrentEntity,
|
|
|
|
|
destinationPathComponentsOfCurrentEntity,
|
|
|
|
|
false
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
this.initializeTree();
|
|
|
|
|
};
|
2024-07-20 05:10:50 +02:00
|
|
|
// stringifiable
|
2024-07-21 21:45:18 +02:00
|
|
|
writeStringifiable = (pathComponents, value) => {
|
2024-07-20 05:10:50 +02:00
|
|
|
const valueString = stringify(value);
|
2024-07-21 21:45:18 +02:00
|
|
|
this.write(pathComponents, valueString);
|
2024-07-20 05:10:50 +02:00
|
|
|
};
|
2024-07-22 01:40:53 +02:00
|
|
|
readStringifiable = (pathComponents, reference) => {
|
2024-07-21 21:45:18 +02:00
|
|
|
const valueString = this.read(pathComponents);
|
2024-07-20 05:10:50 +02:00
|
|
|
if (!valueString) return null;
|
2024-07-22 01:40:53 +02:00
|
|
|
const object = parseValidObject(valueString, reference);
|
|
|
|
|
if (object == null) return null;
|
2024-07-21 23:37:54 +02:00
|
|
|
return object;
|
2024-07-20 05:10:50 +02:00
|
|
|
};
|
2024-07-22 03:01:04 +02:00
|
|
|
// tree
|
2024-07-20 05:10:50 +02:00
|
|
|
initializeTree = () => {
|
|
|
|
|
console.log("initializing tree");
|
|
|
|
|
this.storageEntryTree = {};
|
|
|
|
|
for (const key of Object.keys(localStorage)) {
|
2024-07-21 17:55:53 +02:00
|
|
|
const components = _StorageModel.stringToPathComponents(key);
|
2024-07-20 05:10:50 +02:00
|
|
|
this.updateTree(...components);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
updateTree = (...pathComponents) => {
|
|
|
|
|
let currentParent = this.storageEntryTree;
|
|
|
|
|
for (const pathPart of pathComponents) {
|
|
|
|
|
if (!currentParent[pathPart]) {
|
|
|
|
|
currentParent[pathPart] = {};
|
|
|
|
|
}
|
|
|
|
|
currentParent = currentParent[pathPart];
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-07-21 17:55:53 +02:00
|
|
|
printTree = () => {
|
|
|
|
|
return stringify(this.storageEntryTree);
|
2024-07-20 05:10:50 +02:00
|
|
|
};
|
2024-07-22 03:01:04 +02:00
|
|
|
// init
|
|
|
|
|
constructor() {
|
|
|
|
|
this.initializeTree();
|
|
|
|
|
}
|
|
|
|
|
// utility
|
2024-07-21 18:56:56 +02:00
|
|
|
static getFileName = (pathComponents) => {
|
|
|
|
|
return pathComponents[pathComponents.length - 1] || "\\";
|
|
|
|
|
};
|
|
|
|
|
static getFileNameFromString = (pathString) => {
|
|
|
|
|
const pathComponents = this.stringToPathComponents(pathString);
|
|
|
|
|
return pathComponents[pathComponents.length - 1] || "\\";
|
|
|
|
|
};
|
2024-07-21 17:55:53 +02:00
|
|
|
static pathComponentsToString = (...pathComponents) => {
|
2024-07-22 15:06:01 +02:00
|
|
|
return pathComponents.filter((x) => x != "").join(PATH_COMPONENT_SEPARATOR);
|
2024-07-20 05:10:50 +02:00
|
|
|
};
|
2024-07-21 17:55:53 +02:00
|
|
|
static stringToPathComponents = (string) => {
|
|
|
|
|
return string.split(PATH_COMPONENT_SEPARATOR).filter((x) => x != "");
|
2024-07-20 05:10:50 +02:00
|
|
|
};
|
|
|
|
|
static join = (...items) => {
|
|
|
|
|
let allComponents = [];
|
|
|
|
|
for (const item of items) {
|
2024-07-21 17:55:53 +02:00
|
|
|
const parts = this.stringToPathComponents(item);
|
2024-07-20 05:10:50 +02:00
|
|
|
allComponents.push(...parts);
|
|
|
|
|
}
|
2024-07-21 17:55:53 +02:00
|
|
|
return _StorageModel.pathComponentsToString(...allComponents);
|
2024-07-20 05:10:50 +02:00
|
|
|
};
|
2024-07-22 03:01:04 +02:00
|
|
|
static getPath(locationName, filePath) {
|
|
|
|
|
return [DATA_VERSION, storageLocations[locationName], ...filePath];
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var storageLocations = {
|
|
|
|
|
connectionModel: "connection",
|
|
|
|
|
chat: "chat",
|
|
|
|
|
settingsModel: "settings"
|
|
|
|
|
};
|
|
|
|
|
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"
|
2024-07-22 13:09:33 +02:00
|
|
|
],
|
|
|
|
|
files: (id) => [...filePaths.chat.chatBase(id), "files"]
|
2024-07-22 03:01:04 +02:00
|
|
|
},
|
|
|
|
|
settingsModel: {
|
|
|
|
|
username: ["user-name"],
|
|
|
|
|
firstDayOfWeek: ["first-day-of-week"]
|
|
|
|
|
}
|
2024-07-20 05:10:50 +02:00
|
|
|
};
|
2024-07-22 01:31:47 +02:00
|
|
|
|
2024-07-22 21:07:44 +02:00
|
|
|
// src/colors.ts
|
2024-07-22 14:36:03 +02:00
|
|
|
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 || {});
|
|
|
|
|
|
2024-07-22 03:01:04 +02:00
|
|
|
// src/Model/Files/taskModel.ts
|
2024-07-22 11:49:05 +02:00
|
|
|
var TaskModel = class _TaskModel {
|
2024-07-22 13:09:33 +02:00
|
|
|
storageModel;
|
2024-07-22 03:01:04 +02:00
|
|
|
chatModel;
|
|
|
|
|
fileModel;
|
2024-07-23 01:40:30 +02:00
|
|
|
boardHandler = () => {
|
|
|
|
|
};
|
2024-07-22 13:09:33 +02:00
|
|
|
// paths
|
|
|
|
|
getBasePath = () => {
|
2024-07-22 17:23:48 +02:00
|
|
|
return this.fileModel.getModelContainerPath("taskModel");
|
2024-07-22 13:09:33 +02:00
|
|
|
};
|
2024-07-22 17:23:48 +02:00
|
|
|
getBoardFilePath = (boardId) => {
|
2024-07-22 18:09:58 +02:00
|
|
|
return [...this.fileModel.getFilePath(boardId)];
|
2024-07-22 13:09:33 +02:00
|
|
|
};
|
2024-07-22 19:28:39 +02:00
|
|
|
getTaskFilePath = (taskId) => {
|
|
|
|
|
return [...this.fileModel.getFilePath(taskId)];
|
|
|
|
|
};
|
2024-07-22 17:23:48 +02:00
|
|
|
getBoardContainerPath = () => {
|
2024-07-22 13:09:33 +02:00
|
|
|
return [...this.getBasePath(), subDirectories.boards];
|
|
|
|
|
};
|
2024-07-22 17:23:48 +02:00
|
|
|
getBoardDirectoryPath = (boardId) => {
|
|
|
|
|
return [...this.getBoardContainerPath(), boardId];
|
2024-07-22 13:17:21 +02:00
|
|
|
};
|
2024-07-22 17:23:48 +02:00
|
|
|
getTaskContainerPath = (boardId) => {
|
|
|
|
|
return [...this.getBoardDirectoryPath(boardId), subDirectories.boardTasks];
|
2024-07-22 11:49:05 +02:00
|
|
|
};
|
2024-07-22 19:28:39 +02:00
|
|
|
getTaskReferencePath = (boardId, fileId) => {
|
2024-07-22 17:23:48 +02:00
|
|
|
return [...this.getTaskContainerPath(boardId), fileId];
|
2024-07-22 11:49:05 +02:00
|
|
|
};
|
2024-07-22 23:33:57 +02:00
|
|
|
// handlers
|
2024-07-22 18:11:18 +02:00
|
|
|
handleFileContent = (fileContent) => {
|
|
|
|
|
if (checkMatchesObjectStructure(fileContent, BoardInfoFileContentReference) == true) {
|
|
|
|
|
this.handleBoard(fileContent);
|
|
|
|
|
} else if (checkMatchesObjectStructure(fileContent, TaskFileContentReference) == true) {
|
|
|
|
|
this.handleTask(fileContent);
|
2024-07-22 14:36:03 +02:00
|
|
|
}
|
2024-07-22 13:09:33 +02:00
|
|
|
};
|
2024-07-22 18:11:18 +02:00
|
|
|
handleBoard = (boardInfoFileContent) => {
|
2024-07-23 01:40:30 +02:00
|
|
|
this.updateBoard(boardInfoFileContent);
|
2024-07-22 17:23:48 +02:00
|
|
|
};
|
2024-07-22 18:11:18 +02:00
|
|
|
handleTask = (taskFileContent) => {
|
2024-07-22 19:28:39 +02:00
|
|
|
this.storeTask(taskFileContent);
|
2024-07-22 17:23:48 +02:00
|
|
|
};
|
2024-07-22 14:36:03 +02:00
|
|
|
// boards
|
|
|
|
|
createBoard = (name) => {
|
2024-07-22 18:11:18 +02:00
|
|
|
const boardInfoFileContent = _TaskModel.createBoardInfoFileContent(v4_default(), name, "standard" /* Standard */);
|
2024-07-22 19:28:39 +02:00
|
|
|
this.updateBoard(boardInfoFileContent);
|
2024-07-23 00:32:29 +02:00
|
|
|
return boardInfoFileContent;
|
2024-07-22 13:17:21 +02:00
|
|
|
};
|
2024-07-22 19:28:39 +02:00
|
|
|
updateBoard = (boardInfoFileContent) => {
|
2024-07-23 01:40:30 +02:00
|
|
|
this.boardHandler(boardInfoFileContent);
|
2024-07-22 19:28:39 +02:00
|
|
|
this.storeBoard(boardInfoFileContent);
|
|
|
|
|
this.chatModel.sendMessage("", boardInfoFileContent);
|
2024-07-22 13:17:21 +02:00
|
|
|
};
|
2024-07-22 19:28:39 +02:00
|
|
|
storeBoard = (boardInfoFileContent) => {
|
2024-07-22 18:11:18 +02:00
|
|
|
this.fileModel.addFileContent(boardInfoFileContent);
|
2024-07-22 17:23:48 +02:00
|
|
|
const boardDirectoryPath = this.getBoardDirectoryPath(
|
2024-07-22 18:11:18 +02:00
|
|
|
boardInfoFileContent.fileId
|
2024-07-22 17:23:48 +02:00
|
|
|
);
|
|
|
|
|
this.storageModel.write(boardDirectoryPath, "");
|
2024-07-22 15:06:01 +02:00
|
|
|
};
|
2024-07-22 18:09:58 +02:00
|
|
|
deleteBoard = (boardId) => {
|
|
|
|
|
const boardFilePath = this.getBoardFilePath(boardId);
|
|
|
|
|
const boardDirectoryPath = this.getBoardDirectoryPath(boardId);
|
|
|
|
|
this.storageModel.removeRecursively(boardFilePath);
|
|
|
|
|
this.storageModel.removeRecursively(boardDirectoryPath);
|
|
|
|
|
};
|
2024-07-22 19:28:39 +02:00
|
|
|
listBoardIds = () => {
|
|
|
|
|
const boardContainerPath = this.getBoardContainerPath();
|
2024-07-23 00:32:29 +02:00
|
|
|
const boardIds = this.storageModel.list(boardContainerPath);
|
|
|
|
|
return boardIds;
|
2024-07-22 19:28:39 +02:00
|
|
|
};
|
|
|
|
|
getBoardInfo = (fileId) => {
|
|
|
|
|
const boardInfoFileContentOrNull = this.fileModel.getLatestFileContent(
|
|
|
|
|
fileId,
|
|
|
|
|
BoardInfoFileContentReference
|
|
|
|
|
);
|
|
|
|
|
return boardInfoFileContentOrNull;
|
|
|
|
|
};
|
2024-07-22 14:36:03 +02:00
|
|
|
//tasks
|
|
|
|
|
createTask = (boardId, name) => {
|
2024-07-22 18:11:18 +02:00
|
|
|
const taskFileContent = _TaskModel.createTaskFileContent(
|
2024-07-22 17:23:48 +02:00
|
|
|
v4_default(),
|
2024-07-22 19:28:39 +02:00
|
|
|
name,
|
|
|
|
|
boardId
|
2024-07-22 14:36:03 +02:00
|
|
|
);
|
2024-07-22 19:28:39 +02:00
|
|
|
this.updateTask(taskFileContent);
|
|
|
|
|
};
|
|
|
|
|
updateTask = (taskFileContent) => {
|
|
|
|
|
this.storeTask(taskFileContent);
|
|
|
|
|
this.chatModel.sendMessage("", taskFileContent);
|
|
|
|
|
};
|
|
|
|
|
storeTask = (taskFileContent) => {
|
2024-07-22 18:11:18 +02:00
|
|
|
this.fileModel.addFileContent(taskFileContent);
|
2024-07-22 19:28:39 +02:00
|
|
|
const taskReferencePath = this.getTaskReferencePath(
|
|
|
|
|
taskFileContent.boardId,
|
|
|
|
|
taskFileContent.fileId
|
|
|
|
|
);
|
|
|
|
|
console.log("storing", taskReferencePath);
|
|
|
|
|
this.storageModel.write(taskReferencePath, "");
|
2024-07-22 14:36:03 +02:00
|
|
|
};
|
2024-07-22 19:28:39 +02:00
|
|
|
listTaskIds = (boardId) => {
|
|
|
|
|
const taskContainerPath = this.getTaskContainerPath(boardId);
|
|
|
|
|
console.log("listing", taskContainerPath);
|
|
|
|
|
const fileIds = this.storageModel.list(taskContainerPath);
|
2024-07-22 13:17:21 +02:00
|
|
|
return fileIds;
|
|
|
|
|
};
|
2024-07-22 19:28:39 +02:00
|
|
|
deleteTask = (boardId, taskId) => {
|
|
|
|
|
const taskFilePath = this.getTaskFilePath(taskId);
|
|
|
|
|
const taskReferencePath = this.getTaskReferencePath(
|
|
|
|
|
boardId,
|
|
|
|
|
taskId
|
|
|
|
|
);
|
|
|
|
|
this.storageModel.removeRecursively(taskFilePath);
|
|
|
|
|
this.storageModel.removeRecursively(taskReferencePath);
|
2024-07-22 14:36:03 +02:00
|
|
|
};
|
2024-07-23 01:40:30 +02:00
|
|
|
// other
|
|
|
|
|
setBoardHandler = (handler) => {
|
|
|
|
|
this.boardHandler = handler;
|
|
|
|
|
};
|
2024-07-22 03:01:04 +02:00
|
|
|
// init
|
2024-07-22 13:09:33 +02:00
|
|
|
constructor(storageModel2, chatModel, fileModel) {
|
2024-07-22 03:01:04 +02:00
|
|
|
this.chatModel = chatModel;
|
|
|
|
|
this.fileModel = fileModel;
|
2024-07-22 13:09:33 +02:00
|
|
|
this.storageModel = storageModel2;
|
2024-07-23 01:40:30 +02:00
|
|
|
fileModel.setFileContentHandler(this.handleFileContent);
|
2024-07-22 03:01:04 +02:00
|
|
|
}
|
2024-07-22 11:49:05 +02:00
|
|
|
// utility
|
2024-07-22 18:11:18 +02:00
|
|
|
static createBoardInfoFileContent = (fileId, name, color) => {
|
|
|
|
|
const fileContent = FileModel.createFileContent(
|
2024-07-22 17:23:48 +02:00
|
|
|
fileId,
|
|
|
|
|
"board-info"
|
|
|
|
|
);
|
2024-07-22 14:36:03 +02:00
|
|
|
return {
|
2024-07-22 18:11:18 +02:00
|
|
|
...fileContent,
|
2024-07-22 14:36:03 +02:00
|
|
|
name,
|
|
|
|
|
color
|
|
|
|
|
};
|
|
|
|
|
};
|
2024-07-22 18:11:18 +02:00
|
|
|
static createTaskFileContent = (fileId, name, boardId) => {
|
|
|
|
|
const fileContent = FileModel.createFileContent(
|
2024-07-22 17:23:48 +02:00
|
|
|
fileId,
|
|
|
|
|
"task"
|
|
|
|
|
);
|
2024-07-22 11:49:05 +02:00
|
|
|
return {
|
2024-07-22 18:11:18 +02:00
|
|
|
...fileContent,
|
2024-07-22 11:49:05 +02:00
|
|
|
name,
|
2024-07-22 14:36:03 +02:00
|
|
|
boardId
|
2024-07-22 11:49:05 +02:00
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
2024-07-22 13:09:33 +02:00
|
|
|
var subDirectories = {
|
2024-07-22 14:36:03 +02:00
|
|
|
boards: "boards",
|
|
|
|
|
boardTasks: "tasks"
|
|
|
|
|
};
|
2024-07-22 18:11:18 +02:00
|
|
|
var BoardInfoFileContentReference = {
|
2024-07-22 14:36:03 +02:00
|
|
|
dataVersion: DATA_VERSION,
|
2024-07-22 17:23:48 +02:00
|
|
|
fileId: "string",
|
2024-07-22 18:11:18 +02:00
|
|
|
fileContentId: "",
|
2024-07-22 14:36:03 +02:00
|
|
|
creationDate: "",
|
2024-07-22 17:23:48 +02:00
|
|
|
type: "board-info",
|
2024-07-22 14:36:03 +02:00
|
|
|
name: "",
|
|
|
|
|
color: ""
|
2024-07-22 13:09:33 +02:00
|
|
|
};
|
2024-07-22 18:11:18 +02:00
|
|
|
var TaskFileContentReference = {
|
2024-07-22 11:49:05 +02:00
|
|
|
dataVersion: DATA_VERSION,
|
2024-07-22 17:23:48 +02:00
|
|
|
fileId: "string",
|
2024-07-22 18:11:18 +02:00
|
|
|
fileContentId: "",
|
2024-07-22 11:49:05 +02:00
|
|
|
creationDate: "",
|
|
|
|
|
type: "task",
|
|
|
|
|
name: "",
|
2024-07-22 14:36:03 +02:00
|
|
|
boardId: ""
|
2024-07-22 03:01:04 +02:00
|
|
|
};
|
|
|
|
|
|
2024-07-22 02:05:27 +02:00
|
|
|
// src/Model/Files/fileModel.ts
|
2024-07-21 23:37:54 +02:00
|
|
|
var FileModel = class _FileModel {
|
2024-07-21 22:42:32 +02:00
|
|
|
chatModel;
|
2024-07-21 23:37:54 +02:00
|
|
|
storageModel;
|
2024-07-22 03:01:04 +02:00
|
|
|
taskModel;
|
2024-07-23 00:25:28 +02:00
|
|
|
fileContentHandler = () => {
|
|
|
|
|
};
|
2024-07-22 03:01:04 +02:00
|
|
|
// paths
|
|
|
|
|
getBasePath = () => {
|
|
|
|
|
return StorageModel.getPath(
|
|
|
|
|
"chat",
|
|
|
|
|
filePaths.chat.files(this.chatModel.id)
|
|
|
|
|
);
|
|
|
|
|
};
|
2024-07-22 17:23:48 +02:00
|
|
|
getFileContainerPath = () => {
|
|
|
|
|
return [...this.getBasePath(), subDirectories2.data];
|
|
|
|
|
};
|
|
|
|
|
getModelContainerPath = (modelName) => {
|
|
|
|
|
return [...this.getBasePath(), subDirectories2.model, modelName];
|
2024-07-22 13:09:33 +02:00
|
|
|
};
|
2024-07-22 03:01:04 +02:00
|
|
|
getFilePath = (fileId) => {
|
2024-07-22 17:23:48 +02:00
|
|
|
return [...this.getFileContainerPath(), fileId];
|
2024-07-22 03:01:04 +02:00
|
|
|
};
|
2024-07-22 18:11:18 +02:00
|
|
|
getFileContentPath = (fileId, fileContentId) => {
|
2024-07-22 17:23:48 +02:00
|
|
|
const filePath = this.getFilePath(fileId);
|
2024-07-22 18:11:18 +02:00
|
|
|
return [...filePath, fileContentId];
|
2024-07-22 03:01:04 +02:00
|
|
|
};
|
2024-07-22 23:33:57 +02:00
|
|
|
// handlers
|
2024-07-22 18:11:18 +02:00
|
|
|
handleStringifiedFileContent = (stringifiedFileContent) => {
|
|
|
|
|
const fileContent = parseValidObject(
|
|
|
|
|
stringifiedFileContent,
|
|
|
|
|
FileContentReference
|
2024-07-22 17:23:48 +02:00
|
|
|
);
|
2024-07-22 18:11:18 +02:00
|
|
|
if (fileContent == null) return;
|
|
|
|
|
this.handleFileContent(fileContent);
|
2024-07-22 17:23:48 +02:00
|
|
|
};
|
2024-07-22 18:11:18 +02:00
|
|
|
handleFileContent = (fileContent) => {
|
|
|
|
|
const didStore = this.storeFileContent(fileContent);
|
2024-07-22 17:23:48 +02:00
|
|
|
if (didStore == false) return;
|
2024-07-22 18:11:18 +02:00
|
|
|
this.taskModel.handleFileContent(fileContent);
|
2024-07-23 00:25:28 +02:00
|
|
|
this.fileContentHandler(fileContent);
|
2024-07-21 23:37:54 +02:00
|
|
|
};
|
2024-07-22 11:49:05 +02:00
|
|
|
// methods
|
2024-07-22 18:11:18 +02:00
|
|
|
addFileContent = (fileContent) => {
|
|
|
|
|
this.handleFileContent(fileContent);
|
|
|
|
|
this.chatModel.sendMessage("", fileContent);
|
2024-07-22 11:49:05 +02:00
|
|
|
};
|
|
|
|
|
// storage
|
2024-07-22 18:11:18 +02:00
|
|
|
storeFileContent = (fileContent) => {
|
|
|
|
|
const fileContentPath = this.getFileContentPath(
|
|
|
|
|
fileContent.fileId,
|
|
|
|
|
fileContent.fileContentId
|
2024-07-21 23:37:54 +02:00
|
|
|
);
|
2024-07-22 18:11:18 +02:00
|
|
|
const existingFileContent = this.storageModel.read(fileContentPath);
|
|
|
|
|
if (existingFileContent != null) return false;
|
|
|
|
|
const stringifiedContent = stringify(fileContent);
|
|
|
|
|
this.storageModel.write(fileContentPath, stringifiedContent);
|
2024-07-22 17:23:48 +02:00
|
|
|
return true;
|
2024-07-21 23:37:54 +02:00
|
|
|
};
|
|
|
|
|
listFileIds = () => {
|
2024-07-22 03:01:04 +02:00
|
|
|
return this.storageModel.list(this.getBasePath());
|
2024-07-21 23:37:54 +02:00
|
|
|
};
|
2024-07-22 18:11:18 +02:00
|
|
|
listFileContentIds = (fileId) => {
|
2024-07-22 03:01:04 +02:00
|
|
|
const filePath = this.getFilePath(fileId);
|
2024-07-22 12:39:26 +02:00
|
|
|
return this.storageModel.list(filePath);
|
|
|
|
|
};
|
2024-07-22 18:11:18 +02:00
|
|
|
selectLatestFileContentId = (fileContentIds) => {
|
|
|
|
|
return fileContentIds[fileContentIds.length - 1];
|
2024-07-21 23:37:54 +02:00
|
|
|
};
|
2024-07-22 18:11:18 +02:00
|
|
|
getFileContent = (fileId, fileContentName, reference) => {
|
|
|
|
|
const filePath = this.getFileContentPath(fileId, fileContentName);
|
|
|
|
|
const fileContentOrNull = this.storageModel.readStringifiable(
|
2024-07-22 17:23:48 +02:00
|
|
|
filePath,
|
|
|
|
|
reference
|
|
|
|
|
);
|
2024-07-22 18:11:18 +02:00
|
|
|
return fileContentOrNull;
|
2024-07-22 17:23:48 +02:00
|
|
|
};
|
2024-07-22 18:11:18 +02:00
|
|
|
getLatestFileContent = (fileId, reference) => {
|
|
|
|
|
const fileContentsIds = this.listFileContentIds(fileId);
|
|
|
|
|
const latestFileContentId = this.selectLatestFileContentId(fileContentsIds);
|
|
|
|
|
if (latestFileContentId == void 0) return null;
|
|
|
|
|
const fileContent = this.getFileContent(
|
2024-07-22 17:23:48 +02:00
|
|
|
fileId,
|
2024-07-22 18:11:18 +02:00
|
|
|
latestFileContentId,
|
2024-07-22 17:23:48 +02:00
|
|
|
reference
|
|
|
|
|
);
|
2024-07-22 18:11:18 +02:00
|
|
|
return fileContent;
|
2024-07-21 23:37:54 +02:00
|
|
|
};
|
2024-07-23 00:25:28 +02:00
|
|
|
// other
|
|
|
|
|
setFileContentHandler = (handler) => {
|
|
|
|
|
this.fileContentHandler = handler;
|
|
|
|
|
};
|
2024-07-21 22:42:32 +02:00
|
|
|
// init
|
2024-07-21 23:37:54 +02:00
|
|
|
constructor(chatModel, storageModel2) {
|
2024-07-21 22:42:32 +02:00
|
|
|
this.chatModel = chatModel;
|
2024-07-21 23:37:54 +02:00
|
|
|
this.storageModel = storageModel2;
|
2024-07-22 13:09:33 +02:00
|
|
|
this.taskModel = new TaskModel(this.storageModel, chatModel, this);
|
2024-07-21 22:42:32 +02:00
|
|
|
}
|
2024-07-21 23:37:54 +02:00
|
|
|
// utility
|
2024-07-22 18:11:18 +02:00
|
|
|
static generateFileContentId = (creationDate) => {
|
2024-07-22 17:23:48 +02:00
|
|
|
return creationDate + v4_default();
|
2024-07-21 23:37:54 +02:00
|
|
|
};
|
2024-07-22 18:11:18 +02:00
|
|
|
static createFileContent = (fileId, type) => {
|
2024-07-22 17:23:48 +02:00
|
|
|
const creationDate = createTimestamp();
|
2024-07-22 18:11:18 +02:00
|
|
|
const fileContentId = _FileModel.generateFileContentId(creationDate);
|
2024-07-22 01:11:50 +02:00
|
|
|
return {
|
|
|
|
|
dataVersion: DATA_VERSION,
|
2024-07-22 17:23:48 +02:00
|
|
|
fileId,
|
2024-07-22 18:11:18 +02:00
|
|
|
fileContentId,
|
2024-07-22 17:23:48 +02:00
|
|
|
creationDate,
|
2024-07-22 11:49:05 +02:00
|
|
|
type
|
2024-07-22 01:11:50 +02:00
|
|
|
};
|
|
|
|
|
};
|
2024-07-21 22:42:32 +02:00
|
|
|
};
|
2024-07-22 17:23:48 +02:00
|
|
|
var subDirectories2 = {
|
|
|
|
|
data: "data",
|
|
|
|
|
model: "model",
|
|
|
|
|
taskModel: "tasks"
|
|
|
|
|
};
|
2024-07-22 18:11:18 +02:00
|
|
|
var FileContentReference = {
|
2024-07-22 01:40:53 +02:00
|
|
|
dataVersion: DATA_VERSION,
|
2024-07-22 17:23:48 +02:00
|
|
|
fileId: "",
|
2024-07-22 18:11:18 +02:00
|
|
|
fileContentId: "",
|
2024-07-22 01:40:53 +02:00
|
|
|
creationDate: "",
|
|
|
|
|
type: ""
|
|
|
|
|
};
|
2024-07-21 22:42:32 +02:00
|
|
|
|
2024-07-21 03:17:40 +02:00
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-22 02:05:27 +02:00
|
|
|
// src/Model/Chat/chatModel.ts
|
2024-07-20 21:10:06 +02:00
|
|
|
var ChatModel = class _ChatModel {
|
2024-07-20 20:42:43 +02:00
|
|
|
connectionModel;
|
2024-07-20 05:10:50 +02:00
|
|
|
storageModel;
|
2024-07-20 20:42:43 +02:00
|
|
|
settingsModel;
|
2024-07-20 14:16:26 +02:00
|
|
|
chatListModel;
|
2024-07-21 22:42:32 +02:00
|
|
|
fileModel;
|
2024-07-20 20:42:43 +02:00
|
|
|
// data
|
|
|
|
|
id;
|
2024-07-20 05:10:50 +02:00
|
|
|
info;
|
2024-07-20 18:10:42 +02:00
|
|
|
color;
|
2024-07-21 01:12:35 +02:00
|
|
|
chatMessageHandler = () => {
|
|
|
|
|
};
|
2024-07-22 21:07:44 +02:00
|
|
|
get secondaryChannels() {
|
|
|
|
|
return this.info.secondaryChannels.sort(localeCompare);
|
|
|
|
|
}
|
2024-07-22 03:01:04 +02:00
|
|
|
// paths
|
|
|
|
|
getBasePath = () => {
|
|
|
|
|
return StorageModel.getPath("chat", filePaths.chat.chatBase(this.id));
|
|
|
|
|
};
|
|
|
|
|
getInfoPath = () => {
|
|
|
|
|
return StorageModel.getPath("chat", filePaths.chat.info(this.id));
|
|
|
|
|
};
|
|
|
|
|
getColorPath = () => {
|
|
|
|
|
return StorageModel.getPath("chat", filePaths.chat.color(this.id));
|
|
|
|
|
};
|
|
|
|
|
getMessageDirPath = () => {
|
|
|
|
|
return StorageModel.getPath("chat", filePaths.chat.messages(this.id));
|
|
|
|
|
};
|
|
|
|
|
getMessagePath = (id) => {
|
|
|
|
|
return [...this.getMessageDirPath(), id];
|
|
|
|
|
};
|
2024-07-22 23:33:57 +02:00
|
|
|
// handlers
|
|
|
|
|
handleMessage = (body) => {
|
|
|
|
|
const chatMessage = parseValidObject(
|
|
|
|
|
body,
|
|
|
|
|
ChatMessageReference
|
|
|
|
|
);
|
|
|
|
|
if (chatMessage == null) return;
|
|
|
|
|
chatMessage.status = "received" /* Received */;
|
|
|
|
|
this.addMessage(chatMessage);
|
|
|
|
|
};
|
|
|
|
|
handleMessageSent = (chatMessage) => {
|
|
|
|
|
chatMessage.status = "sent" /* Sent */;
|
|
|
|
|
this.addMessage(chatMessage);
|
|
|
|
|
};
|
2024-07-22 03:01:04 +02:00
|
|
|
// settings
|
2024-07-20 05:10:50 +02:00
|
|
|
setPrimaryChannel = (primaryChannel) => {
|
|
|
|
|
this.info.primaryChannel = primaryChannel;
|
|
|
|
|
this.storeInfo();
|
2024-07-21 01:12:35 +02:00
|
|
|
this.subscribe();
|
2024-07-20 05:10:50 +02:00
|
|
|
};
|
|
|
|
|
setSecondaryChannels = (secondaryChannels) => {
|
|
|
|
|
this.info.secondaryChannels = secondaryChannels;
|
|
|
|
|
this.storeInfo();
|
|
|
|
|
};
|
2024-07-20 19:18:19 +02:00
|
|
|
setEncryptionKey = (key) => {
|
|
|
|
|
this.info.encryptionKey = key;
|
|
|
|
|
this.storeInfo();
|
|
|
|
|
};
|
2024-07-20 18:10:42 +02:00
|
|
|
setColor = (color) => {
|
|
|
|
|
this.color = color;
|
|
|
|
|
this.storeColor();
|
|
|
|
|
};
|
2024-07-20 20:42:43 +02:00
|
|
|
// messaging
|
2024-07-22 23:33:57 +02:00
|
|
|
addMessage = async (chatMessage) => {
|
|
|
|
|
await this.decryptMessage(chatMessage);
|
|
|
|
|
if (chatMessage.body != "") {
|
|
|
|
|
const messagePath = this.getMessagePath(chatMessage.id);
|
|
|
|
|
this.storageModel.writeStringifiable(messagePath, chatMessage);
|
|
|
|
|
this.chatMessageHandler(chatMessage);
|
|
|
|
|
}
|
|
|
|
|
this.fileModel.handleStringifiedFileContent(chatMessage.stringifiedFile);
|
|
|
|
|
};
|
2024-07-22 18:11:18 +02:00
|
|
|
sendMessage = async (body, fileContent) => {
|
2024-07-20 21:10:06 +02:00
|
|
|
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("/");
|
2024-07-21 22:42:32 +02:00
|
|
|
const chatMessage = await _ChatModel.createChatMessage(
|
2024-07-20 21:10:06 +02:00
|
|
|
combinedChannel,
|
|
|
|
|
senderName,
|
2024-07-21 22:42:32 +02:00
|
|
|
this.info.encryptionKey,
|
|
|
|
|
body,
|
2024-07-22 18:11:18 +02:00
|
|
|
fileContent
|
2024-07-20 21:10:06 +02:00
|
|
|
);
|
2024-07-21 14:15:26 +02:00
|
|
|
this.addMessage(chatMessage);
|
2024-07-20 21:10:06 +02:00
|
|
|
this.connectionModel.sendMessageOrStore(chatMessage);
|
|
|
|
|
return true;
|
2024-07-20 20:42:43 +02:00
|
|
|
};
|
2024-07-21 03:17:40 +02:00
|
|
|
decryptMessage = async (chatMessage) => {
|
|
|
|
|
const decryptedBody = await decryptString(
|
|
|
|
|
chatMessage.body,
|
|
|
|
|
this.info.encryptionKey
|
|
|
|
|
);
|
2024-07-21 22:42:32 +02:00
|
|
|
const decryptedFile = await decryptString(
|
|
|
|
|
chatMessage.stringifiedFile ?? "",
|
|
|
|
|
this.info.encryptionKey
|
|
|
|
|
);
|
2024-07-21 03:17:40 +02:00
|
|
|
chatMessage.body = decryptedBody;
|
2024-07-21 22:42:32 +02:00
|
|
|
chatMessage.stringifiedFile = decryptedFile;
|
2024-07-21 03:17:40 +02:00
|
|
|
};
|
2024-07-21 01:12:35 +02:00
|
|
|
subscribe = () => {
|
|
|
|
|
this.connectionModel.addChannel(this.info.primaryChannel);
|
2024-07-20 05:10:50 +02:00
|
|
|
};
|
2024-07-22 03:01:04 +02:00
|
|
|
// storage
|
|
|
|
|
storeInfo = () => {
|
|
|
|
|
this.storageModel.writeStringifiable(this.getInfoPath(), this.info);
|
|
|
|
|
};
|
|
|
|
|
storeColor = () => {
|
|
|
|
|
this.storageModel.write(this.getColorPath(), this.color);
|
|
|
|
|
};
|
|
|
|
|
delete = () => {
|
|
|
|
|
this.chatListModel.untrackChat(this);
|
|
|
|
|
const dirPath = this.getBasePath();
|
|
|
|
|
this.storageModel.removeRecursively(dirPath);
|
|
|
|
|
};
|
2024-07-23 00:25:28 +02:00
|
|
|
// other
|
|
|
|
|
setMessageHandler = (handler) => {
|
|
|
|
|
this.chatMessageHandler = handler;
|
|
|
|
|
};
|
2024-07-22 03:01:04 +02:00
|
|
|
// load
|
|
|
|
|
loadInfo = () => {
|
|
|
|
|
const info = this.storageModel.readStringifiable(
|
|
|
|
|
this.getInfoPath(),
|
|
|
|
|
ChatInfoReference
|
|
|
|
|
);
|
|
|
|
|
if (info != null) {
|
|
|
|
|
this.info = info;
|
|
|
|
|
} else {
|
|
|
|
|
this.info = _ChatModel.generateChatInfo("0");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
loadColor = () => {
|
|
|
|
|
const path = this.getColorPath();
|
|
|
|
|
const color = this.storageModel.read(path);
|
|
|
|
|
if (!color) {
|
|
|
|
|
this.color = "standard" /* Standard */;
|
|
|
|
|
} else {
|
|
|
|
|
this.color = color;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
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;
|
|
|
|
|
}
|
2024-07-20 05:10:50 +02:00
|
|
|
// init
|
2024-07-20 20:42:43 +02:00
|
|
|
constructor(storageModel2, connectionModel2, settingsModel2, chatListModel2, chatId) {
|
2024-07-20 05:10:50 +02:00
|
|
|
this.id = chatId;
|
2024-07-20 20:42:43 +02:00
|
|
|
this.connectionModel = connectionModel2;
|
|
|
|
|
this.settingsModel = settingsModel2;
|
2024-07-20 05:10:50 +02:00
|
|
|
this.storageModel = storageModel2;
|
2024-07-20 20:42:43 +02:00
|
|
|
this.chatListModel = chatListModel2;
|
2024-07-21 21:45:18 +02:00
|
|
|
this.loadInfo();
|
|
|
|
|
this.loadColor();
|
2024-07-21 01:12:35 +02:00
|
|
|
this.subscribe();
|
2024-07-21 14:15:26 +02:00
|
|
|
connectionModel2.setMessageSentHandler(this.handleMessageSent);
|
2024-07-21 23:37:54 +02:00
|
|
|
this.fileModel = new FileModel(this, this.storageModel);
|
2024-07-20 05:10:50 +02:00
|
|
|
}
|
2024-07-20 21:10:06 +02:00
|
|
|
// utility
|
|
|
|
|
static generateChatInfo = (primaryChannel) => {
|
|
|
|
|
return {
|
2024-07-22 01:11:50 +02:00
|
|
|
dataVersion: DATA_VERSION,
|
2024-07-20 21:10:06 +02:00
|
|
|
primaryChannel,
|
|
|
|
|
secondaryChannels: [],
|
|
|
|
|
encryptionKey: "",
|
|
|
|
|
hasUnreadMessages: false
|
|
|
|
|
};
|
2024-07-20 05:10:50 +02:00
|
|
|
};
|
2024-07-22 18:11:18 +02:00
|
|
|
static createChatMessage = async (channel, sender, encryptionKey, body, fileContent) => {
|
2024-07-21 22:42:32 +02:00
|
|
|
const chatMessage = {
|
2024-07-22 01:11:50 +02:00
|
|
|
dataVersion: DATA_VERSION,
|
2024-07-20 21:10:06 +02:00
|
|
|
id: v4_default(),
|
|
|
|
|
channel,
|
|
|
|
|
sender,
|
|
|
|
|
body,
|
2024-07-21 14:15:26 +02:00
|
|
|
dateSent: createTimestamp(),
|
2024-07-21 22:42:32 +02:00
|
|
|
status: "outbox" /* Outbox */,
|
|
|
|
|
stringifiedFile: ""
|
2024-07-20 21:10:06 +02:00
|
|
|
};
|
2024-07-22 18:11:18 +02:00
|
|
|
if (fileContent != void 0) {
|
|
|
|
|
const stringifiedFile = stringify(fileContent);
|
2024-07-21 22:42:32 +02:00
|
|
|
chatMessage.stringifiedFile = stringifiedFile;
|
|
|
|
|
}
|
|
|
|
|
if (encryptionKey != "") {
|
|
|
|
|
chatMessage.body = await encryptString(chatMessage.body, encryptionKey);
|
|
|
|
|
chatMessage.stringifiedFile = await encryptString(
|
|
|
|
|
chatMessage.stringifiedFile,
|
|
|
|
|
encryptionKey
|
|
|
|
|
);
|
2024-07-21 01:12:35 +02:00
|
|
|
}
|
2024-07-21 22:42:32 +02:00
|
|
|
return chatMessage;
|
2024-07-21 01:12:35 +02:00
|
|
|
};
|
2024-07-20 21:10:06 +02:00
|
|
|
};
|
2024-07-22 01:40:53 +02:00
|
|
|
var ChatInfoReference = {
|
|
|
|
|
dataVersion: DATA_VERSION,
|
|
|
|
|
primaryChannel: "",
|
|
|
|
|
secondaryChannels: [""],
|
|
|
|
|
encryptionKey: "",
|
|
|
|
|
hasUnreadMessages: true
|
|
|
|
|
};
|
|
|
|
|
var ChatMessageReference = {
|
|
|
|
|
dataVersion: DATA_VERSION,
|
|
|
|
|
id: "",
|
|
|
|
|
channel: "",
|
|
|
|
|
sender: "",
|
|
|
|
|
body: "",
|
|
|
|
|
dateSent: "",
|
|
|
|
|
status: "",
|
|
|
|
|
stringifiedFile: ""
|
|
|
|
|
};
|
2024-07-20 05:10:50 +02:00
|
|
|
|
2024-07-22 02:05:27 +02:00
|
|
|
// src/Model/Chat/chatListModel.ts
|
2024-07-20 05:10:50 +02:00
|
|
|
var ChatListModel = class {
|
|
|
|
|
storageModel;
|
2024-07-20 20:42:43 +02:00
|
|
|
settingsModel;
|
|
|
|
|
connectionModel;
|
2024-07-20 05:10:50 +02:00
|
|
|
// data
|
2024-07-20 16:49:01 +02:00
|
|
|
chatModels = /* @__PURE__ */ new Set();
|
2024-07-21 01:12:35 +02:00
|
|
|
// handlers
|
|
|
|
|
messageHandler = (data) => {
|
|
|
|
|
for (const chatModel of this.chatModels) {
|
|
|
|
|
const channel = data.messageChannel;
|
|
|
|
|
const body = data.messageBody;
|
|
|
|
|
if (channel == void 0) return;
|
|
|
|
|
if (body == void 0) return;
|
|
|
|
|
const allChannels = channel.split("/");
|
|
|
|
|
for (const channel2 of allChannels) {
|
|
|
|
|
if (channel2 != chatModel.info.primaryChannel) continue;
|
2024-07-21 03:17:40 +02:00
|
|
|
chatModel.handleMessage(body);
|
2024-07-21 01:12:35 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-07-22 03:01:04 +02:00
|
|
|
// storage
|
2024-07-20 05:10:50 +02:00
|
|
|
addChatModel = (chatModel) => {
|
2024-07-20 16:49:01 +02:00
|
|
|
this.chatModels.add(chatModel);
|
2024-07-20 05:10:50 +02:00
|
|
|
};
|
|
|
|
|
createChat = (primaryChannel) => {
|
|
|
|
|
const id = v4_default();
|
2024-07-20 20:42:43 +02:00
|
|
|
const chatModel = new ChatModel(
|
|
|
|
|
this.storageModel,
|
|
|
|
|
this.connectionModel,
|
|
|
|
|
this.settingsModel,
|
|
|
|
|
this,
|
|
|
|
|
id
|
|
|
|
|
);
|
2024-07-20 05:10:50 +02:00
|
|
|
chatModel.setPrimaryChannel(primaryChannel);
|
|
|
|
|
this.addChatModel(chatModel);
|
|
|
|
|
return chatModel;
|
|
|
|
|
};
|
|
|
|
|
untrackChat = (chat) => {
|
2024-07-20 16:49:01 +02:00
|
|
|
this.chatModels.delete(chat);
|
|
|
|
|
};
|
2024-07-21 21:45:18 +02:00
|
|
|
// load
|
2024-07-20 05:10:50 +02:00
|
|
|
loadChats = () => {
|
2024-07-22 03:01:04 +02:00
|
|
|
const chatDir = StorageModel.getPath("chat", filePaths.chat.base);
|
2024-07-20 05:10:50 +02:00
|
|
|
const chatIds = this.storageModel.list(chatDir);
|
|
|
|
|
for (const chatId of chatIds) {
|
2024-07-20 20:42:43 +02:00
|
|
|
const chatModel = new ChatModel(
|
|
|
|
|
this.storageModel,
|
|
|
|
|
this.connectionModel,
|
|
|
|
|
this.settingsModel,
|
|
|
|
|
this,
|
|
|
|
|
chatId
|
|
|
|
|
);
|
2024-07-20 05:10:50 +02:00
|
|
|
this.addChatModel(chatModel);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// init
|
2024-07-20 20:42:43 +02:00
|
|
|
constructor(storageModel2, settingsModel2, connectionModel2) {
|
2024-07-20 05:10:50 +02:00
|
|
|
this.storageModel = storageModel2;
|
2024-07-20 20:42:43 +02:00
|
|
|
this.settingsModel = settingsModel2;
|
|
|
|
|
this.connectionModel = connectionModel2;
|
2024-07-20 05:10:50 +02:00
|
|
|
this.loadChats();
|
2024-07-21 01:12:35 +02:00
|
|
|
connectionModel2.setMessageHandler(this.messageHandler);
|
2024-07-20 05:10:50 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-22 21:07:44 +02:00
|
|
|
// src/ViewModel/Chat/chatMessageViewModel.ts
|
2024-07-21 01:12:35 +02:00
|
|
|
var ChatMessageViewModel = class {
|
2024-07-22 21:07:44 +02:00
|
|
|
messagePageViewModel;
|
2024-07-21 01:12:35 +02:00
|
|
|
// data
|
2024-07-21 13:13:41 +02:00
|
|
|
chatMessage;
|
2024-07-21 01:12:35 +02:00
|
|
|
channel;
|
|
|
|
|
sender;
|
|
|
|
|
dateSent;
|
|
|
|
|
body = new State("");
|
2024-07-21 14:15:26 +02:00
|
|
|
status = new State(void 0);
|
2024-07-21 01:53:25 +02:00
|
|
|
sentByUser;
|
2024-07-21 13:13:41 +02:00
|
|
|
// methods
|
|
|
|
|
copyMessage = () => {
|
|
|
|
|
navigator.clipboard.writeText(this.body.value);
|
|
|
|
|
};
|
|
|
|
|
resendMessage = () => {
|
2024-07-22 21:07:44 +02:00
|
|
|
this.messagePageViewModel.sendMessageFromBody(this.body.value);
|
2024-07-21 13:13:41 +02:00
|
|
|
};
|
|
|
|
|
decryptMessage = () => {
|
2024-07-22 21:07:44 +02:00
|
|
|
this.messagePageViewModel.decryptMessage(this);
|
2024-07-21 13:13:41 +02:00
|
|
|
};
|
|
|
|
|
// load
|
|
|
|
|
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;
|
2024-07-21 14:15:26 +02:00
|
|
|
this.status.value = this.chatMessage.status;
|
2024-07-21 13:13:41 +02:00
|
|
|
};
|
|
|
|
|
// init
|
2024-07-22 21:07:44 +02:00
|
|
|
constructor(messagePageViewModel, chatMessage, sentByUser) {
|
|
|
|
|
this.messagePageViewModel = messagePageViewModel;
|
2024-07-21 13:13:41 +02:00
|
|
|
this.chatMessage = chatMessage;
|
2024-07-21 01:53:25 +02:00
|
|
|
this.sentByUser = sentByUser;
|
2024-07-21 13:13:41 +02:00
|
|
|
this.loadData();
|
2024-07-21 01:12:35 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-22 21:07:44 +02:00
|
|
|
// src/ViewModel/Pages/messagePageViewModel.ts
|
|
|
|
|
var MessagePageViewModel = class {
|
|
|
|
|
chatViewModel;
|
|
|
|
|
// state
|
|
|
|
|
chatMessageViewModels = new MapState();
|
|
|
|
|
composingMessage = new State("");
|
|
|
|
|
// guards
|
|
|
|
|
cannotSendMessage;
|
|
|
|
|
// methods
|
|
|
|
|
sendMessage = () => {
|
|
|
|
|
if (this.cannotSendMessage.value == true) return;
|
|
|
|
|
this.sendMessageFromBody(this.composingMessage.value);
|
|
|
|
|
this.composingMessage.value = "";
|
|
|
|
|
};
|
|
|
|
|
sendMessageFromBody = (body) => {
|
|
|
|
|
this.chatViewModel.chatModel.sendMessage(body);
|
|
|
|
|
};
|
|
|
|
|
decryptMessage = async (messageViewModel) => {
|
|
|
|
|
const chatMessage = messageViewModel.chatMessage;
|
|
|
|
|
await this.chatViewModel.chatModel.decryptMessage(chatMessage);
|
|
|
|
|
this.chatViewModel.chatModel.addMessage(chatMessage);
|
|
|
|
|
messageViewModel.loadData();
|
|
|
|
|
};
|
2024-07-23 00:25:28 +02:00
|
|
|
// view
|
|
|
|
|
showChatMessage = (chatMessage) => {
|
|
|
|
|
const chatMessageModel = new ChatMessageViewModel(
|
|
|
|
|
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, chatMessageModel);
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-07-22 21:07:44 +02:00
|
|
|
// load
|
|
|
|
|
loadData = () => {
|
|
|
|
|
for (const chatMessage of this.chatViewModel.chatModel.messages) {
|
2024-07-23 00:25:28 +02:00
|
|
|
this.showChatMessage(chatMessage);
|
2024-07-22 21:07:44 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// init
|
|
|
|
|
constructor(chatViewModel) {
|
|
|
|
|
this.chatViewModel = chatViewModel;
|
2024-07-23 00:05:48 +02:00
|
|
|
this.cannotSendMessage = createProxyState(
|
|
|
|
|
[this.chatViewModel.settingsViewModel.username, this.composingMessage],
|
|
|
|
|
() => this.chatViewModel.settingsViewModel.username.value == "" || this.composingMessage.value == ""
|
|
|
|
|
);
|
2024-07-22 21:07:44 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// src/ViewModel/Pages/settingsPageViewModel.ts
|
|
|
|
|
var SettingsPageViewModel = class {
|
|
|
|
|
chatViewModel;
|
2024-07-20 14:16:26 +02:00
|
|
|
// state
|
|
|
|
|
primaryChannel = new State("");
|
|
|
|
|
primaryChannelInput = new State("");
|
2024-07-20 18:49:05 +02:00
|
|
|
secondaryChannels = new ListState();
|
|
|
|
|
newSecondaryChannelInput = new State("");
|
2024-07-20 19:18:19 +02:00
|
|
|
encryptionKeyInput = new State("");
|
|
|
|
|
shouldShowEncryptionKey = new State(false);
|
|
|
|
|
encryptionKeyInputType = createProxyState(
|
|
|
|
|
[this.shouldShowEncryptionKey],
|
|
|
|
|
() => this.shouldShowEncryptionKey.value == true ? "text" : "password"
|
|
|
|
|
);
|
2024-07-20 18:10:42 +02:00
|
|
|
color = new State("standard" /* Standard */);
|
2024-07-20 14:16:26 +02:00
|
|
|
// guards
|
|
|
|
|
cannotSetPrimaryChannel = createProxyState(
|
|
|
|
|
[this.primaryChannel, this.primaryChannelInput],
|
|
|
|
|
() => this.primaryChannelInput.value == "" || this.primaryChannelInput.value == this.primaryChannel.value
|
|
|
|
|
);
|
2024-07-20 23:22:08 +02:00
|
|
|
cannotAddSecondaryChannel = createProxyState(
|
|
|
|
|
[this.newSecondaryChannelInput],
|
|
|
|
|
() => this.newSecondaryChannelInput.value == ""
|
|
|
|
|
);
|
2024-07-20 19:18:19 +02:00
|
|
|
cannotSetEncryptionKey;
|
2024-07-22 21:07:44 +02:00
|
|
|
// methods
|
2024-07-20 17:30:36 +02:00
|
|
|
setPrimaryChannel = () => {
|
2024-07-22 21:07:44 +02:00
|
|
|
this.chatViewModel.chatModel.setPrimaryChannel(
|
|
|
|
|
this.primaryChannelInput.value
|
|
|
|
|
);
|
|
|
|
|
this.primaryChannel.value = this.chatViewModel.chatModel.info.primaryChannel;
|
2024-07-20 17:30:36 +02:00
|
|
|
};
|
2024-07-20 18:49:05 +02:00
|
|
|
addSecondaryChannel = () => {
|
|
|
|
|
this.secondaryChannels.add(this.newSecondaryChannelInput.value);
|
|
|
|
|
this.newSecondaryChannelInput.value = "";
|
|
|
|
|
this.storeSecondaryChannels();
|
2024-07-21 21:45:18 +02:00
|
|
|
this.loadSecondaryChannels();
|
2024-07-20 18:49:05 +02:00
|
|
|
};
|
|
|
|
|
removeSecondaryChannel = (secondaryChannel) => {
|
|
|
|
|
this.secondaryChannels.remove(secondaryChannel);
|
|
|
|
|
this.storeSecondaryChannels();
|
|
|
|
|
};
|
|
|
|
|
storeSecondaryChannels = () => {
|
2024-07-22 21:07:44 +02:00
|
|
|
this.chatViewModel.chatModel.setSecondaryChannels([
|
2024-07-20 18:49:05 +02:00
|
|
|
...this.secondaryChannels.value.values()
|
|
|
|
|
]);
|
|
|
|
|
};
|
2024-07-20 19:18:19 +02:00
|
|
|
setEncryptionKey = () => {
|
2024-07-22 21:07:44 +02:00
|
|
|
this.chatViewModel.chatModel.setEncryptionKey(
|
|
|
|
|
this.encryptionKeyInput.value
|
|
|
|
|
);
|
2024-07-20 19:18:19 +02:00
|
|
|
this.encryptionKeyInput.callSubscriptions();
|
|
|
|
|
};
|
2024-07-20 18:10:42 +02:00
|
|
|
setColor = (newColor) => {
|
|
|
|
|
this.color.value = newColor;
|
2024-07-22 21:07:44 +02:00
|
|
|
this.chatViewModel.chatModel.setColor(newColor);
|
2024-07-20 18:10:42 +02:00
|
|
|
};
|
2024-07-20 20:48:13 +02:00
|
|
|
remove = () => {
|
2024-07-22 21:07:44 +02:00
|
|
|
this.chatViewModel.close();
|
|
|
|
|
this.chatViewModel.chatModel.delete();
|
2024-07-23 01:22:08 +02:00
|
|
|
this.chatViewModel.chatListViewModel.untrackChat(this.chatViewModel);
|
2024-07-20 20:48:13 +02:00
|
|
|
};
|
2024-07-22 21:07:44 +02:00
|
|
|
// load
|
2024-07-22 23:33:57 +02:00
|
|
|
loadListRelevantData = () => {
|
2024-07-22 21:07:44 +02:00
|
|
|
this.primaryChannel.value = this.chatViewModel.chatModel.info.primaryChannel;
|
|
|
|
|
this.color.value = this.chatViewModel.chatModel.color;
|
2024-07-20 21:10:06 +02:00
|
|
|
};
|
2024-07-22 21:07:44 +02:00
|
|
|
loadData = () => {
|
|
|
|
|
this.primaryChannelInput.value = this.chatViewModel.chatModel.info.primaryChannel;
|
|
|
|
|
this.loadSecondaryChannels();
|
|
|
|
|
this.encryptionKeyInput.value = this.chatViewModel.chatModel.info.encryptionKey;
|
2024-07-21 13:13:41 +02:00
|
|
|
};
|
2024-07-22 21:07:44 +02:00
|
|
|
loadSecondaryChannels = () => {
|
|
|
|
|
this.secondaryChannels.clear();
|
|
|
|
|
for (const secondaryChannel of this.chatViewModel.chatModel.secondaryChannels) {
|
|
|
|
|
this.secondaryChannels.add(secondaryChannel);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// init
|
|
|
|
|
constructor(chatViewModel) {
|
|
|
|
|
this.chatViewModel = chatViewModel;
|
2024-07-22 23:33:57 +02:00
|
|
|
this.loadListRelevantData();
|
2024-07-23 00:05:48 +02:00
|
|
|
this.cannotSetEncryptionKey = createProxyState(
|
|
|
|
|
[this.encryptionKeyInput],
|
|
|
|
|
() => this.encryptionKeyInput.value == this.chatViewModel.chatModel.info.encryptionKey
|
|
|
|
|
);
|
2024-07-22 21:07:44 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-23 00:57:40 +02:00
|
|
|
// src/ViewModel/Pages/boardViewModel.ts
|
|
|
|
|
var BoardViewModel = class {
|
|
|
|
|
taskPageViewModel;
|
|
|
|
|
// data
|
|
|
|
|
boardInfo;
|
|
|
|
|
name = new State("");
|
|
|
|
|
color = new State("standard" /* Standard */);
|
|
|
|
|
// load
|
|
|
|
|
loadListRelevantData = () => {
|
|
|
|
|
this.name.value = this.boardInfo.name;
|
|
|
|
|
this.color.value = this.boardInfo.color;
|
|
|
|
|
};
|
|
|
|
|
loadData = () => {
|
|
|
|
|
};
|
|
|
|
|
// init
|
|
|
|
|
constructor(taskPageViewModel, boardInfo) {
|
|
|
|
|
this.taskPageViewModel = taskPageViewModel;
|
|
|
|
|
this.boardInfo = boardInfo;
|
|
|
|
|
this.loadListRelevantData();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-22 21:07:44 +02:00
|
|
|
// src/ViewModel/Pages/taskPageViewModel.ts
|
|
|
|
|
var TaskPageViewModel = class {
|
|
|
|
|
taskModel;
|
|
|
|
|
// state
|
|
|
|
|
newBoardNameInput = new State("");
|
2024-07-23 01:40:30 +02:00
|
|
|
boardViewModels = new MapState();
|
2024-07-22 21:07:44 +02:00
|
|
|
// guards
|
|
|
|
|
cannotCreateBoard = createProxyState(
|
|
|
|
|
[this.newBoardNameInput],
|
|
|
|
|
() => this.newBoardNameInput.value == ""
|
|
|
|
|
);
|
2024-07-23 00:25:28 +02:00
|
|
|
// handlers
|
|
|
|
|
handleFileContent = (fileContent) => {
|
|
|
|
|
if (checkMatchesObjectStructure(fileContent, BoardInfoFileContentReference) == false)
|
|
|
|
|
return;
|
|
|
|
|
this.showBoard(fileContent);
|
|
|
|
|
};
|
2024-07-22 21:07:44 +02:00
|
|
|
// methods
|
|
|
|
|
createBoard = () => {
|
|
|
|
|
if (this.cannotCreateBoard.value == true) return;
|
2024-07-23 00:32:29 +02:00
|
|
|
const boardInfoFileContent = this.taskModel.createBoard(this.newBoardNameInput.value);
|
2024-07-22 21:07:44 +02:00
|
|
|
this.newBoardNameInput.value = "";
|
2024-07-23 00:57:40 +02:00
|
|
|
this.showBoard(boardInfoFileContent);
|
2024-07-23 00:05:48 +02:00
|
|
|
};
|
2024-07-23 00:25:28 +02:00
|
|
|
// view
|
|
|
|
|
showBoard = (boardInfo) => {
|
2024-07-23 00:57:40 +02:00
|
|
|
const boardViewModel = new BoardViewModel(this, boardInfo);
|
2024-07-23 01:40:30 +02:00
|
|
|
this.boardViewModels.set(boardInfo.fileId, boardViewModel);
|
2024-07-23 00:25:28 +02:00
|
|
|
};
|
2024-07-23 00:05:48 +02:00
|
|
|
// load
|
|
|
|
|
loadData = () => {
|
2024-07-23 00:57:40 +02:00
|
|
|
this.boardViewModels.clear();
|
2024-07-23 00:05:48 +02:00
|
|
|
const boardIds = this.taskModel.listBoardIds();
|
|
|
|
|
for (const boardId of boardIds) {
|
|
|
|
|
const boardInfo = this.taskModel.getBoardInfo(boardId);
|
|
|
|
|
if (boardInfo == null) continue;
|
2024-07-23 00:25:28 +02:00
|
|
|
this.showBoard(boardInfo);
|
2024-07-23 00:05:48 +02:00
|
|
|
}
|
2024-07-22 21:07:44 +02:00
|
|
|
};
|
|
|
|
|
// init
|
|
|
|
|
constructor(taskModel, storageModel2) {
|
|
|
|
|
this.taskModel = taskModel;
|
2024-07-23 01:40:30 +02:00
|
|
|
taskModel.setBoardHandler((boardInfoFileContent) => {
|
|
|
|
|
this.showBoard(boardInfoFileContent);
|
|
|
|
|
});
|
2024-07-22 21:07:44 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// src/ViewModel/Chat/chatViewModel.ts
|
|
|
|
|
var ChatViewModel = class {
|
|
|
|
|
chatModel;
|
|
|
|
|
storageModel;
|
|
|
|
|
settingsViewModel;
|
|
|
|
|
chatListViewModel;
|
|
|
|
|
taskPageViewModel;
|
|
|
|
|
messagePageViewModel;
|
|
|
|
|
settingsPageViewModel;
|
|
|
|
|
// state
|
|
|
|
|
index = new State(0);
|
|
|
|
|
selectedPage = new State(
|
|
|
|
|
"messages" /* Messages */
|
|
|
|
|
);
|
|
|
|
|
// view
|
|
|
|
|
open = () => {
|
|
|
|
|
this.chatListViewModel.openChat(this);
|
|
|
|
|
};
|
|
|
|
|
close = () => {
|
|
|
|
|
this.chatListViewModel.closeChat();
|
2024-07-21 13:13:41 +02:00
|
|
|
};
|
2024-07-21 21:45:18 +02:00
|
|
|
// load
|
|
|
|
|
loadPageSelection = () => {
|
2024-07-22 03:02:33 +02:00
|
|
|
const path = StorageModel.getPath(
|
|
|
|
|
"chat",
|
|
|
|
|
filePaths.chat.lastUsedPage(this.chatModel.id)
|
|
|
|
|
);
|
2024-07-21 21:45:18 +02:00
|
|
|
const lastUsedPage = this.storageModel.read(path);
|
2024-07-20 16:34:16 +02:00
|
|
|
if (lastUsedPage != null) {
|
|
|
|
|
this.selectedPage.value = lastUsedPage;
|
|
|
|
|
}
|
|
|
|
|
this.selectedPage.subscribeSilent((newPage) => {
|
2024-07-21 21:45:18 +02:00
|
|
|
this.storageModel.write(path, newPage);
|
2024-07-20 16:34:16 +02:00
|
|
|
});
|
|
|
|
|
};
|
2024-07-20 14:16:26 +02:00
|
|
|
// init
|
2024-07-20 21:10:06 +02:00
|
|
|
constructor(chatModel, storageModel2, settingsViewModel2, chatListViewModel2) {
|
2024-07-20 14:16:26 +02:00
|
|
|
this.chatModel = chatModel;
|
2024-07-20 20:42:43 +02:00
|
|
|
this.storageModel = storageModel2;
|
2024-07-20 21:10:06 +02:00
|
|
|
this.settingsViewModel = settingsViewModel2;
|
2024-07-20 14:16:26 +02:00
|
|
|
this.chatListViewModel = chatListViewModel2;
|
2024-07-22 21:07:44 +02:00
|
|
|
this.taskPageViewModel = new TaskPageViewModel(
|
|
|
|
|
this.chatModel.fileModel.taskModel,
|
|
|
|
|
this.storageModel
|
|
|
|
|
);
|
|
|
|
|
this.messagePageViewModel = new MessagePageViewModel(this);
|
|
|
|
|
this.settingsPageViewModel = new SettingsPageViewModel(this);
|
2024-07-21 01:12:35 +02:00
|
|
|
chatModel.setMessageHandler((chatMessage) => {
|
2024-07-23 00:25:28 +02:00
|
|
|
this.messagePageViewModel.showChatMessage(chatMessage);
|
2024-07-21 01:12:35 +02:00
|
|
|
});
|
2024-07-21 21:45:18 +02:00
|
|
|
this.loadPageSelection();
|
2024-07-20 14:16:26 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-22 21:07:44 +02:00
|
|
|
// src/ViewModel/Chat/chatListViewModel.ts
|
2024-07-20 05:10:50 +02:00
|
|
|
var ChatListViewModel = class {
|
|
|
|
|
chatListModel;
|
2024-07-20 20:42:43 +02:00
|
|
|
storageModel;
|
2024-07-20 21:10:06 +02:00
|
|
|
settingsViewModel;
|
2024-07-20 05:10:50 +02:00
|
|
|
// state
|
|
|
|
|
newChatPrimaryChannel = new State("");
|
2024-07-20 14:16:26 +02:00
|
|
|
chatViewModels = new ListState();
|
|
|
|
|
selectedChat = new State(void 0);
|
2024-07-20 05:10:50 +02:00
|
|
|
// guards
|
|
|
|
|
cannotCreateChat = createProxyState(
|
|
|
|
|
[this.newChatPrimaryChannel],
|
|
|
|
|
() => this.newChatPrimaryChannel.value == ""
|
|
|
|
|
);
|
|
|
|
|
// methods
|
|
|
|
|
createChat = () => {
|
|
|
|
|
const chatModel = this.chatListModel.createChat(
|
|
|
|
|
this.newChatPrimaryChannel.value
|
|
|
|
|
);
|
|
|
|
|
this.newChatPrimaryChannel.value = "";
|
2024-07-20 20:42:43 +02:00
|
|
|
const chatViewModel = this.createChatViewModel(chatModel);
|
2024-07-20 14:16:26 +02:00
|
|
|
this.chatViewModels.add(chatViewModel);
|
2024-07-20 05:10:50 +02:00
|
|
|
};
|
2024-07-20 14:16:26 +02:00
|
|
|
untrackChat = (chatViewModel) => {
|
|
|
|
|
this.chatListModel.untrackChat(chatViewModel.chatModel);
|
|
|
|
|
this.chatViewModels.remove(chatViewModel);
|
|
|
|
|
};
|
2024-07-20 20:42:43 +02:00
|
|
|
createChatViewModel = (chatModel) => {
|
2024-07-20 21:10:06 +02:00
|
|
|
return new ChatViewModel(
|
|
|
|
|
chatModel,
|
|
|
|
|
this.storageModel,
|
|
|
|
|
this.settingsViewModel,
|
|
|
|
|
this
|
|
|
|
|
);
|
2024-07-20 20:42:43 +02:00
|
|
|
};
|
|
|
|
|
// view
|
2024-07-20 14:16:26 +02:00
|
|
|
openChat = (chatViewModel) => {
|
|
|
|
|
this.selectedChat.value = chatViewModel;
|
|
|
|
|
};
|
|
|
|
|
closeChat = () => {
|
|
|
|
|
this.selectedChat.value = void 0;
|
2024-07-20 05:10:50 +02:00
|
|
|
};
|
2024-07-21 21:45:18 +02:00
|
|
|
// load
|
|
|
|
|
loadChats = () => {
|
2024-07-20 20:48:13 +02:00
|
|
|
this.chatViewModels.clear();
|
2024-07-20 14:16:26 +02:00
|
|
|
for (const chatModel of this.chatListModel.chatModels.values()) {
|
2024-07-20 20:42:43 +02:00
|
|
|
const chatViewModel = this.createChatViewModel(chatModel);
|
2024-07-20 14:16:26 +02:00
|
|
|
this.chatViewModels.add(chatViewModel);
|
2024-07-20 05:10:50 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// init
|
2024-07-20 21:10:06 +02:00
|
|
|
constructor(chatListModel2, storageModel2, settingsViewModel2) {
|
2024-07-20 20:42:43 +02:00
|
|
|
this.chatListModel = chatListModel2;
|
2024-07-20 14:16:26 +02:00
|
|
|
this.storageModel = storageModel2;
|
2024-07-20 21:10:06 +02:00
|
|
|
this.settingsViewModel = settingsViewModel2;
|
2024-07-21 21:45:18 +02:00
|
|
|
this.loadChats();
|
2024-07-20 05:10:50 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-20 16:28:00 +02:00
|
|
|
// 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 /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "ghost",
|
|
|
|
|
"aria-label": label,
|
|
|
|
|
"toggle:selected": isSelected,
|
|
|
|
|
"on:click": select
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, icon)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-20 02:30:45 +02:00
|
|
|
// src/View/translations.ts
|
|
|
|
|
var englishTranslations = {
|
|
|
|
|
general: {
|
2024-07-21 13:13:41 +02:00
|
|
|
closeButton: "Close",
|
2024-07-20 17:30:36 +02:00
|
|
|
deleteItemButtonAudioLabel: "delete item",
|
2024-07-21 20:21:32 +02:00
|
|
|
abortButton: "Abort",
|
|
|
|
|
confirmButton: "Confirm",
|
2024-07-20 17:30:36 +02:00
|
|
|
setButton: "Set"
|
2024-07-20 02:30:45 +02:00
|
|
|
},
|
|
|
|
|
regional: {
|
|
|
|
|
weekdays: {
|
|
|
|
|
full: [
|
|
|
|
|
"Sunday",
|
|
|
|
|
"Monday",
|
|
|
|
|
"Tuesday",
|
|
|
|
|
"Wednesday",
|
|
|
|
|
"Thursday",
|
|
|
|
|
"Friday",
|
|
|
|
|
"Saturday"
|
|
|
|
|
],
|
2024-07-20 16:28:00 +02:00
|
|
|
abbreviated: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
|
2024-07-20 02:30:45 +02:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
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",
|
2024-07-20 17:30:36 +02:00
|
|
|
setNameButtonAudioLabel: "set name",
|
2024-07-20 02:30:45 +02:00
|
|
|
firstDayOfWeekLabel: "First day of week",
|
2024-07-21 17:55:53 +02:00
|
|
|
manageStorageButton: "Manage storage",
|
2024-07-20 02:30:45 +02:00
|
|
|
scrollToChatButton: "Chats",
|
|
|
|
|
///
|
|
|
|
|
backToOverviewAudioLabel: "go back to overview",
|
|
|
|
|
chatsHeadline: "Chats",
|
|
|
|
|
addChatAudioLabel: "name of new chat",
|
|
|
|
|
addChatPlaceholder: "Add chat",
|
|
|
|
|
addChatButton: "Add chat"
|
|
|
|
|
},
|
2024-07-20 02:56:44 +02:00
|
|
|
connectionModal: {
|
2024-07-20 03:39:47 +02:00
|
|
|
connectionModalHeadline: "Manage Connections",
|
2024-07-20 02:30:45 +02:00
|
|
|
///
|
2024-07-20 03:39:47 +02:00
|
|
|
connectButtonAudioLabel: "connect"
|
2024-07-20 14:16:26 +02:00
|
|
|
},
|
2024-07-21 20:21:32 +02:00
|
|
|
storage: {
|
2024-07-21 19:44:10 +02:00
|
|
|
noItemSelected: "No item selected",
|
|
|
|
|
notAFile: "(not a file)",
|
|
|
|
|
contentEmpty: "(empty)",
|
|
|
|
|
path: "Path",
|
2024-07-21 20:21:32 +02:00
|
|
|
content: "Content",
|
|
|
|
|
deleteItem: "Delete item"
|
2024-07-21 19:44:10 +02:00
|
|
|
},
|
2024-07-20 15:49:58 +02:00
|
|
|
chatPage: {
|
|
|
|
|
closeChatAudioLabe: "close chat",
|
2024-07-20 16:28:00 +02:00
|
|
|
chatSettingsAudioLabel: "chat settings",
|
|
|
|
|
pages: {
|
|
|
|
|
settings: "Settings",
|
|
|
|
|
messages: "Messages",
|
2024-07-22 13:17:21 +02:00
|
|
|
tasks: "Tasks",
|
|
|
|
|
calendar: "Calendar"
|
2024-07-20 17:19:10 +02:00
|
|
|
},
|
|
|
|
|
settings: {
|
2024-07-20 17:30:36 +02:00
|
|
|
settingsHeadline: "Settings",
|
|
|
|
|
primaryChannelLabel: "Primary channel",
|
2024-07-20 18:49:05 +02:00
|
|
|
setPrimaryChannelButtonAudioLabel: "set primary channel",
|
|
|
|
|
newSecondaryChannelPlaceholder: "Add secondary channel",
|
|
|
|
|
newSecondaryChannelAudioLabel: "name of new secondary channel",
|
2024-07-20 19:18:19 +02:00
|
|
|
addSecondaryChannelButtonAudioLabel: "add secondary channel",
|
|
|
|
|
encryptionKeyLabel: "Encryption key",
|
|
|
|
|
setEncryptionKeyButtonAudioLabel: "set encryption key",
|
2024-07-20 20:48:13 +02:00
|
|
|
showEncryptionKey: "Show encryption key",
|
|
|
|
|
deleteChatButton: "Delete entire chat"
|
2024-07-20 21:15:06 +02:00
|
|
|
},
|
|
|
|
|
message: {
|
2024-07-22 21:07:44 +02:00
|
|
|
messagesHeadline: "Messages",
|
2024-07-20 21:15:06 +02:00
|
|
|
composerInputPlaceholder: "Type a message...",
|
2024-07-21 13:13:41 +02:00
|
|
|
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"
|
2024-07-22 19:50:57 +02:00
|
|
|
},
|
|
|
|
|
task: {
|
|
|
|
|
newBoardNamePlaceholder: "Create a board",
|
|
|
|
|
createBoardButtonAudioLabel: "create board"
|
2024-07-20 16:28:00 +02:00
|
|
|
}
|
2024-07-20 15:49:58 +02:00
|
|
|
}
|
2024-07-20 02:30:45 +02:00
|
|
|
};
|
|
|
|
|
var allTranslations = {
|
|
|
|
|
en: englishTranslations
|
|
|
|
|
};
|
|
|
|
|
var language = navigator.language.substring(0, 2);
|
|
|
|
|
var translations = allTranslations[language] || allTranslations.en;
|
|
|
|
|
|
2024-07-21 13:13:41 +02:00
|
|
|
// src/View/Components/chatMessageInfoModal.tsx
|
|
|
|
|
function ChatMessageInfoModal(chatMessageViewModel, isOpen) {
|
|
|
|
|
function closeModal() {
|
|
|
|
|
isOpen.value = false;
|
|
|
|
|
}
|
|
|
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isOpen }, /* @__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": closeModal,
|
|
|
|
|
"aria-label": translations.general.closeButton
|
|
|
|
|
},
|
|
|
|
|
translations.general.closeButton,
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "close")
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Components/chatMessage.tsx
|
|
|
|
|
function ChatMessage(chatMessageViewModel) {
|
|
|
|
|
const isInfoModalOpen = new State(false);
|
|
|
|
|
function openModal() {
|
|
|
|
|
isInfoModalOpen.value = true;
|
|
|
|
|
}
|
2024-07-21 14:15:26 +02:00
|
|
|
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";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
2024-07-21 13:13:41 +02:00
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "message-bubble",
|
|
|
|
|
"toggle:sentbyuser": chatMessageViewModel.sentByUser
|
|
|
|
|
},
|
2024-07-22 00:30:43 +02:00
|
|
|
/* @__PURE__ */ createElement("div", { class: "main tile" }, /* @__PURE__ */ createElement("div", { class: "text-container" }, /* @__PURE__ */ createElement("span", { class: "sender-name ellipsis" }, chatMessageViewModel.sender), /* @__PURE__ */ createElement(
|
2024-07-21 13:13:41 +02:00
|
|
|
"span",
|
|
|
|
|
{
|
|
|
|
|
class: "body",
|
|
|
|
|
"subscribe:innerText": chatMessageViewModel.body
|
|
|
|
|
}
|
2024-07-21 14:15:26 +02:00
|
|
|
), /* @__PURE__ */ createElement("span", { class: "timestamp ellipsis" }, /* @__PURE__ */ createElement("span", { class: "icon", "subscribe:innerText": statusIcon }), chatMessageViewModel.dateSent)), /* @__PURE__ */ createElement("div", { class: "button-container" }, /* @__PURE__ */ createElement(
|
2024-07-21 13:13:41 +02:00
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
"on:click": openModal,
|
|
|
|
|
"aria-label": translations.chatPage.message.showMessageInfoButtonAudioLabel
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "info")
|
|
|
|
|
))),
|
|
|
|
|
ChatMessageInfoModal(chatMessageViewModel, isInfoModalOpen)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
var ChatMessageViewModelToView = (chatMessageViewModel) => {
|
|
|
|
|
return ChatMessage(chatMessageViewModel);
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-20 21:15:06 +02:00
|
|
|
// src/View/ChatPages/messagePage.tsx
|
2024-07-22 21:07:44 +02:00
|
|
|
function MessagePage(messagePageViewModel) {
|
2024-07-23 00:05:48 +02:00
|
|
|
messagePageViewModel.loadData();
|
2024-07-21 01:12:35 +02:00
|
|
|
const messageContainer = /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
id: "message-container",
|
|
|
|
|
"children:append": [
|
2024-07-22 21:07:44 +02:00
|
|
|
messagePageViewModel.chatMessageViewModels,
|
2024-07-21 01:12:35 +02:00
|
|
|
ChatMessageViewModelToView
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
function scrollDown() {
|
|
|
|
|
messageContainer.scrollTop = messageContainer.scrollHeight;
|
|
|
|
|
}
|
|
|
|
|
function scrollDownIfApplicable() {
|
|
|
|
|
const scrollFromBottom = messageContainer.scrollHeight - (messageContainer.scrollTop + messageContainer.offsetHeight);
|
|
|
|
|
if (scrollFromBottom > 400) return;
|
|
|
|
|
scrollDown();
|
|
|
|
|
}
|
2024-07-22 21:07:44 +02:00
|
|
|
messagePageViewModel.chatMessageViewModels.subscribeSilent(
|
|
|
|
|
scrollDownIfApplicable
|
|
|
|
|
);
|
2024-07-21 01:12:35 +02:00
|
|
|
setTimeout(() => scrollDown(), 100);
|
2024-07-22 21:07:44 +02:00
|
|
|
return /* @__PURE__ */ createElement("div", { id: "message-page" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", null, 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(
|
2024-07-20 21:15:06 +02:00
|
|
|
"input",
|
|
|
|
|
{
|
2024-07-22 21:07:44 +02:00
|
|
|
"bind:value": messagePageViewModel.composingMessage,
|
|
|
|
|
"on:enter": messagePageViewModel.sendMessage,
|
2024-07-20 21:15:06 +02:00
|
|
|
placeholder: translations.chatPage.message.composerInputPlaceholder
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary",
|
|
|
|
|
"aria-label": translations.chatPage.message.sendMessageButtonAudioLabel,
|
2024-07-22 21:07:44 +02:00
|
|
|
"on:click": messagePageViewModel.sendMessage,
|
|
|
|
|
"toggle:disabled": messagePageViewModel.cannotSendMessage
|
2024-07-20 21:15:06 +02:00
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "send")
|
2024-07-22 13:29:08 +02:00
|
|
|
)))))));
|
2024-07-20 21:15:06 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-21 20:21:32 +02:00
|
|
|
// 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")
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-20 18:49:05 +02:00
|
|
|
// src/View/Components/deletableListItem.tsx
|
|
|
|
|
function DeletableListItem(text, primaryButton, ondelete) {
|
2024-07-20 22:53:59 +02:00
|
|
|
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(
|
2024-07-20 18:49:05 +02:00
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "danger",
|
|
|
|
|
"aria-label": translations.general.deleteItemButtonAudioLabel,
|
|
|
|
|
"on:click": ondelete
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "delete")
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-20 17:19:10 +02:00
|
|
|
// src/View/ChatPages/settingsPage.tsx
|
2024-07-22 21:07:44 +02:00
|
|
|
function SettingsPage(settingsPageViewModel) {
|
|
|
|
|
settingsPageViewModel.loadData();
|
2024-07-20 18:49:05 +02:00
|
|
|
const secondaryChannelConverter = (secondaryChannel) => {
|
|
|
|
|
return DeletableListItem(secondaryChannel, /* @__PURE__ */ createElement("span", null), () => {
|
2024-07-22 21:07:44 +02:00
|
|
|
settingsPageViewModel.removeSecondaryChannel(secondaryChannel);
|
2024-07-20 18:49:05 +02:00
|
|
|
});
|
|
|
|
|
};
|
2024-07-22 13:29:08 +02:00
|
|
|
return /* @__PURE__ */ createElement("div", { id: "settings-page" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", null, 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(
|
2024-07-20 17:30:36 +02:00
|
|
|
"input",
|
|
|
|
|
{
|
2024-07-22 21:07:44 +02:00
|
|
|
"bind:value": settingsPageViewModel.primaryChannelInput,
|
|
|
|
|
"on:enter": settingsPageViewModel.setPrimaryChannel
|
2024-07-20 17:30:36 +02:00
|
|
|
}
|
|
|
|
|
))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end width-input" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "width-50",
|
|
|
|
|
"aria-label": translations.chatPage.settings.setPrimaryChannelButtonAudioLabel,
|
2024-07-22 21:07:44 +02:00
|
|
|
"on:click": settingsPageViewModel.setPrimaryChannel,
|
|
|
|
|
"toggle:disabled": settingsPageViewModel.cannotSetPrimaryChannel
|
2024-07-20 17:30:36 +02:00
|
|
|
},
|
|
|
|
|
translations.general.setButton,
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "check")
|
2024-07-20 18:49:05 +02:00
|
|
|
)), /* @__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,
|
2024-07-22 21:07:44 +02:00
|
|
|
"bind:value": settingsPageViewModel.newSecondaryChannelInput,
|
|
|
|
|
"on:enter": settingsPageViewModel.addSecondaryChannel
|
2024-07-20 18:49:05 +02:00
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
2024-07-20 18:10:42 +02:00
|
|
|
{
|
2024-07-20 18:49:05 +02:00
|
|
|
class: "primary",
|
|
|
|
|
"aria-label": translations.chatPage.settings.addSecondaryChannelButtonAudioLabel,
|
2024-07-22 21:07:44 +02:00
|
|
|
"on:click": settingsPageViewModel.addSecondaryChannel,
|
|
|
|
|
"toggle:disabled": settingsPageViewModel.cannotAddSecondaryChannel
|
2024-07-20 18:10:42 +02:00
|
|
|
},
|
2024-07-20 18:49:05 +02:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "add")
|
|
|
|
|
)), /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "flex-column gap width-input",
|
|
|
|
|
"children:append": [
|
2024-07-22 21:07:44 +02:00
|
|
|
settingsPageViewModel.secondaryChannels,
|
2024-07-20 18:49:05 +02:00
|
|
|
secondaryChannelConverter
|
|
|
|
|
]
|
|
|
|
|
}
|
2024-07-20 19:18:19 +02:00
|
|
|
), /* @__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",
|
|
|
|
|
{
|
2024-07-22 21:07:44 +02:00
|
|
|
"bind:value": settingsPageViewModel.encryptionKeyInput,
|
|
|
|
|
"on:enter": settingsPageViewModel.setEncryptionKey,
|
|
|
|
|
"set:type": settingsPageViewModel.encryptionKeyInputType
|
2024-07-20 19:18:19 +02:00
|
|
|
}
|
|
|
|
|
))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end width-input" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "width-50",
|
|
|
|
|
"aria-label": translations.chatPage.settings.setEncryptionKeyButtonAudioLabel,
|
2024-07-22 21:07:44 +02:00
|
|
|
"on:click": settingsPageViewModel.setEncryptionKey,
|
|
|
|
|
"toggle:disabled": settingsPageViewModel.cannotSetEncryptionKey
|
2024-07-20 19:18:19 +02:00
|
|
|
},
|
|
|
|
|
translations.general.setButton,
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "check")
|
|
|
|
|
)), /* @__PURE__ */ createElement("label", { class: "inline" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
|
|
|
|
type: "checkbox",
|
2024-07-22 21:07:44 +02:00
|
|
|
"bind:checked": settingsPageViewModel.shouldShowEncryptionKey
|
2024-07-20 19:18:19 +02:00
|
|
|
}
|
|
|
|
|
), translations.chatPage.settings.showEncryptionKey), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-row gap width-input" }, ...Object.values(Color).map((color) => {
|
2024-07-20 18:49:05 +02:00
|
|
|
const isSelected = createProxyState(
|
2024-07-22 21:07:44 +02:00
|
|
|
[settingsPageViewModel.color],
|
|
|
|
|
() => settingsPageViewModel.color.value == color
|
2024-07-20 18:49:05 +02:00
|
|
|
);
|
|
|
|
|
function setColor() {
|
2024-07-22 21:07:44 +02:00
|
|
|
settingsPageViewModel.setColor(color);
|
2024-07-20 18:49:05 +02:00
|
|
|
}
|
|
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
color,
|
|
|
|
|
class: "fill-color width-100 flex",
|
|
|
|
|
style: "height: 2rem",
|
|
|
|
|
"toggle:selected": isSelected,
|
|
|
|
|
"on:click": setColor
|
2024-07-20 18:10:42 +02:00
|
|
|
}
|
2024-07-20 18:49:05 +02:00
|
|
|
);
|
2024-07-21 20:21:32 +02:00
|
|
|
})), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton(
|
2024-07-20 23:22:08 +02:00
|
|
|
translations.chatPage.settings.deleteChatButton,
|
2024-07-21 20:21:32 +02:00
|
|
|
"chat_error",
|
2024-07-22 21:07:44 +02:00
|
|
|
settingsPageViewModel.remove
|
2024-07-22 13:29:08 +02:00
|
|
|
)))));
|
2024-07-20 17:19:10 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-23 00:05:48 +02:00
|
|
|
// src/View/Components/boardEntry.tsx
|
2024-07-23 00:57:40 +02:00
|
|
|
function BoardEntry(boardViewModel) {
|
2024-07-23 00:09:39 +02:00
|
|
|
const view = /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
2024-07-23 00:57:40 +02:00
|
|
|
"set:color": boardViewModel.color,
|
2024-07-23 00:09:39 +02:00
|
|
|
class: "tile colored-tile"
|
|
|
|
|
},
|
2024-07-23 00:57:40 +02:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "shadow", "subscribe:innerText": boardViewModel.name }),
|
|
|
|
|
/* @__PURE__ */ createElement("b", { "subscribe:innerText": boardViewModel.name })
|
2024-07-23 00:09:39 +02:00
|
|
|
);
|
2024-07-23 00:05:48 +02:00
|
|
|
return view;
|
|
|
|
|
}
|
2024-07-23 00:57:40 +02:00
|
|
|
var BoardInfoToEntry = (boardViewModel) => {
|
|
|
|
|
return BoardEntry(boardViewModel);
|
2024-07-23 00:05:48 +02:00
|
|
|
};
|
|
|
|
|
|
2024-07-22 13:17:21 +02:00
|
|
|
// src/View/ChatPages/taskPage.tsx
|
2024-07-22 21:07:44 +02:00
|
|
|
function TaskPage(taskPageViewModel) {
|
2024-07-23 00:05:48 +02:00
|
|
|
taskPageViewModel.loadData();
|
2024-07-22 19:50:57 +02:00
|
|
|
return /* @__PURE__ */ createElement("div", { id: "task-page" }, /* @__PURE__ */ createElement("div", { class: "pane side" }, /* @__PURE__ */ createElement("div", { class: "content" }, /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
2024-07-22 21:07:44 +02:00
|
|
|
"bind:value": taskPageViewModel.newBoardNameInput,
|
|
|
|
|
"on:enter": taskPageViewModel.createBoard,
|
2024-07-22 19:50:57 +02:00
|
|
|
placeholder: translations.chatPage.task.newBoardNamePlaceholder
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary",
|
2024-07-22 21:07:44 +02:00
|
|
|
"aria-label": translations.chatPage.task.createBoardButtonAudioLabel,
|
|
|
|
|
"on:click": taskPageViewModel.createBoard,
|
|
|
|
|
"toggle:disabled": taskPageViewModel.cannotCreateBoard
|
2024-07-22 19:50:57 +02:00
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "add")
|
2024-07-23 00:05:48 +02:00
|
|
|
)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "grid gap",
|
|
|
|
|
style: "grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr))",
|
2024-07-23 00:57:40 +02:00
|
|
|
"children:append": [taskPageViewModel.boardViewModels, BoardInfoToEntry]
|
2024-07-23 00:05:48 +02:00
|
|
|
}
|
|
|
|
|
))), /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }), /* @__PURE__ */ createElement("div", { class: "content" })));
|
2024-07-22 13:17:21 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-20 15:49:58 +02:00
|
|
|
// src/View/chatPage.tsx
|
|
|
|
|
function ChatPage(chatViewModel) {
|
2024-07-20 17:19:10 +02:00
|
|
|
const mainContent = new State(/* @__PURE__ */ createElement("div", null));
|
|
|
|
|
chatViewModel.selectedPage.subscribe((selectedPage) => {
|
|
|
|
|
switch (selectedPage) {
|
2024-07-22 21:07:44 +02:00
|
|
|
case "settings" /* Settings */: {
|
|
|
|
|
mainContent.value = SettingsPage(chatViewModel.settingsPageViewModel);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "tasks" /* Tasks */: {
|
|
|
|
|
mainContent.value = TaskPage(chatViewModel.taskPageViewModel);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
mainContent.value = MessagePage(chatViewModel.messagePageViewModel);
|
|
|
|
|
}
|
2024-07-20 17:19:10 +02:00
|
|
|
}
|
|
|
|
|
});
|
2024-07-22 13:17:21 +02:00
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"article",
|
2024-07-20 15:49:58 +02:00
|
|
|
{
|
2024-07-22 13:17:21 +02:00
|
|
|
id: "chat-page",
|
2024-07-22 21:07:44 +02:00
|
|
|
"set:color": chatViewModel.settingsPageViewModel.color,
|
2024-07-22 13:17:21 +02:00
|
|
|
class: "subtle-background"
|
2024-07-20 15:49:58 +02:00
|
|
|
},
|
2024-07-22 13:17:21 +02:00
|
|
|
/* @__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("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 }))
|
|
|
|
|
);
|
2024-07-20 15:49:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-20 02:30:45 +02:00
|
|
|
// src/View/Modals/connectionModal.tsx
|
|
|
|
|
function ConnectionModal(connectionViewModel2) {
|
2024-07-20 03:34:54 +02:00
|
|
|
const previousAddressConverter = (address) => {
|
2024-07-20 03:39:47 +02:00
|
|
|
function connnect() {
|
|
|
|
|
connectionViewModel2.connectToAddress(address);
|
|
|
|
|
}
|
|
|
|
|
const cannotConnect = createProxyState(
|
|
|
|
|
[connectionViewModel2.isConnected],
|
2024-07-20 22:44:23 +02:00
|
|
|
() => connectionViewModel2.isConnected.value == true && connectionViewModel2.connectionModel.address == address
|
2024-07-20 03:39:47 +02:00
|
|
|
);
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
);
|
2024-07-20 03:34:54 +02:00
|
|
|
};
|
2024-07-20 02:30:45 +02:00
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "modal",
|
|
|
|
|
"toggle:open": connectionViewModel2.isShowingConnectionModal
|
|
|
|
|
},
|
2024-07-20 03:34:54 +02:00
|
|
|
/* @__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
|
|
|
|
|
]
|
|
|
|
|
}
|
2024-07-21 13:13:41 +02:00
|
|
|
)), /* @__PURE__ */ createElement("button", { "on:click": connectionViewModel2.hideConnectionModal }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))
|
2024-07-20 02:30:45 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-09 15:15:17 +02:00
|
|
|
// node_modules/udn-frontend/index.ts
|
|
|
|
|
var UDNFrontend = class {
|
|
|
|
|
ws;
|
|
|
|
|
// HANDLERS
|
|
|
|
|
connectionHandler = () => {
|
|
|
|
|
};
|
|
|
|
|
disconnectionHandler = () => {
|
|
|
|
|
};
|
|
|
|
|
messageHandler = (data) => {
|
|
|
|
|
};
|
2024-07-19 18:00:01 +02:00
|
|
|
mailboxHandler = (mailboxId) => {
|
2024-07-09 15:15:17 +02:00
|
|
|
};
|
2024-07-19 18:00:01 +02:00
|
|
|
mailboxConnectionHandler = (mailboxId) => {
|
2024-07-09 15:15:17 +02:00
|
|
|
};
|
2024-07-19 18:00:01 +02:00
|
|
|
mailboxDeleteHandler = (mailboxId) => {
|
2024-07-09 15:15:17 +02:00
|
|
|
};
|
|
|
|
|
// 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;
|
2024-07-21 14:15:26 +02:00
|
|
|
if (this.ws.readyState != 1) return false;
|
2024-07-09 15:15:17 +02:00
|
|
|
const messageString = JSON.stringify(messageObject);
|
|
|
|
|
this.ws.send(messageString);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// PUBLIC METHODS
|
|
|
|
|
// connection
|
2024-07-19 18:17:00 +02:00
|
|
|
connect(address) {
|
2024-07-09 15:15:17 +02:00
|
|
|
try {
|
|
|
|
|
this.disconnect();
|
2024-07-19 18:17:00 +02:00
|
|
|
this.ws = new WebSocket(address);
|
2024-07-09 15:15:17 +02:00
|
|
|
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.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);
|
|
|
|
|
}
|
2024-07-19 18:00:01 +02:00
|
|
|
connectMailbox(mailboxId) {
|
2024-07-09 15:15:17 +02:00
|
|
|
const messageObject = {
|
2024-07-19 18:00:01 +02:00
|
|
|
requestedMailbox: mailboxId
|
2024-07-09 15:15:17 +02:00
|
|
|
};
|
|
|
|
|
return this.send(messageObject);
|
|
|
|
|
}
|
2024-07-19 18:00:01 +02:00
|
|
|
deleteMailbox(mailboxId) {
|
2024-07-09 15:15:17 +02:00
|
|
|
const messageObject = {
|
2024-07-19 18:00:01 +02:00
|
|
|
deletingMailbox: mailboxId
|
2024-07-09 15:15:17 +02:00
|
|
|
};
|
|
|
|
|
return this.send(messageObject);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-22 02:05:27 +02:00
|
|
|
// src/Model/Global/connectionModel.ts
|
2024-07-19 18:00:01 +02:00
|
|
|
var ConnectionModel = class {
|
|
|
|
|
udn;
|
2024-07-20 02:56:44 +02:00
|
|
|
storageModel;
|
2024-07-19 18:00:01 +02:00
|
|
|
// data
|
|
|
|
|
get isConnected() {
|
|
|
|
|
return this.udn.ws != void 0 && this.udn.ws.readyState == 1;
|
2024-07-13 17:13:59 +02:00
|
|
|
}
|
2024-07-19 18:02:23 +02:00
|
|
|
get address() {
|
2024-07-19 18:00:01 +02:00
|
|
|
return this.udn.ws?.url;
|
2024-07-13 17:13:59 +02:00
|
|
|
}
|
2024-07-20 22:44:23 +02:00
|
|
|
connectionChangeHandler = () => {
|
|
|
|
|
};
|
|
|
|
|
messageHandler = () => {
|
|
|
|
|
};
|
2024-07-21 14:15:26 +02:00
|
|
|
messageSentHandler = () => {
|
|
|
|
|
};
|
2024-07-21 01:12:35 +02:00
|
|
|
channelsToSubscribe = /* @__PURE__ */ new Set();
|
2024-07-22 03:01:04 +02:00
|
|
|
// handlers
|
2024-07-20 20:42:43 +02:00
|
|
|
handleMessage = (data) => {
|
2024-07-20 22:44:23 +02:00
|
|
|
this.messageHandler(data);
|
2024-07-20 20:42:43 +02:00
|
|
|
};
|
2024-07-19 18:02:23 +02:00
|
|
|
handleConnectionChange = () => {
|
|
|
|
|
console.log("connection status:", this.isConnected, this.address);
|
2024-07-20 22:44:23 +02:00
|
|
|
this.connectionChangeHandler();
|
2024-07-20 02:56:44 +02:00
|
|
|
if (this.isConnected == false) return;
|
2024-07-21 01:12:35 +02:00
|
|
|
if (!this.address) return;
|
2024-07-21 15:15:24 +02:00
|
|
|
this.connectMailbox();
|
2024-07-21 01:12:35 +02:00
|
|
|
this.storeAddress(this.address);
|
|
|
|
|
this.sendSubscriptionRequest();
|
2024-07-20 22:44:23 +02:00
|
|
|
this.sendMessagesInOutbox();
|
2024-07-19 18:02:23 +02:00
|
|
|
};
|
2024-07-22 03:01:04 +02:00
|
|
|
// connection
|
|
|
|
|
connect = (address) => {
|
|
|
|
|
this.udn.connect(address);
|
|
|
|
|
};
|
|
|
|
|
disconnect = () => {
|
|
|
|
|
this.udn.disconnect();
|
|
|
|
|
const reconnectAddressPath = StorageModel.getPath(
|
|
|
|
|
"connectionModel",
|
|
|
|
|
filePaths.connectionModel.reconnectAddress
|
|
|
|
|
);
|
|
|
|
|
this.storageModel.remove(reconnectAddressPath);
|
|
|
|
|
};
|
2024-07-21 15:15:24 +02:00
|
|
|
// mailbox
|
2024-07-22 03:01:04 +02:00
|
|
|
getMailboxPath = (address) => {
|
|
|
|
|
const mailboxDirPath = StorageModel.getPath(
|
|
|
|
|
"connectionModel",
|
|
|
|
|
filePaths.connectionModel.mailboxes
|
|
|
|
|
);
|
|
|
|
|
const mailboxFilePath = [...mailboxDirPath, address];
|
|
|
|
|
return mailboxFilePath;
|
|
|
|
|
};
|
2024-07-21 15:15:24 +02:00
|
|
|
requestNewMailbox = () => {
|
|
|
|
|
console.trace("requesting mailbox");
|
|
|
|
|
this.udn.requestMailbox();
|
|
|
|
|
};
|
|
|
|
|
connectMailbox = () => {
|
|
|
|
|
if (this.address == void 0) return;
|
2024-07-22 03:01:04 +02:00
|
|
|
const mailboxId = this.storageModel.read(this.getMailboxPath(this.address));
|
2024-07-21 15:15:24 +02:00
|
|
|
console.log("connecting mailbox", mailboxId);
|
|
|
|
|
if (mailboxId == null) return this.requestNewMailbox();
|
|
|
|
|
this.udn.connectMailbox(mailboxId);
|
|
|
|
|
};
|
|
|
|
|
storeMailbox = (mailboxId) => {
|
|
|
|
|
if (this.address == void 0) return;
|
2024-07-22 03:01:04 +02:00
|
|
|
this.storageModel.write(this.getMailboxPath(this.address), mailboxId);
|
2024-07-21 15:15:24 +02:00
|
|
|
};
|
2024-07-21 01:12:35 +02:00
|
|
|
// subscription
|
|
|
|
|
addChannel = (channel) => {
|
|
|
|
|
this.channelsToSubscribe.add(channel);
|
|
|
|
|
this.sendSubscriptionRequest();
|
|
|
|
|
};
|
|
|
|
|
sendSubscriptionRequest = () => {
|
2024-07-21 03:41:10 +02:00
|
|
|
if (this.isConnected == false) return;
|
2024-07-21 01:12:35 +02:00
|
|
|
for (const channel of this.channelsToSubscribe) {
|
|
|
|
|
this.udn.subscribe(channel);
|
|
|
|
|
}
|
2024-07-21 15:15:24 +02:00
|
|
|
this.connectMailbox();
|
2024-07-21 01:12:35 +02:00
|
|
|
};
|
2024-07-20 20:42:43 +02:00
|
|
|
// outbox
|
2024-07-22 03:01:04 +02:00
|
|
|
getOutboxPath = () => {
|
|
|
|
|
return StorageModel.getPath(
|
|
|
|
|
"connectionModel",
|
|
|
|
|
filePaths.connectionModel.outbox
|
|
|
|
|
);
|
|
|
|
|
};
|
2024-07-20 20:42:43 +02:00
|
|
|
getOutboxMessags = () => {
|
2024-07-22 03:01:04 +02:00
|
|
|
const outboxPath = this.getOutboxPath();
|
2024-07-20 20:42:43 +02:00
|
|
|
const messageIds = this.storageModel.list(outboxPath);
|
2024-07-22 01:40:53 +02:00
|
|
|
let chatMessages = [];
|
2024-07-20 20:42:43 +02:00
|
|
|
for (const messageId of messageIds) {
|
2024-07-22 01:40:53 +02:00
|
|
|
const chatMessage = this.storageModel.readStringifiable(
|
|
|
|
|
[...outboxPath, messageId],
|
|
|
|
|
ChatMessageReference
|
|
|
|
|
);
|
|
|
|
|
if (chatMessage == null) continue;
|
|
|
|
|
chatMessages.push(chatMessage);
|
2024-07-20 20:42:43 +02:00
|
|
|
}
|
2024-07-22 01:40:53 +02:00
|
|
|
return chatMessages;
|
2024-07-20 20:42:43 +02:00
|
|
|
};
|
|
|
|
|
addToOutbox = (chatMessage) => {
|
2024-07-22 03:01:04 +02:00
|
|
|
const messagePath = [...this.getOutboxPath(), chatMessage.id];
|
2024-07-21 21:45:18 +02:00
|
|
|
this.storageModel.writeStringifiable(messagePath, chatMessage);
|
2024-07-20 20:42:43 +02:00
|
|
|
};
|
|
|
|
|
removeFromOutbox = (chatMessage) => {
|
2024-07-22 03:01:04 +02:00
|
|
|
const messagePath = [...this.getOutboxPath(), chatMessage.id];
|
2024-07-20 20:42:43 +02:00
|
|
|
this.storageModel.remove(messagePath);
|
|
|
|
|
};
|
|
|
|
|
sendMessagesInOutbox = () => {
|
|
|
|
|
const messages = this.getOutboxMessags();
|
|
|
|
|
for (const message of messages) {
|
|
|
|
|
const isSent = this.tryToSendMessage(message);
|
|
|
|
|
if (isSent == false) return;
|
|
|
|
|
this.removeFromOutbox(message);
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-07-19 18:00:01 +02:00
|
|
|
// messaging
|
2024-07-20 20:42:43 +02:00
|
|
|
sendMessageOrStore = (chatMessage) => {
|
|
|
|
|
const isSent = this.tryToSendMessage(chatMessage);
|
|
|
|
|
if (isSent == true) return;
|
|
|
|
|
this.addToOutbox(chatMessage);
|
|
|
|
|
};
|
|
|
|
|
tryToSendMessage = (chatMessage) => {
|
2024-07-19 18:00:01 +02:00
|
|
|
const stringifiedBody = stringify(chatMessage);
|
2024-07-21 14:15:26 +02:00
|
|
|
const isSent = this.udn.sendMessage(
|
|
|
|
|
chatMessage.channel,
|
|
|
|
|
stringifiedBody
|
|
|
|
|
);
|
|
|
|
|
if (isSent) this.messageSentHandler(chatMessage);
|
|
|
|
|
return isSent;
|
2024-07-19 18:02:23 +02:00
|
|
|
};
|
2024-07-20 02:56:44 +02:00
|
|
|
// storage
|
2024-07-22 03:01:04 +02:00
|
|
|
getPreviousAddressPath = () => {
|
|
|
|
|
return StorageModel.getPath(
|
|
|
|
|
"connectionModel",
|
|
|
|
|
filePaths.connectionModel.previousAddresses
|
|
|
|
|
);
|
|
|
|
|
};
|
2024-07-20 02:56:44 +02:00
|
|
|
getAddressPath = (address) => {
|
2024-07-22 03:01:04 +02:00
|
|
|
const dirPath = this.getPreviousAddressPath();
|
2024-07-20 02:56:44 +02:00
|
|
|
return [...dirPath, address];
|
|
|
|
|
};
|
2024-07-22 03:01:04 +02:00
|
|
|
getReconnectAddressPath = () => {
|
|
|
|
|
return StorageModel.getPath(
|
|
|
|
|
"connectionModel",
|
|
|
|
|
filePaths.connectionModel.reconnectAddress
|
|
|
|
|
);
|
|
|
|
|
};
|
2024-07-20 02:56:44 +02:00
|
|
|
storeAddress = (address) => {
|
|
|
|
|
const addressPath = this.getAddressPath(address);
|
2024-07-21 21:45:18 +02:00
|
|
|
this.storageModel.write(addressPath, "");
|
2024-07-22 03:01:04 +02:00
|
|
|
const reconnectAddressPath = this.getReconnectAddressPath();
|
2024-07-21 21:45:18 +02:00
|
|
|
this.storageModel.write(reconnectAddressPath, address);
|
2024-07-20 02:56:44 +02:00
|
|
|
};
|
|
|
|
|
removeAddress = (address) => {
|
|
|
|
|
const addressPath = this.getAddressPath(address);
|
|
|
|
|
this.storageModel.remove(addressPath);
|
|
|
|
|
};
|
2024-07-20 02:59:40 +02:00
|
|
|
get addresses() {
|
2024-07-22 03:01:04 +02:00
|
|
|
const dirPath = this.getPreviousAddressPath();
|
2024-07-20 02:56:44 +02:00
|
|
|
return this.storageModel.list(dirPath);
|
2024-07-20 02:59:40 +02:00
|
|
|
}
|
2024-07-22 03:01:04 +02:00
|
|
|
// other
|
2024-07-20 22:44:23 +02:00
|
|
|
setConnectionChangeHandler = (handler) => {
|
|
|
|
|
this.connectionChangeHandler = handler;
|
|
|
|
|
};
|
|
|
|
|
setMessageHandler = (handler) => {
|
|
|
|
|
this.messageHandler = handler;
|
|
|
|
|
};
|
2024-07-21 14:15:26 +02:00
|
|
|
setMessageSentHandler = (handler) => {
|
|
|
|
|
this.messageSentHandler = handler;
|
|
|
|
|
};
|
2024-07-22 03:01:04 +02:00
|
|
|
// init
|
2024-07-20 20:42:43 +02:00
|
|
|
constructor(storageModel2) {
|
2024-07-19 18:00:01 +02:00
|
|
|
this.udn = new UDNFrontend();
|
2024-07-20 20:42:43 +02:00
|
|
|
this.storageModel = storageModel2;
|
2024-07-19 18:17:00 +02:00
|
|
|
this.udn.onmessage = (data) => {
|
2024-07-20 20:42:43 +02:00
|
|
|
this.handleMessage(data);
|
2024-07-19 18:17:00 +02:00
|
|
|
};
|
|
|
|
|
this.udn.onconnect = () => {
|
|
|
|
|
this.handleConnectionChange();
|
|
|
|
|
};
|
|
|
|
|
this.udn.ondisconnect = () => {
|
|
|
|
|
this.handleConnectionChange();
|
|
|
|
|
};
|
2024-07-21 15:15:24 +02:00
|
|
|
this.udn.onmailboxcreate = (mailboxId) => {
|
2024-07-21 15:16:05 +02:00
|
|
|
console.log("created mailbox", mailboxId);
|
2024-07-21 15:15:24 +02:00
|
|
|
this.storeMailbox(mailboxId);
|
|
|
|
|
this.connectMailbox();
|
|
|
|
|
};
|
2024-07-21 15:16:05 +02:00
|
|
|
this.udn.onmailboxdelete = (mailboxId) => {
|
|
|
|
|
console.log(`mailbox ${mailboxId} deleted`);
|
2024-07-21 15:15:24 +02:00
|
|
|
this.requestNewMailbox();
|
|
|
|
|
};
|
|
|
|
|
this.udn.onmailboxconnect = (mailboxId) => {
|
2024-07-21 15:16:05 +02:00
|
|
|
console.log(`using mailbox ${mailboxId}`);
|
2024-07-21 15:15:24 +02:00
|
|
|
};
|
2024-07-22 03:02:33 +02:00
|
|
|
const reconnectAddressPath = this.getReconnectAddressPath();
|
2024-07-21 21:45:18 +02:00
|
|
|
const reconnectAddress = storageModel2.read(reconnectAddressPath);
|
2024-07-21 03:41:10 +02:00
|
|
|
if (reconnectAddress != null) {
|
|
|
|
|
this.connect(reconnectAddress);
|
|
|
|
|
}
|
2024-07-09 17:23:29 +02:00
|
|
|
}
|
|
|
|
|
};
|
2024-07-09 17:27:15 +02:00
|
|
|
|
2024-07-22 21:07:44 +02:00
|
|
|
// src/ViewModel/Global/connectionViewModel.ts
|
2024-07-20 02:18:15 +02:00
|
|
|
var ConnectionViewModel = class {
|
|
|
|
|
connectionModel;
|
|
|
|
|
// state
|
|
|
|
|
serverAddressInput = new State("");
|
|
|
|
|
isConnected = new State(false);
|
2024-07-20 02:30:45 +02:00
|
|
|
isShowingConnectionModal = new State(false);
|
2024-07-20 02:56:44 +02:00
|
|
|
previousAddresses = new ListState();
|
2024-07-20 04:15:14 +02:00
|
|
|
// guards
|
2024-07-20 02:18:15 +02:00
|
|
|
cannotConnect = createProxyState(
|
|
|
|
|
[this.serverAddressInput, this.isConnected],
|
|
|
|
|
() => this.isConnected.value == true && this.serverAddressInput.value == this.connectionModel.address || this.serverAddressInput.value == ""
|
|
|
|
|
);
|
|
|
|
|
cannotDisonnect = createProxyState(
|
|
|
|
|
[this.isConnected],
|
|
|
|
|
() => this.isConnected.value == false
|
|
|
|
|
);
|
2024-07-20 04:15:14 +02:00
|
|
|
hasNoPreviousConnections = createProxyState(
|
|
|
|
|
[this.previousAddresses],
|
|
|
|
|
() => this.previousAddresses.value.size == 0
|
|
|
|
|
);
|
2024-07-20 02:18:15 +02:00
|
|
|
// handlers
|
|
|
|
|
connectionChangeHandler = () => {
|
|
|
|
|
this.isConnected.value = this.connectionModel.isConnected;
|
2024-07-20 02:56:44 +02:00
|
|
|
if (this.connectionModel.isConnected == false) return;
|
2024-07-20 22:53:59 +02:00
|
|
|
if (this.connectionModel.address == void 0) return;
|
|
|
|
|
this.serverAddressInput.value = this.connectionModel.address;
|
|
|
|
|
if (!this.previousAddresses.value.has(this.connectionModel.address)) {
|
2024-07-20 22:44:23 +02:00
|
|
|
this.previousAddresses.add(this.connectionModel.address);
|
2024-07-20 02:18:15 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// methods
|
|
|
|
|
connect = () => {
|
2024-07-20 03:39:47 +02:00
|
|
|
this.connectToAddress(this.serverAddressInput.value);
|
|
|
|
|
};
|
|
|
|
|
connectToAddress = (address) => {
|
|
|
|
|
this.connectionModel.connect(address);
|
2024-07-20 02:18:15 +02:00
|
|
|
};
|
|
|
|
|
disconnect = () => {
|
|
|
|
|
this.connectionModel.disconnect();
|
|
|
|
|
};
|
2024-07-20 03:34:54 +02:00
|
|
|
removePreviousAddress = (address) => {
|
|
|
|
|
this.connectionModel.removeAddress(address);
|
|
|
|
|
this.updatePreviousAddresses();
|
|
|
|
|
};
|
2024-07-22 23:33:57 +02:00
|
|
|
// view
|
2024-07-20 02:30:45 +02:00
|
|
|
showConnectionModal = () => {
|
|
|
|
|
this.isShowingConnectionModal.value = true;
|
|
|
|
|
};
|
|
|
|
|
hideConnectionModal = () => {
|
|
|
|
|
this.isShowingConnectionModal.value = false;
|
|
|
|
|
};
|
2024-07-20 02:59:40 +02:00
|
|
|
updatePreviousAddresses = () => {
|
|
|
|
|
this.previousAddresses.clear();
|
|
|
|
|
this.previousAddresses.add(...this.connectionModel.addresses);
|
|
|
|
|
};
|
2024-07-20 02:00:00 +02:00
|
|
|
// init
|
2024-07-20 20:42:43 +02:00
|
|
|
constructor(connectionModel2) {
|
|
|
|
|
this.connectionModel = connectionModel2;
|
2024-07-20 02:59:40 +02:00
|
|
|
this.updatePreviousAddresses();
|
2024-07-20 22:44:23 +02:00
|
|
|
connectionModel2.setConnectionChangeHandler(this.connectionChangeHandler);
|
2024-07-20 02:00:00 +02:00
|
|
|
}
|
|
|
|
|
};
|
2024-07-19 23:45:07 +02:00
|
|
|
|
2024-07-20 02:00:00 +02:00
|
|
|
// src/View/Components/option.tsx
|
|
|
|
|
function Option(text, value, selectedOnCreate) {
|
|
|
|
|
return /* @__PURE__ */ createElement("option", { value, "toggle:selected": selectedOnCreate }, text);
|
|
|
|
|
}
|
2024-07-20 02:56:44 +02:00
|
|
|
var StringToOption = (string) => {
|
|
|
|
|
return Option(string, string, false);
|
|
|
|
|
};
|
2024-07-20 02:00:00 +02:00
|
|
|
|
2024-07-20 05:10:50 +02:00
|
|
|
// src/View/Components/chatEntry.tsx
|
2024-07-20 14:16:26 +02:00
|
|
|
function ChatEntry(chatViewModel) {
|
2024-07-22 21:07:44 +02:00
|
|
|
const view = /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
2024-07-20 14:16:26 +02:00
|
|
|
{
|
2024-07-22 21:07:44 +02:00
|
|
|
"set:color": chatViewModel.settingsPageViewModel.color,
|
2024-07-23 00:09:39 +02:00
|
|
|
class: "tile colored-tile",
|
|
|
|
|
style: "height: 8rem",
|
2024-07-22 21:07:44 +02:00
|
|
|
"on:click": chatViewModel.open
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement(
|
|
|
|
|
"span",
|
|
|
|
|
{
|
|
|
|
|
class: "shadow",
|
|
|
|
|
"subscribe:innerText": chatViewModel.settingsPageViewModel.primaryChannel
|
|
|
|
|
}
|
|
|
|
|
),
|
|
|
|
|
/* @__PURE__ */ createElement(
|
|
|
|
|
"h2",
|
|
|
|
|
{
|
|
|
|
|
"subscribe:innerText": chatViewModel.settingsPageViewModel.primaryChannel
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
);
|
2024-07-20 16:49:01 +02:00
|
|
|
chatViewModel.index.subscribe((newIndex) => {
|
|
|
|
|
view.style.order = newIndex;
|
|
|
|
|
});
|
|
|
|
|
return view;
|
2024-07-20 05:10:50 +02:00
|
|
|
}
|
2024-07-20 14:16:26 +02:00
|
|
|
var ChatViewModelToChatEntry = (chatViewModel) => {
|
|
|
|
|
return ChatEntry(chatViewModel);
|
2024-07-20 05:10:50 +02:00
|
|
|
};
|
|
|
|
|
|
2024-07-19 23:45:07 +02:00
|
|
|
// src/View/homePage.tsx
|
2024-07-21 17:55:53 +02:00
|
|
|
function HomePage(storageViewModel2, settingsViewModel2, connectionViewModel2, chatListViewModel2) {
|
2024-07-20 02:18:15 +02:00
|
|
|
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",
|
|
|
|
|
{
|
2024-07-20 02:56:44 +02:00
|
|
|
list: "previous-connection-list",
|
2024-07-20 02:20:09 +02:00
|
|
|
placeholder: translations.homePage.serverAddressPlaceholder,
|
2024-07-20 02:18:15 +02:00
|
|
|
"bind:value": connectionViewModel2.serverAddressInput,
|
|
|
|
|
"on:enter": connectionViewModel2.connect
|
|
|
|
|
}
|
2024-07-20 02:56:44 +02:00
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"datalist",
|
|
|
|
|
{
|
|
|
|
|
hidden: true,
|
|
|
|
|
id: "previous-connection-list",
|
|
|
|
|
"children:append": [
|
|
|
|
|
connectionViewModel2.previousAddresses,
|
|
|
|
|
StringToOption
|
|
|
|
|
]
|
|
|
|
|
}
|
2024-07-20 02:18:15 +02:00
|
|
|
))), /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement(
|
2024-07-20 01:14:21 +02:00
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "danger flex justify-center",
|
2024-07-20 02:18:15 +02:00
|
|
|
"aria-label": translations.homePage.disconnectAudioLabel,
|
|
|
|
|
"on:click": connectionViewModel2.disconnect,
|
|
|
|
|
"toggle:disabled": connectionViewModel2.cannotDisonnect
|
2024-07-20 01:14:21 +02:00
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "link_off")
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "flex justify-center",
|
2024-07-20 02:30:45 +02:00
|
|
|
"aria-label": translations.homePage.manageConnectionsAudioLabel,
|
2024-07-20 04:15:14 +02:00
|
|
|
"on:click": connectionViewModel2.showConnectionModal,
|
|
|
|
|
"toggle:disabled": connectionViewModel2.hasNoPreviousConnections
|
2024-07-20 01:14:21 +02:00
|
|
|
},
|
2024-07-20 04:13:43 +02:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "history")
|
2024-07-20 01:14:21 +02:00
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary flex justify-center",
|
2024-07-20 02:18:15 +02:00
|
|
|
"aria-label": translations.homePage.connectAudioLabel,
|
|
|
|
|
"on:click": connectionViewModel2.connect,
|
|
|
|
|
"toggle:disabled": connectionViewModel2.cannotConnect
|
2024-07-20 01:14:21 +02:00
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "link")
|
2024-07-21 15:16:05 +02:00
|
|
|
)), /* @__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(
|
2024-07-20 02:00:00 +02:00
|
|
|
"input",
|
|
|
|
|
{
|
2024-07-20 02:20:09 +02:00
|
|
|
placeholder: translations.homePage.yourNamePlaceholder,
|
2024-07-20 21:10:06 +02:00
|
|
|
"bind:value": settingsViewModel2.usernameInput,
|
2024-07-20 02:00:00 +02:00
|
|
|
"on:enter": settingsViewModel2.setName
|
|
|
|
|
}
|
|
|
|
|
))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "width-50",
|
|
|
|
|
"on:click": settingsViewModel2.setName,
|
2024-07-20 17:30:36 +02:00
|
|
|
"toggle:disabled": settingsViewModel2.cannotSetName,
|
|
|
|
|
"aria-label": translations.homePage.setNameButtonAudioLabel
|
2024-07-20 02:00:00 +02:00
|
|
|
},
|
2024-07-20 17:30:36 +02:00
|
|
|
translations.general.setButton,
|
2024-07-20 02:00:00 +02:00
|
|
|
/* @__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(),
|
2024-07-22 19:50:57 +02:00
|
|
|
i.toString() == settingsViewModel2.firstDayOfWeekInput.value
|
2024-07-20 02:00:00 +02:00
|
|
|
)
|
2024-07-21 17:55:53 +02:00
|
|
|
)), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))), /* @__PURE__ */ createElement("hr", null), /* @__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")))));
|
2024-07-20 00:15:05 +02:00
|
|
|
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(
|
2024-07-19 23:45:07 +02:00
|
|
|
"input",
|
|
|
|
|
{
|
|
|
|
|
placeholder: translations.homePage.addChatPlaceholder,
|
2024-07-20 05:10:50 +02:00
|
|
|
"aria-label": translations.homePage.addChatAudioLabel,
|
|
|
|
|
"bind:value": chatListViewModel2.newChatPrimaryChannel,
|
|
|
|
|
"on:enter": chatListViewModel2.createChat
|
2024-07-19 23:45:07 +02:00
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary",
|
2024-07-20 05:10:50 +02:00
|
|
|
"aria-label": translations.homePage.addChatButton,
|
|
|
|
|
"on:click": chatListViewModel2.createChat,
|
|
|
|
|
"toggle:disabled": chatListViewModel2.cannotCreateChat
|
2024-07-19 23:45:07 +02:00
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "add")
|
2024-07-20 05:10:50 +02:00
|
|
|
)), /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
2024-07-20 13:30:01 +02:00
|
|
|
id: "chat-grid",
|
2024-07-21 17:55:53 +02:00
|
|
|
"children:append": [
|
|
|
|
|
chatListViewModel2.chatViewModels,
|
|
|
|
|
ChatViewModelToChatEntry
|
|
|
|
|
]
|
2024-07-20 05:10:50 +02:00
|
|
|
}
|
|
|
|
|
));
|
2024-07-20 00:15:05 +02:00
|
|
|
function scrollToChat() {
|
|
|
|
|
chatSection.scrollIntoView();
|
|
|
|
|
}
|
2024-07-21 02:56:42 +02:00
|
|
|
return /* @__PURE__ */ createElement("article", { id: "home-page" }, /* @__PURE__ */ createElement("div", null, overviewSection, chatSection));
|
2024-07-19 23:45:07 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-22 02:05:27 +02:00
|
|
|
// src/Model/Global/settingsModel.ts
|
2024-07-20 02:00:00 +02:00
|
|
|
var SettingsModel = class {
|
|
|
|
|
storageModel;
|
2024-07-22 03:01:04 +02:00
|
|
|
// data
|
2024-07-20 02:00:00 +02:00
|
|
|
username;
|
|
|
|
|
firstDayOfWeek;
|
2024-07-22 03:01:04 +02:00
|
|
|
// storage
|
2024-07-20 02:00:00 +02:00
|
|
|
setName(newValue) {
|
|
|
|
|
this.username = newValue;
|
2024-07-22 03:01:04 +02:00
|
|
|
const path = StorageModel.getPath(
|
|
|
|
|
"settingsModel",
|
|
|
|
|
filePaths.settingsModel.username
|
|
|
|
|
);
|
2024-07-21 21:45:18 +02:00
|
|
|
this.storageModel.write(path, newValue);
|
2024-07-20 02:00:00 +02:00
|
|
|
}
|
|
|
|
|
setFirstDayOfWeek(newValue) {
|
|
|
|
|
this.firstDayOfWeek = newValue;
|
2024-07-22 03:01:04 +02:00
|
|
|
const path = StorageModel.getPath(
|
|
|
|
|
"settingsModel",
|
|
|
|
|
filePaths.settingsModel.firstDayOfWeek
|
|
|
|
|
);
|
2024-07-22 01:13:20 +02:00
|
|
|
this.storageModel.write(path, newValue);
|
2024-07-20 02:00:00 +02:00
|
|
|
}
|
2024-07-21 21:45:18 +02:00
|
|
|
// load
|
|
|
|
|
loadUsernam() {
|
2024-07-22 03:01:04 +02:00
|
|
|
const path = StorageModel.getPath(
|
|
|
|
|
"settingsModel",
|
|
|
|
|
filePaths.settingsModel.username
|
|
|
|
|
);
|
2024-07-21 21:45:18 +02:00
|
|
|
const content = this.storageModel.read(path);
|
2024-07-20 02:00:00 +02:00
|
|
|
this.username = content ?? "";
|
|
|
|
|
}
|
2024-07-21 21:45:18 +02:00
|
|
|
loadFirstDayofWeek() {
|
2024-07-22 03:01:04 +02:00
|
|
|
const path = StorageModel.getPath(
|
|
|
|
|
"settingsModel",
|
|
|
|
|
filePaths.settingsModel.firstDayOfWeek
|
|
|
|
|
);
|
2024-07-22 01:13:20 +02:00
|
|
|
const content = this.storageModel.read(path);
|
|
|
|
|
this.firstDayOfWeek = content ?? "0";
|
2024-07-20 02:00:00 +02:00
|
|
|
}
|
|
|
|
|
// init
|
|
|
|
|
constructor(storageModel2) {
|
|
|
|
|
this.storageModel = storageModel2;
|
2024-07-21 21:45:18 +02:00
|
|
|
this.loadUsernam();
|
|
|
|
|
this.loadFirstDayofWeek();
|
2024-07-20 02:00:00 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-22 21:07:44 +02:00
|
|
|
// src/ViewModel/Global/settingsViewModel.ts
|
2024-07-20 02:00:00 +02:00
|
|
|
var SettingsViewModel = class {
|
|
|
|
|
settingsModel;
|
|
|
|
|
// state
|
2024-07-20 21:10:06 +02:00
|
|
|
username = new State("");
|
|
|
|
|
usernameInput = new State("");
|
2024-07-22 01:13:20 +02:00
|
|
|
firstDayOfWeekInput = new State("0");
|
2024-07-20 04:15:14 +02:00
|
|
|
// guards
|
2024-07-20 02:00:00 +02:00
|
|
|
cannotSetName = createProxyState(
|
2024-07-20 21:10:06 +02:00
|
|
|
[this.usernameInput],
|
|
|
|
|
() => this.usernameInput.value == "" || this.usernameInput.value == this.settingsModel.username
|
2024-07-20 02:00:00 +02:00
|
|
|
);
|
2024-07-22 23:33:57 +02:00
|
|
|
// methods
|
2024-07-20 02:00:00 +02:00
|
|
|
setName = () => {
|
2024-07-20 21:10:06 +02:00
|
|
|
this.settingsModel.setName(this.usernameInput.value);
|
|
|
|
|
this.username.value = this.settingsModel.username;
|
|
|
|
|
this.usernameInput.callSubscriptions();
|
2024-07-20 02:00:00 +02:00
|
|
|
};
|
|
|
|
|
setFirstDayofWeek = () => {
|
|
|
|
|
this.settingsModel.setFirstDayOfWeek(this.firstDayOfWeekInput.value);
|
|
|
|
|
};
|
|
|
|
|
// init
|
2024-07-20 20:42:43 +02:00
|
|
|
constructor(settingsModel2) {
|
|
|
|
|
this.settingsModel = settingsModel2;
|
2024-07-20 21:10:06 +02:00
|
|
|
this.username.value = settingsModel2.username;
|
|
|
|
|
this.usernameInput.value = settingsModel2.username;
|
2024-07-20 20:42:43 +02:00
|
|
|
this.firstDayOfWeekInput.value = settingsModel2.firstDayOfWeek;
|
2024-07-20 02:00:00 +02:00
|
|
|
this.firstDayOfWeekInput.subscribe(this.setFirstDayofWeek);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-21 17:55:53 +02:00
|
|
|
// src/View/Components/directoryItemList.tsx
|
2024-07-21 20:21:32 +02:00
|
|
|
function DirectoryItemList(storageViewModel2, pathString = PATH_COMPONENT_SEPARATOR) {
|
|
|
|
|
const StringToDirectoryItemList = (pathString2) => DirectoryItemList(storageViewModel2, pathString2);
|
2024-07-21 17:55:53 +02:00
|
|
|
const path = StorageModel.stringToPathComponents(pathString);
|
2024-07-21 18:56:56 +02:00
|
|
|
const fileName = StorageModel.getFileName(path);
|
2024-07-21 17:55:53 +02:00
|
|
|
const items = new ListState();
|
2024-07-22 19:28:39 +02:00
|
|
|
const style = `text-indent: ${path.length}rem`;
|
2024-07-21 17:55:53 +02:00
|
|
|
function loadItems() {
|
|
|
|
|
items.clear();
|
2024-07-21 20:21:32 +02:00
|
|
|
const directoryItems = storageViewModel2.storageModel.list(path);
|
2024-07-21 17:55:53 +02:00
|
|
|
for (const directoryItem of directoryItems) {
|
|
|
|
|
const itemPath = [...path, directoryItem];
|
|
|
|
|
const pathString2 = StorageModel.pathComponentsToString(...itemPath);
|
|
|
|
|
items.add(pathString2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function select() {
|
2024-07-21 20:21:32 +02:00
|
|
|
storageViewModel2.selectedPath.value = pathString;
|
2024-07-21 17:55:53 +02:00
|
|
|
}
|
2024-07-21 20:21:32 +02:00
|
|
|
storageViewModel2.lastDeletedItemPath.subscribe((lastDeletedItemPath) => {
|
|
|
|
|
if (!items.value.has(lastDeletedItemPath)) return;
|
|
|
|
|
select();
|
|
|
|
|
setTimeout(() => loadItems(), 50);
|
|
|
|
|
});
|
2024-07-21 17:55:53 +02:00
|
|
|
const isSelected = createProxyState(
|
2024-07-21 20:21:32 +02:00
|
|
|
[storageViewModel2.selectedPath],
|
|
|
|
|
() => storageViewModel2.selectedPath.value == pathString
|
2024-07-21 17:55:53 +02:00
|
|
|
);
|
|
|
|
|
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
|
2024-07-21 20:21:32 +02:00
|
|
|
function FileBrowser(storageViewModel2) {
|
|
|
|
|
const detailView = createProxyState(
|
|
|
|
|
[storageViewModel2.selectedPath],
|
|
|
|
|
() => {
|
|
|
|
|
if (storageViewModel2.selectedPath.value == PATH_COMPONENT_SEPARATOR)
|
|
|
|
|
return /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.storage.noItemSelected);
|
|
|
|
|
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
|
|
|
|
|
));
|
|
|
|
|
}
|
2024-07-21 18:56:56 +02:00
|
|
|
);
|
2024-07-21 20:21:32 +02:00
|
|
|
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(
|
2024-07-21 18:56:56 +02:00
|
|
|
"span",
|
|
|
|
|
{
|
|
|
|
|
class: "ellipsis",
|
2024-07-21 20:21:32 +02:00
|
|
|
"subscribe:innerText": storageViewModel2.selectedFileName
|
2024-07-21 18:56:56 +02:00
|
|
|
}
|
2024-07-21 19:44:10 +02:00
|
|
|
), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")))), /* @__PURE__ */ createElement("div", { class: "scroll-area", "children:set": detailView }));
|
2024-07-21 18:56:56 +02:00
|
|
|
function scrollToDetails() {
|
|
|
|
|
view.scrollLeft = view.scrollWidth;
|
|
|
|
|
}
|
|
|
|
|
return view;
|
2024-07-21 17:55:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Modals/storageModal.tsx
|
|
|
|
|
function StorageModal(storageViewModel2) {
|
2024-07-21 20:21:32 +02:00
|
|
|
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"))));
|
2024-07-21 17:55:53 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-22 21:07:44 +02:00
|
|
|
// src/ViewModel/Global/storageViewModel.ts
|
2024-07-21 17:55:53 +02:00
|
|
|
var StorageViewModel = class {
|
|
|
|
|
storageModel;
|
|
|
|
|
// state
|
|
|
|
|
isShowingStorageModal = new State(false);
|
2024-07-21 19:44:10 +02:00
|
|
|
selectedPath = new State(PATH_COMPONENT_SEPARATOR);
|
|
|
|
|
didMakeChanges = new State(false);
|
2024-07-21 20:21:32 +02:00
|
|
|
selectedFileName;
|
|
|
|
|
selectedFileContent;
|
|
|
|
|
lastDeletedItemPath = new State("");
|
|
|
|
|
// methods
|
|
|
|
|
getSelectedItemContent = () => {
|
|
|
|
|
const path = StorageModel.stringToPathComponents(this.selectedPath.value);
|
2024-07-21 21:45:18 +02:00
|
|
|
const content = this.storageModel.read(path);
|
2024-07-21 20:21:32 +02:00
|
|
|
return (content ?? translations.storage.notAFile) || translations.storage.contentEmpty;
|
|
|
|
|
};
|
|
|
|
|
deleteSelectedItem = () => {
|
|
|
|
|
const path = StorageModel.stringToPathComponents(this.selectedPath.value);
|
|
|
|
|
this.lastDeletedItemPath.value = this.selectedPath.value;
|
2024-07-22 01:40:53 +02:00
|
|
|
this.storageModel.removeRecursively(path);
|
2024-07-21 20:21:32 +02:00
|
|
|
this.didMakeChanges.value = true;
|
|
|
|
|
};
|
2024-07-22 23:33:57 +02:00
|
|
|
// view
|
2024-07-21 17:55:53 +02:00
|
|
|
showStorageModal = () => {
|
|
|
|
|
this.isShowingStorageModal.value = true;
|
|
|
|
|
};
|
|
|
|
|
hideStorageModal = () => {
|
2024-07-21 19:44:10 +02:00
|
|
|
if (this.didMakeChanges.value == true) {
|
|
|
|
|
window.location.reload();
|
2024-07-21 20:24:59 +02:00
|
|
|
return;
|
2024-07-21 19:44:10 +02:00
|
|
|
}
|
2024-07-21 17:55:53 +02:00
|
|
|
this.isShowingStorageModal.value = false;
|
|
|
|
|
};
|
|
|
|
|
// init
|
|
|
|
|
constructor(storageModel2) {
|
|
|
|
|
this.storageModel = storageModel2;
|
2024-07-21 20:21:32 +02:00
|
|
|
this.selectedFileName = createProxyState(
|
|
|
|
|
[this.selectedPath],
|
|
|
|
|
() => StorageModel.getFileNameFromString(this.selectedPath.value)
|
|
|
|
|
);
|
|
|
|
|
this.selectedFileContent = createProxyState(
|
|
|
|
|
[this.selectedPath],
|
|
|
|
|
() => this.getSelectedItemContent()
|
|
|
|
|
);
|
2024-07-21 17:55:53 +02:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-19 19:23:48 +02:00
|
|
|
// src/index.tsx
|
|
|
|
|
var storageModel = new StorageModel();
|
2024-07-20 20:42:43 +02:00
|
|
|
var settingsModel = new SettingsModel(storageModel);
|
|
|
|
|
var connectionModel = new ConnectionModel(storageModel);
|
|
|
|
|
var chatListModel = new ChatListModel(
|
|
|
|
|
storageModel,
|
|
|
|
|
settingsModel,
|
|
|
|
|
connectionModel
|
|
|
|
|
);
|
2024-07-21 17:55:53 +02:00
|
|
|
var storageViewModel = new StorageViewModel(storageModel);
|
2024-07-20 20:42:43 +02:00
|
|
|
var settingsViewModel = new SettingsViewModel(settingsModel);
|
|
|
|
|
var connectionViewModel = new ConnectionViewModel(connectionModel);
|
2024-07-20 21:10:06 +02:00
|
|
|
var chatListViewModel = new ChatListViewModel(
|
|
|
|
|
chatListModel,
|
|
|
|
|
storageModel,
|
|
|
|
|
settingsViewModel
|
|
|
|
|
);
|
2024-07-20 23:20:57 +02:00
|
|
|
chatListViewModel.selectedChat.subscribe(() => {
|
|
|
|
|
document.body.toggleAttribute(
|
|
|
|
|
"showing-chat",
|
|
|
|
|
chatListViewModel.selectedChat.value != void 0
|
|
|
|
|
);
|
|
|
|
|
});
|
2024-07-21 02:49:04 +02:00
|
|
|
document.body.append(
|
2024-07-22 01:40:53 +02:00
|
|
|
/* @__PURE__ */ createElement("div", { id: "background-wrapper" }, /* @__PURE__ */ createElement("div", { id: "sky" }), /* @__PURE__ */ createElement("div", { id: "grass-1" }), /* @__PURE__ */ createElement("div", { id: "grass-2" }))
|
2024-07-21 02:49:04 +02:00
|
|
|
);
|
2024-07-20 02:30:45 +02:00
|
|
|
document.querySelector("main").append(
|
2024-07-21 17:55:53 +02:00
|
|
|
HomePage(
|
|
|
|
|
storageViewModel,
|
|
|
|
|
settingsViewModel,
|
|
|
|
|
connectionViewModel,
|
|
|
|
|
chatListViewModel
|
|
|
|
|
),
|
2024-07-20 14:16:26 +02:00
|
|
|
ChatPageWrapper(chatListViewModel),
|
2024-07-21 17:55:53 +02:00
|
|
|
ConnectionModal(connectionViewModel),
|
|
|
|
|
StorageModal(storageViewModel)
|
2024-07-20 02:30:45 +02:00
|
|
|
);
|
2024-07-07 13:43:18 +02:00
|
|
|
})();
|