Compare commits
	
		
			38 Commits
		
	
	
		
			version/20
			...
			web/reques
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c8be337414 | |||
| 5c85c2c9e6 | |||
| 8248163958 | |||
| 9acebec1f6 | |||
| 2a96900dc7 | |||
| ca42506fa0 | |||
| 34de6bfd3a | |||
| 2d94b16411 | |||
| 98503f6009 | |||
| ac4ba5d9e2 | |||
| f19ed14bf8 | |||
| 085debf170 | |||
| cacdf64408 | |||
| 23665d173f | |||
| 272fdc516b | |||
| b08dcc2289 | |||
| c84be1d961 | |||
| 875fc5c735 | |||
| 66cefcc918 | |||
| 5d4c38032f | |||
| 7123b2c57b | |||
| fc00bdee63 | |||
| a056703da0 | |||
| 3f9502072d | |||
| 2d254d6a7e | |||
| a7e3dca917 | |||
| 5d8408287f | |||
| 30beca9118 | |||
| 8946b81dbd | |||
| db96e1a901 | |||
| 8b4e0361c4 | |||
| 22cb5b7379 | |||
| 2d0117d096 | |||
| 035bda4eac | |||
| 50906214e5 | |||
| e505f274b6 | |||
| fe52f44dca | |||
| 3146e5a50f | 
							
								
								
									
										2443
									
								
								tests/wdio/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2443
									
								
								tests/wdio/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -14,8 +14,8 @@ | |||||||
