implement task filter
This commit is contained in:
Vendored
+142
-7
@@ -353,6 +353,29 @@
|
|||||||
}
|
}
|
||||||
return [...values.values()].sort(localeCompare);
|
return [...values.values()].sort(localeCompare);
|
||||||
}
|
}
|
||||||
|
function checkDoesObjectMatchSearch(query, getStringsOfObject, object) {
|
||||||
|
if (query == "") return true;
|
||||||
|
const stringsInObject = getStringsOfObject(object);
|
||||||
|
const wordsInObject = [];
|
||||||
|
for (const string of stringsInObject) {
|
||||||
|
const lowercaseWordsInString = string.toLocaleLowerCase().split(" ").filter((word) => word != "");
|
||||||
|
wordsInObject.push(...lowercaseWordsInString);
|
||||||
|
}
|
||||||
|
const lowercaseWordsInQuery = query.toLowerCase().split(" ").filter((word) => word != "");
|
||||||
|
for (const queryWord of lowercaseWordsInQuery) {
|
||||||
|
if (queryWord[0] == "-") {
|
||||||
|
const wordContent = queryWord.substring(1);
|
||||||
|
if (wordsInObject.includes(wordContent)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (wordsInObject.includes(queryWord) == false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
var HandlerManager = class {
|
var HandlerManager = class {
|
||||||
handlers = /* @__PURE__ */ new Set();
|
handlers = /* @__PURE__ */ new Set();
|
||||||
// manage
|
// manage
|
||||||
@@ -1613,7 +1636,6 @@
|
|||||||
name = new State("");
|
name = new State("");
|
||||||
color = new State("standard" /* Standard */);
|
color = new State("standard" /* Standard */);
|
||||||
index = new State(0);
|
index = new State(0);
|
||||||
taskViewModels = new MapState();
|
|
||||||
selectedPage = new State(
|
selectedPage = new State(
|
||||||
"list" /* List */
|
"list" /* List */
|
||||||
);
|
);
|
||||||
@@ -1621,6 +1643,8 @@
|
|||||||
isSelected;
|
isSelected;
|
||||||
isPresentingSettingsModal = new State(false);
|
isPresentingSettingsModal = new State(false);
|
||||||
isPresentingFilterModal = new State(false);
|
isPresentingFilterModal = new State(false);
|
||||||
|
taskViewModels = new MapState();
|
||||||
|
filteredTaskViewModels = new ListState();
|
||||||
// paths
|
// paths
|
||||||
getBasePath = () => {
|
getBasePath = () => {
|
||||||
return [...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId)];
|
return [...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId)];
|
||||||
@@ -1718,6 +1742,18 @@
|
|||||||
boardViewModel.updateIndex();
|
boardViewModel.updateIndex();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// filter
|
||||||
|
getStringsFromTaskViewModel = (taskViewModel) => {
|
||||||
|
return [
|
||||||
|
taskViewModel.task.name,
|
||||||
|
taskViewModel.task.description ?? "",
|
||||||
|
taskViewModel.task.category ?? "",
|
||||||
|
taskViewModel.task.status ?? "",
|
||||||
|
taskViewModel.task.priority ?? "",
|
||||||
|
taskViewModel.task.date ?? "",
|
||||||
|
taskViewModel.task.time ?? ""
|
||||||
|
];
|
||||||
|
};
|
||||||
// load
|
// load
|
||||||
loadListRelevantData = () => {
|
loadListRelevantData = () => {
|
||||||
this.name.value = this.boardInfo.name;
|
this.name.value = this.boardInfo.name;
|
||||||
@@ -2064,12 +2100,14 @@
|
|||||||
var englishTranslations = {
|
var englishTranslations = {
|
||||||
general: {
|
general: {
|
||||||
deleteItemButtonAudioLabel: "delete item",
|
deleteItemButtonAudioLabel: "delete item",
|
||||||
|
searchButtonAudioLabel: "search",
|
||||||
abortButton: "Abort",
|
abortButton: "Abort",
|
||||||
closeButton: "Close",
|
closeButton: "Close",
|
||||||
confirmButton: "Confirm",
|
confirmButton: "Confirm",
|
||||||
saveButton: "Save",
|
saveButton: "Save",
|
||||||
setButton: "Set",
|
setButton: "Set",
|
||||||
fileVersionLabel: "Version"
|
fileVersionLabel: "Version",
|
||||||
|
searchLabel: "Search"
|
||||||
},
|
},
|
||||||
regional: {
|
regional: {
|
||||||
weekdays: {
|
weekdays: {
|
||||||
@@ -2187,6 +2225,8 @@
|
|||||||
taskTimeLabel: "Time",
|
taskTimeLabel: "Time",
|
||||||
deleteTaskButton: "Delete task",
|
deleteTaskButton: "Delete task",
|
||||||
///
|
///
|
||||||
|
filterTasksHeadline: "Filter Tasks",
|
||||||
|
///
|
||||||
renameCategoryInputPlaceholder: "Rename category,"
|
renameCategoryInputPlaceholder: "Rename category,"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2481,7 +2521,7 @@
|
|||||||
return viewBuilder(propertyValues);
|
return viewBuilder(propertyValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/ViewModel/Pages/taskPropertyBulkChangeViewModel.ts
|
// src/ViewModel/Utility/taskPropertyBulkChangeViewModel.ts
|
||||||
var TaskPropertyBulkChangeViewModel = class {
|
var TaskPropertyBulkChangeViewModel = class {
|
||||||
taskViewModels;
|
taskViewModels;
|
||||||
// state
|
// state
|
||||||
@@ -2598,7 +2638,7 @@
|
|||||||
return FilteredList(
|
return FilteredList(
|
||||||
{ category: categoryName },
|
{ category: categoryName },
|
||||||
(taskViewModel) => taskViewModel.task,
|
(taskViewModel) => taskViewModel.task,
|
||||||
boardViewModel.taskViewModels,
|
boardViewModel.filteredTaskViewModels,
|
||||||
(taskViewModels) => {
|
(taskViewModels) => {
|
||||||
const viewModel = new TaskCategoryBulkChangeViewModel(taskViewModels, categoryName);
|
const viewModel = new TaskCategoryBulkChangeViewModel(taskViewModels, categoryName);
|
||||||
return /* @__PURE__ */ createElement("div", { class: "flex-column flex-no" }, /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
|
return /* @__PURE__ */ createElement("div", { class: "flex-column flex-no" }, /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
|
||||||
@@ -2669,7 +2709,7 @@
|
|||||||
), PropertyValueList(
|
), PropertyValueList(
|
||||||
"category",
|
"category",
|
||||||
(taskViewModel) => taskViewModel.task,
|
(taskViewModel) => taskViewModel.task,
|
||||||
boardViewModel.taskViewModels,
|
boardViewModel.filteredTaskViewModels,
|
||||||
(categories) => {
|
(categories) => {
|
||||||
return /* @__PURE__ */ createElement(
|
return /* @__PURE__ */ createElement(
|
||||||
"div",
|
"div",
|
||||||
@@ -2780,6 +2820,94 @@
|
|||||||
return RibbonButton(label, icon, isSelected, select);
|
return RibbonButton(label, icon, isSelected, select);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// src/ViewModel/Utility/searchViewModel.ts
|
||||||
|
var SearchViewModel = class {
|
||||||
|
// data
|
||||||
|
allObjects;
|
||||||
|
getStringsOfObject;
|
||||||
|
// state
|
||||||
|
appliedQuery = new State("");
|
||||||
|
searchInput = new State("");
|
||||||
|
matchingObjects;
|
||||||
|
// guards
|
||||||
|
cannotApplySearch = createProxyState(
|
||||||
|
[this.searchInput, this.appliedQuery],
|
||||||
|
() => this.searchInput.value == this.appliedQuery.value
|
||||||
|
);
|
||||||
|
// methods
|
||||||
|
applySearch = () => {
|
||||||
|
this.appliedQuery.value = this.searchInput.value;
|
||||||
|
console.log("applying search");
|
||||||
|
this.matchingObjects.clear();
|
||||||
|
for (const object of this.allObjects.value.values()) {
|
||||||
|
const doesMatch = this.checkDoesMatchSearch(object);
|
||||||
|
if (doesMatch == false) continue;
|
||||||
|
this.matchingObjects.add(object);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// init
|
||||||
|
constructor(allObjects, matchingObjects, getStringsOfObject) {
|
||||||
|
this.allObjects = allObjects;
|
||||||
|
this.matchingObjects = matchingObjects;
|
||||||
|
this.getStringsOfObject = getStringsOfObject;
|
||||||
|
this.allObjects.handleAddition((newObject) => {
|
||||||
|
const doesMatch = this.checkDoesMatchSearch(newObject);
|
||||||
|
if (doesMatch == false) {
|
||||||
|
this.matchingObjects.remove(newObject);
|
||||||
|
} else {
|
||||||
|
if (this.matchingObjects.value.has(newObject)) return;
|
||||||
|
this.matchingObjects.add(newObject);
|
||||||
|
this.allObjects.handleRemoval(newObject, () => {
|
||||||
|
this.matchingObjects.remove(newObject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// utility
|
||||||
|
checkDoesMatchSearch = (object) => {
|
||||||
|
return checkDoesObjectMatchSearch(
|
||||||
|
this.appliedQuery.value,
|
||||||
|
this.getStringsOfObject,
|
||||||
|
object
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/View/Modals/searchModal.tsx
|
||||||
|
function SearchModal(headline, allObjects, filteredObjects, converter, getStringsOfObject, isOpen) {
|
||||||
|
const viewModel = new SearchViewModel(
|
||||||
|
allObjects,
|
||||||
|
filteredObjects,
|
||||||
|
getStringsOfObject
|
||||||
|
);
|
||||||
|
function close() {
|
||||||
|
isOpen.value = false;
|
||||||
|
}
|
||||||
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": isOpen }, /* @__PURE__ */ createElement("div", { style: "max-width: 64rem" }, /* @__PURE__ */ createElement("main", null, /* @__PURE__ */ createElement("h2", null, headline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
|
||||||
|
"input",
|
||||||
|
{
|
||||||
|
placeholder: translations.general.searchLabel,
|
||||||
|
"bind:value": viewModel.searchInput,
|
||||||
|
"on:enter": viewModel.applySearch
|
||||||
|
}
|
||||||
|
), /* @__PURE__ */ createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
class: "primary",
|
||||||
|
"aria-label": translations.general.searchButtonAudioLabel,
|
||||||
|
"on:click": viewModel.applySearch,
|
||||||
|
"toggle:disabled": viewModel.cannotApplySearch
|
||||||
|
},
|
||||||
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "search")
|
||||||
|
)), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
class: "grid gap",
|
||||||
|
"children:append": [filteredObjects, converter]
|
||||||
|
}
|
||||||
|
)), /* @__PURE__ */ createElement("button", { "on:click": close }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))));
|
||||||
|
}
|
||||||
|
|
||||||
// src/View/Components/option.tsx
|
// src/View/Components/option.tsx
|
||||||
function Option(text, value, selectedOnCreate) {
|
function Option(text, value, selectedOnCreate) {
|
||||||
return /* @__PURE__ */ createElement("option", { value, "toggle:selected": selectedOnCreate }, text);
|
return /* @__PURE__ */ createElement("option", { value, "toggle:selected": selectedOnCreate }, text);
|
||||||
@@ -2834,7 +2962,7 @@
|
|||||||
{
|
{
|
||||||
class: "task-grid",
|
class: "task-grid",
|
||||||
"children:append": [
|
"children:append": [
|
||||||
boardViewModel.taskViewModels,
|
boardViewModel.filteredTaskViewModels,
|
||||||
TaskViewModelToEntry
|
TaskViewModelToEntry
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -2900,7 +3028,14 @@
|
|||||||
"on:click": boardViewModel.createTask
|
"on:click": boardViewModel.createTask
|
||||||
},
|
},
|
||||||
/* @__PURE__ */ createElement("span", { class: "icon" }, "add")
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "add")
|
||||||
))), /* @__PURE__ */ createElement("div", { class: "content main-content", "children:set": mainContent }), BoardSettingsModal(boardViewModel), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal }));
|
))), /* @__PURE__ */ createElement("div", { class: "content main-content", "children:set": mainContent }), BoardSettingsModal(boardViewModel), SearchModal(
|
||||||
|
translations.chatPage.task.filterTasksHeadline,
|
||||||
|
boardViewModel.taskViewModels,
|
||||||
|
boardViewModel.filteredTaskViewModels,
|
||||||
|
TaskViewModelToEntry,
|
||||||
|
boardViewModel.getStringsFromTaskViewModel,
|
||||||
|
boardViewModel.isPresentingFilterModal
|
||||||
|
), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/View/Components/boardEntry.tsx
|
// src/View/Components/boardEntry.tsx
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export function filterObjectsByWords<T>(
|
|||||||
const matches: Set<T> = new Set();
|
const matches: Set<T> = new Set();
|
||||||
|
|
||||||
object_loop: for (const object of objects) {
|
object_loop: for (const object of objects) {
|
||||||
const doesMatch: boolean = checkIfMatchesFilter(
|
const doesMatch: boolean = checkDoesObjectMatchSearch(
|
||||||
query,
|
query,
|
||||||
getStringsOfObject,
|
getStringsOfObject,
|
||||||
object
|
object
|
||||||
@@ -112,7 +112,7 @@ export function filterObjectsByWords<T>(
|
|||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkIfMatchesFilter<T>(
|
export function checkDoesObjectMatchSearch<T>(
|
||||||
query: string,
|
query: string,
|
||||||
getStringsOfObject: (object: T) => string[],
|
getStringsOfObject: (object: T) => string[],
|
||||||
object: T
|
object: T
|
||||||
@@ -122,11 +122,17 @@ export function checkIfMatchesFilter<T>(
|
|||||||
const stringsInObject: string[] = getStringsOfObject(object);
|
const stringsInObject: string[] = getStringsOfObject(object);
|
||||||
const wordsInObject: string[] = [];
|
const wordsInObject: string[] = [];
|
||||||
for (const string of stringsInObject) {
|
for (const string of stringsInObject) {
|
||||||
const lowercaseWordsInString = string.toLocaleLowerCase().split(" ");
|
const lowercaseWordsInString = string
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.split(" ")
|
||||||
|
.filter((word) => word != "");
|
||||||
wordsInObject.push(...lowercaseWordsInString);
|
wordsInObject.push(...lowercaseWordsInString);
|
||||||
}
|
}
|
||||||
|
|
||||||
const lowercaseWordsInQuery = query.toLowerCase().split(" ");
|
const lowercaseWordsInQuery = query
|
||||||
|
.toLowerCase()
|
||||||
|
.split(" ")
|
||||||
|
.filter((word) => word != "");
|
||||||
for (const queryWord of lowercaseWordsInQuery) {
|
for (const queryWord of lowercaseWordsInQuery) {
|
||||||
if (queryWord[0] == "-") {
|
if (queryWord[0] == "-") {
|
||||||
// exclusion
|
// exclusion
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as React from "bloatless-react";
|
|||||||
import BoardViewModel from "../../ViewModel/Pages/boardViewModel";
|
import BoardViewModel from "../../ViewModel/Pages/boardViewModel";
|
||||||
import { FilteredList } from "../Components/filteredList";
|
import { FilteredList } from "../Components/filteredList";
|
||||||
import { PropertyValueList } from "../Components/propertyList";
|
import { PropertyValueList } from "../Components/propertyList";
|
||||||
import { TaskCategoryBulkChangeViewModel } from "../../ViewModel/Pages/taskPropertyBulkChangeViewModel";
|
import { TaskCategoryBulkChangeViewModel } from "../../ViewModel/Utility/taskPropertyBulkChangeViewModel";
|
||||||
import TaskViewModel from "../../ViewModel/Pages/taskViewModel";
|
import TaskViewModel from "../../ViewModel/Pages/taskViewModel";
|
||||||
import { TaskViewModelToEntry } from "../Components/taskEntry";
|
import { TaskViewModelToEntry } from "../Components/taskEntry";
|
||||||
import { translations } from "../translations";
|
import { translations } from "../translations";
|
||||||
@@ -34,7 +34,7 @@ function KanbanBoard(categoryName: string, boardViewModel: BoardViewModel) {
|
|||||||
return FilteredList(
|
return FilteredList(
|
||||||
{ category: categoryName },
|
{ category: categoryName },
|
||||||
(taskViewModel: TaskViewModel) => taskViewModel.task,
|
(taskViewModel: TaskViewModel) => taskViewModel.task,
|
||||||
boardViewModel.taskViewModels,
|
boardViewModel.filteredTaskViewModels,
|
||||||
(taskViewModels: React.ListState<TaskViewModel>) => {
|
(taskViewModels: React.ListState<TaskViewModel>) => {
|
||||||
const viewModel: TaskCategoryBulkChangeViewModel =
|
const viewModel: TaskCategoryBulkChangeViewModel =
|
||||||
new TaskCategoryBulkChangeViewModel(taskViewModels, categoryName);
|
new TaskCategoryBulkChangeViewModel(taskViewModels, categoryName);
|
||||||
|
|||||||
@@ -8,13 +8,16 @@ import { BoardKanbanPage } from "./boardKanbanPage";
|
|||||||
import { BoardSettingsModal } from "../Modals/boardSettingsModal";
|
import { BoardSettingsModal } from "../Modals/boardSettingsModal";
|
||||||
import { BoardStatusGridPage } from "./boardStatusGridPage";
|
import { BoardStatusGridPage } from "./boardStatusGridPage";
|
||||||
import { BoardViewToggleButton } from "../Components/boardViewToggleButton";
|
import { BoardViewToggleButton } from "../Components/boardViewToggleButton";
|
||||||
|
import { SearchModal } from "../Modals/searchModal";
|
||||||
import { TaskSettingsModal } from "../Modals/taskSettingsModal";
|
import { TaskSettingsModal } from "../Modals/taskSettingsModal";
|
||||||
|
import TaskViewModel from "../../ViewModel/Pages/taskViewModel";
|
||||||
import { TaskViewModelToEntry } from "../Components/taskEntry";
|
import { TaskViewModelToEntry } from "../Components/taskEntry";
|
||||||
import { translations } from "../translations";
|
import { translations } from "../translations";
|
||||||
|
|
||||||
export function BoardPage(boardViewModel: BoardViewModel) {
|
export function BoardPage(boardViewModel: BoardViewModel) {
|
||||||
boardViewModel.loadData();
|
boardViewModel.loadData();
|
||||||
|
|
||||||
|
// main content
|
||||||
const mainContent = React.createProxyState(
|
const mainContent = React.createProxyState(
|
||||||
[boardViewModel.selectedPage],
|
[boardViewModel.selectedPage],
|
||||||
() => {
|
() => {
|
||||||
@@ -30,7 +33,7 @@ export function BoardPage(boardViewModel: BoardViewModel) {
|
|||||||
<div
|
<div
|
||||||
class="task-grid"
|
class="task-grid"
|
||||||
children:append={[
|
children:append={[
|
||||||
boardViewModel.taskViewModels,
|
boardViewModel.filteredTaskViewModels,
|
||||||
TaskViewModelToEntry,
|
TaskViewModelToEntry,
|
||||||
]}
|
]}
|
||||||
></div>
|
></div>
|
||||||
@@ -40,6 +43,7 @@ export function BoardPage(boardViewModel: BoardViewModel) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// task modal
|
||||||
const taskSettingsModal = React.createProxyState(
|
const taskSettingsModal = React.createProxyState(
|
||||||
[boardViewModel.selectedTaskViewModel],
|
[boardViewModel.selectedTaskViewModel],
|
||||||
() => {
|
() => {
|
||||||
@@ -110,6 +114,14 @@ export function BoardPage(boardViewModel: BoardViewModel) {
|
|||||||
<div class="content main-content" children:set={mainContent}></div>
|
<div class="content main-content" children:set={mainContent}></div>
|
||||||
|
|
||||||
{BoardSettingsModal(boardViewModel)}
|
{BoardSettingsModal(boardViewModel)}
|
||||||
|
{SearchModal(
|
||||||
|
translations.chatPage.task.filterTasksHeadline,
|
||||||
|
boardViewModel.taskViewModels,
|
||||||
|
boardViewModel.filteredTaskViewModels,
|
||||||
|
TaskViewModelToEntry,
|
||||||
|
boardViewModel.getStringsFromTaskViewModel,
|
||||||
|
boardViewModel.isPresentingFilterModal
|
||||||
|
)}
|
||||||
<div children:set={taskSettingsModal}></div>
|
<div children:set={taskSettingsModal}></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as React from "bloatless-react";
|
|||||||
import {
|
import {
|
||||||
TaskCategoryBulkChangeViewModel,
|
TaskCategoryBulkChangeViewModel,
|
||||||
TaskStatusBulkChangeViewModel,
|
TaskStatusBulkChangeViewModel,
|
||||||
} from "../../ViewModel/Pages/taskPropertyBulkChangeViewModel";
|
} from "../../ViewModel/Utility/taskPropertyBulkChangeViewModel";
|
||||||
|
|
||||||
import BoardViewModel from "../../ViewModel/Pages/boardViewModel";
|
import BoardViewModel from "../../ViewModel/Pages/boardViewModel";
|
||||||
import { FilteredList } from "../Components/filteredList";
|
import { FilteredList } from "../Components/filteredList";
|
||||||
@@ -46,7 +46,7 @@ export function BoardStatusGridPage(boardViewModel: BoardViewModel) {
|
|||||||
{PropertyValueList(
|
{PropertyValueList(
|
||||||
"category",
|
"category",
|
||||||
(taskViewModel: TaskViewModel) => taskViewModel.task,
|
(taskViewModel: TaskViewModel) => taskViewModel.task,
|
||||||
boardViewModel.taskViewModels,
|
boardViewModel.filteredTaskViewModels,
|
||||||
(categories: React.ListState<string>) => {
|
(categories: React.ListState<string>) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
|
import SearchViewModel from "../../ViewModel/Utility/searchViewModel";
|
||||||
|
import { translations } from "../translations";
|
||||||
|
|
||||||
|
export function SearchModal<T>(
|
||||||
|
headline: string,
|
||||||
|
allObjects: React.ListState<T> | React.MapState<T>,
|
||||||
|
filteredObjects: React.ListState<T>,
|
||||||
|
converter: React.StateItemConverter<T>,
|
||||||
|
getStringsOfObject: (object: T) => string[],
|
||||||
|
isOpen: React.State<boolean>
|
||||||
|
) {
|
||||||
|
const viewModel: SearchViewModel<T> = new SearchViewModel(
|
||||||
|
allObjects,
|
||||||
|
filteredObjects,
|
||||||
|
getStringsOfObject
|
||||||
|
);
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
isOpen.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="modal" toggle:open={isOpen}>
|
||||||
|
<div style="max-width: 64rem">
|
||||||
|
<main>
|
||||||
|
<h2>{headline}</h2>
|
||||||
|
<div class="flex-row width-input">
|
||||||
|
<input
|
||||||
|
placeholder={translations.general.searchLabel}
|
||||||
|
bind:value={viewModel.searchInput}
|
||||||
|
on:enter={viewModel.applySearch}
|
||||||
|
></input>
|
||||||
|
<button
|
||||||
|
class="primary"
|
||||||
|
aria-label={translations.general.searchButtonAudioLabel}
|
||||||
|
on:click={viewModel.applySearch}
|
||||||
|
toggle:disabled={viewModel.cannotApplySearch}
|
||||||
|
>
|
||||||
|
<span class="icon">search</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr></hr>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="grid gap"
|
||||||
|
children:append={[filteredObjects, converter]}
|
||||||
|
></div>
|
||||||
|
</main>
|
||||||
|
<button on:click={close}>
|
||||||
|
{translations.general.closeButton}
|
||||||
|
<span class="icon">close</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
const englishTranslations = {
|
const englishTranslations = {
|
||||||
general: {
|
general: {
|
||||||
deleteItemButtonAudioLabel: "delete item",
|
deleteItemButtonAudioLabel: "delete item",
|
||||||
|
searchButtonAudioLabel: "search",
|
||||||
|
|
||||||
abortButton: "Abort",
|
abortButton: "Abort",
|
||||||
closeButton: "Close",
|
closeButton: "Close",
|
||||||
@@ -10,6 +11,7 @@ const englishTranslations = {
|
|||||||
setButton: "Set",
|
setButton: "Set",
|
||||||
|
|
||||||
fileVersionLabel: "Version",
|
fileVersionLabel: "Version",
|
||||||
|
searchLabel: "Search",
|
||||||
},
|
},
|
||||||
|
|
||||||
regional: {
|
regional: {
|
||||||
@@ -177,6 +179,10 @@ const englishTranslations = {
|
|||||||
|
|
||||||
///
|
///
|
||||||
|
|
||||||
|
filterTasksHeadline: "Filter Tasks",
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
renameCategoryInputPlaceholder: "Rename category,"
|
renameCategoryInputPlaceholder: "Rename category,"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ export default class BoardViewModel {
|
|||||||
|
|
||||||
index: React.State<number> = new React.State(0);
|
index: React.State<number> = new React.State(0);
|
||||||
|
|
||||||
taskViewModels: React.MapState<TaskViewModel> = new React.MapState();
|
|
||||||
|
|
||||||
selectedPage: React.State<BoardPageType> = new React.State<BoardPageType>(
|
selectedPage: React.State<BoardPageType> = new React.State<BoardPageType>(
|
||||||
BoardPageType.List
|
BoardPageType.List
|
||||||
);
|
);
|
||||||
@@ -42,6 +40,10 @@ export default class BoardViewModel {
|
|||||||
isPresentingSettingsModal: React.State<boolean> = new React.State(false);
|
isPresentingSettingsModal: React.State<boolean> = new React.State(false);
|
||||||
isPresentingFilterModal: React.State<boolean> = new React.State(false);
|
isPresentingFilterModal: React.State<boolean> = new React.State(false);
|
||||||
|
|
||||||
|
taskViewModels: React.MapState<TaskViewModel> = new React.MapState();
|
||||||
|
filteredTaskViewModels: React.ListState<TaskViewModel> =
|
||||||
|
new React.ListState();
|
||||||
|
|
||||||
// paths
|
// paths
|
||||||
getBasePath = (): string[] => {
|
getBasePath = (): string[] => {
|
||||||
return [...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId)];
|
return [...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId)];
|
||||||
@@ -162,6 +164,19 @@ export default class BoardViewModel {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// filter
|
||||||
|
getStringsFromTaskViewModel = (taskViewModel: TaskViewModel): string[] => {
|
||||||
|
return [
|
||||||
|
taskViewModel.task.name,
|
||||||
|
taskViewModel.task.description ?? "",
|
||||||
|
taskViewModel.task.category ?? "",
|
||||||
|
taskViewModel.task.status ?? "",
|
||||||
|
taskViewModel.task.priority ?? "",
|
||||||
|
taskViewModel.task.date ?? "",
|
||||||
|
taskViewModel.task.time ?? "",
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
// load
|
// load
|
||||||
loadListRelevantData = (): void => {
|
loadListRelevantData = (): void => {
|
||||||
this.name.value = this.boardInfo.name;
|
this.name.value = this.boardInfo.name;
|
||||||
@@ -174,7 +189,7 @@ export default class BoardViewModel {
|
|||||||
);
|
);
|
||||||
for (const taskId of taskIds) {
|
for (const taskId of taskIds) {
|
||||||
if (this.taskViewModels.value.has(taskId)) return;
|
if (this.taskViewModels.value.has(taskId)) return;
|
||||||
|
|
||||||
const taskFileContent: TaskFileContent | null =
|
const taskFileContent: TaskFileContent | null =
|
||||||
this.boardModel.getLatestTaskFileContent(taskId);
|
this.boardModel.getLatestTaskFileContent(taskId);
|
||||||
if (taskFileContent == null) continue;
|
if (taskFileContent == null) continue;
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
|
import { checkDoesObjectMatchSearch } from "../../Model/Utility/utility";
|
||||||
|
|
||||||
|
export default class SearchViewModel<T> {
|
||||||
|
// data
|
||||||
|
allObjects: React.ListState<T> | React.MapState<T>;
|
||||||
|
getStringsOfObject: (object: T) => string[];
|
||||||
|
|
||||||
|
// state
|
||||||
|
appliedQuery: React.State<string> = new React.State("");
|
||||||
|
searchInput: React.State<string> = new React.State("");
|
||||||
|
matchingObjects: React.ListState<T>;
|
||||||
|
|
||||||
|
// guards
|
||||||
|
cannotApplySearch: React.State<boolean> = React.createProxyState(
|
||||||
|
[this.searchInput, this.appliedQuery],
|
||||||
|
() => this.searchInput.value == this.appliedQuery.value
|
||||||
|
);
|
||||||
|
|
||||||
|
// methods
|
||||||
|
applySearch = (): void => {
|
||||||
|
this.appliedQuery.value = this.searchInput.value;
|
||||||
|
console.log("applying search");
|
||||||
|
|
||||||
|
this.matchingObjects.clear();
|
||||||
|
for (const object of this.allObjects.value.values()) {
|
||||||
|
const doesMatch: boolean = this.checkDoesMatchSearch(object);
|
||||||
|
if (doesMatch == false) continue;
|
||||||
|
|
||||||
|
this.matchingObjects.add(object);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// init
|
||||||
|
constructor(
|
||||||
|
allObjects: React.ListState<T> | React.MapState<T>,
|
||||||
|
matchingObjects: React.ListState<T>,
|
||||||
|
getStringsOfObject: (object: T) => string[]
|
||||||
|
) {
|
||||||
|
this.allObjects = allObjects;
|
||||||
|
this.matchingObjects = matchingObjects;
|
||||||
|
this.getStringsOfObject = getStringsOfObject;
|
||||||
|
|
||||||
|
// handle new objects
|
||||||
|
this.allObjects.handleAddition((newObject: T) => {
|
||||||
|
const doesMatch: boolean = this.checkDoesMatchSearch(newObject);
|
||||||
|
if (doesMatch == false) {
|
||||||
|
this.matchingObjects.remove(newObject);
|
||||||
|
} else {
|
||||||
|
if (this.matchingObjects.value.has(newObject)) return;
|
||||||
|
this.matchingObjects.add(newObject);
|
||||||
|
this.allObjects.handleRemoval(newObject, () => {
|
||||||
|
this.matchingObjects.remove(newObject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// utility
|
||||||
|
checkDoesMatchSearch = (object: T): boolean => {
|
||||||
|
return checkDoesObjectMatchSearch(
|
||||||
|
this.appliedQuery.value,
|
||||||
|
this.getStringsOfObject,
|
||||||
|
object
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "bloatless-react";
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
import TaskViewModel from "./taskViewModel";
|
import TaskViewModel from "../Pages/taskViewModel";
|
||||||
|
|
||||||
export default class TaskPropertyBulkChangeViewModel {
|
export default class TaskPropertyBulkChangeViewModel {
|
||||||
taskViewModels: React.ListState<TaskViewModel>;
|
taskViewModels: React.ListState<TaskViewModel>;
|
||||||
Reference in New Issue
Block a user