add method for filtering

This commit is contained in:
marlon-erler
2026-09-23 00:09:09 +02:00
parent 3458799a3b
commit 26c99085d2
61 changed files with 1703 additions and 1623 deletions
View File
+1 -2
View File
@@ -157,8 +157,7 @@ export default class FileTransferModel {
};
readonly prepareFileForSending = (filePath: string[]): string => {
const fileContent: string | null =
this.storageModel.read(filePath);
const fileContent: string | null = this.storageModel.read(filePath);
if (fileContent == null) return "";
const fileData: FileData = {
Binary file not shown.
+101 -79
View File
@@ -1,5 +1,5 @@
import { checkIsValidObject, checkMatchesObjectStructure } from "./typeSafety";
import * as Hammer from "hammerjs";
import * as React from "../../react";
export interface Stringifiable {
toString(): string;
@@ -18,7 +18,7 @@ export function createTimestamp(): string {
return new Date().toISOString();
}
// filters etc
// filters
export type StringEntryObject = { [key: string]: Stringifiable | undefined };
export function filterObjectsByStringEntries<T>(
@@ -28,7 +28,7 @@ export function filterObjectsByStringEntries<T>(
): Set<T> {
const matches: Set<T> = new Set();
object_loop: for (const object of objects) {
for (const object of objects) {
const doesMatch: boolean = checkDoesObjectMatchReference(
reference,
converter(object),
@@ -39,79 +39,6 @@ export function filterObjectsByStringEntries<T>(
return matches;
}
export function checkDoesObjectMatchReference(
reference: StringEntryObject,
stringEntryObject: StringEntryObject,
explicitEmptyValue: boolean = false,
): boolean {
reference_entry_loop: for (const referenceEntry of Object.entries(
reference,
)) {
const [referenceKey, referenceValue] = referenceEntry;
const stringEntryObjectValue: Stringifiable | undefined =
stringEntryObject[referenceKey];
if (referenceValue == undefined) return false;
if (referenceValue[0] == "-") {
const strippedReferenceValue: string = referenceValue
.toString()
.substring(1);
// property may not exist
if (
strippedReferenceValue == "" &&
stringEntryObjectValue != undefined &&
stringEntryObjectValue != ""
) {
return false;
}
// property may not match
if (stringEntryObjectValue == strippedReferenceValue) {
return false;
}
} else {
if (explicitEmptyValue == false) {
// property must exist but be anything
if (
referenceValue == "" &&
(stringEntryObjectValue == undefined ||
stringEntryObjectValue == "")
) {
return false;
} else if (referenceValue == "") {
continue reference_entry_loop;
}
}
// property must match
if (stringEntryObjectValue != referenceValue) {
return false;
}
}
}
return true;
}
export function collectObjectValuesForKey<T>(
key: string,
converter: (object: T) => StringEntryObject,
objects: T[],
): string[] {
const values: Set<string> = new Set();
for (const object of objects) {
const stringEntryObject: StringEntryObject = converter(object);
const stringEntryObjectValue: Stringifiable | undefined =
stringEntryObject[key];
if (stringEntryObjectValue == undefined) continue;
values.add(stringEntryObjectValue.toString());
}
return [...values.values()];
}
export function filterObjectsByWords<T>(
query: string,
getStringsOfObject: (object: T) => string[],
@@ -119,7 +46,7 @@ export function filterObjectsByWords<T>(
): Set<T> {
const matches: Set<T> = new Set();
object_loop: for (const object of objects) {
for (const object of objects) {
const doesMatch: boolean = checkDoesObjectMatchSearch(
query,
getStringsOfObject,
@@ -170,6 +97,26 @@ export function checkDoesObjectMatchSearch<T>(
return true;
}
export default function implementFilter<T>(
allItems: React.ListState<T>|React.MapState<T>,
matches: React.ListState<T>,
query: React.State<string>,
itemToString: (item: T) => string,
) {
function update() {
matches.clear();
allItems.value.forEach((item: T) => {
if (!itemToString(item).includes(query.value)) return;
matches.add(item);
allItems.handleRemoval(item, () => {
matches.remove(item);
});
});
}
query.subscribe(update);
allItems.handleAddition(update);
}
// handlers
export type Handler<T> = (item: T) => void;
@@ -333,8 +280,8 @@ export function implementPinchZoom(canvas: HTMLElement) {
let x: number, y: number;
if (_offset) [x, y] = _offset;
else {
x = currentX
y = currentY
x = currentX;
y = currentY;
}
el.style.transform = `scale(${factor.toString()}) translate(${x}px, ${y}px)`;
currentX = x;
@@ -409,3 +356,78 @@ export function implementPinchZoom(canvas: HTMLElement) {
]);
});
}
// objecs
export function collectObjectValuesForKey<T>(
key: string,
converter: (object: T) => StringEntryObject,
objects: T[],
): string[] {
const values: Set<string> = new Set();
for (const object of objects) {
const stringEntryObject: StringEntryObject = converter(object);
const stringEntryObjectValue: Stringifiable | undefined =
stringEntryObject[key];
if (stringEntryObjectValue == undefined) continue;
values.add(stringEntryObjectValue.toString());
}
return [...values.values()];
}
// type safety
export function checkDoesObjectMatchReference(
reference: StringEntryObject,
stringEntryObject: StringEntryObject,
explicitEmptyValue: boolean = false,
): boolean {
reference_entry_loop: for (const referenceEntry of Object.entries(
reference,
)) {
const [referenceKey, referenceValue] = referenceEntry;
const stringEntryObjectValue: Stringifiable | undefined =
stringEntryObject[referenceKey];
if (referenceValue == undefined) return false;
if (referenceValue[0] == "-") {
const strippedReferenceValue: string = referenceValue
.toString()
.substring(1);
// property may not exist
if (
strippedReferenceValue == "" &&
stringEntryObjectValue != undefined &&
stringEntryObjectValue != ""
) {
return false;
}
// property may not match
if (stringEntryObjectValue == strippedReferenceValue) {
return false;
}
} else {
if (explicitEmptyValue == false) {
// property must exist but be anything
if (
referenceValue == "" &&
(stringEntryObjectValue == undefined ||
stringEntryObjectValue == "")
) {
return false;
} else if (referenceValue == "") {
continue reference_entry_loop;
}
}
// property must match
if (stringEntryObjectValue != referenceValue) {
return false;
}
}
}
return true;
}
+1 -1
View File
@@ -1,4 +1,3 @@
import * as React from "../../react";
import { ViewController } from "../viewController";
import { TaskViewModelToEntry } from "../Components/taskEntry";
import {
@@ -6,6 +5,7 @@ import {
createPropertyValueIndexState,
} from "../Components/propertyValueList";
import { FilteredList } from "../Components/filteredList";
import * as React from "../../react";
import { TaskCategoryBulkChangeViewModel } from "../../ViewModel/Utility/taskPropertyBulkChangeViewModel";
import TaskViewModel from "../../ViewModel/Pages/taskViewModel";
import BoardViewModel from "../../ViewModel/Pages/boardViewModel";
+1 -1
View File
@@ -1,4 +1,3 @@
import * as React from "../../react";
import { BoardStatusGridPage } from "./boardStatusGridPage";
import { BoardKanbanPage } from "./boardKanbanPage";
import { ViewController } from "../viewController";
@@ -7,6 +6,7 @@ import { SearchModal } from "../Modals/searchModal";
import { BoardSettingsModal } from "../Modals/boardSettingsModal";
import { TaskViewModelToEntry } from "../Components/taskEntry";
import { BoardViewToggleButton } from "../Components/boardViewToggleButton";
import * as React from "../../react";
import BoardViewModel, {
BoardPageTypes,
} from "../../ViewModel/Pages/boardViewModel";
+1 -1
View File
@@ -1,4 +1,3 @@
import * as React from "../../react";
import { ViewController } from "../viewController";
import { TaskViewModelToEntry } from "../Components/taskEntry";
import {
@@ -8,6 +7,7 @@ import {
createSortedPropertyValueState,
} from "../Components/propertyValueList";
import { FilteredList } from "../Components/filteredList";
import * as React from "../../react";
import {
TaskCategoryBulkChangeViewModel,
TaskStatusBulkChangeViewModel,
+1 -1
View File
@@ -1,10 +1,10 @@
import * as React from "../../react";
import "./calendarPage.css";
import { ViewController } from "../viewController";
import { TaskSettingsModal } from "../Modals/taskSettingsModal";
import { TaskViewModelToEntry } from "../Components/taskEntry";
import { PlaceholderView } from "../Components/placeholderView";
import { MonthGrid } from "../Components/monthGrid";
import * as React from "../../react";
import CalendarPageViewModel from "../../ViewModel/Pages/calendarPageViewModel";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
+1 -1
View File
@@ -1,9 +1,9 @@
import * as React from "../../react";
import "./messagePage.css";
import { ViewController } from "../viewController";
import { MessageFilterModal } from "../Modals/messageFilterModal";
import { ReplyPreview } from "../Components/replyPreview";
import { ChatMessage } from "../Components/chatMessage";
import * as React from "../../react";
import MessagePageViewModel from "../../ViewModel/Pages/messagePageViewModel";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel";
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from "../../react";
import { DeletableListItem } from "../Components/deletableListItem";
import { DangerousActionButton } from "../Components/dangerousActionButton";
import { ColorPicker } from "../Components/colorPicker";
import * as React from "../../react";
import SettingsPageViewModel from "../../ViewModel/Pages/settingsPageViewModel";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
+3 -3
View File
@@ -1,9 +1,9 @@
import * as React from "../../react";
import "./taskPage.css";
import { BoardPage } from "./boardPage";
import { ViewController } from "../viewController";
import { PlaceholderView } from "../Components/placeholderView";
import { BoardViewModelToEntry } from "../Components/boardEntry";
import * as React from "../../react";
import TaskPageViewModel from "../../ViewModel/Pages/taskPageViewModel";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
@@ -62,7 +62,7 @@ export function TaskPage(
<div class="flex-row width-input">
<input
class="no-outline"
bind:value={taskPageViewModel.newBoardNameInput}
bind:value={taskPageViewModel.boardQuery}
on:enter={taskPageViewModel.createBoard}
placeholder={
coreViewModel.translations.chatPage.task
@@ -89,7 +89,7 @@ export function TaskPage(
class="grid gap"
style="grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr))"
children:append={[
taskPageViewModel.boardViewModels,
taskPageViewModel.boardMatches,
BoardViewModelToEntry,
]}
></div>
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from "../../react";
import { ViewController } from "../viewController";
import * as React from "../../react";
import BoardViewModel from "../../ViewModel/Pages/boardViewModel";
export function BoardEntry(boardViewModel: BoardViewModel) {
@@ -1,5 +1,5 @@
import * as React from "../../react";
import { RibbonButton } from "./ribbonButton";
import * as React from "../../react";
import BoardViewModel, {
BoardPageTypes,
} from "../../ViewModel/Pages/boardViewModel";
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from "../../react";
import { MessageReactionButtonRow } from "./messageReactionButtonRow";
import { InlineReply } from "./inlineReply";
import { ChatMessageInfoModal } from "../Modals/chatMessageInfoModal";
import * as React from "../../react";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel";
import { ChatMessageStatuses } from "../../Model/Chat/chatModel";
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from "../../react";
import { RibbonButton } from "./ribbonButton";
import * as React from "../../react";
import ChatViewModel, {
ChatPageTypes,
} from "../../ViewModel/Chat/chatViewModel";
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from "../../react";
import { ViewController } from "../viewController";
import * as React from "../../react";
import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel";
export function InlineReply(chatMessageViewModel: ChatMessageViewModel) {
@@ -1,5 +1,5 @@
import * as React from "../../react";
import { MessageReactionButton } from "./messageReactionButton";
import * as React from "../../react";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel";
import { ReactionSymbols } from "../../Model/Chat/chatModel";
+1 -1
View File
@@ -1,6 +1,6 @@
import * as React from "../../react";
import "./monthGrid.css";
import { ViewController } from "../viewController";
import * as React from "../../react";
import TaskViewModel from "../../ViewModel/Pages/taskViewModel";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
import { localeCompare } from "../../Model/Utility/utility";
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from "../../react";
import { OptionButton } from "./optionButton";
import * as React from "../../react";
export function OptionButtonList<T>(
options: React.ListState<[string, T]>,
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from "../../react";
import { ViewController } from "../viewController";
import { DangerousActionButton } from "../Components/dangerousActionButton";
import { ColorPicker } from "../Components/colorPicker";
import * as React from "../../react";
import BoardViewModel from "../../ViewModel/Pages/boardViewModel";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from "../../react";
import { MessageReactionEntry } from "../Components/messageReactionEntry";
import { MessageReactionButtonRow } from "../Components/messageReactionButtonRow";
import { InfoTile } from "../Components/infoTile";
import * as React from "../../react";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel";
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from "../../react";
import { DeletableListItem } from "../Components/deletableListItem";
import * as React from "../../react";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
import ConnectionViewModel from "../../ViewModel/Global/connectionViewModel";
+10 -4
View File
@@ -1,6 +1,6 @@
import * as React from "../../react";
import { ViewController } from "../viewController";
import { StringToTextSpan } from "../Components/textSpan";
import * as React from "../../react";
import FileTransferViewModel, {
FileTransferModals,
FileTransferOption,
@@ -758,7 +758,9 @@ function ExportModal(
</span>
<input
bind:value={fileTransferViewModel.exportKey}
on:enter={fileTransferViewModel.downloadFile}
on:enter={
fileTransferViewModel.downloadFile
}
type="password"
></input>
</div>
@@ -777,7 +779,9 @@ function ExportModal(
bind:value={
fileTransferViewModel.exportKeyConfirmation
}
on:enter={fileTransferViewModel.downloadFile}
on:enter={
fileTransferViewModel.downloadFile
}
type="password"
></input>
</div>
@@ -903,7 +907,9 @@ function ImportDecryptionDataModal(
</span>
<input
bind:value={fileTransferViewModel.importKey}
on:enter={fileTransferViewModel.decryptImport}
on:enter={
fileTransferViewModel.decryptImport
}
type="password"
></input>
</div>
+1 -1
View File
@@ -1,6 +1,6 @@
import * as React from "../../react";
import { ViewController } from "../viewController";
import { MessageReactionFilterButton } from "../Components/messageReactionFilterButton";
import * as React from "../../react";
import MessagePageViewModel from "../../ViewModel/Pages/messagePageViewModel";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
import { ReactionSymbols } from "../../Model/Chat/chatModel";
+26 -8
View File
@@ -1,7 +1,7 @@
import { v4 } from "uuid";
import * as React from "../../react";
import { ViewController } from "../viewController";
import { StringToOption } from "../Components/option";
import * as React from "../../react";
import SearchViewModel from "../../ViewModel/Utility/searchViewModel";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
@@ -70,15 +70,24 @@ export function SearchModal<T>(
</button>
</div>
<div
toggle:hidden={searchViewModel.hasNoSuggestions}>
<div toggle:hidden={searchViewModel.hasNoSuggestions}>
<hr></hr>
<h3>{coreViewModel.translations.general.searchSuggestionsLabel}</h3>
<h3>
{
coreViewModel.translations.general
.searchSuggestionsLabel
}
</h3>
<div
class="flex-column gap"
children:append={[
searchViewModel.suggestions,
(suggestion: string) => SuggestionView(suggestion, searchViewModel, coreViewModel),
(suggestion: string) =>
SuggestionView(
suggestion,
searchViewModel,
coreViewModel,
),
]}
></div>
</div>
@@ -92,8 +101,15 @@ export function SearchModal<T>(
);
}
function SuggestionView<T>(suggestion: string, searchViewModel: SearchViewModel<T>, coreViewModel: CoreViewModel) {
const isApplied = React.createProxyState([searchViewModel.appliedQuery], ()=>searchViewModel.appliedQuery.value == suggestion);
function SuggestionView<T>(
suggestion: string,
searchViewModel: SearchViewModel<T>,
coreViewModel: CoreViewModel,
) {
const isApplied = React.createProxyState(
[searchViewModel.appliedQuery],
() => searchViewModel.appliedQuery.value == suggestion,
);
function deleteSuggestion() {
searchViewModel.deleteSuggestion(suggestion);
@@ -103,7 +119,8 @@ function SuggestionView<T>(suggestion: string, searchViewModel: SearchViewModel<
searchViewModel.search(suggestion);
}
return <div class="flex-row surface align-center">
return (
<div class="flex-row surface align-center">
<span class="width-100 flex-1 padding-h">{suggestion}</span>
<button
class="danger"
@@ -121,4 +138,5 @@ function SuggestionView<T>(suggestion: string, searchViewModel: SearchViewModel<
<span class="icon">check</span>
</button>
</div>
);
}
+1 -1
View File
@@ -1,10 +1,10 @@
import * as React from "../../react";
import { SplitModal } from "./splitModal";
import { languageNames } from "../translations";
import { PlaceholderView } from "../Components/placeholderView";
import { OptionButtonList } from "../Components/optionButtonList";
import { NavigationButton } from "../Components/navigationButton";
import { InfoTile } from "../Components/infoTile";
import * as React from "../../react";
import SettingsViewModel, {
SettingsModalPages,
} from "../../ViewModel/Global/settingsViewModel";
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from "../../react";
import "./splitModal.css";
import * as React from "../../react";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
export function SplitModal<T>(
+1 -1
View File
@@ -1,7 +1,7 @@
import * as React from "../../react";
import { SplitModal } from "./splitModal";
import { DirectoryItemList } from "../Components/directoryItemList";
import { DangerousActionButton } from "../Components/dangerousActionButton";
import * as React from "../../react";
import StorageViewModel from "../../ViewModel/Global/storageViewModel";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
import { PATH_COMPONENT_SEPARATOR } from "../../Model/Global/storageModel";
+35 -30
View File
@@ -1,5 +1,4 @@
import { v4 } from "uuid";
import * as React from "../../react";
import {
Entry,
Option,
@@ -7,6 +6,7 @@ import {
VersionIdToOption,
} from "../Components/option";
import { DangerousActionButton } from "../Components/dangerousActionButton";
import * as React from "../../react";
import TaskViewModel from "../../ViewModel/Pages/taskViewModel";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
@@ -25,7 +25,13 @@ export function TaskSettingsModal(
};
return (
<div class="modal task-settings" open toggle:full-description={taskViewModel.isPresentingFullScreenDescription}>
<div
class="modal task-settings"
open
toggle:full-description={
taskViewModel.isPresentingFullScreenDescription
}
>
<div>
<main id="standard-main">
<h2>
@@ -42,8 +48,8 @@ export function TaskSettingsModal(
<div>
<span>
{
coreViewModel.translations.chatPage.task
.taskNameLabel
coreViewModel.translations.chatPage
.task.taskNameLabel
}
</span>
<input
@@ -57,8 +63,8 @@ export function TaskSettingsModal(
<div>
<span>
{
coreViewModel.translations.chatPage.task
.taskBoardLabel
coreViewModel.translations.chatPage
.task.taskBoardLabel
}
</span>
<select
@@ -78,8 +84,8 @@ export function TaskSettingsModal(
<div>
<span>
{
coreViewModel.translations.chatPage.task
.taskDescriptionLabel
coreViewModel.translations.chatPage
.task.taskDescriptionLabel
}
</span>
<textarea
@@ -91,10 +97,13 @@ export function TaskSettingsModal(
<div class="flex-row justify-end">
<button
class="width-50"
on:click={taskViewModel.openFullscreenDecription}
on:click={
taskViewModel.openFullscreenDecription
}
>
{
coreViewModel.translations.general.fullscreenButton
coreViewModel.translations.general
.fullscreenButton
}
<span class="icon">fullscreen</span>
@@ -110,8 +119,8 @@ export function TaskSettingsModal(
<div>
<span>
{
coreViewModel.translations.chatPage.task
.taskCategoryLabel
coreViewModel.translations.chatPage
.task.taskCategoryLabel
}
</span>
<input
@@ -124,7 +133,8 @@ export function TaskSettingsModal(
hidden
id={categorySuggestionId}
children:append={[
taskViewModel.containingViewModel.taskCategorySuggestions,
taskViewModel.containingViewModel
.taskCategorySuggestions,
StringToOption,
]}
></datalist>
@@ -134,8 +144,8 @@ export function TaskSettingsModal(
<div>
<span>
{
coreViewModel.translations.chatPage.task
.taskStatusLabel
coreViewModel.translations.chatPage
.task.taskStatusLabel
}
</span>
<input
@@ -148,7 +158,8 @@ export function TaskSettingsModal(
hidden
id={statusSuggestionId}
children:append={[
taskViewModel.containingViewModel.taskStatusSuggestions,
taskViewModel.containingViewModel
.taskStatusSuggestions,
StringToOption,
]}
></datalist>
@@ -158,8 +169,8 @@ export function TaskSettingsModal(
<div>
<span>
{
coreViewModel.translations.chatPage.task
.taskPriorityLabel
coreViewModel.translations.chatPage
.task.taskPriorityLabel
}
</span>
<input
@@ -176,8 +187,8 @@ export function TaskSettingsModal(
<div>
<span>
{
coreViewModel.translations.chatPage.task
.taskDateLabel
coreViewModel.translations.chatPage
.task.taskDateLabel
}
</span>
<input
@@ -192,8 +203,8 @@ export function TaskSettingsModal(
<div>
<span>
{
coreViewModel.translations.chatPage.task
.taskTimeLabel
coreViewModel.translations.chatPage
.task.taskTimeLabel
}
</span>
<input
@@ -245,10 +256,7 @@ export function TaskSettingsModal(
bind:value={taskViewModel.description}
></textarea>
</main>
<div
class="flex-row width-100"
id="fullscreen-exit"
>
<div class="flex-row width-100" id="fullscreen-exit">
<button
class="flex"
on:click={taskViewModel.closeFullscreenDecription}
@@ -257,10 +265,7 @@ export function TaskSettingsModal(
<span class="icon">fullscreen_exit</span>
</button>
</div>
<div
class="flex-row width-100"
id="controls"
>
<div class="flex-row width-100" id="controls">
<button
class="flex"
on:click={taskViewModel.closeAndDiscard}
+2 -1
View File
@@ -273,7 +273,8 @@ button
}
}
.modal.task-settings:not([full-description]) :is(#fullscreen-exit, #fullscreen-main) {
.modal.task-settings:not([full-description])
:is(#fullscreen-exit, #fullscreen-main) {
display: none;
}
+3 -2
View File
@@ -1,4 +1,3 @@
import * as React from "../react";
import { ViewController } from "./viewController";
import "./homePage.css";
import { ChatViewToggleButton } from "./Components/chatViewToggleButton";
@@ -6,6 +5,7 @@ import { TaskPage } from "./ChatPages/taskPage";
import { SettingsPage } from "./ChatPages/settingsPage";
import { MessagePage } from "./ChatPages/messagePage";
import { CalendarPage } from "./ChatPages/calendarPage";
import * as React from "../react";
import CoreViewModel from "../ViewModel/Global/coreViewModel";
import ChatViewModel, { ChatPageTypes } from "../ViewModel/Chat/chatViewModel";
@@ -96,7 +96,8 @@ export function ChatPage(
chatViewModel.connectionViewModel.isConnected
}
toggle:disabled={
chatViewModel.connectionViewModel.hasNoPreviousConnections
chatViewModel.connectionViewModel
.hasNoPreviousConnections
}
>
<span class="icon">signal_disconnected</span>
+1 -1
View File
@@ -1,6 +1,6 @@
import * as React from "../react";
import "./chatPage.css";
import { ChatPage } from "./chatPage";
import * as React from "../react";
import CoreViewModel from "../ViewModel/Global/coreViewModel";
import ChatListViewModel from "../ViewModel/Chat/chatListViewModel";
+1 -1
View File
@@ -1,8 +1,8 @@
import * as React from "../react";
import "./homePage.css";
import { StringToOption } from "./Components/option";
import { HomePageButton } from "./Components/homePageButton";
import { ChatViewModelToChatEntry } from "./Components/chatEntry";
import * as React from "../react";
import StorageViewModel from "../ViewModel/Global/storageViewModel";
import SettingsViewModel from "../ViewModel/Global/settingsViewModel";
import FileTransferViewModel from "../ViewModel/Global/fileTransferViewModel";
+2 -2
View File
@@ -484,7 +484,7 @@ export const allTranslations: {
downloadFileButton: "Herunterladen",
importFileButton: "Importieren",
decryptImportButton: "Entschlüsseln",
incorrectPassphraseError: "Der Schlüssel ist nicht korrekt"
incorrectPassphraseError: "Der Schlüssel ist nicht korrekt",
},
storage: {
@@ -785,7 +785,7 @@ export const allTranslations: {
downloadFileButton: "Descargar",
importFileButton: "Importar",
decryptImportButton: "Descifrar",
incorrectPassphraseError: "La clave no es correcta"
incorrectPassphraseError: "La clave no es correcta",
},
storage: {
+1 -1
View File
@@ -1,9 +1,9 @@
import * as React from "../../react";
import ChatViewModel from "./chatViewModel";
import SettingsViewModel from "../Global/settingsViewModel";
import NotificationViewModel from "../Global/notificationViewModel";
import CoreViewModel from "../Global/coreViewModel";
import ConnectionViewModel from "../Global/connectionViewModel";
import * as React from "../../react";
import { IndexManager } from "../../Model/Utility/utility";
import ChatModel from "../../Model/Chat/chatModel";
+1 -1
View File
@@ -1,6 +1,6 @@
import * as React from "../../react";
import MessagePageViewModel from "../Pages/messagePageViewModel";
import CoreViewModel from "../Global/coreViewModel";
import * as React from "../../react";
import {
ChatMessage,
ChatMessageReaction,
+1 -1
View File
@@ -1,4 +1,3 @@
import * as React from "../../react";
import ChatListViewModel from "./chatListViewModel";
import TaskPageViewModel from "../Pages/taskPageViewModel";
import SettingsPageViewModel from "../Pages/settingsPageViewModel";
@@ -10,6 +9,7 @@ import SettingsViewModel from "../Global/settingsViewModel";
import NotificationViewModel from "../Global/notificationViewModel";
import CoreViewModel, { Context, ContextHost } from "../Global/coreViewModel";
import ConnectionViewModel from "../Global/connectionViewModel";
import * as React from "../../react";
import { Colors } from "../../colors";
import { CommonKeys } from "../../View/keystrokes";
import { Entry } from "../../View/Components/option";
Binary file not shown.
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from "../../react";
import CoreViewModel from "./coreViewModel";
import * as React from "../../react";
export default class ConnectionViewModel {
// state
+8 -5
View File
@@ -49,7 +49,8 @@ export default class CoreViewModel {
};
handleKeyDown = (e: KeyboardEvent): void => {
if (!(e instanceof KeyboardEvent)) return console.trace("NOT A KEY EVENT");
if (!(e instanceof KeyboardEvent))
return console.trace("NOT A KEY EVENT");
if (CoreViewModel.checkIsKeystroke(e) == false) return;
console.log(e.key);
e.preventDefault();
@@ -166,9 +167,8 @@ export class ContextHost<T> extends Context {
const selection = this.contextSelection;
if (!selection) return;
const selectedContext: Context | undefined = this.contexts.get(
selection
);
const selectedContext: Context | undefined =
this.contexts.get(selection);
if (!selectedContext) return;
if (selectedContext != this.currentContext.value) {
this.closeCurrentContext();
@@ -178,7 +178,10 @@ export class ContextHost<T> extends Context {
this.currentContext.value = selectedContext;
};
constructor(contextDebugDescription: string, public coreViewModel: CoreViewModel) {
constructor(
contextDebugDescription: string,
public coreViewModel: CoreViewModel,
) {
super(contextDebugDescription);
}
}
@@ -1,5 +1,5 @@
import * as React from "../../react";
import CoreViewModel, { Context } from "./coreViewModel";
import * as React from "../../react";
import { CommonKeys } from "../../View/keystrokes";
import StorageModel, {
StorageModelSubPaths,
@@ -54,7 +54,9 @@ export default class FileTransferViewModel extends Context {
exportKey: React.State<string> = new React.State("");
exportKeyConfirmation: React.State<string> = new React.State("");
importedFile: React.State<File | undefined> = new React.State<File | undefined>(undefined);
importedFile: React.State<File | undefined> = new React.State<
File | undefined
>(undefined);
importedFileString: React.State<string> = new React.State("");
importKey: React.State<string> = new React.State("");
isImportKeyCorrect: React.State<boolean> = new React.State(true);
@@ -1,6 +1,6 @@
import * as React from "../../react";
import { ChatPageTypes } from "../Chat/chatViewModel";
import ChatListViewModel from "../Chat/chatListViewModel";
import * as React from "../../react";
import ChatModel, { ChatMessage } from "../../Model/Chat/chatModel";
export default class NotificationViewModel {
@@ -56,7 +56,8 @@ export default class NotificationViewModel {
return this.stopLoop();
}
const notification: Notification|undefined = this.messagesInMarquee.shift();
const notification: Notification | undefined =
this.messagesInMarquee.shift();
if (!notification) return;
this.seenMessageIds.delete(notification.messageId);
this.marquee.value = notification;
+30 -19
View File
@@ -1,5 +1,5 @@
import * as React from "../../react";
import CoreViewModel, { Context } from "./coreViewModel";
import * as React from "../../react";
import { CommonKeys } from "../../View/keystrokes";
import { Languages, ThemeSettings } from "../../Model/Global/settingsModel";
@@ -13,12 +13,12 @@ export default class SettingsViewModel extends Context {
requiresReload = new React.State<boolean>(false);
firstDayOfWeek: React.State<string> = new React.State("0");
language: React.State<Languages | string> = new React.State<Languages | string>(
Languages.English,
);
theme: React.State<ThemeSettings | string> = new React.State<ThemeSettings | string>(
ThemeSettings.System,
);
language: React.State<Languages | string> = new React.State<
Languages | string
>(Languages.English);
theme: React.State<ThemeSettings | string> = new React.State<
ThemeSettings | string
>(ThemeSettings.System);
// guards
cannotSetName: React.State<boolean> = React.createProxyState(
@@ -82,23 +82,34 @@ export default class SettingsViewModel extends Context {
} else if (hour < 14) {
return DynamicSceneDay;
} else if (hour < 17) {
return DynamicSceneAfternoon
return DynamicSceneAfternoon;
} else {
return DynamicSceneSunset
}
return DynamicSceneSunset;
}
};
setScene = (scene: DynamicScene): void => {
document.body.style.setProperty("--backdrop-grass-filter", `brightness(${scene.brightness})`);
document.body.style.setProperty(
"--backdrop-grass-filter",
`brightness(${scene.brightness})`,
);
function setSkyColor(tone: 1|2, hue: number, saturation: number, luma: number) {
document.body.style.setProperty(`--sky-${tone}`, `hsl(${hue}, ${saturation}%, ${luma}%)`);
function setSkyColor(
tone: 1 | 2,
hue: number,
saturation: number,
luma: number,
) {
document.body.style.setProperty(
`--sky-${tone}`,
`hsl(${hue}, ${saturation}%, ${luma}%)`,
);
}
setSkyColor(1, scene.hue1, scene.saturation, scene.luma);
setSkyColor(2, scene.hue2, scene.saturation - 10, scene.luma - 10);
document.body.setAttribute("theme", scene.baseTheme);
}
};
// exit
close = (): void => {
@@ -180,7 +191,7 @@ export const DynamicSceneNight: DynamicScene = {
luma: 25,
brightness: 0.4,
baseTheme: ThemeSettings.Dark,
}
};
export const DynamicSceneSunrise: DynamicScene = {
hue1: 45,
hue2: 190,
@@ -188,7 +199,7 @@ export const DynamicSceneSunrise: DynamicScene = {
luma: 50,
brightness: 1.2,
baseTheme: ThemeSettings.Light,
}
};
export const DynamicSceneDay: DynamicScene = {
hue1: 190,
hue2: 180,
@@ -196,7 +207,7 @@ export const DynamicSceneDay: DynamicScene = {
luma: 70,
brightness: 1.4,
baseTheme: ThemeSettings.Light,
}
};
export const DynamicSceneAfternoon: DynamicScene = {
hue1: 195,
hue2: 220,
@@ -204,7 +215,7 @@ export const DynamicSceneAfternoon: DynamicScene = {
luma: 50,
brightness: 0.8,
baseTheme: ThemeSettings.Light,
}
};
export const DynamicSceneSunset: DynamicScene = {
hue1: 20,
hue2: 260,
@@ -212,4 +223,4 @@ export const DynamicSceneSunset: DynamicScene = {
luma: 50,
brightness: 0.4,
baseTheme: ThemeSettings.Dark,
}
};
+1 -1
View File
@@ -1,5 +1,5 @@
import * as React from "../../react";
import CoreViewModel, { Context } from "./coreViewModel";
import * as React from "../../react";
import { CommonKeys } from "../../View/keystrokes";
import StorageModel, {
PATH_COMPONENT_SEPARATOR,
Binary file not shown.
+4 -8
View File
@@ -1,10 +1,10 @@
import * as React from "../../react";
import TaskViewModel from "./taskViewModel";
import TaskPageViewModel from "./taskPageViewModel";
import TaskContainingPageViewModel from "./taskContainingPageViewModel";
import SearchViewModel from "../Utility/searchViewModel";
import CoreViewModel from "../Global/coreViewModel";
import ChatViewModel, { ChatPageTypes } from "../Chat/chatViewModel";
import * as React from "../../react";
import { Colors } from "../../colors";
import { CommonKeys } from "../../View/keystrokes";
import BoardsAndTasksModel, {
@@ -115,11 +115,7 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
searchTerm,
];
this.coreViewModel.storageModel.write(suggestionPath, "");
if (
!this.searchSuggestions.value.has(
searchTerm,
) && searchTerm != ""
) {
if (!this.searchSuggestions.value.has(searchTerm) && searchTerm != "") {
this.searchSuggestions.add(searchTerm);
}
@@ -133,7 +129,7 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
searchTerm,
];
this.coreViewModel.storageModel.remove(suggestionPath);
}
};
// view
showTask = (taskFileContent: TaskFileContent): void => {
@@ -234,7 +230,7 @@ export default class BoardViewModel extends TaskContainingPageViewModel {
const dirPath: string[] = this.getPreviousSearchesPath();
const searches: string[] =
this.coreViewModel.storageModel.list(dirPath);
this.searchSuggestions.add(...searches.filter(x => x != ""));
this.searchSuggestions.add(...searches.filter((x) => x != ""));
};
restoreSearch = (): void => {
+1 -1
View File
@@ -1,8 +1,8 @@
import * as React from "../../react";
import TaskViewModel from "./taskViewModel";
import TaskContainingPageViewModel from "./taskContainingPageViewModel";
import CoreViewModel from "../Global/coreViewModel";
import ChatViewModel, { ChatPageTypes } from "../Chat/chatViewModel";
import * as React from "../../react";
import { CommonKeys } from "../../View/keystrokes";
import CalendarModel, { MonthGrid } from "../../Model/Files/calendarModel";
import BoardsAndTasksModel, {
+4 -2
View File
@@ -1,8 +1,8 @@
import * as React from "../../react";
import SearchViewModel from "../Utility/searchViewModel";
import CoreViewModel, { Context } from "../Global/coreViewModel";
import ChatViewModel, { ChatPageTypes } from "../Chat/chatViewModel";
import ChatMessageViewModel from "../Chat/chatMessageViewModel";
import * as React from "../../react";
import { CommonKeys } from "../../View/keystrokes";
import {
ChatMessage,
@@ -68,7 +68,9 @@ export default class MessagePageViewModel extends Context {
);
};
setReply = (chatMessageViewModel: ChatMessageViewModel|undefined): void => {
setReply = (
chatMessageViewModel: ChatMessageViewModel | undefined,
): void => {
this.replyingMessage.value = chatMessageViewModel;
this.setFocus();
};
+1 -1
View File
@@ -1,6 +1,6 @@
import * as React from "../../react";
import CoreViewModel, { Context } from "../Global/coreViewModel";
import ChatViewModel, { ChatPageTypes } from "../Chat/chatViewModel";
import * as React from "../../react";
import { Colors } from "../../colors";
export default class SettingsPageViewModel extends Context {
@@ -1,7 +1,7 @@
import * as React from "../../react";
import TaskViewModel from "./taskViewModel";
import CoreViewModel, { Context } from "../Global/coreViewModel";
import ChatViewModel from "../Chat/chatViewModel";
import * as React from "../../react";
import { IndexManager } from "../../Model/Utility/utility";
import BoardsAndTasksModel, {
TaskFileContent,
@@ -21,7 +21,6 @@ export default class TaskContainingPageViewModel extends Context {
taskCategorySuggestions: React.ListState<string> = new React.ListState();
taskStatusSuggestions: React.ListState<string> = new React.ListState();
// methods
createTaskFromBoardId = (boardId: string): void => {
const taskFileContent: TaskFileContent =
+11 -7
View File
@@ -1,9 +1,9 @@
import * as React from "../../react";
import BoardViewModel from "./boardViewModel";
import CoreViewModel, { ContextHost } from "../Global/coreViewModel";
import ChatViewModel, { ChatPageTypes } from "../Chat/chatViewModel";
import * as React from "../../react";
import { CommonKeys } from "../../View/keystrokes";
import { IndexManager } from "../../Model/Utility/utility";
import implementFilter, { IndexManager } from "../../Model/Utility/utility";
import BoardsAndTasksModel, {
BoardInfoFileContent,
} from "../../Model/Files/boardsAndTasksModel";
@@ -26,8 +26,9 @@ export default class TaskPageViewModel extends ContextHost<string> {
};
// state
newBoardNameInput: React.State<string> = new React.State("");
boardQuery: React.State<string> = new React.State("");
boardViewModels: React.MapState<BoardViewModel> = new React.MapState();
boardMatches: React.ListState<BoardViewModel> = new React.ListState();
isShowingBoadList: React.State<boolean> = new React.State(true);
selectedBoardId: React.State<string | undefined> = new React.State<any>(
undefined,
@@ -47,8 +48,8 @@ export default class TaskPageViewModel extends ContextHost<string> {
// guards
cannotCreateBoard: React.State<boolean> = React.createProxyState(
[this.newBoardNameInput],
() => this.newBoardNameInput.value == "",
[this.boardQuery],
() => this.boardQuery.value == "",
);
// methods
@@ -56,8 +57,8 @@ export default class TaskPageViewModel extends ContextHost<string> {
if (this.cannotCreateBoard.value == true) return;
const boardInfoFileContent: BoardInfoFileContent =
this.boardsAndTasksModel.createBoard(this.newBoardNameInput.value);
this.newBoardNameInput.value = "";
this.boardsAndTasksModel.createBoard(this.boardQuery.value);
this.boardQuery.value = "";
this.showBoardInList(boardInfoFileContent);
this.boardsAndTasksModel.updateBoardAndSend(boardInfoFileContent);
@@ -177,6 +178,9 @@ export default class TaskPageViewModel extends ContextHost<string> {
this.chatViewModel = chatViewModel;
// filter
implementFilter(this.boardViewModels, this.boardMatches, this.boardQuery, (board) => board.name.value);
// context
this.chatViewModel.registerContext(ChatPageTypes.Tasks, this);
this.selectedBoardId.subscribeSilent(this.updateContexts);
+12 -6
View File
@@ -1,7 +1,7 @@
import * as React from "../../react";
import TaskContainingPageViewModel from "./taskContainingPageViewModel";
import CoreViewModel, { Context } from "../Global/coreViewModel";
import ChatViewModel from "../Chat/chatViewModel";
import * as React from "../../react";
import { ViewController } from "../../View/viewController";
import { CommonKeys } from "../../View/keystrokes";
import { localeCompare, padZero } from "../../Model/Utility/utility";
@@ -58,7 +58,9 @@ export default class TaskViewModel extends Context {
selectedVersionId: React.State<string> = new React.State("");
versionIds: React.ListState<string> = new React.ListState();
isPresentingFullScreenDescription: React.State<boolean> = new React.State(false);
isPresentingFullScreenDescription: React.State<boolean> = new React.State(
false,
);
// methods
dragStart = (event: DragEvent): void => {
@@ -100,11 +102,11 @@ export default class TaskViewModel extends Context {
openFullscreenDecription = (): void => {
this.isPresentingFullScreenDescription.value = true;
}
};
closeFullscreenDecription = (): void => {
this.isPresentingFullScreenDescription.value = false;
}
};
updateIndex = (): void => {
const index: number =
@@ -118,7 +120,9 @@ export default class TaskViewModel extends Context {
this.category.value,
) == false
) {
this.containingViewModel.taskCategorySuggestions.add(this.category.value);
this.containingViewModel.taskCategorySuggestions.add(
this.category.value,
);
}
if (
@@ -126,7 +130,9 @@ export default class TaskViewModel extends Context {
this.status.value,
) == false
) {
this.containingViewModel.taskStatusSuggestions.add(this.status.value);
this.containingViewModel.taskStatusSuggestions.add(
this.status.value,
);
}
};
+6 -3
View File
@@ -15,7 +15,10 @@ export default class SearchViewModel<T> {
[this.searchInput],
() => this.searchInput.value == "",
);
hasNoSuggestions: React.State<boolean> = React.createProxyState([this.suggestions], ()=>this.suggestions.value.size == 0);
hasNoSuggestions: React.State<boolean> = React.createProxyState(
[this.suggestions],
() => this.suggestions.value.size == 0,
);
// methods
search = (searchTerm: string): void => {
@@ -37,11 +40,11 @@ export default class SearchViewModel<T> {
clear = (): void => {
this.searchInput.value = "";
}
};
deleteSuggestion = (suggestion: string): void => {
this.suggestions.remove(suggestion);
}
};
// init
constructor(
@@ -1,5 +1,5 @@
import * as React from "../../react";
import TaskViewModel from "../Pages/taskViewModel";
import * as React from "../../react";
export default class TaskPropertyBulkChangeViewModel {
taskViewModels: React.ListState<TaskViewModel>;
+5 -3
View File
@@ -44,7 +44,9 @@ main {
}
body {
transition: --sky-1 1s, --sky-2 5s;
transition:
--sky-1 1s,
--sky-2 5s;
}
#background-wrapper #grass-1 {
@@ -52,7 +54,7 @@ body {
--color-2: hsl(150, 60%, 25%);
filter: var(--backdrop-grass-filter);
opacity: .7;
opacity: 0.7;
background-image: linear-gradient(
350deg,
@@ -67,7 +69,7 @@ body {
--color-2: hsl(110, 60%, 35%);
filter: var(--backdrop-grass-filter);
opacity: .7;
opacity: 0.7;
background-image: linear-gradient(
9deg,
+16 -16
View File
@@ -85,7 +85,7 @@ export class ListState<T> extends State<Set<T>> {
items.forEach((item) => {
this.value.delete(item);
this.genericRemovalHandlers.forEach(handler => handler(item));
this.genericRemovalHandlers.forEach((handler) => handler(item));
if (!this.removalHandlers.has(item)) return;
this.removalHandlers.get(item)!.forEach((handler) => handler(item));
this.removalHandlers.delete(item);
@@ -146,7 +146,7 @@ export class MapState<T> extends State<Map<string, T>> {
this.value.delete(key);
this.callSubscriptions();
this.genericRemovalHandlers.forEach(handler => handler(item));
this.genericRemovalHandlers.forEach((handler) => handler(item));
if (!this.removalHandlers.has(item)) return;
this.removalHandlers.get(item)!.forEach((handler) => handler(item));
this.removalHandlers.delete(item);
@@ -183,18 +183,18 @@ export class MapState<T> extends State<Map<string, T>> {
// UTILITY
export function createProxyState<T>(
statesToSubscibe: State<any>[],
fn: () => T
fn: () => T,
): State<T> {
const proxyState = new State<T>(fn());
statesToSubscibe.forEach((state) =>
state.subscribeSilent(() => (proxyState.value = fn()))
state.subscribeSilent(() => (proxyState.value = fn())),
);
return proxyState;
}
export function bulkSubscribe(
statesToSubscibe: State<any>[],
fn: () => void
fn: () => void,
): void {
statesToSubscibe.forEach((state) => state.subscribeSilent(fn));
}
@@ -208,7 +208,7 @@ function persistState(localStorageKey: string, state: State<any>) {
export function restoreState<T>(
localStorageKey: string,
initialStateValue: T
initialStateValue: T,
): State<T> {
const storedString =
localStorage.getItem(localStorageKey) ??
@@ -223,7 +223,7 @@ export function restoreState<T>(
export function restoreListState<T>(
localStorageKey: string,
initialItems: any[] = []
initialItems: any[] = [],
): ListState<T> {
const storedString = localStorage.getItem(localStorageKey) ?? "";
@@ -241,7 +241,7 @@ export function restoreListState<T>(
export function restoreMapState<T>(
localStorageKey: string,
initialItems: [string, any][] = []
initialItems: [string, any][] = [],
): MapState<T> {
const storedString = localStorage.getItem(localStorageKey) ?? "";
@@ -298,13 +298,13 @@ export function createElement(
if (e.metaKey == false && e.ctrlKey == false) return;
if (e.key != directiveValue) return;
value(e);
})
});
break;
}
case "subscribe": {
const state = value as State<any>;
state.subscribe(
(newValue) => (element[directiveValue] = newValue)
(newValue) => (element[directiveValue] = newValue),
);
break;
@@ -312,11 +312,11 @@ export function createElement(
case "bind": {
const state = value as State<any>;
state.subscribe(
(newValue) => (element[directiveValue] = newValue)
(newValue) => (element[directiveValue] = newValue),
);
element.addEventListener(
"input",
() => (state.value = (element as any)[directiveValue])
() => (state.value = (element as any)[directiveValue]),
);
break;
}
@@ -324,7 +324,7 @@ export function createElement(
if (value.subscribe) {
const state = value as State<any>;
state.subscribe((newValue) =>
element.toggleAttribute(directiveValue, newValue)
element.toggleAttribute(directiveValue, newValue),
);
} else {
element.toggleAttribute(directiveValue, value);
@@ -334,7 +334,7 @@ export function createElement(
case "set": {
const state = value as State<any>;
state.subscribe((newValue) =>
element.setAttribute(directiveValue, newValue)
element.setAttribute(directiveValue, newValue),
);
break;
}
@@ -353,13 +353,13 @@ export function createElement(
try {
const [listState, toElement] = value as [
listState: ListState<any>,
StateItemConverter<any>
StateItemConverter<any>,
];
listState.handleAddition((newItem) => {
const child = toElement(newItem);
listState.handleRemoval(newItem, () =>
child.remove()
child.remove(),
);
if (directiveValue == "append") {
+1 -2
View File
@@ -1,2 +1 @@
declare module "*.css" {
}
declare module "*.css" {}