@@ -3,6 +3,7 @@ import { Task } from "@lit/task";
33import { html , type PropertyValues } from "lit" ;
44import { customElement , state } from "lit/decorators.js" ;
55import { when } from "lit/directives/when.js" ;
6+ import omit from "lodash/fp/omit" ;
67import queryString from "query-string" ;
78
89import type { Profile } from "./types" ;
@@ -20,7 +21,7 @@ import { ClipboardController } from "@/controllers/clipboard";
2021import { SearchParamsValue } from "@/controllers/searchParamsValue" ;
2122import { originsWithRemainder } from "@/features/browser-profiles/templates/origins-with-remainder" ;
2223import { emptyMessage } from "@/layouts/emptyMessage" ;
23- import { page } from "@/layouts/page " ;
24+ import { pageHeader } from "@/layouts/pageHeader " ;
2425import { OrgTab } from "@/routes" ;
2526import type {
2627 APIPaginatedList ,
@@ -30,6 +31,7 @@ import type {
3031import { SortDirection as SortDirectionEnum } from "@/types/utils" ;
3132import { isApiError } from "@/utils/api" ;
3233import { isArchivingDisabled } from "@/utils/orgs" ;
34+ import { toSearchItem , type SearchValues } from "@/utils/searchValues" ;
3335
3436const SORT_DIRECTIONS = [ "asc" , "desc" ] as const ;
3537type SortDirection = ( typeof SORT_DIRECTIONS ) [ number ] ;
@@ -45,7 +47,7 @@ const sortableFields: Record<
4547> = {
4648 name : {
4749 label : msg ( "Name" ) ,
48- defaultDirection : "desc " ,
50+ defaultDirection : "asc " ,
4951 } ,
5052 url : {
5153 label : msg ( "Primary Site" ) ,
@@ -66,7 +68,17 @@ const DEFAULT_SORT_BY = {
6668 direction : sortableFields . modified . defaultDirection || "desc" ,
6769} as const satisfies SortBy ;
6870const INITIAL_PAGE_SIZE = 20 ;
69- const FILTER_BY_CURRENT_USER_STORAGE_KEY = "btrix.filterByCurrentUser.crawls" ;
71+ const FILTER_BY_CURRENT_USER_STORAGE_KEY =
72+ "btrix.filterByCurrentUser.browserProfiles" ;
73+ const SEARCH_KEYS = [ "name" ] as const ;
74+ const DEFAULT_TAGS_TYPE = "or" ;
75+
76+ type FilterBy = {
77+ name ?: string ;
78+ tags ?: string [ ] ;
79+ tagsType ?: "and" | "or" ;
80+ mine ?: boolean ;
81+ } ;
7082
7183const columnsCss = [
7284 "min-content" , // Status
@@ -124,84 +136,84 @@ export class BrowserProfilesList extends BtrixElement {
124136 } ,
125137 ) ;
126138
127- private readonly filterByTags = new SearchParamsValue < string [ ] | undefined > (
128- this ,
129- ( value , params ) => {
130- params . delete ( "tags" ) ;
131- value ?. forEach ( ( v ) => {
132- params . append ( "tags" , v ) ;
133- } ) ;
134- return params ;
135- } ,
136- ( params ) => params . getAll ( "tags" ) ,
137- ) ;
138-
139- private readonly filterByTagsType = new SearchParamsValue < "and" | "or" > (
139+ private readonly filterBy = new SearchParamsValue < FilterBy > (
140140 this ,
141141 ( value , params ) => {
142- if ( value === "and" ) {
143- params . set ( "tagsType" , value ) ;
142+ if ( "name" in value && value [ "name" ] ) {
143+ params . set ( "name" , value [ "name" ] ) ;
144+ } else {
145+ params . delete ( "name" ) ;
146+ }
147+ if ( "tags" in value ) {
148+ params . delete ( "tags" ) ;
149+ value [ "tags" ] ?. forEach ( ( v ) => {
150+ params . append ( "tags" , v ) ;
151+ } ) ;
152+ } else {
153+ params . delete ( "tags" ) ;
154+ }
155+ if ( "tagsType" in value && value [ "tagsType" ] === "and" ) {
156+ params . set ( "tagsType" , value [ "tagsType" ] ) ;
144157 } else {
145158 params . delete ( "tagsType" ) ;
146159 }
147- return params ;
148- } ,
149- ( params ) => ( params . get ( "tagsType" ) === "and" ? "and" : "or" ) ,
150- ) ;
151-
152- private readonly filterByCurrentUser = new SearchParamsValue < boolean > (
153- this ,
154- ( value , params ) => {
155- if ( value ) {
160+ if ( "mine" in value && value [ "mine" ] ) {
156161 params . set ( "mine" , "true" ) ;
162+ window . sessionStorage . setItem (
163+ FILTER_BY_CURRENT_USER_STORAGE_KEY ,
164+ "true" ,
165+ ) ;
157166 } else {
158167 params . delete ( "mine" ) ;
168+ window . sessionStorage . removeItem ( FILTER_BY_CURRENT_USER_STORAGE_KEY ) ;
159169 }
160170 return params ;
161171 } ,
162- ( params ) => params . get ( "mine" ) === "true" ,
172+ ( params ) => ( {
173+ name : params . get ( "name" ) || undefined ,
174+ tags : params . getAll ( "tags" ) ,
175+ tagsType : params . get ( "tagsType" ) === "and" ? "and" : "or" ,
176+ mine : params . get ( "mine" ) === "true" ,
177+ } ) ,
163178 {
164- initial : ( initialValue ) =>
165- window . sessionStorage . getItem ( FILTER_BY_CURRENT_USER_STORAGE_KEY ) ===
166- "true" ||
167- initialValue ||
168- false ,
179+ initial : ( initialValue ) => ( {
180+ ...initialValue ,
181+ mine :
182+ window . sessionStorage . getItem ( FILTER_BY_CURRENT_USER_STORAGE_KEY ) ===
183+ "true" ||
184+ initialValue ?. [ "mine" ] ||
185+ false ,
186+ } ) ,
169187 } ,
170188 ) ;
171189
172190 private get hasFiltersSet ( ) {
173- return [
174- this . filterByCurrentUser . value || undefined ,
175- this . filterByTags . value ?. length || undefined ,
176- ] . some ( ( v ) => v !== undefined ) ;
191+ const filterBy = this . filterBy . value ;
192+ return (
193+ filterBy . name ||
194+ filterBy . tags ?. length ||
195+ ( filterBy . tagsType ?? DEFAULT_TAGS_TYPE ) !== DEFAULT_TAGS_TYPE ||
196+ filterBy . mine
197+ ) ;
177198 }
178199
179200 get isCrawler ( ) {
180201 return this . appState . isCrawler ;
181202 }
182203
183204 private clearFilters ( ) {
184- this . filterByCurrentUser . setValue ( false ) ;
185- this . filterByTags . setValue ( [ ] ) ;
205+ this . filterBy . setValue ( { } ) ;
186206 }
187207
188208 private readonly profilesTask = new Task ( this , {
189- task : async (
190- [
191- pagination ,
192- orderBy ,
193- filterByCurrentUser ,
194- filterByTags ,
195- filterByTagsType ,
196- ] ,
197- { signal } ,
198- ) => {
209+ task : async ( [ pagination , orderBy , filterBy ] , { signal } ) => {
199210 return this . getProfiles (
200211 {
201212 ...pagination ,
202- userid : filterByCurrentUser ? this . userInfo ?. id : undefined ,
203- tags : filterByTags ,
204- tagMatch : filterByTagsType ,
213+ userid : filterBy . mine ? this . userInfo ?. id : undefined ,
214+ name : filterBy . name || undefined ,
215+ tags : filterBy . tags ,
216+ tagMatch : filterBy . tagsType ,
205217 sortBy : orderBy . field ,
206218 sortDirection :
207219 orderBy . direction === "desc"
@@ -212,39 +224,33 @@ export class BrowserProfilesList extends BtrixElement {
212224 ) ;
213225 } ,
214226 args : ( ) =>
215- [
216- this . pagination ,
217- this . orderBy . value ,
218- this . filterByCurrentUser . value ,
219- this . filterByTags . value ,
220- this . filterByTagsType . value ,
221- ] as const ,
227+ [ this . pagination , this . orderBy . value , this . filterBy . value ] as const ,
228+ } ) ;
229+
230+ private readonly searchOptionsTask = new Task ( this , {
231+ task : async ( _args , { signal } ) => {
232+ const data = await this . getSearchValues ( signal ) ;
233+
234+ return [ ...data . names . map ( toSearchItem ( "name" ) ) ] ;
235+ } ,
236+ args : ( ) => [ ] as const ,
222237 } ) ;
223238
224239 protected willUpdate ( changedProperties : PropertyValues ) : void {
225240 if (
226241 changedProperties . has ( "orderBy.internalValue" ) ||
227- changedProperties . has ( "filterByCurrentUser.internalValue" ) ||
228- changedProperties . has ( "filterByTags.internalValue" ) ||
229- changedProperties . has ( "filterByTagsType.internalValue" )
242+ changedProperties . has ( "filterBy.internalValue" )
230243 ) {
231244 this . pagination = {
232245 ...this . pagination ,
233246 page : 1 ,
234247 } ;
235248 }
236-
237- if ( changedProperties . has ( "filterByCurrentUser.internalValue" ) ) {
238- window . sessionStorage . setItem (
239- FILTER_BY_CURRENT_USER_STORAGE_KEY ,
240- this . filterByCurrentUser . value . toString ( ) ,
241- ) ;
242- }
243249 }
244250
245251 render ( ) {
246- return page (
247- {
252+ return html `
253+ ${ pageHeader ( {
248254 title : msg ( "Browser Profiles" ) ,
249255 border : false ,
250256 actions : this . isCrawler
@@ -266,9 +272,9 @@ export class BrowserProfilesList extends BtrixElement {
266272 </ sl-button >
267273 `
268274 : undefined ,
269- } ,
270- this . renderPage ,
271- ) ;
275+ } ) }
276+ ${ this . renderPage ( ) }
277+ ` ;
272278 }
273279
274280 private readonly renderPage = ( ) => {
@@ -377,41 +383,77 @@ export class BrowserProfilesList extends BtrixElement {
377383
378384 private renderControls ( ) {
379385 return html `
380- < div class ="flex flex-wrap items-center justify-between gap-2 ">
386+ < div class ="flex flex-wrap items-center gap-2 md:gap-4 ">
387+ < div class ="grow basis-2/3 "> ${ this . renderSearch ( ) } </ div >
388+
389+ < div class ="flex items-center ">
390+ < label
391+ class ="mr-2 whitespace-nowrap text-sm text-neutral-500 "
392+ for ="sort-select "
393+ >
394+ ${ msg ( "Sort by:" ) }
395+ </ label >
396+ ${ this . renderSortControl ( ) }
397+ </ div >
398+
381399 < div class ="flex flex-wrap items-center gap-2 ">
382400 < span class ="whitespace-nowrap text-neutral-500 ">
383401 ${ msg ( "Filter by:" ) }
384402 </ span >
385403 ${ this . renderFilterControls ( ) }
386404 </ div >
387-
388- < div class ="flex flex-wrap items-center gap-2 ">
389- < label class ="whitespace-nowrap text-neutral-500 " for ="sort-select ">
390- ${ msg ( "Sort by:" ) }
391- </ label >
392- ${ this . renderSortControl ( ) }
393- </ div >
394405 </ div >
395406 ` ;
396407 }
397408
409+ private renderSearch ( ) {
410+ return html `
411+ < btrix-search-combobox
412+ .searchKeys =${ SEARCH_KEYS }
413+ .searchOptions =${ this . searchOptionsTask . value || [ ] }
414+ .searchByValue=${ this . filterBy . value [ "name" ] || "" }
415+ placeholder=${ msg ( "Search by name" ) }
416+ @btrix-select=${ ( e : CustomEvent ) => {
417+ const { key, value } = e . detail ;
418+ this . filterBy . setValue ( {
419+ ...this . filterBy . value ,
420+ [ key ] : value ,
421+ } ) ;
422+ } }
423+ @btrix-clear=${ ( ) => {
424+ const otherFilters = omit ( SEARCH_KEYS , this . filterBy . value ) ;
425+ this . filterBy . setValue ( otherFilters ) ;
426+ } }
427+ >
428+ </ btrix-search-combobox >
429+ ` ;
430+ }
431+
398432 private renderFilterControls ( ) {
433+ const filterBy = this . filterBy . value ;
434+
399435 return html `
400436 < btrix-tag-filter
401437 tagType ="profile "
402- .tags =${ this . filterByTags . value }
403- .type =${ this . filterByTagsType . value }
438+ .tags =${ filterBy . tags }
439+ .type =${ filterBy . tagsType || DEFAULT_TAGS_TYPE }
404440 @btrix-change=${ ( e : BtrixChangeTagFilterEvent ) => {
405- this . filterByTags . setValue ( e . detail . value ?. tags || [ ] ) ;
406- this . filterByTagsType . setValue ( e . detail . value ?. type || "or" ) ;
441+ this . filterBy . setValue ( {
442+ ...this . filterBy . value ,
443+ tags : e . detail . value ?. tags || [ ] ,
444+ tagsType : e . detail . value ?. type || DEFAULT_TAGS_TYPE ,
445+ } ) ;
407446 } }
408447 > </ btrix-tag-filter >
409448
410449 < btrix-filter-chip
411- ?checked =${ this . filterByCurrentUser . value }
450+ ?checked =${ filterBy . mine }
412451 @btrix-change =${ ( e : BtrixFilterChipChangeEvent ) => {
413452 const { checked } = e . target as FilterChip ;
414- this . filterByCurrentUser . setValue ( Boolean ( checked ) ) ;
453+ this . filterBy . setValue ( {
454+ ...this . filterBy . value ,
455+ mine : checked ,
456+ } ) ;
415457 } }
416458 >
417459 ${ msg ( "Mine" ) }
@@ -652,6 +694,7 @@ export class BrowserProfilesList extends BtrixElement {
652694 private async getProfiles (
653695 params : {
654696 userid ?: string ;
697+ name ?: string ;
655698 tags ?: string [ ] ;
656699 tagMatch ?: string ;
657700 } & APIPaginationQuery &
@@ -674,4 +717,13 @@ export class BrowserProfilesList extends BtrixElement {
674717
675718 return data ;
676719 }
720+
721+ private async getSearchValues ( signal : AbortSignal ) {
722+ return this . api . fetch < SearchValues > (
723+ `/orgs/${ this . orgId } /profiles/search-values` ,
724+ {
725+ signal,
726+ } ,
727+ ) ;
728+ }
677729}
0 commit comments