import {Registry} from "tc-shared/events"; import {tr, tra} from "tc-shared/i18n/localize"; export const kIPCIconChannel = "icons"; export const kGlobalIconHandlerId = "global"; export interface RemoteIconEvents { notify_state_changed: { oldState: RemoteIconState, newState: RemoteIconState } } export type RemoteIconState = "loading" | "loaded" | "error" | "empty" | "destroyed"; export type RemoteIconInfo = { iconId: number, serverUniqueId: string, handlerId?: string } export abstract class RemoteIcon { readonly events: Registry; readonly iconId: number; readonly serverUniqueId: string; private state: RemoteIconState; protected imageUrl: string; protected errorMessage: string; protected constructor(serverUniqueId: string, iconId: number) { this.events = new Registry(); this.iconId = iconId; this.serverUniqueId = serverUniqueId; this.state = "loading"; } destroy() { this.setState("destroyed"); this.events.destroy(); this.imageUrl = undefined; } getState() : RemoteIconState { return this.state; } protected setState(state: RemoteIconState) { if(this.state === state) { return; } else if(this.state === "destroyed") { throw tr("remote icon has been destroyed"); } const oldState = this.state; this.state = state; this.events.fire("notify_state_changed", { newState: state, oldState: oldState }); } hasImageUrl() : boolean { return !!this.imageUrl; } /** * Will throw an string if the icon isn't in loaded state */ getImageUrl() : string { if(this.state !== "loaded") { throw tr("icon image url is only available when the state is loaded"); } if(!this.imageUrl) { throw tra("remote {} icon is missing an image url", this.iconId); } return this.imageUrl; } protected setImageUrl(url: string) { if(this.imageUrl) { throw tr("an image url has already been set"); } this.imageUrl = url; } /** * Will throw an string if the state isn't error */ getErrorMessage() : string | undefined { if(this.state !== "error") { throw tr("invalid remote icon state, expected error"); } return this.errorMessage; } protected setErrorMessage(message: string) { this.errorMessage = message; } /** * Waits 'till the icon has been loaded or any other, non loading, state has been reached. */ async awaitLoaded() { while(!this.isLoaded()) { await new Promise(resolve => this.events.one("notify_state_changed", resolve)); } } /** * Returns true if the icon isn't loading any more. * This includes all other states like error, destroy or empty. */ isLoaded() : boolean { return this.state !== "loading"; } } export abstract class AbstractIconManager { protected static iconUniqueKey(iconId: number, serverUniqueId: string) : string { return "v2-" + serverUniqueId + "-" + iconId; } /** * @param iconId The requested icon * @param serverUniqueId The server unique id for the icon * @param handlerId Hint which connection handler should be used if we're downloading the icon */ abstract resolveIcon(iconId: number, serverUniqueId?: string, handlerId?: string) : RemoteIcon; } let globalIconManager: AbstractIconManager; export function setIconManager(instance: AbstractIconManager) { if(globalIconManager) { throw "the global icon manager has already been set"; } globalIconManager = instance; } export function getIconManager() { return globalIconManager; } /* a helper for legacy code */ export function generateIconJQueryTag(icon: RemoteIcon | undefined, options?: { animate?: boolean }) : JQuery { options = options || {}; let icon_container = $.spawn("div").addClass("icon-container icon_empty"); let icon_load_image = $.spawn("div").addClass("icon_loading"); const icon_image = $.spawn("img").attr("width", 16).attr("height", 16).attr("alt", ""); if (icon.iconId == 0) { icon_load_image = undefined; } else if (icon.iconId < 1000) { icon_load_image = undefined; icon_container.removeClass("icon_empty").addClass("icon_em client-group_" + icon.iconId); } else { const loading_done = sync => {//TODO: Show error? if (icon.getState() === "empty") { icon_load_image.remove(); icon_load_image = undefined; } else if (icon.getState() === "error") { //TODO: Error icon? icon_load_image.remove(); icon_load_image = undefined; } else { icon_image.attr("src", icon.getImageUrl()); icon_container.append(icon_image).removeClass("icon_empty"); if (!sync && (typeof (options.animate) !== "boolean" || options.animate)) { icon_image.css("opacity", 0); icon_load_image.animate({opacity: 0}, 50, function () { icon_load_image.remove(); icon_image.animate({opacity: 1}, 150); }); } else { icon_load_image.remove(); icon_load_image = undefined; } } }; if(icon.isLoaded()) { loading_done(true); } else { icon.awaitLoaded().then(() => loading_done(false)); } } if (icon_load_image) { icon_load_image.appendTo(icon_container); } return icon_container; }