Skip to content

Commit dddcecc

Browse files
Fix the god damn theme color setting (#7992)
Fix the god damn theme color
1 parent 06362f9 commit dddcecc

File tree

4 files changed

+187
-36
lines changed

4 files changed

+187
-36
lines changed

e2e/playwright/test-utils.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,3 +1448,94 @@ export async function pinchFromCenter(
14481448

14491449
await locator.dispatchEvent('touchend')
14501450
}
1451+
1452+
// Primarily machinery to click and drag a slider.
1453+
// THIS ASSUMES THE SLIDER IS ALWAYS HORIZONTAL.
1454+
export const inputRangeValueToCoordinate = async function (
1455+
locator: Locator,
1456+
valueTargetStr: string
1457+
): Promise<{
1458+
startPoint: { x: number; y: number }
1459+
offsetFromStartPoint: { x: number; y: number }
1460+
}> {
1461+
const bb = await locator.boundingBox()
1462+
if (bb === null)
1463+
throw new Error("Bounding box is null, can't do fucking shit")
1464+
const editable = await locator.isEditable()
1465+
if (!editable) throw new Error('Cannot slide range, element is not editable')
1466+
const visible = await locator.isVisible()
1467+
if (!visible) throw new Error('Cannot slide range, element is not visible')
1468+
await locator.scrollIntoViewIfNeeded()
1469+
const maybeMin = await locator.getAttribute('min')
1470+
const maybeMax = await locator.getAttribute('max')
1471+
const maybeStep = await locator.getAttribute('step')
1472+
1473+
let low = maybeMin ? Number(maybeMin) : 0
1474+
low = Number.isNaN(low) ? 0 : low
1475+
1476+
let upp = maybeMax ? Number(maybeMax) : 10
1477+
upp = Number.isNaN(upp) ? 10 : upp
1478+
1479+
let step = maybeMax ? Number(maybeStep) : 1
1480+
step = Number.isNaN(step) ? 1 : step
1481+
1482+
let value = Number(valueTargetStr)
1483+
if (Number.isNaN(value)) throw new Error('value must be a number')
1484+
1485+
// into step
1486+
value = value - (value % step)
1487+
1488+
// half step down
1489+
value -= Math.trunc(step / 2)
1490+
1491+
const scale = await locator.page().evaluate(() => window.devicePixelRatio)
1492+
// measured this value in gimp
1493+
const nub = { width: 14 * scale }
1494+
1495+
// 4 is the internal border + padding of the slider
1496+
let offsetX = (bb.width - 4 - nub.width / 2) * (value / (upp + low))
1497+
1498+
// map to coordinate
1499+
// relative to element
1500+
const offsetFromStartPoint = {
1501+
x: Math.trunc(offsetX + 0.5),
1502+
// need to land on the scroll nub
1503+
y: bb.height * 0.5,
1504+
}
1505+
1506+
const startPoint = {
1507+
x: bb.x + nub.width / 2,
1508+
y: bb.y,
1509+
}
1510+
1511+
return {
1512+
startPoint,
1513+
offsetFromStartPoint,
1514+
}
1515+
}
1516+
1517+
export const inputRangeSlideFromCurrentTo = async function (
1518+
locator: Locator,
1519+
valueNext: string
1520+
) {
1521+
const valueCurrent = await locator.inputValue()
1522+
// Can't click it if the computer can't see it!
1523+
await locator.scrollIntoViewIfNeeded()
1524+
const from = await inputRangeValueToCoordinate(locator, valueCurrent)
1525+
const to = await inputRangeValueToCoordinate(locator, valueNext)
1526+
// I (lee) want to force the mouse to be up, but who knows who needs otherwise.
1527+
// await locator.page.mouse.up()
1528+
const page = locator.page()
1529+
await page.mouse.move(
1530+
from.startPoint.x + from.offsetFromStartPoint.x,
1531+
from.startPoint.y + from.offsetFromStartPoint.y,
1532+
{ steps: 10 }
1533+
)
1534+
await page.mouse.down()
1535+
await page.mouse.move(
1536+
to.startPoint.x + to.offsetFromStartPoint.x,
1537+
to.startPoint.y + to.offsetFromStartPoint.y,
1538+
{ steps: 10 }
1539+
)
1540+
await page.mouse.up()
1541+
}

e2e/playwright/testing-settings.spec.ts

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,33 @@ import {
2222
lowerRightMasks,
2323
settingsToToml,
2424
tomlToSettings,
25+
inputRangeSlideFromCurrentTo,
2526
} from '@e2e/playwright/test-utils'
2627
import { expect, test } from '@e2e/playwright/zoo-test'
28+
import type { Page } from '@playwright/test'
29+
30+
const settingsSwitchTab = (page: Page) => async (tab: 'user' | 'proj') => {
31+
const projectSettingsTab = page.getByRole('radio', { name: 'Project' })
32+
const userSettingsTab = page.getByRole('radio', { name: 'User' })
33+
const settingTheme = page.getByTestId('theme')
34+
switch (tab) {
35+
case 'user':
36+
await userSettingsTab.click()
37+
await expect(settingTheme).toBeVisible()
38+
break
39+
case 'proj':
40+
await projectSettingsTab.click()
41+
await expect(settingTheme).not.toBeVisible()
42+
break
43+
default:
44+
const _: never = tab
45+
}
46+
}
2747

2848
test.describe(
2949
'Testing settings',
3050
{
31-
tag: ['@macos', '@windows'],
51+
tag: ['@linux', '@macos', '@windows'],
3252
},
3353
() => {
3454
test('Stored settings are validated and fall back to defaults', async ({
@@ -199,17 +219,18 @@ test.describe(
199219
})
200220

201221
// Selectors and constants
202-
const projectSettingsTab = page.getByRole('radio', { name: 'Project' })
203-
const userSettingsTab = page.getByRole('radio', { name: 'User' })
204222
const resetButton = (level: SettingsLevel) =>
205223
page.getByRole('button', {
206224
name: `Reset ${level}-level settings`,
207225
})
208226
const themeColorSetting = page.locator('#themeColor').getByRole('slider')
227+
209228
const settingValues = {
210229
default: '259',
211-
user: '120',
212-
project: '50',
230+
// Because it's a slider, sometimes the values cannot physically be
231+
// dragged to. You need to adjust this until it works.
232+
user: '48',
233+
project: '77',
213234
}
214235
const resetToast = (level: SettingsLevel) =>
215236
page.getByText(`${level}-level settings were reset`)
@@ -222,21 +243,31 @@ test.describe(
222243
})
223244

224245
await test.step('Set up theme color', async () => {
225-
// Verify we're looking at the project-level settings,
226-
// and it's set to default value
227-
await expect(projectSettingsTab).toBeChecked()
228-
await expect(themeColorSetting).toHaveValue(settingValues.default)
229-
230-
// Set project-level value to 50
231-
await themeColorSetting.fill(settingValues.project)
232-
233-
// Set user-level value to 120
234-
await userSettingsTab.click()
235-
await themeColorSetting.fill(settingValues.user)
236-
await projectSettingsTab.click()
246+
// Verify we're looking at the project-level settings
247+
await settingsSwitchTab(page)('proj')
248+
await themeColorSetting.fill(settingValues.default)
249+
250+
// Set project-level value
251+
await inputRangeSlideFromCurrentTo(
252+
themeColorSetting,
253+
settingValues.project
254+
)
255+
await expect(themeColorSetting).toHaveValue(settingValues.project)
256+
257+
// Set user-level value
258+
// It's the same component so this could fill too soon.
259+
// We need to confirm to wait the user settings tab is loaded.
260+
await settingsSwitchTab(page)('user')
261+
await inputRangeSlideFromCurrentTo(
262+
themeColorSetting,
263+
settingValues.user
264+
)
265+
await expect(themeColorSetting).toHaveValue(settingValues.user)
237266
})
238267

239268
await test.step('Reset project settings', async () => {
269+
await settingsSwitchTab(page)('proj')
270+
240271
// Click the reset settings button.
241272
await resetButton('project').click()
242273

@@ -247,14 +278,17 @@ test.describe(
247278
await expect(themeColorSetting).toHaveValue(settingValues.user)
248279

249280
await test.step(`Check that the user settings did not change`, async () => {
250-
await userSettingsTab.click()
281+
await settingsSwitchTab(page)('user')
251282
await expect(themeColorSetting).toHaveValue(settingValues.user)
252283
})
253284

254285
await test.step(`Set project-level again to test the user-level reset`, async () => {
255-
await projectSettingsTab.click()
256-
await themeColorSetting.fill(settingValues.project)
257-
await userSettingsTab.click()
286+
await settingsSwitchTab(page)('proj')
287+
await inputRangeSlideFromCurrentTo(
288+
themeColorSetting,
289+
settingValues.project
290+
)
291+
await settingsSwitchTab(page)('user')
258292
})
259293
})
260294

@@ -269,7 +303,7 @@ test.describe(
269303
await expect(themeColorSetting).toHaveValue(settingValues.default)
270304

271305
await test.step(`Check that the project settings did not change`, async () => {
272-
await projectSettingsTab.click()
306+
await settingsSwitchTab(page)('proj')
273307
await expect(themeColorSetting).toHaveValue(settingValues.project)
274308
})
275309
})
@@ -303,7 +337,7 @@ test.describe(
303337
projectDirName,
304338
SETTINGS_FILE_NAME
305339
)
306-
const userThemeColor = '120'
340+
const userThemeColor = '175'
307341
const projectThemeColor = '50'
308342
const settingsOpenButton = page.getByRole('link', {
309343
name: 'settings Settings',
@@ -322,7 +356,7 @@ test.describe(
322356
await settingsOpenButton.click()
323357
// The user tab should be selected by default on home
324358
await expect(userSettingsTab).toBeChecked()
325-
await themeColorSetting.fill(userThemeColor)
359+
await inputRangeSlideFromCurrentTo(themeColorSetting, userThemeColor)
326360
await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor)
327361
await settingsCloseButton.click()
328362
await expect
@@ -339,7 +373,10 @@ test.describe(
339373
await settingsOpenButton.click()
340374
// The project tab should be selected by default within a project
341375
await expect(projectSettingsTab).toBeChecked()
342-
await themeColorSetting.fill(projectThemeColor)
376+
await inputRangeSlideFromCurrentTo(
377+
themeColorSetting,
378+
projectThemeColor
379+
)
343380
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
344381
await settingsCloseButton.click()
345382
// Make sure that the project settings file has been written to before continuing
@@ -631,7 +668,7 @@ test.describe(
631668
})
632669

633670
await test.step(`Reset unit setting`, async () => {
634-
await userSettingsTab.click()
671+
await settingsSwitchTab(page)('user')
635672
await defaultUnitSection.hover()
636673
await defaultUnitRollbackButton.click()
637674
await projectSettingsTab.hover()
@@ -666,7 +703,7 @@ test.describe(
666703

667704
// Go to the user tab
668705
await userSettingsTab.hover()
669-
await userSettingsTab.click()
706+
await settingsSwitchTab(page)('user')
670707
await page.waitForTimeout(1000)
671708

672709
await test.step('Change modeling default unit within user tab', async () => {

src/components/Settings/SettingsSection.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export function SettingsSection({
2525
}: SettingsSectionProps) {
2626
return (
2727
<section
28+
data-testid={id}
2829
id={id}
2930
className={
3031
'group p-2 pl-0 grid grid-cols-2 gap-6 items-start ' +

src/lib/settings/initialSettings.tsx

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useRef } from 'react'
1+
import { useRef, useEffect } from 'react'
22

33
import type { CameraOrbitType } from '@rust/kcl-lib/bindings/CameraOrbitType'
44
import type { CameraProjectionType } from '@rust/kcl-lib/bindings/CameraProjectionType'
@@ -158,19 +158,41 @@ export function createSettings() {
158158
defaultValue: '264.5',
159159
description: 'The hue of the primary theme color for the app',
160160
validate: (v) => Number(v) >= 0 && Number(v) < 360,
161+
162+
// The same component instance is used across settings panes / tabs.
161163
Component: ({ value, updateValue }) => {
162-
const preview = (e: React.SyntheticEvent) =>
163-
e.isTrusted &&
164-
'value' in e.currentTarget &&
164+
const refInput = useRef<HTMLInputElement>(null)
165+
166+
const updateColorDot = (value: string) => {
165167
document.documentElement.style.setProperty(
166168
`--primary-hue`,
167-
String(e.currentTarget.value)
169+
String(value)
168170
)
169-
const save = (e: React.SyntheticEvent) =>
171+
}
172+
173+
useEffect(() => {
174+
if (refInput.current === null) return
175+
refInput.current.value = value
176+
updateColorDot(value)
177+
}, [value])
178+
179+
const preview = (e: React.SyntheticEvent) =>
170180
e.isTrusted &&
171181
'value' in e.currentTarget &&
172-
e.currentTarget.value &&
173-
updateValue(String(e.currentTarget.value))
182+
updateColorDot(String(e.currentTarget.value))
183+
184+
const save = (e: React.SyntheticEvent) => {
185+
if (
186+
e.isTrusted &&
187+
'value' in e.currentTarget &&
188+
e.currentTarget.value
189+
) {
190+
const valueNext = String(e.currentTarget.value)
191+
updateValue(valueNext)
192+
updateColorDot(valueNext)
193+
}
194+
}
195+
174196
return (
175197
<div className="flex item-center gap-4 px-2 m-0 py-0">
176198
<div
@@ -181,11 +203,11 @@ export function createSettings() {
181203
/>
182204
<input
183205
type="range"
206+
ref={refInput}
184207
onInput={preview}
185208
onMouseUp={save}
186209
onKeyUp={save}
187210
onPointerUp={save}
188-
defaultValue={value}
189211
min={0}
190212
max={259}
191213
step={1}

0 commit comments

Comments
 (0)