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
)
);
};