TeaWeb/shared/js/ui/modal/ModalMusicManage.ts
2021-04-27 13:30:33 +02:00

2056 lines
No EOL
77 KiB
TypeScript

import {createErrorModal, createModal} from "../../ui/elements/Modal";
import {ConnectionHandler} from "../../ConnectionHandler";
import {MusicClientEntry} from "../../tree/Client";
import {Registry} from "../../events";
import {CommandResult} from "../../connection/ServerConnectionDeclaration";
import {LogCategory, logError, logWarn} from "../../log";
import {tr, tra} from "../../i18n/localize";
import * as tooltip from "../../ui/elements/Tooltip";
import * as i18nc from "../../i18n/CountryFlag";
import {find} from "../../permission/PermissionManager";
import * as htmltags from "../../ui/htmltags";
import {ErrorCode} from "../../connection/ErrorCode";
import ServerGroup = find.ServerGroup;
type BotStatusType = "name" | "description" | "volume" | "country_code" | "channel_commander" | "priority_speaker";
type PlaylistStatusType = "replay_mode" | "finished" | "delete_played" | "max_size" | "notify_song_change";
interface music_manage {
show_container: { container: "settings" | "permissions"; };
/* setting relevant */
query_bot_status: {},
bot_status: {
status: "success" | "error";
error_msg?: string;
data?: {
name: string,
description: string,
volume: number,
country_code: string,
default_country_code: string,
channel_commander: boolean,
priority_speaker: boolean,
client_version: string,
client_platform: string,
uptime_mode: number,
bot_type: number
}
},
set_bot_status: {
key: BotStatusType,
value: any
},
set_bot_status_result: {
key: BotStatusType,
status: "success" | "error" | "timeout",
error_msg?: string,
value?: any
}
query_playlist_status: {},
playlist_status: {
status: "success" | "error",
error_msg?: string,
data?: {
replay_mode: number,
finished: boolean,
delete_played: boolean,
max_size: number,
notify_song_change: boolean
}
},
set_playlist_status: {
key: PlaylistStatusType,
value: any
},
set_playlist_status_result: {
key: PlaylistStatusType,
status: "success" | "error" | "timeout",
error_msg?: string,
value?: any
}
/* permission relevant */
show_client_list: {},
hide_client_list: {},
filter_client_list: { filter: string | undefined },
"refresh_permissions": {},
query_special_clients: {},
special_client_list: {
status: "success" | "error" | "error-permission",
error_msg?: string,
clients?: {
name: string,
unique_id: string,
database_id: number
}[]
},
search_client: { text: string },
search_client_result: {
status: "error" | "timeout" | "empty" | "success",
error_msg?: string,
client?: {
name: string,
unique_id: string,
database_id: number
}
},
/* sets a client to set the permission for */
special_client_set: {
client?: {
name: string,
unique_id: string,
database_id: number
}
},
"query_general_permissions": {},
"general_permissions": {
status: "error" | "timeout" | "success",
error_msg?: string,
permissions?: {[key: string]:number}
},
"set_general_permission_result": {
status: "error" | "success",
key: string,
value?: number,
error_msg?: string
},
"set_general_permission": { /* try to change a permission for the server */
key: string,
value: number
},
"query_client_permissions": { client_database_id: number },
"client_permissions": {
status: "error" | "timeout" | "success",
client_database_id: number,
error_msg?: string,
permissions?: {[key: string]:number}
},
"set_client_permission_result": {
status: "error" | "success",
client_database_id: number,
key: string,
value?: number,
error_msg?: string
},
"set_client_permission": { /* try to change a permission for the server */
client_database_id: number,
key: string,
value: number
},
"query_group_permissions": { permission_name: string },
"group_permissions": {
permission_name: string;
status: "error" | "timeout" | "success"
groups?: {
name: string,
value: number,
id: number
}[],
error_msg?: string
}
}
export function openMusicManage(client: ConnectionHandler, bot: MusicClientEntry) {
const ev_registry = new Registry<music_manage>();
ev_registry.enableDebug("music-manage");
//dummy_controller(ev_registry);
permission_controller(ev_registry, bot, client);
let modal = createModal({
header: tr("Playlist Manage"),
body: () => build_modal(ev_registry),
footer: null,
min_width: "35em",
closeable: true
});
modal.htmlTag.find(".modal-body").addClass("modal-music-manage");
/* "controller" */
{
}
modal.open();
}
function permission_controller(event_registry: Registry<music_manage>, bot: MusicClientEntry, client: ConnectionHandler) {
const error_msg = error => {
if (error instanceof CommandResult) {
if (error.id === ErrorCode.SERVER_INSUFFICIENT_PERMISSIONS) {
const permission = client.permissions.resolveInfo(error.json["failed_permid"]);
return tr("failed on permission ") + (permission ? permission.name : tr("unknown"));
}
return error.extra_message || error.message;
} else if (typeof error === "string")
return error;
else
return tr("command error");
};
{
event_registry.on("query_playlist_status", event => {
const playlist_id = bot.properties.client_playlist_id;
client.serverConnection.command_helper.requestPlaylistInfo(playlist_id).then(result => {
event_registry.fire("playlist_status", {
status: "success",
data: {
replay_mode: result.playlist_replay_mode,
finished: result.playlist_flag_finished,
delete_played: result.playlist_flag_delete_played,
notify_song_change: bot.properties.client_flag_notify_song_change,
max_size: result.playlist_max_songs
}
});
}).catch(error => {
event_registry.fire("playlist_status", {
status: "error",
error_msg: error_msg(error)
});
logError(LogCategory.CLIENT, tr("Failed to query playlist info for playlist %d: %o"), playlist_id, error);
});
});
event_registry.on("set_playlist_status", event => {
const playlist_id = bot.properties.client_playlist_id;
const property_map = {
"replay_mode": "playlist_replay_mode",
"finished": "playlist_flag_finished",
"delete_played": "playlist_flag_delete_played",
"max_size": "playlist_max_songs"
};
Promise.resolve().then(() => {
if (event.key === "notify_song_change") {
return client.serverConnection.send_command("clientedit", {
clid: bot.clientId(),
client_flag_notify_song_change: event.value
});
} else {
const property = property_map[event.key];
if (!property) return Promise.reject(tr("unknown property"));
const data = {
playlist_id: playlist_id
};
data[property] = event.value;
return client.serverConnection.send_command("playlistedit", data);
}
}).then(() => {
event_registry.fire("set_playlist_status_result", {
status: "success",
key: event.key,
value: event.value
});
}).catch(error => {
event_registry.fire("set_playlist_status_result", {
status: "error",
key: event.key,
error_msg: error_msg(error)
});
logError(LogCategory.CLIENT, tr("Failed to change playlist status %s for playlist %d: %o"), event.key, playlist_id, error);
});
});
event_registry.on("query_bot_status", event => {
setTimeout(() => {
event_registry.fire("bot_status", {
status: "success",
data: {
channel_commander: bot.properties.client_is_channel_commander,
volume: bot.properties.player_volume,
description: bot.properties.client_description,
default_country_code: (
!bot.channelTree ? undefined :
!bot.channelTree.server ? undefined : bot.channelTree.server.properties.virtualserver_country_code) || "DE",
country_code: bot.properties.client_country,
name: bot.properties.client_nickname,
priority_speaker: bot.properties.client_is_priority_speaker,
bot_type: bot.properties.client_bot_type,
client_platform: bot.properties.client_platform,
client_version: bot.properties.client_version,
uptime_mode: bot.properties.client_uptime_mode
}
});
}, 0);
});
event_registry.on("set_bot_status", event => {
const property_map = {
"channel_commander": "client_is_channel_commander",
"volume": "player_volume",
"description": "client_description",
"country_code": "client_country",
"name": "client_nickname",
"priority_speaker": "client_is_priority_speaker",
"bot_type": "client_bot_type",
"client_platform": "client_platform",
"client_version": "client_version",
"uptime_mode": "client_uptime_mode"
};
Promise.resolve().then(() => {
const property = property_map[event.key];
if (!property) return Promise.reject(tr("unknown property"));
const data = {
clid: bot.clientId()
};
data[property] = event.value;
return client.serverConnection.send_command("clientedit", data);
}).then(() => {
event_registry.fire("set_bot_status_result", {
status: "success",
key: event.key,
value: event.value
});
}).catch(error => {
event_registry.fire("set_bot_status_result", {
status: "error",
key: event.key,
error_msg: error_msg(error)
});
logError(LogCategory.CLIENT, tr("Failed to change bot setting %s: %o"), event.key, error);
});
});
}
/* permissions */
{
event_registry.on("query_general_permissions", event => {
const playlist_id = bot.properties.client_playlist_id;
client.permissions.requestPlaylistPermissions(playlist_id).then(result => {
const permissions = {};
for (const permission of result)
if (permission.hasValue())
permissions[permission.type.name] = permission.value;
event_registry.fire("general_permissions", {
status: "success",
permissions: permissions
});
}).catch(error => {
event_registry.fire("general_permissions", {
status: "error",
error_msg: error_msg(error)
});
logError(LogCategory.CLIENT, tr("Failed to query playlist general permissions for playlist %d: %o"), playlist_id, error);
});
});
event_registry.on("set_general_permission", event => {
const playlist_id = bot.properties.client_playlist_id;
client.serverConnection.send_command("playlistaddperm", {
playlist_id: playlist_id,
permsid: event.key,
permvalue: event.value,
permskip: false,
permnegated: false
}).then(() => {
event_registry.fire("set_general_permission_result", {
key: event.key,
status: "success",
value: event.value
});
}).catch(error => {
event_registry.fire("set_general_permission_result", {
status: "error",
key: event.key,
error_msg: error_msg(error)
});
logError(LogCategory.CLIENT, tr("Failed to set playlist general permissions for playlist %d and permission %d: %o"), playlist_id, event.key, error);
});
});
event_registry.on("query_client_permissions", event => {
const playlist_id = bot.properties.client_playlist_id;
const client_id = event.client_database_id;
client.permissions.requestPlaylistClientPermissions(playlist_id, client_id).then(result => {
const permissions = {};
for (const permission of result)
if (permission.hasValue())
permissions[permission.type.name] = permission.value;
event_registry.fire("client_permissions", {
status: "success",
client_database_id: event.client_database_id,
permissions: permissions
});
}).catch(error => {
event_registry.fire("client_permissions", {
status: "error",
client_database_id: event.client_database_id,
error_msg: error_msg(error)
});
logError(LogCategory.CLIENT, tr("Failed to query playlist client permissions for playlist %d and client %d: %o"), playlist_id, client_id, error);
});
});
event_registry.on("set_client_permission", event => {
const playlist_id = bot.properties.client_playlist_id;
const client_id = event.client_database_id;
client.serverConnection.send_command("playlistclientaddperm", {
playlist_id: playlist_id,
cldbid: client_id,
permsid: event.key,
permvalue: event.value,
permskip: false,
permnegated: false
}).then(() => {
event_registry.fire("set_client_permission_result", {
key: event.key,
status: "success",
client_database_id: client_id,
value: event.value
});
}).catch(error => {
event_registry.fire("set_client_permission_result", {
status: "error",
key: event.key,
client_database_id: client_id,
error_msg: error_msg(error)
});
logError(LogCategory.CLIENT, tr("Failed to set playlist client permissions for playlist %d, permission %d and client id %d: %o"), playlist_id, event.key, client_id, error);
});
});
event_registry.on("query_special_clients", event => {
const playlist_id = bot.properties.client_playlist_id;
client.serverConnection.command_helper.request_playlist_client_list(playlist_id).then(clients => {
return client.serverConnection.command_helper.getInfoFromClientDatabaseId(...clients);
}).then(clients => {
event_registry.fire("special_client_list", {
status: "success",
clients: clients.map(e => {
return {
name: e.clientNickname,
unique_id: e.clientUniqueId,
database_id: e.clientDatabaseId
}
})
});
}).catch(error => {
event_registry.fire("special_client_list", {
status: "error",
error_msg: error_msg(error)
});
logError(LogCategory.CLIENT, tr("Failed to query special client list for playlist %d: %o"), playlist_id, error);
})
});
event_registry.on("search_client", event => {
if (!event.text) return;
const text = event.text;
Promise.resolve().then(() => {
let is_uuid = false;
try {
is_uuid = atob(text).length === 32;
} catch (e) {
}
if (is_uuid) {
return client.serverConnection.command_helper.getInfoFromUniqueId(text);
} else if (text.match(/^[0-9]{1,7}$/) && !isNaN(parseInt(text))) {
return client.serverConnection.command_helper.getInfoFromClientDatabaseId(parseInt(text));
} else {
//TODO: Database name lookup?
return Promise.reject("no results");
}
}).then(result => {
if (result.length) {
const client = result[0];
event_registry.fire("search_client_result", {
status: "success",
client: {
name: client.clientNickname,
unique_id: client.clientUniqueId,
database_id: client.clientDatabaseId
}
});
} else {
event_registry.fire("search_client_result", {
status: "empty"
});
}
}).catch(error => {
event_registry.fire("search_client_result", {
status: "error",
error_msg: error_msg(error)
});
logError(LogCategory.CLIENT, tr("Failed to lookup search text \"%s\": %o"), text, error);
});
});
event_registry.on("query_group_permissions", event => {
client.permissions.find_permission(event.permission_name).then(result => {
let groups = [];
for (const e of result) {
if (e.type !== "server_group") continue;
const group = client.groups.findServerGroup((e as ServerGroup).group_id);
if (!group) continue;
groups.push({
name: group.name,
value: e.value,
id: group.id
});
}
event_registry.fire("group_permissions", {
status: "success",
groups: groups,
permission_name: event.permission_name
});
}).catch(error => {
event_registry.fire("group_permissions", {
status: "error",
error_msg: error_msg(error),
permission_name: event.permission_name
});
logError(LogCategory.CLIENT, tr("Failed to execute permfind for permission %s: %o"), event.permission_name, error);
});
});
}
}
function dummy_controller(event_registry: Registry<music_manage>) {
/* settings */
{
event_registry.on("query_bot_status", event => {
setTimeout(() => {
event_registry.fire("bot_status", {
status: "success",
data: {
name: "Another TeaSpeak bot",
country_code: "DE",
default_country_code: "GB",
channel_commander: false,
description: "Hello World",
priority_speaker: true,
volume: 66,
uptime_mode: 0,
client_version: "Version",
client_platform: "Platform",
bot_type: 0
}
})
});
});
event_registry.on("query_playlist_status", event => {
setTimeout(() => {
event_registry.fire("playlist_status", {
status: "success",
data: {
max_size: 55,
notify_song_change: true,
delete_played: false,
finished: false,
replay_mode: 2
}
})
});
});
}
/* permissions */
{
event_registry.on("query_special_clients", event => {
setTimeout(() => {
event_registry.fire("special_client_list", {
status: "success",
clients: [{
name: "WolverinDEV",
database_id: 1,
unique_id: "abd"
}, {
name: "WolverinDEV 2",
database_id: 2,
unique_id: "abd1"
}, {
name: "WolverinDEV 3",
database_id: 3,
unique_id: "abd1"
}]
});
}, 0);
});
event_registry.on("query_group_permissions", event => {
setTimeout(() => {
event_registry.fire("group_permissions", {
status: "success",
groups: [{
value: 20,
name: "Server Admin p:20",
id: 0
}, {
value: 10,
name: "Server Mod p:10",
id: 0
}],
permission_name: event.permission_name
});
}, 0);
});
event_registry.on("query_general_permissions", event => {
setTimeout(() => {
event_registry.fire("general_permissions", {
status: "success",
permissions: {
i_playlist_song_needed_add_power: 77
}
})
}, 0);
});
event_registry.on("set_general_permission", event => {
setTimeout(() => {
event_registry.fire("set_general_permission_result", {
key: event.key,
value: event.value,
status: "success"
});
});
});
event_registry.on("query_client_permissions", event => {
setTimeout(() => {
event_registry.fire("client_permissions", {
client_database_id: event.client_database_id,
status: "success",
permissions: {
i_playlist_song_needed_add_power: 77
}
})
}, 500);
});
event_registry.on("set_client_permission", event => {
setTimeout(() => {
event_registry.fire("set_client_permission_result", {
key: event.key,
client_database_id: event.client_database_id,
status: "success",
value: event.value
})
}, 500);
});
}
}
function build_modal(event_registry: Registry<music_manage>): JQuery<HTMLElement> {
const tag = $("#tmpl_music_manage").renderTag();
const container_settings = tag.find(".body > .category-settings");
build_settings_container(event_registry, container_settings);
const container_permissions = tag.find(".body > .category-permissions");
build_permission_container(event_registry, container_permissions);
/* general switch */
{
let shown_container: "settings" | "permissions";
const header = tag.find(".header");
const category_permissions = header.find(".category-permissions");
event_registry.on("show_container", data => {
category_permissions.toggleClass("selected", data.container === "permissions");
container_permissions.toggleClass("hidden", data.container !== "permissions");
});
category_permissions.on('click', event => {
if (shown_container === "permissions") return;
event_registry.fire("show_container", {container: "permissions"});
});
const category_settings = header.find(".category-settings");
event_registry.on("show_container", data => {
category_settings.toggleClass("selected", data.container === "settings");
container_settings.toggleClass("hidden", data.container !== "settings");
});
category_settings.on('click', event => {
if (shown_container === "settings") return;
event_registry.fire("show_container", {container: "settings"});
});
event_registry.on("show_container", data => shown_container = data.container);
}
/* input length fix */
tag.find("input[maxlength]").on("input", event => {
const input = event.target as HTMLInputElement;
const max = parseInt(input.getAttribute("maxlength"));
const text = input.value;
if (!isNaN(max) && text && text.length > max)
//input.value = text.substr(text.length - max);
input.value = text.substr(0, max);
});
/* initialize */
event_registry.fire("show_container", {container: "settings"});
return tag.children();
}
function build_settings_container(event_registry: Registry<music_manage>, tag: JQuery<HTMLElement>) {
const show_change_error = (header, message) => {
createErrorModal(tr("Failed to change value"), header + "<br>" + message).open();
};
/* music bot settings */
{
const container = tag.find(".settings-bot");
/* bot name */
{
const input = container.find(".option-bot-name");
let last_value = undefined;
event_registry.on("query_bot_status", event => {
last_value = undefined;
input
.prop("disabled", true)
.val(null)
.attr("placeholder", tr("loading..."));
});
event_registry.on("bot_status", event => {
if (event.status === "error")
input
.prop("disabled", true)
.val(null)
.attr("placeholder", event.error_msg || tr("error while loading"));
else
input
.prop("disabled", false)
.attr("placeholder", null)
.val(last_value = event.data.name);
});
event_registry.on("set_bot_status_result", event => {
if (event.key !== "name") return;
if (event.status !== "success")
show_change_error(tr("Failed to set bot name"), event.error_msg || tr("timeout"));
else
last_value = event.value;
input
.prop("disabled", false)
.attr("placeholder", null)
.val(last_value);
});
input.on("keyup", event => event.key === "Enter" && input.trigger("focusout"));
input.on("focusout", event => {
const value = input.val() as string;
if (value === last_value) return;
if (!value) {
input.val(last_value);
return;
}
input
.prop("disabled", true)
.val(null)
.attr("placeholder", tr("applying..."));
event_registry.fire("set_bot_status", {
key: "name",
value: value
});
});
}
/* country flag */
{
const input = container.find(".option-bot-country");
const flag = container.find(".container-country .country");
let last_value = undefined, fallback_country = undefined;
const update_country_code = input => {
input = input || fallback_country || "ts";
flag.each((_, e) => {
for (const [index, klass] of e.classList.entries())
if (klass.startsWith("flag-"))
e.classList.remove(klass);
});
flag.addClass("flag-" + input.toLowerCase());
flag.attr("title", i18nc.getCountryName(input, tr("Unknown country")));
};
event_registry.on("query_bot_status", event => {
last_value = undefined;
input
.prop("disabled", true)
.val(null)
.attr("placeholder", "...");
update_country_code("ts");
});
event_registry.on("bot_status", event => {
if (event.status === "error")
input
.prop("disabled", true)
.val(null)
.attr("placeholder", "err");
else {
input
.prop("disabled", false)
.attr("placeholder", null)
.val(last_value = event.data.country_code);
fallback_country = event.data.default_country_code;
}
update_country_code(last_value);
});
event_registry.on("set_bot_status_result", event => {
if (event.key !== "country_code") return;
if (event.status !== "success")
show_change_error(tr("Failed to set bots country"), event.error_msg || tr("timeout"));
else
last_value = event.value;
input
.prop("disabled", false)
.attr("placeholder", null)
.val(last_value);
update_country_code(last_value);
});
input.on("input", () => {
update_country_code(input.val());
input.firstParent(".input-boxed").removeClass("is-invalid");
});
input.on("keyup", event => event.key === "Enter" && input.trigger("focusout"));
input.on("focusout", event => {
const value = input.val() as string;
if (value === last_value) return;
if (value && value.length != 2) {
input.firstParent(".input-boxed").addClass("is-invalid");
return;
}
input
.prop("disabled", true)
.val(null)
.attr("placeholder", "...");
event_registry.fire("set_bot_status", {
key: "country_code",
value: value
});
});
}
/* flag channel commander */
{
const input = container.find(".option-channel-commander") as JQuery<HTMLInputElement>;
const label = input.parents("label");
let last_value = undefined;
event_registry.on("query_bot_status", event => {
last_value = undefined;
label.addClass("disabled");
input
.prop("checked", false)
.prop("disabled", true);
});
event_registry.on("bot_status", event => {
if (event.status === "error") {
label.addClass("disabled");
input
.prop("checked", false)
.prop("disabled", true);
} else {
label.removeClass("disabled");
input
.prop("checked", last_value = event.data.channel_commander)
.prop("disabled", false);
}
});
event_registry.on("set_bot_status_result", event => {
if (event.key !== "channel_commander") return;
if (event.status !== "success")
show_change_error(tr("Failed to change channel commander state"), event.error_msg || tr("timeout"));
else
last_value = event.value;
label.removeClass("disabled");
input
.prop("checked", last_value)
.prop("disabled", false);
});
input.on("change", event => {
label.addClass("disabled");
input.prop("disabled", true);
event_registry.fire("set_bot_status", {
key: "channel_commander",
value: input.prop("checked")
});
});
}
/* flag priority speaker */
{
const input = container.find(".option-priority-speaker") as JQuery<HTMLInputElement>;
const label = input.parents("label");
let last_value = undefined;
event_registry.on("query_bot_status", event => {
last_value = undefined;
label.addClass("disabled");
input
.prop("checked", false)
.prop("disabled", true);
});
event_registry.on("bot_status", event => {
if (event.status === "error") {
label.addClass("disabled");
input
.prop("checked", false)
.prop("disabled", true);
} else {
label.removeClass("disabled");
input
.prop("checked", last_value = event.data.priority_speaker)
.prop("disabled", false);
}
});
event_registry.on("set_bot_status_result", event => {
if (event.key !== "priority_speaker") return;
if (event.status !== "success")
show_change_error(tr("Failed to change priority speaker state"), event.error_msg || tr("timeout"));
else
last_value = event.value;
label.removeClass("disabled");
input
.prop("checked", last_value)
.prop("disabled", false);
});
input.on("change", event => {
label.addClass("disabled");
input.prop("disabled", true);
event_registry.fire("set_bot_status", {
key: "priority_speaker",
value: input.prop("checked")
});
});
}
/* status load timeout */
{
let timeout;
event_registry.on("query_bot_status", event => {
timeout = setTimeout(() => {
event_registry.fire("bot_status", {
status: "error",
error_msg: tr("load timeout")
});
}, 5000);
});
event_registry.on("bot_status", event => clearTimeout(timeout));
}
/* set status timeout */
{
let timeouts: { [key: string]: any } = {};
event_registry.on("set_bot_status", event => {
clearTimeout(timeouts[event.key]);
timeouts[event.key] = setTimeout(() => {
event_registry.fire("set_bot_status_result", {
status: "timeout",
key: event.key,
});
}, 5000);
});
event_registry.on("set_bot_status_result", event => {
clearTimeout(timeouts[event.key]);
delete timeouts[event.key];
});
}
}
/* music bot settings */
{
const container = tag.find(".settings-playlist");
/* playlist replay mode */
{
const input = container.find(".option-replay-mode") as JQuery<HTMLSelectElement>;
let last_value = undefined;
const update_value = text => {
if (text) {
input.prop("disabled", true).addClass("disabled");
input.val("-1");
input.find("option[value=-1]").text(text);
} else if (last_value >= 0 && last_value <= 3) {
input
.prop("disabled", false)
.removeClass("disabled");
input.val(last_value);
} else {
update_value(tr("invalid value"));
}
};
event_registry.on("query_playlist_status", event => {
last_value = undefined;
update_value(tr("loading..."));
});
event_registry.on("playlist_status", event => {
if (event.status === "error") {
update_value(event.error_msg || tr("error while loading"));
} else {
last_value = event.data.replay_mode;
update_value(undefined);
}
});
event_registry.on("set_playlist_status_result", event => {
if (event.key !== "replay_mode") return;
if (event.status !== "success")
show_change_error(tr("Failed to change replay mode"), event.error_msg || tr("timeout"));
else
last_value = event.value;
update_value(undefined);
});
input.on("keyup", event => event.key === "Enter" && input.trigger("focusout"));
input.on("change", event => {
const value = parseInt(input.val() as string);
if (isNaN(value)) return;
update_value(tr("applying..."));
event_registry.fire("set_playlist_status", {
key: "replay_mode",
value: value
});
});
}
/* playlist max size */
{
const input = container.find(".container-max-playlist-size input");
let last_value = undefined;
event_registry.on("query_playlist_status", event => {
last_value = undefined;
input
.prop("disabled", true)
.val(null)
.attr("placeholder", tr("loading..."))
.firstParent(".input-boxed").addClass("disabled");
});
event_registry.on("playlist_status", event => {
if (event.status === "error")
input
.prop("disabled", true)
.val(null)
.attr("placeholder", event.error_msg || tr("error while loading"))
.firstParent(".input-boxed").addClass("disabled");
else
input
.prop("disabled", false)
.attr("placeholder", null)
.val((last_value = event.data.max_size).toString())
.firstParent(".input-boxed").removeClass("disabled");
});
event_registry.on("set_playlist_status_result", event => {
if (event.key !== "max_size") return;
if (event.status !== "success")
show_change_error(tr("Failed to change max playlist size"), event.error_msg || tr("timeout"));
else
last_value = event.value;
input
.prop("disabled", false)
.attr("placeholder", null)
.val(last_value)
.firstParent(".input-boxed").removeClass("disabled");
});
input.on("input", event => input.parentsUntil(".input-boxed").removeClass("is-invalid"));
input.on("keyup", event => event.key === "Enter" && input.trigger("focusout"));
input.on("focusout", event => {
const value = input.val() as string;
if (value === last_value) return;
if (value === "") {
input.val(last_value);
return;
}
if (isNaN(parseInt(value))) {
input.parentsUntil(".input-boxed").addClass("is-invalid");
return;
}
input
.prop("disabled", true)
.val(null)
.attr("placeholder", tr("applying..."))
.firstParent(".input-boxed").addClass("disabled");
event_registry.fire("set_playlist_status", {
key: "max_size",
value: parseInt(value)
});
});
}
/* flag delete played */
{
const input = container.find(".option-delete-played-songs") as JQuery<HTMLInputElement>;
const label = input.parents("label");
let last_value = undefined;
event_registry.on("query_playlist_status", event => {
last_value = undefined;
label.addClass("disabled");
input
.prop("checked", false)
.prop("disabled", true);
});
event_registry.on("playlist_status", event => {
if (event.status === "error") {
label.addClass("disabled");
input
.prop("checked", false)
.prop("disabled", true);
} else {
label.removeClass("disabled");
input
.prop("checked", last_value = event.data.delete_played)
.prop("disabled", false);
}
});
event_registry.on("set_playlist_status_result", event => {
if (event.key !== "delete_played") return;
if (event.status !== "success")
show_change_error(tr("Failed to change delete state"), event.error_msg || tr("timeout"));
else
last_value = event.value;
label.removeClass("disabled");
input
.prop("checked", last_value)
.prop("disabled", false);
});
input.on("change", event => {
label.addClass("disabled");
input.prop("disabled", true);
event_registry.fire("set_playlist_status", {
key: "delete_played",
value: input.prop("checked")
});
});
}
/* flag notify song change */
{
const input = container.find(".option-notify-songs-change") as JQuery<HTMLInputElement>;
const label = input.parents("label");
let last_value = undefined;
event_registry.on("query_playlist_status", event => {
last_value = undefined;
label.addClass("disabled");
input
.prop("checked", false)
.prop("disabled", true);
});
event_registry.on("playlist_status", event => {
if (event.status === "error") {
label.addClass("disabled");
input
.prop("checked", false)
.prop("disabled", true);
} else {
label.removeClass("disabled");
input
.prop("checked", last_value = event.data.notify_song_change)
.prop("disabled", false);
}
});
event_registry.on("set_playlist_status_result", event => {
if (event.key !== "notify_song_change") return;
if (event.status !== "success")
show_change_error(tr("Failed to change notify state"), event.error_msg || tr("timeout"));
else
last_value = event.value;
label.removeClass("disabled");
input
.prop("checked", last_value)
.prop("disabled", false);
});
input.on("change", event => {
label.addClass("disabled");
input.prop("disabled", true);
event_registry.fire("set_playlist_status", {
key: "notify_song_change",
value: input.prop("checked")
});
});
}
/* status load timeout */
{
let timeout;
event_registry.on("query_playlist_status", event => {
timeout = setTimeout(() => {
event_registry.fire("playlist_status", {
status: "error",
error_msg: tr("load timeout")
});
}, 5000);
});
event_registry.on("playlist_status", event => clearTimeout(timeout));
}
/* set status timeout */
{
let timeouts: { [key: string]: any } = {};
event_registry.on("set_playlist_status", event => {
clearTimeout(timeouts[event.key]);
timeouts[event.key] = setTimeout(() => {
event_registry.fire("set_playlist_status_result", {
status: "timeout",
key: event.key,
});
}, 5000);
});
event_registry.on("set_playlist_status_result", event => {
clearTimeout(timeouts[event.key]);
delete timeouts[event.key];
});
}
}
/* reload button */
{
const button = tag.find(".button-reload");
let timeout;
event_registry.on(["query_bot_status", "query_playlist_status"], event => {
button.prop("disabled", true);
clearTimeout(timeout);
timeout = setTimeout(() => {
button.prop("disabled", false);
}, 1000);
});
button.on("click", event => {
event_registry.fire("query_bot_status");
event_registry.fire("query_playlist_status");
});
}
tooltip.initialize(tag);
/* initialize on show */
{
let initialized = false;
event_registry.on("show_container", event => {
if (event.container !== "settings" || initialized) return;
initialized = true;
event_registry.fire("query_bot_status");
event_registry.fire("query_playlist_status");
});
}
}
function build_permission_container(event_registry: Registry<music_manage>, tag: JQuery<HTMLElement>) {
/* client search mechanism */
{
const container = tag.find(".table-head .column-client-specific .client-select");
let list_shown = false;
/* search list show/hide */
{
const button_list_clients = container.find(".button-list-clients");
button_list_clients.on('click', event =>
event_registry.fire(list_shown ? "hide_client_list" : "show_client_list"));
event_registry.on("show_client_list", () => {
list_shown = true;
button_list_clients.text(tr("Hide clients"));
});
event_registry.on("hide_client_list", () => {
list_shown = false;
button_list_clients.text(tr("List clients"));
});
}
/* the search box */
{
const input_search = container.find(".input-search");
const button_search = container.find(".button-search");
let search_timeout;
let last_query;
input_search.on('keyup', event => {
const text = input_search.val() as string;
if (text === last_query) return;
if (text)
event_registry.fire("filter_client_list", {filter: text});
else
event_registry.fire("filter_client_list", {filter: undefined});
input_search.toggleClass("is-invalid", !list_shown && text === last_query);
if (!list_shown) {
button_search.prop("disabled", !text || !!search_timeout);
} else {
last_query = text;
}
});
input_search.on('keydown', event => {
if (event.key === "Enter" && !list_shown && !button_search.prop("disabled"))
button_search.trigger("click");
});
event_registry.on("show_client_list", () => {
button_search.prop("disabled", true);
input_search.attr("placeholder", tr("Search client list"));
});
event_registry.on("hide_client_list", () => {
button_search.prop("disabled", !input_search.val() || !!search_timeout);
input_search.attr("placeholder", tr("Client uid or database id"));
});
button_search.on("click", event => {
button_search.prop("disabled", true);
input_search.blur();
const text = input_search.val() as string;
last_query = text;
event_registry.fire("search_client", {
text: text
});
search_timeout = setTimeout(() => event_registry.fire("search_client_result", {
status: "timeout"
}), 5000);
});
event_registry.on("search_client_result", event => {
clearTimeout(search_timeout);
search_timeout = 0;
button_search.prop("disabled", !input_search.val());
if (event.status === "timeout") {
createErrorModal(tr("Client search failed"), tr("Failed to perform client search.<br>Search resulted in a timeout.")).open();
return;
} else if (event.status === "error" || event.status === "empty") {
//TODO: Display the error somehow?
input_search.addClass("is-invalid");
return;
} else {
event_registry.fire("special_client_set", {
client: event.client
});
}
});
}
/* the client list */
{
const container = tag.find(".overlay-client-list");
event_registry.on("show_client_list", () => container.removeClass("hidden"));
event_registry.on("hide_client_list", () => container.addClass("hidden"));
const button_refresh = container.find(".button-clientlist-refresh");
const container_entries = container.find(".container-client-list");
event_registry.on("special_client_list", data => {
button_refresh.prop("disabled", false);
container.find(".overlay").addClass("hidden");
if (data.status === "error-permission") {
const overlay = container.find(".overlay-query-error-permissions");
overlay.find("a").text(tr("Insufficient permissions"));
overlay.removeClass("hidden");
} else if (data.status === "success") {
container_entries.find(".client").remove(); /* clear */
if (!data.clients.length) {
const overlay = container.find(".overlay-empty-list");
overlay.removeClass("hidden");
} else {
for (const client of data.clients) {
const tag = $.spawn("div").addClass("client").append(
htmltags.generate_client_object({
add_braces: false,
client_id: 0,
client_database_id: client.database_id,
client_name: client.name,
client_unique_id: client.unique_id
})
);
tag.on('dblclick', event => event_registry.fire("special_client_set", {client: client}));
tag.attr("x-filter", client.database_id + "_" + client.name + "_" + client.unique_id);
container_entries.append(tag);
}
}
} else {
const overlay = container.find(".overlay-query-error");
overlay.find("a").text(data.error_msg ? data.error_msg : tr("query failed"));
overlay.removeClass("hidden");
}
});
/* refresh button */
button_refresh.on('click', event => {
button_refresh.prop("disabled", true);
event_registry.fire("query_special_clients");
});
/* special client list query timeout handler */
{
let query_timeout;
event_registry.on("query_special_clients", event => {
query_timeout = setTimeout(() => {
event_registry.fire("special_client_list", {
status: "error",
error_msg: tr("Query timeout")
});
}, 5000);
});
event_registry.on("special_client_list", event => clearTimeout(query_timeout));
}
/* first time client list show */
{
let shown;
event_registry.on('show_client_list', event => {
if (shown) return;
shown = true;
event_registry.fire("query_special_clients");
});
}
/* the client list filter */
{
let filter;
const overlay = container.find(".overlay-filter-no-result");
const update_filter = () => {
let shown = 0, hidden = 0;
container_entries.find(".client").each(function () {
const text = this.getAttribute("x-filter");
if (!filter || text.toLowerCase().indexOf(filter) != -1) {
this.classList.remove("hidden");
shown++;
} else {
this.classList.add("hidden");
hidden++;
}
});
if (shown == 0 && hidden == 0) return;
overlay.toggleClass("hidden", shown != 0);
};
event_registry.on("special_client_list", event => update_filter());
event_registry.on("filter_client_list", event => {
filter = (event.filter || "").toLowerCase();
update_filter();
});
}
}
event_registry.on("special_client_set", event => {
container.toggleClass("hidden", !!event.client);
event_registry.fire("hide_client_list");
});
}
/* the client info */
{
const container = tag.find(".table-head .column-client-specific .client-info");
container.find(".button-client-deselect").on("click", event => {
event_registry.fire("special_client_set", {client: undefined});
});
event_registry.on("special_client_set", event => {
container.toggleClass("hidden", !event.client);
const client_container = container.find(".container-selected-client");
client_container.find(".htmltag-client").remove();
if (event.client) {
client_container.append(htmltags.generate_client_object({
client_unique_id: event.client.unique_id,
client_name: event.client.name,
client_id: 0,
client_database_id: event.client.database_id,
add_braces: false
}));
}
});
}
const power_needed_map = {
i_client_music_rename_power: "i_client_music_needed_rename_power",
i_client_music_modify_power: "i_client_music_needed_modify_power",
i_client_music_delete_power: "i_client_music_needed_delete_power",
i_playlist_view_power: "i_playlist_needed_view_power",
i_playlist_modify_power: "i_playlist_needed_modify_power",
i_playlist_permission_modify_power: "i_playlist_needed_permission_modify_power",
i_playlist_song_add_power: "i_playlist_song_needed_add_power",
i_playlist_song_move_power: "i_playlist_song_needed_move_power",
i_playlist_song_remove_power: "i_playlist_song_needed_remove_power",
b_virtualserver_playlist_permission_list: "b_virtualserver_playlist_permission_list"
};
const needed_power_map = Object.entries(power_needed_map).reduce((ret, entry) => {
const [key, value] = entry;
ret[value] = key;
return ret;
}, {});
/* general permissions */
{
/* permission input functionality */
{
tag.find(".general-permission").each((_, _e) => {
const elem = $(_e) as JQuery<HTMLDivElement>;
const permission_name = elem.attr("x-permission");
if (!permission_name) return;
const input = elem.find("input");
input.attr("maxlength", 6);
let last_sync_value = undefined;
event_registry.on("query_general_permissions", event => {
input.prop("disabled", true).val(null);
input.attr("placeholder", tr("loading..."));
});
event_registry.on("general_permissions", event => {
input.prop("disabled", true).val(null);
if (event.status === "timeout") {
input.attr("placeholder", tr("load timeout"));
} else if (event.status === "success") {
input.prop("disabled", false); //TODO: Check permissions?
input.attr("placeholder", null);
const value = event.permissions ? event.permissions[permission_name] || 0 : 0;
last_sync_value = value;
input.val(value);
} else {
input.attr("placeholder", event.error_msg || tr("load error"));
}
});
event_registry.on("set_general_permission_result", event => {
if (event.key !== permission_name) return;
input.prop("disabled", false); //TODO: Check permissions?
input.attr("placeholder", null);
if (event.status === "success") {
input.val(event.value);
last_sync_value = event.value;
} else if (event.status === "error") {
if (typeof last_sync_value === "number") input.val(last_sync_value);
createErrorModal(tr("Failed to change permission"), tra("Failed to change permission:\n{}", event.error_msg)).open();
}
});
input.on("focusout", event => {
if (input.prop("disabled")) return;
const value = parseInt(input.val() as string);
if (value === last_sync_value) return;
input.prop("disabled", true).val(null);
input.attr("placeholder", tr("applying..."));
event_registry.fire("set_general_permission", {
key: permission_name,
value: value || 0
});
});
input.on("keyup", event => event.key === "Enter" && input.blur());
});
}
/* the tooltip functionality */
{
tag.find(".general-permission").each((_, _e) => {
const elem = $(_e) as JQuery<HTMLDivElement>;
const permission_name = elem.attr("x-permission");
if (!permission_name) return;
const required_power = needed_power_map[permission_name];
if (!required_power) return;
let last_sync_value = undefined;
let current_tag: JQuery;
let loading = false;
let query_result: {
status: "error" | "timeout" | "success"
groups?: {
name: string,
value: number,
id: number
}[],
error_msg?: string
};
event_registry.on("general_permissions", event => {
if (event.status === "success")
last_sync_value = event.permissions ? event.permissions[permission_name] || 0 : 0;
});
event_registry.on("set_general_permission_result", event => {
if (event.key !== permission_name) return;
if (event.status === "success")
last_sync_value = event.value;
});
event_registry.on("refresh_permissions", event => {
query_result = undefined; /* require for the next time */
});
const show_query_result = () => {
if (!current_tag) return;
const container_groups = current_tag.find(".container-groups");
container_groups.children().remove();
current_tag.find(".container-status").addClass("hidden");
if (loading) {
current_tag.find(".status-loading").removeClass("hidden");
} else if (!query_result || query_result.status === "error") {
current_tag
.find(".status-error").removeClass("hidden")
.text((query_result ? query_result.error_msg : "") || tr("failed to query data"));
} else if (query_result.status === "timeout") {
current_tag
.find(".status-error").removeClass("hidden")
.text(tr("timeout while loading"));
} else {
let count = 0;
for (const group of (query_result.groups || [])) {
if (group.value !== -1 && group.value < last_sync_value) continue;
count++;
container_groups.append($.spawn("div").addClass("group").text(
" - " + group.name + " (" + group.id + ")"
));
}
if (count === 0) current_tag.find(".status-no-groups").removeClass("hidden");
}
};
tooltip.initialize(elem, {
on_show(tag: JQuery<HTMLElement>) {
current_tag = tag;
if (!query_result && !loading) {
event_registry.fire("query_group_permissions", {
permission_name: required_power
});
loading = true;
}
show_query_result();
},
on_hide(tag: JQuery<HTMLElement>) {
current_tag = undefined;
}
});
event_registry.on("group_permissions", event => {
if (event.permission_name !== required_power) return;
loading = false;
query_result = event;
show_query_result();
});
});
/* refresh mechanism */
{
event_registry.on("refresh_permissions", event => event_registry.fire("query_general_permissions"));
}
}
/* permission set timeout */
{
let permission_timers: { [key: string]: any } = {};
event_registry.on("set_general_permission", event => {
if (permission_timers[event.key])
clearTimeout(permission_timers[event.key]);
permission_timers[event.key] = setTimeout(() => {
event_registry.fire("set_general_permission_result", {
key: event.key,
status: "error",
error_msg: tr("controller timeout")
});
}, 5000);
});
event_registry.on("set_general_permission_result", event => {
clearTimeout(permission_timers[event.key]);
delete permission_timers[event.key];
});
}
/* group query timeout */
{
let timers: { [key: string]: any } = {};
event_registry.on("query_group_permissions", event => {
if (timers[event.permission_name])
clearTimeout(timers[event.permission_name]);
timers[event.permission_name] = setTimeout(() => {
event_registry.fire("group_permissions", {
permission_name: event.permission_name,
status: "timeout"
});
}, 5000);
});
event_registry.on("group_permissions", event => {
clearTimeout(timers[event.permission_name]);
delete timers[event.permission_name];
});
}
/* query timeout */
{
let query_timeout;
event_registry.on("query_general_permissions", event => {
clearTimeout(query_timeout);
query_timeout = setTimeout(() => {
event_registry.fire("general_permissions", {
status: "timeout"
});
}, 5000);
});
event_registry.on("general_permissions", event => clearTimeout(query_timeout));
}
/* refresh button */
{
const button = tag.find(".button-permission-refresh");
let refresh_timer;
let loading_client_permissions = false;
let loading_general_permissions = false;
const update_button = () =>
button.prop("disabled", refresh_timer || loading_client_permissions || loading_general_permissions);
event_registry.on("query_general_permissions", event => {
loading_general_permissions = true;
update_button();
});
event_registry.on("general_permissions", event => {
loading_general_permissions = false;
update_button();
});
event_registry.on("query_client_permissions", event => {
loading_client_permissions = true;
update_button();
});
event_registry.on("client_permissions", event => {
loading_client_permissions = false;
update_button();
});
button.on('click', event => {
event_registry.fire("refresh_permissions");
/* allow refreshes only every second */
refresh_timer = setTimeout(() => {
refresh_timer = undefined;
update_button();
}, 1000);
});
}
}
/* client specific permissions */
{
const container = tag.find(".column-client-specific");
let client_database_id = 0;
let needed_permissions: { [key: string]: number } = {};
/* needed permissions updater */
{
event_registry.on("general_permissions", event => {
if (event.status !== "success") return;
needed_permissions = event.permissions;
});
event_registry.on("set_general_permission_result", event => {
if (event.status !== "success") return;
needed_permissions[event.key] = event.value;
});
}
event_registry.on("special_client_set", event => {
client_database_id = event.client ? event.client.database_id : 0;
container.find(".client-permission").toggleClass("hidden", !event.client);
if (client_database_id)
event_registry.fire("query_client_permissions", {client_database_id: client_database_id});
});
const enabled_class = "client-apply";
const disabled_class = "client-delete";
container.find(".client-permission").each((_, _e) => {
const elem = $(_e);
const input = elem.find("input");
const status_indicator = elem.find(".icon_em");
const permission_name = elem.attr("x-permission") as string;
const permission_needed_name = power_needed_map[permission_name];
let last_sync_value = undefined;
let hide_indicator = false;
if (typeof permission_needed_name !== "string") {
logWarn(LogCategory.GENERAL, tr("Missing permission needed mapping for %s"), permission_name);
return;
}
const update_indicator = () => {
const value = parseInt(input.val() as string);
const needed = typeof needed_permissions[permission_needed_name] === "number" ? needed_permissions[permission_needed_name] : 0;
const flag = value == -1 ? true : isNaN(value) || value == 0 ? false : value >= needed;
status_indicator.toggle(!hide_indicator);
status_indicator.toggleClass(enabled_class, flag).toggleClass(disabled_class, !flag);
};
event_registry.on("special_client_set", event => {
last_sync_value = undefined;
});
event_registry.on("general_permissions", event => update_indicator());
event_registry.on("set_general_permission_result", event => {
if (event.key !== permission_needed_name) return;
if (event.status !== "success") return;
update_indicator();
});
/* loading the permission */
event_registry.on("query_client_permissions", event => {
if (event.client_database_id !== client_database_id) return;
last_sync_value = undefined;
hide_indicator = true;
input.prop("disabled", true).val(null);
input.attr("placeholder", tr("loading..."));
update_indicator();
});
event_registry.on('client_permissions', event => {
if (event.client_database_id !== client_database_id) return;
hide_indicator = false;
input.prop("disabled", true).val(null);
if (event.status === "timeout") {
input.attr("placeholder", tr("load timeout"));
} else if (event.status === "success") {
input.prop("disabled", false); //TODO: Check permissions?
input.attr("placeholder", null);
const value = event.permissions ? event.permissions[permission_name] || 0 : 0;
last_sync_value = value;
input.val(value);
} else {
input.attr("placeholder", event.error_msg || tr("load error"));
}
update_indicator();
});
/* permission editing */
input.attr("maxlength", 6);
input.on("focusout", event => {
if (!client_database_id) return;
const value = parseInt(input.val() as string);
if (value === last_sync_value) return;
input.prop("disabled", true).val(null);
input.attr("placeholder", tr("applying..."));
event_registry.fire("set_client_permission", {
client_database_id: client_database_id,
key: permission_name,
value: value || 0
});
hide_indicator = true;
update_indicator();
});
input.on("change", () => update_indicator());
input.on("keyup", event => event.key === "Enter" && input.blur());
event_registry.on("set_client_permission_result", event => {
if (event.key !== permission_name) return;
input.prop("disabled", false); //TODO: Check permissions?
input.attr("placeholder", null);
if (event.status === "success") {
input.val(event.value);
last_sync_value = event.value;
} else if (event.status === "error") {
if (typeof last_sync_value === "number") input.val(last_sync_value);
createErrorModal(tr("Failed to change permission"), tra("Failed to change permission:\n{}", event.error_msg)).open();
}
hide_indicator = false;
update_indicator();
});
});
/* client permission query timeout */
{
let timeout: { [key: number]: any } = {};
event_registry.on("query_client_permissions", event => {
if (timeout[event.client_database_id])
clearTimeout(timeout[event.client_database_id]);
timeout[event.client_database_id] = setTimeout(() => {
event_registry.fire("client_permissions", {
status: "timeout",
client_database_id: event.client_database_id
});
}, 5000);
});
event_registry.on("client_permissions", event => {
clearTimeout(timeout[event.client_database_id]);
});
}
/* client permission set timeout */
{
let timeout: { [key: string]: any } = {};
event_registry.on("set_client_permission", event => {
const key = event.client_database_id + "_" + event.key;
if (timeout[key])
clearTimeout(timeout[key]);
timeout[key] = setTimeout(() => {
event_registry.fire("set_client_permission_result", {
key: event.key,
status: "error",
client_database_id: event.client_database_id,
error_msg: tr("timeout")
});
}, 5000);
});
event_registry.on("set_client_permission_result", event => {
const key = event.client_database_id + "_" + event.key;
if (timeout[key]) {
clearTimeout(timeout[key]);
delete timeout[key];
}
});
}
event_registry.on("refresh_permissions", event => {
if (client_database_id)
event_registry.fire("query_client_permissions", {client_database_id: client_database_id});
});
tooltip.initialize(container);
}
/* a title attribute for permission column */
tag.find(".table-body .column-permission a").each(function () {
this.setAttribute("title", this.textContent);
});
/* initialize on show */
{
let initialized = false;
event_registry.on("show_container", event => {
if (event.container !== "permissions" || initialized) return;
initialized = true;
event_registry.fire("special_client_set", {client: undefined});
event_registry.fire("query_general_permissions", {});
});
}
}