feat: Disable gsheets api load for airgapped instance (#26484)
Google sheet file picker library should not be loaded for airgapped instance. This PR ensures that.
This commit is contained in:
parent
734b563237
commit
63854284f5
|
|
@ -1,214 +1,257 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<title>Appsmith</title>
|
||||
<style>
|
||||
#loader {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 4px;
|
||||
background: #D7D7D7;
|
||||
transition: all ease-in 0.3s;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
// '' (empty strings), 'false' are falsy
|
||||
// could return either boolean or string based on value
|
||||
const parseConfig = (config) => {
|
||||
if (config.indexOf("__") === 0 || config.indexOf("$") === 0 || config.indexOf("%") === 0)
|
||||
return "";
|
||||
|
||||
const result = config.trim();
|
||||
if (result.toLowerCase() === "false" || result === "") {
|
||||
return false;
|
||||
} else if (result.toLowerCase() === "true") {
|
||||
return true;
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<title>Appsmith</title>
|
||||
<style>
|
||||
#loader {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 4px;
|
||||
background: #d7d7d7;
|
||||
transition: all ease-in 0.3s;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
// '' (empty strings), 'false' are falsy
|
||||
// could return either boolean or string based on value
|
||||
const parseConfig = (config) => {
|
||||
if (
|
||||
config.indexOf("__") === 0 ||
|
||||
config.indexOf("$") === 0 ||
|
||||
config.indexOf("%") === 0
|
||||
)
|
||||
return "";
|
||||
|
||||
return result;
|
||||
};
|
||||
const CLOUD_HOSTING = parseConfig("__APPSMITH_CLOUD_HOSTING__");
|
||||
const ZIPY_KEY = parseConfig("__APPSMITH_ZIPY_SDK_KEY__");
|
||||
</script>
|
||||
<script>
|
||||
window.__APPSMITH_CHUNKS_TO_PRELOAD =
|
||||
<%=
|
||||
(function () {
|
||||
// This code emits URLs to be preloaded as soon as the main bundle loads.
|
||||
// If you code-split away some files and need to preload them as well, here’s how to do that:
|
||||
// 1) Give your import a name (use the `webpackChunkName` comment: `import(/* webpackChunkName: "my-name" */ "./my-file")`)
|
||||
// 2) Add the name into the `chunksToPreload` array below
|
||||
let chunksToPreload = {
|
||||
'edit-mode': [...getChunkUrls("editor"), ...getChunkUrls("global-search")],
|
||||
'view-mode': getChunkUrls("AppViewer")
|
||||
const result = config.trim();
|
||||
if (result.toLowerCase() === "false" || result === "") {
|
||||
return false;
|
||||
} else if (result.toLowerCase() === "true") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
const CLOUD_HOSTING = parseConfig("__APPSMITH_CLOUD_HOSTING__");
|
||||
const ZIPY_KEY = parseConfig("__APPSMITH_ZIPY_SDK_KEY__");
|
||||
</script>
|
||||
<script>
|
||||
window.__APPSMITH_CHUNKS_TO_PRELOAD =
|
||||
<%=
|
||||
(function () {
|
||||
// This code emits URLs to be preloaded as soon as the main bundle loads.
|
||||
// If you code-split away some files and need to preload them as well, here’s how to do that:
|
||||
// 1) Give your import a name (use the `webpackChunkName` comment: `import(/* webpackChunkName: "my-name" */ "./my-file")`)
|
||||
// 2) Add the name into the `chunksToPreload` array below
|
||||
let chunksToPreload = {
|
||||
'edit-mode': [...getChunkUrls("editor"), ...getChunkUrls("global-search")],
|
||||
'view-mode': getChunkUrls("AppViewer")
|
||||
};
|
||||
|
||||
return JSON.stringify(chunksToPreload);
|
||||
return JSON.stringify(chunksToPreload);
|
||||
|
||||
function getChunkUrls(chunkName) {
|
||||
return compilation.namedChunkGroups.get(chunkName).chunks.flatMap(chunk => [...chunk.files]);
|
||||
function getChunkUrls(chunkName) {
|
||||
return compilation.namedChunkGroups.get(chunkName).chunks.flatMap(chunk => [...chunk.files]);
|
||||
}
|
||||
})()
|
||||
%>
|
||||
</script>
|
||||
<script>
|
||||
if (CLOUD_HOSTING && ZIPY_KEY) {
|
||||
const script = document.createElement("script");
|
||||
script.crossOrigin = "anonymous";
|
||||
script.defer = true;
|
||||
script.src = "https://cdn.zipy.ai/sdk/v1.0/zipy.min.umd.js";
|
||||
script.onload = () => {
|
||||
window.zipy && window.zipy.init(ZIPY_KEY);
|
||||
};
|
||||
const head = document.getElementsByTagName("head")[0];
|
||||
head && head.appendChild(script);
|
||||
}
|
||||
})()
|
||||
%>
|
||||
</script>
|
||||
<script>
|
||||
if (CLOUD_HOSTING && ZIPY_KEY) {
|
||||
const script = document.createElement('script');
|
||||
script.crossOrigin = "anonymous";
|
||||
script.defer = true;
|
||||
script.src = "https://cdn.zipy.ai/sdk/v1.0/zipy.min.umd.js";
|
||||
script.onload = () => {
|
||||
window.zipy && window.zipy.init(ZIPY_KEY);
|
||||
}
|
||||
const head = document.getElementsByTagName('head')[0];
|
||||
head && head.appendChild(script);
|
||||
}
|
||||
// This function is triggered on load of google apis javascript library
|
||||
// Even though the script is loaded asynchronously, in case of firefox run on windows
|
||||
// The gapi script is getting loaded even before the last script of index.html
|
||||
// Hence defining this function before loading gapi
|
||||
// For more info: https://github.com/appsmithorg/appsmith/issues/21033
|
||||
gapiLoaded = () => {
|
||||
window.googleAPIsLoaded = true;
|
||||
}
|
||||
onError = () => {
|
||||
window.googleAPIsLoaded = false;
|
||||
};
|
||||
</script>
|
||||
<!-- Adding this Library to access google file picker API in case of limiting google sheet access -->
|
||||
<script async defer id="googleapis" src="https://apis.google.com/js/api.js" onload="gapiLoaded()"
|
||||
onerror="onError()"></script>
|
||||
</head>
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="appsmith-light-theme">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="loader" style="width: 30vw;"></div>
|
||||
<!--
|
||||
<body class="appsmith-light-theme">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="loader" style="width: 30vw"></div>
|
||||
<!--
|
||||
To keep zIndex for tooltips higher than app comments, todo remove when migrating to Tooltip2
|
||||
Currently the className does not apply to the portal root, so we're unable to work with z-indexes based on that
|
||||
-->
|
||||
<div id="header-root"></div>
|
||||
<div id="root"></div>
|
||||
<div id="date-picker-control" style="position: relative; z-index: 1000;"></div>
|
||||
<script type="text/javascript">
|
||||
// Ref: https://github.com/Modernizr/Modernizr/blob/94592f279a410436530c7c06acc42a6e90c20150/feature-detects/storage/localstorage.js
|
||||
const getIsLocalStorageSupported = () => {
|
||||
try {
|
||||
window.localStorage.setItem("test", "testA");
|
||||
window.localStorage.removeItem("test");
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const isLocalStorageSupported = getIsLocalStorageSupported();
|
||||
|
||||
const handleLocalStorageNotSupportedError = () => {
|
||||
console.error("Localstorage storage is not supported on your device.");
|
||||
}
|
||||
|
||||
const localStorageUtil = {
|
||||
getItem: (key) => {
|
||||
if (!isLocalStorageSupported) {
|
||||
handleLocalStorageNotSupportedError();
|
||||
return;
|
||||
<div id="header-root"></div>
|
||||
<div id="root"></div>
|
||||
<div
|
||||
id="date-picker-control"
|
||||
style="position: relative; z-index: 1000"
|
||||
></div>
|
||||
<script type="text/javascript">
|
||||
// Ref: https://github.com/Modernizr/Modernizr/blob/94592f279a410436530c7c06acc42a6e90c20150/feature-detects/storage/localstorage.js
|
||||
const getIsLocalStorageSupported = () => {
|
||||
try {
|
||||
window.localStorage.setItem("test", "testA");
|
||||
window.localStorage.removeItem("test");
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return window.localStorage.getItem(key);
|
||||
},
|
||||
removeItem: (key) => {
|
||||
if (!isLocalStorageSupported) {
|
||||
handleLocalStorageNotSupportedError();
|
||||
return
|
||||
}
|
||||
return window.localStorage.removeItem(key);
|
||||
},
|
||||
setItem: (key, value) => {
|
||||
if (!isLocalStorageSupported) {
|
||||
handleLocalStorageNotSupportedError();
|
||||
return;
|
||||
}
|
||||
return window.localStorage.setItem(key, value);
|
||||
}
|
||||
};
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
document.getElementById("loader").style.width = "50vw";
|
||||
});
|
||||
};
|
||||
const isLocalStorageSupported = getIsLocalStorageSupported();
|
||||
|
||||
const registerPageServiceWorker = () => {
|
||||
if (
|
||||
"serviceWorker" in navigator
|
||||
// Disable the service worker in Cypress tests. We primarily do this to make
|
||||
// the Performance/LinkRelPreload_Spec.js test work (as it collects URLs all network requests,
|
||||
// and service worker requests fail it.) But, anecdotally, disabling the service worker
|
||||
// also seems to make the tests a bit faster, as the network load is lower.
|
||||
&& !window.Cypress
|
||||
) {
|
||||
window.addEventListener("load", function () {
|
||||
navigator.serviceWorker.register("/pageService.js").catch(error => {
|
||||
console.error("Service Worker Registration failed: " + error);
|
||||
const handleLocalStorageNotSupportedError = () => {
|
||||
console.error("Localstorage storage is not supported on your device.");
|
||||
};
|
||||
|
||||
const localStorageUtil = {
|
||||
getItem: (key) => {
|
||||
if (!isLocalStorageSupported) {
|
||||
handleLocalStorageNotSupportedError();
|
||||
return;
|
||||
}
|
||||
return window.localStorage.getItem(key);
|
||||
},
|
||||
removeItem: (key) => {
|
||||
if (!isLocalStorageSupported) {
|
||||
handleLocalStorageNotSupportedError();
|
||||
return;
|
||||
}
|
||||
return window.localStorage.removeItem(key);
|
||||
},
|
||||
setItem: (key, value) => {
|
||||
if (!isLocalStorageSupported) {
|
||||
handleLocalStorageNotSupportedError();
|
||||
return;
|
||||
}
|
||||
return window.localStorage.setItem(key, value);
|
||||
},
|
||||
};
|
||||
window.addEventListener("DOMContentLoaded", (event) => {
|
||||
document.getElementById("loader").style.width = "50vw";
|
||||
});
|
||||
|
||||
const registerPageServiceWorker = () => {
|
||||
if (
|
||||
"serviceWorker" in navigator &&
|
||||
// Disable the service worker in Cypress tests. We primarily do this to make
|
||||
// the Performance/LinkRelPreload_Spec.js test work (as it collects URLs all network requests,
|
||||
// and service worker requests fail it.) But, anecdotally, disabling the service worker
|
||||
// also seems to make the tests a bit faster, as the network load is lower.
|
||||
!window.Cypress
|
||||
) {
|
||||
window.addEventListener("load", function () {
|
||||
navigator.serviceWorker
|
||||
.register("/pageService.js")
|
||||
.catch((error) => {
|
||||
console.error("Service Worker Registration failed: " + error);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
registerPageServiceWorker();
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
const LOG_LEVELS = ["debug", "error"];
|
||||
const CONFIG_LOG_LEVEL_INDEX = LOG_LEVELS.indexOf(
|
||||
parseConfig("__APPSMITH_CLIENT_LOG_LEVEL__"),
|
||||
);
|
||||
|
||||
const INTERCOM_APP_ID =
|
||||
parseConfig("%REACT_APP_INTERCOM_APP_ID%") ||
|
||||
parseConfig("__APPSMITH_INTERCOM_APP_ID__");
|
||||
const DISABLE_INTERCOM = parseConfig("__APPSMITH_DISABLE_INTERCOM__");
|
||||
|
||||
// Initialize the Intercom library
|
||||
if (INTERCOM_APP_ID.length && !DISABLE_INTERCOM) {
|
||||
(function () {
|
||||
var w = window;
|
||||
var ic = w.Intercom;
|
||||
if (typeof ic === "function") {
|
||||
ic("reattach_activator");
|
||||
ic("update", w.intercomSettings);
|
||||
} else {
|
||||
var d = document;
|
||||
var i = function () {
|
||||
i.c(arguments);
|
||||
};
|
||||
i.q = [];
|
||||
i.c = function (args) {
|
||||
i.q.push(args);
|
||||
};
|
||||
w.Intercom = i;
|
||||
var l = function () {
|
||||
var s = d.createElement("script");
|
||||
s.type = "text/javascript";
|
||||
s.async = true;
|
||||
s.src = "https://widget.intercom.io/widget/" + INTERCOM_APP_ID;
|
||||
var x = d.getElementsByTagName("script")[0];
|
||||
x.parentNode.insertBefore(s, x);
|
||||
};
|
||||
if (document.readyState === "complete") {
|
||||
l();
|
||||
} else if (w.attachEvent) {
|
||||
w.attachEvent("onload", l);
|
||||
} else {
|
||||
w.addEventListener("load", l, false);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
};
|
||||
registerPageServiceWorker();
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
const LOG_LEVELS = ["debug", "error"];
|
||||
const CONFIG_LOG_LEVEL_INDEX = LOG_LEVELS.indexOf(parseConfig("__APPSMITH_CLIENT_LOG_LEVEL__"));
|
||||
|
||||
const INTERCOM_APP_ID = parseConfig("%REACT_APP_INTERCOM_APP_ID%") || parseConfig("__APPSMITH_INTERCOM_APP_ID__");
|
||||
const DISABLE_INTERCOM = parseConfig("__APPSMITH_DISABLE_INTERCOM__");
|
||||
|
||||
// Initialize the Intercom library
|
||||
if (INTERCOM_APP_ID.length && !DISABLE_INTERCOM) {
|
||||
(function () { var w = window; var ic = w.Intercom; if (typeof ic === "function") { ic('reattach_activator'); ic('update', w.intercomSettings); } else { var d = document; var i = function () { i.c(arguments); }; i.q = []; i.c = function (args) { i.q.push(args); }; w.Intercom = i; var l = function () { var s = d.createElement('script'); s.type = 'text/javascript'; s.async = true; s.src = 'https://widget.intercom.io/widget/' + INTERCOM_APP_ID; var x = d.getElementsByTagName('script')[0]; x.parentNode.insertBefore(s, x); }; if (document.readyState === 'complete') { l(); } else if (w.attachEvent) { w.attachEvent('onload', l); } else { w.addEventListener('load', l, false); } } })();
|
||||
}
|
||||
|
||||
window.SENTRY_CONFIG = parseConfig("__APPSMITH_SENTRY_DSN__");
|
||||
window.APPSMITH_FEATURE_CONFIGS = {
|
||||
sentry: {
|
||||
dsn: parseConfig("__APPSMITH_SENTRY_DSN__"),
|
||||
release: parseConfig("__APPSMITH_SENTRY_RELEASE__"),
|
||||
environment: parseConfig("__APPSMITH_SENTRY_ENVIRONMENT__"),
|
||||
},
|
||||
smartLook: {
|
||||
id: parseConfig("__APPSMITH_SMART_LOOK_ID__"),
|
||||
},
|
||||
enableRapidAPI: parseConfig("__APPSMITH_MARKETPLACE_ENABLED__"),
|
||||
segment: {
|
||||
apiKey: parseConfig("__APPSMITH_SEGMENT_KEY__"),
|
||||
ceKey: parseConfig("__APPSMITH_SEGMENT_CE_KEY__"),
|
||||
},
|
||||
fusioncharts: {
|
||||
licenseKey: parseConfig("__APPSMITH_FUSIONCHARTS_LICENSE_KEY__")
|
||||
},
|
||||
enableMixpanel: parseConfig("__APPSMITH_SEGMENT_KEY__"),
|
||||
algolia: {
|
||||
apiId: parseConfig("__APPSMITH_ALGOLIA_API_ID__"),
|
||||
apiKey: parseConfig("__APPSMITH_ALGOLIA_API_KEY__"),
|
||||
indexName: parseConfig("__APPSMITH_ALGOLIA_SEARCH_INDEX_NAME__"),
|
||||
},
|
||||
logLevel: CONFIG_LOG_LEVEL_INDEX > -1 ? LOG_LEVELS[CONFIG_LOG_LEVEL_INDEX] : LOG_LEVELS[1],
|
||||
cloudHosting: CLOUD_HOSTING,
|
||||
enableTNCPP: parseConfig("__APPSMITH_TNC_PP__"),
|
||||
appVersion: {
|
||||
id: parseConfig("__APPSMITH_VERSION_ID__"),
|
||||
releaseDate: parseConfig("__APPSMITH_VERSION_RELEASE_DATE__")
|
||||
},
|
||||
intercomAppID: INTERCOM_APP_ID,
|
||||
mailEnabled: parseConfig("__APPSMITH_MAIL_ENABLED__"),
|
||||
cloudServicesBaseUrl: parseConfig("__APPSMITH_CLOUD_SERVICES_BASE_URL__") || "https://cs.appsmith.com",
|
||||
googleRecaptchaSiteKey: parseConfig("__APPSMITH_RECAPTCHA_SITE_KEY__"),
|
||||
hideWatermark: parseConfig("__APPSMITH_HIDE_WATERMARK__"),
|
||||
disableIframeWidgetSandbox: parseConfig("__APPSMITH_DISABLE_IFRAME_WIDGET_SANDBOX__"),
|
||||
customerPortalUrl: parseConfig("__APPSMITH_CUSTOMER_PORTAL_URL__") || "https://customer.appsmith.com",
|
||||
pricingUrl: parseConfig("__APPSMITH_PRICING_URL__") || "https://www.appsmith.com/pricing",
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
window.SENTRY_CONFIG = parseConfig("__APPSMITH_SENTRY_DSN__");
|
||||
window.APPSMITH_FEATURE_CONFIGS = {
|
||||
sentry: {
|
||||
dsn: parseConfig("__APPSMITH_SENTRY_DSN__"),
|
||||
release: parseConfig("__APPSMITH_SENTRY_RELEASE__"),
|
||||
environment: parseConfig("__APPSMITH_SENTRY_ENVIRONMENT__"),
|
||||
},
|
||||
smartLook: {
|
||||
id: parseConfig("__APPSMITH_SMART_LOOK_ID__"),
|
||||
},
|
||||
enableRapidAPI: parseConfig("__APPSMITH_MARKETPLACE_ENABLED__"),
|
||||
segment: {
|
||||
apiKey: parseConfig("__APPSMITH_SEGMENT_KEY__"),
|
||||
ceKey: parseConfig("__APPSMITH_SEGMENT_CE_KEY__"),
|
||||
},
|
||||
fusioncharts: {
|
||||
licenseKey: parseConfig("__APPSMITH_FUSIONCHARTS_LICENSE_KEY__"),
|
||||
},
|
||||
enableMixpanel: parseConfig("__APPSMITH_SEGMENT_KEY__"),
|
||||
algolia: {
|
||||
apiId: parseConfig("__APPSMITH_ALGOLIA_API_ID__"),
|
||||
apiKey: parseConfig("__APPSMITH_ALGOLIA_API_KEY__"),
|
||||
indexName: parseConfig("__APPSMITH_ALGOLIA_SEARCH_INDEX_NAME__"),
|
||||
},
|
||||
logLevel:
|
||||
CONFIG_LOG_LEVEL_INDEX > -1
|
||||
? LOG_LEVELS[CONFIG_LOG_LEVEL_INDEX]
|
||||
: LOG_LEVELS[1],
|
||||
cloudHosting: CLOUD_HOSTING,
|
||||
enableTNCPP: parseConfig("__APPSMITH_TNC_PP__"),
|
||||
appVersion: {
|
||||
id: parseConfig("__APPSMITH_VERSION_ID__"),
|
||||
releaseDate: parseConfig("__APPSMITH_VERSION_RELEASE_DATE__"),
|
||||
},
|
||||
intercomAppID: INTERCOM_APP_ID,
|
||||
mailEnabled: parseConfig("__APPSMITH_MAIL_ENABLED__"),
|
||||
cloudServicesBaseUrl:
|
||||
parseConfig("__APPSMITH_CLOUD_SERVICES_BASE_URL__") ||
|
||||
"https://cs.appsmith.com",
|
||||
googleRecaptchaSiteKey: parseConfig("__APPSMITH_RECAPTCHA_SITE_KEY__"),
|
||||
hideWatermark: parseConfig("__APPSMITH_HIDE_WATERMARK__"),
|
||||
disableIframeWidgetSandbox: parseConfig(
|
||||
"__APPSMITH_DISABLE_IFRAME_WIDGET_SANDBOX__",
|
||||
),
|
||||
customerPortalUrl:
|
||||
parseConfig("__APPSMITH_CUSTOMER_PORTAL_URL__") ||
|
||||
"https://customer.appsmith.com",
|
||||
pricingUrl:
|
||||
parseConfig("__APPSMITH_PRICING_URL__") ||
|
||||
"https://www.appsmith.com/pricing",
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import { Spinner } from "design-system";
|
|||
import SignpostingOverlay from "pages/Editor/FirstTimeUserOnboarding/Overlay";
|
||||
import { editorInitializer } from "../../utils/editor/EditorUtils";
|
||||
import { widgetInitialisationSuccess } from "../../actions/widgetActions";
|
||||
import { isAirgapped } from "@appsmith/utils/airgapHelpers";
|
||||
|
||||
type EditorProps = {
|
||||
currentApplicationId?: string;
|
||||
|
|
@ -138,7 +139,18 @@ class Editor extends Component<Props> {
|
|||
this.props.resetEditorRequest();
|
||||
}
|
||||
|
||||
// This function is triggered on load of google apis javascript library
|
||||
gapiLoaded = () => {
|
||||
(window as any).googleAPIsLoaded = true;
|
||||
return undefined;
|
||||
};
|
||||
onError = () => {
|
||||
(window as any).googleAPIsLoaded = false;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
public render() {
|
||||
const isAirgappedInstance = isAirgapped();
|
||||
if (!this.props.isEditorInitialized || this.props.loadingGuidedTour) {
|
||||
return (
|
||||
<CenteredWrapper
|
||||
|
|
@ -156,6 +168,17 @@ class Editor extends Component<Props> {
|
|||
<title>
|
||||
{`${this.props.currentApplicationName} |`} Editor | Appsmith
|
||||
</title>
|
||||
|
||||
{!isAirgappedInstance && !(window as any)?.googleAPIsLoaded ? (
|
||||
<script
|
||||
async
|
||||
defer
|
||||
id="googleapis"
|
||||
onError={this.onError()}
|
||||
onLoad={this.gapiLoaded()}
|
||||
src="https://apis.google.com/js/api.js"
|
||||
/>
|
||||
) : null}
|
||||
</Helmet>
|
||||
<GlobalHotKeys>
|
||||
<MainContainer />
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user