go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/common/components/lit_env_provider.tsx (about) 1 // Copyright 2023 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 import { html } from 'lit'; 16 import { customElement } from 'lit/decorators.js'; 17 import { makeObservable, observable, reaction } from 'mobx'; 18 import { ReactNode, useEffect, useRef, useState } from 'react'; 19 20 import { ANONYMOUS_IDENTITY } from '@/common/api/auth_state'; 21 import { MAY_REQUIRE_SIGNIN, OPTIONAL_RESOURCE } from '@/common/common_tags'; 22 import { provideStore, StoreInstance, useStore } from '@/common/store'; 23 import { MobxExtLitElement } from '@/generic_libs/components/lit_mobx_ext'; 24 import { 25 errorHandler, 26 handleLocally, 27 } from '@/generic_libs/tools/error_handler'; 28 import { provider } from '@/generic_libs/tools/lit_context'; 29 import { 30 ProgressiveNotifier, 31 provideNotifier, 32 } from '@/generic_libs/tools/observer_element'; 33 import { hasTags } from '@/generic_libs/tools/tag'; 34 35 function redirectToLogin(err: ErrorEvent, ele: LitEnvProviderElement) { 36 if ( 37 ele.store.authState.identity === ANONYMOUS_IDENTITY && 38 hasTags(err.error, MAY_REQUIRE_SIGNIN) && 39 !hasTags(err.error, OPTIONAL_RESOURCE) 40 ) { 41 window.location.href = `/ui/login?${new URLSearchParams([ 42 ['redirect', location.pathname + location.search + location.hash], 43 ])}`; 44 return false; 45 } 46 return handleLocally(err, ele); 47 } 48 49 /** 50 * Provides context and error handling to lit components. 51 */ 52 @customElement('milo-lit-env-provider') 53 @errorHandler(redirectToLogin) 54 @provider 55 export class LitEnvProviderElement extends MobxExtLitElement { 56 @observable.ref 57 @provideStore({ global: true }) 58 store!: StoreInstance; 59 60 @provideNotifier({ global: true }) readonly notifier = 61 new ProgressiveNotifier({ 62 // Ensures that everything above the current scroll view is rendered. 63 // This reduces page shifting due to incorrect height estimate. 64 rootMargin: '1000000px 0px 0px 0px', 65 }); 66 67 constructor() { 68 super(); 69 makeObservable(this); 70 } 71 72 connectedCallback() { 73 super.connectedCallback(); 74 75 this.addDisposer( 76 reaction( 77 () => this.store, 78 (store) => { 79 // Emulate @property() update. 80 this.updated(new Map([['store', store]])); 81 }, 82 { fireImmediately: true }, 83 ), 84 ); 85 } 86 87 protected render() { 88 return html`<slot></slot>`; 89 } 90 } 91 92 declare global { 93 // eslint-disable-next-line @typescript-eslint/no-namespace 94 namespace JSX { 95 interface IntrinsicElements { 96 'milo-lit-env-provider': { 97 children: ReactNode; 98 ref: React.MutableRefObject<LitEnvProviderElement | null>; 99 }; 100 } 101 } 102 } 103 104 export interface LitEnvProviderProps { 105 readonly children: React.ReactNode; 106 } 107 108 export function LitEnvProvider({ children }: LitEnvProviderProps) { 109 const store = useStore(); 110 const envProviderRef = useRef<LitEnvProviderElement | null>(null); 111 const [initialized, setInitialized] = useState(false); 112 113 // Keep React2LitAdaptorElement.store up to date. 114 useEffect(() => { 115 // This will never happen. But useful for type checking. 116 if (!envProviderRef.current) { 117 return; 118 } 119 envProviderRef.current.store = store; 120 setInitialized(true); 121 }, [store]); 122 123 return ( 124 <milo-lit-env-provider ref={envProviderRef}> 125 {/* Only render the children after the store is populated. */} 126 {initialized ? children : null} 127 </milo-lit-env-provider> 128 ); 129 }