1.8.6
Integrate fonts Add office profile Fix onload pulldown selections
This commit is contained in:
+5
-3
@@ -54,8 +54,8 @@ export function UTCIForecast() {
|
||||
visibleCols, setVisibleCols, toggleCol,
|
||||
showDecimals, toggleShowDecimals,
|
||||
showUnits, toggleShowUnits,
|
||||
activityOptions, placeOptions,
|
||||
activityValue, activityLabel, placeValue, placeLabel,
|
||||
activityOptions, placeOptions, workOptions,
|
||||
activityValue, activityLabel, placeValue, placeLabel, workValue, workLabel,
|
||||
skinType, setSkinType,
|
||||
vehicleType, setVehicleType,
|
||||
vehicleVent, setVehicleVent,
|
||||
@@ -321,12 +321,14 @@ export function UTCIForecast() {
|
||||
proPromptSource=${proPromptSource} setProPromptSource=${setProPromptSource}
|
||||
activeProfile=${activeProfile} activateProfile=${activateProfile} activeCols=${activeCols}
|
||||
visibleCols=${visibleCols}
|
||||
activityOptions=${activityOptions} placeOptions=${placeOptions}
|
||||
activityOptions=${activityOptions} placeOptions=${placeOptions} workOptions=${workOptions}
|
||||
activityValue=${activityValue} activityLabel=${activityLabel}
|
||||
placeValue=${placeValue} placeLabel=${placeLabel}
|
||||
workValue=${workValue} workLabel=${workLabel}
|
||||
outdoorsVariant=${outdoorsVariant} setOutdoorsVariantAndSave=${setOutdoorsVariantAndSave}
|
||||
setActiveProfile=${setActiveProfile} setVisibleCols=${setVisibleCols}
|
||||
setIndoorMode=${setIndoorMode} setIndoorManaged=${setIndoorManaged}
|
||||
setBuildingType=${setBuildingType}
|
||||
dayTabsRef=${dayTabsRef} canScrollLeft=${canScrollLeft} canScrollRight=${canScrollRight}
|
||||
scrollDayTabs=${scrollDayTabs}
|
||||
/>`}
|
||||
|
||||
@@ -22,10 +22,13 @@
|
||||
// visibleCols - object of col-key -> boolean visibility
|
||||
// activityOptions - dropdown options for Activities selector
|
||||
// placeOptions - dropdown options for Places selector
|
||||
// workOptions - dropdown options for Work selector
|
||||
// activityValue - current activity dropdown value
|
||||
// activityLabel - current activity dropdown label
|
||||
// placeValue - current place dropdown value
|
||||
// placeLabel - current place dropdown label
|
||||
// workValue - current work dropdown value
|
||||
// workLabel - current work dropdown label
|
||||
// outdoorsVariant - current outdoors sub-variant key
|
||||
// setOutdoorsVariantAndSave - setter that also persists to localStorage
|
||||
// setActiveProfile - raw setter for activeProfile
|
||||
@@ -41,7 +44,7 @@
|
||||
import { h, Fragment } from '../../vendor/preact.js';
|
||||
import htm from '../../vendor/htm.js';
|
||||
import { confidenceBand } from '../utils.js';
|
||||
import { FREE_DAYS, FILTER_PROFILES, OUTDOORS_VARIANTS, profileButtonOrder, activityVariantKeys, placeVariantKeys } from '../config.js';
|
||||
import { FREE_DAYS, FILTER_PROFILES, OUTDOORS_VARIANTS, profileButtonOrder, activityVariantKeys, placeVariantKeys, workVariantKeys } from '../config.js';
|
||||
import { CloudIcon, PrecipIcon, CustomSelect } from '../components.js';
|
||||
|
||||
const html = htm.bind(h);
|
||||
@@ -54,12 +57,13 @@ export function DayTabs({
|
||||
proPromptSource, setProPromptSource,
|
||||
activeProfile, activateProfile, activeCols,
|
||||
visibleCols,
|
||||
activityOptions, placeOptions,
|
||||
activityOptions, placeOptions, workOptions,
|
||||
activityValue, activityLabel,
|
||||
placeValue, placeLabel,
|
||||
workValue, workLabel,
|
||||
outdoorsVariant, setOutdoorsVariantAndSave,
|
||||
setActiveProfile, setVisibleCols,
|
||||
setIndoorMode, setIndoorManaged,
|
||||
setIndoorMode, setIndoorManaged, setBuildingType,
|
||||
dayTabsRef, canScrollLeft, canScrollRight, scrollDayTabs,
|
||||
}) {
|
||||
return html`
|
||||
@@ -398,11 +402,34 @@ export function DayTabs({
|
||||
<${CustomSelect}
|
||||
key="activities"
|
||||
value=${activityValue}
|
||||
isOn=${activeProfile === 'farming' || (activeProfile === 'outdoors' && activityVariantKeys.includes(outdoorsVariant))}
|
||||
isOn=${activeProfile === 'outdoors' && activityVariantKeys.includes(outdoorsVariant)}
|
||||
noHide=${true}
|
||||
hideLabel="🎯 Activities"
|
||||
buttonLabel=${activityValue === 'off' ? '🎯 Activities' : ` ${activityLabel}`}
|
||||
options=${activityOptions}
|
||||
onChange=${(v) => {
|
||||
if (v === 'off') { activateProfile('basic'); return; }
|
||||
if (OUTDOORS_VARIANTS[v]?.proOnly && !isPro) {
|
||||
setProPromptSource(`variant:${v}`);
|
||||
setProPromptDay(0);
|
||||
return;
|
||||
}
|
||||
try { localStorage.setItem('sunscope_profile', 'outdoors'); } catch (e) { /* ignore */ }
|
||||
setActiveProfile('outdoors');
|
||||
setOutdoorsVariantAndSave(v);
|
||||
setVisibleCols({ ...OUTDOORS_VARIANTS[v].cols });
|
||||
setIndoorMode('off');
|
||||
setIndoorManaged(false);
|
||||
}}
|
||||
/>
|
||||
<${CustomSelect}
|
||||
key="work"
|
||||
value=${workValue}
|
||||
isOn=${activeProfile === 'farming' || (activeProfile === 'outdoors' && workVariantKeys.includes(outdoorsVariant))}
|
||||
noHide=${true}
|
||||
hideLabel="💼 Work"
|
||||
buttonLabel=${workValue === 'off' ? '💼 Work' : ` ${workLabel}`}
|
||||
options=${workOptions}
|
||||
onChange=${(v) => {
|
||||
if (v === 'off') { activateProfile('basic'); return; }
|
||||
if (v === 'farming') { activateProfile('farming'); return; }
|
||||
@@ -415,8 +442,14 @@ export function DayTabs({
|
||||
setActiveProfile('outdoors');
|
||||
setOutdoorsVariantAndSave(v);
|
||||
setVisibleCols({ ...OUTDOORS_VARIANTS[v].cols });
|
||||
setIndoorMode('off');
|
||||
setIndoorManaged(false);
|
||||
if (v === 'office') {
|
||||
setBuildingType('office');
|
||||
setIndoorMode('on');
|
||||
setIndoorManaged(false);
|
||||
} else {
|
||||
setIndoorMode('off');
|
||||
setIndoorManaged(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span class="profile-divider" aria-hidden="true"></span>
|
||||
|
||||
+12
-2
@@ -94,6 +94,10 @@ export const OUTDOORS_VARIANTS = {
|
||||
hiking: { name: 'Hiking', proOnly: true, cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: true, uvA: true, uvB: true, burn: true, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
||||
photography: { name: 'Photography', proOnly: true, cols: { hour: true, air: true, rh: false, dew: false, wind: false, dir: false, cloud: true, sun: true, direct: true, diffuse: true, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: false, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
||||
fishing: { name: 'Fishing', proOnly: true, cols: { hour: true, air: true, rh: false, dew: true, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
||||
// Work variants
|
||||
market: { name: 'Market Trading', cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
||||
windowcleaning: { name: 'Window Cleaning', proOnly: true, cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
||||
office: { name: 'Office', proOnly: true, cols: { hour: true, air: true, rh: true, dew: false, wind: false, dir: false, cloud: true, sun: true, direct: true, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: true, vis: false, aqi: true, pollen: false } },
|
||||
};
|
||||
|
||||
// Pollen type options shown in the pulldown selector.
|
||||
@@ -128,13 +132,19 @@ export const variantIcons = {
|
||||
hiking: '🏔️',
|
||||
photography: '📸',
|
||||
fishing: '🎣',
|
||||
market: '🏪',
|
||||
windowcleaning: '🪟',
|
||||
office: '🏢',
|
||||
};
|
||||
|
||||
// Which variant keys appear in the Activities dropdown.
|
||||
export const activityVariantKeys = ['cycling', 'running', 'dogwalk', 'sailing', 'wintersports', 'naturist', 'hiking', 'photography', 'fishing'];
|
||||
export const activityVariantKeys = ['dogwalk', 'running', 'cycling', 'hiking', 'fishing', 'sailing', 'wintersports', 'photography', 'naturist'];
|
||||
|
||||
// Which variant keys appear in the Places dropdown.
|
||||
export const placeVariantKeys = ['urban', 'beach', 'events', 'park', 'airport', 'festival', 'construction'];
|
||||
export const placeVariantKeys = ['urban', 'park', 'beach', 'events', 'festival', 'airport'];
|
||||
|
||||
// Which variant keys appear in the Work dropdown.
|
||||
export const workVariantKeys = ['farming', 'construction', 'market', 'windowcleaning', 'office'];
|
||||
|
||||
// Tooltip text shown when the user clicks/hovers a table column header.
|
||||
export const COL_DESCRIPTIONS = {
|
||||
|
||||
@@ -26,7 +26,7 @@ import { useState, useEffect, useRef, useCallback } from '../../vendor/preact-ho
|
||||
import {
|
||||
FREE_DAYS, FILTER_PROFILES, OUTDOORS_VARIANTS,
|
||||
profileButtonOrder, variantIcons,
|
||||
activityVariantKeys, placeVariantKeys,
|
||||
activityVariantKeys, placeVariantKeys, workVariantKeys,
|
||||
} from '../config.js';
|
||||
import { utciCategory } from '../utils.js';
|
||||
import { buildHourlyRows } from '../compute.js';
|
||||
@@ -167,8 +167,20 @@ export function useAppState() {
|
||||
|
||||
// ── 5. SKIN, VEHICLE, INDOOR, POLLEN ─────────────────────────────────
|
||||
const [skinType, setSkinType] = useState('II');
|
||||
const [vehicleType, setVehicleType] = useState('car');
|
||||
const [vehicleVent, setVehicleVent] = useState(false);
|
||||
const [vehicleType, setVehicleType] = useState(() => {
|
||||
try { return localStorage.getItem('sunscope_vehicle_type') || 'car'; } catch (e) { return 'car'; }
|
||||
});
|
||||
const setVehicleTypeAndSave = (v) => {
|
||||
try { localStorage.setItem('sunscope_vehicle_type', v); } catch (e) { /* ignore */ }
|
||||
setVehicleType(v);
|
||||
};
|
||||
const [vehicleVent, setVehicleVent] = useState(() => {
|
||||
try { return localStorage.getItem('sunscope_vehicle_vent') === '1'; } catch (e) { return false; }
|
||||
});
|
||||
const setVehicleVentAndSave = (v) => {
|
||||
try { localStorage.setItem('sunscope_vehicle_vent', v ? '1' : '0'); } catch (e) { /* ignore */ }
|
||||
setVehicleVent(v);
|
||||
};
|
||||
|
||||
const [outdoorsVariant, setOutdoorsVariant] = useState(() => {
|
||||
try { return localStorage.getItem('sunscope_outdoors_variant') || 'urban'; } catch (e) { return 'urban'; }
|
||||
@@ -190,15 +202,40 @@ export function useAppState() {
|
||||
? (OUTDOORS_VARIANTS[outdoorsVariant]?.cols ?? FILTER_PROFILES.outdoors.cols)
|
||||
: (FILTER_PROFILES[activeProfile]?.cols ?? FILTER_PROFILES.basic.cols);
|
||||
|
||||
const [buildingType, setBuildingType] = useState('brick');
|
||||
const [indoorManaged, setIndoorManaged] = useState(false);
|
||||
const [indoorMode, setIndoorMode] = useState(() => {
|
||||
const [buildingType, setBuildingType] = useState(() => {
|
||||
try {
|
||||
const saved = localStorage.getItem('sunscope_profile') || 'basic';
|
||||
const cols = FILTER_PROFILES[saved]?.cols ?? FILTER_PROFILES.basic.cols;
|
||||
const saved = localStorage.getItem('sunscope_building_type');
|
||||
if (saved) return saved;
|
||||
// Fall back to office if the saved variant is office and no explicit building saved
|
||||
const savedVariant = localStorage.getItem('sunscope_outdoors_variant') || '';
|
||||
if (savedVariant === 'office') return 'office';
|
||||
} catch (e) { /* ignore */ }
|
||||
return 'brick';
|
||||
});
|
||||
const setBuildingTypeAndSave = (v) => {
|
||||
try { localStorage.setItem('sunscope_building_type', v); } catch (e) { /* ignore */ }
|
||||
setBuildingType(v);
|
||||
};
|
||||
const [indoorManaged, setIndoorManaged] = useState(() => {
|
||||
try { return localStorage.getItem('sunscope_indoor_managed') === '1'; } catch (e) { return false; }
|
||||
});
|
||||
const setIndoorManagedAndSave = (v) => {
|
||||
try { localStorage.setItem('sunscope_indoor_managed', v ? '1' : '0'); } catch (e) { /* ignore */ }
|
||||
setIndoorManaged(v);
|
||||
};
|
||||
const [indoorMode, setIndoorMode] = useState(() => {
|
||||
try {
|
||||
const saved = localStorage.getItem('sunscope_indoor_mode');
|
||||
if (saved) return saved;
|
||||
const profile = localStorage.getItem('sunscope_profile') || 'basic';
|
||||
const cols = FILTER_PROFILES[profile]?.cols ?? FILTER_PROFILES.basic.cols;
|
||||
return (cols['indoorT'] || cols['managedT']) ? 'on' : 'off';
|
||||
} catch (e) { return 'off'; }
|
||||
});
|
||||
const setIndoorModeAndSave = (v) => {
|
||||
try { localStorage.setItem('sunscope_indoor_mode', v); } catch (e) { /* ignore */ }
|
||||
setIndoorMode(v);
|
||||
};
|
||||
|
||||
const [pollenType, setPollenType] = useState(() => {
|
||||
try { return localStorage.getItem('sunscope_pollen_type') || 'all_pollen'; } catch (e) { return 'all_pollen'; }
|
||||
@@ -233,19 +270,13 @@ export function useAppState() {
|
||||
const searchTimeout = useRef(null);
|
||||
|
||||
// Derived selectors used by profile controls
|
||||
const activityOptions = [
|
||||
{
|
||||
value: 'farming',
|
||||
label: `${FILTER_PROFILES.farming.icon} ${FILTER_PROFILES.farming.label}`,
|
||||
},
|
||||
...activityVariantKeys.map((k) => {
|
||||
const v = OUTDOORS_VARIANTS[k];
|
||||
return {
|
||||
value: k,
|
||||
label: `${variantIcons[k]} ${v.name}${v.proOnly && !isPro ? ' 🔒' : ''}`,
|
||||
};
|
||||
}),
|
||||
];
|
||||
const activityOptions = activityVariantKeys.map((k) => {
|
||||
const v = OUTDOORS_VARIANTS[k];
|
||||
return {
|
||||
value: k,
|
||||
label: `${variantIcons[k]} ${v.name}${v.proOnly && !isPro ? ' 🔒' : ''}`,
|
||||
};
|
||||
});
|
||||
const placeOptions = placeVariantKeys.map((k) => {
|
||||
const v = OUTDOORS_VARIANTS[k];
|
||||
return {
|
||||
@@ -253,17 +284,31 @@ export function useAppState() {
|
||||
label: `${variantIcons[k]} ${v.name}${v.proOnly && !isPro ? ' 🔒' : ''}`,
|
||||
};
|
||||
});
|
||||
const workOptions = workVariantKeys.map((k) => {
|
||||
const v = k === 'farming'
|
||||
? { name: FILTER_PROFILES.farming.label, proOnly: false }
|
||||
: OUTDOORS_VARIANTS[k];
|
||||
const icon = k === 'farming' ? FILTER_PROFILES.farming.icon : variantIcons[k];
|
||||
return {
|
||||
value: k,
|
||||
label: `${icon} ${v.name}${v.proOnly && !isPro ? ' 🔒' : ''}`,
|
||||
};
|
||||
});
|
||||
|
||||
const activityValue = activeProfile === 'farming'
|
||||
? 'farming'
|
||||
: activeProfile === 'outdoors' && activityVariantKeys.includes(outdoorsVariant)
|
||||
? outdoorsVariant
|
||||
: 'off';
|
||||
const activityValue = activeProfile === 'outdoors' && activityVariantKeys.includes(outdoorsVariant)
|
||||
? outdoorsVariant
|
||||
: 'off';
|
||||
const activityLabel = activityOptions.find((o) => o.value === activityValue)?.label;
|
||||
const placeValue = activeProfile === 'outdoors' && placeVariantKeys.includes(outdoorsVariant)
|
||||
? outdoorsVariant
|
||||
: 'off';
|
||||
const placeLabel = placeOptions.find((o) => o.value === placeValue)?.label;
|
||||
const workValue = activeProfile === 'farming'
|
||||
? 'farming'
|
||||
: activeProfile === 'outdoors' && workVariantKeys.includes(outdoorsVariant)
|
||||
? outdoorsVariant
|
||||
: 'off';
|
||||
const workLabel = workOptions.find((o) => o.value === workValue)?.label;
|
||||
|
||||
// ── 6. TABLE REFS ────────────────────────────────────────────────────
|
||||
const headStickyRef = useRef(null);
|
||||
@@ -439,17 +484,18 @@ export function useAppState() {
|
||||
activeProfile, setActiveProfile,
|
||||
activateProfile, activeCols,
|
||||
visibleCols, setVisibleCols, toggleCol,
|
||||
activityOptions, placeOptions,
|
||||
activityOptions, placeOptions, workOptions,
|
||||
activityValue, activityLabel,
|
||||
placeValue, placeLabel,
|
||||
workValue, workLabel,
|
||||
// skin, vehicle, indoor, pollen
|
||||
skinType, setSkinType,
|
||||
vehicleType, setVehicleType,
|
||||
vehicleVent, setVehicleVent,
|
||||
vehicleType, setVehicleType: setVehicleTypeAndSave,
|
||||
vehicleVent, setVehicleVent: setVehicleVentAndSave,
|
||||
outdoorsVariant, setOutdoorsVariantAndSave,
|
||||
buildingType, setBuildingType,
|
||||
indoorManaged, setIndoorManaged,
|
||||
indoorMode, setIndoorMode,
|
||||
buildingType, setBuildingType: setBuildingTypeAndSave,
|
||||
indoorManaged, setIndoorManaged: setIndoorManagedAndSave,
|
||||
indoorMode, setIndoorMode: setIndoorModeAndSave,
|
||||
pollenType, setPollenTypeAndSave,
|
||||
showDecimals, toggleShowDecimals,
|
||||
showUnits, toggleShowUnits,
|
||||
|
||||
@@ -211,6 +211,7 @@ export const BUILDING_TYPES = {
|
||||
timber: { name: 'Timber Frame / New Build', uWall: 0.22, lagHours: 2, glazingRatio: 0.22, gValue: 0.35, orientFactor: 0.50, curtainBlock: 0.70, ventAlpha: 0.35, solarScale: 0.08, baseTemp: 17.0, internalGain: 0.8, retainedScale: 0.45 },
|
||||
flat: { name: 'Top-floor Flat', uWall: 0.40, lagHours: 3, glazingRatio: 0.18, gValue: 0.63, orientFactor: 0.50, curtainBlock: 0.75, ventAlpha: 0.20, solarScale: 0.11, baseTemp: 17.0, internalGain: 0.9, retainedScale: 0.45 },
|
||||
conservatory:{ name: 'Conservatory / Sun Room', uWall: 1.20, lagHours: 1, glazingRatio: 0.70, gValue: 0.72, orientFactor: 0.70, curtainBlock: 0.50, ventAlpha: 0.50, solarScale: 0.045, baseTemp: 12.0, internalGain: 0.2, retainedScale: 0.05 },
|
||||
office: { name: 'Office Block', uWall: 0.25, lagHours: 2, glazingRatio: 0.45, gValue: 0.55, orientFactor: 0.65, curtainBlock: 0.50, ventAlpha: 0.15, solarScale: 0.055, baseTemp: 18.5, internalGain: 2.5, retainedScale: 0.30 },
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user