|         "build": "run-s build-locales esbuild:build", |         "build": "run-s build-locales esbuild:build", | ||||||
|         "build-proxy": "run-s build-locales esbuild:build-proxy", |         "build-proxy": "run-s build-locales esbuild:build-proxy", | ||||||
|         "watch": "run-s build-locales esbuild:watch", |         "watch": "run-s build-locales esbuild:watch", | ||||||
|         "lint": "cross-env NODE_OPTIONS='--max_old_space_size=16384' eslint . --max-warnings 0 --fix", |         "lint": "cross-env NODE_OPTIONS='--max_old_space_size=65536' eslint . --max-warnings 0 --fix", | ||||||
|         "lint:precommit": "cross-env NODE_OPTIONS='--max_old_space_size=16384' node scripts/eslint-precommit.mjs", |         "lint:precommit": "cross-env NODE_OPTIONS='--max_old_space_size=65536' node scripts/eslint-precommit.mjs", | ||||||
|         "lint:spelling": "node scripts/check-spelling.mjs", |         "lint:spelling": "node scripts/check-spelling.mjs", | ||||||
|         "lit-analyse": "lit-analyzer src", |         "lit-analyse": "lit-analyzer src", | ||||||
|         "precommit": "npm-run-all --parallel tsc lit-analyse lint:spelling --sequential lint:precommit prettier", |         "precommit": "npm-run-all --parallel tsc lit-analyse lint:spelling --sequential lint:precommit prettier", | ||||||
|  | |||||||
| @ -12,4 +12,8 @@ export const authentikEnterpriseContext = createContext<LicenseSummary>( | |||||||
|  |  | ||||||
| export const authentikBrandContext = createContext<CurrentBrand>(Symbol("authentik-brand-context")); | export const authentikBrandContext = createContext<CurrentBrand>(Symbol("authentik-brand-context")); | ||||||
|  |  | ||||||
|  | export const authentikLocalStoreContext = createContext<unknown>( | ||||||
|  |     Symbol("authentik-local-store-context"), | ||||||
|  | ); | ||||||
|  |  | ||||||
| export default authentikConfigContext; | export default authentikConfigContext; | ||||||
|  | |||||||
							
								
								
									
										31
									
								
								web/src/elements/utils/tryCatch.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								web/src/elements/utils/tryCatch.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | |||||||
|  | type TryFn<T> = () => T; | ||||||
|  | type CatchFn<T> = (error: unknown) => T; | ||||||
|  |  | ||||||
|  | type TryCatchArgs<T> = { | ||||||
|  |     tryFn: TryFn<T>; | ||||||
|  |     catchFn?: CatchFn<T>; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||||
|  | const isTryCatchArgs = <T>(t: any): t is TryCatchArgs<T> => | ||||||
|  |     typeof t === "object" && "tryFn" in t && "catchFn" in t; | ||||||
|  |  | ||||||
|  | export function tryCatch<T>({ tryFn, catchFn }: TryCatchArgs<T>): T; | ||||||
|  | export function tryCatch<T>(tryFn: TryFn<T>): T; | ||||||
|  | export function tryCatch<T>(tryFn: TryFn<T>, catchFn: CatchFn<T>): T; | ||||||
|  | export function tryCatch<T>(tryFn: TryFn<T> | TryCatchArgs<T>, catchFn?: CatchFn<T>): T { | ||||||
|  |     if (isTryCatchArgs(tryFn)) { | ||||||
|  |         catchFn = tryFn.catchFn; | ||||||
|  |         tryFn = tryFn.tryFn; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (catchFn === undefined) { | ||||||
|  |         catchFn = () => null as T; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |         return tryFn(); | ||||||
|  |     } catch (error) { | ||||||
|  |         return catchFn(error); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										95
									
								
								web/src/user/LibraryPage/ApplicationCards.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								web/src/user/LibraryPage/ApplicationCards.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,95 @@ | |||||||
|  | import { LayoutType } from "@goauthentik/common/ui/config"; | ||||||
|  | import { AKElement } from "@goauthentik/elements/Base"; | ||||||
|  |  | ||||||
|  | import { css, html } from "lit"; | ||||||
|  | import { customElement, property } from "lit/decorators.js"; | ||||||
|  | import { ifDefined } from "lit/directives/if-defined.js"; | ||||||
|  |  | ||||||
|  | import PFContent from "@patternfly/patternfly/components/Content/content.css"; | ||||||
|  | import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css"; | ||||||
|  | import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; | ||||||
|  | import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||||
|  |  | ||||||
|  | import type { Application } from "@goauthentik/api"; | ||||||
|  |  | ||||||
|  | import type { AppGroupEntry, AppGroupList } from "./types"; | ||||||
|  |  | ||||||
|  | type Pair = [string, string]; | ||||||
|  |  | ||||||
|  | // prettier-ignore | ||||||
|  | const LAYOUTS = new Map<string, [string, string]>([ | ||||||
|  |     [ | ||||||
|  |         "row", | ||||||
|  |         ["pf-m-12-col", "pf-m-all-6-col-on-sm pf-m-all-4-col-on-md pf-m-all-5-col-on-lg pf-m-all-2-col-on-xl"]], | ||||||
|  |     [ | ||||||
|  |         "2-column", | ||||||
|  |         ["pf-m-6-col", "pf-m-all-12-col-on-sm pf-m-all-12-col-on-md pf-m-all-4-col-on-lg pf-m-all-4-col-on-xl"], | ||||||
|  |     ], | ||||||
|  |     [ | ||||||
|  |         "3-column", | ||||||
|  |         ["pf-m-4-col", "pf-m-all-12-col-on-sm pf-m-all-12-col-on-md pf-m-all-6-col-on-lg pf-m-all-6-col-on-xl"], | ||||||
|  |     ], | ||||||
|  | ]); | ||||||
|  |  | ||||||
|  | @customElement("ak-library-application-cards") | ||||||
|  | export class LibraryPageApplicationCards extends AKElement { | ||||||
|  |     static get styles() { | ||||||
|  |         return [ | ||||||
|  |             PFBase, | ||||||
|  |             PFEmptyState, | ||||||
|  |             PFContent, | ||||||
|  |             PFGrid, | ||||||
|  |             css` | ||||||
|  |                 .app-group-header { | ||||||
|  |                     margin-bottom: 1em; | ||||||
|  |                     margin-top: 1.2em; | ||||||
|  |                 } | ||||||
|  |             `, | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @property({ attribute: true }) | ||||||
|  |     layout = "row" as LayoutType; | ||||||
|  |  | ||||||
|  |     @property({ attribute: true }) | ||||||
|  |     background: string | undefined = undefined; | ||||||
|  |  | ||||||
|  |     @property({ attribute: true }) | ||||||
|  |     selected = ""; | ||||||
|  |  | ||||||
|  |     @property({ attribute: false }) | ||||||
|  |     apps: AppGroupList = []; | ||||||
|  |  | ||||||
|  |     get currentLayout(): Pair { | ||||||
|  |         const layout = LAYOUTS.get(this.layout); | ||||||
|  |         if (!layout) { | ||||||
|  |             console.warn(`Unrecognized layout: ${this.layout || "-undefined-"}`); | ||||||
|  |             return LAYOUTS.get("row") as Pair; | ||||||
|  |         } | ||||||
|  |         return layout; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     render() { | ||||||
|  |         const [groupClass, groupGrid] = this.currentLayout; | ||||||
|  |  | ||||||
|  |         return html`<div class="pf-l-grid pf-m-gutter"> | ||||||
|  |             ${this.apps.map(([group, apps]: AppGroupEntry) => { | ||||||
|  |                 return html`<div class="pf-l-grid__item ${groupClass}"> | ||||||
|  |                     <div class="pf-c-content app-group-header"> | ||||||
|  |                         <h2>${group}</h2> | ||||||
|  |                     </div> | ||||||
|  |                     <div class="pf-l-grid pf-m-gutter ${groupGrid}"> | ||||||
|  |                         ${apps.map((app: Application) => { | ||||||
|  |                             return html`<ak-library-app | ||||||
|  |                                 class="pf-l-grid__item" | ||||||
|  |                                 .application=${app} | ||||||
|  |                                 background=${ifDefined(this.background)} | ||||||
|  |                                 ?selected=${app.slug === this.selected} | ||||||
|  |                             ></ak-library-app>`; | ||||||
|  |                         })} | ||||||
|  |                     </div> | ||||||
|  |                 </div> `; | ||||||
|  |             })} | ||||||
|  |         </div>`; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,48 +1,34 @@ | |||||||
|  | import { PFSize } from "@goauthentik/common/enums.js"; | ||||||
| import { LayoutType } from "@goauthentik/common/ui/config"; | import { LayoutType } from "@goauthentik/common/ui/config"; | ||||||
| import { AKElement } from "@goauthentik/elements/Base"; | import { AKElement, rootInterface } from "@goauthentik/elements/Base"; | ||||||
|  | import { UserInterface } from "@goauthentik/user/UserInterface"; | ||||||
|  |  | ||||||
| import { css, html } from "lit"; | import { msg } from "@lit/localize"; | ||||||
|  | import { css, html, nothing } from "lit"; | ||||||
| import { customElement, property } from "lit/decorators.js"; | import { customElement, property } from "lit/decorators.js"; | ||||||
|  | import { classMap } from "lit/directives/class-map.js"; | ||||||
| import { ifDefined } from "lit/directives/if-defined.js"; | import { ifDefined } from "lit/directives/if-defined.js"; | ||||||
|  |  | ||||||
| import PFContent from "@patternfly/patternfly/components/Content/content.css"; | import PFButton from "@patternfly/patternfly/components/Button/button.css"; | ||||||
| import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css"; | import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css"; | ||||||
| import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; | import PFTable from "@patternfly/patternfly/components/Table/table.css"; | ||||||
| import PFBase from "@patternfly/patternfly/patternfly-base.css"; | import PFBase from "@patternfly/patternfly/patternfly-base.css"; | ||||||
|  |  | ||||||
| import type { Application } from "@goauthentik/api"; | import type { Application } from "@goauthentik/api"; | ||||||
|  |  | ||||||
| import type { AppGroupEntry, AppGroupList } from "./types"; | import type { AppGroupEntry, AppGroupList } from "./types"; | ||||||
|  |  | ||||||
| type Pair = [string, string]; |  | ||||||
|  |  | ||||||
| // prettier-ignore |  | ||||||
| const LAYOUTS = new Map<string, [string, string]>([ |  | ||||||
|     [ |  | ||||||
|         "row", |  | ||||||
|         ["pf-m-12-col", "pf-m-all-6-col-on-sm pf-m-all-4-col-on-md pf-m-all-5-col-on-lg pf-m-all-2-col-on-xl"]], |  | ||||||
|     [ |  | ||||||
|         "2-column", |  | ||||||
|         ["pf-m-6-col", "pf-m-all-12-col-on-sm pf-m-all-12-col-on-md pf-m-all-4-col-on-lg pf-m-all-4-col-on-xl"], |  | ||||||
|     ], |  | ||||||
|     [ |  | ||||||
|         "3-column", |  | ||||||
|         ["pf-m-4-col", "pf-m-all-12-col-on-sm pf-m-all-12-col-on-md pf-m-all-6-col-on-lg pf-m-all-6-col-on-xl"], |  | ||||||
|     ], |  | ||||||
| ]); |  | ||||||
|  |  | ||||||
| @customElement("ak-library-application-list") | @customElement("ak-library-application-list") | ||||||
| export class LibraryPageApplicationList extends AKElement { | export class LibraryPageApplicationList extends AKElement { | ||||||
|     static get styles() { |     static get styles() { | ||||||
|         return [ |         return [ | ||||||
|             PFBase, |             PFBase, | ||||||
|  |             PFTable, | ||||||
|  |             PFButton, | ||||||
|             PFEmptyState, |             PFEmptyState, | ||||||
|             PFContent, |  | ||||||
|             PFGrid, |  | ||||||
|             css` |             css` | ||||||
|                 .app-group-header { |                 .app-row a { | ||||||
|                     margin-bottom: 1em; |                     font-weight: bold; | ||||||
|                     margin-top: 1.2em; |  | ||||||
|                 } |                 } | ||||||
|             `, |             `, | ||||||
|         ]; |         ]; | ||||||
| @ -60,36 +46,110 @@ export class LibraryPageApplicationList extends AKElement { | |||||||
|     @property({ attribute: false }) |     @property({ attribute: false }) | ||||||
|     apps: AppGroupList = []; |     apps: AppGroupList = []; | ||||||
|  |  | ||||||
|     get currentLayout(): Pair { |     expanded = new Set<string>(); | ||||||
|         const layout = LAYOUTS.get(this.layout); |  | ||||||
|         if (!layout) { |  | ||||||
|             console.warn(`Unrecognized layout: ${this.layout || "-undefined-"}`); |  | ||||||
|             return LAYOUTS.get("row") as Pair; |  | ||||||
|         } |  | ||||||
|         return layout; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     render() { |     render() { | ||||||
|         const [groupClass, groupGrid] = this.currentLayout; |         const me = rootInterface<UserInterface>()?.me; | ||||||
|  |         const canEdit = | ||||||
|  |             rootInterface()?.uiConfig?.enabledFeatures.applicationEdit && me?.user.isSuperuser; | ||||||
|  |  | ||||||
|         return html`<div class="pf-l-grid pf-m-gutter"> |         const toggleExpansion = (pk: string) => { | ||||||
|             ${this.apps.map(([group, apps]: AppGroupEntry) => { |             if (this.expanded.has(pk)) { | ||||||
|                 return html`<div class="pf-l-grid__item ${groupClass}"> |                 this.expanded.delete(pk); | ||||||
|                     <div class="pf-c-content app-group-header"> |             } else { | ||||||
|                         <h2>${group}</h2> |                 this.expanded.add(pk); | ||||||
|                     </div> |             } | ||||||
|                     <div class="pf-l-grid pf-m-gutter ${groupGrid}"> |             this.requestUpdate(); | ||||||
|                         ${apps.map((app: Application) => { |         }; | ||||||
|                             return html`<ak-library-app |  | ||||||
|                                 class="pf-l-grid__item" |         const expandedClass = (pk: string) => ({ | ||||||
|                                 .application=${app} |             "pf-m-expanded": this.expanded.has(pk), | ||||||
|                                 background=${ifDefined(this.background)} |         }); | ||||||
|                                 ?selected=${app.slug === this.selected} |  | ||||||
|                             ></ak-library-app>`; |         const renderExpansionCell = (app: Application) => | ||||||
|                         })} |             app.metaDescription | ||||||
|                     </div> |                 ? html`<td class="pf-c-table__toggle" role="cell"> | ||||||
|                 </div> `; |                       <button | ||||||
|             })} |                           class="pf-c-button pf-m-plain ${classMap(expandedClass(app.pk))}" | ||||||
|         </div>`; |                           @click=${() => toggleExpansion(app.pk)} | ||||||
|  |                       > | ||||||
|  |                           <div class="pf-c-table__toggle-icon"> | ||||||
|  |                                <i class="fas fa-angle-down" aria-hidden="true"></i>  | ||||||
|  |                           </div> | ||||||
|  |                       </button> | ||||||
|  |                   </td>` | ||||||
|  |                 : nothing; | ||||||
|  |  | ||||||
|  |         const renderAppIcon = (app: Application) => | ||||||
|  |             html`<a | ||||||
|  |                 href="${ifDefined(app.launchUrl ?? "")}" | ||||||
|  |                 target="${ifDefined(app.openInNewTab ? "_blank" : undefined)}" | ||||||
|  |             > | ||||||
|  |                 <ak-app-icon size=${PFSize.Small} .app=${app}></ak-app-icon> | ||||||
|  |             </a>`; | ||||||
|  |  | ||||||
|  |         const renderAppUrl = (app: Application) => | ||||||
|  |             app.launchUrl === "goauthentik.io://providers/rac/launch" | ||||||
|  |                 ? html`<ak-library-rac-endpoint-launch .app=${app}> | ||||||
|  |                       <a slot="trigger"> ${app.name} </a> | ||||||
|  |                   </ak-library-rac-endpoint-launch>` | ||||||
|  |                 : html`<a | ||||||
|  |                       href="${ifDefined(app.launchUrl ?? "")}" | ||||||
|  |                       target="${ifDefined(app.openInNewTab ? "_blank" : undefined)}" | ||||||
|  |                       >${app.name}</a | ||||||
|  |                   >`; | ||||||
|  |  | ||||||
|  |         const renderAppDescription = (app: Application) => | ||||||
|  |             app.metaDescription | ||||||
|  |                 ? html` <tr | ||||||
|  |                       class="pf-c-table__expandable-row ${classMap(expandedClass(app.pk))}" | ||||||
|  |                       role="row" | ||||||
|  |                   > | ||||||
|  |                       <td></td> | ||||||
|  |                       <td></td> | ||||||
|  |                       <td colspan="3">${app.metaDescription}</td> | ||||||
|  |                   </tr>` | ||||||
|  |                 : nothing; | ||||||
|  |  | ||||||
|  |         const renderGroup = ([group, apps]: AppGroupEntry) => html` | ||||||
|  |             ${group | ||||||
|  |                 ? html`<tr> | ||||||
|  |                       <td colspan="5"><h2>${group}</h2></td> | ||||||
|  |                   </tr>` | ||||||
|  |                 : nothing} | ||||||
|  |             ${apps.map( | ||||||
|  |                 (app: Application) => | ||||||
|  |                     html`<tr> | ||||||
|  |                             <td>${renderExpansionCell(app)}</td> | ||||||
|  |                             <td>${renderAppIcon(app)}</td> | ||||||
|  |                             <td class="app-row">${renderAppUrl(app)}</td> | ||||||
|  |                             <td>${app.metaPublisher ?? ""}</td> | ||||||
|  |                             <td> | ||||||
|  |                                 <a | ||||||
|  |                                     class="pf-c-button pf-m-control pf-m-small pf-m-block" | ||||||
|  |                                     href="/if/admin/#/core/applications/${app?.slug}" | ||||||
|  |                                 > | ||||||
|  |                                     <i class="fas fa-edit"></i> ${msg("Edit")} | ||||||
|  |                                 </a> | ||||||
|  |                             </td> | ||||||
|  |                         </tr> | ||||||
|  |                         ${this.expanded.has(app.pk) ? renderAppDescription(app) : nothing} `, | ||||||
|  |             )} | ||||||
|  |         `; | ||||||
|  |  | ||||||
|  |         return html`<table class="pf-c-table pf-m-compact pf-m-grid-sm pf-m-expandable"> | ||||||
|  |             <thead> | ||||||
|  |                 <tr role="row"> | ||||||
|  |                     <th></th> | ||||||
|  |                     <th></th> | ||||||
|  |                     <th>${msg("Application")}</th> | ||||||
|  |                     <th>${msg("Publisher")}</th> | ||||||
|  |                     ${canEdit ? html`<th>${msg("Edit")}</th>` : nothing} | ||||||
|  |                 </tr> | ||||||
|  |             </thead> | ||||||
|  |             <tbody> | ||||||
|  |                 ${this.apps.map(renderGroup)} | ||||||
|  |             </tbody> | ||||||
|  |         </table> `; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -34,6 +34,30 @@ export const styles = [PFBase, PFDisplay, PFEmptyState, PFPage, PFContent].conca | |||||||
|     .pf-c-page__main-section { |     .pf-c-page__main-section { | ||||||
|         background-color: transparent; |         background-color: transparent; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #library-page-title { | ||||||
|  |         display: flex; | ||||||
|  |         flex-direction: row; | ||||||
|  |         gap: 0.5rem; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #library-page-title h1 { | ||||||
|  |         padding-right: 0.5rem; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #library-page-title i { | ||||||
|  |         display: inline-block; | ||||||
|  |         padding: 0.25rem; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #library-page-title i[checked] { | ||||||
|  |         border: 3px solid var(--pf-global--BorderColor--100); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #library-page-title a, | ||||||
|  |     #library-page-title i { | ||||||
|  |         vertical-align: bottom; | ||||||
|  |     } | ||||||
| `); | `); | ||||||
|  |  | ||||||
| export default styles; | export default styles; | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| import { groupBy } from "@goauthentik/common/utils"; | import { groupBy } from "@goauthentik/common/utils"; | ||||||
| import { AKElement } from "@goauthentik/elements/Base"; | import { AKElement } from "@goauthentik/elements/Base"; | ||||||
| import "@goauthentik/elements/EmptyState"; | import "@goauthentik/elements/EmptyState"; | ||||||
|  | import { tryCatch } from "@goauthentik/elements/utils/tryCatch.js"; | ||||||
| import "@goauthentik/user/LibraryApplication"; | import "@goauthentik/user/LibraryApplication"; | ||||||
|  |  | ||||||
| import { msg } from "@lit/localize"; | import { msg } from "@lit/localize"; | ||||||
| @ -12,6 +13,7 @@ import styles from "./LibraryPageImpl.css"; | |||||||
|  |  | ||||||
| import type { Application } from "@goauthentik/api"; | import type { Application } from "@goauthentik/api"; | ||||||
|  |  | ||||||
|  | import "./ApplicationCards"; | ||||||
| import "./ApplicationEmptyState"; | import "./ApplicationEmptyState"; | ||||||
| import "./ApplicationList"; | import "./ApplicationList"; | ||||||
| import "./ApplicationSearch"; | import "./ApplicationSearch"; | ||||||
| @ -20,6 +22,8 @@ import { SEARCH_ITEM_SELECTED, SEARCH_UPDATED } from "./constants"; | |||||||
| import { isCustomEvent, loading } from "./helpers"; | import { isCustomEvent, loading } from "./helpers"; | ||||||
| import type { AppGroupList, PageUIConfig } from "./types"; | import type { AppGroupList, PageUIConfig } from "./types"; | ||||||
|  |  | ||||||
|  | const VIEW_KEY = "ak-library-page-view-preference"; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * List of Applications available |  * List of Applications available | ||||||
|  * |  * | ||||||
| @ -53,6 +57,9 @@ export class LibraryPage extends AKElement { | |||||||
|     @state() |     @state() | ||||||
|     filteredApps: Application[] = []; |     filteredApps: Application[] = []; | ||||||
|  |  | ||||||
|  |     @property() | ||||||
|  |     viewPreference?: string; | ||||||
|  |  | ||||||
|     constructor() { |     constructor() { | ||||||
|         super(); |         super(); | ||||||
|         this.searchUpdated = this.searchUpdated.bind(this); |         this.searchUpdated = this.searchUpdated.bind(this); | ||||||
| @ -66,6 +73,12 @@ export class LibraryPage extends AKElement { | |||||||
|     connectedCallback() { |     connectedCallback() { | ||||||
|         super.connectedCallback(); |         super.connectedCallback(); | ||||||
|         this.filteredApps = this.apps; |         this.filteredApps = this.apps; | ||||||
|  |         this.viewPreference = | ||||||
|  |             this.viewPreference ?? | ||||||
|  |             tryCatch( | ||||||
|  |                 () => window.localStorage.getItem(VIEW_KEY) ?? undefined, | ||||||
|  |                 (e) => "card", | ||||||
|  |             ); | ||||||
|         if (this.filteredApps === undefined) { |         if (this.filteredApps === undefined) { | ||||||
|             throw new Error( |             throw new Error( | ||||||
|                 "Application.results should never be undefined when passed to the Library Page.", |                 "Application.results should never be undefined when passed to the Library Page.", | ||||||
| @ -110,6 +123,13 @@ export class LibraryPage extends AKElement { | |||||||
|         return groupBy(this.filteredApps.filter(appHasLaunchUrl), (app) => app.group || ""); |         return groupBy(this.filteredApps.filter(appHasLaunchUrl), (app) => app.group || ""); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     setView(view: string) { | ||||||
|  |         this.viewPreference = view; | ||||||
|  |         tryCatch(() => { | ||||||
|  |             window.localStorage.setItem(VIEW_KEY, view); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     renderEmptyState() { |     renderEmptyState() { | ||||||
|         return html`<ak-library-application-empty-list |         return html`<ak-library-application-empty-list | ||||||
|             ?isadmin=${this.isAdmin} |             ?isadmin=${this.isAdmin} | ||||||
| @ -122,12 +142,17 @@ export class LibraryPage extends AKElement { | |||||||
|         const layout = this.uiConfig.layout as string; |         const layout = this.uiConfig.layout as string; | ||||||
|         const background = this.uiConfig.background; |         const background = this.uiConfig.background; | ||||||
|  |  | ||||||
|         return html`<ak-library-application-list |         return this.viewPreference === "list" | ||||||
|             layout="${layout}" |             ? html`<ak-library-application-list | ||||||
|             background="${ifDefined(background)}" |                   selected="${ifDefined(selected)}" | ||||||
|             selected="${ifDefined(selected)}" |                   .apps=${apps} | ||||||
|             .apps=${apps} |               ></ak-library-application-list>` | ||||||
|         ></ak-library-application-list>`; |             : html`<ak-library-application-cards | ||||||
|  |                   layout="${layout}" | ||||||
|  |                   background="${ifDefined(background)}" | ||||||
|  |                   selected="${ifDefined(selected)}" | ||||||
|  |                   .apps=${apps} | ||||||
|  |               ></ak-library-application-cards>`; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     renderSearch() { |     renderSearch() { | ||||||
| @ -137,9 +162,15 @@ export class LibraryPage extends AKElement { | |||||||
|     render() { |     render() { | ||||||
|         return html`<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content"> |         return html`<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content"> | ||||||
|             <div class="pf-c-content header"> |             <div class="pf-c-content header"> | ||||||
|                 <h1 role="heading" aria-level="1" id="library-page-title"> |                 <div id="library-page-title"> | ||||||
|                     ${msg("My applications")} |                     <h1 role="heading" aria-level="1">${msg("My applications")}</h1> | ||||||
|                 </h1> |                     <a id="card-view" @click=${() => this.setView("card")} | ||||||
|  |                         ><i ?checked=${this.viewPreference === "card"} class="fas fa-th-large"></i | ||||||
|  |                     ></a> | ||||||
|  |                     <a id="list-view" @click=${() => this.setView("list")} | ||||||
|  |                         ><i ?checked=${this.viewPreference === "list"} class="fas fa-list"></i | ||||||
|  |                     ></a> | ||||||
|  |                 </div> | ||||||
|                 ${this.uiConfig.searchEnabled ? this.renderSearch() : html``} |                 ${this.uiConfig.searchEnabled ? this.renderSearch() : html``} | ||||||
|             </div> |             </div> | ||||||
|             <section class="pf-c-page__main-section"> |             <section class="pf-c-page__main-section"> | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	