Skip to content

Commit 1a0dbd0

Browse files
committed
Refine color mode labels and URL formatting
1 parent 0047e87 commit 1a0dbd0

4 files changed

Lines changed: 140 additions & 21 deletions

File tree

src/components/controls/AdvancedControls.vue

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -73,40 +73,40 @@
7373
<div id="hue-set" :class="panelClasses('hue-set')" role="tabpanel">
7474
<div class="hue-set-layout">
7575
<div class="hue-set-sliders">
76-
<div class="hue-set-sliders__title">Distance:</div>
76+
<div class="hue-set-sliders__title">Deviation:</div>
7777
<div class="hue-set-slider">
78-
<label class="hue-set-slider__label">Sat</label>
78+
<label class="hue-set-slider__label">H</label>
7979
<div class="hue-set-slider__control">
8080
<BaseSlider
81-
:value="saturationRange"
82-
:min="0"
83-
:max="saturationRangeMax"
81+
:value="hueDistance"
82+
:min="hueDistanceMin"
83+
:max="hueDistanceMax"
8484
:step="1"
85-
@update="updateSaturationRange"
85+
@update="updateHueDistance"
8686
/>
8787
</div>
8888
</div>
8989
<div class="hue-set-slider">
90-
<label class="hue-set-slider__label">Col</label>
90+
<label class="hue-set-slider__label">S</label>
9191
<div class="hue-set-slider__control">
9292
<BaseSlider
93-
:value="lightnessRange"
93+
:value="saturationRange"
9494
:min="0"
95-
:max="lightnessRangeMax"
95+
:max="saturationRangeMax"
9696
:step="1"
97-
@update="updateLightnessRange"
97+
@update="updateSaturationRange"
9898
/>
9999
</div>
100100
</div>
101101
<div class="hue-set-slider">
102-
<label class="hue-set-slider__label">Hue</label>
102+
<label class="hue-set-slider__label">L</label>
103103
<div class="hue-set-slider__control">
104104
<BaseSlider
105-
:value="hueDistance"
106-
:min="hueDistanceMin"
107-
:max="hueDistanceMax"
105+
:value="lightnessRange"
106+
:min="0"
107+
:max="lightnessRangeMax"
108108
:step="1"
109-
@update="updateHueDistance"
109+
@update="updateLightnessRange"
110110
/>
111111
</div>
112112
</div>
@@ -226,7 +226,7 @@ export default {
226226
{ id: 'dye', label: 'Dye' },
227227
{ id: 'background', label: 'Bg' },
228228
{ id: 'foreground', label: 'Fg' },
229-
{ id: 'hue-set', label: 'Hue Set' },
229+
{ id: 'hue-set', label: 'Color Mode' },
230230
],
231231
dyeOptions: DYE_OPTIONS,
232232
backgroundOptions: SPECIAL_COLOR_OPTIONS,

src/services/SchemeUrlState.js

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ const SCHEME_QUERY_KEYS = Object.freeze([
2929
'customForegroundColor',
3030
]);
3131
export const SCHEME_STORAGE_KEY = '4bit:scheme-search';
32+
const LIGHTNESS_STEP = 25 / 64;
33+
const QUANTIZE_EPSILON = 1e-9;
3234

