namespace Modals { export function spawnSettingsModal(default_page?: string) : Modal { let modal: Modal; modal = createModal({ header: tr("Settings"), body: () => { const tag = $("#tmpl_settings").renderTag().dividerfy(); /* general "tab" mechanic */ const left = tag.find("> .left"); const right = tag.find("> .right"); { left.find(".entry:not(.group)").on('click', event => { const entry = $(event.target); right.find("> .container").addClass("hidden"); left.find(".selected").removeClass("selected"); const target = entry.attr("container"); if(!target) return; right.find("> .container." + target).removeClass("hidden"); entry.addClass("selected"); }) } /* initialize all tabs */ /* enable one tab */ { left.find(".entry[container" + (default_page ? ("='" + default_page + "'") : "") + "]").first().trigger('click'); } return tag; }, footer: null }); modal.htmlTag.find(".modal-body").addClass("modal-settings"); settings_general_application(modal.htmlTag.find(".right .container.general-application"), modal); settings_general_language(modal.htmlTag.find(".right .container.general-language"), modal); settings_general_chat(modal.htmlTag.find(".right .container.general-chat"), modal); settings_audio_microphone(modal.htmlTag.find(".right .container.audio-microphone"), modal); settings_audio_speaker(modal.htmlTag.find(".right .container.audio-speaker"), modal); settings_audio_sounds(modal.htmlTag.find(".right .container.audio-sounds"), modal); const update_profiles = settings_identity_profiles(modal.htmlTag.find(".right .container.identity-profiles"), modal); settings_identity_forum(modal.htmlTag.find(".right .container.identity-forum"), modal, update_profiles as any); modal.open(); return modal; } function settings_general_application(container: JQuery, modal: Modal) { /* hostbanner */ { const option = container.find(".option-hostbanner-background") as JQuery; option.on('change', event => { settings.changeGlobal(Settings.KEY_HOSTBANNER_BACKGROUND, option[0].checked); for(const sc of server_connections.server_connection_handlers()) sc.hostbanner.update(); }).prop("checked", settings.static_global(Settings.KEY_HOSTBANNER_BACKGROUND)); } /* font size */ { const current_size = parseInt(getComputedStyle(document.body).fontSize); //settings.static_global(Settings.KEY_FONT_SIZE, 12); const select = container.find(".option-font-size"); if(select.find("option[value='" + current_size + "']").length) select.find("option[value='" + current_size + "']").prop("selected", true); else select.find("option[value='-1']").prop("selected", true); select.on('change', event => { const value = parseInt(select.val() as string); settings.changeGlobal(Settings.KEY_FONT_SIZE, value); console.log("Changed font size to %dpx", value); $(document.body).css("font-size", value + "px"); }); } /* all permissions */ { const option = container.find(".option-all-permissions") as JQuery; option.on('change', event => { settings.changeGlobal(Settings.KEY_HOSTBANNER_BACKGROUND, option[0].checked); }).prop("checked", settings.global(Settings.KEY_PERMISSIONS_SHOW_ALL)); } } function settings_general_language(container: JQuery, modal: Modal) { const container_entries = container.find(".container-list .entries"); const tag_loading = container.find(".cover-loading"); const template = $("#settings-translations-list-entry"); const restart_hint = container.find(".restart-note").hide(); const display_repository_info = (repository: i18n.TranslationRepository) => { const info_modal = createModal({ header: tr("Repository info"), body: () => { return $("#settings-translations-list-entry-info").renderTag({ type: "repository", name: repository.name, url: repository.url, contact: repository.contact, translations: repository.translations || [] }); }, footer: () => { let footer = $.spawn("div"); footer.addClass("modal-button-group"); footer.css("margin-top", "5px"); footer.css("margin-bottom", "5px"); footer.css("text-align", "right"); let buttonOk = $.spawn("button"); buttonOk.text(tr("Close")); buttonOk.click(() => info_modal.close()); footer.append(buttonOk); return footer; } }); info_modal.open() }; const display_translation_info = (translation: i18n.RepositoryTranslation, repository: i18n.TranslationRepository) => { const info_modal = createModal({ header: tr("Translation info"), body: () => { const tag = $("#settings-translations-list-entry-info").renderTag({ type: "translation", name: translation.name, url: translation.path, repository_name: repository.name, contributors: translation.contributors || [] }); tag.find(".button-info").on('click', () => display_repository_info(repository)); return tag; }, footer: () => { let footer = $.spawn("div"); footer.addClass("modal-button-group"); footer.css("margin-top", "5px"); footer.css("margin-bottom", "5px"); footer.css("text-align", "right"); let buttonOk = $.spawn("button"); buttonOk.text(tr("Close")); buttonOk.click(() => info_modal.close()); footer.append(buttonOk); return footer; } }); info_modal.open() }; const update_current_selected = () => { const container_current = container.find(".selected-language"); container_current.empty().text(tr("Loading")); let current_translation: i18n.RepositoryTranslation; i18n.iterate_repositories(repository => { if(current_translation) return; for(const entry of repository.translations) if(i18n.config.translation_config().current_translation_path == entry.path) { current_translation = entry; return; } }).then(() => { container_current.empty(); const language = current_translation ? current_translation.country_code : "gb"; $.spawn("div").addClass("country flag-" + language.toLowerCase()).attr('title', i18n.country_name(language, tr("Unknown language"))).appendTo(container_current); $.spawn("a").text(current_translation ? current_translation.name : tr("English (Default)")).appendTo(container_current); }).catch(error => { /* This shall never happen */ }); }; const initially_selected = i18n.config.translation_config().current_translation_url; const update_list = () => { container_entries.empty(); const currently_selected = i18n.config.translation_config().current_translation_url; //Default translation { const tag = template.renderTag({ type: "default", selected: !currently_selected || currently_selected == "default" }); tag.on('click', () => { i18n.select_translation(undefined, undefined); container_entries.find(".selected").removeClass("selected"); tag.addClass("selected"); update_current_selected(); restart_hint.toggle(initially_selected !== i18n.config.translation_config().current_translation_url); }); tag.appendTo(container_entries); } { tag_loading.show(); i18n.iterate_repositories(repo => { let repo_tag = container_entries.find("[repository=\"" + repo.unique_id + "\"]"); if (repo_tag.length == 0) { repo_tag = template.renderTag({ type: "repository", name: repo.name || repo.url, id: repo.unique_id }); repo_tag.find(".button-delete").on('click', e => { e.preventDefault(); Modals.spawnYesNo(tr("Are you sure?"), tr("Do you really want to delete this repository?"), answer => { if (answer) { i18n.delete_repository(repo); update_list(); } }); }); repo_tag.find(".button-info").on('click', e => { e.preventDefault(); display_repository_info(repo); }); container_entries.append(repo_tag); } for(const translation of repo.translations) { const tag = template.renderTag({ type: "translation", name: translation.name || translation.path, id: repo.unique_id, country_code: translation.country_code, selected: i18n.config.translation_config().current_translation_path == translation.path }); tag.find(".button-info").on('click', e => { e.preventDefault(); display_translation_info(translation, repo); }); tag.on('click', e => { if (e.isDefaultPrevented()) return; i18n.select_translation(repo, translation); container_entries.find(".selected").removeClass("selected"); tag.addClass("selected"); update_current_selected(); restart_hint.toggle(initially_selected !== i18n.config.translation_config().current_translation_url); }); tag.insertAfter(repo_tag); } }).then(() => tag_loading.hide()).catch(error => { console.error(error); /* this should NEVER happen */ }) } }; /* button add repository */ { container.find(".button-add-repository").on('click', () => { createInputModal(tr("Enter repository URL"), tr("Enter repository URL:"), text => { try { new URL(text); return true; } catch(error) { return false; } }, url => { if (!url) return; tag_loading.show(); i18n.load_repository(url as string).then(repository => { i18n.register_repository(repository); update_list(); }).catch(error => { tag_loading.hide(); createErrorModal("Failed to load repository", tr("Failed to query repository.
Ensure that this repository is valid and reachable.
Error: ") + error).open(); }) }).open(); }); } container.find(".button-restart").on('click', () => { if(app.is_web()) { location.reload(); } else { createErrorModal(tr("Not implemented"), tr("Client restart isn't implemented.
Please do it manually!")).open(); } }); update_list(); update_current_selected(); } function settings_general_chat(container: JQuery, modal: Modal) { /* timestamp format */ { const option_fixed = container.find(".option-fixed-timestamps") as JQuery; const option_colloquial = container.find(".option-colloquial-timestamps") as JQuery; option_colloquial.on('change', event => { settings.changeGlobal(Settings.KEY_CHAT_COLLOQUIAL_TIMESTAMPS, option_colloquial[0].checked); }); option_fixed.on('change', event => { settings.changeGlobal(Settings.KEY_CHAT_FIXED_TIMESTAMPS, option_fixed[0].checked); option_colloquial .prop("disabled", option_fixed[0].checked) .parents("label").toggleClass("disabled", option_fixed[0].checked); if(option_fixed[0].checked) { option_colloquial.prop("checked", false); } else { option_colloquial.prop("checked", settings.static_global(Settings.KEY_CHAT_COLLOQUIAL_TIMESTAMPS)); } }).prop("checked", settings.static_global(Settings.KEY_CHAT_FIXED_TIMESTAMPS)).trigger('change'); } { const option = container.find(".option-instant-channel-switch") as JQuery; option.on('change', event => { settings.changeGlobal(Settings.KEY_SWITCH_INSTANT_CHAT, option[0].checked); }).prop("checked", settings.static_global(Settings.KEY_SWITCH_INSTANT_CHAT)); } { const option = container.find(".option-instant-client-switch") as JQuery; option.on('change', event => { settings.changeGlobal(Settings.KEY_SWITCH_INSTANT_CLIENT, option[0].checked); }).prop("checked", settings.static_global(Settings.KEY_SWITCH_INSTANT_CLIENT)); } { const option = container.find(".option-colored-emojies") as JQuery; option.on('change', event => { settings.changeGlobal(Settings.KEY_CHAT_COLORED_EMOJIES, option[0].checked); }).prop("checked", settings.static_global(Settings.KEY_CHAT_COLORED_EMOJIES)); } { const option = container.find(".option-support-markdown") as JQuery; option.on('change', event => { settings.changeGlobal(Settings.KEY_CHAT_ENABLE_MARKDOWN, option[0].checked); }).prop("checked", settings.static_global(Settings.KEY_CHAT_ENABLE_MARKDOWN)); } { const option = container.find(".option-url-tagging") as JQuery; option.on('change', event => { settings.changeGlobal(Settings.KEY_CHAT_TAG_URLS, option[0].checked); }).prop("checked", settings.static_global(Settings.KEY_CHAT_TAG_URLS)); } /* Icon size */ { const container_slider = container.find(".container-icon-size .container-slider"); const container_value = container.find(".container-icon-size .value"); sliderfy(container_slider, { unit: '%', min_value: 25, max_value: 300, step: 5, initial_value: settings.static_global(Settings.KEY_ICON_SIZE), value_field: container_value }); container_slider.on('change', event => { const value = parseInt(container_slider.attr("value") as string); settings.changeGlobal(Settings.KEY_ICON_SIZE, value); console.log("Changed icon size to %sem", (value / 100).toFixed(2)); MessageHelper.set_icon_size((value / 100).toFixed(2) + "em"); }); } } function settings_audio_microphone(container: JQuery, modal: Modal) { let _callbacks_filter_change: (() => any)[] = []; /* devices */ { const container_devices = container.find(".container-devices"); let level_meters: audio.recorder.LevelMeter[] = []; modal.close_listener.push(() => { for(const meter of level_meters) meter.destory(); level_meters = []; }); const update_devices = () => { container_devices.children().remove(); for(const meter of level_meters) meter.destory(); level_meters = []; const current_selected = default_recorder.current_device(); const generate_device = (device: audio.recorder.InputDevice | undefined) => { const selected = device === current_selected || (typeof(current_selected) !== "undefined" && typeof(device) !== "undefined" && current_selected.unique_id == device.unique_id); let tag_volume: JQuery, tag_volume_error: JQuery; const tag = $.spawn("div").addClass("device").toggleClass("selected", selected).append( $.spawn("div").addClass("container-selected").append( $.spawn("div").addClass("icon_em client-apply") ), $.spawn("div").addClass("container-name").append( $.spawn("div").addClass("device-driver").text( device ? (device.driver || "Unknown driver") : "No device" ), $.spawn("div").addClass("device-name").text( device ? (device.name || "Unknown name") : "No device" ), ), $.spawn("div").addClass("container-activity").append( $.spawn("div").addClass("container-activity-bar").append( tag_volume = $.spawn("div").addClass("bar-hider"), tag_volume_error = $.spawn("div").addClass("bar-error") ) ) ); tag.on('click', event => { if(tag.hasClass("selected")) return; const _old = container_devices.find(".selected"); _old.removeClass("selected"); tag.addClass("selected"); default_recorder.set_device(device).then(() => { console.debug(tr("Changed default microphone device")); for(const cb of _callbacks_filter_change) cb(); }).catch((error) => { _old.addClass("selected"); tag.removeClass("selected"); console.error(tr("Failed to change microphone to device %o: %o"), device, error); createErrorModal(tr("Failed to change microphone"), MessageHelper.formatMessage(tr("Failed to change the microphone to the target microphone{:br:}{}"), error)).open(); }); }); tag_volume.css('width', '100%'); if(device) { audio.recorder.create_levelmeter(device).then(meter => { level_meters.push(meter); meter.set_observer(value => { tag_volume.css('width', (100 - value) + '%'); }); }).catch(error => { console.warn(tr("Failed to generate levelmeter for device %o: %o"), device, error); tag_volume_error.attr('title', error).text(error); }); } return tag; }; generate_device(undefined).appendTo(container_devices); audio.recorder.devices().forEach(e => generate_device(e).appendTo(container_devices)); }; update_devices(); const button_update = container.find(".button-update"); button_update.on('click', async event => { button_update.prop("disabled", true); if(audio.recorder.device_refresh_available()) { try { await audio.recorder.refresh_devices(); } catch(error) { console.warn(tr("Failed to refresh input devices: %o"), error); } } try { update_devices(); } catch(error) { console.error(tr("Failed to build new device list: %o"), error); } button_update.prop("disabled", false); }); } /* settings */ { /* volume */ { const container_volume = container.find(".container-volume"); const slider = container_volume.find(".container-slider"); sliderfy(slider, { min_value: 0, max_value: 100, step: 1, initial_value: default_recorder.get_volume() }); slider.on('change', event => { const value = parseInt(slider.attr("value")); default_recorder.set_volume(value); }); } /* vad select */ { const container_select = container.find(".container-select-vad"); container_select.find("input").on('change', event => { if(!(event.target).checked) return; const mode = (event.target).value; if(mode == "active") default_recorder.set_vad_type("active"); else if(mode == "threshold") default_recorder.set_vad_type("threshold"); else default_recorder.set_vad_type("push_to_talk"); for(const cb of _callbacks_filter_change) cb(); }); let elements = container_select.find('input[value="' + default_recorder.get_vad_type() + '"]'); if(elements.length < 1) elements = container_select.find('input[value]'); elements.first().trigger('click'); } /* Sensitivity */ { const container_sensitivity = container.find(".container-sensitivity"); const container_bar = container_sensitivity.find(".container-activity-bar"); const bar_hider = container_bar.find(".bar-hider"); sliderfy(container_bar, { min_value: 0, max_value: 100, step: 1, initial_value: default_recorder.get_vad_threshold() }); container_bar.on('change', event => { const threshold = parseInt(container_bar.attr("value")); default_recorder.set_vad_threshold(threshold); }); const _set_level = level => { bar_hider.css("width", (100 - level) + "%"); }; let _last_filter: audio.recorder.filter.ThresholdFilter; modal.close_listener.push(() => { if(_last_filter) { _last_filter.callback_level = undefined; _last_filter = undefined; } }); _callbacks_filter_change.push(() => { container_sensitivity.toggleClass("disabled", default_recorder.get_vad_type() !== "threshold"); if(_last_filter) { _last_filter.callback_level = undefined; _last_filter = undefined; } if(default_recorder.get_vad_type() !== "threshold") { container_sensitivity.addClass("disabled"); return; } container_sensitivity.removeClass("disabled"); _set_level(0); if(!default_recorder.input) return; if(default_recorder.input.current_state() === audio.recorder.InputState.PAUSED) default_recorder.input.start().then(result => { if(result === audio.recorder.InputStartResult.EOK) { for(const cb of _callbacks_filter_change) cb(); } }); /* for us to show the VAD */ const filter = default_recorder.input.get_filter(audio.recorder.filter.Type.THRESHOLD) as audio.recorder.filter.ThresholdFilter; if(!filter) return; _last_filter = filter; filter.callback_level = _set_level; }); } /* push to talk */ { /* PPT Key */ { const button_key = container.find(".container-ppt button"); _callbacks_filter_change.push(() => { button_key.prop('disabled', default_recorder.get_vad_type() !== "push_to_talk"); }); button_key.on('click', event => { Modals.spawnKeySelect(key => { if(!key) return; default_recorder.set_vad_ppt_key(key); button_key.text(ppt.key_description(key)); }); }); button_key.text(ppt.key_description(default_recorder.get_vad_ppt_key())); } /* Delay */ { const container_delay = container.find(".container-ppt-delay"); const input_time = container_delay.find("input.delay-time"); const input_enabled = container_delay.find("input.delay-enabled"); input_enabled.on('change', event => { const enabled = input_enabled.prop("checked"); if(enabled) { if(default_recorder.get_vad_type() === "push_to_talk") input_time.prop("disabled", false).parent().removeClass("disabled"); default_recorder.set_vad_ppt_delay(Math.abs(default_recorder.get_vad_ppt_delay())); } else { input_time.prop("disabled", true).parent().addClass("disabled"); default_recorder.set_vad_ppt_delay(-Math.abs(default_recorder.get_vad_ppt_delay())); } }); input_time.on('change', event => { const value = parseFloat(input_time.val() as any); default_recorder.set_vad_ppt_delay(value * 1000); }).val(Math.abs(default_recorder.get_vad_ppt_delay() / 1000).toFixed(2)); input_enabled.prop("checked", default_recorder.get_vad_ppt_delay() >= 0); _callbacks_filter_change.push(() => { let enabled = default_recorder.get_vad_type() === "push_to_talk"; input_enabled.prop("disabled", !enabled).parent().toggleClass("disabled", !enabled); enabled = enabled && input_enabled.prop("checked"); input_time.prop("disabled", !enabled).parent().toggleClass("disabled", !enabled); }); } //delay-time } } for(const cb of _callbacks_filter_change) cb(); } function settings_audio_speaker(container: JQuery, modal: Modal) { /* devices */ { const container_devices = container.find(".left .container-devices"); const contianer_error = container.find(".left .container-error"); const update_devices = () => { container_devices.children().remove(); const current_selected = audio.player.current_device(); const generate_device = (device: audio.player.Device | undefined) => { const selected = device === current_selected || (typeof(current_selected) !== "undefined" && typeof(device) !== "undefined" && current_selected.device_id == device.device_id); const tag = $.spawn("div").addClass("device").toggleClass("selected", selected).append( $.spawn("div").addClass("container-selected").append( $.spawn("div").addClass("icon_em client-apply") ), $.spawn("div").addClass("container-name").append( $.spawn("div").addClass("device-driver").text( device ? (device.driver || "Unknown driver") : "No device" ), $.spawn("div").addClass("device-name").text( device ? (device.name || "Unknown name") : "No device" ) ) ); tag.on('click', event => { if(tag.hasClass("selected")) return; const _old = container_devices.find(".selected"); _old.removeClass("selected"); tag.addClass("selected"); audio.player.set_device(device ? device.device_id : null).then(() => { console.debug(tr("Changed default speaker device")); }).catch((error) => { _old.addClass("selected"); tag.removeClass("selected"); console.error(tr("Failed to change speaker to device %o: %o"), device, error); createErrorModal(tr("Failed to change speaker"), MessageHelper.formatMessage(tr("Failed to change the speaker device to the target speaker{:br:}{}"), error)).open(); }); }); return tag; }; generate_device(undefined).appendTo(container_devices); audio.player.available_devices().then(result => { contianer_error.text("").hide(); result.forEach(e => generate_device(e).appendTo(container_devices)); }).catch(error => { if(typeof(error) === "string") contianer_error.text(error).show(); console.log(tr("Failed to query available speaker devices: %o"), error); contianer_error.text(tr("Errors occurred (View console)")).show(); }); }; update_devices(); const button_update = container.find(".button-update"); button_update.on('click', async event => { button_update.prop("disabled", true); try { update_devices(); } catch(error) { console.error(tr("Failed to build new speaker device list: %o"), error); } button_update.prop("disabled", false); }); } /* slider */ { { const container_master = container.find(".container-volume-master"); const slider = container_master.find(".container-slider"); sliderfy(slider, { min_value: 0, max_value: 100, step: 1, initial_value: settings.static_global(Settings.KEY_SOUND_MASTER, 100), value_field: [container_master.find(".container-value")] }); slider.on('change', event => { const volume = parseInt(slider.attr('value')); if(audio.player.set_master_volume) audio.player.set_master_volume(volume / 100); settings.changeGlobal(Settings.KEY_SOUND_MASTER, volume); }); } { const container_soundpack = container.find(".container-volume-soundpack"); const slider = container_soundpack.find(".container-slider"); sliderfy(slider, { min_value: 0, max_value: 100, step: 1, initial_value: settings.static_global(Settings.KEY_SOUND_MASTER_SOUNDS, 100), value_field: [container_soundpack.find(".container-value")] }); slider.on('change', event => { const volume = parseInt(slider.attr('value')); sound.set_master_volume(volume / 100); settings.changeGlobal(Settings.KEY_SOUND_MASTER_SOUNDS, volume); }); } } /* button test sound */ { container.find(".button-test-sound").on('click', event => { sound.manager.play(Sound.SOUND_TEST, { default_volume: 1, ignore_muted: true, ignore_overlap: true }) }); } } function settings_audio_sounds(contianer: JQuery, modal: Modal) { /* initialize sound list */ { const container_sounds = contianer.find(".container-sounds"); const generate_sound = (_sound: Sound) => { let tag_play_pause: JQuery, tag_play: JQuery, tag_pause: JQuery, tag_input_muted: JQuery; let tag = $.spawn("div").addClass("sound").append( tag_play_pause = $.spawn("div").addClass("container-button-play_pause").append( tag_play = $.spawn("img").attr("src", "img/icon_sound_play.svg"), tag_pause = $.spawn("img").attr("src", "img/icon_sound_pause.svg") ), $.spawn("div").addClass("container-name").text(_sound), $.spawn("label").addClass("container-button-toggle").append( $.spawn("div").addClass("switch").append( tag_input_muted = $.spawn("input").attr("type", "checkbox"), $.spawn("span").addClass("slider").append( $.spawn("div").addClass("dot") ) ) ) ); tag_play_pause.on('click', event => { if(tag_pause.is(":visible")) return; tag_play.hide(); tag_pause.show(); const _done = flag => { tag_pause.hide(); tag_play.show(); }; const _timeout = setTimeout(() => _done(false), 10 * 1000); /* the sounds are not longer than 10 seconds */ sound.manager.play(_sound, { ignore_overlap: true, ignore_muted: true, default_volume: 1, callback: flag => { clearTimeout(_timeout); _done(flag); } }); }); tag_pause.hide(); tag_input_muted.prop("checked", sound.get_sound_volume(_sound, 1) > 0); tag_input_muted.on('change', event => { const volume = tag_input_muted.prop("checked") ? 1 : 0; sound.set_sound_volume(_sound, volume); console.log(tr("Changed sound volume to %o for sound %o"), volume, _sound); }); return tag; }; //container-sounds for(const sound_key in Sound) generate_sound(Sound[sound_key as any] as any).appendTo(container_sounds); /* the filter */ const input_filter = contianer.find(".input-sounds-filter"); input_filter.on('change keyup', event => { const filter = input_filter.val() as string; container_sounds.find(".sound").each((_, _element) => { const element = $(_element); element.toggle(filter.length == 0 || element.text().toLowerCase().indexOf(filter) !== -1); }) }); } const overlap_tag = contianer.find(".option-overlap-same"); overlap_tag.on('change', event => { const activated = (event.target).checked; sound.set_overlap_activated(activated); }).prop("checked", sound.overlap_activated()); const mute_tag = contianer.find(".option-mute-output"); mute_tag.on('change', event => { const activated = (event.target).checked; sound.set_ignore_output_muted(!activated); }).prop("checked", !sound.ignore_output_muted()); modal.close_listener.push(sound.save); } type SelectedIdentity = { identity: profiles.ConnectionProfile; update_name(text?: string); update_valid_flag(); update_type(); update_avatar(); } function settings_identity_profiles(container: JQuery, modal: Modal) { let selected_profile: SelectedIdentity; let selected_profile_changed: (() => void)[] = []; let profile_identity_changed: (() => void)[] = []; let update_profiles: (selected_id: string) => void; /* profile list */ { const container_profiles = container.find(".container-profiles"); const build_profile = (profile: profiles.ConnectionProfile, selected: boolean) => { let tag_name: JQuery, tag_default: JQuery, tag_valid: JQuery, tag_type: JQuery, tag_avatar: JQuery; let tag = $.spawn("div").addClass("profile").append( tag_avatar = $.spawn("div").addClass("container-avatar"), $.spawn("div").addClass("container-info").append( $.spawn("div").addClass("container-type").append( tag_type = $.spawn("div").text(profile.selected_identity_type || tr("Type unset")), tag_default = $.spawn("div").addClass("tag-default").text(tr("(Default)")), tag_valid = $.spawn("div").addClass("icon_em icon-status") .toggleClass("client-apply", profile.valid()) .toggleClass("client-delete", !profile.valid()) ), tag_name = $.spawn("div").addClass("profile-name").text(profile.profile_name || tr("Unnamed")) ) ); tag_avatar.hide(); /* no avatars yet */ tag_default.toggle(profile.id === "default"); tag.on('click', event => { if(tag.hasClass('selected')) return; container_profiles.find(".selected").removeClass("selected"); tag.addClass("selected"); /* reset profile name if may in change */ if(selected_profile) selected_profile.update_name(); selected_profile = { identity: profile, update_name(text) { tag_name.text(typeof(text) === "string" ? text : (profile.profile_name || tr("Unnamed"))) }, update_type() { tag_type.text(profile.selected_identity_type || tr("Type unset")); }, update_valid_flag() { tag_valid .toggleClass("client-apply", profile.valid()) .toggleClass("client-delete", !profile.valid()) }, update_avatar() { //TODO HERE! } }; for(const listener of selected_profile_changed) listener(); }); if(selected) tag.trigger('click'); return tag; }; update_profiles = (selected_id) => { selected_id = selected_id || "default"; container_profiles.children().remove(); profiles.profiles().forEach(e => build_profile(e, e.id == selected_id).appendTo(container_profiles)); }; } /* profile general info */ { const input_name = container.find(".right input.profile-name"); const input_default_name = container.find(".right input.profile-default-name"); const select_type = container.find(".right select.profile-identity-type"); selected_profile_changed.push(() => { //profile-identity-type if(!selected_profile.identity) { input_name.val(tr("No profile selected")).prop("disabled", true); input_default_name.val("").prop("disabled", true); select_type.val("unset").prop("disabled", true); select_type.parent().toggleClass("is-invalid", true); } else { input_name.val(selected_profile.identity.profile_name).prop("disabled", false); input_default_name .val(selected_profile.identity.default_username) .attr("placeholder", selected_profile.identity.connect_username() || "Another TeaSpeak user") .prop("disabled", false); select_type.val(selected_profile.identity.selected_identity_type || "unset").prop("disabled", false); } for(const listener of profile_identity_changed) listener(); }); input_name.on('keyup', event => { const text = input_name.val() as string; const profile = profiles.find_profile_by_name(text); input_name.parent().toggleClass("is-invalid", text.length < 3 || (profile && profile != selected_profile.identity)); selected_profile.update_name(text); }).on('change', event => { const text = input_name.val() as string; const profile = profiles.find_profile_by_name(text); if(text.length < 3 || (profile && profile != selected_profile.identity)) return; selected_profile.identity.profile_name = text; profiles.mark_need_save(); }); input_default_name.on('change', event => { selected_profile.identity.default_username = input_default_name.val() as string; profiles.mark_need_save(); }); select_type.on('change', event => { selected_profile.identity.selected_identity_type = (select_type.val() as string).toLowerCase(); profiles.mark_need_save(); selected_profile.update_type(); for(const listener of profile_identity_changed) listener(); selected_profile.update_valid_flag(); }); profile_identity_changed.push(() => { select_type.parent() .toggleClass("is-invalid", typeof(profiles.identities.IdentitifyType[selected_profile.identity.selected_identity_type.toUpperCase()]) === "undefined"); }); } /* profile special info */ { /* teamspeak */ { const container_settings = container.find(".container-teamspeak"); const container_valid = container_settings.find(".container-valid"); const container_invalid = container_settings.find(".container-invalid"); const input_current_level = container_settings.find(".current-level"); const input_unique_id = container_settings.find(".unique-id"); const button_new = container_settings.find(".button-new"); const button_improve = container_settings.find(".button-improve"); const button_import = container_settings.find(".button-import"); const button_export = container_settings.find(".button-export"); button_improve.on('click', event => { const profile = selected_profile.identity.selected_identity(profiles.identities.IdentitifyType.TEAMSPEAK) as profiles.identities.TeaSpeakIdentity; if (!profile) return; Modals.spawnTeamSpeakIdentityImprove(profile, selected_profile.identity.profile_name).close_listener.push(() => { profiles.mark_need_save(); for(const listener of profile_identity_changed) listener(); }); }); button_new.on('click', event => { const profile = selected_profile.identity.selected_identity(profiles.identities.IdentitifyType.TEAMSPEAK) as profiles.identities.TeaSpeakIdentity; const generate_identity = () => { profiles.identities.TeaSpeakIdentity.generate_new().then(identity => { selected_profile.identity.set_identity(profiles.identities.IdentitifyType.TEAMSPEAK, identity); createInfoModal(tr("Identity generated"), tr("A new identity had been successfully generated")).open(); profiles.mark_need_save(); for(const listener of profile_identity_changed) listener(); }).catch(error => { console.error(tr("Failed to generate a new identity. Error object: %o"), error); createErrorModal(tr("Failed to generate identity"), tr("Failed to generate a new identity.
Error:") + error).open(); }); }; if (profile && profile.valid()) { spawnYesNo(tr("Are you sure"), tr("Do you really want to generate a new identity and override the old identity?"), result => { if (result) generate_identity(); }); } else generate_identity(); }); button_import.on('click', event => { const profile = selected_profile.identity.selected_identity(profiles.identities.IdentitifyType.TEAMSPEAK) as profiles.identities.TeaSpeakIdentity; const set_identity = (identity: profiles.identities.TeaSpeakIdentity) => { selected_profile.identity.set_identity(profiles.identities.IdentitifyType.TEAMSPEAK, identity); createInfoModal(tr("Identity imported"), tr("Your identity has been successfully imported!")).open(); profiles.mark_need_save(); for(const listener of profile_identity_changed) listener(); }; if (profile && profile.valid()) { spawnYesNo(tr("Are you sure"), tr("Do you really want to import a new identity and override the old identity?"), result => { if (result) spawnTeamSpeakIdentityImport(set_identity); }); } else { spawnTeamSpeakIdentityImport(set_identity); } }); button_export.on('click', event => { const profile = selected_profile.identity.selected_identity(profiles.identities.IdentitifyType.TEAMSPEAK) as profiles.identities.TeaSpeakIdentity; if(!profile) return; createInputModal(tr("File name"), tr("Please enter the file name"), text => !!text, name => { if (name) { profile.export_ts(true).then(data => { const element = $.spawn("a") .text("donwload") .attr("href", "data:test/plain;charset=utf-8," + encodeURIComponent(data)) .attr("download", name + ".ini") .css("display", "none") .appendTo($("body")); element[0].click(); element.remove(); }).catch(error => { console.error(error); createErrorModal(tr("Failed to export identity"), tr("Failed to export and save identity.
Error: ") + error).open(); }); } }).open(); }); profile_identity_changed.push(() => { const enabled = selected_profile && selected_profile.identity.selected_identity_type === "teamspeak"; container_settings.toggle(enabled); if(!enabled) return; const profile = selected_profile.identity.selected_identity(profiles.identities.IdentitifyType.TEAMSPEAK) as profiles.identities.TeaSpeakIdentity; button_improve.prop("disabled", !profile); button_export.toggle(!!profile); button_import.toggleClass("btn-danger", !!profile).toggleClass("btn-success", !profile); button_new.toggleClass("btn-danger", !!profile).toggleClass("btn-success", !profile); container_invalid.toggle(!profile); container_valid.toggle(!!profile); if (!profile) { input_current_level.val("no profile"); input_unique_id.val("no profile"); } else { input_current_level.val("loading...."); profile.level().then(level => input_current_level.val(level + "")); input_unique_id.val(profile.uid()); } selected_profile.update_valid_flag(); }); } /* teaspeak forum */ { const container_settings = container.find(".container-teaforo"); const continer_valid = container_settings.find(".container-valid"); const continer_invalid = container_settings.find(".container-invalid"); const button_setup = container_settings.find(".button-setup"); profile_identity_changed.push(() => { container_settings.toggle(selected_profile && selected_profile.identity.selected_identity_type === "teaforo"); const profile = selected_profile.identity.selected_identity(profiles.identities.IdentitifyType.TEAFORO) as profiles.identities.TeaForumIdentity; const valid = profile && profile.valid(); continer_valid.toggle(valid); continer_invalid.toggle(!valid); }); button_setup.on('click', event => { modal.htmlTag.find('.entry[container="identity-forum"]').trigger('click'); }); } /* nickname */ { const container_settings = container.find(".container-nickname"); const input_nickname = container_settings.find(".nickname"); profile_identity_changed.push(() => { const active = selected_profile && selected_profile.identity.selected_identity_type === "nickname"; container_settings.toggle(active); if(!active) return; let profile = selected_profile.identity.selected_identity(profiles.identities.IdentitifyType.NICKNAME) as profiles.identities.NameIdentity; if(!profile) selected_profile.identity.set_identity(profiles.identities.IdentitifyType.NICKNAME, profile = new profiles.identities.NameIdentity()); input_nickname.val(profile.name()).trigger('change'); }); input_nickname.on('keydown', event => { const profile = selected_profile.identity.selected_identity(profiles.identities.IdentitifyType.NICKNAME) as profiles.identities.NameIdentity; if(!profile) return; profile.set_name(input_nickname.val() as string); profiles.mark_need_save(); selected_profile.update_valid_flag(); input_nickname.parent().toggleClass('is-invalid', !profile.valid()); }); } } /* change avatar button */ { container.find(".button-change-avatar").hide(); } /* create new button */ { container.find(".button-create").on('click', event => { createInputModal(tr("Please enter a name"), tr("Please enter a name for the new profile:"), text => text.length >= 3 && !profiles.find_profile_by_name(text), value => { if (value) { const profile = profiles.create_new_profile(value as string); update_profiles(profile.id); profiles.mark_need_save(); } }).open(); }); } /* set as default button */ { const button = container.find(".button-set-default"); button.on('click', event => { profiles.set_default_profile(selected_profile.identity); profiles.mark_need_save(); update_profiles(selected_profile.identity.id); }); selected_profile_changed.push(() => { button.prop("disabled", !selected_profile || selected_profile.identity.id === "default"); }); } /* delete button */ { const button = container.find(".button-delete"); button.on('click', event => { if (selected_profile && selected_profile.identity.id != 'default') { spawnYesNo(tr("Are you sure?"), tr("Do you really want to delete this profile?"), result => { if (result) { profiles.delete_profile(selected_profile.identity); profiles.mark_need_save(); update_profiles(undefined); } }); } }); selected_profile_changed.push(() => { button.prop("disabled", !selected_profile || selected_profile.identity.id === "default"); }); } update_profiles(undefined); modal.close_listener.push(() => { if(profiles.requires_save()) profiles.save(); }); return update_profiles; } function settings_identity_forum(container: JQuery, modal: Modal, update_profiles: () => any) { const containers_connected = container.find(".show-connected"); const containers_disconnected = container.find(".show-disconnected"); const update_state = () => { const logged_in = forum.logged_in(); containers_connected.toggle(logged_in); containers_disconnected.toggle(!logged_in); if(logged_in) { container.find(".forum-username").text(forum.data().name()); container.find(".forum-premium").text(forum.data().is_premium() ? tr("Yes") : tr("No")); } }; /* login */ { const button_login = container.find(".button-login"); const input_username = container.find(".input-username"); const input_password = container.find(".input-password"); const container_error = container.find(".container-login .container-error"); const container_captcha_g = container.find(".g-recaptcha"); let captcha: boolean | string = false; const update_button_state = () => { let enabled = true; enabled = enabled && !!input_password.val(); enabled = enabled && !!input_username.val(); enabled = enabled && (typeof(captcha) === "boolean" ? !captcha : !!captcha); button_login.prop("disabled", !enabled); }; /* username */ input_username.on('change keyup', update_button_state); /* password */ input_password.on('change keyup', update_button_state); button_login.on('click', event => { input_username.prop("disabled", true); input_password.prop("disabled", true); button_login.prop("disabled", true); container_error.removeClass("shown"); forum.login(input_username.val() as string, input_password.val() as string, typeof(captcha) === "string" ? captcha : undefined).then(state => { captcha = false; console.debug(tr("Forum login result: %o"), state); if(state.status === "success") { update_state(); update_profiles(); return; } setTimeout(() => { if(!!state.error_message) /* clear password if we have an error */ input_password.val(""); input_password.focus(); update_button_state(); }, 0); if(state.status === "captcha") { //TODO Works currently only with localhost! button_login.hide(); container_error.text(state.error_message || tr("Captcha required")).addClass("shown"); captcha = ""; console.log(tr("Showing captcha for site-key: %o"), state.captcha.data); forum.gcaptcha.spawn(container_captcha_g, state.captcha.data, token => { captcha = token; console.debug(tr("Got captcha token: %o"), token); container_captcha_g.hide(); button_login.show(); update_button_state(); }).catch(error => { console.error(tr("Failed to initialize forum captcha: %o"), error); container_error.text("Failed to initialize GReCaptcha! No authentication possible.").addClass("shown"); container_captcha_g.hide(); button_login.hide(); }); container_captcha_g.show(); } else { container_error.text(state.error_message || tr("Unknown error")).addClass("shown"); } }).catch(error => { console.error(tr("Failed to login within the forum. Error: %o"), error); createErrorModal(tr("Forum login failed."), tr("Forum login failed. Lookup the console for more information")).open(); }).then(() => { input_username.prop("disabled", false); input_password.prop("disabled", false); update_button_state(); }); }); update_button_state(); } /* logout */ { container.find(".button-logout").on('click', event => { forum.logout().catch(error => { console.error(tr("Failed to logout from forum: %o"), error); createErrorModal(tr("Forum logout failed"), MessageHelper.formatMessage(tr("Failed to logout from forum account.{:br:}Error: {}"), error)).open(); }).then(() => { if (modal.shown) update_state(); update_profiles(); }); }); } update_state(); } }