From 845a9d2f357407c693005de57a4627188beca3d8 Mon Sep 17 00:00:00 2001 From: marlon-erler Date: Sat, 27 Jul 2024 03:41:47 +0200 Subject: [PATCH] add history of searches in board --- dist/index.js | 169 ++++++++++++++--------- dist/styles.css | 2 +- src/View/ChatPages/boardPage.tsx | 4 +- src/View/Modals/searchModal.tsx | 30 ++-- src/ViewModel/Pages/boardViewModel.ts | 36 ++++- src/ViewModel/Utility/searchViewModel.ts | 11 +- 6 files changed, 165 insertions(+), 87 deletions(-) diff --git a/dist/index.js b/dist/index.js index eb78ed00..31e7f464 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2006,6 +2006,65 @@ }; }; + // src/ViewModel/Utility/searchViewModel.ts + var SearchViewModel = class { + // data + allObjects; + getStringsOfObject; + // state + appliedQuery = new State(""); + searchInput = new State(""); + matchingObjects; + suggestions; + // guards + cannotApplySearch = createProxyState( + [this.searchInput, this.appliedQuery], + () => this.searchInput.value == this.appliedQuery.value + ); + // methods + search = (searchTerm) => { + this.searchInput.value = searchTerm; + this.applySearch(); + }; + 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, suggestions) { + this.allObjects = allObjects; + this.matchingObjects = matchingObjects; + this.getStringsOfObject = getStringsOfObject; + this.suggestions = suggestions; + 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/ViewModel/Pages/boardViewModel.ts var BoardViewModel = class extends TaskContainingPageViewModel { // init @@ -2035,6 +2094,15 @@ this.updateTaskIndices(); } ); + this.searchViewModel = new SearchViewModel( + this.taskViewModels, + this.filteredTaskViewModels, + TaskViewModel.getStringsForFilter, + this.searchSuggestions + ); + this.searchViewModel.appliedQuery.subscribe((newQuery) => { + this.storeSearchTerm(newQuery); + }); } storageModel; boardsAndTasksModel; @@ -2051,7 +2119,9 @@ isSelected; isPresentingSettingsModal = new State(false); isPresentingFilterModal = new State(false); + searchViewModel; filteredTaskViewModels = new ListState(); + searchSuggestions = new ListState(); // paths getBasePath = () => { return [...this.taskPageViewModel.getBoardViewPath(this.boardInfo.fileId)]; @@ -2059,6 +2129,9 @@ getLastUsedBoardPath = () => { return [...this.getBasePath(), "last-used-view" /* LastUsedView */]; }; + getPreviousSearchesPath = () => { + return [...this.getBasePath(), "previous-searches" /* PreviousSearches */]; + }; // settings saveSettings = () => { const newBoardInfoFileContent = BoardsAndTasksModel.createBoardInfoFileContent( @@ -2101,6 +2174,13 @@ if (lastUsedView == null) return; this.selectedPage.value = lastUsedView; }; + storeSearchTerm = (searchTerm) => { + const path = [...this.getPreviousSearchesPath(), searchTerm]; + this.storageModel.write(path, ""); + if (!this.searchSuggestions.value.has(searchTerm)) { + this.searchSuggestions.add(searchTerm); + } + }; // view showTask = (taskFileContent) => { if (taskFileContent.boardId != this.boardInfo.fileId) { @@ -2170,9 +2250,15 @@ } this.updateTaskIndices(); }; + loadSearchSuggestions = () => { + const dirPath = this.getPreviousSearchesPath(); + const searches = this.storageModel.list(dirPath); + this.searchSuggestions.add(...searches); + }; loadData = () => { this.restoreLastUsedView(); this.loadTasks(); + this.loadSearchSuggestions(); }; }; @@ -3521,83 +3607,34 @@ 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 SearchModal(searchViewModel, headline, converter, isOpen) { function close() { isOpen.value = false; } + const id = v4_default(); 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 + "bind:value": searchViewModel.searchInput, + "on:enter": searchViewModel.applySearch, + list: id + } + ), /* @__PURE__ */ createElement( + "datalist", + { + hidden: true, + id, + "children:append": [searchViewModel.suggestions, StringToOption] } ), /* @__PURE__ */ createElement( "button", { class: "primary", "aria-label": translations.general.searchButtonAudioLabel, - "on:click": viewModel.applySearch, - "toggle:disabled": viewModel.cannotApplySearch + "on:click": searchViewModel.applySearch, + "toggle:disabled": searchViewModel.cannotApplySearch }, /* @__PURE__ */ createElement("span", { class: "icon" }, "search") )), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement( @@ -3605,7 +3642,7 @@ { class: "grid gap", style: "grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr))", - "children:append": [filteredObjects, converter] + "children:append": [searchViewModel.matchingObjects, converter] } )), /* @__PURE__ */ createElement("button", { "on:click": close }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close")))); } @@ -3696,11 +3733,9 @@ }, /* @__PURE__ */ createElement("span", { class: "icon" }, "add") ))), /* @__PURE__ */ createElement("div", { class: "content main-content", "children:set": mainContent }), BoardSettingsModal(boardViewModel), SearchModal( + boardViewModel.searchViewModel, translations.chatPage.task.filterTasksHeadline, - boardViewModel.taskViewModels, - boardViewModel.filteredTaskViewModels, TaskViewModelToEntry, - TaskViewModel.getStringsForFilter, boardViewModel.isPresentingFilterModal ), /* @__PURE__ */ createElement("div", { "children:set": taskSettingsModal })); } diff --git a/dist/styles.css b/dist/styles.css index 9d92c337..161f34c5 100644 --- a/dist/styles.css +++ b/dist/styles.css @@ -38,7 +38,7 @@ COLORS /* definition */ :root { --background: hsla(0, 0%, 100%, 100%); - --background-glass: hsla(0, 0%, 100%, 40%); + --background-glass: hsla(0, 0%, 100%, 5%); --background-transparent: hsla(0, 0%, 0%, 5%); --background-hover: hsla(0, 0%, 55%, 12%); --background-active: hsla(0, 0%, 55%, 12%); diff --git a/src/View/ChatPages/boardPage.tsx b/src/View/ChatPages/boardPage.tsx index 08c925a3..23e95851 100644 --- a/src/View/ChatPages/boardPage.tsx +++ b/src/View/ChatPages/boardPage.tsx @@ -115,11 +115,9 @@ export function BoardPage(boardViewModel: BoardViewModel) { {BoardSettingsModal(boardViewModel)} {SearchModal( + boardViewModel.searchViewModel, translations.chatPage.task.filterTasksHeadline, - boardViewModel.taskViewModels, - boardViewModel.filteredTaskViewModels, TaskViewModelToEntry, - TaskViewModel.getStringsForFilter, boardViewModel.isPresentingFilterModal )}
diff --git a/src/View/Modals/searchModal.tsx b/src/View/Modals/searchModal.tsx index b7988e88..36775094 100644 --- a/src/View/Modals/searchModal.tsx +++ b/src/View/Modals/searchModal.tsx @@ -1,26 +1,22 @@ import * as React from "bloatless-react"; import SearchViewModel from "../../ViewModel/Utility/searchViewModel"; +import { StringToOption } from "../Components/option"; import { translations } from "../translations"; +import { v4 } from "uuid"; export function SearchModal( + searchViewModel: SearchViewModel, headline: string, - allObjects: React.ListState | React.MapState, - filteredObjects: React.ListState, converter: React.StateItemConverter, - getStringsOfObject: (object: T) => string[], isOpen: React.State ) { - const viewModel: SearchViewModel = new SearchViewModel( - allObjects, - filteredObjects, - getStringsOfObject - ); - function close() { isOpen.value = false; } + const id = v4(); + return (