Add GA4 events

This commit is contained in:
Misode
2022-05-09 02:20:56 +02:00
parent 177e32c000
commit e63b4839dc
8 changed files with 210 additions and 37 deletions
+13
View File
@@ -38,6 +38,7 @@
"@rollup/plugin-alias": "^3.1.9",
"@rollup/plugin-html": "^0.2.3",
"@types/google.analytics": "0.0.40",
"@types/gtag.js": "^0.0.10",
"@types/howler": "^2.2.4",
"@types/js-yaml": "^4.0.4",
"@types/lz-string": "^1.3.34",
@@ -594,6 +595,12 @@
"integrity": "sha512-R3HpnLkqmKxhUAf8kIVvDVGJqPtaaZlW4yowNwjOZUTmYUQEgHh8Nh5wkSXKMroNAuQM8gbXJHmNbbgA8tdb7Q==",
"dev": true
},
"node_modules/@types/gtag.js": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.10.tgz",
"integrity": "sha512-98Hy7woUb3jMAMXkZQwfIOYNyfxmI0+U4m0PpCGdnd/FHk0tDpQFCqgXdNkdEoXsKkcGya/2Gew1cAJjKJspVw==",
"dev": true
},
"node_modules/@types/howler": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/@types/howler/-/howler-2.2.4.tgz",
@@ -5584,6 +5591,12 @@
"integrity": "sha512-R3HpnLkqmKxhUAf8kIVvDVGJqPtaaZlW4yowNwjOZUTmYUQEgHh8Nh5wkSXKMroNAuQM8gbXJHmNbbgA8tdb7Q==",
"dev": true
},
"@types/gtag.js": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.10.tgz",
"integrity": "sha512-98Hy7woUb3jMAMXkZQwfIOYNyfxmI0+U4m0PpCGdnd/FHk0tDpQFCqgXdNkdEoXsKkcGya/2Gew1cAJjKJspVw==",
"dev": true
},
"@types/howler": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/@types/howler/-/howler-2.2.4.tgz",
+1
View File
@@ -44,6 +44,7 @@
"@rollup/plugin-alias": "^3.1.9",
"@rollup/plugin-html": "^0.2.3",
"@types/google.analytics": "0.0.40",
"@types/gtag.js": "^0.0.10",
"@types/howler": "^2.2.4",
"@types/js-yaml": "^4.0.4",
"@types/lz-string": "^1.3.34",
+155 -9
View File
@@ -1,4 +1,10 @@
import type { VersionId } from './services'
type Method = 'menu' | 'hotkey'
export namespace Analytics {
/** Universal Analytics */
const ID_SITE = 'Site'
const ID_GENERATOR = 'Generator'
@@ -22,19 +28,40 @@ export namespace Analytics {
ga('send', 'pageview')
}
export function setLanguage(language: string) {
dimension(DIM_LANGUAGE, language)
event(ID_SITE, 'set-language', language)
/**
* @deprecated
*/
export function generatorEvent(action: string, label?: string) {
event(ID_GENERATOR, action, label)
}
function legacyMethod(method: Method) {
return method === 'menu' ? 'Menu' : 'Hotkey'
}
/** END Universal Analytics 4 */
export function setLocale(locale: string) {
dimension(DIM_LANGUAGE, locale)
event(ID_SITE, 'set-language', locale)
gtag('event', 'use_locale', {
locale,
})
}
export function setTheme(theme: string) {
dimension(DIM_THEME, theme)
event(ID_SITE, 'set-theme', theme)
gtag('event', 'use_theme', {
theme,
})
}
export function setVersion(version: string) {
dimension(DIM_VERSION, version)
event(ID_GENERATOR, 'set-version', version)
gtag('event', 'use_version', {
version,
})
}
export function setPreview(preview: string) {
@@ -42,15 +69,134 @@ export namespace Analytics {
event(ID_GENERATOR, 'set-preview', preview)
}
export function setGenerator(generator: string) {
dimension(DIM_GENERATOR, generator)
export function setGenerator(file_type: string) {
dimension(DIM_GENERATOR, file_type)
gtag('event', 'use_generator', {
file_type,
})
}
export function setPrefersColorScheme(colorScheme: string) {
dimension(DIM_PREFERS_COLOR_SCHEME, colorScheme)
export function setPrefersColorScheme(color_scheme: string) {
dimension(DIM_PREFERS_COLOR_SCHEME, color_scheme)
gtag('event', 'prefers_color_scheme', {
color_scheme,
})
}
export function generatorEvent(action: string, label?: string) {
event(ID_GENERATOR, action, label)
export function resetGenerator(file_type: string, history: number, method: Method) {
event(ID_GENERATOR, 'reset')
gtag('event', 'reset_generator', {
file_type,
history,
method,
})
}
export function undoGenerator(file_type: string, history: number, method: Method) {
event(ID_GENERATOR, 'undo', legacyMethod(method))
gtag('event', 'undo_generator', {
file_type,
history,
method,
})
}
export function redoGenerator(file_type: string, history: number, method: Method) {
event(ID_GENERATOR, 'undo', legacyMethod(method))
gtag('event', 'redo_generator', {
file_type,
history,
method,
})
}
export function saveProjectFile(file_type: string, project_size: number, projects_count: number, method: Method) {
event(ID_GENERATOR, 'save-project-file', legacyMethod(method))
gtag('event', 'save_project_file', {
file_type,
project_size,
projects_count,
method,
})
}
export function loadPreset(file_type: string, file_name: string) {
event(ID_GENERATOR, 'load-preset', file_name)
gtag('event', 'load_generator_preset', {
file_type,
file_name,
})
}
export function openPreset(file_type: string, file_name: string) {
gtag('event', 'open_generator_preset', {
file_type,
file_name,
})
}
export function createSnippet(file_type: string, snippet_id: string, version: VersionId, data_size: number, compressed_size: number, compression_rate: number) {
gtag('event', 'create_generator_snippet', {
file_type,
snippet_id,
version,
data_size,
compressed_size,
compression_rate,
})
}
export function openSnippet(file_type: string, snippet_id: string, version: VersionId) {
gtag('event', 'open_generator_snippet', {
file_type,
snippet_id,
version,
})
}
export function copyOutput(file_type: string, method: Method) {
gtag('event', 'copy_generator_output', {
file_type,
method,
})
}
export function downloadOutput(file_type: string, method: Method) {
gtag('event', 'download_generator_output', {
file_type,
method,
})
}
export function showOutput(file_type: string, method: Method) {
event(ID_GENERATOR, 'toggle-output', 'visible')
gtag('event', 'show_generator_output', {
file_type,
method,
})
}
export function hideOutput(file_type: string, method: Method) {
event(ID_GENERATOR, 'toggle-output', 'hidden')
gtag('event', 'hide_generator_output', {
file_type,
method,
})
}
export function showPreview(file_type: string, method: Method) {
event(ID_GENERATOR, 'toggle-preview', 'visible')
gtag('event', 'show_generator_preview', {
file_type,
method,
})
}
export function hidePreview(file_type: string, method: Method) {
event(ID_GENERATOR, 'toggle-preview', 'hidden')
gtag('event', 'hide_generator_preview', {
file_type,
method,
})
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ const Themes: Record<string, keyof typeof Octicon> = {
}
export function Header() {
const { lang, locale, changeLanguage } = useLocale()
const { lang, locale, changeLocale: changeLanguage } = useLocale()
const { theme, changeTheme } = useTheme()
const { version } = useVersion()
const { title } = useTitle()
+6 -6
View File
@@ -9,12 +9,12 @@ import { Store } from '../Store'
interface Locale {
lang: string,
locale: (key: string, ...params: string[]) => string,
changeLanguage: (lang: string) => unknown,
changeLocale: (lang: string) => unknown,
}
const Locale = createContext<Locale>({
lang: 'none',
locale: key => key,
changeLanguage: () => {},
changeLocale: () => {},
})
export const Locales: {
@@ -59,9 +59,9 @@ export function LocaleProvider({ children }: { children: ComponentChildren }) {
return localize(lang, key, ...params)
}, [lang])
const changeLanguage = useCallback(async (lang: string) => {
const changeLocale = useCallback(async (lang: string) => {
await loadLocale(lang)
Analytics.setLanguage(lang)
Analytics.setLocale(lang)
Store.setLanguage(lang)
setLanguage(lang)
}, [])
@@ -79,8 +79,8 @@ export function LocaleProvider({ children }: { children: ComponentChildren }) {
const value: Locale = {
lang,
locale: locale,
changeLanguage,
locale,
changeLocale,
}
return <Locale.Provider value={value}>
+3
View File
@@ -26,6 +26,7 @@ export type ProjectFile = {
}
interface ProjectContext {
projects: Project[],
project: Project,
file?: ProjectFile,
changeProject: (name: string) => unknown,
@@ -35,6 +36,7 @@ interface ProjectContext {
closeFile: () => unknown,
}
const Project = createContext<ProjectContext>({
projects: [DRAFT_PROJECT],
project: DRAFT_PROJECT,
changeProject: () => {},
updateProject: () => {},
@@ -105,6 +107,7 @@ export function ProjectProvider({ children }: { children: ComponentChildren }) {
}, [])
const value: ProjectContext = {
projects,
project,
file,
changeProject: setProjectName,
+27 -14
View File
@@ -8,17 +8,19 @@ import { useLocale, useProject, useTitle, useVersion } from '../contexts'
import { AsyncCancel, useActiveTimeout, useAsync, useModel, useSearchParam } from '../hooks'
import { getOutput } from '../schema/transformOutput'
import type { VersionId } from '../services'
import { checkVersion, fetchPreset, getBlockStates, getCollections, getModel, getSnippet, shareSnippet, SHARE_KEY } from '../services'
import { checkVersion, fetchPreset, getBlockStates, getCollections, getModel, getSnippet, shareSnippet } from '../services'
import { Store } from '../Store'
import { cleanUrl, deepEqual, getGenerator } from '../Utils'
export const SHARE_KEY = 'share'
interface Props {
default?: true,
}
export function Generator({}: Props) {
const { locale } = useLocale()
const { version, changeVersion, changeTargetVersion } = useVersion()
const { project, file, updateFile, openFile, closeFile } = useProject()
const { projects, project, file, updateFile, openFile, closeFile } = useProject()
const [error, setError] = useState<Error | string | null>(null)
const [errorBoundary, errorRetry] = useErrorBoundary()
if (errorBoundary) {
@@ -87,6 +89,7 @@ export function Generator({}: Props) {
setPreviewShown(true)
setSourceShown(false)
}
Analytics.openSnippet(gen.id, sharedSnippetId, version)
data = snippet.data
}
const [model, blockStates] = await Promise.all([
@@ -160,26 +163,26 @@ export function Generator({}: Props) {
}, [file, model])
const reset = () => {
Analytics.generatorEvent('reset')
Analytics.resetGenerator(gen.id, model?.historyIndex ?? 1, 'menu')
model?.reset(DataModel.wrapLists(model.schema.default()), true)
}
const undo = (e: MouseEvent) => {
e.stopPropagation()
Analytics.generatorEvent('undo', 'Menu')
Analytics.undoGenerator(gen.id, (model?.historyIndex ?? 1) - 1, 'menu')
model?.undo()
}
const redo = (e: MouseEvent) => {
e.stopPropagation()
Analytics.generatorEvent('redo', 'Menu')
Analytics.redoGenerator(gen.id, (model?.historyIndex ?? 1) + 1, 'menu')
model?.redo()
}
const onKeyUp = (e: KeyboardEvent) => {
if (e.ctrlKey && e.key === 'z') {
Analytics.generatorEvent('undo', 'Hotkey')
Analytics.undoGenerator(gen.id, (model?.historyIndex ?? 1) - 1, 'hotkey')
model?.undo()
} else if (e.ctrlKey && e.key === 'y') {
Analytics.generatorEvent('redo', 'Hotkey')
Analytics.redoGenerator(gen.id, (model?.historyIndex ?? 1) + 1, 'hotkey')
model?.redo()
}
}
@@ -187,7 +190,7 @@ export function Generator({}: Props) {
if (e.ctrlKey && e.key === 's') {
e.preventDefault()
if (model && blockStates && file) {
Analytics.generatorEvent('save', 'Hotkey')
Analytics.saveProjectFile(gen.id, project.files.length, projects.length, 'hotkey')
const data = getOutput(model, blockStates)
updateFile(gen.id, file?.id, { id: file?.id, data })
setDirty(false)
@@ -213,7 +216,7 @@ export function Generator({}: Props) {
}, [version, gen.id])
const selectPreset = (id: string) => {
Analytics.generatorEvent('load-preset', id)
Analytics.loadPreset(gen.id, id)
setSharedSnippetId(undefined, true)
changeTargetVersion(version, true)
setCurrentPreset(id)
@@ -260,7 +263,9 @@ export function Generator({}: Props) {
setShareShown(true)
} else {
shareSnippet(gen.id, version, output, previewShown)
.then(url => {
.then(({ id, length, compressed, rate }) => {
Analytics.createSnippet(gen.id, id, version, length, compressed, rate)
const url = `${location.origin}/${gen.url}/?${SHARE_KEY}=${id}`
setShareUrl(url)
setShareShown(true)
})
@@ -289,11 +294,11 @@ export function Generator({}: Props) {
const [doImport, setImport] = useState(0)
const copySource = () => {
Analytics.generatorEvent('copy')
Analytics.copyOutput(gen.id, 'menu')
setCopy(doCopy + 1)
}
const downloadSource = () => {
Analytics.generatorEvent('download')
Analytics.downloadOutput(gen.id, 'menu')
setDownload(doDownload + 1)
}
const importSource = () => {
@@ -302,7 +307,11 @@ export function Generator({}: Props) {
setImport(doImport + 1)
}
const toggleSource = () => {
Analytics.generatorEvent('toggle-output', !sourceShown ? 'visible' : 'hidden')
if (sourceShown) {
Analytics.hideOutput(gen.id, 'menu')
} else {
Analytics.showOutput(gen.id, 'menu')
}
setSourceShown(!sourceShown)
setCopy(0)
setDownload(0)
@@ -319,7 +328,11 @@ export function Generator({}: Props) {
if (sourceShown) actionsShown += 2
const togglePreview = () => {
Analytics.generatorEvent('toggle-preview', !previewShown ? 'visible' : 'hidden')
if (sourceShown) {
Analytics.hidePreview(gen.id, 'menu')
} else {
Analytics.showPreview(gen.id, 'menu')
}
setPreviewShown(!previewShown)
if (!previewShown && sourceShown) {
setSourceShown(false)
+4 -7
View File
@@ -1,17 +1,15 @@
import lz from 'lz-string'
import config from '../../config.json'
import type { VersionId } from './Schemas'
const API_PREFIX = 'https://z15g7can.directus.app/items'
export const SHARE_KEY = 'share'
const ShareCache = new Map<string, string>()
export async function shareSnippet(type: string, version: VersionId, jsonData: any, show_preview: boolean) {
try {
const data = lz.compressToBase64(JSON.stringify(jsonData))
const raw = btoa(JSON.stringify(jsonData))
console.log('Compression rate', raw.length / data.length)
const raw = JSON.stringify(jsonData)
const data = lz.compressToBase64(raw)
console.log('Compression rate', raw.length / raw.length)
const body = JSON.stringify({ data, type, version, show_preview })
let id = ShareCache.get(body)
if (!id) {
@@ -19,8 +17,7 @@ export async function shareSnippet(type: string, version: VersionId, jsonData: a
ShareCache.set(body, snippet.id)
id = snippet.id as string
}
const gen = config.generators.find(g => g.id === type)!
return `${location.protocol}//${location.host}/${gen.url}/?${SHARE_KEY}=${id}`
return { id, length: raw.length, compressed: data.length, rate: raw.length / data.length }
} catch (e) {
if (e instanceof Error) {
e.message = `Error creating share link: ${e.message}`