| 695 | } | 695 | } |
| 696 | } | 696 | } |
| 697 | | 697 | |
| 698 | /** | | |
| 699 | * Adds a quick-switch connection profile button to the bottom-left of the chat bar. | | |
| 700 | * Clicking it opens a small popup listing all saved connection profiles; clicking a | | |
| 701 | * profile switches to it by driving the Connection Manager's #connection_profiles select. | | |
| 702 | */ | | |
| 703 | function addQuickSwitchButton() { | | |
| 704 | const leftSendForm = document.getElementById('leftSendForm'); | | |
| 705 | if (!leftSendForm) { | | |
| 706 | console.warn('[Connection Manager] #leftSendForm not found, skipping quick-switch button'); | | |
| 707 | return; | | |
| 708 | } | | |
| 709 | | | |
| 710 | // Avoid adding the button twice (e.g. on extension re-activation) | | |
| 711 | if (document.getElementById('connection_profile_switcher')) { | | |
| 712 | return; | | |
| 713 | } | | |
| 714 | | | |
| 715 | const button = document.createElement('div'); | | |
| 716 | button.id = 'connection_profile_switcher'; | | |
| 717 | button.className = 'fa-solid fa-plug interactable'; | | |
| 718 | button.tabIndex = 0; | | |
| 719 | button.title = t`Quick-switch connection profile`; | | |
| 720 | button.setAttribute('data-i18n', '[title]Quick-switch connection profile'); | | |
| 721 | leftSendForm.appendChild(button); | | |
| 722 | | | |
| 723 | const menu = document.createElement('div'); | | |
| 724 | menu.id = 'connection_profile_switcher_menu'; | | |
| 725 | menu.style.display = 'none'; | | |
| 726 | const list = document.createElement('ul'); | | |
| 727 | list.id = 'connection_profile_switcher_list'; | | |
| 728 | list.className = 'list-group'; | | |
| 729 | menu.appendChild(list); | | |
| 730 | document.body.appendChild(menu); | | |
| 731 | | | |
| 732 | const $menu = $(menu); | | |
| 733 | const popper = Popper.createPopper(button, menu, { | | |
| 734 | placement: 'top-start', | | |
| 735 | }); | | |
| 736 | | | |
| 737 | /** | | |
| 738 | * Rebuilds the list of profiles from the current settings. | | |
| 739 | */ | | |
| 740 | function rebuildList() { | | |
| 741 | list.innerHTML = ''; | | |
| 742 | const profiles = extension_settings.connectionManager.profiles; | | |
| 743 | const selectedProfile = extension_settings.connectionManager.selectedProfile; | | |
| 744 | | | |
| 745 | if (!Array.isArray(profiles) || profiles.length === 0) { | | |
| 746 | const emptyItem = document.createElement('li'); | | |
| 747 | emptyItem.className = 'list-group-item disabled'; | | |
| 748 | emptyItem.textContent = t`No connection profiles saved`; | | |
| 749 | emptyItem.setAttribute('data-i18n', 'No connection profiles saved'); | | |
| 750 | list.appendChild(emptyItem); | | |
| 751 | return; | | |
| 752 | } | | |
| 753 | | | |
| 754 | const sortedProfiles = profiles.slice().sort((a, b) => a.name.localeCompare(b.name)); | | |
| 755 | for (const profile of sortedProfiles) { | | |
| 756 | const item = document.createElement('li'); | | |
| 757 | item.className = 'list-group-item interactable'; | | |
| 758 | item.dataset.profileId = profile.id; | | |
| 759 | if (profile.id === selectedProfile) { | | |
| 760 | item.classList.add('selected'); | | |
| 761 | } | | |
| 762 | const icon = document.createElement('i'); | | |
| 763 | icon.className = profile.id === selectedProfile ? 'fa-fw fa-solid fa-check' : 'fa-fw fa-solid'; | | |
| 764 | const label = document.createElement('span'); | | |
| 765 | label.textContent = profile.name; | | |
| 766 | item.appendChild(icon); | | |
| 767 | item.appendChild(label); | | |
| 768 | list.appendChild(item); | | |
| 769 | } | | |
| 770 | } | | |
| 771 | | | |
| 772 | button.addEventListener('click', (e) => { | | |
| 773 | e.preventDefault(); | | |
| 774 | if ($menu.is(':visible')) { | | |
| 775 | $menu.fadeOut(animation_duration); | | |
| 776 | return; | | |
| 777 | } | | |
| 778 | rebuildList(); | | |
| 779 | $menu.fadeIn(animation_duration); | | |
| 780 | popper.update(); | | |
| 781 | }); | | |
| 782 | | | |
| 783 | // Switch profile when a list item is clicked (event delegation). | | |
| 784 | list.addEventListener('click', (e) => { | | |
| 785 | const item = e.target instanceof Element ? e.target.closest('li[data-profile-id]') : null; | | |
| 786 | if (!item) { | | |
| 787 | return; | | |
| 788 | } | | |
| 789 | const profileId = item.dataset.profileId; | | |
| 790 | $menu.fadeOut(animation_duration); | | |
| 791 | if (!profileId) { | | |
| 792 | return; | | |
| 793 | } | | |
| 794 | /** @type {HTMLSelectElement} */ | | |
| 795 | // @ts-ignore | | |
| 796 | const select = document.getElementById('connection_profiles'); | | |
| 797 | if (!select) { | | |
| 798 | console.warn('[Connection Manager] #connection_profiles select not found'); | | |
| 799 | return; | | |
| 800 | } | | |
| 801 | if (!Array.from(select.options).some(o => o.value === profileId)) { | | |
| 802 | console.warn(`[Connection Manager] Profile option not found in select: ${profileId}`); | | |
| 803 | return; | | |
| 804 | } | | |
| 805 | select.value = profileId; | | |
| 806 | select.dispatchEvent(new Event('change')); | | |
| 807 | }); | | |
| 808 | | | |
| 809 | // Close the menu when clicking outside of it or the button. | | |
| 810 | document.addEventListener('click', (e) => { | | |
| 811 | if (!$menu.is(':visible')) { | | |
| 812 | return; | | |
| 813 | } | | |
| 814 | const target = e.target; | | |
| 815 | if (target instanceof Node && (menu.contains(target) || button.contains(target) || button === target)) { | | |
| 816 | return; | | |
| 817 | } | | |
| 818 | $menu.fadeOut(animation_duration); | | |
| 819 | }); | | |
| 820 | | | |
| 821 | // Keep the highlighted item up to date while the menu is open. | | |
| 822 | eventSource.on(event_types.CONNECTION_PROFILE_LOADED, () => { | | |
| 823 | if ($menu.is(':visible')) { | | |
| 824 | rebuildList(); | | |
| 825 | popper.update(); | | |
| 826 | } | | |
| 827 | }); | | |
| 828 | } | | |
| 829 | | | |
| 830 | export async function init() { | 698 | export async function init() { |
| 831 | extension_settings.connectionManager = extension_settings.connectionManager || structuredClone(DEFAULT_SETTINGS); | 699 | extension_settings.connectionManager = extension_settings.connectionManager || structuredClone(DEFAULT_SETTINGS); |
| 832 | | 700 | |