mirror of
https://github.com/enso-org/enso.git
synced 2024-08-16 16:30:33 +03:00
Google Analytics events for opening and closing the app, and opening and closing projects (#9779)
- Closes #9778 - Add `open_app`, `close_app`, `open_workflow`, and `close_workflow` events - Miscellaneous fixes for Google Analytics: - Fix `open_chat` and `close_chat` events firing even when chat is not visible - Add Google Analytics script to GUI2 entrypoint (i.e. the entrypoint used by the desktop app) Unrelated changes: - Add Nix development shell to allow Nix users to build GUI2 and the build script - Java dependencies have *not* been added in this PR to keep things simple # Important Notes None
This commit is contained in:
parent
0d495ffd97
commit
3a53d470eb
7
.envrc
Normal file
7
.envrc
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
strict_env
|
||||||
|
|
||||||
|
if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then
|
||||||
|
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4="
|
||||||
|
fi
|
||||||
|
|
||||||
|
use flake
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -162,3 +162,9 @@ test-results
|
|||||||
*.ir
|
*.ir
|
||||||
*.meta
|
*.meta
|
||||||
.enso/
|
.enso/
|
||||||
|
|
||||||
|
##################
|
||||||
|
## direnv cache ##
|
||||||
|
##################
|
||||||
|
|
||||||
|
.direnv
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"./src/appConfig": "./src/appConfig.js",
|
"./src/appConfig": "./src/appConfig.js",
|
||||||
"./src/buildUtils": "./src/buildUtils.js",
|
"./src/buildUtils": "./src/buildUtils.js",
|
||||||
"./src/detect": "./src/detect.ts",
|
"./src/detect": "./src/detect.ts",
|
||||||
"./src/gtag": "./src/gtag.ts"
|
"./src/gtag": "./src/gtag.ts",
|
||||||
|
"./src/load": "./src/load.ts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,12 @@ import * as fs from 'node:fs/promises'
|
|||||||
import * as path from 'node:path'
|
import * as path from 'node:path'
|
||||||
import * as url from 'node:url'
|
import * as url from 'node:url'
|
||||||
|
|
||||||
|
// =================
|
||||||
|
// === Constants ===
|
||||||
|
// =================
|
||||||
|
|
||||||
|
const ENSO_CLOUD_GOOGLE_ANALYTICS_TAG = 'G-CLTBJ37MDM'
|
||||||
|
|
||||||
// ===============================
|
// ===============================
|
||||||
// === readEnvironmentFromFile ===
|
// === readEnvironmentFromFile ===
|
||||||
// ===============================
|
// ===============================
|
||||||
@ -35,6 +41,10 @@ export async function readEnvironmentFromFile() {
|
|||||||
}
|
}
|
||||||
const variables = Object.fromEntries(entries)
|
const variables = Object.fromEntries(entries)
|
||||||
Object.assign(process.env, variables)
|
Object.assign(process.env, variables)
|
||||||
|
if (!('' in process.env)) {
|
||||||
|
// @ts-expect-error This is the only place where this environment variable is set.
|
||||||
|
process.env.ENSO_CLOUD_GOOGLE_ANALYTICS_TAG = ENSO_CLOUD_GOOGLE_ANALYTICS_TAG
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isProduction) {
|
if (isProduction) {
|
||||||
console.warn('Could not load `.env` file; disabling cloud backend.')
|
console.warn('Could not load `.env` file; disabling cloud backend.')
|
||||||
|
@ -17,15 +17,27 @@ export enum Platform {
|
|||||||
windows = 'Windows',
|
windows = 'Windows',
|
||||||
macOS = 'macOS',
|
macOS = 'macOS',
|
||||||
linux = 'Linux',
|
linux = 'Linux',
|
||||||
|
windowsPhone = 'Windows Phone',
|
||||||
|
iPhoneOS = 'iPhone OS',
|
||||||
|
android = 'Android',
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return the platform the app is currently running on.
|
/** The platform the app is currently running on.
|
||||||
* This is used to determine whether `metaKey` or `ctrlKey` is used in shortcuts. */
|
* This is used to determine whether `metaKey` or `ctrlKey` is used in shortcuts. */
|
||||||
export function platform(): Platform {
|
export function platform() {
|
||||||
if (isOnWindows()) {
|
if (isOnWindowsPhone()) {
|
||||||
|
// MUST be before Android and Windows.
|
||||||
|
return Platform.windowsPhone
|
||||||
|
} else if (isOnWindows()) {
|
||||||
return Platform.windows
|
return Platform.windows
|
||||||
|
} else if (isOnIPhoneOS()) {
|
||||||
|
// MUST be before macOS.
|
||||||
|
return Platform.iPhoneOS
|
||||||
} else if (isOnMacOS()) {
|
} else if (isOnMacOS()) {
|
||||||
return Platform.macOS
|
return Platform.macOS
|
||||||
|
} else if (isOnAndroid()) {
|
||||||
|
// MUST be before Linux.
|
||||||
|
return Platform.android
|
||||||
} else if (isOnLinux()) {
|
} else if (isOnLinux()) {
|
||||||
return Platform.linux
|
return Platform.linux
|
||||||
} else {
|
} else {
|
||||||
@ -33,22 +45,37 @@ export function platform(): Platform {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return whether the device is running Windows. */
|
/** Whether the device is running Windows. */
|
||||||
export function isOnWindows() {
|
export function isOnWindows() {
|
||||||
return /windows/i.test(navigator.userAgent)
|
return /windows/i.test(navigator.userAgent)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return whether the device is running macOS. */
|
/** Whether the device is running macOS. */
|
||||||
export function isOnMacOS() {
|
export function isOnMacOS() {
|
||||||
return /mac os/i.test(navigator.userAgent)
|
return /mac os/i.test(navigator.userAgent)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return whether the device is running Linux. */
|
/** Whether the device is running Linux. */
|
||||||
export function isOnLinux() {
|
export function isOnLinux() {
|
||||||
return /linux/i.test(navigator.userAgent)
|
return /linux/i.test(navigator.userAgent)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return whether the device is running an unknown OS. */
|
/** Whether the device is running Windows Phone. */
|
||||||
|
export function isOnWindowsPhone() {
|
||||||
|
return /windows phone/i.test(navigator.userAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether the device is running iPhone OS. */
|
||||||
|
export function isOnIPhoneOS() {
|
||||||
|
return /iPhone/i.test(navigator.userAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether the device is running Android. */
|
||||||
|
export function isOnAndroid() {
|
||||||
|
return /android/i.test(navigator.userAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether the device is running an unknown OS. */
|
||||||
export function isOnUnknownOS() {
|
export function isOnUnknownOS() {
|
||||||
return platform() === Platform.unknown
|
return platform() === Platform.unknown
|
||||||
}
|
}
|
||||||
@ -126,3 +153,73 @@ export function isOnSafari() {
|
|||||||
export function isOnUnknownBrowser() {
|
export function isOnUnknownBrowser() {
|
||||||
return browser() === Browser.unknown
|
return browser() === Browser.unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ====================
|
||||||
|
// === Architecture ===
|
||||||
|
// ====================
|
||||||
|
|
||||||
|
let detectedArchitecture: string | null = null
|
||||||
|
// Only implemented by Chromium.
|
||||||
|
// @ts-expect-error This API exists, but no typings exist for it yet.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||||
|
navigator.userAgentData
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
?.getHighEntropyValues(['architecture'])
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
.then((values: unknown) => {
|
||||||
|
if (
|
||||||
|
typeof values === 'object' &&
|
||||||
|
values != null &&
|
||||||
|
'architecture' in values &&
|
||||||
|
typeof values.architecture === 'string'
|
||||||
|
) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
detectedArchitecture = String(values.architecture)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Possible processor architectures. */
|
||||||
|
export enum Architecture {
|
||||||
|
intel64 = 'x86_64',
|
||||||
|
arm64 = 'arm64',
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The processor architecture of the current system. */
|
||||||
|
export function architecture() {
|
||||||
|
if (detectedArchitecture != null) {
|
||||||
|
switch (detectedArchitecture) {
|
||||||
|
case 'arm': {
|
||||||
|
return Architecture.arm64
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return Architecture.intel64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch (platform()) {
|
||||||
|
case Platform.windows:
|
||||||
|
case Platform.linux:
|
||||||
|
case Platform.unknown: {
|
||||||
|
return Architecture.intel64
|
||||||
|
}
|
||||||
|
case Platform.macOS:
|
||||||
|
case Platform.iPhoneOS:
|
||||||
|
case Platform.android:
|
||||||
|
case Platform.windowsPhone: {
|
||||||
|
// Assume the macOS device is on a M-series CPU.
|
||||||
|
// This is highly unreliable, but operates under the assumption that all
|
||||||
|
// new macOS devices will be ARM64.
|
||||||
|
return Architecture.arm64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether the device has an Intel 64-bit CPU. */
|
||||||
|
export function isIntel64() {
|
||||||
|
return architecture() === Architecture.intel64
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether the device has an ARM 64-bit CPU. */
|
||||||
|
export function isArm64() {
|
||||||
|
return architecture() === Architecture.arm64
|
||||||
|
}
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
/** @file Google Analytics tag. */
|
/** @file Google Analytics tag. */
|
||||||
|
import * as load from './load'
|
||||||
|
|
||||||
|
if (process.env.ENSO_CLOUD_GOOGLE_ANALYTICS_TAG != null) {
|
||||||
|
void load.loadScript(
|
||||||
|
`https://www.googletagmanager.com/gtag/js?id=${process.env.ENSO_CLOUD_GOOGLE_ANALYTICS_TAG}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// @ts-expect-error This is explicitly not given types as it is a mistake to acess this
|
// @ts-expect-error This is explicitly not given types as it is a mistake to acess this
|
||||||
// anywhere else.
|
// anywhere else.
|
||||||
|
32
app/ide-desktop/lib/common/src/load.ts
Normal file
32
app/ide-desktop/lib/common/src/load.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/** @file Utilities for loading resources. */
|
||||||
|
|
||||||
|
/** Add a script to the DOM. */
|
||||||
|
export function loadScript(url: string) {
|
||||||
|
const script = document.createElement('script')
|
||||||
|
script.crossOrigin = 'anonymous'
|
||||||
|
script.src = url
|
||||||
|
document.head.appendChild(script)
|
||||||
|
return new Promise<HTMLScriptElement>((resolve, reject) => {
|
||||||
|
script.onload = () => {
|
||||||
|
resolve(script)
|
||||||
|
}
|
||||||
|
script.onerror = reject
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Add a CSS stylesheet to the DOM. */
|
||||||
|
export function loadStyle(url: string) {
|
||||||
|
const style = document.createElement('link')
|
||||||
|
style.crossOrigin = 'anonymous'
|
||||||
|
style.href = url
|
||||||
|
style.rel = 'stylesheet'
|
||||||
|
style.media = 'screen'
|
||||||
|
style.type = 'text/css'
|
||||||
|
document.head.appendChild(style)
|
||||||
|
return new Promise<HTMLLinkElement>((resolve, reject) => {
|
||||||
|
style.onload = () => {
|
||||||
|
resolve(style)
|
||||||
|
}
|
||||||
|
style.onerror = reject
|
||||||
|
})
|
||||||
|
}
|
@ -42,10 +42,5 @@
|
|||||||
<noscript>
|
<noscript>
|
||||||
This page requires JavaScript to run. Please enable it in your browser.
|
This page requires JavaScript to run. Please enable it in your browser.
|
||||||
</noscript>
|
</noscript>
|
||||||
<!-- Google tag (gtag.js) -->
|
|
||||||
<script
|
|
||||||
async
|
|
||||||
src="https://www.googletagmanager.com/gtag/js?id=G-CLTBJ37MDM"
|
|
||||||
></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -43,10 +43,5 @@
|
|||||||
<noscript>
|
<noscript>
|
||||||
This page requires JavaScript to run. Please enable it in your browser.
|
This page requires JavaScript to run. Please enable it in your browser.
|
||||||
</noscript>
|
</noscript>
|
||||||
<!-- Google tag (gtag.js) -->
|
|
||||||
<script
|
|
||||||
async
|
|
||||||
src="https://www.googletagmanager.com/gtag/js?id=G-CLTBJ37MDM"
|
|
||||||
></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -70,6 +70,9 @@ const MODIFIER_JSX: Readonly<
|
|||||||
</aria.Text>
|
</aria.Text>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
[detect.Platform.iPhoneOS]: {},
|
||||||
|
[detect.Platform.android]: {},
|
||||||
|
[detect.Platform.windowsPhone]: {},
|
||||||
/* eslint-enable @typescript-eslint/naming-convention */
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,9 +5,9 @@ import * as gtag from 'enso-common/src/gtag'
|
|||||||
|
|
||||||
import * as authProvider from '#/providers/AuthProvider'
|
import * as authProvider from '#/providers/AuthProvider'
|
||||||
|
|
||||||
// ===================
|
// ====================
|
||||||
// === useGtag ===
|
// === useGtagEvent ===
|
||||||
// ===================
|
// ====================
|
||||||
|
|
||||||
/** A hook that returns a no-op if the user is offline, otherwise it returns
|
/** A hook that returns a no-op if the user is offline, otherwise it returns
|
||||||
* a transparent wrapper around `gtag.event`. */
|
* a transparent wrapper around `gtag.event`. */
|
||||||
@ -22,3 +22,28 @@ export function useGtagEvent() {
|
|||||||
[sessionType]
|
[sessionType]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================
|
||||||
|
// === gtagOpenCloseCallback ===
|
||||||
|
// =============================
|
||||||
|
|
||||||
|
/** Send an event indicating that something has been opened, and return a cleanup function
|
||||||
|
* sending an event indicating that it has been closed.
|
||||||
|
*
|
||||||
|
* Also sends the close event when the window is unloaded. */
|
||||||
|
export function gtagOpenCloseCallback(
|
||||||
|
gtagEventRef: React.MutableRefObject<ReturnType<typeof useGtagEvent>>,
|
||||||
|
openEvent: string,
|
||||||
|
closeEvent: string
|
||||||
|
) {
|
||||||
|
const gtagEventCurrent = gtagEventRef.current
|
||||||
|
gtagEventCurrent(openEvent)
|
||||||
|
const onBeforeUnload = () => {
|
||||||
|
gtagEventCurrent(closeEvent)
|
||||||
|
}
|
||||||
|
window.addEventListener('beforeunload', onBeforeUnload)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('beforeunload', onBeforeUnload)
|
||||||
|
gtagEventCurrent(closeEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -243,7 +243,6 @@ interface InternalChatHeaderProps {
|
|||||||
function ChatHeader(props: InternalChatHeaderProps) {
|
function ChatHeader(props: InternalChatHeaderProps) {
|
||||||
const { threads, setThreads, threadId, threadTitle, setThreadTitle } = props
|
const { threads, setThreads, threadId, threadTitle, setThreadTitle } = props
|
||||||
const { switchThread, sendMessage, doClose } = props
|
const { switchThread, sendMessage, doClose } = props
|
||||||
const gtagEvent = gtagHooks.useGtagEvent()
|
|
||||||
const [isThreadListVisible, setIsThreadListVisible] = React.useState(false)
|
const [isThreadListVisible, setIsThreadListVisible] = React.useState(false)
|
||||||
// These will never be `null` as their values are set immediately.
|
// These will never be `null` as their values are set immediately.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
@ -258,12 +257,10 @@ function ChatHeader(props: InternalChatHeaderProps) {
|
|||||||
setIsThreadListVisible(false)
|
setIsThreadListVisible(false)
|
||||||
}
|
}
|
||||||
document.addEventListener('click', onClick)
|
document.addEventListener('click', onClick)
|
||||||
gtagEvent('cloud_open_chat')
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('click', onClick)
|
document.removeEventListener('click', onClick)
|
||||||
gtagEvent('cloud_close_chat')
|
|
||||||
}
|
}
|
||||||
}, [gtagEvent])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -394,6 +391,17 @@ export default function Chat(props: ChatProps) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
const gtagEvent = gtagHooks.useGtagEvent()
|
||||||
|
const gtagEventRef = React.useRef(gtagEvent)
|
||||||
|
gtagEventRef.current = gtagEvent
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
return gtagHooks.gtagOpenCloseCallback(gtagEventRef, 'cloud_open_chat', 'cloud_close_chat')
|
||||||
|
}
|
||||||
|
}, [isOpen])
|
||||||
|
|
||||||
/** This is SAFE, because this component is only rendered when `accessToken` is present.
|
/** This is SAFE, because this component is only rendered when `accessToken` is present.
|
||||||
* See `dashboard.tsx` for its sole usage. */
|
* See `dashboard.tsx` for its sole usage. */
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
/** @file The container that launches the IDE. */
|
/** @file The container that launches the IDE. */
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
|
import * as load from 'enso-common/src/load'
|
||||||
|
|
||||||
import * as appUtils from '#/appUtils'
|
import * as appUtils from '#/appUtils'
|
||||||
|
|
||||||
|
import * as gtagHooks from '#/hooks/gtagHooks'
|
||||||
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
import * as toastAndLogHooks from '#/hooks/toastAndLogHooks'
|
||||||
|
|
||||||
import * as backendModule from '#/services/Backend'
|
import * as backendModule from '#/services/Backend'
|
||||||
|
|
||||||
import * as load from '#/utilities/load'
|
|
||||||
|
|
||||||
// =================
|
// =================
|
||||||
// === Constants ===
|
// === Constants ===
|
||||||
// =================
|
// =================
|
||||||
@ -39,6 +40,9 @@ export interface EditorProps {
|
|||||||
export default function Editor(props: EditorProps) {
|
export default function Editor(props: EditorProps) {
|
||||||
const { hidden, supportsLocalBackend, projectStartupInfo, appRunner } = props
|
const { hidden, supportsLocalBackend, projectStartupInfo, appRunner } = props
|
||||||
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
const toastAndLog = toastAndLogHooks.useToastAndLog()
|
||||||
|
const gtagEvent = gtagHooks.useGtagEvent()
|
||||||
|
const gtagEventRef = React.useRef(gtagEvent)
|
||||||
|
gtagEventRef.current = gtagEvent
|
||||||
const [initialized, setInitialized] = React.useState(supportsLocalBackend)
|
const [initialized, setInitialized] = React.useState(supportsLocalBackend)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@ -48,6 +52,14 @@ export default function Editor(props: EditorProps) {
|
|||||||
}
|
}
|
||||||
}, [hidden])
|
}, [hidden])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (hidden) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
return gtagHooks.gtagOpenCloseCallback(gtagEventRef, 'open_workflow', 'close_workflow')
|
||||||
|
}
|
||||||
|
}, [projectStartupInfo, hidden])
|
||||||
|
|
||||||
let hasEffectRun = false
|
let hasEffectRun = false
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
@ -6,6 +6,8 @@ import type * as stripeTypes from '@stripe/stripe-js'
|
|||||||
import * as stripe from '@stripe/stripe-js/pure'
|
import * as stripe from '@stripe/stripe-js/pure'
|
||||||
import * as toast from 'react-toastify'
|
import * as toast from 'react-toastify'
|
||||||
|
|
||||||
|
import * as load from 'enso-common/src/load'
|
||||||
|
|
||||||
import * as appUtils from '#/appUtils'
|
import * as appUtils from '#/appUtils'
|
||||||
import type * as text from '#/text'
|
import type * as text from '#/text'
|
||||||
|
|
||||||
@ -20,7 +22,6 @@ import UnstyledButton from '#/components/UnstyledButton'
|
|||||||
|
|
||||||
import * as backendModule from '#/services/Backend'
|
import * as backendModule from '#/services/Backend'
|
||||||
|
|
||||||
import * as load from '#/utilities/load'
|
|
||||||
import * as string from '#/utilities/string'
|
import * as string from '#/utilities/string'
|
||||||
|
|
||||||
// =================
|
// =================
|
||||||
|
@ -10,10 +10,13 @@ import isNetworkError from 'is-network-error'
|
|||||||
import * as router from 'react-router-dom'
|
import * as router from 'react-router-dom'
|
||||||
import * as toast from 'react-toastify'
|
import * as toast from 'react-toastify'
|
||||||
|
|
||||||
|
import * as detect from 'enso-common/src/detect'
|
||||||
import * as gtag from 'enso-common/src/gtag'
|
import * as gtag from 'enso-common/src/gtag'
|
||||||
|
|
||||||
import * as appUtils from '#/appUtils'
|
import * as appUtils from '#/appUtils'
|
||||||
|
|
||||||
|
import * as gtagHooks from '#/hooks/gtagHooks'
|
||||||
|
|
||||||
import * as backendProvider from '#/providers/BackendProvider'
|
import * as backendProvider from '#/providers/BackendProvider'
|
||||||
import * as localStorageProvider from '#/providers/LocalStorageProvider'
|
import * as localStorageProvider from '#/providers/LocalStorageProvider'
|
||||||
import * as loggerProvider from '#/providers/LoggerProvider'
|
import * as loggerProvider from '#/providers/LoggerProvider'
|
||||||
@ -238,6 +241,16 @@ export default function AuthProvider(props: AuthProviderProps) {
|
|||||||
},
|
},
|
||||||
[userSession?.type]
|
[userSession?.type]
|
||||||
)
|
)
|
||||||
|
const gtagEventRef = React.useRef(gtagEvent)
|
||||||
|
gtagEventRef.current = gtagEvent
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
gtag.gtag('set', {
|
||||||
|
platform: detect.platform(),
|
||||||
|
architecture: detect.architecture(),
|
||||||
|
})
|
||||||
|
return gtagHooks.gtagOpenCloseCallback(gtagEventRef, 'open_app', 'close_app')
|
||||||
|
}, [])
|
||||||
|
|
||||||
// This is identical to `hooks.useOnlineCheck`, however it is inline here to avoid any possible
|
// This is identical to `hooks.useOnlineCheck`, however it is inline here to avoid any possible
|
||||||
// circular dependency.
|
// circular dependency.
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
/** @file Utilities for loading resources. */
|
|
||||||
|
|
||||||
/** Add a script to the DOM. */
|
|
||||||
export function loadScript(url: string) {
|
|
||||||
const script = document.createElement('script')
|
|
||||||
script.crossOrigin = 'anonymous'
|
|
||||||
script.src = url
|
|
||||||
document.head.appendChild(script)
|
|
||||||
return new Promise<HTMLScriptElement>((resolve, reject) => {
|
|
||||||
script.onload = () => {
|
|
||||||
resolve(script)
|
|
||||||
}
|
|
||||||
script.onerror = reject
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Add a CSS stylesheet to the DOM. */
|
|
||||||
export function loadStyle(url: string) {
|
|
||||||
const style = document.createElement('link')
|
|
||||||
style.crossOrigin = 'anonymous'
|
|
||||||
style.href = url
|
|
||||||
style.rel = 'stylesheet'
|
|
||||||
style.media = 'screen'
|
|
||||||
style.type = 'text/css'
|
|
||||||
document.head.appendChild(style)
|
|
||||||
return new Promise<HTMLLinkElement>((resolve, reject) => {
|
|
||||||
style.onload = () => {
|
|
||||||
resolve(style)
|
|
||||||
}
|
|
||||||
style.onerror = reject
|
|
||||||
})
|
|
||||||
}
|
|
2
app/ide-desktop/lib/types/globals.d.ts
vendored
2
app/ide-desktop/lib/types/globals.d.ts
vendored
@ -140,6 +140,8 @@ declare global {
|
|||||||
// @ts-expect-error The index signature is intentional to disallow unknown env vars.
|
// @ts-expect-error The index signature is intentional to disallow unknown env vars.
|
||||||
readonly ENSO_CLOUD_COGNITO_REGION?: string
|
readonly ENSO_CLOUD_COGNITO_REGION?: string
|
||||||
// @ts-expect-error The index signature is intentional to disallow unknown env vars.
|
// @ts-expect-error The index signature is intentional to disallow unknown env vars.
|
||||||
|
readonly ENSO_CLOUD_GOOGLE_ANALYTICS_TAG?: string
|
||||||
|
// @ts-expect-error The index signature is intentional to disallow unknown env vars.
|
||||||
readonly ENSO_SUPPORTS_VIBRANCY?: string
|
readonly ENSO_SUPPORTS_VIBRANCY?: string
|
||||||
|
|
||||||
// === Electron watch script variables ===
|
// === Electron watch script variables ===
|
||||||
|
64
flake.lock
Normal file
64
flake.lock
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"fenix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": ["nixpkgs"],
|
||||||
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1713939967,
|
||||||
|
"narHash": "sha256-3YQSEYvAIHE40tx5nM9dgeEe0gsHjf15+gurUpyDYNw=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "fenix",
|
||||||
|
"rev": "5c3ff469526a6ca54a887fbda9d67aef4dd4a921",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "fenix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1713805509,
|
||||||
|
"narHash": "sha256-YgSEan4CcrjivCNO5ZNzhg7/8ViLkZ4CB/GrGBVSudo=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "1e1dc66fe68972a76679644a5577828b6a7e8be4",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"fenix": "fenix",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-analyzer-src": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1713801366,
|
||||||
|
"narHash": "sha256-VmzP5s59kb6//mj+ES+hslTLuugjd7OluGIXXcwuyHg=",
|
||||||
|
"owner": "rust-lang",
|
||||||
|
"repo": "rust-analyzer",
|
||||||
|
"rev": "e31c9f3fe11148514c3ad254b639b2ed7dbe35de",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "rust-lang",
|
||||||
|
"ref": "nightly",
|
||||||
|
"repo": "rust-analyzer",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
46
flake.nix
Normal file
46
flake.nix
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = github:nixos/nixpkgs/nixpkgs-unstable;
|
||||||
|
fenix.url = github:nix-community/fenix;
|
||||||
|
fenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
outputs = { self, nixpkgs, fenix }:
|
||||||
|
let
|
||||||
|
forAllSystems = with nixpkgs.lib; f: foldAttrs mergeAttrs { }
|
||||||
|
(map (s: { ${s} = f s; }) systems.flakeExposed);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShell = forAllSystems
|
||||||
|
(system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
rust = fenix.packages.${system}.fromToolchainFile {
|
||||||
|
dir = ./.;
|
||||||
|
sha256 = "sha256-o/MRwGYjLPyD1zZQe3LX0dOynwRJpVygfF9+vSnqTOc=";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
pkgs.mkShell {
|
||||||
|
packages = with pkgs; [
|
||||||
|
# === TypeScript dependencies ===
|
||||||
|
nodejs_20 # should match the Node.JS version of the lambdas
|
||||||
|
corepack
|
||||||
|
# === Electron ===
|
||||||
|
electron
|
||||||
|
# === node-gyp dependencies ===
|
||||||
|
python3
|
||||||
|
gnumake
|
||||||
|
# === WASM parser dependencies ===
|
||||||
|
rust
|
||||||
|
wasm-pack
|
||||||
|
# Java and SBT omitted for now
|
||||||
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
# `sccache` can be used to speed up compile times for Rust crates.
|
||||||
|
# `~/.cargo/bin/sccache` is provided by `cargo install sccache`.
|
||||||
|
# `~/.cargo/bin` must be in the `PATH` for the binary to be accessible.
|
||||||
|
export PATH=$HOME/.cargo/bin:$PATH
|
||||||
|
'';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user