import { render } from "lit-html"; import "./style.css"; import { clientBuildImageUrl } from "./generator/client/clientGenerator"; import { backendBuildImageUrl } from "./generator/backend/backendGenerator"; import { template } from "./uiTemplate"; import { defaultSettings } from "./defaultSettings"; import { AvatarOptions } from "./generator/client/avatar"; import { CanvasLayer } from "./generator/client/Layers"; import { createTracker, PiwikTracker } from "./tracker"; export type LanuageSettings = { imageWidth?: number; imageHeight?: number; borderWidth?: number; uploadButtonTitle?: string; imagePreview?: string; downloadButtonTitle?: string; languageSelectorLabel?: string; imageAltText?: string; fileName?: string; templateUrl?: string; templateRenderFunction?: (settings: AvatarOptions) => CanvasLayer[]; initialLanguage?: string; privacyNote?: string; privacyLinkText?: string; privacyLink?: string; zoomLabel?: string; }; export enum Generator { CLIENT = "client", BACKEND = "backend" } export type ProfilePictureGeneratorSettings = { container: HTMLElement; tracking: boolean; piwikHost: string; piwikPageId: number; generator: Generator.BACKEND | Generator.CLIENT; containerClass?: string[]; buttonGroupClass?: string[]; dropDownClass?: string[]; dropDownButtonClass?: string[]; dropDownMenuClass?: string[]; zoom: boolean; uploadButtonClass?: string[]; languageSelectorClass?: string[]; imageClass?: string[]; downloadButtonClass?: string[]; font?: string; currentLanguage?: string; languages?: { [key: string]: LanuageSettings & { name: string }; }; } & LanuageSettings; export type ProfilePictureGenerator = ( settings: ProfilePictureGeneratorSettings ) => Promise; declare global { interface Window { ProfilePictureGenerator: ProfilePictureGenerator; } } window.ProfilePictureGenerator = window.ProfilePictureGenerator || async function(settings: ProfilePictureGeneratorSettings) { const originalSettings = settings; settings = { ...defaultSettings, ...settings }; const container = settings.container; const state: { loading: boolean; currentFile: File; downloadUrl: string; zoomFactor: number; } = { loading: false, currentFile: null, downloadUrl: null, zoomFactor: 1 }; // Create UI container.classList.add(...settings.containerClass); render(template(settings, state), container); // Load piwik tracker asynchronos to not impact the loading of the generator, // events before the load has finished are ignored let tracker: PiwikTracker | undefined; if (settings.tracking && !navigator.doNotTrack) { createTracker(container, settings.piwikHost, settings.piwikPageId).then( t => (tracker = t) ); } /** * Track an event if a tracker exists */ function trackEvent( category: string, action: string, name?: string, value?: number ): void { if (tracker) { tracker.trackEvent(category, action, name, value); } } /** * Generates the profile picture */ async function generateProfilePicture(file: File) { state.loading = true; render(template(settings, state), container); // Generate Image with choosen generator let url = ""; if (settings.generator === Generator.CLIENT) { console.time("clientBuildImageUrl()"); url = await clientBuildImageUrl( file, settings.imageWidth, settings.imageHeight, settings.borderWidth, state.zoomFactor, settings.templateUrl, settings.templateRenderFunction ); console.timeEnd("clientBuildImageUrl()"); } else { console.time("backendBuildImageUrl()"); url = await backendBuildImageUrl( settings.currentLanguage, file ); console.timeEnd("backendBuildImageUrl()"); } // Update download link and image preview with generated image state.downloadUrl = url; state.loading = false; render(template(settings, state), container); trackEvent( "profile-picture-generator", "generate", settings.currentLanguage ); } /** * Changes the language of the component, returns true if successfull, false otherwise */ function changeLanguage(lang: string): boolean { if (!settings.languages || !settings.languages[lang]) return false; // Load new settings settings = { ...defaultSettings, ...(defaultSettings.languages ? defaultSettings.languages[lang] : {}), ...originalSettings, ...(originalSettings.languages ? originalSettings.languages[lang] : {}), currentLanguage: lang }; // Update texts render(template(settings, state), container); // Update image if (state.currentFile) { generateProfilePicture(state.currentFile); } return true; } /** * Changes the zoom factor for the image, returns true if successfull, false otherwise */ function changeZoomFactor(zoomLevelStr: string): boolean { const zoomLevel = Number(zoomLevelStr); if (zoomLevel < 0 || 100 < zoomLevel) return false; // non-linear mapping from 0..100 to 0.5..2 with 50 at 1 const zoomFactor = 0.5 * Math.pow(2, zoomLevel/50); // update state state.zoomFactor = zoomFactor; // Update image if (state.currentFile) { generateProfilePicture(state.currentFile); } return true; } // Load initial langauge changeLanguage(settings.initialLanguage); // Automaticly select best matching user language // IE11 only supports navigator.language if (navigator.languages) { for (const lang of navigator.languages) { if (changeLanguage(lang)) break; } } else if (navigator.language) { changeLanguage(navigator.language); } // Add event listener container.addEventListener("change-language", (e: CustomEvent) => changeLanguage(e.detail) ); container.addEventListener("change-zoom-factor", (e: CustomEvent) => changeZoomFactor(e.detail) ); container.addEventListener("upload-avatar", (e: CustomEvent) => { state.currentFile = e.detail; generateProfilePicture(e.detail); }); container.addEventListener("download", () => trackEvent( "profile-picture-generator", "download", settings.currentLanguage ) ); };