11import { localized , msg } from "@lit/localize" ;
2+ import { Task } from "@lit/task" ;
23import type {
34 SlChangeEvent ,
45 SlDrawer ,
56 SlSelect ,
67} from "@shoelace-style/shoelace" ;
7- import { html , nothing , type PropertyValues } from "lit" ;
8+ import clsx from "clsx" ;
9+ import { html , nothing } from "lit" ;
810import { customElement , property , query , state } from "lit/decorators.js" ;
911import { ifDefined } from "lit/directives/if-defined.js" ;
1012import { when } from "lit/directives/when.js" ;
11- import orderBy from "lodash/fp/orderBy " ;
13+ import queryString from "query-string " ;
1214
1315import { BtrixElement } from "@/classes/BtrixElement" ;
1416import { none } from "@/layouts/empty" ;
1517import { pageHeading } from "@/layouts/page" ;
1618import { CrawlerChannelImage , type Profile } from "@/pages/org/types" ;
1719import { OrgTab } from "@/routes" ;
18- import type { APIPaginatedList } from "@/types/api" ;
20+ import type {
21+ APIPaginatedList ,
22+ APIPaginationQuery ,
23+ APISortQuery ,
24+ } from "@/types/api" ;
25+ import { SortDirection } from "@/types/utils" ;
1926import { AppStateService } from "@/utils/state" ;
27+ import { tw } from "@/utils/tailwind" ;
2028
2129type SelectBrowserProfileChangeDetail = {
2230 value : Profile | undefined ;
2331} ;
2432
33+ // TODO Paginate results
34+ const INITIAL_PAGE_SIZE = 1000 ;
35+
2536export type SelectBrowserProfileChangeEvent =
2637 CustomEvent < SelectBrowserProfileChangeDetail > ;
2738
@@ -47,68 +58,132 @@ export class SelectBrowserProfile extends BtrixElement {
4758 profileId ?: string ;
4859
4960 @state ( )
50- private selectedProfile ?: Profile ;
61+ selectedProfile ?: Profile ;
5162
52- @state ( )
53- private browserProfiles ?: Profile [ ] ;
63+ @query ( "sl-select" )
64+ private readonly select ?: SlSelect | null ;
5465
5566 @query ( "sl-drawer" )
5667 private readonly drawer ?: SlDrawer | null ;
5768
58- willUpdate ( changedProperties : PropertyValues < this> ) {
59- if ( changedProperties . has ( "profileId" ) ) {
60- void this . updateSelectedProfile ( ) ;
61- }
69+ public get value ( ) {
70+ return this . select ?. value as string ;
6271 }
6372
64- firstUpdated ( ) {
65- void this . updateSelectedProfile ( ) ;
73+ private readonly profilesTask = new Task ( this , {
74+ task : async ( _args , { signal } ) => {
75+ return this . getProfiles (
76+ {
77+ sortBy : "name" ,
78+ sortDirection : SortDirection . Ascending ,
79+ pageSize : INITIAL_PAGE_SIZE ,
80+ } ,
81+ signal ,
82+ ) ;
83+ } ,
84+ args : ( ) => [ ] as const ,
85+ } ) ;
86+
87+ private readonly selectedProfileTask = new Task ( this , {
88+ task : async ( [ profileId , profiles ] , { signal } ) => {
89+ if ( ! profileId || ! profiles || signal . aborted ) return ;
90+
91+ this . selectedProfile = this . findProfileById ( profileId ) ;
92+ } ,
93+ args : ( ) => [ this . profileId , this . profilesTask . value ] as const ,
94+ } ) ;
95+
96+ private findProfileById ( profileId ?: string ) {
97+ if ( ! profileId ) return ;
98+ return this . profilesTask . value ?. items . find ( ( { id } ) => id === profileId ) ;
6699 }
67100
68101 render ( ) {
102+ const selectedProfile = this . selectedProfile ;
103+ const browserProfiles = this . profilesTask . value ;
104+
69105 return html `
70106 < sl-select
71- name ="profileid "
72107 label =${ msg ( "Browser Profile" ) }
73- value =${ this . selectedProfile ?. id || "" }
74- placeholder=${ this . browserProfiles
108+ value =${ selectedProfile ?. id || "" }
109+ placeholder=${ browserProfiles
75110 ? msg ( "No custom profile" )
76111 : msg ( "Loading" ) }
77112 size=${ ifDefined ( this . size ) }
78113 hoist
114+ clearable
79115 @sl-change=${ this . onChange }
80- @sl-focus=${ ( ) => {
81- // Refetch to keep list up to date
82- void this . fetchBrowserProfiles ( ) ;
83- } }
84116 @sl-hide=${ this . stopProp }
85117 @sl-after-hide=${ this . stopProp }
86118 >
87- ${ this . browserProfiles
119+ ${ when (
120+ selectedProfile ?. proxyId ,
121+ ( proxyId ) => html `
122+ < btrix-proxy-badge
123+ slot ="suffix "
124+ proxyId =${ proxyId }
125+ > </ btrix-proxy-badge >
126+ ` ,
127+ ) }
128+ ${ browserProfiles
88129 ? html `
89130 < sl-option value =""> ${ msg ( "No custom profile" ) } </ sl-option >
90- < sl-divider > </ sl-divider >
131+ ${ browserProfiles . items . length
132+ ? html `
133+ < sl-divider > </ sl-divider >
134+ < sl-menu-label > ${ msg ( "Saved Profiles" ) } </ sl-menu-label >
135+ `
136+ : nothing }
91137 `
92138 : html ` < sl-spinner slot ="prefix "> </ sl-spinner > ` }
93- ${ this . browserProfiles ?. map (
94- ( profile ) => html `
95- < sl-option value =${ profile . id } >
96- ${ profile . name }
97- < div slot ="suffix ">
98- < btrix-format-date
99- class ="text-xs "
100- .date =${ profile . modified || profile . created }
101- dateStyle ="medium"
102- > </ btrix-format-date >
139+ ${ browserProfiles ?. items . map (
140+ ( profile , i ) => html `
141+ < sl-option
142+ value =${ profile . id }
143+ class =${ clsx (
144+ tw `part-[base]:flex-wrap` ,
145+ tw `part-[prefix]:order-2` ,
146+ tw `part-[label]:order-1 part-[label]:basis-1/2 part-[label]:overflow-hidden` ,
147+ tw `part-[suffix]:order-3 part-[suffix]:basis-full part-[suffix]:overflow-hidden` ,
148+ i && tw `border-t` ,
149+ ) }
150+ >
151+ < span class ="font-medium "> ${ profile . name } </ span >
152+ < span
153+ class ="whitespace-nowrap text-xs text-neutral-500 "
154+ slot ="prefix "
155+ >
156+ ${ this . localize . relativeDate (
157+ profile . modified || profile . created ,
158+ { capitalize : true } ,
159+ ) }
160+ </ span >
161+ < div
162+ slot ="suffix "
163+ class ="flex w-full items-center justify-between gap-1.5 overflow-hidden pl-2.5 pt-0.5 "
164+ >
165+ < btrix-code
166+ class ="w-0 flex-1 text-xs "
167+ language ="url "
168+ value =${ profile . origins [ 0 ] }
169+ noWrap
170+ truncate
171+ > </ btrix-code >
172+ ${ when (
173+ profile . proxyId ,
174+ ( proxyId ) => html `
175+ < btrix-proxy-badge proxyId =${ proxyId } > </ btrix-proxy-badge >
176+ ` ,
177+ ) }
103178 </ div >
104179 </ sl-option >
105180 ` ,
106181 ) }
107- ${ this . browserProfiles && ! this . browserProfiles . length
182+ ${ browserProfiles && ! browserProfiles . total
108183 ? this . renderNoProfiles ( )
109184 : "" }
110185 < div slot ="help-text " class ="flex justify-between ">
111- ${ this . selectedProfile
186+ ${ selectedProfile
112187 ? html `
113188 < button
114189 class ="text-blue-500 transition-colors duration-fast hover:text-blue-600 "
@@ -119,13 +194,12 @@ export class SelectBrowserProfile extends BtrixElement {
119194 < span >
120195 ${ msg ( "Last saved" ) }
121196 ${ this . localize . relativeDate (
122- this . selectedProfile . modified ||
123- this . selectedProfile . created ,
197+ selectedProfile . modified || selectedProfile . created ,
124198 { capitalize : true } ,
125199 ) }
126200 </ span >
127201 `
128- : this . browserProfiles
202+ : browserProfiles
129203 ? html `
130204 < btrix-link
131205 class ="ml-auto "
@@ -140,7 +214,9 @@ export class SelectBrowserProfile extends BtrixElement {
140214 </ div >
141215 </ sl-select >
142216
143- ${ this . browserProfiles ?. length ? this . renderSelectedProfileInfo ( ) : "" }
217+ ${ browserProfiles || selectedProfile
218+ ? this . renderSelectedProfileInfo ( )
219+ : "" }
144220 ` ;
145221 }
146222
@@ -285,9 +361,8 @@ export class SelectBrowserProfile extends BtrixElement {
285361 }
286362
287363 private async onChange ( e : SlChangeEvent ) {
288- this . selectedProfile = this . browserProfiles ?. find (
289- ( { id } ) => id === ( e . target as SlSelect | null ) ?. value ,
290- ) ;
364+ const profileId = ( e . target as SlSelect | null ) ?. value as string ;
365+ this . selectedProfile = this . findProfileById ( profileId ) ;
291366
292367 await this . updateComplete ;
293368
@@ -300,43 +375,30 @@ export class SelectBrowserProfile extends BtrixElement {
300375 ) ;
301376 }
302377
303- private async updateSelectedProfile ( ) {
304- await this . fetchBrowserProfiles ( ) ;
305- await this . updateComplete ;
306-
307- if ( this . profileId && ! this . selectedProfile ) {
308- this . selectedProfile = this . browserProfiles ?. find (
309- ( { id } ) => id === this . profileId ,
310- ) ;
311- }
312- }
313-
314- /**
315- * Fetch browser profiles and update internal state
316- */
317- private async fetchBrowserProfiles ( ) : Promise < void > {
318- try {
319- const data = await this . getProfiles ( ) ;
320-
321- this . browserProfiles = orderBy ( [ "name" , "modified" ] ) ( [ "asc" , "desc" ] ) (
322- data ,
323- ) as Profile [ ] ;
324- } catch ( e ) {
325- this . notify . toast ( {
326- message : msg ( "Sorry, couldn't retrieve browser profiles at this time." ) ,
327- variant : "danger" ,
328- icon : "exclamation-octagon" ,
329- id : "browser-profile-status" ,
330- } ) ;
331- }
332- }
378+ private async getProfiles (
379+ params : {
380+ userid ?: string ;
381+ tags ?: string [ ] ;
382+ tagMatch ?: string ;
383+ } & APIPaginationQuery &
384+ APISortQuery ,
385+ signal : AbortSignal ,
386+ ) {
387+ const query = queryString . stringify (
388+ {
389+ ...params ,
390+ } ,
391+ {
392+ arrayFormat : "none" , // For tags
393+ } ,
394+ ) ;
333395
334- private async getProfiles ( ) {
335396 const data = await this . api . fetch < APIPaginatedList < Profile > > (
336- `/orgs/${ this . orgId } /profiles` ,
397+ `/orgs/${ this . orgId } /profiles?${ query } ` ,
398+ { signal } ,
337399 ) ;
338400
339- return data . items ;
401+ return data ;
340402 }
341403
342404 /**
0 commit comments