add progress bar to data transfer

This commit is contained in:
marlon-erler
2026-09-30 18:57:09 +02:00
parent c78c5747cf
commit 397a5f76e4
25 changed files with 1125 additions and 1048 deletions
+67 -57
View File
@@ -436,9 +436,8 @@
sendButton: "Send",
sendAgainButton: "Send again",
///
filesSentCount: (count) => `Files sent: ${count}.`,
allFilesSent: "Done.",
filesReceivedCount: (count) => `Files processed: ${count}.`,
filesSentProgress: (progress, total) => `Sent ${progress} of ${total} files`,
filesReceivedProgress: (progress, total) => `Received ${progress} of ${total} files`,
//
exportKey: "Encryption key",
exportKeyConfirmation: "Confirm",
@@ -702,9 +701,8 @@
transferKeyHeadline: "Schl\xFCssel",
sendButton: "Senden",
sendAgainButton: "Erneut senden",
filesSentCount: (count) => `Dateien gesendet: ${count}.`,
allFilesSent: "Fertig.",
filesReceivedCount: (count) => `Dateien empfangen: ${count}.`,
filesSentProgress: (progress, total) => `${progress} von ${total} Dateien gesendet`,
filesReceivedProgress: (progress, total) => `${progress} von ${total} Dateien empfangen`,
exportKey: "Schl\xFCssel",
exportKeyConfirmation: "Schl\xFCssel best\xE4tigen",
downloadFileButton: "Herunterladen",
@@ -954,9 +952,8 @@
transferKeyHeadline: "Clave de encriptaci\xF3n de transferencia",
sendButton: "Enviar",
sendAgainButton: "Enviar otra vez",
filesSentCount: (count) => `Archivos enviados: ${count}.`,
allFilesSent: "Hecho.",
filesReceivedCount: (count) => `Archivos recibidos: ${count}.`,
filesSentProgress: (progress, total) => `${progress} de ${total} enviados`,
filesReceivedProgress: (progress, total) => `${progress} de ${total} recibidos`,
exportKey: "Clave de encriptaci\xF3n",
exportKeyConfirmation: "Confirmar clave",
downloadFileButton: "Descargar",
@@ -2290,27 +2287,13 @@
this.receivingTransferChannel = new State("");
this.receivingTransferKey = new State("");
this.filePathsSent = new ListState();
this.filesSentCount = createProxyState(
[this.filePathsSent],
() => this.filePathsSent.value.size
);
this.filesSentText = createProxyState(
[this.filesSentCount],
() => this.coreViewModel.translations.dataTransferModal.filesSentCount(
this.filesSentCount.value
)
);
this.fileTransferTotal = new State(0);
this.fileTransferProgress = new State(0);
this.filePathsReceived = new ListState();
this.filesReceivedCount = createProxyState(
[this.filePathsReceived],
() => this.filePathsReceived.value.size
);
this.filesReceivedText = createProxyState(
[this.filesReceivedCount],
() => this.coreViewModel.translations.dataTransferModal.filesReceivedCount(
this.filesReceivedCount.value
)
);
this.exportKey = new State("");
this.exportKeyConfirmation = new State("");
this.importedFile = new State(void 0);
@@ -2322,7 +2305,6 @@
[this.selectedPaths],
() => this.selectedPaths.value.size == 0
);
this.didNotFinishSending = new State(true);
this.cannotPrepareToReceive = createProxyState(
[this.receivingTransferChannel, this.receivingTransferKey],
() => this.receivingTransferChannel.value == "" || this.receivingTransferKey.value == ""
@@ -2346,6 +2328,7 @@
// handlers
this.handleReceivedFile = (path) => {
this.filePathsReceived.add(path);
this.fileTransferProgress.value = this.filePathsReceived.value.size;
};
// methods
this.getOptions = () => {
@@ -2454,7 +2437,6 @@
};
this.initiateTransfer = () => {
this.presentedModal.value = 3 /* TransferDisplay */;
this.didNotFinishSending.value = true;
this.filePathsSent.clear();
this.coreViewModel.fileTransferModel.sendFiles(
this.selectedPaths.value.values(),
@@ -2462,7 +2444,6 @@
this.filePathsSent.add(path);
}
);
this.didNotFinishSending.value = false;
};
this.showTransferDataInputModal = () => {
this.presentedModal.value = 4 /* TransferDataInput */;
@@ -2496,6 +2477,10 @@
this.handleContextClose = () => {
this.presentedModal.value = void 0;
};
this.coreViewModel.fileTransferModel.transferSizeHandlerManager.setHandler(
"file-transfer-view-model",
(count) => this.fileTransferTotal.value = count
);
this.coreViewModel.fileTransferModel.fileHandlerManager.setHandler(
"file-transfer-view-model",
this.handleReceivedFile
@@ -4742,7 +4727,6 @@
};
this.randomizeKey = () => {
this.encryptionKeyInput.value = random(24);
this.setEncryptionKey();
};
this.applyColor = () => {
this.chatViewModel.setColor(this.color.value);
@@ -8165,6 +8149,21 @@
return /* @__PURE__ */ createElement("span", { class: "ellipsis" }, string);
};
// src/View/Components/progressBar.tsx
function ProgressBar(valueLabel, value, maximum) {
const style = createProxyState(
[value, maximum],
() => `width: ${100 * (value.value / maximum.value)}%`
);
return /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "width-100 surface-alt", style: "height: 18px" }, /* @__PURE__ */ createElement(
"div",
{
class: "height-100 background-primary",
"set:style": style
}
)), /* @__PURE__ */ createElement("span", { class: "secondary", "subscribe:innerText": valueLabel }));
}
// src/View/Modals/dataTransferModal.tsx
function DataTransferModalWrapper(coreViewModel2, connectionViewModel2, fileTransferViewModel2) {
return /* @__PURE__ */ createElement("div", null, DirectionSelectionModal(
@@ -8320,20 +8319,8 @@
[fileTransferViewModel2.presentedModal],
() => fileTransferViewModel2.presentedModal.value == 3 /* TransferDisplay */
);
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.transferDataHeadline), /* @__PURE__ */ createElement(
"p",
{
class: "secondary",
"subscribe:innerText": fileTransferViewModel2.filesSentText
}
), /* @__PURE__ */ createElement(
"p",
{
class: "secondary",
"toggle:hidden": fileTransferViewModel2.didNotFinishSending
},
coreViewModel2.translations.dataTransferModal.allFilesSent
), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.transferChannelHeadline), /* @__PURE__ */ createElement(
const progressLabel = createProxyState([fileTransferViewModel2.fileTransferProgress, fileTransferViewModel2.fileTransferTotal], () => coreViewModel2.translations.dataTransferModal.filesSentProgress(fileTransferViewModel2.fileTransferProgress.value, fileTransferViewModel2.fileTransferTotal.value));
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.transferDataHeadline), ProgressBar(progressLabel, fileTransferViewModel2.fileTransferProgress, fileTransferViewModel2.fileTransferTotal), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-column gap content-margin-bottom" }, /* @__PURE__ */ createElement("div", { class: "tile" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "forum"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.dataTransferModal.transferChannelHeadline), /* @__PURE__ */ createElement(
"b",
{
"subscribe:innerText": fileTransferViewModel2.transferChannel
@@ -8364,8 +8351,7 @@
"button",
{
class: "flex",
"on:click": fileTransferViewModel2.close,
"toggle:disabled": fileTransferViewModel2.didNotFinishSending
"on:click": fileTransferViewModel2.close
},
coreViewModel2.translations.general.closeButton,
/* @__PURE__ */ createElement("span", { class: "icon" }, "close")
@@ -8413,13 +8399,8 @@
[fileTransferViewModel2.presentedModal],
() => fileTransferViewModel2.presentedModal.value == 5 /* ReceptionDisplay */
);
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.receiveHeadline), /* @__PURE__ */ createElement(
"p",
{
class: "secondary",
"subscribe:innerText": fileTransferViewModel2.filesReceivedText
}
), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement(
const progressLabel = createProxyState([fileTransferViewModel2.fileTransferProgress, fileTransferViewModel2.fileTransferTotal], () => coreViewModel2.translations.dataTransferModal.filesReceivedProgress(fileTransferViewModel2.fileTransferProgress.value, fileTransferViewModel2.fileTransferTotal.value));
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isPresented }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.dataTransferModal.receiveHeadline), ProgressBar(progressLabel, fileTransferViewModel2.fileTransferProgress, fileTransferViewModel2.fileTransferTotal), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement(
"div",
{
class: "tile flex-column align-start",
@@ -8641,6 +8622,7 @@
this.direction = 0 /* Send */;
// handler managers
this.fileHandlerManager = new HandlerManager();
this.transferSizeHandlerManager = new HandlerManager();
this.readyToSendHandlerManager = new HandlerManager();
// general
this.generateTransferData = () => {
@@ -8700,6 +8682,11 @@
};
this.handleDecryptedFile = (data) => {
const parsed = parse(data);
if (!parsed.type) return;
if (parsed.type == "transfer-size" && typeof parsed.count == "number") {
this.transferSizeHandlerManager.trigger(parsed.count);
return;
}
const isFileData = checkMatchesObjectStructure(
parsed,
FileDataReference
@@ -8714,16 +8701,22 @@
};
// sending
this.sendFiles = (directoryPaths, fileCallback) => {
const filesToSend = /* @__PURE__ */ new Set();
for (const directoryPath of directoryPaths) {
this.storageModel.recurse(directoryPath, (filePath) => {
const stringifiedFileData = this.prepareFileForSending(filePath);
this.sendFile(stringifiedFileData);
const pathString = StorageModel.pathComponentsToString(
...filePath
);
fileCallback(pathString);
filesToSend.add([stringifiedFileData, filePath]);
});
}
this.sendTransferSize(filesToSend.size);
for (const file of filesToSend) {
const [stringifiedFileData, filePath] = file;
this.sendFile(stringifiedFileData);
const pathString = StorageModel.pathComponentsToString(
...filePath
);
fileCallback(pathString);
}
};
this.generateBackup = async (directoryPaths, passphrase) => {
const files = [];
@@ -8739,10 +8732,26 @@
const blob = new Blob([encrypted], { type: "text/plain" });
return blob;
};
this.sendTransferSize = async (count) => {
if (!this.transferData) return;
const data = {
type: "transfer-size",
count
};
const encryptedData = await encryptString(
stringify(data),
this.transferData.key
);
this.connectionModel.sendPlainMessage(
this.transferData.channel,
encryptedData
);
};
this.prepareFileForSending = (filePath) => {
const fileContent = this.storageModel.read(filePath);
if (fileContent == null) return "";
const fileData = {
type: "file-data",
path: filePath,
body: fileContent
};
@@ -8771,6 +8780,7 @@
}
};
var FileDataReference = {
type: "file-data",
path: [""],
body: ""
};
Vendored
+1 -1
View File
@@ -1,4 +1,4 @@
const VERSION = "2610 Beta 21.12"
const VERSION = "2610 Beta 21.13"
function processUrl(event) {
const url = event.request.url;
+1 -1
View File
@@ -1 +1 @@
2610 Beta 21.12
2610 Beta 21.13
+44 -5
View File
@@ -24,6 +24,7 @@ export default class FileTransferModel {
// handler managers
readonly fileHandlerManager = new HandlerManager<string>();
readonly transferSizeHandlerManager = new HandlerManager<number>();
readonly readyToSendHandlerManager = new HandlerManager<boolean>();
// general
@@ -105,6 +106,12 @@ export default class FileTransferModel {
readonly handleDecryptedFile = (data: string): void => {
const parsed: any = parse(data);
if (!parsed.type) return;
if (parsed.type == "transfer-size" && typeof parsed.count == "number") {
this.transferSizeHandlerManager.trigger(parsed.count);
return;
}
const isFileData: boolean = checkMatchesObjectStructure(
parsed,
FileDataReference,
@@ -126,17 +133,23 @@ export default class FileTransferModel {
directoryPaths: IteratorObject<string[]>,
fileCallback: (path: string) => void,
): void => {
const filesToSend = new Set<[string, string[]]>();
for (const directoryPath of directoryPaths) {
this.storageModel.recurse(directoryPath, (filePath: string[]) => {
const stringifiedFileData =
this.prepareFileForSending(filePath);
this.sendFile(stringifiedFileData);
const pathString: string = StorageModel.pathComponentsToString(
...filePath,
);
fileCallback(pathString);
filesToSend.add([stringifiedFileData, filePath]);
});
}
this.sendTransferSize(filesToSend.size);
for (const file of filesToSend) {
const [stringifiedFileData, filePath] = file;
this.sendFile(stringifiedFileData);
const pathString: string = StorageModel.pathComponentsToString(
...filePath,
);
fileCallback(pathString);
}
};
readonly generateBackup = async (
@@ -158,11 +171,30 @@ export default class FileTransferModel {
return blob;
};
readonly sendTransferSize = async (count: number): Promise<void> => {
if (!this.transferData) return;
const data: TransferSize = {
type: "transfer-size",
count,
};
const encryptedData: string = await encryptString(
stringify(data),
this.transferData.key,
);
this.connectionModel.sendPlainMessage(
this.transferData.channel,
encryptedData,
);
};
readonly prepareFileForSending = (filePath: string[]): string => {
const fileContent: string | null = this.storageModel.read(filePath);
if (fileContent == null) return "";
const fileData: FileData = {
type: "file-data",
path: filePath,
body: fileContent,
};
@@ -200,11 +232,18 @@ export interface TransferData {
}
export interface FileData {
type: "file-data";
path: string[];
body: string;
}
export interface TransferSize {
type: "transfer-size";
count: number;
}
export const FileDataReference: FileData = {
type: "file-data",
path: [""],
body: "",
};
+1 -1
View File
@@ -5,7 +5,7 @@ const ENCRYPTION_ALG = "AES-GCM";
// random
export function random(length: number): string {
return cryptoRandomString({length})
return cryptoRandomString({ length });
}
// encrypt
+6 -5
View File
@@ -140,11 +140,12 @@ export function SettingsPage(
<div class="flex-row width-input">
<button
class="width-50"
on:click={
settingsPageViewModel.randomizeKey
}
on:click={settingsPageViewModel.randomizeKey}
>
{coreViewModel.translations.chatPage.settings.randomizeKey}
{
coreViewModel.translations.chatPage.settings
.randomizeKey
}
<span class="icon">shuffle</span>
</button>
<button
@@ -176,7 +177,7 @@ export function SettingsPage(
coreViewModel.translations.chatPage.settings
.showEncryptionKey
}
</label>
</label>
<hr></hr>
+4 -4
View File
@@ -65,10 +65,10 @@ export function TaskPage(
class="no-outline"
bind:value={taskPageViewModel.boardQuery}
on:enter={taskPageViewModel.createBoard}
placeholder={
coreViewModel.translations.general
.filterOrCreateLabel(coreViewModel.translations.chatPage.task.typeBoard)
}
placeholder={coreViewModel.translations.general.filterOrCreateLabel(
coreViewModel.translations.chatPage.task
.typeBoard,
)}
></input>
</div>
</div>
@@ -12,8 +12,8 @@ export function BoardViewToggleButton(
key: string,
) {
function select() {
boardViewModel.selectedPage.value = page;
boardViewModel.resetPinchZoom();
boardViewModel.selectedPage.value = page;
boardViewModel.resetPinchZoom();
}
const isSelected = React.createProxyState(
+24
View File
@@ -0,0 +1,24 @@
import * as React from "../../react";
export function ProgressBar(
valueLabel: React.State<string>,
value: React.State<number>,
maximum: React.State<number>,
) {
const style = React.createProxyState(
[value, maximum],
() => `width: ${100 * (value.value / maximum.value)}%`,
);
return (
<div>
<div class="width-100 surface-alt" style="height: 18px">
<div
class="height-100 background-primary"
set:style={style}
></div>
</div>
<span class="secondary" subscribe:innerText={valueLabel}></span>
</div>
);
}
+36 -31
View File
@@ -1,6 +1,7 @@
import { ViewController } from "../viewController";
import { CommonKeys, getKeySymbol } from "../keystrokes";
import { StringToTextSpan } from "../Components/textSpan";
import { ProgressBar } from "../Components/progressBar";
import * as React from "../../react";
import FileTransferViewModel, {
FileTransferModals,
@@ -370,6 +371,18 @@ function TransferDisplayModal(
FileTransferModals.TransferDisplay,
);
const progressLabel = React.createProxyState(
[
fileTransferViewModel.fileTransferProgress,
fileTransferViewModel.fileTransferTotal,
],
() =>
coreViewModel.translations.dataTransferModal.filesSentProgress(
fileTransferViewModel.fileTransferProgress.value,
fileTransferViewModel.fileTransferTotal.value,
),
);
return (
<div class="modal" toggle:open={isPresented}>
<div>
@@ -381,24 +394,11 @@ function TransferDisplayModal(
}
</h2>
<p
class="secondary"
subscribe:innerText={
fileTransferViewModel.filesSentText
}
></p>
<p
class="secondary"
toggle:hidden={
fileTransferViewModel.didNotFinishSending
}
>
{
coreViewModel.translations.dataTransferModal
.allFilesSent
}
</p>
{ProgressBar(
progressLabel,
fileTransferViewModel.fileTransferProgress,
fileTransferViewModel.fileTransferTotal,
)}
<hr></hr>
@@ -460,13 +460,7 @@ function TransferDisplayModal(
}
<span class="icon">restart_alt</span>
</button>
<button
class="flex"
on:click={fileTransferViewModel.close}
toggle:disabled={
fileTransferViewModel.didNotFinishSending
}
>
<button class="flex" on:click={fileTransferViewModel.close}>
{coreViewModel.translations.general.closeButton}
<span class="icon">close</span>
</button>
@@ -588,6 +582,18 @@ function DataReceptionModal(
FileTransferModals.ReceptionDisplay,
);
const progressLabel = React.createProxyState(
[
fileTransferViewModel.fileTransferProgress,
fileTransferViewModel.fileTransferTotal,
],
() =>
coreViewModel.translations.dataTransferModal.filesReceivedProgress(
fileTransferViewModel.fileTransferProgress.value,
fileTransferViewModel.fileTransferTotal.value,
),
);
return (
<div class="modal" toggle:open={isPresented}>
<div>
@@ -599,12 +605,11 @@ function DataReceptionModal(
}
</h2>
<p
class="secondary"
subscribe:innerText={
fileTransferViewModel.filesReceivedText
}
></p>
{ProgressBar(
progressLabel,
fileTransferViewModel.fileTransferProgress,
fileTransferViewModel.fileTransferTotal,
)}
<hr></hr>
+4 -4
View File
@@ -50,7 +50,7 @@
@media (min-width: 640px) {
#home-page > div {
gap: var(--content-padding);
gap: var(--content-padding);
}
#home-page #overview-section {
@@ -66,11 +66,11 @@
@media (min-width: 1000px) {
#home-page > div {
gap: var(--large-gap);
gap: var(--large-gap);
}
#home-page > h1 {
padding-top: 3rem;
padding-bottom: 1rem;
padding-top: 3rem;
padding-bottom: 1rem;
}
}
+29 -20
View File
@@ -1,5 +1,6 @@
import "./homePage.css";
import { StringToOption } from "./Components/option";
import { NewItemEntry } from "./Components/newItemEntry";
import { HomePageButton } from "./Components/homePageButton";
import { ChatViewModelToChatEntry } from "./Components/chatEntry";
import * as React from "../react";
@@ -9,7 +10,6 @@ import FileTransferViewModel from "../ViewModel/Global/fileTransferViewModel";
import CoreViewModel from "../ViewModel/Global/coreViewModel";
import ConnectionViewModel from "../ViewModel/Global/connectionViewModel";
import ChatListViewModel from "../ViewModel/Chat/chatListViewModel";
import {NewItemEntry} from "./Components/newItemEntry";
export function HomePage(
coreViewModel: CoreViewModel,
@@ -19,9 +19,18 @@ export function HomePage(
fileTransferViewModel: FileTransferViewModel,
chatListViewModel: ChatListViewModel,
) {
const greeting = React.createProxyState([settingsViewModel.username], ()=>coreViewModel.translations.homePage.greeting(settingsViewModel.username.value));
const greeting = React.createProxyState([settingsViewModel.username], () =>
coreViewModel.translations.homePage.greeting(
settingsViewModel.username.value,
),
);
const isChatPlaceholderHidden = React.createProxyState([chatListViewModel.chatMatches, chatListViewModel.chatQuery], ()=>chatListViewModel.chatMatches.value.size > 0 || chatListViewModel.chatQuery.value != "");
const isChatPlaceholderHidden = React.createProxyState(
[chatListViewModel.chatMatches, chatListViewModel.chatQuery],
() =>
chatListViewModel.chatMatches.value.size > 0 ||
chatListViewModel.chatQuery.value != "",
);
// sections
const overviewSection = (
@@ -120,7 +129,7 @@ export function HomePage(
<span class="icon">update</span>
</button>
<hr class="mobile-only"></hr>
<hr class="mobile-only"></hr>
</div>
);
@@ -128,25 +137,25 @@ export function HomePage(
<div id="chat-section">
<div class="flex-row width-input">
<input
placeholder={
coreViewModel.translations.general
.filterOrCreateLabel(coreViewModel.translations.homePage.typeChat)
}
placeholder={coreViewModel.translations.general.filterOrCreateLabel(
coreViewModel.translations.homePage.typeChat,
)}
bind:value={chatListViewModel.chatQuery}
on:enter={chatListViewModel.createChat}
></input>
</div>
</div>
<hr></hr>
<hr></hr>
{NewItemEntry(coreViewModel, chatListViewModel.chatQuery, chatListViewModel.createChat)}
{NewItemEntry(
coreViewModel,
chatListViewModel.chatQuery,
chatListViewModel.createChat,
)}
<span
class="secondary"
toggle:hidden={isChatPlaceholderHidden}
>
{coreViewModel.translations.homePage.noChatsPlaceholder}
</span>
<span class="secondary" toggle:hidden={isChatPlaceholderHidden}>
{coreViewModel.translations.homePage.noChatsPlaceholder}
</span>
<div
id="chat-grid"
@@ -154,14 +163,14 @@ export function HomePage(
chatListViewModel.chatMatches,
ChatViewModelToChatEntry,
]}
></div>
></div>
</div>
);
// final
return (
<article id="home-page">
<h1 subscribe:innerText={greeting}></h1>
<article id="home-page">
<h1 subscribe:innerText={greeting}></h1>
<div>
{overviewSection}
{chatSection}
+567 -566
View File
File diff suppressed because it is too large Load Diff
+7 -4
View File
@@ -35,9 +35,7 @@ export default class ChatListViewModel {
// methods
createChat = (): void => {
const chatModel: ChatModel =
this.coreViewModel.chatListModel.createChat(
this.chatQuery.value,
);
this.coreViewModel.chatListModel.createChat(this.chatQuery.value);
this.chatQuery.value = "";
const chatViewModel: ChatViewModel =
@@ -109,7 +107,12 @@ export default class ChatListViewModel {
this.loadChats();
implementFilter(this.chatViewModels, this.chatMatches, this.chatQuery, chat => chat.chatModel.info.name)
implementFilter(
this.chatViewModels,
this.chatMatches,
this.chatQuery,
(chat) => chat.chatModel.info.name,
);
}
// utility
+13 -12
View File
@@ -52,22 +52,23 @@ export default class CoreViewModel {
)
return;
let exitNext: boolean = false;
let exitNext: boolean = false;
while (this.contexts.length > 0) {
const currentContext: Context | undefined = this.context;
if (!currentContext) break;
const response = currentContext.handleContextClose(fromHistoryEvent);
if (response == false) break;
const response =
currentContext.handleContextClose(fromHistoryEvent);
if (response == false) break;
this.contextStack.delete(currentContext.contextId);
if (response == true) {
exitNext = true;
continue;
} else if (exitNext == true) {
break
} else if (currentContext.contextId == contextId) {
break;
}
if (response == true) {
exitNext = true;
continue;
} else if (exitNext == true) {
break;
} else if (currentContext.contextId == contextId) {
break;
}
}
};
@@ -206,7 +207,7 @@ export class Context {
};
close = (): void => {};
handleContextClose = (fromHistoryEvent: boolean): void|boolean=> {};
handleContextClose = (fromHistoryEvent: boolean): void | boolean => {};
registerKeyStroke = (key: string, fn: () => void): void => {
this.keystrokes.set(key, fn);
+9 -23
View File
@@ -11,7 +11,7 @@ import ChatModel from "../../Model/Chat/chatModel";
export default class FileTransferViewModel extends Context {
// state
exitReception: () => void | undefined = undefined;
exitReception: (() => void) | undefined = undefined;
presentedModal: React.State<FileTransferModals | undefined> =
new React.State<any>(undefined);
@@ -29,30 +29,14 @@ export default class FileTransferViewModel extends Context {
receivingTransferKey: React.State<string> = new React.State("");
filePathsSent: React.ListState<string> = new React.ListState();
filesSentCount: React.State<number> = React.createProxyState(
[this.filePathsSent],
() => this.filePathsSent.value.size,
);
filesSentText: React.State<string> = React.createProxyState(
[this.filesSentCount],
() =>
this.coreViewModel.translations.dataTransferModal.filesSentCount(
this.filesSentCount.value,
),
);
fileTransferTotal: React.State<number> = new React.State(0);
fileTransferProgress: React.State<number> = new React.State(0);
filePathsReceived: React.ListState<string> = new React.ListState();
filesReceivedCount: React.State<number> = React.createProxyState(
[this.filePathsReceived],
() => this.filePathsReceived.value.size,
);
filesReceivedText: React.State<string> = React.createProxyState(
[this.filesReceivedCount],
() =>
this.coreViewModel.translations.dataTransferModal.filesReceivedCount(
this.filesReceivedCount.value,
),
);
exportKey: React.State<string> = new React.State("");
exportKeyConfirmation: React.State<string> = new React.State("");
@@ -69,7 +53,6 @@ export default class FileTransferViewModel extends Context {
[this.selectedPaths],
() => this.selectedPaths.value.size == 0,
);
didNotFinishSending: React.State<boolean> = new React.State(true);
cannotPrepareToReceive: React.State<boolean> = React.createProxyState(
[this.receivingTransferChannel, this.receivingTransferKey],
() =>
@@ -99,6 +82,7 @@ export default class FileTransferViewModel extends Context {
// handlers
handleReceivedFile = (path: string): void => {
this.filePathsReceived.add(path);
this.fileTransferProgress.value = this.filePathsReceived.value.size;
};
// methods
@@ -229,7 +213,6 @@ export default class FileTransferViewModel extends Context {
initiateTransfer = (): void => {
this.presentedModal.value = FileTransferModals.TransferDisplay;
this.didNotFinishSending.value = true;
this.filePathsSent.clear();
this.coreViewModel.fileTransferModel.sendFiles(
@@ -238,8 +221,6 @@ export default class FileTransferViewModel extends Context {
this.filePathsSent.add(path);
},
);
this.didNotFinishSending.value = false;
};
showTransferDataInputModal = (): void => {
@@ -287,6 +268,11 @@ export default class FileTransferViewModel extends Context {
constructor(public readonly coreViewModel: CoreViewModel) {
super("file-transfer");
this.coreViewModel.fileTransferModel.transferSizeHandlerManager.setHandler(
"file-transfer-view-model",
(count) => (this.fileTransferTotal.value = count),
);
this.coreViewModel.fileTransferModel.fileHandlerManager.setHandler(
"file-transfer-view-model",
this.handleReceivedFile,
+200 -200
View File
@@ -21,7 +21,7 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
index: React.State<number> = new React.State(0);
selectedPage: React.State<BoardPageTypes> = new React.State<any>(
BoardPageTypes.List,
BoardPageTypes.List,
);
isSelected: React.State<boolean>;
@@ -31,339 +31,339 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
searchViewModel: SearchViewModel<TaskViewModel>;
searchSuggestions = new React.ListState<string>();
filteredTaskViewModels: React.ListState<TaskViewModel> =
new React.ListState();
new React.ListState();
isFilterActive: React.State<boolean>;
pinchToZoomData: React.State<PinchToZoomData> = new React.State({
zoom: 1,
x: 0,
y: 0,
zoom: 1,
x: 0,
y: 0,
});
// paths
getBasePath = (): string[] => {
return [
...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId),
];
return [
...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId),
];
};
getLastUsedBoardPath = (): string[] => {
return [...this.getBasePath(), BoardViewModelSubPaths.LastUsedView];
return [...this.getBasePath(), BoardViewModelSubPaths.LastUsedView];
};
getPreviousSearchesPath = (): string[] => {
return [...this.getBasePath(), BoardViewModelSubPaths.PreviousSearches];
return [...this.getBasePath(), BoardViewModelSubPaths.PreviousSearches];
};
getLastSearchPath = (): string[] => {
return [...this.getBasePath(), BoardViewModelSubPaths.LastSearch];
return [...this.getBasePath(), BoardViewModelSubPaths.LastSearch];
};
// settings
saveSettings = (): void => {
const newBoardInfoFileContent: BoardInfoFileContent =
BoardsAndTasksModel.createBoardInfoFileContent(
this.boardInfo.fileId,
this.name.value,
this.color.value,
);
const newBoardInfoFileContent: BoardInfoFileContent =
BoardsAndTasksModel.createBoardInfoFileContent(
this.boardInfo.fileId,
this.name.value,
this.color.value,
);
this.taskPageViewModel.updateBoard(newBoardInfoFileContent);
this.taskPageViewModel.updateBoard(newBoardInfoFileContent);
};
applyColor = (): void => {
this.taskPageViewModel.chatViewModel.setDisplayedColor(
this.color.value,
);
this.taskPageViewModel.chatViewModel.setDisplayedColor(
this.color.value,
);
};
deleteBoard = (): void => {
this.taskPageViewModel.deleteBoard(this.boardInfo);
this.chatViewModel.taskBoardSuggestions.remove(this.boardInfo.fileId);
this.close();
this.taskPageViewModel.deleteBoard(this.boardInfo);
this.chatViewModel.taskBoardSuggestions.remove(this.boardInfo.fileId);
this.close();
};
// methods
createTask = (): void => {
this.createTaskFromBoardId(this.boardInfo.fileId);
this.createTaskFromBoardId(this.boardInfo.fileId);
};
handleDropWithinBoard = (category?: string, status?: string): void => {
const draggedObject: any = this.coreViewModel.draggedObject.value;
if (draggedObject instanceof TaskViewModel == false) return;
draggedObject.setCategoryAndStatus(category, status);
const draggedObject: any = this.coreViewModel.draggedObject.value;
if (draggedObject instanceof TaskViewModel == false) return;
draggedObject.setCategoryAndStatus(category, status);
};
handleDropBetweenBoards = (): void => {
const draggedObject: any = this.coreViewModel.draggedObject.value;
if (draggedObject instanceof TaskViewModel == false) return;
draggedObject.setBoardId(this.boardInfo.fileId);
const draggedObject: any = this.coreViewModel.draggedObject.value;
if (draggedObject instanceof TaskViewModel == false) return;
draggedObject.setBoardId(this.boardInfo.fileId);
};
// storage
storeLastUsedView = (): void => {
const path: string[] = this.getLastUsedBoardPath();
const lastUsedView: string = this.selectedPage.value;
this.coreViewModel.storageModel.write(path, lastUsedView);
const path: string[] = this.getLastUsedBoardPath();
const lastUsedView: string = this.selectedPage.value;
this.coreViewModel.storageModel.write(path, lastUsedView);
};
restoreLastUsedView = (): void => {
const path: string[] = this.getLastUsedBoardPath();
const lastUsedView: string | null =
this.coreViewModel.storageModel.read(path);
if (lastUsedView == null) return;
const path: string[] = this.getLastUsedBoardPath();
const lastUsedView: string | null =
this.coreViewModel.storageModel.read(path);
if (lastUsedView == null) return;
this.selectedPage.value = lastUsedView as BoardPageTypes;
this.selectedPage.value = lastUsedView as BoardPageTypes;
};
handleNewSearch = (searchTerm: string): void => {
const suggestionPath: string[] = [
...this.getPreviousSearchesPath(),
searchTerm,
];
this.coreViewModel.storageModel.write(suggestionPath, "");
if (!this.searchSuggestions.value.has(searchTerm) && searchTerm != "") {
this.searchSuggestions.add(searchTerm);
}
const suggestionPath: string[] = [
...this.getPreviousSearchesPath(),
searchTerm,
];
this.coreViewModel.storageModel.write(suggestionPath, "");
if (!this.searchSuggestions.value.has(searchTerm) && searchTerm != "") {
this.searchSuggestions.add(searchTerm);
}
const lastSearchPath: string[] = this.getLastSearchPath();
this.coreViewModel.storageModel.write(lastSearchPath, searchTerm);
const lastSearchPath: string[] = this.getLastSearchPath();
this.coreViewModel.storageModel.write(lastSearchPath, searchTerm);
};
handleSearchRemoved = (searchTerm: string): void => {
const suggestionPath: string[] = [
...this.getPreviousSearchesPath(),
searchTerm,
];
this.coreViewModel.storageModel.remove(suggestionPath);
const suggestionPath: string[] = [
...this.getPreviousSearchesPath(),
searchTerm,
];
this.coreViewModel.storageModel.remove(suggestionPath);
};
// view
showTask = (taskFileContent: TaskFileContent): void => {
if (taskFileContent.boardId != this.boardInfo.fileId) {
// remove task that was moved to different board
this.boardsAndTasksModel.deleteTaskReference(
this.boardInfo.fileId,
taskFileContent.fileId,
);
this.removeTaskFromView(taskFileContent);
return;
}
if (taskFileContent.boardId != this.boardInfo.fileId) {
// remove task that was moved to different board
this.boardsAndTasksModel.deleteTaskReference(
this.boardInfo.fileId,
taskFileContent.fileId,
);
this.removeTaskFromView(taskFileContent);
return;
}
const taskViewModel: TaskViewModel = new TaskViewModel(
this.coreViewModel,
this.chatViewModel,
this.boardsAndTasksModel,
this,
taskFileContent,
);
this.taskViewModels.set(taskFileContent.fileId, taskViewModel);
const taskViewModel: TaskViewModel = new TaskViewModel(
this.coreViewModel,
this.chatViewModel,
this.boardsAndTasksModel,
this,
taskFileContent,
);
this.taskViewModels.set(taskFileContent.fileId, taskViewModel);
};
removeTaskFromView = (taskFileContent: TaskFileContent): void => {
this.taskViewModels.remove(taskFileContent.fileId);
this.updateIndex();
this.taskViewModels.remove(taskFileContent.fileId);
this.updateIndex();
};
select = (): void => {
this.taskPageViewModel.selectBoard(this);
this.taskPageViewModel.selectBoard(this);
};
close = (): void => {
this.taskPageViewModel.closeBoard();
this.taskPageViewModel.storeLastUsedBoard();
this.taskViewModels.clear();
this.taskPageViewModel.closeBoard();
this.taskPageViewModel.storeLastUsedBoard();
this.taskViewModels.clear();
};
showSettings = (): void => {
this.isPresentingSettingsModal.value = true;
this.isPresentingSettingsModal.value = true;
};
hideSettings = (): void => {
this.saveSettings();
this.isPresentingSettingsModal.value = false;
this.saveSettings();
this.isPresentingSettingsModal.value = false;
};
showFilterModal = (): void => {
this.isPresentingFilterModal.value = true;
this.isPresentingFilterModal.value = true;
};
hideFilterModal = (): void => {
this.isPresentingFilterModal.value = false;
this.isPresentingFilterModal.value = false;
};
resetFilter = (): void => {
this.searchViewModel.search("");
this.searchViewModel.search("");
};
updateIndex = (): void => {
const index: number =
this.taskPageViewModel.boardIndexManager.getIndex(this);
this.index.value = index;
const index: number =
this.taskPageViewModel.boardIndexManager.getIndex(this);
this.index.value = index;
};
resetPinchZoom = (): void => {
this.pinchToZoomData.value = {x: 0, y: 0, zoom: 1};
}
this.pinchToZoomData.value = { x: 0, y: 0, zoom: 1 };
};
// load
preloadData = (): void => {
this.name.value = this.boardInfo.name;
this.color.value = this.boardInfo.color;
this.name.value = this.boardInfo.name;
this.color.value = this.boardInfo.color;
};
loadTasks = (): void => {
const taskIds: string[] = this.boardsAndTasksModel.listTaskIds(
this.boardInfo.fileId,
);
for (const taskId of taskIds) {
if (this.taskViewModels.value.has(taskId)) continue;
const taskIds: string[] = this.boardsAndTasksModel.listTaskIds(
this.boardInfo.fileId,
);
for (const taskId of taskIds) {
if (this.taskViewModels.value.has(taskId)) continue;
const taskFileContent: TaskFileContent | null =
this.boardsAndTasksModel.getLatestTaskFileContent(taskId);
if (taskFileContent == null) continue;
const taskFileContent: TaskFileContent | null =
this.boardsAndTasksModel.getLatestTaskFileContent(taskId);
if (taskFileContent == null) continue;
const taskViewModel: TaskViewModel = new TaskViewModel(
this.coreViewModel,
this.chatViewModel,
this.boardsAndTasksModel,
this,
taskFileContent,
);
this.taskViewModels.set(taskFileContent.fileId, taskViewModel);
}
const taskViewModel: TaskViewModel = new TaskViewModel(
this.coreViewModel,
this.chatViewModel,
this.boardsAndTasksModel,
this,
taskFileContent,
);
this.taskViewModels.set(taskFileContent.fileId, taskViewModel);
}
this.updateTaskIndices();
this.updateTaskIndices();
};
loadSearchSuggestions = (): void => {
const dirPath: string[] = this.getPreviousSearchesPath();
const searches: string[] =
this.coreViewModel.storageModel.list(dirPath);
this.searchSuggestions.add(...searches.filter((x) => x != ""));
const dirPath: string[] = this.getPreviousSearchesPath();
const searches: string[] =
this.coreViewModel.storageModel.list(dirPath);
this.searchSuggestions.add(...searches.filter((x) => x != ""));
};
restoreSearch = (): void => {
const lastSearchPath: string[] = this.getLastSearchPath();
const lastSearch: string | null =
this.coreViewModel.storageModel.read(lastSearchPath);
if (lastSearch != null) {
this.searchViewModel.search(lastSearch);
}
const lastSearchPath: string[] = this.getLastSearchPath();
const lastSearch: string | null =
this.coreViewModel.storageModel.read(lastSearchPath);
if (lastSearch != null) {
this.searchViewModel.search(lastSearch);
}
};
loadData = (): void => {
this.restoreLastUsedView();
this.loadTasks();
this.loadSearchSuggestions();
this.restoreLastUsedView();
this.loadTasks();
this.loadSearchSuggestions();
};
// exit
handleContextClose = (fromHistoryEvent: boolean): void => {
this.taskPageViewModel.handleBoardClosed(this);
this.taskPageViewModel.handleBoardClosed(this);
if (!fromHistoryEvent) return;
this.taskPageViewModel.storeLastUsedBoard();
if (!fromHistoryEvent) return;
this.taskPageViewModel.storeLastUsedBoard();
};
// init
constructor(
public readonly coreViewModel: CoreViewModel,
public readonly chatViewModel: ChatViewModel,
public readonly boardsAndTasksModel: BoardsAndTasksModel,
public readonly taskPageViewModel: TaskPageViewModel,
public readonly boardInfo: BoardInfoFileContent,
public readonly coreViewModel: CoreViewModel,
public readonly chatViewModel: ChatViewModel,
public readonly boardsAndTasksModel: BoardsAndTasksModel,
public readonly taskPageViewModel: TaskPageViewModel,
public readonly boardInfo: BoardInfoFileContent,
) {
super(coreViewModel, chatViewModel, boardsAndTasksModel, "board");
super(coreViewModel, chatViewModel, boardsAndTasksModel, "board");
// load
this.preloadData();
// load
this.preloadData();
// search
this.searchViewModel = new SearchViewModel(
this.taskViewModels,
this.filteredTaskViewModels,
TaskViewModel.getStringsForFilter,
this.searchSuggestions,
);
this.searchViewModel.appliedQuery.subscribeSilent((newQuery) => {
this.handleNewSearch(newQuery);
});
this.restoreSearch();
this.searchSuggestions.handleRemovals(this.handleSearchRemoved);
// search
this.searchViewModel = new SearchViewModel(
this.taskViewModels,
this.filteredTaskViewModels,
TaskViewModel.getStringsForFilter,
this.searchSuggestions,
);
this.searchViewModel.appliedQuery.subscribeSilent((newQuery) => {
this.handleNewSearch(newQuery);
});
this.restoreSearch();
this.searchSuggestions.handleRemovals(this.handleSearchRemoved);
this.isFilterActive = React.createProxyState(
[this.searchViewModel.appliedQuery],
() => this.searchViewModel.appliedQuery.value != "",
);
this.isFilterActive = React.createProxyState(
[this.searchViewModel.appliedQuery],
() => this.searchViewModel.appliedQuery.value != "",
);
// subscriptions
this.isSelected = React.createProxyState(
[this.taskPageViewModel.selectedBoardId],
() =>
this.taskPageViewModel.selectedBoardId.value ==
this.boardInfo.fileId,
);
// subscriptions
this.isSelected = React.createProxyState(
[this.taskPageViewModel.selectedBoardId],
() =>
this.taskPageViewModel.selectedBoardId.value ==
this.boardInfo.fileId,
);
this.color.subscribe(() => {
if (this.isSelected.value == false) return;
if (this.chatViewModel.selectedPage.value != ChatPageTypes.Tasks)
return;
this.applyColor();
});
this.color.subscribe(() => {
if (this.isSelected.value == false) return;
if (this.chatViewModel.selectedPage.value != ChatPageTypes.Tasks)
return;
this.applyColor();
});
this.selectedPage.subscribeSilent(() => {
this.storeLastUsedView();
});
this.selectedPage.subscribeSilent(() => {
this.storeLastUsedView();
});
this.searchViewModel.appliedQuery.subscribeSilent(()=> {
this.resetPinchZoom();
})
this.searchViewModel.appliedQuery.subscribeSilent(() => {
this.resetPinchZoom();
});
// handlers
boardsAndTasksModel.taskHandlerManager.setHandler(
this.boardInfo.fileId,
(taskFileContent: TaskFileContent) => {
if (taskFileContent.boardId != this.boardInfo.fileId) return;
this.showTask(taskFileContent);
this.updateTaskIndices();
},
);
// handlers
boardsAndTasksModel.taskHandlerManager.setHandler(
this.boardInfo.fileId,
(taskFileContent: TaskFileContent) => {
if (taskFileContent.boardId != this.boardInfo.fileId) return;
this.showTask(taskFileContent);
this.updateTaskIndices();
},
);
// keystrokes
this.registerKeyStroke(CommonKeys.Filter, this.showFilterModal);
this.registerKeyStroke(CommonKeys.Reset, this.resetFilter); //
this.registerKeyStroke(CommonKeys.CloseOrCancel, this.hideFilterModal);
this.registerKeyStroke(CommonKeys.Settings, this.showSettings);
this.registerKeyStroke(CommonKeys.Apply, this.hideSettings);
this.registerKeyStroke(CommonKeys.Create, this.createTask);
// keystrokes
this.registerKeyStroke(CommonKeys.Filter, this.showFilterModal);
this.registerKeyStroke(CommonKeys.Reset, this.resetFilter); //
this.registerKeyStroke(CommonKeys.CloseOrCancel, this.hideFilterModal);
this.registerKeyStroke(CommonKeys.Settings, this.showSettings);
this.registerKeyStroke(CommonKeys.Apply, this.hideSettings);
this.registerKeyStroke(CommonKeys.Create, this.createTask);
this.registerKeyStroke(
"j",
() => (this.selectedPage.value = BoardPageTypes.List),
);
this.registerKeyStroke(
"k",
() => (this.selectedPage.value = BoardPageTypes.Kanban),
);
this.registerKeyStroke(
"l",
() => (this.selectedPage.value = BoardPageTypes.StatusGrid),
);
this.registerKeyStroke(
"j",
() => (this.selectedPage.value = BoardPageTypes.List),
);
this.registerKeyStroke(
"k",
() => (this.selectedPage.value = BoardPageTypes.Kanban),
);
this.registerKeyStroke(
"l",
() => (this.selectedPage.value = BoardPageTypes.StatusGrid),
);
this.taskPageViewModel.registerContext(this.boardInfo.fileId, this);
this.taskPageViewModel.registerContext(this.boardInfo.fileId, this);
}
}
export enum BoardViewModelSubPaths {
LastUsedView = "last-used-view",
PreviousSearches = "previous-searches",
LastSearch = "last-search",
PreviousSearches = "previous-searches",
LastSearch = "last-search",
}
// types
export enum BoardPageTypes {
List = "list",
Kanban = "kanban",
StatusGrid = "status-grid",
Kanban = "kanban",
StatusGrid = "status-grid",
}
+3 -3
View File
@@ -222,9 +222,9 @@ export default class CalendarPageViewModel extends TaskContainingPageViewModel {
// context
handleContextClose = (fromHistoryEvent: boolean): void | boolean => {
if (!fromHistoryEvent) return;
return true;
}
if (!fromHistoryEvent) return;
return true;
};
// init
constructor(
+19 -20
View File
@@ -1,34 +1,33 @@
import * as React from "../../react";
import StorageViewModel from "../Global/storageViewModel";
import SettingsViewModel from "../Global/settingsViewModel";
import FileTransferViewModel from "../Global/fileTransferViewModel";
import CoreViewModel, { Context } from "../Global/coreViewModel";
import ConnectionViewModel from "../Global/connectionViewModel";
import * as React from "../../react";
import { CommonKeys } from "../../View/keystrokes";
export default class HomeViewModel extends Context {
constructor(
public readonly coreViewModel: CoreViewModel,
public readonly settingsViewModel: SettingsViewModel,
public readonly fileTransferViewModel: FileTransferViewModel,
public readonly storageViewModel: StorageViewModel,
public readonly connectionViewModel: ConnectionViewModel,
public readonly coreViewModel: CoreViewModel,
public readonly settingsViewModel: SettingsViewModel,
public readonly fileTransferViewModel: FileTransferViewModel,
public readonly storageViewModel: StorageViewModel,
public readonly connectionViewModel: ConnectionViewModel,
) {
super("home");
super("home");
this.coreViewModel.context = this;
this.coreViewModel.context = this;
this.registerKeyStroke(
CommonKeys.Settings,
this.settingsViewModel.showSettingsModal,
);
this.registerKeyStroke(
"t",
this.fileTransferViewModel.showDirectionSelectionModal,
);
this.registerKeyStroke("e", this.storageViewModel.showStorageModal);
this.registerKeyStroke("x", this.connectionViewModel.disconnect);
this.registerKeyStroke("c", this.connectionViewModel.connect);
this.registerKeyStroke(
CommonKeys.Settings,
this.settingsViewModel.showSettingsModal,
);
this.registerKeyStroke(
"t",
this.fileTransferViewModel.showDirectionSelectionModal,
);
this.registerKeyStroke("e", this.storageViewModel.showStorageModal);
this.registerKeyStroke("x", this.connectionViewModel.disconnect);
this.registerKeyStroke("c", this.connectionViewModel.connect);
}
}
+46 -46
View File
@@ -197,61 +197,61 @@ export default class MessagePageViewModel extends Context {
};
// context
handleContextClose = (fromHistoryEvent: boolean):boolean=> {
if (fromHistoryEvent) {
if (this.replyViewSelectedMessage.value == undefined) return true;
handleContextClose = (fromHistoryEvent: boolean): boolean | void => {
if (fromHistoryEvent) {
if (this.replyViewSelectedMessage.value == undefined) return true;
this.resetReplyView();
return false;
}
}
this.resetReplyView();
return false;
}
};
// init
constructor(
public readonly coreViewModel: CoreViewModel,
public readonly chatViewModel: ChatViewModel,
public readonly contactListViewModel: ContactListViewModel,
public readonly coreViewModel: CoreViewModel,
public readonly chatViewModel: ChatViewModel,
public readonly contactListViewModel: ContactListViewModel,
) {
super("message-page");
this.restoreFilter();
super("message-page");
this.restoreFilter();
// states
this.cannotSendMessage = React.createProxyState(
[
this.chatViewModel.settingsViewModel.username,
this.composingMessage,
],
() =>
this.chatViewModel.settingsViewModel.username.value == "" ||
this.composingMessage.value == "",
);
// states
this.cannotSendMessage = React.createProxyState(
[
this.chatViewModel.settingsViewModel.username,
this.composingMessage,
],
() =>
this.chatViewModel.settingsViewModel.username.value == "" ||
this.composingMessage.value == "",
);
this.searchViewModel = new SearchViewModel(
this.chatMessageViewModels,
this.filteredMessageViewModels,
(chatMessageViewModel) => [chatMessageViewModel.body.value],
new React.ListState(),
);
this.searchViewModel = new SearchViewModel(
this.chatMessageViewModels,
this.filteredMessageViewModels,
(chatMessageViewModel) => [chatMessageViewModel.body.value],
new React.ListState(),
);
this.isFilterActive = React.createProxyState(
[this.searchViewModel.appliedQuery, this.reactionFilter],
() =>
this.searchViewModel.appliedQuery.value != "" ||
this.reactionFilter.value != undefined,
);
this.isFilterActive = React.createProxyState(
[this.searchViewModel.appliedQuery, this.reactionFilter],
() =>
this.searchViewModel.appliedQuery.value != "" ||
this.reactionFilter.value != undefined,
);
// keystrokes
this.registerKeyStroke(CommonKeys.Filter, this.showFilterModal);
this.registerKeyStroke(CommonKeys.CloseOrCancel, () => {
if (this.isFilterModalOpen.value == true) {
this.hideFilterModal();
} else {
this.replyingMessage.value = undefined;
}
});
this.registerKeyStroke(CommonKeys.Reset, this.resetFilter);
this.registerKeyStroke(CommonKeys.Create, this.setFocus);
// keystrokes
this.registerKeyStroke(CommonKeys.Filter, this.showFilterModal);
this.registerKeyStroke(CommonKeys.CloseOrCancel, () => {
if (this.isFilterModalOpen.value == true) {
this.hideFilterModal();
} else {
this.replyingMessage.value = undefined;
}
});
this.registerKeyStroke(CommonKeys.Reset, this.resetFilter);
this.registerKeyStroke(CommonKeys.Create, this.setFocus);
this.chatViewModel.registerContext(ChatPageTypes.Messages, this);
this.chatViewModel.registerContext(ChatPageTypes.Messages, this);
}
}
+6 -7
View File
@@ -2,7 +2,7 @@ import CoreViewModel, { Context } from "../Global/coreViewModel";
import ChatViewModel, { ChatPageTypes } from "../Chat/chatViewModel";
import * as React from "../../react";
import { Colors } from "../../colors";
import {random} from "../../Model/Utility/crypto";
import { random } from "../../Model/Utility/crypto";
export default class SettingsPageViewModel extends Context {
// state
@@ -75,9 +75,8 @@ export default class SettingsPageViewModel extends Context {
};
randomizeKey = (): void => {
this.encryptionKeyInput.value = random(24);
this.setEncryptionKey();
}
this.encryptionKeyInput.value = random(24);
};
applyColor = (): void => {
this.chatViewModel.setColor(this.color.value);
@@ -123,9 +122,9 @@ export default class SettingsPageViewModel extends Context {
// context
handleContextClose = (fromHistoryEvent: boolean): void | boolean => {
if (!fromHistoryEvent) return;
return true;
}
if (!fromHistoryEvent) return;
return true;
};
// init
constructor(
+3 -3
View File
@@ -168,9 +168,9 @@ export default class TaskPageViewModel extends ContextHost<string> {
// context
handleContextClose = (fromHistoryEvent: boolean): void | boolean => {
if (!fromHistoryEvent) return;
return true;
}
if (!fromHistoryEvent) return;
return true;
};
// init
constructor(
+1 -1
View File
@@ -40,7 +40,7 @@ export default class SearchViewModel<T> {
clear = (): void => {
this.searchInput.value = "";
this.applySearch();
this.applySearch();
};
deleteSuggestion = (suggestion: string): void => {
+2 -2
View File
@@ -30,7 +30,7 @@ main {
z-index: -1;
pointer-events: none;
opacity: .3;
opacity: 0.3;
}
#background-wrapper > div > div {
@@ -143,6 +143,6 @@ body[theme="dark"] #background-wrapper #grass-2 {
}
body[keystroke-active] [ctkeystroke]::after {
opacity: .7;
opacity: 0.7;
transform: translateY(0px);
}
+23 -23
View File
@@ -85,32 +85,32 @@ chatListViewModel.selectedChat.subscribe(() => {
document.body.append(
<div id="background-wrapper">
<div>
<div id="sky"></div>
<div id="grass-1"></div>
<div id="grass-2"></div>
</div>
<div>
<div id="sky"></div>
<div id="grass-1"></div>
<div id="grass-2"></div>
</div>
</div>,
);
document
.querySelector("main")!
.append(
HomePage(
coreViewModel,
storageViewModel,
settingsViewModel,
connectionViewModel,
fileTransferViewModel,
chatListViewModel,
),
ChatPageWrapper(coreViewModel, chatListViewModel),
OnboardingModalWrapper(coreViewModel, onboardingViewModel),
ConnectionModal(coreViewModel, connectionViewModel),
DataTransferModalWrapper(
coreViewModel,
connectionViewModel,
fileTransferViewModel,
),
StorageModal(coreViewModel, storageViewModel),
SettingsModal(coreViewModel, settingsViewModel),
HomePage(
coreViewModel,
storageViewModel,
settingsViewModel,
connectionViewModel,
fileTransferViewModel,
chatListViewModel,
),
ChatPageWrapper(coreViewModel, chatListViewModel),
OnboardingModalWrapper(coreViewModel, onboardingViewModel),
ConnectionModal(coreViewModel, connectionViewModel),
DataTransferModalWrapper(
coreViewModel,
connectionViewModel,
fileTransferViewModel,
),
StorageModal(coreViewModel, storageViewModel),
SettingsModal(coreViewModel, settingsViewModel),
);