Skip to content

Commit bfeca69

Browse files
committed
perf: Optimize typing performance in prompt field and overall UI (#108)
- Fix laggy typing experience in prompt field for longer chats by replacing CSS `:has()` usage - Improve performance related to GPThems Customization tabs and theme rendering Changes summary: Resolved laggy typing in the prompt field for longer chats and enhanced overall performance for GPThems Customization tabs and theme interactions.
1 parent 7fc7f9e commit bfeca69

File tree

5 files changed

+133
-153
lines changed

5 files changed

+133
-153
lines changed

src/js/app/settingsManager.js

+55-39
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { renderColorsTab, resetAllAccents } from './mainColors.js'
2-
import { renderFontsTab, handleFontsListeners } from './mainFonts.js'
3-
import { renderWidthsTab, handleWidthsListeners } from './mainWidths.js'
1+
import { renderColorsTab, resetAllAccents } from "./mainColors.js";
2+
import { renderFontsTab, handleFontsListeners } from "./mainFonts.js";
3+
import { renderWidthsTab, handleWidthsListeners } from "./mainWidths.js";
44

5-
let $settings = null
6-
let $resetAllAccentsBtn = null
5+
let $settings = null;
6+
let $resetAllAccentsBtn = null;
77

8-
const SETTINGS_OPEN_CLASS = 'gpth-settings--open'
8+
const SETTINGS_OPEN_CLASS = "gpth-settings--open";
99

1010
async function createSettings() {
11-
const gpthSettings = document.createElement('div')
12-
gpthSettings.className = 'gpth-settings fixed flex flex-col'
11+
const gpthSettings = document.createElement("div");
12+
gpthSettings.className = "gpth-settings fixed flex flex-col";
1313

1414
gpthSettings.innerHTML = `
1515
<header class="mb-5">
@@ -32,57 +32,73 @@ async function createSettings() {
3232
</div>
3333
</div>
3434
</main>
35-
`
35+
`;
3636

37-
document.body.appendChild(gpthSettings)
38-
cacheElements(gpthSettings)
39-
addListeners()
37+
document.body.appendChild(gpthSettings);
38+
cacheElements(gpthSettings);
39+
addListeners();
4040
}
4141

4242
function cacheElements(gpthSettings) {
43-
$settings = gpthSettings
44-
$resetAllAccentsBtn = $settings.querySelector('#resetAllAccents')
45-
$resetAllAccentsBtn.disabled = true
43+
$settings = gpthSettings;
44+
$resetAllAccentsBtn = $settings.querySelector("#resetAllAccents");
45+
$resetAllAccentsBtn.disabled = true;
4646
}
4747
function addListeners() {
48-
document.getElementById('gpth-settings-close').addEventListener('click', closeSettings)
49-
handleTabsSwitching()
50-
handleFontsListeners()
51-
handleWidthsListeners()
52-
$resetAllAccentsBtn.addEventListener('click', resetAllAccents)
48+
document.getElementById("gpth-settings-close").addEventListener("click", closeSettings);
49+
handleTabsSwitching();
50+
handleFontsListeners();
51+
handleWidthsListeners();
52+
$resetAllAccentsBtn.addEventListener("click", resetAllAccents);
5353
}
5454
// ___ Settings management
5555
function openSettings() {
56-
$settings.classList.add(SETTINGS_OPEN_CLASS)
57-
$settings.addEventListener('transitionend', handleSettingsOpened)
58-
$resetAllAccentsBtn.disabled = false
56+
$settings.classList.add(SETTINGS_OPEN_CLASS);
57+
$settings.addEventListener("transitionend", handleSettingsOpened);
58+
$resetAllAccentsBtn.disabled = false;
5959
}
6060
function handleSettingsOpened() {
61-
document.body.addEventListener('click', handleClickOutsideSettings)
62-
$settings.removeEventListener('transitionend', handleSettingsOpened)
61+
document.body.addEventListener("click", handleClickOutsideSettings);
62+
$settings.removeEventListener("transitionend", handleSettingsOpened);
6363
}
6464
function closeSettings() {
65-
$settings.classList.remove(SETTINGS_OPEN_CLASS)
66-
document.body.removeEventListener('click', handleClickOutsideSettings)
67-
$resetAllAccentsBtn.disabled = true
65+
$settings.classList.remove(SETTINGS_OPEN_CLASS);
66+
document.body.removeEventListener("click", handleClickOutsideSettings);
67+
$resetAllAccentsBtn.disabled = true;
6868
}
6969
function handleClickOutsideSettings(e) {
70-
if (!$settings.contains(e.target) && e.target.id !== 'gpth-open-settings') closeSettings()
70+
if (!$settings.contains(e.target) && e.target.id !== "gpth-open-settings") closeSettings();
7171
}
7272

7373
function handleTabsSwitching() {
74-
const tabs = document.querySelectorAll('.gpth-settings .tab-button')
75-
const panes = document.querySelectorAll('.gpth-settings .tab-pane')
74+
const tabs = document.querySelectorAll(".gpth-settings .tab-button");
75+
const panes = document.querySelectorAll(".gpth-settings .tab-pane");
7676

77+
// tabs.forEach((tab, index) => {
78+
// tab.addEventListener('click', () => {
79+
// document.querySelector('.tab-button.active').classList.remove('active')
80+
// document.querySelector('.tab-pane:not(.hidden)').classList.add('hidden')
81+
82+
// tab.classList.add('active')
83+
// panes[index].classList.remove('hidden')
84+
// })
85+
// })
86+
// Cache the query results outside the event handler to avoid repeated DOM queries
7787
tabs.forEach((tab, index) => {
78-
tab.addEventListener('click', () => {
79-
document.querySelector('.tab-button.active').classList.remove('active')
80-
document.querySelector('.tab-pane:not(.hidden)').classList.add('hidden')
88+
tab.addEventListener("click", () => {
89+
const activeTab = document.querySelector(".tab-button.active");
90+
const activePane = document.querySelector(".tab-pane:not(.hidden)");
91+
92+
// Only do DOM updates if necessary
93+
if (activeTab !== tab) {
94+
activeTab.classList.remove("active");
95+
activePane.classList.add("hidden");
8196

82-
tab.classList.add('active')
83-
panes[index].classList.remove('hidden')
84-
})
85-
})
97+
tab.classList.add("active");
98+
panes[index].classList.remove("hidden");
99+
}
100+
});
101+
});
86102
}
87103

88-
export { createSettings, openSettings, closeSettings, $settings }
104+
export { createSettings, openSettings, closeSettings, $settings };

src/js/app/themeManager.js

+51-44
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,101 @@
1-
import { openSettings } from './settingsManager.js'
1+
import { openSettings } from "./settingsManager.js";
22

33
// Constants for theme management
44
const THEMES = {
5-
LIGHT: 'light',
6-
DARK: 'dark',
7-
SYSTEM: 'system',
8-
OLED: 'oled',
9-
}
5+
LIGHT: "light",
6+
DARK: "dark",
7+
SYSTEM: "system",
8+
OLED: "oled",
9+
};
1010

11-
const mediaQuery = window.matchMedia('(prefers-color-scheme: light)')
11+
const mediaQuery = window.matchMedia("(prefers-color-scheme: light)");
1212

1313
// Core theme management
1414
function getSystemTheme() {
15-
return mediaQuery.matches ? THEMES.LIGHT : THEMES.DARK
15+
return mediaQuery.matches ? THEMES.LIGHT : THEMES.DARK;
1616
}
1717
// Theme state management
1818
function getStoredThemeState() {
1919
return {
20-
theme: localStorage.getItem('theme') || THEMES.SYSTEM,
21-
isOLED: localStorage.getItem('isOLED') === 'true',
22-
}
20+
theme: localStorage.getItem("theme") || THEMES.SYSTEM,
21+
isOLED: localStorage.getItem("isOLED") === "true",
22+
};
2323
}
2424

2525
function setRootTheme(theme, isOLED) {
26-
const root = document.documentElement
27-
const effectiveTheme = theme === THEMES.SYSTEM ? getSystemTheme() : theme
26+
const root = document.documentElement;
27+
const effectiveTheme = theme === THEMES.SYSTEM ? getSystemTheme() : theme;
2828

2929
// Single source of truth for theme application
30-
root.className = effectiveTheme
31-
root.style.colorScheme = effectiveTheme
32-
root.dataset.gptheme = effectiveTheme === THEMES.DARK && isOLED ? 'oled' : effectiveTheme
30+
root.className = effectiveTheme;
31+
root.style.colorScheme = effectiveTheme;
32+
root.dataset.gptheme = effectiveTheme === THEMES.DARK && isOLED ? "oled" : effectiveTheme;
3333
}
3434

3535
function updateTheme(newTheme, isOLED = false) {
36-
const { theme: currentTheme } = getStoredThemeState()
36+
const { theme: currentTheme } = getStoredThemeState();
3737

38-
if (currentTheme === newTheme && String(isOLED) === localStorage.getItem('isOLED')) return
38+
if (currentTheme === newTheme && String(isOLED) === localStorage.getItem("isOLED")) return;
3939

4040
// Update storage and DOM in a single operation
41-
localStorage.setItem('theme', newTheme)
42-
localStorage.setItem('isOLED', isOLED)
41+
localStorage.setItem("theme", newTheme);
42+
localStorage.setItem("isOLED", isOLED);
4343

44-
setRootTheme(newTheme, isOLED)
44+
setRootTheme(newTheme, isOLED);
4545

4646
// Notify other tabs/windows
4747
window.dispatchEvent(
48-
new StorageEvent('storage', {
49-
key: 'theme',
48+
new StorageEvent("storage", {
49+
key: "theme",
5050
newValue: newTheme,
5151
// oldValue: currentTheme,
5252
storageArea: localStorage,
5353
})
54-
)
54+
);
5555
}
5656

5757
// Event handlers
5858
function handleChangeTheme(e) {
59-
const themeBtn = e.target.closest('button')
60-
if (!themeBtn) return
59+
const themeBtn = e.target.closest("button");
60+
if (!themeBtn) return;
6161

62-
const themeId = themeBtn.id
62+
const themeId = themeBtn.id;
6363

64-
console.log(themeId)
64+
console.log(themeId);
6565

6666
switch (themeId) {
6767
case THEMES.LIGHT:
6868
case THEMES.DARK:
6969
case THEMES.SYSTEM:
70-
updateTheme(themeId, false)
71-
break
72-
case 'oled':
73-
updateTheme(THEMES.DARK, true)
74-
break
75-
case 'gpth-open-settings':
76-
openSettings()
77-
break
70+
updateTheme(themeId, false);
71+
break;
72+
case "oled":
73+
updateTheme(THEMES.DARK, true);
74+
break;
75+
case "gpth-open-settings":
76+
openSettings();
77+
break;
7878
}
7979
}
8080

8181
function init() {
82-
const { theme, isOLED } = getStoredThemeState()
83-
84-
setRootTheme(theme, isOLED)
82+
const { theme, isOLED } = getStoredThemeState();
83+
setRootTheme(theme, isOLED);
8584

86-
mediaQuery.addEventListener('change', () => {
87-
const { theme, isOLED } = getStoredThemeState()
85+
const mediaQueryListener = () => {
86+
const { theme, isOLED } = getStoredThemeState();
8887
if (theme === THEMES.SYSTEM) {
89-
setRootTheme(THEMES.SYSTEM, isOLED)
88+
setRootTheme(THEMES.SYSTEM, isOLED);
9089
}
91-
})
90+
};
91+
92+
// Add listener for theme change based on system preferences
93+
mediaQuery.addEventListener("change", mediaQueryListener);
94+
95+
// Clean up the event listener when the component is destroyed
96+
return () => {
97+
mediaQuery.removeEventListener("change", mediaQueryListener);
98+
};
9299
}
93100

94-
export { init, handleChangeTheme }
101+
export { init, handleChangeTheme };

src/js/app/ui.js

-42
This file was deleted.

src/js/content.js

+10-12
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
import { init as initThemes } from './app/themeManager'
2-
import { init as initFloating } from './app/floatingBtn'
3-
import { init as initColors } from './app/mainColors'
4-
import { init as initFonts } from './app/mainFonts'
5-
import { init as initWidths } from './app/mainWidths'
6-
// import { initCanvasObserver } from './app/ui' // nah, it doesnt work for canvas...
1+
import { init as initThemes } from "./app/themeManager";
2+
import { init as initFloating } from "./app/floatingBtn";
3+
import { init as initColors } from "./app/mainColors";
4+
import { init as initFonts } from "./app/mainFonts";
5+
import { init as initWidths } from "./app/mainWidths";
76

8-
initThemes()
9-
initFloating()
10-
initColors()
11-
initFonts()
12-
initWidths()
13-
// initCanvasObserver()
7+
initThemes();
8+
initFloating();
9+
initColors();
10+
initFonts();
11+
initWidths();
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
// main:has(#prompt-textarea) [class*='lg:max-w-[40rem]'] {
22
// main:has(#prompt-textarea) [class*="xl:max-w-[48rem]"] {
3-
main:has(#composer-background) {
3+
// main:has(#composer-background) {
4+
main .composer-parent {
45
// container: inline-size;
56

6-
.px-3.text-base.m-auto {
7-
// padding-left: var(--px-chat-bubble-edge-gap) !important;
8-
// padding-right: var(--px-chat-bubble-edge-gap) !important;
7+
// .px-3.text-base.m-auto {
8+
// padding-left: var(--px-chat-bubble-edge-gap) !important;
9+
// padding-right: var(--px-chat-bubble-edge-gap) !important;
910

10-
/* Only textarea wrapper */
11-
.mx-auto.flex.flex-1.text-base:has(>form) {
12-
max-width: var(--w_prompt_textarea);
11+
/* Only textarea wrapper */
12+
[class*="md:max-w-3xl"].mx-auto.flex.flex-1.text-base:has(>form) {
13+
will-change: width, max-width;
14+
max-width: var(--w_prompt_textarea);
1315

14-
@include container('md') {
15-
--w_prompt_textarea: 100%;
16-
// border: 1px solid greenyellow !important;
17-
}
16+
@include container('md') {
17+
--w_prompt_textarea: 100%;
18+
// border: 1px solid greenyellow !important;
19+
}
1820

19-
/* TODO This element causes weird empty space before prompt field which makes prompt fields visually unshifted or misaligned. The best would be to display:none, but idk if that would be filled with some important content in the future. So I use :empty to remove it only when this element is without any content. */
20-
& > div.flex.justify-center:has(~form):empty {
21-
// border: 4px solid orange !important;
22-
display: none !important;
23-
}
21+
/* TODO This element causes weird empty space before prompt field which makes prompt fields visually unshifted or misaligned. The best would be to display:none, but idk if that would be filled with some important content in the future. So I use :empty to remove it only when this element is without any content. */
22+
& > div.flex.justify-center:has(~form):empty {
23+
// border: 4px solid orange !important;
24+
display: none !important;
2425
}
2526
}
2627
}

0 commit comments

Comments
 (0)