diff --git a/shared/js/FileManager.ts b/shared/js/FileManager.ts index f443a423..0c6f1a1a 100644 --- a/shared/js/FileManager.ts +++ b/shared/js/FileManager.ts @@ -591,15 +591,31 @@ class IconManager { } } - loadIcon(id: number) : Promise { + download_icon(id: number) : Promise { return this._loading_promises[id] || (this._loading_promises[id] = this._load_icon(id)); } + async resolve_icon(id: number) : Promise { + id = id >>> 0; + try { + return await this.resolved_cached(id); + } catch(error) { } + + try { + return await this.download_icon(id); + } catch(error) { + console.error(tr("Icon download failed of icon %d: %o"), id, error); + } + + throw "icon not found"; + } + generateTag(id: number, options?: { animate?: boolean }) : JQuery { options = options || {}; + id = id >>> 0; if(id == 0) return $.spawn("div").addClass("icon_empty"); else if(id < 1000) @@ -617,18 +633,7 @@ class IconManager { icon_load_image.appendTo(icon_container); (async () => { - let icon: Icon; - try { - icon = await this.resolved_cached(id); - } catch(error) { - console.error(error); - } - - if(!icon) - icon = await this.loadIcon(id); - - if(!icon) - throw "failed to download icon"; + let icon: Icon = await this.resolve_icon(id); icon_image.attr("src", icon.url); icon_container.append(icon_image).removeClass("icon_empty"); diff --git a/shared/js/ui/modal/permission/ModalPermissionEdit.ts b/shared/js/ui/modal/permission/ModalPermissionEdit.ts index 2058b1bf..bdc373b2 100644 --- a/shared/js/ui/modal/permission/ModalPermissionEdit.ts +++ b/shared/js/ui/modal/permission/ModalPermissionEdit.ts @@ -65,6 +65,9 @@ namespace Modals { private entry_editor: ui.PermissionEditor; + icon_resolver: (id: number) => Promise; + icon_selector: (current_id: number) => Promise; + constructor(permissions: GroupedPermissions[]) { this.permissions = permissions; this.entry_editor = new ui.PermissionEditor(permissions); @@ -167,6 +170,15 @@ namespace Modals { element.flag_negate = entry.flag_negate; } + if(permission.name === "i_icon_id") { + this.icon_resolver(entry.value).then(e => { + entry.set_icon_id_image(e); + entry.request_full_draw(); + this.entry_editor.request_draw(false); + }).catch(error => { + console.warn(tr("Failed to load icon for permission editor: %o"), error); + }); + } entry.request_full_draw(); this.entry_editor.request_draw(false); }).catch(() => { @@ -301,10 +313,21 @@ namespace Modals { entry.value = value.value; entry.flag_skip = value.flag_skip; entry.flag_negate = value.flag_negate; + if(permission.name === "i_icon_id") { + this.icon_resolver(value.value).then(e => { + entry.set_icon_id_image(e); + entry.request_full_draw(); + this.entry_editor.request_draw(false); + }).catch(error => { + console.warn(tr("Failed to load icon for permission editor: %o"), error); + }); + entry.on_icon_select = this.icon_selector; + } } else { entry.value = undefined; entry.flag_skip = false; entry.flag_negate = false; + entry.set_icon_id_image(undefined); } if(value && value.hasGrant()) { @@ -353,6 +376,22 @@ namespace Modals { let tag = $("#tmpl_server_permissions").renderTag(properties); const pe = new PermissionEditor(connection.permissions.groupedPermissions()); pe.build_tag(); + pe.icon_resolver = id => connection.fileManager.icons.resolve_icon(id).then(async icon => { + if(!icon) + return undefined; + + const tag = document.createElement("img"); + await new Promise((resolve, reject) => { + tag.onerror = reject; + tag.onload = resolve; + tag.src = icon.url; + }); + return tag; + }); + pe.icon_selector = current_icon => new Promise(resolve => { + spawnIconSelect(connection, id => resolve(new Int32Array([id])[0]), current_icon); + }); + /* initialisation */ { const pe_server = tag.find("permission-editor.group-server"); @@ -972,6 +1011,7 @@ namespace Modals { let current_group; /* list all groups */ + let update_icon: (icon_id: number) => any; { let group_list = tab_tag.find(".list-group-server .entries"); @@ -986,7 +1026,10 @@ namespace Modals { continue; } let tag = $.spawn("div").addClass("group").attr("group-id", group.id); - connection.fileManager.icons.generateTag(group.properties.iconid).appendTo(tag); + let icon_tag = connection.fileManager.icons.generateTag(group.properties.iconid); + icon_tag.appendTo(tag); + const _update_icon = icon_id => icon_tag.replaceWith(icon_tag = connection.fileManager.icons.generateTag(icon_id)); + { let name = $.spawn("a").text(group.name + " (" + group.id + ")").addClass("name"); if(group.properties.savedb) @@ -999,6 +1042,7 @@ namespace Modals { tag.on('click', event => { current_group = group; + update_icon = _update_icon; group_list.find(".selected").removeClass("selected"); tag.addClass("selected"); editor.trigger_update(); @@ -1042,6 +1086,10 @@ namespace Modals { return connection.serverConnection.send_command("servergroupdelperm", { sgid: current_group.id, permid: permission.id, + }).then(e => { + if(permission.name === "i_icon_id" && update_icon) + update_icon(0); + return e; }); } else { log.info(LogCategory.PERMISSIONS, tr("Removing server group grant permission %s. permission.id: %o"), @@ -1072,6 +1120,10 @@ namespace Modals { permvalue: value.value, permskip: value.flag_skip, permnegate: value.flag_negate + }).then(e => { + if(permission.name === "i_icon_id" && update_icon) + update_icon(value.value); + return e; }); } else { log.info(LogCategory.PERMISSIONS, tr("Adding or updating server group grant permission %s. permission.{id: %o, value: %o}"), diff --git a/shared/js/ui/modal/permission/PermissionEditor.ts b/shared/js/ui/modal/permission/PermissionEditor.ts index 2a8875f6..807d8b37 100644 --- a/shared/js/ui/modal/permission/PermissionEditor.ts +++ b/shared/js/ui/modal/permission/PermissionEditor.ts @@ -459,7 +459,9 @@ namespace ui { private _listener_value: InteractionListener; private _listener_grant: InteractionListener; private _listener_general: InteractionListener; + private _icon_image: HTMLImageElement | undefined; + on_icon_select?: (current_id: number) => Promise; on_context_menu?: (x: number, y: number) => any; on_grant_change?: () => any; on_change?: () => any; @@ -469,6 +471,16 @@ namespace ui { this._permission = permission; } + set_icon_id_image(image: HTMLImageElement | undefined) { + if(this._icon_image === image) + return; + this._icon_image = image; + if(image) { + image.height = 16; + image.width = 16; + } + } + permission() { return this._permission; } draw(ctx: CanvasRenderingContext2D, full: boolean) { @@ -561,11 +573,19 @@ namespace ui { const x = w + PermissionEntry.COLUMN_VALUE - PermissionEntry.CHECKBOX_HEIGHT; const y = 1; + this._listener_value.region.width = PermissionEntry.CHECKBOX_HEIGHT; this._listener_value.region.x = original_x + x; this._listener_value.region.y = original_y + y; this._draw_checkbox_field(ctx, this.colors.permission.value_b, x, y, PermissionEntry.CHECKBOX_HEIGHT, this.value > 0, this.flag_value_hovered); + } else if(this._permission.name === "i_icon_id" && this._icon_image) { + this._listener_value.region.x = original_x + w; + this._listener_value.region.y = original_y; + this._listener_value.region.width = PermissionEntry.CHECKBOX_HEIGHT; + + this._draw_icon_field(ctx, this.colors.permission.value_b, w, 0, PermissionEntry.COLUMN_VALUE, this.flag_value_hovered, this._icon_image); } else { + this._listener_value.region.width = PermissionEntry.COLUMN_VALUE; this._listener_value.region.x = original_x + w; this._listener_value.region.y = original_y; @@ -600,6 +620,20 @@ namespace ui { this._listener_general.region.x = -1e8; } + private _draw_icon_field(ctx: CanvasRenderingContext2D, scheme: scheme.CheckBox, x: number, y: number, width: number, hovered: boolean, image: HTMLImageElement) { + const line = ctx.lineWidth; + ctx.lineWidth = 2; + ctx.fillStyle = scheme.border; + ctx.strokeRect(x + 1, y + 1, PermissionEntry.HEIGHT - 2, PermissionEntry.HEIGHT - 2); + ctx.lineWidth = line; + + ctx.fillStyle = hovered ? scheme.background_hovered : scheme.background; + ctx.fillRect(x + 1, y + 1, PermissionEntry.HEIGHT - 2, PermissionEntry.HEIGHT - 2); + + const center_y = y + PermissionEntry.HEIGHT / 2; + const center_x = x + PermissionEntry.HEIGHT / 2; + ctx.drawImage(image, center_x - image.width / 2, center_y - image.height / 2); + } private _draw_number_field(ctx: CanvasRenderingContext2D, scheme: scheme.TextField, x: number, y: number, width: number, value: number, hovered: boolean) { ctx.fillStyle = hovered ? scheme.background_hovered : scheme.background; @@ -721,6 +755,14 @@ namespace ui { if(this.on_change) this.on_change(); return RepaintMode.REPAINT_OBJECT_FULL; + } else if(this._permission.name === "i_icon_id") { + this.on_icon_select(this.value).then(value => { + this.value = value; + if(this.on_change) + this.on_change(); + }).catch(error => { + console.warn(tr("Failed to select icon: %o"), error); + }) } else { this._spawn_number_edit( this._listener_value.region.x,