| 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 | |
| 698 | export async function init() { | 830 | export async function init() { |
| 699 | extension_settings.connectionManager = extension_settings.connectionManager || structuredClone(DEFAULT_SETTINGS); | 831 | extension_settings.connectionManager = extension_settings.connectionManager || structuredClone(DEFAULT_SETTINGS); |
| 700 | | 832 | |