3335
function browserWindow() {
3436
return typeof window !== 'undefined' ? window : null;
@@ -66,6 +68,14 @@ function searchParamsFor(search = '') {
6668
return new URLSearchParams(normalizedSearch ? normalizedSearch.slice(1) : '');
6769
}
6870

71+
function serializeSearchParams(params) {
72+
return Array.from(params.entries())
73+
.map(([key, value]) => (
74+
`${encodeURIComponent(key)}=${encodeURIComponent(value).replace(/%2C/gi, ',')}`
75+
))
76+
.join('&');
77+
}
78+
6979
function safelyReadPersistedSearch(storage) {
7080
try {
7181
return normalizeSearch(storage?.getItem(SCHEME_STORAGE_KEY) || '');
@@ -96,7 +106,7 @@ function mergeSchemeSearch(currentSearch = '', schemeSearch = '') {
96106
params.set(key, value);
97107
});
98108

99-
const mergedSearch = params.toString();
109+
const mergedSearch = serializeSearchParams(params);
100110
return mergedSearch ? `?${mergedSearch}` : '';
101111
}
102112

@@ -172,9 +182,81 @@ function sameValue(first, second) {
172182
return first === second;
173183
}
174184

185+
function serializeRoundedNumber(value, maxDecimals) {
186+
const roundedValue = Number(value.toFixed(maxDecimals));
187+
188+
if (Object.is(roundedValue, -0)) {
189+
return '0';
190+
}
191+
192+
return String(roundedValue);
193+
}
194+
195+
function serializeQuantizedNumber(value, { step, maxDecimals }) {
196+
const numericValue = Number(value);
197+
198+
if (!Number.isFinite(numericValue)) {
199+
return String(value);
200+
}
201+
202+
const quantizedValue = Math.round(numericValue / step) * step;
203+
204+
if (Math.abs(numericValue - quantizedValue) <= QUANTIZE_EPSILON) {
205+
return serializeRoundedNumber(quantizedValue, maxDecimals);
206+
}
207+
208+
return String(numericValue);
209+
}
210+
211+
function serializeIntegerLike(value) {
212+
return serializeQuantizedNumber(value, { step: 1, maxDecimals: 0 });
213+
}
214+
215+
function serializePickerNumber(value) {
216+
return serializeQuantizedNumber(value, { step: 0.01, maxDecimals: 2 });
217+
}
218+
219+
function serializeLightnessValue(value) {
220+
return serializeQuantizedNumber(value, { step: LIGHTNESS_STEP, maxDecimals: 6 });
221+
}
222+
223+
function serializeSchemeQueryValue(key, value) {
224+
switch (key) {
225+
case 'hue':
226+
case 'hueDistance':
227+
case 'degrees':
228+
case 'saturation':
229+
case 'saturationRange':
230+
case 'lightnessRange':
231+
return Array.isArray(value)
232+
? value.map(serializeIntegerLike).join(',')
233+
: serializeIntegerLike(value);
234+
case 'chromaticLightness':
235+
case 'blackLightness':
236+
case 'whiteLightness':
237+
return value.map(serializeLightnessValue).join(',');
238+
case 'dyeColor':
239+
return [
240+
serializeIntegerLike(value[0]),
241+
serializePickerNumber(value[1]),
242+
serializePickerNumber(value[2]),
243+
serializePickerNumber(value[3]),
244+
].join(',');
245+
case 'customBackgroundColor':
246+
case 'customForegroundColor':
247+
return [
248+
serializeIntegerLike(value[0]),
249+
serializePickerNumber(value[1]),
250+
serializePickerNumber(value[2]),
251+
].join(',');
252+
default:
253+
return Array.isArray(value) ? value.join(',') : String(value);
254+
}
255+
}
256+
175257
function setParamIfChanged(params, key, value, defaultValue) {
176258
if (!sameValue(value, defaultValue)) {
177-
params.set(key, Array.isArray(value) ? value.join(',') : String(value));
259+
params.set(key, serializeSchemeQueryValue(key, value));
178260
}
179261
}
180262

@@ -397,7 +479,7 @@ export function buildSchemeQueryParams(scheme) {
397479
}
398480

399481
export function buildSchemeSearch(scheme) {
400-
const params = buildSchemeQueryParams(scheme).toString();
482+
const params = serializeSearchParams(buildSchemeQueryParams(scheme));
401483
return params ? `?${params}` : '';
402484
}
403485

@@ -460,7 +542,7 @@ export class SchemeUrlSync {
460542
currentParams.set(key, value);
461543
});
462544

463-
const nextSearch = currentParams.toString();
545+
const nextSearch = serializeSearchParams(currentParams);
464546
const nextUrl = `${this.location.pathname}${nextSearch ? `?${nextSearch}` : ''}${this.location.hash}`;
465547
const currentUrl = `${this.location.pathname}${this.location.search}${this.location.hash}`;
466548

tests/SchemeUrlState.test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,43 @@ describe('SchemeUrlState', () => {
9393
expect(readSchemeFromSearch(buildSchemeSearch(scheme))).toEqual(scheme);
9494
});
9595

96+
it('serializes quantized numeric values compactly without losing precision', () => {
97+
const scheme = createDefaultScheme();
98+
99+
scheme.normalChromaticLightness = 127 / 2.56;
100+
scheme.brightChromaticLightness = 191 / 2.56;
101+
scheme.normalBlackLightness = 1 / 2.56;
102+
scheme.brightBlackLightness = 32 / 2.56;
103+
scheme.dyeColor = {
104+
hue: 180,
105+
saturation: 50.1,
106+
lightness: 50.25,
107+
alpha: 0.25,
108+
};
109+
110+
const params = new URLSearchParams(buildSchemeSearch(scheme).slice(1));
111+
112+
expect(params.get('chromaticLightness')).toBe('49.609375,74.609375');
113+
expect(params.get('blackLightness')).toBe('0.390625,12.5');
114+
expect(params.get('dyeColor')).toBe('180,50.1,50.25,0.25');
115+
expect(readSchemeFromSearch(`?${params.toString()}`)).toEqual(scheme);
116+
});
117+
118+
it('keeps comma-separated numeric lists readable in the generated URL', () => {
119+
const scheme = createDefaultScheme();
120+
121+
scheme.normalChromaticLightness = 120 / 2.56;
122+
scheme.brightChromaticLightness = 192 / 2.56;
123+
scheme.normalBlackLightness = 1 / 2.56;
124+
scheme.brightBlackLightness = 32 / 2.56;
125+
126+
const search = buildSchemeSearch(scheme);
127+
128+
expect(search).toContain('chromaticLightness=46.875,75');
129+
expect(search).toContain('blackLightness=0.390625,12.5');
130+
expect(search).not.toContain('%2C');
131+
});
132+
96133
it('preserves unrelated query params while updating scheme params', () => {
97134
const scheme = createDefaultScheme();
98135
scheme.hue = 10;

tests/SocialShare.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('SocialShare', () => {
1818
origin: 'https://ciembor.github.io',
1919
pathname: '/4bit/',
2020
})).toBe(
21-
'https://ciembor.github.io/4bit/?hue=12&hueSet=duotone&hueDistance=18&degrees=0%2C18%2C180%2C198%2C162%2C342'
21+
'https://ciembor.github.io/4bit/?hue=12&hueSet=duotone&hueDistance=18&degrees=0,18,180,198,162,342'
2222
);
2323
});
2424

0 commit comments

Comments
 (0)