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
Binary file not shown.
+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";
+148 -142
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,36 +779,38 @@ function ExportModal(
bind:value={
fileTransferViewModel.exportKeyConfirmation
}
on:enter={fileTransferViewModel.downloadFile}
type="password"
></input>
</div>
</label>
</div>
</main>
<div class="flex-row width-100">
<button
class="flex"
on:click={
fileTransferViewModel.showExportSelectionModal
}
>
{coreViewModel.translations.general.backButton}
</button>
<button
class="primary flex"
on:click={fileTransferViewModel.downloadFile}
toggle:disabled={fileTransferViewModel.cannotExport}
>
{
coreViewModel.translations.dataTransferModal
.downloadFileButton
}
<span class="icon">download</span>
</button>
</div>
</div>
</div>
on:enter={
fileTransferViewModel.downloadFile
}
type="password"
></input>
</div>
</label>
</div>
</main>
<div class="flex-row width-100">
<button
class="flex"
on:click={
fileTransferViewModel.showExportSelectionModal
}
>
{coreViewModel.translations.general.backButton}
</button>
<button
class="primary flex"
on:click={fileTransferViewModel.downloadFile}
toggle:disabled={fileTransferViewModel.cannotExport}
>
{
coreViewModel.translations.dataTransferModal
.downloadFileButton
}
<span class="icon">download</span>
</button>
</div>
</div>
</div>
);
}
@@ -815,57 +819,57 @@ function ImportModal(
fileTransferViewModel: FileTransferViewModel,
) {
const isPresented = React.createProxyState(
[fileTransferViewModel.presentedModal],
() =>
fileTransferViewModel.presentedModal.value ==
FileTransferModals.ImportSelection,
[fileTransferViewModel.presentedModal],
() =>
fileTransferViewModel.presentedModal.value ==
FileTransferModals.ImportSelection,
);
return (
<div class="modal" toggle:open={isPresented}>
<div>
<main>
<h2>
{
coreViewModel.translations.dataTransferModal
.importHeadline
}
</h2>
<div class="modal" toggle:open={isPresented}>
<div>
<main>
<h2>
{
coreViewModel.translations.dataTransferModal
.importHeadline
}
</h2>
<div class="flex-column gap content-margin-bottom">
<input
id="file-transfer-input"
type="file"
on:change={
fileTransferViewModel.updateImportSelection
}
></input>
</div>
</main>
<div class="flex-column gap content-margin-bottom">
<input
id="file-transfer-input"
type="file"
on:change={
fileTransferViewModel.updateImportSelection
}
></input>
</div>
</main>
<div class="flex-row width-100">
<button
class="flex"
on:click={
fileTransferViewModel.showDirectionSelectionModal
}
>
{coreViewModel.translations.general.backButton}
</button>
<button
class="primary flex"
on:click={fileTransferViewModel.importFile}
toggle:disabled={fileTransferViewModel.cannotImport}
>
{
coreViewModel.translations.dataTransferModal
.importFileButton
}
<span class="icon">arrow_forward</span>
</button>
</div>
</div>
</div>
<div class="flex-row width-100">
<button
class="flex"
on:click={
fileTransferViewModel.showDirectionSelectionModal
}
>
{coreViewModel.translations.general.backButton}
</button>
<button
class="primary flex"
on:click={fileTransferViewModel.importFile}
toggle:disabled={fileTransferViewModel.cannotImport}
>
{
coreViewModel.translations.dataTransferModal
.importFileButton
}
<span class="icon">arrow_forward</span>
</button>
</div>
</div>
</div>
);
}
@@ -874,74 +878,76 @@ function ImportDecryptionDataModal(
fileTransferViewModel: FileTransferViewModel,
) {
const isPresented = React.createProxyState(
[fileTransferViewModel.presentedModal],
() =>
fileTransferViewModel.presentedModal.value ==
FileTransferModals.ImportDecryptData,
[fileTransferViewModel.presentedModal],
() =>
fileTransferViewModel.presentedModal.value ==
FileTransferModals.ImportDecryptData,
);
return (
<div class="modal" toggle:open={isPresented}>
<div>
<main>
<h2>
{
coreViewModel.translations.dataTransferModal
.importHeadline
}
</h2>
<div class="modal" toggle:open={isPresented}>
<div>
<main>
<h2>
{
coreViewModel.translations.dataTransferModal
.importHeadline
}
</h2>
<div class="flex-column gap content-margin-bottom">
<label class="tile">
<span class="icon">key</span>
<div>
<span class="secondary">
{
coreViewModel.translations
.dataTransferModal.exportKey
}
</span>
<input
bind:value={fileTransferViewModel.importKey}
on:enter={fileTransferViewModel.decryptImport}
type="password"
></input>
</div>
</label>
</div>
<div class="flex-column gap content-margin-bottom">
<label class="tile">
<span class="icon">key</span>
<div>
<span class="secondary">
{
coreViewModel.translations
.dataTransferModal.exportKey
}
</span>
<input
bind:value={fileTransferViewModel.importKey}
on:enter={
fileTransferViewModel.decryptImport
}
type="password"
></input>
</div>
</label>
</div>
<p
class="error"
toggle:hidden={fileTransferViewModel.isImportKeyCorrect}
>
{
coreViewModel.translations.dataTransferModal
.incorrectPassphraseError
}
</p>
</main>
<div class="flex-row width-100">
<button
class="flex"
on:click={fileTransferViewModel.showImportModal}
>
{coreViewModel.translations.general.backButton}
</button>
<button
class="primary flex"
on:click={fileTransferViewModel.decryptImport}
toggle:disabled={
fileTransferViewModel.importDecryptSuccessful
}
>
{
coreViewModel.translations.dataTransferModal
.decryptImportButton
}
<span class="icon">arrow_forward</span>
</button>
</div>
</div>
</div>
<p
class="error"
toggle:hidden={fileTransferViewModel.isImportKeyCorrect}
>
{
coreViewModel.translations.dataTransferModal
.incorrectPassphraseError
}
</p>
</main>
<div class="flex-row width-100">
<button
class="flex"
on:click={fileTransferViewModel.showImportModal}
>
{coreViewModel.translations.general.backButton}
</button>
<button
class="primary flex"
on:click={fileTransferViewModel.decryptImport}
toggle:disabled={
fileTransferViewModel.importDecryptSuccessful
}
>
{
coreViewModel.translations.dataTransferModal
.decryptImportButton
}
<span class="icon">arrow_forward</span>
</button>
</div>
</div>
</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";
+62 -44
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";
@@ -29,8 +29,8 @@ export function SearchModal<T>(
<h2>{headline}</h2>
<div class="flex-row">
<input
id="focused"
style="max-width: unset"
id="focused"
style="max-width: unset"
placeholder={
coreViewModel.translations.general.searchLabel
}
@@ -70,55 +70,73 @@ export function SearchModal<T>(
</button>
</div>
<div
toggle:hidden={searchViewModel.hasNoSuggestions}>
<hr></hr>
<h3>{coreViewModel.translations.general.searchSuggestionsLabel}</h3>
<div
class="flex-column gap"
children:append={[
searchViewModel.suggestions,
(suggestion: string) => SuggestionView(suggestion, searchViewModel, coreViewModel),
]}
></div>
</div>
</main>
<button on:click={close}>
{coreViewModel.translations.general.closeButton}
<span class="icon">close</span>
</button>
</div>
</div>
<div toggle:hidden={searchViewModel.hasNoSuggestions}>
<hr></hr>
<h3>
{
coreViewModel.translations.general
.searchSuggestionsLabel
}
</h3>
<div
class="flex-column gap"
children:append={[
searchViewModel.suggestions,
(suggestion: string) =>
SuggestionView(
suggestion,
searchViewModel,
coreViewModel,
),
]}
></div>
</div>
</main>
<button on:click={close}>
{coreViewModel.translations.general.closeButton}
<span class="icon">close</span>
</button>
</div>
</div>
);
}
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);
searchViewModel.deleteSuggestion(suggestion);
}
function applySuggestion() {
searchViewModel.search(suggestion);
searchViewModel.search(suggestion);
}
return <div class="flex-row surface align-center">
<span class="width-100 flex-1 padding-h">{suggestion}</span>
<button
class="danger"
aria-label={coreViewModel.translations.general.deleteButton}
on:click={deleteSuggestion}
>
<span class="icon">delete</span>
</button>
<button
class="primary"
aria-label={coreViewModel.translations.general.applyButton}
on:click={applySuggestion}
toggle:disabled={isApplied}
>
<span class="icon">check</span>
</button>
</div>
return (
<div class="flex-row surface align-center">
<span class="width-100 flex-1 padding-h">{suggestion}</span>
<button
class="danger"
aria-label={coreViewModel.translations.general.deleteButton}
on:click={deleteSuggestion}
>
<span class="icon">delete</span>
</button>
<button
class="primary"
aria-label={coreViewModel.translations.general.applyButton}
on:click={applySuggestion}
toggle:disabled={isApplied}
>
<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";
+2 -2
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>(
@@ -10,7 +10,7 @@ export function SplitModal<T>(
navigationState?: React.State<T | undefined>,
) {
function closePage() {
if (!navigationState) return;
if (!navigationState) return;
navigationState.value = undefined;
}
+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";
+247 -242
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";
@@ -18,264 +18,269 @@ export function TaskSettingsModal(
const statusSuggestionId = v4();
const BoardOptionConverter: React.StateItemConverter<Entry> = (
entry: Entry,
entry: Entry,
) => {
const isSelected = entry[0] == taskViewModel.task.boardId;
return Option(entry[1], entry[0], isSelected);
const isSelected = entry[0] == taskViewModel.task.boardId;
return Option(entry[1], entry[0], isSelected);
};
return (
<div class="modal task-settings" open toggle:full-description={taskViewModel.isPresentingFullScreenDescription}>
<div>
<main id="standard-main">
<h2>
{
coreViewModel.translations.chatPage.task
.taskSettingsHeadline
}
</h2>
<div
class="modal task-settings"
open
toggle:full-description={
taskViewModel.isPresentingFullScreenDescription
}
>
<div>
<main id="standard-main">
<h2>
{
coreViewModel.translations.chatPage.task
.taskSettingsHeadline
}
</h2>
<div class="column-wrapper">
<div class="flex-column">
<label class="tile flex-no">
<span class="icon">label</span>
<div>
<span>
{
coreViewModel.translations.chatPage.task
.taskNameLabel
}
</span>
<input
bind:value={taskViewModel.name}
></input>
</div>
</label>
<div class="column-wrapper">
<div class="flex-column">
<label class="tile flex-no">
<span class="icon">label</span>
<div>
<span>
{
coreViewModel.translations.chatPage
.task.taskNameLabel
}
</span>
<input
bind:value={taskViewModel.name}
></input>
</div>
</label>
<label class="tile flex-no">
<span class="icon">category</span>
<div>
<span>
{
coreViewModel.translations.chatPage.task
.taskBoardLabel
}
</span>
<select
bind:value={taskViewModel.boardId}
children:append={[
taskViewModel.chatViewModel
.taskBoardSuggestions,
BoardOptionConverter,
]}
></select>
<span class="icon">arrow_drop_down</span>
</div>
</label>
<label class="tile flex-no">
<span class="icon">category</span>
<div>
<span>
{
coreViewModel.translations.chatPage
.task.taskBoardLabel
}
</span>
<select
bind:value={taskViewModel.boardId}
children:append={[
taskViewModel.chatViewModel
.taskBoardSuggestions,
BoardOptionConverter,
]}
></select>
<span class="icon">arrow_drop_down</span>
</div>
</label>
<label class="tile flex-no">
<span class="icon">description</span>
<div>
<span>
{
coreViewModel.translations.chatPage.task
.taskDescriptionLabel
}
</span>
<textarea
rows="10"
bind:value={taskViewModel.description}
></textarea>
</div>
</label>
<div class="flex-row justify-end">
<button
class="width-50"
on:click={taskViewModel.openFullscreenDecription}
>
{
coreViewModel.translations.general.fullscreenButton
}
<label class="tile flex-no">
<span class="icon">description</span>
<div>
<span>
{
coreViewModel.translations.chatPage
.task.taskDescriptionLabel
}
</span>
<textarea
rows="10"
bind:value={taskViewModel.description}
></textarea>
</div>
</label>
<div class="flex-row justify-end">
<button
class="width-50"
on:click={
taskViewModel.openFullscreenDecription
}
>
{
coreViewModel.translations.general
.fullscreenButton
}
<span class="icon">fullscreen</span>
</button>
</div>
</div>
<span class="icon">fullscreen</span>
</button>
</div>
</div>
<hr class="mobile-only"></hr>
<hr class="mobile-only"></hr>
<div class="flex-column">
<label class="tile flex-no">
<span class="icon">category</span>
<div>
<span>
{
coreViewModel.translations.chatPage.task
.taskCategoryLabel
}
</span>
<input
bind:value={taskViewModel.category}
list={categorySuggestionId}
></input>
</div>
</label>
<datalist
hidden
id={categorySuggestionId}
children:append={[
taskViewModel.containingViewModel.taskCategorySuggestions,
StringToOption,
]}
></datalist>
<div class="flex-column">
<label class="tile flex-no">
<span class="icon">category</span>
<div>
<span>
{
coreViewModel.translations.chatPage
.task.taskCategoryLabel
}
</span>
<input
bind:value={taskViewModel.category}
list={categorySuggestionId}
></input>
</div>
</label>
<datalist
hidden
id={categorySuggestionId}
children:append={[
taskViewModel.containingViewModel
.taskCategorySuggestions,
StringToOption,
]}
></datalist>
<label class="tile flex-no">
<span class="icon">clock_loader_40</span>
<div>
<span>
{
coreViewModel.translations.chatPage.task
.taskStatusLabel
}
</span>
<input
bind:value={taskViewModel.status}
list={statusSuggestionId}
></input>
</div>
</label>
<datalist
hidden
id={statusSuggestionId}
children:append={[
taskViewModel.containingViewModel.taskStatusSuggestions,
StringToOption,
]}
></datalist>
<label class="tile flex-no">
<span class="icon">clock_loader_40</span>
<div>
<span>
{
coreViewModel.translations.chatPage
.task.taskStatusLabel
}
</span>
<input
bind:value={taskViewModel.status}
list={statusSuggestionId}
></input>
</div>
</label>
<datalist
hidden
id={statusSuggestionId}
children:append={[
taskViewModel.containingViewModel
.taskStatusSuggestions,
StringToOption,
]}
></datalist>
<label class="tile flex-no">
<span class="icon">priority_high</span>
<div>
<span>
{
coreViewModel.translations.chatPage.task
.taskPriorityLabel
}
</span>
<input
type="number"
bind:value={taskViewModel.priority}
></input>
</div>
</label>
<label class="tile flex-no">
<span class="icon">priority_high</span>
<div>
<span>
{
coreViewModel.translations.chatPage
.task.taskPriorityLabel
}
</span>
<input
type="number"
bind:value={taskViewModel.priority}
></input>
</div>
</label>
<hr></hr>
<hr></hr>
<label class="tile flex-no">
<span class="icon">calendar_month</span>
<div>
<span>
{
coreViewModel.translations.chatPage.task
.taskDateLabel
}
</span>
<input
type="date"
bind:value={taskViewModel.date}
></input>
</div>
</label>
<label class="tile flex-no">
<span class="icon">calendar_month</span>
<div>
<span>
{
coreViewModel.translations.chatPage
.task.taskDateLabel
}
</span>
<input
type="date"
bind:value={taskViewModel.date}
></input>
</div>
</label>
<label class="tile flex-no">
<span class="icon">schedule</span>
<div>
<span>
{
coreViewModel.translations.chatPage.task
.taskTimeLabel
}
</span>
<input
type="time"
bind:value={taskViewModel.time}
></input>
</div>
</label>
</div>
</div>
<label class="tile flex-no">
<span class="icon">schedule</span>
<div>
<span>
{
coreViewModel.translations.chatPage
.task.taskTimeLabel
}
</span>
<input
type="time"
bind:value={taskViewModel.time}
></input>
</div>
</label>
</div>
</div>
<hr></hr>
<hr></hr>
<label class="tile flex-no">
<span class="icon">history</span>
<div>
<span>
{
coreViewModel.translations.general
.fileVersionLabel
}
</span>
<select
bind:value={taskViewModel.selectedVersionId}
children:append={[
taskViewModel.versionIds,
VersionIdToOption,
]}
></select>
<span class="icon">arrow_drop_down</span>
</div>
</label>
<label class="tile flex-no">
<span class="icon">history</span>
<div>
<span>
{
coreViewModel.translations.general
.fileVersionLabel
}
</span>
<select
bind:value={taskViewModel.selectedVersionId}
children:append={[
taskViewModel.versionIds,
VersionIdToOption,
]}
></select>
<span class="icon">arrow_drop_down</span>
</div>
</label>
<hr></hr>
<hr></hr>
<div class="width-input">
{DangerousActionButton(
coreViewModel,
coreViewModel.translations.chatPage.task
.deleteTaskButton,
"delete_forever",
taskViewModel.deleteTask,
)}
</div>
</main>
<main id="fullscreen-main">
<textarea
style="height: 100%; width: 100%; max-width: unset"
bind:value={taskViewModel.description}
></textarea>
</main>
<div
class="flex-row width-100"
id="fullscreen-exit"
>
<button
class="flex"
on:click={taskViewModel.closeFullscreenDecription}
>
{coreViewModel.translations.general.exitButton}
<span class="icon">fullscreen_exit</span>
</button>
</div>
<div
class="flex-row width-100"
id="controls"
>
<button
class="flex"
on:click={taskViewModel.closeAndDiscard}
>
{coreViewModel.translations.general.closeButton}
</button>
<button
class="flex primary"
on:click={taskViewModel.closeAndSave}
>
{coreViewModel.translations.general.saveButton}
<span class="icon">save</span>
</button>
</div>
</div>
</div>
);
<div class="width-input">
{DangerousActionButton(
coreViewModel,
coreViewModel.translations.chatPage.task
.deleteTaskButton,
"delete_forever",
taskViewModel.deleteTask,
)}
</div>
</main>
<main id="fullscreen-main">
<textarea
style="height: 100%; width: 100%; max-width: unset"
bind:value={taskViewModel.description}
></textarea>
</main>
<div class="flex-row width-100" id="fullscreen-exit">
<button
class="flex"
on:click={taskViewModel.closeFullscreenDecription}
>
{coreViewModel.translations.general.exitButton}
<span class="icon">fullscreen_exit</span>
</button>
</div>
<div class="flex-row width-100" id="controls">
<button
class="flex"
on:click={taskViewModel.closeAndDiscard}
>
{coreViewModel.translations.general.closeButton}
</button>
<button
class="flex primary"
on:click={taskViewModel.closeAndSave}
>
{coreViewModel.translations.general.saveButton}
<span class="icon">save</span>
</button>
</div>
</div>
</div>
);
}