implement auto reconnect

This commit is contained in:
marlon-erler
2026-04-01 08:29:39 +00:00
parent c52d7b7685
commit 9d400a9929
82 changed files with 8340 additions and 7641 deletions
+1 -1
View File
@@ -827,7 +827,7 @@ body[showing-chat] #chat-page-wrapper {
.file-browser code { .file-browser code {
width: 100%; width: 100%;
height: 100%; height: 100%;
font-size: .9rem; font-size: 0.9rem;
} }
@media (min-width: 640px) { @media (min-width: 640px) {
.file-browser > div { .file-browser > div {
+316 -86
View File
@@ -532,7 +532,10 @@
this.initializeTree(); this.initializeTree();
}; };
this.removeRecursively = (pathComponents) => { this.removeRecursively = (pathComponents) => {
this.recurse(pathComponents, (path) => this.remove(path, false)); this.recurse(
pathComponents,
(path) => this.remove(path, false)
);
this.initializeTree(); this.initializeTree();
}; };
this.renameRecursively = (sourcePathComponents, destinationPathComponents) => { this.renameRecursively = (sourcePathComponents, destinationPathComponents) => {
@@ -658,7 +661,9 @@
constructor(storageModel2, settingsModel2, fileModel) { constructor(storageModel2, settingsModel2, fileModel) {
// paths // paths
this.getBasePath = () => { this.getBasePath = () => {
return this.fileModel.getModelContainerPath("calendar" /* ModelCalendar */); return this.fileModel.getModelContainerPath(
"calendar" /* ModelCalendar */
);
}; };
this.getViewPath = () => { this.getViewPath = () => {
return [...this.getBasePath(), "view" /* ModelView */]; return [...this.getBasePath(), "view" /* ModelView */];
@@ -806,9 +811,15 @@
}; };
// handlers // handlers
this.handleFileContent = (fileContent) => { this.handleFileContent = (fileContent) => {
if (checkMatchesObjectStructure(fileContent, BoardInfoFileContentReference) == true) { if (checkMatchesObjectStructure(
fileContent,
BoardInfoFileContentReference
) == true) {
this.handleBoard(fileContent); this.handleBoard(fileContent);
} else if (checkMatchesObjectStructure(fileContent, TaskFileContentReference) == true) { } else if (checkMatchesObjectStructure(
fileContent,
TaskFileContentReference
) == true) {
this.handleTask(fileContent); this.handleTask(fileContent);
} }
}; };
@@ -897,7 +908,10 @@
return versionIds; return versionIds;
}; };
this.getLatestTaskFileContent = (taskId) => { this.getLatestTaskFileContent = (taskId) => {
const taskFileContentOrNull = this.fileModel.getLatestFileContent(taskId, TaskFileContentReference); const taskFileContentOrNull = this.fileModel.getLatestFileContent(
taskId,
TaskFileContentReference
);
return taskFileContentOrNull; return taskFileContentOrNull;
}; };
this.getSpecificTaskFileContent = (taskId, versionId) => { this.getSpecificTaskFileContent = (taskId, versionId) => {
@@ -933,10 +947,7 @@
static { static {
// utility // utility
this.createBoardInfoFileContent = (fileId, name, color) => { this.createBoardInfoFileContent = (fileId, name, color) => {
const fileContent = FileModel2.createFileContent( const fileContent = FileModel2.createFileContent(fileId, "board-info");
fileId,
"board-info"
);
return { return {
...fileContent, ...fileContent,
name, name,
@@ -1043,7 +1054,10 @@
return fileContentIds[fileContentIds.length - 1]; return fileContentIds[fileContentIds.length - 1];
}; };
this.getFileContent = (fileId, fileContentName, reference) => { this.getFileContent = (fileId, fileContentName, reference) => {
const filePath = this.getFileContentPath(fileId, fileContentName); const filePath = this.getFileContentPath(
fileId,
fileContentName
);
const fileContentOrNull = this.storageModel.readStringifiable( const fileContentOrNull = this.storageModel.readStringifiable(
filePath, filePath,
reference reference
@@ -1247,7 +1261,9 @@
this.storageModel.writeStringifiable(messagePath, chatMessage); this.storageModel.writeStringifiable(messagePath, chatMessage);
this.chatMessageHandlerManager.trigger(chatMessage); this.chatMessageHandlerManager.trigger(chatMessage);
} }
this.fileModel.handleStringifiedFileContent(chatMessage.stringifiedFile); this.fileModel.handleStringifiedFileContent(
chatMessage.stringifiedFile
);
}; };
this.sendMessage = async (body, fileContent) => { this.sendMessage = async (body, fileContent) => {
const senderName = this.settingsModel.username; const senderName = this.settingsModel.username;
@@ -1328,7 +1344,11 @@
this.loadInfo(); this.loadInfo();
this.loadColor(); this.loadColor();
this.subscribe(); this.subscribe();
this.fileModel = new FileModel2(this.storageModel, this.settingsModel, this); this.fileModel = new FileModel2(
this.storageModel,
this.settingsModel,
this
);
} }
get secondaryChannels() { get secondaryChannels() {
return this.info.secondaryChannels.sort(localeCompare); return this.info.secondaryChannels.sort(localeCompare);
@@ -1341,7 +1361,10 @@
const chatMessages = []; const chatMessages = [];
for (const messageId of messageIds) { for (const messageId of messageIds) {
const messagePath = this.getMessagePath(messageId); const messagePath = this.getMessagePath(messageId);
const chatMessage = this.storageModel.readStringifiable(messagePath, ChatMessageReference); const chatMessage = this.storageModel.readStringifiable(
messagePath,
ChatMessageReference
);
chatMessages.push(chatMessage); chatMessages.push(chatMessage);
} }
const sorted = chatMessages.sort( const sorted = chatMessages.sort(
@@ -1378,7 +1401,10 @@
chatMessage.stringifiedFile = stringifiedFile; chatMessage.stringifiedFile = stringifiedFile;
} }
if (encryptionKey != "") { if (encryptionKey != "") {
chatMessage.body = await encryptString(chatMessage.body, encryptionKey); chatMessage.body = await encryptString(
chatMessage.body,
encryptionKey
);
chatMessage.stringifiedFile = await encryptString( chatMessage.stringifiedFile = await encryptString(
chatMessage.stringifiedFile, chatMessage.stringifiedFile,
encryptionKey encryptionKey
@@ -1463,7 +1489,10 @@
}; };
// load // load
this.loadChats = () => { this.loadChats = () => {
const chatDir = StorageModel.getPath("chat" /* Chat */, filePaths.chat.base); const chatDir = StorageModel.getPath(
"chat" /* Chat */,
filePaths.chat.base
);
const chatIds = this.storageModel.list(chatDir); const chatIds = this.storageModel.list(chatDir);
for (const chatId of chatIds) { for (const chatId of chatIds) {
const chatModel = new ChatModel( const chatModel = new ChatModel(
@@ -1563,7 +1592,9 @@
) == false) { ) == false) {
this.coreViewModel.taskCategorySuggestions.add(this.category.value); this.coreViewModel.taskCategorySuggestions.add(this.category.value);
} }
if (this.coreViewModel.taskStatusSuggestions.value.has(this.status.value) == false) { if (this.coreViewModel.taskStatusSuggestions.value.has(
this.status.value
) == false) {
this.coreViewModel.taskStatusSuggestions.add(this.status.value); this.coreViewModel.taskStatusSuggestions.add(this.status.value);
} }
}; };
@@ -1588,14 +1619,15 @@
}; };
this.deleteTask = () => { this.deleteTask = () => {
this.close(); this.close();
this.boardsAndTasksModel.deleteTask(this.task.boardId, this.task.fileId); this.boardsAndTasksModel.deleteTask(
this.task.boardId,
this.task.fileId
);
this.containingModel.removeTaskFromView(this.task); this.containingModel.removeTaskFromView(this.task);
}; };
// load // load
this.loadVersionIds = () => { this.loadVersionIds = () => {
const versionIds = this.boardsAndTasksModel.listTaskVersionIds( const versionIds = this.boardsAndTasksModel.listTaskVersionIds(this.task.fileId);
this.task.fileId
);
const sortedVersionIds = versionIds.sort(localeCompare).reverse(); const sortedVersionIds = versionIds.sort(localeCompare).reverse();
this.versionIds.clear(); this.versionIds.clear();
this.versionIds.add(...sortedVersionIds); this.versionIds.add(...sortedVersionIds);
@@ -1810,7 +1842,11 @@
} }
}; };
this.handleDrop = (year, month, date) => { this.handleDrop = (year, month, date) => {
const ISOString = CalendarModel.getISODateString(year, month, date); const ISOString = CalendarModel.getISODateString(
year,
month,
date
);
const draggedObject = this.coreViewModel.draggedObject.value; const draggedObject = this.coreViewModel.draggedObject.value;
if (draggedObject instanceof TaskViewModel == false) return; if (draggedObject instanceof TaskViewModel == false) return;
draggedObject.setDate(ISOString); draggedObject.setDate(ISOString);
@@ -1822,7 +1858,9 @@
this.selectedMonth.value, this.selectedMonth.value,
() => new MapState() () => new MapState()
); );
const taskIds = this.calendarModel.listTaskIds(this.monthString); const taskIds = this.calendarModel.listTaskIds(
this.monthString
);
for (const taskId of taskIds) { for (const taskId of taskIds) {
const taskFileContent = this.boardsAndTasksModel.getLatestTaskFileContent(taskId); const taskFileContent = this.boardsAndTasksModel.getLatestTaskFileContent(taskId);
if (taskFileContent == null) continue; if (taskFileContent == null) continue;
@@ -1935,7 +1973,10 @@
existingChatMessageViewModel.body.value = chatMessage.body; existingChatMessageViewModel.body.value = chatMessage.body;
existingChatMessageViewModel.status.value = chatMessage.status; existingChatMessageViewModel.status.value = chatMessage.status;
} else { } else {
this.chatMessageViewModels.set(chatMessage.id, chatMessageViewModel); this.chatMessageViewModels.set(
chatMessage.id,
chatMessageViewModel
);
} }
}; };
// load // load
@@ -1947,7 +1988,10 @@
}; };
this.chatViewModel = chatViewModel; this.chatViewModel = chatViewModel;
this.cannotSendMessage = createProxyState( this.cannotSendMessage = createProxyState(
[this.chatViewModel.settingsViewModel.username, this.composingMessage], [
this.chatViewModel.settingsViewModel.username,
this.composingMessage
],
() => this.chatViewModel.settingsViewModel.username.value == "" || this.composingMessage.value == "" () => this.chatViewModel.settingsViewModel.username.value == "" || this.composingMessage.value == ""
); );
} }
@@ -2116,7 +2160,9 @@
this.filteredTaskViewModels = new ListState(); this.filteredTaskViewModels = new ListState();
// paths // paths
this.getBasePath = () => { this.getBasePath = () => {
return [...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId)]; return [
...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId)
];
}; };
this.getLastUsedBoardPath = () => { this.getLastUsedBoardPath = () => {
return [...this.getBasePath(), "last-used-view" /* LastUsedView */]; return [...this.getBasePath(), "last-used-view" /* LastUsedView */];
@@ -2137,7 +2183,9 @@
this.taskPageViewModel.updateBoard(newBoardInfoFileContent); this.taskPageViewModel.updateBoard(newBoardInfoFileContent);
}; };
this.applyColor = () => { this.applyColor = () => {
this.taskPageViewModel.chatViewModel.setDisplayedColor(this.color.value); this.taskPageViewModel.chatViewModel.setDisplayedColor(
this.color.value
);
}; };
this.deleteBoard = () => { this.deleteBoard = () => {
this.taskPageViewModel.deleteBoard(this.boardInfo); this.taskPageViewModel.deleteBoard(this.boardInfo);
@@ -3266,7 +3314,10 @@
"select", "select",
{ {
"bind:value": taskViewModel.selectedVersionId, "bind:value": taskViewModel.selectedVersionId,
"children:append": [taskViewModel.versionIds, VersionIdToOption] "children:append": [
taskViewModel.versionIds,
VersionIdToOption
]
} }
), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskNameLabel), /* @__PURE__ */ createElement("input", { "bind:value": taskViewModel.name }))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "category"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskBoardLabel), /* @__PURE__ */ createElement( ), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskNameLabel), /* @__PURE__ */ createElement("input", { "bind:value": taskViewModel.name }))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "category"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskBoardLabel), /* @__PURE__ */ createElement(
"select", "select",
@@ -3315,11 +3366,44 @@
StringToOption StringToOption
] ]
} }
), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "priority_high"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskPriorityLabel), /* @__PURE__ */ createElement("input", { type: "number", "bind:value": taskViewModel.priority }))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "calendar_month"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskDateLabel), /* @__PURE__ */ createElement("input", { type: "date", "bind:value": taskViewModel.date }))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "schedule"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskTimeLabel), /* @__PURE__ */ createElement("input", { type: "time", "bind:value": taskViewModel.time }))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton( ), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "priority_high"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskPriorityLabel), /* @__PURE__ */ createElement(
"input",
{
type: "number",
"bind:value": taskViewModel.priority
}
))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "calendar_month"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskDateLabel), /* @__PURE__ */ createElement(
"input",
{
type: "date",
"bind:value": taskViewModel.date
}
))), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "schedule"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.taskTimeLabel), /* @__PURE__ */ createElement(
"input",
{
type: "time",
"bind:value": taskViewModel.time
}
))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton(
translations.chatPage.task.deleteTaskButton, translations.chatPage.task.deleteTaskButton,
"delete_forever", "delete_forever",
taskViewModel.deleteTask taskViewModel.deleteTask
))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement("button", { class: "flex", "on:click": taskViewModel.closeAndDiscard }, translations.general.closeButton), /* @__PURE__ */ createElement("button", { class: "flex primary", "on:click": taskViewModel.closeAndSave }, translations.general.saveButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "save"))))); ))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement(
"button",
{
class: "flex",
"on:click": taskViewModel.closeAndDiscard
},
translations.general.closeButton
), /* @__PURE__ */ createElement(
"button",
{
class: "flex primary",
"on:click": taskViewModel.closeAndSave
},
translations.general.saveButton,
/* @__PURE__ */ createElement("span", { class: "icon" }, "save")
))));
} }
// src/View/Components/taskEntry.tsx // src/View/Components/taskEntry.tsx
@@ -3341,7 +3425,13 @@
"on:click": taskViewModel.open, "on:click": taskViewModel.open,
"on:dragstart": taskViewModel.dragStart "on:dragstart": taskViewModel.dragStart
}, },
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", { class: "ellipsis", "subscribe:innerText": taskViewModel.name }), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement(
"b",
{
class: "ellipsis",
"subscribe:innerText": taskViewModel.name
}
), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement(
"div", "div",
{ {
class: "grid secondary", class: "grid secondary",
@@ -3384,7 +3474,11 @@
date date
); );
}; };
return MonthGrid2(monthGrid, calendarPageViewModel.selectedDate, drop); return MonthGrid2(
monthGrid,
calendarPageViewModel.selectedDate,
drop
);
} }
} }
); );
@@ -3399,7 +3493,9 @@
if (listState == void 0) { if (listState == void 0) {
return /* @__PURE__ */ createElement("div", null); return /* @__PURE__ */ createElement("div", null);
} else { } else {
const sidePaneContent = createProxyState([listState], () => { const sidePaneContent = createProxyState(
[listState],
() => {
if (listState.value.size == 0) { if (listState.value.size == 0) {
return /* @__PURE__ */ createElement("div", { class: "width-100 height-100 flex-column justify-center align-center" }, /* @__PURE__ */ createElement("span", { class: "secondary slide-up" }, translations.chatPage.calendar.noEvents)); return /* @__PURE__ */ createElement("div", { class: "width-100 height-100 flex-column justify-center align-center" }, /* @__PURE__ */ createElement("span", { class: "secondary slide-up" }, translations.chatPage.calendar.noEvents));
} else { } else {
@@ -3407,11 +3503,15 @@
"div", "div",
{ {
class: "flex-column gap slide-up padding-bottom", class: "flex-column gap slide-up padding-bottom",
"children:append": [listState, TaskViewModelToEntry] "children:append": [
listState,
TaskViewModelToEntry
]
} }
); );
} }
}); }
);
return /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement(
"div", "div",
{ {
@@ -3484,13 +3584,25 @@
"on:click": calendarPageViewModel.createEvent "on:click": calendarPageViewModel.createEvent
}, },
/* @__PURE__ */ createElement("span", { class: "icon" }, "add") /* @__PURE__ */ createElement("span", { class: "icon" }, "add")
))), /* @__PURE__ */ createElement("div", { class: "content padding-0", "children:set": mainContent }))), /* @__PURE__ */ createElement( ))), /* @__PURE__ */ createElement(
"div",
{
class: "content padding-0",
"children:set": mainContent
}
))), /* @__PURE__ */ createElement(
"div", "div",
{ {
class: "pane-wrapper side background", class: "pane-wrapper side background",
"set:color": calendarPageViewModel.chatViewModel.displayedColor "set:color": calendarPageViewModel.chatViewModel.displayedColor
}, },
/* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement("div", { class: "content", "children:set": sidePaneContentWrapper })) /* @__PURE__ */ createElement("div", { class: "pane" }, /* @__PURE__ */ createElement(
"div",
{
class: "content",
"children:set": sidePaneContentWrapper
}
))
), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal })); ), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal }));
} }
@@ -3522,13 +3634,20 @@
// src/View/Modals/chatMessageInfoModal.tsx // src/View/Modals/chatMessageInfoModal.tsx
function ChatMessageInfoModal(chatMessageViewModel) { function ChatMessageInfoModal(chatMessageViewModel) {
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": chatMessageViewModel.isPresentingInfoModal }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.chatPage.message.messageInfoHeadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "account_circle"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.sentBy), /* @__PURE__ */ createElement("b", { class: "break-word" }, chatMessageViewModel.sender))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "schedule"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.timeSent), /* @__PURE__ */ createElement("b", { class: "break-word" }, chatMessageViewModel.dateSent))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.channel), /* @__PURE__ */ createElement("b", { class: "break-word" }, chatMessageViewModel.channel))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "description"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.messageContent), /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement(
"div",
{
class: "modal",
"toggle:open": chatMessageViewModel.isPresentingInfoModal
},
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.chatPage.message.messageInfoHeadline), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "account_circle"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.sentBy), /* @__PURE__ */ createElement("b", { class: "break-word" }, chatMessageViewModel.sender))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "schedule"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.timeSent), /* @__PURE__ */ createElement("b", { class: "break-word" }, chatMessageViewModel.dateSent))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.channel), /* @__PURE__ */ createElement("b", { class: "break-word" }, chatMessageViewModel.channel))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "description"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.message.messageContent), /* @__PURE__ */ createElement(
"b", "b",
{ {
class: "break-word", class: "break-word",
"subscribe:innerText": chatMessageViewModel.body "subscribe:innerText": chatMessageViewModel.body
} }
)))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.copyMessage }, translations.chatPage.message.copyMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "content_copy")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.resendMessage }, translations.chatPage.message.resendMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "redo")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.decryptMessage }, translations.chatPage.message.decryptMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "key")))), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.hideInfoModal }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); )))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.copyMessage }, translations.chatPage.message.copyMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "content_copy")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.resendMessage }, translations.chatPage.message.resendMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "redo")), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.decryptMessage }, translations.chatPage.message.decryptMessageButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "key")))), /* @__PURE__ */ createElement("button", { "on:click": chatMessageViewModel.hideInfoModal }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))
);
} }
// src/View/Components/chatMessage.tsx // src/View/Components/chatMessage.tsx
@@ -3560,7 +3679,13 @@
class: "body", class: "body",
"subscribe:innerText": chatMessageViewModel.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( ), /* @__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", "button",
{ {
"on:click": chatMessageViewModel.showInfoModal, "on:click": chatMessageViewModel.showInfoModal,
@@ -3872,11 +3997,21 @@
(taskViewModel) => taskViewModel.task, (taskViewModel) => taskViewModel.task,
boardViewModel.filteredTaskViewModels, boardViewModel.filteredTaskViewModels,
(taskViewModels) => { (taskViewModels) => {
const viewModel = new TaskCategoryBulkChangeViewModel(taskViewModels, categoryName); const viewModel = new TaskCategoryBulkChangeViewModel(
taskViewModels,
categoryName
);
function drop() { function drop() {
boardViewModel.handleDropWithinBoard(categoryName); boardViewModel.handleDropWithinBoard(categoryName);
} }
const view = /* @__PURE__ */ createElement("div", { class: "flex-column flex-no", "on:dragover": allowDrop, "on:drop": drop }, /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( const view = /* @__PURE__ */ createElement(
"div",
{
class: "flex-column flex-no",
"on:dragover": allowDrop,
"on:drop": drop
},
/* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
"input", "input",
{ {
placeholder: translations.chatPage.task.renameCategoryInputPlaceholder, placeholder: translations.chatPage.task.renameCategoryInputPlaceholder,
@@ -3891,13 +4026,16 @@
"toggle:disabled": viewModel.cannotSet "toggle:disabled": viewModel.cannotSet
}, },
/* @__PURE__ */ createElement("span", { class: "icon" }, "check") /* @__PURE__ */ createElement("span", { class: "icon" }, "check")
)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( )),
/* @__PURE__ */ createElement("hr", null),
/* @__PURE__ */ createElement(
"div", "div",
{ {
class: "kanban-column", class: "kanban-column",
"children:append": [taskViewModels, TaskViewModelToEntry] "children:append": [taskViewModels, TaskViewModelToEntry]
} }
)); )
);
index.subscribe((newIndex) => { index.subscribe((newIndex) => {
view.style.order = newIndex; view.style.order = newIndex;
}); });
@@ -3908,7 +4046,13 @@
// src/View/Modals/boardSettingsModal.tsx // src/View/Modals/boardSettingsModal.tsx
function BoardSettingsModal(boardViewModel) { function BoardSettingsModal(boardViewModel) {
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": boardViewModel.isPresentingSettingsModal }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.chatPage.task.boardSettingsHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.boardNameInputLabel), /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement(
"div",
{
class: "modal",
"toggle:open": boardViewModel.isPresentingSettingsModal
},
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.chatPage.task.boardSettingsHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "label"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.chatPage.task.boardNameInputLabel), /* @__PURE__ */ createElement(
"input", "input",
{ {
"on:enter": boardViewModel.saveSettings, "on:enter": boardViewModel.saveSettings,
@@ -3918,7 +4062,8 @@
translations.chatPage.task.deleteBoardButton, translations.chatPage.task.deleteBoardButton,
"delete_forever", "delete_forever",
boardViewModel.deleteBoard boardViewModel.deleteBoard
))), /* @__PURE__ */ createElement("button", { "on:click": boardViewModel.hideSettings }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); ))), /* @__PURE__ */ createElement("button", { "on:click": boardViewModel.hideSettings }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))
);
} }
// src/View/ChatPages/boardStatusGridPage.tsx // src/View/ChatPages/boardStatusGridPage.tsx
@@ -3978,9 +4123,12 @@
const doesMatchStatus = taskViewModel.task.status == statusName; const doesMatchStatus = taskViewModel.task.status == statusName;
if (doesMatchStatus == false) return; if (doesMatchStatus == false) return;
taskViewModelsWithMatchingStatus.add(taskViewModel); taskViewModelsWithMatchingStatus.add(taskViewModel);
boardViewModel.filteredTaskViewModels.handleRemoval(taskViewModel, () => { boardViewModel.filteredTaskViewModels.handleRemoval(
taskViewModel,
() => {
taskViewModelsWithMatchingStatus.remove(taskViewModel); taskViewModelsWithMatchingStatus.remove(taskViewModel);
}); }
);
} }
); );
const viewModel = new TaskStatusBulkChangeViewModel( const viewModel = new TaskStatusBulkChangeViewModel(
@@ -4015,7 +4163,10 @@
boardViewModel.filteredTaskViewModels, boardViewModel.filteredTaskViewModels,
(taskViewModels) => { (taskViewModels) => {
const statusNameConverter = (statusName) => { const statusNameConverter = (statusName) => {
const index2 = createPropertyValueIndexState(sortedStatuses, statusName); const index2 = createPropertyValueIndexState(
sortedStatuses,
statusName
);
return CategoryStatusColumn( return CategoryStatusColumn(
categoryName, categoryName,
statusName, statusName,
@@ -4024,7 +4175,10 @@
taskViewModels taskViewModels
); );
}; };
const viewModel = new TaskCategoryBulkChangeViewModel(taskViewModels, categoryName); const viewModel = new TaskCategoryBulkChangeViewModel(
taskViewModels,
categoryName
);
const view = /* @__PURE__ */ createElement("div", { class: "flex-row flex-no large-gap" }, /* @__PURE__ */ createElement("div", { class: "property-input-wrapper" }, /* @__PURE__ */ createElement( const view = /* @__PURE__ */ createElement("div", { class: "flex-row flex-no large-gap" }, /* @__PURE__ */ createElement("div", { class: "property-input-wrapper" }, /* @__PURE__ */ createElement(
"input", "input",
{ {
@@ -4113,7 +4267,10 @@
{ {
hidden: true, hidden: true,
id: suggestionId, id: suggestionId,
"children:append": [searchViewModel.suggestions, StringToOption] "children:append": [
searchViewModel.suggestions,
StringToOption
]
} }
), /* @__PURE__ */ createElement( ), /* @__PURE__ */ createElement(
"button", "button",
@@ -4129,7 +4286,10 @@
{ {
class: "grid gap", class: "grid gap",
style: "grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr))", style: "grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr))",
"children:append": [searchViewModel.matchingObjects, converter] "children:append": [
searchViewModel.matchingObjects,
converter
]
} }
)), /* @__PURE__ */ createElement("button", { "on:click": close }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); )), /* @__PURE__ */ createElement("button", { "on:click": close }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))));
} }
@@ -4168,7 +4328,9 @@
if (boardViewModel.selectedTaskViewModel.value == void 0) { if (boardViewModel.selectedTaskViewModel.value == void 0) {
return /* @__PURE__ */ createElement("div", null); return /* @__PURE__ */ createElement("div", null);
} else { } else {
return TaskSettingsModal(boardViewModel.selectedTaskViewModel.value); return TaskSettingsModal(
boardViewModel.selectedTaskViewModel.value
);
} }
} }
); );
@@ -4248,7 +4410,13 @@
"on:dragover": allowDrop, "on:dragover": allowDrop,
"on:drop": boardViewModel.handleDropBetweenBoards "on:drop": boardViewModel.handleDropBetweenBoards
}, },
/* @__PURE__ */ createElement("span", { class: "shadow", "subscribe:innerText": boardViewModel.name }), /* @__PURE__ */ createElement(
"span",
{
class: "shadow",
"subscribe:innerText": boardViewModel.name
}
),
/* @__PURE__ */ createElement("b", { "subscribe:innerText": boardViewModel.name }) /* @__PURE__ */ createElement("b", { "subscribe:innerText": boardViewModel.name })
); );
boardViewModel.index.subscribe((newIndex) => { boardViewModel.index.subscribe((newIndex) => {
@@ -4281,7 +4449,14 @@
return BoardPage(selectedBoard); return BoardPage(selectedBoard);
} }
); );
return /* @__PURE__ */ createElement("div", { id: "task-page", "toggle:isshowingboard": isShowingBoard, "set:showingboardlist": taskPageViewModel.isShowingBoadList }, /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement(
"div",
{
id: "task-page",
"toggle:isshowingboard": isShowingBoard,
"set:showingboardlist": taskPageViewModel.isShowingBoadList
},
/* @__PURE__ */ createElement(
"div", "div",
{ {
id: "board-list", id: "board-list",
@@ -4316,14 +4491,16 @@
] ]
} }
))) )))
), /* @__PURE__ */ createElement( ),
/* @__PURE__ */ createElement(
"div", "div",
{ {
id: "board-content", id: "board-content",
class: "pane-wrapper", class: "pane-wrapper",
"children:set": paneContent "children:set": paneContent
} }
)); )
);
} }
// src/View/chatPage.tsx // src/View/chatPage.tsx
@@ -4399,13 +4576,7 @@
} }
} }
); );
return /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement("div", { id: "chat-page-wrapper", "children:set": chatPageContent });
"div",
{
id: "chat-page-wrapper",
"children:set": chatPageContent
}
);
} }
// src/View/Modals/connectionModal.tsx // src/View/Modals/connectionModal.tsx
@@ -4572,6 +4743,8 @@
var ConnectionModel = class { var ConnectionModel = class {
// init // init
constructor(storageModel2) { constructor(storageModel2) {
this.reconnectInterval = void 0;
this.shouldAttemptReconnect = false;
this.connectionChangeHandlerManager = new HandlerManager(); this.connectionChangeHandlerManager = new HandlerManager();
this.messageHandlerManager = new HandlerManager(); this.messageHandlerManager = new HandlerManager();
this.messageSentHandlerManager = new HandlerManager(); this.messageSentHandlerManager = new HandlerManager();
@@ -4583,11 +4756,22 @@
this.handleConnectionChange = () => { this.handleConnectionChange = () => {
console.log("connection status:", this.isConnected, this.address); console.log("connection status:", this.isConnected, this.address);
this.connectionChangeHandlerManager.trigger(); this.connectionChangeHandlerManager.trigger();
if (this.isConnected == false) return; if (this.isConnected == false) {
if (this.shouldAttemptReconnect == false) return;
this.reconnectInterval = setInterval(() => {
if (this.isConnected == true) {
clearInterval(this.reconnectInterval);
return;
}
this.reconnect();
}, 5e3);
return;
}
if (this.address == void 0) return; if (this.address == void 0) return;
this.storeAddress(this.address); this.storeAddress(this.address);
this.sendSubscriptionRequest(); this.sendSubscriptionRequest();
this.sendMessagesInOutbox(); this.sendMessagesInOutbox();
this.shouldAttemptReconnect = true;
}; };
// connection // connection
this.connect = (address) => { this.connect = (address) => {
@@ -4600,6 +4784,14 @@
filePaths.connectionModel.reconnectAddress filePaths.connectionModel.reconnectAddress
); );
this.storageModel.remove(reconnectAddressPath); this.storageModel.remove(reconnectAddressPath);
this.shouldAttemptReconnect = false;
};
this.reconnect = () => {
const reconnectAddressPath = this.getReconnectAddressPath();
const reconnectAddress = this.storageModel.read(reconnectAddressPath);
if (reconnectAddress == null) return;
console.log("reconnecting...");
this.connect(reconnectAddress);
}; };
// mailbox // mailbox
this.getMailboxPath = (address) => { this.getMailboxPath = (address) => {
@@ -4616,7 +4808,9 @@
}; };
this.connectMailbox = () => { this.connectMailbox = () => {
if (this.address == void 0) return; if (this.address == void 0) return;
const mailboxId = this.storageModel.read(this.getMailboxPath(this.address)); const mailboxId = this.storageModel.read(
this.getMailboxPath(this.address)
);
console.log("connecting mailbox", mailboxId); console.log("connecting mailbox", mailboxId);
if (mailboxId == null) return this.requestNewMailbox(); if (mailboxId == null) return this.requestNewMailbox();
this.udn.connectMailbox(mailboxId); this.udn.connectMailbox(mailboxId);
@@ -4742,11 +4936,7 @@
this.udn.onmailboxconnect = (mailboxId) => { this.udn.onmailboxconnect = (mailboxId) => {
console.log(`using mailbox ${mailboxId}`); console.log(`using mailbox ${mailboxId}`);
}; };
const reconnectAddressPath = this.getReconnectAddressPath(); this.reconnect();
const reconnectAddress = storageModel2.read(reconnectAddressPath);
if (reconnectAddress != null) {
this.connect(reconnectAddress);
}
} }
// data // data
get isConnected() { get isConnected() {
@@ -4860,7 +5050,9 @@
); );
this.filesSentText = createProxyState( this.filesSentText = createProxyState(
[this.filesSentCount], [this.filesSentCount],
() => translations.dataTransferModal.filesSentCount(this.filesSentCount.value) () => translations.dataTransferModal.filesSentCount(
this.filesSentCount.value
)
); );
this.filePathsReceived = new ListState(); this.filePathsReceived = new ListState();
this.filesReceivedCount = createProxyState( this.filesReceivedCount = createProxyState(
@@ -4902,7 +5094,10 @@
}, },
{ {
label: translations.dataTransferModal.settingsData, label: translations.dataTransferModal.settingsData,
path: StorageModel.getPath("settings" /* SettingsModel */, []) path: StorageModel.getPath(
"settings" /* SettingsModel */,
[]
)
} }
); );
const chatModels = this.chatListModel.chatModels; const chatModels = this.chatListModel.chatModels;
@@ -4973,7 +5168,10 @@
// src/View/Modals/dataTransferModal.tsx // src/View/Modals/dataTransferModal.tsx
function DataTransferModalWrapper(connectionViewModel2, fileTransferViewModel2) { function DataTransferModalWrapper(connectionViewModel2, fileTransferViewModel2) {
return /* @__PURE__ */ createElement("div", null, DirectionSelectionModal(connectionViewModel2, fileTransferViewModel2), FileSelectionModal(fileTransferViewModel2), TransferDataDisplayModal(fileTransferViewModel2), TransferDisplayModal(fileTransferViewModel2), TransferDataInputModal(fileTransferViewModel2), DataReceptionModal(fileTransferViewModel2)); return /* @__PURE__ */ createElement("div", null, DirectionSelectionModal(
connectionViewModel2,
fileTransferViewModel2
), FileSelectionModal(fileTransferViewModel2), TransferDataDisplayModal(fileTransferViewModel2), TransferDisplayModal(fileTransferViewModel2), TransferDataInputModal(fileTransferViewModel2), DataReceptionModal(fileTransferViewModel2));
} }
function DirectionSelectionModal(connectionViewModel2, fileTransferViewModel2) { function DirectionSelectionModal(connectionViewModel2, fileTransferViewModel2) {
const isPresented = createProxyState( const isPresented = createProxyState(
@@ -4984,7 +5182,14 @@
[connectionViewModel2.isConnected], [connectionViewModel2.isConnected],
() => connectionViewModel2.isConnected.value == false () => connectionViewModel2.isConnected.value == false
); );
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement("p", { class: "error", "toggle:hidden": connectionViewModel2.isConnected }, translations.dataTransferModal.notConnectedError), /* @__PURE__ */ createElement( return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement(
"p",
{
class: "error",
"toggle:hidden": connectionViewModel2.isConnected
},
translations.dataTransferModal.notConnectedError
), /* @__PURE__ */ createElement(
"div", "div",
{ {
class: "flex-column gap content-margin-bottom", class: "flex-column gap content-margin-bottom",
@@ -5031,7 +5236,9 @@
function toggle() { function toggle() {
isSelected.value = !isSelected.value; 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)))); return /* @__PURE__ */ createElement("button", { class: "tile", "toggle:selected": isSelected, "on:click": toggle }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", { class: "ellipsis" }, fileOption.label), /* @__PURE__ */ createElement("span", { class: "secondary ellipsis" }, StorageModel.pathComponentsToString(
...fileOption.path
))));
} }
const isPresented = createProxyState( const isPresented = createProxyState(
[fileTransferViewModel2.presentedModal], [fileTransferViewModel2.presentedModal],
@@ -5083,7 +5290,12 @@
{ {
"subscribe:innerText": fileTransferViewModel2.transferChannel "subscribe:innerText": fileTransferViewModel2.transferChannel
} }
))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.dataTransferModal.transferKeyHeadline), /* @__PURE__ */ createElement("b", { "subscribe:innerText": fileTransferViewModel2.transferKey }))))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement( ))), /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "key"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.dataTransferModal.transferKeyHeadline), /* @__PURE__ */ createElement(
"b",
{
"subscribe:innerText": fileTransferViewModel2.transferKey
}
))))), /* @__PURE__ */ createElement("div", { class: "flex-row width-100" }, /* @__PURE__ */ createElement(
"button", "button",
{ {
class: "flex", class: "flex",
@@ -5279,7 +5491,9 @@
}; };
this.storageModel = storageModel2; this.storageModel = storageModel2;
this.connectionModel = connectionModel2; this.connectionModel = connectionModel2;
this.connectionModel.messageHandlerManager.addHandler(this.handleMessage); this.connectionModel.messageHandlerManager.addHandler(
this.handleMessage
);
} }
}; };
var FileDataReference = { var FileDataReference = {
@@ -5399,7 +5613,15 @@
}, },
/* @__PURE__ */ createElement("span", { class: "icon" }, "sync_alt"), /* @__PURE__ */ createElement("span", { class: "icon" }, "sync_alt"),
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.homePage.transferDataButton)) /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.homePage.transferDataButton))
), /* @__PURE__ */ createElement("button", { class: "tile flex-no", "on:click": storageViewModel2.showStorageModal }, /* @__PURE__ */ createElement("span", { class: "icon" }, "hard_drive_2"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.homePage.manageStorageButton))), /* @__PURE__ */ createElement("div", { class: "mobile-only" }, /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, /* @__PURE__ */ createElement("button", { class: "ghost width-50", "on:click": scrollToChat }, translations.homePage.scrollToChatButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward"))))); ), /* @__PURE__ */ createElement(
"button",
{
class: "tile flex-no",
"on:click": storageViewModel2.showStorageModal
},
/* @__PURE__ */ createElement("span", { class: "icon" }, "hard_drive_2"),
/* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.homePage.manageStorageButton))
), /* @__PURE__ */ createElement("div", { class: "mobile-only" }, /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, /* @__PURE__ */ createElement("button", { class: "ghost width-50", "on:click": scrollToChat }, translations.homePage.scrollToChatButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")))));
const chatSection = /* @__PURE__ */ createElement("div", { id: "chat-section" }, /* @__PURE__ */ createElement("h2", null, translations.homePage.chatsHeadline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement( const chatSection = /* @__PURE__ */ createElement("div", { id: "chat-section" }, /* @__PURE__ */ createElement("h2", null, translations.homePage.chatsHeadline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
"input", "input",
{ {
@@ -5611,17 +5833,23 @@
this.coreViewModel = coreViewModel; this.coreViewModel = coreViewModel;
// state // state
this.isShowingStorageModal = new State(false); this.isShowingStorageModal = new State(false);
this.selectedPath = new State(PATH_COMPONENT_SEPARATOR); this.selectedPath = new State(
PATH_COMPONENT_SEPARATOR
);
this.didMakeChanges = new State(false); this.didMakeChanges = new State(false);
this.lastDeletedItemPath = new State(""); this.lastDeletedItemPath = new State("");
// methods // methods
this.getSelectedItemContent = () => { this.getSelectedItemContent = () => {
const path = StorageModel.stringToPathComponents(this.selectedPath.value); const path = StorageModel.stringToPathComponents(
this.selectedPath.value
);
const content = this.storageModel.read(path); const content = this.storageModel.read(path);
return (content ?? translations.storage.notAFile) || translations.storage.contentEmpty; return (content ?? translations.storage.notAFile) || translations.storage.contentEmpty;
}; };
this.deleteSelectedItem = () => { this.deleteSelectedItem = () => {
const path = StorageModel.stringToPathComponents(this.selectedPath.value); const path = StorageModel.stringToPathComponents(
this.selectedPath.value
);
this.lastDeletedItemPath.value = this.selectedPath.value; this.lastDeletedItemPath.value = this.selectedPath.value;
this.storageModel.removeRecursively(path); this.storageModel.removeRecursively(path);
this.didMakeChanges.value = true; this.didMakeChanges.value = true;
@@ -5699,9 +5927,7 @@
if (primaryChannel == null) return; if (primaryChannel == null) return;
const parsedPriamryChannel = parseOrFallback(primaryChannel); const parsedPriamryChannel = parseOrFallback(primaryChannel);
const chatModel = this.chatListModel.createChat(parsedPriamryChannel); const chatModel = this.chatListModel.createChat(parsedPriamryChannel);
const secondaryChannelString = getLocalStorageItemAndClear( const secondaryChannelString = getLocalStorageItemAndClear(storageKeys.secondaryChannels(id));
storageKeys.secondaryChannels(id)
);
if (secondaryChannelString != null) { if (secondaryChannelString != null) {
const potentialSecondaryChannels = parseArray( const potentialSecondaryChannels = parseArray(
secondaryChannelString secondaryChannelString
@@ -5744,7 +5970,9 @@
}; };
chatModel.addMessage(convertedChatMessage); chatModel.addMessage(convertedChatMessage);
if (status == "outbox" /* Outbox */) { if (status == "outbox" /* Outbox */) {
this.connectionModel.sendMessageOrStore(convertedChatMessage); this.connectionModel.sendMessageOrStore(
convertedChatMessage
);
} }
} }
}; };
@@ -5789,7 +6017,9 @@
time: potentialVersion.time ?? "" time: potentialVersion.time ?? ""
}; };
console.log(convertedTaskFileContent); console.log(convertedTaskFileContent);
chatModel.fileModel.handleFileContent(convertedTaskFileContent); chatModel.fileModel.handleFileContent(
convertedTaskFileContent
);
} }
} }
}; };
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Comms", "name": "Comms",
"display": "standalone", "display": "fullscreen",
"icons": [ "icons": [
{ {
"src": "icon.png", "src": "icon.png",
+15 -9
View File
@@ -1,5 +1,8 @@
import ChatModel, { ChatMessage } from "./chatModel"; import ChatModel, { ChatMessage } from "./chatModel";
import StorageModel, { StorageModelSubPath, filePaths } from "../Global/storageModel"; import StorageModel, {
StorageModelSubPath,
filePaths,
} from "../Global/storageModel";
import ConnectionModel from "../Global/connectionModel"; import ConnectionModel from "../Global/connectionModel";
import { Message } from "udn-frontend"; import { Message } from "udn-frontend";
@@ -22,7 +25,7 @@ export default class ChatListModel {
if (body == undefined) return; if (body == undefined) return;
this.routeMessageToCorrectChatModel(channel, (chatModel: ChatModel) => this.routeMessageToCorrectChatModel(channel, (chatModel: ChatModel) =>
chatModel.handleMessage(body) chatModel.handleMessage(body),
); );
}; };
@@ -30,14 +33,14 @@ export default class ChatListModel {
const channel: string = chatMessage.channel; const channel: string = chatMessage.channel;
this.routeMessageToCorrectChatModel(channel, (chatModel: ChatModel) => this.routeMessageToCorrectChatModel(channel, (chatModel: ChatModel) =>
chatModel.handleMessageSent(chatMessage) chatModel.handleMessageSent(chatMessage),
); );
}; };
// methods // methods
routeMessageToCorrectChatModel = ( routeMessageToCorrectChatModel = (
channel: string, channel: string,
fn: (chatModel: ChatModel) => void fn: (chatModel: ChatModel) => void,
): void => { ): void => {
const allChannels = channel.split("/"); const allChannels = channel.split("/");
@@ -63,7 +66,7 @@ export default class ChatListModel {
this.connectionModel, this.connectionModel,
this.settingsModel, this.settingsModel,
this, this,
id id,
); );
chatModel.setPrimaryChannel(primaryChannel); chatModel.setPrimaryChannel(primaryChannel);
@@ -77,7 +80,10 @@ export default class ChatListModel {
// load // load
loadChats = (): void => { loadChats = (): void => {
const chatDir = StorageModel.getPath(StorageModelSubPath.Chat, filePaths.chat.base); const chatDir = StorageModel.getPath(
StorageModelSubPath.Chat,
filePaths.chat.base,
);
const chatIds = this.storageModel.list(chatDir); const chatIds = this.storageModel.list(chatDir);
for (const chatId of chatIds) { for (const chatId of chatIds) {
const chatModel = new ChatModel( const chatModel = new ChatModel(
@@ -85,7 +91,7 @@ export default class ChatListModel {
this.connectionModel, this.connectionModel,
this.settingsModel, this.settingsModel,
this, this,
chatId chatId,
); );
this.addChatModel(chatModel); this.addChatModel(chatModel);
} }
@@ -95,7 +101,7 @@ export default class ChatListModel {
constructor( constructor(
storageModel: StorageModel, storageModel: StorageModel,
settingsModel: SettingsModel, settingsModel: SettingsModel,
connectionModel: ConnectionModel connectionModel: ConnectionModel,
) { ) {
this.storageModel = storageModel; this.storageModel = storageModel;
this.settingsModel = settingsModel; this.settingsModel = settingsModel;
@@ -104,7 +110,7 @@ export default class ChatListModel {
connectionModel.messageHandlerManager.addHandler(this.messageHandler); connectionModel.messageHandlerManager.addHandler(this.messageHandler);
connectionModel.messageSentHandlerManager.addHandler( connectionModel.messageSentHandlerManager.addHandler(
this.messageSentHandler this.messageSentHandler,
); );
} }
} }
+34 -21
View File
@@ -34,7 +34,8 @@ export default class ChatModel {
info: ChatInfo; info: ChatInfo;
color: Color; color: Color;
chatMessageHandlerManager: HandlerManager<ChatMessage> = new HandlerManager(); chatMessageHandlerManager: HandlerManager<ChatMessage> =
new HandlerManager();
get secondaryChannels(): string[] { get secondaryChannels(): string[] {
return this.info.secondaryChannels.sort(localeCompare); return this.info.secondaryChannels.sort(localeCompare);
@@ -44,28 +45,28 @@ export default class ChatModel {
getBasePath = (): string[] => { getBasePath = (): string[] => {
return StorageModel.getPath( return StorageModel.getPath(
StorageModelSubPath.Chat, StorageModelSubPath.Chat,
filePaths.chat.chatBase(this.id) filePaths.chat.chatBase(this.id),
); );
}; };
getInfoPath = (): string[] => { getInfoPath = (): string[] => {
return StorageModel.getPath( return StorageModel.getPath(
StorageModelSubPath.Chat, StorageModelSubPath.Chat,
filePaths.chat.info(this.id) filePaths.chat.info(this.id),
); );
}; };
getColorPath = (): string[] => { getColorPath = (): string[] => {
return StorageModel.getPath( return StorageModel.getPath(
StorageModelSubPath.Chat, StorageModelSubPath.Chat,
filePaths.chat.color(this.id) filePaths.chat.color(this.id),
); );
}; };
getMessageDirPath = (): string[] => { getMessageDirPath = (): string[] => {
return StorageModel.getPath( return StorageModel.getPath(
StorageModelSubPath.Chat, StorageModelSubPath.Chat,
filePaths.chat.messages(this.id) filePaths.chat.messages(this.id),
); );
}; };
@@ -77,7 +78,7 @@ export default class ChatModel {
handleMessage = (body: string): void => { handleMessage = (body: string): void => {
const chatMessage: ChatMessage | null = parseValidObject( const chatMessage: ChatMessage | null = parseValidObject(
body, body,
ChatMessageReference ChatMessageReference,
); );
if (chatMessage == null) return; if (chatMessage == null) return;
@@ -127,12 +128,14 @@ export default class ChatModel {
} }
// file // file
this.fileModel.handleStringifiedFileContent(chatMessage.stringifiedFile); this.fileModel.handleStringifiedFileContent(
chatMessage.stringifiedFile,
);
}; };
sendMessage = async ( sendMessage = async (
body: string, body: string,
fileContent?: FileContent<string> fileContent?: FileContent<string>,
): Promise<boolean> => { ): Promise<boolean> => {
const senderName = this.settingsModel.username; const senderName = this.settingsModel.username;
if (senderName == "") return false; if (senderName == "") return false;
@@ -149,7 +152,7 @@ export default class ChatModel {
senderName, senderName,
this.info.encryptionKey, this.info.encryptionKey,
body, body,
fileContent fileContent,
); );
this.addMessage(chatMessage); this.addMessage(chatMessage);
@@ -160,11 +163,11 @@ export default class ChatModel {
decryptMessage = async (chatMessage: ChatMessage): Promise<void> => { decryptMessage = async (chatMessage: ChatMessage): Promise<void> => {
const decryptedBody: string = await decryptString( const decryptedBody: string = await decryptString(
chatMessage.body, chatMessage.body,
this.info.encryptionKey this.info.encryptionKey,
); );
const decryptedFile: string = await decryptString( const decryptedFile: string = await decryptString(
chatMessage.stringifiedFile ?? "", chatMessage.stringifiedFile ?? "",
this.info.encryptionKey this.info.encryptionKey,
); );
chatMessage.body = decryptedBody; chatMessage.body = decryptedBody;
chatMessage.stringifiedFile = decryptedFile; chatMessage.stringifiedFile = decryptedFile;
@@ -177,7 +180,7 @@ export default class ChatModel {
markRead = (): void => { markRead = (): void => {
this.info.hasUnreadMessages = false; this.info.hasUnreadMessages = false;
this.storeInfo(); this.storeInfo();
} };
// storage // storage
storeInfo = (): void => { storeInfo = (): void => {
@@ -201,7 +204,7 @@ export default class ChatModel {
loadInfo = (): void => { loadInfo = (): void => {
const info: ChatInfo | null = this.storageModel.readStringifiable( const info: ChatInfo | null = this.storageModel.readStringifiable(
this.getInfoPath(), this.getInfoPath(),
ChatInfoReference ChatInfoReference,
); );
if (info != null) { if (info != null) {
this.info = info; this.info = info;
@@ -222,7 +225,7 @@ export default class ChatModel {
get messages(): ChatMessage[] { get messages(): ChatMessage[] {
const messageIds: string[] = this.storageModel.list( const messageIds: string[] = this.storageModel.list(
this.getMessageDirPath() this.getMessageDirPath(),
); );
if (!Array.isArray(messageIds)) return []; if (!Array.isArray(messageIds)) return [];
@@ -230,12 +233,15 @@ export default class ChatModel {
for (const messageId of messageIds) { for (const messageId of messageIds) {
const messagePath: string[] = this.getMessagePath(messageId); const messagePath: string[] = this.getMessagePath(messageId);
const chatMessage: ChatMessage | any = const chatMessage: ChatMessage | any =
this.storageModel.readStringifiable(messagePath, ChatMessageReference); this.storageModel.readStringifiable(
messagePath,
ChatMessageReference,
);
chatMessages.push(chatMessage); chatMessages.push(chatMessage);
} }
const sorted = chatMessages.sort((a, b) => const sorted = chatMessages.sort((a, b) =>
a.dateSent.localeCompare(b.dateSent) a.dateSent.localeCompare(b.dateSent),
); );
return sorted; return sorted;
} }
@@ -246,7 +252,7 @@ export default class ChatModel {
connectionModel: ConnectionModel, connectionModel: ConnectionModel,
settingsModel: SettingsModel, settingsModel: SettingsModel,
chatListModel: ChatListModel, chatListModel: ChatListModel,
chatId: string chatId: string,
) { ) {
this.id = chatId; this.id = chatId;
this.connectionModel = connectionModel; this.connectionModel = connectionModel;
@@ -258,7 +264,11 @@ export default class ChatModel {
this.loadColor(); this.loadColor();
this.subscribe(); this.subscribe();
this.fileModel = new FileModel(this.storageModel, this.settingsModel, this); this.fileModel = new FileModel(
this.storageModel,
this.settingsModel,
this,
);
} }
// utility // utility
@@ -278,7 +288,7 @@ export default class ChatModel {
sender: string, sender: string,
encryptionKey: string, encryptionKey: string,
body: string, body: string,
fileContent?: FileContent<string> fileContent?: FileContent<string>,
): Promise<ChatMessage> => { ): Promise<ChatMessage> => {
const chatMessage: ChatMessage = { const chatMessage: ChatMessage = {
dataVersion: DATA_VERSION, dataVersion: DATA_VERSION,
@@ -299,10 +309,13 @@ export default class ChatModel {
} }
if (encryptionKey != "") { if (encryptionKey != "") {
chatMessage.body = await encryptString(chatMessage.body, encryptionKey); chatMessage.body = await encryptString(
chatMessage.body,
encryptionKey,
);
chatMessage.stringifiedFile = await encryptString( chatMessage.stringifiedFile = await encryptString(
chatMessage.stringifiedFile, chatMessage.stringifiedFile,
encryptionKey encryptionKey,
); );
} }
+31 -23
View File
@@ -66,12 +66,17 @@ export default class BoardsAndTasksModel {
// handlers // handlers
handleFileContent = (fileContent: FileContent<string>): void => { handleFileContent = (fileContent: FileContent<string>): void => {
if ( if (
checkMatchesObjectStructure(fileContent, BoardInfoFileContentReference) == checkMatchesObjectStructure(
true fileContent,
BoardInfoFileContentReference,
) == true
) { ) {
this.handleBoard(fileContent as BoardInfoFileContent); this.handleBoard(fileContent as BoardInfoFileContent);
} else if ( } else if (
checkMatchesObjectStructure(fileContent, TaskFileContentReference) == true checkMatchesObjectStructure(
fileContent,
TaskFileContentReference,
) == true
) { ) {
this.handleTask(fileContent as TaskFileContent); this.handleTask(fileContent as TaskFileContent);
} }
@@ -91,7 +96,7 @@ export default class BoardsAndTasksModel {
BoardsAndTasksModel.createBoardInfoFileContent( BoardsAndTasksModel.createBoardInfoFileContent(
v4(), v4(),
name, name,
Color.Standard Color.Standard,
); );
return boardInfoFileContent; return boardInfoFileContent;
}; };
@@ -112,14 +117,15 @@ export default class BoardsAndTasksModel {
// add to list // add to list
const boardDirectoryPath: string[] = this.getBoardDirectoryPath( const boardDirectoryPath: string[] = this.getBoardDirectoryPath(
boardInfoFileContent.fileId boardInfoFileContent.fileId,
); );
this.storageModel.write(boardDirectoryPath, ""); this.storageModel.write(boardDirectoryPath, "");
}; };
deleteBoard = (boardId: string): void => { deleteBoard = (boardId: string): void => {
const boardFilePath: string[] = this.getBoardFilePath(boardId); const boardFilePath: string[] = this.getBoardFilePath(boardId);
const boardDirectoryPath: string[] = this.getBoardDirectoryPath(boardId); const boardDirectoryPath: string[] =
this.getBoardDirectoryPath(boardId);
this.storageModel.removeRecursively(boardFilePath); this.storageModel.removeRecursively(boardFilePath);
this.storageModel.removeRecursively(boardDirectoryPath); this.storageModel.removeRecursively(boardDirectoryPath);
@@ -135,16 +141,17 @@ export default class BoardsAndTasksModel {
const boardInfoFileContentOrNull: BoardInfoFileContent | null = const boardInfoFileContentOrNull: BoardInfoFileContent | null =
this.fileModel.getLatestFileContent( this.fileModel.getLatestFileContent(
fileId, fileId,
BoardInfoFileContentReference BoardInfoFileContentReference,
); );
return boardInfoFileContentOrNull; return boardInfoFileContentOrNull;
}; };
getBoardName = (boardId: string): string => { getBoardName = (boardId: string): string => {
const boardInfo: BoardInfoFileContent|null = this.getBoardInfo(boardId); const boardInfo: BoardInfoFileContent | null =
this.getBoardInfo(boardId);
if (boardInfo == null) return ""; if (boardInfo == null) return "";
return boardInfo.name; return boardInfo.name;
} };
//tasks //tasks
createTask = (boardId: string): TaskFileContent => { createTask = (boardId: string): TaskFileContent => {
@@ -170,7 +177,7 @@ export default class BoardsAndTasksModel {
// add to board // add to board
const taskReferencePath: string[] = this.getTaskReferencePath( const taskReferencePath: string[] = this.getTaskReferencePath(
taskFileContent.boardId, taskFileContent.boardId,
taskFileContent.fileId taskFileContent.fileId,
); );
this.storageModel.write(taskReferencePath, ""); this.storageModel.write(taskReferencePath, "");
@@ -191,19 +198,22 @@ export default class BoardsAndTasksModel {
getLatestTaskFileContent = (taskId: string): TaskFileContent | null => { getLatestTaskFileContent = (taskId: string): TaskFileContent | null => {
const taskFileContentOrNull: TaskFileContent | null = const taskFileContentOrNull: TaskFileContent | null =
this.fileModel.getLatestFileContent(taskId, TaskFileContentReference); this.fileModel.getLatestFileContent(
taskId,
TaskFileContentReference,
);
return taskFileContentOrNull; return taskFileContentOrNull;
}; };
getSpecificTaskFileContent = ( getSpecificTaskFileContent = (
taskId: string, taskId: string,
versionId: string versionId: string,
): TaskFileContent | null => { ): TaskFileContent | null => {
const taskFileContentOrNull: TaskFileContent | null = const taskFileContentOrNull: TaskFileContent | null =
this.fileModel.getFileContent( this.fileModel.getFileContent(
taskId, taskId,
versionId, versionId,
TaskFileContentReference TaskFileContentReference,
); );
return taskFileContentOrNull; return taskFileContentOrNull;
}; };
@@ -218,7 +228,7 @@ export default class BoardsAndTasksModel {
deleteTaskReference = (boardId: string, taskId: string): void => { deleteTaskReference = (boardId: string, taskId: string): void => {
const taskReferencePath: string[] = this.getTaskReferencePath( const taskReferencePath: string[] = this.getTaskReferencePath(
boardId, boardId,
taskId taskId,
); );
this.storageModel.removeRecursively(taskReferencePath); this.storageModel.removeRecursively(taskReferencePath);
}; };
@@ -228,7 +238,7 @@ export default class BoardsAndTasksModel {
storageModel: StorageModel, storageModel: StorageModel,
settingsModel: SettingsModel, settingsModel: SettingsModel,
chatModel: ChatModel, chatModel: ChatModel,
fileModel: FileModel fileModel: FileModel,
) { ) {
this.storageModel = storageModel; this.storageModel = storageModel;
this.settingsModel = settingsModel; this.settingsModel = settingsModel;
@@ -238,7 +248,7 @@ export default class BoardsAndTasksModel {
this.calendarModel = new CalendarModel( this.calendarModel = new CalendarModel(
this.storageModel, this.storageModel,
this.settingsModel, this.settingsModel,
this.fileModel this.fileModel,
); );
} }
@@ -246,12 +256,10 @@ export default class BoardsAndTasksModel {
static createBoardInfoFileContent = ( static createBoardInfoFileContent = (
fileId: string, fileId: string,
name: string, name: string,
color: Color color: Color,
): BoardInfoFileContent => { ): BoardInfoFileContent => {
const fileContent: FileContent<"board-info"> = FileModel.createFileContent( const fileContent: FileContent<"board-info"> =
fileId, FileModel.createFileContent(fileId, "board-info");
"board-info"
);
return { return {
...fileContent, ...fileContent,
@@ -263,11 +271,11 @@ export default class BoardsAndTasksModel {
static createTaskFileContent = ( static createTaskFileContent = (
fileId: string, fileId: string,
name: string, name: string,
boardId: string boardId: string,
): TaskFileContent => { ): TaskFileContent => {
const fileContent: FileContent<"task"> = FileModel.createFileContent( const fileContent: FileContent<"task"> = FileModel.createFileContent(
fileId, fileId,
"task" "task",
); );
return { return {
...fileContent, ...fileContent,
+9 -7
View File
@@ -13,7 +13,9 @@ export default class CalendarModel {
// paths // paths
getBasePath = (): string[] => { getBasePath = (): string[] => {
return this.fileModel.getModelContainerPath(FileModelSubPath.ModelCalendar); return this.fileModel.getModelContainerPath(
FileModelSubPath.ModelCalendar,
);
}; };
getViewPath = (): string[] => { getViewPath = (): string[] => {
@@ -33,7 +35,7 @@ export default class CalendarModel {
if (taskFileContent.date == undefined) return; if (taskFileContent.date == undefined) return;
const monthString: string = CalendarModel.isoToMonthString( const monthString: string = CalendarModel.isoToMonthString(
taskFileContent.date taskFileContent.date,
); );
const monthPath: string[] = this.getMonthPath(monthString); const monthPath: string[] = this.getMonthPath(monthString);
@@ -57,7 +59,7 @@ export default class CalendarModel {
generateMonthGrid = <T>( generateMonthGrid = <T>(
year: number, year: number,
month: number, month: number,
defaultValueCreator: () => T defaultValueCreator: () => T,
): MonthGrid<T> => { ): MonthGrid<T> => {
// get today // get today
const date: Date = new Date(); const date: Date = new Date();
@@ -71,7 +73,7 @@ export default class CalendarModel {
const firstWeekdayOfMonth = date.getDay(); const firstWeekdayOfMonth = date.getDay();
const firstDayOfWeekSetting: number = parseInt( const firstDayOfWeekSetting: number = parseInt(
this.settingsModel.firstDayOfWeek this.settingsModel.firstDayOfWeek,
); );
const offset: number = const offset: number =
@@ -95,7 +97,7 @@ export default class CalendarModel {
for (let i = 0; i < daysInMonth; i++) { for (let i = 0; i < daysInMonth; i++) {
const paddedDate: string = CalendarModel.padDateOrMonth( const paddedDate: string = CalendarModel.padDateOrMonth(
(i + 1).toString() (i + 1).toString(),
); );
grid.days[paddedDate] = defaultValueCreator(); grid.days[paddedDate] = defaultValueCreator();
} }
@@ -107,7 +109,7 @@ export default class CalendarModel {
constructor( constructor(
storageModel: StorageModel, storageModel: StorageModel,
settingsModel: SettingsModel, settingsModel: SettingsModel,
fileModel: FileModel fileModel: FileModel,
) { ) {
this.storageModel = storageModel; this.storageModel = storageModel;
this.settingsModel = settingsModel; this.settingsModel = settingsModel;
@@ -135,7 +137,7 @@ export default class CalendarModel {
static getISODateString = ( static getISODateString = (
year: string, year: string,
month: string, month: string,
date: string date: string,
): string => { ): string => {
const monthString: string = CalendarModel.getMonthString(year, month); const monthString: string = CalendarModel.getMonthString(year, month);
const paddedDate: string = CalendarModel.padDateOrMonth(date); const paddedDate: string = CalendarModel.padDateOrMonth(date);
+18 -15
View File
@@ -28,7 +28,7 @@ export default class FileModel {
getBasePath = (): string[] => { getBasePath = (): string[] => {
return StorageModel.getPath( return StorageModel.getPath(
StorageModelSubPath.Chat, StorageModelSubPath.Chat,
filePaths.chat.files(this.chatModel.id) filePaths.chat.files(this.chatModel.id),
); );
}; };
@@ -53,7 +53,7 @@ export default class FileModel {
handleStringifiedFileContent = (stringifiedFileContent: string): void => { handleStringifiedFileContent = (stringifiedFileContent: string): void => {
const fileContent: FileContent<string> | null = parseValidObject( const fileContent: FileContent<string> | null = parseValidObject(
stringifiedFileContent, stringifiedFileContent,
FileContentReference FileContentReference,
); );
if (fileContent == null) return; if (fileContent == null) return;
@@ -77,7 +77,7 @@ export default class FileModel {
storeFileContent = (fileContent: FileContent<string>): boolean => { storeFileContent = (fileContent: FileContent<string>): boolean => {
const fileContentPath: string[] = this.getFileContentPath( const fileContentPath: string[] = this.getFileContentPath(
fileContent.fileId, fileContent.fileId,
fileContent.fileContentId fileContent.fileContentId,
); );
// check if fileContent already exists // check if fileContent already exists
@@ -100,7 +100,7 @@ export default class FileModel {
}; };
selectLatestFileContentId = ( selectLatestFileContentId = (
fileContentIds: string[] fileContentIds: string[],
): string | undefined => { ): string | undefined => {
return fileContentIds[fileContentIds.length - 1]; return fileContentIds[fileContentIds.length - 1];
}; };
@@ -108,19 +108,22 @@ export default class FileModel {
getFileContent = <T extends FileContent<string>>( getFileContent = <T extends FileContent<string>>(
fileId: string, fileId: string,
fileContentName: string, fileContentName: string,
reference: T reference: T,
): T | null => { ): T | null => {
const filePath: string[] = this.getFileContentPath(fileId, fileContentName); const filePath: string[] = this.getFileContentPath(
fileId,
fileContentName,
);
const fileContentOrNull: T | null = this.storageModel.readStringifiable( const fileContentOrNull: T | null = this.storageModel.readStringifiable(
filePath, filePath,
reference reference,
); );
return fileContentOrNull; return fileContentOrNull;
}; };
getLatestFileContent = <T extends FileContent<string>>( getLatestFileContent = <T extends FileContent<string>>(
fileId: string, fileId: string,
reference: T reference: T,
): T | null => { ): T | null => {
const fileContentsIds: string[] = this.listFileContentIds(fileId); const fileContentsIds: string[] = this.listFileContentIds(fileId);
const latestFileContentId: string | undefined = const latestFileContentId: string | undefined =
@@ -130,7 +133,7 @@ export default class FileModel {
const fileContent: T | null = this.getFileContent( const fileContent: T | null = this.getFileContent(
fileId, fileId,
latestFileContentId, latestFileContentId,
reference reference,
); );
return fileContent; return fileContent;
}; };
@@ -139,7 +142,7 @@ export default class FileModel {
constructor( constructor(
storageModel: StorageModel, storageModel: StorageModel,
settingsModel: SettingsModel, settingsModel: SettingsModel,
chatModel: ChatModel chatModel: ChatModel,
) { ) {
this.chatModel = chatModel; this.chatModel = chatModel;
this.settingsModel = settingsModel; this.settingsModel = settingsModel;
@@ -149,7 +152,7 @@ export default class FileModel {
this.storageModel, this.storageModel,
this.settingsModel, this.settingsModel,
chatModel, chatModel,
this this,
); );
} }
@@ -160,10 +163,11 @@ export default class FileModel {
static createFileContent = <T extends string>( static createFileContent = <T extends string>(
fileId: string, fileId: string,
type: T type: T,
): FileContent<T> => { ): FileContent<T> => {
const creationDate: string = createTimestamp(); const creationDate: string = createTimestamp();
const fileContentId: string = FileModel.generateFileContentId(creationDate); const fileContentId: string =
FileModel.generateFileContentId(creationDate);
return { return {
dataVersion: DATA_VERSION, dataVersion: DATA_VERSION,
@@ -187,8 +191,7 @@ export enum FileModelSubPath {
// types // types
export interface FileContent<T extends string> export interface FileContent<T extends string>
extends ValidObject, extends ValidObject, StringEntryObject {
StringEntryObject {
fileId: string; fileId: string;
fileContentId: string; fileContentId: string;
creationDate: string; creationDate: string;
+40 -17
View File
@@ -8,6 +8,8 @@ import UDNFrontend, { Message } from "udn-frontend";
export default class ConnectionModel { export default class ConnectionModel {
udn: UDNFrontend; udn: UDNFrontend;
storageModel: StorageModel; storageModel: StorageModel;
reconnectInterval: number | undefined = undefined;
shouldAttemptReconnect: boolean = false;
// data // data
get isConnected(): boolean { get isConnected(): boolean {
@@ -20,7 +22,8 @@ export default class ConnectionModel {
connectionChangeHandlerManager: HandlerManager<void> = new HandlerManager(); connectionChangeHandlerManager: HandlerManager<void> = new HandlerManager();
messageHandlerManager: HandlerManager<Message> = new HandlerManager(); messageHandlerManager: HandlerManager<Message> = new HandlerManager();
messageSentHandlerManager: HandlerManager<ChatMessage> = new HandlerManager(); messageSentHandlerManager: HandlerManager<ChatMessage> =
new HandlerManager();
channelsToSubscribe: Set<string> = new Set(); channelsToSubscribe: Set<string> = new Set();
@@ -33,12 +36,25 @@ export default class ConnectionModel {
console.log("connection status:", this.isConnected, this.address); console.log("connection status:", this.isConnected, this.address);
this.connectionChangeHandlerManager.trigger(); this.connectionChangeHandlerManager.trigger();
if (this.isConnected == false) return; if (this.isConnected == false) {
if (this.shouldAttemptReconnect == false) return;
this.reconnectInterval = setInterval(() => {
if (this.isConnected == true) {
clearInterval(this.reconnectInterval);
return;
}
this.reconnect();
}, 5000);
return;
}
if (this.address == undefined) return; if (this.address == undefined) return;
this.storeAddress(this.address); this.storeAddress(this.address);
this.sendSubscriptionRequest(); this.sendSubscriptionRequest();
this.sendMessagesInOutbox(); this.sendMessagesInOutbox();
this.shouldAttemptReconnect = true;
}; };
// connection // connection
@@ -52,16 +68,27 @@ export default class ConnectionModel {
// do not reconnect // do not reconnect
const reconnectAddressPath: string[] = StorageModel.getPath( const reconnectAddressPath: string[] = StorageModel.getPath(
StorageModelSubPath.ConnectionModel, StorageModelSubPath.ConnectionModel,
filePaths.connectionModel.reconnectAddress filePaths.connectionModel.reconnectAddress,
); );
this.storageModel.remove(reconnectAddressPath); this.storageModel.remove(reconnectAddressPath);
this.shouldAttemptReconnect = false;
};
reconnect = (): void => {
const reconnectAddressPath: string[] = this.getReconnectAddressPath();
const reconnectAddress: string | null =
this.storageModel.read(reconnectAddressPath);
if (reconnectAddress == null) return;
console.log("reconnecting...");
this.connect(reconnectAddress);
}; };
// mailbox // mailbox
getMailboxPath = (address: string): string[] => { getMailboxPath = (address: string): string[] => {
const mailboxDirPath = StorageModel.getPath( const mailboxDirPath = StorageModel.getPath(
StorageModelSubPath.ConnectionModel, StorageModelSubPath.ConnectionModel,
filePaths.connectionModel.mailboxes filePaths.connectionModel.mailboxes,
); );
const mailboxFilePath = [...mailboxDirPath, address]; const mailboxFilePath = [...mailboxDirPath, address];
return mailboxFilePath; return mailboxFilePath;
@@ -75,7 +102,9 @@ export default class ConnectionModel {
connectMailbox = (): void => { connectMailbox = (): void => {
if (this.address == undefined) return; if (this.address == undefined) return;
const mailboxId = this.storageModel.read(this.getMailboxPath(this.address)); const mailboxId = this.storageModel.read(
this.getMailboxPath(this.address),
);
console.log("connecting mailbox", mailboxId); console.log("connecting mailbox", mailboxId);
if (mailboxId == null) return this.requestNewMailbox(); if (mailboxId == null) return this.requestNewMailbox();
@@ -107,7 +136,7 @@ export default class ConnectionModel {
getOutboxPath = (): string[] => { getOutboxPath = (): string[] => {
return StorageModel.getPath( return StorageModel.getPath(
StorageModelSubPath.ConnectionModel, StorageModelSubPath.ConnectionModel,
filePaths.connectionModel.outbox filePaths.connectionModel.outbox,
); );
}; };
@@ -120,7 +149,7 @@ export default class ConnectionModel {
const chatMessage: ChatMessage | null = const chatMessage: ChatMessage | null =
this.storageModel.readStringifiable( this.storageModel.readStringifiable(
[...outboxPath, messageId], [...outboxPath, messageId],
ChatMessageReference ChatMessageReference,
); );
if (chatMessage == null) continue; if (chatMessage == null) continue;
@@ -163,7 +192,7 @@ export default class ConnectionModel {
const stringifiedBody: string = stringify(chatMessage); const stringifiedBody: string = stringify(chatMessage);
const isSent: boolean = this.sendPlainMessage( const isSent: boolean = this.sendPlainMessage(
chatMessage.channel, chatMessage.channel,
stringifiedBody stringifiedBody,
); );
if (isSent) this.messageSentHandlerManager.trigger(chatMessage); if (isSent) this.messageSentHandlerManager.trigger(chatMessage);
return isSent; return isSent;
@@ -177,7 +206,7 @@ export default class ConnectionModel {
getPreviousAddressPath = (): string[] => { getPreviousAddressPath = (): string[] => {
return StorageModel.getPath( return StorageModel.getPath(
StorageModelSubPath.ConnectionModel, StorageModelSubPath.ConnectionModel,
filePaths.connectionModel.previousAddresses filePaths.connectionModel.previousAddresses,
); );
}; };
@@ -189,7 +218,7 @@ export default class ConnectionModel {
getReconnectAddressPath = (): string[] => { getReconnectAddressPath = (): string[] => {
return StorageModel.getPath( return StorageModel.getPath(
StorageModelSubPath.ConnectionModel, StorageModelSubPath.ConnectionModel,
filePaths.connectionModel.reconnectAddress filePaths.connectionModel.reconnectAddress,
); );
}; };
@@ -243,13 +272,7 @@ export default class ConnectionModel {
console.log(`using mailbox ${mailboxId}`); console.log(`using mailbox ${mailboxId}`);
}; };
// reconnect this.reconnect();
const reconnectAddressPath: string[] = this.getReconnectAddressPath();
const reconnectAddress: string | null =
storageModel.read(reconnectAddressPath);
if (reconnectAddress != null) {
this.connect(reconnectAddress);
}
} }
} }
+10 -8
View File
@@ -50,14 +50,14 @@ export default class FileTransferModel {
const decrypted: string = await decryptString( const decrypted: string = await decryptString(
encryptedFileData, encryptedFileData,
this.transferData.key this.transferData.key,
); );
console.log(decrypted); console.log(decrypted);
const parsed: any = parse(decrypted); const parsed: any = parse(decrypted);
const isFileData: boolean = checkMatchesObjectStructure( const isFileData: boolean = checkMatchesObjectStructure(
parsed, parsed,
FileDataReference FileDataReference,
); );
if (isFileData == false) return; if (isFileData == false) return;
@@ -65,7 +65,7 @@ export default class FileTransferModel {
this.storageModel.write(fileData.path, fileData.body); this.storageModel.write(fileData.path, fileData.body);
const pathString: string = StorageModel.pathComponentsToString( const pathString: string = StorageModel.pathComponentsToString(
...fileData.path ...fileData.path,
); );
this.fileHandlerManager.trigger(pathString); this.fileHandlerManager.trigger(pathString);
}; };
@@ -73,13 +73,13 @@ export default class FileTransferModel {
// sending // sending
sendFiles = ( sendFiles = (
directoryPaths: IteratorObject<string[]>, directoryPaths: IteratorObject<string[]>,
callback: (path: string) => void callback: (path: string) => void,
): void => { ): void => {
for (const directoryPath of directoryPaths) { for (const directoryPath of directoryPaths) {
this.storageModel.recurse(directoryPath, (filePath: string[]) => { this.storageModel.recurse(directoryPath, (filePath: string[]) => {
this.sendFile(filePath); this.sendFile(filePath);
const pathString: string = StorageModel.pathComponentsToString( const pathString: string = StorageModel.pathComponentsToString(
...filePath ...filePath,
); );
callback(pathString); callback(pathString);
}); });
@@ -98,11 +98,11 @@ export default class FileTransferModel {
const stringifiedFileData: string = stringify(fileData); const stringifiedFileData: string = stringify(fileData);
const encryptedFileData: string = await encryptString( const encryptedFileData: string = await encryptString(
stringifiedFileData, stringifiedFileData,
this.transferData.key this.transferData.key,
); );
this.connectionModel.sendPlainMessage( this.connectionModel.sendPlainMessage(
this.transferData.channel, this.transferData.channel,
encryptedFileData encryptedFileData,
); );
}; };
@@ -111,7 +111,9 @@ export default class FileTransferModel {
this.storageModel = storageModel; this.storageModel = storageModel;
this.connectionModel = connectionModel; this.connectionModel = connectionModel;
this.connectionModel.messageHandlerManager.addHandler(this.handleMessage); this.connectionModel.messageHandlerManager.addHandler(
this.handleMessage,
);
} }
} }
+4 -4
View File
@@ -14,7 +14,7 @@ export default class SettingsModel {
this.username = newValue; this.username = newValue;
const path = StorageModel.getPath( const path = StorageModel.getPath(
StorageModelSubPath.SettingsModel, StorageModelSubPath.SettingsModel,
filePaths.settingsModel.username filePaths.settingsModel.username,
); );
this.storageModel.write(path, newValue); this.storageModel.write(path, newValue);
} }
@@ -23,7 +23,7 @@ export default class SettingsModel {
this.firstDayOfWeek = newValue; this.firstDayOfWeek = newValue;
const path = StorageModel.getPath( const path = StorageModel.getPath(
StorageModelSubPath.SettingsModel, StorageModelSubPath.SettingsModel,
filePaths.settingsModel.firstDayOfWeek filePaths.settingsModel.firstDayOfWeek,
); );
this.storageModel.write(path, newValue); this.storageModel.write(path, newValue);
} }
@@ -32,7 +32,7 @@ export default class SettingsModel {
loadUsernam(): void { loadUsernam(): void {
const path = StorageModel.getPath( const path = StorageModel.getPath(
StorageModelSubPath.SettingsModel, StorageModelSubPath.SettingsModel,
filePaths.settingsModel.username filePaths.settingsModel.username,
); );
const content = this.storageModel.read(path); const content = this.storageModel.read(path);
this.username = content ?? ""; this.username = content ?? "";
@@ -41,7 +41,7 @@ export default class SettingsModel {
loadFirstDayofWeek(): void { loadFirstDayofWeek(): void {
const path = StorageModel.getPath( const path = StorageModel.getPath(
StorageModelSubPath.SettingsModel, StorageModelSubPath.SettingsModel,
filePaths.settingsModel.firstDayOfWeek filePaths.settingsModel.firstDayOfWeek,
); );
const content = this.storageModel.read(path); const content = this.storageModel.read(path);
this.firstDayOfWeek = content ?? "0"; this.firstDayOfWeek = content ?? "0";
+27 -17
View File
@@ -11,7 +11,7 @@ export default class StorageModel {
// read // read
read = (pathComponents: string[]): string | null => { read = (pathComponents: string[]): string | null => {
const pathString: string = StorageModel.pathComponentsToString( const pathString: string = StorageModel.pathComponentsToString(
...pathComponents ...pathComponents,
); );
return localStorage.getItem(pathString); return localStorage.getItem(pathString);
}; };
@@ -19,7 +19,8 @@ export default class StorageModel {
list = (pathComponents: string[]): string[] => { list = (pathComponents: string[]): string[] => {
let currentParent: StorageEntry = this.storageEntryTree; let currentParent: StorageEntry = this.storageEntryTree;
for (const component of pathComponents) { for (const component of pathComponents) {
const nextParent: StorageEntry | undefined = currentParent[component]; const nextParent: StorageEntry | undefined =
currentParent[component];
if (nextParent == undefined) return []; if (nextParent == undefined) return [];
currentParent = nextParent; currentParent = nextParent;
} }
@@ -30,7 +31,7 @@ export default class StorageModel {
// write // write
write = (pathComponents: string[], value: string): void => { write = (pathComponents: string[], value: string): void => {
const pathString: string = StorageModel.pathComponentsToString( const pathString: string = StorageModel.pathComponentsToString(
...pathComponents ...pathComponents,
); );
localStorage.setItem(pathString, value); localStorage.setItem(pathString, value);
this.updateTree(...pathComponents); this.updateTree(...pathComponents);
@@ -38,10 +39,10 @@ export default class StorageModel {
remove = ( remove = (
pathComponents: string[], pathComponents: string[],
shouldInitialize: boolean = true shouldInitialize: boolean = true,
): void => { ): void => {
const pathString: string = StorageModel.pathComponentsToString( const pathString: string = StorageModel.pathComponentsToString(
...pathComponents ...pathComponents,
); );
localStorage.removeItem(pathString); localStorage.removeItem(pathString);
@@ -53,7 +54,7 @@ export default class StorageModel {
rename = ( rename = (
sourcePathComponents: string[], sourcePathComponents: string[],
destinationPathComponents: string[], destinationPathComponents: string[],
shouldInitialize: boolean = true shouldInitialize: boolean = true,
): boolean => { ): boolean => {
const content: string | null = this.read(sourcePathComponents); const content: string | null = this.read(sourcePathComponents);
if (content == null) return false; if (content == null) return false;
@@ -92,18 +93,20 @@ export default class StorageModel {
}; };
removeRecursively = (pathComponents: string[]): void => { removeRecursively = (pathComponents: string[]): void => {
this.recurse(pathComponents, (path: string[]) => this.remove(path, false)); this.recurse(pathComponents, (path: string[]) =>
this.remove(path, false),
);
this.initializeTree(); this.initializeTree();
}; };
renameRecursively = ( renameRecursively = (
sourcePathComponents: string[], sourcePathComponents: string[],
destinationPathComponents: string[] destinationPathComponents: string[],
): void => { ): void => {
this.recurse(sourcePathComponents, (path: string[]) => { this.recurse(sourcePathComponents, (path: string[]) => {
// assemble destinationPath // assemble destinationPath
const relativePathOfCurrentEntity = path.slice( const relativePathOfCurrentEntity = path.slice(
sourcePathComponents.length sourcePathComponents.length,
); );
const destinationPathComponentsOfCurrentEntity = [ const destinationPathComponentsOfCurrentEntity = [
...destinationPathComponents, ...destinationPathComponents,
@@ -117,14 +120,17 @@ export default class StorageModel {
}; };
// stringifiable // stringifiable
writeStringifiable = (pathComponents: string[], value: ValidObject): void => { writeStringifiable = (
pathComponents: string[],
value: ValidObject,
): void => {
const valueString: string = stringify(value); const valueString: string = stringify(value);
this.write(pathComponents, valueString); this.write(pathComponents, valueString);
}; };
readStringifiable = <T extends ValidObject>( readStringifiable = <T extends ValidObject>(
pathComponents: string[], pathComponents: string[],
reference: T reference: T,
): T | null => { ): T | null => {
const valueString: string | null = this.read(pathComponents); const valueString: string | null = this.read(pathComponents);
if (!valueString) return null; if (!valueString) return null;
@@ -140,8 +146,8 @@ export default class StorageModel {
this.recurse([], (path: string[]) => { this.recurse([], (path: string[]) => {
if (path[0] == DATA_VERSION) return; if (path[0] == DATA_VERSION) return;
this.remove(path); this.remove(path);
}) });
} };
// tree // tree
initializeTree = (): void => { initializeTree = (): void => {
@@ -149,7 +155,8 @@ export default class StorageModel {
this.storageEntryTree = {}; this.storageEntryTree = {};
for (const key of Object.keys(localStorage)) { for (const key of Object.keys(localStorage)) {
const components: string[] = StorageModel.stringToPathComponents(key); const components: string[] =
StorageModel.stringToPathComponents(key);
this.updateTree(...components); this.updateTree(...components);
} }
}; };
@@ -180,12 +187,15 @@ export default class StorageModel {
}; };
static getFileNameFromString = (pathString: string): string => { static getFileNameFromString = (pathString: string): string => {
const pathComponents: string[] = this.stringToPathComponents(pathString); const pathComponents: string[] =
this.stringToPathComponents(pathString);
return pathComponents[pathComponents.length - 1] || "\\"; return pathComponents[pathComponents.length - 1] || "\\";
}; };
static pathComponentsToString = (...pathComponents: string[]): string => { static pathComponentsToString = (...pathComponents: string[]): string => {
return pathComponents.filter((x) => x != "").join(PATH_COMPONENT_SEPARATOR); return pathComponents
.filter((x) => x != "")
.join(PATH_COMPONENT_SEPARATOR);
}; };
static stringToPathComponents = (string: string): string[] => { static stringToPathComponents = (string: string): string[] => {
@@ -203,7 +213,7 @@ export default class StorageModel {
static getPath( static getPath(
locationName: StorageModelSubPath, locationName: StorageModelSubPath,
filePath: string[] filePath: string[],
): string[] { ): string[] {
return [DATA_VERSION, locationName, ...filePath]; return [DATA_VERSION, locationName, ...filePath];
} }
+8 -8
View File
@@ -4,7 +4,7 @@ const ENCRYPTION_ALG = "AES-GCM";
// minimal // minimal
export async function encryptString( export async function encryptString(
plaintext: string, plaintext: string,
passphrase: string passphrase: string,
): Promise<string> { ): Promise<string> {
if (!window.crypto.subtle) return plaintext; if (!window.crypto.subtle) return plaintext;
@@ -21,7 +21,7 @@ export async function encryptString(
export async function decryptString( export async function decryptString(
cyphertext: string, cyphertext: string,
passphrase: string passphrase: string,
): Promise<string> { ): Promise<string> {
try { try {
const encrypionData = JSON.parse(atob(cyphertext)); const encrypionData = JSON.parse(atob(cyphertext));
@@ -47,12 +47,12 @@ export function decode(array: Uint8Array): string {
export async function encrypt( export async function encrypt(
iv: Uint8Array, iv: Uint8Array,
key: CryptoKey, key: CryptoKey,
message: string message: string,
): Promise<Uint8Array> { ): Promise<Uint8Array> {
const arrayBuffer = await window.crypto.subtle.encrypt( const arrayBuffer = await window.crypto.subtle.encrypt(
{ name: ENCRYPTION_ALG, iv }, { name: ENCRYPTION_ALG, iv },
key, key,
encode(message) encode(message),
); );
return new Uint8Array(arrayBuffer); return new Uint8Array(arrayBuffer);
} }
@@ -60,12 +60,12 @@ export async function encrypt(
export async function decrypt( export async function decrypt(
iv: Uint8Array, iv: Uint8Array,
key: CryptoKey, key: CryptoKey,
cyphertext: Uint8Array cyphertext: Uint8Array,
): Promise<string> { ): Promise<string> {
const arrayBuffer = await crypto.subtle.decrypt( const arrayBuffer = await crypto.subtle.decrypt(
{ name: ENCRYPTION_ALG, iv }, { name: ENCRYPTION_ALG, iv },
key, key,
cyphertext cyphertext,
); );
return arrayBufferToString(arrayBuffer); return arrayBufferToString(arrayBuffer);
} }
@@ -89,14 +89,14 @@ export function splitMergedString(mergedString: string): [Uint8Array, string] {
// key // key
export async function importKey( export async function importKey(
passphrase: string, passphrase: string,
purpose: "encrypt" | "decrypt" purpose: "encrypt" | "decrypt",
): Promise<CryptoKey> { ): Promise<CryptoKey> {
return await crypto.subtle.importKey( return await crypto.subtle.importKey(
"raw", "raw",
await hash(encode(passphrase)), await hash(encode(passphrase)),
{ name: ENCRYPTION_ALG }, { name: ENCRYPTION_ALG },
false, false,
[purpose] [purpose],
); );
} }
+3 -3
View File
@@ -6,7 +6,7 @@ export function checkIsValidObject(object: any): boolean {
export function checkMatchesObjectStructure( export function checkMatchesObjectStructure(
objectToCheck: any, objectToCheck: any,
reference: any reference: any,
): boolean { ): boolean {
if (typeof reference != "object") { if (typeof reference != "object") {
return typeof objectToCheck == typeof reference; return typeof objectToCheck == typeof reference;
@@ -27,14 +27,14 @@ export function checkMatchesObjectStructure(
const [fisrtOfReference] = reference[key]; const [fisrtOfReference] = reference[key];
const doesFirstItemMatch = checkMatchesObjectStructure( const doesFirstItemMatch = checkMatchesObjectStructure(
firstOfObjectToCheck, firstOfObjectToCheck,
fisrtOfReference fisrtOfReference,
); );
if (doesFirstItemMatch == false) return false; if (doesFirstItemMatch == false) return false;
} else if (requiredType == "object") { } else if (requiredType == "object") {
// recurse into objects // recurse into objects
const doesNestedObjectMatch = checkMatchesObjectStructure( const doesNestedObjectMatch = checkMatchesObjectStructure(
objectToCheck[key], objectToCheck[key],
reference[key] reference[key],
); );
if (doesNestedObjectMatch == false) return false; if (doesNestedObjectMatch == false) return false;
} }
+11 -10
View File
@@ -22,14 +22,14 @@ export type StringEntryObject = { [key: string]: Stringifiable | undefined };
export function filterObjectsByStringEntries<T>( export function filterObjectsByStringEntries<T>(
reference: StringEntryObject, reference: StringEntryObject,
converter: (object: T) => StringEntryObject, converter: (object: T) => StringEntryObject,
objects: T[] objects: T[],
): Set<T> { ): Set<T> {
const matches: Set<T> = new Set(); const matches: Set<T> = new Set();
object_loop: for (const object of objects) { object_loop: for (const object of objects) {
const doesMatch: boolean = checkDoesObjectMatchReference( const doesMatch: boolean = checkDoesObjectMatchReference(
reference, reference,
converter(object) converter(object),
); );
if (doesMatch) matches.add(object); if (doesMatch) matches.add(object);
} }
@@ -39,10 +39,10 @@ export function filterObjectsByStringEntries<T>(
export function checkDoesObjectMatchReference( export function checkDoesObjectMatchReference(
reference: StringEntryObject, reference: StringEntryObject,
stringEntryObject: StringEntryObject stringEntryObject: StringEntryObject,
): boolean { ): boolean {
reference_entry_loop: for (const referenceEntry of Object.entries( reference_entry_loop: for (const referenceEntry of Object.entries(
reference reference,
)) { )) {
const [referenceKey, referenceValue] = referenceEntry; const [referenceKey, referenceValue] = referenceEntry;
const stringEntryObjectValue: Stringifiable | undefined = const stringEntryObjectValue: Stringifiable | undefined =
@@ -71,7 +71,8 @@ export function checkDoesObjectMatchReference(
// property must exist but be anything // property must exist but be anything
if ( if (
referenceValue == "" && referenceValue == "" &&
(stringEntryObjectValue == undefined || stringEntryObjectValue == "") (stringEntryObjectValue == undefined ||
stringEntryObjectValue == "")
) { ) {
return false; return false;
} else if (referenceValue == "") { } else if (referenceValue == "") {
@@ -90,7 +91,7 @@ export function checkDoesObjectMatchReference(
export function collectObjectValuesForKey<T>( export function collectObjectValuesForKey<T>(
key: string, key: string,
converter: (object: T) => StringEntryObject, converter: (object: T) => StringEntryObject,
objects: T[] objects: T[],
): string[] { ): string[] {
const values: Set<string> = new Set(); const values: Set<string> = new Set();
@@ -110,7 +111,7 @@ export function collectObjectValuesForKey<T>(
export function filterObjectsByWords<T>( export function filterObjectsByWords<T>(
query: string, query: string,
getStringsOfObject: (object: T) => string[], getStringsOfObject: (object: T) => string[],
objects: T[] objects: T[],
): Set<T> { ): Set<T> {
const matches: Set<T> = new Set(); const matches: Set<T> = new Set();
@@ -118,7 +119,7 @@ export function filterObjectsByWords<T>(
const doesMatch: boolean = checkDoesObjectMatchSearch( const doesMatch: boolean = checkDoesObjectMatchSearch(
query, query,
getStringsOfObject, getStringsOfObject,
object object,
); );
if (doesMatch) matches.add(object); if (doesMatch) matches.add(object);
} }
@@ -129,7 +130,7 @@ export function filterObjectsByWords<T>(
export function checkDoesObjectMatchSearch<T>( export function checkDoesObjectMatchSearch<T>(
query: string, query: string,
getStringsOfObject: (object: T) => string[], getStringsOfObject: (object: T) => string[],
object: T object: T,
): boolean { ): boolean {
if (query == "") return true; if (query == "") return true;
@@ -250,7 +251,7 @@ export function parseValidObject<T>(string: string, reference: T): T | null {
const doesMatchReference: boolean = checkMatchesObjectStructure( const doesMatchReference: boolean = checkMatchesObjectStructure(
parsed, parsed,
reference reference,
); );
if (doesMatchReference == false) return null; if (doesMatchReference == false) return null;
+30 -19
View File
@@ -8,7 +8,11 @@ import ChatModel, {
ChatMessage, ChatMessage,
ChatMessageStatus, ChatMessageStatus,
} from "../Model/Chat/chatModel"; } from "../Model/Chat/chatModel";
import { getLocalStorageItemAndClear, parseArray, parseOrFallback } from "../Model/Utility/utility"; import {
getLocalStorageItemAndClear,
parseArray,
parseOrFallback,
} from "../Model/Utility/utility";
import ChatListModel from "../Model/Chat/chatListModel"; import ChatListModel from "../Model/Chat/chatListModel";
import ConnectionModel from "../Model/Global/connectionModel"; import ConnectionModel from "../Model/Global/connectionModel";
@@ -30,7 +34,8 @@ export default class v1Upgrader {
const firstDayOfWeek: string | null = const firstDayOfWeek: string | null =
getLocalStorageItemAndClear("first-day-of-week"); getLocalStorageItemAndClear("first-day-of-week");
if (firstDayOfWeek != null) { if (firstDayOfWeek != null) {
const parsedFirstDayOfWeek: string = parseOrFallback(firstDayOfWeek); const parsedFirstDayOfWeek: string =
parseOrFallback(firstDayOfWeek);
this.settingsModel.setFirstDayOfWeek(parsedFirstDayOfWeek); this.settingsModel.setFirstDayOfWeek(parsedFirstDayOfWeek);
} }
}; };
@@ -49,7 +54,8 @@ export default class v1Upgrader {
// chats // chats
migrateChats = (): void => { migrateChats = (): void => {
const chatIdString: string | null = getLocalStorageItemAndClear("chat-ids"); const chatIdString: string | null =
getLocalStorageItemAndClear("chat-ids");
if (chatIdString == null) return; if (chatIdString == null) return;
const chatIds: any[] = parseArray(chatIdString); const chatIds: any[] = parseArray(chatIdString);
@@ -62,7 +68,7 @@ export default class v1Upgrader {
migrateChatById = (id: string): void => { migrateChatById = (id: string): void => {
// get primary channel // get primary channel
const primaryChannel: string | null = getLocalStorageItemAndClear( const primaryChannel: string | null = getLocalStorageItemAndClear(
storageKeys.primaryChannel(id) storageKeys.primaryChannel(id),
); );
if (primaryChannel == null) return; if (primaryChannel == null) return;
const parsedPriamryChannel: string = parseOrFallback(primaryChannel); const parsedPriamryChannel: string = parseOrFallback(primaryChannel);
@@ -72,12 +78,11 @@ export default class v1Upgrader {
this.chatListModel.createChat(parsedPriamryChannel); this.chatListModel.createChat(parsedPriamryChannel);
// secondary channels // secondary channels
const secondaryChannelString: string | null = getLocalStorageItemAndClear( const secondaryChannelString: string | null =
storageKeys.secondaryChannels(id) getLocalStorageItemAndClear(storageKeys.secondaryChannels(id));
);
if (secondaryChannelString != null) { if (secondaryChannelString != null) {
const potentialSecondaryChannels: any[] = parseArray( const potentialSecondaryChannels: any[] = parseArray(
secondaryChannelString secondaryChannelString,
); );
const confirmedSecondaryChannels: string[] = []; const confirmedSecondaryChannels: string[] = [];
@@ -91,7 +96,7 @@ export default class v1Upgrader {
// encryption key // encryption key
const encryptionKey: string | null = getLocalStorageItemAndClear( const encryptionKey: string | null = getLocalStorageItemAndClear(
storageKeys.encyptionKey(id) storageKeys.encyptionKey(id),
); );
if (encryptionKey != null) { if (encryptionKey != null) {
const parsedEncryptionKey: string = parseOrFallback(encryptionKey); const parsedEncryptionKey: string = parseOrFallback(encryptionKey);
@@ -104,19 +109,21 @@ export default class v1Upgrader {
const messageOutboxString: string = const messageOutboxString: string =
getLocalStorageItemAndClear(storageKeys.outbox(id)) ?? ""; getLocalStorageItemAndClear(storageKeys.outbox(id)) ?? "";
const potentialMessages: any[] = parseArray(messagesString); const potentialMessages: any[] = parseArray(messagesString);
const potentialMessagesInOutbox: any[] = parseArray(messageOutboxString); const potentialMessagesInOutbox: any[] =
parseArray(messageOutboxString);
const addMessages = ( const addMessages = (
potentialMessages: any[], potentialMessages: any[],
status: ChatMessageStatus status: ChatMessageStatus,
) => { ) => {
for (const potentialMessage of potentialMessages) { for (const potentialMessage of potentialMessages) {
const isV1ChatMessage: boolean = checkMatchesObjectStructure( const isV1ChatMessage: boolean = checkMatchesObjectStructure(
potentialMessage, potentialMessage,
V1ChatMessageReference V1ChatMessageReference,
); );
if (isV1ChatMessage == false) continue; if (isV1ChatMessage == false) continue;
const v1ChatMessage: typeof V1ChatMessageReference = potentialMessage; const v1ChatMessage: typeof V1ChatMessageReference =
potentialMessage;
const convertedChatMessage: ChatMessage = { const convertedChatMessage: ChatMessage = {
dataVersion: "v2", dataVersion: "v2",
@@ -133,7 +140,9 @@ export default class v1Upgrader {
}; };
chatModel.addMessage(convertedChatMessage); chatModel.addMessage(convertedChatMessage);
if (status == ChatMessageStatus.Outbox) { if (status == ChatMessageStatus.Outbox) {
this.connectionModel.sendMessageOrStore(convertedChatMessage); this.connectionModel.sendMessageOrStore(
convertedChatMessage,
);
} }
} }
}; };
@@ -152,7 +161,7 @@ export default class v1Upgrader {
const addObjects = (potentialObjects: any[]): void => { const addObjects = (potentialObjects: any[]): void => {
const board: BoardInfoFileContent = const board: BoardInfoFileContent =
chatModel.fileModel.boardsAndTasksModel.createBoard( chatModel.fileModel.boardsAndTasksModel.createBoard(
translations.updater.migrated translations.updater.migrated,
); );
chatModel.fileModel.boardsAndTasksModel.updateBoard(board); chatModel.fileModel.boardsAndTasksModel.updateBoard(board);
@@ -166,7 +175,7 @@ export default class v1Upgrader {
const objectName: string = potentialObject.title; const objectName: string = potentialObject.title;
const contentVersions: any[] = Object.values( const contentVersions: any[] = Object.values(
potentialObject.contentVersions potentialObject.contentVersions,
); );
for (const potentialVersion of contentVersions) { for (const potentialVersion of contentVersions) {
const isV1MessageObjectContent: boolean = const isV1MessageObjectContent: boolean =
@@ -178,7 +187,7 @@ export default class v1Upgrader {
fileId: objectId, fileId: objectId,
fileContentId: FileModel.generateFileContentId( fileContentId: FileModel.generateFileContentId(
potentialVersion.isoDateVersionCreated potentialVersion.isoDateVersionCreated,
), ),
creationDate: potentialVersion.isoDateVersionCreated, creationDate: potentialVersion.isoDateVersionCreated,
type: "task", type: "task",
@@ -196,7 +205,9 @@ export default class v1Upgrader {
time: potentialVersion.time ?? "", time: potentialVersion.time ?? "",
}; };
console.log(convertedTaskFileContent); console.log(convertedTaskFileContent);
chatModel.fileModel.handleFileContent(convertedTaskFileContent); chatModel.fileModel.handleFileContent(
convertedTaskFileContent,
);
} }
} }
}; };
@@ -208,7 +219,7 @@ export default class v1Upgrader {
constructor( constructor(
public settingsModel: SettingsModel, public settingsModel: SettingsModel,
public connectionModel: ConnectionModel, public connectionModel: ConnectionModel,
public chatListModel: ChatListModel public chatListModel: ChatListModel,
) { ) {
this.migrateSettings(); this.migrateSettings();
this.migrateConnections(); this.migrateConnections();
+19 -10
View File
@@ -20,14 +20,15 @@ export function BoardKanbanPage(boardViewModel: BoardViewModel) {
boardViewModel.taskViewModels, boardViewModel.taskViewModels,
( (
categories: React.ListState<string>, categories: React.ListState<string>,
sortedCategories: React.State<string[]> sortedCategories: React.State<string[]>,
) => { ) => {
const categoryNameConverter: React.StateItemConverter<string> = ( const categoryNameConverter: React.StateItemConverter<string> = (
categoryName: string categoryName: string,
) => { ) => {
const index: React.State<number> = createPropertyValueIndexState( const index: React.State<number> =
createPropertyValueIndexState(
sortedCategories, sortedCategories,
categoryName categoryName,
); );
return KanbanBoard(categoryName, index, boardViewModel); return KanbanBoard(categoryName, index, boardViewModel);
}; };
@@ -38,14 +39,14 @@ export function BoardKanbanPage(boardViewModel: BoardViewModel) {
children:append={[categories, categoryNameConverter]} children:append={[categories, categoryNameConverter]}
></div> ></div>
); );
} },
); );
} }
function KanbanBoard( function KanbanBoard(
categoryName: string, categoryName: string,
index: React.State<number>, index: React.State<number>,
boardViewModel: BoardViewModel boardViewModel: BoardViewModel,
) { ) {
return FilteredList( return FilteredList(
{ category: categoryName }, { category: categoryName },
@@ -53,18 +54,26 @@ function KanbanBoard(
boardViewModel.filteredTaskViewModels, boardViewModel.filteredTaskViewModels,
(taskViewModels: React.ListState<TaskViewModel>) => { (taskViewModels: React.ListState<TaskViewModel>) => {
const viewModel: TaskCategoryBulkChangeViewModel = const viewModel: TaskCategoryBulkChangeViewModel =
new TaskCategoryBulkChangeViewModel(taskViewModels, categoryName); new TaskCategoryBulkChangeViewModel(
taskViewModels,
categoryName,
);
function drop() { function drop() {
boardViewModel.handleDropWithinBoard(categoryName); boardViewModel.handleDropWithinBoard(categoryName);
} }
const view = ( const view = (
<div class="flex-column flex-no" on:dragover={allowDrop} on:drop={drop}> <div
class="flex-column flex-no"
on:dragover={allowDrop}
on:drop={drop}
>
<div class="flex-row width-input"> <div class="flex-row width-input">
<input <input
placeholder={ placeholder={
translations.chatPage.task.renameCategoryInputPlaceholder translations.chatPage.task
.renameCategoryInputPlaceholder
} }
bind:value={viewModel.inputValue} bind:value={viewModel.inputValue}
on:enter={viewModel.set} on:enter={viewModel.set}
@@ -92,6 +101,6 @@ function KanbanBoard(
}); });
return view; return view;
} },
); );
} }
+34 -14
View File
@@ -39,7 +39,7 @@ export function BoardPage(boardViewModel: BoardViewModel) {
); );
} }
} }
} },
); );
// task modal // task modal
@@ -49,9 +49,11 @@ export function BoardPage(boardViewModel: BoardViewModel) {
if (boardViewModel.selectedTaskViewModel.value == undefined) { if (boardViewModel.selectedTaskViewModel.value == undefined) {
return <div></div>; return <div></div>;
} else { } else {
return TaskSettingsModal(boardViewModel.selectedTaskViewModel.value); return TaskSettingsModal(
} boardViewModel.selectedTaskViewModel.value,
);
} }
},
); );
return ( return (
@@ -60,22 +62,34 @@ export function BoardPage(boardViewModel: BoardViewModel) {
<span> <span>
<button <button
class="ghost board-close-button" class="ghost board-close-button"
aria-label={translations.chatPage.task.closeBoardButtonAudioLabel} aria-label={
translations.chatPage.task
.closeBoardButtonAudioLabel
}
on:click={boardViewModel.close} on:click={boardViewModel.close}
> >
<span class="icon">arrow_back</span> <span class="icon">arrow_back</span>
</button> </button>
<button <button
class="ghost board-toggle-button inset-outline" class="ghost board-toggle-button inset-outline"
aria-label={translations.chatPage.task.toggleBoardButtonAudioLabel} aria-label={
on:click={boardViewModel.taskPageViewModel.toggleBoardList} translations.chatPage.task
toggle:selected={boardViewModel.taskPageViewModel.isShowingBoadList} .toggleBoardButtonAudioLabel
}
on:click={
boardViewModel.taskPageViewModel.toggleBoardList
}
toggle:selected={
boardViewModel.taskPageViewModel.isShowingBoadList
}
> >
<span class="icon">dock_to_right</span> <span class="icon">dock_to_right</span>
</button> </button>
<button <button
class="ghost" class="ghost"
aria-label={translations.chatPage.task.boardSettingsHeadline} aria-label={
translations.chatPage.task.boardSettingsHeadline
}
on:click={boardViewModel.showSettings} on:click={boardViewModel.showSettings}
> >
<span class="icon">settings</span> <span class="icon">settings</span>
@@ -86,32 +100,38 @@ export function BoardPage(boardViewModel: BoardViewModel) {
translations.chatPage.task.listViewButtonAudioLabel, translations.chatPage.task.listViewButtonAudioLabel,
"view_list", "view_list",
BoardPageType.List, BoardPageType.List,
boardViewModel boardViewModel,
)} )}
{BoardViewToggleButton( {BoardViewToggleButton(
translations.chatPage.task.kanbanViewButtonAudioLabel, translations.chatPage.task.kanbanViewButtonAudioLabel,
"view_kanban", "view_kanban",
BoardPageType.Kanban, BoardPageType.Kanban,
boardViewModel boardViewModel,
)} )}
{BoardViewToggleButton( {BoardViewToggleButton(
translations.chatPage.task.statusViewButtonAudioLabel, translations.chatPage.task.statusViewButtonAudioLabel,
"grid_view", "grid_view",
BoardPageType.StatusGrid, BoardPageType.StatusGrid,
boardViewModel boardViewModel,
)} )}
</span> </span>
<span> <span>
<button <button
class="ghost" class="ghost"
aria-label={translations.chatPage.task.filterTasksButtonAudioLabel} aria-label={
translations.chatPage.task
.filterTasksButtonAudioLabel
}
on:click={boardViewModel.showFilterModal} on:click={boardViewModel.showFilterModal}
> >
<span class="icon">filter_alt</span> <span class="icon">filter_alt</span>
</button> </button>
<button <button
class="ghost" class="ghost"
aria-label={translations.chatPage.task.createTaskButtonAudioLabel} aria-label={
translations.chatPage.task
.createTaskButtonAudioLabel
}
on:click={boardViewModel.createTask} on:click={boardViewModel.createTask}
> >
<span class="icon">add</span> <span class="icon">add</span>
@@ -125,7 +145,7 @@ export function BoardPage(boardViewModel: BoardViewModel) {
boardViewModel.searchViewModel, boardViewModel.searchViewModel,
translations.chatPage.task.filterTasksHeadline, translations.chatPage.task.filterTasksHeadline,
TaskViewModelToEntry, TaskViewModelToEntry,
boardViewModel.isPresentingFilterModal boardViewModel.isPresentingFilterModal,
)} )}
<div children:set={taskSettingsModal}></div> <div children:set={taskSettingsModal}></div>
</div> </div>
+42 -27
View File
@@ -28,12 +28,12 @@ export function BoardStatusGridPage(boardViewModel: BoardViewModel) {
"status", "status",
(taskViewModel: TaskViewModel) => taskViewModel.task, (taskViewModel: TaskViewModel) => taskViewModel.task,
boardViewModel.taskViewModels, boardViewModel.taskViewModels,
statuses statuses,
); );
}); });
const statusNameCellConverter: React.StateItemConverter<string> = ( const statusNameCellConverter: React.StateItemConverter<string> = (
statusName: string statusName: string,
) => { ) => {
const index = createPropertyValueIndexState(sortedStatuses, statusName); const index = createPropertyValueIndexState(sortedStatuses, statusName);
return StatusNameCell(statusName, index, boardViewModel); return StatusNameCell(statusName, index, boardViewModel);
@@ -51,14 +51,15 @@ export function BoardStatusGridPage(boardViewModel: BoardViewModel) {
boardViewModel.filteredTaskViewModels, boardViewModel.filteredTaskViewModels,
( (
categories: React.ListState<string>, categories: React.ListState<string>,
sortedCategories: React.State<string[]> sortedCategories: React.State<string[]>,
) => { ) => {
const categoryRowConverter: React.StateItemConverter<string> = ( const categoryRowConverter: React.StateItemConverter<
categoryName: string string
) => { > = (categoryName: string) => {
const index: React.State<number> = createPropertyValueIndexState( const index: React.State<number> =
createPropertyValueIndexState(
sortedCategories, sortedCategories,
categoryName categoryName,
); );
return CategoryRow( return CategoryRow(
@@ -66,7 +67,7 @@ export function BoardStatusGridPage(boardViewModel: BoardViewModel) {
index, index,
statuses, statuses,
sortedStatuses, sortedStatuses,
boardViewModel boardViewModel,
); );
}; };
@@ -76,7 +77,7 @@ export function BoardStatusGridPage(boardViewModel: BoardViewModel) {
children:append={[categories, categoryRowConverter]} children:append={[categories, categoryRowConverter]}
></div> ></div>
); );
} },
)} )}
</div> </div>
); );
@@ -85,27 +86,31 @@ export function BoardStatusGridPage(boardViewModel: BoardViewModel) {
function StatusNameCell( function StatusNameCell(
statusName: string, statusName: string,
index: React.State<number>, index: React.State<number>,
boardViewModel: BoardViewModel boardViewModel: BoardViewModel,
) { ) {
const taskViewModelsWithMatchingStatus: React.ListState<TaskViewModel> = const taskViewModelsWithMatchingStatus: React.ListState<TaskViewModel> =
new React.ListState(); new React.ListState();
boardViewModel.filteredTaskViewModels.handleAddition( boardViewModel.filteredTaskViewModels.handleAddition(
(taskViewModel: TaskViewModel) => { (taskViewModel: TaskViewModel) => {
const doesMatchStatus: boolean = taskViewModel.task.status == statusName; const doesMatchStatus: boolean =
taskViewModel.task.status == statusName;
if (doesMatchStatus == false) return; if (doesMatchStatus == false) return;
taskViewModelsWithMatchingStatus.add(taskViewModel); taskViewModelsWithMatchingStatus.add(taskViewModel);
boardViewModel.filteredTaskViewModels.handleRemoval(taskViewModel, () => { boardViewModel.filteredTaskViewModels.handleRemoval(
taskViewModel,
() => {
taskViewModelsWithMatchingStatus.remove(taskViewModel); taskViewModelsWithMatchingStatus.remove(taskViewModel);
}); },
} );
},
); );
const viewModel: TaskStatusBulkChangeViewModel = const viewModel: TaskStatusBulkChangeViewModel =
new TaskStatusBulkChangeViewModel( new TaskStatusBulkChangeViewModel(
taskViewModelsWithMatchingStatus, taskViewModelsWithMatchingStatus,
statusName statusName,
); );
const view = ( const view = (
@@ -113,7 +118,8 @@ function StatusNameCell(
<div class="property-input-wrapper"> <div class="property-input-wrapper">
<input <input
placeholder={ placeholder={
translations.chatPage.task.renameCategoryInputPlaceholder translations.chatPage.task
.renameCategoryInputPlaceholder
} }
bind:value={viewModel.inputValue} bind:value={viewModel.inputValue}
on:enter={viewModel.set} on:enter={viewModel.set}
@@ -141,7 +147,7 @@ function CategoryRow(
index: React.State<number>, index: React.State<number>,
allStatuses: React.ListState<string>, allStatuses: React.ListState<string>,
sortedStatuses: React.State<string[]>, sortedStatuses: React.State<string[]>,
boardViewModel: BoardViewModel boardViewModel: BoardViewModel,
) { ) {
return FilteredList( return FilteredList(
{ category: categoryName }, { category: categoryName },
@@ -149,27 +155,34 @@ function CategoryRow(
boardViewModel.filteredTaskViewModels, boardViewModel.filteredTaskViewModels,
(taskViewModels: React.ListState<TaskViewModel>) => { (taskViewModels: React.ListState<TaskViewModel>) => {
const statusNameConverter: React.StateItemConverter<string> = ( const statusNameConverter: React.StateItemConverter<string> = (
statusName: string statusName: string,
) => { ) => {
const index = createPropertyValueIndexState(sortedStatuses, statusName); const index = createPropertyValueIndexState(
sortedStatuses,
statusName,
);
return CategoryStatusColumn( return CategoryStatusColumn(
categoryName, categoryName,
statusName, statusName,
index, index,
boardViewModel, boardViewModel,
taskViewModels taskViewModels,
); );
}; };
const viewModel: TaskCategoryBulkChangeViewModel = const viewModel: TaskCategoryBulkChangeViewModel =
new TaskCategoryBulkChangeViewModel(taskViewModels, categoryName); new TaskCategoryBulkChangeViewModel(
taskViewModels,
categoryName,
);
const view = ( const view = (
<div class="flex-row flex-no large-gap"> <div class="flex-row flex-no large-gap">
<div class="property-input-wrapper"> <div class="property-input-wrapper">
<input <input
placeholder={ placeholder={
translations.chatPage.task.renameCategoryInputPlaceholder translations.chatPage.task
.renameCategoryInputPlaceholder
} }
bind:value={viewModel.inputValue} bind:value={viewModel.inputValue}
on:enter={viewModel.set} on:enter={viewModel.set}
@@ -195,7 +208,7 @@ function CategoryRow(
}); });
return view; return view;
} },
); );
} }
@@ -204,12 +217,14 @@ function CategoryStatusColumn(
statusName: string, statusName: string,
index: React.State<number>, index: React.State<number>,
boardViewModel: BoardViewModel, boardViewModel: BoardViewModel,
taskViewModelsWithMatchingCategory: React.ListState<TaskViewModel> taskViewModelsWithMatchingCategory: React.ListState<TaskViewModel>,
) { ) {
const taskViewModels: React.ListState<TaskViewModel> = new React.ListState(); const taskViewModels: React.ListState<TaskViewModel> =
new React.ListState();
taskViewModelsWithMatchingCategory.handleAddition((taskViewModel) => { taskViewModelsWithMatchingCategory.handleAddition((taskViewModel) => {
const doesMatchStatus: boolean = taskViewModel.status.value == statusName; const doesMatchStatus: boolean =
taskViewModel.status.value == statusName;
if (doesMatchStatus == false) return; if (doesMatchStatus == false) return;
taskViewModels.add(taskViewModel); taskViewModels.add(taskViewModel);
-1
View File
@@ -62,7 +62,6 @@
width: 3.1rem; width: 3.1rem;
} }
@media (max-width: 1024px) { @media (max-width: 1024px) {
#chat-page > div > #main > #calendar-page { #chat-page > div > #main > #calendar-page {
flex-direction: column; flex-direction: column;
+59 -24
View File
@@ -24,13 +24,17 @@ export function CalendarPage(calendarPageViewModel: CalendarPageViewModel) {
calendarPageViewModel.handleDrop( calendarPageViewModel.handleDrop(
monthGrid!.year.toString(), monthGrid!.year.toString(),
monthGrid!.month.toString(), monthGrid!.month.toString(),
date date,
); );
} }
return MonthGrid(monthGrid, calendarPageViewModel.selectedDate, drop); return MonthGrid(
} monthGrid,
calendarPageViewModel.selectedDate,
drop,
);
} }
},
); );
const sidePaneContentWrapper = React.createProxyState( const sidePaneContentWrapper = React.createProxyState(
@@ -45,12 +49,17 @@ export function CalendarPage(calendarPageViewModel: CalendarPageViewModel) {
if (listState == undefined) { if (listState == undefined) {
return <div></div>; return <div></div>;
} else { } else {
const sidePaneContent = React.createProxyState([listState], () => { const sidePaneContent = React.createProxyState(
[listState],
() => {
if (listState.value.size == 0) { if (listState.value.size == 0) {
return ( return (
<div class="width-100 height-100 flex-column justify-center align-center"> <div class="width-100 height-100 flex-column justify-center align-center">
<span class="secondary slide-up"> <span class="secondary slide-up">
{translations.chatPage.calendar.noEvents} {
translations.chatPage.calendar
.noEvents
}
</span> </span>
</div> </div>
); );
@@ -58,11 +67,15 @@ export function CalendarPage(calendarPageViewModel: CalendarPageViewModel) {
return ( return (
<div <div
class="flex-column gap slide-up padding-bottom" class="flex-column gap slide-up padding-bottom"
children:append={[listState, TaskViewModelToEntry]} children:append={[
listState,
TaskViewModelToEntry,
]}
></div> ></div>
); );
} }
}); },
);
return ( return (
<div <div
@@ -71,20 +84,22 @@ export function CalendarPage(calendarPageViewModel: CalendarPageViewModel) {
></div> ></div>
); );
} }
} },
); );
const taskSettingsModal = React.createProxyState( const taskSettingsModal = React.createProxyState(
[calendarPageViewModel.selectedTaskViewModel], [calendarPageViewModel.selectedTaskViewModel],
() => { () => {
if (calendarPageViewModel.selectedTaskViewModel.value == undefined) { if (
calendarPageViewModel.selectedTaskViewModel.value == undefined
) {
return <div></div>; return <div></div>;
} else { } else {
return TaskSettingsModal( return TaskSettingsModal(
calendarPageViewModel.selectedTaskViewModel.value calendarPageViewModel.selectedTaskViewModel.value,
); );
} }
} },
); );
return ( return (
@@ -96,7 +111,8 @@ export function CalendarPage(calendarPageViewModel: CalendarPageViewModel) {
<button <button
class="ghost" class="ghost"
aria-label={ aria-label={
translations.chatPage.calendar.todayButtonAudioLabel translations.chatPage.calendar
.todayButtonAudioLabel
} }
on:click={calendarPageViewModel.showToday} on:click={calendarPageViewModel.showToday}
> >
@@ -107,9 +123,12 @@ export function CalendarPage(calendarPageViewModel: CalendarPageViewModel) {
<button <button
class="ghost" class="ghost"
aria-label={ aria-label={
translations.chatPage.calendar.previousMonthButtonAudioLabel translations.chatPage.calendar
.previousMonthButtonAudioLabel
}
on:click={
calendarPageViewModel.showPreviousMonth
} }
on:click={calendarPageViewModel.showPreviousMonth}
> >
<span class="icon">arrow_back</span> <span class="icon">arrow_back</span>
</button> </button>
@@ -118,29 +137,38 @@ export function CalendarPage(calendarPageViewModel: CalendarPageViewModel) {
class="year-input" class="year-input"
type="number" type="number"
aria-label={ aria-label={
translations.chatPage.calendar.yearInputAudioLabel translations.chatPage.calendar
.yearInputAudioLabel
} }
placeholder={ placeholder={
translations.chatPage.calendar.yearInputPlaceholder translations.chatPage.calendar
.yearInputPlaceholder
}
bind:value={
calendarPageViewModel.selectedYear
} }
bind:value={calendarPageViewModel.selectedYear}
></input> ></input>
<input <input
class="month-input" class="month-input"
type="number" type="number"
aria-label={ aria-label={
translations.chatPage.calendar.monthInputAudioLabel translations.chatPage.calendar
.monthInputAudioLabel
} }
placeholder={ placeholder={
translations.chatPage.calendar.monthInputPlaceholder translations.chatPage.calendar
.monthInputPlaceholder
}
bind:value={
calendarPageViewModel.selectedMonth
} }
bind:value={calendarPageViewModel.selectedMonth}
></input> ></input>
</span> </span>
<button <button
class="ghost" class="ghost"
aria-label={ aria-label={
translations.chatPage.calendar.nextMonthButtonAudioLabel translations.chatPage.calendar
.nextMonthButtonAudioLabel
} }
on:click={calendarPageViewModel.showNextMonth} on:click={calendarPageViewModel.showNextMonth}
> >
@@ -151,7 +179,8 @@ export function CalendarPage(calendarPageViewModel: CalendarPageViewModel) {
<button <button
class="ghost" class="ghost"
aria-label={ aria-label={
translations.chatPage.task.createTaskButtonAudioLabel translations.chatPage.task
.createTaskButtonAudioLabel
} }
on:click={calendarPageViewModel.createEvent} on:click={calendarPageViewModel.createEvent}
> >
@@ -159,7 +188,10 @@ export function CalendarPage(calendarPageViewModel: CalendarPageViewModel) {
</button> </button>
</span> </span>
</div> </div>
<div class="content padding-0" children:set={mainContent}></div> <div
class="content padding-0"
children:set={mainContent}
></div>
</div> </div>
</div> </div>
<div <div
@@ -167,7 +199,10 @@ export function CalendarPage(calendarPageViewModel: CalendarPageViewModel) {
set:color={calendarPageViewModel.chatViewModel.displayedColor} set:color={calendarPageViewModel.chatViewModel.displayedColor}
> >
<div class="pane"> <div class="pane">
<div class="content" children:set={sidePaneContentWrapper}></div> <div
class="content"
children:set={sidePaneContentWrapper}
></div>
</div> </div>
</div> </div>
+20 -8
View File
@@ -31,7 +31,7 @@ export function MessagePage(messagePageViewModel: MessagePageViewModel) {
scrollDown(); scrollDown();
} }
messagePageViewModel.chatMessageViewModels.subscribeSilent( messagePageViewModel.chatMessageViewModels.subscribeSilent(
scrollDownIfApplicable scrollDownIfApplicable,
); );
setTimeout(() => scrollDown(), 100); setTimeout(() => scrollDown(), 100);
@@ -40,7 +40,9 @@ export function MessagePage(messagePageViewModel: MessagePageViewModel) {
<div class="pane-wrapper"> <div class="pane-wrapper">
<div class="pane"> <div class="pane">
<div class="toolbar"> <div class="toolbar">
<span class="title">{translations.chatPage.message.messagesHeadline}</span> <span class="title">
{translations.chatPage.message.messagesHeadline}
</span>
</div> </div>
<div class="content"> <div class="content">
{messageContainer} {messageContainer}
@@ -48,19 +50,29 @@ export function MessagePage(messagePageViewModel: MessagePageViewModel) {
<div class="content-width-constraint"> <div class="content-width-constraint">
<div class="input-width-constraint"> <div class="input-width-constraint">
<input <input
bind:value={messagePageViewModel.composingMessage} bind:value={
on:enter={messagePageViewModel.sendMessage} messagePageViewModel.composingMessage
}
on:enter={
messagePageViewModel.sendMessage
}
placeholder={ placeholder={
translations.chatPage.message.composerInputPlaceholder translations.chatPage.message
.composerInputPlaceholder
} }
></input> ></input>
<button <button
class="primary" class="primary"
aria-label={ aria-label={
translations.chatPage.message.sendMessageButtonAudioLabel translations.chatPage.message
.sendMessageButtonAudioLabel
}
on:click={
messagePageViewModel.sendMessage
}
toggle:disabled={
messagePageViewModel.cannotSendMessage
} }
on:click={messagePageViewModel.sendMessage}
toggle:disabled={messagePageViewModel.cannotSendMessage}
> >
<span class="icon">send</span> <span class="icon">send</span>
</button> </button>
+55 -19
View File
@@ -11,7 +11,7 @@ export function SettingsPage(settingsPageViewModel: SettingsPageViewModel) {
settingsPageViewModel.loadData(); settingsPageViewModel.loadData();
const secondaryChannelConverter: React.StateItemConverter<string> = ( const secondaryChannelConverter: React.StateItemConverter<string> = (
secondaryChannel: string secondaryChannel: string,
) => { ) => {
return DeletableListItem(secondaryChannel, <span></span>, () => { return DeletableListItem(secondaryChannel, <span></span>, () => {
settingsPageViewModel.removeSecondaryChannel(secondaryChannel); settingsPageViewModel.removeSecondaryChannel(secondaryChannel);
@@ -32,11 +32,18 @@ export function SettingsPage(settingsPageViewModel: SettingsPageViewModel) {
<span class="icon">forum</span> <span class="icon">forum</span>
<div> <div>
<span> <span>
{translations.chatPage.settings.primaryChannelLabel} {
translations.chatPage.settings
.primaryChannelLabel
}
</span> </span>
<input <input
bind:value={settingsPageViewModel.primaryChannelInput} bind:value={
on:enter={settingsPageViewModel.setPrimaryChannel} settingsPageViewModel.primaryChannelInput
}
on:enter={
settingsPageViewModel.setPrimaryChannel
}
></input> ></input>
</div> </div>
</label> </label>
@@ -47,8 +54,12 @@ export function SettingsPage(settingsPageViewModel: SettingsPageViewModel) {
translations.chatPage.settings translations.chatPage.settings
.setPrimaryChannelButtonAudioLabel .setPrimaryChannelButtonAudioLabel
} }
on:click={settingsPageViewModel.setPrimaryChannel} on:click={
toggle:disabled={settingsPageViewModel.cannotSetPrimaryChannel} settingsPageViewModel.setPrimaryChannel
}
toggle:disabled={
settingsPageViewModel.cannotSetPrimaryChannel
}
> >
{translations.general.setButton} {translations.general.setButton}
<span class="icon">check</span> <span class="icon">check</span>
@@ -60,13 +71,19 @@ export function SettingsPage(settingsPageViewModel: SettingsPageViewModel) {
<div class="flex-row width-input margin-bottom"> <div class="flex-row width-input margin-bottom">
<input <input
aria-label={ aria-label={
translations.chatPage.settings.newSecondaryChannelAudioLabel translations.chatPage.settings
.newSecondaryChannelAudioLabel
} }
placeholder={ placeholder={
translations.chatPage.settings.newSecondaryChannelPlaceholder translations.chatPage.settings
.newSecondaryChannelPlaceholder
}
bind:value={
settingsPageViewModel.newSecondaryChannelInput
}
on:enter={
settingsPageViewModel.addSecondaryChannel
} }
bind:value={settingsPageViewModel.newSecondaryChannelInput}
on:enter={settingsPageViewModel.addSecondaryChannel}
></input> ></input>
<button <button
class="primary" class="primary"
@@ -74,7 +91,9 @@ export function SettingsPage(settingsPageViewModel: SettingsPageViewModel) {
translations.chatPage.settings translations.chatPage.settings
.addSecondaryChannelButtonAudioLabel .addSecondaryChannelButtonAudioLabel
} }
on:click={settingsPageViewModel.addSecondaryChannel} on:click={
settingsPageViewModel.addSecondaryChannel
}
toggle:disabled={ toggle:disabled={
settingsPageViewModel.cannotAddSecondaryChannel settingsPageViewModel.cannotAddSecondaryChannel
} }
@@ -96,11 +115,22 @@ export function SettingsPage(settingsPageViewModel: SettingsPageViewModel) {
<label class="tile flex-no"> <label class="tile flex-no">
<span class="icon">key</span> <span class="icon">key</span>
<div> <div>
<span>{translations.chatPage.settings.encryptionKeyLabel}</span> <span>
{
translations.chatPage.settings
.encryptionKeyLabel
}
</span>
<input <input
bind:value={settingsPageViewModel.encryptionKeyInput} bind:value={
on:enter={settingsPageViewModel.setEncryptionKey} settingsPageViewModel.encryptionKeyInput
set:type={settingsPageViewModel.encryptionKeyInputType} }
on:enter={
settingsPageViewModel.setEncryptionKey
}
set:type={
settingsPageViewModel.encryptionKeyInputType
}
></input> ></input>
</div> </div>
</label> </label>
@@ -111,8 +141,12 @@ export function SettingsPage(settingsPageViewModel: SettingsPageViewModel) {
translations.chatPage.settings translations.chatPage.settings
.setEncryptionKeyButtonAudioLabel .setEncryptionKeyButtonAudioLabel
} }
on:click={settingsPageViewModel.setEncryptionKey} on:click={
toggle:disabled={settingsPageViewModel.cannotSetEncryptionKey} settingsPageViewModel.setEncryptionKey
}
toggle:disabled={
settingsPageViewModel.cannotSetEncryptionKey
}
> >
{translations.general.setButton} {translations.general.setButton}
<span class="icon">check</span> <span class="icon">check</span>
@@ -122,7 +156,9 @@ export function SettingsPage(settingsPageViewModel: SettingsPageViewModel) {
<label class="inline"> <label class="inline">
<input <input
type="checkbox" type="checkbox"
bind:checked={settingsPageViewModel.shouldShowEncryptionKey} bind:checked={
settingsPageViewModel.shouldShowEncryptionKey
}
></input> ></input>
{translations.chatPage.settings.showEncryptionKey} {translations.chatPage.settings.showEncryptionKey}
</label> </label>
@@ -137,7 +173,7 @@ export function SettingsPage(settingsPageViewModel: SettingsPageViewModel) {
{DangerousActionButton( {DangerousActionButton(
translations.chatPage.settings.deleteChatButton, translations.chatPage.settings.deleteChatButton,
"chat_error", "chat_error",
settingsPageViewModel.remove settingsPageViewModel.remove,
)} )}
</div> </div>
</div> </div>
+16 -6
View File
@@ -12,7 +12,7 @@ export function TaskPage(taskPageViewModel: TaskPageViewModel) {
const isShowingBoard = React.createProxyState( const isShowingBoard = React.createProxyState(
[taskPageViewModel.selectedBoardId], [taskPageViewModel.selectedBoardId],
() => taskPageViewModel.selectedBoardId.value != undefined () => taskPageViewModel.selectedBoardId.value != undefined,
); );
const paneContent = React.createProxyState( const paneContent = React.createProxyState(
@@ -41,11 +41,15 @@ export function TaskPage(taskPageViewModel: TaskPageViewModel) {
} }
return BoardPage(selectedBoard); return BoardPage(selectedBoard);
} },
); );
return ( return (
<div id="task-page" toggle:isshowingboard={isShowingBoard} set:showingboardlist={taskPageViewModel.isShowingBoadList}> <div
id="task-page"
toggle:isshowingboard={isShowingBoard}
set:showingboardlist={taskPageViewModel.isShowingBoadList}
>
<div <div
id="board-list" id="board-list"
class="pane-wrapper side background" class="pane-wrapper side background"
@@ -58,15 +62,21 @@ export function TaskPage(taskPageViewModel: TaskPageViewModel) {
class="no-outline" class="no-outline"
bind:value={taskPageViewModel.newBoardNameInput} bind:value={taskPageViewModel.newBoardNameInput}
on:enter={taskPageViewModel.createBoard} on:enter={taskPageViewModel.createBoard}
placeholder={translations.chatPage.task.newBoardNamePlaceholder} placeholder={
translations.chatPage.task
.newBoardNamePlaceholder
}
></input> ></input>
<button <button
class="primary" class="primary"
aria-label={ aria-label={
translations.chatPage.task.createBoardButtonAudioLabel translations.chatPage.task
.createBoardButtonAudioLabel
} }
on:click={taskPageViewModel.createBoard} on:click={taskPageViewModel.createBoard}
toggle:disabled={taskPageViewModel.cannotCreateBoard} toggle:disabled={
taskPageViewModel.cannotCreateBoard
}
> >
<span class="icon">add</span> <span class="icon">add</span>
</button> </button>
+5 -2
View File
@@ -13,7 +13,10 @@ export function BoardEntry(boardViewModel: BoardViewModel) {
on:dragover={allowDrop} on:dragover={allowDrop}
on:drop={boardViewModel.handleDropBetweenBoards} on:drop={boardViewModel.handleDropBetweenBoards}
> >
<span class="shadow" subscribe:innerText={boardViewModel.name}></span> <span
class="shadow"
subscribe:innerText={boardViewModel.name}
></span>
<b subscribe:innerText={boardViewModel.name}></b> <b subscribe:innerText={boardViewModel.name}></b>
</button> </button>
); );
@@ -26,7 +29,7 @@ export function BoardEntry(boardViewModel: BoardViewModel) {
} }
export const BoardViewModelToEntry: React.StateItemConverter<BoardViewModel> = ( export const BoardViewModelToEntry: React.StateItemConverter<BoardViewModel> = (
boardViewModel: BoardViewModel boardViewModel: BoardViewModel,
) => { ) => {
return BoardEntry(boardViewModel); return BoardEntry(boardViewModel);
}; };
@@ -10,7 +10,7 @@ export function BoardViewToggleButton(
label: string, label: string,
icon: string, icon: string,
page: BoardPageType, page: BoardPageType,
boardViewModel: BoardViewModel boardViewModel: BoardViewModel,
) { ) {
function select() { function select() {
boardViewModel.selectedPage.value = page; boardViewModel.selectedPage.value = page;
@@ -18,7 +18,7 @@ export function BoardViewToggleButton(
const isSelected = React.createProxyState( const isSelected = React.createProxyState(
[boardViewModel.selectedPage], [boardViewModel.selectedPage],
() => boardViewModel.selectedPage.value == page () => boardViewModel.selectedPage.value == page,
); );
return RibbonButton(label, icon, isSelected, select); return RibbonButton(label, icon, isSelected, select);
+6 -2
View File
@@ -13,10 +13,14 @@ export function ChatEntry(chatViewModel: ChatViewModel) {
> >
<span <span
class="shadow" class="shadow"
subscribe:innerText={chatViewModel.settingsPageViewModel.primaryChannel} subscribe:innerText={
chatViewModel.settingsPageViewModel.primaryChannel
}
></span> ></span>
<h2 <h2
subscribe:innerText={chatViewModel.settingsPageViewModel.primaryChannel} subscribe:innerText={
chatViewModel.settingsPageViewModel.primaryChannel
}
></h2> ></h2>
</button> </button>
); );
+7 -3
View File
@@ -19,7 +19,7 @@ export function ChatMessage(chatMessageViewModel: ChatMessageViewModel) {
default: default:
return "warning"; return "warning";
} }
} },
); );
return ( return (
@@ -37,7 +37,10 @@ export function ChatMessage(chatMessageViewModel: ChatMessageViewModel) {
subscribe:innerText={chatMessageViewModel.body} subscribe:innerText={chatMessageViewModel.body}
></span> ></span>
<span class="timestamp ellipsis"> <span class="timestamp ellipsis">
<span class="icon" subscribe:innerText={statusIcon}></span> <span
class="icon"
subscribe:innerText={statusIcon}
></span>
{chatMessageViewModel.dateSent} {chatMessageViewModel.dateSent}
</span> </span>
</div> </div>
@@ -46,7 +49,8 @@ export function ChatMessage(chatMessageViewModel: ChatMessageViewModel) {
<button <button
on:click={chatMessageViewModel.showInfoModal} on:click={chatMessageViewModel.showInfoModal}
aria-label={ aria-label={
translations.chatPage.message.showMessageInfoButtonAudioLabel translations.chatPage.message
.showMessageInfoButtonAudioLabel
} }
> >
<span class="icon">info</span> <span class="icon">info</span>
+2 -2
View File
@@ -10,7 +10,7 @@ export function ChatViewToggleButton(
label: string, label: string,
icon: string, icon: string,
page: ChatPageType, page: ChatPageType,
chatViewModel: ChatViewModel chatViewModel: ChatViewModel,
) { ) {
function select() { function select() {
chatViewModel.selectedPage.value = page; chatViewModel.selectedPage.value = page;
@@ -18,7 +18,7 @@ export function ChatViewToggleButton(
const isSelected = React.createProxyState( const isSelected = React.createProxyState(
[chatViewModel.selectedPage], [chatViewModel.selectedPage],
() => chatViewModel.selectedPage.value == page () => chatViewModel.selectedPage.value == page,
); );
return RibbonButton(label, icon, isSelected, select); return RibbonButton(label, icon, isSelected, select);
+1 -1
View File
@@ -8,7 +8,7 @@ export function ColorPicker(selectedColor: React.State<Color>) {
{...Object.values(Color).map((color) => { {...Object.values(Color).map((color) => {
const isSelected = React.createProxyState( const isSelected = React.createProxyState(
[selectedColor], [selectedColor],
() => selectedColor.value == color () => selectedColor.value == color,
); );
function setColor() { function setColor() {
@@ -5,12 +5,12 @@ import { translations } from "../translations";
export function DangerousActionButton( export function DangerousActionButton(
label: string, label: string,
icon: string, icon: string,
action: () => void action: () => void,
) { ) {
const isActionRequested = new React.State(false); const isActionRequested = new React.State(false);
const cannotConfirm = React.createProxyState( const cannotConfirm = React.createProxyState(
[isActionRequested], [isActionRequested],
() => isActionRequested.value == false () => isActionRequested.value == false,
); );
function requestAction() { function requestAction() {
+1 -1
View File
@@ -5,7 +5,7 @@ import { translations } from "../translations";
export function DeletableListItem( export function DeletableListItem(
text: string, text: string,
primaryButton: HTMLElement, primaryButton: HTMLElement,
ondelete: () => void ondelete: () => void,
) { ) {
return ( return (
<div class="tile flex-row justify-apart align-center padding-0"> <div class="tile flex-row justify-apart align-center padding-0">
+3 -3
View File
@@ -8,11 +8,11 @@ import StorageViewModel from "../../ViewModel/Global/storageViewModel";
export function DirectoryItemList( export function DirectoryItemList(
storageViewModel: StorageViewModel, storageViewModel: StorageViewModel,
pathString: string = PATH_COMPONENT_SEPARATOR pathString: string = PATH_COMPONENT_SEPARATOR,
) { ) {
// converter // converter
const StringToDirectoryItemList: React.StateItemConverter<string> = ( const StringToDirectoryItemList: React.StateItemConverter<string> = (
pathString: string pathString: string,
) => DirectoryItemList(storageViewModel, pathString); ) => DirectoryItemList(storageViewModel, pathString);
// data // data
@@ -50,7 +50,7 @@ export function DirectoryItemList(
// state // state
const isSelected = React.createProxyState( const isSelected = React.createProxyState(
[storageViewModel.selectedPath], [storageViewModel.selectedPath],
() => storageViewModel.selectedPath.value == pathString () => storageViewModel.selectedPath.value == pathString,
); );
isSelected.subscribe(() => { isSelected.subscribe(() => {
+1 -1
View File
@@ -45,7 +45,7 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
font-size: .9rem; font-size: 0.9rem;
} }
@media (min-width: 640px) { @media (min-width: 640px) {
+18 -8
View File
@@ -15,14 +15,16 @@ export function FileBrowser(storageViewModel: StorageViewModel) {
if (storageViewModel.selectedPath.value == PATH_COMPONENT_SEPARATOR) if (storageViewModel.selectedPath.value == PATH_COMPONENT_SEPARATOR)
return ( return (
<div> <div>
<span class="secondary">{translations.storage.noItemSelected}</span> <span class="secondary">
{translations.storage.noItemSelected}
</span>
<hr></hr> <hr></hr>
{DangerousActionButton( {DangerousActionButton(
translations.storage.removeJunkButton, translations.storage.removeJunkButton,
"delete_forever", "delete_forever",
storageViewModel.removeJunk storageViewModel.removeJunk,
)} )}
</div> </div>
); );
@@ -34,7 +36,9 @@ export function FileBrowser(storageViewModel: StorageViewModel) {
<b>{translations.storage.path}</b> <b>{translations.storage.path}</b>
<span <span
class="break-all" class="break-all"
subscribe:innerText={storageViewModel.selectedPath} subscribe:innerText={
storageViewModel.selectedPath
}
></span> ></span>
</div> </div>
</div> </div>
@@ -42,7 +46,9 @@ export function FileBrowser(storageViewModel: StorageViewModel) {
<div> <div>
<b>{translations.storage.content}</b> <b>{translations.storage.content}</b>
<code <code
subscribe:innerText={storageViewModel.selectedFileContent} subscribe:innerText={
storageViewModel.selectedFileContent
}
></code> ></code>
</div> </div>
</div> </div>
@@ -50,22 +56,26 @@ export function FileBrowser(storageViewModel: StorageViewModel) {
{DangerousActionButton( {DangerousActionButton(
translations.storage.deleteItem, translations.storage.deleteItem,
"delete_forever", "delete_forever",
storageViewModel.deleteSelectedItem storageViewModel.deleteSelectedItem,
)} )}
</div> </div>
); );
} },
); );
const view = ( const view = (
<div class="file-browser"> <div class="file-browser">
<div> <div>
<div class="scroll-area">{DirectoryItemList(storageViewModel)}</div> <div class="scroll-area">
{DirectoryItemList(storageViewModel)}
</div>
<div class="detail-button-wrapper"> <div class="detail-button-wrapper">
<button class="ghost" on:click={scrollToDetails}> <button class="ghost" on:click={scrollToDetails}>
<span <span
class="ellipsis" class="ellipsis"
subscribe:innerText={storageViewModel.selectedFileName} subscribe:innerText={
storageViewModel.selectedFileName
}
></span> ></span>
<span class="icon">arrow_forward</span> <span class="icon">arrow_forward</span>
</button> </button>
+2 -2
View File
@@ -9,14 +9,14 @@ export function FilteredList<T>(
reference: StringEntryObject, reference: StringEntryObject,
stringEntryObjectConverter: (object: T) => StringEntryObject, stringEntryObjectConverter: (object: T) => StringEntryObject,
objects: React.ListState<T> | React.MapState<T>, objects: React.ListState<T> | React.MapState<T>,
viewBuilder: (matchingObjects: React.ListState<T>) => HTMLElement viewBuilder: (matchingObjects: React.ListState<T>) => HTMLElement,
) { ) {
const matchingObjects: React.ListState<T> = new React.ListState(); const matchingObjects: React.ListState<T> = new React.ListState();
objects.handleAddition((newObject) => { objects.handleAddition((newObject) => {
const doesMatch: boolean = checkDoesObjectMatchReference( const doesMatch: boolean = checkDoesObjectMatchReference(
reference, reference,
stringEntryObjectConverter(newObject) stringEntryObjectConverter(newObject),
); );
if (doesMatch == false) return; if (doesMatch == false) return;
+10 -8
View File
@@ -10,7 +10,7 @@ import { localeCompare } from "../../Model/Utility/utility";
export function MonthGrid<T>( export function MonthGrid<T>(
monthGrid: MonthGrid<React.MapState<T>>, monthGrid: MonthGrid<React.MapState<T>>,
selectedDate: React.State<number>, selectedDate: React.State<number>,
handleDrop: (date: string) => void handleDrop: (date: string) => void,
) { ) {
const dayLabels: HTMLElement[] = []; const dayLabels: HTMLElement[] = [];
let currentWeekday = monthGrid.firstDayOfWeek; let currentWeekday = monthGrid.firstDayOfWeek;
@@ -18,7 +18,7 @@ export function MonthGrid<T>(
dayLabels.push( dayLabels.push(
<span class="secondary"> <span class="secondary">
{translations.regional.weekdays.abbreviated[currentWeekday]} {translations.regional.weekdays.abbreviated[currentWeekday]}
</span> </span>,
); );
currentWeekday++; currentWeekday++;
if (currentWeekday == 7) currentWeekday = 0; if (currentWeekday == 7) currentWeekday = 0;
@@ -30,7 +30,7 @@ export function MonthGrid<T>(
} }
const converter: React.StateItemConverter<TaskViewModel> = ( const converter: React.StateItemConverter<TaskViewModel> = (
taskViewModel taskViewModel,
) => { ) => {
const view = ( const view = (
<span class="ellipsis secondary">{taskViewModel.task.name}</span> <span class="ellipsis secondary">{taskViewModel.task.name}</span>
@@ -55,21 +55,23 @@ export function MonthGrid<T>(
const isSelected = React.createProxyState( const isSelected = React.createProxyState(
[selectedDate], [selectedDate],
() => selectedDate.value == parseInt(date) () => selectedDate.value == parseInt(date),
); );
const isToday: boolean = const isToday: boolean =
monthGrid.isCurrentMonth == true && monthGrid.isCurrentMonth == true &&
parseInt(date) == new Date().getDate(); parseInt(date) == new Date().getDate();
const eventCount: React.State<number> = React.createProxyState( const eventCount: React.State<number> =
React.createProxyState(
[mapState], [mapState],
() => mapState.value.size () => mapState.value.size,
); );
const hasEvents: React.State<boolean> = React.createProxyState( const hasEvents: React.State<boolean> =
React.createProxyState(
[eventCount], [eventCount],
() => eventCount.value != 0 () => eventCount.value != 0,
); );
function select() { function select() {
+3 -3
View File
@@ -9,19 +9,19 @@ export function Option(text: string, value: string, selectedOnCreate: boolean) {
} }
export const StringToOption: React.StateItemConverter<string> = ( export const StringToOption: React.StateItemConverter<string> = (
string: string string: string,
) => { ) => {
return Option(string, string, false); return Option(string, string, false);
}; };
export const EntryToOption: React.StateItemConverter<Entry> = ( export const EntryToOption: React.StateItemConverter<Entry> = (
entry: Entry entry: Entry,
) => { ) => {
return Option(entry[1], entry[0], false); return Option(entry[1], entry[0], false);
}; };
export const VersionIdToOption: React.StateItemConverter<string> = ( export const VersionIdToOption: React.StateItemConverter<string> = (
versionId: string versionId: string,
) => { ) => {
const [date, rest] = versionId.split("T"); const [date, rest] = versionId.split("T");
const [time] = rest.split("."); const [time] = rest.split(".");
+11 -10
View File
@@ -12,17 +12,18 @@ export function PropertyValueList<T>(
objects: React.ListState<T> | React.MapState<T>, objects: React.ListState<T> | React.MapState<T>,
viewBuilder: ( viewBuilder: (
keys: React.ListState<string>, keys: React.ListState<string>,
sortedKeys: React.State<string[]> sortedKeys: React.State<string[]>,
) => HTMLElement ) => HTMLElement,
) { ) {
const propertyValues: React.ListState<string> = new React.ListState(); const propertyValues: React.ListState<string> = new React.ListState();
const sortedPropertyValues: React.State<string[]> = createSortedPropertyValueState(propertyValues) const sortedPropertyValues: React.State<string[]> =
createSortedPropertyValueState(propertyValues);
objects.subscribe(() => { objects.subscribe(() => {
collectPropertyValuesToState( collectPropertyValuesToState(
propertyKey, propertyKey,
stringEntryObjectConverter, stringEntryObjectConverter,
objects, objects,
propertyValues propertyValues,
); );
}); });
@@ -33,12 +34,12 @@ export function collectPropertyValuesToState<T>(
propertyKey: string, propertyKey: string,
stringEntryObjectConverter: (object: T) => StringEntryObject, stringEntryObjectConverter: (object: T) => StringEntryObject,
objects: React.ListState<T> | React.MapState<T>, objects: React.ListState<T> | React.MapState<T>,
propertyValues: React.ListState<string> propertyValues: React.ListState<string>,
) { ) {
const values: string[] = collectObjectValuesForKey( const values: string[] = collectObjectValuesForKey(
propertyKey, propertyKey,
stringEntryObjectConverter, stringEntryObjectConverter,
[...objects.value.values()] [...objects.value.values()],
); );
for (const existingValue of values) { for (const existingValue of values) {
if (propertyValues.value.has(existingValue)) continue; if (propertyValues.value.has(existingValue)) continue;
@@ -53,18 +54,18 @@ export function collectPropertyValuesToState<T>(
} }
export function createSortedPropertyValueState( export function createSortedPropertyValueState(
propertyValues: React.ListState<string> propertyValues: React.ListState<string>,
): React.State<string[]> { ): React.State<string[]> {
return React.createProxyState([propertyValues], () => return React.createProxyState([propertyValues], () =>
[...propertyValues.value.values()].sort(localeCompare) [...propertyValues.value.values()].sort(localeCompare),
); );
} }
export function createPropertyValueIndexState( export function createPropertyValueIndexState(
sortedKeys: React.State<string[]>, sortedKeys: React.State<string[]>,
key: string key: string,
): React.State<number> { ): React.State<number> {
return React.createProxyState([sortedKeys], () => return React.createProxyState([sortedKeys], () =>
sortedKeys.value.indexOf(key) sortedKeys.value.indexOf(key),
); );
} }
+1 -1
View File
@@ -8,7 +8,7 @@ export function RibbonButton(
label: string, label: string,
icon: string, icon: string,
isSelected: React.State<boolean>, isSelected: React.State<boolean>,
select: () => void select: () => void,
) { ) {
return ( return (
<button <button
+5 -2
View File
@@ -21,7 +21,10 @@ export function TaskEntry(taskViewModel: TaskViewModel) {
on:dragstart={taskViewModel.dragStart} on:dragstart={taskViewModel.dragStart}
> >
<div> <div>
<b class="ellipsis" subscribe:innerText={taskViewModel.name}></b> <b
class="ellipsis"
subscribe:innerText={taskViewModel.name}
></b>
<hr></hr> <hr></hr>
<div <div
class="grid secondary" class="grid secondary"
@@ -51,7 +54,7 @@ export function TaskEntry(taskViewModel: TaskViewModel) {
} }
export const TaskViewModelToEntry: React.StateItemConverter<TaskViewModel> = ( export const TaskViewModelToEntry: React.StateItemConverter<TaskViewModel> = (
taskViewModel: TaskViewModel taskViewModel: TaskViewModel,
) => { ) => {
return TaskEntry(taskViewModel); return TaskEntry(taskViewModel);
}; };
+2 -2
View File
@@ -1,7 +1,7 @@
import * as React from "bloatless-react"; import * as React from "bloatless-react";
export const StringToTextSpan: React.StateItemConverter<string> = ( export const StringToTextSpan: React.StateItemConverter<string> = (
string: string string: string,
) => { ) => {
return <span class="ellipsis">{string}</span> return <span class="ellipsis">{string}</span>;
}; };
+8 -3
View File
@@ -7,7 +7,10 @@ import { translations } from "../translations";
export function BoardSettingsModal(boardViewModel: BoardViewModel) { export function BoardSettingsModal(boardViewModel: BoardViewModel) {
return ( return (
<div class="modal" toggle:open={boardViewModel.isPresentingSettingsModal}> <div
class="modal"
toggle:open={boardViewModel.isPresentingSettingsModal}
>
<div> <div>
<main> <main>
<h2>{translations.chatPage.task.boardSettingsHeadline}</h2> <h2>{translations.chatPage.task.boardSettingsHeadline}</h2>
@@ -15,7 +18,9 @@ export function BoardSettingsModal(boardViewModel: BoardViewModel) {
<label class="tile flex-no"> <label class="tile flex-no">
<span class="icon">label</span> <span class="icon">label</span>
<div> <div>
<span>{translations.chatPage.task.boardNameInputLabel}</span> <span>
{translations.chatPage.task.boardNameInputLabel}
</span>
<input <input
on:enter={boardViewModel.saveSettings} on:enter={boardViewModel.saveSettings}
bind:value={boardViewModel.name} bind:value={boardViewModel.name}
@@ -33,7 +38,7 @@ export function BoardSettingsModal(boardViewModel: BoardViewModel) {
{DangerousActionButton( {DangerousActionButton(
translations.chatPage.task.deleteBoardButton, translations.chatPage.task.deleteBoardButton,
"delete_forever", "delete_forever",
boardViewModel.deleteBoard boardViewModel.deleteBoard,
)} )}
</div> </div>
</main> </main>
+32 -10
View File
@@ -4,10 +4,13 @@ import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel";
import { translations } from "../translations"; import { translations } from "../translations";
export function ChatMessageInfoModal( export function ChatMessageInfoModal(
chatMessageViewModel: ChatMessageViewModel chatMessageViewModel: ChatMessageViewModel,
) { ) {
return ( return (
<div class="modal" toggle:open={chatMessageViewModel.isPresentingInfoModal}> <div
class="modal"
toggle:open={chatMessageViewModel.isPresentingInfoModal}
>
<div> <div>
<main> <main>
<h2>{translations.chatPage.message.messageInfoHeadline}</h2> <h2>{translations.chatPage.message.messageInfoHeadline}</h2>
@@ -16,34 +19,53 @@ export function ChatMessageInfoModal(
<div class="tile"> <div class="tile">
<span class="icon">account_circle</span> <span class="icon">account_circle</span>
<div> <div>
<span>{translations.chatPage.message.sentBy}</span> <span>
<b class="break-word">{chatMessageViewModel.sender}</b> {translations.chatPage.message.sentBy}
</span>
<b class="break-word">
{chatMessageViewModel.sender}
</b>
</div> </div>
</div> </div>
<div class="tile"> <div class="tile">
<span class="icon">schedule</span> <span class="icon">schedule</span>
<div> <div>
<span>{translations.chatPage.message.timeSent}</span> <span>
<b class="break-word">{chatMessageViewModel.dateSent}</b> {translations.chatPage.message.timeSent}
</span>
<b class="break-word">
{chatMessageViewModel.dateSent}
</b>
</div> </div>
</div> </div>
<div class="tile"> <div class="tile">
<span class="icon">forum</span> <span class="icon">forum</span>
<div> <div>
<span>{translations.chatPage.message.channel}</span> <span>
<b class="break-word">{chatMessageViewModel.channel}</b> {translations.chatPage.message.channel}
</span>
<b class="break-word">
{chatMessageViewModel.channel}
</b>
</div> </div>
</div> </div>
<div class="tile"> <div class="tile">
<span class="icon">description</span> <span class="icon">description</span>
<div> <div>
<span>{translations.chatPage.message.messageContent}</span> <span>
{
translations.chatPage.message
.messageContent
}
</span>
<b <b
class="break-word" class="break-word"
subscribe:innerText={chatMessageViewModel.body} subscribe:innerText={
chatMessageViewModel.body
}
></b> ></b>
</div> </div>
</div> </div>
+9 -5
View File
@@ -6,7 +6,7 @@ import { translations } from "../translations";
export function ConnectionModal(connectionViewModel: ConnectionViewModel) { export function ConnectionModal(connectionViewModel: ConnectionViewModel) {
const previousAddressConverter: React.StateItemConverter<string> = ( const previousAddressConverter: React.StateItemConverter<string> = (
address: string address: string,
) => { ) => {
function connnect() { function connnect() {
connectionViewModel.connectToAddress(address); connectionViewModel.connectToAddress(address);
@@ -16,7 +16,7 @@ export function ConnectionModal(connectionViewModel: ConnectionViewModel) {
[connectionViewModel.isConnected], [connectionViewModel.isConnected],
() => () =>
connectionViewModel.isConnected.value == true && connectionViewModel.isConnected.value == true &&
connectionViewModel.connectionModel.address == address connectionViewModel.connectionModel.address == address,
); );
return DeletableListItem( return DeletableListItem(
@@ -25,13 +25,15 @@ export function ConnectionModal(connectionViewModel: ConnectionViewModel) {
class="primary" class="primary"
on:click={connnect} on:click={connnect}
toggle:disabled={cannotConnect} toggle:disabled={cannotConnect}
aria-label={translations.connectionModal.connectButtonAudioLabel} aria-label={
translations.connectionModal.connectButtonAudioLabel
}
> >
<span class="icon">link</span> <span class="icon">link</span>
</button>, </button>,
() => { () => {
connectionViewModel.removePreviousAddress(address); connectionViewModel.removePreviousAddress(address);
} },
); );
}; };
@@ -42,7 +44,9 @@ export function ConnectionModal(connectionViewModel: ConnectionViewModel) {
> >
<div> <div>
<main> <main>
<h2>{translations.connectionModal.connectionModalHeadline}</h2> <h2>
{translations.connectionModal.connectionModalHeadline}
</h2>
<div <div
class="flex-column gap" class="flex-column gap"
+121 -43
View File
@@ -13,11 +13,14 @@ import { translations } from "../translations";
export function DataTransferModalWrapper( export function DataTransferModalWrapper(
connectionViewModel: ConnectionViewModel, connectionViewModel: ConnectionViewModel,
fileTransferViewModel: FileTransferViewModel fileTransferViewModel: FileTransferViewModel,
) { ) {
return ( return (
<div> <div>
{DirectionSelectionModal(connectionViewModel, fileTransferViewModel)} {DirectionSelectionModal(
connectionViewModel,
fileTransferViewModel,
)}
{FileSelectionModal(fileTransferViewModel)} {FileSelectionModal(fileTransferViewModel)}
{TransferDataDisplayModal(fileTransferViewModel)} {TransferDataDisplayModal(fileTransferViewModel)}
{TransferDisplayModal(fileTransferViewModel)} {TransferDisplayModal(fileTransferViewModel)}
@@ -29,27 +32,32 @@ export function DataTransferModalWrapper(
function DirectionSelectionModal( function DirectionSelectionModal(
connectionViewModel: ConnectionViewModel, connectionViewModel: ConnectionViewModel,
fileTransferViewModel: FileTransferViewModel fileTransferViewModel: FileTransferViewModel,
) { ) {
const isPresented = React.createProxyState( const isPresented = React.createProxyState(
[fileTransferViewModel.presentedModal], [fileTransferViewModel.presentedModal],
() => () =>
fileTransferViewModel.presentedModal.value == fileTransferViewModel.presentedModal.value ==
FileTransferModal.DirectionSelection FileTransferModal.DirectionSelection,
); );
const isDisconnected = React.createProxyState( const isDisconnected = React.createProxyState(
[connectionViewModel.isConnected], [connectionViewModel.isConnected],
() => connectionViewModel.isConnected.value == false () => connectionViewModel.isConnected.value == false,
); );
return ( return (
<div class="modal" toggle:open={isPresented}> <div class="modal" toggle:open={isPresented}>
<div> <div>
<main> <main>
<h2>{translations.dataTransferModal.transferDataHeadline}</h2> <h2>
{translations.dataTransferModal.transferDataHeadline}
</h2>
<p class="error" toggle:hidden={connectionViewModel.isConnected}> <p
class="error"
toggle:hidden={connectionViewModel.isConnected}
>
{translations.dataTransferModal.notConnectedError} {translations.dataTransferModal.notConnectedError}
</p> </p>
@@ -59,21 +67,35 @@ function DirectionSelectionModal(
> >
<button <button
class="tile" class="tile"
on:click={fileTransferViewModel.showFileSelectionModal} on:click={
fileTransferViewModel.showFileSelectionModal
}
> >
<span class="icon">upload</span> <span class="icon">upload</span>
<div> <div>
<b>{translations.dataTransferModal.fromThisDeviceButton}</b> <b>
{
translations.dataTransferModal
.fromThisDeviceButton
}
</b>
</div> </div>
<span class="icon">arrow_forward</span> <span class="icon">arrow_forward</span>
</button> </button>
<button <button
class="tile" class="tile"
on:click={fileTransferViewModel.showTransferDataInputModal} on:click={
fileTransferViewModel.showTransferDataInputModal
}
> >
<span class="icon">download</span> <span class="icon">download</span>
<div> <div>
<b>{translations.dataTransferModal.toThisDeviceButton}</b> <b>
{
translations.dataTransferModal
.toThisDeviceButton
}
</b>
</div> </div>
<span class="icon">arrow_forward</span> <span class="icon">arrow_forward</span>
</button> </button>
@@ -91,14 +113,14 @@ function DirectionSelectionModal(
function FileSelectionModal(fileTransferViewModel: FileTransferViewModel) { function FileSelectionModal(fileTransferViewModel: FileTransferViewModel) {
// option // option
const OptionConverter: React.StateItemConverter<FileTransferOption> = ( const OptionConverter: React.StateItemConverter<FileTransferOption> = (
fileOption: FileTransferOption fileOption: FileTransferOption,
) => { ) => {
return OptionEntry(fileOption, fileTransferViewModel); return OptionEntry(fileOption, fileTransferViewModel);
}; };
function OptionEntry( function OptionEntry(
fileOption: FileTransferOption, fileOption: FileTransferOption,
fileTransferViewModel: FileTransferViewModel fileTransferViewModel: FileTransferViewModel,
) { ) {
const isSelected = new React.State(false); const isSelected = new React.State(false);
if (fileTransferViewModel.selectedPaths.value.has(fileOption.path)) { if (fileTransferViewModel.selectedPaths.value.has(fileOption.path)) {
@@ -120,7 +142,9 @@ function FileSelectionModal(fileTransferViewModel: FileTransferViewModel) {
<div> <div>
<b class="ellipsis">{fileOption.label}</b> <b class="ellipsis">{fileOption.label}</b>
<span class="secondary ellipsis"> <span class="secondary ellipsis">
{StorageModel.pathComponentsToString(...fileOption.path)} {StorageModel.pathComponentsToString(
...fileOption.path,
)}
</span> </span>
</div> </div>
</button> </button>
@@ -132,14 +156,16 @@ function FileSelectionModal(fileTransferViewModel: FileTransferViewModel) {
[fileTransferViewModel.presentedModal], [fileTransferViewModel.presentedModal],
() => () =>
fileTransferViewModel.presentedModal.value == fileTransferViewModel.presentedModal.value ==
FileTransferModal.FileSelection FileTransferModal.FileSelection,
); );
return ( return (
<div class="modal" toggle:open={isPresented}> <div class="modal" toggle:open={isPresented}>
<div> <div>
<main> <main>
<h2>{translations.dataTransferModal.transferDataHeadline}</h2> <h2>
{translations.dataTransferModal.transferDataHeadline}
</h2>
<span class="secondary"> <span class="secondary">
{translations.dataTransferModal.selectionDescription} {translations.dataTransferModal.selectionDescription}
</span> </span>
@@ -167,14 +193,18 @@ function FileSelectionModal(fileTransferViewModel: FileTransferViewModel) {
<div class="flex-row width-100"> <div class="flex-row width-100">
<button <button
class="flex" class="flex"
on:click={fileTransferViewModel.showDirectionSelectionModal} on:click={
fileTransferViewModel.showDirectionSelectionModal
}
> >
{translations.general.backButton} {translations.general.backButton}
</button> </button>
<button <button
class="primary flex" class="primary flex"
on:click={fileTransferViewModel.showTransferDataModal} on:click={fileTransferViewModel.showTransferDataModal}
toggle:disabled={fileTransferViewModel.hasNoPathsSelected} toggle:disabled={
fileTransferViewModel.hasNoPathsSelected
}
> >
{translations.general.continueButton} {translations.general.continueButton}
<span class="icon">arrow_forward</span> <span class="icon">arrow_forward</span>
@@ -186,20 +216,22 @@ function FileSelectionModal(fileTransferViewModel: FileTransferViewModel) {
} }
function TransferDataDisplayModal( function TransferDataDisplayModal(
fileTransferViewModel: FileTransferViewModel fileTransferViewModel: FileTransferViewModel,
) { ) {
const isPresented = React.createProxyState( const isPresented = React.createProxyState(
[fileTransferViewModel.presentedModal], [fileTransferViewModel.presentedModal],
() => () =>
fileTransferViewModel.presentedModal.value == fileTransferViewModel.presentedModal.value ==
FileTransferModal.TransferDataDisplay FileTransferModal.TransferDataDisplay,
); );
return ( return (
<div class="modal" toggle:open={isPresented}> <div class="modal" toggle:open={isPresented}>
<div> <div>
<main> <main>
<h2>{translations.dataTransferModal.transferDataHeadline}</h2> <h2>
{translations.dataTransferModal.transferDataHeadline}
</h2>
<span class="secondary"> <span class="secondary">
{translations.dataTransferModal.dataEntryDescription} {translations.dataTransferModal.dataEntryDescription}
@@ -212,10 +244,15 @@ function TransferDataDisplayModal(
<span class="icon">forum</span> <span class="icon">forum</span>
<div> <div>
<span class="secondary"> <span class="secondary">
{translations.dataTransferModal.transferChannelHeadline} {
translations.dataTransferModal
.transferChannelHeadline
}
</span> </span>
<b <b
subscribe:innerText={fileTransferViewModel.transferChannel} subscribe:innerText={
fileTransferViewModel.transferChannel
}
></b> ></b>
</div> </div>
</div> </div>
@@ -223,9 +260,16 @@ function TransferDataDisplayModal(
<span class="icon">key</span> <span class="icon">key</span>
<div> <div>
<span class="secondary"> <span class="secondary">
{translations.dataTransferModal.transferKeyHeadline} {
translations.dataTransferModal
.transferKeyHeadline
}
</span> </span>
<b subscribe:innerText={fileTransferViewModel.transferKey}></b> <b
subscribe:innerText={
fileTransferViewModel.transferKey
}
></b>
</div> </div>
</div> </div>
</div> </div>
@@ -255,23 +299,29 @@ function TransferDisplayModal(fileTransferViewModel: FileTransferViewModel) {
[fileTransferViewModel.presentedModal], [fileTransferViewModel.presentedModal],
() => () =>
fileTransferViewModel.presentedModal.value == fileTransferViewModel.presentedModal.value ==
FileTransferModal.TransferDisplay FileTransferModal.TransferDisplay,
); );
return ( return (
<div class="modal" toggle:open={isPresented}> <div class="modal" toggle:open={isPresented}>
<div> <div>
<main> <main>
<h2>{translations.dataTransferModal.transferDataHeadline}</h2> <h2>
{translations.dataTransferModal.transferDataHeadline}
</h2>
<p <p
class="secondary" class="secondary"
subscribe:innerText={fileTransferViewModel.filesSentText} subscribe:innerText={
fileTransferViewModel.filesSentText
}
></p> ></p>
<p <p
class="secondary" class="secondary"
toggle:hidden={fileTransferViewModel.didNotFinishSending} toggle:hidden={
fileTransferViewModel.didNotFinishSending
}
> >
{translations.dataTransferModal.allFilesSent} {translations.dataTransferModal.allFilesSent}
</p> </p>
@@ -297,7 +347,9 @@ function TransferDisplayModal(fileTransferViewModel: FileTransferViewModel) {
<button <button
class="flex" class="flex"
on:click={fileTransferViewModel.hideModal} on:click={fileTransferViewModel.hideModal}
toggle:disabled={fileTransferViewModel.didNotFinishSending} toggle:disabled={
fileTransferViewModel.didNotFinishSending
}
> >
{translations.general.closeButton} {translations.general.closeButton}
<span class="icon">close</span> <span class="icon">close</span>
@@ -313,17 +365,22 @@ function TransferDataInputModal(fileTransferViewModel: FileTransferViewModel) {
[fileTransferViewModel.presentedModal], [fileTransferViewModel.presentedModal],
() => () =>
fileTransferViewModel.presentedModal.value == fileTransferViewModel.presentedModal.value ==
FileTransferModal.TransferDataInput FileTransferModal.TransferDataInput,
); );
return ( return (
<div class="modal" toggle:open={isPresented}> <div class="modal" toggle:open={isPresented}>
<div> <div>
<main> <main>
<h2>{translations.dataTransferModal.transferDataHeadline}</h2> <h2>
{translations.dataTransferModal.transferDataHeadline}
</h2>
<span class="secondary"> <span class="secondary">
{translations.dataTransferModal.dataEntryInputDescription} {
translations.dataTransferModal
.dataEntryInputDescription
}
</span> </span>
<hr></hr> <hr></hr>
@@ -333,10 +390,15 @@ function TransferDataInputModal(fileTransferViewModel: FileTransferViewModel) {
<span class="icon">forum</span> <span class="icon">forum</span>
<div> <div>
<span class="secondary"> <span class="secondary">
{translations.dataTransferModal.transferChannelHeadline} {
translations.dataTransferModal
.transferChannelHeadline
}
</span> </span>
<input <input
bind:value={fileTransferViewModel.receivingTransferChannel} bind:value={
fileTransferViewModel.receivingTransferChannel
}
></input> ></input>
</div> </div>
</label> </label>
@@ -344,10 +406,15 @@ function TransferDataInputModal(fileTransferViewModel: FileTransferViewModel) {
<span class="icon">key</span> <span class="icon">key</span>
<div> <div>
<span class="secondary"> <span class="secondary">
{translations.dataTransferModal.transferKeyHeadline} {
translations.dataTransferModal
.transferKeyHeadline
}
</span> </span>
<input <input
bind:value={fileTransferViewModel.receivingTransferKey} bind:value={
fileTransferViewModel.receivingTransferKey
}
></input> ></input>
</div> </div>
</label> </label>
@@ -356,14 +423,18 @@ function TransferDataInputModal(fileTransferViewModel: FileTransferViewModel) {
<div class="flex-row width-100"> <div class="flex-row width-100">
<button <button
class="flex" class="flex"
on:click={fileTransferViewModel.showDirectionSelectionModal} on:click={
fileTransferViewModel.showDirectionSelectionModal
}
> >
{translations.general.backButton} {translations.general.backButton}
</button> </button>
<button <button
class="primary flex" class="primary flex"
on:click={fileTransferViewModel.prepareReceivingData} on:click={fileTransferViewModel.prepareReceivingData}
toggle:disabled={fileTransferViewModel.cannotPrepareToReceive} toggle:disabled={
fileTransferViewModel.cannotPrepareToReceive
}
> >
{translations.general.continueButton} {translations.general.continueButton}
<span class="icon">arrow_forward</span> <span class="icon">arrow_forward</span>
@@ -379,22 +450,29 @@ function DataReceptionModal(fileTransferViewModel: FileTransferViewModel) {
[fileTransferViewModel.presentedModal], [fileTransferViewModel.presentedModal],
() => () =>
fileTransferViewModel.presentedModal.value == fileTransferViewModel.presentedModal.value ==
FileTransferModal.ReceptionDisplay FileTransferModal.ReceptionDisplay,
); );
return ( return (
<div class="modal" toggle:open={isPresented}> <div class="modal" toggle:open={isPresented}>
<div> <div>
<main> <main>
<h2>{translations.dataTransferModal.transferDataHeadline}</h2> <h2>
{translations.dataTransferModal.transferDataHeadline}
</h2>
<p class="secondary"> <p class="secondary">
{translations.dataTransferModal.readyToReceiveDescription} {
translations.dataTransferModal
.readyToReceiveDescription
}
</p> </p>
<p <p
class="secondary" class="secondary"
subscribe:innerText={fileTransferViewModel.filesReceivedText} subscribe:innerText={
fileTransferViewModel.filesReceivedText
}
></p> ></p>
<hr></hr> <hr></hr>
+12 -4
View File
@@ -9,7 +9,7 @@ export function SearchModal<T>(
searchViewModel: SearchViewModel<T>, searchViewModel: SearchViewModel<T>,
headline: string, headline: string,
converter: React.StateItemConverter<T>, converter: React.StateItemConverter<T>,
isOpen: React.State<boolean> isOpen: React.State<boolean>,
) { ) {
function close() { function close() {
isOpen.value = false; isOpen.value = false;
@@ -32,11 +32,16 @@ export function SearchModal<T>(
<datalist <datalist
hidden hidden
id={suggestionId} id={suggestionId}
children:append={[searchViewModel.suggestions, StringToOption]} children:append={[
searchViewModel.suggestions,
StringToOption,
]}
></datalist> ></datalist>
<button <button
class="primary" class="primary"
aria-label={translations.general.searchButtonAudioLabel} aria-label={
translations.general.searchButtonAudioLabel
}
on:click={searchViewModel.applySearch} on:click={searchViewModel.applySearch}
toggle:disabled={searchViewModel.cannotApplySearch} toggle:disabled={searchViewModel.cannotApplySearch}
> >
@@ -49,7 +54,10 @@ export function SearchModal<T>(
<div <div
class="grid gap" class="grid gap"
style="grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr))" style="grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr))"
children:append={[searchViewModel.matchingObjects, converter]} children:append={[
searchViewModel.matchingObjects,
converter,
]}
></div> ></div>
</main> </main>
<button on:click={close}> <button on:click={close}>
+55 -17
View File
@@ -18,7 +18,7 @@ export function TaskSettingsModal(taskViewModel: TaskViewModel) {
const statusSuggestionId = v4(); const statusSuggestionId = v4();
const BoardOptionConverter: React.StateItemConverter<Entry> = ( const BoardOptionConverter: React.StateItemConverter<Entry> = (
entry: Entry entry: Entry,
) => { ) => {
const isSelected = entry[0] == taskViewModel.task.boardId; const isSelected = entry[0] == taskViewModel.task.boardId;
return Option(entry[1], entry[0], isSelected); return Option(entry[1], entry[0], isSelected);
@@ -36,7 +36,10 @@ export function TaskSettingsModal(taskViewModel: TaskViewModel) {
<span>{translations.general.fileVersionLabel}</span> <span>{translations.general.fileVersionLabel}</span>
<select <select
bind:value={taskViewModel.selectedVersionId} bind:value={taskViewModel.selectedVersionId}
children:append={[taskViewModel.versionIds, VersionIdToOption]} children:append={[
taskViewModel.versionIds,
VersionIdToOption,
]}
></select> ></select>
<span class="icon">arrow_drop_down</span> <span class="icon">arrow_drop_down</span>
</div> </div>
@@ -47,7 +50,9 @@ export function TaskSettingsModal(taskViewModel: TaskViewModel) {
<label class="tile flex-no"> <label class="tile flex-no">
<span class="icon">label</span> <span class="icon">label</span>
<div> <div>
<span>{translations.chatPage.task.taskNameLabel}</span> <span>
{translations.chatPage.task.taskNameLabel}
</span>
<input bind:value={taskViewModel.name}></input> <input bind:value={taskViewModel.name}></input>
</div> </div>
</label> </label>
@@ -55,11 +60,14 @@ export function TaskSettingsModal(taskViewModel: TaskViewModel) {
<label class="tile flex-no"> <label class="tile flex-no">
<span class="icon">category</span> <span class="icon">category</span>
<div> <div>
<span>{translations.chatPage.task.taskBoardLabel}</span> <span>
{translations.chatPage.task.taskBoardLabel}
</span>
<select <select
bind:value={taskViewModel.boardId} bind:value={taskViewModel.boardId}
children:append={[ children:append={[
taskViewModel.chatViewModel.taskBoardSuggestions, taskViewModel.chatViewModel
.taskBoardSuggestions,
BoardOptionConverter, BoardOptionConverter,
]} ]}
></select> ></select>
@@ -70,7 +78,12 @@ export function TaskSettingsModal(taskViewModel: TaskViewModel) {
<label class="tile flex-no"> <label class="tile flex-no">
<span class="icon">description</span> <span class="icon">description</span>
<div> <div>
<span>{translations.chatPage.task.taskDescriptionLabel}</span> <span>
{
translations.chatPage.task
.taskDescriptionLabel
}
</span>
<textarea <textarea
rows="10" rows="10"
bind:value={taskViewModel.description} bind:value={taskViewModel.description}
@@ -83,7 +96,9 @@ export function TaskSettingsModal(taskViewModel: TaskViewModel) {
<label class="tile flex-no"> <label class="tile flex-no">
<span class="icon">category</span> <span class="icon">category</span>
<div> <div>
<span>{translations.chatPage.task.taskCategoryLabel}</span> <span>
{translations.chatPage.task.taskCategoryLabel}
</span>
<input <input
bind:value={taskViewModel.category} bind:value={taskViewModel.category}
list={categorySuggestionId} list={categorySuggestionId}
@@ -102,7 +117,9 @@ export function TaskSettingsModal(taskViewModel: TaskViewModel) {
<label class="tile flex-no"> <label class="tile flex-no">
<span class="icon">clock_loader_40</span> <span class="icon">clock_loader_40</span>
<div> <div>
<span>{translations.chatPage.task.taskStatusLabel}</span> <span>
{translations.chatPage.task.taskStatusLabel}
</span>
<input <input
bind:value={taskViewModel.status} bind:value={taskViewModel.status}
list={statusSuggestionId} list={statusSuggestionId}
@@ -121,8 +138,13 @@ export function TaskSettingsModal(taskViewModel: TaskViewModel) {
<label class="tile flex-no"> <label class="tile flex-no">
<span class="icon">priority_high</span> <span class="icon">priority_high</span>
<div> <div>
<span>{translations.chatPage.task.taskPriorityLabel}</span> <span>
<input type="number" bind:value={taskViewModel.priority}></input> {translations.chatPage.task.taskPriorityLabel}
</span>
<input
type="number"
bind:value={taskViewModel.priority}
></input>
</div> </div>
</label> </label>
@@ -131,16 +153,26 @@ export function TaskSettingsModal(taskViewModel: TaskViewModel) {
<label class="tile flex-no"> <label class="tile flex-no">
<span class="icon">calendar_month</span> <span class="icon">calendar_month</span>
<div> <div>
<span>{translations.chatPage.task.taskDateLabel}</span> <span>
<input type="date" bind:value={taskViewModel.date}></input> {translations.chatPage.task.taskDateLabel}
</span>
<input
type="date"
bind:value={taskViewModel.date}
></input>
</div> </div>
</label> </label>
<label class="tile flex-no"> <label class="tile flex-no">
<span class="icon">schedule</span> <span class="icon">schedule</span>
<div> <div>
<span>{translations.chatPage.task.taskTimeLabel}</span> <span>
<input type="time" bind:value={taskViewModel.time}></input> {translations.chatPage.task.taskTimeLabel}
</span>
<input
type="time"
bind:value={taskViewModel.time}
></input>
</div> </div>
</label> </label>
@@ -150,15 +182,21 @@ export function TaskSettingsModal(taskViewModel: TaskViewModel) {
{DangerousActionButton( {DangerousActionButton(
translations.chatPage.task.deleteTaskButton, translations.chatPage.task.deleteTaskButton,
"delete_forever", "delete_forever",
taskViewModel.deleteTask taskViewModel.deleteTask,
)} )}
</div> </div>
</main> </main>
<div class="flex-row width-100"> <div class="flex-row width-100">
<button class="flex" on:click={taskViewModel.closeAndDiscard}> <button
class="flex"
on:click={taskViewModel.closeAndDiscard}
>
{translations.general.closeButton} {translations.general.closeButton}
</button> </button>
<button class="flex primary" on:click={taskViewModel.closeAndSave}> <button
class="flex primary"
on:click={taskViewModel.closeAndSave}
>
{translations.general.saveButton} {translations.general.saveButton}
<span class="icon">save</span> <span class="icon">save</span>
</button> </button>
+6 -6
View File
@@ -25,13 +25,13 @@ export function ChatPage(chatViewModel: ChatViewModel) {
return TaskPage(chatViewModel.taskPageViewModel); return TaskPage(chatViewModel.taskPageViewModel);
} }
case ChatPageType.Calendar: { case ChatPageType.Calendar: {
return CalendarPage(chatViewModel.calendarViewModel) return CalendarPage(chatViewModel.calendarViewModel);
} }
default: { default: {
return MessagePage(chatViewModel.messagePageViewModel); return MessagePage(chatViewModel.messagePageViewModel);
} }
} }
} },
); );
return ( return (
@@ -55,25 +55,25 @@ export function ChatPage(chatViewModel: ChatViewModel) {
translations.chatPage.pages.calendar, translations.chatPage.pages.calendar,
"calendar_month", "calendar_month",
ChatPageType.Calendar, ChatPageType.Calendar,
chatViewModel chatViewModel,
)} )}
{ChatViewToggleButton( {ChatViewToggleButton(
translations.chatPage.pages.tasks, translations.chatPage.pages.tasks,
"task_alt", "task_alt",
ChatPageType.Tasks, ChatPageType.Tasks,
chatViewModel chatViewModel,
)} )}
{ChatViewToggleButton( {ChatViewToggleButton(
translations.chatPage.pages.messages, translations.chatPage.pages.messages,
"forum", "forum",
ChatPageType.Messages, ChatPageType.Messages,
chatViewModel chatViewModel,
)} )}
{ChatViewToggleButton( {ChatViewToggleButton(
translations.chatPage.pages.settings, translations.chatPage.pages.settings,
"settings", "settings",
ChatPageType.Settings, ChatPageType.Settings,
chatViewModel chatViewModel,
)} )}
</span> </span>
</div> </div>
+2 -7
View File
@@ -14,13 +14,8 @@ export function ChatPageWrapper(chatListViewModel: ChatListViewModel) {
} else { } else {
return ChatPage(chatListViewModel.selectedChat.value); return ChatPage(chatListViewModel.selectedChat.value);
} }
} },
); );
return ( return <div id="chat-page-wrapper" children:set={chatPageContent}></div>;
<div
id="chat-page-wrapper"
children:set={chatPageContent}
></div>
);
} }
+20 -8
View File
@@ -17,7 +17,7 @@ export function HomePage(
settingsViewModel: SettingsViewModel, settingsViewModel: SettingsViewModel,
connectionViewModel: ConnectionViewModel, connectionViewModel: ConnectionViewModel,
fileTransferViewModel: FileTransferViewModel, fileTransferViewModel: FileTransferViewModel,
chatListViewModel: ChatListViewModel chatListViewModel: ChatListViewModel,
) { ) {
// sections // sections
const overviewSection = ( const overviewSection = (
@@ -30,7 +30,9 @@ export function HomePage(
<span>{translations.homePage.serverAddress}</span> <span>{translations.homePage.serverAddress}</span>
<input <input
list="previous-connection-list" list="previous-connection-list"
placeholder={translations.homePage.serverAddressPlaceholder} placeholder={
translations.homePage.serverAddressPlaceholder
}
bind:value={connectionViewModel.serverAddressInput} bind:value={connectionViewModel.serverAddressInput}
on:enter={connectionViewModel.connect} on:enter={connectionViewModel.connect}
></input> ></input>
@@ -56,9 +58,13 @@ export function HomePage(
</button> </button>
<button <button
class="flex justify-center" class="flex justify-center"
aria-label={translations.homePage.manageConnectionsAudioLabel} aria-label={
translations.homePage.manageConnectionsAudioLabel
}
on:click={connectionViewModel.showConnectionModal} on:click={connectionViewModel.showConnectionModal}
toggle:disabled={connectionViewModel.hasNoPreviousConnections} toggle:disabled={
connectionViewModel.hasNoPreviousConnections
}
> >
<span class="icon">history</span> <span class="icon">history</span>
</button> </button>
@@ -104,12 +110,15 @@ export function HomePage(
<div> <div>
<span>{translations.homePage.firstDayOfWeekLabel}</span> <span>{translations.homePage.firstDayOfWeekLabel}</span>
<select bind:value={settingsViewModel.firstDayOfWeekInput}> <select bind:value={settingsViewModel.firstDayOfWeekInput}>
{...translations.regional.weekdays.full.map((weekdayName, i) => {...translations.regional.weekdays.full.map(
(weekdayName, i) =>
Option( Option(
weekdayName, weekdayName,
i.toString(), i.toString(),
i.toString() == settingsViewModel.firstDayOfWeekInput.value i.toString() ==
) settingsViewModel.firstDayOfWeekInput
.value,
),
)} )}
</select> </select>
<span class="icon">arrow_drop_down</span> <span class="icon">arrow_drop_down</span>
@@ -128,7 +137,10 @@ export function HomePage(
</div> </div>
</button> </button>
<button class="tile flex-no" on:click={storageViewModel.showStorageModal}> <button
class="tile flex-no"
on:click={storageViewModel.showStorageModal}
>
<span class="icon">hard_drive_2</span> <span class="icon">hard_drive_2</span>
<div> <div>
<span>{translations.homePage.manageStorageButton}</span> <span>{translations.homePage.manageStorageButton}</span>
+28 -14
View File
@@ -85,7 +85,8 @@ const englishTranslations = {
transferDataHeadline: "Data Transfer", transferDataHeadline: "Data Transfer",
selectionDescription: "Select the data that you want to transfer.", selectionDescription: "Select the data that you want to transfer.",
dataEntryDescription: "Enter this data on the other device.", dataEntryDescription: "Enter this data on the other device.",
dataEntryInputDescription: "Enter the data displayed on the other device.", dataEntryInputDescription:
"Enter the data displayed on the other device.",
readyToReceiveDescription: "Click 'send' on the other device.", readyToReceiveDescription: "Click 'send' on the other device.",
notConnectedError: "You are not connected to any server.", notConnectedError: "You are not connected to any server.",
@@ -350,12 +351,14 @@ const allTranslations: { [language: string]: typeof englishTranslations } = {
dataTransferModal: { dataTransferModal: {
transferDataHeadline: "Datenübertragung", transferDataHeadline: "Datenübertragung",
selectionDescription: "Wähle die Daten aus, die du übertragen möchtest.", selectionDescription:
"Wähle die Daten aus, die du übertragen möchtest.",
dataEntryDescription: dataEntryDescription:
"Gib diese Informationen auf dem anderen Gerät ein.", "Gib diese Informationen auf dem anderen Gerät ein.",
dataEntryInputDescription: dataEntryInputDescription:
"Gib die auf dem anderen Gerät angezeigten Informationen ein.", "Gib die auf dem anderen Gerät angezeigten Informationen ein.",
readyToReceiveDescription: "Klicke auf dem anderen Gerät auf 'Senden'.", readyToReceiveDescription:
"Klicke auf dem anderen Gerät auf 'Senden'.",
notConnectedError: "Du bist mit keinem Server verbunden.", notConnectedError: "Du bist mit keinem Server verbunden.",
@@ -412,8 +415,10 @@ const allTranslations: { [language: string]: typeof englishTranslations } = {
setPrimaryChannelButtonAudioLabel: "Hauptkanal festlegen", setPrimaryChannelButtonAudioLabel: "Hauptkanal festlegen",
newSecondaryChannelPlaceholder: "Sekundären Kanal hinzufügen", newSecondaryChannelPlaceholder: "Sekundären Kanal hinzufügen",
newSecondaryChannelAudioLabel: "Name des neuen sekundären Kanals", newSecondaryChannelAudioLabel:
addSecondaryChannelButtonAudioLabel: "Sekundären Kanal hinzufügen", "Name des neuen sekundären Kanals",
addSecondaryChannelButtonAudioLabel:
"Sekundären Kanal hinzufügen",
encryptionKeyLabel: "Schlüssel", encryptionKeyLabel: "Schlüssel",
setEncryptionKeyButtonAudioLabel: "Schlüssel festlegen", setEncryptionKeyButtonAudioLabel: "Schlüssel festlegen",
@@ -451,7 +456,8 @@ const allTranslations: { [language: string]: typeof englishTranslations } = {
closeBoardButtonAudioLabel: "board schließen", closeBoardButtonAudioLabel: "board schließen",
toggleBoardButtonAudioLabel: "board-liste ein/ausblenden", toggleBoardButtonAudioLabel: "board-liste ein/ausblenden",
showBoardSettingsButtonAudioLabel: "Board-Einstellungen anzeigen", showBoardSettingsButtonAudioLabel:
"Board-Einstellungen anzeigen",
listViewButtonAudioLabel: "Listenansicht", listViewButtonAudioLabel: "Listenansicht",
kanbanViewButtonAudioLabel: "Kanban-Ansicht", kanbanViewButtonAudioLabel: "Kanban-Ansicht",
@@ -587,11 +593,14 @@ const allTranslations: { [language: string]: typeof englishTranslations } = {
dataTransferModal: { dataTransferModal: {
transferDataHeadline: "Transferencia de Datos", transferDataHeadline: "Transferencia de Datos",
selectionDescription: "Selecciona los datos que quieres transferir.", selectionDescription:
dataEntryDescription: "Introduce estos datos en el otro dispositivo.", "Selecciona los datos que quieres transferir.",
dataEntryDescription:
"Introduce estos datos en el otro dispositivo.",
dataEntryInputDescription: dataEntryInputDescription:
"Introduce los datos mostrados en el otro dispositivo.", "Introduce los datos mostrados en el otro dispositivo.",
readyToReceiveDescription: "Haz clic en 'enviar' en el otro dispositivo.", readyToReceiveDescription:
"Haz clic en 'enviar' en el otro dispositivo.",
notConnectedError: "No estás conectado a ningún servidor.", notConnectedError: "No estás conectado a ningún servidor.",
@@ -648,11 +657,13 @@ const allTranslations: { [language: string]: typeof englishTranslations } = {
setPrimaryChannelButtonAudioLabel: "establecer canal principal", setPrimaryChannelButtonAudioLabel: "establecer canal principal",
newSecondaryChannelPlaceholder: "Añadir canal secundario", newSecondaryChannelPlaceholder: "Añadir canal secundario",
newSecondaryChannelAudioLabel: "nombre del nuevo canal secundario", newSecondaryChannelAudioLabel:
"nombre del nuevo canal secundario",
addSecondaryChannelButtonAudioLabel: "añadir canal secundario", addSecondaryChannelButtonAudioLabel: "añadir canal secundario",
encryptionKeyLabel: "Clave de encriptación", encryptionKeyLabel: "Clave de encriptación",
setEncryptionKeyButtonAudioLabel: "establecer clave de encriptación", setEncryptionKeyButtonAudioLabel:
"establecer clave de encriptación",
showEncryptionKey: "Mostrar clave de encriptación", showEncryptionKey: "Mostrar clave de encriptación",
deleteChatButton: "Eliminar todo el chat", deleteChatButton: "Eliminar todo el chat",
@@ -664,7 +675,8 @@ const allTranslations: { [language: string]: typeof englishTranslations } = {
composerInputPlaceholder: "Escribe un mensaje...", composerInputPlaceholder: "Escribe un mensaje...",
sendMessageButtonAudioLabel: "enviar mensaje", sendMessageButtonAudioLabel: "enviar mensaje",
showMessageInfoButtonAudioLabel: "mostrar información del mensaje", showMessageInfoButtonAudioLabel:
"mostrar información del mensaje",
messageInfoHeadline: "Información del Mensaje", messageInfoHeadline: "Información del Mensaje",
sentBy: "Enviado por", sentBy: "Enviado por",
@@ -686,8 +698,10 @@ const allTranslations: { [language: string]: typeof englishTranslations } = {
boardNotFound: "Tablero no encontrado", boardNotFound: "Tablero no encontrado",
closeBoardButtonAudioLabel: "cerrar tablero", closeBoardButtonAudioLabel: "cerrar tablero",
toggleBoardButtonAudioLabel: "mostrar o ocultar lista de tableros", toggleBoardButtonAudioLabel:
showBoardSettingsButtonAudioLabel: "mostrar configuración del tablero", "mostrar o ocultar lista de tableros",
showBoardSettingsButtonAudioLabel:
"mostrar configuración del tablero",
listViewButtonAudioLabel: "vista de lista", listViewButtonAudioLabel: "vista de lista",
kanbanViewButtonAudioLabel: "vista kanban", kanbanViewButtonAudioLabel: "vista kanban",
+1 -1
View File
@@ -3,7 +3,7 @@ export function allowDrop(event: DragEvent) {
} }
export function allowDrag(event: DragEvent) { export function allowDrag(event: DragEvent) {
event.dataTransfer?.setData("text", "") event.dataTransfer?.setData("text", "");
} }
export function reload() { export function reload() {
+8 -7
View File
@@ -16,7 +16,7 @@ export default class ChatListViewModel {
// data // data
chatIndexManager: IndexManager<ChatViewModel> = new IndexManager( chatIndexManager: IndexManager<ChatViewModel> = new IndexManager(
(chatViewModel: ChatViewModel) => (chatViewModel: ChatViewModel) =>
chatViewModel.settingsPageViewModel.primaryChannel.value chatViewModel.settingsPageViewModel.primaryChannel.value,
); );
// state // state
@@ -24,23 +24,24 @@ export default class ChatListViewModel {
chatViewModels: React.ListState<ChatViewModel> = new React.ListState(); chatViewModels: React.ListState<ChatViewModel> = new React.ListState();
selectedChat: React.State<ChatViewModel | undefined> = new React.State<any>( selectedChat: React.State<ChatViewModel | undefined> = new React.State<any>(
undefined undefined,
); );
// guards // guards
cannotCreateChat = React.createProxyState( cannotCreateChat = React.createProxyState(
[this.newChatPrimaryChannel], [this.newChatPrimaryChannel],
() => this.newChatPrimaryChannel.value == "" () => this.newChatPrimaryChannel.value == "",
); );
// methods // methods
createChat = (): void => { createChat = (): void => {
const chatModel: ChatModel = this.chatListModel.createChat( const chatModel: ChatModel = this.chatListModel.createChat(
this.newChatPrimaryChannel.value this.newChatPrimaryChannel.value,
); );
this.newChatPrimaryChannel.value = ""; this.newChatPrimaryChannel.value = "";
const chatViewModel: ChatViewModel = this.createChatViewModel(chatModel); const chatViewModel: ChatViewModel =
this.createChatViewModel(chatModel);
this.trackChat(chatViewModel); this.trackChat(chatViewModel);
this.updateIndices(); this.updateIndices();
}; };
@@ -60,7 +61,7 @@ export default class ChatListViewModel {
this.storageModel, this.storageModel,
chatModel, chatModel,
this.settingsViewModel, this.settingsViewModel,
this this,
); );
}; };
@@ -95,7 +96,7 @@ export default class ChatListViewModel {
public coreViewModel: CoreViewModel, public coreViewModel: CoreViewModel,
storageModel: StorageModel, storageModel: StorageModel,
chatListModel: ChatListModel, chatListModel: ChatListModel,
settingsViewModel: SettingsViewModel settingsViewModel: SettingsViewModel,
) { ) {
this.storageModel = storageModel; this.storageModel = storageModel;
this.chatListModel = chatListModel; this.chatListModel = chatListModel;
+2 -2
View File
@@ -15,7 +15,7 @@ export default class ChatMessageViewModel {
dateSent: string; dateSent: string;
body: React.State<string> = new React.State(""); body: React.State<string> = new React.State("");
status: React.State<ChatMessageStatus | any> = new React.State<any>( status: React.State<ChatMessageStatus | any> = new React.State<any>(
undefined undefined,
); );
sentByUser: boolean; sentByUser: boolean;
@@ -56,7 +56,7 @@ export default class ChatMessageViewModel {
public coreViewModel: CoreViewModel, public coreViewModel: CoreViewModel,
messagePageViewModel: MessagePageViewModel, messagePageViewModel: MessagePageViewModel,
chatMessage: ChatMessage, chatMessage: ChatMessage,
sentByUser: boolean sentByUser: boolean,
) { ) {
this.messagePageViewModel = messagePageViewModel; this.messagePageViewModel = messagePageViewModel;
+8 -8
View File
@@ -33,7 +33,7 @@ export default class ChatViewModel {
// state // state
displayedColor: React.State<Color> = new React.State<any>(Color.Standard); displayedColor: React.State<Color> = new React.State<any>(Color.Standard);
selectedPage: React.State<ChatPageType> = new React.State<any>( selectedPage: React.State<ChatPageType> = new React.State<any>(
ChatPageType.Messages ChatPageType.Messages,
); );
index: React.State<number> = new React.State(0); index: React.State<number> = new React.State(0);
@@ -81,7 +81,7 @@ export default class ChatViewModel {
loadPageSelection = (): void => { loadPageSelection = (): void => {
const path: string[] = StorageModel.getPath( const path: string[] = StorageModel.getPath(
StorageModelSubPath.Chat, StorageModelSubPath.Chat,
filePaths.chat.lastUsedPage(this.chatModel.id) filePaths.chat.lastUsedPage(this.chatModel.id),
); );
const lastUsedPage: string | null = this.storageModel.read(path); const lastUsedPage: string | null = this.storageModel.read(path);
if (lastUsedPage != null) { if (lastUsedPage != null) {
@@ -109,7 +109,7 @@ export default class ChatViewModel {
storageModel: StorageModel, storageModel: StorageModel,
chatModel: ChatModel, chatModel: ChatModel,
settingsViewModel: SettingsViewModel, settingsViewModel: SettingsViewModel,
chatListViewModel: ChatListViewModel chatListViewModel: ChatListViewModel,
) { ) {
// models // models
this.storageModel = storageModel; this.storageModel = storageModel;
@@ -123,21 +123,21 @@ export default class ChatViewModel {
this, this,
this.storageModel, this.storageModel,
this.chatModel.fileModel.boardsAndTasksModel.calendarModel, this.chatModel.fileModel.boardsAndTasksModel.calendarModel,
this.chatModel.fileModel.boardsAndTasksModel this.chatModel.fileModel.boardsAndTasksModel,
); );
this.taskPageViewModel = new TaskPageViewModel( this.taskPageViewModel = new TaskPageViewModel(
this.coreViewModel, this.coreViewModel,
this, this,
this.storageModel, this.storageModel,
this.chatModel.fileModel.boardsAndTasksModel this.chatModel.fileModel.boardsAndTasksModel,
); );
this.messagePageViewModel = new MessagePageViewModel( this.messagePageViewModel = new MessagePageViewModel(
this.coreViewModel, this.coreViewModel,
this this,
); );
this.settingsPageViewModel = new SettingsPageViewModel( this.settingsPageViewModel = new SettingsPageViewModel(
this.coreViewModel, this.coreViewModel,
this this,
); );
// handlers // handlers
@@ -145,7 +145,7 @@ export default class ChatViewModel {
(chatMessage: ChatMessage) => { (chatMessage: ChatMessage) => {
this.messagePageViewModel.showChatMessage(chatMessage); this.messagePageViewModel.showChatMessage(chatMessage);
this.markRead(); this.markRead();
} },
); );
// load // load
+7 -6
View File
@@ -19,16 +19,17 @@ export default class ConnectionViewModel {
[this.serverAddressInput, this.isConnected], [this.serverAddressInput, this.isConnected],
() => () =>
(this.isConnected.value == true && (this.isConnected.value == true &&
this.serverAddressInput.value == this.connectionModel.address) || this.serverAddressInput.value ==
this.serverAddressInput.value == "" this.connectionModel.address) ||
this.serverAddressInput.value == "",
); );
cannotDisonnect: React.State<boolean> = React.createProxyState( cannotDisonnect: React.State<boolean> = React.createProxyState(
[this.isConnected], [this.isConnected],
() => this.isConnected.value == false () => this.isConnected.value == false,
); );
hasNoPreviousConnections: React.State<boolean> = React.createProxyState( hasNoPreviousConnections: React.State<boolean> = React.createProxyState(
[this.previousAddresses], [this.previousAddresses],
() => this.previousAddresses.value.size == 0 () => this.previousAddresses.value.size == 0,
); );
// handlers // handlers
@@ -80,13 +81,13 @@ export default class ConnectionViewModel {
// init // init
constructor( constructor(
public coreViewModel: CoreViewModel, public coreViewModel: CoreViewModel,
connectionModel: ConnectionModel connectionModel: ConnectionModel,
) { ) {
this.connectionModel = connectionModel; this.connectionModel = connectionModel;
this.updatePreviousAddresses(); this.updatePreviousAddresses();
connectionModel.connectionChangeHandlerManager.addHandler( connectionModel.connectionChangeHandlerManager.addHandler(
this.connectionChangeHandler this.connectionChangeHandler,
); );
} }
} }
+20 -14
View File
@@ -22,7 +22,8 @@ export default class FileTransferViewModel {
generalFileOptions: React.ListState<FileTransferOption> = generalFileOptions: React.ListState<FileTransferOption> =
new React.ListState(); new React.ListState();
chatFileOptions: React.ListState<FileTransferOption> = new React.ListState(); chatFileOptions: React.ListState<FileTransferOption> =
new React.ListState();
selectedPaths: React.ListState<string[]> = new React.ListState(); selectedPaths: React.ListState<string[]> = new React.ListState();
transferChannel: React.State<string> = new React.State(""); transferChannel: React.State<string> = new React.State("");
@@ -34,38 +35,40 @@ export default class FileTransferViewModel {
filePathsSent: React.ListState<string> = new React.ListState(); filePathsSent: React.ListState<string> = new React.ListState();
filesSentCount: React.State<number> = React.createProxyState( filesSentCount: React.State<number> = React.createProxyState(
[this.filePathsSent], [this.filePathsSent],
() => this.filePathsSent.value.size () => this.filePathsSent.value.size,
); );
filesSentText: React.State<string> = React.createProxyState( filesSentText: React.State<string> = React.createProxyState(
[this.filesSentCount], [this.filesSentCount],
() => () =>
translations.dataTransferModal.filesSentCount(this.filesSentCount.value) translations.dataTransferModal.filesSentCount(
this.filesSentCount.value,
),
); );
filePathsReceived: React.ListState<string> = new React.ListState(); filePathsReceived: React.ListState<string> = new React.ListState();
filesReceivedCount: React.State<number> = React.createProxyState( filesReceivedCount: React.State<number> = React.createProxyState(
[this.filePathsReceived], [this.filePathsReceived],
() => this.filePathsReceived.value.size () => this.filePathsReceived.value.size,
); );
filesReceivedText: React.State<string> = React.createProxyState( filesReceivedText: React.State<string> = React.createProxyState(
[this.filesReceivedCount], [this.filesReceivedCount],
() => () =>
translations.dataTransferModal.filesReceivedCount( translations.dataTransferModal.filesReceivedCount(
this.filesReceivedCount.value this.filesReceivedCount.value,
) ),
); );
// guards // guards
hasNoPathsSelected: React.State<boolean> = React.createProxyState( hasNoPathsSelected: React.State<boolean> = React.createProxyState(
[this.selectedPaths], [this.selectedPaths],
() => this.selectedPaths.value.size == 0 () => this.selectedPaths.value.size == 0,
); );
didNotFinishSending: React.State<boolean> = new React.State(true); didNotFinishSending: React.State<boolean> = new React.State(true);
cannotPrepareToReceive: React.State<boolean> = React.createProxyState( cannotPrepareToReceive: React.State<boolean> = React.createProxyState(
[this.receivingTransferChannel, this.receivingTransferKey], [this.receivingTransferChannel, this.receivingTransferKey],
() => () =>
this.receivingTransferChannel.value == "" || this.receivingTransferChannel.value == "" ||
this.receivingTransferKey.value == "" this.receivingTransferKey.value == "",
); );
// handlers // handlers
@@ -84,13 +87,16 @@ export default class FileTransferViewModel {
label: translations.dataTransferModal.connectionData, label: translations.dataTransferModal.connectionData,
path: StorageModel.getPath( path: StorageModel.getPath(
StorageModelSubPath.ConnectionModel, StorageModelSubPath.ConnectionModel,
filePaths.connectionModel.previousAddresses filePaths.connectionModel.previousAddresses,
), ),
}, },
{ {
label: translations.dataTransferModal.settingsData, label: translations.dataTransferModal.settingsData,
path: StorageModel.getPath(StorageModelSubPath.SettingsModel, []), path: StorageModel.getPath(
} StorageModelSubPath.SettingsModel,
[],
),
},
); );
const chatModels: Set<ChatModel> = this.chatListModel.chatModels; const chatModels: Set<ChatModel> = this.chatListModel.chatModels;
@@ -134,7 +140,7 @@ export default class FileTransferViewModel {
(path: string) => { (path: string) => {
console.log(path); console.log(path);
this.filePathsSent.add(path); this.filePathsSent.add(path);
} },
); );
this.didNotFinishSending.value = false; this.didNotFinishSending.value = false;
@@ -162,13 +168,13 @@ export default class FileTransferViewModel {
// init // init
constructor( constructor(
fileTransferModel: FileTransferModel, fileTransferModel: FileTransferModel,
chatListModel: ChatListModel chatListModel: ChatListModel,
) { ) {
this.fileTransferModel = fileTransferModel; this.fileTransferModel = fileTransferModel;
this.chatListModel = chatListModel; this.chatListModel = chatListModel;
this.fileTransferModel.fileHandlerManager.addHandler( this.fileTransferModel.fileHandlerManager.addHandler(
this.handleReceivedFile this.handleReceivedFile,
); );
} }
} }
+2 -2
View File
@@ -16,7 +16,7 @@ export default class SettingsViewModel {
[this.usernameInput], [this.usernameInput],
() => () =>
this.usernameInput.value == "" || this.usernameInput.value == "" ||
this.usernameInput.value == this.settingsModel.username this.usernameInput.value == this.settingsModel.username,
); );
// methods // methods
@@ -33,7 +33,7 @@ export default class SettingsViewModel {
// init // init
constructor( constructor(
public coreViewModel: CoreViewModel, public coreViewModel: CoreViewModel,
settingsModel: SettingsModel settingsModel: SettingsModel,
) { ) {
this.settingsModel = settingsModel; this.settingsModel = settingsModel;
+19 -8
View File
@@ -12,7 +12,9 @@ export default class StorageViewModel {
// state // state
isShowingStorageModal: React.State<boolean> = new React.State(false); isShowingStorageModal: React.State<boolean> = new React.State(false);
selectedPath: React.State<string> = new React.State(PATH_COMPONENT_SEPARATOR); selectedPath: React.State<string> = new React.State(
PATH_COMPONENT_SEPARATOR,
);
didMakeChanges: React.State<boolean> = new React.State(false); didMakeChanges: React.State<boolean> = new React.State(false);
selectedFileName: React.State<string>; selectedFileName: React.State<string>;
@@ -22,7 +24,9 @@ export default class StorageViewModel {
// methods // methods
getSelectedItemContent = (): string => { getSelectedItemContent = (): string => {
const path = StorageModel.stringToPathComponents(this.selectedPath.value); const path = StorageModel.stringToPathComponents(
this.selectedPath.value,
);
const content = this.storageModel.read(path); const content = this.storageModel.read(path);
return ( return (
(content ?? translations.storage.notAFile) || (content ?? translations.storage.notAFile) ||
@@ -31,7 +35,9 @@ export default class StorageViewModel {
}; };
deleteSelectedItem = (): void => { deleteSelectedItem = (): void => {
const path = StorageModel.stringToPathComponents(this.selectedPath.value); const path = StorageModel.stringToPathComponents(
this.selectedPath.value,
);
this.lastDeletedItemPath.value = this.selectedPath.value; this.lastDeletedItemPath.value = this.selectedPath.value;
this.storageModel.removeRecursively(path); this.storageModel.removeRecursively(path);
@@ -57,14 +63,19 @@ export default class StorageViewModel {
}; };
// init // init
constructor(public coreViewModel: CoreViewModel, storageModel: StorageModel) { constructor(
public coreViewModel: CoreViewModel,
storageModel: StorageModel,
) {
this.storageModel = storageModel; this.storageModel = storageModel;
this.selectedFileName = React.createProxyState([this.selectedPath], () => this.selectedFileName = React.createProxyState(
StorageModel.getFileNameFromString(this.selectedPath.value) [this.selectedPath],
() => StorageModel.getFileNameFromString(this.selectedPath.value),
); );
this.selectedFileContent = React.createProxyState([this.selectedPath], () => this.selectedFileContent = React.createProxyState(
this.getSelectedItemContent() [this.selectedPath],
() => this.getSelectedItemContent(),
); );
} }
} }
+19 -13
View File
@@ -30,7 +30,7 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
index: React.State<number> = new React.State(0); index: React.State<number> = new React.State(0);
selectedPage: React.State<BoardPageType> = new React.State<any>( selectedPage: React.State<BoardPageType> = new React.State<any>(
BoardPageType.List BoardPageType.List,
); );
isSelected: React.State<boolean>; isSelected: React.State<boolean>;
@@ -43,7 +43,9 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
// paths // paths
getBasePath = (): string[] => { getBasePath = (): string[] => {
return [...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId)]; return [
...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId),
];
}; };
getLastUsedBoardPath = (): string[] => { getLastUsedBoardPath = (): string[] => {
@@ -64,13 +66,15 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
BoardsAndTasksModel.createBoardInfoFileContent( BoardsAndTasksModel.createBoardInfoFileContent(
this.boardInfo.fileId, this.boardInfo.fileId,
this.name.value, this.name.value,
this.color.value this.color.value,
); );
this.taskPageViewModel.updateBoard(newBoardInfoFileContent); this.taskPageViewModel.updateBoard(newBoardInfoFileContent);
}; };
applyColor = (): void => { applyColor = (): void => {
this.taskPageViewModel.chatViewModel.setDisplayedColor(this.color.value); this.taskPageViewModel.chatViewModel.setDisplayedColor(
this.color.value,
);
}; };
deleteBoard = (): void => { deleteBoard = (): void => {
@@ -131,7 +135,7 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
// remove task that was moved to different board // remove task that was moved to different board
this.boardsAndTasksModel.deleteTaskReference( this.boardsAndTasksModel.deleteTaskReference(
this.boardInfo.fileId, this.boardInfo.fileId,
taskFileContent.fileId taskFileContent.fileId,
); );
this.removeTaskFromView(taskFileContent); this.removeTaskFromView(taskFileContent);
return; return;
@@ -142,7 +146,7 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
this.chatViewModel, this.chatViewModel,
this.boardsAndTasksModel, this.boardsAndTasksModel,
this, this,
taskFileContent taskFileContent,
); );
this.taskViewModels.set(taskFileContent.fileId, taskViewModel); this.taskViewModels.set(taskFileContent.fileId, taskViewModel);
}; };
@@ -192,7 +196,7 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
loadTasks = (): void => { loadTasks = (): void => {
const taskIds: string[] = this.boardsAndTasksModel.listTaskIds( const taskIds: string[] = this.boardsAndTasksModel.listTaskIds(
this.boardInfo.fileId this.boardInfo.fileId,
); );
for (const taskId of taskIds) { for (const taskId of taskIds) {
if (this.taskViewModels.value.has(taskId)) return; if (this.taskViewModels.value.has(taskId)) return;
@@ -206,7 +210,7 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
this.chatViewModel, this.chatViewModel,
this.boardsAndTasksModel, this.boardsAndTasksModel,
this, this,
taskFileContent taskFileContent,
); );
this.taskViewModels.set(taskFileContent.fileId, taskViewModel); this.taskViewModels.set(taskFileContent.fileId, taskViewModel);
} }
@@ -222,7 +226,8 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
restoreSearch = (): void => { restoreSearch = (): void => {
const lastSearchPath: string[] = this.getLastSearchPath(); const lastSearchPath: string[] = this.getLastSearchPath();
const lastSearch: string | null = this.storageModel.read(lastSearchPath); const lastSearch: string | null =
this.storageModel.read(lastSearchPath);
if (lastSearch != null) { if (lastSearch != null) {
this.searchViewModel.search(lastSearch); this.searchViewModel.search(lastSearch);
} }
@@ -241,7 +246,7 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
storageModel: StorageModel, storageModel: StorageModel,
boardsAndTasksModel: BoardsAndTasksModel, boardsAndTasksModel: BoardsAndTasksModel,
taskPageViewModel: TaskPageViewModel, taskPageViewModel: TaskPageViewModel,
boardInfo: BoardInfoFileContent boardInfo: BoardInfoFileContent,
) { ) {
super(coreViewModel, chatViewModel, boardsAndTasksModel); super(coreViewModel, chatViewModel, boardsAndTasksModel);
@@ -258,7 +263,8 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
this.isSelected = React.createProxyState( this.isSelected = React.createProxyState(
[this.taskPageViewModel.selectedBoardId], [this.taskPageViewModel.selectedBoardId],
() => () =>
this.taskPageViewModel.selectedBoardId.value == this.boardInfo.fileId this.taskPageViewModel.selectedBoardId.value ==
this.boardInfo.fileId,
); );
this.color.subscribe(() => { this.color.subscribe(() => {
@@ -276,7 +282,7 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
if (taskFileContent.boardId != this.boardInfo.fileId) return; if (taskFileContent.boardId != this.boardInfo.fileId) return;
this.showTask(taskFileContent); this.showTask(taskFileContent);
this.updateTaskIndices(); this.updateTaskIndices();
} },
); );
// search // search
@@ -284,7 +290,7 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
this.taskViewModels, this.taskViewModels,
this.filteredTaskViewModels, this.filteredTaskViewModels,
TaskViewModel.getStringsForFilter, TaskViewModel.getStringsForFilter,
this.coreViewModel.boardSearchSuggestions this.coreViewModel.boardSearchSuggestions,
); );
this.searchViewModel.appliedQuery.subscribeSilent((newQuery) => { this.searchViewModel.appliedQuery.subscribeSilent((newQuery) => {
this.handleNewSearch(newQuery); this.handleNewSearch(newQuery);
+23 -16
View File
@@ -22,7 +22,7 @@ export default class CalendarPageViewModel extends TaskContainingPageViewModel {
get monthString(): string { get monthString(): string {
return CalendarModel.getMonthString( return CalendarModel.getMonthString(
this.selectedYear.value.toString(), this.selectedYear.value.toString(),
this.selectedMonth.value.toString() this.selectedMonth.value.toString(),
); );
} }
@@ -36,8 +36,9 @@ export default class CalendarPageViewModel extends TaskContainingPageViewModel {
selectedMonth: React.State<number> = new React.State(0); selectedMonth: React.State<number> = new React.State(0);
selectedDate: React.State<number> = new React.State(0); selectedDate: React.State<number> = new React.State(0);
monthGrid: React.State<MonthGrid<React.MapState<TaskViewModel>> | undefined> = monthGrid: React.State<
new React.State<any>(undefined); MonthGrid<React.MapState<TaskViewModel>> | undefined
> = new React.State<any>(undefined);
// methods // methods
createEvent = (): void => { createEvent = (): void => {
@@ -46,7 +47,7 @@ export default class CalendarPageViewModel extends TaskContainingPageViewModel {
taskFileContent.date = CalendarModel.getISODateString( taskFileContent.date = CalendarModel.getISODateString(
this.selectedYear.value.toString(), this.selectedYear.value.toString(),
this.selectedMonth.value.toString(), this.selectedMonth.value.toString(),
this.selectedDate.value.toString() this.selectedDate.value.toString(),
); );
const taskViewModel: TaskViewModel = new TaskViewModel( const taskViewModel: TaskViewModel = new TaskViewModel(
@@ -54,7 +55,7 @@ export default class CalendarPageViewModel extends TaskContainingPageViewModel {
this.chatViewModel, this.chatViewModel,
this.boardsAndTasksModel, this.boardsAndTasksModel,
this, this,
taskFileContent taskFileContent,
); );
this.selectTask(taskViewModel); this.selectTask(taskViewModel);
@@ -63,7 +64,7 @@ export default class CalendarPageViewModel extends TaskContainingPageViewModel {
getEventsForDate = (): React.MapState<TaskViewModel> | undefined => { getEventsForDate = (): React.MapState<TaskViewModel> | undefined => {
const paddedDate: string = CalendarModel.padDateOrMonth( const paddedDate: string = CalendarModel.padDateOrMonth(
this.selectedDate.toString() this.selectedDate.toString(),
); );
if (this.monthGrid.value == undefined) { if (this.monthGrid.value == undefined) {
@@ -75,25 +76,25 @@ export default class CalendarPageViewModel extends TaskContainingPageViewModel {
// view // view
getTaskMapState = ( getTaskMapState = (
taskFileContent: TaskFileContent taskFileContent: TaskFileContent,
): React.MapState<TaskViewModel> | null => { ): React.MapState<TaskViewModel> | null => {
if (this.monthGrid.value == null) return null; if (this.monthGrid.value == null) return null;
const date: string = CalendarModel.isoToDateString( const date: string = CalendarModel.isoToDateString(
taskFileContent.date ?? "" taskFileContent.date ?? "",
); );
return this.monthGrid.value.days[date]; return this.monthGrid.value.days[date];
}; };
showTask = (taskFileContent: TaskFileContent): void => { showTask = (taskFileContent: TaskFileContent): void => {
const monthString: string | undefined = CalendarModel.isoToMonthString( const monthString: string | undefined = CalendarModel.isoToMonthString(
taskFileContent.date ?? "" taskFileContent.date ?? "",
); );
if (monthString == undefined || monthString != this.monthString) { if (monthString == undefined || monthString != this.monthString) {
this.removeTaskFromView(taskFileContent); this.removeTaskFromView(taskFileContent);
this.calendarModel.deleteTaskReference( this.calendarModel.deleteTaskReference(
this.monthString, this.monthString,
taskFileContent.fileId taskFileContent.fileId,
); );
return; return;
} }
@@ -103,7 +104,7 @@ export default class CalendarPageViewModel extends TaskContainingPageViewModel {
this.chatViewModel, this.chatViewModel,
this.boardsAndTasksModel, this.boardsAndTasksModel,
this, this,
taskFileContent taskFileContent,
); );
const mapState: React.MapState<TaskViewModel> | null = const mapState: React.MapState<TaskViewModel> | null =
this.getTaskMapState(taskFileContent); this.getTaskMapState(taskFileContent);
@@ -148,7 +149,11 @@ export default class CalendarPageViewModel extends TaskContainingPageViewModel {
}; };
handleDrop = (year: string, month: string, date: string): void => { handleDrop = (year: string, month: string, date: string): void => {
const ISOString: string = CalendarModel.getISODateString(year, month, date); const ISOString: string = CalendarModel.getISODateString(
year,
month,
date,
);
const draggedObject: any = this.coreViewModel.draggedObject.value; const draggedObject: any = this.coreViewModel.draggedObject.value;
if (draggedObject instanceof TaskViewModel == false) return; if (draggedObject instanceof TaskViewModel == false) return;
@@ -161,9 +166,11 @@ export default class CalendarPageViewModel extends TaskContainingPageViewModel {
this.monthGrid.value = this.calendarModel.generateMonthGrid( this.monthGrid.value = this.calendarModel.generateMonthGrid(
this.selectedYear.value, this.selectedYear.value,
this.selectedMonth.value, this.selectedMonth.value,
() => new React.MapState() () => new React.MapState(),
);
const taskIds: string[] = this.calendarModel.listTaskIds(
this.monthString,
); );
const taskIds: string[] = this.calendarModel.listTaskIds(this.monthString);
for (const taskId of taskIds) { for (const taskId of taskIds) {
const taskFileContent: TaskFileContent | null = const taskFileContent: TaskFileContent | null =
@@ -186,7 +193,7 @@ export default class CalendarPageViewModel extends TaskContainingPageViewModel {
public chatViewModel: ChatViewModel, public chatViewModel: ChatViewModel,
storageModel: StorageModel, storageModel: StorageModel,
calendarModel: CalendarModel, calendarModel: CalendarModel,
boardsAndTasksModel: BoardsAndTasksModel boardsAndTasksModel: BoardsAndTasksModel,
) { ) {
super(coreViewModel, chatViewModel, boardsAndTasksModel); super(coreViewModel, chatViewModel, boardsAndTasksModel);
@@ -204,7 +211,7 @@ export default class CalendarPageViewModel extends TaskContainingPageViewModel {
boardsAndTasksModel.taskHandlerManager.addHandler( boardsAndTasksModel.taskHandlerManager.addHandler(
(taskFileContent: TaskFileContent) => { (taskFileContent: TaskFileContent) => {
this.showTask(taskFileContent); this.showTask(taskFileContent);
} },
); );
} }
} }
+13 -6
View File
@@ -29,7 +29,7 @@ export default class MessagePageViewModel {
}; };
decryptMessage = async ( decryptMessage = async (
messageViewModel: ChatMessageViewModel messageViewModel: ChatMessageViewModel,
): Promise<void> => { ): Promise<void> => {
const chatMessage: ChatMessage = messageViewModel.chatMessage; const chatMessage: ChatMessage = messageViewModel.chatMessage;
await this.chatViewModel.chatModel.decryptMessage(chatMessage); await this.chatViewModel.chatModel.decryptMessage(chatMessage);
@@ -43,7 +43,8 @@ export default class MessagePageViewModel {
this.coreViewModel, this.coreViewModel,
this, this,
chatMessage, chatMessage,
chatMessage.sender == this.chatViewModel.settingsViewModel.username.value chatMessage.sender ==
this.chatViewModel.settingsViewModel.username.value,
); );
const existingChatMessageViewModel: ChatMessageViewModel | undefined = const existingChatMessageViewModel: ChatMessageViewModel | undefined =
@@ -52,7 +53,10 @@ export default class MessagePageViewModel {
existingChatMessageViewModel.body.value = chatMessage.body; existingChatMessageViewModel.body.value = chatMessage.body;
existingChatMessageViewModel.status.value = chatMessage.status; existingChatMessageViewModel.status.value = chatMessage.status;
} else { } else {
this.chatMessageViewModels.set(chatMessage.id, chatMessageViewModel); this.chatMessageViewModels.set(
chatMessage.id,
chatMessageViewModel,
);
} }
}; };
@@ -67,15 +71,18 @@ export default class MessagePageViewModel {
// init // init
constructor( constructor(
public coreViewModel: CoreViewModel, public coreViewModel: CoreViewModel,
chatViewModel: ChatViewModel chatViewModel: ChatViewModel,
) { ) {
this.chatViewModel = chatViewModel; this.chatViewModel = chatViewModel;
this.cannotSendMessage = React.createProxyState( this.cannotSendMessage = React.createProxyState(
[this.chatViewModel.settingsViewModel.username, this.composingMessage], [
this.chatViewModel.settingsViewModel.username,
this.composingMessage,
],
() => () =>
this.chatViewModel.settingsViewModel.username.value == "" || this.chatViewModel.settingsViewModel.username.value == "" ||
this.composingMessage.value == "" this.composingMessage.value == "",
); );
} }
} }
+7 -7
View File
@@ -18,7 +18,7 @@ export default class SettingsPageViewModel {
shouldShowEncryptionKey: React.State<boolean> = new React.State(false); shouldShowEncryptionKey: React.State<boolean> = new React.State(false);
encryptionKeyInputType: React.State<"text" | "password"> = encryptionKeyInputType: React.State<"text" | "password"> =
React.createProxyState([this.shouldShowEncryptionKey], () => React.createProxyState([this.shouldShowEncryptionKey], () =>
this.shouldShowEncryptionKey.value == true ? "text" : "password" this.shouldShowEncryptionKey.value == true ? "text" : "password",
); );
color: React.State<Color> = new React.State<any>(Color.Standard); color: React.State<Color> = new React.State<any>(Color.Standard);
@@ -28,18 +28,18 @@ export default class SettingsPageViewModel {
[this.primaryChannel, this.primaryChannelInput], [this.primaryChannel, this.primaryChannelInput],
() => () =>
this.primaryChannelInput.value == "" || this.primaryChannelInput.value == "" ||
this.primaryChannelInput.value == this.primaryChannel.value this.primaryChannelInput.value == this.primaryChannel.value,
); );
cannotAddSecondaryChannel: React.State<boolean> = React.createProxyState( cannotAddSecondaryChannel: React.State<boolean> = React.createProxyState(
[this.newSecondaryChannelInput], [this.newSecondaryChannelInput],
() => this.newSecondaryChannelInput.value == "" () => this.newSecondaryChannelInput.value == "",
); );
cannotSetEncryptionKey: React.State<boolean>; cannotSetEncryptionKey: React.State<boolean>;
// methods // methods
setPrimaryChannel = (): void => { setPrimaryChannel = (): void => {
this.chatViewModel.chatModel.setPrimaryChannel( this.chatViewModel.chatModel.setPrimaryChannel(
this.primaryChannelInput.value this.primaryChannelInput.value,
); );
this.primaryChannel.value = this.primaryChannel.value =
this.chatViewModel.chatModel.info.primaryChannel; this.chatViewModel.chatModel.info.primaryChannel;
@@ -67,7 +67,7 @@ export default class SettingsPageViewModel {
setEncryptionKey = (): void => { setEncryptionKey = (): void => {
this.chatViewModel.chatModel.setEncryptionKey( this.chatViewModel.chatModel.setEncryptionKey(
this.encryptionKeyInput.value this.encryptionKeyInput.value,
); );
// disable button // disable button
@@ -113,7 +113,7 @@ export default class SettingsPageViewModel {
// init // init
constructor( constructor(
public coreViewModel: CoreViewModel, public coreViewModel: CoreViewModel,
chatViewModel: ChatViewModel chatViewModel: ChatViewModel,
) { ) {
this.chatViewModel = chatViewModel; this.chatViewModel = chatViewModel;
this.loadListRelevantData(); this.loadListRelevantData();
@@ -122,7 +122,7 @@ export default class SettingsPageViewModel {
[this.encryptionKeyInput], [this.encryptionKeyInput],
() => () =>
this.encryptionKeyInput.value == this.encryptionKeyInput.value ==
this.chatViewModel.chatModel.info.encryptionKey this.chatViewModel.chatModel.info.encryptionKey,
); );
this.color.subscribe((newColor) => { this.color.subscribe((newColor) => {
@@ -14,7 +14,7 @@ export default class TaskContainingPageViewModel {
// state // state
taskIndexManager: IndexManager<TaskViewModel> = new IndexManager( taskIndexManager: IndexManager<TaskViewModel> = new IndexManager(
(taskViewModel: TaskViewModel) => taskViewModel.sortingString (taskViewModel: TaskViewModel) => taskViewModel.sortingString,
); );
selectedTaskViewModel: React.State<TaskViewModel | undefined> = selectedTaskViewModel: React.State<TaskViewModel | undefined> =
@@ -31,7 +31,7 @@ export default class TaskContainingPageViewModel {
this.chatViewModel, this.chatViewModel,
this.boardsAndTasksModel, this.boardsAndTasksModel,
this, this,
taskFileContent taskFileContent,
); );
this.selectTask(taskViewModel); this.selectTask(taskViewModel);
this.updateTaskIndices(); this.updateTaskIndices();
@@ -60,7 +60,7 @@ export default class TaskContainingPageViewModel {
constructor( constructor(
public coreViewModel: CoreViewModel, public coreViewModel: CoreViewModel,
public chatViewModel: ChatViewModel, public chatViewModel: ChatViewModel,
boardsAndTasksModel: BoardsAndTasksModel boardsAndTasksModel: BoardsAndTasksModel,
) { ) {
this.boardsAndTasksModel = boardsAndTasksModel; this.boardsAndTasksModel = boardsAndTasksModel;
} }
+6 -6
View File
@@ -16,7 +16,7 @@ export default class TaskPageViewModel {
// data // data
boardIndexManager: IndexManager<BoardViewModel> = new IndexManager( boardIndexManager: IndexManager<BoardViewModel> = new IndexManager(
(boardViewModel: BoardViewModel) => boardViewModel.name.value (boardViewModel: BoardViewModel) => boardViewModel.name.value,
); );
// paths // paths
@@ -40,13 +40,13 @@ export default class TaskPageViewModel {
isShowingBoadList: React.State<boolean> = new React.State(true); isShowingBoadList: React.State<boolean> = new React.State(true);
selectedBoardId: React.State<string | undefined> = new React.State<any>( selectedBoardId: React.State<string | undefined> = new React.State<any>(
undefined undefined,
); );
// guards // guards
cannotCreateBoard: React.State<boolean> = React.createProxyState( cannotCreateBoard: React.State<boolean> = React.createProxyState(
[this.newBoardNameInput], [this.newBoardNameInput],
() => this.newBoardNameInput.value == "" () => this.newBoardNameInput.value == "",
); );
// methods // methods
@@ -85,7 +85,7 @@ export default class TaskPageViewModel {
this.storageModel, this.storageModel,
this.boardsAndTasksModel, this.boardsAndTasksModel,
this, this,
boardInfo boardInfo,
); );
this.boardViewModels.set(boardInfo.fileId, boardViewModel); this.boardViewModels.set(boardInfo.fileId, boardViewModel);
@@ -157,7 +157,7 @@ export default class TaskPageViewModel {
public coreViewModel: CoreViewModel, public coreViewModel: CoreViewModel,
public chatViewModel: ChatViewModel, public chatViewModel: ChatViewModel,
storageModel: StorageModel, storageModel: StorageModel,
boardModel: BoardsAndTasksModel boardModel: BoardsAndTasksModel,
) { ) {
this.storageModel = storageModel; this.storageModel = storageModel;
this.boardsAndTasksModel = boardModel; this.boardsAndTasksModel = boardModel;
@@ -169,7 +169,7 @@ export default class TaskPageViewModel {
(boardInfoFileContent: BoardInfoFileContent) => { (boardInfoFileContent: BoardInfoFileContent) => {
this.showBoardInList(boardInfoFileContent); this.showBoardInList(boardInfoFileContent);
this.updateBoardIndices(); this.updateBoardIndices();
} },
); );
} }
} }
+23 -13
View File
@@ -32,7 +32,13 @@ export default class TaskViewModel {
const invertedPriority = 100 - priorityNumber; const invertedPriority = 100 - priorityNumber;
return ( return (
year + month + date + hour + minute + invertedPriority + this.name.value year +
month +
date +
hour +
minute +
invertedPriority +
this.name.value
); );
} }
@@ -93,7 +99,7 @@ export default class TaskViewModel {
closeAndDiscard = (): void => { closeAndDiscard = (): void => {
this.close(); this.close();
this.loadTaskData(); this.loadTaskData();
} };
closeAndSave = (): void => { closeAndSave = (): void => {
this.close(); this.close();
@@ -101,22 +107,24 @@ export default class TaskViewModel {
}; };
updateIndex = (): void => { updateIndex = (): void => {
const index: number = this.containingModel.taskIndexManager.getIndex(this); const index: number =
this.containingModel.taskIndexManager.getIndex(this);
this.index.value = index; this.index.value = index;
}; };
updateSuggestions = (): void => { updateSuggestions = (): void => {
if ( if (
this.coreViewModel.taskCategorySuggestions.value.has( this.coreViewModel.taskCategorySuggestions.value.has(
this.category.value this.category.value,
) == false ) == false
) { ) {
this.coreViewModel.taskCategorySuggestions.add(this.category.value); this.coreViewModel.taskCategorySuggestions.add(this.category.value);
} }
if ( if (
this.coreViewModel.taskStatusSuggestions.value.has(this.status.value) == this.coreViewModel.taskStatusSuggestions.value.has(
false this.status.value,
) == false
) { ) {
this.coreViewModel.taskStatusSuggestions.add(this.status.value); this.coreViewModel.taskStatusSuggestions.add(this.status.value);
} }
@@ -128,7 +136,7 @@ export default class TaskViewModel {
BoardsAndTasksModel.createTaskFileContent( BoardsAndTasksModel.createTaskFileContent(
this.task.fileId, this.task.fileId,
this.name.value, this.name.value,
this.task.boardId this.task.boardId,
); );
newTaskFileContent.boardId = this.boardId.value; newTaskFileContent.boardId = this.boardId.value;
@@ -149,15 +157,17 @@ export default class TaskViewModel {
deleteTask = (): void => { deleteTask = (): void => {
this.close(); this.close();
this.boardsAndTasksModel.deleteTask(this.task.boardId, this.task.fileId); this.boardsAndTasksModel.deleteTask(
this.task.boardId,
this.task.fileId,
);
this.containingModel.removeTaskFromView(this.task); this.containingModel.removeTaskFromView(this.task);
}; };
// load // load
loadVersionIds = (): void => { loadVersionIds = (): void => {
const versionIds: string[] = this.boardsAndTasksModel.listTaskVersionIds( const versionIds: string[] =
this.task.fileId this.boardsAndTasksModel.listTaskVersionIds(this.task.fileId);
);
const sortedVersionIds = versionIds.sort(localeCompare).reverse(); const sortedVersionIds = versionIds.sort(localeCompare).reverse();
this.versionIds.clear(); this.versionIds.clear();
this.versionIds.add(...sortedVersionIds); this.versionIds.add(...sortedVersionIds);
@@ -167,7 +177,7 @@ export default class TaskViewModel {
const taskFileContent: TaskFileContent | null = const taskFileContent: TaskFileContent | null =
this.boardsAndTasksModel.getSpecificTaskFileContent( this.boardsAndTasksModel.getSpecificTaskFileContent(
this.task.fileId, this.task.fileId,
versionId versionId,
); );
if (taskFileContent == null) return; if (taskFileContent == null) return;
@@ -204,7 +214,7 @@ export default class TaskViewModel {
public chatViewModel: ChatViewModel, public chatViewModel: ChatViewModel,
boardsAndTasksModel: BoardsAndTasksModel, boardsAndTasksModel: BoardsAndTasksModel,
containingModel: TaskContainingPageViewModel, containingModel: TaskContainingPageViewModel,
taskFileContent: TaskFileContent taskFileContent: TaskFileContent,
) { ) {
this.boardsAndTasksModel = boardsAndTasksModel; this.boardsAndTasksModel = boardsAndTasksModel;
this.containingModel = containingModel; this.containingModel = containingModel;
+3 -3
View File
@@ -17,7 +17,7 @@ export default class SearchViewModel<T> {
// guards // guards
cannotApplySearch: React.State<boolean> = React.createProxyState( cannotApplySearch: React.State<boolean> = React.createProxyState(
[this.searchInput, this.appliedQuery], [this.searchInput, this.appliedQuery],
() => this.searchInput.value == this.appliedQuery.value () => this.searchInput.value == this.appliedQuery.value,
); );
// methods // methods
@@ -43,7 +43,7 @@ export default class SearchViewModel<T> {
allObjects: React.ListState<T> | React.MapState<T>, allObjects: React.ListState<T> | React.MapState<T>,
matchingObjects: React.ListState<T>, matchingObjects: React.ListState<T>,
getStringsOfObject: (object: T) => string[], getStringsOfObject: (object: T) => string[],
suggestions: React.ListState<string> suggestions: React.ListState<string>,
) { ) {
this.allObjects = allObjects; this.allObjects = allObjects;
this.matchingObjects = matchingObjects; this.matchingObjects = matchingObjects;
@@ -70,7 +70,7 @@ export default class SearchViewModel<T> {
return checkDoesObjectMatchSearch( return checkDoesObjectMatchSearch(
this.appliedQuery.value, this.appliedQuery.value,
this.getStringsOfObject, this.getStringsOfObject,
object object,
); );
}; };
} }
@@ -25,7 +25,7 @@ export default class TaskPropertyBulkChangeViewModel {
constructor( constructor(
taskViewModels: React.ListState<TaskViewModel>, taskViewModels: React.ListState<TaskViewModel>,
valueSetter: (newValue: string, taskViewModel: TaskViewModel) => void, valueSetter: (newValue: string, taskViewModel: TaskViewModel) => void,
initialValue: string initialValue: string,
) { ) {
this.taskViewModels = taskViewModels; this.taskViewModels = taskViewModels;
this.inputValue.value = initialValue; this.inputValue.value = initialValue;
@@ -34,7 +34,9 @@ export default class TaskPropertyBulkChangeViewModel {
this.cannotSet = React.createProxyState( this.cannotSet = React.createProxyState(
[this.inputValue], [this.inputValue],
() => this.inputValue.value == "" || this.inputValue.value == initialValue () =>
this.inputValue.value == "" ||
this.inputValue.value == initialValue,
); );
} }
} }
@@ -42,7 +44,7 @@ export default class TaskPropertyBulkChangeViewModel {
export class TaskCategoryBulkChangeViewModel extends TaskPropertyBulkChangeViewModel { export class TaskCategoryBulkChangeViewModel extends TaskPropertyBulkChangeViewModel {
constructor( constructor(
taskViewModels: React.ListState<TaskViewModel>, taskViewModels: React.ListState<TaskViewModel>,
initialValue: string initialValue: string,
) { ) {
super( super(
taskViewModels, taskViewModels,
@@ -50,7 +52,7 @@ export class TaskCategoryBulkChangeViewModel extends TaskPropertyBulkChangeViewM
taskViewModel.category.value = newCategory; taskViewModel.category.value = newCategory;
taskViewModel.save(); taskViewModel.save();
}, },
initialValue initialValue,
); );
} }
} }
@@ -58,7 +60,7 @@ export class TaskCategoryBulkChangeViewModel extends TaskPropertyBulkChangeViewM
export class TaskStatusBulkChangeViewModel extends TaskPropertyBulkChangeViewModel { export class TaskStatusBulkChangeViewModel extends TaskPropertyBulkChangeViewModel {
constructor( constructor(
taskViewModels: React.ListState<TaskViewModel>, taskViewModels: React.ListState<TaskViewModel>,
initialValue: string initialValue: string,
) { ) {
super( super(
taskViewModels, taskViewModels,
@@ -66,7 +68,7 @@ export class TaskStatusBulkChangeViewModel extends TaskPropertyBulkChangeViewMod
taskViewModel.status.value = newStatus; taskViewModel.status.value = newStatus;
taskViewModel.save(); taskViewModel.save();
}, },
initialValue initialValue,
); );
} }
} }
+8 -8
View File
@@ -29,7 +29,7 @@ const connectionModel = new ConnectionModel(storageModel);
const chatListModel = new ChatListModel( const chatListModel = new ChatListModel(
storageModel, storageModel,
settingsModel, settingsModel,
connectionModel connectionModel,
); );
const fileTransferModel = new FileTransferModel(storageModel, connectionModel); const fileTransferModel = new FileTransferModel(storageModel, connectionModel);
@@ -43,24 +43,24 @@ const storageViewModel = new StorageViewModel(coreVieWModel, storageModel);
const settingsViewModel = new SettingsViewModel(coreVieWModel, settingsModel); const settingsViewModel = new SettingsViewModel(coreVieWModel, settingsModel);
const connectionViewModel = new ConnectionViewModel( const connectionViewModel = new ConnectionViewModel(
coreVieWModel, coreVieWModel,
connectionModel connectionModel,
); );
const chatListViewModel = new ChatListViewModel( const chatListViewModel = new ChatListViewModel(
coreVieWModel, coreVieWModel,
storageModel, storageModel,
chatListModel, chatListModel,
settingsViewModel settingsViewModel,
); );
const fileTransferViewModel = new FileTransferViewModel( const fileTransferViewModel = new FileTransferViewModel(
fileTransferModel, fileTransferModel,
chatListModel chatListModel,
); );
// view // view
chatListViewModel.selectedChat.subscribe(() => { chatListViewModel.selectedChat.subscribe(() => {
document.body.toggleAttribute( document.body.toggleAttribute(
"showing-chat", "showing-chat",
chatListViewModel.selectedChat.value != undefined chatListViewModel.selectedChat.value != undefined,
); );
}); });
@@ -69,7 +69,7 @@ document.body.append(
<div id="sky"></div> <div id="sky"></div>
<div id="grass-1"></div> <div id="grass-1"></div>
<div id="grass-2"></div> <div id="grass-2"></div>
</div> </div>,
); );
document document
.querySelector("main")! .querySelector("main")!
@@ -79,10 +79,10 @@ document
settingsViewModel, settingsViewModel,
connectionViewModel, connectionViewModel,
fileTransferViewModel, fileTransferViewModel,
chatListViewModel chatListViewModel,
), ),
ChatPageWrapper(chatListViewModel), ChatPageWrapper(chatListViewModel),
ConnectionModal(connectionViewModel), ConnectionModal(connectionViewModel),
DataTransferModalWrapper(connectionViewModel, fileTransferViewModel), DataTransferModalWrapper(connectionViewModel, fileTransferViewModel),
StorageModal(storageViewModel) StorageModal(storageViewModel),
); );