9392 lines
352 KiB
JavaScript
9392 lines
352 KiB
JavaScript
(() => {
|
|
// node_modules/uuid/dist/stringify.js
|
|
var byteToHex = [];
|
|
for (let i = 0; i < 256; ++i) {
|
|
byteToHex.push((i + 256).toString(16).slice(1));
|
|
}
|
|
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/rng.js
|
|
var rnds8 = new Uint8Array(16);
|
|
function rng() {
|
|
return crypto.getRandomValues(rnds8);
|
|
}
|
|
|
|
// node_modules/uuid/dist/v4.js
|
|
function v4(options, buf, offset) {
|
|
if (!buf && !options && crypto.randomUUID) {
|
|
return crypto.randomUUID();
|
|
}
|
|
return _v4(options, buf, offset);
|
|
}
|
|
function _v4(options, buf, offset) {
|
|
options = options || {};
|
|
const rnds = options.random ?? options.rng?.() ?? rng();
|
|
if (rnds.length < 16) {
|
|
throw new Error("Random bytes length must be >= 16");
|
|
}
|
|
rnds[6] = rnds[6] & 15 | 64;
|
|
rnds[8] = rnds[8] & 63 | 128;
|
|
if (buf) {
|
|
offset = offset || 0;
|
|
if (offset < 0 || offset + 16 > buf.length) {
|
|
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
|
|
}
|
|
for (let i = 0; i < 16; ++i) {
|
|
buf[offset + i] = rnds[i];
|
|
}
|
|
return buf;
|
|
}
|
|
return unsafeStringify(rnds);
|
|
}
|
|
var v4_default = v4;
|
|
|
|
// src/react.ts
|
|
function UUID() {
|
|
return v4_default();
|
|
}
|
|
var State = class {
|
|
// init
|
|
constructor(initialValue) {
|
|
this._bindings = /* @__PURE__ */ new Set();
|
|
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));
|
|
this.additionHandlers = /* @__PURE__ */ new Set();
|
|
this.removalHandlers = /* @__PURE__ */ new Map();
|
|
this.genericRemovalHandlers = /* @__PURE__ */ new Set();
|
|
}
|
|
// list
|
|
add(...items) {
|
|
items.forEach((item) => {
|
|
if (this.value.has(item)) return;
|
|
this.value.add(item);
|
|
this.additionHandlers.forEach((handler) => handler(item));
|
|
});
|
|
this.callSubscriptions();
|
|
}
|
|
remove(...items) {
|
|
items.forEach((item) => {
|
|
this.value.delete(item);
|
|
this.genericRemovalHandlers.forEach((handler) => handler(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);
|
|
}
|
|
handleRemovals(handler) {
|
|
this.genericRemovalHandlers.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));
|
|
this.additionHandlers = /* @__PURE__ */ new Set();
|
|
this.removalHandlers = /* @__PURE__ */ new Map();
|
|
this.genericRemovalHandlers = /* @__PURE__ */ new Set();
|
|
}
|
|
// 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();
|
|
this.genericRemovalHandlers.forEach((handler) => handler(item));
|
|
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);
|
|
}
|
|
handleRemovals(handler) {
|
|
this.genericRemovalHandlers.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.subscribeSilent(() => 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 instanceof KeyboardEvent)) return;
|
|
if (e.key != "Enter") return;
|
|
value(e);
|
|
});
|
|
break;
|
|
}
|
|
default: {
|
|
element.addEventListener(directiveValue, value);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case "keystroke": {
|
|
element.addEventListener("keydown", (e) => {
|
|
if (!(e instanceof KeyboardEvent)) return;
|
|
if (e.metaKey == false && e.ctrlKey == false) return;
|
|
if (e.key != directiveValue) return;
|
|
value(e);
|
|
});
|
|
break;
|
|
}
|
|
case "subscribe": {
|
|
const state = value;
|
|
state.subscribe(
|
|
(newValue) => element[directiveValue] = newValue
|
|
);
|
|
break;
|
|
}
|
|
case "bind": {
|
|
const state = value;
|
|
state.subscribe(
|
|
(newValue) => element[directiveValue] = newValue
|
|
);
|
|
element.addEventListener(
|
|
"input",
|
|
() => state.value = element[directiveValue]
|
|
);
|
|
break;
|
|
}
|
|
case "toggle": {
|
|
if (value.subscribe) {
|
|
const state = value;
|
|
state.subscribe(
|
|
(newValue) => element.toggleAttribute(directiveValue, newValue)
|
|
);
|
|
} else {
|
|
element.toggleAttribute(directiveValue, value);
|
|
}
|
|
break;
|
|
}
|
|
case "set": {
|
|
const state = value;
|
|
state.subscribe(
|
|
(newValue) => element.setAttribute(directiveValue, newValue)
|
|
);
|
|
break;
|
|
}
|
|
case "children": {
|
|
switch (directiveValue) {
|
|
case "set": {
|
|
const state = value;
|
|
state.subscribe((newValue) => {
|
|
element.innerHTML = "";
|
|
element.append(...[newValue].flat());
|
|
});
|
|
break;
|
|
}
|
|
case "append":
|
|
case "prepend": {
|
|
try {
|
|
const [listState, toElement] = value;
|
|
listState.handleAddition((newItem) => {
|
|
const child = toElement(newItem);
|
|
listState.handleRemoval(
|
|
newItem,
|
|
() => child.remove()
|
|
);
|
|
if (directiveValue == "append") {
|
|
element.append(child);
|
|
} else if (directiveValue == "prepend") {
|
|
element.prepend(child);
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error(error);
|
|
throw `error: cannot process subscribe:children directive.
|
|
Usage: "children:append={[list, converter]}"; you can find a more detailed example in the documentation.`;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
element.setAttribute(attributename, value);
|
|
}
|
|
});
|
|
children.filter((x) => x).forEach((child) => element.append(child));
|
|
return element;
|
|
}
|
|
|
|
// src/View/translations.ts
|
|
var englishTranslations = {
|
|
updater: {
|
|
migrated: "Migrated"
|
|
},
|
|
general: {
|
|
deleteItemButtonAudioLabel: "delete item",
|
|
searchButtonAudioLabel: "search",
|
|
searchButtonClearAudioLabel: "clear search query",
|
|
abortButton: "Abort",
|
|
applyButton: "Apply",
|
|
backButton: "Back",
|
|
cancelButton: "Cancel",
|
|
confirmButton: "Confirm",
|
|
continueButton: "Continue",
|
|
closeButton: "Close",
|
|
deleteButton: "Delete",
|
|
exitButton: "Exit",
|
|
fullscreenButton: "Fullscreen",
|
|
refreshButton: "Refresh",
|
|
saveButton: "Save",
|
|
setButton: "Set",
|
|
selectAllLabel: "Select all",
|
|
filterOrCreateLabel: (type) => `Search or create ${type}`,
|
|
createLabel: (query) => `Create "${query}"`,
|
|
reloadAppButton: "Reload App",
|
|
fileVersionLabel: "Version",
|
|
searchLabel: "Search",
|
|
searchSuggestionsLabel: "Recent searches",
|
|
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: "Coordination Tool",
|
|
greeting: (name) => `Hi ${name}`,
|
|
///
|
|
serverAddress: "Server address",
|
|
serverAddressPlaceholder: "wss://192.168.0.69:3000",
|
|
connectAudioLabel: "connect to server",
|
|
disconnectAudioLabel: "disconnect from server",
|
|
manageConnectionsAudioLabel: "manage connections",
|
|
settingsButton: "Settings",
|
|
manageStorageButton: "Manage storage",
|
|
transferDataButton: "Transfer or export data",
|
|
updateButton: (version) => `Update to ${version}`,
|
|
///
|
|
typeChat: "chat",
|
|
noChatsPlaceholder: "No chats"
|
|
},
|
|
settings: {
|
|
pages: {
|
|
appearance: "Appearance",
|
|
account: "Account",
|
|
regional: "Language & Region",
|
|
info: "About CT"
|
|
},
|
|
themes: {
|
|
dynamic: "Dynamic",
|
|
dark: "Dark",
|
|
light: "Light",
|
|
system: "Device theme",
|
|
black: "Black"
|
|
},
|
|
account: {
|
|
yourNameLabel: "Your name",
|
|
yourNamePlaceholder: "Jane Doe",
|
|
setNameButtonAudioLabel: "set name"
|
|
},
|
|
regional: {
|
|
language: "Language",
|
|
firstDayOfWeekLabel: "First day of week"
|
|
},
|
|
about: {
|
|
version: "Version",
|
|
checkUpdatesButton: "Check for updates"
|
|
}
|
|
},
|
|
onboarding: {
|
|
connectionHeadline: "Connect to UDN",
|
|
connectionDescription: "To transfer files and send messages, connect to the Universal Decentralized Network (UDN).",
|
|
connectButton: "Connect",
|
|
connectionNextButton: (isConnected) => isConnected ? "Continue" : "Set up without UDN",
|
|
///
|
|
transferOptionsHeadline: "Copy Your Data",
|
|
transferOptionsDescription: "If you already use the Coordination Tool on another device, copy your identity here. You can use both devices at the same time.",
|
|
transferOptionTransfer: "Copy my data here",
|
|
transferOptionNew: "Set up as new device",
|
|
//
|
|
nameHeadline: "Your Name",
|
|
nameDescription: "This is what others see when you send messages. You can always change your name in settings.",
|
|
//
|
|
transferHeadline: "Data Transfer",
|
|
transferDescription: "On your other device, prepare to transfer your identity and personal data alongside any chats you wish to copy. Then, enter the displayed data here. You can find the data transfer on the home screen of the other device.",
|
|
transferButton: "Copy data"
|
|
},
|
|
connectionModal: {
|
|
connectionModalHeadline: "Manage Connections",
|
|
///
|
|
connectButtonAudioLabel: "connect"
|
|
},
|
|
dataTransferModal: {
|
|
transferDataHeadline: "Data Transfer",
|
|
sendHeadline: "Transfer to other device",
|
|
receiveHeadline: "Transfer to This Device",
|
|
exportHeadline: "Export Data",
|
|
importHeadline: "Import Data",
|
|
selectionDescription: "Select the data that you want to transfer.",
|
|
exportSelectionDescription: "Select the data that you want to export.",
|
|
dataEntryDescription: "Enter this data on the other device.",
|
|
dataEntryInputDescription: "Enter the data displayed on the other device.",
|
|
///
|
|
fromThisDeviceButton: "Send to other device",
|
|
toThisDeviceButton: "Send to this device",
|
|
exportButton: "Export to file",
|
|
importButton: "Import from file",
|
|
///
|
|
generalHeadline: "General",
|
|
connectionData: "Connection data",
|
|
settingsData: "Identity & personal data",
|
|
chatsHeadline: "Chats",
|
|
noChatsPlaceholder: "No chats",
|
|
///
|
|
transferChannelHeadline: "Transfer Chanel",
|
|
transferKeyHeadline: "Transfer Encryption Key",
|
|
sendButton: "Send",
|
|
sendAgainButton: "Send again",
|
|
///
|
|
filesSentProgress: (progress, total) => `Sent ${progress} of ${total} files`,
|
|
filesReceivedProgress: (progress, total) => `Received ${progress} of ${total} files`,
|
|
//
|
|
exportKey: "Encryption key",
|
|
exportKeyConfirmation: "Confirm",
|
|
downloadFileButton: "Download",
|
|
importFileButton: "Import",
|
|
decryptImportButton: "Decrypt",
|
|
incorrectPassphraseError: "The encryption key is incorrect"
|
|
},
|
|
storage: {
|
|
noItemSelected: "No item selected",
|
|
notAFile: "(not a file)",
|
|
contentEmpty: "(empty)",
|
|
usageLabel: "Usage",
|
|
usageVauleLabel: (used, max) => `${used} of ${max} MB`,
|
|
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",
|
|
nameLabel: "Chat name",
|
|
setNameButtonAudioLabel: "set name",
|
|
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",
|
|
randomizeKey: "Randomize",
|
|
setColorButtonAudioLabel: "set color",
|
|
deleteChatButton: "Delete entire chat",
|
|
deleteChatHeadline: "Delete chat?",
|
|
deleteChatDescription: "This will delete all messages, tasks, and other data related to this chat from device."
|
|
},
|
|
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",
|
|
cancelReplyAudioLabel: "cancel reply",
|
|
sentBy: "Sent by",
|
|
timeSent: "Time sent",
|
|
channel: "Channel",
|
|
messageContent: "Message content",
|
|
copyMessageButton: "Copy message",
|
|
resendMessageButton: "Resend message",
|
|
decryptMessageButton: "Decrypt message",
|
|
replyToMessageButton: "Reply to 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",
|
|
//
|
|
replyPrefixLabel: "Replies: ",
|
|
replyHeaderLabel: (message) => `Replies to "${message}"`
|
|
},
|
|
task: {
|
|
noBoardSelected: "No board selected",
|
|
boardNotFound: "Board not found",
|
|
typeBoard: "board",
|
|
///
|
|
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",
|
|
//
|
|
eventNext: "Up next"
|
|
}
|
|
}
|
|
};
|
|
var allTranslations = {
|
|
en: englishTranslations,
|
|
de: {
|
|
updater: {
|
|
migrated: "Migriert"
|
|
},
|
|
general: {
|
|
deleteItemButtonAudioLabel: "element l\xF6schen",
|
|
searchButtonAudioLabel: "suchen",
|
|
searchButtonClearAudioLabel: "suche zur\xFCcksetzen",
|
|
abortButton: "Abbrechen",
|
|
applyButton: "Anwenden",
|
|
backButton: "Zur\xFCck",
|
|
cancelButton: "Abbrechen",
|
|
continueButton: "Weiter",
|
|
confirmButton: "Best\xE4tigen",
|
|
closeButton: "Schlie\xDFen",
|
|
deleteButton: "L\xF6schen",
|
|
exitButton: "Schlie\xDFen",
|
|
fullscreenButton: "Vollbild",
|
|
refreshButton: "Aktualisieren",
|
|
saveButton: "Speichern",
|
|
setButton: "OK",
|
|
selectAllLabel: "Alle ausw\xE4hlen",
|
|
filterOrCreateLabel: (type) => `${type} suchen oder erstellen`,
|
|
createLabel: (query) => `"${query}" erstellen`,
|
|
reloadAppButton: "Neu laden",
|
|
fileVersionLabel: "Version",
|
|
searchLabel: "Suche",
|
|
searchSuggestionsLabel: "Vorherige Suchen",
|
|
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"]
|
|
}
|
|
},
|
|
homePage: {
|
|
appName: "Coordination Tool",
|
|
greeting: (name) => `Hallo ${name}`,
|
|
serverAddress: "Serveradresse",
|
|
serverAddressPlaceholder: "wss://192.168.0.69:3000",
|
|
connectAudioLabel: "mit Server verbinden",
|
|
disconnectAudioLabel: "vom Server trennen",
|
|
manageConnectionsAudioLabel: "Verbindungen verwalten",
|
|
settingsButton: "Einstellungen",
|
|
manageStorageButton: "Daten verwalten",
|
|
transferDataButton: "Daten \xFCbertragen",
|
|
updateButton: (version) => `Aktualisieren: ${version}`,
|
|
typeChat: "Chat",
|
|
noChatsPlaceholder: "Keine Chats"
|
|
},
|
|
settings: {
|
|
pages: {
|
|
appearance: "Erscheinungsbild",
|
|
account: "Konto",
|
|
regional: "Sprache & Region",
|
|
info: "\xDCber CT"
|
|
},
|
|
themes: {
|
|
dynamic: "Dynamisch",
|
|
dark: "Dunkel",
|
|
light: "Hell",
|
|
system: "Ger\xE4teeinstellung",
|
|
black: "Schwarz"
|
|
},
|
|
account: {
|
|
yourNameLabel: "Dein Name",
|
|
yourNamePlaceholder: "Max Mustermann",
|
|
setNameButtonAudioLabel: "Namen festlegen"
|
|
},
|
|
regional: {
|
|
language: "Sprache",
|
|
firstDayOfWeekLabel: "Erster Wochentag"
|
|
},
|
|
about: {
|
|
version: "Version",
|
|
checkUpdatesButton: "Nach Updates suchen"
|
|
}
|
|
},
|
|
onboarding: {
|
|
connectionHeadline: "Mit UDN verbinden",
|
|
connectionDescription: "Um Daten und Nachichten zu verschicken, verbinde dich mit dem Universellen Dezentralen Netzwerk (UDN).",
|
|
connectButton: "Verbinden",
|
|
connectionNextButton: (isConnected) => isConnected ? "Weiter" : "Ohne UDN einrichten",
|
|
///
|
|
transferOptionsHeadline: "Deine Daten kopieren",
|
|
transferOptionsDescription: "Wenn du Coordination Tool bereits auf einem anderen Ger\xE4t verwendest, kopiere deine Kontodaten. Du kannst beide Ger\xE4te gleichzeitig verwenden.",
|
|
transferOptionTransfer: "Daten \xFCbertragen",
|
|
transferOptionNew: "Als neues Ger\xE4t enrichten",
|
|
//
|
|
nameHeadline: "Dein Name",
|
|
nameDescription: "Diesen Namen sehen andere, wenn du Nachrichten versendest. Du kannst deinen Namen jederzeit in den Einstellungen \xE4ndern.",
|
|
//
|
|
transferHeadline: "Daten\xFCbertragung",
|
|
transferDescription: "Verwende die Funktion zur Daten\xFCbertragung auf deinem anderen Get\xE4t, um deine Identit\xE4t und pers\xF6nlichen Daten zu kopieren. Gib die dazu angezeigten Daten unten ein. Du findest die Daten\xFCbertragung auf der Startseite des anderen Ger\xE4ts.",
|
|
transferButton: "Daten kopieren"
|
|
},
|
|
connectionModal: {
|
|
connectionModalHeadline: "Verbindungen verwalten",
|
|
connectButtonAudioLabel: "verbinden"
|
|
},
|
|
dataTransferModal: {
|
|
transferDataHeadline: "Daten\xFCbertragung",
|
|
sendHeadline: "An anderes Ger\xE4t senden",
|
|
receiveHeadline: "An dieses Ger\xE4t senden",
|
|
exportHeadline: "Daten exportieren",
|
|
importHeadline: "Daten importieren",
|
|
selectionDescription: "W\xE4hle die Daten aus, die du \xFCbertragen m\xF6chtest.",
|
|
exportSelectionDescription: "W\xE4hle die Daten aus, die du exportieren m\xF6chtest.",
|
|
dataEntryDescription: "Gib diese Informationen auf dem anderen Ger\xE4t ein.",
|
|
dataEntryInputDescription: "Gib die auf dem anderen Ger\xE4t angezeigten Informationen ein.",
|
|
fromThisDeviceButton: "An anderes Ger\xE4t senden",
|
|
toThisDeviceButton: "An dieses Ger\xE4t senden",
|
|
exportButton: "Als Datei exportieren",
|
|
importButton: "Aus Datei importieren",
|
|
generalHeadline: "Allgemein",
|
|
connectionData: "Verbindungsdaten",
|
|
settingsData: "Identit\xE4t und pers\xF6nliche Daten",
|
|
chatsHeadline: "Chats",
|
|
noChatsPlaceholder: "Keine Chats",
|
|
transferChannelHeadline: "\xDCbertragungskanal",
|
|
transferKeyHeadline: "Schl\xFCssel",
|
|
sendButton: "Senden",
|
|
sendAgainButton: "Erneut senden",
|
|
filesSentProgress: (progress, total) => `${progress} von ${total} Dateien gesendet`,
|
|
filesReceivedProgress: (progress, total) => `${progress} von ${total} Dateien empfangen`,
|
|
exportKey: "Schl\xFCssel",
|
|
exportKeyConfirmation: "Schl\xFCssel best\xE4tigen",
|
|
downloadFileButton: "Herunterladen",
|
|
importFileButton: "Importieren",
|
|
decryptImportButton: "Entschl\xFCsseln",
|
|
incorrectPassphraseError: "Der Schl\xFCssel ist nicht korrekt"
|
|
},
|
|
storage: {
|
|
noItemSelected: "Kein Element ausgew\xE4hlt",
|
|
notAFile: "(keine Datei)",
|
|
contentEmpty: "(leer)",
|
|
usageLabel: "Speicher",
|
|
usageVauleLabel: (used, max) => `${used} von ${max} MB belegt`,
|
|
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",
|
|
nameLabel: "Name",
|
|
setNameButtonAudioLabel: "namen 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",
|
|
randomizeKey: "Zuf\xE4llig generieren",
|
|
setColorButtonAudioLabel: "Farbe festlegen",
|
|
deleteChatButton: "Gesamten Chat l\xF6schen",
|
|
deleteChatHeadline: "Gesamten Chat l\xF6schen?",
|
|
deleteChatDescription: "Dies l\xF6scht alle Nachrichten, Aufgaben und alle anderen Daten, die zu diesem Chat geh\xF6ren."
|
|
},
|
|
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",
|
|
cancelReplyAudioLabel: "antwort abbrechen",
|
|
sentBy: "Gesendet von",
|
|
timeSent: "Sendezeit",
|
|
channel: "Kanal",
|
|
messageContent: "Nachrichteninhalt",
|
|
copyMessageButton: "Nachricht kopieren",
|
|
resendMessageButton: "Nachricht erneut senden",
|
|
decryptMessageButton: "Nachricht entschl\xFCsseln",
|
|
replyToMessageButton: "Auf Nachricht antworten",
|
|
deleteMessageButton: "Nachricht l\xF6schen",
|
|
//
|
|
thumbsUpReaction: "Reaktion: Daumen hoch",
|
|
checkReaction: "Reaktion: Haken",
|
|
stopReaction: "Reaktion: Stopp",
|
|
attentionReaction: "Reaktion: Ausrufezeichen",
|
|
doubleAttentionReaction: "Reaktion: doppeltes Ausrufezeichen",
|
|
questionReaction: "Reaktion: Fragezeichen",
|
|
replyPrefixLabel: "Antworten: ",
|
|
replyHeaderLabel: (message) => `Antworten an "${message}"`
|
|
},
|
|
task: {
|
|
noBoardSelected: "Kein Board ausgew\xE4hlt",
|
|
boardNotFound: "Board nicht gefunden",
|
|
typeBoard: "Board",
|
|
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",
|
|
eventNext: "Als n\xE4chstes"
|
|
}
|
|
}
|
|
},
|
|
es: {
|
|
updater: {
|
|
migrated: "Migrado"
|
|
},
|
|
general: {
|
|
deleteItemButtonAudioLabel: "eliminar elemento",
|
|
searchButtonAudioLabel: "buscar",
|
|
searchButtonClearAudioLabel: "borrar b\xFCsqueda",
|
|
abortButton: "Abortar",
|
|
applyButton: "Guardar",
|
|
backButton: "Atr\xE1s",
|
|
cancelButton: "Cancelar",
|
|
continueButton: "Continuar",
|
|
confirmButton: "Confirmar",
|
|
closeButton: "Cerrar",
|
|
deleteButton: "Borrar",
|
|
exitButton: "Salir",
|
|
fullscreenButton: "Pantalla completa",
|
|
refreshButton: "Actualizar",
|
|
saveButton: "Guardar",
|
|
setButton: "OK",
|
|
selectAllLabel: "Seleccionar todos",
|
|
filterOrCreateLabel: (type) => `Buscar o crear ${type}`,
|
|
createLabel: (query) => `Crear "${query}"`,
|
|
reloadAppButton: "Recargar app",
|
|
fileVersionLabel: "Versi\xF3n",
|
|
searchLabel: "Buscar",
|
|
searchSuggestionsLabel: "B\xFAsquedas anteriores",
|
|
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"]
|
|
}
|
|
},
|
|
homePage: {
|
|
appName: "Coordination Tool",
|
|
greeting: (name) => `Hola ${name}`,
|
|
serverAddress: "Direcci\xF3n del servidor",
|
|
serverAddressPlaceholder: "wss://192.168.0.69:3000",
|
|
connectAudioLabel: "conectar al servidor",
|
|
disconnectAudioLabel: "desconectar del servidor",
|
|
manageConnectionsAudioLabel: "gestionar conexiones",
|
|
settingsButton: "Ajustes",
|
|
manageStorageButton: "Gestionar almacenamiento",
|
|
transferDataButton: "Enviar o exportar archivos",
|
|
updateButton: (version) => `Actualizar a ${version}`,
|
|
typeChat: "chat",
|
|
noChatsPlaceholder: "No hay chats"
|
|
},
|
|
settings: {
|
|
pages: {
|
|
appearance: "Aspecto",
|
|
account: "Cuenta",
|
|
regional: "Idioma y Regi\xF3n",
|
|
info: "Sobre CT"
|
|
},
|
|
themes: {
|
|
dynamic: "Din\xE1mico",
|
|
dark: "Oscuro",
|
|
light: "Claro",
|
|
system: "Seg\xFAn dispositivo",
|
|
black: "Negro"
|
|
},
|
|
account: {
|
|
yourNameLabel: "Tu nombre",
|
|
yourNamePlaceholder: "Juan P\xE9rez",
|
|
setNameButtonAudioLabel: "establecer nombre"
|
|
},
|
|
regional: {
|
|
language: "Idioma",
|
|
firstDayOfWeekLabel: "Primer d\xEDa de la semana"
|
|
},
|
|
about: {
|
|
version: "Versi\xF3n",
|
|
checkUpdatesButton: "Buscar actualisaciones"
|
|
}
|
|
},
|
|
onboarding: {
|
|
connectionHeadline: "Conectar al UDN",
|
|
connectionDescription: "Para transferir datos y enviar mensajes, con\xE9ctate a la Red Descentralizada Universal (UDN).",
|
|
connectButton: "Conectar",
|
|
connectionNextButton: (isConnected) => isConnected ? "Continuar" : "Configurar sin UDN",
|
|
///
|
|
transferOptionsHeadline: "Copiar tus datos",
|
|
transferOptionsDescription: "Si ya usas el Coordination Tool en otro disposistivo, copia tu idantidad aqu\xED. Puedes usar ambos dispositivos al mismo tiempo.",
|
|
transferOptionTransfer: "Copiar mis datos aqu\xED",
|
|
transferOptionNew: "Configurar como dispositivo nuevo",
|
|
//
|
|
nameHeadline: "Tu nombre",
|
|
nameDescription: "Esto es lo que ven otros cuando env\xEDas menajes. Siempre lo puedes cambiar en las ajustes.",
|
|
//
|
|
transferHeadline: "Transferencia de datos",
|
|
transferDescription: "En el otro dispositivo, prepara la transferencia de tu identidad y datos personales. Ingresa los datos que se muestran a continuaci\xF3n. La opci\xF3n de la transferencia de datos se encuentra en la pantalla inicial en el otro dispositivo.",
|
|
transferButton: "Copiar datos"
|
|
},
|
|
connectionModal: {
|
|
connectionModalHeadline: "Gestionar Conexiones",
|
|
connectButtonAudioLabel: "conectar"
|
|
},
|
|
dataTransferModal: {
|
|
transferDataHeadline: "Transferencia de datos",
|
|
sendHeadline: "Enviar a otro dispositivo",
|
|
receiveHeadline: "Enviar a este dispositivo",
|
|
exportHeadline: "Exportar archivo",
|
|
importHeadline: "Importar archivo",
|
|
selectionDescription: "Selecciona los datos que quieres transferir.",
|
|
exportSelectionDescription: "Selecciona los datos que quieres exportar.",
|
|
dataEntryDescription: "Introduce estos datos en el otro dispositivo.",
|
|
dataEntryInputDescription: "Introduce los datos mostrados en el otro dispositivo.",
|
|
fromThisDeviceButton: "Enviar a otro dispositivo",
|
|
toThisDeviceButton: "Enviar a este dispositivo",
|
|
exportButton: "Exportar archivo",
|
|
importButton: "Importar archivo",
|
|
generalHeadline: "General",
|
|
connectionData: "Datos de conexi\xF3n",
|
|
settingsData: "Identidad y datos personales",
|
|
chatsHeadline: "Chats",
|
|
noChatsPlaceholder: "No hay chats",
|
|
transferChannelHeadline: "Canal de Transferencia",
|
|
transferKeyHeadline: "Clave de encriptaci\xF3n de transferencia",
|
|
sendButton: "Enviar",
|
|
sendAgainButton: "Enviar otra vez",
|
|
filesSentProgress: (progress, total) => `${progress} de ${total} enviados`,
|
|
filesReceivedProgress: (progress, total) => `${progress} de ${total} recibidos`,
|
|
exportKey: "Clave de encriptaci\xF3n",
|
|
exportKeyConfirmation: "Confirmar clave",
|
|
downloadFileButton: "Descargar",
|
|
importFileButton: "Importar",
|
|
decryptImportButton: "Descifrar",
|
|
incorrectPassphraseError: "La clave no es correcta"
|
|
},
|
|
storage: {
|
|
noItemSelected: "Ning\xFAn elemento seleccionado",
|
|
notAFile: "(no es un archivo)",
|
|
contentEmpty: "(vac\xEDo)",
|
|
usageLabel: "Almacenamiento",
|
|
usageVauleLabel: (used, max) => `${used} de ${max} MB en uso`,
|
|
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",
|
|
nameLabel: "Nombre del chat",
|
|
setNameButtonAudioLabel: "establecer nombre",
|
|
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",
|
|
randomizeKey: "Aleatorizar",
|
|
setColorButtonAudioLabel: "establecer color",
|
|
deleteChatButton: "Eliminar todo el chat",
|
|
deleteChatHeadline: "\xBFEliminar chat?",
|
|
deleteChatDescription: "\xBFQuieres borrar todas las mensajes, tareas y otros datos que pertenecen a este 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",
|
|
cancelReplyAudioLabel: "abortar la respuesta",
|
|
sentBy: "Enviado por",
|
|
timeSent: "Hora de env\xEDo",
|
|
channel: "Canal",
|
|
messageContent: "Contenido del mensaje",
|
|
copyMessageButton: "Copiar mensaje",
|
|
resendMessageButton: "Reenviar mensaje",
|
|
decryptMessageButton: "Desencriptar mensaje",
|
|
replyToMessageButton: "Responder al 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",
|
|
replyPrefixLabel: "Respuestas: ",
|
|
replyHeaderLabel: (message) => `Respuestas a "${message}"`
|
|
},
|
|
task: {
|
|
noBoardSelected: "Ning\xFAn tablero seleccionado",
|
|
boardNotFound: "Tablero no encontrado",
|
|
typeBoard: "board",
|
|
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",
|
|
eventNext: "A continuaci\xF3n"
|
|
}
|
|
}
|
|
}
|
|
};
|
|
var languageNames = {
|
|
en: "English",
|
|
de: "Deutsch",
|
|
es: "Espa\xF1ol"
|
|
};
|
|
|
|
// src/Model/Utility/typeSafety.ts
|
|
function checkIsValidObject(object) {
|
|
return object.dataVersion == DATA_VERSION;
|
|
}
|
|
function checkMatchesObjectStructure(objectToCheck, reference) {
|
|
if (typeof reference != "object") {
|
|
return typeof objectToCheck == typeof reference;
|
|
}
|
|
for (const key of Object.keys(reference)) {
|
|
const requiredType = typeof reference[key];
|
|
const actualType = typeof objectToCheck[key];
|
|
if (requiredType != actualType) return false;
|
|
if (Array.isArray(reference[key]) || reference[key] instanceof Set) {
|
|
if (objectToCheck[key].length == 0) continue;
|
|
if (objectToCheck[key].size == 0) continue;
|
|
const [firstOfObjectToCheck] = objectToCheck[key];
|
|
const [fisrtOfReference] = reference[key];
|
|
const doesFirstItemMatch = checkMatchesObjectStructure(
|
|
firstOfObjectToCheck,
|
|
fisrtOfReference
|
|
);
|
|
if (doesFirstItemMatch == false) return false;
|
|
} else if (requiredType == "object") {
|
|
const doesNestedObjectMatch = checkMatchesObjectStructure(
|
|
objectToCheck[key],
|
|
reference[key]
|
|
);
|
|
if (doesNestedObjectMatch == false) return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
var DATA_VERSION = "2610";
|
|
|
|
// src/Model/Utility/utility.ts
|
|
function generateRandomToken(length) {
|
|
const array = new Uint8Array(length);
|
|
crypto.getRandomValues(array);
|
|
const string = array.join("");
|
|
return string.substring(0, length);
|
|
}
|
|
function createTimestamp() {
|
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
}
|
|
function isoToDateString(dateISOString) {
|
|
const [year, month, date] = dateISOString.split("-");
|
|
const paddedDate = padZero(date ?? "", 2);
|
|
return paddedDate;
|
|
}
|
|
function isoToMonthString(dateISOString) {
|
|
const [year, month, _] = dateISOString.split("-");
|
|
return formatMonthStringFromParts(year, month);
|
|
}
|
|
function formatISO(date) {
|
|
return formatISOFromParts(
|
|
date.getFullYear(),
|
|
date.getMonth() + 1,
|
|
date.getDate()
|
|
);
|
|
}
|
|
function formatISOFromParts(year, month, date) {
|
|
return [year, month, date].map((x, i) => i == 0 ? x : padZero(x.toString(), 2)).join("-");
|
|
}
|
|
function formatMonthStringFromParts(year = "", month = "") {
|
|
const paddedYear = padZero(year, 4);
|
|
const paddedMonth = padZero(month, 2);
|
|
return `${paddedYear}-${paddedMonth}`;
|
|
}
|
|
function formatTime(date) {
|
|
return [date.getHours(), date.getMinutes()].map((x) => padZero(x.toString(), 2)).join(":");
|
|
}
|
|
function checkDoesObjectMatchSearch(query, getStringsOfObject, object) {
|
|
if (query == "") return true;
|
|
const stringsInObject = getStringsOfObject(object);
|
|
const wordsInObject = [];
|
|
for (const string of stringsInObject) {
|
|
const lowercaseWordsInString = string.toString().toLowerCase().split(" ").filter((word) => word != "");
|
|
wordsInObject.push(...lowercaseWordsInString);
|
|
}
|
|
const lowercaseWordsInQuery = query.toLowerCase().split(" ").filter((word) => word != "");
|
|
for (const queryWord of lowercaseWordsInQuery) {
|
|
if (queryWord[0] == "-") {
|
|
const wordContent = queryWord.substring(1);
|
|
if (wordsInObject.includes(wordContent)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (wordsInObject.includes(queryWord) == false) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function implementFilter(allItems, matches, query, itemToString) {
|
|
function update() {
|
|
matches.clear();
|
|
allItems.value.forEach((item) => {
|
|
if (!itemToString(item).toLowerCase().includes(query.value.toLowerCase()))
|
|
return;
|
|
matches.add(item);
|
|
allItems.handleRemoval(item, () => {
|
|
matches.remove(item);
|
|
});
|
|
});
|
|
}
|
|
query.subscribe(update);
|
|
allItems.handleAddition(update);
|
|
}
|
|
var HandlerManager = class {
|
|
constructor() {
|
|
this.handlers = /* @__PURE__ */ new Map();
|
|
// manage
|
|
this.setHandler = (id, handler) => {
|
|
this.handlers.set(id, handler);
|
|
};
|
|
this.deleteHandler = (id) => {
|
|
this.handlers.delete(id);
|
|
};
|
|
// trigger
|
|
this.trigger = (item) => {
|
|
[...this.handlers.values()].forEach((handler) => handler(item));
|
|
};
|
|
}
|
|
};
|
|
var IndexManager = class {
|
|
// init
|
|
constructor(itemToString) {
|
|
this.sortedStrings = [];
|
|
// methods
|
|
this.update = (items) => {
|
|
this.sortedStrings = [];
|
|
let strings = [];
|
|
for (const item of items) {
|
|
const string = this.itemToString(item);
|
|
strings.push(string);
|
|
}
|
|
this.sortedStrings = strings.sort(localeCompare);
|
|
};
|
|
this.getIndex = (item) => {
|
|
const string = this.itemToString(item);
|
|
const index = this.sortedStrings.indexOf(string);
|
|
return index;
|
|
};
|
|
this.itemToString = itemToString;
|
|
}
|
|
};
|
|
function bytesToMB(bytes) {
|
|
return bytes / (1024 * 1024);
|
|
}
|
|
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 localeCompare(a, b) {
|
|
return a.localeCompare(b);
|
|
}
|
|
function implementPinchZoom(canvas, data) {
|
|
let pinching = false;
|
|
let dragging = false;
|
|
let lastElement = void 0;
|
|
let initialDistance, initialZoom, initialX, initialY, initialTouchX, initialTouchY;
|
|
function reset() {
|
|
initialDistance = 0;
|
|
initialZoom = 1;
|
|
initialX = 0;
|
|
initialY = 0;
|
|
initialTouchX = 0;
|
|
initialTouchY = 0;
|
|
}
|
|
reset();
|
|
const MIN = 0.25;
|
|
const MAX = 5;
|
|
const element = () => canvas.querySelector(".zoom");
|
|
const distance = (e) => Math.hypot(
|
|
e.touches[0].pageX - e.touches[1].pageX,
|
|
e.touches[0].pageY - e.touches[1].pageY
|
|
);
|
|
const point = (e, direction, i) => e.touches[i][direction == "x" ? "clientX" : "clientY"];
|
|
const midpoint = (e, direction) => (point(e, direction, 0) + point(e, direction, 1)) / 2;
|
|
function apply(zoom, _offset) {
|
|
if (zoom < MIN) return apply(MIN, _offset);
|
|
if (zoom > MAX) return apply(5, _offset);
|
|
let { x, y } = data.value;
|
|
if (_offset) [x, y] = _offset;
|
|
data.value = {
|
|
zoom,
|
|
x,
|
|
y
|
|
};
|
|
}
|
|
data.subscribe((data2) => {
|
|
const el = element();
|
|
if (!el) return;
|
|
el.style.transform = `scale(${data2.zoom.toString()}) translate(${data2.x}px, ${data2.y}px)`;
|
|
});
|
|
canvas.addEventListener("wheel", (event) => {
|
|
event.preventDefault();
|
|
initialZoom = data.value.zoom;
|
|
initialX = data.value.x;
|
|
initialY = data.value.y;
|
|
apply(data.value.zoom - event.deltaY * 5e-3);
|
|
});
|
|
canvas.addEventListener("scroll", (event) => {
|
|
event.preventDefault();
|
|
});
|
|
canvas.addEventListener("mousedown", (event) => {
|
|
const target = event.target;
|
|
console.log(target);
|
|
if (!target || !target.classList.contains("allow-drag-move")) return;
|
|
event.preventDefault();
|
|
initialZoom = data.value.zoom;
|
|
initialX = data.value.x;
|
|
initialY = data.value.y;
|
|
initialTouchX = event.clientX;
|
|
initialTouchY = event.clientY;
|
|
dragging = true;
|
|
});
|
|
canvas.addEventListener("mouseup", () => {
|
|
dragging = false;
|
|
});
|
|
canvas.addEventListener("mousemove", (event) => {
|
|
if (!dragging) return;
|
|
event.preventDefault();
|
|
apply(data.value.zoom, [
|
|
initialX + (event.clientX - initialTouchX) / initialZoom,
|
|
initialY + (event.clientY - initialTouchY) / initialZoom
|
|
]);
|
|
});
|
|
canvas.addEventListener("touchstart", (event) => {
|
|
document.activeElement?.blur();
|
|
const el = element();
|
|
if (!el) return;
|
|
if (lastElement != void 0 && lastElement != el) reset();
|
|
lastElement = el;
|
|
initialZoom = data.value.zoom;
|
|
initialX = data.value.x;
|
|
initialY = data.value.y;
|
|
if (event.touches.length != 2) {
|
|
pinching = false;
|
|
initialTouchX = event.touches[0].clientX;
|
|
initialTouchY = event.touches[0].clientY;
|
|
return;
|
|
}
|
|
pinching = true;
|
|
initialDistance = distance(event);
|
|
initialTouchX = midpoint(event, "x");
|
|
initialTouchY = midpoint(event, "y");
|
|
});
|
|
canvas.addEventListener("touchmove", (event) => {
|
|
if (!pinching) {
|
|
apply(data.value.zoom, [
|
|
initialX + (event.touches[0].clientX - initialTouchX) / initialZoom,
|
|
initialY + (event.touches[0].clientY - initialTouchY) / initialZoom
|
|
]);
|
|
return;
|
|
}
|
|
if (event.touches.length < 2) return;
|
|
event.preventDefault();
|
|
const currentDistance = distance(event);
|
|
const midX = midpoint(event, "x");
|
|
const midY = midpoint(event, "y");
|
|
const ratio = currentDistance / initialDistance;
|
|
const difference = currentDistance - initialDistance;
|
|
apply(initialZoom * ratio, [
|
|
initialX + (midX - difference - initialTouchX) / initialZoom / 2,
|
|
initialY + (midY - difference - initialTouchY) / initialZoom / 2
|
|
]);
|
|
});
|
|
}
|
|
function collectObjectValuesForKey(key, converter, objects) {
|
|
const values = /* @__PURE__ */ new Set();
|
|
for (const object of objects) {
|
|
const stringEntryObject = converter(object);
|
|
const stringEntryObjectValue = stringEntryObject[key];
|
|
if (stringEntryObjectValue == void 0) continue;
|
|
values.add(stringEntryObjectValue.toString());
|
|
}
|
|
return [...values.values()];
|
|
}
|
|
function checkDoesObjectMatchReference(reference, stringEntryObject, explicitEmptyValue = false) {
|
|
reference_entry_loop: for (const referenceEntry of Object.entries(
|
|
reference
|
|
)) {
|
|
const [referenceKey, referenceValue] = referenceEntry;
|
|
const stringEntryObjectValue = stringEntryObject[referenceKey];
|
|
if (referenceValue == void 0) return false;
|
|
if (referenceValue[0] == "-") {
|
|
const strippedReferenceValue = referenceValue.toString().substring(1);
|
|
if (strippedReferenceValue == "" && stringEntryObjectValue != void 0 && stringEntryObjectValue != "") {
|
|
return false;
|
|
}
|
|
if (stringEntryObjectValue == strippedReferenceValue) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (explicitEmptyValue == false) {
|
|
if (referenceValue == "" && (stringEntryObjectValue == void 0 || stringEntryObjectValue == "")) {
|
|
return false;
|
|
} else if (referenceValue == "") {
|
|
continue reference_entry_loop;
|
|
}
|
|
}
|
|
if (stringEntryObjectValue != referenceValue) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// src/ViewModel/Global/coreViewModel.ts
|
|
var CoreViewModel = class _CoreViewModel {
|
|
// init
|
|
constructor(storageModel2, settingsModel2, connectionModel2, chatListModel2, fileTransferModel2) {
|
|
this.storageModel = storageModel2;
|
|
this.settingsModel = settingsModel2;
|
|
this.connectionModel = connectionModel2;
|
|
this.chatListModel = chatListModel2;
|
|
this.fileTransferModel = fileTransferModel2;
|
|
this.swRegistration = void 0;
|
|
this.version = new State("");
|
|
this.latestVersion = new State("");
|
|
this.noUpdateAvailable = createProxyState(
|
|
[this.latestVersion],
|
|
() => {
|
|
return this.latestVersion.value == "" || this.latestVersion.value == this.version.value;
|
|
}
|
|
);
|
|
this.updateText = new State("");
|
|
// CONTEXT
|
|
this.contextStack = /* @__PURE__ */ new Map();
|
|
this.closeContext = (contextId, fromHistoryEvent = false) => {
|
|
if (!this.contexts.map((context) => context.contextId).includes(contextId))
|
|
return;
|
|
let exitNext = false;
|
|
while (this.contexts.length > 0) {
|
|
const currentContext = this.context;
|
|
if (!currentContext) break;
|
|
const response = currentContext.handleContextClose(fromHistoryEvent);
|
|
if (response == false) break;
|
|
this.contextStack.delete(currentContext.contextId);
|
|
if (response == true) {
|
|
exitNext = true;
|
|
continue;
|
|
} else if (exitNext == true) {
|
|
break;
|
|
} else if (currentContext.contextId == contextId) {
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
this.handleKeyDown = (e) => {
|
|
if (!(e instanceof KeyboardEvent))
|
|
return console.trace("NOT A KEY EVENT");
|
|
if (_CoreViewModel.checkIsKeystroke(e) == false) return;
|
|
document.body.setAttribute("keystroke-active", "");
|
|
e.preventDefault();
|
|
const contexts = this.contexts;
|
|
while (contexts.length > 0) {
|
|
const currentContext = contexts.pop();
|
|
if (!currentContext) return;
|
|
const isHandled = currentContext.handleKeystroke(e);
|
|
if (isHandled == true) break;
|
|
}
|
|
};
|
|
this.handleKeyUp = () => {
|
|
document.body.removeAttribute("keystroke-active");
|
|
};
|
|
// CHRON
|
|
this.chronHandlerManager = new HandlerManager();
|
|
this.todayDate = new State(/* @__PURE__ */ new Date());
|
|
this.time = new State("99:99");
|
|
this.startChron = () => {
|
|
setInterval(this.chronHandlerManager.trigger, 1e3);
|
|
};
|
|
this.handleChron = () => {
|
|
const newDate = /* @__PURE__ */ new Date();
|
|
if (this.unwrappedTodayDate.toDateString() != newDate.toDateString())
|
|
this.todayDate.value = /* @__PURE__ */ new Date();
|
|
if (formatTime(newDate) != this.time.value)
|
|
this.time.value = formatTime(newDate);
|
|
};
|
|
// DRAG & DROP
|
|
this.draggedObject = new State(void 0);
|
|
// OFFLINE MODE & UPDATE
|
|
this.configureServiceWorker = async () => {
|
|
if ("serviceWorker" in navigator) {
|
|
try {
|
|
this.swRegistration = await navigator.serviceWorker.register(
|
|
"sw.js",
|
|
{
|
|
scope: "/"
|
|
}
|
|
);
|
|
if (this.swRegistration.active) {
|
|
console.log("Service worker active");
|
|
}
|
|
} catch (error) {
|
|
console.error(`Could not install service worker: ${error}`);
|
|
}
|
|
}
|
|
};
|
|
this.checkUpdates = () => {
|
|
fetch("/version").then(async (response) => {
|
|
this.version.value = await response.text();
|
|
});
|
|
fetch("/latestVersion").then(async (response) => {
|
|
this.latestVersion.value = (await response.text()).replace(
|
|
"\n",
|
|
""
|
|
);
|
|
});
|
|
};
|
|
this.update = async () => {
|
|
if (!this.swRegistration) return;
|
|
await this.swRegistration.update();
|
|
setTimeout(() => {
|
|
window.location.reload();
|
|
}, 500);
|
|
};
|
|
this.translations = allTranslations[settingsModel2.language] || allTranslations.en;
|
|
this.latestVersion.subscribe((latestVersion) => {
|
|
this.updateText.value = this.translations.homePage.updateButton(latestVersion);
|
|
});
|
|
document.body.addEventListener("keydown", this.handleKeyDown);
|
|
document.body.addEventListener("keyup", this.handleKeyUp);
|
|
window.onpopstate = () => {
|
|
if (!this.context) return;
|
|
this.closeContext(this.context.contextId, true);
|
|
};
|
|
this.configureServiceWorker();
|
|
this.checkUpdates();
|
|
this.startChron();
|
|
this.chronHandlerManager.setHandler(
|
|
"core-view-model",
|
|
this.handleChron
|
|
);
|
|
}
|
|
get contexts() {
|
|
return [...this.contextStack.values()];
|
|
}
|
|
get context() {
|
|
return this.contexts.pop();
|
|
}
|
|
set context(context) {
|
|
if (this.contextStack.has(context.contextId)) return;
|
|
this.contextStack.set(context.contextId, context);
|
|
history.pushState({ id: context.contextId }, "");
|
|
}
|
|
get unwrappedTodayDate() {
|
|
return new Date(this.todayDate.value);
|
|
}
|
|
// util
|
|
static checkIsKeystroke(e) {
|
|
return (e.metaKey || e.altKey) && e.ctrlKey;
|
|
}
|
|
};
|
|
var Context = class {
|
|
constructor(contextDebugDescription) {
|
|
this.contextDebugDescription = contextDebugDescription;
|
|
this.contextId = v4_default();
|
|
this.keystrokes = /* @__PURE__ */ new Map();
|
|
this.handleKeystroke = (e) => {
|
|
const fn = this.keystrokes.get(
|
|
e.key.toLowerCase()
|
|
);
|
|
if (!fn) return false;
|
|
fn();
|
|
return true;
|
|
};
|
|
this.close = () => {
|
|
};
|
|
this.handleContextClose = (fromHistoryEvent) => {
|
|
};
|
|
this.registerKeyStroke = (key, fn) => {
|
|
this.keystrokes.set(key, fn);
|
|
};
|
|
}
|
|
};
|
|
var ContextHost = class extends Context {
|
|
constructor(contextDebugDescription, coreViewModel2) {
|
|
super(contextDebugDescription);
|
|
this.coreViewModel = coreViewModel2;
|
|
this.contexts = /* @__PURE__ */ new Map();
|
|
this.currentContext = new State(void 0);
|
|
this.registerContext = (key, context) => {
|
|
this.contexts.set(key, context);
|
|
};
|
|
this.closeCurrentContext = () => {
|
|
if (this.currentContext.value) {
|
|
this.coreViewModel.closeContext(
|
|
this.currentContext.value.contextId
|
|
);
|
|
}
|
|
this.currentContext.value = void 0;
|
|
};
|
|
this.updateContexts = () => {
|
|
if (this.isOpen == false) return;
|
|
const selection = this.contextSelection;
|
|
if (!selection) return;
|
|
const selectedContext = this.contexts.get(selection);
|
|
if (!selectedContext) return;
|
|
if (selectedContext != this.currentContext.value) {
|
|
this.closeCurrentContext();
|
|
}
|
|
this.coreViewModel.context = selectedContext;
|
|
this.currentContext.value = selectedContext;
|
|
};
|
|
}
|
|
get isOpen() {
|
|
return false;
|
|
}
|
|
get contextSelection() {
|
|
return void 0;
|
|
}
|
|
};
|
|
|
|
// src/View/keystrokes.ts
|
|
function getKeySymbol(keystroke) {
|
|
switch (keystroke) {
|
|
case "backspace":
|
|
return "\u232B";
|
|
case "enter":
|
|
return "\u23CE";
|
|
default:
|
|
return keystroke;
|
|
}
|
|
}
|
|
|
|
// src/ViewModel/Pages/homeViewModel.ts
|
|
var HomeViewModel = class extends Context {
|
|
constructor(coreViewModel2, settingsViewModel2, fileTransferViewModel2, storageViewModel2, connectionViewModel2) {
|
|
super("home");
|
|
this.coreViewModel = coreViewModel2;
|
|
this.settingsViewModel = settingsViewModel2;
|
|
this.fileTransferViewModel = fileTransferViewModel2;
|
|
this.storageViewModel = storageViewModel2;
|
|
this.connectionViewModel = connectionViewModel2;
|
|
this.coreViewModel.context = this;
|
|
this.registerKeyStroke(
|
|
"," /* Settings */,
|
|
this.settingsViewModel.showSettingsModal
|
|
);
|
|
this.registerKeyStroke(
|
|
"t",
|
|
this.fileTransferViewModel.showDirectionSelectionModal
|
|
);
|
|
this.registerKeyStroke("e", this.storageViewModel.showStorageModal);
|
|
this.registerKeyStroke("x", this.connectionViewModel.disconnect);
|
|
this.registerKeyStroke("c", this.connectionViewModel.connect);
|
|
}
|
|
};
|
|
|
|
// src/Model/Global/storageModel.ts
|
|
var PATH_COMPONENT_SEPARATOR = "\\";
|
|
var StorageModel = class _StorageModel {
|
|
// init
|
|
constructor() {
|
|
this.storageEntryTree = {};
|
|
// read
|
|
this.read = (pathComponents) => {
|
|
const pathString = _StorageModel.pathComponentsToString(
|
|
...pathComponents
|
|
);
|
|
return localStorage.getItem(pathString);
|
|
};
|
|
this.list = (pathComponents) => {
|
|
let currentParent = this.storageEntryTree;
|
|
for (const component of pathComponents) {
|
|
const nextParent = currentParent[component];
|
|
if (nextParent == void 0) return [];
|
|
currentParent = nextParent;
|
|
}
|
|
return [...Object.keys(currentParent).sort(localeCompare)];
|
|
};
|
|
// write
|
|
this.write = (pathComponents, value) => {
|
|
const pathString = _StorageModel.pathComponentsToString(
|
|
...pathComponents
|
|
);
|
|
localStorage.setItem(pathString, value);
|
|
this.updateTree(...pathComponents);
|
|
};
|
|
this.remove = (pathComponents, shouldInitialize = true) => {
|
|
const pathString = _StorageModel.pathComponentsToString(
|
|
...pathComponents
|
|
);
|
|
localStorage.removeItem(pathString);
|
|
if (shouldInitialize == true) {
|
|
this.initializeTree();
|
|
}
|
|
};
|
|
this.rename = (sourcePathComponents, destinationPathComponents, shouldInitialize = true) => {
|
|
const content = this.read(sourcePathComponents);
|
|
if (content == null) return false;
|
|
this.write(destinationPathComponents, content);
|
|
this.remove(sourcePathComponents);
|
|
if (shouldInitialize == true) {
|
|
this.initializeTree();
|
|
}
|
|
return true;
|
|
};
|
|
// recursion
|
|
this.recurse = (rootDirectory, fn) => {
|
|
loop_over_files: for (const key of Object.keys(localStorage)) {
|
|
const pathComponentsOfCurrentEntity = _StorageModel.stringToPathComponents(key);
|
|
loop_over_path_components: for (let i = 0; i < rootDirectory.length; i++) {
|
|
if (!pathComponentsOfCurrentEntity[i]) continue loop_over_files;
|
|
if (pathComponentsOfCurrentEntity[i] != rootDirectory[i])
|
|
continue loop_over_files;
|
|
}
|
|
fn(pathComponentsOfCurrentEntity);
|
|
}
|
|
this.initializeTree();
|
|
};
|
|
this.removeRecursively = (pathComponents) => {
|
|
this.recurse(
|
|
pathComponents,
|
|
(path) => this.remove(path, false)
|
|
);
|
|
this.initializeTree();
|
|
};
|
|
this.renameRecursively = (sourcePathComponents, destinationPathComponents) => {
|
|
this.recurse(sourcePathComponents, (path) => {
|
|
const relativePathOfCurrentEntity = path.slice(
|
|
sourcePathComponents.length
|
|
);
|
|
const destinationPathComponentsOfCurrentEntity = [
|
|
...destinationPathComponents,
|
|
...relativePathOfCurrentEntity
|
|
];
|
|
this.rename(path, destinationPathComponentsOfCurrentEntity, false);
|
|
});
|
|
this.initializeTree();
|
|
};
|
|
// stringifiable
|
|
this.writeStringifiable = (pathComponents, value) => {
|
|
const valueString = stringify(value);
|
|
this.write(pathComponents, valueString);
|
|
};
|
|
this.readStringifiable = (pathComponents, reference) => {
|
|
const valueString = this.read(pathComponents);
|
|
if (!valueString) return null;
|
|
const object = parseValidObject(valueString, reference);
|
|
if (object == null) return null;
|
|
return object;
|
|
};
|
|
// cleaning & usage
|
|
this.removeJunk = () => {
|
|
this.recurse([], (path) => {
|
|
if (path[0] == DATA_VERSION) return;
|
|
this.remove(path);
|
|
});
|
|
};
|
|
this.determineCapacity = () => {
|
|
const KEY = "storage-capacity-check";
|
|
let chunk = "0";
|
|
for (let i = 0; i < 1024 * 256; i++) {
|
|
chunk += "0";
|
|
}
|
|
localStorage.setItem(KEY, "");
|
|
while (true) {
|
|
try {
|
|
const current = localStorage.getItem(KEY);
|
|
console.log(current);
|
|
localStorage.setItem(KEY, current + chunk);
|
|
} catch (e) {
|
|
const capacity = this.calculateUsage();
|
|
localStorage.removeItem(KEY);
|
|
return capacity;
|
|
}
|
|
}
|
|
};
|
|
this.calculateUsage = () => {
|
|
let data = JSON.stringify(localStorage);
|
|
const encoder = new TextEncoder();
|
|
const encoded = encoder.encode(data);
|
|
const bytes = encoded.byteLength;
|
|
const mb = bytesToMB(bytes);
|
|
const rounded = Math.round(mb * 100) / 100;
|
|
return rounded;
|
|
};
|
|
// tree
|
|
this.initializeTree = () => {
|
|
console.log("initializing tree");
|
|
this.storageEntryTree = {};
|
|
for (const key of Object.keys(localStorage)) {
|
|
const components = _StorageModel.stringToPathComponents(key);
|
|
this.updateTree(...components);
|
|
}
|
|
};
|
|
this.updateTree = (...pathComponents) => {
|
|
let currentParent = this.storageEntryTree;
|
|
for (const pathPart of pathComponents) {
|
|
if (!currentParent[pathPart]) {
|
|
currentParent[pathPart] = {};
|
|
}
|
|
currentParent = currentParent[pathPart];
|
|
}
|
|
};
|
|
this.printTree = () => {
|
|
return stringify(this.storageEntryTree);
|
|
};
|
|
// upgrade
|
|
this.upgrade = () => {
|
|
};
|
|
this.initializeTree();
|
|
this.upgrade();
|
|
}
|
|
static {
|
|
// utility
|
|
this.getFileName = (pathComponents) => {
|
|
return pathComponents[pathComponents.length - 1] || "\\";
|
|
};
|
|
}
|
|
static {
|
|
this.getFileNameFromString = (pathString) => {
|
|
const pathComponents = this.stringToPathComponents(pathString);
|
|
return pathComponents[pathComponents.length - 1] || "\\";
|
|
};
|
|
}
|
|
static {
|
|
this.pathComponentsToString = (...pathComponents) => {
|
|
return pathComponents.filter((x) => x != "").join(PATH_COMPONENT_SEPARATOR);
|
|
};
|
|
}
|
|
static {
|
|
this.stringToPathComponents = (string) => {
|
|
return string.split(PATH_COMPONENT_SEPARATOR).filter((x) => x != "");
|
|
};
|
|
}
|
|
static {
|
|
this.join = (...items) => {
|
|
let allComponents = [];
|
|
for (const item of items) {
|
|
const parts = this.stringToPathComponents(item);
|
|
allComponents.push(...parts);
|
|
}
|
|
return _StorageModel.pathComponentsToString(...allComponents);
|
|
};
|
|
}
|
|
static getPath(locationName, filePath) {
|
|
return [DATA_VERSION, locationName, ...filePath];
|
|
}
|
|
};
|
|
var filePaths = {
|
|
connectionModel: {
|
|
socketAddress: ["socket-address"],
|
|
reconnectAddress: ["reconnect-address"],
|
|
outbox: ["outbox"],
|
|
mailboxes: ["mailboxes"],
|
|
previousAddresses: ["previous-addresses"]
|
|
},
|
|
chat: {
|
|
base: [],
|
|
chatBase: (id) => [id],
|
|
info: (id) => [...filePaths.chat.chatBase(id), "info"],
|
|
color: (id) => [...filePaths.chat.chatBase(id), "color"],
|
|
messages: (id) => [...filePaths.chat.chatBase(id), "messages"],
|
|
reactions: (id) => [
|
|
...filePaths.chat.chatBase(id),
|
|
"reactions"
|
|
],
|
|
previousFilter: (id) => [
|
|
...filePaths.chat.chatBase(id),
|
|
"previous-filter"
|
|
],
|
|
lastUsedPage: (id) => [
|
|
...filePaths.chat.chatBase(id),
|
|
"last-used-page"
|
|
],
|
|
files: (id) => [...filePaths.chat.chatBase(id), "files"]
|
|
},
|
|
notificationModel: {
|
|
base: []
|
|
},
|
|
settingsModel: {
|
|
username: ["user-name"],
|
|
userid: ["user-id"],
|
|
firstDayOfWeek: ["first-day-of-week"],
|
|
language: ["language"],
|
|
theme: ["theme"]
|
|
}
|
|
};
|
|
|
|
// src/ViewModel/Global/storageViewModel.ts
|
|
var StorageViewModel = class extends Context {
|
|
// init
|
|
constructor(coreViewModel2) {
|
|
super("storage");
|
|
this.coreViewModel = coreViewModel2;
|
|
// state
|
|
this.isShowingStorageModal = new State(false);
|
|
this.selectedPath = new State(
|
|
PATH_COMPONENT_SEPARATOR
|
|
);
|
|
this.didMakeChanges = new State(false);
|
|
this.occupiedSpaceMB = new State(0);
|
|
this.maximumSpaceMB = new State(0);
|
|
this.lastDeletedItemPath = new State("");
|
|
// methods
|
|
this.getSelectedItemContent = () => {
|
|
const path = StorageModel.stringToPathComponents(
|
|
this.selectedPath.value
|
|
);
|
|
const content = this.coreViewModel.storageModel.read(path);
|
|
return (content ?? this.coreViewModel.translations.storage.notAFile) || this.coreViewModel.translations.storage.contentEmpty;
|
|
};
|
|
this.deleteSelectedItem = () => {
|
|
const path = StorageModel.stringToPathComponents(
|
|
this.selectedPath.value
|
|
);
|
|
this.lastDeletedItemPath.value = this.selectedPath.value;
|
|
this.coreViewModel.storageModel.removeRecursively(path);
|
|
this.didMakeChanges.value = true;
|
|
};
|
|
this.removeJunk = () => {
|
|
this.coreViewModel.storageModel.removeJunk();
|
|
this.selectedPath.value = PATH_COMPONENT_SEPARATOR;
|
|
};
|
|
this.calculateUsage = () => {
|
|
this.occupiedSpaceMB.value = this.coreViewModel.storageModel.calculateUsage();
|
|
this.maximumSpaceMB.value = this.coreViewModel.storageModel.determineCapacity();
|
|
};
|
|
// view
|
|
this.showStorageModal = () => {
|
|
this.coreViewModel.context = this;
|
|
this.isShowingStorageModal.value = true;
|
|
this.calculateUsage();
|
|
};
|
|
// exit
|
|
this.close = () => {
|
|
this.coreViewModel.closeContext(this.contextId);
|
|
};
|
|
this.handleContextClose = () => {
|
|
if (this.didMakeChanges.value == true) {
|
|
window.location.reload();
|
|
return;
|
|
}
|
|
this.isShowingStorageModal.value = false;
|
|
};
|
|
this.selectedFileName = createProxyState(
|
|
[this.selectedPath],
|
|
() => StorageModel.getFileNameFromString(this.selectedPath.value)
|
|
);
|
|
this.selectedFileContent = createProxyState(
|
|
[this.selectedPath],
|
|
() => this.getSelectedItemContent()
|
|
);
|
|
this.registerKeyStroke("backspace" /* CloseOrCancel */, this.close);
|
|
}
|
|
};
|
|
|
|
// src/Model/Global/settingsModel.ts
|
|
var SettingsModel = class _SettingsModel {
|
|
// init
|
|
constructor(storageModel2) {
|
|
this.storageModel = storageModel2;
|
|
// data
|
|
this.username = "";
|
|
this.userid = "";
|
|
this.firstDayOfWeek = "";
|
|
this.language = "";
|
|
this.theme = "";
|
|
// storage
|
|
this.storeSetting = (pathName, value) => {
|
|
const path = StorageModel.getPath(
|
|
"settings" /* SettingsModel */,
|
|
filePaths.settingsModel[pathName]
|
|
);
|
|
this.storageModel.write(path, value);
|
|
};
|
|
this.setId = (newValue) => {
|
|
this.userid = newValue;
|
|
this.storeSetting("userid", newValue);
|
|
};
|
|
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.loadId = () => {
|
|
const content = this.readSetting("userid");
|
|
const id = content ?? UUID();
|
|
if (content) this.userid = content;
|
|
else this.setId(id);
|
|
};
|
|
this.loadUsername = () => {
|
|
const content = this.readSetting("username");
|
|
this.username = content ?? "";
|
|
};
|
|
this.loadFirstDayofWeek = () => {
|
|
const content = this.readSetting("firstDayOfWeek");
|
|
this.firstDayOfWeek = content ?? "0";
|
|
};
|
|
this.loadLanguage = () => {
|
|
const content = this.readSetting("language");
|
|
this.language = content ?? _SettingsModel.getSystemLanguage();
|
|
};
|
|
this.loadTheme = () => {
|
|
const content = this.readSetting("theme");
|
|
this.theme = content ?? "system" /* System */;
|
|
};
|
|
this.loadId();
|
|
this.loadUsername();
|
|
this.loadFirstDayofWeek();
|
|
this.loadLanguage();
|
|
this.loadTheme();
|
|
}
|
|
static getSystemLanguage() {
|
|
switch (navigator.language.substring(0, 2)) {
|
|
case "de":
|
|
return "de" /* German */;
|
|
case "es":
|
|
return "es" /* Spanish */;
|
|
default:
|
|
return "en" /* English */;
|
|
}
|
|
}
|
|
};
|
|
var Languages = /* @__PURE__ */ ((Languages2) => {
|
|
Languages2["English"] = "en";
|
|
Languages2["German"] = "de";
|
|
Languages2["Spanish"] = "es";
|
|
return Languages2;
|
|
})(Languages || {});
|
|
|
|
// src/ViewModel/Global/settingsViewModel.ts
|
|
var SettingsViewModel = class _SettingsViewModel extends Context {
|
|
// init
|
|
constructor(coreViewModel2, settingsModel2) {
|
|
super("settings");
|
|
this.coreViewModel = coreViewModel2;
|
|
this.settingsModel = settingsModel2;
|
|
// state
|
|
this.username = new State("");
|
|
this.usernameInput = new State("");
|
|
this.isShowingSettingsModal = new State(false);
|
|
this.selectedModalPage = new State(void 0);
|
|
this.requiresReload = new State(false);
|
|
this.firstDayOfWeek = new State("0");
|
|
this.language = new State("en" /* English */);
|
|
this.theme = new State("system" /* System */);
|
|
// guards
|
|
this.cannotSetName = createProxyState(
|
|
[this.usernameInput],
|
|
() => this.usernameInput.value == "" || this.usernameInput.value == this.coreViewModel.settingsModel.username
|
|
);
|
|
// methods
|
|
this.setName = (name) => {
|
|
if (typeof name == "string") {
|
|
this.usernameInput.value = name;
|
|
}
|
|
this.coreViewModel.settingsModel.setName(this.usernameInput.value);
|
|
this.username.value = this.coreViewModel.settingsModel.username;
|
|
this.usernameInput.callSubscriptions();
|
|
};
|
|
this.setFirstDayofWeek = () => {
|
|
this.coreViewModel.settingsModel.setFirstDayOfWeek(
|
|
this.firstDayOfWeek.value
|
|
);
|
|
};
|
|
this.showSettingsModal = () => {
|
|
this.coreViewModel.context = this;
|
|
this.isShowingSettingsModal.value = true;
|
|
};
|
|
this.showModalPage = (page) => {
|
|
this.selectedModalPage.value = page;
|
|
};
|
|
// view
|
|
this.applyTheme = () => {
|
|
let theme = this.theme.value;
|
|
if (theme == "system" /* System */)
|
|
theme = _SettingsViewModel.getSystemTheme();
|
|
switch (theme) {
|
|
case "dynamic" /* Dynamic */:
|
|
const scene = this.selectDynamicScene();
|
|
this.setScene(scene);
|
|
break;
|
|
case "dark" /* Dark */:
|
|
this.setScene(DynamicSceneNight);
|
|
break;
|
|
case "black" /* Black */:
|
|
this.setScene(DynamicSceneBlack);
|
|
break;
|
|
default:
|
|
this.setScene(DynamicSceneDay);
|
|
}
|
|
};
|
|
this.selectDynamicScene = () => {
|
|
let hour = (/* @__PURE__ */ new Date()).getHours();
|
|
if (hour >= 21 || hour <= 6) {
|
|
return DynamicSceneNight;
|
|
} else if (hour > 6 && hour < 10) {
|
|
return DynamicSceneSunrise;
|
|
} else if (hour < 14) {
|
|
return DynamicSceneDay;
|
|
} else if (hour < 17) {
|
|
return DynamicSceneAfternoon;
|
|
} else {
|
|
return DynamicSceneSunset;
|
|
}
|
|
};
|
|
this.setScene = (scene) => {
|
|
document.body.style.setProperty(
|
|
"--backdrop-grass-filter",
|
|
`brightness(${scene.brightness})`
|
|
);
|
|
function setSkyColor(tone, hue, saturation, luma) {
|
|
document.body.style.setProperty(
|
|
`--sky-${tone}`,
|
|
`hsl(${hue}, ${saturation}%, ${luma}%)`
|
|
);
|
|
}
|
|
setSkyColor(1, scene.hue1, scene.saturation, scene.luma);
|
|
setSkyColor(2, scene.hue2, scene.saturation - 10, scene.luma - 10);
|
|
document.body.setAttribute("theme", scene.baseTheme);
|
|
};
|
|
// exit
|
|
this.close = () => {
|
|
this.coreViewModel.closeContext(this.contextId);
|
|
};
|
|
this.handleContextClose = () => {
|
|
this.isShowingSettingsModal.value = false;
|
|
if (this.requiresReload.value == true) {
|
|
window.location.reload();
|
|
}
|
|
};
|
|
this.username.value = coreViewModel2.settingsModel.username;
|
|
this.usernameInput.value = coreViewModel2.settingsModel.username;
|
|
this.firstDayOfWeek.value = coreViewModel2.settingsModel.firstDayOfWeek;
|
|
this.language.value = coreViewModel2.settingsModel.language;
|
|
this.theme.value = coreViewModel2.settingsModel.theme;
|
|
this.firstDayOfWeek.subscribe(this.setFirstDayofWeek);
|
|
this.language.subscribeSilent((newValue) => {
|
|
this.coreViewModel.settingsModel.setLanguage(newValue);
|
|
this.requiresReload.value = true;
|
|
});
|
|
this.theme.subscribeSilent((newValue) => {
|
|
this.coreViewModel.settingsModel.setTheme(newValue);
|
|
});
|
|
this.theme.subscribe(() => {
|
|
this.applyTheme();
|
|
});
|
|
_SettingsViewModel.generateThemeMedia().addEventListener(
|
|
"change",
|
|
() => this.applyTheme()
|
|
);
|
|
const seconds = (/* @__PURE__ */ new Date()).getSeconds();
|
|
const secondsUntilNewMinute = 60 - seconds;
|
|
setTimeout(() => {
|
|
setInterval(this.applyTheme, 1e3 * 60);
|
|
}, secondsUntilNewMinute);
|
|
this.registerKeyStroke("backspace" /* CloseOrCancel */, this.close);
|
|
}
|
|
static generateThemeMedia() {
|
|
return window.matchMedia("(prefers-color-scheme: dark)");
|
|
}
|
|
static getSystemTheme() {
|
|
const media = _SettingsViewModel.generateThemeMedia();
|
|
return media.matches == true ? "dark" /* Dark */ : "light" /* Light */;
|
|
}
|
|
};
|
|
var DynamicSceneNight = {
|
|
hue1: 215,
|
|
hue2: 220,
|
|
saturation: 50,
|
|
luma: 25,
|
|
brightness: 0.4,
|
|
baseTheme: "dark" /* Dark */
|
|
};
|
|
var DynamicSceneSunrise = {
|
|
hue1: 45,
|
|
hue2: 190,
|
|
saturation: 100,
|
|
luma: 50,
|
|
brightness: 1.2,
|
|
baseTheme: "light" /* Light */
|
|
};
|
|
var DynamicSceneDay = {
|
|
hue1: 190,
|
|
hue2: 230,
|
|
saturation: 100,
|
|
luma: 70,
|
|
brightness: 1.1,
|
|
baseTheme: "light" /* Light */
|
|
};
|
|
var DynamicSceneAfternoon = {
|
|
hue1: 220,
|
|
hue2: 250,
|
|
saturation: 100,
|
|
luma: 50,
|
|
brightness: 0.8,
|
|
baseTheme: "light" /* Light */
|
|
};
|
|
var DynamicSceneSunset = {
|
|
hue1: 20,
|
|
hue2: 260,
|
|
saturation: 100,
|
|
luma: 50,
|
|
brightness: 0.4,
|
|
baseTheme: "dark" /* Dark */
|
|
};
|
|
var DynamicSceneBlack = {
|
|
hue1: 0,
|
|
hue2: 0,
|
|
saturation: 0,
|
|
luma: 0,
|
|
brightness: 0,
|
|
baseTheme: "black" /* Black */
|
|
};
|
|
|
|
// src/ViewModel/Global/onboardingViewModel.ts
|
|
var OnboardingViewModel = class {
|
|
// init
|
|
constructor(connectionViewmodel, fileTransferViewModel2, settingsViewModel2) {
|
|
this.connectionViewmodel = connectionViewmodel;
|
|
this.fileTransferViewModel = fileTransferViewModel2;
|
|
this.settingsViewModel = settingsViewModel2;
|
|
// state
|
|
this.presentedModal = new State(void 0);
|
|
// guards
|
|
this.cannotTransfer = createProxyState(
|
|
[this.connectionViewmodel.isConnected],
|
|
() => !this.connectionViewmodel.isConnected.value
|
|
);
|
|
// navigation
|
|
this.open = () => {
|
|
this.presentedModal.value = 0 /* Connection */;
|
|
};
|
|
this.showTransferOption = () => {
|
|
this.presentedModal.value = 1 /* TransferOrNew */;
|
|
};
|
|
this.setupNew = () => {
|
|
this.presentedModal.value = 2 /* Name */;
|
|
};
|
|
this.showTransferData = () => {
|
|
this.presentedModal.value = 3 /* Transfer */;
|
|
};
|
|
// methods
|
|
this.transferData = () => {
|
|
this.fileTransferViewModel.exitReception = () => this.showTransferData();
|
|
this.fileTransferViewModel.prepareReceivingData();
|
|
this.presentedModal.value = void 0;
|
|
};
|
|
this.finish = () => {
|
|
this.settingsViewModel.setName();
|
|
this.presentedModal.value = void 0;
|
|
};
|
|
if (this.settingsViewModel.settingsModel.username == "") this.open();
|
|
}
|
|
};
|
|
|
|
// src/ViewModel/Global/fileTransferViewModel.ts
|
|
var FileTransferViewModel = class extends Context {
|
|
// init
|
|
constructor(coreViewModel2) {
|
|
super("file-transfer");
|
|
this.coreViewModel = coreViewModel2;
|
|
// state
|
|
this.exitReception = void 0;
|
|
this.presentedModal = new State(void 0);
|
|
this.generalFileOptions = new ListState();
|
|
this.chatFileOptions = new ListState();
|
|
this.selectedPaths = new ListState();
|
|
this.allOptionsSelected = new State(false);
|
|
this.transferChannel = new State("");
|
|
this.transferKey = new State("");
|
|
this.receivingTransferChannel = new State("");
|
|
this.receivingTransferKey = new State("");
|
|
this.filePathsSent = new ListState();
|
|
this.fileTransferTotal = new State(0);
|
|
this.fileTransferProgress = new State(0);
|
|
this.filePathsReceived = new ListState();
|
|
this.filesReceivedCount = createProxyState(
|
|
[this.filePathsReceived],
|
|
() => this.filePathsReceived.value.size
|
|
);
|
|
this.exportKey = new State("");
|
|
this.exportKeyConfirmation = new State("");
|
|
this.importedFile = new State(void 0);
|
|
this.importedFileString = new State("");
|
|
this.importKey = new State("");
|
|
this.isImportKeyCorrect = new State(true);
|
|
// guards
|
|
this.hasNoPathsSelected = createProxyState(
|
|
[this.selectedPaths],
|
|
() => this.selectedPaths.value.size == 0
|
|
);
|
|
this.cannotPrepareToReceive = createProxyState(
|
|
[this.receivingTransferChannel, this.receivingTransferKey],
|
|
() => this.receivingTransferChannel.value == "" || this.receivingTransferKey.value == ""
|
|
);
|
|
this.cannotExitReception = createProxyState(
|
|
[this.filePathsReceived],
|
|
() => this.filePathsReceived.value.size > 0
|
|
);
|
|
this.cannotExport = createProxyState(
|
|
[this.exportKey, this.exportKeyConfirmation],
|
|
() => this.exportKey.value == "" || this.exportKey.value != this.exportKeyConfirmation.value
|
|
);
|
|
this.cannotImport = createProxyState(
|
|
[this.importedFile],
|
|
() => this.importedFile.value == void 0
|
|
);
|
|
this.importDecryptSuccessful = createProxyState(
|
|
[this.importKey],
|
|
() => this.importKey.value == ""
|
|
);
|
|
// handlers
|
|
this.handleReceivedFile = (path) => {
|
|
this.filePathsReceived.add(path);
|
|
this.fileTransferProgress.value = this.filePathsReceived.value.size;
|
|
};
|
|
// methods
|
|
this.getOptions = () => {
|
|
this.generalFileOptions.clear();
|
|
this.chatFileOptions.clear();
|
|
this.selectedPaths.clear();
|
|
this.generalFileOptions.add(
|
|
{
|
|
label: this.coreViewModel.translations.dataTransferModal.connectionData,
|
|
path: StorageModel.getPath(
|
|
"connection" /* ConnectionModel */,
|
|
filePaths.connectionModel.previousAddresses
|
|
)
|
|
},
|
|
{
|
|
label: this.coreViewModel.translations.dataTransferModal.settingsData,
|
|
path: StorageModel.getPath(
|
|
"settings" /* SettingsModel */,
|
|
[]
|
|
)
|
|
}
|
|
);
|
|
const chatModels = this.coreViewModel.chatListModel.chatModels;
|
|
for (const chatModel of chatModels) {
|
|
this.chatFileOptions.add({
|
|
label: chatModel.info.name,
|
|
path: chatModel.getBasePath()
|
|
});
|
|
}
|
|
};
|
|
this.getTransferData = () => {
|
|
const transferData = this.coreViewModel.fileTransferModel.generateTransferData();
|
|
this.transferChannel.value = transferData.channel;
|
|
this.transferKey.value = transferData.key;
|
|
};
|
|
this.downloadFile = async () => {
|
|
if (this.cannotExport.value == true) return;
|
|
const date = formatISO(/* @__PURE__ */ new Date());
|
|
const backup = await this.coreViewModel.fileTransferModel.generateBackup(
|
|
this.selectedPaths.value.values(),
|
|
this.exportKey.value
|
|
);
|
|
const anchor = document.createElement("a");
|
|
anchor.href = window.URL.createObjectURL(backup);
|
|
anchor.download = `ct-${date}.bak`;
|
|
anchor.click();
|
|
};
|
|
this.updateImportSelection = () => {
|
|
const input = document.getElementById("file-transfer-input");
|
|
if (!input) return;
|
|
if (!(input instanceof HTMLInputElement)) return;
|
|
if (!input.files) return;
|
|
const file = input.files[0];
|
|
this.importedFile.value = file;
|
|
};
|
|
this.importFile = () => {
|
|
const file = this.importedFile.value;
|
|
if (!file) return;
|
|
const reader = new FileReader();
|
|
reader.readAsText(file, "utf8");
|
|
reader.onload = () => {
|
|
const result = reader.result;
|
|
if (!result) return;
|
|
this.importedFileString.value = result.toString();
|
|
this.showImportDecryptDataModal();
|
|
};
|
|
};
|
|
this.decryptImport = async () => {
|
|
const fileString = this.importedFileString.value;
|
|
const key = this.importKey.value;
|
|
if (!fileString || !key) return;
|
|
this.importDecryptSuccessful.value = true;
|
|
try {
|
|
await this.coreViewModel.fileTransferModel.handleBackupFile(
|
|
fileString,
|
|
key
|
|
);
|
|
window.location.reload();
|
|
} catch (e) {
|
|
console.error(e);
|
|
this.isImportKeyCorrect.value = false;
|
|
this.importDecryptSuccessful.value = false;
|
|
}
|
|
};
|
|
// view
|
|
this.showDirectionSelectionModal = () => {
|
|
this.coreViewModel.context = this;
|
|
this.presentedModal.value = 0 /* DirectionSelection */;
|
|
this.getOptions();
|
|
};
|
|
this.showFileSelectionModal = () => {
|
|
this.presentedModal.value = 1 /* FileSelection */;
|
|
};
|
|
this.showTransferDataModal = () => {
|
|
this.presentedModal.value = 2 /* TransferDataDisplay */;
|
|
this.getTransferData();
|
|
this.coreViewModel.fileTransferModel.prepareToSend();
|
|
};
|
|
this.correctTransferData = () => {
|
|
if (this.exitReception != void 0) {
|
|
this.presentedModal.value = void 0;
|
|
this.exitReception();
|
|
return;
|
|
}
|
|
this.showTransferDataInputModal();
|
|
};
|
|
this.initiateTransfer = () => {
|
|
this.presentedModal.value = 3 /* TransferDisplay */;
|
|
this.filePathsSent.clear();
|
|
this.coreViewModel.fileTransferModel.sendFiles(
|
|
this.selectedPaths.value.values(),
|
|
(path) => {
|
|
this.filePathsSent.add(path);
|
|
}
|
|
);
|
|
};
|
|
this.showTransferDataInputModal = () => {
|
|
this.presentedModal.value = 4 /* TransferDataInput */;
|
|
};
|
|
this.showExportSelectionModal = () => {
|
|
this.presentedModal.value = 6 /* ExportFileSelection */;
|
|
};
|
|
this.showExportModal = () => {
|
|
this.presentedModal.value = 7 /* Export */;
|
|
};
|
|
this.showImportModal = () => {
|
|
this.presentedModal.value = 8 /* ImportSelection */;
|
|
};
|
|
this.showImportDecryptDataModal = () => {
|
|
this.presentedModal.value = 9 /* ImportDecryptData */;
|
|
};
|
|
this.prepareReceivingData = () => {
|
|
if (this.cannotPrepareToReceive.value == true) return;
|
|
this.presentedModal.value = 5 /* ReceptionDisplay */;
|
|
this.filePathsReceived.clear();
|
|
const transferData = {
|
|
channel: this.receivingTransferChannel.value,
|
|
key: this.receivingTransferKey.value
|
|
};
|
|
this.coreViewModel.fileTransferModel.prepareToReceive(transferData);
|
|
};
|
|
// exit
|
|
this.close = () => {
|
|
this.coreViewModel.closeContext(this.contextId);
|
|
};
|
|
this.handleContextClose = () => {
|
|
this.presentedModal.value = void 0;
|
|
};
|
|
this.coreViewModel.fileTransferModel.transferSizeHandlerManager.setHandler(
|
|
"file-transfer-view-model",
|
|
(count) => this.fileTransferTotal.value = count
|
|
);
|
|
this.coreViewModel.fileTransferModel.fileHandlerManager.setHandler(
|
|
"file-transfer-view-model",
|
|
this.handleReceivedFile
|
|
);
|
|
this.coreViewModel.fileTransferModel.readyToSendHandlerManager.setHandler(
|
|
"file-transfer-view-model",
|
|
() => this.initiateTransfer()
|
|
);
|
|
this.registerKeyStroke("backspace" /* CloseOrCancel */, this.close);
|
|
}
|
|
};
|
|
|
|
// src/ViewModel/Global/contactViewModel.ts
|
|
var ContactListViewModel = class {
|
|
// init
|
|
constructor(contactListModel2, settingsViewModel2) {
|
|
this.contactListModel = contactListModel2;
|
|
this.settingsViewModel = settingsViewModel2;
|
|
// state
|
|
this.contacts = /* @__PURE__ */ new Map();
|
|
// main
|
|
this.updateContact = (id, name) => {
|
|
if (id == this.settingsViewModel.settingsModel.userid) {
|
|
this.settingsViewModel.setName(name);
|
|
}
|
|
const match = this.contacts.get(id);
|
|
if (match) {
|
|
match.name.value = name;
|
|
return match;
|
|
}
|
|
const vm = new ContactViewModel(id, name);
|
|
this.contacts.set(id, vm);
|
|
return vm;
|
|
};
|
|
this.handleContact = (contact) => {
|
|
this.updateContact(...contact);
|
|
};
|
|
this.unwrapContact = (id, knownName) => {
|
|
if (this.contacts.has(id)) return this.contacts.get(id);
|
|
return this.updateContact(id, knownName);
|
|
};
|
|
// load
|
|
this.loadData = () => {
|
|
const contacts = this.contactListModel.loadContacts();
|
|
for (const contact of contacts) {
|
|
const vm = new ContactViewModel(...contact);
|
|
this.contacts.set(vm.id, vm);
|
|
}
|
|
};
|
|
this.loadData();
|
|
this.contactListModel.contactHandlerManager.setHandler(
|
|
"contact-list-view-model",
|
|
this.handleContact
|
|
);
|
|
}
|
|
};
|
|
var ContactViewModel = class {
|
|
// init
|
|
constructor(id, name) {
|
|
this.id = id;
|
|
// state
|
|
this.name = new State("");
|
|
this.name.value = name;
|
|
}
|
|
};
|
|
|
|
// src/ViewModel/Global/connectionViewModel.ts
|
|
var ConnectionViewModel = class {
|
|
// init
|
|
constructor(coreViewModel2) {
|
|
this.coreViewModel = coreViewModel2;
|
|
// state
|
|
this.serverAddressInput = new State("");
|
|
this.isConnected = new State(false);
|
|
this.isShowingConnectionModal = new State(false);
|
|
this.previousAddresses = new ListState();
|
|
// guards
|
|
this.cannotConnect = createProxyState(
|
|
[this.serverAddressInput, this.isConnected],
|
|
() => this.isConnected.value == true && this.serverAddressInput.value == this.coreViewModel.connectionModel.address || this.serverAddressInput.value == ""
|
|
);
|
|
this.cannotDisonnect = createProxyState(
|
|
[this.isConnected],
|
|
() => this.isConnected.value == false
|
|
);
|
|
this.hasNoPreviousConnections = createProxyState(
|
|
[this.previousAddresses],
|
|
() => this.previousAddresses.value.size == 0
|
|
);
|
|
// handlers
|
|
this.connectionChangeHandler = () => {
|
|
this.isConnected.value = this.coreViewModel.connectionModel.isConnected;
|
|
if (this.coreViewModel.connectionModel.isConnected == false) return;
|
|
if (this.coreViewModel.connectionModel.address == void 0) return;
|
|
this.serverAddressInput.value = this.coreViewModel.connectionModel.address;
|
|
if (!this.previousAddresses.value.has(
|
|
this.coreViewModel.connectionModel.address
|
|
)) {
|
|
this.previousAddresses.add(
|
|
this.coreViewModel.connectionModel.address
|
|
);
|
|
}
|
|
this.hideConnectionModal();
|
|
};
|
|
// 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();
|
|
if (this.previousAddresses.value.size > 0) return;
|
|
this.hideConnectionModal();
|
|
};
|
|
// view
|
|
this.showConnectionModal = () => {
|
|
this.isShowingConnectionModal.value = true;
|
|
};
|
|
this.hideConnectionModal = () => {
|
|
this.isShowingConnectionModal.value = false;
|
|
};
|
|
this.updatePreviousAddresses = () => {
|
|
this.previousAddresses.clear();
|
|
this.previousAddresses.add(
|
|
...this.coreViewModel.connectionModel.addresses
|
|
);
|
|
};
|
|
this.updatePreviousAddresses();
|
|
coreViewModel2.connectionModel.connectionChangeHandlerManager.setHandler(
|
|
"connection-view-model",
|
|
this.connectionChangeHandler
|
|
);
|
|
}
|
|
};
|
|
|
|
// src/ViewModel/Pages/taskContainingPageViewModel.ts
|
|
var TaskContainingPageViewModel = class extends Context {
|
|
// init
|
|
constructor(coreViewModel2, chatViewModel, boardsAndTasksModel, contextDebugDescription) {
|
|
super(contextDebugDescription);
|
|
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();
|
|
this.taskCategorySuggestions = new ListState();
|
|
this.taskStatusSuggestions = new ListState();
|
|
// methods
|
|
this.createTaskFromBoardId = (boardId) => {
|
|
const taskFileContent = this.boardsAndTasksModel.createTask(boardId);
|
|
const taskViewModel = new TaskViewModel(
|
|
this.coreViewModel,
|
|
this.chatViewModel,
|
|
this.boardsAndTasksModel,
|
|
this,
|
|
taskFileContent
|
|
);
|
|
taskViewModel.open();
|
|
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, "calendar");
|
|
this.coreViewModel = coreViewModel2;
|
|
this.chatViewModel = chatViewModel;
|
|
this.calendarModel = calendarModel;
|
|
this.boardsAndTasksModel = boardsAndTasksModel;
|
|
// paths
|
|
this.getBasePath = () => {
|
|
return [...this.calendarModel.getViewPath()];
|
|
};
|
|
// state
|
|
this.currentTodayDate = void 0;
|
|
this.selectedYear = new State(0);
|
|
this.selectedMonth = new State(0);
|
|
this.selectedDate = new State(0);
|
|
this.monthGrid = new State(void 0);
|
|
this.nextTask = new State(void 0);
|
|
// methods
|
|
this.createEvent = () => {
|
|
const taskFileContent = this.boardsAndTasksModel.createTask(CALENDAR_EVENT_BOARD_ID);
|
|
taskFileContent.date = formatISOFromParts(
|
|
this.selectedYear.value.toString(),
|
|
this.selectedMonth.value.toString(),
|
|
this.selectedDate.value.toString()
|
|
);
|
|
const taskViewModel = new TaskViewModel(
|
|
this.coreViewModel,
|
|
this.chatViewModel,
|
|
this.boardsAndTasksModel,
|
|
this,
|
|
taskFileContent
|
|
);
|
|
taskViewModel.open();
|
|
this.updateTaskIndices();
|
|
};
|
|
this.getEventsForDate = () => {
|
|
const paddedDate = padZero(this.selectedDate.toString(), 2);
|
|
if (this.monthGrid.value == void 0) {
|
|
return void 0;
|
|
}
|
|
return this.monthGrid.value.days[paddedDate];
|
|
};
|
|
// view
|
|
this.getTaskMapState = (taskFileContent) => {
|
|
if (this.monthGrid.value == null) return null;
|
|
const date = isoToDateString(taskFileContent.date ?? "");
|
|
return this.monthGrid.value.days[date];
|
|
};
|
|
this.showTask = (taskFileContent) => {
|
|
const monthString = isoToMonthString(
|
|
taskFileContent.date ?? ""
|
|
);
|
|
if (monthString == void 0 || monthString != this.monthString) {
|
|
this.removeTaskFromView(taskFileContent);
|
|
this.calendarModel.deleteTaskReference(
|
|
this.monthString,
|
|
taskFileContent.fileId
|
|
);
|
|
return;
|
|
}
|
|
const taskViewModel = new TaskViewModel(
|
|
this.coreViewModel,
|
|
this.chatViewModel,
|
|
this.boardsAndTasksModel,
|
|
this,
|
|
taskFileContent
|
|
);
|
|
const mapState = this.getTaskMapState(taskFileContent);
|
|
this.taskViewModels.handleRemoval(taskViewModel, () => {
|
|
mapState?.remove(taskFileContent.fileId);
|
|
});
|
|
this.taskViewModels.remove(taskFileContent.fileId);
|
|
this.taskViewModels.set(taskFileContent.fileId, taskViewModel);
|
|
mapState?.set(taskFileContent.fileId, taskViewModel);
|
|
this.updateTaskIndices();
|
|
};
|
|
this.removeTaskFromView = (taskFileContent) => {
|
|
this.taskViewModels.remove(taskFileContent.fileId);
|
|
};
|
|
this.showToday = () => {
|
|
const today = this.coreViewModel.todayDate.value;
|
|
this.selectedYear.value = today.getFullYear();
|
|
this.selectedMonth.value = today.getMonth() + 1;
|
|
this.selectedDate.value = today.getDate();
|
|
};
|
|
this.showPreviousMonth = () => {
|
|
this.selectedMonth.value -= 1;
|
|
if (this.selectedMonth.value <= 0) {
|
|
this.selectedYear.value -= 1;
|
|
this.selectedMonth.value = 12;
|
|
}
|
|
};
|
|
this.showNextMonth = () => {
|
|
this.selectedMonth.value += 1;
|
|
if (this.selectedMonth.value >= 13) {
|
|
this.selectedYear.value += 1;
|
|
this.selectedMonth.value = 1;
|
|
}
|
|
};
|
|
this.handleDrop = (year, month, date) => {
|
|
const ISOString = formatISOFromParts(year, month, date);
|
|
const draggedObject = this.coreViewModel.draggedObject.value;
|
|
if (draggedObject instanceof TaskViewModel == false) return;
|
|
draggedObject.setDate(ISOString);
|
|
};
|
|
this.handleChron = () => {
|
|
this.updateMonthGrid();
|
|
this.updateNextTask();
|
|
};
|
|
this.updateMonthGrid = () => {
|
|
const today = formatISO(this.coreViewModel.todayDate.value);
|
|
if (this.currentTodayDate == today) return;
|
|
this.loadMonthTasks();
|
|
this.currentTodayDate = today;
|
|
this.selectedDate.callSubscriptions();
|
|
};
|
|
this.updateNextTask = () => {
|
|
if (this.selectedDateString != this.currentTodayDate) {
|
|
this.nextTask.value = void 0;
|
|
return;
|
|
}
|
|
const tasks = this.getEventsForDate();
|
|
if (tasks == void 0) return;
|
|
const taskArray = [...tasks.value.values()];
|
|
const sorted = taskArray.sort((a, b) => a.index.value - b.index.value);
|
|
for (const task of sorted) {
|
|
if (task.time.value <= this.coreViewModel.time.value) continue;
|
|
this.nextTask.value = task;
|
|
break;
|
|
}
|
|
};
|
|
// load
|
|
this.loadMonthTasks = () => {
|
|
this.monthGrid.value = this.calendarModel.generateMonthGrid(
|
|
this.coreViewModel,
|
|
this.selectedYear.value,
|
|
this.selectedMonth.value,
|
|
() => new MapState()
|
|
);
|
|
const taskIds = this.calendarModel.listTaskIds(
|
|
this.monthString
|
|
);
|
|
for (const taskId of taskIds) {
|
|
const taskFileContent = this.boardsAndTasksModel.getLatestTaskFileContent(taskId);
|
|
if (taskFileContent == null) continue;
|
|
this.showTask(taskFileContent);
|
|
}
|
|
};
|
|
this.loadData = () => {
|
|
};
|
|
// context
|
|
this.handleContextClose = (fromHistoryEvent) => {
|
|
if (!fromHistoryEvent) return;
|
|
return true;
|
|
};
|
|
this.calendarModel = calendarModel;
|
|
this.boardsAndTasksModel = boardsAndTasksModel;
|
|
this.chatViewModel = chatViewModel;
|
|
bulkSubscribe([this.selectedYear, this.selectedMonth], () => {
|
|
this.loadMonthTasks();
|
|
});
|
|
this.selectedDate.subscribeSilent(() => {
|
|
this.updateNextTask();
|
|
});
|
|
boardsAndTasksModel.taskHandlerManager.setHandler(
|
|
"calendar" + this.chatViewModel.chatModel.id,
|
|
(taskFileContent) => {
|
|
this.showTask(taskFileContent);
|
|
}
|
|
);
|
|
this.coreViewModel.chronHandlerManager.setHandler(
|
|
`calendar-${this.chatViewModel.chatModel.id}`,
|
|
this.handleChron
|
|
);
|
|
this.showToday();
|
|
this.registerKeyStroke("-" /* Reset */, this.showToday);
|
|
this.registerKeyStroke("k", this.showPreviousMonth);
|
|
this.registerKeyStroke("l", this.showNextMonth);
|
|
this.registerKeyStroke("a" /* Create */, this.createEvent);
|
|
this.chatViewModel.registerContext("calendar" /* Calendar */, this);
|
|
}
|
|
// data
|
|
get monthString() {
|
|
return formatMonthStringFromParts(
|
|
this.selectedYear.value.toString(),
|
|
this.selectedMonth.value.toString()
|
|
);
|
|
}
|
|
get selectedDateString() {
|
|
return formatISOFromParts(
|
|
this.selectedYear.value,
|
|
this.selectedMonth.value,
|
|
this.selectedDate.value
|
|
);
|
|
}
|
|
};
|
|
|
|
// src/View/viewController.ts
|
|
var Tracker = class {
|
|
};
|
|
var TrackerMap = class {
|
|
constructor() {
|
|
this.trackers = /* @__PURE__ */ new Map();
|
|
this.register = (key) => {
|
|
if (this.trackers.has(key)) {
|
|
return this.trackers.get(key);
|
|
}
|
|
const tracker = new Tracker();
|
|
this.trackers.set(key, tracker);
|
|
return tracker;
|
|
};
|
|
this.setState = (key, stateBuilder) => {
|
|
const tracker = this.register(key);
|
|
if (tracker.state != void 0) {
|
|
return tracker.state;
|
|
} else {
|
|
tracker.state = stateBuilder();
|
|
return tracker.state;
|
|
}
|
|
};
|
|
}
|
|
};
|
|
var ViewController = class {
|
|
static {
|
|
this.chatPages = new Tracker();
|
|
}
|
|
static {
|
|
this.taskPages = new TrackerMap();
|
|
}
|
|
static {
|
|
this.boardPages = new TrackerMap();
|
|
}
|
|
static {
|
|
this.inlineReplies = new TrackerMap();
|
|
}
|
|
static {
|
|
this.scrollToView = (id) => {
|
|
const element = document.getElementById(id);
|
|
if (!element) return;
|
|
element.scrollIntoView();
|
|
element.setAttribute("scrolled", "");
|
|
setTimeout(() => element.removeAttribute("scrolled"), 1e3);
|
|
};
|
|
}
|
|
static {
|
|
this.allowDrop = (event) => {
|
|
event.preventDefault();
|
|
};
|
|
}
|
|
static {
|
|
this.allowDrag = (event) => {
|
|
event.dataTransfer?.setData("text", "");
|
|
};
|
|
}
|
|
static {
|
|
this.reload = () => {
|
|
window.location.reload();
|
|
};
|
|
}
|
|
static {
|
|
this.setFocus = () => {
|
|
const focusedInModal = document.querySelector(
|
|
".modal[open] #focused"
|
|
);
|
|
if (focusedInModal) return focusedInModal.focus();
|
|
document.getElementById("focused")?.focus();
|
|
};
|
|
}
|
|
static {
|
|
this.setFocusWithDelay = () => {
|
|
setTimeout(this.setFocus, 100);
|
|
};
|
|
}
|
|
};
|
|
|
|
// node_modules/uint8array-extras/index.js
|
|
var objectToString = Object.prototype.toString;
|
|
var uint8ArrayStringified = "[object Uint8Array]";
|
|
var arrayBufferStringified = "[object ArrayBuffer]";
|
|
function isType(value, typeConstructor, typeStringified) {
|
|
if (!value) {
|
|
return false;
|
|
}
|
|
if (value.constructor === typeConstructor) {
|
|
return true;
|
|
}
|
|
return objectToString.call(value) === typeStringified;
|
|
}
|
|
function isUint8Array(value) {
|
|
return isType(value, Uint8Array, uint8ArrayStringified);
|
|
}
|
|
function isArrayBuffer(value) {
|
|
return isType(value, ArrayBuffer, arrayBufferStringified);
|
|
}
|
|
function isUint8ArrayOrArrayBuffer(value) {
|
|
return isUint8Array(value) || isArrayBuffer(value);
|
|
}
|
|
function toUint8ArrayIfArrayBuffer(value) {
|
|
return isArrayBuffer(value) ? new Uint8Array(value) : value;
|
|
}
|
|
function assertUint8ArrayOrArrayBuffer(value) {
|
|
if (!isUint8ArrayOrArrayBuffer(value)) {
|
|
throw new TypeError(`Expected \`Uint8Array\` or \`ArrayBuffer\`, got \`${typeof value}\``);
|
|
}
|
|
}
|
|
var cachedDecoders = {
|
|
utf8: new globalThis.TextDecoder("utf8")
|
|
};
|
|
var cachedEncoder = new globalThis.TextEncoder();
|
|
function base64ToBase64Url(base64) {
|
|
return base64.replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
}
|
|
var MAX_BLOCK_SIZE = 65535;
|
|
function uint8ArrayToBase64(array, { urlSafe = false } = {}) {
|
|
assertUint8ArrayOrArrayBuffer(array);
|
|
array = toUint8ArrayIfArrayBuffer(array);
|
|
let base64 = "";
|
|
for (let index = 0; index < array.length; index += MAX_BLOCK_SIZE) {
|
|
const chunk = array.subarray(index, index + MAX_BLOCK_SIZE);
|
|
base64 += globalThis.btoa(String.fromCodePoint.apply(void 0, chunk));
|
|
}
|
|
return urlSafe ? base64ToBase64Url(base64) : base64;
|
|
}
|
|
var byteToHexLookupTable = Array.from({ length: 256 }, (_, index) => index.toString(16).padStart(2, "0"));
|
|
function uint8ArrayToHex(array) {
|
|
assertUint8ArrayOrArrayBuffer(array);
|
|
array = toUint8ArrayIfArrayBuffer(array);
|
|
let hexString = "";
|
|
for (let index = 0; index < array.length; index++) {
|
|
hexString += byteToHexLookupTable[array[index]];
|
|
}
|
|
return hexString;
|
|
}
|
|
|
|
// node_modules/crypto-random-string/index.js
|
|
var maxBytesPerRequest = 65536;
|
|
var maxCharacterSetSize = 65536;
|
|
function fillWithRandomValues(typedArray) {
|
|
const maxElementsPerRequest = maxBytesPerRequest / typedArray.BYTES_PER_ELEMENT;
|
|
for (let offset = 0; offset < typedArray.length; offset += maxElementsPerRequest) {
|
|
crypto.getRandomValues(typedArray.subarray(offset, offset + maxElementsPerRequest));
|
|
}
|
|
return typedArray;
|
|
}
|
|
var randomBytes = (byteLength) => fillWithRandomValues(new Uint8Array(byteLength));
|
|
var generateForCustomCharacters = (length, characters) => {
|
|
const characterCount = characters.length;
|
|
const validSelectorCount = Math.floor(maxCharacterSetSize / characterCount) * characterCount;
|
|
const entropyLength = Math.ceil(1.1 * length * (maxCharacterSetSize / validSelectorCount));
|
|
let string = "";
|
|
let stringLength = 0;
|
|
while (stringLength < length) {
|
|
const entropy = fillWithRandomValues(new Uint16Array(entropyLength));
|
|
for (let index = 0; index < entropyLength; index++) {
|
|
const entropyValue = entropy[index];
|
|
if (entropyValue < validSelectorCount) {
|
|
string += characters[entropyValue % characterCount];
|
|
stringLength++;
|
|
if (stringLength === length) {
|
|
return string;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return string;
|
|
};
|
|
var characterSets = /* @__PURE__ */ new Map([
|
|
["url-safe", [..."abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"]],
|
|
["numeric", [..."0123456789"]],
|
|
["distinguishable", [..."CDEHKMPRTUWXY012458"]],
|
|
["ascii-printable", [..."!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"]],
|
|
["alphanumeric", [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"]]
|
|
]);
|
|
var allowedTypes = /* @__PURE__ */ new Set(["hex", "base64", ...characterSets.keys()]);
|
|
function cryptoRandomString({ length, type, characters }) {
|
|
if (!Number.isSafeInteger(length) || length < 0) {
|
|
throw new TypeError("Expected `length` to be a non-negative integer");
|
|
}
|
|
if (type !== void 0 && characters !== void 0) {
|
|
throw new TypeError("Expected either `type` or `characters`");
|
|
}
|
|
if (characters !== void 0) {
|
|
if (typeof characters !== "string") {
|
|
throw new TypeError("Expected `characters` to be a string");
|
|
}
|
|
const customCharacterSet = [...characters];
|
|
if (customCharacterSet.length === 0) {
|
|
throw new TypeError("Expected `characters` to contain at least 1 character");
|
|
}
|
|
if (customCharacterSet.length > maxCharacterSetSize) {
|
|
throw new TypeError(`Expected \`characters\` to contain at most ${maxCharacterSetSize} characters, got ${customCharacterSet.length}`);
|
|
}
|
|
return generateForCustomCharacters(length, customCharacterSet);
|
|
}
|
|
if (type !== void 0 && !allowedTypes.has(type)) {
|
|
throw new TypeError(`Unknown type: ${type}`);
|
|
}
|
|
const characterSet = characterSets.get(type);
|
|
if (characterSet !== void 0) {
|
|
return generateForCustomCharacters(length, characterSet);
|
|
}
|
|
if (type === "base64") {
|
|
return uint8ArrayToBase64(randomBytes(Math.ceil(length * 0.75))).slice(0, length);
|
|
}
|
|
return uint8ArrayToHex(randomBytes(Math.ceil(length * 0.5))).slice(0, length);
|
|
}
|
|
|
|
// src/Model/Utility/crypto.ts
|
|
var IV_SIZE = 12;
|
|
var ENCRYPTION_ALG = "AES-GCM";
|
|
function random(length) {
|
|
return cryptoRandomString({ length });
|
|
}
|
|
async function encryptString(plaintext, passphrase) {
|
|
if (!window.crypto.subtle) return plaintext;
|
|
const iv = generateIV();
|
|
const key = await importKey(passphrase, "encrypt");
|
|
const encryptedArray = await encrypt(iv, key, plaintext);
|
|
const encryptionData = {
|
|
iv: uInt8ToArray(iv),
|
|
encryptedArray: uInt8ToArray(encryptedArray)
|
|
};
|
|
return btoa(JSON.stringify(encryptionData));
|
|
}
|
|
async function decryptString(cyphertext, passphrase) {
|
|
try {
|
|
const encrypionData = JSON.parse(atob(cyphertext));
|
|
const iv = arrayToUint8(encrypionData.iv);
|
|
const encryptedArray = arrayToUint8(encrypionData.encryptedArray);
|
|
const key = await importKey(passphrase, "decrypt");
|
|
return await decrypt(iv, key, encryptedArray);
|
|
} catch {
|
|
return cyphertext;
|
|
}
|
|
}
|
|
function encode(string) {
|
|
return new TextEncoder().encode(string);
|
|
}
|
|
function decode(array) {
|
|
return new TextDecoder("utf-8").decode(array);
|
|
}
|
|
async function encrypt(iv, key, message) {
|
|
const arrayBuffer = await window.crypto.subtle.encrypt(
|
|
{ name: ENCRYPTION_ALG, iv },
|
|
key,
|
|
encode(message)
|
|
);
|
|
return new Uint8Array(arrayBuffer);
|
|
}
|
|
async function decrypt(iv, key, cyphertext) {
|
|
const arrayBuffer = await crypto.subtle.decrypt(
|
|
{ name: ENCRYPTION_ALG, iv },
|
|
key,
|
|
cyphertext
|
|
);
|
|
return arrayBufferToString(arrayBuffer);
|
|
}
|
|
async function hash(encoded) {
|
|
return await crypto.subtle.digest("SHA-256", encoded);
|
|
}
|
|
function generateIV() {
|
|
return crypto.getRandomValues(new Uint8Array(IV_SIZE));
|
|
}
|
|
async function importKey(passphrase, purpose) {
|
|
return await crypto.subtle.importKey(
|
|
"raw",
|
|
await hash(encode(passphrase)),
|
|
{ name: ENCRYPTION_ALG },
|
|
false,
|
|
[purpose]
|
|
);
|
|
}
|
|
function arrayBufferToString(arrayBuffer) {
|
|
const uInt8Array = new Uint8Array(arrayBuffer);
|
|
return decode(uInt8Array);
|
|
}
|
|
function uInt8ToArray(uInt8Array) {
|
|
return Array.from(uInt8Array);
|
|
}
|
|
function arrayToUint8(array) {
|
|
return new Uint8Array(array);
|
|
}
|
|
|
|
// src/colors.ts
|
|
var Colors = /* @__PURE__ */ ((Colors2) => {
|
|
Colors2["Standard"] = "standard";
|
|
Colors2["Coral"] = "coral";
|
|
Colors2["Yellow"] = "yellow";
|
|
Colors2["Green"] = "green";
|
|
Colors2["LightBlue"] = "lightblue";
|
|
Colors2["Blue"] = "blue";
|
|
Colors2["purple"] = "purple";
|
|
return Colors2;
|
|
})(Colors || {});
|
|
|
|
// src/Model/Chat/chatModel.ts
|
|
var ChatModel = class _ChatModel {
|
|
// init
|
|
constructor(storageModel2, connectionModel2, settingsModel2, chatListModel2, contactListModel2, chatId) {
|
|
this.storageModel = storageModel2;
|
|
this.connectionModel = connectionModel2;
|
|
this.settingsModel = settingsModel2;
|
|
this.chatListModel = chatListModel2;
|
|
this.contactListModel = contactListModel2;
|
|
// handler managers
|
|
this.chatMessageHandlerManager = new HandlerManager();
|
|
this.reactionHandlerManager = new HandlerManager();
|
|
this.changeHandlerManager = new HandlerManager();
|
|
// paths
|
|
this.getBasePath = () => {
|
|
return StorageModel.getPath(
|
|
"chat" /* Chat */,
|
|
filePaths.chat.chatBase(this.id)
|
|
);
|
|
};
|
|
this.getInfoPath = () => {
|
|
return StorageModel.getPath(
|
|
"chat" /* Chat */,
|
|
filePaths.chat.info(this.id)
|
|
);
|
|
};
|
|
this.getColorPath = () => {
|
|
return StorageModel.getPath(
|
|
"chat" /* Chat */,
|
|
filePaths.chat.color(this.id)
|
|
);
|
|
};
|
|
this.getMessageDirPath = () => {
|
|
return StorageModel.getPath(
|
|
"chat" /* Chat */,
|
|
filePaths.chat.messages(this.id)
|
|
);
|
|
};
|
|
this.getMessagePath = (id) => {
|
|
return [...this.getMessageDirPath(), id];
|
|
};
|
|
this.getReactionDirPath = () => {
|
|
return StorageModel.getPath(
|
|
"chat" /* Chat */,
|
|
filePaths.chat.reactions(this.id)
|
|
);
|
|
};
|
|
this.getReactionPath = (id) => {
|
|
return [...this.getReactionDirPath(), id];
|
|
};
|
|
this.getPreviousFilterPath = () => {
|
|
return StorageModel.getPath(
|
|
"chat" /* Chat */,
|
|
filePaths.chat.previousFilter(this.id)
|
|
);
|
|
};
|
|
// handlers
|
|
this.handleMessage = (body) => {
|
|
const chatMessage = parseValidObject(
|
|
body,
|
|
ChatMessageReference
|
|
);
|
|
if (chatMessage == null) return;
|
|
chatMessage.status = "received" /* Received */;
|
|
this.addMessage(chatMessage);
|
|
if (chatMessage.stringifiedFile) return;
|
|
if (chatMessage.senderId == this.settingsModel.userid) return;
|
|
this.setReadStatus(true);
|
|
};
|
|
this.handleReaction = (reaction) => {
|
|
if (!checkMatchesObjectStructure(reaction, ChatMessageReactionReference))
|
|
return;
|
|
const reactionPath = this.getReactionPath(reaction.fileId);
|
|
if (reaction.isDeleting == true) {
|
|
this.storageModel.remove(reactionPath);
|
|
} else {
|
|
this.storageModel.writeStringifiable(reactionPath, reaction);
|
|
}
|
|
this.reactionHandlerManager.trigger(reaction);
|
|
};
|
|
this.handleMessageSent = (chatMessage) => {
|
|
chatMessage.status = "sent" /* Sent */;
|
|
this.addMessage(chatMessage);
|
|
};
|
|
// settings
|
|
this.setName = (name) => {
|
|
this.info.name = name;
|
|
this.storeInfo();
|
|
this.syncInfo();
|
|
this.subscribe();
|
|
};
|
|
this.setSecondaryChannels = (secondaryChannels) => {
|
|
this.info.secondaryChannels = secondaryChannels;
|
|
this.storeInfo();
|
|
};
|
|
this.setEncryptionKey = (key) => {
|
|
this.info.encryptionKey = key;
|
|
this.storeInfo();
|
|
};
|
|
this.setColor = (color) => {
|
|
this.info.color = color;
|
|
this.syncInfo();
|
|
this.storeInfo();
|
|
};
|
|
// messaging
|
|
this.addMessage = async (chatMessage) => {
|
|
await this.decryptMessage(chatMessage);
|
|
this.contactListModel.storeContact(
|
|
chatMessage.senderId,
|
|
chatMessage.senderName
|
|
);
|
|
if (chatMessage.body != "") {
|
|
const messagePath = this.getMessagePath(
|
|
chatMessage.fileId
|
|
);
|
|
this.storageModel.writeStringifiable(messagePath, chatMessage);
|
|
this.chatMessageHandlerManager.trigger(chatMessage);
|
|
}
|
|
this.fileModel.handleStringifiedFileContent(
|
|
chatMessage.stringifiedFile
|
|
);
|
|
};
|
|
this.getSendingData = () => {
|
|
const id = this.settingsModel.userid;
|
|
const senderName = this.settingsModel.username || "?";
|
|
const allChannels = [this.id];
|
|
for (const secondaryChannel of this.info.secondaryChannels) {
|
|
allChannels.push(secondaryChannel);
|
|
}
|
|
const combinedChannel = allChannels.join("/");
|
|
return [id, senderName, combinedChannel];
|
|
};
|
|
this.sendMessage = async (body, inlineReplyId, fileContent) => {
|
|
const [senderId, senderName, combinedChannel] = this.getSendingData();
|
|
const chatMessage = await _ChatModel.createChatMessage(
|
|
combinedChannel,
|
|
senderId,
|
|
senderName,
|
|
this.info.encryptionKey,
|
|
body,
|
|
inlineReplyId,
|
|
fileContent
|
|
);
|
|
this.addMessage(chatMessage);
|
|
this.connectionModel.sendMessageOrStore(chatMessage);
|
|
return chatMessage.fileId;
|
|
};
|
|
this.decryptMessage = async (chatMessage) => {
|
|
const decryptedBody = await decryptString(
|
|
chatMessage.body,
|
|
this.info.encryptionKey
|
|
);
|
|
const decryptedFile = await decryptString(
|
|
chatMessage.stringifiedFile ?? "",
|
|
this.info.encryptionKey
|
|
);
|
|
chatMessage.body = decryptedBody;
|
|
chatMessage.stringifiedFile = decryptedFile;
|
|
};
|
|
this.sendReaction = async (messageId, content, isDeleting = false) => {
|
|
const [senderId, senderName, combinedChannel] = this.getSendingData();
|
|
const reaction = _ChatModel.createMessageReaction(
|
|
messageId,
|
|
senderId,
|
|
senderName,
|
|
content,
|
|
isDeleting
|
|
);
|
|
this.sendMessage("", void 0, reaction);
|
|
this.handleReaction(reaction);
|
|
};
|
|
this.subscribe = () => {
|
|
this.connectionModel.addChannel(this.id);
|
|
};
|
|
this.setReadStatus = (hasUnreadMessages) => {
|
|
this.info.hasUnreadMessages = hasUnreadMessages;
|
|
this.storeInfo();
|
|
};
|
|
// storage
|
|
this.storeInfo = () => {
|
|
this.storageModel.writeStringifiable(this.getInfoPath(), this.info);
|
|
};
|
|
this.syncInfo = () => {
|
|
this.sendMessage("", void 0, this.info);
|
|
};
|
|
this.storeFilter = (filter) => {
|
|
this.storageModel.write(this.getPreviousFilterPath(), filter);
|
|
};
|
|
this.getFilter = () => {
|
|
return this.storageModel.read(this.getPreviousFilterPath()) || "";
|
|
};
|
|
this.delete = () => {
|
|
this.chatListModel.untrackChat(this);
|
|
const dirPath = this.getBasePath();
|
|
this.storageModel.removeRecursively(dirPath);
|
|
};
|
|
// load
|
|
this.loadInfo = () => {
|
|
let info = this.storageModel.readStringifiable(
|
|
this.getInfoPath(),
|
|
ChatInfoReference
|
|
);
|
|
this.info = info || _ChatModel.generateChatInfo("0", "0", "standard" /* Standard */);
|
|
this.storeInfo();
|
|
};
|
|
this.handleInfo = (info) => {
|
|
this.info = info;
|
|
this.changeHandlerManager.trigger(null);
|
|
this.storeInfo();
|
|
};
|
|
this.id = chatId;
|
|
this.loadInfo();
|
|
this.subscribe();
|
|
this.fileModel = new FileModel(
|
|
this.storageModel,
|
|
this.settingsModel,
|
|
this
|
|
);
|
|
}
|
|
/* load function called in constructor */
|
|
get secondaryChannels() {
|
|
return this.info.secondaryChannels.sort(localeCompare);
|
|
}
|
|
get color() {
|
|
const color = this.info.color;
|
|
if ([...Object.values(Colors)].includes(color)) return color;
|
|
return "standard" /* Standard */;
|
|
}
|
|
get messages() {
|
|
const messageIds = this.storageModel.list(
|
|
this.getMessageDirPath()
|
|
);
|
|
if (!Array.isArray(messageIds)) return [];
|
|
const chatMessages = [];
|
|
for (const messageId of messageIds) {
|
|
const messagePath = this.getMessagePath(messageId);
|
|
const chatMessage = this.storageModel.readStringifiable(
|
|
messagePath,
|
|
ChatMessageReference
|
|
);
|
|
if (chatMessage == null) continue;
|
|
chatMessages.push(chatMessage);
|
|
}
|
|
const sorted = chatMessages.sort(
|
|
(a, b) => a.dateSent.localeCompare(b.dateSent)
|
|
);
|
|
return sorted;
|
|
}
|
|
get reactions() {
|
|
const reactionIds = this.storageModel.list(
|
|
this.getReactionDirPath()
|
|
);
|
|
if (!Array.isArray(reactionIds)) return [];
|
|
const reactions = [];
|
|
for (const reactionId of reactionIds) {
|
|
const reactionPath = this.getReactionPath(reactionId);
|
|
const reaction = this.storageModel.readStringifiable(
|
|
reactionPath,
|
|
ChatMessageReactionReference
|
|
);
|
|
if (reaction == null) continue;
|
|
reactions.push(reaction);
|
|
}
|
|
return reactions;
|
|
}
|
|
// utility
|
|
static splitChannel(channelString) {
|
|
return channelString.split("/");
|
|
}
|
|
static {
|
|
this.generateChatInfo = (name, id, color) => {
|
|
const file = FileModel.createFileContent(id, "chat-info");
|
|
return {
|
|
...file,
|
|
name,
|
|
secondaryChannels: [],
|
|
encryptionKey: "",
|
|
color,
|
|
hasUnreadMessages: false
|
|
};
|
|
};
|
|
}
|
|
static {
|
|
this.createChatMessage = async (channel, senderId, senderName, encryptionKey, body, inlineReplyId, fileContent) => {
|
|
const messageFileContent = FileModel.createFileContent(v4_default(), "message");
|
|
const chatMessage = {
|
|
...messageFileContent,
|
|
channel,
|
|
senderName,
|
|
senderId,
|
|
body,
|
|
dateSent: createTimestamp(),
|
|
inlineReplyId,
|
|
status: "outbox" /* Outbox */,
|
|
stringifiedFile: ""
|
|
};
|
|
if (fileContent != void 0) {
|
|
const stringifiedFile = stringify(fileContent);
|
|
chatMessage.stringifiedFile = stringifiedFile;
|
|
}
|
|
if (encryptionKey != "") {
|
|
chatMessage.body = await encryptString(
|
|
chatMessage.body,
|
|
encryptionKey
|
|
);
|
|
chatMessage.stringifiedFile = await encryptString(
|
|
chatMessage.stringifiedFile,
|
|
encryptionKey
|
|
);
|
|
}
|
|
return chatMessage;
|
|
};
|
|
}
|
|
static {
|
|
this.createMessageReaction = (messageId, senderId, senderName, content, isDeleting) => {
|
|
const fileContent = FileModel.createFileContent(v4_default(), "reaction");
|
|
const reaction = {
|
|
...fileContent,
|
|
fileId: _ChatModel.createMessageReactionId(messageId, senderId),
|
|
messageId,
|
|
senderId,
|
|
senderName,
|
|
content,
|
|
isDeleting
|
|
};
|
|
return reaction;
|
|
};
|
|
}
|
|
static {
|
|
this.createMessageReactionId = (messageId, senderId) => {
|
|
return messageId + senderId;
|
|
};
|
|
}
|
|
};
|
|
var ReactionSymbols = /* @__PURE__ */ ((ReactionSymbols2) => {
|
|
ReactionSymbols2["ThumbsUp"] = "\u{1F44D}";
|
|
ReactionSymbols2["Check"] = "\u2705";
|
|
ReactionSymbols2["Stop"] = "\u{1F6D1}";
|
|
ReactionSymbols2["Attention"] = "\u2757\uFE0F";
|
|
ReactionSymbols2["DoubleAttention"] = "\u203C\uFE0F";
|
|
ReactionSymbols2["Question"] = "\u2753";
|
|
return ReactionSymbols2;
|
|
})(ReactionSymbols || {});
|
|
var ChatInfoReference = {
|
|
dataVersion: DATA_VERSION,
|
|
fileId: "",
|
|
fileContentId: "",
|
|
creationDate: "",
|
|
type: "chat-info",
|
|
name: "",
|
|
secondaryChannels: [""],
|
|
encryptionKey: "",
|
|
color: "",
|
|
hasUnreadMessages: true
|
|
};
|
|
var ChatMessageReference = {
|
|
dataVersion: DATA_VERSION,
|
|
fileId: "",
|
|
fileContentId: "",
|
|
creationDate: "",
|
|
type: "message",
|
|
channel: "",
|
|
senderName: "",
|
|
senderId: "",
|
|
body: "",
|
|
dateSent: "",
|
|
status: "",
|
|
stringifiedFile: ""
|
|
};
|
|
var ChatMessageReactionReference = {
|
|
dataVersion: DATA_VERSION,
|
|
fileId: "",
|
|
fileContentId: "",
|
|
creationDate: "",
|
|
type: "reaction",
|
|
messageId: "",
|
|
senderId: "",
|
|
senderName: "",
|
|
content: "",
|
|
isDeleting: false
|
|
};
|
|
|
|
// src/Model/Files/fileModel.ts
|
|
var FileModel = class _FileModel {
|
|
// init
|
|
constructor(storageModel2, settingsModel2, chatModel) {
|
|
this.getFileContainerPath = () => {
|
|
return [...this.basePath, "data" /* Data */];
|
|
};
|
|
this.getModelContainerPath = (modelName) => {
|
|
return [...this.basePath, "model" /* Model */, modelName];
|
|
};
|
|
this.getFilePath = (fileId) => {
|
|
return [...this.getFileContainerPath(), fileId];
|
|
};
|
|
this.getFileContentPath = (fileId, fileContentId) => {
|
|
const filePath = this.getFilePath(fileId);
|
|
return [...filePath, fileContentId];
|
|
};
|
|
// handlers
|
|
this.handleStringifiedFileContent = (stringifiedFileContent) => {
|
|
const fileContent = parseValidObject(
|
|
stringifiedFileContent,
|
|
FileContentReference
|
|
);
|
|
if (fileContent == null) return;
|
|
this.handleFileContent(fileContent);
|
|
};
|
|
this.handleFileContent = (fileContent) => {
|
|
console.log(fileContent.type);
|
|
if (fileContent.type == "chat-info") {
|
|
if (checkMatchesObjectStructure(fileContent, ChatInfoReference) == false)
|
|
return;
|
|
this.chatModel.handleInfo(fileContent);
|
|
}
|
|
const didStore = this.storeFileContent(fileContent);
|
|
if (didStore == false) return;
|
|
switch (fileContent.type) {
|
|
case "board-info":
|
|
case "task":
|
|
this.boardsAndTasksModel.handleFileContent(fileContent);
|
|
break;
|
|
case "reaction":
|
|
this.chatModel.handleReaction(fileContent);
|
|
break;
|
|
}
|
|
};
|
|
// methods
|
|
this.addFileContentAndSend = (fileContent) => {
|
|
this.handleFileContent(fileContent);
|
|
this.chatModel.sendMessage("", void 0, fileContent);
|
|
};
|
|
// storage
|
|
this.storeFileContent = (fileContent) => {
|
|
const fileContentPath = this.getFileContentPath(
|
|
fileContent.fileId,
|
|
fileContent.fileContentId
|
|
);
|
|
const existingFileContent = this.storageModel.read(fileContentPath);
|
|
if (existingFileContent != null) return false;
|
|
const stringifiedContent = stringify(fileContent);
|
|
this.storageModel.write(fileContentPath, stringifiedContent);
|
|
return true;
|
|
};
|
|
this.listFileIds = () => {
|
|
return this.storageModel.list(this.basePath);
|
|
};
|
|
this.listFileContentIds = (fileId) => {
|
|
const filePath = this.getFilePath(fileId);
|
|
return this.storageModel.list(filePath);
|
|
};
|
|
this.selectLatestFileContentId = (fileContentIds) => {
|
|
return fileContentIds[fileContentIds.length - 1];
|
|
};
|
|
this.getFileContent = (fileId, fileContentName, reference) => {
|
|
const filePath = this.getFileContentPath(
|
|
fileId,
|
|
fileContentName
|
|
);
|
|
const fileContentOrNull = this.storageModel.readStringifiable(
|
|
filePath,
|
|
reference
|
|
);
|
|
return fileContentOrNull;
|
|
};
|
|
this.getLatestFileContent = (fileId, reference) => {
|
|
const fileContentsIds = this.listFileContentIds(fileId);
|
|
const latestFileContentId = this.selectLatestFileContentId(fileContentsIds);
|
|
if (latestFileContentId == void 0) return null;
|
|
const fileContent = this.getFileContent(
|
|
fileId,
|
|
latestFileContentId,
|
|
reference
|
|
);
|
|
return fileContent;
|
|
};
|
|
this.chatModel = chatModel;
|
|
this.settingsModel = settingsModel2;
|
|
this.storageModel = storageModel2;
|
|
this.boardsAndTasksModel = new BoardsAndTasksModel(
|
|
this.storageModel,
|
|
this.settingsModel,
|
|
chatModel,
|
|
this
|
|
);
|
|
}
|
|
// paths
|
|
get basePath() {
|
|
return StorageModel.getPath(
|
|
"chat" /* Chat */,
|
|
filePaths.chat.files(this.chatModel.id)
|
|
);
|
|
}
|
|
static {
|
|
// utility
|
|
this.generateFileContentId = (creationDate) => {
|
|
return creationDate + v4_default();
|
|
};
|
|
}
|
|
static {
|
|
this.createFileContent = (fileId, type) => {
|
|
const creationDate = createTimestamp();
|
|
const fileContentId = _FileModel.generateFileContentId(creationDate);
|
|
return {
|
|
dataVersion: DATA_VERSION,
|
|
fileId,
|
|
fileContentId,
|
|
creationDate,
|
|
type
|
|
};
|
|
};
|
|
}
|
|
};
|
|
var FileContentReference = {
|
|
dataVersion: DATA_VERSION,
|
|
fileId: "",
|
|
fileContentId: "",
|
|
creationDate: "",
|
|
type: ""
|
|
};
|
|
|
|
// src/Model/Files/calendarModel.ts
|
|
var CalendarModel = class {
|
|
// init
|
|
constructor(storageModel2, settingsModel2, fileModel) {
|
|
this.getViewPath = () => {
|
|
return [...this.basePath, "view" /* ModelView */];
|
|
};
|
|
this.getMonthContainerPath = () => {
|
|
return [...this.basePath, "months" /* Months */];
|
|
};
|
|
this.getMonthPath = (monthString) => {
|
|
return [...this.getMonthContainerPath(), monthString];
|
|
};
|
|
// task references
|
|
this.storeTaskReference = (taskFileContent) => {
|
|
if (taskFileContent.date == void 0) return;
|
|
const monthString = isoToMonthString(taskFileContent.date);
|
|
const monthPath = this.getMonthPath(monthString);
|
|
const referencePath = [...monthPath, taskFileContent.fileId];
|
|
this.storageModel.write(referencePath, "");
|
|
};
|
|
this.deleteTaskReference = (monthString, taskId) => {
|
|
const monthPath = this.getMonthPath(monthString);
|
|
const referencePath = [...monthPath, taskId];
|
|
this.storageModel.write(referencePath, "");
|
|
};
|
|
// data
|
|
this.listTaskIds = (monthString) => {
|
|
const monthPath = this.getMonthPath(monthString);
|
|
return this.storageModel.list(monthPath);
|
|
};
|
|
// util
|
|
this.generateMonthGrid = (coreViewModel2, year, month, defaultValueCreator) => {
|
|
const date = coreViewModel2.unwrappedTodayDate;
|
|
const isCurrentMonth = year == date.getFullYear() && month == date.getMonth() + 1;
|
|
date.setDate(1);
|
|
date.setMonth(month - 1);
|
|
date.setFullYear(year);
|
|
const firstWeekdayOfMonth = date.getDay();
|
|
const firstDayOfWeekSetting = parseInt(
|
|
this.settingsModel.firstDayOfWeek
|
|
);
|
|
const offset = firstWeekdayOfMonth < firstDayOfWeekSetting ? 7 - firstDayOfWeekSetting : firstWeekdayOfMonth - firstDayOfWeekSetting;
|
|
date.setMonth(month);
|
|
date.setDate(-1);
|
|
const daysInMonth = date.getDate() + 1;
|
|
const grid = {
|
|
offset,
|
|
firstDayOfWeek: parseInt(this.settingsModel.firstDayOfWeek),
|
|
isCurrentMonth,
|
|
year,
|
|
month,
|
|
days: {}
|
|
};
|
|
for (let i = 0; i < daysInMonth; i++) {
|
|
const paddedDate = padZero((i + 1).toString(), 2);
|
|
grid.days[paddedDate] = defaultValueCreator();
|
|
}
|
|
return grid;
|
|
};
|
|
this.storageModel = storageModel2;
|
|
this.settingsModel = settingsModel2;
|
|
this.fileModel = fileModel;
|
|
}
|
|
// paths
|
|
get basePath() {
|
|
return this.fileModel.getModelContainerPath(
|
|
"calendar" /* ModelCalendar */
|
|
);
|
|
}
|
|
};
|
|
|
|
// src/Model/Files/boardsAndTasksModel.ts
|
|
var BoardsAndTasksModel = class _BoardsAndTasksModel {
|
|
// init
|
|
constructor(storageModel2, settingsModel2, chatModel, fileModel) {
|
|
// data
|
|
this.boardHandlerManager = new HandlerManager();
|
|
this.taskHandlerManager = new HandlerManager();
|
|
// paths
|
|
this.getBasePath = () => {
|
|
return this.fileModel.getModelContainerPath("tasks" /* ModelTask */);
|
|
};
|
|
this.getViewPath = () => {
|
|
return [...this.getBasePath(), "view" /* ModelView */];
|
|
};
|
|
this.getBoardFilePath = (boardId) => {
|
|
return [...this.fileModel.getFilePath(boardId)];
|
|
};
|
|
this.getTaskFilePath = (taskId) => {
|
|
return [...this.fileModel.getFilePath(taskId)];
|
|
};
|
|
this.getBoardContainerPath = () => {
|
|
return [...this.getBasePath(), "boards" /* Boards */];
|
|
};
|
|
this.getBoardDirectoryPath = (boardId) => {
|
|
return [...this.getBoardContainerPath(), boardId];
|
|
};
|
|
this.getTaskContainerPath = (boardId) => {
|
|
return [
|
|
...this.getBoardDirectoryPath(boardId),
|
|
"tasks" /* BoardTasks */
|
|
];
|
|
};
|
|
this.getTaskReferencePath = (boardId, fileId) => {
|
|
return [...this.getTaskContainerPath(boardId), fileId];
|
|
};
|
|
// handlers
|
|
this.handleFileContent = (fileContent) => {
|
|
if (checkMatchesObjectStructure(
|
|
fileContent,
|
|
BoardInfoFileContentReference
|
|
) == true) {
|
|
this.handleBoard(fileContent);
|
|
} else if (checkMatchesObjectStructure(
|
|
fileContent,
|
|
TaskFileContentReference
|
|
) == true) {
|
|
this.handleTask(fileContent);
|
|
}
|
|
};
|
|
this.handleBoard = (boardInfoFileContent) => {
|
|
this.updateBoard(boardInfoFileContent);
|
|
};
|
|
this.handleTask = (taskFileContent) => {
|
|
this.updateTask(taskFileContent);
|
|
};
|
|
// boards
|
|
this.createBoard = (name) => {
|
|
const boardInfoFileContent = _BoardsAndTasksModel.createBoardInfoFileContent(
|
|
v4_default(),
|
|
name,
|
|
"standard" /* Standard */
|
|
);
|
|
return boardInfoFileContent;
|
|
};
|
|
this.updateBoard = (boardInfoFileContent) => {
|
|
this.storeBoard(boardInfoFileContent);
|
|
this.boardHandlerManager.trigger(boardInfoFileContent);
|
|
};
|
|
this.updateBoardAndSend = (boardInfoFileContent) => {
|
|
this.updateBoard(boardInfoFileContent);
|
|
this.chatModel.sendMessage("", void 0, boardInfoFileContent);
|
|
};
|
|
this.storeBoard = (boardInfoFileContent) => {
|
|
this.fileModel.storeFileContent(boardInfoFileContent);
|
|
const boardDirectoryPath = this.getBoardDirectoryPath(
|
|
boardInfoFileContent.fileId
|
|
);
|
|
this.storageModel.write(boardDirectoryPath, "");
|
|
};
|
|
this.deleteBoard = (boardId) => {
|
|
const boardFilePath = this.getBoardFilePath(boardId);
|
|
const boardDirectoryPath = this.getBoardDirectoryPath(boardId);
|
|
this.storageModel.removeRecursively(boardFilePath);
|
|
this.storageModel.removeRecursively(boardDirectoryPath);
|
|
};
|
|
this.listBoardIds = () => {
|
|
const boardContainerPath = this.getBoardContainerPath();
|
|
const boardIds = this.storageModel.list(boardContainerPath);
|
|
return boardIds;
|
|
};
|
|
this.getBoardInfo = (fileId) => {
|
|
const boardInfoFileContentOrNull = this.fileModel.getLatestFileContent(
|
|
fileId,
|
|
BoardInfoFileContentReference
|
|
);
|
|
return boardInfoFileContentOrNull;
|
|
};
|
|
this.getBoardName = (boardId) => {
|
|
const boardInfo = this.getBoardInfo(boardId);
|
|
if (boardInfo == null) return "";
|
|
return boardInfo.name;
|
|
};
|
|
//tasks
|
|
this.createTask = (boardId) => {
|
|
const taskFileContent = _BoardsAndTasksModel.createTaskFileContent(v4_default(), "", boardId);
|
|
return taskFileContent;
|
|
};
|
|
this.updateTask = (taskFileContent) => {
|
|
this.storeTask(taskFileContent);
|
|
this.taskHandlerManager.trigger(taskFileContent);
|
|
};
|
|
this.updateTaskAndSend = (taskFileContent) => {
|
|
this.updateTask(taskFileContent);
|
|
this.chatModel.sendMessage("", void 0, taskFileContent);
|
|
};
|
|
this.storeTask = (taskFileContent) => {
|
|
this.fileModel.storeFileContent(taskFileContent);
|
|
const taskReferencePath = this.getTaskReferencePath(
|
|
taskFileContent.boardId,
|
|
taskFileContent.fileId
|
|
);
|
|
this.storageModel.write(taskReferencePath, "");
|
|
this.calendarModel.storeTaskReference(taskFileContent);
|
|
};
|
|
this.listTaskIds = (boardId) => {
|
|
const taskContainerPath = this.getTaskContainerPath(boardId);
|
|
const fileIds = this.storageModel.list(taskContainerPath);
|
|
return fileIds;
|
|
};
|
|
this.listTaskVersionIds = (taskId) => {
|
|
const versionIds = this.fileModel.listFileContentIds(taskId);
|
|
return versionIds;
|
|
};
|
|
this.getLatestTaskFileContent = (taskId) => {
|
|
const taskFileContentOrNull = this.fileModel.getLatestFileContent(
|
|
taskId,
|
|
TaskFileContentReference
|
|
);
|
|
return taskFileContentOrNull;
|
|
};
|
|
this.getSpecificTaskFileContent = (taskId, versionId) => {
|
|
const taskFileContentOrNull = this.fileModel.getFileContent(
|
|
taskId,
|
|
versionId,
|
|
TaskFileContentReference
|
|
);
|
|
return taskFileContentOrNull;
|
|
};
|
|
this.deleteTask = (boardId, taskId) => {
|
|
const taskFilePath = this.getTaskFilePath(taskId);
|
|
this.storageModel.removeRecursively(taskFilePath);
|
|
this.deleteTaskReference(boardId, taskId);
|
|
};
|
|
this.deleteTaskReference = (boardId, taskId) => {
|
|
const taskReferencePath = this.getTaskReferencePath(
|
|
boardId,
|
|
taskId
|
|
);
|
|
this.storageModel.removeRecursively(taskReferencePath);
|
|
};
|
|
this.storageModel = storageModel2;
|
|
this.settingsModel = settingsModel2;
|
|
this.chatModel = chatModel;
|
|
this.fileModel = fileModel;
|
|
this.calendarModel = new CalendarModel(
|
|
this.storageModel,
|
|
this.settingsModel,
|
|
this.fileModel
|
|
);
|
|
}
|
|
static {
|
|
// utility
|
|
this.createBoardInfoFileContent = (fileId, name, color) => {
|
|
const fileContent = FileModel.createFileContent(fileId, "board-info");
|
|
return {
|
|
...fileContent,
|
|
name,
|
|
color
|
|
};
|
|
};
|
|
}
|
|
static {
|
|
this.createTaskFileContent = (fileId, name, boardId) => {
|
|
const fileContent = FileModel.createFileContent(
|
|
fileId,
|
|
"task"
|
|
);
|
|
return {
|
|
...fileContent,
|
|
name,
|
|
boardId
|
|
};
|
|
};
|
|
}
|
|
};
|
|
var BoardInfoFileContentReference = {
|
|
dataVersion: DATA_VERSION,
|
|
fileId: "",
|
|
fileContentId: "",
|
|
creationDate: "",
|
|
type: "board-info",
|
|
name: "",
|
|
color: ""
|
|
};
|
|
var TaskFileContentReference = {
|
|
dataVersion: DATA_VERSION,
|
|
fileId: "string",
|
|
fileContentId: "",
|
|
creationDate: "",
|
|
type: "task",
|
|
name: "",
|
|
boardId: ""
|
|
};
|
|
|
|
// src/ViewModel/Pages/taskViewModel.ts
|
|
var TaskViewModel = class extends Context {
|
|
// init
|
|
constructor(coreViewModel2, chatViewModel, boardsAndTasksModel, containingViewModel, task) {
|
|
super("task");
|
|
this.coreViewModel = coreViewModel2;
|
|
this.chatViewModel = chatViewModel;
|
|
this.boardsAndTasksModel = boardsAndTasksModel;
|
|
this.containingViewModel = containingViewModel;
|
|
this.task = 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.isNotNext = new State(true);
|
|
this.selectedVersionId = new State("");
|
|
this.versionIds = new ListState();
|
|
this.isPresentingFullScreenDescription = new State(
|
|
false
|
|
);
|
|
// methods
|
|
this.dragStart = (event) => {
|
|
ViewController.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.containingViewModel.selectTask(this);
|
|
};
|
|
this.closeAndDiscard = () => {
|
|
this.close();
|
|
this.loadTaskData();
|
|
};
|
|
this.closeAndSave = () => {
|
|
this.close();
|
|
this.save();
|
|
};
|
|
this.openFullscreenDecription = () => {
|
|
this.isPresentingFullScreenDescription.value = true;
|
|
};
|
|
this.closeFullscreenDecription = () => {
|
|
this.isPresentingFullScreenDescription.value = false;
|
|
};
|
|
this.updateIndex = () => {
|
|
const index = this.containingViewModel.taskIndexManager.getIndex(this);
|
|
this.index.value = index;
|
|
};
|
|
this.updateSuggestions = () => {
|
|
if (this.containingViewModel.taskCategorySuggestions.value.has(
|
|
this.category.value
|
|
) == false) {
|
|
this.containingViewModel.taskCategorySuggestions.add(
|
|
this.category.value
|
|
);
|
|
}
|
|
if (this.containingViewModel.taskStatusSuggestions.value.has(
|
|
this.status.value
|
|
) == false) {
|
|
this.containingViewModel.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.containingViewModel.showTask(newTaskFileContent);
|
|
this.containingViewModel.updateTaskIndices();
|
|
this.updateSuggestions();
|
|
};
|
|
this.deleteTask = () => {
|
|
this.close();
|
|
this.boardsAndTasksModel.deleteTask(
|
|
this.task.boardId,
|
|
this.task.fileId
|
|
);
|
|
this.containingViewModel.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();
|
|
if (this.containingViewModel instanceof CalendarPageViewModel) {
|
|
this.containingViewModel.nextTask.subscribe((nextTask) => {
|
|
this.isNotNext.value = nextTask != this;
|
|
});
|
|
}
|
|
};
|
|
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.updateSuggestions();
|
|
};
|
|
// exit
|
|
this.close = () => {
|
|
this.coreViewModel.closeContext(this.contextId);
|
|
};
|
|
this.handleContextClose = () => {
|
|
this.containingViewModel.closeTask();
|
|
};
|
|
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/Utility/searchViewModel.ts
|
|
var SearchViewModel = class {
|
|
// init
|
|
constructor(allObjects, matchingObjects, getStringsOfObject, suggestions) {
|
|
this.allObjects = allObjects;
|
|
this.matchingObjects = matchingObjects;
|
|
this.getStringsOfObject = getStringsOfObject;
|
|
this.suggestions = suggestions;
|
|
// state
|
|
this.appliedQuery = new State("");
|
|
this.searchInput = new State("");
|
|
// guards
|
|
this.cannotApplySearch = createProxyState(
|
|
[this.searchInput, this.appliedQuery],
|
|
() => this.searchInput.value == this.appliedQuery.value
|
|
);
|
|
this.cannotClear = createProxyState(
|
|
[this.searchInput],
|
|
() => this.searchInput.value == ""
|
|
);
|
|
this.hasNoSuggestions = createProxyState(
|
|
[this.suggestions],
|
|
() => this.suggestions.value.size == 0
|
|
);
|
|
// 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);
|
|
}
|
|
};
|
|
this.clear = () => {
|
|
this.searchInput.value = "";
|
|
this.applySearch();
|
|
};
|
|
this.deleteSuggestion = (suggestion) => {
|
|
this.suggestions.remove(suggestion);
|
|
};
|
|
// utility
|
|
this.checkDoesMatchSearch = (object) => {
|
|
return checkDoesObjectMatchSearch(
|
|
this.appliedQuery.value,
|
|
this.getStringsOfObject,
|
|
object
|
|
);
|
|
};
|
|
this.allObjects.handleAddition((newObject) => {
|
|
const doesMatch = this.checkDoesMatchSearch(newObject);
|
|
if (doesMatch == false) {
|
|
this.matchingObjects.remove(newObject);
|
|
} else {
|
|
if (this.matchingObjects.value.has(newObject)) return;
|
|
this.matchingObjects.add(newObject);
|
|
this.allObjects.handleRemoval(newObject, () => {
|
|
this.matchingObjects.remove(newObject);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
// src/ViewModel/Pages/boardViewModel.ts
|
|
var BoardViewModel = class extends TaskContainingPageViewModel {
|
|
// init
|
|
constructor(coreViewModel2, chatViewModel, boardsAndTasksModel, taskPageViewModel, boardInfo) {
|
|
super(coreViewModel2, chatViewModel, boardsAndTasksModel, "board");
|
|
this.coreViewModel = coreViewModel2;
|
|
this.chatViewModel = chatViewModel;
|
|
this.boardsAndTasksModel = boardsAndTasksModel;
|
|
this.taskPageViewModel = taskPageViewModel;
|
|
this.boardInfo = boardInfo;
|
|
// 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.searchSuggestions = new ListState();
|
|
this.filteredTaskViewModels = new ListState();
|
|
this.pinchToZoomData = new State({
|
|
zoom: 1,
|
|
x: 0,
|
|
y: 0
|
|
});
|
|
// 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.searchSuggestions.value.has(searchTerm) && searchTerm != "") {
|
|
this.searchSuggestions.add(searchTerm);
|
|
}
|
|
const lastSearchPath = this.getLastSearchPath();
|
|
this.coreViewModel.storageModel.write(lastSearchPath, searchTerm);
|
|
};
|
|
this.handleSearchRemoved = (searchTerm) => {
|
|
const suggestionPath = [
|
|
...this.getPreviousSearchesPath(),
|
|
searchTerm
|
|
];
|
|
this.coreViewModel.storageModel.remove(suggestionPath);
|
|
};
|
|
// 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.taskPageViewModel.storeLastUsedBoard();
|
|
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;
|
|
};
|
|
this.resetPinchZoom = () => {
|
|
this.pinchToZoomData.value = { x: 0, y: 0, zoom: 1 };
|
|
};
|
|
// load
|
|
this.preloadData = () => {
|
|
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)) continue;
|
|
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.searchSuggestions.add(...searches.filter((x) => x != ""));
|
|
};
|
|
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();
|
|
};
|
|
// exit
|
|
this.handleContextClose = (fromHistoryEvent) => {
|
|
this.taskPageViewModel.handleBoardClosed(this);
|
|
if (!fromHistoryEvent) return;
|
|
this.taskPageViewModel.storeLastUsedBoard();
|
|
};
|
|
this.preloadData();
|
|
this.searchViewModel = new SearchViewModel(
|
|
this.taskViewModels,
|
|
this.filteredTaskViewModels,
|
|
TaskViewModel.getStringsForFilter,
|
|
this.searchSuggestions
|
|
);
|
|
this.searchViewModel.appliedQuery.subscribeSilent((newQuery) => {
|
|
this.handleNewSearch(newQuery);
|
|
});
|
|
this.restoreSearch();
|
|
this.searchSuggestions.handleRemovals(this.handleSearchRemoved);
|
|
this.isFilterActive = createProxyState(
|
|
[this.searchViewModel.appliedQuery],
|
|
() => this.searchViewModel.appliedQuery.value != ""
|
|
);
|
|
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();
|
|
});
|
|
this.searchViewModel.appliedQuery.subscribeSilent(() => {
|
|
this.resetPinchZoom();
|
|
});
|
|
boardsAndTasksModel.taskHandlerManager.setHandler(
|
|
this.boardInfo.fileId,
|
|
(taskFileContent) => {
|
|
if (taskFileContent.boardId != this.boardInfo.fileId) return;
|
|
this.showTask(taskFileContent);
|
|
this.updateTaskIndices();
|
|
}
|
|
);
|
|
this.registerKeyStroke("=" /* Filter */, this.showFilterModal);
|
|
this.registerKeyStroke("-" /* Reset */, this.resetFilter);
|
|
this.registerKeyStroke("backspace" /* CloseOrCancel */, this.hideFilterModal);
|
|
this.registerKeyStroke("," /* Settings */, this.showSettings);
|
|
this.registerKeyStroke("enter" /* Apply */, this.hideSettings);
|
|
this.registerKeyStroke("a" /* Create */, this.createTask);
|
|
this.registerKeyStroke(
|
|
"j",
|
|
() => this.selectedPage.value = "list" /* List */
|
|
);
|
|
this.registerKeyStroke(
|
|
"k",
|
|
() => this.selectedPage.value = "kanban" /* Kanban */
|
|
);
|
|
this.registerKeyStroke(
|
|
"l",
|
|
() => this.selectedPage.value = "status-grid" /* StatusGrid */
|
|
);
|
|
this.taskPageViewModel.registerContext(this.boardInfo.fileId, this);
|
|
}
|
|
};
|
|
|
|
// src/ViewModel/Pages/taskPageViewModel.ts
|
|
var TaskPageViewModel = class extends ContextHost {
|
|
// init
|
|
constructor(coreViewModel2, chatViewModel, boardsAndTasksModel) {
|
|
super("task-page", coreViewModel2);
|
|
this.coreViewModel = coreViewModel2;
|
|
this.chatViewModel = chatViewModel;
|
|
this.boardsAndTasksModel = boardsAndTasksModel;
|
|
// 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.boardQuery = new State("");
|
|
this.boardViewModels = new MapState();
|
|
this.boardMatches = new ListState();
|
|
this.isShowingBoadList = new State(true);
|
|
this.selectedBoardId = new State(
|
|
void 0
|
|
);
|
|
// guards
|
|
this.cannotCreateBoard = createProxyState(
|
|
[this.boardQuery],
|
|
() => this.boardQuery.value == ""
|
|
);
|
|
// methods
|
|
this.createBoard = () => {
|
|
if (this.cannotCreateBoard.value == true) return;
|
|
const boardInfoFileContent = this.boardsAndTasksModel.createBoard(this.boardQuery.value);
|
|
this.boardQuery.value = "";
|
|
this.showBoardInList(boardInfoFileContent);
|
|
this.boardsAndTasksModel.updateBoardAndSend(boardInfoFileContent);
|
|
this.updateBoardIndices();
|
|
};
|
|
this.updateBoard = (boardInfoFileContent) => {
|
|
this.boardsAndTasksModel.updateBoardAndSend(boardInfoFileContent);
|
|
};
|
|
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.chatViewModel.displayedColor.value = boardViewModel.color.value;
|
|
if (this.selectedBoardId.value == boardViewModel.boardInfo.fileId) {
|
|
this.updateContexts();
|
|
return;
|
|
}
|
|
this.selectedBoardId.value = boardViewModel.boardInfo.fileId;
|
|
this.storeLastUsedBoard();
|
|
};
|
|
this.closeBoard = () => {
|
|
this.closeCurrentContext();
|
|
};
|
|
this.handleBoardClosed = (boardViewModel) => {
|
|
if (boardViewModel.boardInfo.fileId != this.selectedBoardId.value)
|
|
return;
|
|
this.selectedBoardId.value = void 0;
|
|
this.chatViewModel.resetColor();
|
|
};
|
|
this.updateBoardIndices = () => {
|
|
this.boardIndexManager.update([...this.boardViewModels.value.values()]);
|
|
for (const boardViewModel of this.boardViewModels.value.values()) {
|
|
boardViewModel.updateIndex();
|
|
}
|
|
};
|
|
// storage
|
|
this.storeLastUsedBoard = () => {
|
|
const path = this.getLastUsedBoardPath();
|
|
const lastUsedBoardId = this.selectedBoardId.value ?? "";
|
|
this.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.closeCurrentContext();
|
|
const boardIds = this.boardsAndTasksModel.listBoardIds();
|
|
for (const boardId of boardIds) {
|
|
if (this.boardViewModels.value.has(boardId)) continue;
|
|
const boardInfo = this.boardsAndTasksModel.getBoardInfo(boardId);
|
|
if (boardInfo == null) continue;
|
|
this.showBoardInList(boardInfo);
|
|
}
|
|
this.updateBoardIndices();
|
|
this.openLastUsedBoard();
|
|
};
|
|
// context
|
|
this.handleContextClose = (fromHistoryEvent) => {
|
|
if (!fromHistoryEvent) return;
|
|
return true;
|
|
};
|
|
this.loadData();
|
|
this.chatViewModel = chatViewModel;
|
|
implementFilter(
|
|
this.boardViewModels,
|
|
this.boardMatches,
|
|
this.boardQuery,
|
|
(board) => board.name.value
|
|
);
|
|
this.chatViewModel.registerContext("tasks" /* Tasks */, this);
|
|
this.selectedBoardId.subscribeSilent(this.updateContexts);
|
|
boardsAndTasksModel.boardHandlerManager.setHandler(
|
|
"task-page" + this.chatViewModel.chatModel.id,
|
|
(boardInfoFileContent) => {
|
|
this.showBoardInList(boardInfoFileContent);
|
|
this.updateBoardIndices();
|
|
}
|
|
);
|
|
this.chatViewModel.currentContext.subscribeSilent((context) => {
|
|
if (!this.isOpen) return;
|
|
if (context != this) return;
|
|
this.openLastUsedBoard();
|
|
});
|
|
this.registerKeyStroke("." /* Options */, this.toggleBoardList);
|
|
}
|
|
// context
|
|
get isOpen() {
|
|
return this.chatViewModel.isOpen && this.chatViewModel.selectedPage.value == "tasks" /* Tasks */;
|
|
}
|
|
get contextSelection() {
|
|
return this.selectedBoardId.value;
|
|
}
|
|
};
|
|
|
|
// src/ViewModel/Pages/settingsPageViewModel.ts
|
|
var SettingsPageViewModel = class extends Context {
|
|
// init
|
|
constructor(coreViewModel2, chatViewModel) {
|
|
super("settings");
|
|
this.coreViewModel = coreViewModel2;
|
|
this.chatViewModel = chatViewModel;
|
|
// state
|
|
this.name = new State("");
|
|
this.nameInput = 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 */);
|
|
this.appliedColor = new State("standard" /* Standard */);
|
|
// guards
|
|
this.cannotSetPrimaryChannel = createProxyState(
|
|
[this.name, this.nameInput],
|
|
() => this.nameInput.value == "" || this.nameInput.value == this.name.value
|
|
);
|
|
this.cannotSetColor = createProxyState(
|
|
[this.color, this.appliedColor],
|
|
() => this.color.value == this.appliedColor.value
|
|
);
|
|
this.cannotAddSecondaryChannel = createProxyState(
|
|
[this.newSecondaryChannelInput],
|
|
() => this.newSecondaryChannelInput.value == ""
|
|
);
|
|
// methods
|
|
this.setName = () => {
|
|
this.chatViewModel.chatModel.setName(this.nameInput.value);
|
|
this.name.value = this.chatViewModel.chatModel.info.name;
|
|
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.randomizeKey = () => {
|
|
this.encryptionKeyInput.value = random(24);
|
|
};
|
|
this.applyColor = () => {
|
|
this.chatViewModel.setColor(this.color.value);
|
|
this.appliedColor.value = this.color.value;
|
|
};
|
|
this.remove = () => {
|
|
this.chatViewModel.close();
|
|
this.chatViewModel.chatModel.delete();
|
|
this.chatViewModel.chatListViewModel.untrackChat(this.chatViewModel);
|
|
};
|
|
// load
|
|
this.preloadData = () => {
|
|
this.name.value = this.chatViewModel.chatModel.info.name;
|
|
this.color.value = this.chatViewModel.chatModel.color;
|
|
this.appliedColor.value = this.color.value;
|
|
};
|
|
this.updateData = () => {
|
|
this.preloadData();
|
|
this.nameInput.value = this.name.value;
|
|
this.chatViewModel.resetColor();
|
|
};
|
|
this.loadData = () => {
|
|
this.nameInput.value = this.name.value;
|
|
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);
|
|
}
|
|
};
|
|
// context
|
|
this.handleContextClose = (fromHistoryEvent) => {
|
|
if (!fromHistoryEvent) return;
|
|
return true;
|
|
};
|
|
this.preloadData();
|
|
this.cannotSetEncryptionKey = createProxyState(
|
|
[this.encryptionKeyInput],
|
|
() => this.encryptionKeyInput.value == this.chatViewModel.chatModel.info.encryptionKey
|
|
);
|
|
this.chatViewModel.registerContext("settings" /* Settings */, this);
|
|
}
|
|
};
|
|
|
|
// src/ViewModel/Chat/chatMessageViewModel.ts
|
|
var ChatMessageViewModel = class {
|
|
// init
|
|
constructor(coreViewModel2, messagePageViewModel, chatMessage, contact, sentByUser) {
|
|
this.coreViewModel = coreViewModel2;
|
|
this.messagePageViewModel = messagePageViewModel;
|
|
this.contact = contact;
|
|
this.channel = "";
|
|
this.senderId = "";
|
|
this.dateSent = "";
|
|
this.body = new State("");
|
|
this.inlineReply = void 0;
|
|
this.replies = new MapState();
|
|
this.replyCount = createProxyState(
|
|
[this.replies],
|
|
() => this.replies.value.size
|
|
);
|
|
this.status = new State(
|
|
void 0
|
|
);
|
|
this.isHidden = new State(false);
|
|
this.allReactions = new MapState();
|
|
this.userReaction = new State(
|
|
void 0
|
|
);
|
|
this.reactionsThumbsUp = MapState;
|
|
this.reactionsCheck = MapState;
|
|
this.reactionsStop = MapState;
|
|
this.reactionsAttention = MapState;
|
|
this.reactionsDoubleAttention = MapState;
|
|
this.reactionsQuestion = MapState;
|
|
this.generateReactionCountProxyState = (content) => {
|
|
return createProxyState(
|
|
[this.allReactions],
|
|
() => [...this.allReactions.value.values()].filter(
|
|
(x) => x.content == content
|
|
).length
|
|
);
|
|
};
|
|
this.reactionsThumbsUpCount = this.generateReactionCountProxyState(
|
|
"\u{1F44D}" /* ThumbsUp */
|
|
);
|
|
this.reactionsCheckCount = this.generateReactionCountProxyState(
|
|
"\u2705" /* Check */
|
|
);
|
|
this.reactionsStopCount = this.generateReactionCountProxyState(
|
|
"\u{1F6D1}" /* Stop */
|
|
);
|
|
this.reactionsAttentionCount = this.generateReactionCountProxyState(
|
|
"\u2757\uFE0F" /* Attention */
|
|
);
|
|
this.reactionsDoubleAttentionCount = this.generateReactionCountProxyState(
|
|
"\u203C\uFE0F" /* DoubleAttention */
|
|
);
|
|
this.reactionsQuestionCount = this.generateReactionCountProxyState(
|
|
"\u2753" /* Question */
|
|
);
|
|
// state
|
|
this.isPresentingInfoModal = new State(false);
|
|
// methods
|
|
this.copyMessage = () => {
|
|
navigator.clipboard.writeText(this.body.value);
|
|
};
|
|
this.resendMessage = () => {
|
|
this.messagePageViewModel.sendMessageFromBody(this.body.value);
|
|
};
|
|
this.decryptMessage = () => {
|
|
this.messagePageViewModel.decryptMessage(this);
|
|
};
|
|
this.reply = () => {
|
|
this.messagePageViewModel.setReply(this);
|
|
this.hideInfoModal();
|
|
};
|
|
this.cancelReply = () => {
|
|
if (this.messagePageViewModel.replyingMessage.value != this) return;
|
|
this.messagePageViewModel.setReply(void 0);
|
|
};
|
|
// view
|
|
this.showInfoModal = () => {
|
|
this.isPresentingInfoModal.value = true;
|
|
};
|
|
this.hideInfoModal = () => {
|
|
this.isPresentingInfoModal.value = false;
|
|
};
|
|
this.toggleHiding = () => {
|
|
let hideForReactions = false;
|
|
let hideForReplyView = false;
|
|
const reactionFilter = this.messagePageViewModel.reactionFilter.value;
|
|
if (reactionFilter == void 0) {
|
|
hideForReactions = false;
|
|
} else {
|
|
let count = 0;
|
|
switch (reactionFilter) {
|
|
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;
|
|
}
|
|
}
|
|
hideForReactions = count == 0;
|
|
}
|
|
const selectedMessage = this.messagePageViewModel.replyViewSelectedMessage.value;
|
|
if (selectedMessage == void 0) hideForReplyView = false;
|
|
else if (selectedMessage.chatMessage.fileId == this.chatMessage.fileId)
|
|
hideForReplyView = false;
|
|
else if (this.inlineReply != void 0 && selectedMessage.chatMessage.fileId == this.inlineReply.chatMessage.fileId)
|
|
hideForReplyView = false;
|
|
else hideForReplyView = true;
|
|
this.isHidden.value = hideForReactions || hideForReplyView;
|
|
};
|
|
// reactions
|
|
this.handleReaction = (reaction) => {
|
|
if (reaction.isDeleting) {
|
|
this.allReactions.remove(reaction.senderId);
|
|
} else {
|
|
this.allReactions.set(reaction.senderId, reaction);
|
|
}
|
|
if (reaction.senderId != this.coreViewModel.settingsModel.userid)
|
|
return;
|
|
this.userReaction.value = reaction.isDeleting ? void 0 : reaction.content;
|
|
};
|
|
this.sendReaction = (content, isDeleting) => {
|
|
this.messagePageViewModel.sendReaction(
|
|
this.chatMessage.fileId,
|
|
content,
|
|
isDeleting
|
|
);
|
|
};
|
|
// load
|
|
this.loadData = () => {
|
|
this.channel = this.chatMessage.channel;
|
|
this.senderId = this.chatMessage.senderId;
|
|
this.dateSent = new Date(this.chatMessage.dateSent).toLocaleString();
|
|
this.body.value = this.chatMessage.body;
|
|
this.status.value = this.chatMessage.status;
|
|
if (this.chatMessage.inlineReplyId) {
|
|
this.inlineReply = this.messagePageViewModel.chatMessageViewModels.value.get(
|
|
this.chatMessage.inlineReplyId
|
|
);
|
|
this.inlineReply?.replies.set(this.chatMessage.fileId, this);
|
|
}
|
|
};
|
|
this.chatMessage = chatMessage;
|
|
this.sentByUser = sentByUser;
|
|
this.loadData();
|
|
bulkSubscribe(
|
|
[
|
|
this.messagePageViewModel.reactionFilter,
|
|
this.messagePageViewModel.replyViewSelectedMessage,
|
|
this.allReactions
|
|
],
|
|
this.toggleHiding
|
|
);
|
|
this.toggleHiding();
|
|
}
|
|
};
|
|
|
|
// src/ViewModel/Pages/messagePageViewModel.ts
|
|
var MessagePageViewModel = class extends Context {
|
|
// init
|
|
constructor(coreViewModel2, chatViewModel, contactListViewModel2) {
|
|
super("message-page");
|
|
this.coreViewModel = coreViewModel2;
|
|
this.chatViewModel = chatViewModel;
|
|
this.contactListViewModel = contactListViewModel2;
|
|
// state
|
|
this.chatMessageViewModels = new MapState();
|
|
this.filteredMessageViewModels = new ListState();
|
|
this.replyViewSelectedMessage = new State(void 0);
|
|
this.isReplyViewActive = createProxyState(
|
|
[this.replyViewSelectedMessage],
|
|
() => this.replyViewSelectedMessage.value != void 0
|
|
);
|
|
this.isReplyViewInactive = createProxyState(
|
|
[this.isReplyViewActive],
|
|
() => !this.isReplyViewActive.value
|
|
);
|
|
this.isFilterModalOpen = new State(false);
|
|
this.reactionFilter = new State(void 0);
|
|
this.replyingMessage = new State(
|
|
void 0
|
|
);
|
|
this.composingMessage = new State("");
|
|
this.focusSetter = new State(null);
|
|
// methods
|
|
this.sendMessage = async () => {
|
|
if (this.cannotSendMessage.value == true) return;
|
|
const idPromise = this.sendMessageFromBody(this.composingMessage.value);
|
|
this.composingMessage.value = "";
|
|
const id = await idPromise;
|
|
if (id && this.reactionFilter.value != void 0) {
|
|
this.sendReaction(id, this.reactionFilter.value, false);
|
|
}
|
|
};
|
|
this.sendMessageFromBody = (body) => {
|
|
let replyId = void 0;
|
|
if (this.replyingMessage.value) {
|
|
replyId = this.replyingMessage.value.chatMessage.fileId;
|
|
}
|
|
const id = this.chatViewModel.chatModel.sendMessage(body, replyId);
|
|
if (this.replyViewSelectedMessage.value == void 0)
|
|
this.replyingMessage.value = void 0;
|
|
return id;
|
|
};
|
|
this.decryptMessage = async (messageViewModel) => {
|
|
const chatMessage = messageViewModel.chatMessage;
|
|
await this.chatViewModel.chatModel.decryptMessage(chatMessage);
|
|
this.chatViewModel.chatModel.addMessage(chatMessage);
|
|
messageViewModel.loadData();
|
|
};
|
|
this.sendReaction = (messageId, content, isDeleting) => {
|
|
this.chatViewModel.chatModel.sendReaction(
|
|
messageId,
|
|
content,
|
|
isDeleting
|
|
);
|
|
};
|
|
this.setReply = (chatMessageViewModel, setFocus = true) => {
|
|
this.replyingMessage.value = chatMessageViewModel;
|
|
if (setFocus == false || chatMessageViewModel == void 0) return;
|
|
this.setFocus();
|
|
};
|
|
this.resetReply = (setFocus = true) => {
|
|
this.replyingMessage.value = void 0;
|
|
if (setFocus == false) return;
|
|
this.setFocus();
|
|
};
|
|
// view
|
|
this.showChatMessage = (chatMessage) => {
|
|
const contact = this.contactListViewModel.unwrapContact(
|
|
chatMessage.senderId,
|
|
chatMessage.senderName
|
|
);
|
|
const chatMessageViewModel = new ChatMessageViewModel(
|
|
this.coreViewModel,
|
|
this,
|
|
chatMessage,
|
|
contact,
|
|
chatMessage.senderId == this.chatViewModel.settingsViewModel.settingsModel.userid
|
|
);
|
|
const existingChatMessageViewModel = this.chatMessageViewModels.value.get(chatMessage.fileId);
|
|
if (existingChatMessageViewModel != void 0) {
|
|
existingChatMessageViewModel.body.value = chatMessage.body;
|
|
existingChatMessageViewModel.status.value = chatMessage.status;
|
|
} else {
|
|
this.chatMessageViewModels.set(
|
|
chatMessage.fileId,
|
|
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 = (persist = true) => {
|
|
this.reactionFilter.value = void 0;
|
|
if (persist) this.chatViewModel.chatModel.storeFilter("");
|
|
};
|
|
this.setReactionFilter = (content) => {
|
|
this.reactionFilter.value = content;
|
|
this.chatViewModel.chatModel.storeFilter(content);
|
|
};
|
|
this.resetFilter = () => {
|
|
this.revokeReactionFilter();
|
|
this.searchViewModel.search("");
|
|
};
|
|
this.setReplyView = (message) => {
|
|
this.replyViewSelectedMessage.value = message;
|
|
this.revokeReactionFilter(false);
|
|
this.setReply(message, false);
|
|
};
|
|
this.resetReplyView = () => {
|
|
this.replyViewSelectedMessage.value = void 0;
|
|
this.resetReply(false);
|
|
this.restoreFilter();
|
|
};
|
|
this.setFocus = () => {
|
|
this.focusSetter.callSubscriptions();
|
|
};
|
|
// load
|
|
this.loadData = () => {
|
|
this.chatMessageViewModels.clear();
|
|
for (const chatMessage of this.chatViewModel.chatModel.messages) {
|
|
this.showChatMessage(chatMessage);
|
|
}
|
|
for (const reaction of this.chatViewModel.chatModel.reactions) {
|
|
this.handleReaction(reaction);
|
|
}
|
|
};
|
|
this.restoreFilter = () => {
|
|
const previousFilter = this.chatViewModel.chatModel.getFilter();
|
|
if (Object.values(ReactionSymbols).includes(previousFilter))
|
|
this.setReactionFilter(previousFilter);
|
|
};
|
|
// context
|
|
this.handleContextClose = (fromHistoryEvent) => {
|
|
if (fromHistoryEvent) {
|
|
if (this.replyViewSelectedMessage.value == void 0) return true;
|
|
this.resetReplyView();
|
|
return false;
|
|
}
|
|
};
|
|
this.restoreFilter();
|
|
this.cannotSendMessage = createProxyState(
|
|
[
|
|
this.chatViewModel.settingsViewModel.username,
|
|
this.composingMessage
|
|
],
|
|
() => this.chatViewModel.settingsViewModel.username.value == "" || this.composingMessage.value == ""
|
|
);
|
|
this.searchViewModel = new SearchViewModel(
|
|
this.chatMessageViewModels,
|
|
this.filteredMessageViewModels,
|
|
(chatMessageViewModel) => [chatMessageViewModel.body.value],
|
|
new ListState()
|
|
);
|
|
this.isFilterActive = createProxyState(
|
|
[this.searchViewModel.appliedQuery, this.reactionFilter],
|
|
() => this.searchViewModel.appliedQuery.value != "" || this.reactionFilter.value != void 0
|
|
);
|
|
this.registerKeyStroke("=" /* Filter */, this.showFilterModal);
|
|
this.registerKeyStroke("backspace" /* CloseOrCancel */, () => {
|
|
if (this.isFilterModalOpen.value == true) {
|
|
this.hideFilterModal();
|
|
} else {
|
|
this.replyingMessage.value = void 0;
|
|
}
|
|
});
|
|
this.registerKeyStroke("-" /* Reset */, this.resetFilter);
|
|
this.registerKeyStroke("a" /* Create */, this.setFocus);
|
|
this.chatViewModel.registerContext("messages" /* Messages */, this);
|
|
}
|
|
};
|
|
|
|
// src/ViewModel/Chat/chatViewModel.ts
|
|
var ChatViewModel6 = class extends ContextHost {
|
|
// init
|
|
constructor(coreViewModel2, chatModel, settingsViewModel2, notificationViewModel, connectionViewModel2, chatListViewModel2, contactListViewModel2) {
|
|
super("chat", coreViewModel2);
|
|
this.coreViewModel = coreViewModel2;
|
|
this.chatModel = chatModel;
|
|
this.settingsViewModel = settingsViewModel2;
|
|
this.notificationViewModel = notificationViewModel;
|
|
this.connectionViewModel = connectionViewModel2;
|
|
this.chatListViewModel = chatListViewModel2;
|
|
this.contactListViewModel = contactListViewModel2;
|
|
// state
|
|
this.displayedColor = new State("standard" /* Standard */);
|
|
this.selectedPage = new State(
|
|
"messages" /* Messages */
|
|
);
|
|
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.openPage = (page) => {
|
|
this.closeCurrentContext();
|
|
this.selectedPage.value = page;
|
|
};
|
|
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.openPage(lastUsedPage);
|
|
}
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
);
|
|
};
|
|
// exit
|
|
this.close = () => {
|
|
this.coreViewModel.closeContext(this.contextId);
|
|
};
|
|
this.handleContextClose = () => {
|
|
this.chatListViewModel.closeChat();
|
|
};
|
|
this.calendarViewModel = new CalendarPageViewModel(
|
|
coreViewModel2,
|
|
this,
|
|
this.chatModel.fileModel.boardsAndTasksModel.calendarModel,
|
|
this.chatModel.fileModel.boardsAndTasksModel
|
|
);
|
|
this.taskPageViewModel = new TaskPageViewModel(
|
|
this.coreViewModel,
|
|
this,
|
|
this.chatModel.fileModel.boardsAndTasksModel
|
|
);
|
|
this.messagePageViewModel = new MessagePageViewModel(
|
|
this.coreViewModel,
|
|
this,
|
|
this.contactListViewModel
|
|
);
|
|
this.settingsPageViewModel = new SettingsPageViewModel(
|
|
this.coreViewModel,
|
|
this
|
|
);
|
|
chatModel.chatMessageHandlerManager.setHandler(
|
|
this.chatModel.id,
|
|
(chatMessage) => {
|
|
this.messagePageViewModel.showChatMessage(chatMessage);
|
|
this.updateReadStatus();
|
|
this.notificationViewModel.showNotification(chatMessage);
|
|
}
|
|
);
|
|
chatModel.reactionHandlerManager.setHandler(
|
|
this.chatModel.id,
|
|
(reaction) => {
|
|
this.messagePageViewModel.handleReaction(reaction);
|
|
}
|
|
);
|
|
chatModel.changeHandlerManager.setHandler(
|
|
this.chatModel.id,
|
|
this.settingsPageViewModel.updateData
|
|
);
|
|
this.loadPageSelection();
|
|
this.resetColor();
|
|
this.loadInfo();
|
|
this.subscribeReadStatus();
|
|
this.registerKeyStroke("w" /* Home */, this.close);
|
|
this.registerKeyStroke(
|
|
"u",
|
|
() => this.openPage("messages" /* Messages */)
|
|
);
|
|
this.registerKeyStroke("i", () => this.openPage("tasks" /* Tasks */));
|
|
this.registerKeyStroke(
|
|
"o",
|
|
() => this.openPage("calendar" /* Calendar */)
|
|
);
|
|
this.registerKeyStroke(
|
|
"," /* Settings */,
|
|
() => this.openPage("settings" /* Settings */)
|
|
);
|
|
this.chatListViewModel.selectedChat.subscribeSilent(
|
|
this.updateContexts
|
|
);
|
|
this.selectedPage.subscribeSilent(this.updateContexts);
|
|
}
|
|
// context
|
|
get isOpen() {
|
|
return this.chatListViewModel.selectedChat.value == this;
|
|
}
|
|
get contextSelection() {
|
|
return this.selectedPage.value;
|
|
}
|
|
};
|
|
|
|
// src/ViewModel/Global/notificationViewModel.ts
|
|
var NotificationViewModel = class {
|
|
// init
|
|
constructor(chatListViewModel2, settingsViewModel2) {
|
|
this.chatListViewModel = chatListViewModel2;
|
|
this.settingsViewModel = settingsViewModel2;
|
|
// 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) => {
|
|
if (this.seenMessageIds.has(message.fileId)) return;
|
|
if (this.chatListViewModel.selectedChat.value == void 0) return;
|
|
if (message.senderId == this.settingsViewModel.settingsModel.userid)
|
|
return;
|
|
const notification = this.createNotification(message);
|
|
const currentChat = this.chatListViewModel.selectedChat.value.chatModel.id;
|
|
const currentPage = this.chatListViewModel.selectedChat.value.selectedPage.value;
|
|
if (notification.fullChannel == 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.id == notification.fullChannel);
|
|
if (!chat) return;
|
|
chat.open();
|
|
chat.openPage("messages" /* Messages */);
|
|
};
|
|
// loop
|
|
this.loop = () => {
|
|
if (this.messagesInMarquee.length == 0) {
|
|
this.marquee.value = void 0;
|
|
return this.stopLoop();
|
|
}
|
|
const notification = this.messagesInMarquee.shift();
|
|
if (!notification) return;
|
|
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();
|
|
};
|
|
}
|
|
// util
|
|
createNotification(message) {
|
|
const fullChannel = ChatModel.splitChannel(message.channel)[0];
|
|
const chat = this.chatListViewModel.getDisplayName(fullChannel);
|
|
return {
|
|
messageId: message.fileId,
|
|
chat,
|
|
fullChannel,
|
|
sender: message.senderName,
|
|
body: message.body
|
|
};
|
|
}
|
|
};
|
|
|
|
// src/ViewModel/Chat/chatListViewModel.ts
|
|
var ChatListViewModel = class {
|
|
// init
|
|
constructor(coreViewModel2, settingsViewModel2, connectionViewModel2, contactListViewModel2) {
|
|
this.coreViewModel = coreViewModel2;
|
|
this.settingsViewModel = settingsViewModel2;
|
|
this.connectionViewModel = connectionViewModel2;
|
|
this.contactListViewModel = contactListViewModel2;
|
|
// data
|
|
this.chatIndexManager = new IndexManager(
|
|
(chatViewModel) => chatViewModel.settingsPageViewModel.name.value
|
|
);
|
|
// state
|
|
this.chatQuery = new State("");
|
|
this.chatViewModels = new ListState();
|
|
this.chatMatches = new ListState();
|
|
this.selectedChat = new State(
|
|
void 0
|
|
);
|
|
// guards
|
|
this.cannotCreateChat = createProxyState(
|
|
[this.chatQuery],
|
|
() => this.chatQuery.value == ""
|
|
);
|
|
// methods
|
|
this.createChat = () => {
|
|
const chatModel = this.coreViewModel.chatListModel.createChat(this.chatQuery.value);
|
|
this.chatQuery.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,
|
|
this.contactListViewModel
|
|
);
|
|
};
|
|
this.updateIndices = () => {
|
|
this.chatIndexManager.update([...this.chatViewModels.value.values()]);
|
|
for (const chatViewModel of this.chatViewModels.value) {
|
|
chatViewModel.updateIndex();
|
|
}
|
|
};
|
|
// view
|
|
this.openChat = (chatViewModel) => {
|
|
this.selectedChat.value = chatViewModel;
|
|
this.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);
|
|
}
|
|
this.updateIndices();
|
|
};
|
|
// utility
|
|
this.getDisplayName = (fullChannel) => {
|
|
for (const vm of this.chatViewModels.value) {
|
|
const fullReference = vm.chatModel.id;
|
|
if (fullReference == fullChannel) return vm.chatModel.info.name;
|
|
}
|
|
return fullChannel;
|
|
};
|
|
this.notificationViewModel = new NotificationViewModel(
|
|
this,
|
|
this.settingsViewModel
|
|
);
|
|
this.loadChats();
|
|
implementFilter(
|
|
this.chatViewModels,
|
|
this.chatMatches,
|
|
this.chatQuery,
|
|
(chat) => chat.chatModel.info.name
|
|
);
|
|
}
|
|
};
|
|
|
|
// src/View/Components/option.tsx
|
|
function Option(text, value, selectedOnCreate) {
|
|
return /* @__PURE__ */ createElement("option", { value, "toggle:selected": selectedOnCreate }, text);
|
|
}
|
|
var StringToOption = (string) => {
|
|
return Option(string, string, false);
|
|
};
|
|
var VersionIdToOption = (versionId) => {
|
|
const [date, rest] = versionId.split("T");
|
|
const [time] = rest.split(".");
|
|
const readableName = `${date} ${time}`;
|
|
return Option(readableName, versionId, false);
|
|
};
|
|
|
|
// src/View/Components/newItemEntry.tsx
|
|
function NewItemEntry(coreViewModel2, query, fn) {
|
|
const isHidden = createProxyState([query], () => query.value == "");
|
|
const label = createProxyState(
|
|
[query],
|
|
() => coreViewModel2.translations.general.createLabel(query.value)
|
|
);
|
|
return /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "standard slide-up",
|
|
"toggle:hidden": isHidden,
|
|
"on:click": fn
|
|
},
|
|
/* @__PURE__ */ createElement("span", { "subscribe:innerText": label }),
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "add")
|
|
);
|
|
}
|
|
|
|
// src/View/Components/homePageButton.tsx
|
|
function HomePageButton(action, label, icon, key) {
|
|
return /* @__PURE__ */ createElement("button", { class: "tile flex-no", "on:click": action, ctkeystroke: key }, /* @__PURE__ */ createElement("span", { class: "icon" }, icon), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, label)));
|
|
}
|
|
|
|
// src/View/Components/chatEntry.tsx
|
|
function ChatEntry(chatViewModel) {
|
|
const view = /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "tile colored-tile chat-entry animate-highlight slide-up",
|
|
"set:color": chatViewModel.settingsPageViewModel.color,
|
|
style: "height: 8rem",
|
|
"on:click": chatViewModel.open,
|
|
"toggle:highlight": chatViewModel.hasUnreadMessages
|
|
},
|
|
/* @__PURE__ */ createElement(
|
|
"span",
|
|
{
|
|
class: "shadow",
|
|
"subscribe:innerText": chatViewModel.settingsPageViewModel.name
|
|
}
|
|
),
|
|
/* @__PURE__ */ createElement(
|
|
"h2",
|
|
{
|
|
"subscribe:innerText": chatViewModel.settingsPageViewModel.name
|
|
}
|
|
)
|
|
);
|
|
chatViewModel.index.subscribe((newIndex) => {
|
|
view.style.order = newIndex;
|
|
});
|
|
return view;
|
|
}
|
|
var ChatViewModelToChatEntry = (chatViewModel) => {
|
|
return ChatEntry(chatViewModel);
|
|
};
|
|
|
|
// src/View/homePage.tsx
|
|
function HomePage(coreViewModel2, storageViewModel2, settingsViewModel2, connectionViewModel2, fileTransferViewModel2, chatListViewModel2) {
|
|
const greeting = createProxyState(
|
|
[settingsViewModel2.username],
|
|
() => coreViewModel2.translations.homePage.greeting(
|
|
settingsViewModel2.username.value
|
|
)
|
|
);
|
|
const isChatPlaceholderHidden = createProxyState(
|
|
[chatListViewModel2.chatMatches, chatListViewModel2.chatQuery],
|
|
() => chatListViewModel2.chatMatches.value.size > 0 || chatListViewModel2.chatQuery.value != ""
|
|
);
|
|
const overviewSection = /* @__PURE__ */ createElement("div", { id: "overview-section" }, /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "cell_tower"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.homePage.serverAddress), /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
list: "previous-connection-list",
|
|
placeholder: coreViewModel2.translations.homePage.serverAddressPlaceholder,
|
|
"bind:value": connectionViewModel2.serverAddressInput,
|
|
"on:enter": connectionViewModel2.connect
|
|
}
|
|
), /* @__PURE__ */ createElement(
|
|
"datalist",
|
|
{
|
|
hidden: true,
|
|
id: "previous-connection-list",
|
|
"children:append": [
|
|
connectionViewModel2.previousAddresses,
|
|
StringToOption
|
|
]
|
|
}
|
|
))), /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "danger flex justify-center",
|
|
"aria-label": coreViewModel2.translations.homePage.disconnectAudioLabel,
|
|
"on:click": connectionViewModel2.disconnect,
|
|
"toggle:disabled": connectionViewModel2.cannotDisonnect,
|
|
ctkeystroke: "x"
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "link_off")
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "flex justify-center",
|
|
"aria-label": coreViewModel2.translations.homePage.manageConnectionsAudioLabel,
|
|
"on:click": connectionViewModel2.showConnectionModal,
|
|
"toggle:disabled": connectionViewModel2.hasNoPreviousConnections
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "history")
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "primary flex justify-center",
|
|
"aria-label": coreViewModel2.translations.homePage.connectAudioLabel,
|
|
"on:click": connectionViewModel2.connect,
|
|
"toggle:disabled": connectionViewModel2.cannotConnect,
|
|
ctkeystroke: "c"
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "link")
|
|
)), /* @__PURE__ */ createElement("hr", null), HomePageButton(
|
|
settingsViewModel2.showSettingsModal,
|
|
coreViewModel2.translations.homePage.settingsButton,
|
|
"settings",
|
|
","
|
|
), HomePageButton(
|
|
fileTransferViewModel2.showDirectionSelectionModal,
|
|
coreViewModel2.translations.homePage.transferDataButton,
|
|
"sync_alt",
|
|
"t"
|
|
), HomePageButton(
|
|
storageViewModel2.showStorageModal,
|
|
coreViewModel2.translations.homePage.manageStorageButton,
|
|
"hard_drive",
|
|
"e"
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "primary",
|
|
"on:click": coreViewModel2.update,
|
|
"toggle:hidden": coreViewModel2.noUpdateAvailable
|
|
},
|
|
/* @__PURE__ */ createElement("span", { "subscribe:innerText": coreViewModel2.updateText }),
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "update")
|
|
), /* @__PURE__ */ createElement("hr", { class: "mobile-only" }));
|
|
const chatSection = /* @__PURE__ */ createElement("div", { id: "chat-section" }, /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
placeholder: coreViewModel2.translations.general.filterOrCreateLabel(
|
|
coreViewModel2.translations.homePage.typeChat
|
|
),
|
|
"bind:value": chatListViewModel2.chatQuery,
|
|
"on:enter": chatListViewModel2.createChat
|
|
}
|
|
)), /* @__PURE__ */ createElement("hr", null), NewItemEntry(
|
|
coreViewModel2,
|
|
chatListViewModel2.chatQuery,
|
|
chatListViewModel2.createChat
|
|
), /* @__PURE__ */ createElement("span", { class: "secondary", "toggle:hidden": isChatPlaceholderHidden }, coreViewModel2.translations.homePage.noChatsPlaceholder), /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
id: "chat-grid",
|
|
"children:append": [
|
|
chatListViewModel2.chatMatches,
|
|
ChatViewModelToChatEntry
|
|
]
|
|
}
|
|
));
|
|
return /* @__PURE__ */ createElement("article", { id: "home-page" }, /* @__PURE__ */ createElement("h1", { "subscribe:innerText": greeting }), /* @__PURE__ */ createElement("div", null, overviewSection, chatSection));
|
|
}
|
|
|
|
// src/View/Components/ribbonButton.tsx
|
|
function RibbonButton(label, icon, isSelected, select, isHighlighted = new State(false), key) {
|
|
return /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "ribbon-button animate-highlight",
|
|
"aria-label": label,
|
|
"toggle:selected": isSelected,
|
|
"toggle:highlight": isHighlighted,
|
|
"on:click": select,
|
|
ctkeystroke: key
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, icon)
|
|
);
|
|
}
|
|
|
|
// src/View/Components/chatViewToggleButton.tsx
|
|
function ChatViewToggleButton(label, icon, page, chatViewModel, key) {
|
|
function select() {
|
|
chatViewModel.openPage(page);
|
|
}
|
|
const isSelected = createProxyState(
|
|
[chatViewModel.selectedPage],
|
|
() => chatViewModel.selectedPage.value == page
|
|
);
|
|
const isHighlighted = new State(false);
|
|
if (page == "messages" /* Messages */) {
|
|
chatViewModel.hasUnreadMessages.subscribe(
|
|
(hasUnreadMessages) => isHighlighted.value = hasUnreadMessages
|
|
);
|
|
}
|
|
return RibbonButton(label, icon, isSelected, select, isHighlighted, key);
|
|
}
|
|
|
|
// src/View/Components/taskEntry.tsx
|
|
function TaskEntry(taskViewModel) {
|
|
const details = {
|
|
description: taskViewModel.description.value || "---",
|
|
priority_high: taskViewModel.priority.value || "---",
|
|
category: taskViewModel.category.value || "---",
|
|
clock_loader_40: taskViewModel.status.value || "---",
|
|
calendar_month: taskViewModel.date.value || "---",
|
|
schedule: taskViewModel.time.value || "---"
|
|
};
|
|
const className = createProxyState(
|
|
[taskViewModel.isNotNext],
|
|
() => `${taskViewModel.isNotNext.value ? "standard" : "primary"} tile flex-no`
|
|
);
|
|
const view = /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
draggable: "true",
|
|
"set:class": className,
|
|
style: "user-select: none; -webkit-user-select: none",
|
|
"on:click": taskViewModel.open,
|
|
"on:dragstart": taskViewModel.dragStart
|
|
},
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary", "toggle:hidden": taskViewModel.isNotNext }, taskViewModel.coreViewModel.translations.chatPage.calendar.eventNext, /* @__PURE__ */ createElement("hr", null)), /* @__PURE__ */ createElement(
|
|
"b",
|
|
{
|
|
class: "ellipsis",
|
|
"subscribe:innerText": taskViewModel.name
|
|
}
|
|
), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "grid secondary",
|
|
style: "grid-template-columns: repeat(2, 1fr); column-gap: 1rem; row-gap: .5rem"
|
|
},
|
|
...Object.entries(details).map((entry) => /* @__PURE__ */ createElement(
|
|
"span",
|
|
{
|
|
class: "flex-row align-center width-100 flex-no clip",
|
|
style: "gap: 1rem"
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon", style: "font-size: 1.1rem" }, entry[0]),
|
|
/* @__PURE__ */ createElement("span", { class: "ellipsis" }, entry[1])
|
|
))
|
|
))
|
|
);
|
|
taskViewModel.index.subscribe((newIndex) => {
|
|
view.style.order = newIndex;
|
|
});
|
|
return view;
|
|
}
|
|
var TaskViewModelToEntry = (taskViewModel) => {
|
|
return TaskEntry(taskViewModel);
|
|
};
|
|
|
|
// src/View/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("");
|
|
// methods
|
|
this.set = () => {
|
|
if (this.cannotSet.value == true) return;
|
|
this.taskViewModels.value.forEach((taskViewModel) => {
|
|
this.setValue(this.inputValue.value, taskViewModel);
|
|
});
|
|
};
|
|
this.taskViewModels = taskViewModels;
|
|
this.inputValue.value = initialValue;
|
|
this.setValue = valueSetter;
|
|
this.cannotSet = createProxyState(
|
|
[this.inputValue],
|
|
() => this.inputValue.value == "" || this.inputValue.value == initialValue
|
|
);
|
|
}
|
|
};
|
|
var TaskCategoryBulkChangeViewModel = class extends TaskPropertyBulkChangeViewModel {
|
|
constructor(taskViewModels, initialValue) {
|
|
super(
|
|
taskViewModels,
|
|
(newCategory, taskViewModel) => {
|
|
taskViewModel.category.value = newCategory;
|
|
taskViewModel.save();
|
|
},
|
|
initialValue
|
|
);
|
|
}
|
|
};
|
|
var TaskStatusBulkChangeViewModel = class extends TaskPropertyBulkChangeViewModel {
|
|
constructor(taskViewModels, initialValue) {
|
|
super(
|
|
taskViewModels,
|
|
(newStatus, taskViewModel) => {
|
|
taskViewModel.status.value = newStatus;
|
|
taskViewModel.save();
|
|
},
|
|
initialValue
|
|
);
|
|
}
|
|
};
|
|
|
|
// src/View/ChatPages/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 zoom" }, /* @__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
|
|
},
|
|
/* @__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 allow-drag-move" }, /* @__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;
|
|
}
|
|
);
|
|
}
|
|
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);
|
|
}
|
|
const view = /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "status-column gap allow-drag-move",
|
|
"on:dragover": ViewController.allowDrop,
|
|
"on:drop": drop,
|
|
"children:append": [taskViewModels, TaskViewModelToEntry]
|
|
}
|
|
);
|
|
index.subscribe((newIndex) => {
|
|
view.style.order = newIndex;
|
|
});
|
|
return view;
|
|
}
|
|
|
|
// 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 zoom allow-drag-move",
|
|
"children:append": [categories, categoryNameConverter]
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}
|
|
function Column(coreViewModel2, categoryName, index, boardViewModel) {
|
|
return FilteredList(
|
|
{ category: categoryName },
|
|
(taskViewModel) => taskViewModel.task,
|
|
boardViewModel.filteredTaskViewModels,
|
|
(taskViewModels) => {
|
|
const viewModel = new TaskCategoryBulkChangeViewModel(
|
|
taskViewModels,
|
|
categoryName
|
|
);
|
|
function drop() {
|
|
boardViewModel.handleDropWithinBoard(categoryName);
|
|
}
|
|
const view = /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "flex-column flex-no",
|
|
"on:dragover": ViewController.allowDrop,
|
|
"on:drop": drop
|
|
},
|
|
/* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
placeholder: coreViewModel2.translations.chatPage.task.renameCategoryInputPlaceholder,
|
|
"bind:value": viewModel.inputValue,
|
|
"on:enter": viewModel.set
|
|
}
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "primary",
|
|
"on:click": viewModel.set,
|
|
"toggle:disabled": viewModel.cannotSet
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "check")
|
|
)),
|
|
/* @__PURE__ */ createElement("hr", null),
|
|
/* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "kanban-column",
|
|
"children:append": [taskViewModels, TaskViewModelToEntry]
|
|
}
|
|
)
|
|
);
|
|
index.subscribe((newIndex) => {
|
|
view.style.order = newIndex;
|
|
});
|
|
return view;
|
|
}
|
|
);
|
|
}
|
|
|
|
// src/View/Components/dangerousActionButton.tsx
|
|
function DangerousActionButton(coreViewModel2, label, icon, action) {
|
|
const isActionRequested = new State(false);
|
|
const cannotConfirm = createProxyState(
|
|
[isActionRequested],
|
|
() => isActionRequested.value == false
|
|
);
|
|
function requestAction() {
|
|
isActionRequested.value = true;
|
|
}
|
|
function abort() {
|
|
isActionRequested.value = false;
|
|
}
|
|
return /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement("button", { class: "flex", "on:click": abort, "toggle:hidden": cannotConfirm }, coreViewModel2.translations.general.abortButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "undo")), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "danger flex",
|
|
"on:click": requestAction,
|
|
"toggle:hidden": isActionRequested
|
|
},
|
|
label,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, icon)
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "danger flex",
|
|
"on:click": action,
|
|
"toggle:hidden": cannotConfirm
|
|
},
|
|
coreViewModel2.translations.general.confirmButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "warning")
|
|
));
|
|
}
|
|
|
|
// src/View/Modals/taskSettingsModal.tsx
|
|
function TaskSettingsModal(coreViewModel2, taskViewModel) {
|
|
const categorySuggestionId = v4_default();
|
|
const statusSuggestionId = v4_default();
|
|
const BoardOptionConverter = (entry) => {
|
|
const isSelected = entry[0] == taskViewModel.task.boardId;
|
|
return Option(entry[1], entry[0], isSelected);
|
|
};
|
|
return /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "modal task-settings",
|
|
open: true,
|
|
"toggle:full-description": taskViewModel.isPresentingFullScreenDescription
|
|
},
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", { id: "standard-main" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.chatPage.task.taskSettingsHeadline), /* @__PURE__ */ createElement("div", { class: "column-wrapper" }, /* @__PURE__ */ createElement("div", { class: "flex-column" }, /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskNameLabel), /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
"bind:value": taskViewModel.name
|
|
}
|
|
))), /* @__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("div", { class: "flex-row justify-end" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "width-50",
|
|
"on:click": taskViewModel.openFullscreenDecription
|
|
},
|
|
coreViewModel2.translations.general.fullscreenButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "fullscreen")
|
|
))), /* @__PURE__ */ createElement("hr", { class: "mobile-only" }), /* @__PURE__ */ createElement("div", { class: "flex-column" }, /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "category"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskCategoryLabel), /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
"bind:value": taskViewModel.category,
|
|
list: categorySuggestionId
|
|
}
|
|
))), /* @__PURE__ */ createElement(
|
|
"datalist",
|
|
{
|
|
hidden: true,
|
|
id: categorySuggestionId,
|
|
"children:append": [
|
|
taskViewModel.containingViewModel.taskCategorySuggestions,
|
|
StringToOption
|
|
]
|
|
}
|
|
), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "clock_loader_40"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskStatusLabel), /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
"bind:value": taskViewModel.status,
|
|
list: statusSuggestionId
|
|
}
|
|
))), /* @__PURE__ */ createElement(
|
|
"datalist",
|
|
{
|
|
hidden: true,
|
|
id: statusSuggestionId,
|
|
"children:append": [
|
|
taskViewModel.containingViewModel.taskStatusSuggestions,
|
|
StringToOption
|
|
]
|
|
}
|
|
), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "priority_high"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskPriorityLabel), /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
type: "number",
|
|
"bind:value": taskViewModel.priority
|
|
}
|
|
))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "calendar_month"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskDateLabel), /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
type: "date",
|
|
"bind:value": taskViewModel.date
|
|
}
|
|
))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "schedule"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.taskTimeLabel), /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
type: "time",
|
|
"bind:value": taskViewModel.time
|
|
}
|
|
))))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("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("div", { class: "width-input" }, DangerousActionButton(
|
|
coreViewModel2,
|
|
coreViewModel2.translations.chatPage.task.deleteTaskButton,
|
|
"delete_forever",
|
|
taskViewModel.deleteTask
|
|
))), /* @__PURE__ */ createElement("main", { id: "fullscreen-main" }, /* @__PURE__ */ createElement(
|
|
"textarea",
|
|
{
|
|
style: "height: 100%; width: 100%; max-width: unset",
|
|
"bind:value": taskViewModel.description
|
|
}
|
|
)), /* @__PURE__ */ createElement("div", { class: "flex-row width-100", id: "fullscreen-exit" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "flex",
|
|
"on:click": taskViewModel.closeFullscreenDecription
|
|
},
|
|
coreViewModel2.translations.general.exitButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "fullscreen_exit")
|
|
)), /* @__PURE__ */ createElement("div", { class: "flex-row width-100", id: "controls" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "flex",
|
|
"on:click": taskViewModel.closeAndDiscard,
|
|
ctkeystroke: getKeySymbol("backspace" /* CloseOrCancel */)
|
|
},
|
|
coreViewModel2.translations.general.closeButton
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "flex primary",
|
|
"on:click": taskViewModel.closeAndSave,
|
|
ctkeystroke: getKeySymbol("enter" /* Apply */)
|
|
},
|
|
coreViewModel2.translations.general.saveButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "save")
|
|
)))
|
|
);
|
|
}
|
|
|
|
// 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/Modals/searchModal.tsx
|
|
function SearchModal(coreViewModel2, searchViewModel, headline, isOpen) {
|
|
function close() {
|
|
isOpen.value = false;
|
|
}
|
|
const suggestionId = v4_default();
|
|
isOpen.subscribe((isOpen2) => {
|
|
if (!isOpen2) return;
|
|
ViewController.setFocusWithDelay();
|
|
});
|
|
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" }, /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
id: "focused",
|
|
style: "max-width: unset",
|
|
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: "standard",
|
|
"aria-label": coreViewModel2.translations.general.searchButtonClearAudioLabel,
|
|
"on:click": searchViewModel.clear,
|
|
"toggle:disabled": searchViewModel.cannotClear,
|
|
ctkeystroke: getKeySymbol("-" /* Reset */)
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "close")
|
|
), /* @__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("div", { "toggle:hidden": searchViewModel.hasNoSuggestions }, /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.general.searchSuggestionsLabel), /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "flex-column gap",
|
|
"children:append": [
|
|
searchViewModel.suggestions,
|
|
(suggestion) => SuggestionView(
|
|
suggestion,
|
|
searchViewModel,
|
|
coreViewModel2
|
|
)
|
|
]
|
|
}
|
|
))), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
"on:click": close,
|
|
ctkeystroke: getKeySymbol("backspace" /* CloseOrCancel */)
|
|
},
|
|
coreViewModel2.translations.general.closeButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "close")
|
|
)));
|
|
}
|
|
function SuggestionView(suggestion, searchViewModel, coreViewModel2) {
|
|
const isApplied = createProxyState(
|
|
[searchViewModel.appliedQuery],
|
|
() => searchViewModel.appliedQuery.value == suggestion
|
|
);
|
|
function deleteSuggestion() {
|
|
searchViewModel.deleteSuggestion(suggestion);
|
|
}
|
|
function applySuggestion() {
|
|
searchViewModel.search(suggestion);
|
|
}
|
|
return DeletableListItem(
|
|
coreViewModel2,
|
|
suggestion,
|
|
/* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "primary",
|
|
"aria-label": coreViewModel2.translations.general.applyButton,
|
|
"on:click": applySuggestion,
|
|
"toggle:disabled": isApplied
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "check")
|
|
),
|
|
deleteSuggestion
|
|
);
|
|
}
|
|
|
|
// src/View/Components/colorPicker.tsx
|
|
function ColorPicker(selectedColor) {
|
|
return /* @__PURE__ */ createElement("div", { class: "flex-row gap width-input" }, ...Object.values(Colors).map((color) => {
|
|
const isSelected = createProxyState(
|
|
[selectedColor],
|
|
() => selectedColor.value == color
|
|
);
|
|
function setColor() {
|
|
selectedColor.value = color;
|
|
}
|
|
return /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
color,
|
|
class: "fill-color width-100 flex",
|
|
style: "height: 2rem",
|
|
"toggle:selected": isSelected,
|
|
"on:click": setColor
|
|
}
|
|
);
|
|
}));
|
|
}
|
|
|
|
// src/View/Modals/boardSettingsModal.tsx
|
|
function BoardSettingsModal(coreViewModel2, boardViewModel) {
|
|
boardViewModel.isPresentingSettingsModal.subscribe(
|
|
ViewController.setFocusWithDelay
|
|
);
|
|
return /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "modal",
|
|
"toggle:open": boardViewModel.isPresentingSettingsModal
|
|
},
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.chatPage.task.boardSettingsHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.task.boardNameInputLabel), /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
id: "focused",
|
|
"on:enter": boardViewModel.saveSettings,
|
|
"bind:value": boardViewModel.name
|
|
}
|
|
))), /* @__PURE__ */ createElement("hr", null), ColorPicker(boardViewModel.color), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton(
|
|
coreViewModel2,
|
|
coreViewModel2.translations.chatPage.task.deleteBoardButton,
|
|
"delete_forever",
|
|
boardViewModel.deleteBoard
|
|
))), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
"on:click": boardViewModel.hideSettings,
|
|
ctkeystroke: getKeySymbol("enter" /* Apply */)
|
|
},
|
|
coreViewModel2.translations.general.closeButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "close")
|
|
))
|
|
);
|
|
}
|
|
|
|
// src/View/Components/boardViewToggleButton.tsx
|
|
function BoardViewToggleButton(label, icon, page, boardViewModel, key) {
|
|
function select() {
|
|
boardViewModel.selectedPage.value = page;
|
|
boardViewModel.resetPinchZoom();
|
|
}
|
|
const isSelected = createProxyState(
|
|
[boardViewModel.selectedPage],
|
|
() => boardViewModel.selectedPage.value == page
|
|
);
|
|
return RibbonButton(label, icon, isSelected, select, void 0, key);
|
|
}
|
|
|
|
// src/View/ChatPages/boardPage.tsx
|
|
function BoardPage(coreViewModel2, boardViewModel) {
|
|
boardViewModel.loadData();
|
|
const persistenceId = boardViewModel.boardInfo.fileId;
|
|
const pages = ViewController.boardPages.setState(
|
|
persistenceId,
|
|
() => 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
|
|
]
|
|
}
|
|
);
|
|
}
|
|
}
|
|
})
|
|
);
|
|
const wrapper = /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "content main-content allow-drag-move",
|
|
"children:set": pages
|
|
}
|
|
);
|
|
implementPinchZoom(wrapper, boardViewModel.pinchToZoomData);
|
|
const taskSettingsModal = createProxyState(
|
|
[boardViewModel.selectedTaskViewModel],
|
|
() => {
|
|
if (boardViewModel.selectedTaskViewModel.value == void 0) {
|
|
return /* @__PURE__ */ createElement("div", null);
|
|
} else {
|
|
ViewController.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
|
|
},
|
|
/* @__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,
|
|
ctkeystroke: getKeySymbol("." /* Options */)
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "dock_to_right")
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "ghost",
|
|
"aria-label": coreViewModel2.translations.chatPage.task.boardSettingsHeadline,
|
|
"on:click": boardViewModel.showSettings,
|
|
ctkeystroke: getKeySymbol("," /* Settings */)
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "settings")
|
|
)), /* @__PURE__ */ createElement("span", { class: "scroll-h ribbon" }, BoardViewToggleButton(
|
|
coreViewModel2.translations.chatPage.task.listViewButtonAudioLabel,
|
|
"view_list",
|
|
"list" /* List */,
|
|
boardViewModel,
|
|
"j"
|
|
), BoardViewToggleButton(
|
|
coreViewModel2.translations.chatPage.task.kanbanViewButtonAudioLabel,
|
|
"view_kanban",
|
|
"kanban" /* Kanban */,
|
|
boardViewModel,
|
|
"k"
|
|
), BoardViewToggleButton(
|
|
coreViewModel2.translations.chatPage.task.statusViewButtonAudioLabel,
|
|
"grid_view",
|
|
"status-grid" /* StatusGrid */,
|
|
boardViewModel,
|
|
"l"
|
|
)), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "ghost inset-outline",
|
|
"aria-label": coreViewModel2.translations.chatPage.task.filterTasksButtonAudioLabel,
|
|
"on:click": boardViewModel.showFilterModal,
|
|
"toggle:selected": boardViewModel.isFilterActive,
|
|
ctkeystroke: "=" /* Filter */
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "filter_alt")
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "ghost",
|
|
"aria-label": coreViewModel2.translations.chatPage.task.createTaskButtonAudioLabel,
|
|
"on:click": boardViewModel.createTask,
|
|
ctkeystroke: getKeySymbol("a" /* Create */)
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "add")
|
|
))), wrapper, BoardSettingsModal(coreViewModel2, boardViewModel), SearchModal(
|
|
coreViewModel2,
|
|
boardViewModel.searchViewModel,
|
|
coreViewModel2.translations.chatPage.task.filterTasksHeadline,
|
|
boardViewModel.isPresentingFilterModal
|
|
), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal }));
|
|
}
|
|
|
|
// src/View/Components/placeholderView.tsx
|
|
function PlaceholderView(text) {
|
|
return /* @__PURE__ */ createElement("div", { class: "width-100 height-100 flex-column justify-center align-center" }, /* @__PURE__ */ createElement("span", { class: "secondary slide-up" }, text));
|
|
}
|
|
|
|
// src/View/Components/boardEntry.tsx
|
|
function BoardEntry(boardViewModel) {
|
|
const view = /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
"set:color": boardViewModel.color,
|
|
class: "tile colored-tile slide-up",
|
|
"toggle:selected": boardViewModel.isSelected,
|
|
"on:click": boardViewModel.select,
|
|
"on:dragover": ViewController.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 persistenceId = taskPageViewModel.chatViewModel.chatModel.id;
|
|
const pages = ViewController.taskPages.setState(
|
|
persistenceId,
|
|
() => 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
|
|
},
|
|
/* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
id: "board-list",
|
|
class: "pane-wrapper side background",
|
|
"set:color": taskPageViewModel.chatViewModel.displayedColor
|
|
},
|
|
/* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
class: "no-outline",
|
|
"bind:value": taskPageViewModel.boardQuery,
|
|
"on:enter": taskPageViewModel.createBoard,
|
|
placeholder: coreViewModel2.translations.general.filterOrCreateLabel(
|
|
coreViewModel2.translations.chatPage.task.typeBoard
|
|
)
|
|
}
|
|
))), /* @__PURE__ */ createElement("div", { class: "content gap" }, NewItemEntry(
|
|
coreViewModel2,
|
|
taskPageViewModel.boardQuery,
|
|
taskPageViewModel.createBoard
|
|
), /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "grid gap",
|
|
style: "grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr))",
|
|
"children:append": [
|
|
taskPageViewModel.boardMatches,
|
|
BoardViewModelToEntry
|
|
]
|
|
}
|
|
)))
|
|
),
|
|
/* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
id: "board-content",
|
|
class: "pane-wrapper",
|
|
"children:set": pages
|
|
}
|
|
)
|
|
);
|
|
}
|
|
|
|
// src/View/Components/confirmationDialog.tsx
|
|
function ConfirmationDialog(coreViewModel2, buttonLabel, icon, headline, description, action) {
|
|
const isModalOpen = new State(false);
|
|
function openModal() {
|
|
isModalOpen.value = true;
|
|
}
|
|
function closeModal() {
|
|
isModalOpen.value = false;
|
|
}
|
|
return /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "danger width-100",
|
|
"on:click": openModal
|
|
},
|
|
/* @__PURE__ */ createElement("span", null, buttonLabel),
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, icon)
|
|
), /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "modal",
|
|
"toggle:open": isModalOpen
|
|
},
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, headline), /* @__PURE__ */ createElement("p", { class: "secondary" }, description)), /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "standard width-50",
|
|
"on:click": closeModal
|
|
},
|
|
coreViewModel2.translations.general.cancelButton
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "danger width-50",
|
|
"on:click": action
|
|
},
|
|
/* @__PURE__ */ createElement("span", null, buttonLabel),
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, icon)
|
|
)))
|
|
));
|
|
}
|
|
|
|
// src/View/ChatPages/settingsPage.tsx
|
|
function SettingsPage(coreViewModel2, settingsPageViewModel) {
|
|
settingsPageViewModel.loadData();
|
|
const secondaryChannelConverter = (secondaryChannel) => {
|
|
return DeletableListItem(
|
|
coreViewModel2,
|
|
secondaryChannel,
|
|
/* @__PURE__ */ createElement("span", null),
|
|
() => {
|
|
settingsPageViewModel.removeSecondaryChannel(secondaryChannel);
|
|
}
|
|
);
|
|
};
|
|
return /* @__PURE__ */ createElement("div", { id: "settings-page" }, /* @__PURE__ */ createElement("div", { class: "pane-wrapper" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", { class: "title" }, coreViewModel2.translations.chatPage.settings.settingsHeadline)), /* @__PURE__ */ createElement("div", { class: "content" }, /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.settings.nameLabel), /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
"bind:value": settingsPageViewModel.nameInput,
|
|
"on:enter": settingsPageViewModel.setName
|
|
}
|
|
))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end width-input" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "width-50",
|
|
"aria-label": coreViewModel2.translations.chatPage.settings.setNameButtonAudioLabel,
|
|
"on:click": settingsPageViewModel.setName,
|
|
"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 width-input" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "width-50",
|
|
"on:click": settingsPageViewModel.randomizeKey
|
|
},
|
|
coreViewModel2.translations.chatPage.settings.randomizeKey,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "shuffle")
|
|
), /* @__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("div", { class: "flex-row justify-end width-input" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "width-50",
|
|
"aria-label": coreViewModel2.translations.chatPage.settings.setColorButtonAudioLabel,
|
|
"on:click": settingsPageViewModel.applyColor,
|
|
"toggle:disabled": settingsPageViewModel.cannotSetColor
|
|
},
|
|
coreViewModel2.translations.general.setButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "check")
|
|
)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, ConfirmationDialog(coreViewModel2, coreViewModel2.translations.chatPage.settings.deleteChatButton, "delete_forever", coreViewModel2.translations.chatPage.settings.deleteChatHeadline, coreViewModel2.translations.chatPage.settings.deleteChatDescription, settingsPageViewModel.remove))))));
|
|
}
|
|
|
|
// src/View/Components/messageReactionFilterButton.tsx
|
|
function MessageReactionFilterButton(messagePageViewModel, content) {
|
|
const select = () => {
|
|
messagePageViewModel.setReactionFilter(content);
|
|
};
|
|
const isSelected = createProxyState(
|
|
[messagePageViewModel.reactionFilter],
|
|
() => messagePageViewModel.reactionFilter.value == content
|
|
);
|
|
return /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "flex justify-center",
|
|
"on:click": select,
|
|
"toggle:selected": isSelected
|
|
},
|
|
content
|
|
);
|
|
}
|
|
|
|
// src/View/Modals/messageFilterModal.tsx
|
|
function MessageFilterModal(coreViewModel2, messagePageViewModel, converter) {
|
|
const noFilter = createProxyState(
|
|
[messagePageViewModel.reactionFilter],
|
|
() => messagePageViewModel.reactionFilter.value == void 0
|
|
);
|
|
messagePageViewModel.isFilterModalOpen.subscribe((isOpen) => {
|
|
if (isOpen == false) return;
|
|
ViewController.setFocusWithDelay();
|
|
});
|
|
createProxyState([messagePageViewModel.isFilterModalOpen], () => {
|
|
if (messagePageViewModel.isFilterModalOpen.value == false) return;
|
|
ViewController.setFocusWithDelay();
|
|
});
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": messagePageViewModel.isFilterModalOpen }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.chatPage.message.messageFilterHeadline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
id: "focused",
|
|
placeholder: coreViewModel2.translations.general.searchLabel,
|
|
"bind:value": messagePageViewModel.searchViewModel.searchInput,
|
|
"on:enter": messagePageViewModel.searchViewModel.applySearch
|
|
}
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "primary",
|
|
"aria-label": coreViewModel2.translations.general.searchButtonAudioLabel,
|
|
"on:click": messagePageViewModel.searchViewModel.applySearch,
|
|
"toggle:disabled": messagePageViewModel.searchViewModel.cannotApplySearch
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "search")
|
|
)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.chatPage.message.messageFilterReactionsHadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
"toggle:selected": noFilter,
|
|
"on:click": messagePageViewModel.revokeReactionFilter
|
|
},
|
|
coreViewModel2.translations.chatPage.message.messageFilterAllReactionsButton
|
|
), /* @__PURE__ */ createElement("div", { class: "grid gap message-reaction-filter" }, MessageReactionFilterButton(
|
|
messagePageViewModel,
|
|
"\u{1F44D}" /* ThumbsUp */
|
|
), MessageReactionFilterButton(
|
|
messagePageViewModel,
|
|
"\u2705" /* Check */
|
|
), MessageReactionFilterButton(
|
|
messagePageViewModel,
|
|
"\u{1F6D1}" /* Stop */
|
|
), MessageReactionFilterButton(
|
|
messagePageViewModel,
|
|
"\u2757\uFE0F" /* Attention */
|
|
), MessageReactionFilterButton(
|
|
messagePageViewModel,
|
|
"\u203C\uFE0F" /* DoubleAttention */
|
|
), MessageReactionFilterButton(
|
|
messagePageViewModel,
|
|
"\u2753" /* Question */
|
|
)))), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
"on:click": messagePageViewModel.hideFilterModal,
|
|
ctkeystroke: getKeySymbol("backspace" /* CloseOrCancel */)
|
|
},
|
|
coreViewModel2.translations.general.closeButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "close")
|
|
)));
|
|
}
|
|
|
|
// src/View/Components/replyPreview.tsx
|
|
function ReplyPreview(coreViewModel2, chatMessageViewModel) {
|
|
return /* @__PURE__ */ createElement("div", { class: "reply-preview" }, /* @__PURE__ */ createElement("div", { class: "surface blur" }, /* @__PURE__ */ createElement(
|
|
"span",
|
|
{
|
|
class: "secondary",
|
|
"subscribe:innerText": chatMessageViewModel.contact.name
|
|
}
|
|
), /* @__PURE__ */ createElement(
|
|
"b",
|
|
{
|
|
class: "ellipsis",
|
|
"subscribe:innerText": chatMessageViewModel.body
|
|
}
|
|
)), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "standard square blur",
|
|
"on:click": chatMessageViewModel.cancelReply,
|
|
"aria-label": coreViewModel2.translations.chatPage.message.cancelReplyAudioLabel,
|
|
ctkeystroke: getKeySymbol("backspace" /* CloseOrCancel */)
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "close")
|
|
));
|
|
}
|
|
|
|
// src/View/Components/replyLink.tsx
|
|
function ReplyLink(coreViewModel2, chatMessageViewModel) {
|
|
const isHidden = createProxyState(
|
|
[chatMessageViewModel.replyCount],
|
|
() => chatMessageViewModel.replyCount.value == 0
|
|
);
|
|
function select() {
|
|
chatMessageViewModel.messagePageViewModel.setReplyView(
|
|
chatMessageViewModel
|
|
);
|
|
}
|
|
return /* @__PURE__ */ createElement("div", { class: "reply-link", "toggle:hidden": isHidden, "on:click": select }, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.chatPage.message.replyPrefixLabel), /* @__PURE__ */ createElement(
|
|
"b",
|
|
{
|
|
class: "ellipsis",
|
|
"subscribe:innerText": chatMessageViewModel.replyCount
|
|
}
|
|
));
|
|
}
|
|
|
|
// src/View/Components/messageReactionButton.tsx
|
|
function MessageReactionButton(coreViewModel2, chatMessageViewModel, content) {
|
|
let audioLabel;
|
|
let count;
|
|
let isActive = createProxyState(
|
|
[chatMessageViewModel.userReaction],
|
|
() => chatMessageViewModel.userReaction.value == content
|
|
);
|
|
function sendReaction() {
|
|
chatMessageViewModel.sendReaction(content, isActive.value);
|
|
}
|
|
switch (content) {
|
|
case "\u{1F44D}" /* ThumbsUp */: {
|
|
audioLabel = coreViewModel2.translations.chatPage.message.thumbsUpReaction;
|
|
count = chatMessageViewModel.reactionsThumbsUpCount;
|
|
break;
|
|
}
|
|
case "\u2705" /* Check */: {
|
|
audioLabel = coreViewModel2.translations.chatPage.message.checkReaction;
|
|
count = chatMessageViewModel.reactionsCheckCount;
|
|
break;
|
|
}
|
|
case "\u{1F6D1}" /* Stop */: {
|
|
audioLabel = coreViewModel2.translations.chatPage.message.stopReaction;
|
|
count = chatMessageViewModel.reactionsStopCount;
|
|
break;
|
|
}
|
|
case "\u2757\uFE0F" /* Attention */: {
|
|
audioLabel = coreViewModel2.translations.chatPage.message.attentionReaction;
|
|
count = chatMessageViewModel.reactionsAttentionCount;
|
|
break;
|
|
}
|
|
case "\u203C\uFE0F" /* DoubleAttention */: {
|
|
audioLabel = coreViewModel2.translations.chatPage.message.doubleAttentionReaction;
|
|
count = chatMessageViewModel.reactionsDoubleAttentionCount;
|
|
break;
|
|
}
|
|
case "\u2753" /* Question */: {
|
|
audioLabel = coreViewModel2.translations.chatPage.message.questionReaction;
|
|
count = chatMessageViewModel.reactionsQuestionCount;
|
|
break;
|
|
}
|
|
}
|
|
return /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "flex",
|
|
"on:click": sendReaction,
|
|
"aria-label": audioLabel,
|
|
"toggle:selected": isActive,
|
|
"set:count": count
|
|
},
|
|
content,
|
|
/* @__PURE__ */ createElement("span", { "subscribe:innerText": count })
|
|
);
|
|
}
|
|
|
|
// src/View/Components/messageReactionButtonRow.tsx
|
|
function MessageReactionButtonRow(coreViewModel2, chatMessageViewModel) {
|
|
return /* @__PURE__ */ createElement("div", { class: "grid gap width-100 message-reaction-row" }, MessageReactionButton(
|
|
coreViewModel2,
|
|
chatMessageViewModel,
|
|
"\u{1F44D}" /* ThumbsUp */
|
|
), MessageReactionButton(
|
|
coreViewModel2,
|
|
chatMessageViewModel,
|
|
"\u2705" /* Check */
|
|
), MessageReactionButton(
|
|
coreViewModel2,
|
|
chatMessageViewModel,
|
|
"\u{1F6D1}" /* Stop */
|
|
), MessageReactionButton(
|
|
coreViewModel2,
|
|
chatMessageViewModel,
|
|
"\u2757\uFE0F" /* Attention */
|
|
), MessageReactionButton(
|
|
coreViewModel2,
|
|
chatMessageViewModel,
|
|
"\u203C\uFE0F" /* DoubleAttention */
|
|
), MessageReactionButton(
|
|
coreViewModel2,
|
|
chatMessageViewModel,
|
|
"\u2753" /* Question */
|
|
));
|
|
}
|
|
|
|
// src/View/Components/inlineReply.tsx
|
|
function InlineReply(chatMessageViewModel) {
|
|
const reply = chatMessageViewModel.inlineReply;
|
|
if (reply == void 0) return /* @__PURE__ */ createElement("div", null);
|
|
const scroll = () => {
|
|
ViewController.scrollToView(reply.chatMessage.fileId);
|
|
};
|
|
return /* @__PURE__ */ createElement("div", { class: "inline-reply", "on:click": scroll }, /* @__PURE__ */ createElement("span", { "subscribe:innerText": reply.contact.name }), /* @__PURE__ */ createElement("b", { class: "ellipsis", "subscribe:innerText": reply.body }));
|
|
}
|
|
|
|
// src/View/Components/messageReactionEntry.tsx
|
|
function MessageReactionEntry(reaction) {
|
|
return /* @__PURE__ */ createElement("div", { class: "tile flex-row" }, /* @__PURE__ */ createElement("span", { class: "flex width-100" }, reaction.senderName), /* @__PURE__ */ createElement("span", null, reaction.content));
|
|
}
|
|
|
|
// src/View/Components/infoTile.tsx
|
|
function InfoTile(icon, label, content) {
|
|
let contentState;
|
|
if (typeof content == "string") {
|
|
contentState = new State(content);
|
|
} else {
|
|
contentState = content;
|
|
}
|
|
return /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, icon), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, label), /* @__PURE__ */ createElement("b", { class: "break-word", "subscribe:innerText": contentState })));
|
|
}
|
|
|
|
// src/View/Modals/chatMessageInfoModal.tsx
|
|
function ChatMessageInfoModal(coreViewModel2, chatMessageViewModel) {
|
|
return /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "modal",
|
|
"toggle:open": chatMessageViewModel.isPresentingInfoModal
|
|
},
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.chatPage.message.messageInfoHeadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, InfoTile(
|
|
"account_circle",
|
|
coreViewModel2.translations.chatPage.message.sentBy,
|
|
chatMessageViewModel.senderId
|
|
), InfoTile(
|
|
"schedule",
|
|
coreViewModel2.translations.chatPage.message.timeSent,
|
|
chatMessageViewModel.dateSent
|
|
), InfoTile(
|
|
"forum",
|
|
coreViewModel2.translations.chatPage.message.channel,
|
|
chatMessageViewModel.channel
|
|
), InfoTile(
|
|
"description",
|
|
coreViewModel2.translations.chatPage.message.messageContent,
|
|
chatMessageViewModel.body
|
|
)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.copyMessage }, coreViewModel2.translations.chatPage.message.copyMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "content_copy")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.resendMessage }, coreViewModel2.translations.chatPage.message.resendMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "redo")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.decryptMessage }, coreViewModel2.translations.chatPage.message.decryptMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "key")), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
"on:click": chatMessageViewModel.reply,
|
|
"toggle:disabled": chatMessageViewModel.messagePageViewModel.isReplyViewActive
|
|
},
|
|
coreViewModel2.translations.chatPage.message.replyToMessageButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "reply")
|
|
)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, MessageReactionButtonRow(
|
|
coreViewModel2,
|
|
chatMessageViewModel
|
|
), /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "flex-column gap",
|
|
"children:append": [
|
|
chatMessageViewModel.allReactions,
|
|
MessageReactionEntry
|
|
]
|
|
}
|
|
))), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.hideInfoModal }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))
|
|
);
|
|
}
|
|
|
|
// src/View/Components/chatMessage.tsx
|
|
function ChatMessage4(coreViewModel2, chatMessageViewModel) {
|
|
const statusIcon = createProxyState(
|
|
[chatMessageViewModel.status],
|
|
() => {
|
|
switch (chatMessageViewModel.status.value) {
|
|
case "outbox" /* Outbox */:
|
|
return "hourglass_top";
|
|
case "sent" /* Sent */:
|
|
return "check";
|
|
case "received" /* Received */:
|
|
return "done_all";
|
|
default:
|
|
return "warning";
|
|
}
|
|
}
|
|
);
|
|
return /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "message-bubble",
|
|
id: chatMessageViewModel.chatMessage.fileId,
|
|
"toggle:sentbyuser": chatMessageViewModel.sentByUser,
|
|
"toggle:hidden": chatMessageViewModel.isHidden
|
|
},
|
|
InlineReply(chatMessageViewModel),
|
|
/* @__PURE__ */ createElement("div", { class: "main tile" }, /* @__PURE__ */ createElement("div", { class: "text-container" }, /* @__PURE__ */ createElement(
|
|
"span",
|
|
{
|
|
class: "sender-name ellipsis",
|
|
"subscribe:innerText": chatMessageViewModel.contact.name
|
|
}
|
|
), /* @__PURE__ */ createElement(
|
|
"span",
|
|
{
|
|
class: "body",
|
|
"subscribe:innerText": chatMessageViewModel.body
|
|
}
|
|
), /* @__PURE__ */ createElement("span", { class: "timestamp ellipsis" }, /* @__PURE__ */ createElement(
|
|
"span",
|
|
{
|
|
class: "icon",
|
|
"subscribe:innerText": statusIcon
|
|
}
|
|
), chatMessageViewModel.dateSent)), /* @__PURE__ */ createElement("div", { class: "button-container" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
"on:click": chatMessageViewModel.showInfoModal,
|
|
"aria-label": coreViewModel2.translations.chatPage.message.showMessageInfoButtonAudioLabel
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "info")
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "reply-button",
|
|
"on:click": chatMessageViewModel.reply,
|
|
"aria-label": coreViewModel2.translations.chatPage.message.replyToMessageButton,
|
|
"toggle:hidden": chatMessageViewModel.messagePageViewModel.isReplyViewActive
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "reply")
|
|
))),
|
|
MessageReactionButtonRow(coreViewModel2, chatMessageViewModel),
|
|
ReplyLink(coreViewModel2, chatMessageViewModel),
|
|
ChatMessageInfoModal(coreViewModel2, chatMessageViewModel)
|
|
);
|
|
}
|
|
|
|
// src/View/ChatPages/messagePage.tsx
|
|
function MessagePage(coreViewModel2, messagePageViewModel) {
|
|
messagePageViewModel.loadData();
|
|
const ChatMessageViewModelToView = (chatMessageViewModel) => {
|
|
return ChatMessage4(coreViewModel2, chatMessageViewModel);
|
|
};
|
|
const messageContainer = /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
id: "message-container",
|
|
"children:append": [
|
|
messagePageViewModel.filteredMessageViewModels,
|
|
ChatMessageViewModelToView
|
|
]
|
|
}
|
|
);
|
|
const persistenceId = messagePageViewModel.chatViewModel.chatModel.id;
|
|
const replyPreview = ViewController.inlineReplies.setState(
|
|
persistenceId,
|
|
() => createProxyState(
|
|
[messagePageViewModel.replyingMessage],
|
|
() => {
|
|
if (messagePageViewModel.replyingMessage.value == void 0)
|
|
return /* @__PURE__ */ createElement("div", null);
|
|
return ReplyPreview(
|
|
coreViewModel2,
|
|
messagePageViewModel.replyingMessage.value
|
|
);
|
|
}
|
|
)
|
|
);
|
|
function scrollDown(hard = false) {
|
|
if (hard) {
|
|
messageContainer.setAttribute("scroll-hard", "");
|
|
}
|
|
messageContainer.scrollTop = messageContainer.scrollHeight;
|
|
messageContainer.removeAttribute("scroll-hard");
|
|
}
|
|
function scrollDownIfApplicable() {
|
|
const scrollFromBottom = messageContainer.scrollHeight - (messageContainer.scrollTop + messageContainer.offsetHeight);
|
|
if (scrollFromBottom > 400) return;
|
|
scrollDown();
|
|
}
|
|
messagePageViewModel.filteredMessageViewModels.subscribeSilent(
|
|
scrollDownIfApplicable
|
|
);
|
|
bulkSubscribe(
|
|
[
|
|
messagePageViewModel.reactionFilter,
|
|
messagePageViewModel.searchViewModel.appliedQuery
|
|
],
|
|
scrollDown
|
|
);
|
|
messagePageViewModel.replyViewSelectedMessage.subscribeSilent(
|
|
(selectedMessage) => {
|
|
if (selectedMessage != void 0) return;
|
|
setTimeout(scrollDown, 100);
|
|
}
|
|
);
|
|
setTimeout(() => scrollDown(true), 100);
|
|
messagePageViewModel.focusSetter.subscribeSilent(() => {
|
|
ViewController.setFocusWithDelay();
|
|
});
|
|
const isInReplyView = createProxyState(
|
|
[messagePageViewModel.replyViewSelectedMessage],
|
|
() => messagePageViewModel.replyViewSelectedMessage.value != void 0
|
|
);
|
|
const title = createProxyState(
|
|
[messagePageViewModel.isReplyViewActive],
|
|
() => messagePageViewModel.isReplyViewActive.value ? coreViewModel2.translations.chatPage.message.replyHeaderLabel(
|
|
messagePageViewModel.replyViewSelectedMessage.value.body.value
|
|
) : coreViewModel2.translations.chatPage.message.messagesHeadline
|
|
);
|
|
return /* @__PURE__ */ createElement("div", { id: "message-page", "toggle:reply-view": isInReplyView }, /* @__PURE__ */ createElement("div", { class: "pane-wrapper" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "ghost",
|
|
"on:click": messagePageViewModel.resetReplyView,
|
|
"toggle:hidden": messagePageViewModel.isReplyViewInactive
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_back")
|
|
), /* @__PURE__ */ createElement(
|
|
"span",
|
|
{
|
|
class: "title ellipsis width-100 flex",
|
|
"subscribe:innerText": title
|
|
}
|
|
), /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "ghost inset-outline",
|
|
"on:click": messagePageViewModel.showFilterModal,
|
|
"aria-label": coreViewModel2.translations.chatPage.message.filterMessagesButtonAudioLabel,
|
|
"toggle:selected": messagePageViewModel.isFilterActive,
|
|
ctkeystroke: getKeySymbol("=" /* Filter */)
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "filter_alt")
|
|
))), /* @__PURE__ */ createElement("div", { class: "content" }, messageContainer, /* @__PURE__ */ createElement("div", { id: "composer" }, /* @__PURE__ */ createElement("div", { class: "content-width-constraint" }, /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "reply-preview-wrapper",
|
|
"children:set": replyPreview
|
|
}
|
|
), /* @__PURE__ */ createElement("div", { class: "input-width-constraint" }, /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
id: "focused",
|
|
class: "blur",
|
|
"bind:value": messagePageViewModel.composingMessage,
|
|
"on:enter": messagePageViewModel.sendMessage,
|
|
placeholder: coreViewModel2.translations.chatPage.message.composerInputPlaceholder
|
|
}
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "primary blur",
|
|
"aria-label": coreViewModel2.translations.chatPage.message.sendMessageButtonAudioLabel,
|
|
"on:click": messagePageViewModel.sendMessage,
|
|
"toggle:disabled": messagePageViewModel.cannotSendMessage
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "send")
|
|
))))))), MessageFilterModal(
|
|
coreViewModel2,
|
|
messagePageViewModel,
|
|
ChatMessageViewModelToView
|
|
));
|
|
}
|
|
|
|
// src/View/Components/monthGrid.tsx
|
|
function MonthGrid(coreViewModel2, monthGrid, selectedDate, handleDrop) {
|
|
const dayLabels = [];
|
|
let currentWeekday = monthGrid.firstDayOfWeek;
|
|
while (dayLabels.length < 7) {
|
|
dayLabels.push(
|
|
/* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.regional.weekdays.abbreviated[currentWeekday])
|
|
);
|
|
currentWeekday++;
|
|
if (currentWeekday == 7) currentWeekday = 0;
|
|
}
|
|
const offsetElements = [];
|
|
for (let i = 0; i < monthGrid.offset; i++) {
|
|
offsetElements.push(/* @__PURE__ */ createElement("div", null));
|
|
}
|
|
const converter = (taskViewModel) => {
|
|
const view = /* @__PURE__ */ createElement("span", { class: "ellipsis secondary" }, taskViewModel.task.name);
|
|
taskViewModel.index.subscribe((newIndex) => {
|
|
view.style.order = newIndex;
|
|
});
|
|
return view;
|
|
};
|
|
return /* @__PURE__ */ createElement("div", { class: "month-grid-wrapper" }, /* @__PURE__ */ createElement("div", { class: "day-labels" }, ...dayLabels), /* @__PURE__ */ createElement("div", { class: "month-grid" }, ...offsetElements, ...Object.entries(monthGrid.days).sort((a, b) => localeCompare(a[0], b[0])).map((entry) => {
|
|
const [date, mapState] = entry;
|
|
const isSelected = createProxyState(
|
|
[selectedDate],
|
|
() => selectedDate.value == parseInt(date)
|
|
);
|
|
const isToday = createProxyState(
|
|
[coreViewModel2.todayDate],
|
|
() => {
|
|
return monthGrid.isCurrentMonth == true && date == coreViewModel2.unwrappedTodayDate.getDate().toString().padStart(2, "0");
|
|
}
|
|
);
|
|
const eventCount = createProxyState(
|
|
[mapState],
|
|
() => mapState.value.size
|
|
);
|
|
const hasEvents = createProxyState(
|
|
[eventCount],
|
|
() => eventCount.value != 0
|
|
);
|
|
function select() {
|
|
selectedDate.value = parseInt(date);
|
|
}
|
|
function drop() {
|
|
handleDrop(date);
|
|
}
|
|
return /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "tile",
|
|
"on:click": select,
|
|
"toggle:selected": isSelected,
|
|
"toggle:today": isToday,
|
|
"on:dragover": ViewController.allowDrop,
|
|
"on:drop": drop
|
|
},
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, date), /* @__PURE__ */ createElement(
|
|
"span",
|
|
{
|
|
class: "event-count",
|
|
"toggle:has-events": hasEvents,
|
|
"subscribe:innerText": eventCount
|
|
}
|
|
), /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "event-list",
|
|
"children:append": [mapState, converter]
|
|
}
|
|
))
|
|
);
|
|
})));
|
|
}
|
|
|
|
// src/View/ChatPages/calendarPage.tsx
|
|
function CalendarPage(coreViewModel2, calendarPageViewModel) {
|
|
calendarPageViewModel.loadData();
|
|
const mainContent = createProxyState(
|
|
[calendarPageViewModel.monthGrid],
|
|
() => {
|
|
const monthGrid = calendarPageViewModel.monthGrid.value;
|
|
if (monthGrid == void 0) {
|
|
return /* @__PURE__ */ createElement("div", null);
|
|
} else {
|
|
let drop = function(date) {
|
|
calendarPageViewModel.handleDrop(
|
|
monthGrid.year.toString(),
|
|
monthGrid.month.toString(),
|
|
date
|
|
);
|
|
};
|
|
return MonthGrid(
|
|
coreViewModel2,
|
|
monthGrid,
|
|
calendarPageViewModel.selectedDate,
|
|
drop
|
|
);
|
|
}
|
|
}
|
|
);
|
|
const sidePaneContentWrapper = createProxyState(
|
|
[
|
|
calendarPageViewModel.selectedYear,
|
|
calendarPageViewModel.selectedMonth,
|
|
calendarPageViewModel.selectedDate
|
|
],
|
|
() => {
|
|
const listState = calendarPageViewModel.getEventsForDate();
|
|
if (listState == void 0) {
|
|
return /* @__PURE__ */ createElement("div", null);
|
|
} else {
|
|
const sidePaneContent = createProxyState(
|
|
[listState],
|
|
() => {
|
|
if (listState.value.size == 0) {
|
|
return PlaceholderView(
|
|
coreViewModel2.translations.chatPage.calendar.noEvents
|
|
);
|
|
} else {
|
|
return /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "flex-column gap slide-up padding-bottom",
|
|
"children:append": [
|
|
listState,
|
|
TaskViewModelToEntry
|
|
]
|
|
}
|
|
);
|
|
}
|
|
}
|
|
);
|
|
return /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "width-100 height-100",
|
|
"children:set": sidePaneContent
|
|
}
|
|
);
|
|
}
|
|
}
|
|
);
|
|
const taskSettingsModal = createProxyState(
|
|
[calendarPageViewModel.selectedTaskViewModel],
|
|
() => {
|
|
if (calendarPageViewModel.selectedTaskViewModel.value == void 0) {
|
|
return /* @__PURE__ */ createElement("div", null);
|
|
} else {
|
|
ViewController.setFocusWithDelay();
|
|
return TaskSettingsModal(
|
|
coreViewModel2,
|
|
calendarPageViewModel.selectedTaskViewModel.value
|
|
);
|
|
}
|
|
}
|
|
);
|
|
return /* @__PURE__ */ createElement("div", { id: "calendar-page" }, /* @__PURE__ */ createElement("div", { class: "pane-wrapper grid-pane-wrapper" }, /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "toolbar" }, /* @__PURE__ */ createElement("span", null, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "ghost",
|
|
"aria-label": coreViewModel2.translations.chatPage.calendar.todayButtonAudioLabel,
|
|
"on:click": calendarPageViewModel.showToday,
|
|
ctkeystroke: getKeySymbol("-" /* Reset */)
|
|
},
|
|
/* @__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,
|
|
ctkeystroke: "k"
|
|
},
|
|
/* @__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,
|
|
ctkeystroke: "l"
|
|
},
|
|
/* @__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,
|
|
ctkeystroke: getKeySymbol("a" /* Create */)
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "add")
|
|
))), /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "content padding-0",
|
|
"children:set": mainContent
|
|
}
|
|
))), /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "pane-wrapper side background",
|
|
"set:color": calendarPageViewModel.chatViewModel.displayedColor
|
|
},
|
|
/* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "content",
|
|
"children:set": sidePaneContentWrapper
|
|
}
|
|
))
|
|
), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal }));
|
|
}
|
|
|
|
// src/View/chatPage.tsx
|
|
function ChatPage(coreViewModel2, chatViewModel) {
|
|
ViewController.chatPages.state = createProxyState(
|
|
[chatViewModel.selectedPage],
|
|
() => {
|
|
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"
|
|
},
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { id: "ribbon" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "ghost",
|
|
id: "close-button",
|
|
"aria-label": coreViewModel2.translations.chatPage.closeChatAudioLabe,
|
|
"on:click": chatViewModel.close,
|
|
ctkeystroke: getKeySymbol("w" /* Home */)
|
|
},
|
|
/* @__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,
|
|
"toggle:disabled": chatViewModel.connectionViewModel.hasNoPreviousConnections
|
|
},
|
|
/* @__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,
|
|
"o"
|
|
), ChatViewToggleButton(
|
|
coreViewModel2.translations.chatPage.pages.tasks,
|
|
"task_alt",
|
|
"tasks" /* Tasks */,
|
|
chatViewModel,
|
|
"i"
|
|
), ChatViewToggleButton(
|
|
coreViewModel2.translations.chatPage.pages.messages,
|
|
"forum",
|
|
"messages" /* Messages */,
|
|
chatViewModel,
|
|
"u"
|
|
), ChatViewToggleButton(
|
|
coreViewModel2.translations.chatPage.pages.settings,
|
|
"settings",
|
|
"settings" /* Settings */,
|
|
chatViewModel,
|
|
getKeySymbol("," /* Settings */)
|
|
))), /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
id: "main",
|
|
"children:set": ViewController.chatPages.state
|
|
}
|
|
))
|
|
);
|
|
}
|
|
|
|
// 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/splitModal.tsx
|
|
function SplitModal(coreViewModel2, leftView, rightView, extendedStyle = false, navigationState) {
|
|
function closePage() {
|
|
if (!navigationState) return;
|
|
navigationState.value = void 0;
|
|
}
|
|
const hasPageOpen = new State(false);
|
|
navigationState?.subscribe(
|
|
(newValue) => hasPageOpen.value = newValue != void 0
|
|
);
|
|
const view = /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "split-modal",
|
|
"toggle:extended": extendedStyle,
|
|
"toggle:navigation": navigationState != void 0,
|
|
"toggle:page-open": hasPageOpen
|
|
},
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "scroll-area", "children:set": leftView })),
|
|
/* @__PURE__ */ createElement("div", { class: "scroll-area slide-up" }, /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "flex-row width-100 mobile-only",
|
|
"toggle:hidden": navigationState == void 0
|
|
},
|
|
/* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "ghost square",
|
|
"aria-label": coreViewModel2.translations.general.backButton,
|
|
"on:click": closePage
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_back")
|
|
)
|
|
), /* @__PURE__ */ createElement("div", { "children:set": rightView }))
|
|
);
|
|
return view;
|
|
}
|
|
|
|
// src/View/Components/usageBar.tsx
|
|
function UsageBar(label, valueLabel, value, maximum) {
|
|
const style = createProxyState(
|
|
[value, maximum],
|
|
() => `width: ${100 * (value.value / maximum.value)}%`
|
|
);
|
|
return /* @__PURE__ */ createElement("div", { class: "surface flex-column padding gap" }, /* @__PURE__ */ createElement("b", null, label), /* @__PURE__ */ createElement("div", { class: "width-100 surface-alt", style: "height: 18px" }, /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "height-100 background-primary",
|
|
"set:style": style
|
|
}
|
|
)), /* @__PURE__ */ createElement("span", { class: "secondary", "subscribe:innerText": valueLabel }));
|
|
}
|
|
|
|
// src/View/Components/directoryItemList.tsx
|
|
function DirectoryItemList(storageViewModel2, pathString = PATH_COMPONENT_SEPARATOR) {
|
|
const StringToDirectoryItemList = (pathString2) => DirectoryItemList(storageViewModel2, pathString2);
|
|
const path = StorageModel.stringToPathComponents(pathString);
|
|
const fileName = StorageModel.getFileName(path);
|
|
const items = new ListState();
|
|
const style = `text-indent: ${path.length}rem`;
|
|
function loadItems() {
|
|
items.clear();
|
|
const directoryItems = storageViewModel2.coreViewModel.storageModel.list(path);
|
|
for (const directoryItem of directoryItems) {
|
|
const itemPath = [...path, directoryItem];
|
|
const pathString2 = StorageModel.pathComponentsToString(...itemPath);
|
|
items.add(pathString2);
|
|
}
|
|
}
|
|
function select() {
|
|
storageViewModel2.selectedPath.value = pathString;
|
|
}
|
|
storageViewModel2.lastDeletedItemPath.subscribe((lastDeletedItemPath) => {
|
|
if (!items.value.has(lastDeletedItemPath)) return;
|
|
select();
|
|
setTimeout(() => loadItems(), 50);
|
|
});
|
|
const isSelected = createProxyState(
|
|
[storageViewModel2.selectedPath],
|
|
() => storageViewModel2.selectedPath.value == pathString
|
|
);
|
|
isSelected.subscribe(() => {
|
|
if (isSelected.value == false) return;
|
|
loadItems();
|
|
});
|
|
return /* @__PURE__ */ createElement("div", { class: "flex-column" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "width-100 flex-1 clip",
|
|
"toggle:selected": isSelected,
|
|
"on:click": select
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "ellipsis width-100 flex-1", style }, fileName)
|
|
), /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "flex-column",
|
|
"children:append": [items, StringToDirectoryItemList]
|
|
}
|
|
));
|
|
}
|
|
|
|
// src/View/Modals/storageModal.tsx
|
|
function StorageModal(coreViewModel2, storageViewModel2) {
|
|
const usageValueLabel = createProxyState(
|
|
[storageViewModel2.occupiedSpaceMB, storageViewModel2.maximumSpaceMB],
|
|
() => coreViewModel2.translations.storage.usageVauleLabel(
|
|
storageViewModel2.occupiedSpaceMB.value,
|
|
storageViewModel2.maximumSpaceMB.value
|
|
)
|
|
);
|
|
const detailView = createProxyState(
|
|
[storageViewModel2.selectedPath],
|
|
() => {
|
|
if (storageViewModel2.selectedPath.value == PATH_COMPONENT_SEPARATOR)
|
|
return /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.storage.noItemSelected), /* @__PURE__ */ createElement("hr", null), UsageBar(
|
|
coreViewModel2.translations.storage.usageLabel,
|
|
usageValueLabel,
|
|
storageViewModel2.occupiedSpaceMB,
|
|
storageViewModel2.maximumSpaceMB
|
|
), /* @__PURE__ */ createElement("hr", null), DangerousActionButton(
|
|
coreViewModel2,
|
|
coreViewModel2.translations.storage.removeJunkButton,
|
|
"delete_forever",
|
|
storageViewModel2.removeJunk
|
|
));
|
|
return /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("div", { class: "tile flex-no" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.storage.path), /* @__PURE__ */ createElement(
|
|
"span",
|
|
{
|
|
class: "break-all",
|
|
"subscribe:innerText": storageViewModel2.selectedPath
|
|
}
|
|
))), /* @__PURE__ */ createElement("div", { class: "tile flex-no" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.storage.content), /* @__PURE__ */ createElement(
|
|
"code",
|
|
{
|
|
"subscribe:innerText": storageViewModel2.selectedFileContent
|
|
}
|
|
))), DangerousActionButton(
|
|
coreViewModel2,
|
|
coreViewModel2.translations.storage.deleteItem,
|
|
"delete_forever",
|
|
storageViewModel2.deleteSelectedItem
|
|
));
|
|
}
|
|
);
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": storageViewModel2.isShowingStorageModal }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", { class: "padding-0" }, SplitModal(
|
|
coreViewModel2,
|
|
new State(DirectoryItemList(storageViewModel2)),
|
|
detailView,
|
|
true
|
|
)), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
"on:click": storageViewModel2.close,
|
|
ctkeystroke: getKeySymbol("backspace" /* CloseOrCancel */)
|
|
},
|
|
coreViewModel2.translations.general.closeButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "close")
|
|
)));
|
|
}
|
|
|
|
// src/View/Components/optionButton.tsx
|
|
function OptionButton(text, value, selection) {
|
|
function select() {
|
|
selection.value = value;
|
|
}
|
|
const isSelected = createProxyState(
|
|
[selection],
|
|
() => selection.value == value
|
|
);
|
|
return /* @__PURE__ */ createElement("button", { class: "standard", "toggle:selected": isSelected, "on:click": select }, text);
|
|
}
|
|
|
|
// src/View/Components/optionButtonList.tsx
|
|
function OptionButtonList(options, selection) {
|
|
function OptionToView(option) {
|
|
const [text, value] = option;
|
|
return OptionButton(text, value, selection);
|
|
}
|
|
return /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "flex-column gap",
|
|
"children:append": [options, OptionToView]
|
|
}
|
|
);
|
|
}
|
|
|
|
// src/View/Components/navigationButton.tsx
|
|
function NavigationButton(coreViewModel2, label, navigationState, page, arrowMobileOnly) {
|
|
const isSelected = createProxyState(
|
|
[navigationState],
|
|
() => navigationState.value == page
|
|
);
|
|
function open() {
|
|
navigationState.value = page;
|
|
}
|
|
const iconClass = `icon ${arrowMobileOnly == true ? "mobile-only" : ""}`;
|
|
return /* @__PURE__ */ createElement("button", { "toggle:selected": isSelected, "on:click": open }, label, /* @__PURE__ */ createElement("span", { class: iconClass }, "arrow_forward"));
|
|
}
|
|
|
|
// src/View/Modals/settingsModal.tsx
|
|
function SettingsModal(coreViewModel2, settingsViewModel2) {
|
|
const detailView = createProxyState(
|
|
[settingsViewModel2.selectedModalPage],
|
|
() => {
|
|
switch (settingsViewModel2.selectedModalPage.value) {
|
|
case void 0: {
|
|
return PlaceholderView(
|
|
coreViewModel2.translations.general.noPageSelected
|
|
);
|
|
}
|
|
case 0 /* Appearance */:
|
|
return SettingsAppearancePane(
|
|
coreViewModel2,
|
|
settingsViewModel2
|
|
);
|
|
case 1 /* Account */:
|
|
return SettingsAccountPane(
|
|
coreViewModel2,
|
|
settingsViewModel2
|
|
);
|
|
case 2 /* Regional */:
|
|
return SettingsRegionalPane(
|
|
coreViewModel2,
|
|
settingsViewModel2
|
|
);
|
|
case 3 /* Info */:
|
|
return SettingsInfoPane(coreViewModel2, settingsViewModel2);
|
|
}
|
|
}
|
|
);
|
|
return /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "modal",
|
|
"toggle:open": settingsViewModel2.isShowingSettingsModal
|
|
},
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", { class: "padding-0" }, SplitModal(
|
|
coreViewModel2,
|
|
new State(
|
|
SettingsLeftPane(coreViewModel2, settingsViewModel2)
|
|
),
|
|
detailView,
|
|
true,
|
|
settingsViewModel2.selectedModalPage
|
|
)), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
"on:click": settingsViewModel2.close,
|
|
ctkeystroke: getKeySymbol("backspace" /* CloseOrCancel */)
|
|
},
|
|
coreViewModel2.translations.general.closeButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "close")
|
|
))
|
|
);
|
|
}
|
|
function SettingsLeftPane(coreViewModel2, settingsViewModel2) {
|
|
return /* @__PURE__ */ createElement("div", { class: "flex-column gap slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.settingsButton), SettingsPaneButton(
|
|
coreViewModel2,
|
|
settingsViewModel2,
|
|
0 /* Appearance */,
|
|
coreViewModel2.translations.settings.pages.appearance
|
|
), SettingsPaneButton(
|
|
coreViewModel2,
|
|
settingsViewModel2,
|
|
1 /* Account */,
|
|
coreViewModel2.translations.settings.pages.account
|
|
), SettingsPaneButton(
|
|
coreViewModel2,
|
|
settingsViewModel2,
|
|
2 /* Regional */,
|
|
coreViewModel2.translations.settings.pages.regional
|
|
), SettingsPaneButton(
|
|
coreViewModel2,
|
|
settingsViewModel2,
|
|
3 /* Info */,
|
|
coreViewModel2.translations.settings.pages.info
|
|
));
|
|
}
|
|
function SettingsPaneButton(coreViewModel2, settingsViewModel2, page, label) {
|
|
return NavigationButton(
|
|
coreViewModel2,
|
|
label,
|
|
settingsViewModel2.selectedModalPage,
|
|
page,
|
|
true
|
|
);
|
|
}
|
|
function SettingsInfoPane(coreViewModel2, settingsViewModel2) {
|
|
return /* @__PURE__ */ createElement("div", { class: "slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.homePage.appName), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, InfoTile(
|
|
"build",
|
|
coreViewModel2.translations.settings.about.version,
|
|
settingsViewModel2.coreViewModel.version
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "primary",
|
|
"on:click": coreViewModel2.update,
|
|
"toggle:hidden": coreViewModel2.noUpdateAvailable
|
|
},
|
|
/* @__PURE__ */ createElement("span", { "subscribe:innerText": coreViewModel2.updateText }),
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "update")
|
|
), /* @__PURE__ */ createElement("button", { class: "standard", "on:click": coreViewModel2.checkUpdates }, coreViewModel2.translations.settings.about.checkUpdatesButton)));
|
|
}
|
|
function SettingsRegionalPane(coreViewModel2, settingsViewModel2) {
|
|
return /* @__PURE__ */ createElement("div", { class: "slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.settings.pages.regional), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.settings.regional.language), OptionButtonList(
|
|
new ListState(
|
|
Object.values(Languages).map((x) => [languageNames[x], x])
|
|
),
|
|
settingsViewModel2.language
|
|
), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.settings.regional.firstDayOfWeekLabel), OptionButtonList(
|
|
new ListState(
|
|
coreViewModel2.translations.regional.weekdays.full.map(
|
|
(x, i) => [x, i.toString()]
|
|
)
|
|
),
|
|
settingsViewModel2.firstDayOfWeek
|
|
));
|
|
}
|
|
function SettingsAccountPane(coreViewModel2, settingsViewModel2) {
|
|
return /* @__PURE__ */ createElement("div", { class: "slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.settings.pages.account), /* @__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.settings.account.yourNameLabel), /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
placeholder: coreViewModel2.translations.settings.account.yourNamePlaceholder,
|
|
"bind:value": settingsViewModel2.usernameInput,
|
|
"on:enter": settingsViewModel2.setName
|
|
}
|
|
))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end width-input" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "width-50",
|
|
"on:click": settingsViewModel2.setName,
|
|
"toggle:disabled": settingsViewModel2.cannotSetName,
|
|
"aria-label": coreViewModel2.translations.settings.account.setNameButtonAudioLabel
|
|
},
|
|
coreViewModel2.translations.general.setButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "check")
|
|
)));
|
|
}
|
|
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.dynamic,
|
|
"dynamic" /* Dynamic */
|
|
],
|
|
[
|
|
coreViewModel2.translations.settings.themes.system,
|
|
"system" /* System */
|
|
],
|
|
[
|
|
coreViewModel2.translations.settings.themes.dark,
|
|
"dark" /* Dark */
|
|
],
|
|
[
|
|
coreViewModel2.translations.settings.themes.light,
|
|
"light" /* Light */
|
|
],
|
|
[
|
|
coreViewModel2.translations.settings.themes.black,
|
|
"black" /* Black */
|
|
]
|
|
]),
|
|
settingsViewModel2.theme
|
|
));
|
|
}
|
|
|
|
// src/View/Modals/onboardingModal.tsx
|
|
function OnboardingModalWrapper(coreViewModel2, onboardingViewModel2) {
|
|
return /* @__PURE__ */ createElement("div", null, ConnectionModal(coreViewModel2, onboardingViewModel2), TransferModal(coreViewModel2, onboardingViewModel2), NameModal(coreViewModel2, onboardingViewModel2), TransferDataModal(coreViewModel2, onboardingViewModel2));
|
|
}
|
|
function ConnectionModal(coreViewModel2, onboardingViewModel2) {
|
|
const isPresented = createProxyState(
|
|
[onboardingViewModel2.presentedModal],
|
|
() => onboardingViewModel2.presentedModal.value == 0 /* Connection */
|
|
);
|
|
const connectionViewModel2 = onboardingViewModel2.connectionViewmodel;
|
|
const nextButton = createProxyState(
|
|
[connectionViewModel2.isConnected],
|
|
() => coreViewModel2.translations.onboarding.connectionNextButton(
|
|
connectionViewModel2.isConnected.value
|
|
)
|
|
);
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.onboarding.connectionHeadline), /* @__PURE__ */ createElement("p", { class: "secondary width-input" }, coreViewModel2.translations.onboarding.connectionDescription), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "cell_tower"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, coreViewModel2.translations.homePage.serverAddress), /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
placeholder: coreViewModel2.translations.homePage.serverAddressPlaceholder,
|
|
"bind:value": connectionViewModel2.serverAddressInput,
|
|
"on:enter": connectionViewModel2.connect
|
|
}
|
|
))), /* @__PURE__ */ createElement("div", { class: "flex-row width-input justify-end" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "standard width-50",
|
|
"on:click": connectionViewModel2.connect,
|
|
"toggle:disabled": connectionViewModel2.cannotConnect
|
|
},
|
|
coreViewModel2.translations.onboarding.connectButton
|
|
))), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "primary width-50",
|
|
"on:click": onboardingViewModel2.showTransferOption
|
|
},
|
|
/* @__PURE__ */ createElement("span", { "subscribe:innerText": nextButton }),
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
))));
|
|
}
|
|
function TransferModal(coreViewModel2, onboardingViewModel2) {
|
|
const isPresented = createProxyState(
|
|
[onboardingViewModel2.presentedModal],
|
|
() => onboardingViewModel2.presentedModal.value == 1 /* TransferOrNew */
|
|
);
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.onboarding.transferOptionsHeadline), /* @__PURE__ */ createElement("p", { class: "secondary width-input" }, coreViewModel2.translations.onboarding.transferOptionsDescription), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "tile",
|
|
"on:click": onboardingViewModel2.showTransferData,
|
|
"toggle:disabled": onboardingViewModel2.cannotTransfer
|
|
},
|
|
/* @__PURE__ */ createElement("div", null, coreViewModel2.translations.onboarding.transferOptionTransfer),
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "tile",
|
|
"on:click": onboardingViewModel2.setupNew
|
|
},
|
|
/* @__PURE__ */ createElement("div", null, coreViewModel2.translations.onboarding.transferOptionNew),
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
))), /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "standard width-50",
|
|
"on:click": onboardingViewModel2.open
|
|
},
|
|
coreViewModel2.translations.general.backButton
|
|
))));
|
|
}
|
|
function NameModal(coreViewModel2, onboardingViewModel2) {
|
|
const isPresented = createProxyState(
|
|
[onboardingViewModel2.presentedModal],
|
|
() => onboardingViewModel2.presentedModal.value == 2 /* Name */
|
|
);
|
|
const settingsViewModel2 = onboardingViewModel2.settingsViewModel;
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.onboarding.nameHeadline), /* @__PURE__ */ createElement("p", { class: "secondary width-input" }, coreViewModel2.translations.onboarding.nameDescription), /* @__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.settings.account.yourNameLabel), /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
placeholder: coreViewModel2.translations.settings.account.yourNamePlaceholder,
|
|
"bind:value": settingsViewModel2.usernameInput
|
|
}
|
|
)))), /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "standard width-50",
|
|
"on:click": onboardingViewModel2.showTransferOption
|
|
},
|
|
coreViewModel2.translations.general.backButton
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "primary width-50",
|
|
"on:click": onboardingViewModel2.finish
|
|
},
|
|
coreViewModel2.translations.general.setButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
))));
|
|
}
|
|
function TransferDataModal(coreViewModel2, onboardingViewModel2) {
|
|
const isPresented = createProxyState(
|
|
[onboardingViewModel2.presentedModal],
|
|
() => onboardingViewModel2.presentedModal.value == 3 /* Transfer */
|
|
);
|
|
const fileTransferViewModel2 = onboardingViewModel2.fileTransferViewModel;
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.onboarding.transferHeadline), /* @__PURE__ */ createElement("p", { class: "secondary width-input" }, coreViewModel2.translations.onboarding.transferDescription), /* @__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(
|
|
"input",
|
|
{
|
|
type: "number",
|
|
"on:enter": fileTransferViewModel2.prepareReceivingData,
|
|
"bind:value": fileTransferViewModel2.receivingTransferChannel
|
|
}
|
|
))), /* @__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(
|
|
"input",
|
|
{
|
|
type: "number",
|
|
"on:enter": fileTransferViewModel2.prepareReceivingData,
|
|
"bind:value": fileTransferViewModel2.receivingTransferKey
|
|
}
|
|
))))), /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "standard width-50",
|
|
"on:click": onboardingViewModel2.showTransferOption,
|
|
"toggle:disabled": fileTransferViewModel2.cannotExitReception
|
|
},
|
|
coreViewModel2.translations.general.backButton
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "primary width-50",
|
|
"on:click": onboardingViewModel2.transferData
|
|
},
|
|
coreViewModel2.translations.onboarding.transferButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
))));
|
|
}
|
|
|
|
// src/View/Components/textSpan.tsx
|
|
var StringToTextSpan = (string) => {
|
|
return /* @__PURE__ */ createElement("span", { class: "ellipsis" }, string);
|
|
};
|
|
|
|
// src/View/Components/progressBar.tsx
|
|
function ProgressBar(valueLabel, value, maximum) {
|
|
const style = createProxyState(
|
|
[value, maximum],
|
|
() => `width: ${100 * (value.value / maximum.value)}%`
|
|
);
|
|
return /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "width-100 surface-alt", style: "height: 18px" }, /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "height-100 background-primary",
|
|
"set:style": style
|
|
}
|
|
)), /* @__PURE__ */ createElement("span", { class: "secondary", "subscribe:innerText": valueLabel }));
|
|
}
|
|
|
|
// src/View/Modals/dataTransferModal.tsx
|
|
function DataTransferModalWrapper(coreViewModel2, connectionViewModel2, fileTransferViewModel2) {
|
|
return /* @__PURE__ */ createElement("div", null, DirectionSelectionModal(
|
|
coreViewModel2,
|
|
connectionViewModel2,
|
|
fileTransferViewModel2
|
|
), FileSelectionModal(coreViewModel2, fileTransferViewModel2), TransferDataDisplayModal(coreViewModel2, fileTransferViewModel2), TransferDisplayModal(coreViewModel2, fileTransferViewModel2), TransferDataInputModal(coreViewModel2, fileTransferViewModel2), DataReceptionModal(coreViewModel2, fileTransferViewModel2), ExportFileSelectionModal(coreViewModel2, fileTransferViewModel2), ExportModal(coreViewModel2, fileTransferViewModel2), ImportModal(coreViewModel2, fileTransferViewModel2), ImportDecryptionDataModal(coreViewModel2, fileTransferViewModel2));
|
|
}
|
|
function OptionEntry(fileOption, fileTransferViewModel2) {
|
|
const isSelected = new State(false);
|
|
if (fileTransferViewModel2.selectedPaths.value.has(fileOption.path)) {
|
|
isSelected.value = true;
|
|
}
|
|
isSelected.subscribeSilent((isSelected2) => {
|
|
if (isSelected2 == true) {
|
|
fileTransferViewModel2.selectedPaths.add(fileOption.path);
|
|
} else {
|
|
fileTransferViewModel2.selectedPaths.remove(fileOption.path);
|
|
}
|
|
});
|
|
fileTransferViewModel2.allOptionsSelected.subscribe((allSelected) => {
|
|
isSelected.value = allSelected;
|
|
});
|
|
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))));
|
|
}
|
|
function FileSelectionDetail(coreViewModel2, fileTransferViewModel2) {
|
|
const OptionConverter = (fileOption) => {
|
|
return OptionEntry(fileOption, fileTransferViewModel2);
|
|
};
|
|
const chatList = createProxyState(
|
|
[fileTransferViewModel2.chatFileOptions],
|
|
() => fileTransferViewModel2.chatFileOptions.value.size == 0 ? /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.noChatsPlaceholder) : /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "flex-column gap",
|
|
"children:append": [
|
|
fileTransferViewModel2.chatFileOptions,
|
|
OptionConverter
|
|
]
|
|
}
|
|
)
|
|
);
|
|
return /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.dataTransferModal.generalHeadline), /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "flex-column gap content-margin-bottom",
|
|
"children:append": [
|
|
fileTransferViewModel2.generalFileOptions,
|
|
OptionConverter
|
|
]
|
|
}
|
|
), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("h3", null, coreViewModel2.translations.dataTransferModal.chatsHeadline), /* @__PURE__ */ createElement("div", { "children:set": chatList }), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "inline" }, /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
type: "checkbox",
|
|
"bind:checked": fileTransferViewModel2.allOptionsSelected
|
|
}
|
|
), coreViewModel2.translations.general.selectAllLabel));
|
|
}
|
|
function DirectionSelectionModal(coreViewModel2, connectionViewModel2, fileTransferViewModel2) {
|
|
const isPresented = createProxyState(
|
|
[fileTransferViewModel2.presentedModal],
|
|
() => fileTransferViewModel2.presentedModal.value == 0 /* DirectionSelection */
|
|
);
|
|
const isDisconnected = createProxyState(
|
|
[connectionViewModel2.isConnected],
|
|
() => connectionViewModel2.isConnected.value == false
|
|
);
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "tile",
|
|
"toggle:disabled": isDisconnected,
|
|
"on:click": fileTransferViewModel2.showFileSelectionModal
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "share_windows"),
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.dataTransferModal.fromThisDeviceButton)),
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "tile",
|
|
"toggle:disabled": isDisconnected,
|
|
"on:click": fileTransferViewModel2.showTransferDataInputModal
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "cloud_download"),
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.dataTransferModal.toThisDeviceButton)),
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "tile",
|
|
"on:click": fileTransferViewModel2.showExportSelectionModal
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "file_save"),
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.dataTransferModal.exportButton)),
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "tile",
|
|
"on:click": fileTransferViewModel2.showImportModal
|
|
},
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "upload_file"),
|
|
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, coreViewModel2.translations.dataTransferModal.importButton)),
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
))), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
"on:click": fileTransferViewModel2.close,
|
|
ctkeystroke: getKeySymbol("backspace" /* CloseOrCancel */)
|
|
},
|
|
coreViewModel2.translations.general.closeButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "close")
|
|
)));
|
|
}
|
|
function FileSelectionModal(coreViewModel2, fileTransferViewModel2) {
|
|
const isPresented = createProxyState(
|
|
[fileTransferViewModel2.presentedModal],
|
|
() => fileTransferViewModel2.presentedModal.value == 1 /* FileSelection */
|
|
);
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.sendHeadline), /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.selectionDescription), /* @__PURE__ */ createElement("hr", null), FileSelectionDetail(coreViewModel2, fileTransferViewModel2)), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "flex",
|
|
"on:click": fileTransferViewModel2.showDirectionSelectionModal
|
|
},
|
|
coreViewModel2.translations.general.backButton
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "primary flex",
|
|
"on:click": fileTransferViewModel2.showTransferDataModal,
|
|
"toggle:disabled": fileTransferViewModel2.hasNoPathsSelected
|
|
},
|
|
coreViewModel2.translations.general.continueButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
))));
|
|
}
|
|
function TransferDataDisplayModal(coreViewModel2, fileTransferViewModel2) {
|
|
const isPresented = createProxyState(
|
|
[fileTransferViewModel2.presentedModal],
|
|
() => fileTransferViewModel2.presentedModal.value == 2 /* TransferDataDisplay */
|
|
);
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.sendHeadline), /* @__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(
|
|
"b",
|
|
{
|
|
"subscribe:innerText": fileTransferViewModel2.transferChannel
|
|
}
|
|
))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.transferKeyHeadline), /* @__PURE__ */ createElement(
|
|
"b",
|
|
{
|
|
"subscribe:innerText": fileTransferViewModel2.transferKey
|
|
}
|
|
))))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "flex",
|
|
"on:click": fileTransferViewModel2.showFileSelectionModal
|
|
},
|
|
coreViewModel2.translations.general.backButton
|
|
), /* @__PURE__ */ createElement("button", { disabled: true, class: "flex" }, coreViewModel2.translations.general.waitingLabel))));
|
|
}
|
|
function TransferDisplayModal(coreViewModel2, fileTransferViewModel2) {
|
|
const isPresented = createProxyState(
|
|
[fileTransferViewModel2.presentedModal],
|
|
() => fileTransferViewModel2.presentedModal.value == 3 /* TransferDisplay */
|
|
);
|
|
const progressLabel = createProxyState(
|
|
[
|
|
fileTransferViewModel2.fileTransferProgress,
|
|
fileTransferViewModel2.fileTransferTotal
|
|
],
|
|
() => coreViewModel2.translations.dataTransferModal.filesSentProgress(
|
|
fileTransferViewModel2.fileTransferProgress.value,
|
|
fileTransferViewModel2.fileTransferTotal.value
|
|
)
|
|
);
|
|
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), ProgressBar(
|
|
progressLabel,
|
|
fileTransferViewModel2.fileTransferProgress,
|
|
fileTransferViewModel2.fileTransferTotal
|
|
), /* @__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(
|
|
"b",
|
|
{
|
|
"subscribe:innerText": fileTransferViewModel2.transferChannel
|
|
}
|
|
))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.transferKeyHeadline), /* @__PURE__ */ createElement(
|
|
"b",
|
|
{
|
|
"subscribe:innerText": fileTransferViewModel2.transferKey
|
|
}
|
|
)))), /* @__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
|
|
},
|
|
coreViewModel2.translations.dataTransferModal.sendAgainButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "restart_alt")
|
|
), /* @__PURE__ */ createElement("button", { class: "flex", "on:click": fileTransferViewModel2.close }, coreViewModel2.translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))));
|
|
}
|
|
function TransferDataInputModal(coreViewModel2, fileTransferViewModel2) {
|
|
const isPresented = createProxyState(
|
|
[fileTransferViewModel2.presentedModal],
|
|
() => fileTransferViewModel2.presentedModal.value == 4 /* TransferDataInput */
|
|
);
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.receiveHeadline), /* @__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(
|
|
"input",
|
|
{
|
|
type: "number",
|
|
"on:enter": fileTransferViewModel2.prepareReceivingData,
|
|
"bind:value": fileTransferViewModel2.receivingTransferChannel
|
|
}
|
|
))), /* @__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(
|
|
"input",
|
|
{
|
|
type: "number",
|
|
"on:enter": fileTransferViewModel2.prepareReceivingData,
|
|
"bind:value": fileTransferViewModel2.receivingTransferKey
|
|
}
|
|
))))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "flex",
|
|
"on:click": fileTransferViewModel2.showDirectionSelectionModal
|
|
},
|
|
coreViewModel2.translations.general.backButton
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "primary flex",
|
|
"on:click": fileTransferViewModel2.prepareReceivingData,
|
|
"toggle:disabled": fileTransferViewModel2.cannotPrepareToReceive
|
|
},
|
|
coreViewModel2.translations.general.continueButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
))));
|
|
}
|
|
function DataReceptionModal(coreViewModel2, fileTransferViewModel2) {
|
|
const isPresented = createProxyState(
|
|
[fileTransferViewModel2.presentedModal],
|
|
() => fileTransferViewModel2.presentedModal.value == 5 /* ReceptionDisplay */
|
|
);
|
|
const progressLabel = createProxyState(
|
|
[
|
|
fileTransferViewModel2.fileTransferProgress,
|
|
fileTransferViewModel2.fileTransferTotal
|
|
],
|
|
() => coreViewModel2.translations.dataTransferModal.filesReceivedProgress(
|
|
fileTransferViewModel2.fileTransferProgress.value,
|
|
fileTransferViewModel2.fileTransferTotal.value
|
|
)
|
|
);
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.receiveHeadline), ProgressBar(
|
|
progressLabel,
|
|
fileTransferViewModel2.fileTransferProgress,
|
|
fileTransferViewModel2.fileTransferTotal
|
|
), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement(
|
|
"div",
|
|
{
|
|
class: "tile flex-column align-start",
|
|
"children:append": [
|
|
fileTransferViewModel2.filePathsReceived,
|
|
StringToTextSpan
|
|
]
|
|
}
|
|
)), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "flex",
|
|
"on:click": fileTransferViewModel2.correctTransferData,
|
|
"toggle:disabled": fileTransferViewModel2.cannotExitReception
|
|
},
|
|
coreViewModel2.translations.general.backButton
|
|
), /* @__PURE__ */ createElement("button", { class: "flex", "on:click": ViewController.reload }, coreViewModel2.translations.general.reloadAppButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "refresh")))));
|
|
}
|
|
function ExportFileSelectionModal(coreViewModel2, fileTransferViewModel2) {
|
|
const OptionConverter = (fileOption) => {
|
|
return OptionEntry(fileOption, fileTransferViewModel2);
|
|
};
|
|
const isPresented = createProxyState(
|
|
[fileTransferViewModel2.presentedModal],
|
|
() => fileTransferViewModel2.presentedModal.value == 6 /* ExportFileSelection */
|
|
);
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.exportHeadline), /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.exportSelectionDescription), /* @__PURE__ */ createElement("hr", null), FileSelectionDetail(coreViewModel2, fileTransferViewModel2)), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "flex",
|
|
"on:click": fileTransferViewModel2.showDirectionSelectionModal
|
|
},
|
|
coreViewModel2.translations.general.backButton
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "primary flex",
|
|
"on:click": fileTransferViewModel2.showExportModal,
|
|
"toggle:disabled": fileTransferViewModel2.hasNoPathsSelected
|
|
},
|
|
coreViewModel2.translations.general.continueButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
))));
|
|
}
|
|
function ExportModal(coreViewModel2, fileTransferViewModel2) {
|
|
const isPresented = createProxyState(
|
|
[fileTransferViewModel2.presentedModal],
|
|
() => fileTransferViewModel2.presentedModal.value == 7 /* Export */
|
|
);
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.exportHeadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.exportKey), /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
"bind:value": fileTransferViewModel2.exportKey,
|
|
"on:enter": fileTransferViewModel2.downloadFile,
|
|
type: "password"
|
|
}
|
|
))), /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "check_circle"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.exportKeyConfirmation), /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
"bind:value": fileTransferViewModel2.exportKeyConfirmation,
|
|
"on:enter": fileTransferViewModel2.downloadFile,
|
|
type: "password"
|
|
}
|
|
))))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "flex",
|
|
"on:click": fileTransferViewModel2.showExportSelectionModal
|
|
},
|
|
coreViewModel2.translations.general.backButton
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "primary flex",
|
|
"on:click": fileTransferViewModel2.downloadFile,
|
|
"toggle:disabled": fileTransferViewModel2.cannotExport
|
|
},
|
|
coreViewModel2.translations.dataTransferModal.downloadFileButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "download")
|
|
))));
|
|
}
|
|
function ImportModal(coreViewModel2, fileTransferViewModel2) {
|
|
const isPresented = createProxyState(
|
|
[fileTransferViewModel2.presentedModal],
|
|
() => fileTransferViewModel2.presentedModal.value == 8 /* ImportSelection */
|
|
);
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.importHeadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
id: "file-transfer-input",
|
|
type: "file",
|
|
"on:change": fileTransferViewModel2.updateImportSelection
|
|
}
|
|
))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "flex",
|
|
"on:click": fileTransferViewModel2.showDirectionSelectionModal
|
|
},
|
|
coreViewModel2.translations.general.backButton
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "primary flex",
|
|
"on:click": fileTransferViewModel2.importFile,
|
|
"toggle:disabled": fileTransferViewModel2.cannotImport
|
|
},
|
|
coreViewModel2.translations.dataTransferModal.importFileButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
))));
|
|
}
|
|
function ImportDecryptionDataModal(coreViewModel2, fileTransferViewModel2) {
|
|
const isPresented = createProxyState(
|
|
[fileTransferViewModel2.presentedModal],
|
|
() => fileTransferViewModel2.presentedModal.value == 9 /* ImportDecryptData */
|
|
);
|
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.importHeadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement("label", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.exportKey), /* @__PURE__ */ createElement(
|
|
"input",
|
|
{
|
|
"bind:value": fileTransferViewModel2.importKey,
|
|
"on:enter": fileTransferViewModel2.decryptImport,
|
|
type: "password"
|
|
}
|
|
)))), /* @__PURE__ */ createElement(
|
|
"p",
|
|
{
|
|
class: "error",
|
|
"toggle:hidden": fileTransferViewModel2.isImportKeyCorrect
|
|
},
|
|
coreViewModel2.translations.dataTransferModal.incorrectPassphraseError
|
|
)), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "flex",
|
|
"on:click": fileTransferViewModel2.showImportModal
|
|
},
|
|
coreViewModel2.translations.general.backButton
|
|
), /* @__PURE__ */ createElement(
|
|
"button",
|
|
{
|
|
class: "primary flex",
|
|
"on:click": fileTransferViewModel2.decryptImport,
|
|
"toggle:disabled": fileTransferViewModel2.importDecryptSuccessful
|
|
},
|
|
coreViewModel2.translations.dataTransferModal.decryptImportButton,
|
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")
|
|
))));
|
|
}
|
|
|
|
// src/View/Modals/connectionModal.tsx
|
|
function ConnectionModal2(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")))
|
|
);
|
|
}
|
|
|
|
// src/Model/Global/fileTransferModel.ts
|
|
var FileTransferModel = class _FileTransferModel {
|
|
// init
|
|
constructor(storageModel2, connectionModel2) {
|
|
this.direction = 0 /* Send */;
|
|
// handler managers
|
|
this.fileHandlerManager = new HandlerManager();
|
|
this.transferSizeHandlerManager = new HandlerManager();
|
|
this.readyToSendHandlerManager = new HandlerManager();
|
|
// general
|
|
this.generateTransferData = () => {
|
|
const transferData = {
|
|
channel: generateRandomToken(4),
|
|
key: generateRandomToken(6)
|
|
};
|
|
this.transferData = transferData;
|
|
return transferData;
|
|
};
|
|
this.prepareToSend = () => {
|
|
if (!this.transferData) return;
|
|
this.direction = 0 /* Send */;
|
|
this.connectionModel.addChannel(this.transferData.channel);
|
|
};
|
|
this.prepareToReceive = (transferData) => {
|
|
this.direction = 1 /* Receive */;
|
|
this.connectionModel.addChannel(transferData.channel);
|
|
this.transferData = transferData;
|
|
this.connectionModel.sendPlainMessage(
|
|
transferData.channel,
|
|
_FileTransferModel.READY_MESSAGE
|
|
);
|
|
};
|
|
// handlers
|
|
this.handleMessage = (data) => {
|
|
if (this.transferData == void 0) return;
|
|
if (data.messageChannel != this.transferData.channel) return;
|
|
if (data.messageBody == _FileTransferModel.READY_MESSAGE && this.direction == 0 /* Send */) {
|
|
this.readyToSendHandlerManager.trigger(true);
|
|
}
|
|
if (this.transferData == void 0) return;
|
|
if (data.messageBody == void 0) return;
|
|
this.handleTransferredFile(data.messageBody);
|
|
};
|
|
this.handleTransferredFile = async (encryptedFileData) => {
|
|
if (this.transferData == void 0) return;
|
|
const decrypted = await decryptString(
|
|
encryptedFileData,
|
|
this.transferData.key
|
|
);
|
|
this.handleDecryptedFile(decrypted);
|
|
};
|
|
this.handleBackupFile = async (encryptedBackup, passphrase) => {
|
|
const decrypted = await decryptString(
|
|
encryptedBackup,
|
|
passphrase
|
|
);
|
|
const parsed = parse(decrypted);
|
|
if (!Array.isArray(parsed)) {
|
|
console.error("Canceling backup - not an array", decrypted);
|
|
throw "no array";
|
|
}
|
|
for (const file of parsed) {
|
|
this.handleDecryptedFile(file);
|
|
}
|
|
};
|
|
this.handleDecryptedFile = (data) => {
|
|
const parsed = parse(data);
|
|
if (!parsed.type) return;
|
|
if (parsed.type == "transfer-size" && typeof parsed.count == "number") {
|
|
this.transferSizeHandlerManager.trigger(parsed.count);
|
|
return;
|
|
}
|
|
const isFileData = checkMatchesObjectStructure(
|
|
parsed,
|
|
FileDataReference
|
|
);
|
|
if (isFileData == false) throw "not file data";
|
|
const fileData = parsed;
|
|
this.storageModel.write(fileData.path, fileData.body);
|
|
const pathString = StorageModel.pathComponentsToString(
|
|
...fileData.path
|
|
);
|
|
this.fileHandlerManager.trigger(pathString);
|
|
};
|
|
// sending
|
|
this.sendFiles = (directoryPaths, fileCallback) => {
|
|
const filesToSend = /* @__PURE__ */ new Set();
|
|
for (const directoryPath of directoryPaths) {
|
|
this.storageModel.recurse(directoryPath, (filePath) => {
|
|
const stringifiedFileData = this.prepareFileForSending(filePath);
|
|
filesToSend.add([stringifiedFileData, filePath]);
|
|
});
|
|
}
|
|
this.sendTransferSize(filesToSend.size);
|
|
for (const file of filesToSend) {
|
|
const [stringifiedFileData, filePath] = file;
|
|
this.sendFile(stringifiedFileData);
|
|
const pathString = StorageModel.pathComponentsToString(
|
|
...filePath
|
|
);
|
|
fileCallback(pathString);
|
|
}
|
|
};
|
|
this.generateBackup = async (directoryPaths, passphrase) => {
|
|
const files = [];
|
|
for (const directoryPath of directoryPaths) {
|
|
this.storageModel.recurse(directoryPath, (filePath) => {
|
|
const stringifiedFileData = this.prepareFileForSending(filePath);
|
|
files.push(stringifiedFileData);
|
|
});
|
|
}
|
|
const rawBackup = stringify(files);
|
|
console.log(rawBackup);
|
|
const encrypted = await encryptString(rawBackup, passphrase);
|
|
const blob = new Blob([encrypted], { type: "text/plain" });
|
|
return blob;
|
|
};
|
|
this.sendTransferSize = async (count) => {
|
|
if (!this.transferData) return;
|
|
const data = {
|
|
type: "transfer-size",
|
|
count
|
|
};
|
|
const encryptedData = await encryptString(
|
|
stringify(data),
|
|
this.transferData.key
|
|
);
|
|
this.connectionModel.sendPlainMessage(
|
|
this.transferData.channel,
|
|
encryptedData
|
|
);
|
|
};
|
|
this.prepareFileForSending = (filePath) => {
|
|
const fileContent = this.storageModel.read(filePath);
|
|
if (fileContent == null) return "";
|
|
const fileData = {
|
|
type: "file-data",
|
|
path: filePath,
|
|
body: fileContent
|
|
};
|
|
return stringify(fileData);
|
|
};
|
|
this.sendFile = async (stringifiedFileData) => {
|
|
if (!this.transferData) return;
|
|
const encryptedFileData = await encryptString(
|
|
stringifiedFileData,
|
|
this.transferData.key
|
|
);
|
|
this.connectionModel.sendPlainMessage(
|
|
this.transferData.channel,
|
|
encryptedFileData
|
|
);
|
|
};
|
|
this.storageModel = storageModel2;
|
|
this.connectionModel = connectionModel2;
|
|
this.connectionModel.messageHandlerManager.setHandler(
|
|
"file-transfer",
|
|
this.handleMessage
|
|
);
|
|
}
|
|
static {
|
|
this.READY_MESSAGE = "ready";
|
|
}
|
|
};
|
|
var FileDataReference = {
|
|
type: "file-data",
|
|
path: [""],
|
|
body: ""
|
|
};
|
|
|
|
// src/Model/Global/contactListModel.ts
|
|
var ContactListModel = class {
|
|
constructor(storageModel2, settingsModel2) {
|
|
this.storageModel = storageModel2;
|
|
this.settingsModel = settingsModel2;
|
|
this.contactHandlerManager = new HandlerManager();
|
|
this.getContactPath = (contactId) => {
|
|
return StorageModel.getPath("contacts" /* ContactsModel */, [
|
|
contactId
|
|
]);
|
|
};
|
|
this.loadContacts = () => {
|
|
const contactIds = this.storageModel.list([
|
|
"contacts" /* ContactsModel */
|
|
]);
|
|
const contacts = contactIds.map((id) => [
|
|
id,
|
|
this.storageModel.read(this.getContactPath(id)) ?? "?"
|
|
]);
|
|
contacts.push([this.settingsModel.userid, this.settingsModel.username]);
|
|
return contacts;
|
|
};
|
|
this.storeContact = (id, name) => {
|
|
this.contactHandlerManager.trigger([id, name]);
|
|
if (id == this.settingsModel.userid) return;
|
|
const path = this.getContactPath(id);
|
|
this.storageModel.write(path, name);
|
|
};
|
|
}
|
|
};
|
|
|
|
// node_modules/udn-frontend/index.ts
|
|
var UDNFrontend = class {
|
|
ws;
|
|
// HANDLERS
|
|
connectionHandler = () => {
|
|
};
|
|
disconnectionHandler = () => {
|
|
};
|
|
messageHandler = (data) => {
|
|
};
|
|
mailboxHandler = (mailboxId) => {
|
|
};
|
|
mailboxConnectionHandler = (mailboxId) => {
|
|
};
|
|
mailboxDeleteHandler = (mailboxId) => {
|
|
};
|
|
// INIT
|
|
set onconnect(handler) {
|
|
this.connectionHandler = handler;
|
|
}
|
|
set ondisconnect(handler) {
|
|
this.disconnectionHandler = handler;
|
|
}
|
|
set onmessage(handler) {
|
|
this.messageHandler = handler;
|
|
}
|
|
set onmailboxcreate(handler) {
|
|
this.mailboxHandler = handler;
|
|
}
|
|
set onmailboxconnect(handler) {
|
|
this.mailboxConnectionHandler = handler;
|
|
}
|
|
set onmailboxdelete(handler) {
|
|
this.mailboxDeleteHandler = handler;
|
|
}
|
|
// UTILITY METHODS
|
|
send(messageObject) {
|
|
if (this.ws == void 0) return false;
|
|
if (this.ws.readyState != 1) return false;
|
|
const messageString = JSON.stringify(messageObject);
|
|
this.ws.send(messageString);
|
|
return true;
|
|
}
|
|
// PUBLIC METHODS
|
|
// connection
|
|
connect(address) {
|
|
try {
|
|
this.disconnect();
|
|
this.ws = new WebSocket(address);
|
|
this.ws.addEventListener("open", this.connectionHandler);
|
|
this.ws.addEventListener("close", this.disconnectionHandler);
|
|
this.ws.addEventListener("message", (message) => {
|
|
const dataString = message.data.toString();
|
|
const data = JSON.parse(dataString);
|
|
if (data.assignedMailboxId) {
|
|
return this.mailboxHandler(data.assignedMailboxId);
|
|
} else if (data.connectedMailboxId) {
|
|
return this.mailboxConnectionHandler(data.connectedMailboxId);
|
|
} else if (data.deletedMailbox) {
|
|
return this.mailboxDeleteHandler(data.deletedMailbox);
|
|
} else {
|
|
this.send({ uuid: data.uuid, confirmingMessageReceived: true });
|
|
this.messageHandler(data);
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
disconnect() {
|
|
this.ws?.close();
|
|
}
|
|
// message
|
|
sendMessage(channel, body) {
|
|
const messageObject = {
|
|
messageChannel: channel,
|
|
messageBody: body
|
|
};
|
|
return this.send(messageObject);
|
|
}
|
|
// subscription
|
|
subscribe(channel) {
|
|
const messageObject = {
|
|
subscribeChannel: channel
|
|
};
|
|
return this.send(messageObject);
|
|
}
|
|
unsubscribe(channel) {
|
|
const messageObject = {
|
|
unsubscribeChannel: channel
|
|
};
|
|
return this.send(messageObject);
|
|
}
|
|
// mailbox
|
|
requestMailbox() {
|
|
const messageObject = {
|
|
requestingMailboxSetup: true
|
|
};
|
|
return this.send(messageObject);
|
|
}
|
|
connectMailbox(mailboxId) {
|
|
const messageObject = {
|
|
requestedMailbox: mailboxId
|
|
};
|
|
return this.send(messageObject);
|
|
}
|
|
deleteMailbox(mailboxId) {
|
|
const messageObject = {
|
|
deletingMailbox: mailboxId
|
|
};
|
|
return this.send(messageObject);
|
|
}
|
|
};
|
|
|
|
// src/Model/Global/connectionModel.ts
|
|
var ConnectionModel = class {
|
|
// init
|
|
constructor(storageModel2) {
|
|
// config
|
|
this.reconnectInterval = void 0;
|
|
this.shouldAttemptReconnect = false;
|
|
// handler managers
|
|
this.connectionChangeHandlerManager = new HandlerManager();
|
|
this.messageHandlerManager = new HandlerManager();
|
|
this.messageSentHandlerManager = new HandlerManager();
|
|
this.channelsToSubscribe = /* @__PURE__ */ new Set();
|
|
// handlers
|
|
this.handleMessage = (data) => {
|
|
this.messageHandlerManager.trigger(data);
|
|
};
|
|
this.handleConnectionChange = () => {
|
|
console.log("connection status:", this.isConnected, this.address);
|
|
this.connectionChangeHandlerManager.trigger();
|
|
if (this.address == void 0) return;
|
|
this.storeAddress(this.address);
|
|
this.sendSubscriptionRequest();
|
|
this.sendMessagesInOutbox();
|
|
};
|
|
// connection
|
|
this.connect = (address) => {
|
|
console.log("connecting...", address);
|
|
this.shouldAttemptReconnect = true;
|
|
this.udn.connect(address);
|
|
};
|
|
this.disconnect = () => {
|
|
this.shouldAttemptReconnect = false;
|
|
this.udn.disconnect();
|
|
const reconnectAddressPath = StorageModel.getPath(
|
|
"connection" /* ConnectionModel */,
|
|
filePaths.connectionModel.reconnectAddress
|
|
);
|
|
this.storageModel.remove(reconnectAddressPath);
|
|
};
|
|
this.reconnect = () => {
|
|
const reconnectAddressPath = this.getReconnectAddressPath();
|
|
const reconnectAddress = this.storageModel.read(reconnectAddressPath);
|
|
if (reconnectAddress == null) return;
|
|
console.log("reconnecting...");
|
|
this.connect(reconnectAddress);
|
|
};
|
|
// mailbox
|
|
this.getMailboxPath = (address) => {
|
|
const mailboxDirPath = StorageModel.getPath(
|
|
"connection" /* ConnectionModel */,
|
|
filePaths.connectionModel.mailboxes
|
|
);
|
|
const mailboxFilePath = [...mailboxDirPath, address];
|
|
return mailboxFilePath;
|
|
};
|
|
this.requestNewMailbox = () => {
|
|
console.log("requesting new mailbox");
|
|
this.udn.requestMailbox();
|
|
};
|
|
this.connectMailbox = () => {
|
|
if (this.address == void 0) return;
|
|
const mailboxId = this.storageModel.read(
|
|
this.getMailboxPath(this.address)
|
|
);
|
|
console.log("connecting mailbox", mailboxId);
|
|
if (mailboxId == null) return this.requestNewMailbox();
|
|
this.udn.connectMailbox(mailboxId);
|
|
};
|
|
this.storeMailbox = (mailboxId) => {
|
|
if (this.address == void 0) return;
|
|
this.storageModel.write(this.getMailboxPath(this.address), mailboxId);
|
|
};
|
|
// subscription
|
|
this.addChannel = (channel) => {
|
|
this.channelsToSubscribe.add(channel);
|
|
this.sendSubscriptionRequest();
|
|
};
|
|
this.sendSubscriptionRequest = () => {
|
|
if (this.isConnected == false) return;
|
|
for (const channel of this.channelsToSubscribe) {
|
|
this.udn.subscribe(channel);
|
|
}
|
|
this.connectMailbox();
|
|
};
|
|
// outbox
|
|
this.getOutboxPath = () => {
|
|
return StorageModel.getPath(
|
|
"connection" /* ConnectionModel */,
|
|
filePaths.connectionModel.outbox
|
|
);
|
|
};
|
|
this.getOutboxMessags = () => {
|
|
const outboxPath = this.getOutboxPath();
|
|
const messageIds = this.storageModel.list(outboxPath);
|
|
let chatMessages = [];
|
|
for (const messageId of messageIds) {
|
|
const chatMessage = this.storageModel.readStringifiable(
|
|
[...outboxPath, messageId],
|
|
ChatMessageReference
|
|
);
|
|
if (chatMessage == null) continue;
|
|
chatMessages.push(chatMessage);
|
|
}
|
|
return chatMessages;
|
|
};
|
|
this.addToOutbox = (chatMessage) => {
|
|
const messagePath = [
|
|
...this.getOutboxPath(),
|
|
chatMessage.fileId
|
|
];
|
|
this.storageModel.writeStringifiable(messagePath, chatMessage);
|
|
};
|
|
this.removeFromOutbox = (chatMessage) => {
|
|
const messagePath = [
|
|
...this.getOutboxPath(),
|
|
chatMessage.fileId
|
|
];
|
|
this.storageModel.remove(messagePath);
|
|
};
|
|
this.sendMessagesInOutbox = () => {
|
|
const messages = this.getOutboxMessags();
|
|
for (const message of messages) {
|
|
const isSent = this.tryToSendMessage(message);
|
|
if (isSent == false) return;
|
|
this.removeFromOutbox(message);
|
|
}
|
|
};
|
|
// messaging
|
|
this.sendMessageOrStore = (chatMessage) => {
|
|
const isSent = this.tryToSendMessage(chatMessage);
|
|
if (isSent == true) return;
|
|
this.addToOutbox(chatMessage);
|
|
};
|
|
this.tryToSendMessage = (chatMessage) => {
|
|
const stringifiedBody = stringify(chatMessage);
|
|
const isSent = this.sendPlainMessage(
|
|
chatMessage.channel,
|
|
stringifiedBody
|
|
);
|
|
if (isSent) this.messageSentHandlerManager.trigger(chatMessage);
|
|
return isSent;
|
|
};
|
|
this.sendPlainMessage = (channel, body) => {
|
|
return this.udn.sendMessage(channel, body);
|
|
};
|
|
// storage
|
|
this.getPreviousAddressPath = () => {
|
|
return StorageModel.getPath(
|
|
"connection" /* ConnectionModel */,
|
|
filePaths.connectionModel.previousAddresses
|
|
);
|
|
};
|
|
this.getAddressPath = (address) => {
|
|
const dirPath = this.getPreviousAddressPath();
|
|
return [...dirPath, address];
|
|
};
|
|
this.getReconnectAddressPath = () => {
|
|
return StorageModel.getPath(
|
|
"connection" /* ConnectionModel */,
|
|
filePaths.connectionModel.reconnectAddress
|
|
);
|
|
};
|
|
this.storeAddress = (address) => {
|
|
const addressPath = this.getAddressPath(address);
|
|
this.storageModel.write(addressPath, "");
|
|
const reconnectAddressPath = this.getReconnectAddressPath();
|
|
this.storageModel.write(reconnectAddressPath, address);
|
|
};
|
|
this.removeAddress = (address) => {
|
|
const addressPath = this.getAddressPath(address);
|
|
this.storageModel.remove(addressPath);
|
|
};
|
|
this.udn = new UDNFrontend();
|
|
this.storageModel = storageModel2;
|
|
this.udn.onmessage = (data) => {
|
|
this.handleMessage(data);
|
|
};
|
|
this.udn.onconnect = () => {
|
|
this.handleConnectionChange();
|
|
};
|
|
this.udn.ondisconnect = () => {
|
|
this.handleConnectionChange();
|
|
};
|
|
this.udn.onmailboxcreate = (mailboxId) => {
|
|
console.log("created mailbox", mailboxId);
|
|
this.storeMailbox(mailboxId);
|
|
this.connectMailbox();
|
|
};
|
|
this.udn.onmailboxdelete = (mailboxId) => {
|
|
console.log(`mailbox ${mailboxId} deleted`);
|
|
this.requestNewMailbox();
|
|
};
|
|
this.udn.onmailboxconnect = (mailboxId) => {
|
|
console.log(`using mailbox ${mailboxId}`);
|
|
};
|
|
setInterval(() => {
|
|
if (this.isConnected == true) return;
|
|
if (this.shouldAttemptReconnect == false) return;
|
|
this.reconnect();
|
|
}, 5e3);
|
|
this.reconnect();
|
|
}
|
|
// data
|
|
get isConnected() {
|
|
return this.udn.ws != void 0 && this.udn.ws.readyState == 1;
|
|
}
|
|
get address() {
|
|
return this.udn.ws?.url;
|
|
}
|
|
get addresses() {
|
|
const dirPath = this.getPreviousAddressPath();
|
|
return this.storageModel.list(dirPath);
|
|
}
|
|
};
|
|
|
|
// src/Model/Chat/chatListModel.ts
|
|
var ChatListModel = class {
|
|
// init
|
|
constructor(storageModel2, settingsModel2, connectionModel2, contactListModel2) {
|
|
this.storageModel = storageModel2;
|
|
this.settingsModel = settingsModel2;
|
|
this.connectionModel = connectionModel2;
|
|
this.contactListModel = contactListModel2;
|
|
// data
|
|
this.chatModels = /* @__PURE__ */ new Set();
|
|
// chat handling
|
|
this.addChatModel = (chatModel) => {
|
|
this.chatModels.add(chatModel);
|
|
};
|
|
this.createChat = (primaryChannel) => {
|
|
const id = v4_default();
|
|
const chatModel = new ChatModel(
|
|
this.storageModel,
|
|
this.connectionModel,
|
|
this.settingsModel,
|
|
this,
|
|
this.contactListModel,
|
|
id
|
|
);
|
|
chatModel.setName(primaryChannel);
|
|
this.addChatModel(chatModel);
|
|
return chatModel;
|
|
};
|
|
this.untrackChat = (chat) => {
|
|
this.chatModels.delete(chat);
|
|
};
|
|
// message handlers
|
|
this.messageHandler = (data) => {
|
|
const channel = data.messageChannel;
|
|
const body = data.messageBody;
|
|
if (channel == void 0) return;
|
|
if (body == void 0) return;
|
|
this.routeMessageToCorrectChatModel(
|
|
channel,
|
|
(chatModel) => chatModel.handleMessage(body)
|
|
);
|
|
};
|
|
this.messageSentHandler = (chatMessage) => {
|
|
const channel = chatMessage.channel;
|
|
this.routeMessageToCorrectChatModel(
|
|
channel,
|
|
(chatModel) => chatModel.handleMessageSent(chatMessage)
|
|
);
|
|
};
|
|
// util
|
|
this.routeMessageToCorrectChatModel = (channel, fn) => {
|
|
const allChannels = channel.split("/");
|
|
for (const chatModel of this.chatModels) {
|
|
for (const channel2 of allChannels) {
|
|
if (channel2 != chatModel.id) continue;
|
|
fn(chatModel);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
// load
|
|
this.loadChats = () => {
|
|
const chatDir = StorageModel.getPath(
|
|
"chat" /* Chat */,
|
|
filePaths.chat.base
|
|
);
|
|
const chatIds = this.storageModel.list(chatDir);
|
|
for (const chatId of chatIds) {
|
|
const chatModel = new ChatModel(
|
|
this.storageModel,
|
|
this.connectionModel,
|
|
this.settingsModel,
|
|
this,
|
|
this.contactListModel,
|
|
chatId
|
|
);
|
|
this.addChatModel(chatModel);
|
|
}
|
|
};
|
|
this.loadChats();
|
|
this.connectionModel.messageHandlerManager.setHandler(
|
|
"chat-list",
|
|
this.messageHandler
|
|
);
|
|
this.connectionModel.messageSentHandlerManager.setHandler(
|
|
"chat-list",
|
|
this.messageSentHandler
|
|
);
|
|
}
|
|
};
|
|
|
|
// src/index.tsx
|
|
var storageModel = new StorageModel();
|
|
var settingsModel = new SettingsModel(storageModel);
|
|
var contactListModel = new ContactListModel(storageModel, settingsModel);
|
|
var connectionModel = new ConnectionModel(storageModel);
|
|
var chatListModel = new ChatListModel(
|
|
storageModel,
|
|
settingsModel,
|
|
connectionModel,
|
|
contactListModel
|
|
);
|
|
var fileTransferModel = new FileTransferModel(storageModel, connectionModel);
|
|
var coreViewModel = new CoreViewModel(
|
|
storageModel,
|
|
settingsModel,
|
|
connectionModel,
|
|
chatListModel,
|
|
fileTransferModel
|
|
);
|
|
var storageViewModel = new StorageViewModel(coreViewModel);
|
|
var settingsViewModel = new SettingsViewModel(coreViewModel, settingsModel);
|
|
var contactListViewModel = new ContactListViewModel(
|
|
contactListModel,
|
|
settingsViewModel
|
|
);
|
|
var connectionViewModel = new ConnectionViewModel(coreViewModel);
|
|
var chatListViewModel = new ChatListViewModel(
|
|
coreViewModel,
|
|
settingsViewModel,
|
|
connectionViewModel,
|
|
contactListViewModel
|
|
);
|
|
var fileTransferViewModel = new FileTransferViewModel(coreViewModel);
|
|
var onboardingViewModel = new OnboardingViewModel(
|
|
connectionViewModel,
|
|
fileTransferViewModel,
|
|
settingsViewModel
|
|
);
|
|
var homeViewModel = new HomeViewModel(
|
|
coreViewModel,
|
|
settingsViewModel,
|
|
fileTransferViewModel,
|
|
storageViewModel,
|
|
connectionViewModel
|
|
);
|
|
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", null, /* @__PURE__ */ createElement("div", { id: "sky" }), /* @__PURE__ */ createElement("div", { id: "grass-1" }), /* @__PURE__ */ createElement("div", { id: "grass-2" })))
|
|
);
|
|
document.querySelector("main").append(
|
|
HomePage(
|
|
coreViewModel,
|
|
storageViewModel,
|
|
settingsViewModel,
|
|
connectionViewModel,
|
|
fileTransferViewModel,
|
|
chatListViewModel
|
|
),
|
|
ChatPageWrapper(coreViewModel, chatListViewModel),
|
|
OnboardingModalWrapper(coreViewModel, onboardingViewModel),
|
|
ConnectionModal2(coreViewModel, connectionViewModel),
|
|
DataTransferModalWrapper(
|
|
coreViewModel,
|
|
connectionViewModel,
|
|
fileTransferViewModel
|
|
),
|
|
StorageModal(coreViewModel, storageViewModel),
|
|
SettingsModal(coreViewModel, settingsViewModel)
|
|
);
|
|
})();
|