2024-07-07 13:43:18 +02:00
|
|
|
(() => {
|
2026-04-12 19:20:37 +00:00
|
|
|
// ../../bloatless-react/index.ts
|
2026-04-01 08:53:50 +00:00
|
|
|
var State = class {
|
|
|
|
|
// init
|
|
|
|
|
constructor(initialValue) {
|
2026-04-12 19:20:37 +00:00
|
|
|
this._bindings = /* @__PURE__ */ new Set();
|
2026-04-01 08:53:50 +00:00
|
|
|
this._value = initialValue;
|
|
|
|
|
}
|
|
|
|
|
// value
|
|
|
|
|
get value() {
|
|
|
|
|
return this._value;
|
|
|
|
|
}
|
|
|
|
|
set value(newValue) {
|
|
|
|
|
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 {
|
|
|
|
|
// init
|
|
|
|
|
constructor(initialItems) {
|
|
|
|
|
super(new Set(initialItems));
|
2026-04-12 19:20:37 +00:00
|
|
|
this.additionHandlers = /* @__PURE__ */ new Set();
|
|
|
|
|
this.removalHandlers = /* @__PURE__ */ new Map();
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
// 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 {
|
|
|
|
|
// init
|
|
|
|
|
constructor(initialItems) {
|
|
|
|
|
super(new Map(initialItems));
|
2026-04-12 19:20:37 +00:00
|
|
|
this.additionHandlers = /* @__PURE__ */ new Set();
|
|
|
|
|
this.removalHandlers = /* @__PURE__ */ new Map();
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
// list
|
|
|
|
|
set(key, item) {
|
|
|
|
|
this.remove(key);
|
|
|
|
|
this.value.set(key, item);
|
|
|
|
|
this.additionHandlers.forEach((handler) => handler(item));
|
|
|
|
|
this.callSubscriptions();
|
|
|
|
|
}
|
|
|
|
|
remove(key) {
|
|
|
|
|
const item = this.value.get(key);
|
|
|
|
|
if (!item) return;
|
|
|
|
|
this.value.delete(key);
|
|
|
|
|
this.callSubscriptions();
|
|
|
|
|
if (!this.removalHandlers.has(item)) return;
|
|
|
|
|
this.removalHandlers.get(item).forEach((handler) => handler(item));
|
|
|
|
|
this.removalHandlers.delete(item);
|
|
|
|
|
}
|
|
|
|
|
clear() {
|
|
|
|
|
[...this.value.keys()].forEach((key) => this.remove(key));
|
|
|
|
|
}
|
|
|
|
|
// handlers
|
|
|
|
|
handleAddition(handler) {
|
|
|
|
|
this.additionHandlers.add(handler);
|
|
|
|
|
[...this.value.values()].forEach(handler);
|
|
|
|
|
}
|
|
|
|
|
handleRemoval(item, handler) {
|
|
|
|
|
if (!this.removalHandlers.has(item))
|
|
|
|
|
this.removalHandlers.set(item, /* @__PURE__ */ new Set());
|
|
|
|
|
this.removalHandlers.get(item).add(handler);
|
|
|
|
|
}
|
|
|
|
|
// stringification
|
|
|
|
|
toString() {
|
|
|
|
|
const array = [...this.value.entries()];
|
|
|
|
|
const json = JSON.stringify(array);
|
|
|
|
|
return json;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
function createProxyState(statesToSubscibe, fn) {
|
|
|
|
|
const proxyState = new State(fn());
|
|
|
|
|
statesToSubscibe.forEach(
|
|
|
|
|
(state) => state.subscribe(() => proxyState.value = fn())
|
|
|
|
|
);
|
|
|
|
|
return proxyState;
|
|
|
|
|
}
|
|
|
|
|
function bulkSubscribe(statesToSubscibe, fn) {
|
|
|
|
|
statesToSubscibe.forEach((state) => state.subscribeSilent(fn));
|
|
|
|
|
}
|
|
|
|
|
function createElement(tagName, attributes = {}, ...children) {
|
|
|
|
|
const element = document.createElement(tagName);
|
|
|
|
|
if (attributes != null)
|
|
|
|
|
Object.entries(attributes).forEach((entry) => {
|
|
|
|
|
const [attributename, value] = entry;
|
|
|
|
|
const [directiveKey, directiveValue] = attributename.split(":");
|
|
|
|
|
switch (directiveKey) {
|
|
|
|
|
case "on": {
|
|
|
|
|
switch (directiveValue) {
|
|
|
|
|
case "enter": {
|
|
|
|
|
element.addEventListener("keydown", (e) => {
|
|
|
|
|
if (e.key != "Enter") return;
|
2026-04-06 15:09:48 +00:00
|
|
|
value(e);
|
2026-04-01 08:53:50 +00:00
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
element.addEventListener(directiveValue, value);
|
|
|
|
|
}
|
2026-04-01 08:46:10 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
break;
|
|
|
|
|
}
|
2026-04-08 20:28:59 +00:00
|
|
|
case "keystroke": {
|
2026-04-06 15:09:48 +00:00
|
|
|
element.addEventListener("keydown", (e) => {
|
2026-04-08 20:28:59 +00:00
|
|
|
if (e.metaKey == false && e.ctrlKey == false) return;
|
2026-04-06 15:09:48 +00:00
|
|
|
if (e.key != directiveValue) return;
|
|
|
|
|
value(e);
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
case "subscribe": {
|
|
|
|
|
const state = value;
|
|
|
|
|
state.subscribe(
|
|
|
|
|
(newValue) => element[directiveValue] = newValue
|
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "bind": {
|
|
|
|
|
const state = value;
|
|
|
|
|
state.subscribe(
|
|
|
|
|
(newValue) => element[directiveValue] = newValue
|
|
|
|
|
);
|
|
|
|
|
element.addEventListener(
|
|
|
|
|
"input",
|
|
|
|
|
() => state.value = element[directiveValue]
|
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "toggle": {
|
|
|
|
|
if (value.subscribe) {
|
|
|
|
|
const state = value;
|
|
|
|
|
state.subscribe(
|
|
|
|
|
(newValue) => element.toggleAttribute(directiveValue, newValue)
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
element.toggleAttribute(directiveValue, value);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "set": {
|
|
|
|
|
const state = value;
|
|
|
|
|
state.subscribe(
|
|
|
|
|
(newValue) => element.setAttribute(directiveValue, newValue)
|
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "children": {
|
|
|
|
|
switch (directiveValue) {
|
|
|
|
|
case "set": {
|
|
|
|
|
const state = value;
|
|
|
|
|
state.subscribe((newValue) => {
|
|
|
|
|
element.innerHTML = "";
|
|
|
|
|
element.append(...[newValue].flat());
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "append":
|
|
|
|
|
case "prepend": {
|
|
|
|
|
try {
|
|
|
|
|
const [listState, toElement] = value;
|
|
|
|
|
listState.handleAddition((newItem) => {
|
|
|
|
|
const child = toElement(newItem);
|
|
|
|
|
listState.handleRemoval(
|
|
|
|
|
newItem,
|
|
|
|
|
() => child.remove()
|
|
|
|
|
);
|
|
|
|
|
if (directiveValue == "append") {
|
|
|
|
|
element.append(child);
|
|
|
|
|
} else if (directiveValue == "prepend") {
|
|
|
|
|
element.prepend(child);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
throw `error: cannot process subscribe:children directive.
|
|
|
|
|
Usage: "children:append={[list, converter]}"; you can find a more detailed example in the documentation.`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
element.setAttribute(attributename, value);
|
2024-07-25 02:49:23 +02:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
});
|
|
|
|
|
children.filter((x) => x).forEach((child) => element.append(child));
|
|
|
|
|
return element;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/Model/Utility/typeSafety.ts
|
|
|
|
|
var DATA_VERSION = "v2";
|
|
|
|
|
function checkIsValidObject(object) {
|
|
|
|
|
return object.dataVersion == DATA_VERSION;
|
|
|
|
|
}
|
|
|
|
|
function checkMatchesObjectStructure(objectToCheck, reference) {
|
|
|
|
|
if (typeof reference != "object") {
|
|
|
|
|
return typeof objectToCheck == typeof reference;
|
|
|
|
|
}
|
|
|
|
|
for (const key of Object.keys(reference)) {
|
|
|
|
|
const requiredType = typeof reference[key];
|
|
|
|
|
const actualType = typeof objectToCheck[key];
|
|
|
|
|
if (requiredType != actualType) return false;
|
|
|
|
|
if (Array.isArray(reference[key]) || reference[key] instanceof Set) {
|
|
|
|
|
if (objectToCheck[key].length == 0) continue;
|
|
|
|
|
if (objectToCheck[key].size == 0) continue;
|
|
|
|
|
const [firstOfObjectToCheck] = objectToCheck[key];
|
|
|
|
|
const [fisrtOfReference] = reference[key];
|
|
|
|
|
const doesFirstItemMatch = checkMatchesObjectStructure(
|
|
|
|
|
firstOfObjectToCheck,
|
|
|
|
|
fisrtOfReference
|
|
|
|
|
);
|
|
|
|
|
if (doesFirstItemMatch == false) return false;
|
|
|
|
|
} else if (requiredType == "object") {
|
|
|
|
|
const doesNestedObjectMatch = checkMatchesObjectStructure(
|
|
|
|
|
objectToCheck[key],
|
|
|
|
|
reference[key]
|
|
|
|
|
);
|
|
|
|
|
if (doesNestedObjectMatch == false) return false;
|
|
|
|
|
}
|
2024-07-25 15:31:27 +02:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
2026-04-01 08:46:10 +00:00
|
|
|
|
2026-04-01 08:53:50 +00:00
|
|
|
// src/Model/Utility/utility.ts
|
|
|
|
|
function generateRandomToken(length) {
|
|
|
|
|
const array = new Uint8Array(length);
|
|
|
|
|
crypto.getRandomValues(array);
|
2026-04-01 13:40:07 +00:00
|
|
|
const string = array.join("");
|
|
|
|
|
return string.substring(0, length);
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
function createTimestamp() {
|
|
|
|
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
|
|
|
}
|
2026-04-01 09:33:30 +00:00
|
|
|
function checkDoesObjectMatchReference(reference, stringEntryObject, explicitEmptyValue = false) {
|
2026-04-01 08:53:50 +00:00
|
|
|
reference_entry_loop: for (const referenceEntry of Object.entries(
|
|
|
|
|
reference
|
|
|
|
|
)) {
|
|
|
|
|
const [referenceKey, referenceValue] = referenceEntry;
|
|
|
|
|
const stringEntryObjectValue = stringEntryObject[referenceKey];
|
|
|
|
|
if (referenceValue == void 0) return false;
|
|
|
|
|
if (referenceValue[0] == "-") {
|
|
|
|
|
const strippedReferenceValue = referenceValue.toString().substring(1);
|
|
|
|
|
if (strippedReferenceValue == "" && stringEntryObjectValue != void 0 && stringEntryObjectValue != "") {
|
|
|
|
|
return false;
|
2024-07-25 15:31:27 +02:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
if (stringEntryObjectValue == strippedReferenceValue) {
|
|
|
|
|
return false;
|
2024-07-25 15:31:27 +02:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
} else {
|
2026-04-01 09:33:30 +00:00
|
|
|
if (explicitEmptyValue == false) {
|
|
|
|
|
if (referenceValue == "" && (stringEntryObjectValue == void 0 || stringEntryObjectValue == "")) {
|
|
|
|
|
return false;
|
|
|
|
|
} else if (referenceValue == "") {
|
|
|
|
|
continue reference_entry_loop;
|
|
|
|
|
}
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
if (stringEntryObjectValue != referenceValue) {
|
|
|
|
|
return false;
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
function collectObjectValuesForKey(key, converter, objects) {
|
|
|
|
|
const values = /* @__PURE__ */ new Set();
|
|
|
|
|
for (const object of objects) {
|
|
|
|
|
const stringEntryObject = converter(object);
|
|
|
|
|
const stringEntryObjectValue = stringEntryObject[key];
|
2026-04-01 09:42:51 +00:00
|
|
|
if (stringEntryObjectValue == void 0) continue;
|
2026-04-01 08:53:50 +00:00
|
|
|
values.add(stringEntryObjectValue.toString());
|
|
|
|
|
}
|
|
|
|
|
return [...values.values()];
|
|
|
|
|
}
|
|
|
|
|
function checkDoesObjectMatchSearch(query, getStringsOfObject, object) {
|
|
|
|
|
if (query == "") return true;
|
|
|
|
|
const stringsInObject = getStringsOfObject(object);
|
|
|
|
|
const wordsInObject = [];
|
|
|
|
|
for (const string of stringsInObject) {
|
|
|
|
|
const lowercaseWordsInString = string.toString().toLowerCase().split(" ").filter((word) => word != "");
|
|
|
|
|
wordsInObject.push(...lowercaseWordsInString);
|
|
|
|
|
}
|
|
|
|
|
const lowercaseWordsInQuery = query.toLowerCase().split(" ").filter((word) => word != "");
|
|
|
|
|
for (const queryWord of lowercaseWordsInQuery) {
|
|
|
|
|
if (queryWord[0] == "-") {
|
|
|
|
|
const wordContent = queryWord.substring(1);
|
|
|
|
|
if (wordsInObject.includes(wordContent)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (wordsInObject.includes(queryWord) == false) {
|
|
|
|
|
return false;
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
var HandlerManager = class {
|
|
|
|
|
constructor() {
|
|
|
|
|
this.handlers = /* @__PURE__ */ new Set();
|
|
|
|
|
// manage
|
|
|
|
|
this.addHandler = (handler) => {
|
|
|
|
|
this.handlers.add(handler);
|
|
|
|
|
};
|
|
|
|
|
this.deleteHandler = (handler) => {
|
|
|
|
|
this.handlers.delete(handler);
|
|
|
|
|
};
|
|
|
|
|
// trigger
|
|
|
|
|
this.trigger = (item) => {
|
|
|
|
|
for (const handler of this.handlers) {
|
|
|
|
|
handler(item);
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var IndexManager = class {
|
|
|
|
|
// init
|
|
|
|
|
constructor(itemToString) {
|
|
|
|
|
this.sortedStrings = [];
|
|
|
|
|
// methods
|
|
|
|
|
this.update = (items) => {
|
|
|
|
|
this.sortedStrings = [];
|
|
|
|
|
let strings = [];
|
|
|
|
|
for (const item of items) {
|
|
|
|
|
const string = this.itemToString(item);
|
|
|
|
|
strings.push(string);
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
this.sortedStrings = strings.sort(localeCompare);
|
|
|
|
|
};
|
|
|
|
|
this.getIndex = (item) => {
|
|
|
|
|
const string = this.itemToString(item);
|
|
|
|
|
const index = this.sortedStrings.indexOf(string);
|
|
|
|
|
return index;
|
|
|
|
|
};
|
|
|
|
|
this.itemToString = itemToString;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
function getLocalStorageItemAndClear(key) {
|
|
|
|
|
const value = localStorage.getItem(key);
|
|
|
|
|
localStorage.removeItem(key);
|
|
|
|
|
if (value != null) localStorage.setItem(`_${key}`, value);
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
function stringify(data) {
|
|
|
|
|
return JSON.stringify(data, null, 4);
|
|
|
|
|
}
|
|
|
|
|
function padZero(string, length) {
|
|
|
|
|
return (string ?? "").padStart(length, "0");
|
|
|
|
|
}
|
|
|
|
|
function parse(string) {
|
|
|
|
|
try {
|
|
|
|
|
return JSON.parse(string);
|
|
|
|
|
} catch {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function parseValidObject(string, reference) {
|
|
|
|
|
const parsed = parse(string);
|
|
|
|
|
if (checkIsValidObject(parsed) == false) return null;
|
|
|
|
|
const doesMatchReference = checkMatchesObjectStructure(
|
|
|
|
|
parsed,
|
|
|
|
|
reference
|
|
|
|
|
);
|
|
|
|
|
if (doesMatchReference == false) return null;
|
|
|
|
|
return parsed;
|
|
|
|
|
}
|
|
|
|
|
function parseOrFallback(inputString) {
|
|
|
|
|
try {
|
|
|
|
|
return JSON.parse(inputString);
|
|
|
|
|
} catch {
|
|
|
|
|
return inputString;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function parseArray(inputString) {
|
|
|
|
|
const parsed = parseOrFallback(inputString);
|
|
|
|
|
if (Array.isArray(parsed) == false) return [];
|
|
|
|
|
return parsed;
|
|
|
|
|
}
|
|
|
|
|
function localeCompare(a, b) {
|
|
|
|
|
return a.localeCompare(b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/Model/Global/storageModel.ts
|
|
|
|
|
var PATH_COMPONENT_SEPARATOR = "\\";
|
|
|
|
|
var StorageModel = class _StorageModel {
|
|
|
|
|
// init
|
|
|
|
|
constructor() {
|
|
|
|
|
this.storageEntryTree = {};
|
|
|
|
|
// read
|
|
|
|
|
this.read = (pathComponents) => {
|
|
|
|
|
const pathString = _StorageModel.pathComponentsToString(
|
|
|
|
|
...pathComponents
|
|
|
|
|
);
|
|
|
|
|
return localStorage.getItem(pathString);
|
|
|
|
|
};
|
|
|
|
|
this.list = (pathComponents) => {
|
|
|
|
|
let currentParent = this.storageEntryTree;
|
|
|
|
|
for (const component of pathComponents) {
|
|
|
|
|
const nextParent = currentParent[component];
|
|
|
|
|
if (nextParent == void 0) return [];
|
|
|
|
|
currentParent = nextParent;
|
|
|
|
|
}
|
|
|
|
|
return [...Object.keys(currentParent).sort(localeCompare)];
|
|
|
|
|
};
|
|
|
|
|
// write
|
|
|
|
|
this.write = (pathComponents, value) => {
|
|
|
|
|
const pathString = _StorageModel.pathComponentsToString(
|
|
|
|
|
...pathComponents
|
|
|
|
|
);
|
|
|
|
|
localStorage.setItem(pathString, value);
|
|
|
|
|
this.updateTree(...pathComponents);
|
|
|
|
|
};
|
|
|
|
|
this.remove = (pathComponents, shouldInitialize = true) => {
|
|
|
|
|
const pathString = _StorageModel.pathComponentsToString(
|
|
|
|
|
...pathComponents
|
|
|
|
|
);
|
|
|
|
|
localStorage.removeItem(pathString);
|
|
|
|
|
if (shouldInitialize == true) {
|
|
|
|
|
this.initializeTree();
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
this.rename = (sourcePathComponents, destinationPathComponents, shouldInitialize = true) => {
|
|
|
|
|
const content = this.read(sourcePathComponents);
|
|
|
|
|
if (content == null) return false;
|
|
|
|
|
this.write(destinationPathComponents, content);
|
|
|
|
|
this.remove(sourcePathComponents);
|
|
|
|
|
if (shouldInitialize == true) {
|
|
|
|
|
this.initializeTree();
|
2024-07-22 15:06:01 +02:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
// recursion
|
|
|
|
|
this.recurse = (rootDirectory, fn) => {
|
|
|
|
|
loop_over_files: for (const key of Object.keys(localStorage)) {
|
|
|
|
|
const pathComponentsOfCurrentEntity = _StorageModel.stringToPathComponents(key);
|
|
|
|
|
loop_over_path_components: for (let i = 0; i < rootDirectory.length; i++) {
|
|
|
|
|
if (!pathComponentsOfCurrentEntity[i]) continue loop_over_files;
|
|
|
|
|
if (pathComponentsOfCurrentEntity[i] != rootDirectory[i])
|
|
|
|
|
continue loop_over_files;
|
|
|
|
|
}
|
|
|
|
|
fn(pathComponentsOfCurrentEntity);
|
|
|
|
|
}
|
|
|
|
|
this.initializeTree();
|
|
|
|
|
};
|
|
|
|
|
this.removeRecursively = (pathComponents) => {
|
|
|
|
|
this.recurse(
|
|
|
|
|
pathComponents,
|
|
|
|
|
(path) => this.remove(path, false)
|
|
|
|
|
);
|
|
|
|
|
this.initializeTree();
|
|
|
|
|
};
|
|
|
|
|
this.renameRecursively = (sourcePathComponents, destinationPathComponents) => {
|
|
|
|
|
this.recurse(sourcePathComponents, (path) => {
|
|
|
|
|
const relativePathOfCurrentEntity = path.slice(
|
|
|
|
|
sourcePathComponents.length
|
|
|
|
|
);
|
|
|
|
|
const destinationPathComponentsOfCurrentEntity = [
|
|
|
|
|
...destinationPathComponents,
|
|
|
|
|
...relativePathOfCurrentEntity
|
|
|
|
|
];
|
|
|
|
|
this.rename(path, destinationPathComponentsOfCurrentEntity, false);
|
|
|
|
|
});
|
|
|
|
|
this.initializeTree();
|
|
|
|
|
};
|
|
|
|
|
// stringifiable
|
|
|
|
|
this.writeStringifiable = (pathComponents, value) => {
|
|
|
|
|
const valueString = stringify(value);
|
|
|
|
|
this.write(pathComponents, valueString);
|
|
|
|
|
};
|
|
|
|
|
this.readStringifiable = (pathComponents, reference) => {
|
|
|
|
|
const valueString = this.read(pathComponents);
|
|
|
|
|
if (!valueString) return null;
|
|
|
|
|
const object = parseValidObject(valueString, reference);
|
|
|
|
|
if (object == null) return null;
|
|
|
|
|
return object;
|
|
|
|
|
};
|
|
|
|
|
// cleaning
|
|
|
|
|
this.removeJunk = () => {
|
|
|
|
|
this.recurse([], (path) => {
|
|
|
|
|
if (path[0] == DATA_VERSION) return;
|
|
|
|
|
this.remove(path);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
// tree
|
|
|
|
|
this.initializeTree = () => {
|
|
|
|
|
console.log("initializing tree");
|
|
|
|
|
this.storageEntryTree = {};
|
|
|
|
|
for (const key of Object.keys(localStorage)) {
|
|
|
|
|
const components = _StorageModel.stringToPathComponents(key);
|
|
|
|
|
this.updateTree(...components);
|
2024-07-20 05:10:50 +02:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
this.updateTree = (...pathComponents) => {
|
|
|
|
|
let currentParent = this.storageEntryTree;
|
|
|
|
|
for (const pathPart of pathComponents) {
|
|
|
|
|
if (!currentParent[pathPart]) {
|
|
|
|
|
currentParent[pathPart] = {};
|
|
|
|
|
}
|
|
|
|
|
currentParent = currentParent[pathPart];
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
this.printTree = () => {
|
|
|
|
|
return stringify(this.storageEntryTree);
|
|
|
|
|
};
|
|
|
|
|
this.initializeTree();
|
|
|
|
|
}
|
|
|
|
|
static {
|
|
|
|
|
// utility
|
|
|
|
|
this.getFileName = (pathComponents) => {
|
|
|
|
|
return pathComponents[pathComponents.length - 1] || "\\";
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
static {
|
|
|
|
|
this.getFileNameFromString = (pathString) => {
|
|
|
|
|
const pathComponents = this.stringToPathComponents(pathString);
|
|
|
|
|
return pathComponents[pathComponents.length - 1] || "\\";
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
static {
|
|
|
|
|
this.pathComponentsToString = (...pathComponents) => {
|
|
|
|
|
return pathComponents.filter((x) => x != "").join(PATH_COMPONENT_SEPARATOR);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
static {
|
|
|
|
|
this.stringToPathComponents = (string) => {
|
|
|
|
|
return string.split(PATH_COMPONENT_SEPARATOR).filter((x) => x != "");
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
static {
|
|
|
|
|
this.join = (...items) => {
|
|
|
|
|
let allComponents = [];
|
|
|
|
|
for (const item of items) {
|
|
|
|
|
const parts = this.stringToPathComponents(item);
|
|
|
|
|
allComponents.push(...parts);
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
return _StorageModel.pathComponentsToString(...allComponents);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
static getPath(locationName, filePath) {
|
|
|
|
|
return [DATA_VERSION, locationName, ...filePath];
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var filePaths = {
|
|
|
|
|
connectionModel: {
|
|
|
|
|
socketAddress: ["socket-address"],
|
|
|
|
|
reconnectAddress: ["reconnect-address"],
|
|
|
|
|
outbox: ["outbox"],
|
|
|
|
|
mailboxes: ["mailboxes"],
|
|
|
|
|
previousAddresses: ["previous-addresses"]
|
|
|
|
|
},
|
|
|
|
|
chat: {
|
|
|
|
|
base: [],
|
|
|
|
|
chatBase: (id) => [id],
|
|
|
|
|
info: (id) => [...filePaths.chat.chatBase(id), "info"],
|
|
|
|
|
color: (id) => [...filePaths.chat.chatBase(id), "color"],
|
|
|
|
|
messages: (id) => [...filePaths.chat.chatBase(id), "messages"],
|
2026-04-06 20:47:49 +00:00
|
|
|
reactions: (id) => [
|
|
|
|
|
...filePaths.chat.chatBase(id),
|
|
|
|
|
"reactions"
|
|
|
|
|
],
|
2026-04-01 08:53:50 +00:00
|
|
|
lastUsedPage: (id) => [
|
|
|
|
|
...filePaths.chat.chatBase(id),
|
|
|
|
|
"last-used-page"
|
|
|
|
|
],
|
|
|
|
|
files: (id) => [...filePaths.chat.chatBase(id), "files"]
|
|
|
|
|
},
|
2026-04-01 19:00:16 +00:00
|
|
|
notificationModel: {
|
|
|
|
|
base: []
|
|
|
|
|
},
|
2026-04-01 08:53:50 +00:00
|
|
|
settingsModel: {
|
|
|
|
|
username: ["user-name"],
|
2026-04-09 16:48:22 +00:00
|
|
|
firstDayOfWeek: ["first-day-of-week"],
|
2026-04-09 18:09:26 +00:00
|
|
|
language: ["language"],
|
|
|
|
|
theme: ["theme"]
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// src/Model/Files/calendarModel.ts
|
|
|
|
|
var CalendarModel = class _CalendarModel {
|
|
|
|
|
// init
|
|
|
|
|
constructor(storageModel2, settingsModel2, fileModel) {
|
|
|
|
|
// paths
|
|
|
|
|
this.getBasePath = () => {
|
|
|
|
|
return this.fileModel.getModelContainerPath(
|
|
|
|
|
"calendar" /* ModelCalendar */
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.getViewPath = () => {
|
|
|
|
|
return [...this.getBasePath(), "view" /* ModelView */];
|
|
|
|
|
};
|
|
|
|
|
this.getMonthContainerPath = () => {
|
|
|
|
|
return [...this.getBasePath(), "months" /* Months */];
|
|
|
|
|
};
|
|
|
|
|
this.getMonthPath = (monthString) => {
|
|
|
|
|
return [...this.getMonthContainerPath(), monthString];
|
|
|
|
|
};
|
|
|
|
|
// main
|
|
|
|
|
this.storeTaskReference = (taskFileContent) => {
|
|
|
|
|
if (taskFileContent.date == void 0) return;
|
|
|
|
|
const monthString = _CalendarModel.isoToMonthString(
|
|
|
|
|
taskFileContent.date
|
|
|
|
|
);
|
|
|
|
|
const monthPath = this.getMonthPath(monthString);
|
|
|
|
|
const referencePath = [...monthPath, taskFileContent.fileId];
|
|
|
|
|
this.storageModel.write(referencePath, "");
|
|
|
|
|
};
|
|
|
|
|
this.deleteTaskReference = (monthString, taskId) => {
|
|
|
|
|
const monthPath = this.getMonthPath(monthString);
|
|
|
|
|
const referencePath = [...monthPath, taskId];
|
|
|
|
|
this.storageModel.write(referencePath, "");
|
|
|
|
|
};
|
|
|
|
|
this.listTaskIds = (monthString) => {
|
|
|
|
|
const monthPath = this.getMonthPath(monthString);
|
|
|
|
|
return this.storageModel.list(monthPath);
|
|
|
|
|
};
|
|
|
|
|
this.generateMonthGrid = (year, month, defaultValueCreator) => {
|
|
|
|
|
const date = /* @__PURE__ */ new Date();
|
|
|
|
|
const isCurrentMonth = year == date.getFullYear() && month == date.getMonth() + 1;
|
|
|
|
|
date.setDate(1);
|
|
|
|
|
date.setMonth(month - 1);
|
|
|
|
|
date.setFullYear(year);
|
|
|
|
|
const firstWeekdayOfMonth = date.getDay();
|
|
|
|
|
const firstDayOfWeekSetting = parseInt(
|
|
|
|
|
this.settingsModel.firstDayOfWeek
|
|
|
|
|
);
|
|
|
|
|
const offset = firstWeekdayOfMonth < firstDayOfWeekSetting ? 7 - firstDayOfWeekSetting : firstWeekdayOfMonth - firstDayOfWeekSetting;
|
|
|
|
|
date.setMonth(month);
|
|
|
|
|
date.setDate(-1);
|
|
|
|
|
const daysInMonth = date.getDate() + 1;
|
|
|
|
|
const grid = {
|
|
|
|
|
offset,
|
|
|
|
|
firstDayOfWeek: parseInt(this.settingsModel.firstDayOfWeek),
|
|
|
|
|
isCurrentMonth,
|
|
|
|
|
year,
|
|
|
|
|
month,
|
|
|
|
|
days: {}
|
|
|
|
|
};
|
|
|
|
|
for (let i = 0; i < daysInMonth; i++) {
|
|
|
|
|
const paddedDate = _CalendarModel.padDateOrMonth(
|
|
|
|
|
(i + 1).toString()
|
|
|
|
|
);
|
|
|
|
|
grid.days[paddedDate] = defaultValueCreator();
|
2026-04-01 08:46:10 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
return grid;
|
|
|
|
|
};
|
|
|
|
|
this.storageModel = storageModel2;
|
|
|
|
|
this.settingsModel = settingsModel2;
|
|
|
|
|
this.fileModel = fileModel;
|
|
|
|
|
}
|
|
|
|
|
static {
|
|
|
|
|
// utility
|
|
|
|
|
this.isoToMonthString = (dateISOString) => {
|
|
|
|
|
const [year, month, _] = dateISOString.split("-");
|
|
|
|
|
return _CalendarModel.getMonthString(year, month);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
static {
|
|
|
|
|
this.isoToDateString = (dateISOString) => {
|
|
|
|
|
const [year, month, date, _] = dateISOString.split("-");
|
|
|
|
|
const paddedDate = _CalendarModel.padDateOrMonth(date ?? "");
|
|
|
|
|
return paddedDate;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
static {
|
|
|
|
|
this.getMonthString = (year = "", month = "") => {
|
|
|
|
|
const paddedYear = year.padStart(4, "0");
|
|
|
|
|
const paddedMonth = _CalendarModel.padDateOrMonth(month);
|
|
|
|
|
return `${paddedYear}-${paddedMonth}`;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
static {
|
|
|
|
|
this.getISODateString = (year, month, date) => {
|
|
|
|
|
const monthString = _CalendarModel.getMonthString(year, month);
|
|
|
|
|
const paddedDate = _CalendarModel.padDateOrMonth(date);
|
|
|
|
|
return `${monthString}-${paddedDate}`;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
static {
|
|
|
|
|
this.padDateOrMonth = (input) => {
|
|
|
|
|
return input.padStart(2, "0");
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// src/colors.ts
|
2026-04-08 14:02:12 +00:00
|
|
|
var Colors = /* @__PURE__ */ ((Colors2) => {
|
|
|
|
|
Colors2["Standard"] = "standard";
|
|
|
|
|
Colors2["Coral"] = "coral";
|
|
|
|
|
Colors2["Yellow"] = "yellow";
|
|
|
|
|
Colors2["Green"] = "green";
|
|
|
|
|
Colors2["LightBlue"] = "lightblue";
|
|
|
|
|
Colors2["Blue"] = "blue";
|
|
|
|
|
Colors2["purple"] = "purple";
|
|
|
|
|
return Colors2;
|
|
|
|
|
})(Colors || {});
|
2026-04-01 08:53:50 +00:00
|
|
|
|
2026-04-06 15:09:48 +00: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;
|
|
|
|
|
|
2026-04-01 08:53:50 +00:00
|
|
|
// src/Model/Files/boardsAndTasksModel.ts
|
|
|
|
|
var BoardsAndTasksModel = class _BoardsAndTasksModel {
|
|
|
|
|
// init
|
|
|
|
|
constructor(storageModel2, settingsModel2, chatModel, fileModel) {
|
|
|
|
|
// data
|
|
|
|
|
this.boardHandlerManager = new HandlerManager();
|
|
|
|
|
this.taskHandlerManager = new HandlerManager();
|
|
|
|
|
// paths
|
|
|
|
|
this.getBasePath = () => {
|
|
|
|
|
return this.fileModel.getModelContainerPath("tasks" /* ModelTask */);
|
|
|
|
|
};
|
|
|
|
|
this.getViewPath = () => {
|
|
|
|
|
return [...this.getBasePath(), "view" /* ModelView */];
|
|
|
|
|
};
|
|
|
|
|
this.getBoardFilePath = (boardId) => {
|
|
|
|
|
return [...this.fileModel.getFilePath(boardId)];
|
|
|
|
|
};
|
|
|
|
|
this.getTaskFilePath = (taskId) => {
|
|
|
|
|
return [...this.fileModel.getFilePath(taskId)];
|
|
|
|
|
};
|
|
|
|
|
this.getBoardContainerPath = () => {
|
|
|
|
|
return [...this.getBasePath(), "boards" /* Boards */];
|
|
|
|
|
};
|
|
|
|
|
this.getBoardDirectoryPath = (boardId) => {
|
|
|
|
|
return [...this.getBoardContainerPath(), boardId];
|
|
|
|
|
};
|
|
|
|
|
this.getTaskContainerPath = (boardId) => {
|
|
|
|
|
return [
|
|
|
|
|
...this.getBoardDirectoryPath(boardId),
|
|
|
|
|
"tasks" /* BoardTasks */
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
this.getTaskReferencePath = (boardId, fileId) => {
|
|
|
|
|
return [...this.getTaskContainerPath(boardId), fileId];
|
|
|
|
|
};
|
|
|
|
|
// handlers
|
|
|
|
|
this.handleFileContent = (fileContent) => {
|
|
|
|
|
if (checkMatchesObjectStructure(
|
|
|
|
|
fileContent,
|
|
|
|
|
BoardInfoFileContentReference
|
|
|
|
|
) == true) {
|
|
|
|
|
this.handleBoard(fileContent);
|
|
|
|
|
} else if (checkMatchesObjectStructure(
|
|
|
|
|
fileContent,
|
|
|
|
|
TaskFileContentReference
|
|
|
|
|
) == true) {
|
|
|
|
|
this.handleTask(fileContent);
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
this.handleBoard = (boardInfoFileContent) => {
|
|
|
|
|
this.updateBoard(boardInfoFileContent);
|
|
|
|
|
};
|
|
|
|
|
this.handleTask = (taskFileContent) => {
|
|
|
|
|
this.updateTask(taskFileContent);
|
|
|
|
|
};
|
|
|
|
|
// boards
|
|
|
|
|
this.createBoard = (name) => {
|
|
|
|
|
const boardInfoFileContent = _BoardsAndTasksModel.createBoardInfoFileContent(
|
|
|
|
|
v4_default(),
|
|
|
|
|
name,
|
|
|
|
|
"standard" /* Standard */
|
|
|
|
|
);
|
|
|
|
|
return boardInfoFileContent;
|
|
|
|
|
};
|
|
|
|
|
this.updateBoard = (boardInfoFileContent) => {
|
|
|
|
|
this.storeBoard(boardInfoFileContent);
|
|
|
|
|
this.boardHandlerManager.trigger(boardInfoFileContent);
|
|
|
|
|
};
|
|
|
|
|
this.updateBoardAndSend = (boardInfoFileContent) => {
|
|
|
|
|
this.updateBoard(boardInfoFileContent);
|
|
|
|
|
this.chatModel.sendMessage("", boardInfoFileContent);
|
|
|
|
|
};
|
|
|
|
|
this.storeBoard = (boardInfoFileContent) => {
|
|
|
|
|
this.fileModel.storeFileContent(boardInfoFileContent);
|
|
|
|
|
const boardDirectoryPath = this.getBoardDirectoryPath(
|
|
|
|
|
boardInfoFileContent.fileId
|
|
|
|
|
);
|
|
|
|
|
this.storageModel.write(boardDirectoryPath, "");
|
|
|
|
|
};
|
|
|
|
|
this.deleteBoard = (boardId) => {
|
|
|
|
|
const boardFilePath = this.getBoardFilePath(boardId);
|
|
|
|
|
const boardDirectoryPath = this.getBoardDirectoryPath(boardId);
|
|
|
|
|
this.storageModel.removeRecursively(boardFilePath);
|
|
|
|
|
this.storageModel.removeRecursively(boardDirectoryPath);
|
|
|
|
|
};
|
|
|
|
|
this.listBoardIds = () => {
|
|
|
|
|
const boardContainerPath = this.getBoardContainerPath();
|
|
|
|
|
const boardIds = this.storageModel.list(boardContainerPath);
|
|
|
|
|
return boardIds;
|
|
|
|
|
};
|
|
|
|
|
this.getBoardInfo = (fileId) => {
|
|
|
|
|
const boardInfoFileContentOrNull = this.fileModel.getLatestFileContent(
|
|
|
|
|
fileId,
|
|
|
|
|
BoardInfoFileContentReference
|
|
|
|
|
);
|
|
|
|
|
return boardInfoFileContentOrNull;
|
|
|
|
|
};
|
|
|
|
|
this.getBoardName = (boardId) => {
|
|
|
|
|
const boardInfo = this.getBoardInfo(boardId);
|
|
|
|
|
if (boardInfo == null) return "";
|
|
|
|
|
return boardInfo.name;
|
|
|
|
|
};
|
|
|
|
|
//tasks
|
|
|
|
|
this.createTask = (boardId) => {
|
|
|
|
|
const taskFileContent = _BoardsAndTasksModel.createTaskFileContent(v4_default(), "", boardId);
|
|
|
|
|
return taskFileContent;
|
|
|
|
|
};
|
|
|
|
|
this.updateTask = (taskFileContent) => {
|
|
|
|
|
this.storeTask(taskFileContent);
|
|
|
|
|
this.taskHandlerManager.trigger(taskFileContent);
|
|
|
|
|
};
|
|
|
|
|
this.updateTaskAndSend = (taskFileContent) => {
|
|
|
|
|
this.updateTask(taskFileContent);
|
|
|
|
|
this.chatModel.sendMessage("", taskFileContent);
|
|
|
|
|
};
|
|
|
|
|
this.storeTask = (taskFileContent) => {
|
|
|
|
|
this.fileModel.storeFileContent(taskFileContent);
|
|
|
|
|
const taskReferencePath = this.getTaskReferencePath(
|
|
|
|
|
taskFileContent.boardId,
|
|
|
|
|
taskFileContent.fileId
|
|
|
|
|
);
|
|
|
|
|
this.storageModel.write(taskReferencePath, "");
|
|
|
|
|
this.calendarModel.storeTaskReference(taskFileContent);
|
|
|
|
|
};
|
|
|
|
|
this.listTaskIds = (boardId) => {
|
|
|
|
|
const taskContainerPath = this.getTaskContainerPath(boardId);
|
|
|
|
|
const fileIds = this.storageModel.list(taskContainerPath);
|
|
|
|
|
return fileIds;
|
|
|
|
|
};
|
|
|
|
|
this.listTaskVersionIds = (taskId) => {
|
|
|
|
|
const versionIds = this.fileModel.listFileContentIds(taskId);
|
|
|
|
|
return versionIds;
|
|
|
|
|
};
|
|
|
|
|
this.getLatestTaskFileContent = (taskId) => {
|
|
|
|
|
const taskFileContentOrNull = this.fileModel.getLatestFileContent(
|
|
|
|
|
taskId,
|
|
|
|
|
TaskFileContentReference
|
|
|
|
|
);
|
|
|
|
|
return taskFileContentOrNull;
|
|
|
|
|
};
|
|
|
|
|
this.getSpecificTaskFileContent = (taskId, versionId) => {
|
|
|
|
|
const taskFileContentOrNull = this.fileModel.getFileContent(
|
|
|
|
|
taskId,
|
|
|
|
|
versionId,
|
|
|
|
|
TaskFileContentReference
|
|
|
|
|
);
|
|
|
|
|
return taskFileContentOrNull;
|
|
|
|
|
};
|
|
|
|
|
this.deleteTask = (boardId, taskId) => {
|
|
|
|
|
const taskFilePath = this.getTaskFilePath(taskId);
|
|
|
|
|
this.storageModel.removeRecursively(taskFilePath);
|
|
|
|
|
this.deleteTaskReference(boardId, taskId);
|
|
|
|
|
};
|
|
|
|
|
this.deleteTaskReference = (boardId, taskId) => {
|
|
|
|
|
const taskReferencePath = this.getTaskReferencePath(
|
|
|
|
|
boardId,
|
|
|
|
|
taskId
|
|
|
|
|
);
|
|
|
|
|
this.storageModel.removeRecursively(taskReferencePath);
|
|
|
|
|
};
|
|
|
|
|
this.storageModel = storageModel2;
|
|
|
|
|
this.settingsModel = settingsModel2;
|
|
|
|
|
this.chatModel = chatModel;
|
|
|
|
|
this.fileModel = fileModel;
|
|
|
|
|
this.calendarModel = new CalendarModel(
|
|
|
|
|
this.storageModel,
|
|
|
|
|
this.settingsModel,
|
|
|
|
|
this.fileModel
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
static {
|
|
|
|
|
// utility
|
|
|
|
|
this.createBoardInfoFileContent = (fileId, name, color) => {
|
|
|
|
|
const fileContent = FileModel2.createFileContent(fileId, "board-info");
|
|
|
|
|
return {
|
|
|
|
|
...fileContent,
|
|
|
|
|
name,
|
|
|
|
|
color
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
static {
|
|
|
|
|
this.createTaskFileContent = (fileId, name, boardId) => {
|
|
|
|
|
const fileContent = FileModel2.createFileContent(
|
|
|
|
|
fileId,
|
|
|
|
|
"task"
|
|
|
|
|
);
|
|
|
|
|
return {
|
|
|
|
|
...fileContent,
|
|
|
|
|
name,
|
|
|
|
|
boardId
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var BoardInfoFileContentReference = {
|
|
|
|
|
dataVersion: DATA_VERSION,
|
|
|
|
|
fileId: "string",
|
|
|
|
|
fileContentId: "",
|
|
|
|
|
creationDate: "",
|
|
|
|
|
type: "board-info",
|
|
|
|
|
name: "",
|
|
|
|
|
color: ""
|
|
|
|
|
};
|
|
|
|
|
var TaskFileContentReference = {
|
|
|
|
|
dataVersion: DATA_VERSION,
|
|
|
|
|
fileId: "string",
|
|
|
|
|
fileContentId: "",
|
|
|
|
|
creationDate: "",
|
|
|
|
|
type: "task",
|
|
|
|
|
name: "",
|
|
|
|
|
boardId: ""
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// src/Model/Files/fileModel.ts
|
|
|
|
|
var FileModel2 = class _FileModel {
|
|
|
|
|
// init
|
|
|
|
|
constructor(storageModel2, settingsModel2, chatModel) {
|
|
|
|
|
// paths
|
|
|
|
|
this.getBasePath = () => {
|
|
|
|
|
return StorageModel.getPath(
|
|
|
|
|
"chat" /* Chat */,
|
|
|
|
|
filePaths.chat.files(this.chatModel.id)
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.getFileContainerPath = () => {
|
|
|
|
|
return [...this.getBasePath(), "data" /* Data */];
|
|
|
|
|
};
|
|
|
|
|
this.getModelContainerPath = (modelName) => {
|
|
|
|
|
return [...this.getBasePath(), "model" /* Model */, modelName];
|
|
|
|
|
};
|
|
|
|
|
this.getFilePath = (fileId) => {
|
|
|
|
|
return [...this.getFileContainerPath(), fileId];
|
|
|
|
|
};
|
|
|
|
|
this.getFileContentPath = (fileId, fileContentId) => {
|
|
|
|
|
const filePath = this.getFilePath(fileId);
|
|
|
|
|
return [...filePath, fileContentId];
|
|
|
|
|
};
|
|
|
|
|
// handlers
|
|
|
|
|
this.handleStringifiedFileContent = (stringifiedFileContent) => {
|
|
|
|
|
const fileContent = parseValidObject(
|
|
|
|
|
stringifiedFileContent,
|
|
|
|
|
FileContentReference
|
|
|
|
|
);
|
|
|
|
|
if (fileContent == null) return;
|
|
|
|
|
this.handleFileContent(fileContent);
|
|
|
|
|
};
|
|
|
|
|
this.handleFileContent = (fileContent) => {
|
|
|
|
|
const didStore = this.storeFileContent(fileContent);
|
|
|
|
|
if (didStore == false) return;
|
|
|
|
|
this.boardsAndTasksModel.handleFileContent(fileContent);
|
2026-04-06 20:47:49 +00:00
|
|
|
this.chatModel.handleReaction(fileContent);
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
// methods
|
|
|
|
|
this.addFileContentAndSend = (fileContent) => {
|
|
|
|
|
this.handleFileContent(fileContent);
|
|
|
|
|
this.chatModel.sendMessage("", fileContent);
|
|
|
|
|
};
|
|
|
|
|
// storage
|
|
|
|
|
this.storeFileContent = (fileContent) => {
|
|
|
|
|
const fileContentPath = this.getFileContentPath(
|
|
|
|
|
fileContent.fileId,
|
|
|
|
|
fileContent.fileContentId
|
|
|
|
|
);
|
|
|
|
|
const existingFileContent = this.storageModel.read(fileContentPath);
|
|
|
|
|
if (existingFileContent != null) return false;
|
|
|
|
|
const stringifiedContent = stringify(fileContent);
|
|
|
|
|
this.storageModel.write(fileContentPath, stringifiedContent);
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
this.listFileIds = () => {
|
|
|
|
|
return this.storageModel.list(this.getBasePath());
|
|
|
|
|
};
|
|
|
|
|
this.listFileContentIds = (fileId) => {
|
|
|
|
|
const filePath = this.getFilePath(fileId);
|
|
|
|
|
return this.storageModel.list(filePath);
|
|
|
|
|
};
|
|
|
|
|
this.selectLatestFileContentId = (fileContentIds) => {
|
|
|
|
|
return fileContentIds[fileContentIds.length - 1];
|
|
|
|
|
};
|
|
|
|
|
this.getFileContent = (fileId, fileContentName, reference) => {
|
|
|
|
|
const filePath = this.getFileContentPath(
|
|
|
|
|
fileId,
|
|
|
|
|
fileContentName
|
|
|
|
|
);
|
|
|
|
|
const fileContentOrNull = this.storageModel.readStringifiable(
|
|
|
|
|
filePath,
|
|
|
|
|
reference
|
|
|
|
|
);
|
|
|
|
|
return fileContentOrNull;
|
|
|
|
|
};
|
|
|
|
|
this.getLatestFileContent = (fileId, reference) => {
|
|
|
|
|
const fileContentsIds = this.listFileContentIds(fileId);
|
|
|
|
|
const latestFileContentId = this.selectLatestFileContentId(fileContentsIds);
|
|
|
|
|
if (latestFileContentId == void 0) return null;
|
|
|
|
|
const fileContent = this.getFileContent(
|
|
|
|
|
fileId,
|
|
|
|
|
latestFileContentId,
|
|
|
|
|
reference
|
|
|
|
|
);
|
|
|
|
|
return fileContent;
|
|
|
|
|
};
|
|
|
|
|
this.chatModel = chatModel;
|
|
|
|
|
this.settingsModel = settingsModel2;
|
|
|
|
|
this.storageModel = storageModel2;
|
|
|
|
|
this.boardsAndTasksModel = new BoardsAndTasksModel(
|
|
|
|
|
this.storageModel,
|
|
|
|
|
this.settingsModel,
|
|
|
|
|
chatModel,
|
|
|
|
|
this
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
static {
|
|
|
|
|
// utility
|
|
|
|
|
this.generateFileContentId = (creationDate) => {
|
|
|
|
|
return creationDate + v4_default();
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
static {
|
|
|
|
|
this.createFileContent = (fileId, type) => {
|
|
|
|
|
const creationDate = createTimestamp();
|
|
|
|
|
const fileContentId = _FileModel.generateFileContentId(creationDate);
|
|
|
|
|
return {
|
|
|
|
|
dataVersion: DATA_VERSION,
|
|
|
|
|
fileId,
|
|
|
|
|
fileContentId,
|
|
|
|
|
creationDate,
|
|
|
|
|
type
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var FileContentReference = {
|
|
|
|
|
dataVersion: DATA_VERSION,
|
|
|
|
|
fileId: "",
|
|
|
|
|
fileContentId: "",
|
|
|
|
|
creationDate: "",
|
|
|
|
|
type: ""
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// src/Model/Utility/crypto.ts
|
|
|
|
|
var IV_SIZE = 12;
|
|
|
|
|
var ENCRYPTION_ALG = "AES-GCM";
|
|
|
|
|
async function encryptString(plaintext, passphrase) {
|
|
|
|
|
if (!window.crypto.subtle) return plaintext;
|
|
|
|
|
const iv = generateIV();
|
|
|
|
|
const key = await importKey(passphrase, "encrypt");
|
|
|
|
|
const encryptedArray = await encrypt(iv, key, plaintext);
|
|
|
|
|
const encryptionData = {
|
|
|
|
|
iv: uInt8ToArray(iv),
|
|
|
|
|
encryptedArray: uInt8ToArray(encryptedArray)
|
2024-07-21 03:17:40 +02:00
|
|
|
};
|
2026-04-01 08:53:50 +00:00
|
|
|
return btoa(JSON.stringify(encryptionData));
|
|
|
|
|
}
|
|
|
|
|
async function decryptString(cyphertext, passphrase) {
|
|
|
|
|
try {
|
|
|
|
|
const encrypionData = JSON.parse(atob(cyphertext));
|
|
|
|
|
const iv = arrayToUint8(encrypionData.iv);
|
|
|
|
|
const encryptedArray = arrayToUint8(encrypionData.encryptedArray);
|
|
|
|
|
const key = await importKey(passphrase, "decrypt");
|
|
|
|
|
return await decrypt(iv, key, encryptedArray);
|
|
|
|
|
} catch {
|
|
|
|
|
return cyphertext;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function encode(string) {
|
|
|
|
|
return new TextEncoder().encode(string);
|
|
|
|
|
}
|
|
|
|
|
function decode(array) {
|
|
|
|
|
return new TextDecoder("utf-8").decode(array);
|
|
|
|
|
}
|
|
|
|
|
async function encrypt(iv, key, message) {
|
|
|
|
|
const arrayBuffer = await window.crypto.subtle.encrypt(
|
|
|
|
|
{ name: ENCRYPTION_ALG, iv },
|
|
|
|
|
key,
|
|
|
|
|
encode(message)
|
|
|
|
|
);
|
|
|
|
|
return new Uint8Array(arrayBuffer);
|
|
|
|
|
}
|
|
|
|
|
async function decrypt(iv, key, cyphertext) {
|
|
|
|
|
const arrayBuffer = await crypto.subtle.decrypt(
|
|
|
|
|
{ name: ENCRYPTION_ALG, iv },
|
|
|
|
|
key,
|
|
|
|
|
cyphertext
|
|
|
|
|
);
|
|
|
|
|
return arrayBufferToString(arrayBuffer);
|
|
|
|
|
}
|
|
|
|
|
async function hash(encoded) {
|
|
|
|
|
return await crypto.subtle.digest("SHA-256", encoded);
|
|
|
|
|
}
|
|
|
|
|
function generateIV() {
|
|
|
|
|
return crypto.getRandomValues(new Uint8Array(IV_SIZE));
|
|
|
|
|
}
|
|
|
|
|
async function importKey(passphrase, purpose) {
|
|
|
|
|
return await crypto.subtle.importKey(
|
|
|
|
|
"raw",
|
|
|
|
|
await hash(encode(passphrase)),
|
|
|
|
|
{ name: ENCRYPTION_ALG },
|
|
|
|
|
false,
|
|
|
|
|
[purpose]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
function arrayBufferToString(arrayBuffer) {
|
|
|
|
|
const uInt8Array = new Uint8Array(arrayBuffer);
|
|
|
|
|
return decode(uInt8Array);
|
|
|
|
|
}
|
|
|
|
|
function uInt8ToArray(uInt8Array) {
|
|
|
|
|
return Array.from(uInt8Array);
|
|
|
|
|
}
|
|
|
|
|
function arrayToUint8(array) {
|
|
|
|
|
return new Uint8Array(array);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/Model/Chat/chatModel.ts
|
|
|
|
|
var ChatModel = class _ChatModel {
|
|
|
|
|
// init
|
|
|
|
|
constructor(storageModel2, connectionModel2, settingsModel2, chatListModel2, chatId) {
|
|
|
|
|
this.chatMessageHandlerManager = new HandlerManager();
|
2026-04-06 20:47:49 +00:00
|
|
|
this.reactionHandlerManager = new HandlerManager();
|
2026-04-01 08:53:50 +00:00
|
|
|
// paths
|
|
|
|
|
this.getBasePath = () => {
|
|
|
|
|
return StorageModel.getPath(
|
|
|
|
|
"chat" /* Chat */,
|
|
|
|
|
filePaths.chat.chatBase(this.id)
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.getInfoPath = () => {
|
|
|
|
|
return StorageModel.getPath(
|
|
|
|
|
"chat" /* Chat */,
|
|
|
|
|
filePaths.chat.info(this.id)
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.getColorPath = () => {
|
|
|
|
|
return StorageModel.getPath(
|
|
|
|
|
"chat" /* Chat */,
|
|
|
|
|
filePaths.chat.color(this.id)
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.getMessageDirPath = () => {
|
|
|
|
|
return StorageModel.getPath(
|
|
|
|
|
"chat" /* Chat */,
|
|
|
|
|
filePaths.chat.messages(this.id)
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.getMessagePath = (id) => {
|
|
|
|
|
return [...this.getMessageDirPath(), id];
|
|
|
|
|
};
|
2026-04-06 20:47:49 +00:00
|
|
|
this.getReactionDirPath = () => {
|
|
|
|
|
return StorageModel.getPath(
|
|
|
|
|
"chat" /* Chat */,
|
|
|
|
|
filePaths.chat.reactions(this.id)
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.getReactionPath = (id) => {
|
|
|
|
|
return [...this.getReactionDirPath(), id];
|
|
|
|
|
};
|
2026-04-01 08:53:50 +00:00
|
|
|
// handlers
|
|
|
|
|
this.handleMessage = (body) => {
|
|
|
|
|
const chatMessage = parseValidObject(
|
|
|
|
|
body,
|
|
|
|
|
ChatMessageReference
|
|
|
|
|
);
|
|
|
|
|
if (chatMessage == null) return;
|
|
|
|
|
chatMessage.status = "received" /* Received */;
|
|
|
|
|
this.addMessage(chatMessage);
|
2026-04-01 14:16:38 +00:00
|
|
|
this.setReadStatus(true);
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
2026-04-06 20:47:49 +00:00
|
|
|
this.handleReaction = (reaction) => {
|
2026-04-07 18:39:55 +00:00
|
|
|
if (!checkMatchesObjectStructure(reaction, ChatMessageReactionReference))
|
|
|
|
|
return;
|
2026-04-07 18:48:01 +00:00
|
|
|
const reactionPath = this.getReactionPath(reaction.fileId);
|
2026-04-07 19:13:29 +00:00
|
|
|
if (reaction.isDeleting == true) {
|
|
|
|
|
this.storageModel.remove(reactionPath);
|
|
|
|
|
} else {
|
|
|
|
|
this.storageModel.writeStringifiable(reactionPath, reaction);
|
|
|
|
|
}
|
2026-04-07 18:48:01 +00:00
|
|
|
this.reactionHandlerManager.trigger(reaction);
|
2026-04-06 20:47:49 +00:00
|
|
|
};
|
2026-04-01 08:53:50 +00:00
|
|
|
this.handleMessageSent = (chatMessage) => {
|
|
|
|
|
chatMessage.status = "sent" /* Sent */;
|
|
|
|
|
this.addMessage(chatMessage);
|
|
|
|
|
};
|
|
|
|
|
// settings
|
|
|
|
|
this.setPrimaryChannel = (primaryChannel) => {
|
|
|
|
|
this.info.primaryChannel = primaryChannel;
|
|
|
|
|
this.storeInfo();
|
|
|
|
|
this.subscribe();
|
|
|
|
|
};
|
|
|
|
|
this.setSecondaryChannels = (secondaryChannels) => {
|
|
|
|
|
this.info.secondaryChannels = secondaryChannels;
|
|
|
|
|
this.storeInfo();
|
|
|
|
|
};
|
|
|
|
|
this.setEncryptionKey = (key) => {
|
|
|
|
|
this.info.encryptionKey = key;
|
|
|
|
|
this.storeInfo();
|
|
|
|
|
};
|
|
|
|
|
this.setColor = (color) => {
|
|
|
|
|
this.color = color;
|
|
|
|
|
this.storeColor();
|
|
|
|
|
};
|
|
|
|
|
// messaging
|
|
|
|
|
this.addMessage = async (chatMessage) => {
|
|
|
|
|
await this.decryptMessage(chatMessage);
|
|
|
|
|
if (chatMessage.body != "") {
|
|
|
|
|
const messagePath = this.getMessagePath(chatMessage.id);
|
|
|
|
|
this.storageModel.writeStringifiable(messagePath, chatMessage);
|
|
|
|
|
this.chatMessageHandlerManager.trigger(chatMessage);
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
this.fileModel.handleStringifiedFileContent(
|
|
|
|
|
chatMessage.stringifiedFile
|
|
|
|
|
);
|
|
|
|
|
};
|
2026-04-06 20:47:49 +00:00
|
|
|
this.getNameAndChannel = () => {
|
2026-04-01 08:53:50 +00: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);
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
const combinedChannel = allChannels.join("/");
|
2026-04-06 20:47:49 +00:00
|
|
|
return [senderName, combinedChannel];
|
|
|
|
|
};
|
|
|
|
|
this.sendMessage = async (body, fileContent) => {
|
|
|
|
|
const nameAndChannel = this.getNameAndChannel();
|
|
|
|
|
if (nameAndChannel == false) return;
|
|
|
|
|
const [senderName, combinedChannel] = nameAndChannel;
|
2026-04-01 08:53:50 +00:00
|
|
|
const chatMessage = await _ChatModel.createChatMessage(
|
|
|
|
|
combinedChannel,
|
|
|
|
|
senderName,
|
|
|
|
|
this.info.encryptionKey,
|
|
|
|
|
body,
|
|
|
|
|
fileContent
|
|
|
|
|
);
|
|
|
|
|
this.addMessage(chatMessage);
|
|
|
|
|
this.connectionModel.sendMessageOrStore(chatMessage);
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
this.decryptMessage = async (chatMessage) => {
|
|
|
|
|
const decryptedBody = await decryptString(
|
|
|
|
|
chatMessage.body,
|
|
|
|
|
this.info.encryptionKey
|
|
|
|
|
);
|
|
|
|
|
const decryptedFile = await decryptString(
|
|
|
|
|
chatMessage.stringifiedFile ?? "",
|
|
|
|
|
this.info.encryptionKey
|
|
|
|
|
);
|
|
|
|
|
chatMessage.body = decryptedBody;
|
|
|
|
|
chatMessage.stringifiedFile = decryptedFile;
|
|
|
|
|
};
|
2026-04-07 18:48:01 +00:00
|
|
|
this.sendReaction = async (messageId, content, isDeleting) => {
|
2026-04-06 20:47:49 +00:00
|
|
|
const nameAndChannel = this.getNameAndChannel();
|
|
|
|
|
if (nameAndChannel == false) return;
|
|
|
|
|
const [senderName, combinedChannel] = nameAndChannel;
|
|
|
|
|
const reaction = _ChatModel.createMessageReaction(
|
|
|
|
|
messageId,
|
|
|
|
|
senderName,
|
2026-04-07 18:48:01 +00:00
|
|
|
content,
|
|
|
|
|
isDeleting
|
2026-04-06 20:47:49 +00:00
|
|
|
);
|
|
|
|
|
this.sendMessage("", reaction);
|
2026-04-07 18:48:01 +00:00
|
|
|
this.handleReaction(reaction);
|
2026-04-06 20:47:49 +00:00
|
|
|
};
|
2026-04-01 08:53:50 +00:00
|
|
|
this.subscribe = () => {
|
|
|
|
|
this.connectionModel.addChannel(this.info.primaryChannel);
|
|
|
|
|
};
|
2026-04-01 14:16:38 +00:00
|
|
|
this.setReadStatus = (hasUnreadMessages) => {
|
|
|
|
|
this.info.hasUnreadMessages = hasUnreadMessages;
|
2026-04-01 08:53:50 +00:00
|
|
|
this.storeInfo();
|
|
|
|
|
};
|
|
|
|
|
// storage
|
|
|
|
|
this.storeInfo = () => {
|
|
|
|
|
this.storageModel.writeStringifiable(this.getInfoPath(), this.info);
|
|
|
|
|
};
|
|
|
|
|
this.storeColor = () => {
|
|
|
|
|
this.storageModel.write(this.getColorPath(), this.color);
|
|
|
|
|
};
|
|
|
|
|
this.delete = () => {
|
|
|
|
|
this.chatListModel.untrackChat(this);
|
|
|
|
|
const dirPath = this.getBasePath();
|
|
|
|
|
this.storageModel.removeRecursively(dirPath);
|
|
|
|
|
};
|
|
|
|
|
// load
|
|
|
|
|
this.loadInfo = () => {
|
|
|
|
|
const info = this.storageModel.readStringifiable(
|
|
|
|
|
this.getInfoPath(),
|
|
|
|
|
ChatInfoReference
|
|
|
|
|
);
|
|
|
|
|
if (info != null) {
|
|
|
|
|
this.info = info;
|
|
|
|
|
} else {
|
|
|
|
|
this.info = _ChatModel.generateChatInfo("0");
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
this.loadColor = () => {
|
|
|
|
|
const path = this.getColorPath();
|
|
|
|
|
const color = this.storageModel.read(path);
|
|
|
|
|
if (!color) {
|
|
|
|
|
this.color = "standard" /* Standard */;
|
|
|
|
|
} else {
|
|
|
|
|
this.color = color;
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
this.id = chatId;
|
|
|
|
|
this.connectionModel = connectionModel2;
|
|
|
|
|
this.settingsModel = settingsModel2;
|
|
|
|
|
this.storageModel = storageModel2;
|
|
|
|
|
this.chatListModel = chatListModel2;
|
|
|
|
|
this.loadInfo();
|
|
|
|
|
this.loadColor();
|
|
|
|
|
this.subscribe();
|
|
|
|
|
this.fileModel = new FileModel2(
|
|
|
|
|
this.storageModel,
|
|
|
|
|
this.settingsModel,
|
|
|
|
|
this
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
get secondaryChannels() {
|
|
|
|
|
return this.info.secondaryChannels.sort(localeCompare);
|
|
|
|
|
}
|
|
|
|
|
get messages() {
|
|
|
|
|
const messageIds = this.storageModel.list(
|
|
|
|
|
this.getMessageDirPath()
|
|
|
|
|
);
|
|
|
|
|
if (!Array.isArray(messageIds)) return [];
|
|
|
|
|
const chatMessages = [];
|
|
|
|
|
for (const messageId of messageIds) {
|
|
|
|
|
const messagePath = this.getMessagePath(messageId);
|
|
|
|
|
const chatMessage = this.storageModel.readStringifiable(
|
|
|
|
|
messagePath,
|
|
|
|
|
ChatMessageReference
|
|
|
|
|
);
|
2026-04-06 20:47:49 +00:00
|
|
|
if (chatMessage == null) continue;
|
2026-04-01 08:53:50 +00:00
|
|
|
chatMessages.push(chatMessage);
|
|
|
|
|
}
|
|
|
|
|
const sorted = chatMessages.sort(
|
|
|
|
|
(a, b) => a.dateSent.localeCompare(b.dateSent)
|
|
|
|
|
);
|
|
|
|
|
return sorted;
|
|
|
|
|
}
|
2026-04-06 20:47:49 +00:00
|
|
|
get reactions() {
|
|
|
|
|
const reactionIds = this.storageModel.list(
|
|
|
|
|
this.getReactionDirPath()
|
|
|
|
|
);
|
|
|
|
|
if (!Array.isArray(reactionIds)) return [];
|
|
|
|
|
const reactions = [];
|
|
|
|
|
for (const reactionId of reactionIds) {
|
|
|
|
|
const reactionPath = this.getReactionPath(reactionId);
|
|
|
|
|
const reaction = this.storageModel.readStringifiable(
|
|
|
|
|
reactionPath,
|
|
|
|
|
ChatMessageReactionReference
|
|
|
|
|
);
|
|
|
|
|
if (reaction == null) continue;
|
|
|
|
|
reactions.push(reaction);
|
|
|
|
|
}
|
|
|
|
|
return reactions;
|
|
|
|
|
}
|
2026-04-01 19:00:16 +00:00
|
|
|
// utility
|
|
|
|
|
static splitChannel(channelString) {
|
|
|
|
|
return channelString.split("/");
|
|
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
static {
|
|
|
|
|
this.generateChatInfo = (primaryChannel) => {
|
|
|
|
|
return {
|
|
|
|
|
dataVersion: DATA_VERSION,
|
|
|
|
|
primaryChannel,
|
|
|
|
|
secondaryChannels: [],
|
|
|
|
|
encryptionKey: "",
|
|
|
|
|
hasUnreadMessages: false
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
static {
|
|
|
|
|
this.createChatMessage = async (channel, sender, encryptionKey, body, fileContent) => {
|
|
|
|
|
const chatMessage = {
|
|
|
|
|
dataVersion: DATA_VERSION,
|
|
|
|
|
id: v4_default(),
|
|
|
|
|
channel,
|
|
|
|
|
sender,
|
|
|
|
|
body,
|
|
|
|
|
dateSent: createTimestamp(),
|
|
|
|
|
status: "outbox" /* Outbox */,
|
|
|
|
|
stringifiedFile: ""
|
|
|
|
|
};
|
|
|
|
|
if (fileContent != void 0) {
|
|
|
|
|
const stringifiedFile = stringify(fileContent);
|
|
|
|
|
chatMessage.stringifiedFile = stringifiedFile;
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
if (encryptionKey != "") {
|
|
|
|
|
chatMessage.body = await encryptString(
|
|
|
|
|
chatMessage.body,
|
|
|
|
|
encryptionKey
|
|
|
|
|
);
|
|
|
|
|
chatMessage.stringifiedFile = await encryptString(
|
|
|
|
|
chatMessage.stringifiedFile,
|
|
|
|
|
encryptionKey
|
|
|
|
|
);
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
return chatMessage;
|
|
|
|
|
};
|
|
|
|
|
}
|
2026-04-06 20:47:49 +00:00
|
|
|
static {
|
2026-04-07 18:48:01 +00:00
|
|
|
this.createMessageReaction = (messageId, sender, content, isDeleting) => {
|
2026-04-06 20:47:49 +00:00
|
|
|
const fileContent = FileModel2.createFileContent(v4_default(), "reaction");
|
|
|
|
|
const reaction = {
|
|
|
|
|
...fileContent,
|
2026-04-11 17:47:24 +00:00
|
|
|
fileId: _ChatModel.createMessageReactionId(messageId, sender),
|
2026-04-06 20:47:49 +00:00
|
|
|
messageId,
|
|
|
|
|
sender,
|
2026-04-07 18:48:01 +00:00
|
|
|
content,
|
|
|
|
|
isDeleting
|
2026-04-06 20:47:49 +00:00
|
|
|
};
|
|
|
|
|
return reaction;
|
|
|
|
|
};
|
|
|
|
|
}
|
2026-04-07 20:19:52 +00:00
|
|
|
static {
|
2026-04-11 17:47:24 +00:00
|
|
|
this.createMessageReactionId = (messageId, sender) => {
|
|
|
|
|
return messageId + sender;
|
2026-04-07 20:19:52 +00:00
|
|
|
};
|
|
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
var ChatInfoReference = {
|
|
|
|
|
dataVersion: DATA_VERSION,
|
|
|
|
|
primaryChannel: "",
|
|
|
|
|
secondaryChannels: [""],
|
|
|
|
|
encryptionKey: "",
|
|
|
|
|
hasUnreadMessages: true
|
|
|
|
|
};
|
|
|
|
|
var ChatMessageReference = {
|
|
|
|
|
dataVersion: DATA_VERSION,
|
|
|
|
|
id: "",
|
|
|
|
|
channel: "",
|
|
|
|
|
sender: "",
|
|
|
|
|
body: "",
|
|
|
|
|
dateSent: "",
|
|
|
|
|
status: "",
|
|
|
|
|
stringifiedFile: ""
|
|
|
|
|
};
|
2026-04-06 20:47:49 +00:00
|
|
|
var ChatMessageReactionReference = {
|
|
|
|
|
dataVersion: DATA_VERSION,
|
|
|
|
|
fileId: "",
|
|
|
|
|
fileContentId: "",
|
|
|
|
|
creationDate: "",
|
|
|
|
|
type: "reaction",
|
|
|
|
|
messageId: "",
|
|
|
|
|
sender: "",
|
2026-04-07 18:48:01 +00:00
|
|
|
content: "",
|
|
|
|
|
isDeleting: false
|
2026-04-06 20:47:49 +00:00
|
|
|
};
|
2026-04-01 08:53:50 +00:00
|
|
|
|
|
|
|
|
// src/Model/Chat/chatListModel.ts
|
|
|
|
|
var ChatListModel = class {
|
|
|
|
|
// init
|
|
|
|
|
constructor(storageModel2, settingsModel2, connectionModel2) {
|
|
|
|
|
// data
|
|
|
|
|
this.chatModels = /* @__PURE__ */ new Set();
|
|
|
|
|
// handlers
|
|
|
|
|
this.messageHandler = (data) => {
|
|
|
|
|
const channel = data.messageChannel;
|
|
|
|
|
const body = data.messageBody;
|
|
|
|
|
if (channel == void 0) return;
|
|
|
|
|
if (body == void 0) return;
|
|
|
|
|
this.routeMessageToCorrectChatModel(
|
|
|
|
|
channel,
|
|
|
|
|
(chatModel) => chatModel.handleMessage(body)
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.messageSentHandler = (chatMessage) => {
|
|
|
|
|
const channel = chatMessage.channel;
|
|
|
|
|
this.routeMessageToCorrectChatModel(
|
|
|
|
|
channel,
|
|
|
|
|
(chatModel) => chatModel.handleMessageSent(chatMessage)
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
// methods
|
|
|
|
|
this.routeMessageToCorrectChatModel = (channel, fn) => {
|
|
|
|
|
const allChannels = channel.split("/");
|
|
|
|
|
for (const chatModel of this.chatModels) {
|
|
|
|
|
for (const channel2 of allChannels) {
|
|
|
|
|
if (channel2 != chatModel.info.primaryChannel) continue;
|
|
|
|
|
fn(chatModel);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2024-07-21 01:12:35 +02:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
// storage
|
|
|
|
|
this.addChatModel = (chatModel) => {
|
|
|
|
|
this.chatModels.add(chatModel);
|
|
|
|
|
};
|
|
|
|
|
this.createChat = (primaryChannel) => {
|
|
|
|
|
const id = v4_default();
|
|
|
|
|
const chatModel = new ChatModel(
|
|
|
|
|
this.storageModel,
|
|
|
|
|
this.connectionModel,
|
|
|
|
|
this.settingsModel,
|
|
|
|
|
this,
|
|
|
|
|
id
|
2026-04-01 08:29:39 +00:00
|
|
|
);
|
2026-04-01 08:53:50 +00:00
|
|
|
chatModel.setPrimaryChannel(primaryChannel);
|
|
|
|
|
this.addChatModel(chatModel);
|
|
|
|
|
return chatModel;
|
|
|
|
|
};
|
|
|
|
|
this.untrackChat = (chat) => {
|
|
|
|
|
this.chatModels.delete(chat);
|
|
|
|
|
};
|
|
|
|
|
// load
|
|
|
|
|
this.loadChats = () => {
|
|
|
|
|
const chatDir = StorageModel.getPath(
|
|
|
|
|
"chat" /* Chat */,
|
|
|
|
|
filePaths.chat.base
|
|
|
|
|
);
|
|
|
|
|
const chatIds = this.storageModel.list(chatDir);
|
|
|
|
|
for (const chatId of chatIds) {
|
|
|
|
|
const chatModel = new ChatModel(
|
|
|
|
|
this.storageModel,
|
|
|
|
|
this.connectionModel,
|
|
|
|
|
this.settingsModel,
|
|
|
|
|
this,
|
|
|
|
|
chatId
|
|
|
|
|
);
|
|
|
|
|
this.addChatModel(chatModel);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.storageModel = storageModel2;
|
|
|
|
|
this.settingsModel = settingsModel2;
|
|
|
|
|
this.connectionModel = connectionModel2;
|
|
|
|
|
this.loadChats();
|
|
|
|
|
connectionModel2.messageHandlerManager.addHandler(this.messageHandler);
|
|
|
|
|
connectionModel2.messageSentHandlerManager.addHandler(
|
|
|
|
|
this.messageSentHandler
|
|
|
|
|
);
|
2024-07-20 05:10:50 +02:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
2024-07-20 05:10:50 +02:00
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/View/translations.ts
|
|
|
|
|
var englishTranslations = {
|
|
|
|
|
updater: {
|
|
|
|
|
migrated: "Migrated"
|
|
|
|
|
},
|
|
|
|
|
general: {
|
|
|
|
|
deleteItemButtonAudioLabel: "delete item",
|
|
|
|
|
searchButtonAudioLabel: "search",
|
|
|
|
|
abortButton: "Abort",
|
|
|
|
|
cancelButton: "Cancel",
|
|
|
|
|
closeButton: "Close",
|
|
|
|
|
backButton: "Back",
|
|
|
|
|
continueButton: "Continue",
|
|
|
|
|
confirmButton: "Confirm",
|
|
|
|
|
saveButton: "Save",
|
|
|
|
|
setButton: "Set",
|
|
|
|
|
reloadAppButton: "Reload App",
|
|
|
|
|
fileVersionLabel: "Version",
|
|
|
|
|
searchLabel: "Search",
|
|
|
|
|
waitingLabel: "Waiting...",
|
|
|
|
|
restoreConnection: "Restore connection",
|
|
|
|
|
noPageSelected: "No page selected"
|
|
|
|
|
},
|
|
|
|
|
regional: {
|
|
|
|
|
weekdays: {
|
|
|
|
|
full: [
|
|
|
|
|
"Sunday",
|
|
|
|
|
"Monday",
|
|
|
|
|
"Tuesday",
|
|
|
|
|
"Wednesday",
|
|
|
|
|
"Thursday",
|
|
|
|
|
"Friday",
|
|
|
|
|
"Saturday"
|
|
|
|
|
],
|
|
|
|
|
abbreviated: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
homePage: {
|
|
|
|
|
appName: "Comms",
|
|
|
|
|
///
|
|
|
|
|
overviewHeadline: "Overview",
|
|
|
|
|
serverAddress: "Server address",
|
|
|
|
|
serverAddressPlaceholder: "wss://192.168.0.69:3000",
|
|
|
|
|
connectAudioLabel: "connect to server",
|
|
|
|
|
disconnectAudioLabel: "disconnect from server",
|
|
|
|
|
manageConnectionsAudioLabel: "manage connections",
|
|
|
|
|
yourNameLabel: "Your name",
|
|
|
|
|
yourNamePlaceholder: "Jane Doe",
|
|
|
|
|
setNameButtonAudioLabel: "set name",
|
|
|
|
|
settingsButton: "Settings",
|
|
|
|
|
manageStorageButton: "Manage storage",
|
|
|
|
|
transferDataButton: "Data Transfer",
|
|
|
|
|
scrollToChatButton: "Chats",
|
|
|
|
|
///
|
|
|
|
|
backToOverviewAudioLabel: "go back to overview",
|
|
|
|
|
chatsHeadline: "Chats",
|
|
|
|
|
addChatAudioLabel: "name of new chat",
|
|
|
|
|
addChatPlaceholder: "Add chat",
|
|
|
|
|
addChatButton: "Add chat"
|
|
|
|
|
},
|
|
|
|
|
settings: {
|
|
|
|
|
pages: {
|
|
|
|
|
appearance: "Appearance",
|
|
|
|
|
regional: "Language & Region",
|
|
|
|
|
info: "About Comms"
|
|
|
|
|
},
|
|
|
|
|
themes: {
|
|
|
|
|
dark: "Dark",
|
|
|
|
|
light: "Light",
|
|
|
|
|
system: "Device theme"
|
|
|
|
|
},
|
|
|
|
|
version: "Version",
|
|
|
|
|
language: "Language",
|
|
|
|
|
firstDayOfWeekLabel: "First day of week"
|
|
|
|
|
},
|
|
|
|
|
connectionModal: {
|
|
|
|
|
connectionModalHeadline: "Manage Connections",
|
|
|
|
|
///
|
|
|
|
|
connectButtonAudioLabel: "connect"
|
|
|
|
|
},
|
|
|
|
|
dataTransferModal: {
|
|
|
|
|
transferDataHeadline: "Data Transfer",
|
|
|
|
|
selectionDescription: "Select the data that you want to transfer.",
|
|
|
|
|
dataEntryDescription: "Enter this data on the other device.",
|
|
|
|
|
dataEntryInputDescription: "Enter the data displayed on the other device.",
|
|
|
|
|
notConnectedError: "You are not connected to any server.",
|
|
|
|
|
///
|
|
|
|
|
fromThisDeviceButton: "From this device",
|
|
|
|
|
toThisDeviceButton: "To this device",
|
|
|
|
|
///
|
|
|
|
|
generalHeadline: "General",
|
|
|
|
|
connectionData: "Connection Data",
|
|
|
|
|
settingsData: "Settings Data",
|
|
|
|
|
chatsHeadline: "Chats",
|
|
|
|
|
///
|
|
|
|
|
transferChannelHeadline: "Transfer Chanel",
|
|
|
|
|
transferKeyHeadline: "Transfer Encryption Key",
|
|
|
|
|
sendButton: "Send",
|
|
|
|
|
sendAgainButton: "Send again",
|
|
|
|
|
///
|
|
|
|
|
filesSentCount: (count) => `Files sent: ${count}.`,
|
|
|
|
|
allFilesSent: "Done.",
|
|
|
|
|
filesReceivedCount: (count) => `Files received: ${count}.`
|
|
|
|
|
},
|
|
|
|
|
storage: {
|
|
|
|
|
noItemSelected: "No item selected",
|
|
|
|
|
notAFile: "(not a file)",
|
|
|
|
|
contentEmpty: "(empty)",
|
|
|
|
|
path: "Path",
|
|
|
|
|
content: "Content",
|
|
|
|
|
deleteItem: "Delete item",
|
|
|
|
|
removeJunkButton: "Delete junk files"
|
|
|
|
|
},
|
|
|
|
|
chatPage: {
|
|
|
|
|
closeChatAudioLabe: "close chat",
|
|
|
|
|
chatSettingsAudioLabel: "chat settings",
|
|
|
|
|
pages: {
|
|
|
|
|
settings: "Settings",
|
|
|
|
|
messages: "Messages",
|
|
|
|
|
tasks: "Tasks",
|
|
|
|
|
calendar: "Calendar"
|
|
|
|
|
},
|
|
|
|
|
settings: {
|
|
|
|
|
settingsHeadline: "Settings",
|
|
|
|
|
primaryChannelLabel: "Primary channel",
|
|
|
|
|
setPrimaryChannelButtonAudioLabel: "set primary channel",
|
|
|
|
|
newSecondaryChannelPlaceholder: "Add secondary channel",
|
|
|
|
|
newSecondaryChannelAudioLabel: "name of new secondary channel",
|
|
|
|
|
addSecondaryChannelButtonAudioLabel: "add secondary channel",
|
|
|
|
|
encryptionKeyLabel: "Encryption key",
|
|
|
|
|
setEncryptionKeyButtonAudioLabel: "set encryption key",
|
|
|
|
|
showEncryptionKey: "Show encryption key",
|
|
|
|
|
deleteChatButton: "Delete entire chat"
|
|
|
|
|
},
|
|
|
|
|
message: {
|
|
|
|
|
messagesHeadline: "Messages",
|
|
|
|
|
messageFilterHeadline: "Filter messages",
|
|
|
|
|
messageFilterReactionsHadline: "Reactions",
|
|
|
|
|
messageFilterAllReactionsButton: "Show all",
|
|
|
|
|
///
|
|
|
|
|
composerInputPlaceholder: "Type a message...",
|
|
|
|
|
sendMessageButtonAudioLabel: "send message",
|
|
|
|
|
filterMessagesButtonAudioLabel: "filter messages",
|
|
|
|
|
///
|
|
|
|
|
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",
|
|
|
|
|
//
|
|
|
|
|
thumbsUpReaction: "Reaction: thumbs up",
|
|
|
|
|
checkReaction: "Reaction: check",
|
|
|
|
|
stopReaction: "Reaction: stop sign",
|
|
|
|
|
attentionReaction: "Reaction: exclamation mark",
|
|
|
|
|
doubleAttentionReaction: "Reaction: double exclamation mark",
|
|
|
|
|
questionReaction: "Reaction: question mark"
|
|
|
|
|
},
|
|
|
|
|
task: {
|
|
|
|
|
newBoardNamePlaceholder: "Create a board",
|
|
|
|
|
createBoardButtonAudioLabel: "create board",
|
|
|
|
|
///
|
|
|
|
|
noBoardSelected: "No board selected",
|
|
|
|
|
boardNotFound: "Board not found",
|
|
|
|
|
///
|
|
|
|
|
closeBoardButtonAudioLabel: "close board",
|
|
|
|
|
toggleBoardButtonAudioLabel: "toggle board list",
|
|
|
|
|
showBoardSettingsButtonAudioLabel: "show board settigns",
|
|
|
|
|
listViewButtonAudioLabel: "list view",
|
|
|
|
|
kanbanViewButtonAudioLabel: "kanban view",
|
|
|
|
|
statusViewButtonAudioLabel: "status grid view",
|
|
|
|
|
filterTasksButtonAudioLabel: "filter tasks",
|
|
|
|
|
createTaskButtonAudioLabel: "create new task",
|
|
|
|
|
///
|
|
|
|
|
boardSettingsHeadline: "Board Settings",
|
|
|
|
|
boardNameInputLabel: "Board name",
|
|
|
|
|
deleteBoardButton: "Delete board and all tasks",
|
|
|
|
|
///
|
|
|
|
|
taskSettingsHeadline: "Edit Task",
|
|
|
|
|
taskNameLabel: "Title",
|
|
|
|
|
taskBoardLabel: "Board",
|
|
|
|
|
taskCategoryLabel: "Category",
|
|
|
|
|
taskStatusLabel: "Status",
|
|
|
|
|
taskPriorityLabel: "Priority",
|
|
|
|
|
taskDescriptionLabel: "Description",
|
|
|
|
|
taskDateLabel: "Date",
|
|
|
|
|
taskTimeLabel: "Time",
|
|
|
|
|
deleteTaskButton: "Delete task",
|
|
|
|
|
///
|
|
|
|
|
filterTasksHeadline: "Filter Tasks",
|
|
|
|
|
///
|
|
|
|
|
renameCategoryInputPlaceholder: "Rename category",
|
|
|
|
|
renameStatusInputPlaceholder: "Rename status"
|
|
|
|
|
},
|
|
|
|
|
calendar: {
|
|
|
|
|
eventsBoard: "Events",
|
|
|
|
|
///
|
|
|
|
|
todayButtonAudioLabel: "go to today",
|
|
|
|
|
previousMonthButtonAudioLabel: "previous month",
|
|
|
|
|
nextMonthButtonAudioLabel: "next month",
|
|
|
|
|
yearInputAudioLabel: "year",
|
|
|
|
|
monthInputAudioLabel: "month",
|
|
|
|
|
yearInputPlaceholder: "2000",
|
|
|
|
|
monthInputPlaceholder: "01",
|
|
|
|
|
///
|
|
|
|
|
searchEventsHeadline: "Search Events",
|
|
|
|
|
///
|
|
|
|
|
events: "Events",
|
|
|
|
|
noEvents: "No events"
|
|
|
|
|
}
|
2024-07-26 06:20:16 +02:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
2026-04-12 19:20:37 +00:00
|
|
|
var allTranslations = {
|
|
|
|
|
en: englishTranslations,
|
|
|
|
|
de: {
|
|
|
|
|
updater: {
|
|
|
|
|
migrated: "Migriert"
|
|
|
|
|
},
|
|
|
|
|
general: {
|
|
|
|
|
deleteItemButtonAudioLabel: "element l\xF6schen",
|
|
|
|
|
searchButtonAudioLabel: "suchen",
|
|
|
|
|
abortButton: "Abbrechen",
|
|
|
|
|
cancelButton: "Abbrechen",
|
|
|
|
|
closeButton: "Schlie\xDFen",
|
|
|
|
|
backButton: "Zur\xFCck",
|
|
|
|
|
continueButton: "Weiter",
|
|
|
|
|
confirmButton: "Best\xE4tigen",
|
|
|
|
|
saveButton: "Speichern",
|
|
|
|
|
setButton: "OK",
|
|
|
|
|
reloadAppButton: "Neu laden",
|
|
|
|
|
fileVersionLabel: "Version",
|
|
|
|
|
searchLabel: "Suche",
|
|
|
|
|
waitingLabel: "Warten...",
|
|
|
|
|
restoreConnection: "Verbindung wiederherstellen",
|
|
|
|
|
noPageSelected: "Keine Seite ausgew\xE4hlt"
|
|
|
|
|
},
|
|
|
|
|
regional: {
|
|
|
|
|
weekdays: {
|
|
|
|
|
full: [
|
|
|
|
|
"Sonntag",
|
|
|
|
|
"Montag",
|
|
|
|
|
"Dienstag",
|
|
|
|
|
"Mittwoch",
|
|
|
|
|
"Donnerstag",
|
|
|
|
|
"Freitag",
|
|
|
|
|
"Samstag"
|
|
|
|
|
],
|
|
|
|
|
abbreviated: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"]
|
2026-04-01 08:46:10 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
},
|
|
|
|
|
homePage: {
|
|
|
|
|
appName: "Comms",
|
|
|
|
|
overviewHeadline: "\xDCbersicht",
|
|
|
|
|
serverAddress: "Serveradresse",
|
|
|
|
|
serverAddressPlaceholder: "wss://192.168.0.69:3000",
|
|
|
|
|
connectAudioLabel: "mit Server verbinden",
|
|
|
|
|
disconnectAudioLabel: "vom Server trennen",
|
|
|
|
|
manageConnectionsAudioLabel: "Verbindungen verwalten",
|
|
|
|
|
yourNameLabel: "Dein Name",
|
|
|
|
|
yourNamePlaceholder: "Max Mustermann",
|
|
|
|
|
setNameButtonAudioLabel: "Name speichern",
|
|
|
|
|
settingsButton: "Einstellungen",
|
|
|
|
|
manageStorageButton: "Daten verwalten",
|
|
|
|
|
transferDataButton: "Daten\xFCbertragung",
|
|
|
|
|
scrollToChatButton: "Chats",
|
|
|
|
|
backToOverviewAudioLabel: "zur\xFCck zur \xFCbersicht",
|
|
|
|
|
chatsHeadline: "Chats",
|
|
|
|
|
addChatAudioLabel: "Name des neuen Chats",
|
|
|
|
|
addChatPlaceholder: "Chat hinzuf\xFCgen",
|
|
|
|
|
addChatButton: "Chat hinzuf\xFCgen"
|
|
|
|
|
},
|
|
|
|
|
settings: {
|
|
|
|
|
pages: {
|
|
|
|
|
appearance: "Erscheinungsbild",
|
|
|
|
|
regional: "Sprache & Region",
|
|
|
|
|
info: "\xDCber Comms"
|
|
|
|
|
},
|
|
|
|
|
themes: {
|
|
|
|
|
dark: "Dunkel",
|
|
|
|
|
light: "Hell",
|
|
|
|
|
system: "Ger\xE4teeinstellung"
|
|
|
|
|
},
|
|
|
|
|
version: "Version",
|
|
|
|
|
language: "Sprache",
|
|
|
|
|
firstDayOfWeekLabel: "Erster Wochentag"
|
|
|
|
|
},
|
|
|
|
|
connectionModal: {
|
|
|
|
|
connectionModalHeadline: "Verbindungen verwalten",
|
|
|
|
|
connectButtonAudioLabel: "verbinden"
|
|
|
|
|
},
|
|
|
|
|
dataTransferModal: {
|
|
|
|
|
transferDataHeadline: "Daten\xFCbertragung",
|
|
|
|
|
selectionDescription: "W\xE4hle die Daten aus, die du \xFCbertragen m\xF6chtest.",
|
|
|
|
|
dataEntryDescription: "Gib diese Informationen auf dem anderen Ger\xE4t ein.",
|
|
|
|
|
dataEntryInputDescription: "Gib die auf dem anderen Ger\xE4t angezeigten Informationen ein.",
|
|
|
|
|
notConnectedError: "Du bist mit keinem Server verbunden.",
|
|
|
|
|
fromThisDeviceButton: "Von diesem Ger\xE4t",
|
|
|
|
|
toThisDeviceButton: "An dieses Ger\xE4t",
|
|
|
|
|
generalHeadline: "Allgemein",
|
|
|
|
|
connectionData: "Verbindungsdaten",
|
|
|
|
|
settingsData: "Einstellungen",
|
|
|
|
|
chatsHeadline: "Chats",
|
|
|
|
|
transferChannelHeadline: "\xDCbertragungskanal",
|
|
|
|
|
transferKeyHeadline: "Schl\xFCssel",
|
|
|
|
|
sendButton: "Senden",
|
|
|
|
|
sendAgainButton: "Erneut senden",
|
|
|
|
|
filesSentCount: (count) => `Dateien gesendet: ${count}.`,
|
|
|
|
|
allFilesSent: "Fertig.",
|
|
|
|
|
filesReceivedCount: (count) => `Dateien empfangen: ${count}.`
|
|
|
|
|
},
|
|
|
|
|
storage: {
|
|
|
|
|
noItemSelected: "Kein Element ausgew\xE4hlt",
|
|
|
|
|
notAFile: "(keine Datei)",
|
|
|
|
|
contentEmpty: "(leer)",
|
|
|
|
|
path: "Pfad",
|
|
|
|
|
content: "Inhalt",
|
|
|
|
|
deleteItem: "Element l\xF6schen",
|
|
|
|
|
removeJunkButton: "Datenm\xFCll l\xF6schen"
|
|
|
|
|
},
|
|
|
|
|
chatPage: {
|
|
|
|
|
closeChatAudioLabe: "Chat schlie\xDFen",
|
|
|
|
|
chatSettingsAudioLabel: "Chateinstellungen",
|
|
|
|
|
pages: {
|
|
|
|
|
settings: "Einstellungen",
|
|
|
|
|
messages: "Nachrichten",
|
|
|
|
|
tasks: "Aufgaben",
|
|
|
|
|
calendar: "Kalender"
|
|
|
|
|
},
|
|
|
|
|
settings: {
|
|
|
|
|
settingsHeadline: "Einstellungen",
|
|
|
|
|
primaryChannelLabel: "Hauptkanal",
|
|
|
|
|
setPrimaryChannelButtonAudioLabel: "Hauptkanal festlegen",
|
|
|
|
|
newSecondaryChannelPlaceholder: "Sekund\xE4ren Kanal hinzuf\xFCgen",
|
|
|
|
|
newSecondaryChannelAudioLabel: "Name des neuen sekund\xE4ren Kanals",
|
|
|
|
|
addSecondaryChannelButtonAudioLabel: "Sekund\xE4ren Kanal hinzuf\xFCgen",
|
|
|
|
|
encryptionKeyLabel: "Schl\xFCssel",
|
|
|
|
|
setEncryptionKeyButtonAudioLabel: "Schl\xFCssel festlegen",
|
|
|
|
|
showEncryptionKey: "Schl\xFCssel anzeigen",
|
|
|
|
|
deleteChatButton: "Gesamten Chat l\xF6schen"
|
|
|
|
|
},
|
|
|
|
|
message: {
|
|
|
|
|
messagesHeadline: "Nachrichten",
|
|
|
|
|
messageFilterHeadline: "Nachrichten filtern",
|
|
|
|
|
messageFilterReactionsHadline: "Reaktionen",
|
|
|
|
|
messageFilterAllReactionsButton: "Alle anzeigen",
|
|
|
|
|
composerInputPlaceholder: "Schreib eine Nachricht...",
|
|
|
|
|
sendMessageButtonAudioLabel: "nachricht senden",
|
|
|
|
|
filterMessagesButtonAudioLabel: "nachrichten filtern",
|
|
|
|
|
showMessageInfoButtonAudioLabel: "nachrichteninfo anzeigen",
|
|
|
|
|
messageInfoHeadline: "Nachrichteninfo",
|
|
|
|
|
sentBy: "Gesendet von",
|
|
|
|
|
timeSent: "Sendezeit",
|
|
|
|
|
channel: "Kanal",
|
|
|
|
|
messageContent: "Nachrichteninhalt",
|
|
|
|
|
copyMessageButton: "Nachricht kopieren",
|
|
|
|
|
resendMessageButton: "Nachricht erneut senden",
|
|
|
|
|
decryptMessageButton: "Nachricht entschl\xFCsseln",
|
|
|
|
|
deleteMessageButton: "Nachricht l\xF6schen",
|
|
|
|
|
//
|
|
|
|
|
thumbsUpReaction: "Reaktion: Daumen hoch",
|
|
|
|
|
checkReaction: "Reaktion: Haken",
|
|
|
|
|
stopReaction: "Reaktion: Stopp",
|
|
|
|
|
attentionReaction: "Reaktion: Ausrufezeichen",
|
|
|
|
|
doubleAttentionReaction: "Reaktion: doppeltes Ausrufezeichen",
|
|
|
|
|
questionReaction: "Reaktion: Fragezeichen"
|
|
|
|
|
},
|
|
|
|
|
task: {
|
|
|
|
|
newBoardNamePlaceholder: "Board erstellen",
|
|
|
|
|
createBoardButtonAudioLabel: "board erstellen",
|
|
|
|
|
noBoardSelected: "Kein Board ausgew\xE4hlt",
|
|
|
|
|
boardNotFound: "Board nicht gefunden",
|
|
|
|
|
closeBoardButtonAudioLabel: "board schlie\xDFen",
|
|
|
|
|
toggleBoardButtonAudioLabel: "board-liste ein/ausblenden",
|
|
|
|
|
showBoardSettingsButtonAudioLabel: "Board-Einstellungen anzeigen",
|
|
|
|
|
listViewButtonAudioLabel: "Listenansicht",
|
|
|
|
|
kanbanViewButtonAudioLabel: "Kanban-Ansicht",
|
|
|
|
|
statusViewButtonAudioLabel: "Statusrasteransicht",
|
|
|
|
|
filterTasksButtonAudioLabel: "Aufgaben filtern",
|
|
|
|
|
createTaskButtonAudioLabel: "Neue Aufgabe erstellen",
|
|
|
|
|
boardSettingsHeadline: "Board-Einstellungen",
|
|
|
|
|
boardNameInputLabel: "Boardname",
|
|
|
|
|
deleteBoardButton: "Board und alle Aufgaben l\xF6schen",
|
|
|
|
|
taskSettingsHeadline: "Aufgabe bearbeiten",
|
|
|
|
|
taskNameLabel: "Titel",
|
|
|
|
|
taskBoardLabel: "Board",
|
|
|
|
|
taskCategoryLabel: "Kategorie",
|
|
|
|
|
taskStatusLabel: "Status",
|
|
|
|
|
taskPriorityLabel: "Priorit\xE4t",
|
|
|
|
|
taskDescriptionLabel: "Beschreibung",
|
|
|
|
|
taskDateLabel: "Datum",
|
|
|
|
|
taskTimeLabel: "Uhrzeit",
|
|
|
|
|
deleteTaskButton: "Aufgabe l\xF6schen",
|
|
|
|
|
filterTasksHeadline: "Aufgaben filtern",
|
|
|
|
|
renameCategoryInputPlaceholder: "Kategorie umbenennen",
|
|
|
|
|
renameStatusInputPlaceholder: "Status umbenennen"
|
|
|
|
|
},
|
|
|
|
|
calendar: {
|
|
|
|
|
eventsBoard: "Ereignisse",
|
|
|
|
|
///
|
|
|
|
|
todayButtonAudioLabel: "gehe zu heute",
|
|
|
|
|
previousMonthButtonAudioLabel: "vorheriger monat",
|
|
|
|
|
nextMonthButtonAudioLabel: "n\xE4chster monat",
|
|
|
|
|
yearInputAudioLabel: "Jahr",
|
|
|
|
|
monthInputAudioLabel: "Monat",
|
|
|
|
|
yearInputPlaceholder: "2000",
|
|
|
|
|
monthInputPlaceholder: "01",
|
|
|
|
|
searchEventsHeadline: "Ereignisse suchen",
|
|
|
|
|
events: "Ereignisse",
|
|
|
|
|
noEvents: "Keine Ereignisse"
|
2026-04-01 14:16:38 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
es: {
|
|
|
|
|
updater: {
|
|
|
|
|
migrated: "Migrado"
|
|
|
|
|
},
|
|
|
|
|
general: {
|
|
|
|
|
deleteItemButtonAudioLabel: "eliminar elemento",
|
|
|
|
|
searchButtonAudioLabel: "buscar",
|
|
|
|
|
abortButton: "Abortar",
|
|
|
|
|
cancelButton: "Cancelar",
|
|
|
|
|
closeButton: "Cerrar",
|
|
|
|
|
backButton: "Atr\xE1s",
|
|
|
|
|
continueButton: "Continuar",
|
|
|
|
|
confirmButton: "Confirmar",
|
|
|
|
|
saveButton: "Guardar",
|
|
|
|
|
setButton: "OK",
|
|
|
|
|
reloadAppButton: "Recargar app",
|
|
|
|
|
fileVersionLabel: "Versi\xF3n",
|
|
|
|
|
searchLabel: "Buscar",
|
|
|
|
|
waitingLabel: "Esperando...",
|
|
|
|
|
restoreConnection: "Conectar de nuevo",
|
|
|
|
|
noPageSelected: "No p\xE1gina seleccionada"
|
|
|
|
|
},
|
|
|
|
|
regional: {
|
|
|
|
|
weekdays: {
|
|
|
|
|
full: [
|
|
|
|
|
"Domingo",
|
|
|
|
|
"Lunes",
|
|
|
|
|
"Martes",
|
|
|
|
|
"Mi\xE9rcoles",
|
|
|
|
|
"Jueves",
|
|
|
|
|
"Viernes",
|
|
|
|
|
"S\xE1bado"
|
|
|
|
|
],
|
|
|
|
|
abbreviated: ["Dom", "Lun", "Mar", "Mi\xE9", "Jue", "Vie", "S\xE1b"]
|
2024-07-26 05:43:49 +02:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
},
|
|
|
|
|
homePage: {
|
|
|
|
|
appName: "Comms",
|
|
|
|
|
overviewHeadline: "Resumen",
|
|
|
|
|
serverAddress: "Direcci\xF3n del servidor",
|
|
|
|
|
serverAddressPlaceholder: "wss://192.168.0.69:3000",
|
|
|
|
|
connectAudioLabel: "conectar al servidor",
|
|
|
|
|
disconnectAudioLabel: "desconectar del servidor",
|
|
|
|
|
manageConnectionsAudioLabel: "gestionar conexiones",
|
|
|
|
|
yourNameLabel: "Tu nombre",
|
|
|
|
|
yourNamePlaceholder: "Juan P\xE9rez",
|
|
|
|
|
setNameButtonAudioLabel: "establecer nombre",
|
|
|
|
|
settingsButton: "Ajustes",
|
|
|
|
|
manageStorageButton: "Gestionar almacenamiento",
|
|
|
|
|
transferDataButton: "Transferencia de datos",
|
|
|
|
|
scrollToChatButton: "Chats",
|
|
|
|
|
backToOverviewAudioLabel: "volver al resumen",
|
|
|
|
|
chatsHeadline: "Chats",
|
|
|
|
|
addChatAudioLabel: "nombre del nuevo chat",
|
|
|
|
|
addChatPlaceholder: "A\xF1adir chat",
|
|
|
|
|
addChatButton: "A\xF1adir chat"
|
|
|
|
|
},
|
|
|
|
|
settings: {
|
|
|
|
|
pages: {
|
|
|
|
|
appearance: "Aspecto",
|
|
|
|
|
regional: "Idioma y Regi\xF3n",
|
|
|
|
|
info: "Sobre Comms"
|
|
|
|
|
},
|
|
|
|
|
themes: {
|
|
|
|
|
dark: "Oscuro",
|
|
|
|
|
light: "Claro",
|
|
|
|
|
system: "Seg\xFAn dispositivo"
|
|
|
|
|
},
|
|
|
|
|
version: "Versi\xF3n",
|
|
|
|
|
language: "Idioma",
|
|
|
|
|
firstDayOfWeekLabel: "Primer d\xEDa de la semana"
|
|
|
|
|
},
|
|
|
|
|
connectionModal: {
|
|
|
|
|
connectionModalHeadline: "Gestionar Conexiones",
|
|
|
|
|
connectButtonAudioLabel: "conectar"
|
|
|
|
|
},
|
|
|
|
|
dataTransferModal: {
|
|
|
|
|
transferDataHeadline: "Transferencia de Datos",
|
|
|
|
|
selectionDescription: "Selecciona los datos que quieres transferir.",
|
|
|
|
|
dataEntryDescription: "Introduce estos datos en el otro dispositivo.",
|
|
|
|
|
dataEntryInputDescription: "Introduce los datos mostrados en el otro dispositivo.",
|
|
|
|
|
notConnectedError: "No est\xE1s conectado a ning\xFAn servidor.",
|
|
|
|
|
fromThisDeviceButton: "Desde este dispositivo",
|
|
|
|
|
toThisDeviceButton: "A este dispositivo",
|
|
|
|
|
generalHeadline: "General",
|
|
|
|
|
connectionData: "Datos de Conexi\xF3n",
|
|
|
|
|
settingsData: "Datos de Configuraci\xF3n",
|
|
|
|
|
chatsHeadline: "Chats",
|
|
|
|
|
transferChannelHeadline: "Canal de Transferencia",
|
|
|
|
|
transferKeyHeadline: "Clave de Encriptaci\xF3n de Transferencia",
|
|
|
|
|
sendButton: "Enviar",
|
|
|
|
|
sendAgainButton: "Enviar otra vez",
|
|
|
|
|
filesSentCount: (count) => `Archivos enviados: ${count}.`,
|
|
|
|
|
allFilesSent: "Hecho.",
|
|
|
|
|
filesReceivedCount: (count) => `Archivos recibidos: ${count}.`
|
|
|
|
|
},
|
|
|
|
|
storage: {
|
|
|
|
|
noItemSelected: "Ning\xFAn elemento seleccionado",
|
|
|
|
|
notAFile: "(no es un archivo)",
|
|
|
|
|
contentEmpty: "(vac\xEDo)",
|
|
|
|
|
path: "Ruta",
|
|
|
|
|
content: "Contenido",
|
|
|
|
|
deleteItem: "Eliminar elemento",
|
|
|
|
|
removeJunkButton: "Eliminar archivos basura"
|
|
|
|
|
},
|
|
|
|
|
chatPage: {
|
|
|
|
|
closeChatAudioLabe: "cerrar chat",
|
|
|
|
|
chatSettingsAudioLabel: "configuraci\xF3n del chat",
|
|
|
|
|
pages: {
|
|
|
|
|
settings: "Configuraci\xF3n",
|
|
|
|
|
messages: "Mensajes",
|
|
|
|
|
tasks: "Tareas",
|
|
|
|
|
calendar: "Calendario"
|
|
|
|
|
},
|
|
|
|
|
settings: {
|
|
|
|
|
settingsHeadline: "Configuraci\xF3n",
|
|
|
|
|
primaryChannelLabel: "Canal principal",
|
|
|
|
|
setPrimaryChannelButtonAudioLabel: "establecer canal principal",
|
|
|
|
|
newSecondaryChannelPlaceholder: "A\xF1adir canal secundario",
|
|
|
|
|
newSecondaryChannelAudioLabel: "nombre del nuevo canal secundario",
|
|
|
|
|
addSecondaryChannelButtonAudioLabel: "a\xF1adir canal secundario",
|
|
|
|
|
encryptionKeyLabel: "Clave de encriptaci\xF3n",
|
|
|
|
|
setEncryptionKeyButtonAudioLabel: "establecer clave de encriptaci\xF3n",
|
|
|
|
|
showEncryptionKey: "Mostrar clave de encriptaci\xF3n",
|
|
|
|
|
deleteChatButton: "Eliminar todo el chat"
|
|
|
|
|
},
|
|
|
|
|
message: {
|
|
|
|
|
messagesHeadline: "Mensajes",
|
|
|
|
|
messageFilterHeadline: "Filtrar mensajes",
|
|
|
|
|
messageFilterReactionsHadline: "Reacciones",
|
|
|
|
|
messageFilterAllReactionsButton: "Mostrar todas",
|
|
|
|
|
composerInputPlaceholder: "Escribe un mensaje...",
|
|
|
|
|
sendMessageButtonAudioLabel: "enviar mensaje",
|
|
|
|
|
filterMessagesButtonAudioLabel: "filtrar mensajes",
|
|
|
|
|
showMessageInfoButtonAudioLabel: "mostrar informaci\xF3n del mensaje",
|
|
|
|
|
messageInfoHeadline: "Informaci\xF3n del Mensaje",
|
|
|
|
|
sentBy: "Enviado por",
|
|
|
|
|
timeSent: "Hora de env\xEDo",
|
|
|
|
|
channel: "Canal",
|
|
|
|
|
messageContent: "Contenido del mensaje",
|
|
|
|
|
copyMessageButton: "Copiar mensaje",
|
|
|
|
|
resendMessageButton: "Reenviar mensaje",
|
|
|
|
|
decryptMessageButton: "Desencriptar mensaje",
|
|
|
|
|
deleteMessageButton: "Eliminar mensaje",
|
|
|
|
|
//
|
|
|
|
|
thumbsUpReaction: "Reacci\xF3n: pulgar hacia arriba",
|
|
|
|
|
checkReaction: "Reacci\xF3n: marca de verificaci\xF3n",
|
|
|
|
|
stopReaction: "Reacci\xF3n: signo de parada",
|
|
|
|
|
attentionReaction: "Reaccion: signo de atenci\xF3n",
|
|
|
|
|
doubleAttentionReaction: "Reaccion: signo de atenci\xF3n doble",
|
|
|
|
|
questionReaction: "Reaccion: signo de interrogaci\xF3n"
|
|
|
|
|
},
|
|
|
|
|
task: {
|
|
|
|
|
newBoardNamePlaceholder: "Crear un tablero",
|
|
|
|
|
createBoardButtonAudioLabel: "crear tablero",
|
|
|
|
|
noBoardSelected: "Ning\xFAn tablero seleccionado",
|
|
|
|
|
boardNotFound: "Tablero no encontrado",
|
|
|
|
|
closeBoardButtonAudioLabel: "cerrar tablero",
|
|
|
|
|
toggleBoardButtonAudioLabel: "mostrar o ocultar lista de tableros",
|
|
|
|
|
showBoardSettingsButtonAudioLabel: "mostrar configuraci\xF3n del tablero",
|
|
|
|
|
listViewButtonAudioLabel: "vista de lista",
|
|
|
|
|
kanbanViewButtonAudioLabel: "vista kanban",
|
|
|
|
|
statusViewButtonAudioLabel: "vista de cuadr\xEDcula de estado",
|
|
|
|
|
filterTasksButtonAudioLabel: "filtrar tareas",
|
|
|
|
|
createTaskButtonAudioLabel: "crear nueva tarea",
|
|
|
|
|
boardSettingsHeadline: "Configuraci\xF3n del Tablero",
|
|
|
|
|
boardNameInputLabel: "Nombre del tablero",
|
|
|
|
|
deleteBoardButton: "Eliminar tablero y todas las tareas",
|
|
|
|
|
taskSettingsHeadline: "Editar Tarea",
|
|
|
|
|
taskNameLabel: "T\xEDtulo",
|
|
|
|
|
taskBoardLabel: "Tablero",
|
|
|
|
|
taskCategoryLabel: "Categor\xEDa",
|
|
|
|
|
taskStatusLabel: "Estado",
|
|
|
|
|
taskPriorityLabel: "Prioridad",
|
|
|
|
|
taskDescriptionLabel: "Descripci\xF3n",
|
|
|
|
|
taskDateLabel: "Fecha",
|
|
|
|
|
taskTimeLabel: "Hora",
|
|
|
|
|
deleteTaskButton: "Eliminar tarea",
|
|
|
|
|
filterTasksHeadline: "Filtrar Tareas",
|
|
|
|
|
renameCategoryInputPlaceholder: "Renombrar categor\xEDa",
|
|
|
|
|
renameStatusInputPlaceholder: "Renombrar estado"
|
|
|
|
|
},
|
|
|
|
|
calendar: {
|
|
|
|
|
eventsBoard: "Eventos",
|
|
|
|
|
///
|
|
|
|
|
todayButtonAudioLabel: "ir a hoy",
|
|
|
|
|
previousMonthButtonAudioLabel: "mes anterior",
|
|
|
|
|
nextMonthButtonAudioLabel: "mes siguiente",
|
|
|
|
|
yearInputAudioLabel: "a\xF1o",
|
|
|
|
|
monthInputAudioLabel: "mes",
|
|
|
|
|
yearInputPlaceholder: "2000",
|
|
|
|
|
monthInputPlaceholder: "01",
|
|
|
|
|
searchEventsHeadline: "Buscar Eventos",
|
|
|
|
|
events: "Eventos",
|
|
|
|
|
noEvents: "No hay eventos"
|
2026-04-07 18:39:55 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
};
|
2026-04-12 19:20:37 +00:00
|
|
|
var languageNames = {
|
|
|
|
|
en: "English",
|
|
|
|
|
de: "Deutsch",
|
|
|
|
|
es: "Espa\xF1ol"
|
2026-04-01 19:00:16 +00:00
|
|
|
};
|
|
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/ViewModel/Global/coreViewModel.ts
|
2026-04-13 10:58:21 +00:00
|
|
|
var CoreViewModel = class {
|
2026-04-01 08:53:50 +00:00
|
|
|
// init
|
2026-04-12 19:20:37 +00:00
|
|
|
constructor(storageModel2, settingsModel2, connectionModel2, chatListModel2, fileTransferModel2) {
|
|
|
|
|
this.storageModel = storageModel2;
|
|
|
|
|
this.settingsModel = settingsModel2;
|
|
|
|
|
this.connectionModel = connectionModel2;
|
|
|
|
|
this.chatListModel = chatListModel2;
|
|
|
|
|
this.fileTransferModel = fileTransferModel2;
|
2026-04-13 10:58:21 +00:00
|
|
|
this.BUILD = "Build 26.04.13.A";
|
2026-04-12 19:20:37 +00:00
|
|
|
// CONTEXT
|
|
|
|
|
this.contextStack = /* @__PURE__ */ new Map();
|
|
|
|
|
this.logContexts = () => {
|
|
|
|
|
console.log(
|
|
|
|
|
"context",
|
|
|
|
|
this.contexts.map(
|
|
|
|
|
(x) => x.contextDebugDescription + "-" + x.contextId
|
|
|
|
|
)
|
|
|
|
|
);
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
2026-04-12 19:20:37 +00:00
|
|
|
this.closeContext = (contextId) => {
|
|
|
|
|
if (!this.contexts.map((context) => context.contextId).includes(contextId)) return;
|
|
|
|
|
while (this.contexts.length > 0) {
|
|
|
|
|
const currentContext = this.context;
|
|
|
|
|
this.contextStack.delete(currentContext.contextId);
|
|
|
|
|
if (currentContext.contextId == contextId) break;
|
|
|
|
|
}
|
|
|
|
|
this.logContexts();
|
|
|
|
|
};
|
|
|
|
|
this.handleKeyDown = (e) => {
|
|
|
|
|
const contexts = this.contexts;
|
|
|
|
|
while (contexts.length > 0) {
|
|
|
|
|
const currentContext = contexts.pop();
|
|
|
|
|
if (!currentContext) contexts;
|
|
|
|
|
const isHandled = currentContext.handleKeystroke(e);
|
|
|
|
|
if (isHandled == true) break;
|
2026-04-01 08:29:39 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
2026-04-12 19:20:37 +00:00
|
|
|
// DRAG & DROP
|
|
|
|
|
this.draggedObject = new State(void 0);
|
|
|
|
|
// SUGGESTIONS
|
|
|
|
|
// boards & tasks
|
|
|
|
|
this.boardFilterStringSuggestions = new ListState();
|
|
|
|
|
this.taskCategorySuggestions = new ListState();
|
|
|
|
|
this.taskStatusSuggestions = new ListState();
|
|
|
|
|
this.translations = allTranslations[settingsModel2.language] || allTranslations.en;
|
|
|
|
|
document.body.addEventListener("keydown", this.handleKeyDown);
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
get contexts() {
|
|
|
|
|
return [...this.contextStack.values()];
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
get context() {
|
|
|
|
|
return this.contexts.pop();
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
set context(context) {
|
|
|
|
|
if (this.contextStack.has(context.contextId)) return;
|
|
|
|
|
this.contextStack.set(context.contextId, context);
|
|
|
|
|
this.logContexts();
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
// util
|
|
|
|
|
static checkIsKeystroke(e) {
|
2026-04-13 10:12:24 +00:00
|
|
|
return (e.metaKey || e.altKey) && e.ctrlKey;
|
2026-04-12 19:20:37 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var Context = class {
|
|
|
|
|
constructor() {
|
|
|
|
|
this.contextId = v4_default();
|
|
|
|
|
this.keystrokes = /* @__PURE__ */ new Map();
|
|
|
|
|
this.handleKeystroke = (e) => {
|
|
|
|
|
const fn = this.keystrokes.get(e.key.toLowerCase());
|
|
|
|
|
if (!fn) return false;
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
fn();
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
this.registerKeyStroke = (key, fn) => {
|
|
|
|
|
this.keystrokes.set(key, fn);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var ContextHost = class extends Context {
|
|
|
|
|
constructor() {
|
|
|
|
|
super(...arguments);
|
|
|
|
|
this.contexts = /* @__PURE__ */ new Map();
|
|
|
|
|
this.currentContext = new State(void 0);
|
|
|
|
|
this.registerContext = (key, context) => {
|
|
|
|
|
this.contexts.set(key, context);
|
|
|
|
|
};
|
|
|
|
|
this.closeContext = () => {
|
|
|
|
|
if (this.currentContext.value) {
|
|
|
|
|
this.coreViewModel.closeContext(this.currentContext.value.contextId);
|
|
|
|
|
}
|
|
|
|
|
this.currentContext.value = void 0;
|
|
|
|
|
};
|
|
|
|
|
this.updateContexts = () => {
|
|
|
|
|
if (this.isOpen == false) return;
|
|
|
|
|
const selectedContext = this.contexts.get(this.contextSelection);
|
|
|
|
|
if (!selectedContext) return;
|
|
|
|
|
if (selectedContext != this.currentContext.value) {
|
|
|
|
|
this.closeContext();
|
|
|
|
|
}
|
|
|
|
|
;
|
|
|
|
|
this.coreViewModel.logContexts();
|
|
|
|
|
this.coreViewModel.context = selectedContext;
|
|
|
|
|
this.currentContext.value = selectedContext;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
get isOpen() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
get contextSelection() {
|
|
|
|
|
return void 0;
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/View/utility.ts
|
|
|
|
|
function allowDrop(event) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
}
|
|
|
|
|
function allowDrag(event) {
|
|
|
|
|
event.dataTransfer?.setData("text", "");
|
|
|
|
|
}
|
|
|
|
|
function reload() {
|
|
|
|
|
window.location.reload();
|
|
|
|
|
}
|
|
|
|
|
function setFocus() {
|
2026-04-13 10:26:03 +00:00
|
|
|
const focusedInModal = document.querySelector(".modal[open] #focused");
|
|
|
|
|
if (focusedInModal) return focusedInModal.focus();
|
2026-04-12 19:20:37 +00:00
|
|
|
document.getElementById("focused")?.focus();
|
|
|
|
|
}
|
|
|
|
|
function setFocusWithDelay() {
|
|
|
|
|
setTimeout(setFocus, 100);
|
2026-04-10 18:17:16 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/ViewModel/Pages/taskViewModel.ts
|
|
|
|
|
var TaskViewModel = class extends Context {
|
|
|
|
|
// init
|
|
|
|
|
constructor(coreViewModel2, chatViewModel, boardsAndTasksModel, containingModel, task) {
|
|
|
|
|
super();
|
|
|
|
|
this.coreViewModel = coreViewModel2;
|
|
|
|
|
this.chatViewModel = chatViewModel;
|
|
|
|
|
this.boardsAndTasksModel = boardsAndTasksModel;
|
|
|
|
|
this.containingModel = containingModel;
|
|
|
|
|
this.task = task;
|
|
|
|
|
this.contextDebugDescription = "task";
|
|
|
|
|
// paths
|
|
|
|
|
this.getFilePath = () => {
|
|
|
|
|
return this.boardsAndTasksModel.getTaskFilePath(this.task.fileId);
|
|
|
|
|
};
|
|
|
|
|
// state
|
|
|
|
|
this.index = new State(0);
|
|
|
|
|
this.boardId = new State("");
|
|
|
|
|
this.name = new State("");
|
|
|
|
|
this.description = new State("");
|
|
|
|
|
this.category = new State("");
|
|
|
|
|
this.status = new State("");
|
|
|
|
|
this.priority = new State("");
|
|
|
|
|
this.date = new State("");
|
|
|
|
|
this.time = new State("");
|
|
|
|
|
this.selectedVersionId = new State("");
|
|
|
|
|
this.versionIds = new ListState();
|
|
|
|
|
// methods
|
|
|
|
|
this.dragStart = (event) => {
|
|
|
|
|
allowDrag(event);
|
|
|
|
|
this.coreViewModel.draggedObject.value = this;
|
|
|
|
|
};
|
|
|
|
|
this.setCategoryAndStatus = (category, status) => {
|
|
|
|
|
if (category != void 0) this.category.value = category;
|
|
|
|
|
if (status != void 0) this.status.value = status;
|
|
|
|
|
this.save();
|
|
|
|
|
};
|
|
|
|
|
this.setBoardId = (boardId) => {
|
|
|
|
|
this.boardId.value = boardId;
|
|
|
|
|
this.save();
|
|
|
|
|
};
|
|
|
|
|
this.setDate = (dateISOString) => {
|
|
|
|
|
this.date.value = dateISOString;
|
|
|
|
|
this.save();
|
|
|
|
|
};
|
|
|
|
|
// view
|
|
|
|
|
this.open = () => {
|
|
|
|
|
this.coreViewModel.context = this;
|
|
|
|
|
this.containingModel.selectTask(this);
|
|
|
|
|
};
|
|
|
|
|
this.close = () => {
|
|
|
|
|
this.coreViewModel.closeContext(this.contextId);
|
|
|
|
|
this.containingModel.closeTask();
|
|
|
|
|
};
|
|
|
|
|
this.closeAndDiscard = () => {
|
|
|
|
|
this.close();
|
|
|
|
|
this.loadTaskData();
|
|
|
|
|
};
|
|
|
|
|
this.closeAndSave = () => {
|
|
|
|
|
this.close();
|
|
|
|
|
this.save();
|
|
|
|
|
};
|
|
|
|
|
this.updateIndex = () => {
|
|
|
|
|
const index = this.containingModel.taskIndexManager.getIndex(this);
|
|
|
|
|
this.index.value = index;
|
|
|
|
|
};
|
|
|
|
|
this.updateSuggestions = () => {
|
|
|
|
|
if (this.coreViewModel.taskCategorySuggestions.value.has(
|
|
|
|
|
this.category.value
|
|
|
|
|
) == false) {
|
|
|
|
|
this.coreViewModel.taskCategorySuggestions.add(this.category.value);
|
|
|
|
|
}
|
|
|
|
|
if (this.coreViewModel.taskStatusSuggestions.value.has(
|
|
|
|
|
this.status.value
|
|
|
|
|
) == false) {
|
|
|
|
|
this.coreViewModel.taskStatusSuggestions.add(this.status.value);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// settings
|
|
|
|
|
this.save = () => {
|
|
|
|
|
const newTaskFileContent = BoardsAndTasksModel.createTaskFileContent(
|
|
|
|
|
this.task.fileId,
|
|
|
|
|
this.name.value,
|
|
|
|
|
this.task.boardId
|
|
|
|
|
);
|
|
|
|
|
newTaskFileContent.boardId = this.boardId.value;
|
|
|
|
|
newTaskFileContent.description = this.description.value;
|
|
|
|
|
newTaskFileContent.status = this.status.value;
|
|
|
|
|
newTaskFileContent.category = this.category.value;
|
|
|
|
|
newTaskFileContent.priority = this.priority.value;
|
|
|
|
|
newTaskFileContent.date = this.date.value;
|
|
|
|
|
newTaskFileContent.time = this.time.value;
|
|
|
|
|
this.boardsAndTasksModel.updateTaskAndSend(newTaskFileContent);
|
|
|
|
|
this.containingModel.showTask(newTaskFileContent);
|
|
|
|
|
this.containingModel.updateTaskIndices();
|
|
|
|
|
this.updateSuggestions();
|
|
|
|
|
};
|
|
|
|
|
this.deleteTask = () => {
|
|
|
|
|
this.close();
|
|
|
|
|
this.boardsAndTasksModel.deleteTask(
|
|
|
|
|
this.task.boardId,
|
|
|
|
|
this.task.fileId
|
|
|
|
|
);
|
|
|
|
|
this.containingModel.removeTaskFromView(this.task);
|
|
|
|
|
};
|
|
|
|
|
// load
|
|
|
|
|
this.loadVersionIds = () => {
|
|
|
|
|
const versionIds = this.boardsAndTasksModel.listTaskVersionIds(this.task.fileId);
|
|
|
|
|
const sortedVersionIds = versionIds.sort(localeCompare).reverse();
|
|
|
|
|
this.versionIds.clear();
|
|
|
|
|
this.versionIds.add(...sortedVersionIds);
|
|
|
|
|
};
|
|
|
|
|
this.switchVersion = (versionId) => {
|
|
|
|
|
const taskFileContent = this.boardsAndTasksModel.getSpecificTaskFileContent(
|
|
|
|
|
this.task.fileId,
|
|
|
|
|
versionId
|
|
|
|
|
);
|
|
|
|
|
if (taskFileContent == null) return;
|
|
|
|
|
this.task = taskFileContent;
|
|
|
|
|
this.loadTaskData();
|
|
|
|
|
};
|
|
|
|
|
this.loadAllData = () => {
|
|
|
|
|
this.loadTaskData();
|
|
|
|
|
this.loadVersionIds();
|
|
|
|
|
};
|
|
|
|
|
this.loadTaskData = () => {
|
|
|
|
|
this.boardId.value = this.task.boardId;
|
|
|
|
|
this.name.value = this.task.name;
|
|
|
|
|
this.description.value = this.task.description ?? "";
|
|
|
|
|
this.category.value = this.task.category ?? "";
|
|
|
|
|
this.status.value = this.task.status ?? "";
|
|
|
|
|
this.priority.value = this.task.priority ?? "";
|
|
|
|
|
this.date.value = this.task.date ?? "";
|
|
|
|
|
this.time.value = this.task.time ?? "";
|
|
|
|
|
this.selectedVersionId.value = this.task.fileContentId;
|
|
|
|
|
this.updateSuggestions();
|
|
|
|
|
};
|
|
|
|
|
this.loadAllData();
|
|
|
|
|
this.selectedVersionId.subscribeSilent((selectedVersionId) => {
|
|
|
|
|
this.switchVersion(selectedVersionId);
|
|
|
|
|
});
|
|
|
|
|
this.registerKeyStroke("enter" /* Apply */, this.closeAndSave);
|
|
|
|
|
this.registerKeyStroke("backspace" /* CloseOrCancel */, this.closeAndDiscard);
|
|
|
|
|
}
|
|
|
|
|
// util
|
|
|
|
|
get sortingString() {
|
|
|
|
|
const splitDate = this.date.value.split("-");
|
|
|
|
|
const year = padZero(splitDate[0], 4);
|
|
|
|
|
const month = padZero(splitDate[1], 2);
|
|
|
|
|
const date = padZero(splitDate[2], 2);
|
|
|
|
|
const splitTime = this.time.value.split(":");
|
|
|
|
|
const hour = padZero(splitTime[0], 2);
|
|
|
|
|
const minute = padZero(splitTime[1], 2);
|
|
|
|
|
const priorityNumber = parseInt(this.priority.value);
|
|
|
|
|
const invertedPriority = 100 - priorityNumber;
|
|
|
|
|
return year + month + date + hour + minute + invertedPriority + this.name.value;
|
|
|
|
|
}
|
|
|
|
|
static {
|
|
|
|
|
// utility
|
|
|
|
|
this.getStringsForFilter = (taskViewModel) => {
|
|
|
|
|
return [
|
|
|
|
|
taskViewModel.task.name,
|
|
|
|
|
taskViewModel.task.category ?? "",
|
|
|
|
|
taskViewModel.task.status ?? "",
|
|
|
|
|
taskViewModel.task.priority ?? "",
|
|
|
|
|
taskViewModel.task.date ?? "",
|
|
|
|
|
taskViewModel.task.time ?? ""
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// src/ViewModel/Pages/taskContainingPageViewModel.ts
|
|
|
|
|
var TaskContainingPageViewModel = class extends Context {
|
|
|
|
|
// init
|
|
|
|
|
constructor(coreViewModel2, chatViewModel, boardsAndTasksModel) {
|
|
|
|
|
super();
|
|
|
|
|
this.coreViewModel = coreViewModel2;
|
|
|
|
|
this.chatViewModel = chatViewModel;
|
|
|
|
|
this.boardsAndTasksModel = boardsAndTasksModel;
|
|
|
|
|
// state
|
|
|
|
|
this.taskIndexManager = new IndexManager(
|
|
|
|
|
(taskViewModel) => taskViewModel.sortingString
|
|
|
|
|
);
|
|
|
|
|
this.selectedTaskViewModel = new State(void 0);
|
|
|
|
|
this.taskViewModels = new MapState();
|
|
|
|
|
// methods
|
|
|
|
|
this.createTaskFromBoardId = (boardId) => {
|
|
|
|
|
const taskFileContent = this.boardsAndTasksModel.createTask(boardId);
|
|
|
|
|
const taskViewModel = new TaskViewModel(
|
|
|
|
|
this.coreViewModel,
|
|
|
|
|
this.chatViewModel,
|
|
|
|
|
this.boardsAndTasksModel,
|
|
|
|
|
this,
|
|
|
|
|
taskFileContent
|
|
|
|
|
);
|
|
|
|
|
this.selectTask(taskViewModel);
|
|
|
|
|
this.updateTaskIndices();
|
|
|
|
|
};
|
|
|
|
|
// view
|
|
|
|
|
this.showTask = (taskFileContent) => {
|
|
|
|
|
};
|
|
|
|
|
this.removeTaskFromView = (taskFileContent) => {
|
|
|
|
|
};
|
|
|
|
|
this.selectTask = (selectedTask) => {
|
|
|
|
|
this.selectedTaskViewModel.value = selectedTask;
|
|
|
|
|
};
|
|
|
|
|
this.closeTask = () => {
|
|
|
|
|
this.selectedTaskViewModel.value = void 0;
|
|
|
|
|
};
|
|
|
|
|
this.updateTaskIndices = () => {
|
|
|
|
|
this.taskIndexManager.update([...this.taskViewModels.value.values()]);
|
|
|
|
|
for (const taskViewModel of this.taskViewModels.value.values()) {
|
|
|
|
|
taskViewModel.updateIndex();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// src/ViewModel/Pages/calendarPageViewModel.ts
|
|
|
|
|
var CALENDAR_EVENT_BOARD_ID = "events";
|
|
|
|
|
var CalendarPageViewModel = class extends TaskContainingPageViewModel {
|
|
|
|
|
// init
|
|
|
|
|
constructor(coreViewModel2, chatViewModel, calendarModel, boardsAndTasksModel) {
|
|
|
|
|
super(coreViewModel2, chatViewModel, boardsAndTasksModel);
|
|
|
|
|
this.coreViewModel = coreViewModel2;
|
|
|
|
|
this.chatViewModel = chatViewModel;
|
|
|
|
|
this.calendarModel = calendarModel;
|
|
|
|
|
this.boardsAndTasksModel = boardsAndTasksModel;
|
|
|
|
|
this.contextDebugDescription = "calendar";
|
|
|
|
|
// paths
|
|
|
|
|
this.getBasePath = () => {
|
|
|
|
|
return [...this.calendarModel.getViewPath()];
|
|
|
|
|
};
|
|
|
|
|
// state
|
|
|
|
|
this.selectedYear = new State(0);
|
|
|
|
|
this.selectedMonth = new State(0);
|
|
|
|
|
this.selectedDate = new State(0);
|
|
|
|
|
this.monthGrid = new State(void 0);
|
|
|
|
|
// methods
|
|
|
|
|
this.createEvent = () => {
|
|
|
|
|
const taskFileContent = this.boardsAndTasksModel.createTask(CALENDAR_EVENT_BOARD_ID);
|
|
|
|
|
taskFileContent.date = CalendarModel.getISODateString(
|
|
|
|
|
this.selectedYear.value.toString(),
|
|
|
|
|
this.selectedMonth.value.toString(),
|
|
|
|
|
this.selectedDate.value.toString()
|
|
|
|
|
);
|
|
|
|
|
const taskViewModel = new TaskViewModel(
|
|
|
|
|
this.coreViewModel,
|
|
|
|
|
this.chatViewModel,
|
|
|
|
|
this.boardsAndTasksModel,
|
|
|
|
|
this,
|
|
|
|
|
taskFileContent
|
|
|
|
|
);
|
|
|
|
|
this.selectTask(taskViewModel);
|
|
|
|
|
this.updateTaskIndices();
|
|
|
|
|
};
|
|
|
|
|
this.getEventsForDate = () => {
|
|
|
|
|
const paddedDate = CalendarModel.padDateOrMonth(
|
|
|
|
|
this.selectedDate.toString()
|
|
|
|
|
);
|
|
|
|
|
if (this.monthGrid.value == void 0) {
|
|
|
|
|
return void 0;
|
2026-04-01 08:46:10 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
return this.monthGrid.value.days[paddedDate];
|
|
|
|
|
};
|
|
|
|
|
// view
|
|
|
|
|
this.getTaskMapState = (taskFileContent) => {
|
|
|
|
|
if (this.monthGrid.value == null) return null;
|
|
|
|
|
const date = CalendarModel.isoToDateString(
|
|
|
|
|
taskFileContent.date ?? ""
|
|
|
|
|
);
|
|
|
|
|
return this.monthGrid.value.days[date];
|
|
|
|
|
};
|
|
|
|
|
this.showTask = (taskFileContent) => {
|
|
|
|
|
const monthString = CalendarModel.isoToMonthString(
|
|
|
|
|
taskFileContent.date ?? ""
|
|
|
|
|
);
|
|
|
|
|
if (monthString == void 0 || monthString != this.monthString) {
|
|
|
|
|
this.removeTaskFromView(taskFileContent);
|
|
|
|
|
this.calendarModel.deleteTaskReference(
|
|
|
|
|
this.monthString,
|
|
|
|
|
taskFileContent.fileId
|
2026-04-01 08:53:50 +00:00
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
return;
|
2026-04-01 08:46:10 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
const taskViewModel = new TaskViewModel(
|
|
|
|
|
this.coreViewModel,
|
|
|
|
|
this.chatViewModel,
|
|
|
|
|
this.boardsAndTasksModel,
|
|
|
|
|
this,
|
|
|
|
|
taskFileContent
|
|
|
|
|
);
|
|
|
|
|
const mapState = this.getTaskMapState(taskFileContent);
|
|
|
|
|
this.taskViewModels.handleRemoval(taskViewModel, () => {
|
|
|
|
|
mapState?.remove(taskFileContent.fileId);
|
|
|
|
|
});
|
|
|
|
|
this.taskViewModels.remove(taskFileContent.fileId);
|
|
|
|
|
this.taskViewModels.set(taskFileContent.fileId, taskViewModel);
|
|
|
|
|
mapState?.set(taskFileContent.fileId, taskViewModel);
|
|
|
|
|
this.updateTaskIndices();
|
|
|
|
|
};
|
|
|
|
|
this.removeTaskFromView = (taskFileContent) => {
|
|
|
|
|
this.taskViewModels.remove(taskFileContent.fileId);
|
|
|
|
|
};
|
|
|
|
|
this.showToday = () => {
|
|
|
|
|
const today = /* @__PURE__ */ new Date();
|
|
|
|
|
this.selectedYear.value = today.getFullYear();
|
|
|
|
|
this.selectedMonth.value = today.getMonth() + 1;
|
|
|
|
|
this.selectedDate.value = today.getDate();
|
|
|
|
|
};
|
|
|
|
|
this.showPreviousMonth = () => {
|
|
|
|
|
this.selectedMonth.value -= 1;
|
|
|
|
|
if (this.selectedMonth.value <= 0) {
|
|
|
|
|
this.selectedYear.value -= 1;
|
|
|
|
|
this.selectedMonth.value = 12;
|
2026-04-01 08:46:10 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
};
|
|
|
|
|
this.showNextMonth = () => {
|
|
|
|
|
this.selectedMonth.value += 1;
|
|
|
|
|
if (this.selectedMonth.value >= 13) {
|
|
|
|
|
this.selectedYear.value += 1;
|
|
|
|
|
this.selectedMonth.value = 1;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.handleDrop = (year, month, date) => {
|
|
|
|
|
const ISOString = CalendarModel.getISODateString(
|
|
|
|
|
year,
|
|
|
|
|
month,
|
|
|
|
|
date
|
|
|
|
|
);
|
|
|
|
|
const draggedObject = this.coreViewModel.draggedObject.value;
|
|
|
|
|
if (draggedObject instanceof TaskViewModel == false) return;
|
|
|
|
|
draggedObject.setDate(ISOString);
|
|
|
|
|
};
|
|
|
|
|
// load
|
|
|
|
|
this.loadMonthTasks = () => {
|
|
|
|
|
this.monthGrid.value = this.calendarModel.generateMonthGrid(
|
|
|
|
|
this.selectedYear.value,
|
|
|
|
|
this.selectedMonth.value,
|
|
|
|
|
() => new MapState()
|
|
|
|
|
);
|
|
|
|
|
const taskIds = this.calendarModel.listTaskIds(
|
|
|
|
|
this.monthString
|
|
|
|
|
);
|
|
|
|
|
for (const taskId of taskIds) {
|
|
|
|
|
const taskFileContent = this.boardsAndTasksModel.getLatestTaskFileContent(taskId);
|
|
|
|
|
if (taskFileContent == null) continue;
|
|
|
|
|
this.showTask(taskFileContent);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.loadData = () => {
|
|
|
|
|
this.loadMonthTasks();
|
|
|
|
|
};
|
|
|
|
|
this.calendarModel = calendarModel;
|
|
|
|
|
this.boardsAndTasksModel = boardsAndTasksModel;
|
|
|
|
|
this.chatViewModel = chatViewModel;
|
|
|
|
|
bulkSubscribe([this.selectedYear, this.selectedMonth], () => {
|
|
|
|
|
this.loadMonthTasks();
|
|
|
|
|
});
|
|
|
|
|
boardsAndTasksModel.taskHandlerManager.addHandler(
|
|
|
|
|
(taskFileContent) => {
|
|
|
|
|
this.showTask(taskFileContent);
|
2026-04-01 08:46:10 +00:00
|
|
|
}
|
2026-04-02 19:22:10 +00:00
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
this.showToday();
|
|
|
|
|
this.registerKeyStroke("-" /* Reset */, this.showToday);
|
|
|
|
|
this.registerKeyStroke("p", this.showNextMonth);
|
|
|
|
|
this.registerKeyStroke("o", this.showPreviousMonth);
|
|
|
|
|
this.chatViewModel.registerContext("calendar" /* Calendar */, this);
|
2026-04-02 19:22:10 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
// data
|
|
|
|
|
get monthString() {
|
|
|
|
|
return CalendarModel.getMonthString(
|
|
|
|
|
this.selectedYear.value.toString(),
|
|
|
|
|
this.selectedMonth.value.toString()
|
|
|
|
|
);
|
2026-04-07 18:39:55 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
};
|
2026-04-07 18:39:55 +00:00
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/ViewModel/Chat/chatMessageViewModel.ts
|
|
|
|
|
var ChatMessageViewModel = class {
|
|
|
|
|
// init
|
|
|
|
|
constructor(coreViewModel2, messagePageViewModel, chatMessage, sentByUser) {
|
|
|
|
|
this.coreViewModel = coreViewModel2;
|
|
|
|
|
this.messagePageViewModel = messagePageViewModel;
|
|
|
|
|
this.body = new State("");
|
|
|
|
|
this.status = new State(
|
|
|
|
|
void 0
|
|
|
|
|
);
|
|
|
|
|
this.isHidden = new State(false);
|
|
|
|
|
this.allReactions = new MapState();
|
|
|
|
|
this.userReaction = new State(
|
|
|
|
|
void 0
|
|
|
|
|
);
|
|
|
|
|
this.reactionsThumbsUp = MapState;
|
|
|
|
|
this.reactionsCheck = MapState;
|
|
|
|
|
this.reactionsStop = MapState;
|
|
|
|
|
this.reactionsAttention = MapState;
|
|
|
|
|
this.reactionsDoubleAttention = MapState;
|
|
|
|
|
this.reactionsQuestion = MapState;
|
|
|
|
|
this.generateReactionCountProxyState = (content) => {
|
|
|
|
|
return createProxyState(
|
|
|
|
|
[this.allReactions],
|
|
|
|
|
() => [...this.allReactions.value.values()].filter(
|
|
|
|
|
(x) => x.content == content
|
|
|
|
|
).length
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.reactionsThumbsUpCount = this.generateReactionCountProxyState(
|
|
|
|
|
"\u{1F44D}" /* ThumbsUp */
|
|
|
|
|
);
|
|
|
|
|
this.reactionsCheckCount = this.generateReactionCountProxyState(
|
|
|
|
|
"\u2705" /* Check */
|
|
|
|
|
);
|
|
|
|
|
this.reactionsStopCount = this.generateReactionCountProxyState(
|
|
|
|
|
"\u{1F6D1}" /* Stop */
|
|
|
|
|
);
|
|
|
|
|
this.reactionsAttentionCount = this.generateReactionCountProxyState(
|
|
|
|
|
"\u2757\uFE0F" /* Attention */
|
|
|
|
|
);
|
|
|
|
|
this.reactionsDoubleAttentionCount = this.generateReactionCountProxyState(
|
|
|
|
|
"\u203C\uFE0F" /* DoubleAttention */
|
|
|
|
|
);
|
|
|
|
|
this.reactionsQuestionCount = this.generateReactionCountProxyState(
|
|
|
|
|
"\u2753" /* Question */
|
|
|
|
|
);
|
|
|
|
|
// state
|
|
|
|
|
this.isPresentingInfoModal = new State(false);
|
|
|
|
|
// methods
|
|
|
|
|
this.copyMessage = () => {
|
|
|
|
|
navigator.clipboard.writeText(this.body.value);
|
|
|
|
|
};
|
|
|
|
|
this.resendMessage = () => {
|
|
|
|
|
this.messagePageViewModel.sendMessageFromBody(this.body.value);
|
|
|
|
|
};
|
|
|
|
|
this.decryptMessage = () => {
|
|
|
|
|
this.messagePageViewModel.decryptMessage(this);
|
|
|
|
|
};
|
|
|
|
|
// view
|
|
|
|
|
this.showInfoModal = () => {
|
|
|
|
|
this.isPresentingInfoModal.value = true;
|
|
|
|
|
};
|
|
|
|
|
this.hideInfoModal = () => {
|
|
|
|
|
this.isPresentingInfoModal.value = false;
|
|
|
|
|
};
|
|
|
|
|
// reactions
|
|
|
|
|
this.handleReaction = (reaction) => {
|
|
|
|
|
if (reaction.isDeleting) {
|
|
|
|
|
this.allReactions.remove(reaction.sender);
|
|
|
|
|
} else {
|
|
|
|
|
this.allReactions.set(reaction.sender, reaction);
|
|
|
|
|
}
|
|
|
|
|
if (reaction.sender != this.coreViewModel.settingsModel.username)
|
|
|
|
|
return;
|
|
|
|
|
this.userReaction.value = reaction.isDeleting ? void 0 : reaction.content;
|
|
|
|
|
};
|
|
|
|
|
this.sendReaction = (content, isDeleting) => {
|
|
|
|
|
this.messagePageViewModel.sendReaction(
|
|
|
|
|
this.chatMessage.id,
|
|
|
|
|
content,
|
|
|
|
|
isDeleting
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
// load
|
|
|
|
|
this.loadData = () => {
|
|
|
|
|
this.channel = this.chatMessage.channel;
|
|
|
|
|
this.sender = this.chatMessage.sender;
|
|
|
|
|
this.dateSent = new Date(this.chatMessage.dateSent).toLocaleString();
|
|
|
|
|
this.body.value = this.chatMessage.body;
|
|
|
|
|
this.status.value = this.chatMessage.status;
|
|
|
|
|
};
|
|
|
|
|
this.chatMessage = chatMessage;
|
|
|
|
|
this.sentByUser = sentByUser;
|
|
|
|
|
this.loadData();
|
|
|
|
|
this.messagePageViewModel.reactionFilter.subscribe((content) => {
|
|
|
|
|
if (content == void 0) {
|
|
|
|
|
this.isHidden.value = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let count = 0;
|
|
|
|
|
switch (content) {
|
|
|
|
|
case "\u{1F44D}" /* ThumbsUp */: {
|
|
|
|
|
count = this.reactionsThumbsUpCount.value;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "\u2705" /* Check */: {
|
|
|
|
|
count = this.reactionsCheckCount.value;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "\u{1F6D1}" /* Stop */: {
|
|
|
|
|
count = this.reactionsStopCount.value;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "\u2757\uFE0F" /* Attention */: {
|
|
|
|
|
count = this.reactionsAttentionCount.value;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "\u203C\uFE0F" /* DoubleAttention */: {
|
|
|
|
|
count = this.reactionsDoubleAttentionCount.value;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "\u2753" /* Question */: {
|
|
|
|
|
count = this.reactionsQuestionCount.value;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
this.isHidden.value = count == 0;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-04-07 19:13:29 +00:00
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/ViewModel/Utility/searchViewModel.ts
|
|
|
|
|
var SearchViewModel = class {
|
|
|
|
|
// init
|
|
|
|
|
constructor(allObjects, matchingObjects, getStringsOfObject, suggestions) {
|
|
|
|
|
// state
|
|
|
|
|
this.appliedQuery = new State("");
|
|
|
|
|
this.searchInput = new State("");
|
|
|
|
|
// guards
|
|
|
|
|
this.cannotApplySearch = createProxyState(
|
|
|
|
|
[this.searchInput, this.appliedQuery],
|
|
|
|
|
() => this.searchInput.value == this.appliedQuery.value
|
|
|
|
|
);
|
|
|
|
|
// methods
|
|
|
|
|
this.search = (searchTerm) => {
|
|
|
|
|
this.searchInput.value = searchTerm;
|
|
|
|
|
this.applySearch();
|
|
|
|
|
};
|
|
|
|
|
this.applySearch = () => {
|
|
|
|
|
this.appliedQuery.value = this.searchInput.value;
|
|
|
|
|
this.matchingObjects.clear();
|
|
|
|
|
for (const object of this.allObjects.value.values()) {
|
|
|
|
|
const doesMatch = this.checkDoesMatchSearch(object);
|
|
|
|
|
if (doesMatch == false) continue;
|
|
|
|
|
this.matchingObjects.add(object);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// utility
|
|
|
|
|
this.checkDoesMatchSearch = (object) => {
|
|
|
|
|
return checkDoesObjectMatchSearch(
|
|
|
|
|
this.appliedQuery.value,
|
|
|
|
|
this.getStringsOfObject,
|
|
|
|
|
object
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.allObjects = allObjects;
|
|
|
|
|
this.matchingObjects = matchingObjects;
|
|
|
|
|
this.getStringsOfObject = getStringsOfObject;
|
|
|
|
|
this.suggestions = suggestions;
|
|
|
|
|
this.allObjects.handleAddition((newObject) => {
|
|
|
|
|
const doesMatch = this.checkDoesMatchSearch(newObject);
|
|
|
|
|
if (doesMatch == false) {
|
|
|
|
|
this.matchingObjects.remove(newObject);
|
|
|
|
|
} else {
|
|
|
|
|
if (this.matchingObjects.value.has(newObject)) return;
|
|
|
|
|
this.matchingObjects.add(newObject);
|
|
|
|
|
this.allObjects.handleRemoval(newObject, () => {
|
|
|
|
|
this.matchingObjects.remove(newObject);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
2026-04-09 15:45:19 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
};
|
2026-04-09 15:45:19 +00:00
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/ViewModel/Pages/messagePageViewModel.ts
|
|
|
|
|
var MessagePageViewModel = class extends Context {
|
|
|
|
|
// init
|
|
|
|
|
constructor(coreViewModel2, chatViewModel) {
|
|
|
|
|
super();
|
|
|
|
|
this.coreViewModel = coreViewModel2;
|
|
|
|
|
this.chatViewModel = chatViewModel;
|
|
|
|
|
this.contextDebugDescription = "message-page";
|
|
|
|
|
// state
|
|
|
|
|
this.chatMessageViewModels = new MapState();
|
|
|
|
|
this.filteredMessageViewModels = new ListState();
|
|
|
|
|
this.isFilterModalOpen = new State(false);
|
|
|
|
|
this.reactionFilter = new State(void 0);
|
|
|
|
|
this.composingMessage = new State("");
|
|
|
|
|
// methods
|
|
|
|
|
this.sendMessage = () => {
|
|
|
|
|
if (this.cannotSendMessage.value == true) return;
|
|
|
|
|
this.sendMessageFromBody(this.composingMessage.value);
|
|
|
|
|
this.composingMessage.value = "";
|
|
|
|
|
};
|
|
|
|
|
this.sendMessageFromBody = (body) => {
|
|
|
|
|
this.chatViewModel.chatModel.sendMessage(body);
|
|
|
|
|
};
|
|
|
|
|
this.decryptMessage = async (messageViewModel) => {
|
|
|
|
|
const chatMessage = messageViewModel.chatMessage;
|
|
|
|
|
await this.chatViewModel.chatModel.decryptMessage(chatMessage);
|
|
|
|
|
this.chatViewModel.chatModel.addMessage(chatMessage);
|
|
|
|
|
messageViewModel.loadData();
|
|
|
|
|
};
|
|
|
|
|
this.sendReaction = (messageId, content, isDeleting) => {
|
|
|
|
|
this.chatViewModel.chatModel.sendReaction(
|
|
|
|
|
messageId,
|
|
|
|
|
content,
|
|
|
|
|
isDeleting
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
// view
|
|
|
|
|
this.showChatMessage = (chatMessage) => {
|
|
|
|
|
const chatMessageViewModel = new ChatMessageViewModel(
|
|
|
|
|
this.coreViewModel,
|
|
|
|
|
this,
|
|
|
|
|
chatMessage,
|
|
|
|
|
chatMessage.sender == this.chatViewModel.settingsViewModel.username.value
|
|
|
|
|
);
|
|
|
|
|
const existingChatMessageViewModel = this.chatMessageViewModels.value.get(chatMessage.id);
|
|
|
|
|
if (existingChatMessageViewModel != void 0) {
|
|
|
|
|
existingChatMessageViewModel.body.value = chatMessage.body;
|
|
|
|
|
existingChatMessageViewModel.status.value = chatMessage.status;
|
|
|
|
|
} else {
|
|
|
|
|
this.chatMessageViewModels.set(
|
|
|
|
|
chatMessage.id,
|
|
|
|
|
chatMessageViewModel
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.handleReaction = (reaction) => {
|
|
|
|
|
const messageViewModel = this.chatMessageViewModels.value.get(reaction.messageId);
|
|
|
|
|
if (messageViewModel == void 0) return;
|
|
|
|
|
messageViewModel.handleReaction(reaction);
|
|
|
|
|
};
|
|
|
|
|
this.showFilterModal = () => {
|
|
|
|
|
this.isFilterModalOpen.value = true;
|
|
|
|
|
};
|
|
|
|
|
this.hideFilterModal = () => {
|
|
|
|
|
this.isFilterModalOpen.value = false;
|
|
|
|
|
};
|
|
|
|
|
this.revokeReactionFilter = () => {
|
|
|
|
|
this.reactionFilter.value = void 0;
|
|
|
|
|
};
|
|
|
|
|
this.setReactionFilter = (content) => {
|
|
|
|
|
this.reactionFilter.value = content;
|
|
|
|
|
};
|
|
|
|
|
this.resetFilter = () => {
|
|
|
|
|
this.revokeReactionFilter();
|
|
|
|
|
this.searchViewModel.search("");
|
|
|
|
|
};
|
|
|
|
|
// load
|
|
|
|
|
this.loadData = () => {
|
|
|
|
|
this.chatMessageViewModels.clear();
|
|
|
|
|
for (const chatMessage of this.chatViewModel.chatModel.messages) {
|
|
|
|
|
this.showChatMessage(chatMessage);
|
|
|
|
|
}
|
|
|
|
|
for (const reaction of this.chatViewModel.chatModel.reactions) {
|
|
|
|
|
this.handleReaction(reaction);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.cannotSendMessage = createProxyState(
|
|
|
|
|
[
|
|
|
|
|
this.chatViewModel.settingsViewModel.username,
|
|
|
|
|
this.composingMessage
|
|
|
|
|
],
|
|
|
|
|
() => this.chatViewModel.settingsViewModel.username.value == "" || this.composingMessage.value == ""
|
|
|
|
|
);
|
|
|
|
|
this.searchViewModel = new SearchViewModel(
|
|
|
|
|
this.chatMessageViewModels,
|
|
|
|
|
this.filteredMessageViewModels,
|
|
|
|
|
(chatMessageViewModel) => [chatMessageViewModel.body.value],
|
|
|
|
|
new ListState()
|
|
|
|
|
);
|
|
|
|
|
this.isFilterActive = createProxyState(
|
|
|
|
|
[this.searchViewModel.appliedQuery, this.reactionFilter],
|
|
|
|
|
() => this.searchViewModel.appliedQuery.value != "" || this.reactionFilter.value != void 0
|
|
|
|
|
);
|
|
|
|
|
this.registerKeyStroke("=" /* Filter */, this.showFilterModal);
|
|
|
|
|
this.registerKeyStroke("backspace" /* CloseOrCancel */, this.hideFilterModal);
|
|
|
|
|
this.registerKeyStroke("-" /* Reset */, this.resetFilter);
|
|
|
|
|
this.chatViewModel.registerContext("messages" /* Messages */, this);
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-04-11 17:47:24 +00:00
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/ViewModel/Pages/settingsPageViewModel.ts
|
|
|
|
|
var SettingsPageViewModel = class extends Context {
|
|
|
|
|
// init
|
|
|
|
|
constructor(coreViewModel2, chatViewModel) {
|
|
|
|
|
super();
|
|
|
|
|
this.coreViewModel = coreViewModel2;
|
|
|
|
|
this.chatViewModel = chatViewModel;
|
|
|
|
|
this.contextDebugDescription = "settings";
|
|
|
|
|
// state
|
|
|
|
|
this.primaryChannel = new State("");
|
|
|
|
|
this.primaryChannelInput = new State("");
|
|
|
|
|
this.secondaryChannels = new ListState();
|
|
|
|
|
this.newSecondaryChannelInput = new State("");
|
|
|
|
|
this.encryptionKeyInput = new State("");
|
|
|
|
|
this.shouldShowEncryptionKey = new State(false);
|
|
|
|
|
this.encryptionKeyInputType = createProxyState(
|
|
|
|
|
[this.shouldShowEncryptionKey],
|
|
|
|
|
() => this.shouldShowEncryptionKey.value == true ? "text" : "password"
|
|
|
|
|
);
|
|
|
|
|
this.color = new State("standard" /* Standard */);
|
|
|
|
|
// guards
|
|
|
|
|
this.cannotSetPrimaryChannel = createProxyState(
|
|
|
|
|
[this.primaryChannel, this.primaryChannelInput],
|
|
|
|
|
() => this.primaryChannelInput.value == "" || this.primaryChannelInput.value == this.primaryChannel.value
|
|
|
|
|
);
|
|
|
|
|
this.cannotAddSecondaryChannel = createProxyState(
|
|
|
|
|
[this.newSecondaryChannelInput],
|
|
|
|
|
() => this.newSecondaryChannelInput.value == ""
|
|
|
|
|
);
|
|
|
|
|
// methods
|
|
|
|
|
this.setPrimaryChannel = () => {
|
|
|
|
|
this.chatViewModel.chatModel.setPrimaryChannel(
|
|
|
|
|
this.primaryChannelInput.value
|
|
|
|
|
);
|
|
|
|
|
this.primaryChannel.value = this.chatViewModel.chatModel.info.primaryChannel;
|
|
|
|
|
this.chatViewModel.chatListViewModel.updateIndices();
|
|
|
|
|
};
|
|
|
|
|
this.addSecondaryChannel = () => {
|
|
|
|
|
this.secondaryChannels.add(this.newSecondaryChannelInput.value);
|
|
|
|
|
this.newSecondaryChannelInput.value = "";
|
|
|
|
|
this.storeSecondaryChannels();
|
|
|
|
|
this.loadSecondaryChannels();
|
|
|
|
|
};
|
|
|
|
|
this.removeSecondaryChannel = (secondaryChannel) => {
|
|
|
|
|
this.secondaryChannels.remove(secondaryChannel);
|
|
|
|
|
this.storeSecondaryChannels();
|
|
|
|
|
};
|
|
|
|
|
this.storeSecondaryChannels = () => {
|
|
|
|
|
this.chatViewModel.chatModel.setSecondaryChannels([
|
|
|
|
|
...this.secondaryChannels.value.values()
|
|
|
|
|
]);
|
|
|
|
|
};
|
|
|
|
|
this.setEncryptionKey = () => {
|
|
|
|
|
this.chatViewModel.chatModel.setEncryptionKey(
|
|
|
|
|
this.encryptionKeyInput.value
|
|
|
|
|
);
|
|
|
|
|
this.encryptionKeyInput.callSubscriptions();
|
|
|
|
|
};
|
|
|
|
|
this.applyColor = (newColor) => {
|
|
|
|
|
this.chatViewModel.setColor(newColor);
|
|
|
|
|
};
|
|
|
|
|
this.remove = () => {
|
|
|
|
|
this.chatViewModel.close();
|
|
|
|
|
this.chatViewModel.chatModel.delete();
|
|
|
|
|
this.chatViewModel.chatListViewModel.untrackChat(this.chatViewModel);
|
|
|
|
|
};
|
|
|
|
|
// load
|
|
|
|
|
this.loadListRelevantData = () => {
|
|
|
|
|
this.primaryChannel.value = this.chatViewModel.chatModel.info.primaryChannel;
|
|
|
|
|
this.color.value = this.chatViewModel.chatModel.color;
|
|
|
|
|
};
|
|
|
|
|
this.loadData = () => {
|
|
|
|
|
this.primaryChannelInput.value = this.chatViewModel.chatModel.info.primaryChannel;
|
|
|
|
|
this.loadSecondaryChannels();
|
|
|
|
|
this.encryptionKeyInput.value = this.chatViewModel.chatModel.info.encryptionKey;
|
|
|
|
|
};
|
|
|
|
|
this.loadSecondaryChannels = () => {
|
|
|
|
|
this.secondaryChannels.clear();
|
|
|
|
|
for (const secondaryChannel of this.chatViewModel.chatModel.secondaryChannels) {
|
|
|
|
|
this.secondaryChannels.add(secondaryChannel);
|
2026-04-11 17:47:24 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
};
|
|
|
|
|
this.loadListRelevantData();
|
|
|
|
|
this.cannotSetEncryptionKey = createProxyState(
|
|
|
|
|
[this.encryptionKeyInput],
|
|
|
|
|
() => this.encryptionKeyInput.value == this.chatViewModel.chatModel.info.encryptionKey
|
|
|
|
|
);
|
|
|
|
|
this.color.subscribe((newColor) => {
|
|
|
|
|
this.applyColor(newColor);
|
|
|
|
|
});
|
|
|
|
|
this.chatViewModel.registerContext("settings" /* Settings */, this);
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-04-01 08:53:50 +00:00
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/ViewModel/Pages/boardViewModel.ts
|
|
|
|
|
var BoardViewModel = class extends TaskContainingPageViewModel {
|
|
|
|
|
// init
|
|
|
|
|
constructor(coreViewModel2, chatViewModel, boardsAndTasksModel, taskPageViewModel, boardInfo) {
|
|
|
|
|
super(coreViewModel2, chatViewModel, boardsAndTasksModel);
|
|
|
|
|
this.coreViewModel = coreViewModel2;
|
|
|
|
|
this.chatViewModel = chatViewModel;
|
|
|
|
|
this.boardsAndTasksModel = boardsAndTasksModel;
|
|
|
|
|
this.taskPageViewModel = taskPageViewModel;
|
|
|
|
|
this.boardInfo = boardInfo;
|
|
|
|
|
this.contextDebugDescription = "board";
|
|
|
|
|
// state
|
|
|
|
|
this.name = new State("");
|
|
|
|
|
this.color = new State("standard" /* Standard */);
|
|
|
|
|
this.index = new State(0);
|
|
|
|
|
this.selectedPage = new State(
|
|
|
|
|
"list" /* List */
|
|
|
|
|
);
|
|
|
|
|
this.isPresentingSettingsModal = new State(false);
|
|
|
|
|
this.isPresentingFilterModal = new State(false);
|
|
|
|
|
this.filteredTaskViewModels = new ListState();
|
|
|
|
|
// paths
|
|
|
|
|
this.getBasePath = () => {
|
|
|
|
|
return [
|
|
|
|
|
...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId)
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
this.getLastUsedBoardPath = () => {
|
|
|
|
|
return [...this.getBasePath(), "last-used-view" /* LastUsedView */];
|
|
|
|
|
};
|
|
|
|
|
this.getPreviousSearchesPath = () => {
|
|
|
|
|
return [...this.getBasePath(), "previous-searches" /* PreviousSearches */];
|
|
|
|
|
};
|
|
|
|
|
this.getLastSearchPath = () => {
|
|
|
|
|
return [...this.getBasePath(), "last-search" /* LastSearch */];
|
|
|
|
|
};
|
|
|
|
|
// settings
|
|
|
|
|
this.saveSettings = () => {
|
|
|
|
|
const newBoardInfoFileContent = BoardsAndTasksModel.createBoardInfoFileContent(
|
|
|
|
|
this.boardInfo.fileId,
|
|
|
|
|
this.name.value,
|
|
|
|
|
this.color.value
|
|
|
|
|
);
|
|
|
|
|
this.taskPageViewModel.updateBoard(newBoardInfoFileContent);
|
|
|
|
|
};
|
|
|
|
|
this.applyColor = () => {
|
|
|
|
|
this.taskPageViewModel.chatViewModel.setDisplayedColor(
|
|
|
|
|
this.color.value
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.deleteBoard = () => {
|
|
|
|
|
this.taskPageViewModel.deleteBoard(this.boardInfo);
|
|
|
|
|
this.chatViewModel.taskBoardSuggestions.remove(this.boardInfo.fileId);
|
|
|
|
|
this.close();
|
|
|
|
|
};
|
|
|
|
|
// methods
|
|
|
|
|
this.createTask = () => {
|
|
|
|
|
this.createTaskFromBoardId(this.boardInfo.fileId);
|
|
|
|
|
};
|
|
|
|
|
this.handleDropWithinBoard = (category, status) => {
|
|
|
|
|
const draggedObject = this.coreViewModel.draggedObject.value;
|
|
|
|
|
if (draggedObject instanceof TaskViewModel == false) return;
|
|
|
|
|
draggedObject.setCategoryAndStatus(category, status);
|
|
|
|
|
};
|
|
|
|
|
this.handleDropBetweenBoards = () => {
|
|
|
|
|
const draggedObject = this.coreViewModel.draggedObject.value;
|
|
|
|
|
if (draggedObject instanceof TaskViewModel == false) return;
|
|
|
|
|
draggedObject.setBoardId(this.boardInfo.fileId);
|
|
|
|
|
};
|
|
|
|
|
// storage
|
|
|
|
|
this.storeLastUsedView = () => {
|
|
|
|
|
const path = this.getLastUsedBoardPath();
|
|
|
|
|
const lastUsedView = this.selectedPage.value;
|
|
|
|
|
this.coreViewModel.storageModel.write(path, lastUsedView);
|
|
|
|
|
};
|
|
|
|
|
this.restoreLastUsedView = () => {
|
|
|
|
|
const path = this.getLastUsedBoardPath();
|
|
|
|
|
const lastUsedView = this.coreViewModel.storageModel.read(path);
|
|
|
|
|
if (lastUsedView == null) return;
|
|
|
|
|
this.selectedPage.value = lastUsedView;
|
|
|
|
|
};
|
|
|
|
|
this.handleNewSearch = (searchTerm) => {
|
|
|
|
|
const suggestionPath = [
|
|
|
|
|
...this.getPreviousSearchesPath(),
|
|
|
|
|
searchTerm
|
|
|
|
|
];
|
|
|
|
|
this.coreViewModel.storageModel.write(suggestionPath, "");
|
|
|
|
|
if (!this.coreViewModel.boardFilterStringSuggestions.value.has(
|
|
|
|
|
searchTerm
|
|
|
|
|
)) {
|
|
|
|
|
this.coreViewModel.boardFilterStringSuggestions.add(searchTerm);
|
|
|
|
|
}
|
|
|
|
|
const lastSearchPath = this.getLastSearchPath();
|
|
|
|
|
this.coreViewModel.storageModel.write(lastSearchPath, searchTerm);
|
|
|
|
|
};
|
|
|
|
|
// view
|
|
|
|
|
this.showTask = (taskFileContent) => {
|
|
|
|
|
if (taskFileContent.boardId != this.boardInfo.fileId) {
|
|
|
|
|
this.boardsAndTasksModel.deleteTaskReference(
|
|
|
|
|
this.boardInfo.fileId,
|
|
|
|
|
taskFileContent.fileId
|
|
|
|
|
);
|
|
|
|
|
this.removeTaskFromView(taskFileContent);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const taskViewModel = new TaskViewModel(
|
|
|
|
|
this.coreViewModel,
|
|
|
|
|
this.chatViewModel,
|
|
|
|
|
this.boardsAndTasksModel,
|
|
|
|
|
this,
|
|
|
|
|
taskFileContent
|
|
|
|
|
);
|
|
|
|
|
this.taskViewModels.set(taskFileContent.fileId, taskViewModel);
|
|
|
|
|
};
|
|
|
|
|
this.removeTaskFromView = (taskFileContent) => {
|
|
|
|
|
this.taskViewModels.remove(taskFileContent.fileId);
|
|
|
|
|
this.updateIndex();
|
|
|
|
|
};
|
|
|
|
|
this.select = () => {
|
|
|
|
|
this.taskPageViewModel.selectBoard(this);
|
|
|
|
|
};
|
|
|
|
|
this.close = () => {
|
|
|
|
|
this.taskPageViewModel.closeBoard();
|
|
|
|
|
this.taskViewModels.clear();
|
|
|
|
|
};
|
|
|
|
|
this.showSettings = () => {
|
|
|
|
|
this.isPresentingSettingsModal.value = true;
|
|
|
|
|
};
|
|
|
|
|
this.hideSettings = () => {
|
|
|
|
|
this.saveSettings();
|
|
|
|
|
this.isPresentingSettingsModal.value = false;
|
|
|
|
|
};
|
|
|
|
|
this.showFilterModal = () => {
|
|
|
|
|
this.isPresentingFilterModal.value = true;
|
|
|
|
|
};
|
|
|
|
|
this.hideFilterModal = () => {
|
|
|
|
|
this.isPresentingFilterModal.value = false;
|
|
|
|
|
};
|
|
|
|
|
this.resetFilter = () => {
|
|
|
|
|
this.searchViewModel.search("");
|
|
|
|
|
};
|
|
|
|
|
this.updateIndex = () => {
|
|
|
|
|
const index = this.taskPageViewModel.boardIndexManager.getIndex(this);
|
|
|
|
|
this.index.value = index;
|
|
|
|
|
};
|
|
|
|
|
// load
|
|
|
|
|
this.loadListRelevantData = () => {
|
|
|
|
|
this.name.value = this.boardInfo.name;
|
|
|
|
|
this.color.value = this.boardInfo.color;
|
|
|
|
|
};
|
|
|
|
|
this.loadTasks = () => {
|
|
|
|
|
const taskIds = this.boardsAndTasksModel.listTaskIds(
|
|
|
|
|
this.boardInfo.fileId
|
|
|
|
|
);
|
|
|
|
|
for (const taskId of taskIds) {
|
|
|
|
|
if (this.taskViewModels.value.has(taskId)) return;
|
|
|
|
|
const taskFileContent = this.boardsAndTasksModel.getLatestTaskFileContent(taskId);
|
|
|
|
|
if (taskFileContent == null) continue;
|
|
|
|
|
const taskViewModel = new TaskViewModel(
|
|
|
|
|
this.coreViewModel,
|
|
|
|
|
this.chatViewModel,
|
|
|
|
|
this.boardsAndTasksModel,
|
|
|
|
|
this,
|
|
|
|
|
taskFileContent
|
|
|
|
|
);
|
|
|
|
|
this.taskViewModels.set(taskFileContent.fileId, taskViewModel);
|
|
|
|
|
}
|
|
|
|
|
this.updateTaskIndices();
|
|
|
|
|
};
|
|
|
|
|
this.loadSearchSuggestions = () => {
|
|
|
|
|
const dirPath = this.getPreviousSearchesPath();
|
|
|
|
|
const searches = this.coreViewModel.storageModel.list(dirPath);
|
|
|
|
|
this.coreViewModel.boardFilterStringSuggestions.add(...searches);
|
|
|
|
|
};
|
|
|
|
|
this.restoreSearch = () => {
|
|
|
|
|
const lastSearchPath = this.getLastSearchPath();
|
|
|
|
|
const lastSearch = this.coreViewModel.storageModel.read(lastSearchPath);
|
|
|
|
|
if (lastSearch != null) {
|
|
|
|
|
this.searchViewModel.search(lastSearch);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.loadData = () => {
|
|
|
|
|
this.restoreLastUsedView();
|
|
|
|
|
this.loadTasks();
|
|
|
|
|
this.loadSearchSuggestions();
|
|
|
|
|
};
|
|
|
|
|
this.loadListRelevantData();
|
|
|
|
|
this.isSelected = createProxyState(
|
|
|
|
|
[this.taskPageViewModel.selectedBoardId],
|
|
|
|
|
() => this.taskPageViewModel.selectedBoardId.value == this.boardInfo.fileId
|
|
|
|
|
);
|
|
|
|
|
this.color.subscribe(() => {
|
|
|
|
|
if (this.isSelected.value == false) return;
|
|
|
|
|
if (this.chatViewModel.selectedPage.value != "tasks" /* Tasks */)
|
|
|
|
|
return;
|
|
|
|
|
this.applyColor();
|
|
|
|
|
});
|
|
|
|
|
this.selectedPage.subscribeSilent(() => {
|
|
|
|
|
this.storeLastUsedView();
|
|
|
|
|
});
|
|
|
|
|
boardsAndTasksModel.taskHandlerManager.addHandler(
|
|
|
|
|
(taskFileContent) => {
|
|
|
|
|
if (taskFileContent.boardId != this.boardInfo.fileId) return;
|
|
|
|
|
this.showTask(taskFileContent);
|
|
|
|
|
this.updateTaskIndices();
|
2024-07-21 13:13:41 +02:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
);
|
|
|
|
|
this.searchViewModel = new SearchViewModel(
|
|
|
|
|
this.taskViewModels,
|
|
|
|
|
this.filteredTaskViewModels,
|
|
|
|
|
TaskViewModel.getStringsForFilter,
|
|
|
|
|
this.coreViewModel.boardFilterStringSuggestions
|
|
|
|
|
);
|
|
|
|
|
this.searchViewModel.appliedQuery.subscribeSilent((newQuery) => {
|
|
|
|
|
this.handleNewSearch(newQuery);
|
|
|
|
|
});
|
|
|
|
|
this.restoreSearch();
|
|
|
|
|
this.isFilterActive = createProxyState(
|
|
|
|
|
[this.searchViewModel.appliedQuery],
|
|
|
|
|
() => this.searchViewModel.appliedQuery.value != ""
|
|
|
|
|
);
|
|
|
|
|
this.registerKeyStroke("=" /* Filter */, this.showFilterModal);
|
|
|
|
|
this.registerKeyStroke("backspace" /* CloseOrCancel */, this.hideFilterModal);
|
|
|
|
|
this.registerKeyStroke("-" /* Reset */, this.resetFilter);
|
|
|
|
|
this.taskPageViewModel.registerContext(this.boardInfo.fileId, this);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// src/ViewModel/Pages/taskPageViewModel.ts
|
|
|
|
|
var TaskPageViewModel = class extends ContextHost {
|
|
|
|
|
// init
|
|
|
|
|
constructor(coreViewModel2, chatViewModel, boardsAndTasksModel) {
|
|
|
|
|
super();
|
|
|
|
|
this.coreViewModel = coreViewModel2;
|
|
|
|
|
this.chatViewModel = chatViewModel;
|
|
|
|
|
this.boardsAndTasksModel = boardsAndTasksModel;
|
|
|
|
|
this.contextDebugDescription = "task-page";
|
|
|
|
|
// data
|
|
|
|
|
this.boardIndexManager = new IndexManager(
|
|
|
|
|
(boardViewModel) => boardViewModel.name.value
|
|
|
|
|
);
|
|
|
|
|
// paths
|
|
|
|
|
this.getBasePath = () => {
|
|
|
|
|
return [...this.boardsAndTasksModel.getViewPath()];
|
|
|
|
|
};
|
|
|
|
|
this.getBoardViewPath = (boardId) => {
|
|
|
|
|
return [...this.getBasePath(), boardId];
|
|
|
|
|
};
|
|
|
|
|
this.getLastUsedBoardPath = () => {
|
|
|
|
|
return [...this.getBasePath(), "last-used-board" /* LastUsedBoard */];
|
|
|
|
|
};
|
|
|
|
|
// state
|
|
|
|
|
this.newBoardNameInput = new State("");
|
|
|
|
|
this.boardViewModels = new MapState();
|
|
|
|
|
this.isShowingBoadList = new State(true);
|
|
|
|
|
this.selectedBoardId = new State(
|
|
|
|
|
void 0
|
|
|
|
|
);
|
|
|
|
|
// guards
|
|
|
|
|
this.cannotCreateBoard = createProxyState(
|
|
|
|
|
[this.newBoardNameInput],
|
|
|
|
|
() => this.newBoardNameInput.value == ""
|
|
|
|
|
);
|
|
|
|
|
// methods
|
|
|
|
|
this.createBoard = () => {
|
|
|
|
|
if (this.cannotCreateBoard.value == true) return;
|
|
|
|
|
const boardInfoFileContent = this.boardsAndTasksModel.createBoard(this.newBoardNameInput.value);
|
|
|
|
|
this.newBoardNameInput.value = "";
|
|
|
|
|
this.showBoardInList(boardInfoFileContent);
|
|
|
|
|
this.boardsAndTasksModel.updateBoardAndSend(boardInfoFileContent);
|
|
|
|
|
this.updateBoardIndices();
|
|
|
|
|
};
|
|
|
|
|
this.updateBoard = (boardInfoFileContent) => {
|
|
|
|
|
this.boardsAndTasksModel.updateBoardAndSend(boardInfoFileContent);
|
|
|
|
|
this.updateBoardIndices();
|
|
|
|
|
};
|
|
|
|
|
this.deleteBoard = (boardInfoFileContent) => {
|
|
|
|
|
this.boardsAndTasksModel.deleteBoard(boardInfoFileContent.fileId);
|
|
|
|
|
this.boardViewModels.remove(boardInfoFileContent.fileId);
|
|
|
|
|
this.updateBoardIndices();
|
|
|
|
|
};
|
|
|
|
|
// view
|
|
|
|
|
this.toggleBoardList = () => {
|
|
|
|
|
this.isShowingBoadList.value = !this.isShowingBoadList.value;
|
|
|
|
|
};
|
|
|
|
|
this.showBoardInList = (boardInfo) => {
|
|
|
|
|
const boardViewModel = new BoardViewModel(
|
|
|
|
|
this.coreViewModel,
|
|
|
|
|
this.chatViewModel,
|
|
|
|
|
this.boardsAndTasksModel,
|
|
|
|
|
this,
|
|
|
|
|
boardInfo
|
|
|
|
|
);
|
|
|
|
|
this.boardViewModels.set(boardInfo.fileId, boardViewModel);
|
|
|
|
|
this.chatViewModel.taskBoardSuggestions.set(boardInfo.fileId, [
|
|
|
|
|
boardInfo.fileId,
|
|
|
|
|
this.boardsAndTasksModel.getBoardName(boardInfo.fileId)
|
|
|
|
|
]);
|
|
|
|
|
};
|
|
|
|
|
this.selectBoard = (boardViewModel) => {
|
|
|
|
|
this.selectedBoardId.value = boardViewModel.boardInfo.fileId;
|
|
|
|
|
this.chatViewModel.displayedColor.value = boardViewModel.color.value;
|
|
|
|
|
this.storeLastUsedBoard();
|
|
|
|
|
};
|
|
|
|
|
this.closeBoard = () => {
|
|
|
|
|
this.closeContext();
|
|
|
|
|
this.selectedBoardId.value = void 0;
|
|
|
|
|
this.chatViewModel.resetColor();
|
|
|
|
|
this.storeLastUsedBoard();
|
|
|
|
|
};
|
|
|
|
|
this.updateBoardIndices = () => {
|
|
|
|
|
this.boardIndexManager.update([...this.boardViewModels.value.values()]);
|
|
|
|
|
for (const boardViewModel of this.boardViewModels.value.values()) {
|
|
|
|
|
boardViewModel.updateIndex();
|
2026-04-01 08:29:39 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
};
|
|
|
|
|
// storage
|
|
|
|
|
this.storeLastUsedBoard = () => {
|
|
|
|
|
const path = this.getLastUsedBoardPath();
|
|
|
|
|
const lastUsedBoardId = this.selectedBoardId.value ?? "";
|
|
|
|
|
this.coreViewModel.storageModel.write(path, lastUsedBoardId);
|
|
|
|
|
};
|
|
|
|
|
this.openLastUsedBoard = () => {
|
|
|
|
|
const path = this.getLastUsedBoardPath();
|
|
|
|
|
const lastUsedBoardId = this.coreViewModel.storageModel.read(path);
|
|
|
|
|
if (lastUsedBoardId == null) return;
|
|
|
|
|
const boardViewModel = this.boardViewModels.value.get(lastUsedBoardId);
|
|
|
|
|
if (boardViewModel == void 0) return;
|
|
|
|
|
this.selectBoard(boardViewModel);
|
|
|
|
|
};
|
|
|
|
|
// load
|
|
|
|
|
this.loadData = () => {
|
|
|
|
|
this.boardViewModels.clear();
|
|
|
|
|
const boardIds = this.boardsAndTasksModel.listBoardIds();
|
|
|
|
|
for (const boardId of boardIds) {
|
|
|
|
|
const boardInfo = this.boardsAndTasksModel.getBoardInfo(boardId);
|
|
|
|
|
if (boardInfo == null) continue;
|
|
|
|
|
this.showBoardInList(boardInfo);
|
2026-04-01 08:46:10 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
this.updateBoardIndices();
|
|
|
|
|
this.openLastUsedBoard();
|
|
|
|
|
};
|
|
|
|
|
this.chatViewModel = chatViewModel;
|
|
|
|
|
this.loadData();
|
|
|
|
|
boardsAndTasksModel.boardHandlerManager.addHandler(
|
|
|
|
|
(boardInfoFileContent) => {
|
|
|
|
|
this.showBoardInList(boardInfoFileContent);
|
|
|
|
|
this.updateBoardIndices();
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
this.chatViewModel.registerContext("tasks" /* Tasks */, this);
|
|
|
|
|
this.selectedBoardId.subscribeSilent(this.updateContexts);
|
2026-04-13 10:26:03 +00:00
|
|
|
this.registerKeyStroke("." /* Options */, this.toggleBoardList);
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
// context
|
|
|
|
|
get isOpen() {
|
|
|
|
|
return this.chatViewModel.isOpen && this.chatViewModel.selectedPage.value == "tasks" /* Tasks */;
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
get contextSelection() {
|
|
|
|
|
return this.selectedBoardId.value;
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-07-20 21:15:06 +02:00
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/ViewModel/Chat/chatViewModel.ts
|
|
|
|
|
var ChatViewModel6 = class extends ContextHost {
|
|
|
|
|
// init
|
|
|
|
|
constructor(coreViewModel2, chatModel, settingsViewModel2, notificationViewModel, connectionViewModel2, chatListViewModel2) {
|
|
|
|
|
super();
|
|
|
|
|
this.coreViewModel = coreViewModel2;
|
|
|
|
|
this.chatModel = chatModel;
|
|
|
|
|
this.settingsViewModel = settingsViewModel2;
|
|
|
|
|
this.notificationViewModel = notificationViewModel;
|
|
|
|
|
this.connectionViewModel = connectionViewModel2;
|
|
|
|
|
this.chatListViewModel = chatListViewModel2;
|
|
|
|
|
this.contextDebugDescription = "chat";
|
|
|
|
|
// state
|
|
|
|
|
this.displayedColor = new State("standard" /* Standard */);
|
|
|
|
|
this.selectedPage = new State(
|
|
|
|
|
"messages" /* Messages */
|
2026-04-01 08:53:50 +00:00
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
this.pageContexts = new MapState();
|
|
|
|
|
this.index = new State(0);
|
|
|
|
|
this.hasUnreadMessages = new State(false);
|
|
|
|
|
this.taskBoardSuggestions = new MapState();
|
|
|
|
|
// view
|
|
|
|
|
this.open = () => {
|
|
|
|
|
this.coreViewModel.context = this;
|
|
|
|
|
this.chatListViewModel.openChat(this);
|
|
|
|
|
};
|
|
|
|
|
this.close = () => {
|
|
|
|
|
this.coreViewModel.closeContext(this.contextId);
|
|
|
|
|
this.chatListViewModel.closeChat();
|
|
|
|
|
};
|
|
|
|
|
this.openPage = (page) => {
|
|
|
|
|
this.closeContext();
|
|
|
|
|
this.selectedPage.value = page;
|
|
|
|
|
};
|
|
|
|
|
this.closeSubPages = () => {
|
|
|
|
|
};
|
|
|
|
|
this.setColor = (color) => {
|
|
|
|
|
this.setDisplayedColor(color);
|
|
|
|
|
this.chatModel.setColor(color);
|
|
|
|
|
};
|
|
|
|
|
this.setDisplayedColor = (color) => {
|
|
|
|
|
this.displayedColor.value = color;
|
|
|
|
|
};
|
|
|
|
|
this.resetColor = () => {
|
|
|
|
|
this.displayedColor.value = this.settingsPageViewModel.color.value;
|
|
|
|
|
};
|
|
|
|
|
this.updateIndex = () => {
|
|
|
|
|
const index = this.chatListViewModel.chatIndexManager.getIndex(this);
|
|
|
|
|
this.index.value = index;
|
|
|
|
|
};
|
|
|
|
|
// load
|
|
|
|
|
this.loadPageSelection = () => {
|
|
|
|
|
const path = StorageModel.getPath(
|
|
|
|
|
"chat" /* Chat */,
|
|
|
|
|
filePaths.chat.lastUsedPage(this.chatModel.id)
|
|
|
|
|
);
|
|
|
|
|
const lastUsedPage = this.coreViewModel.storageModel.read(path);
|
|
|
|
|
if (lastUsedPage != null) {
|
|
|
|
|
this.selectedPage.value = lastUsedPage;
|
2024-07-23 13:23:27 +02:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
this.selectedPage.subscribeSilent((newPage) => {
|
|
|
|
|
this.coreViewModel.storageModel.write(path, newPage);
|
|
|
|
|
this.resetColor();
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
this.loadInfo = () => {
|
|
|
|
|
this.updateReadStatus();
|
|
|
|
|
this.taskBoardSuggestions.set(CALENDAR_EVENT_BOARD_ID, [
|
|
|
|
|
CALENDAR_EVENT_BOARD_ID,
|
|
|
|
|
this.coreViewModel.translations.chatPage.calendar.eventsBoard
|
|
|
|
|
]);
|
|
|
|
|
};
|
|
|
|
|
this.updateReadStatus = () => {
|
|
|
|
|
if (this.chatListViewModel.selectedChat.value == this && this.selectedPage.value == "messages" /* Messages */) {
|
|
|
|
|
this.chatModel.setReadStatus(false);
|
2026-04-09 16:48:22 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
this.hasUnreadMessages.value = this.chatModel.info.hasUnreadMessages;
|
|
|
|
|
};
|
|
|
|
|
this.setReadStatus = (hasUnreadMessages) => {
|
|
|
|
|
this.chatModel.setReadStatus(hasUnreadMessages);
|
|
|
|
|
this.hasUnreadMessages.value = hasUnreadMessages;
|
|
|
|
|
};
|
|
|
|
|
this.subscribeReadStatus = () => {
|
|
|
|
|
createProxyState(
|
|
|
|
|
[this.selectedPage, this.chatListViewModel.selectedChat],
|
|
|
|
|
() => {
|
|
|
|
|
if (this.chatListViewModel.selectedChat.value != this) return;
|
|
|
|
|
if (this.selectedPage.value != "messages" /* Messages */) return;
|
|
|
|
|
this.setReadStatus(false);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.calendarViewModel = new CalendarPageViewModel(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
this,
|
|
|
|
|
this.chatModel.fileModel.boardsAndTasksModel.calendarModel,
|
|
|
|
|
this.chatModel.fileModel.boardsAndTasksModel
|
2026-04-09 16:48:22 +00:00
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
this.taskPageViewModel = new TaskPageViewModel(
|
|
|
|
|
this.coreViewModel,
|
|
|
|
|
this,
|
|
|
|
|
this.chatModel.fileModel.boardsAndTasksModel
|
2026-04-01 08:53:50 +00:00
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
this.messagePageViewModel = new MessagePageViewModel(
|
|
|
|
|
this.coreViewModel,
|
|
|
|
|
this
|
|
|
|
|
);
|
|
|
|
|
this.settingsPageViewModel = new SettingsPageViewModel(
|
|
|
|
|
this.coreViewModel,
|
|
|
|
|
this
|
|
|
|
|
);
|
|
|
|
|
chatModel.chatMessageHandlerManager.addHandler(
|
|
|
|
|
(chatMessage) => {
|
|
|
|
|
this.messagePageViewModel.showChatMessage(chatMessage);
|
|
|
|
|
this.updateReadStatus();
|
|
|
|
|
this.notificationViewModel.showNotification(chatMessage);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
chatModel.reactionHandlerManager.addHandler(
|
|
|
|
|
(reaction) => {
|
|
|
|
|
this.messagePageViewModel.handleReaction(reaction);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
this.loadPageSelection();
|
|
|
|
|
this.resetColor();
|
|
|
|
|
this.loadInfo();
|
|
|
|
|
this.subscribeReadStatus();
|
|
|
|
|
this.registerKeyStroke(" " /* Home */, this.close);
|
|
|
|
|
this.registerKeyStroke(
|
|
|
|
|
"h",
|
|
|
|
|
() => this.openPage("messages" /* Messages */)
|
|
|
|
|
);
|
|
|
|
|
this.registerKeyStroke("j", () => this.openPage("tasks" /* Tasks */));
|
|
|
|
|
this.registerKeyStroke(
|
|
|
|
|
"k",
|
|
|
|
|
() => this.openPage("calendar" /* Calendar */)
|
|
|
|
|
);
|
|
|
|
|
this.registerKeyStroke(
|
|
|
|
|
"," /* Settings */,
|
|
|
|
|
() => this.openPage("settings" /* Settings */)
|
|
|
|
|
);
|
|
|
|
|
this.chatListViewModel.selectedChat.subscribeSilent(this.updateContexts);
|
|
|
|
|
this.selectedPage.subscribeSilent(this.updateContexts);
|
2024-07-25 18:42:06 +02:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
// context
|
|
|
|
|
get isOpen() {
|
|
|
|
|
return this.chatListViewModel.selectedChat.value == this;
|
2024-07-25 18:42:06 +02:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
get contextSelection() {
|
|
|
|
|
return this.selectedPage.value;
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-04-01 08:53:50 +00:00
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/ViewModel/Global/notificationViewModel.ts
|
|
|
|
|
var NotificationViewModel = class _NotificationViewModel {
|
2026-04-01 08:53:50 +00:00
|
|
|
// init
|
2026-04-12 19:20:37 +00:00
|
|
|
constructor(chatListViewModel2) {
|
|
|
|
|
this.chatListViewModel = chatListViewModel2;
|
|
|
|
|
// data
|
|
|
|
|
this.seenMessageIds = /* @__PURE__ */ new Set();
|
|
|
|
|
this.messagesInMarquee = [];
|
|
|
|
|
this.marquee = new State(void 0);
|
|
|
|
|
this.currentIndex = 0;
|
|
|
|
|
this.interval = void 0;
|
|
|
|
|
// main
|
|
|
|
|
this.showNotification = (message) => {
|
|
|
|
|
const notification = _NotificationViewModel.createNotification(message);
|
|
|
|
|
if (this.seenMessageIds.has(message.id)) return;
|
|
|
|
|
const currentChat = this.chatListViewModel.selectedChat.value.chatModel.info.primaryChannel;
|
|
|
|
|
const currentPage = this.chatListViewModel.selectedChat.value.selectedPage.value;
|
|
|
|
|
if (notification.chat == currentChat && currentPage == "messages" /* Messages */)
|
|
|
|
|
return;
|
|
|
|
|
this.messagesInMarquee.push(notification);
|
|
|
|
|
this.startLoop();
|
|
|
|
|
};
|
|
|
|
|
this.openNotification = () => {
|
|
|
|
|
const notification = this.marquee.value;
|
|
|
|
|
if (notification == void 0) return;
|
|
|
|
|
const chat = [
|
|
|
|
|
...this.chatListViewModel.chatViewModels.value.values()
|
|
|
|
|
].find(
|
|
|
|
|
(chat2) => chat2.chatModel.info.primaryChannel == notification.chat
|
|
|
|
|
);
|
|
|
|
|
chat.open();
|
|
|
|
|
chat.selectedPage.value = "messages" /* Messages */;
|
|
|
|
|
};
|
|
|
|
|
// loop
|
|
|
|
|
this.loop = () => {
|
|
|
|
|
if (this.messagesInMarquee.length == 0) {
|
|
|
|
|
this.marquee.value = void 0;
|
|
|
|
|
return this.stopLoop();
|
|
|
|
|
}
|
|
|
|
|
const notification = this.messagesInMarquee.shift();
|
|
|
|
|
this.seenMessageIds.delete(notification.messageId);
|
|
|
|
|
this.marquee.value = notification;
|
|
|
|
|
};
|
|
|
|
|
this.startLoop = () => {
|
|
|
|
|
if (this.interval != void 0) return;
|
|
|
|
|
this.loop();
|
|
|
|
|
this.interval = setInterval(() => {
|
|
|
|
|
this.loop();
|
|
|
|
|
}, 5e3);
|
|
|
|
|
};
|
|
|
|
|
this.stopLoop = () => {
|
|
|
|
|
clearInterval(this.interval);
|
|
|
|
|
this.interval = void 0;
|
|
|
|
|
};
|
|
|
|
|
this.skipLoop = () => {
|
|
|
|
|
this.stopLoop();
|
|
|
|
|
this.startLoop();
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
2024-07-25 02:49:23 +02:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
// util
|
|
|
|
|
static createNotification(message) {
|
|
|
|
|
return {
|
|
|
|
|
messageId: message.id,
|
|
|
|
|
chat: ChatModel.splitChannel(message.channel)[0],
|
|
|
|
|
sender: message.sender,
|
|
|
|
|
body: message.body
|
|
|
|
|
};
|
2024-07-25 04:12:30 +02:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/ViewModel/Chat/chatListViewModel.ts
|
|
|
|
|
var ChatListViewModel = class {
|
|
|
|
|
// init
|
|
|
|
|
constructor(coreViewModel2, settingsViewModel2, connectionViewModel2) {
|
|
|
|
|
this.coreViewModel = coreViewModel2;
|
|
|
|
|
this.settingsViewModel = settingsViewModel2;
|
|
|
|
|
this.connectionViewModel = connectionViewModel2;
|
|
|
|
|
// data
|
|
|
|
|
this.chatIndexManager = new IndexManager(
|
|
|
|
|
(chatViewModel) => chatViewModel.settingsPageViewModel.primaryChannel.value
|
|
|
|
|
);
|
|
|
|
|
// state
|
|
|
|
|
this.newChatPrimaryChannel = new State("");
|
|
|
|
|
this.chatViewModels = new ListState();
|
|
|
|
|
this.selectedChat = new State(
|
|
|
|
|
void 0
|
|
|
|
|
);
|
|
|
|
|
// guards
|
|
|
|
|
this.cannotCreateChat = createProxyState(
|
|
|
|
|
[this.newChatPrimaryChannel],
|
|
|
|
|
() => this.newChatPrimaryChannel.value == ""
|
|
|
|
|
);
|
|
|
|
|
// methods
|
|
|
|
|
this.createChat = () => {
|
|
|
|
|
const chatModel = this.coreViewModel.chatListModel.createChat(
|
|
|
|
|
this.newChatPrimaryChannel.value
|
2024-07-25 02:49:23 +02:00
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
this.newChatPrimaryChannel.value = "";
|
|
|
|
|
const chatViewModel = this.createChatViewModel(chatModel);
|
|
|
|
|
this.trackChat(chatViewModel);
|
|
|
|
|
this.updateIndices();
|
|
|
|
|
};
|
|
|
|
|
this.trackChat = (chatViewModel) => {
|
|
|
|
|
this.chatViewModels.add(chatViewModel);
|
|
|
|
|
};
|
|
|
|
|
this.untrackChat = (chatViewModel) => {
|
|
|
|
|
this.coreViewModel.chatListModel.untrackChat(chatViewModel.chatModel);
|
|
|
|
|
this.chatViewModels.remove(chatViewModel);
|
|
|
|
|
};
|
|
|
|
|
this.createChatViewModel = (chatModel) => {
|
|
|
|
|
return new ChatViewModel6(
|
|
|
|
|
this.coreViewModel,
|
|
|
|
|
chatModel,
|
|
|
|
|
this.settingsViewModel,
|
|
|
|
|
this.notificationViewModel,
|
|
|
|
|
this.connectionViewModel,
|
|
|
|
|
this
|
2026-04-01 08:46:10 +00:00
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
};
|
|
|
|
|
this.updateIndices = () => {
|
|
|
|
|
this.chatIndexManager.update([...this.chatViewModels.value.values()]);
|
|
|
|
|
for (const chatViewModel of this.chatViewModels.value) {
|
|
|
|
|
chatViewModel.updateIndex();
|
2024-07-25 16:20:43 +02:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
};
|
|
|
|
|
// view
|
|
|
|
|
this.openChat = (chatViewModel) => {
|
|
|
|
|
this.selectedChat.value = chatViewModel;
|
|
|
|
|
this.notificationViewModel.skipLoop();
|
|
|
|
|
};
|
|
|
|
|
this.closeChat = () => {
|
|
|
|
|
this.selectedChat.value = void 0;
|
|
|
|
|
};
|
|
|
|
|
// load
|
|
|
|
|
this.loadChats = () => {
|
|
|
|
|
this.chatViewModels.clear();
|
|
|
|
|
for (const chatModel of this.coreViewModel.chatListModel.chatModels.values()) {
|
|
|
|
|
const chatViewModel = this.createChatViewModel(chatModel);
|
|
|
|
|
this.trackChat(chatViewModel);
|
2026-04-01 08:46:10 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
this.updateIndices();
|
|
|
|
|
};
|
|
|
|
|
this.notificationViewModel = new NotificationViewModel(this);
|
|
|
|
|
this.loadChats();
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-04-01 08:53:50 +00:00
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/View/Components/monthGrid.tsx
|
|
|
|
|
function MonthGrid2(coreViewModel2, monthGrid, selectedDate, handleDrop) {
|
|
|
|
|
const dayLabels = [];
|
|
|
|
|
let currentWeekday = monthGrid.firstDayOfWeek;
|
|
|
|
|
while (dayLabels.length < 7) {
|
|
|
|
|
dayLabels.push(
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.regional.weekdays.abbreviated[currentWeekday])
|
2026-04-01 08:53:50 +00:00
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
currentWeekday++;
|
|
|
|
|
if (currentWeekday == 7) currentWeekday = 0;
|
|
|
|
|
}
|
|
|
|
|
const offsetElements = [];
|
|
|
|
|
for (let i = 0; i < monthGrid.offset; i++) {
|
|
|
|
|
offsetElements.push(/* @__PURE__ */ createElement("div", null));
|
|
|
|
|
}
|
|
|
|
|
const converter = (taskViewModel) => {
|
|
|
|
|
const view = /* @__PURE__ */ createElement("span", { class: "ellipsis secondary" }, taskViewModel.task.name);
|
|
|
|
|
taskViewModel.index.subscribe((newIndex) => {
|
|
|
|
|
view.style.order = newIndex;
|
|
|
|
|
});
|
|
|
|
|
return view;
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
2026-04-12 19:20:37 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", { class: "month-grid-wrapper" }, /* @__PURE__ */ createElement("div", { class: "day-labels" }, ...dayLabels), /* @__PURE__ */ createElement("div", { class: "month-grid" }, ...offsetElements, ...Object.entries(monthGrid.days).sort((a, b) => localeCompare(a[0], b[0])).map((entry) => {
|
|
|
|
|
const [date, mapState] = entry;
|
|
|
|
|
const isSelected = createProxyState(
|
|
|
|
|
[selectedDate],
|
|
|
|
|
() => selectedDate.value == parseInt(date)
|
|
|
|
|
);
|
|
|
|
|
const isToday = monthGrid.isCurrentMonth == true && parseInt(date) == (/* @__PURE__ */ new Date()).getDate();
|
|
|
|
|
const eventCount = createProxyState(
|
|
|
|
|
[mapState],
|
|
|
|
|
() => mapState.value.size
|
|
|
|
|
);
|
|
|
|
|
const hasEvents = createProxyState(
|
|
|
|
|
[eventCount],
|
|
|
|
|
() => eventCount.value != 0
|
|
|
|
|
);
|
|
|
|
|
function select() {
|
|
|
|
|
selectedDate.value = parseInt(date);
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
function drop() {
|
|
|
|
|
handleDrop(date);
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "tile",
|
|
|
|
|
"on:click": select,
|
|
|
|
|
"toggle:selected": isSelected,
|
|
|
|
|
"toggle:today": isToday,
|
|
|
|
|
"on:dragover": allowDrop,
|
|
|
|
|
"on:drop": drop
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, date), /* @__PURE__ */ createElement(
|
|
|
|
|
"span",
|
2026-04-01 08:53:50 +00:00
|
|
|
{
|
2026-04-12 19:20:37 +00:00
|
|
|
class: "event-count",
|
|
|
|
|
"toggle:has-events": hasEvents,
|
|
|
|
|
"subscribe:innerText": eventCount
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
2026-04-12 19:20:37 +00:00
|
|
|
class: "event-list",
|
|
|
|
|
"children:append": [mapState, converter]
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
))
|
|
|
|
|
);
|
|
|
|
|
})));
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
|
|
|
|
|
// src/View/Components/option.tsx
|
|
|
|
|
function Option(text, value, selectedOnCreate) {
|
|
|
|
|
return /* @__PURE__ */ createElement("option", { value, "toggle:selected": selectedOnCreate }, text);
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
var StringToOption = (string) => {
|
|
|
|
|
return Option(string, string, false);
|
|
|
|
|
};
|
|
|
|
|
var VersionIdToOption = (versionId) => {
|
|
|
|
|
const [date, rest] = versionId.split("T");
|
|
|
|
|
const [time] = rest.split(".");
|
|
|
|
|
const readableName = `${date} ${time}`;
|
|
|
|
|
return Option(readableName, versionId, false);
|
|
|
|
|
};
|
2026-04-01 08:53:50 +00:00
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/View/Components/dangerousActionButton.tsx
|
|
|
|
|
function DangerousActionButton(coreViewModel2, label, icon, action) {
|
|
|
|
|
const isActionRequested = new State(false);
|
|
|
|
|
const cannotConfirm = createProxyState(
|
|
|
|
|
[isActionRequested],
|
|
|
|
|
() => isActionRequested.value == false
|
2026-04-01 08:53:50 +00:00
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
function requestAction() {
|
|
|
|
|
isActionRequested.value = true;
|
|
|
|
|
}
|
|
|
|
|
function abort() {
|
|
|
|
|
isActionRequested.value = false;
|
|
|
|
|
}
|
|
|
|
|
return /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement("button", { class: "flex", "on:click": abort, "toggle:hidden": cannotConfirm }, coreViewModel2.translations.general.abortButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "undo")), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "danger flex",
|
|
|
|
|
"on:click": requestAction,
|
|
|
|
|
"toggle:hidden": isActionRequested
|
|
|
|
|
},
|
|
|
|
|
label,
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, icon)
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "danger flex",
|
|
|
|
|
"on:click": action,
|
|
|
|
|
"toggle:hidden": cannotConfirm
|
|
|
|
|
},
|
|
|
|
|
coreViewModel2.translations.general.confirmButton,
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "warning")
|
|
|
|
|
));
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/View/Modals/taskSettingsModal.tsx
|
|
|
|
|
function TaskSettingsModal(coreViewModel2, taskViewModel) {
|
|
|
|
|
const categorySuggestionId = v4_default();
|
|
|
|
|
const statusSuggestionId = v4_default();
|
|
|
|
|
const BoardOptionConverter = (entry) => {
|
|
|
|
|
const isSelected = entry[0] == taskViewModel.task.boardId;
|
|
|
|
|
return Option(entry[1], entry[0], isSelected);
|
|
|
|
|
};
|
|
|
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", open: true }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.chatPage.task.taskSettingsHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "history"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.general.fileVersionLabel), /* @__PURE__ */ createElement(
|
|
|
|
|
"select",
|
|
|
|
|
{
|
|
|
|
|
"bind:value": taskViewModel.selectedVersionId,
|
|
|
|
|
"children:append": [
|
|
|
|
|
taskViewModel.versionIds,
|
|
|
|
|
VersionIdToOption
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskNameLabel), /* @__PURE__ */ createElement(
|
2026-04-11 20:37:19 +00:00
|
|
|
"input",
|
|
|
|
|
{
|
2026-04-12 19:20:37 +00:00
|
|
|
"bind:value": taskViewModel.name,
|
|
|
|
|
id: "focused"
|
2026-04-11 20:37:19 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "category"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskBoardLabel), /* @__PURE__ */ createElement(
|
|
|
|
|
"select",
|
|
|
|
|
{
|
|
|
|
|
"bind:value": taskViewModel.boardId,
|
|
|
|
|
"children:append": [
|
|
|
|
|
taskViewModel.chatViewModel.taskBoardSuggestions,
|
|
|
|
|
BoardOptionConverter
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "description"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskDescriptionLabel), /* @__PURE__ */ createElement(
|
|
|
|
|
"textarea",
|
|
|
|
|
{
|
|
|
|
|
rows: "10",
|
|
|
|
|
"bind:value": taskViewModel.description
|
|
|
|
|
}
|
|
|
|
|
))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "category"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskCategoryLabel), /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
|
|
|
|
"bind:value": taskViewModel.category,
|
|
|
|
|
list: categorySuggestionId
|
|
|
|
|
}
|
|
|
|
|
))), /* @__PURE__ */ createElement(
|
2026-04-11 20:37:19 +00:00
|
|
|
"datalist",
|
|
|
|
|
{
|
|
|
|
|
hidden: true,
|
2026-04-12 19:20:37 +00:00
|
|
|
id: categorySuggestionId,
|
2026-04-11 20:37:19 +00:00
|
|
|
"children:append": [
|
2026-04-12 19:20:37 +00:00
|
|
|
taskViewModel.coreViewModel.taskCategorySuggestions,
|
2026-04-11 20:37:19 +00:00
|
|
|
StringToOption
|
|
|
|
|
]
|
|
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "clock_loader_40"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskStatusLabel), /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
2026-04-11 20:37:19 +00:00
|
|
|
{
|
2026-04-12 19:20:37 +00:00
|
|
|
"bind:value": taskViewModel.status,
|
|
|
|
|
list: statusSuggestionId
|
|
|
|
|
}
|
|
|
|
|
))), /* @__PURE__ */ createElement(
|
|
|
|
|
"datalist",
|
2026-04-11 20:37:19 +00:00
|
|
|
{
|
2026-04-12 19:20:37 +00:00
|
|
|
hidden: true,
|
|
|
|
|
id: statusSuggestionId,
|
2026-04-11 20:37:19 +00:00
|
|
|
"children:append": [
|
2026-04-12 19:20:37 +00:00
|
|
|
taskViewModel.coreViewModel.taskStatusSuggestions,
|
|
|
|
|
StringToOption
|
2026-04-11 20:37:19 +00:00
|
|
|
]
|
|
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "priority_high"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskPriorityLabel), /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
2026-04-01 08:53:50 +00:00
|
|
|
{
|
2026-04-12 19:20:37 +00:00
|
|
|
type: "number",
|
|
|
|
|
"bind:value": taskViewModel.priority
|
|
|
|
|
}
|
|
|
|
|
))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "calendar_month"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskDateLabel), /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
2026-04-01 08:53:50 +00:00
|
|
|
{
|
2026-04-12 19:20:37 +00:00
|
|
|
type: "date",
|
|
|
|
|
"bind:value": taskViewModel.date
|
|
|
|
|
}
|
|
|
|
|
))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "schedule"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskTimeLabel), /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
|
|
|
|
type: "time",
|
|
|
|
|
"bind:value": taskViewModel.time
|
|
|
|
|
}
|
|
|
|
|
))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
coreViewModel2.translations.chatPage.task.deleteTaskButton,
|
|
|
|
|
"delete_forever",
|
|
|
|
|
taskViewModel.deleteTask
|
|
|
|
|
))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"button",
|
|
|
|
|
{
|
2026-04-12 19:20:37 +00:00
|
|
|
class: "flex",
|
|
|
|
|
"on:click": taskViewModel.closeAndDiscard
|
2026-04-01 08:53:50 +00:00
|
|
|
},
|
2026-04-12 19:20:37 +00:00
|
|
|
coreViewModel2.translations.general.closeButton
|
2026-04-01 08:53:50 +00:00
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
2026-04-12 19:20:37 +00:00
|
|
|
class: "flex primary",
|
|
|
|
|
"on:click": taskViewModel.closeAndSave
|
2026-04-01 08:53:50 +00:00
|
|
|
},
|
2026-04-12 19:20:37 +00:00
|
|
|
coreViewModel2.translations.general.saveButton,
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "save")
|
|
|
|
|
))));
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/View/Components/taskEntry.tsx
|
|
|
|
|
function TaskEntry(taskViewModel) {
|
|
|
|
|
const details = {
|
|
|
|
|
description: taskViewModel.description.value || "---",
|
|
|
|
|
priority_high: taskViewModel.priority.value || "---",
|
|
|
|
|
category: taskViewModel.category.value || "---",
|
|
|
|
|
clock_loader_40: taskViewModel.status.value || "---",
|
|
|
|
|
calendar_month: taskViewModel.date.value || "---",
|
|
|
|
|
schedule: taskViewModel.time.value || "---"
|
|
|
|
|
};
|
2026-04-01 08:53:50 +00:00
|
|
|
const view = /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
2026-04-12 19:20:37 +00:00
|
|
|
draggable: "true",
|
|
|
|
|
class: "tile flex-no",
|
|
|
|
|
style: "user-select: none; -webkit-user-select: none",
|
|
|
|
|
"on:click": taskViewModel.open,
|
|
|
|
|
"on:dragstart": taskViewModel.dragStart
|
2026-04-01 08:53:50 +00:00
|
|
|
},
|
2026-04-12 19:20:37 +00:00
|
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement(
|
|
|
|
|
"b",
|
2026-04-01 08:53:50 +00:00
|
|
|
{
|
2026-04-12 19:20:37 +00:00
|
|
|
class: "ellipsis",
|
|
|
|
|
"subscribe:innerText": taskViewModel.name
|
2024-07-22 21:07:44 +02:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "grid secondary",
|
|
|
|
|
style: "grid-template-columns: repeat(2, 1fr); column-gap: 1rem; row-gap: .5rem"
|
|
|
|
|
},
|
|
|
|
|
...Object.entries(details).map((entry) => /* @__PURE__ */ createElement(
|
|
|
|
|
"span",
|
|
|
|
|
{
|
|
|
|
|
class: "flex-row align-center width-100 flex-no clip",
|
|
|
|
|
style: "gap: 1rem"
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon", style: "font-size: 1.1rem" }, entry[0]),
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "ellipsis" }, entry[1])
|
|
|
|
|
))
|
|
|
|
|
))
|
2026-04-01 08:53:50 +00:00
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
taskViewModel.index.subscribe((newIndex) => {
|
2026-04-01 08:53:50 +00:00
|
|
|
view.style.order = newIndex;
|
|
|
|
|
});
|
|
|
|
|
return view;
|
|
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
var TaskViewModelToEntry = (taskViewModel) => {
|
|
|
|
|
return TaskEntry(taskViewModel);
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/View/Components/placeholderView.tsx
|
|
|
|
|
function PlaceholderView(text) {
|
|
|
|
|
return /* @__PURE__ */ createElement("div", { class: "width-100 height-100 flex-column justify-center align-center" }, /* @__PURE__ */ createElement("span", { class: "secondary slide-up" }, text));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/ChatPages/calendarPage.tsx
|
|
|
|
|
function CalendarPage(coreViewModel2, calendarPageViewModel) {
|
|
|
|
|
calendarPageViewModel.loadData();
|
|
|
|
|
const mainContent = createProxyState(
|
|
|
|
|
[calendarPageViewModel.monthGrid],
|
|
|
|
|
() => {
|
|
|
|
|
const monthGrid = calendarPageViewModel.monthGrid.value;
|
|
|
|
|
if (monthGrid == void 0) {
|
|
|
|
|
return /* @__PURE__ */ createElement("div", null);
|
|
|
|
|
} else {
|
|
|
|
|
let drop = function(date) {
|
|
|
|
|
calendarPageViewModel.handleDrop(
|
|
|
|
|
monthGrid.year.toString(),
|
|
|
|
|
monthGrid.month.toString(),
|
|
|
|
|
date
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
return MonthGrid2(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
monthGrid,
|
|
|
|
|
calendarPageViewModel.selectedDate,
|
|
|
|
|
drop
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
const sidePaneContentWrapper = createProxyState(
|
|
|
|
|
[
|
|
|
|
|
calendarPageViewModel.selectedYear,
|
|
|
|
|
calendarPageViewModel.selectedMonth,
|
|
|
|
|
calendarPageViewModel.selectedDate
|
|
|
|
|
],
|
2026-04-01 08:53:50 +00:00
|
|
|
() => {
|
2026-04-12 19:20:37 +00:00
|
|
|
const listState = calendarPageViewModel.getEventsForDate();
|
|
|
|
|
if (listState == void 0) {
|
|
|
|
|
return /* @__PURE__ */ createElement("div", null);
|
|
|
|
|
} else {
|
|
|
|
|
const sidePaneContent = createProxyState(
|
|
|
|
|
[listState],
|
|
|
|
|
() => {
|
|
|
|
|
if (listState.value.size == 0) {
|
|
|
|
|
return PlaceholderView(
|
|
|
|
|
coreViewModel2.translations.chatPage.calendar.noEvents
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "flex-column gap slide-up padding-bottom",
|
|
|
|
|
"children:append": [
|
|
|
|
|
listState,
|
|
|
|
|
TaskViewModelToEntry
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "width-100 height-100",
|
|
|
|
|
"children:set": sidePaneContent
|
|
|
|
|
}
|
2026-04-10 18:17:16 +00:00
|
|
|
);
|
2024-07-20 15:49:58 +02:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
const taskSettingsModal = createProxyState(
|
|
|
|
|
[calendarPageViewModel.selectedTaskViewModel],
|
|
|
|
|
() => {
|
|
|
|
|
if (calendarPageViewModel.selectedTaskViewModel.value == void 0) {
|
|
|
|
|
return /* @__PURE__ */ createElement("div", null);
|
|
|
|
|
} else {
|
|
|
|
|
setFocusWithDelay();
|
|
|
|
|
return TaskSettingsModal(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
calendarPageViewModel.selectedTaskViewModel.value
|
|
|
|
|
);
|
2024-07-20 03:39:47 +02:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", { id: "calendar-page" }, /* @__PURE__ */ createElement("div", { class: "pane-wrapper grid-pane-wrapper" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
2026-04-01 08:53:50 +00:00
|
|
|
{
|
2026-04-12 19:20:37 +00:00
|
|
|
class: "ghost",
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.calendar.todayButtonAudioLabel,
|
|
|
|
|
"on:click": calendarPageViewModel.showToday
|
2026-04-01 08:53:50 +00:00
|
|
|
},
|
2026-04-12 19:20:37 +00:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "today")
|
|
|
|
|
)), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "ghost",
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.calendar.previousMonthButtonAudioLabel,
|
|
|
|
|
"on:click": calendarPageViewModel.showPreviousMonth
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_back")
|
|
|
|
|
), /* @__PURE__ */ createElement("span", { class: "input-wrapper" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
|
|
|
|
class: "year-input",
|
|
|
|
|
type: "number",
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.calendar.yearInputAudioLabel,
|
|
|
|
|
placeholder: coreViewModel2.translations.chatPage.calendar.yearInputPlaceholder,
|
|
|
|
|
"bind:value": calendarPageViewModel.selectedYear
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
|
|
|
|
class: "month-input",
|
|
|
|
|
type: "number",
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.calendar.monthInputAudioLabel,
|
|
|
|
|
placeholder: coreViewModel2.translations.chatPage.calendar.monthInputPlaceholder,
|
|
|
|
|
"bind:value": calendarPageViewModel.selectedMonth
|
|
|
|
|
}
|
|
|
|
|
)), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "ghost",
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.calendar.nextMonthButtonAudioLabel,
|
|
|
|
|
"on:click": calendarPageViewModel.showNextMonth
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
|
|
|
)), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "ghost",
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.task.createTaskButtonAudioLabel,
|
|
|
|
|
"on:click": calendarPageViewModel.createEvent
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "add")
|
|
|
|
|
))), /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "content padding-0",
|
|
|
|
|
"children:set": mainContent
|
|
|
|
|
}
|
|
|
|
|
))), /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "pane-wrapper side background",
|
|
|
|
|
"set:color": calendarPageViewModel.chatViewModel.displayedColor
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"div",
|
|
|
|
|
{
|
2026-04-12 19:20:37 +00:00
|
|
|
class: "content",
|
|
|
|
|
"children:set": sidePaneContentWrapper
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
))
|
|
|
|
|
), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Components/ribbonButton.tsx
|
|
|
|
|
function RibbonButton(label, icon, isSelected, select, isHighlighted = new State(false)) {
|
|
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "ribbon-button animate-highlight",
|
|
|
|
|
"aria-label": label,
|
|
|
|
|
"toggle:selected": isSelected,
|
|
|
|
|
"toggle:highlight": isHighlighted,
|
|
|
|
|
"on:click": select
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, icon)
|
2026-04-01 08:53:50 +00:00
|
|
|
);
|
|
|
|
|
}
|
2024-07-20 02:30:45 +02:00
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/View/Components/chatViewToggleButton.tsx
|
|
|
|
|
function ChatViewToggleButton(label, icon, page, chatViewModel) {
|
|
|
|
|
function select() {
|
|
|
|
|
chatViewModel.selectedPage.value = page;
|
|
|
|
|
}
|
|
|
|
|
const isSelected = createProxyState(
|
2026-04-01 08:53:50 +00:00
|
|
|
[chatViewModel.selectedPage],
|
2026-04-12 19:20:37 +00:00
|
|
|
() => chatViewModel.selectedPage.value == page
|
2026-04-01 08:53:50 +00:00
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
const isHighlighted = new State(false);
|
|
|
|
|
if (page == "messages" /* Messages */) {
|
|
|
|
|
chatViewModel.hasUnreadMessages.subscribe(
|
|
|
|
|
(hasUnreadMessages) => isHighlighted.value = hasUnreadMessages
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return RibbonButton(label, icon, isSelected, select, isHighlighted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Components/messageReactionButton.tsx
|
|
|
|
|
function MessageReactionButton(coreViewModel2, chatMessageViewModel, content) {
|
|
|
|
|
let audioLabel;
|
|
|
|
|
let count;
|
|
|
|
|
let isActive = createProxyState(
|
|
|
|
|
[chatMessageViewModel.userReaction],
|
|
|
|
|
() => chatMessageViewModel.userReaction.value == content
|
2026-04-01 19:00:16 +00:00
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
function sendReaction() {
|
|
|
|
|
chatMessageViewModel.sendReaction(content, isActive.value);
|
|
|
|
|
}
|
|
|
|
|
switch (content) {
|
|
|
|
|
case "\u{1F44D}" /* ThumbsUp */: {
|
|
|
|
|
audioLabel = coreViewModel2.translations.chatPage.message.thumbsUpReaction;
|
|
|
|
|
count = chatMessageViewModel.reactionsThumbsUpCount;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "\u2705" /* Check */: {
|
|
|
|
|
audioLabel = coreViewModel2.translations.chatPage.message.checkReaction;
|
|
|
|
|
count = chatMessageViewModel.reactionsCheckCount;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "\u{1F6D1}" /* Stop */: {
|
|
|
|
|
audioLabel = coreViewModel2.translations.chatPage.message.stopReaction;
|
|
|
|
|
count = chatMessageViewModel.reactionsStopCount;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "\u2757\uFE0F" /* Attention */: {
|
|
|
|
|
audioLabel = coreViewModel2.translations.chatPage.message.attentionReaction;
|
|
|
|
|
count = chatMessageViewModel.reactionsAttentionCount;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "\u203C\uFE0F" /* DoubleAttention */: {
|
|
|
|
|
audioLabel = coreViewModel2.translations.chatPage.message.doubleAttentionReaction;
|
|
|
|
|
count = chatMessageViewModel.reactionsDoubleAttentionCount;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case "\u2753" /* Question */: {
|
|
|
|
|
audioLabel = coreViewModel2.translations.chatPage.message.questionReaction;
|
|
|
|
|
count = chatMessageViewModel.reactionsQuestionCount;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
return /* @__PURE__ */ createElement(
|
2026-04-12 19:20:37 +00:00
|
|
|
"button",
|
2026-04-01 08:53:50 +00:00
|
|
|
{
|
2026-04-12 19:20:37 +00:00
|
|
|
class: "flex",
|
|
|
|
|
"on:click": sendReaction,
|
|
|
|
|
"aria-label": audioLabel,
|
|
|
|
|
"toggle:selected": isActive,
|
|
|
|
|
"set:count": count
|
2026-04-01 08:53:50 +00:00
|
|
|
},
|
2026-04-12 19:20:37 +00:00
|
|
|
content,
|
|
|
|
|
/* @__PURE__ */ createElement("span", { "subscribe:innerText": count })
|
2026-04-01 08:53:50 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/View/Components/messageReactionButtonRow.tsx
|
|
|
|
|
function MessageReactionButtonRow(coreViewModel2, chatMessageViewModel) {
|
|
|
|
|
return /* @__PURE__ */ createElement("div", { class: "grid gap width-100 message-reaction-row" }, MessageReactionButton(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
chatMessageViewModel,
|
|
|
|
|
"\u{1F44D}" /* ThumbsUp */
|
|
|
|
|
), MessageReactionButton(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
chatMessageViewModel,
|
|
|
|
|
"\u2705" /* Check */
|
|
|
|
|
), MessageReactionButton(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
chatMessageViewModel,
|
|
|
|
|
"\u{1F6D1}" /* Stop */
|
|
|
|
|
), MessageReactionButton(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
chatMessageViewModel,
|
|
|
|
|
"\u2757\uFE0F" /* Attention */
|
|
|
|
|
), MessageReactionButton(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
chatMessageViewModel,
|
|
|
|
|
"\u203C\uFE0F" /* DoubleAttention */
|
|
|
|
|
), MessageReactionButton(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
chatMessageViewModel,
|
|
|
|
|
"\u2753" /* Question */
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Components/infoTile.tsx
|
|
|
|
|
function InfoTile(icon, label, content) {
|
|
|
|
|
let contentState;
|
|
|
|
|
if (typeof content == "string") {
|
|
|
|
|
contentState = new State(content);
|
|
|
|
|
} else {
|
|
|
|
|
contentState = content;
|
|
|
|
|
}
|
|
|
|
|
return /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, icon), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, label), /* @__PURE__ */ createElement("b", { class: "break-word", "subscribe:innerText": contentState })));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Components/messageReactionEntry.tsx
|
|
|
|
|
function MessageReactionEntry(reaction) {
|
|
|
|
|
return /* @__PURE__ */ createElement("div", { class: "tile flex-row" }, /* @__PURE__ */ createElement("span", { class: "flex width-100" }, reaction.sender), /* @__PURE__ */ createElement("span", null, reaction.content));
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/View/Modals/chatMessageInfoModal.tsx
|
|
|
|
|
function ChatMessageInfoModal(coreViewModel2, chatMessageViewModel) {
|
2026-04-01 08:53:50 +00:00
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "modal",
|
2026-04-12 19:20:37 +00:00
|
|
|
"toggle:open": chatMessageViewModel.isPresentingInfoModal
|
2026-04-01 08:53:50 +00:00
|
|
|
},
|
2026-04-12 19:20:37 +00:00
|
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.chatPage.message.messageInfoHeadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, InfoTile(
|
|
|
|
|
"account_circle",
|
|
|
|
|
coreViewModel2.translations.chatPage.message.sentBy,
|
|
|
|
|
chatMessageViewModel.sender
|
|
|
|
|
), InfoTile(
|
|
|
|
|
"schedule",
|
|
|
|
|
coreViewModel2.translations.chatPage.message.timeSent,
|
|
|
|
|
chatMessageViewModel.dateSent
|
|
|
|
|
), InfoTile(
|
|
|
|
|
"forum",
|
|
|
|
|
coreViewModel2.translations.chatPage.message.channel,
|
|
|
|
|
chatMessageViewModel.channel
|
|
|
|
|
), InfoTile(
|
|
|
|
|
"description",
|
|
|
|
|
coreViewModel2.translations.chatPage.message.messageContent,
|
|
|
|
|
chatMessageViewModel.body
|
|
|
|
|
)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.copyMessage }, coreViewModel2.translations.chatPage.message.copyMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "content_copy")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.resendMessage }, coreViewModel2.translations.chatPage.message.resendMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "redo")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.decryptMessage }, coreViewModel2.translations.chatPage.message.decryptMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, MessageReactionButtonRow(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
chatMessageViewModel
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "flex-column gap",
|
2026-04-12 19:20:37 +00:00
|
|
|
"children:append": [
|
|
|
|
|
chatMessageViewModel.allReactions,
|
|
|
|
|
MessageReactionEntry
|
|
|
|
|
]
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
))), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.hideInfoModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Components/chatMessage.tsx
|
|
|
|
|
function ChatMessage4(coreViewModel2, chatMessageViewModel) {
|
|
|
|
|
const statusIcon = createProxyState(
|
|
|
|
|
[chatMessageViewModel.status],
|
|
|
|
|
() => {
|
|
|
|
|
switch (chatMessageViewModel.status.value) {
|
|
|
|
|
case "outbox" /* Outbox */:
|
|
|
|
|
return "hourglass_top";
|
|
|
|
|
case "sent" /* Sent */:
|
|
|
|
|
return "check";
|
|
|
|
|
case "received" /* Received */:
|
|
|
|
|
return "done_all";
|
|
|
|
|
default:
|
|
|
|
|
return "warning";
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "message-bubble",
|
|
|
|
|
"toggle:sentbyuser": chatMessageViewModel.sentByUser,
|
|
|
|
|
"toggle:hidden": chatMessageViewModel.isHidden
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("div", { class: "main tile" }, /* @__PURE__ */ createElement("div", { class: "text-container" }, /* @__PURE__ */ createElement("span", { class: "sender-name ellipsis" }, chatMessageViewModel.sender), /* @__PURE__ */ createElement(
|
|
|
|
|
"span",
|
|
|
|
|
{
|
|
|
|
|
class: "body",
|
|
|
|
|
"subscribe:innerText": chatMessageViewModel.body
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00: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(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
"on:click": chatMessageViewModel.showInfoModal,
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.message.showMessageInfoButtonAudioLabel
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "info")
|
|
|
|
|
))),
|
|
|
|
|
MessageReactionButtonRow(coreViewModel2, chatMessageViewModel),
|
|
|
|
|
ChatMessageInfoModal(coreViewModel2, chatMessageViewModel)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Components/messageReactionFilterButton.tsx
|
|
|
|
|
function MessageReactionFilterButton(messagePageViewModel, content) {
|
|
|
|
|
const select = () => {
|
|
|
|
|
messagePageViewModel.setReactionFilter(content);
|
|
|
|
|
};
|
|
|
|
|
const isSelected = createProxyState(
|
|
|
|
|
[messagePageViewModel.reactionFilter],
|
|
|
|
|
() => messagePageViewModel.reactionFilter.value == content
|
|
|
|
|
);
|
|
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "flex justify-center",
|
|
|
|
|
"on:click": select,
|
|
|
|
|
"toggle:selected": isSelected
|
|
|
|
|
},
|
|
|
|
|
content
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Modals/messageFilterModal.tsx
|
|
|
|
|
function MessageFilterModal(coreViewModel2, messagePageViewModel, converter) {
|
|
|
|
|
const noFilter = createProxyState(
|
|
|
|
|
[messagePageViewModel.reactionFilter],
|
|
|
|
|
() => messagePageViewModel.reactionFilter.value == void 0
|
|
|
|
|
);
|
2026-04-13 10:26:03 +00:00
|
|
|
messagePageViewModel.isFilterModalOpen.subscribe(setFocusWithDelay);
|
2026-04-12 19:20:37 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": messagePageViewModel.isFilterModalOpen }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.chatPage.message.messageFilterHeadline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
2026-04-13 10:26:03 +00:00
|
|
|
id: "focused",
|
2026-04-12 19:20:37 +00:00
|
|
|
placeholder: coreViewModel2.translations.general.searchLabel,
|
|
|
|
|
"bind:value": messagePageViewModel.searchViewModel.searchInput,
|
|
|
|
|
"on:enter": messagePageViewModel.searchViewModel.applySearch
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary",
|
|
|
|
|
"aria-label": coreViewModel2.translations.general.searchButtonAudioLabel,
|
|
|
|
|
"on:click": messagePageViewModel.searchViewModel.applySearch,
|
|
|
|
|
"toggle:disabled": messagePageViewModel.searchViewModel.cannotApplySearch
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "search")
|
|
|
|
|
)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.chatPage.message.messageFilterReactionsHadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
"toggle:selected": noFilter,
|
|
|
|
|
"on:click": messagePageViewModel.revokeReactionFilter
|
|
|
|
|
},
|
|
|
|
|
coreViewModel2.translations.chatPage.message.messageFilterAllReactionsButton
|
|
|
|
|
), /* @__PURE__ */ createElement("div", { class: "grid gap message-reaction-filter" }, MessageReactionFilterButton(
|
|
|
|
|
messagePageViewModel,
|
|
|
|
|
"\u{1F44D}" /* ThumbsUp */
|
|
|
|
|
), MessageReactionFilterButton(
|
|
|
|
|
messagePageViewModel,
|
|
|
|
|
"\u2705" /* Check */
|
|
|
|
|
), MessageReactionFilterButton(
|
|
|
|
|
messagePageViewModel,
|
|
|
|
|
"\u{1F6D1}" /* Stop */
|
|
|
|
|
), MessageReactionFilterButton(
|
|
|
|
|
messagePageViewModel,
|
|
|
|
|
"\u2757\uFE0F" /* Attention */
|
|
|
|
|
), MessageReactionFilterButton(
|
|
|
|
|
messagePageViewModel,
|
|
|
|
|
"\u203C\uFE0F" /* DoubleAttention */
|
|
|
|
|
), MessageReactionFilterButton(
|
|
|
|
|
messagePageViewModel,
|
|
|
|
|
"\u2753" /* Question */
|
|
|
|
|
)))), /* @__PURE__ */ createElement("button", { "on:click": messagePageViewModel.hideFilterModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/ChatPages/messagePage.tsx
|
|
|
|
|
function MessagePage(coreViewModel2, messagePageViewModel) {
|
|
|
|
|
messagePageViewModel.loadData();
|
|
|
|
|
const ChatMessageViewModelToView = (chatMessageViewModel) => {
|
|
|
|
|
return ChatMessage4(coreViewModel2, chatMessageViewModel);
|
|
|
|
|
};
|
|
|
|
|
const messageContainer = /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
id: "message-container",
|
|
|
|
|
"children:append": [
|
|
|
|
|
messagePageViewModel.filteredMessageViewModels,
|
|
|
|
|
ChatMessageViewModelToView
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
function scrollDown(hard = false) {
|
|
|
|
|
if (hard) {
|
|
|
|
|
messageContainer.setAttribute("scroll-hard", "");
|
|
|
|
|
}
|
|
|
|
|
messageContainer.scrollTop = messageContainer.scrollHeight;
|
|
|
|
|
messageContainer.removeAttribute("scroll-hard");
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
function scrollDownIfApplicable() {
|
|
|
|
|
const scrollFromBottom = messageContainer.scrollHeight - (messageContainer.scrollTop + messageContainer.offsetHeight);
|
|
|
|
|
if (scrollFromBottom > 400) return;
|
|
|
|
|
scrollDown();
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
messagePageViewModel.filteredMessageViewModels.subscribeSilent(
|
|
|
|
|
scrollDownIfApplicable
|
|
|
|
|
);
|
|
|
|
|
setTimeout(() => scrollDown(true), 100);
|
2026-04-13 10:26:03 +00:00
|
|
|
setFocusWithDelay();
|
2026-04-12 19:20:37 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", { id: "message-page" }, /* @__PURE__ */ createElement("div", { class: "pane-wrapper" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", { class: "title" }, coreViewModel2.translations.chatPage.message.messagesHeadline), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "ghost",
|
|
|
|
|
"on:click": messagePageViewModel.showFilterModal,
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.message.filterMessagesButtonAudioLabel,
|
|
|
|
|
"toggle:selected": messagePageViewModel.isFilterActive
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "filter_alt")
|
|
|
|
|
))), /* @__PURE__ */ createElement("div", { class: "content" }, messageContainer, /* @__PURE__ */ createElement("div", { id: "composer" }, /* @__PURE__ */ createElement("div", { class: "content-width-constraint" }, /* @__PURE__ */ createElement("div", { class: "input-width-constraint" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
2026-04-13 10:26:03 +00:00
|
|
|
id: "focused",
|
2026-04-12 19:20:37 +00:00
|
|
|
"bind:value": messagePageViewModel.composingMessage,
|
|
|
|
|
"on:enter": messagePageViewModel.sendMessage,
|
|
|
|
|
placeholder: coreViewModel2.translations.chatPage.message.composerInputPlaceholder
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary",
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.message.sendMessageButtonAudioLabel,
|
|
|
|
|
"on:click": messagePageViewModel.sendMessage,
|
|
|
|
|
"toggle:disabled": messagePageViewModel.cannotSendMessage
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "send")
|
|
|
|
|
))))))), MessageFilterModal(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
messagePageViewModel,
|
|
|
|
|
ChatMessageViewModelToView
|
|
|
|
|
));
|
|
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/View/Components/colorPicker.tsx
|
|
|
|
|
function ColorPicker(selectedColor) {
|
|
|
|
|
return /* @__PURE__ */ createElement("div", { class: "flex-row gap width-input" }, ...Object.values(Colors).map((color) => {
|
|
|
|
|
const isSelected = createProxyState(
|
|
|
|
|
[selectedColor],
|
|
|
|
|
() => selectedColor.value == color
|
2026-04-09 16:48:22 +00:00
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
function setColor() {
|
|
|
|
|
selectedColor.value = color;
|
|
|
|
|
}
|
|
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
color,
|
|
|
|
|
class: "fill-color width-100 flex",
|
|
|
|
|
style: "height: 2rem",
|
|
|
|
|
"toggle:selected": isSelected,
|
|
|
|
|
"on:click": setColor
|
|
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Components/deletableListItem.tsx
|
|
|
|
|
function DeletableListItem(coreViewModel2, text, primaryButton, ondelete) {
|
|
|
|
|
return /* @__PURE__ */ createElement("div", { class: "tile flex-row justify-apart align-center padding-0" }, /* @__PURE__ */ createElement("span", { class: "padding-h ellipsis" }, text), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, primaryButton, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "danger",
|
|
|
|
|
"aria-label": coreViewModel2.translations.general.deleteItemButtonAudioLabel,
|
|
|
|
|
"on:click": ondelete
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "delete")
|
|
|
|
|
)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/ChatPages/settingsPage.tsx
|
|
|
|
|
function SettingsPage(coreViewModel2, settingsPageViewModel) {
|
|
|
|
|
settingsPageViewModel.loadData();
|
|
|
|
|
const secondaryChannelConverter = (secondaryChannel) => {
|
|
|
|
|
return DeletableListItem(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
secondaryChannel,
|
|
|
|
|
/* @__PURE__ */ createElement("span", null),
|
|
|
|
|
() => {
|
|
|
|
|
settingsPageViewModel.removeSecondaryChannel(secondaryChannel);
|
2026-04-09 16:48:22 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
return /* @__PURE__ */ createElement("div", { id: "settings-page" }, /* @__PURE__ */ createElement("div", { class: "pane-wrapper" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", { class: "title" }, coreViewModel2.translations.chatPage.settings.settingsHeadline)), /* @__PURE__ */ createElement("div", { class: "content" }, /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.settings.primaryChannelLabel), /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
|
|
|
|
"bind:value": settingsPageViewModel.primaryChannelInput,
|
|
|
|
|
"on:enter": settingsPageViewModel.setPrimaryChannel
|
|
|
|
|
}
|
|
|
|
|
))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end width-input" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "width-50",
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.settings.setPrimaryChannelButtonAudioLabel,
|
|
|
|
|
"on:click": settingsPageViewModel.setPrimaryChannel,
|
|
|
|
|
"toggle:disabled": settingsPageViewModel.cannotSetPrimaryChannel
|
|
|
|
|
},
|
|
|
|
|
coreViewModel2.translations.general.setButton,
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "check")
|
|
|
|
|
)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-row width-input margin-bottom" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.settings.newSecondaryChannelAudioLabel,
|
|
|
|
|
placeholder: coreViewModel2.translations.chatPage.settings.newSecondaryChannelPlaceholder,
|
|
|
|
|
"bind:value": settingsPageViewModel.newSecondaryChannelInput,
|
|
|
|
|
"on:enter": settingsPageViewModel.addSecondaryChannel
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary",
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.settings.addSecondaryChannelButtonAudioLabel,
|
|
|
|
|
"on:click": settingsPageViewModel.addSecondaryChannel,
|
|
|
|
|
"toggle:disabled": settingsPageViewModel.cannotAddSecondaryChannel
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "add")
|
|
|
|
|
)), /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "flex-column gap width-input",
|
|
|
|
|
"children:append": [
|
|
|
|
|
settingsPageViewModel.secondaryChannels,
|
|
|
|
|
secondaryChannelConverter
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.settings.encryptionKeyLabel), /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
|
|
|
|
"bind:value": settingsPageViewModel.encryptionKeyInput,
|
|
|
|
|
"on:enter": settingsPageViewModel.setEncryptionKey,
|
|
|
|
|
"set:type": settingsPageViewModel.encryptionKeyInputType
|
|
|
|
|
}
|
|
|
|
|
))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end width-input" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "width-50",
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.settings.setEncryptionKeyButtonAudioLabel,
|
|
|
|
|
"on:click": settingsPageViewModel.setEncryptionKey,
|
|
|
|
|
"toggle:disabled": settingsPageViewModel.cannotSetEncryptionKey
|
|
|
|
|
},
|
|
|
|
|
coreViewModel2.translations.general.setButton,
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "check")
|
|
|
|
|
)), /* @__PURE__ */ createElement("label", { class: "inline" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
|
|
|
|
type: "checkbox",
|
|
|
|
|
"bind:checked": settingsPageViewModel.shouldShowEncryptionKey
|
|
|
|
|
}
|
|
|
|
|
), coreViewModel2.translations.chatPage.settings.showEncryptionKey), /* @__PURE__ */ createElement("hr", null), ColorPicker(settingsPageViewModel.color), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
coreViewModel2.translations.chatPage.settings.deleteChatButton,
|
|
|
|
|
"chat_error",
|
|
|
|
|
settingsPageViewModel.remove
|
|
|
|
|
))))));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Components/propertyValueList.tsx
|
|
|
|
|
function PropertyValueList(propertyKey, stringEntryObjectConverter, objects, viewBuilder) {
|
|
|
|
|
const propertyValues = new ListState();
|
|
|
|
|
const sortedPropertyValues = createSortedPropertyValueState(propertyValues);
|
|
|
|
|
objects.subscribe(() => {
|
|
|
|
|
collectPropertyValuesToState(
|
|
|
|
|
propertyKey,
|
|
|
|
|
stringEntryObjectConverter,
|
|
|
|
|
objects,
|
|
|
|
|
propertyValues
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
return viewBuilder(propertyValues, sortedPropertyValues);
|
|
|
|
|
}
|
|
|
|
|
function collectPropertyValuesToState(propertyKey, stringEntryObjectConverter, objects, propertyValues) {
|
|
|
|
|
const values = collectObjectValuesForKey(
|
|
|
|
|
propertyKey,
|
|
|
|
|
stringEntryObjectConverter,
|
|
|
|
|
[...objects.value.values()]
|
|
|
|
|
);
|
|
|
|
|
for (const existingValue of values) {
|
|
|
|
|
if (propertyValues.value.has(existingValue)) continue;
|
|
|
|
|
propertyValues.add(existingValue);
|
|
|
|
|
}
|
|
|
|
|
for (const displayedValue of propertyValues.value.values()) {
|
|
|
|
|
if (values.includes(displayedValue)) continue;
|
|
|
|
|
propertyValues.remove(displayedValue);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function createSortedPropertyValueState(propertyValues) {
|
|
|
|
|
return createProxyState(
|
|
|
|
|
[propertyValues],
|
|
|
|
|
() => [...propertyValues.value.values()].sort(localeCompare)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
function createPropertyValueIndexState(sortedKeys, key) {
|
|
|
|
|
return createProxyState(
|
|
|
|
|
[sortedKeys],
|
|
|
|
|
() => sortedKeys.value.indexOf(key)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Components/filteredList.tsx
|
|
|
|
|
function FilteredList(reference, stringEntryObjectConverter, objects, viewBuilder) {
|
|
|
|
|
const matchingObjects = new ListState();
|
|
|
|
|
objects.handleAddition((newObject) => {
|
|
|
|
|
const doesMatch = checkDoesObjectMatchReference(
|
|
|
|
|
reference,
|
|
|
|
|
stringEntryObjectConverter(newObject),
|
|
|
|
|
true
|
|
|
|
|
);
|
|
|
|
|
if (doesMatch == false) return;
|
|
|
|
|
matchingObjects.add(newObject);
|
|
|
|
|
objects.handleRemoval(newObject, () => {
|
|
|
|
|
matchingObjects.remove(newObject);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
return viewBuilder(matchingObjects);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/ViewModel/Utility/taskPropertyBulkChangeViewModel.ts
|
|
|
|
|
var TaskPropertyBulkChangeViewModel = class {
|
|
|
|
|
// init
|
|
|
|
|
constructor(taskViewModels, valueSetter, initialValue) {
|
|
|
|
|
// state
|
|
|
|
|
this.inputValue = new State("");
|
2026-04-09 16:48:22 +00:00
|
|
|
// methods
|
2026-04-12 19:20:37 +00:00
|
|
|
this.set = () => {
|
|
|
|
|
if (this.cannotSet.value == true) return;
|
|
|
|
|
this.taskViewModels.value.forEach((taskViewModel) => {
|
|
|
|
|
this.setValue(this.inputValue.value, taskViewModel);
|
|
|
|
|
});
|
2026-04-09 16:48:22 +00:00
|
|
|
};
|
2026-04-12 19:20:37 +00:00
|
|
|
this.taskViewModels = taskViewModels;
|
|
|
|
|
this.inputValue.value = initialValue;
|
|
|
|
|
this.setValue = valueSetter;
|
|
|
|
|
this.cannotSet = createProxyState(
|
|
|
|
|
[this.inputValue],
|
|
|
|
|
() => this.inputValue.value == "" || this.inputValue.value == initialValue
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var TaskCategoryBulkChangeViewModel = class extends TaskPropertyBulkChangeViewModel {
|
|
|
|
|
constructor(taskViewModels, initialValue) {
|
|
|
|
|
super(
|
|
|
|
|
taskViewModels,
|
|
|
|
|
(newCategory, taskViewModel) => {
|
|
|
|
|
taskViewModel.category.value = newCategory;
|
|
|
|
|
taskViewModel.save();
|
|
|
|
|
},
|
|
|
|
|
initialValue
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var TaskStatusBulkChangeViewModel = class extends TaskPropertyBulkChangeViewModel {
|
|
|
|
|
constructor(taskViewModels, initialValue) {
|
|
|
|
|
super(
|
|
|
|
|
taskViewModels,
|
|
|
|
|
(newStatus, taskViewModel) => {
|
|
|
|
|
taskViewModel.status.value = newStatus;
|
|
|
|
|
taskViewModel.save();
|
|
|
|
|
},
|
|
|
|
|
initialValue
|
2026-04-09 16:48:22 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/View/ChatPages/boardKanbanPage.tsx
|
|
|
|
|
function BoardKanbanPage(coreViewModel2, boardViewModel) {
|
|
|
|
|
return PropertyValueList(
|
|
|
|
|
"category",
|
|
|
|
|
(taskViewModel) => taskViewModel.task,
|
|
|
|
|
boardViewModel.filteredTaskViewModels,
|
|
|
|
|
(categories, sortedCategories) => {
|
|
|
|
|
const categoryNameConverter = (categoryName) => {
|
|
|
|
|
const index = createPropertyValueIndexState(
|
|
|
|
|
sortedCategories,
|
|
|
|
|
categoryName
|
|
|
|
|
);
|
|
|
|
|
return Column(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
categoryName,
|
|
|
|
|
index,
|
|
|
|
|
boardViewModel
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "kanban-board-wrapper",
|
|
|
|
|
"children:append": [categories, categoryNameConverter]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
function Column(coreViewModel2, categoryName, index, boardViewModel) {
|
|
|
|
|
return FilteredList(
|
|
|
|
|
{ category: categoryName },
|
|
|
|
|
(taskViewModel) => taskViewModel.task,
|
|
|
|
|
boardViewModel.filteredTaskViewModels,
|
|
|
|
|
(taskViewModels) => {
|
|
|
|
|
const viewModel = new TaskCategoryBulkChangeViewModel(
|
|
|
|
|
taskViewModels,
|
|
|
|
|
categoryName
|
|
|
|
|
);
|
|
|
|
|
function drop() {
|
|
|
|
|
boardViewModel.handleDropWithinBoard(categoryName);
|
|
|
|
|
}
|
|
|
|
|
const view = /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "flex-column flex-no",
|
|
|
|
|
"on:dragover": allowDrop,
|
|
|
|
|
"on:drop": drop
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
|
|
|
|
placeholder: coreViewModel2.translations.chatPage.task.renameCategoryInputPlaceholder,
|
|
|
|
|
"bind:value": viewModel.inputValue,
|
|
|
|
|
"on:enter": viewModel.set
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary",
|
|
|
|
|
"on:click": viewModel.set,
|
|
|
|
|
"toggle:disabled": viewModel.cannotSet
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "check")
|
|
|
|
|
)),
|
|
|
|
|
/* @__PURE__ */ createElement("hr", null),
|
|
|
|
|
/* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "kanban-column",
|
|
|
|
|
"children:append": [taskViewModels, TaskViewModelToEntry]
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
index.subscribe((newIndex) => {
|
|
|
|
|
view.style.order = newIndex;
|
|
|
|
|
});
|
|
|
|
|
return view;
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Modals/boardSettingsModal.tsx
|
|
|
|
|
function BoardSettingsModal(coreViewModel2, boardViewModel) {
|
|
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "modal",
|
|
|
|
|
"toggle:open": boardViewModel.isPresentingSettingsModal
|
2026-04-09 16:48:22 +00:00
|
|
|
},
|
2026-04-12 19:20:37 +00:00
|
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.chatPage.task.boardSettingsHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.boardNameInputLabel), /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
|
|
|
|
"on:enter": boardViewModel.saveSettings,
|
|
|
|
|
"bind:value": boardViewModel.name
|
|
|
|
|
}
|
|
|
|
|
))), /* @__PURE__ */ createElement("hr", null), ColorPicker(boardViewModel.color), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
coreViewModel2.translations.chatPage.task.deleteBoardButton,
|
|
|
|
|
"delete_forever",
|
|
|
|
|
boardViewModel.deleteBoard
|
|
|
|
|
))), /* @__PURE__ */ createElement("button", { "on:click": boardViewModel.hideSettings }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/ChatPages/boardStatusGridPage.tsx
|
|
|
|
|
function BoardStatusGridPage(coreViewModel2, boardViewModel) {
|
|
|
|
|
const statuses = new ListState();
|
|
|
|
|
const sortedStatuses = createSortedPropertyValueState(statuses);
|
|
|
|
|
boardViewModel.filteredTaskViewModels.subscribe(() => {
|
|
|
|
|
collectPropertyValuesToState(
|
|
|
|
|
"status",
|
|
|
|
|
(taskViewModel) => taskViewModel.task,
|
|
|
|
|
boardViewModel.filteredTaskViewModels,
|
|
|
|
|
statuses
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
const statusNameCellConverter = (statusName) => {
|
|
|
|
|
const index = createPropertyValueIndexState(sortedStatuses, statusName);
|
|
|
|
|
return StatusNameCell(coreViewModel2, statusName, index, boardViewModel);
|
|
|
|
|
};
|
|
|
|
|
return /* @__PURE__ */ createElement("div", { class: "status-page-content" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "status-name-row",
|
|
|
|
|
"children:append": [statuses, statusNameCellConverter]
|
|
|
|
|
}
|
|
|
|
|
), PropertyValueList(
|
|
|
|
|
"category",
|
|
|
|
|
(taskViewModel) => taskViewModel.task,
|
|
|
|
|
boardViewModel.filteredTaskViewModels,
|
|
|
|
|
(categories, sortedCategories) => {
|
|
|
|
|
const categoryRowConverter = (categoryName) => {
|
|
|
|
|
const index = createPropertyValueIndexState(
|
|
|
|
|
sortedCategories,
|
|
|
|
|
categoryName
|
|
|
|
|
);
|
|
|
|
|
return CategoryRow(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
categoryName,
|
|
|
|
|
index,
|
|
|
|
|
statuses,
|
|
|
|
|
sortedStatuses,
|
|
|
|
|
boardViewModel
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "status-grid-wrapper",
|
|
|
|
|
"children:append": [categories, categoryRowConverter]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
function StatusNameCell(coreViewModel2, statusName, index, boardViewModel) {
|
|
|
|
|
const taskViewModelsWithMatchingStatus = new ListState();
|
|
|
|
|
boardViewModel.filteredTaskViewModels.handleAddition(
|
|
|
|
|
(taskViewModel) => {
|
|
|
|
|
const doesMatchStatus = taskViewModel.task.status == statusName;
|
|
|
|
|
if (doesMatchStatus == false) return;
|
|
|
|
|
taskViewModelsWithMatchingStatus.add(taskViewModel);
|
|
|
|
|
boardViewModel.filteredTaskViewModels.handleRemoval(
|
|
|
|
|
taskViewModel,
|
|
|
|
|
() => {
|
|
|
|
|
taskViewModelsWithMatchingStatus.remove(taskViewModel);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
const viewModel = new TaskStatusBulkChangeViewModel(
|
|
|
|
|
taskViewModelsWithMatchingStatus,
|
|
|
|
|
statusName
|
|
|
|
|
);
|
|
|
|
|
const view = /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement("div", { class: "property-input-wrapper" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
|
|
|
|
placeholder: coreViewModel2.translations.chatPage.task.renameStatusInputPlaceholder,
|
|
|
|
|
"bind:value": viewModel.inputValue,
|
|
|
|
|
"on:enter": viewModel.set
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary",
|
|
|
|
|
"on:click": viewModel.set,
|
|
|
|
|
"toggle:disabled": viewModel.cannotSet
|
2026-04-09 16:48:22 +00:00
|
|
|
},
|
2026-04-12 19:20:37 +00:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "check")
|
|
|
|
|
)));
|
|
|
|
|
index.subscribe((newIndex) => {
|
|
|
|
|
view.style.order = newIndex;
|
|
|
|
|
});
|
|
|
|
|
return view;
|
|
|
|
|
}
|
|
|
|
|
function CategoryRow(coreViewModel2, categoryName, index, allStatuses, sortedStatuses, boardViewModel) {
|
|
|
|
|
return FilteredList(
|
|
|
|
|
{ category: categoryName },
|
|
|
|
|
(taskViewModel) => taskViewModel.task,
|
|
|
|
|
boardViewModel.filteredTaskViewModels,
|
|
|
|
|
(taskViewModels) => {
|
|
|
|
|
const statusNameConverter = (statusName) => {
|
|
|
|
|
const index2 = createPropertyValueIndexState(
|
|
|
|
|
sortedStatuses,
|
|
|
|
|
statusName
|
|
|
|
|
);
|
|
|
|
|
return StatusColumn(
|
|
|
|
|
categoryName,
|
|
|
|
|
statusName,
|
|
|
|
|
index2,
|
|
|
|
|
boardViewModel,
|
|
|
|
|
taskViewModels
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
const viewModel = new TaskCategoryBulkChangeViewModel(
|
|
|
|
|
taskViewModels,
|
|
|
|
|
categoryName
|
|
|
|
|
);
|
|
|
|
|
const view = /* @__PURE__ */ createElement("div", { class: "flex-row flex-no large-gap" }, /* @__PURE__ */ createElement("div", { class: "property-input-wrapper" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
|
|
|
|
placeholder: coreViewModel2.translations.chatPage.task.renameCategoryInputPlaceholder,
|
|
|
|
|
"bind:value": viewModel.inputValue,
|
|
|
|
|
"on:enter": viewModel.set
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary",
|
|
|
|
|
"on:click": viewModel.set,
|
|
|
|
|
"toggle:disabled": viewModel.cannotSet
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "check")
|
|
|
|
|
)), /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "flex-row large-gap padding-right",
|
|
|
|
|
"children:append": [allStatuses, statusNameConverter]
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
index.subscribe((newIndex) => {
|
|
|
|
|
view.style.order = newIndex;
|
|
|
|
|
});
|
|
|
|
|
return view;
|
2026-04-09 16:48:22 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
function StatusColumn(categoryName, statusName, index, boardViewModel, taskViewModelsWithMatchingCategory) {
|
|
|
|
|
const taskViewModels = new ListState();
|
|
|
|
|
taskViewModelsWithMatchingCategory.handleAddition((taskViewModel) => {
|
|
|
|
|
const doesMatchStatus = taskViewModel.status.value == statusName;
|
|
|
|
|
if (doesMatchStatus == false) return;
|
|
|
|
|
taskViewModels.add(taskViewModel);
|
|
|
|
|
taskViewModelsWithMatchingCategory.handleRemoval(taskViewModel, () => {
|
|
|
|
|
taskViewModels.remove(taskViewModel);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
function drop() {
|
|
|
|
|
boardViewModel.handleDropWithinBoard(categoryName, statusName);
|
2026-04-09 16:48:22 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
const view = /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "status-column gap",
|
|
|
|
|
"on:dragover": allowDrop,
|
|
|
|
|
"on:drop": drop,
|
|
|
|
|
"children:append": [taskViewModels, TaskViewModelToEntry]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
index.subscribe((newIndex) => {
|
|
|
|
|
view.style.order = newIndex;
|
|
|
|
|
});
|
|
|
|
|
return view;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Components/boardViewToggleButton.tsx
|
|
|
|
|
function BoardViewToggleButton(label, icon, page, boardViewModel) {
|
|
|
|
|
function select() {
|
|
|
|
|
boardViewModel.selectedPage.value = page;
|
|
|
|
|
}
|
|
|
|
|
const isSelected = createProxyState(
|
|
|
|
|
[boardViewModel.selectedPage],
|
|
|
|
|
() => boardViewModel.selectedPage.value == page
|
|
|
|
|
);
|
|
|
|
|
return RibbonButton(label, icon, isSelected, select);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Modals/searchModal.tsx
|
|
|
|
|
function SearchModal(coreViewModel2, searchViewModel, headline, converter, isOpen) {
|
|
|
|
|
function close() {
|
|
|
|
|
isOpen.value = false;
|
|
|
|
|
}
|
|
|
|
|
const suggestionId = v4_default();
|
2026-04-13 10:26:03 +00:00
|
|
|
isOpen.subscribe(setFocusWithDelay);
|
2026-04-12 19:20:37 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isOpen, extended: true }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, headline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
2026-04-13 10:26:03 +00:00
|
|
|
id: "focused",
|
2026-04-12 19:20:37 +00:00
|
|
|
placeholder: coreViewModel2.translations.general.searchLabel,
|
|
|
|
|
"bind:value": searchViewModel.searchInput,
|
|
|
|
|
"on:enter": searchViewModel.applySearch,
|
|
|
|
|
list: suggestionId
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"datalist",
|
|
|
|
|
{
|
|
|
|
|
hidden: true,
|
|
|
|
|
id: suggestionId,
|
|
|
|
|
"children:append": [
|
|
|
|
|
searchViewModel.suggestions,
|
|
|
|
|
StringToOption
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary",
|
|
|
|
|
"aria-label": coreViewModel2.translations.general.searchButtonAudioLabel,
|
|
|
|
|
"on:click": searchViewModel.applySearch,
|
|
|
|
|
"toggle:disabled": searchViewModel.cannotApplySearch
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "search")
|
|
|
|
|
)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "grid gap",
|
|
|
|
|
style: "grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr))",
|
|
|
|
|
"children:append": [
|
|
|
|
|
searchViewModel.matchingObjects,
|
|
|
|
|
converter
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
)), /* @__PURE__ */ createElement("button", { "on:click": close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/ChatPages/boardPage.tsx
|
|
|
|
|
function BoardPage(coreViewModel2, boardViewModel) {
|
|
|
|
|
boardViewModel.loadData();
|
|
|
|
|
const mainContent = createProxyState(
|
|
|
|
|
[boardViewModel.selectedPage],
|
|
|
|
|
() => {
|
|
|
|
|
switch (boardViewModel.selectedPage.value) {
|
|
|
|
|
case "kanban" /* Kanban */: {
|
|
|
|
|
return BoardKanbanPage(coreViewModel2, boardViewModel);
|
|
|
|
|
}
|
|
|
|
|
case "status-grid" /* StatusGrid */: {
|
|
|
|
|
return BoardStatusGridPage(coreViewModel2, boardViewModel);
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "task-grid",
|
|
|
|
|
"children:append": [
|
|
|
|
|
boardViewModel.filteredTaskViewModels,
|
|
|
|
|
TaskViewModelToEntry
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
);
|
|
|
|
|
const taskSettingsModal = createProxyState(
|
|
|
|
|
[boardViewModel.selectedTaskViewModel],
|
|
|
|
|
() => {
|
|
|
|
|
if (boardViewModel.selectedTaskViewModel.value == void 0) {
|
|
|
|
|
return /* @__PURE__ */ createElement("div", null);
|
|
|
|
|
} else {
|
|
|
|
|
setFocusWithDelay();
|
|
|
|
|
return TaskSettingsModal(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
boardViewModel.selectedTaskViewModel.value
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "ghost board-close-button",
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.task.closeBoardButtonAudioLabel,
|
|
|
|
|
"on:click": boardViewModel.close
|
2026-04-09 16:48:22 +00:00
|
|
|
},
|
2026-04-12 19:20:37 +00:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_back")
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "ghost board-toggle-button inset-outline",
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.task.toggleBoardButtonAudioLabel,
|
|
|
|
|
"on:click": boardViewModel.taskPageViewModel.toggleBoardList,
|
|
|
|
|
"toggle:selected": boardViewModel.taskPageViewModel.isShowingBoadList
|
2026-04-09 16:48:22 +00:00
|
|
|
},
|
2026-04-12 19:20:37 +00:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "dock_to_right")
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "ghost",
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.task.boardSettingsHeadline,
|
|
|
|
|
"on:click": boardViewModel.showSettings
|
2026-04-09 16:48:22 +00:00
|
|
|
},
|
2026-04-12 19:20:37 +00:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "settings")
|
|
|
|
|
)), /* @__PURE__ */ createElement("span", { class: "scroll-h ribbon" }, BoardViewToggleButton(
|
|
|
|
|
coreViewModel2.translations.chatPage.task.listViewButtonAudioLabel,
|
|
|
|
|
"view_list",
|
|
|
|
|
"list" /* List */,
|
|
|
|
|
boardViewModel
|
|
|
|
|
), BoardViewToggleButton(
|
|
|
|
|
coreViewModel2.translations.chatPage.task.kanbanViewButtonAudioLabel,
|
|
|
|
|
"view_kanban",
|
|
|
|
|
"kanban" /* Kanban */,
|
|
|
|
|
boardViewModel
|
|
|
|
|
), BoardViewToggleButton(
|
|
|
|
|
coreViewModel2.translations.chatPage.task.statusViewButtonAudioLabel,
|
|
|
|
|
"grid_view",
|
|
|
|
|
"status-grid" /* StatusGrid */,
|
|
|
|
|
boardViewModel
|
|
|
|
|
)), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
2026-04-12 19:30:17 +00:00
|
|
|
class: "ghost inset-outline",
|
2026-04-12 19:20:37 +00:00
|
|
|
"aria-label": coreViewModel2.translations.chatPage.task.filterTasksButtonAudioLabel,
|
|
|
|
|
"on:click": boardViewModel.showFilterModal,
|
|
|
|
|
"toggle:selected": boardViewModel.isFilterActive
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "filter_alt")
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "ghost",
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.task.createTaskButtonAudioLabel,
|
|
|
|
|
"on:click": boardViewModel.createTask
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "add")
|
|
|
|
|
))), /* @__PURE__ */ createElement("div", { class: "content main-content", "children:set": mainContent }), BoardSettingsModal(coreViewModel2, boardViewModel), SearchModal(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
boardViewModel.searchViewModel,
|
|
|
|
|
coreViewModel2.translations.chatPage.task.filterTasksHeadline,
|
|
|
|
|
TaskViewModelToEntry,
|
|
|
|
|
boardViewModel.isPresentingFilterModal
|
|
|
|
|
), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Components/boardEntry.tsx
|
|
|
|
|
function BoardEntry(boardViewModel) {
|
|
|
|
|
const view = /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
"set:color": boardViewModel.color,
|
|
|
|
|
class: "tile colored-tile",
|
|
|
|
|
"toggle:selected": boardViewModel.isSelected,
|
|
|
|
|
"on:click": boardViewModel.select,
|
|
|
|
|
"on:dragover": allowDrop,
|
|
|
|
|
"on:drop": boardViewModel.handleDropBetweenBoards
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement(
|
|
|
|
|
"span",
|
|
|
|
|
{
|
|
|
|
|
class: "shadow",
|
|
|
|
|
"subscribe:innerText": boardViewModel.name
|
|
|
|
|
}
|
|
|
|
|
),
|
|
|
|
|
/* @__PURE__ */ createElement("b", { "subscribe:innerText": boardViewModel.name })
|
|
|
|
|
);
|
|
|
|
|
boardViewModel.index.subscribe((newIndex) => {
|
|
|
|
|
view.style.order = newIndex;
|
|
|
|
|
});
|
|
|
|
|
return view;
|
|
|
|
|
}
|
|
|
|
|
var BoardViewModelToEntry = (boardViewModel) => {
|
|
|
|
|
return BoardEntry(boardViewModel);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// src/View/ChatPages/taskPage.tsx
|
|
|
|
|
function TaskPage(coreViewModel2, taskPageViewModel) {
|
|
|
|
|
taskPageViewModel.loadData();
|
|
|
|
|
const isShowingBoard = createProxyState(
|
|
|
|
|
[taskPageViewModel.selectedBoardId],
|
|
|
|
|
() => taskPageViewModel.selectedBoardId.value != void 0
|
|
|
|
|
);
|
|
|
|
|
const paneContent = createProxyState(
|
|
|
|
|
[taskPageViewModel.selectedBoardId],
|
|
|
|
|
() => {
|
|
|
|
|
const selectedBoardId = taskPageViewModel.selectedBoardId.value;
|
|
|
|
|
if (selectedBoardId == void 0) {
|
|
|
|
|
return PlaceholderView(
|
|
|
|
|
coreViewModel2.translations.chatPage.task.noBoardSelected
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
const selectedBoard = taskPageViewModel.boardViewModels.value.get(selectedBoardId);
|
|
|
|
|
if (selectedBoard == void 0) {
|
|
|
|
|
return /* @__PURE__ */ createElement("div", { class: "pane align-center justify-center" }, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.chatPage.task.boardNotFound));
|
|
|
|
|
}
|
|
|
|
|
return BoardPage(coreViewModel2, selectedBoard);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
id: "task-page",
|
|
|
|
|
"toggle:isshowingboard": isShowingBoard,
|
|
|
|
|
"set:showingboardlist": taskPageViewModel.isShowingBoadList
|
2026-04-09 16:48:22 +00:00
|
|
|
},
|
2026-04-12 19:20:37 +00:00
|
|
|
/* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
id: "board-list",
|
|
|
|
|
class: "pane-wrapper side background",
|
|
|
|
|
"set:color": taskPageViewModel.chatViewModel.displayedColor
|
2026-04-09 18:09:26 +00:00
|
|
|
},
|
2026-04-12 19:20:37 +00:00
|
|
|
/* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"input",
|
|
|
|
|
{
|
|
|
|
|
class: "no-outline",
|
|
|
|
|
"bind:value": taskPageViewModel.newBoardNameInput,
|
|
|
|
|
"on:enter": taskPageViewModel.createBoard,
|
|
|
|
|
placeholder: coreViewModel2.translations.chatPage.task.newBoardNamePlaceholder
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary",
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.task.createBoardButtonAudioLabel,
|
|
|
|
|
"on:click": taskPageViewModel.createBoard,
|
|
|
|
|
"toggle:disabled": taskPageViewModel.cannotCreateBoard
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "add")
|
|
|
|
|
))), /* @__PURE__ */ createElement("div", { class: "content" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "grid gap",
|
|
|
|
|
style: "grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr))",
|
|
|
|
|
"children:append": [
|
|
|
|
|
taskPageViewModel.boardViewModels,
|
|
|
|
|
BoardViewModelToEntry
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
)))
|
|
|
|
|
),
|
|
|
|
|
/* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
id: "board-content",
|
|
|
|
|
class: "pane-wrapper",
|
|
|
|
|
"children:set": paneContent
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/chatPage.tsx
|
|
|
|
|
function ChatPage(coreViewModel2, chatViewModel) {
|
|
|
|
|
const mainContent = createProxyState(
|
|
|
|
|
[chatViewModel.selectedPage],
|
|
|
|
|
() => {
|
|
|
|
|
chatViewModel.closeSubPages();
|
|
|
|
|
switch (chatViewModel.selectedPage.value) {
|
|
|
|
|
case "settings" /* Settings */: {
|
|
|
|
|
return SettingsPage(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
chatViewModel.settingsPageViewModel
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
case "tasks" /* Tasks */: {
|
|
|
|
|
return TaskPage(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
chatViewModel.taskPageViewModel
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
case "calendar" /* Calendar */: {
|
|
|
|
|
return CalendarPage(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
chatViewModel.calendarViewModel
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
return MessagePage(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
chatViewModel.messagePageViewModel
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
const marqueeContent = createProxyState(
|
|
|
|
|
[chatViewModel.notificationViewModel.marquee],
|
|
|
|
|
() => {
|
|
|
|
|
const value = chatViewModel.notificationViewModel.marquee.value;
|
|
|
|
|
if (value == void 0) return /* @__PURE__ */ createElement("span", null);
|
|
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"span",
|
|
|
|
|
{
|
|
|
|
|
"on:click": chatViewModel.notificationViewModel.openNotification
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("b", null, value.sender),
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "secondary" }, value.chat, ": "),
|
|
|
|
|
/* @__PURE__ */ createElement("span", null, value.body)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"article",
|
|
|
|
|
{
|
|
|
|
|
id: "chat-page",
|
|
|
|
|
"set:color": chatViewModel.displayedColor,
|
|
|
|
|
class: "subtle-background"
|
2026-04-09 16:48:22 +00:00
|
|
|
},
|
2026-04-12 19:20:37 +00:00
|
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { id: "ribbon" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "ghost",
|
|
|
|
|
"aria-label": coreViewModel2.translations.chatPage.closeChatAudioLabe,
|
|
|
|
|
"on:click": chatViewModel.close
|
2026-04-09 16:48:22 +00:00
|
|
|
},
|
2026-04-12 19:20:37 +00:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "close")
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "danger",
|
|
|
|
|
"aria-label": coreViewModel2.translations.general.restoreConnection,
|
|
|
|
|
"on:click": chatViewModel.connectionViewModel.showConnectionModal,
|
|
|
|
|
"toggle:hidden": chatViewModel.connectionViewModel.isConnected
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "signal_disconnected")
|
|
|
|
|
), /* @__PURE__ */ createElement("span", { class: "marquee", "children:set": marqueeContent }), /* @__PURE__ */ createElement("span", { class: "navigation-buttons" }, ChatViewToggleButton(
|
|
|
|
|
coreViewModel2.translations.chatPage.pages.calendar,
|
|
|
|
|
"calendar_month",
|
|
|
|
|
"calendar" /* Calendar */,
|
|
|
|
|
chatViewModel
|
|
|
|
|
), ChatViewToggleButton(
|
|
|
|
|
coreViewModel2.translations.chatPage.pages.tasks,
|
|
|
|
|
"task_alt",
|
|
|
|
|
"tasks" /* Tasks */,
|
|
|
|
|
chatViewModel
|
|
|
|
|
), ChatViewToggleButton(
|
|
|
|
|
coreViewModel2.translations.chatPage.pages.messages,
|
|
|
|
|
"forum",
|
|
|
|
|
"messages" /* Messages */,
|
|
|
|
|
chatViewModel
|
|
|
|
|
), ChatViewToggleButton(
|
|
|
|
|
coreViewModel2.translations.chatPage.pages.settings,
|
|
|
|
|
"settings",
|
|
|
|
|
"settings" /* Settings */,
|
|
|
|
|
chatViewModel
|
|
|
|
|
))), /* @__PURE__ */ createElement("div", { id: "main", "children:set": mainContent }))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/chatPageWrapper.tsx
|
|
|
|
|
function ChatPageWrapper(coreViewModel2, chatListViewModel2) {
|
|
|
|
|
const chatPageContent = createProxyState(
|
|
|
|
|
[chatListViewModel2.selectedChat],
|
|
|
|
|
() => {
|
|
|
|
|
if (chatListViewModel2.selectedChat.value == void 0) {
|
|
|
|
|
return /* @__PURE__ */ createElement("div", null);
|
|
|
|
|
} else {
|
|
|
|
|
return ChatPage(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
chatListViewModel2.selectedChat.value
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return /* @__PURE__ */ createElement("div", { id: "chat-page-wrapper", "children:set": chatPageContent });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Modals/connectionModal.tsx
|
|
|
|
|
function ConnectionModal(coreViewModel2, connectionViewModel2) {
|
|
|
|
|
const previousAddressConverter = (address) => {
|
|
|
|
|
function connnect() {
|
|
|
|
|
connectionViewModel2.connectToAddress(address);
|
|
|
|
|
}
|
|
|
|
|
const cannotConnect = createProxyState(
|
|
|
|
|
[connectionViewModel2.isConnected],
|
|
|
|
|
() => connectionViewModel2.isConnected.value == true && connectionViewModel2.coreViewModel.connectionModel.address == address
|
|
|
|
|
);
|
|
|
|
|
return DeletableListItem(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
address,
|
|
|
|
|
/* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary",
|
|
|
|
|
"on:click": connnect,
|
|
|
|
|
"toggle:disabled": cannotConnect,
|
|
|
|
|
"aria-label": coreViewModel2.translations.connectionModal.connectButtonAudioLabel
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "link")
|
|
|
|
|
),
|
|
|
|
|
() => {
|
|
|
|
|
connectionViewModel2.removePreviousAddress(address);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "modal",
|
|
|
|
|
"toggle:open": connectionViewModel2.isShowingConnectionModal
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.connectionModal.connectionModalHeadline), /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "flex-column gap",
|
|
|
|
|
"children:append": [
|
|
|
|
|
connectionViewModel2.previousAddresses,
|
|
|
|
|
previousAddressConverter
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
)), /* @__PURE__ */ createElement("button", { "on:click": connectionViewModel2.hideConnectionModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// node_modules/udn-frontend/index.ts
|
|
|
|
|
var UDNFrontend = class {
|
|
|
|
|
ws;
|
|
|
|
|
// HANDLERS
|
|
|
|
|
connectionHandler = () => {
|
|
|
|
|
};
|
|
|
|
|
disconnectionHandler = () => {
|
|
|
|
|
};
|
|
|
|
|
messageHandler = (data) => {
|
|
|
|
|
};
|
|
|
|
|
mailboxHandler = (mailboxId) => {
|
|
|
|
|
};
|
|
|
|
|
mailboxConnectionHandler = (mailboxId) => {
|
|
|
|
|
};
|
|
|
|
|
mailboxDeleteHandler = (mailboxId) => {
|
|
|
|
|
};
|
|
|
|
|
// INIT
|
|
|
|
|
set onconnect(handler) {
|
|
|
|
|
this.connectionHandler = handler;
|
|
|
|
|
}
|
|
|
|
|
set ondisconnect(handler) {
|
|
|
|
|
this.disconnectionHandler = handler;
|
|
|
|
|
}
|
|
|
|
|
set onmessage(handler) {
|
|
|
|
|
this.messageHandler = handler;
|
|
|
|
|
}
|
|
|
|
|
set onmailboxcreate(handler) {
|
|
|
|
|
this.mailboxHandler = handler;
|
|
|
|
|
}
|
|
|
|
|
set onmailboxconnect(handler) {
|
|
|
|
|
this.mailboxConnectionHandler = handler;
|
|
|
|
|
}
|
|
|
|
|
set onmailboxdelete(handler) {
|
|
|
|
|
this.mailboxDeleteHandler = handler;
|
|
|
|
|
}
|
|
|
|
|
// UTILITY METHODS
|
|
|
|
|
send(messageObject) {
|
|
|
|
|
if (this.ws == void 0) return false;
|
|
|
|
|
if (this.ws.readyState != 1) return false;
|
|
|
|
|
const messageString = JSON.stringify(messageObject);
|
|
|
|
|
this.ws.send(messageString);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
// PUBLIC METHODS
|
|
|
|
|
// connection
|
|
|
|
|
connect(address) {
|
|
|
|
|
try {
|
|
|
|
|
this.disconnect();
|
|
|
|
|
this.ws = new WebSocket(address);
|
|
|
|
|
this.ws.addEventListener("open", this.connectionHandler);
|
|
|
|
|
this.ws.addEventListener("close", this.disconnectionHandler);
|
|
|
|
|
this.ws.addEventListener("message", (message) => {
|
|
|
|
|
const dataString = message.data.toString();
|
|
|
|
|
const data = JSON.parse(dataString);
|
|
|
|
|
if (data.assignedMailboxId) {
|
|
|
|
|
return this.mailboxHandler(data.assignedMailboxId);
|
|
|
|
|
} else if (data.connectedMailboxId) {
|
|
|
|
|
return this.mailboxConnectionHandler(data.connectedMailboxId);
|
|
|
|
|
} else if (data.deletedMailbox) {
|
|
|
|
|
return this.mailboxDeleteHandler(data.deletedMailbox);
|
|
|
|
|
} else {
|
|
|
|
|
this.send({ uuid: data.uuid, confirmingMessageReceived: true });
|
|
|
|
|
this.messageHandler(data);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
disconnect() {
|
|
|
|
|
this.ws?.close();
|
|
|
|
|
}
|
|
|
|
|
// message
|
|
|
|
|
sendMessage(channel, body) {
|
|
|
|
|
const messageObject = {
|
|
|
|
|
messageChannel: channel,
|
|
|
|
|
messageBody: body
|
|
|
|
|
};
|
|
|
|
|
return this.send(messageObject);
|
|
|
|
|
}
|
|
|
|
|
// subscription
|
|
|
|
|
subscribe(channel) {
|
|
|
|
|
const messageObject = {
|
|
|
|
|
subscribeChannel: channel
|
|
|
|
|
};
|
|
|
|
|
return this.send(messageObject);
|
|
|
|
|
}
|
|
|
|
|
unsubscribe(channel) {
|
|
|
|
|
const messageObject = {
|
|
|
|
|
unsubscribeChannel: channel
|
|
|
|
|
};
|
|
|
|
|
return this.send(messageObject);
|
|
|
|
|
}
|
|
|
|
|
// mailbox
|
|
|
|
|
requestMailbox() {
|
|
|
|
|
const messageObject = {
|
|
|
|
|
requestingMailboxSetup: true
|
|
|
|
|
};
|
|
|
|
|
return this.send(messageObject);
|
|
|
|
|
}
|
|
|
|
|
connectMailbox(mailboxId) {
|
|
|
|
|
const messageObject = {
|
|
|
|
|
requestedMailbox: mailboxId
|
|
|
|
|
};
|
|
|
|
|
return this.send(messageObject);
|
|
|
|
|
}
|
|
|
|
|
deleteMailbox(mailboxId) {
|
|
|
|
|
const messageObject = {
|
|
|
|
|
deletingMailbox: mailboxId
|
|
|
|
|
};
|
|
|
|
|
return this.send(messageObject);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// src/Model/Global/connectionModel.ts
|
|
|
|
|
var ConnectionModel = class {
|
|
|
|
|
// init
|
|
|
|
|
constructor(storageModel2) {
|
|
|
|
|
this.reconnectInterval = void 0;
|
|
|
|
|
this.shouldAttemptReconnect = false;
|
|
|
|
|
this.connectionChangeHandlerManager = new HandlerManager();
|
|
|
|
|
this.messageHandlerManager = new HandlerManager();
|
|
|
|
|
this.messageSentHandlerManager = new HandlerManager();
|
|
|
|
|
this.channelsToSubscribe = /* @__PURE__ */ new Set();
|
|
|
|
|
// handlers
|
|
|
|
|
this.handleMessage = (data) => {
|
|
|
|
|
this.messageHandlerManager.trigger(data);
|
|
|
|
|
};
|
|
|
|
|
this.handleConnectionChange = () => {
|
|
|
|
|
console.log("connection status:", this.isConnected, this.address);
|
|
|
|
|
this.connectionChangeHandlerManager.trigger();
|
|
|
|
|
if (this.address == void 0) return;
|
|
|
|
|
this.storeAddress(this.address);
|
|
|
|
|
this.sendSubscriptionRequest();
|
|
|
|
|
this.sendMessagesInOutbox();
|
|
|
|
|
};
|
|
|
|
|
// connection
|
|
|
|
|
this.connect = (address) => {
|
|
|
|
|
console.log("connecting...", address);
|
|
|
|
|
this.shouldAttemptReconnect = true;
|
|
|
|
|
this.udn.connect(address);
|
|
|
|
|
};
|
|
|
|
|
this.disconnect = () => {
|
|
|
|
|
this.shouldAttemptReconnect = false;
|
|
|
|
|
this.udn.disconnect();
|
|
|
|
|
const reconnectAddressPath = StorageModel.getPath(
|
|
|
|
|
"connection" /* ConnectionModel */,
|
|
|
|
|
filePaths.connectionModel.reconnectAddress
|
|
|
|
|
);
|
|
|
|
|
this.storageModel.remove(reconnectAddressPath);
|
|
|
|
|
};
|
|
|
|
|
this.reconnect = () => {
|
|
|
|
|
const reconnectAddressPath = this.getReconnectAddressPath();
|
|
|
|
|
const reconnectAddress = this.storageModel.read(reconnectAddressPath);
|
|
|
|
|
if (reconnectAddress == null) return;
|
|
|
|
|
console.log("reconnecting...");
|
|
|
|
|
this.connect(reconnectAddress);
|
|
|
|
|
};
|
|
|
|
|
// mailbox
|
|
|
|
|
this.getMailboxPath = (address) => {
|
|
|
|
|
const mailboxDirPath = StorageModel.getPath(
|
|
|
|
|
"connection" /* ConnectionModel */,
|
|
|
|
|
filePaths.connectionModel.mailboxes
|
|
|
|
|
);
|
|
|
|
|
const mailboxFilePath = [...mailboxDirPath, address];
|
|
|
|
|
return mailboxFilePath;
|
|
|
|
|
};
|
|
|
|
|
this.requestNewMailbox = () => {
|
|
|
|
|
console.log("requesting new mailbox");
|
|
|
|
|
this.udn.requestMailbox();
|
|
|
|
|
};
|
|
|
|
|
this.connectMailbox = () => {
|
|
|
|
|
if (this.address == void 0) return;
|
|
|
|
|
const mailboxId = this.storageModel.read(
|
|
|
|
|
this.getMailboxPath(this.address)
|
|
|
|
|
);
|
|
|
|
|
console.log("connecting mailbox", mailboxId);
|
|
|
|
|
if (mailboxId == null) return this.requestNewMailbox();
|
|
|
|
|
this.udn.connectMailbox(mailboxId);
|
|
|
|
|
};
|
|
|
|
|
this.storeMailbox = (mailboxId) => {
|
|
|
|
|
if (this.address == void 0) return;
|
|
|
|
|
this.storageModel.write(this.getMailboxPath(this.address), mailboxId);
|
|
|
|
|
};
|
|
|
|
|
// subscription
|
|
|
|
|
this.addChannel = (channel) => {
|
|
|
|
|
this.channelsToSubscribe.add(channel);
|
|
|
|
|
this.sendSubscriptionRequest();
|
|
|
|
|
};
|
|
|
|
|
this.sendSubscriptionRequest = () => {
|
|
|
|
|
if (this.isConnected == false) return;
|
|
|
|
|
for (const channel of this.channelsToSubscribe) {
|
|
|
|
|
this.udn.subscribe(channel);
|
2026-04-09 16:48:22 +00:00
|
|
|
}
|
2026-04-12 19:20:37 +00:00
|
|
|
this.connectMailbox();
|
|
|
|
|
};
|
|
|
|
|
// outbox
|
|
|
|
|
this.getOutboxPath = () => {
|
|
|
|
|
return StorageModel.getPath(
|
|
|
|
|
"connection" /* ConnectionModel */,
|
|
|
|
|
filePaths.connectionModel.outbox
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.getOutboxMessags = () => {
|
|
|
|
|
const outboxPath = this.getOutboxPath();
|
|
|
|
|
const messageIds = this.storageModel.list(outboxPath);
|
|
|
|
|
let chatMessages = [];
|
|
|
|
|
for (const messageId of messageIds) {
|
|
|
|
|
const chatMessage = this.storageModel.readStringifiable(
|
|
|
|
|
[...outboxPath, messageId],
|
|
|
|
|
ChatMessageReference
|
|
|
|
|
);
|
|
|
|
|
if (chatMessage == null) continue;
|
|
|
|
|
chatMessages.push(chatMessage);
|
|
|
|
|
}
|
|
|
|
|
return chatMessages;
|
|
|
|
|
};
|
|
|
|
|
this.addToOutbox = (chatMessage) => {
|
|
|
|
|
const messagePath = [...this.getOutboxPath(), chatMessage.id];
|
|
|
|
|
this.storageModel.writeStringifiable(messagePath, chatMessage);
|
|
|
|
|
};
|
|
|
|
|
this.removeFromOutbox = (chatMessage) => {
|
|
|
|
|
const messagePath = [...this.getOutboxPath(), chatMessage.id];
|
|
|
|
|
this.storageModel.remove(messagePath);
|
|
|
|
|
};
|
|
|
|
|
this.sendMessagesInOutbox = () => {
|
|
|
|
|
const messages = this.getOutboxMessags();
|
|
|
|
|
for (const message of messages) {
|
|
|
|
|
const isSent = this.tryToSendMessage(message);
|
|
|
|
|
if (isSent == false) return;
|
|
|
|
|
this.removeFromOutbox(message);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// messaging
|
|
|
|
|
this.sendMessageOrStore = (chatMessage) => {
|
|
|
|
|
const isSent = this.tryToSendMessage(chatMessage);
|
|
|
|
|
if (isSent == true) return;
|
|
|
|
|
this.addToOutbox(chatMessage);
|
|
|
|
|
};
|
|
|
|
|
this.tryToSendMessage = (chatMessage) => {
|
|
|
|
|
const stringifiedBody = stringify(chatMessage);
|
|
|
|
|
const isSent = this.sendPlainMessage(
|
|
|
|
|
chatMessage.channel,
|
|
|
|
|
stringifiedBody
|
|
|
|
|
);
|
|
|
|
|
if (isSent) this.messageSentHandlerManager.trigger(chatMessage);
|
|
|
|
|
return isSent;
|
|
|
|
|
};
|
|
|
|
|
this.sendPlainMessage = (channel, body) => {
|
|
|
|
|
return this.udn.sendMessage(channel, body);
|
|
|
|
|
};
|
|
|
|
|
// storage
|
|
|
|
|
this.getPreviousAddressPath = () => {
|
|
|
|
|
return StorageModel.getPath(
|
|
|
|
|
"connection" /* ConnectionModel */,
|
|
|
|
|
filePaths.connectionModel.previousAddresses
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.getAddressPath = (address) => {
|
|
|
|
|
const dirPath = this.getPreviousAddressPath();
|
|
|
|
|
return [...dirPath, address];
|
|
|
|
|
};
|
|
|
|
|
this.getReconnectAddressPath = () => {
|
|
|
|
|
return StorageModel.getPath(
|
|
|
|
|
"connection" /* ConnectionModel */,
|
|
|
|
|
filePaths.connectionModel.reconnectAddress
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.storeAddress = (address) => {
|
|
|
|
|
const addressPath = this.getAddressPath(address);
|
|
|
|
|
this.storageModel.write(addressPath, "");
|
|
|
|
|
const reconnectAddressPath = this.getReconnectAddressPath();
|
|
|
|
|
this.storageModel.write(reconnectAddressPath, address);
|
|
|
|
|
};
|
|
|
|
|
this.removeAddress = (address) => {
|
|
|
|
|
const addressPath = this.getAddressPath(address);
|
|
|
|
|
this.storageModel.remove(addressPath);
|
|
|
|
|
};
|
|
|
|
|
this.udn = new UDNFrontend();
|
|
|
|
|
this.storageModel = storageModel2;
|
|
|
|
|
this.udn.onmessage = (data) => {
|
|
|
|
|
this.handleMessage(data);
|
|
|
|
|
};
|
|
|
|
|
this.udn.onconnect = () => {
|
|
|
|
|
this.handleConnectionChange();
|
|
|
|
|
};
|
|
|
|
|
this.udn.ondisconnect = () => {
|
|
|
|
|
this.handleConnectionChange();
|
|
|
|
|
};
|
|
|
|
|
this.udn.onmailboxcreate = (mailboxId) => {
|
|
|
|
|
console.log("created mailbox", mailboxId);
|
|
|
|
|
this.storeMailbox(mailboxId);
|
|
|
|
|
this.connectMailbox();
|
|
|
|
|
};
|
|
|
|
|
this.udn.onmailboxdelete = (mailboxId) => {
|
|
|
|
|
console.log(`mailbox ${mailboxId} deleted`);
|
|
|
|
|
this.requestNewMailbox();
|
|
|
|
|
};
|
|
|
|
|
this.udn.onmailboxconnect = (mailboxId) => {
|
|
|
|
|
console.log(`using mailbox ${mailboxId}`);
|
|
|
|
|
};
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
if (this.isConnected == true) return;
|
|
|
|
|
if (this.shouldAttemptReconnect == false) return;
|
|
|
|
|
this.reconnect();
|
|
|
|
|
}, 5e3);
|
|
|
|
|
this.reconnect();
|
|
|
|
|
}
|
|
|
|
|
// data
|
|
|
|
|
get isConnected() {
|
|
|
|
|
return this.udn.ws != void 0 && this.udn.ws.readyState == 1;
|
|
|
|
|
}
|
|
|
|
|
get address() {
|
|
|
|
|
return this.udn.ws?.url;
|
|
|
|
|
}
|
|
|
|
|
get addresses() {
|
|
|
|
|
const dirPath = this.getPreviousAddressPath();
|
|
|
|
|
return this.storageModel.list(dirPath);
|
2026-03-31 19:52:17 +00:00
|
|
|
}
|
2026-04-09 17:14:33 +00:00
|
|
|
};
|
2024-07-19 23:45:07 +02:00
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/ViewModel/Global/connectionViewModel.ts
|
|
|
|
|
var ConnectionViewModel = class {
|
2026-04-09 16:48:22 +00:00
|
|
|
// init
|
2026-04-12 19:20:37 +00:00
|
|
|
constructor(coreViewModel2) {
|
|
|
|
|
this.coreViewModel = coreViewModel2;
|
|
|
|
|
// state
|
|
|
|
|
this.serverAddressInput = new State("");
|
|
|
|
|
this.isConnected = new State(false);
|
|
|
|
|
this.isShowingConnectionModal = new State(false);
|
|
|
|
|
this.previousAddresses = new ListState();
|
|
|
|
|
// guards
|
|
|
|
|
this.cannotConnect = createProxyState(
|
|
|
|
|
[this.serverAddressInput, this.isConnected],
|
|
|
|
|
() => this.isConnected.value == true && this.serverAddressInput.value == this.coreViewModel.connectionModel.address || this.serverAddressInput.value == ""
|
|
|
|
|
);
|
|
|
|
|
this.cannotDisonnect = createProxyState(
|
|
|
|
|
[this.isConnected],
|
|
|
|
|
() => this.isConnected.value == false
|
|
|
|
|
);
|
|
|
|
|
this.hasNoPreviousConnections = createProxyState(
|
|
|
|
|
[this.previousAddresses],
|
|
|
|
|
() => this.previousAddresses.value.size == 0
|
|
|
|
|
);
|
|
|
|
|
// handlers
|
|
|
|
|
this.connectionChangeHandler = () => {
|
|
|
|
|
this.isConnected.value = this.coreViewModel.connectionModel.isConnected;
|
|
|
|
|
if (this.coreViewModel.connectionModel.isConnected == false) return;
|
|
|
|
|
if (this.coreViewModel.connectionModel.address == void 0) return;
|
|
|
|
|
this.serverAddressInput.value = this.coreViewModel.connectionModel.address;
|
|
|
|
|
if (!this.previousAddresses.value.has(
|
|
|
|
|
this.coreViewModel.connectionModel.address
|
|
|
|
|
)) {
|
|
|
|
|
this.previousAddresses.add(
|
|
|
|
|
this.coreViewModel.connectionModel.address
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// methods
|
|
|
|
|
this.connect = () => {
|
|
|
|
|
this.connectToAddress(this.serverAddressInput.value);
|
|
|
|
|
};
|
|
|
|
|
this.connectToAddress = (address) => {
|
|
|
|
|
console.log(address);
|
|
|
|
|
this.coreViewModel.connectionModel.connect(address);
|
|
|
|
|
};
|
|
|
|
|
this.disconnect = () => {
|
|
|
|
|
this.coreViewModel.connectionModel.disconnect();
|
|
|
|
|
};
|
|
|
|
|
this.removePreviousAddress = (address) => {
|
|
|
|
|
this.coreViewModel.connectionModel.removeAddress(address);
|
|
|
|
|
this.updatePreviousAddresses();
|
|
|
|
|
};
|
|
|
|
|
// view
|
|
|
|
|
this.showConnectionModal = () => {
|
|
|
|
|
this.isShowingConnectionModal.value = true;
|
|
|
|
|
};
|
|
|
|
|
this.hideConnectionModal = () => {
|
|
|
|
|
this.isShowingConnectionModal.value = false;
|
|
|
|
|
};
|
|
|
|
|
this.updatePreviousAddresses = () => {
|
|
|
|
|
this.previousAddresses.clear();
|
|
|
|
|
this.previousAddresses.add(
|
|
|
|
|
...this.coreViewModel.connectionModel.addresses
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.updatePreviousAddresses();
|
|
|
|
|
coreViewModel2.connectionModel.connectionChangeHandlerManager.addHandler(
|
|
|
|
|
this.connectionChangeHandler
|
|
|
|
|
);
|
2024-07-28 00:02:17 +02:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
2024-07-28 00:02:17 +02:00
|
|
|
|
2026-04-01 08:53:50 +00:00
|
|
|
// src/ViewModel/Global/fileTransferViewModel.ts
|
2026-04-12 19:20:37 +00:00
|
|
|
var FileTransferViewModel = class extends Context {
|
2026-04-01 08:53:50 +00:00
|
|
|
// init
|
2026-04-09 16:48:22 +00:00
|
|
|
constructor(coreViewModel2) {
|
2026-04-12 19:20:37 +00:00
|
|
|
super();
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel = coreViewModel2;
|
2026-04-12 19:20:37 +00:00
|
|
|
this.contextDebugDescription = "transfer";
|
2026-04-01 08:53:50 +00:00
|
|
|
// state
|
|
|
|
|
this.presentedModal = new State(void 0);
|
|
|
|
|
this.generalFileOptions = new ListState();
|
|
|
|
|
this.chatFileOptions = new ListState();
|
|
|
|
|
this.selectedPaths = new ListState();
|
|
|
|
|
this.transferChannel = new State("");
|
|
|
|
|
this.transferKey = new State("");
|
|
|
|
|
this.receivingTransferChannel = new State("");
|
|
|
|
|
this.receivingTransferKey = new State("");
|
|
|
|
|
this.filePathsSent = new ListState();
|
|
|
|
|
this.filesSentCount = createProxyState(
|
|
|
|
|
[this.filePathsSent],
|
|
|
|
|
() => this.filePathsSent.value.size
|
|
|
|
|
);
|
|
|
|
|
this.filesSentText = createProxyState(
|
|
|
|
|
[this.filesSentCount],
|
2026-04-09 16:48:22 +00:00
|
|
|
() => this.coreViewModel.translations.dataTransferModal.filesSentCount(
|
2026-04-01 08:53:50 +00:00
|
|
|
this.filesSentCount.value
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
this.filePathsReceived = new ListState();
|
|
|
|
|
this.filesReceivedCount = createProxyState(
|
|
|
|
|
[this.filePathsReceived],
|
|
|
|
|
() => this.filePathsReceived.value.size
|
|
|
|
|
);
|
|
|
|
|
this.filesReceivedText = createProxyState(
|
|
|
|
|
[this.filesReceivedCount],
|
2026-04-09 16:48:22 +00:00
|
|
|
() => this.coreViewModel.translations.dataTransferModal.filesReceivedCount(
|
2026-04-01 08:53:50 +00:00
|
|
|
this.filesReceivedCount.value
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
// guards
|
|
|
|
|
this.hasNoPathsSelected = createProxyState(
|
|
|
|
|
[this.selectedPaths],
|
|
|
|
|
() => this.selectedPaths.value.size == 0
|
|
|
|
|
);
|
|
|
|
|
this.didNotFinishSending = new State(true);
|
|
|
|
|
this.cannotPrepareToReceive = createProxyState(
|
|
|
|
|
[this.receivingTransferChannel, this.receivingTransferKey],
|
|
|
|
|
() => this.receivingTransferChannel.value == "" || this.receivingTransferKey.value == ""
|
|
|
|
|
);
|
|
|
|
|
// handlers
|
|
|
|
|
this.handleReceivedFile = (path) => {
|
|
|
|
|
this.filePathsReceived.add(path);
|
|
|
|
|
};
|
|
|
|
|
// methods
|
|
|
|
|
this.getOptions = () => {
|
|
|
|
|
this.generalFileOptions.clear();
|
|
|
|
|
this.chatFileOptions.clear();
|
|
|
|
|
this.selectedPaths.clear();
|
|
|
|
|
this.generalFileOptions.add(
|
|
|
|
|
{
|
2026-04-09 16:48:22 +00:00
|
|
|
label: this.coreViewModel.translations.dataTransferModal.connectionData,
|
2026-04-01 08:53:50 +00:00
|
|
|
path: StorageModel.getPath(
|
|
|
|
|
"connection" /* ConnectionModel */,
|
|
|
|
|
filePaths.connectionModel.previousAddresses
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
{
|
2026-04-09 16:48:22 +00:00
|
|
|
label: this.coreViewModel.translations.dataTransferModal.settingsData,
|
2026-04-01 08:53:50 +00:00
|
|
|
path: StorageModel.getPath(
|
|
|
|
|
"settings" /* SettingsModel */,
|
|
|
|
|
[]
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
);
|
2026-04-09 16:48:22 +00:00
|
|
|
const chatModels = this.coreViewModel.chatListModel.chatModels;
|
2026-04-01 08:53:50 +00:00
|
|
|
for (const chatModel of chatModels) {
|
|
|
|
|
this.chatFileOptions.add({
|
|
|
|
|
label: chatModel.info.primaryChannel,
|
|
|
|
|
path: chatModel.getBasePath()
|
|
|
|
|
});
|
2026-04-01 08:46:10 +00:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
this.getTransferData = () => {
|
2026-04-09 16:48:22 +00:00
|
|
|
const transferData = this.coreViewModel.fileTransferModel.generateTransferData();
|
2026-04-01 08:53:50 +00:00
|
|
|
this.transferChannel.value = transferData.channel;
|
|
|
|
|
this.transferKey.value = transferData.key;
|
|
|
|
|
};
|
|
|
|
|
// view
|
|
|
|
|
this.showDirectionSelectionModal = () => {
|
2026-04-12 19:20:37 +00:00
|
|
|
this.coreViewModel.context = this;
|
2026-04-01 08:53:50 +00:00
|
|
|
this.presentedModal.value = 0 /* DirectionSelection */;
|
|
|
|
|
this.getOptions();
|
|
|
|
|
};
|
|
|
|
|
this.showFileSelectionModal = () => {
|
|
|
|
|
this.presentedModal.value = 1 /* FileSelection */;
|
|
|
|
|
};
|
|
|
|
|
this.showTransferDataModal = () => {
|
|
|
|
|
this.presentedModal.value = 2 /* TransferDataDisplay */;
|
|
|
|
|
this.getTransferData();
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.fileTransferModel.prepareToSend();
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
this.initiateTransfer = () => {
|
|
|
|
|
this.presentedModal.value = 3 /* TransferDisplay */;
|
|
|
|
|
this.didNotFinishSending.value = true;
|
|
|
|
|
this.filePathsSent.clear();
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.fileTransferModel.sendFiles(
|
2026-04-01 08:53:50 +00:00
|
|
|
this.selectedPaths.value.values(),
|
|
|
|
|
(path) => {
|
|
|
|
|
console.log(path);
|
|
|
|
|
this.filePathsSent.add(path);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
this.didNotFinishSending.value = false;
|
|
|
|
|
};
|
|
|
|
|
this.showTransferDataInputModal = () => {
|
|
|
|
|
this.presentedModal.value = 4 /* TransferDataInput */;
|
|
|
|
|
};
|
|
|
|
|
this.prepareReceivingData = () => {
|
2026-04-13 10:58:21 +00:00
|
|
|
if (this.cannotPrepareToReceive.value == true) return;
|
2026-04-01 08:53:50 +00:00
|
|
|
this.presentedModal.value = 5 /* ReceptionDisplay */;
|
|
|
|
|
this.filePathsReceived.clear();
|
|
|
|
|
const transferData = {
|
|
|
|
|
channel: this.receivingTransferChannel.value,
|
|
|
|
|
key: this.receivingTransferKey.value
|
|
|
|
|
};
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.fileTransferModel.prepareToReceive(transferData);
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
this.hideModal = () => {
|
2026-04-12 19:20:37 +00:00
|
|
|
this.coreViewModel.closeContext(this.contextId);
|
2026-04-01 08:53:50 +00:00
|
|
|
this.presentedModal.value = void 0;
|
|
|
|
|
};
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.fileTransferModel.fileHandlerManager.addHandler(
|
2026-04-01 08:53:50 +00:00
|
|
|
this.handleReceivedFile
|
|
|
|
|
);
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.fileTransferModel.readyToSendHandlerManager.addHandler(
|
2026-04-06 15:50:13 +00:00
|
|
|
() => this.initiateTransfer()
|
|
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
this.registerKeyStroke("backspace" /* CloseOrCancel */, this.hideModal);
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
};
|
2024-07-28 02:27:09 +02:00
|
|
|
|
2026-04-01 08:53:50 +00:00
|
|
|
// src/View/Components/textSpan.tsx
|
|
|
|
|
var StringToTextSpan = (string) => {
|
|
|
|
|
return /* @__PURE__ */ createElement("span", { class: "ellipsis" }, string);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// src/View/Modals/dataTransferModal.tsx
|
2026-04-09 16:48:22 +00:00
|
|
|
function DataTransferModalWrapper(coreViewModel2, connectionViewModel2, fileTransferViewModel2) {
|
2026-04-01 08:53:50 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", null, DirectionSelectionModal(
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2,
|
2026-04-01 08:53:50 +00:00
|
|
|
connectionViewModel2,
|
|
|
|
|
fileTransferViewModel2
|
2026-04-09 16:48:22 +00:00
|
|
|
), FileSelectionModal(coreViewModel2, fileTransferViewModel2), TransferDataDisplayModal(coreViewModel2, fileTransferViewModel2), TransferDisplayModal(coreViewModel2, fileTransferViewModel2), TransferDataInputModal(coreViewModel2, fileTransferViewModel2), DataReceptionModal(coreViewModel2, fileTransferViewModel2));
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
function DirectionSelectionModal(coreViewModel2, connectionViewModel2, fileTransferViewModel2) {
|
2026-04-01 08:53:50 +00:00
|
|
|
const isPresented = createProxyState(
|
|
|
|
|
[fileTransferViewModel2.presentedModal],
|
|
|
|
|
() => fileTransferViewModel2.presentedModal.value == 0 /* DirectionSelection */
|
|
|
|
|
);
|
|
|
|
|
const isDisconnected = createProxyState(
|
|
|
|
|
[connectionViewModel2.isConnected],
|
|
|
|
|
() => connectionViewModel2.isConnected.value == false
|
|
|
|
|
);
|
2026-04-09 16:48:22 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"p",
|
|
|
|
|
{
|
|
|
|
|
class: "error",
|
|
|
|
|
"toggle:hidden": connectionViewModel2.isConnected
|
|
|
|
|
},
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.dataTransferModal.notConnectedError
|
2026-04-01 08:53:50 +00:00
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "flex-column gap content-margin-bottom",
|
|
|
|
|
"toggle:hidden": isDisconnected
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "tile",
|
|
|
|
|
"on:click": fileTransferViewModel2.showFileSelectionModal
|
2026-04-01 08:46:10 +00:00
|
|
|
},
|
2026-04-01 08:53:50 +00:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "upload"),
|
2026-04-09 16:48:22 +00:00
|
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.dataTransferModal.fromThisDeviceButton)),
|
2026-04-01 08:53:50 +00:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
|
|
|
),
|
|
|
|
|
/* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "tile",
|
|
|
|
|
"on:click": fileTransferViewModel2.showTransferDataInputModal
|
2024-07-28 19:37:04 +02:00
|
|
|
},
|
2026-04-01 08:53:50 +00:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "download"),
|
2026-04-09 16:48:22 +00:00
|
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.dataTransferModal.toThisDeviceButton)),
|
2026-04-01 08:53:50 +00:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
|
|
|
)
|
2026-04-09 16:48:22 +00:00
|
|
|
)), /* @__PURE__ */ createElement("button", { "on:click": fileTransferViewModel2.hideModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))));
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
function FileSelectionModal(coreViewModel2, fileTransferViewModel2) {
|
2026-04-01 08:53:50 +00:00
|
|
|
const OptionConverter = (fileOption) => {
|
|
|
|
|
return OptionEntry(fileOption, fileTransferViewModel2);
|
2024-07-28 00:02:17 +02:00
|
|
|
};
|
2026-04-01 08:53:50 +00:00
|
|
|
function OptionEntry(fileOption, fileTransferViewModel3) {
|
|
|
|
|
const isSelected = new State(false);
|
|
|
|
|
if (fileTransferViewModel3.selectedPaths.value.has(fileOption.path)) {
|
|
|
|
|
isSelected.value = true;
|
|
|
|
|
}
|
|
|
|
|
isSelected.subscribeSilent((isSelected2) => {
|
|
|
|
|
if (isSelected2 == true) {
|
|
|
|
|
fileTransferViewModel3.selectedPaths.add(fileOption.path);
|
|
|
|
|
} else {
|
|
|
|
|
fileTransferViewModel3.selectedPaths.remove(fileOption.path);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
function toggle() {
|
|
|
|
|
isSelected.value = !isSelected.value;
|
|
|
|
|
}
|
|
|
|
|
return /* @__PURE__ */ createElement("button", { class: "tile", "toggle:selected": isSelected, "on:click": toggle }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", { class: "ellipsis" }, fileOption.label), /* @__PURE__ */ createElement("span", { class: "secondary ellipsis" }, StorageModel.pathComponentsToString(
|
|
|
|
|
...fileOption.path
|
|
|
|
|
))));
|
2024-07-27 19:40:16 +02:00
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
const isPresented = createProxyState(
|
|
|
|
|
[fileTransferViewModel2.presentedModal],
|
|
|
|
|
() => fileTransferViewModel2.presentedModal.value == 1 /* FileSelection */
|
2024-07-28 00:02:17 +02:00
|
|
|
);
|
2026-04-09 16:48:22 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.selectionDescription), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.dataTransferModal.generalHeadline), /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "flex-column gap content-margin-bottom",
|
|
|
|
|
"children:append": [
|
|
|
|
|
fileTransferViewModel2.generalFileOptions,
|
|
|
|
|
OptionConverter
|
|
|
|
|
]
|
|
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
), /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.dataTransferModal.chatsHeadline), /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "flex-column gap",
|
|
|
|
|
"children:append": [
|
|
|
|
|
fileTransferViewModel2.chatFileOptions,
|
|
|
|
|
OptionConverter
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
)), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "flex",
|
|
|
|
|
"on:click": fileTransferViewModel2.showDirectionSelectionModal
|
|
|
|
|
},
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.general.backButton
|
2026-04-01 08:53:50 +00:00
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary flex",
|
|
|
|
|
"on:click": fileTransferViewModel2.showTransferDataModal,
|
|
|
|
|
"toggle:disabled": fileTransferViewModel2.hasNoPathsSelected
|
|
|
|
|
},
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.general.continueButton,
|
2026-04-01 08:53:50 +00:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
|
|
|
))));
|
|
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
function TransferDataDisplayModal(coreViewModel2, fileTransferViewModel2) {
|
2026-04-01 08:53:50 +00:00
|
|
|
const isPresented = createProxyState(
|
|
|
|
|
[fileTransferViewModel2.presentedModal],
|
|
|
|
|
() => fileTransferViewModel2.presentedModal.value == 2 /* TransferDataDisplay */
|
2024-07-28 00:25:51 +02:00
|
|
|
);
|
2026-04-09 16:48:22 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.dataEntryDescription), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.transferChannelHeadline), /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"b",
|
|
|
|
|
{
|
|
|
|
|
"subscribe:innerText": fileTransferViewModel2.transferChannel
|
|
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.transferKeyHeadline), /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"b",
|
|
|
|
|
{
|
|
|
|
|
"subscribe:innerText": fileTransferViewModel2.transferKey
|
|
|
|
|
}
|
|
|
|
|
))))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "flex",
|
|
|
|
|
"on:click": fileTransferViewModel2.showFileSelectionModal
|
|
|
|
|
},
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.general.backButton
|
|
|
|
|
), /* @__PURE__ */ createElement("button", { disabled: true, class: "flex" }, coreViewModel2.translations.general.waitingLabel))));
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
function TransferDisplayModal(coreViewModel2, fileTransferViewModel2) {
|
2026-04-01 08:53:50 +00:00
|
|
|
const isPresented = createProxyState(
|
|
|
|
|
[fileTransferViewModel2.presentedModal],
|
|
|
|
|
() => fileTransferViewModel2.presentedModal.value == 3 /* TransferDisplay */
|
2024-07-28 02:27:09 +02:00
|
|
|
);
|
2026-04-09 16:48:22 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"p",
|
|
|
|
|
{
|
|
|
|
|
class: "secondary",
|
|
|
|
|
"subscribe:innerText": fileTransferViewModel2.filesSentText
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"p",
|
|
|
|
|
{
|
|
|
|
|
class: "secondary",
|
|
|
|
|
"toggle:hidden": fileTransferViewModel2.didNotFinishSending
|
|
|
|
|
},
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.dataTransferModal.allFilesSent
|
2026-04-01 08:53:50 +00:00
|
|
|
), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "tile flex-column align-start",
|
|
|
|
|
"children:append": [
|
|
|
|
|
fileTransferViewModel2.filePathsSent,
|
|
|
|
|
StringToTextSpan
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
)), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "flex",
|
|
|
|
|
"on:click": fileTransferViewModel2.initiateTransfer
|
|
|
|
|
},
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.dataTransferModal.sendAgainButton,
|
2026-04-01 08:53:50 +00:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "restart_alt")
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "flex",
|
|
|
|
|
"on:click": fileTransferViewModel2.hideModal,
|
|
|
|
|
"toggle:disabled": fileTransferViewModel2.didNotFinishSending
|
|
|
|
|
},
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.general.closeButton,
|
2026-04-01 08:53:50 +00:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "close")
|
|
|
|
|
))));
|
|
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
function TransferDataInputModal(coreViewModel2, fileTransferViewModel2) {
|
2026-04-01 08:53:50 +00:00
|
|
|
const isPresented = createProxyState(
|
|
|
|
|
[fileTransferViewModel2.presentedModal],
|
|
|
|
|
() => fileTransferViewModel2.presentedModal.value == 4 /* TransferDataInput */
|
2024-07-28 03:46:08 +02:00
|
|
|
);
|
2026-04-09 16:48:22 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.dataEntryInputDescription), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.transferChannelHeadline), /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"input",
|
|
|
|
|
{
|
2026-04-13 10:26:03 +00:00
|
|
|
"on:enter": fileTransferViewModel2.prepareReceivingData,
|
2026-04-01 08:53:50 +00:00
|
|
|
"bind:value": fileTransferViewModel2.receivingTransferChannel
|
|
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
))), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.transferKeyHeadline), /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"input",
|
|
|
|
|
{
|
2026-04-13 10:26:03 +00:00
|
|
|
"on:enter": fileTransferViewModel2.prepareReceivingData,
|
2026-04-01 08:53:50 +00:00
|
|
|
"bind:value": fileTransferViewModel2.receivingTransferKey
|
|
|
|
|
}
|
|
|
|
|
))))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "flex",
|
|
|
|
|
"on:click": fileTransferViewModel2.showDirectionSelectionModal
|
|
|
|
|
},
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.general.backButton
|
2026-04-01 08:53:50 +00:00
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary flex",
|
|
|
|
|
"on:click": fileTransferViewModel2.prepareReceivingData,
|
|
|
|
|
"toggle:disabled": fileTransferViewModel2.cannotPrepareToReceive
|
|
|
|
|
},
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.general.continueButton,
|
2026-04-01 08:53:50 +00:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
|
|
|
))));
|
|
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
function DataReceptionModal(coreViewModel2, fileTransferViewModel2) {
|
2026-04-01 08:53:50 +00:00
|
|
|
const isPresented = createProxyState(
|
|
|
|
|
[fileTransferViewModel2.presentedModal],
|
|
|
|
|
() => fileTransferViewModel2.presentedModal.value == 5 /* ReceptionDisplay */
|
2024-07-28 03:46:08 +02:00
|
|
|
);
|
2026-04-09 16:48:22 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"p",
|
|
|
|
|
{
|
|
|
|
|
class: "secondary",
|
|
|
|
|
"subscribe:innerText": fileTransferViewModel2.filesReceivedText
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "tile flex-column align-start",
|
|
|
|
|
"children:append": [
|
|
|
|
|
fileTransferViewModel2.filePathsReceived,
|
|
|
|
|
StringToTextSpan
|
|
|
|
|
]
|
|
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
)), /* @__PURE__ */ createElement("button", { "on:click": reload }, coreViewModel2.translations.general.reloadAppButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "refresh"))));
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/Model/Global/fileTransferModel.ts
|
2026-04-06 15:50:13 +00:00
|
|
|
var FileTransferModel = class _FileTransferModel {
|
2026-04-01 08:53:50 +00:00
|
|
|
// init
|
|
|
|
|
constructor(storageModel2, connectionModel2) {
|
|
|
|
|
this.fileHandlerManager = new HandlerManager();
|
2026-04-06 15:50:13 +00:00
|
|
|
this.readyToSendHandlerManager = new HandlerManager();
|
|
|
|
|
this.direction = 0 /* Send */;
|
2026-04-01 08:53:50 +00:00
|
|
|
// general
|
|
|
|
|
this.generateTransferData = () => {
|
|
|
|
|
const transferData = {
|
2026-04-01 13:40:07 +00:00
|
|
|
channel: generateRandomToken(4),
|
|
|
|
|
key: generateRandomToken(6)
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
this.transferData = transferData;
|
|
|
|
|
return transferData;
|
|
|
|
|
};
|
2026-04-06 15:50:13 +00:00
|
|
|
this.prepareToSend = () => {
|
|
|
|
|
this.direction = 0 /* Send */;
|
|
|
|
|
this.connectionModel.addChannel(this.transferData.channel);
|
|
|
|
|
};
|
2026-04-01 08:53:50 +00:00
|
|
|
this.prepareToReceive = (transferData) => {
|
2026-04-06 15:50:13 +00:00
|
|
|
this.direction = 1 /* Receive */;
|
2026-04-01 08:53:50 +00:00
|
|
|
this.connectionModel.addChannel(transferData.channel);
|
|
|
|
|
this.transferData = transferData;
|
2026-04-06 15:50:13 +00:00
|
|
|
this.connectionModel.sendPlainMessage(
|
|
|
|
|
transferData.channel,
|
|
|
|
|
_FileTransferModel.READY_MESSAGE
|
|
|
|
|
);
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
// handlers
|
|
|
|
|
this.handleMessage = (data) => {
|
2026-04-06 19:19:57 +00:00
|
|
|
if (this.transferData == void 0) return;
|
2026-04-06 15:50:13 +00:00
|
|
|
if (data.messageChannel != this.transferData.channel) return;
|
|
|
|
|
if (data.messageBody == _FileTransferModel.READY_MESSAGE && this.direction == 0 /* Send */) {
|
|
|
|
|
this.readyToSendHandlerManager.trigger(true);
|
|
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
if (this.transferData == void 0) return;
|
|
|
|
|
if (data.messageBody == void 0) return;
|
|
|
|
|
this.handleFile(data.messageBody);
|
|
|
|
|
};
|
|
|
|
|
this.handleFile = async (encryptedFileData) => {
|
|
|
|
|
if (this.transferData == void 0) return;
|
|
|
|
|
const decrypted = await decryptString(
|
|
|
|
|
encryptedFileData,
|
|
|
|
|
this.transferData.key
|
|
|
|
|
);
|
|
|
|
|
const parsed = parse(decrypted);
|
|
|
|
|
const isFileData = checkMatchesObjectStructure(
|
|
|
|
|
parsed,
|
|
|
|
|
FileDataReference
|
|
|
|
|
);
|
|
|
|
|
if (isFileData == false) return;
|
|
|
|
|
const fileData = parsed;
|
|
|
|
|
this.storageModel.write(fileData.path, fileData.body);
|
|
|
|
|
const pathString = StorageModel.pathComponentsToString(
|
|
|
|
|
...fileData.path
|
2026-03-31 19:52:17 +00:00
|
|
|
);
|
2026-04-01 08:53:50 +00:00
|
|
|
this.fileHandlerManager.trigger(pathString);
|
|
|
|
|
};
|
|
|
|
|
// sending
|
|
|
|
|
this.sendFiles = (directoryPaths, callback) => {
|
|
|
|
|
for (const directoryPath of directoryPaths) {
|
|
|
|
|
this.storageModel.recurse(directoryPath, (filePath) => {
|
|
|
|
|
this.sendFile(filePath);
|
|
|
|
|
const pathString = StorageModel.pathComponentsToString(
|
|
|
|
|
...filePath
|
|
|
|
|
);
|
|
|
|
|
callback(pathString);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.sendFile = async (filePath) => {
|
|
|
|
|
if (this.transferData == void 0) return;
|
|
|
|
|
const fileContent = this.storageModel.read(filePath);
|
|
|
|
|
if (fileContent == null) return;
|
|
|
|
|
const fileData = {
|
|
|
|
|
path: filePath,
|
|
|
|
|
body: fileContent
|
|
|
|
|
};
|
|
|
|
|
const stringifiedFileData = stringify(fileData);
|
|
|
|
|
const encryptedFileData = await encryptString(
|
|
|
|
|
stringifiedFileData,
|
|
|
|
|
this.transferData.key
|
|
|
|
|
);
|
|
|
|
|
this.connectionModel.sendPlainMessage(
|
|
|
|
|
this.transferData.channel,
|
|
|
|
|
encryptedFileData
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
this.storageModel = storageModel2;
|
|
|
|
|
this.connectionModel = connectionModel2;
|
|
|
|
|
this.connectionModel.messageHandlerManager.addHandler(
|
|
|
|
|
this.handleMessage
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-04-06 15:50:13 +00:00
|
|
|
static {
|
|
|
|
|
this.READY_MESSAGE = "ready";
|
|
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
var FileDataReference = {
|
|
|
|
|
path: [""],
|
|
|
|
|
body: ""
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// src/View/Components/chatEntry.tsx
|
|
|
|
|
function ChatEntry(chatViewModel) {
|
|
|
|
|
const view = /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
2026-04-12 12:39:29 +00:00
|
|
|
class: "tile colored-tile chat-entry animate-highlight",
|
2026-04-01 08:53:50 +00:00
|
|
|
"set:color": chatViewModel.settingsPageViewModel.color,
|
|
|
|
|
style: "height: 8rem",
|
|
|
|
|
"on:click": chatViewModel.open,
|
2026-04-12 12:39:29 +00:00
|
|
|
"toggle:highlight": chatViewModel.hasUnreadMessages
|
2026-04-01 08:53:50 +00:00
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement(
|
|
|
|
|
"span",
|
|
|
|
|
{
|
|
|
|
|
class: "shadow",
|
|
|
|
|
"subscribe:innerText": chatViewModel.settingsPageViewModel.primaryChannel
|
|
|
|
|
}
|
|
|
|
|
),
|
|
|
|
|
/* @__PURE__ */ createElement(
|
|
|
|
|
"h2",
|
|
|
|
|
{
|
|
|
|
|
"subscribe:innerText": chatViewModel.settingsPageViewModel.primaryChannel
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
chatViewModel.index.subscribe((newIndex) => {
|
|
|
|
|
view.style.order = newIndex;
|
2024-07-21 20:21:32 +02:00
|
|
|
});
|
2026-04-01 08:53:50 +00:00
|
|
|
return view;
|
|
|
|
|
}
|
|
|
|
|
var ChatViewModelToChatEntry = (chatViewModel) => {
|
|
|
|
|
return ChatEntry(chatViewModel);
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-08 20:31:25 +00:00
|
|
|
// src/View/Components/homePageButton.tsx
|
2026-04-08 20:35:06 +00:00
|
|
|
function HomePageButton(action, label, icon) {
|
|
|
|
|
return /* @__PURE__ */ createElement("button", { class: "tile flex-no", "on:click": action }, /* @__PURE__ */ createElement("span", { class: "icon" }, icon), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, label)));
|
2026-04-08 20:31:25 +00:00
|
|
|
}
|
|
|
|
|
|
2026-04-01 08:53:50 +00:00
|
|
|
// src/View/homePage.tsx
|
2026-04-09 16:48:22 +00:00
|
|
|
function HomePage(coreViewModel2, storageViewModel2, settingsViewModel2, connectionViewModel2, fileTransferViewModel2, chatListViewModel2) {
|
|
|
|
|
const overviewSection = /* @__PURE__ */ createElement("div", { id: "overview-section" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.overviewHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "cell_tower"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.homePage.serverAddress), /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"input",
|
|
|
|
|
{
|
|
|
|
|
list: "previous-connection-list",
|
2026-04-09 16:48:22 +00:00
|
|
|
placeholder: coreViewModel2.translations.homePage.serverAddressPlaceholder,
|
2026-04-01 08:53:50 +00:00
|
|
|
"bind:value": connectionViewModel2.serverAddressInput,
|
|
|
|
|
"on:enter": connectionViewModel2.connect
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"datalist",
|
|
|
|
|
{
|
|
|
|
|
hidden: true,
|
|
|
|
|
id: "previous-connection-list",
|
|
|
|
|
"children:append": [
|
|
|
|
|
connectionViewModel2.previousAddresses,
|
|
|
|
|
StringToOption
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
))), /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "danger flex justify-center",
|
2026-04-09 16:48:22 +00:00
|
|
|
"aria-label": coreViewModel2.translations.homePage.disconnectAudioLabel,
|
2026-04-01 08:53:50 +00:00
|
|
|
"on:click": connectionViewModel2.disconnect,
|
|
|
|
|
"toggle:disabled": connectionViewModel2.cannotDisonnect
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "link_off")
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "flex justify-center",
|
2026-04-09 16:48:22 +00:00
|
|
|
"aria-label": coreViewModel2.translations.homePage.manageConnectionsAudioLabel,
|
2026-04-01 08:53:50 +00:00
|
|
|
"on:click": connectionViewModel2.showConnectionModal,
|
|
|
|
|
"toggle:disabled": connectionViewModel2.hasNoPreviousConnections
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "history")
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary flex justify-center",
|
2026-04-09 16:48:22 +00:00
|
|
|
"aria-label": coreViewModel2.translations.homePage.connectAudioLabel,
|
2026-04-01 08:53:50 +00:00
|
|
|
"on:click": connectionViewModel2.connect,
|
|
|
|
|
"toggle:disabled": connectionViewModel2.cannotConnect
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "link")
|
2026-04-09 16:48:22 +00: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, coreViewModel2.translations.homePage.yourNameLabel), /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"input",
|
|
|
|
|
{
|
2026-04-09 16:48:22 +00:00
|
|
|
placeholder: coreViewModel2.translations.homePage.yourNamePlaceholder,
|
2026-04-01 08:53:50 +00:00
|
|
|
"bind:value": settingsViewModel2.usernameInput,
|
|
|
|
|
"on:enter": settingsViewModel2.setName
|
|
|
|
|
}
|
|
|
|
|
))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "width-50",
|
|
|
|
|
"on:click": settingsViewModel2.setName,
|
|
|
|
|
"toggle:disabled": settingsViewModel2.cannotSetName,
|
2026-04-09 16:48:22 +00:00
|
|
|
"aria-label": coreViewModel2.translations.homePage.setNameButtonAudioLabel
|
2026-04-01 08:53:50 +00:00
|
|
|
},
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.general.setButton,
|
2026-04-01 08:53:50 +00:00
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "check")
|
2026-04-09 15:49:19 +00:00
|
|
|
)), /* @__PURE__ */ createElement("hr", null), HomePageButton(
|
2026-04-08 20:39:12 +00:00
|
|
|
settingsViewModel2.showSettingsModal,
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.homePage.settingsButton,
|
2026-04-08 20:35:06 +00:00
|
|
|
"settings"
|
|
|
|
|
), HomePageButton(
|
|
|
|
|
fileTransferViewModel2.showDirectionSelectionModal,
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.homePage.transferDataButton,
|
2026-04-08 20:35:06 +00:00
|
|
|
"sync_alt"
|
2026-04-08 20:31:25 +00:00
|
|
|
), HomePageButton(
|
|
|
|
|
storageViewModel2.showStorageModal,
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.homePage.manageStorageButton,
|
2026-04-08 20:35:06 +00:00
|
|
|
"hard_drive"
|
2026-04-09 16:48:22 +00:00
|
|
|
), /* @__PURE__ */ createElement("div", { class: "mobile-only" }, /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, /* @__PURE__ */ createElement("button", { class: "ghost width-50", "on:click": scrollToChat }, coreViewModel2.translations.homePage.scrollToChatButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")))));
|
|
|
|
|
const chatSection = /* @__PURE__ */ createElement("div", { id: "chat-section" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.chatsHeadline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"input",
|
|
|
|
|
{
|
2026-04-09 16:48:22 +00:00
|
|
|
placeholder: coreViewModel2.translations.homePage.addChatPlaceholder,
|
|
|
|
|
"aria-label": coreViewModel2.translations.homePage.addChatAudioLabel,
|
2026-04-01 08:53:50 +00:00
|
|
|
"bind:value": chatListViewModel2.newChatPrimaryChannel,
|
|
|
|
|
"on:enter": chatListViewModel2.createChat
|
|
|
|
|
}
|
|
|
|
|
), /* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "primary",
|
2026-04-09 16:48:22 +00:00
|
|
|
"aria-label": coreViewModel2.translations.homePage.addChatButton,
|
2026-04-01 08:53:50 +00:00
|
|
|
"on:click": chatListViewModel2.createChat,
|
|
|
|
|
"toggle:disabled": chatListViewModel2.cannotCreateChat
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "add")
|
|
|
|
|
)), /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
id: "chat-grid",
|
|
|
|
|
"children:append": [
|
|
|
|
|
chatListViewModel2.chatViewModels,
|
|
|
|
|
ChatViewModelToChatEntry
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
function scrollToChat() {
|
|
|
|
|
chatSection.scrollIntoView();
|
|
|
|
|
}
|
|
|
|
|
return /* @__PURE__ */ createElement("article", { id: "home-page" }, /* @__PURE__ */ createElement("div", null, overviewSection, chatSection));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/Model/Global/settingsModel.ts
|
2026-04-09 16:48:22 +00:00
|
|
|
var SettingsModel = class _SettingsModel {
|
2026-04-01 08:53:50 +00:00
|
|
|
// init
|
|
|
|
|
constructor(storageModel2) {
|
2026-04-13 10:58:21 +00:00
|
|
|
this.setName = (newValue) => {
|
|
|
|
|
this.username = newValue;
|
|
|
|
|
this.storeSetting("username", newValue);
|
|
|
|
|
};
|
|
|
|
|
this.setFirstDayOfWeek = (newValue) => {
|
|
|
|
|
this.firstDayOfWeek = newValue;
|
|
|
|
|
this.storeSetting("firstDayOfWeek", newValue);
|
|
|
|
|
};
|
|
|
|
|
this.setLanguage = (newValue) => {
|
|
|
|
|
this.language = newValue;
|
|
|
|
|
this.storeSetting("language", newValue);
|
|
|
|
|
};
|
|
|
|
|
this.setTheme = (newValue) => {
|
|
|
|
|
this.theme = newValue;
|
|
|
|
|
this.storeSetting("theme", newValue);
|
|
|
|
|
};
|
|
|
|
|
// load
|
|
|
|
|
this.readSetting = (pathName) => {
|
|
|
|
|
const path = StorageModel.getPath(
|
|
|
|
|
"settings" /* SettingsModel */,
|
|
|
|
|
filePaths.settingsModel[pathName]
|
|
|
|
|
);
|
|
|
|
|
return this.storageModel.read(path);
|
|
|
|
|
};
|
|
|
|
|
this.loadUsername = () => {
|
|
|
|
|
const content = this.readSetting("username");
|
|
|
|
|
this.username = content ?? "";
|
|
|
|
|
};
|
|
|
|
|
this.loadFirstDayofWeek = () => {
|
|
|
|
|
const content = this.readSetting("firstDayOfWeek");
|
|
|
|
|
this.firstDayOfWeek = content ?? "0";
|
|
|
|
|
};
|
|
|
|
|
this.loadLanguage = () => {
|
|
|
|
|
const content = this.readSetting("language");
|
|
|
|
|
this.language = content ?? _SettingsModel.getSystemLanguage();
|
|
|
|
|
};
|
|
|
|
|
this.loadTheme = () => {
|
|
|
|
|
const content = this.readSetting("theme");
|
|
|
|
|
this.theme = content ?? "system" /* System */;
|
|
|
|
|
};
|
2026-04-01 08:53:50 +00:00
|
|
|
this.storageModel = storageModel2;
|
2026-04-09 17:14:33 +00:00
|
|
|
this.loadUsername();
|
2026-04-01 08:53:50 +00:00
|
|
|
this.loadFirstDayofWeek();
|
2026-04-09 17:14:33 +00:00
|
|
|
this.loadLanguage();
|
2026-04-09 18:09:26 +00:00
|
|
|
this.loadTheme();
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
2026-04-13 10:58:21 +00:00
|
|
|
// storage
|
|
|
|
|
storeSetting(pathName, value) {
|
|
|
|
|
const path = StorageModel.getPath(
|
|
|
|
|
"settings" /* SettingsModel */,
|
|
|
|
|
filePaths.settingsModel[pathName]
|
|
|
|
|
);
|
|
|
|
|
this.storageModel.write(path, value);
|
|
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
static getSystemLanguage() {
|
|
|
|
|
switch (navigator.language.substring(0, 2)) {
|
|
|
|
|
case "de":
|
|
|
|
|
return "de" /* German */;
|
|
|
|
|
case "es":
|
|
|
|
|
return "es" /* Spanish */;
|
|
|
|
|
default:
|
|
|
|
|
return "en" /* English */;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
2026-04-09 18:09:26 +00:00
|
|
|
var Languages = /* @__PURE__ */ ((Languages2) => {
|
|
|
|
|
Languages2["English"] = "en";
|
|
|
|
|
Languages2["German"] = "de";
|
|
|
|
|
Languages2["Spanish"] = "es";
|
|
|
|
|
return Languages2;
|
|
|
|
|
})(Languages || {});
|
2026-04-01 08:53:50 +00:00
|
|
|
|
|
|
|
|
// src/ViewModel/Global/settingsViewModel.ts
|
2026-04-12 19:20:37 +00:00
|
|
|
var SettingsViewModel = class _SettingsViewModel extends Context {
|
2026-04-01 08:53:50 +00:00
|
|
|
// init
|
2026-04-09 16:48:22 +00:00
|
|
|
constructor(coreViewModel2) {
|
2026-04-12 19:20:37 +00:00
|
|
|
super();
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel = coreViewModel2;
|
2026-04-12 19:20:37 +00:00
|
|
|
this.contextDebugDescription = "settings";
|
2026-04-01 08:53:50 +00:00
|
|
|
// state
|
|
|
|
|
this.username = new State("");
|
|
|
|
|
this.usernameInput = new State("");
|
2026-04-08 20:39:12 +00:00
|
|
|
this.isShowingSettingsModal = new State(false);
|
2026-04-10 19:00:59 +00:00
|
|
|
this.selectedModalPage = new State(void 0);
|
2026-04-09 17:14:33 +00:00
|
|
|
this.requiresReload = new State(false);
|
2026-04-09 16:48:22 +00:00
|
|
|
this.firstDayOfWeek = new State("0");
|
2026-04-09 18:09:26 +00:00
|
|
|
this.language = new State(
|
|
|
|
|
"en" /* English */
|
|
|
|
|
);
|
|
|
|
|
this.theme = new State(
|
|
|
|
|
"system" /* System */
|
|
|
|
|
);
|
2026-04-01 08:53:50 +00:00
|
|
|
// guards
|
|
|
|
|
this.cannotSetName = createProxyState(
|
|
|
|
|
[this.usernameInput],
|
2026-04-09 16:48:22 +00:00
|
|
|
() => this.usernameInput.value == "" || this.usernameInput.value == this.coreViewModel.settingsModel.username
|
2026-04-01 08:53:50 +00:00
|
|
|
);
|
|
|
|
|
// methods
|
|
|
|
|
this.setName = () => {
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.settingsModel.setName(this.usernameInput.value);
|
|
|
|
|
this.username.value = this.coreViewModel.settingsModel.username;
|
2026-04-01 08:53:50 +00:00
|
|
|
this.usernameInput.callSubscriptions();
|
|
|
|
|
};
|
|
|
|
|
this.setFirstDayofWeek = () => {
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.settingsModel.setFirstDayOfWeek(
|
|
|
|
|
this.firstDayOfWeek.value
|
|
|
|
|
);
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
2026-04-08 20:39:12 +00:00
|
|
|
this.showSettingsModal = () => {
|
2026-04-12 19:20:37 +00:00
|
|
|
this.coreViewModel.context = this;
|
2026-04-08 20:39:12 +00:00
|
|
|
this.isShowingSettingsModal.value = true;
|
|
|
|
|
};
|
|
|
|
|
this.hideSettingsModal = () => {
|
2026-04-12 19:20:37 +00:00
|
|
|
this.coreViewModel.closeContext(this.contextId);
|
2026-04-08 20:39:12 +00:00
|
|
|
this.isShowingSettingsModal.value = false;
|
2026-04-09 17:14:33 +00:00
|
|
|
if (this.requiresReload.value == true) {
|
|
|
|
|
window.location.reload();
|
|
|
|
|
}
|
2026-04-08 20:39:12 +00:00
|
|
|
};
|
2026-04-08 21:03:36 +00:00
|
|
|
this.showModalPage = (page) => {
|
|
|
|
|
this.selectedModalPage.value = page;
|
|
|
|
|
};
|
2026-04-09 18:09:26 +00:00
|
|
|
// view
|
|
|
|
|
this.applyTheme = () => {
|
|
|
|
|
let theme = this.theme.value;
|
|
|
|
|
if (theme == "system" /* System */) {
|
|
|
|
|
theme = _SettingsViewModel.getSystemTheme();
|
|
|
|
|
}
|
|
|
|
|
document.body.setAttribute("theme", theme);
|
|
|
|
|
};
|
2026-04-09 16:48:22 +00:00
|
|
|
this.username.value = coreViewModel2.settingsModel.username;
|
|
|
|
|
this.usernameInput.value = coreViewModel2.settingsModel.username;
|
|
|
|
|
this.firstDayOfWeek.value = coreViewModel2.settingsModel.firstDayOfWeek;
|
2026-04-09 17:14:33 +00:00
|
|
|
this.language.value = coreViewModel2.settingsModel.language;
|
2026-04-09 18:09:26 +00:00
|
|
|
this.theme.value = coreViewModel2.settingsModel.theme;
|
2026-04-09 16:48:22 +00:00
|
|
|
this.firstDayOfWeek.subscribe(this.setFirstDayofWeek);
|
2026-04-09 17:14:33 +00:00
|
|
|
this.language.subscribeSilent((newValue) => {
|
|
|
|
|
this.coreViewModel.settingsModel.setLanguage(newValue);
|
|
|
|
|
this.requiresReload.value = true;
|
|
|
|
|
});
|
2026-04-09 18:09:26 +00:00
|
|
|
this.theme.subscribeSilent((newValue) => {
|
|
|
|
|
this.coreViewModel.settingsModel.setTheme(newValue);
|
|
|
|
|
});
|
|
|
|
|
this.theme.subscribe(() => {
|
|
|
|
|
this.applyTheme();
|
|
|
|
|
});
|
|
|
|
|
_SettingsViewModel.generateThemeMedia().addEventListener(
|
|
|
|
|
"change",
|
|
|
|
|
() => this.applyTheme()
|
|
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
this.registerKeyStroke("backspace" /* CloseOrCancel */, this.hideSettingsModal);
|
2026-04-09 18:09:26 +00:00
|
|
|
}
|
|
|
|
|
static generateThemeMedia() {
|
|
|
|
|
return window.matchMedia("(prefers-color-scheme: dark)");
|
|
|
|
|
}
|
|
|
|
|
static getSystemTheme() {
|
|
|
|
|
const media = _SettingsViewModel.generateThemeMedia();
|
|
|
|
|
return media.matches == true ? "dark" /* Dark */ : "light" /* Light */;
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-11 20:30:35 +00:00
|
|
|
// src/View/Modals/splitModal.tsx
|
2026-04-10 19:00:59 +00:00
|
|
|
function SplitModal(coreViewModel2, leftView, rightView, extendedStyle = false, navigationState) {
|
|
|
|
|
function closePage() {
|
|
|
|
|
navigationState.value = void 0;
|
|
|
|
|
}
|
|
|
|
|
const hasPageOpen = new State(false);
|
|
|
|
|
navigationState?.subscribe(
|
|
|
|
|
(newValue) => hasPageOpen.value = newValue != void 0
|
|
|
|
|
);
|
2026-04-10 18:17:16 +00:00
|
|
|
const view = /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "split-modal",
|
|
|
|
|
"toggle:extended": extendedStyle,
|
2026-04-10 19:00:59 +00:00
|
|
|
"toggle:navigation": navigationState != void 0,
|
|
|
|
|
"toggle:page-open": hasPageOpen
|
2026-04-10 18:17:16 +00:00
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "scroll-area", "children:set": leftView })),
|
2026-04-10 19:00:59 +00:00
|
|
|
/* @__PURE__ */ createElement("div", { class: "scroll-area slide-up" }, /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "flex-row width-100 mobile-only",
|
|
|
|
|
"toggle:hidden": navigationState == void 0
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement(
|
|
|
|
|
"button",
|
|
|
|
|
{
|
|
|
|
|
class: "ghost square",
|
|
|
|
|
"aria-label": coreViewModel2.translations.general.backButton,
|
|
|
|
|
"on:click": closePage
|
|
|
|
|
},
|
|
|
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_back")
|
|
|
|
|
)
|
|
|
|
|
), /* @__PURE__ */ createElement("div", { "children:set": rightView }))
|
2026-04-10 18:17:16 +00:00
|
|
|
);
|
2026-04-08 21:03:36 +00:00
|
|
|
return view;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 08:53:50 +00:00
|
|
|
// src/View/Components/directoryItemList.tsx
|
|
|
|
|
function DirectoryItemList(storageViewModel2, pathString = PATH_COMPONENT_SEPARATOR) {
|
|
|
|
|
const StringToDirectoryItemList = (pathString2) => DirectoryItemList(storageViewModel2, pathString2);
|
|
|
|
|
const path = StorageModel.stringToPathComponents(pathString);
|
|
|
|
|
const fileName = StorageModel.getFileName(path);
|
|
|
|
|
const items = new ListState();
|
|
|
|
|
const style = `text-indent: ${path.length}rem`;
|
|
|
|
|
function loadItems() {
|
|
|
|
|
items.clear();
|
2026-04-09 16:48:22 +00:00
|
|
|
const directoryItems = storageViewModel2.coreViewModel.storageModel.list(path);
|
2026-04-01 08:53:50 +00:00
|
|
|
for (const directoryItem of directoryItems) {
|
|
|
|
|
const itemPath = [...path, directoryItem];
|
|
|
|
|
const pathString2 = StorageModel.pathComponentsToString(...itemPath);
|
|
|
|
|
items.add(pathString2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function select() {
|
|
|
|
|
storageViewModel2.selectedPath.value = pathString;
|
|
|
|
|
}
|
|
|
|
|
storageViewModel2.lastDeletedItemPath.subscribe((lastDeletedItemPath) => {
|
|
|
|
|
if (!items.value.has(lastDeletedItemPath)) return;
|
|
|
|
|
select();
|
|
|
|
|
setTimeout(() => loadItems(), 50);
|
|
|
|
|
});
|
|
|
|
|
const isSelected = createProxyState(
|
|
|
|
|
[storageViewModel2.selectedPath],
|
|
|
|
|
() => storageViewModel2.selectedPath.value == pathString
|
|
|
|
|
);
|
|
|
|
|
isSelected.subscribe(() => {
|
|
|
|
|
if (isSelected.value == false) return;
|
|
|
|
|
loadItems();
|
|
|
|
|
});
|
2026-04-08 21:16:56 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", { class: "flex-column" }, /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"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",
|
|
|
|
|
{
|
2026-04-08 21:16:56 +00:00
|
|
|
class: "flex-column",
|
2026-04-01 08:53:50 +00:00
|
|
|
"children:append": [items, StringToDirectoryItemList]
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 21:03:36 +00:00
|
|
|
// src/View/Modals/storageModal.tsx
|
2026-04-09 16:48:22 +00:00
|
|
|
function StorageModal(coreViewModel2, storageViewModel2) {
|
2026-04-01 08:53:50 +00:00
|
|
|
const detailView = createProxyState(
|
|
|
|
|
[storageViewModel2.selectedPath],
|
|
|
|
|
() => {
|
|
|
|
|
if (storageViewModel2.selectedPath.value == PATH_COMPONENT_SEPARATOR)
|
2026-04-09 16:48:22 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.storage.noItemSelected), /* @__PURE__ */ createElement("hr", null), DangerousActionButton(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
coreViewModel2.translations.storage.removeJunkButton,
|
2026-04-01 08:53:50 +00:00
|
|
|
"delete_forever",
|
|
|
|
|
storageViewModel2.removeJunk
|
|
|
|
|
));
|
2026-04-09 16:48:22 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("div", { class: "tile flex-no" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.storage.path), /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"span",
|
|
|
|
|
{
|
|
|
|
|
class: "break-all",
|
|
|
|
|
"subscribe:innerText": storageViewModel2.selectedPath
|
|
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
))), /* @__PURE__ */ createElement("div", { class: "tile flex-no" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.storage.content), /* @__PURE__ */ createElement(
|
2026-04-01 08:53:50 +00:00
|
|
|
"code",
|
|
|
|
|
{
|
|
|
|
|
"subscribe:innerText": storageViewModel2.selectedFileContent
|
|
|
|
|
}
|
|
|
|
|
))), DangerousActionButton(
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2,
|
|
|
|
|
coreViewModel2.translations.storage.deleteItem,
|
2026-04-01 08:53:50 +00:00
|
|
|
"delete_forever",
|
|
|
|
|
storageViewModel2.deleteSelectedItem
|
|
|
|
|
));
|
|
|
|
|
}
|
2024-07-21 18:56:56 +02:00
|
|
|
);
|
2026-04-08 21:14:37 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": storageViewModel2.isShowingStorageModal }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", { class: "padding-0" }, SplitModal(
|
2026-04-10 19:00:59 +00:00
|
|
|
coreViewModel2,
|
2026-04-08 21:03:36 +00:00
|
|
|
new State(DirectoryItemList(storageViewModel2)),
|
|
|
|
|
detailView,
|
2026-04-08 21:14:37 +00:00
|
|
|
true
|
2026-04-09 16:48:22 +00:00
|
|
|
)), /* @__PURE__ */ createElement("button", { "on:click": storageViewModel2.hideStorageModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))));
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/ViewModel/Global/storageViewModel.ts
|
2026-04-12 19:20:37 +00:00
|
|
|
var StorageViewModel = class extends Context {
|
2026-04-01 08:53:50 +00:00
|
|
|
// init
|
2026-04-09 16:48:22 +00:00
|
|
|
constructor(coreViewModel2) {
|
2026-04-12 19:20:37 +00:00
|
|
|
super();
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel = coreViewModel2;
|
2026-04-12 19:20:37 +00:00
|
|
|
this.contextDebugDescription = "storage";
|
2026-04-01 08:53:50 +00:00
|
|
|
// state
|
|
|
|
|
this.isShowingStorageModal = new State(false);
|
|
|
|
|
this.selectedPath = new State(
|
|
|
|
|
PATH_COMPONENT_SEPARATOR
|
|
|
|
|
);
|
|
|
|
|
this.didMakeChanges = new State(false);
|
|
|
|
|
this.lastDeletedItemPath = new State("");
|
|
|
|
|
// methods
|
|
|
|
|
this.getSelectedItemContent = () => {
|
|
|
|
|
const path = StorageModel.stringToPathComponents(
|
|
|
|
|
this.selectedPath.value
|
|
|
|
|
);
|
2026-04-09 16:48:22 +00:00
|
|
|
const content = this.coreViewModel.storageModel.read(path);
|
|
|
|
|
return (content ?? this.coreViewModel.translations.storage.notAFile) || this.coreViewModel.translations.storage.contentEmpty;
|
2026-04-01 08:53:50 +00:00
|
|
|
};
|
|
|
|
|
this.deleteSelectedItem = () => {
|
|
|
|
|
const path = StorageModel.stringToPathComponents(
|
|
|
|
|
this.selectedPath.value
|
|
|
|
|
);
|
|
|
|
|
this.lastDeletedItemPath.value = this.selectedPath.value;
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.storageModel.removeRecursively(path);
|
2026-04-01 08:53:50 +00:00
|
|
|
this.didMakeChanges.value = true;
|
|
|
|
|
};
|
|
|
|
|
this.removeJunk = () => {
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.storageModel.removeJunk();
|
2026-04-01 08:53:50 +00:00
|
|
|
this.selectedPath.value = PATH_COMPONENT_SEPARATOR;
|
|
|
|
|
};
|
|
|
|
|
// view
|
|
|
|
|
this.showStorageModal = () => {
|
2026-04-12 19:20:37 +00:00
|
|
|
this.coreViewModel.context = this;
|
2026-04-01 08:53:50 +00:00
|
|
|
this.isShowingStorageModal.value = true;
|
|
|
|
|
};
|
|
|
|
|
this.hideStorageModal = () => {
|
2026-04-12 19:20:37 +00:00
|
|
|
this.coreViewModel.closeContext(this.contextId);
|
2026-04-01 08:53:50 +00:00
|
|
|
if (this.didMakeChanges.value == true) {
|
|
|
|
|
window.location.reload();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
this.isShowingStorageModal.value = false;
|
|
|
|
|
};
|
|
|
|
|
this.selectedFileName = createProxyState(
|
|
|
|
|
[this.selectedPath],
|
|
|
|
|
() => StorageModel.getFileNameFromString(this.selectedPath.value)
|
|
|
|
|
);
|
|
|
|
|
this.selectedFileContent = createProxyState(
|
|
|
|
|
[this.selectedPath],
|
|
|
|
|
() => this.getSelectedItemContent()
|
|
|
|
|
);
|
2026-04-12 19:20:37 +00:00
|
|
|
this.registerKeyStroke("backspace" /* CloseOrCancel */, this.hideStorageModal);
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// src/Upgrader/v1.ts
|
|
|
|
|
var v1Upgrader = class {
|
|
|
|
|
// init
|
2026-04-09 16:48:22 +00:00
|
|
|
constructor(coreViewModel2) {
|
|
|
|
|
this.coreViewModel = coreViewModel2;
|
2026-04-01 08:53:50 +00:00
|
|
|
// general
|
|
|
|
|
this.migrateSettings = () => {
|
|
|
|
|
const name = getLocalStorageItemAndClear("sender-name");
|
|
|
|
|
if (name != null) {
|
|
|
|
|
const parsedName = parseOrFallback(name);
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.settingsModel.setName(parsedName);
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
const firstDayOfWeek = getLocalStorageItemAndClear("first-day-of-week");
|
|
|
|
|
if (firstDayOfWeek != null) {
|
|
|
|
|
const parsedFirstDayOfWeek = parseOrFallback(firstDayOfWeek);
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.settingsModel.setFirstDayOfWeek(
|
|
|
|
|
parsedFirstDayOfWeek
|
|
|
|
|
);
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.migrateConnections = () => {
|
|
|
|
|
const previousAddressString = getLocalStorageItemAndClear("previous-addresses");
|
|
|
|
|
if (previousAddressString == null) return;
|
|
|
|
|
const previousAddresses = parseArray(previousAddressString);
|
|
|
|
|
for (const address of previousAddresses) {
|
|
|
|
|
if (typeof address != "string") continue;
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.connectionModel.storeAddress(address);
|
2026-04-01 08:53:50 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// chats
|
|
|
|
|
this.migrateChats = () => {
|
|
|
|
|
const chatIdString = getLocalStorageItemAndClear("chat-ids");
|
|
|
|
|
if (chatIdString == null) return;
|
|
|
|
|
const chatIds = parseArray(chatIdString);
|
|
|
|
|
for (const chatId of chatIds) {
|
|
|
|
|
if (typeof chatId != "string") continue;
|
|
|
|
|
this.migrateChatById(chatId);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.migrateChatById = (id) => {
|
|
|
|
|
const primaryChannel = getLocalStorageItemAndClear(
|
|
|
|
|
storageKeys.primaryChannel(id)
|
|
|
|
|
);
|
|
|
|
|
if (primaryChannel == null) return;
|
|
|
|
|
const parsedPriamryChannel = parseOrFallback(primaryChannel);
|
2026-04-09 16:48:22 +00:00
|
|
|
const chatModel = this.coreViewModel.chatListModel.createChat(parsedPriamryChannel);
|
2026-04-01 08:53:50 +00:00
|
|
|
const secondaryChannelString = getLocalStorageItemAndClear(storageKeys.secondaryChannels(id));
|
|
|
|
|
if (secondaryChannelString != null) {
|
|
|
|
|
const potentialSecondaryChannels = parseArray(
|
|
|
|
|
secondaryChannelString
|
|
|
|
|
);
|
|
|
|
|
const confirmedSecondaryChannels = [];
|
|
|
|
|
for (const secondaryChannel of potentialSecondaryChannels) {
|
|
|
|
|
if (typeof secondaryChannel != "string") continue;
|
|
|
|
|
confirmedSecondaryChannels.push(secondaryChannel);
|
|
|
|
|
}
|
|
|
|
|
chatModel.setSecondaryChannels(confirmedSecondaryChannels);
|
|
|
|
|
}
|
|
|
|
|
const encryptionKey = getLocalStorageItemAndClear(
|
|
|
|
|
storageKeys.encyptionKey(id)
|
2026-03-31 19:52:17 +00:00
|
|
|
);
|
2026-04-01 08:53:50 +00:00
|
|
|
if (encryptionKey != null) {
|
|
|
|
|
const parsedEncryptionKey = parseOrFallback(encryptionKey);
|
|
|
|
|
chatModel.setEncryptionKey(parsedEncryptionKey);
|
|
|
|
|
}
|
|
|
|
|
const messagesString = getLocalStorageItemAndClear(storageKeys.messages(id)) ?? "";
|
|
|
|
|
const messageOutboxString = getLocalStorageItemAndClear(storageKeys.outbox(id)) ?? "";
|
|
|
|
|
const potentialMessages = parseArray(messagesString);
|
|
|
|
|
const potentialMessagesInOutbox = parseArray(messageOutboxString);
|
|
|
|
|
const addMessages = (potentialMessages2, status) => {
|
|
|
|
|
for (const potentialMessage of potentialMessages2) {
|
|
|
|
|
const isV1ChatMessage = checkMatchesObjectStructure(
|
|
|
|
|
potentialMessage,
|
|
|
|
|
V1ChatMessageReference
|
|
|
|
|
);
|
|
|
|
|
if (isV1ChatMessage == false) continue;
|
|
|
|
|
const v1ChatMessage = potentialMessage;
|
|
|
|
|
const convertedChatMessage = {
|
|
|
|
|
dataVersion: "v2",
|
|
|
|
|
id: v4_default(),
|
|
|
|
|
channel: v1ChatMessage.channel,
|
|
|
|
|
sender: v1ChatMessage.sender,
|
|
|
|
|
body: v1ChatMessage.body,
|
|
|
|
|
dateSent: v1ChatMessage.isoDate,
|
|
|
|
|
status,
|
|
|
|
|
stringifiedFile: ""
|
|
|
|
|
};
|
|
|
|
|
chatModel.addMessage(convertedChatMessage);
|
|
|
|
|
if (status == "outbox" /* Outbox */) {
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.connectionModel.sendMessageOrStore(
|
2026-04-01 08:53:50 +00:00
|
|
|
convertedChatMessage
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
addMessages(potentialMessages, "received" /* Received */);
|
|
|
|
|
addMessages(potentialMessagesInOutbox, "outbox" /* Outbox */);
|
|
|
|
|
const objectsString = getLocalStorageItemAndClear(storageKeys.objects(id)) ?? "";
|
|
|
|
|
const objectOutboxString = getLocalStorageItemAndClear(storageKeys.itemOutbox(id)) ?? "";
|
|
|
|
|
const potentialObjects = parseArray(objectsString);
|
|
|
|
|
const potentialObjectsInOutbox = parseArray(objectOutboxString);
|
|
|
|
|
const addObjects = (potentialObjects2) => {
|
|
|
|
|
const board = chatModel.fileModel.boardsAndTasksModel.createBoard(
|
2026-04-09 16:48:22 +00:00
|
|
|
this.coreViewModel.translations.updater.migrated
|
2026-04-01 08:53:50 +00:00
|
|
|
);
|
|
|
|
|
chatModel.fileModel.boardsAndTasksModel.updateBoard(board);
|
|
|
|
|
for (const potentialObjectEntry of potentialObjects2) {
|
|
|
|
|
const potentialObject = potentialObjectEntry[1];
|
|
|
|
|
const isV1MessageObject = checkIsV1MessageObject(potentialObject);
|
|
|
|
|
if (isV1MessageObject == false) continue;
|
|
|
|
|
const objectId = potentialObject.id;
|
|
|
|
|
const objectName = potentialObject.title;
|
|
|
|
|
const contentVersions = Object.values(
|
|
|
|
|
potentialObject.contentVersions
|
|
|
|
|
);
|
|
|
|
|
for (const potentialVersion of contentVersions) {
|
|
|
|
|
const isV1MessageObjectContent = checkIsV1MessageObjectContent(potentialVersion);
|
|
|
|
|
if (isV1MessageObjectContent == false) continue;
|
|
|
|
|
const convertedTaskFileContent = {
|
|
|
|
|
dataVersion: "v2",
|
|
|
|
|
fileId: objectId,
|
|
|
|
|
fileContentId: FileModel2.generateFileContentId(
|
|
|
|
|
potentialVersion.isoDateVersionCreated
|
|
|
|
|
),
|
|
|
|
|
creationDate: potentialVersion.isoDateVersionCreated,
|
|
|
|
|
type: "task",
|
|
|
|
|
name: objectName ?? "",
|
|
|
|
|
boardId: board.fileId ?? "",
|
|
|
|
|
description: potentialVersion.noteContent ?? "",
|
|
|
|
|
category: potentialVersion.categoryName ?? "",
|
|
|
|
|
status: potentialVersion.status ?? "",
|
|
|
|
|
priority: potentialVersion.priority ?? "",
|
|
|
|
|
date: potentialVersion.date ?? "",
|
|
|
|
|
time: potentialVersion.time ?? ""
|
|
|
|
|
};
|
|
|
|
|
console.log(convertedTaskFileContent);
|
|
|
|
|
chatModel.fileModel.handleFileContent(
|
|
|
|
|
convertedTaskFileContent
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
addObjects([...potentialObjects, ...potentialObjectsInOutbox]);
|
|
|
|
|
};
|
|
|
|
|
this.migrateSettings();
|
|
|
|
|
this.migrateConnections();
|
|
|
|
|
this.migrateChats();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var storageKeys = {
|
|
|
|
|
viewType(id) {
|
|
|
|
|
return id + "view-type";
|
|
|
|
|
},
|
|
|
|
|
hasUnread(id) {
|
|
|
|
|
return id + "has-unread-messages";
|
|
|
|
|
},
|
|
|
|
|
primaryChannel(id) {
|
|
|
|
|
return id + "primary-channel";
|
|
|
|
|
},
|
|
|
|
|
secondaryChannels(id) {
|
|
|
|
|
return id + "secondary-channels";
|
|
|
|
|
},
|
|
|
|
|
encyptionKey(id) {
|
|
|
|
|
return id + "encryption-key";
|
|
|
|
|
},
|
|
|
|
|
messages(id) {
|
|
|
|
|
return id + "messages";
|
|
|
|
|
},
|
|
|
|
|
objects(id) {
|
|
|
|
|
return id + "items";
|
|
|
|
|
},
|
|
|
|
|
outbox(id) {
|
|
|
|
|
return id + "outbox";
|
|
|
|
|
},
|
|
|
|
|
itemOutbox(id) {
|
|
|
|
|
return id + "item-outbox";
|
|
|
|
|
},
|
|
|
|
|
composingMessage(id) {
|
|
|
|
|
return id + "composing-message";
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var V1ChatMessageReference = {
|
|
|
|
|
channel: "",
|
|
|
|
|
sender: "",
|
|
|
|
|
body: "",
|
|
|
|
|
isoDate: ""
|
|
|
|
|
};
|
|
|
|
|
function checkIsV1MessageObject(object) {
|
|
|
|
|
if (object.id == void 0) return false;
|
|
|
|
|
if (object.title == void 0) return false;
|
|
|
|
|
if (object.contentVersions == void 0) return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
function checkIsV1MessageObjectContent(object) {
|
|
|
|
|
if (object.id == void 0) return false;
|
|
|
|
|
if (object.isoDateVersionCreated == void 0) return false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 18:09:26 +00:00
|
|
|
// src/View/Components/optionButton.tsx
|
|
|
|
|
function OptionButton(text, value, selection) {
|
|
|
|
|
function select() {
|
|
|
|
|
selection.value = value;
|
|
|
|
|
}
|
|
|
|
|
const isSelected = createProxyState(
|
|
|
|
|
[selection],
|
|
|
|
|
() => selection.value == value
|
|
|
|
|
);
|
|
|
|
|
return /* @__PURE__ */ createElement("button", { class: "standard", "toggle:selected": isSelected, "on:click": select }, text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// src/View/Components/optionButtonList.tsx
|
|
|
|
|
function OptionButtonList(options, selection) {
|
|
|
|
|
function OptionToView(option) {
|
|
|
|
|
const [text, value] = option;
|
|
|
|
|
return OptionButton(text, value, selection);
|
|
|
|
|
}
|
|
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "flex-column gap",
|
|
|
|
|
"children:append": [options, OptionToView]
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-10 19:20:47 +00:00
|
|
|
// src/View/Components/navigationButton.tsx
|
|
|
|
|
function NavigationButton(coreViewModel2, label, navigationState, page, arrowMobileOnly) {
|
|
|
|
|
const isSelected = createProxyState(
|
|
|
|
|
[navigationState],
|
|
|
|
|
() => navigationState.value == page
|
|
|
|
|
);
|
|
|
|
|
function open() {
|
|
|
|
|
navigationState.value = page;
|
|
|
|
|
}
|
|
|
|
|
const iconClass = `icon ${arrowMobileOnly == true ? "mobile-only" : ""}`;
|
|
|
|
|
return /* @__PURE__ */ createElement("button", { "toggle:selected": isSelected, "on:click": open }, label, /* @__PURE__ */ createElement("span", { class: iconClass }, "arrow_forward"));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 20:39:12 +00:00
|
|
|
// src/View/Modals/settingsModal.tsx
|
2026-04-09 16:48:22 +00:00
|
|
|
function SettingsModal(coreViewModel2, settingsViewModel2) {
|
2026-04-09 15:45:19 +00:00
|
|
|
const detailView = createProxyState(
|
|
|
|
|
[settingsViewModel2.selectedModalPage],
|
|
|
|
|
() => {
|
|
|
|
|
switch (settingsViewModel2.selectedModalPage.value) {
|
2026-04-10 19:00:59 +00:00
|
|
|
case void 0: {
|
|
|
|
|
return PlaceholderView(
|
|
|
|
|
coreViewModel2.translations.general.noPageSelected
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-04-09 15:45:19 +00:00
|
|
|
case 0 /* Appearance */:
|
2026-04-09 18:09:26 +00:00
|
|
|
return SettingsAppearancePane(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
settingsViewModel2
|
|
|
|
|
);
|
2026-04-09 15:45:19 +00:00
|
|
|
case 1 /* Regional */:
|
2026-04-09 16:48:22 +00:00
|
|
|
return SettingsRegionalPane(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
settingsViewModel2
|
|
|
|
|
);
|
2026-04-09 15:45:19 +00:00
|
|
|
case 2 /* Info */:
|
2026-04-09 16:48:22 +00:00
|
|
|
return SettingsInfoPane(coreViewModel2, settingsViewModel2);
|
2026-04-09 15:45:19 +00:00
|
|
|
}
|
2026-04-09 08:35:14 +00:00
|
|
|
}
|
2026-04-09 15:45:19 +00:00
|
|
|
);
|
2026-04-08 21:03:36 +00:00
|
|
|
return /* @__PURE__ */ createElement(
|
|
|
|
|
"div",
|
|
|
|
|
{
|
|
|
|
|
class: "modal",
|
|
|
|
|
"toggle:open": settingsViewModel2.isShowingSettingsModal
|
|
|
|
|
},
|
2026-04-08 21:14:37 +00:00
|
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", { class: "padding-0" }, SplitModal(
|
2026-04-10 19:00:59 +00:00
|
|
|
coreViewModel2,
|
2026-04-09 16:48:22 +00:00
|
|
|
new State(
|
|
|
|
|
SettingsLeftPane(coreViewModel2, settingsViewModel2)
|
|
|
|
|
),
|
2026-04-09 08:35:14 +00:00
|
|
|
detailView,
|
2026-04-09 21:45:56 +00:00
|
|
|
true,
|
2026-04-08 21:14:37 +00:00
|
|
|
settingsViewModel2.selectedModalPage
|
2026-04-09 16:48:22 +00:00
|
|
|
)), /* @__PURE__ */ createElement("button", { "on:click": settingsViewModel2.hideSettingsModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))
|
2026-04-08 21:03:36 +00:00
|
|
|
);
|
|
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
function SettingsLeftPane(coreViewModel2, settingsViewModel2) {
|
2026-04-10 19:00:59 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", { class: "flex-column gap slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.settingsButton), SettingsPaneButton(
|
2026-04-10 19:20:47 +00:00
|
|
|
coreViewModel2,
|
2026-04-08 21:14:37 +00:00
|
|
|
settingsViewModel2,
|
|
|
|
|
0 /* Appearance */,
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.settings.pages.appearance
|
2026-04-08 21:14:37 +00:00
|
|
|
), SettingsPaneButton(
|
2026-04-10 19:20:47 +00:00
|
|
|
coreViewModel2,
|
2026-04-08 21:14:37 +00:00
|
|
|
settingsViewModel2,
|
|
|
|
|
1 /* Regional */,
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.settings.pages.regional
|
2026-04-08 21:14:37 +00:00
|
|
|
), SettingsPaneButton(
|
2026-04-10 19:20:47 +00:00
|
|
|
coreViewModel2,
|
2026-04-08 21:14:37 +00:00
|
|
|
settingsViewModel2,
|
|
|
|
|
2 /* Info */,
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.settings.pages.info
|
2026-04-08 21:14:37 +00:00
|
|
|
));
|
2026-04-08 21:03:36 +00:00
|
|
|
}
|
2026-04-10 19:20:47 +00:00
|
|
|
function SettingsPaneButton(coreViewModel2, settingsViewModel2, page, label) {
|
|
|
|
|
return NavigationButton(
|
|
|
|
|
coreViewModel2,
|
|
|
|
|
label,
|
|
|
|
|
settingsViewModel2.selectedModalPage,
|
|
|
|
|
page,
|
|
|
|
|
true
|
2026-04-08 21:03:36 +00:00
|
|
|
);
|
2026-04-08 20:39:12 +00:00
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
function SettingsInfoPane(coreViewModel2, settingsViewModel2) {
|
|
|
|
|
return /* @__PURE__ */ createElement("div", { class: "slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.appName), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, InfoTile(
|
2026-04-09 15:45:48 +00:00
|
|
|
"build",
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel2.translations.settings.version,
|
2026-04-09 15:45:48 +00:00
|
|
|
settingsViewModel2.coreViewModel.BUILD
|
|
|
|
|
)));
|
2026-04-09 15:45:19 +00:00
|
|
|
}
|
2026-04-09 16:48:22 +00:00
|
|
|
function SettingsRegionalPane(coreViewModel2, settingsViewModel2) {
|
2026-04-09 21:45:56 +00:00
|
|
|
return /* @__PURE__ */ createElement("div", { class: "slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.settings.pages.regional), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.settings.language), OptionButtonList(
|
|
|
|
|
new ListState(
|
2026-04-10 18:17:16 +00:00
|
|
|
Object.values(Languages).map((x) => [languageNames[x], x])
|
2026-04-09 21:45:56 +00:00
|
|
|
),
|
|
|
|
|
settingsViewModel2.language
|
|
|
|
|
), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.settings.firstDayOfWeekLabel), OptionButtonList(
|
|
|
|
|
new ListState(
|
|
|
|
|
coreViewModel2.translations.regional.weekdays.full.map(
|
|
|
|
|
(x, i) => [x, i.toString()]
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
settingsViewModel2.firstDayOfWeek
|
|
|
|
|
));
|
2026-04-09 15:49:19 +00:00
|
|
|
}
|
2026-04-09 18:09:26 +00:00
|
|
|
function SettingsAppearancePane(coreViewModel2, settingsViewModel2) {
|
|
|
|
|
return /* @__PURE__ */ createElement("div", { class: "slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.settings.pages.appearance), /* @__PURE__ */ createElement("hr", null), OptionButtonList(
|
|
|
|
|
new ListState([
|
|
|
|
|
[
|
|
|
|
|
coreViewModel2.translations.settings.themes.dark,
|
|
|
|
|
"dark" /* Dark */
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
coreViewModel2.translations.settings.themes.light,
|
|
|
|
|
"light" /* Light */
|
|
|
|
|
],
|
|
|
|
|
[
|
|
|
|
|
coreViewModel2.translations.settings.themes.system,
|
|
|
|
|
"system" /* System */
|
|
|
|
|
]
|
|
|
|
|
]),
|
|
|
|
|
settingsViewModel2.theme
|
|
|
|
|
));
|
|
|
|
|
}
|
2026-04-08 20:39:12 +00:00
|
|
|
|
2026-04-12 19:20:37 +00:00
|
|
|
// src/ViewModel/Pages/homeViewModel.ts
|
|
|
|
|
var HomeViewModel = class extends Context {
|
|
|
|
|
constructor(coreViewModel2, settingsViewModel2, fileTransferViewModel2, storageViewModel2, connectionViewModel2) {
|
|
|
|
|
super();
|
|
|
|
|
this.coreViewModel = coreViewModel2;
|
|
|
|
|
this.settingsViewModel = settingsViewModel2;
|
|
|
|
|
this.fileTransferViewModel = fileTransferViewModel2;
|
|
|
|
|
this.storageViewModel = storageViewModel2;
|
|
|
|
|
this.connectionViewModel = connectionViewModel2;
|
|
|
|
|
this.contextDebugDescription = "home";
|
|
|
|
|
this.coreViewModel.context = this;
|
|
|
|
|
this.registerKeyStroke(
|
|
|
|
|
"," /* Settings */,
|
|
|
|
|
this.settingsViewModel.showSettingsModal
|
|
|
|
|
);
|
|
|
|
|
this.registerKeyStroke(
|
2026-04-13 10:26:03 +00:00
|
|
|
"t",
|
2026-04-12 19:20:37 +00:00
|
|
|
this.fileTransferViewModel.showDirectionSelectionModal
|
|
|
|
|
);
|
|
|
|
|
this.registerKeyStroke("e", this.storageViewModel.showStorageModal);
|
|
|
|
|
this.registerKeyStroke("x", this.connectionViewModel.disconnect);
|
|
|
|
|
this.registerKeyStroke("c", this.connectionViewModel.connect);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-01 08:53:50 +00:00
|
|
|
// src/index.tsx
|
|
|
|
|
var storageModel = new StorageModel();
|
|
|
|
|
var settingsModel = new SettingsModel(storageModel);
|
|
|
|
|
var connectionModel = new ConnectionModel(storageModel);
|
|
|
|
|
var chatListModel = new ChatListModel(
|
|
|
|
|
storageModel,
|
|
|
|
|
settingsModel,
|
|
|
|
|
connectionModel
|
|
|
|
|
);
|
|
|
|
|
var fileTransferModel = new FileTransferModel(storageModel, connectionModel);
|
2026-04-09 16:48:22 +00:00
|
|
|
var coreViewModel = new CoreViewModel(
|
2026-04-01 08:53:50 +00:00
|
|
|
storageModel,
|
2026-04-09 16:48:22 +00:00
|
|
|
settingsModel,
|
|
|
|
|
connectionModel,
|
2026-04-01 08:53:50 +00:00
|
|
|
chatListModel,
|
2026-04-09 16:48:22 +00:00
|
|
|
fileTransferModel
|
|
|
|
|
);
|
|
|
|
|
new v1Upgrader(coreViewModel);
|
|
|
|
|
var storageViewModel = new StorageViewModel(coreViewModel);
|
|
|
|
|
var settingsViewModel = new SettingsViewModel(coreViewModel);
|
|
|
|
|
var connectionViewModel = new ConnectionViewModel(coreViewModel);
|
|
|
|
|
var chatListViewModel = new ChatListViewModel(
|
|
|
|
|
coreViewModel,
|
2026-04-01 08:53:50 +00:00
|
|
|
settingsViewModel,
|
|
|
|
|
connectionViewModel
|
|
|
|
|
);
|
2026-04-09 16:48:22 +00:00
|
|
|
var fileTransferViewModel = new FileTransferViewModel(coreViewModel);
|
2026-04-12 19:20:37 +00:00
|
|
|
var homeViewModel = new HomeViewModel(
|
|
|
|
|
coreViewModel,
|
|
|
|
|
settingsViewModel,
|
|
|
|
|
fileTransferViewModel,
|
|
|
|
|
storageViewModel,
|
|
|
|
|
connectionViewModel
|
|
|
|
|
);
|
2026-04-01 08:53:50 +00:00
|
|
|
chatListViewModel.selectedChat.subscribe(() => {
|
|
|
|
|
document.body.toggleAttribute(
|
|
|
|
|
"showing-chat",
|
|
|
|
|
chatListViewModel.selectedChat.value != void 0
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
document.body.append(
|
|
|
|
|
/* @__PURE__ */ createElement("div", { id: "background-wrapper" }, /* @__PURE__ */ createElement("div", { id: "sky" }), /* @__PURE__ */ createElement("div", { id: "grass-1" }), /* @__PURE__ */ createElement("div", { id: "grass-2" }))
|
|
|
|
|
);
|
|
|
|
|
document.querySelector("main").append(
|
|
|
|
|
HomePage(
|
2026-04-09 16:48:22 +00:00
|
|
|
coreViewModel,
|
2026-04-01 08:53:50 +00:00
|
|
|
storageViewModel,
|
|
|
|
|
settingsViewModel,
|
|
|
|
|
connectionViewModel,
|
|
|
|
|
fileTransferViewModel,
|
|
|
|
|
chatListViewModel
|
|
|
|
|
),
|
2026-04-09 16:48:22 +00:00
|
|
|
ChatPageWrapper(coreViewModel, chatListViewModel),
|
|
|
|
|
ConnectionModal(coreViewModel, connectionViewModel),
|
|
|
|
|
DataTransferModalWrapper(
|
|
|
|
|
coreViewModel,
|
|
|
|
|
connectionViewModel,
|
|
|
|
|
fileTransferViewModel
|
|
|
|
|
),
|
|
|
|
|
StorageModal(coreViewModel, storageViewModel),
|
|
|
|
|
SettingsModal(coreViewModel, settingsViewModel)
|
2026-04-01 08:53:50 +00:00
|
|
|
);
|
2024-07-07 13:43:18 +02:00
|
|
|
})();
|