197 lines
5.7 KiB
TypeScript
197 lines
5.7 KiB
TypeScript
import {Registry} from "tc-shared/events";
|
|
|
|
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<RemoteIconEvents>;
|
|
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<RemoteIconEvents>();
|
|
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<HTMLDivElement> {
|
|
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;
|
|
} |