import { type JSX, PureComponent, type ReactNode } from 'react'
import invariant from 'invariant'
import {
makeLoadScriptUrl,
type LoadScriptUrlOptions,
} from './utils/make-load-script-url.js'
import { isBrowser } from './utils/isbrowser.js'
import { injectScript } from './utils/injectscript.js'
import { preventGoogleFonts } from './utils/prevent-google-fonts.js'
let cleaningUp = false
type LoadScriptState = {
loaded: boolean
}
export type LoadScriptProps = LoadScriptUrlOptions & {
children?: ReactNode | undefined
id: string
nonce?: string | undefined
loadingElement?: ReactNode
onLoad?: () => void
onError?: (error: Error) => void
onUnmount?: () => void
preventGoogleFontsLoading?: boolean
}
export function DefaultLoadingElement(): JSX.Element {
return
{`Loading...`}
}
export const defaultLoadScriptProps = {
id: 'script-loader',
version: 'weekly',
}
class LoadScript extends PureComponent {
public static defaultProps = defaultLoadScriptProps
check: HTMLDivElement | null = null
override state = {
loaded: false,
}
cleanupCallback = (): void => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete window.google.maps
this.injectScript()
}
override componentDidMount(): void {
if (isBrowser) {
if (window.google && window.google.maps && !cleaningUp) {
console.error('google api is already presented')
return
}
this.isCleaningUp()
.then(this.injectScript)
.catch(function error(err) {
console.error('Error at injecting script after cleaning up: ', err)
})
}
}
override componentDidUpdate(prevProps: LoadScriptProps): void {
if (this.props.libraries !== prevProps.libraries) {
console.warn(
'Performance warning! LoadScript has been reloaded unintentionally! You should not pass `libraries` prop as new array. Please keep an array of libraries as static class property for Components and PureComponents, or just a const variable outside of component, or somewhere in config files or ENV variables'
)
}
if (isBrowser && prevProps.language !== this.props.language) {
this.cleanup()
// TODO: refactor to use gDSFP maybe... wait for hooks refactoring.
this.setState(function setLoaded() {
return {
loaded: false,
}
}, this.cleanupCallback)
}
}
override componentWillUnmount(): void {
if (isBrowser) {
this.cleanup()
const timeoutCallback = (): void => {
if (!this.check) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete window.google
cleaningUp = false
}
}
window.setTimeout(timeoutCallback, 1)
if (this.props.onUnmount) {
this.props.onUnmount()
}
}
}
isCleaningUp = async (): Promise => {
function promiseCallback(resolve: () => void): void {
if (!cleaningUp) {
resolve()
} else {
if (isBrowser) {
const timer = window.setInterval(function interval() {
if (!cleaningUp) {
window.clearInterval(timer)
resolve()
}
}, 1)
}
}
return
}
return new Promise(promiseCallback)
}
cleanup = (): void => {
cleaningUp = true
const script = document.getElementById(this.props.id)
if (script && script.parentNode) {
script.parentNode.removeChild(script)
}
Array.prototype.slice
.call(document.getElementsByTagName('script'))
.filter(function filter(script: HTMLScriptElement): boolean {
return (
typeof script.src === 'string' &&
script.src.includes('maps.googleapis')
)
})
.forEach(function forEach(script: HTMLScriptElement): void {
if (script.parentNode) {
script.parentNode.removeChild(script)
}
})
Array.prototype.slice
.call(document.getElementsByTagName('link'))
.filter(function filter(link: HTMLLinkElement): boolean {
return (
link.href ===
'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Google+Sans'
)
})
.forEach(function forEach(link: HTMLLinkElement) {
if (link.parentNode) {
link.parentNode.removeChild(link)
}
})
Array.prototype.slice
.call(document.getElementsByTagName('style'))
.filter(function filter(style: HTMLStyleElement): boolean {
return (
style.innerText !== undefined &&
style.innerText.length > 0 &&
style.innerText.includes('.gm-')
)
})
.forEach(function forEach(style: HTMLStyleElement) {
if (style.parentNode) {
style.parentNode.removeChild(style)
}
})
}
injectScript = (): void => {
if (this.props.preventGoogleFontsLoading) {
preventGoogleFonts()
}
invariant(
!!this.props.id,
'LoadScript requires "id" prop to be a string: %s',
this.props.id
)
const injectScriptOptions = {
id: this.props.id,
nonce: this.props.nonce,
url: makeLoadScriptUrl(this.props),
}
injectScript(injectScriptOptions)
.then(() => {
if (this.props.onLoad) {
this.props.onLoad()
}
this.setState(function setLoaded() {
return {
loaded: true,
}
})
return
})
.catch((err) => {
if (this.props.onError) {
this.props.onError(err)
}
console.error(`
There has been an Error with loading Google Maps API script, please check that you provided correct google API key (${
this.props.googleMapsApiKey || '-'
}) or Client ID (${
this.props.googleMapsClientId || '-'
}) to
Otherwise it is a Network issue.
`)
})
}
getRef = (el: HTMLDivElement | null): void => {
this.check = el
}
override render(): ReactNode {
return (
<>
{this.state.loaded
? this.props.children
: this.props.loadingElement || }
>
)
}
}
export default LoadScript