Skip to content

Commit a2dd33a

Browse files
committed
allowing to provide light/dark-specific theming colors
(might be useful for dark themes in order to switch contrasts)
1 parent 1f304d9 commit a2dd33a

File tree

6 files changed

+147
-61
lines changed

6 files changed

+147
-61
lines changed

cloud/functions/src/crawlers/crawler-parsers.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,23 @@ export const THEMABLE_LANGUAGE_PARSER = z.object({
4646
themeColor: HEX_COLOR_PARSER
4747
})
4848

49+
export const THEME_COLORS_PARSER = z.object({
50+
primaryHex: HEX_COLOR_PARSER,
51+
primaryContrastHex: HEX_COLOR_PARSER,
52+
secondaryHex: HEX_COLOR_PARSER,
53+
secondaryContrastHex: HEX_COLOR_PARSER,
54+
tertiaryHex: HEX_COLOR_PARSER,
55+
tertiaryContrastHex: HEX_COLOR_PARSER
56+
})
57+
4958
export const EVENT_THEME_PARSER = z.object({
50-
colors: z.object({
51-
primaryHex: HEX_COLOR_PARSER,
52-
primaryContrastHex: HEX_COLOR_PARSER,
53-
secondaryHex: HEX_COLOR_PARSER,
54-
secondaryContrastHex: HEX_COLOR_PARSER,
55-
tertiaryHex: HEX_COLOR_PARSER,
56-
tertiaryContrastHex: HEX_COLOR_PARSER
57-
}),
59+
colors: z.union([
60+
THEME_COLORS_PARSER,
61+
z.object({
62+
light: THEME_COLORS_PARSER,
63+
dark: THEME_COLORS_PARSER,
64+
})
65+
]),
5866
headingCustomStyles: z.object({
5967
title: z.string().nullable(),
6068
subTitle: z.string().nullable(),

cloud/functions/src/models/Event.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ListableEvent } from "@shared/event-list.firestore";
1+
import {ListableEvent} from "@shared/event-list.firestore";
22
import {BreakTimeSlot, DailySchedule, DetailedTalk, Talk} from "@shared/daily-schedule.firestore";
33
import {ConferenceDescriptor} from "@shared/conference-descriptor.firestore";
44
import {Replace} from "@shared/type-utils";
@@ -9,10 +9,12 @@ export type BreakTimeslotWithPotentiallyUnknownIcon = Replace<BreakTimeSlot, {
99
}>
1010
}>
1111

12+
export type DescriptorableEvent<T extends Omit<ListableEvent, "websiteUrl">> = Omit<T, "eventFamily"|"eventName"|"websiteUrl"|"visibility"|"spaceToken">;
13+
1214
export interface FullEvent {
1315
id: string,
14-
conferenceDescriptor: Omit<ConferenceDescriptor, "eventFamily"|"eventName"|"websiteUrl"|"visibility"|"spaceToken">,
15-
info: Omit<ListableEvent, "eventFamily"|"eventName"|"websiteUrl"|"visibility"|"spaceToken">,
16+
conferenceDescriptor: DescriptorableEvent<ConferenceDescriptor>,
17+
info: DescriptorableEvent<ListableEvent>,
1618
daySchedules: DailySchedule[],
1719
talks: DetailedTalk[],
1820
}
Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,54 @@
11
import {VoxxrinEventTheme} from "@/models/VoxxrinEvent";
2-
import {DirectiveBinding} from "vue";
2+
import {ObjectDirective} from "vue";
33

4-
export function provideThemedEventStyles(el: HTMLElement, binding: DirectiveBinding<{ theming: VoxxrinEventTheme, backgroundUrl: string, logoUrl: string }|undefined>) {
5-
if(binding.value) {
6-
const variableStyles = {
4+
declare global {
5+
interface HTMLElement {
6+
__colorSchemeListener__?: (e: MediaQueryListEvent) => void;
7+
}
8+
}
9+
10+
const isDarkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
11+
12+
export const provideThemedEventStyles: ObjectDirective<HTMLElement, { theming: VoxxrinEventTheme, backgroundUrl: string, logoUrl: string }|undefined> = {
13+
mounted(el, binding) {
14+
const applyStyles = (isDark: boolean) => {
15+
if(binding.value) {
16+
const colorsStyles = isDark ? binding.value.theming.colors.dark : binding.value.theming.colors.light;
17+
18+
const variableStyles = {
719
'--voxxrin-event-background-url': `url('${binding.value.backgroundUrl}')`,
820
'--voxxrin-event-logo-url': `url('${binding.value.logoUrl}')`,
9-
'--voxxrin-event-theme-colors-primary-hex': binding.value.theming.colors.primaryHex,
10-
'--voxxrin-event-theme-colors-primary-rgb': binding.value.theming.colors.primaryRGB,
11-
'--voxxrin-event-theme-colors-primary-contrast-hex': binding.value.theming.colors.primaryContrastHex,
12-
'--voxxrin-event-theme-colors-primary-contrast-rgb': binding.value.theming.colors.primaryContrastRGB,
13-
'--voxxrin-event-theme-colors-secondary-hex': binding.value.theming.colors.secondaryHex,
14-
'--voxxrin-event-theme-colors-secondary-rgb': binding.value.theming.colors.secondaryRGB,
15-
'--voxxrin-event-theme-colors-secondary-contrast-hex': binding.value.theming.colors.secondaryContrastHex,
16-
'--voxxrin-event-theme-colors-secondary-contrast-rgb': binding.value.theming.colors.secondaryContrastRGB,
17-
'--voxxrin-event-theme-colors-tertiary-hex': binding.value.theming.colors.tertiaryHex,
18-
'--voxxrin-event-theme-colors-tertiary-rgb': binding.value.theming.colors.tertiaryRGB,
19-
'--voxxrin-event-theme-colors-tertiary-contrast-hex': binding.value.theming.colors.tertiaryContrastHex,
20-
'--voxxrin-event-theme-colors-tertiary-contrast-rgb': binding.value.theming.colors.tertiaryContrastRGB,
21-
};
22-
23-
(Object.keys(variableStyles) as Array<keyof typeof variableStyles>).forEach((varName) => {
21+
'--voxxrin-event-theme-colors-primary-hex': colorsStyles.primaryHex,
22+
'--voxxrin-event-theme-colors-primary-rgb': colorsStyles.primaryRGB,
23+
'--voxxrin-event-theme-colors-primary-contrast-hex': colorsStyles.primaryContrastHex,
24+
'--voxxrin-event-theme-colors-primary-contrast-rgb': colorsStyles.primaryContrastRGB,
25+
'--voxxrin-event-theme-colors-secondary-hex': colorsStyles.secondaryHex,
26+
'--voxxrin-event-theme-colors-secondary-rgb': colorsStyles.secondaryRGB,
27+
'--voxxrin-event-theme-colors-secondary-contrast-hex': colorsStyles.secondaryContrastHex,
28+
'--voxxrin-event-theme-colors-secondary-contrast-rgb': colorsStyles.secondaryContrastRGB,
29+
'--voxxrin-event-theme-colors-tertiary-hex': colorsStyles.tertiaryHex,
30+
'--voxxrin-event-theme-colors-tertiary-rgb': colorsStyles.tertiaryRGB,
31+
'--voxxrin-event-theme-colors-tertiary-contrast-hex': colorsStyles.tertiaryContrastHex,
32+
'--voxxrin-event-theme-colors-tertiary-contrast-rgb': colorsStyles.tertiaryContrastRGB,
33+
};
34+
35+
(Object.keys(variableStyles) as Array<keyof typeof variableStyles>).forEach((varName) => {
2436
el.style.setProperty(varName, variableStyles[varName]);
25-
})
37+
})
38+
}
39+
};
40+
41+
const listener = (e: MediaQueryListEvent) => applyStyles(e.matches)
42+
el.__colorSchemeListener__ = listener;
43+
isDarkMediaQuery.addEventListener('change', listener);
44+
45+
applyStyles(isDarkMediaQuery.matches);
46+
},
47+
unmounted(el) {
48+
if (el.__colorSchemeListener__) {
49+
isDarkMediaQuery.removeEventListener('change', el.__colorSchemeListener__);
50+
delete el.__colorSchemeListener__;
2651
}
52+
}
53+
2754
}

mobile/src/models/VoxxrinEvent.ts

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {hexToRGB, ValueObject} from "@/models/utils";
2-
import {EventTheme, ListableEvent} from "@shared/event-list.firestore";
2+
import {EventTheme, ListableEvent, ThemeColors} from "@shared/event-list.firestore";
33
import {DayId, VoxxrinDay} from "@/models/VoxxrinDay";
44
import {Temporal} from "temporal-polyfill";
55
import {useCurrentClock} from "@/state/useCurrentClock";
@@ -40,16 +40,21 @@ export type ListableVoxxrinEvent = Replace<ListableEvent, {
4040
theming: VoxxrinEventTheme,
4141
} & ListableVoxxrinEventVisibility>
4242

43-
export type VoxxrinEventTheme = Replace<EventTheme, {
44-
colors: EventTheme['colors'] & {
45-
primaryRGB: string,
46-
primaryContrastRGB: string,
47-
secondaryRGB: string,
48-
secondaryContrastRGB: string,
49-
tertiaryRGB: string,
50-
tertiaryContrastRGB: string
43+
export type VoxxringThemeColors = ThemeColors & {
44+
primaryRGB: string,
45+
primaryContrastRGB: string,
46+
secondaryRGB: string,
47+
secondaryContrastRGB: string,
48+
tertiaryRGB: string,
49+
tertiaryContrastRGB: string
50+
}
51+
52+
export type VoxxrinEventTheme = Omit<EventTheme, "colors"> & {
53+
colors: {
54+
light: VoxxringThemeColors,
55+
dark: VoxxringThemeColors,
5156
}
52-
}>
57+
}
5358

5459
export function searchEvents(events: ListableVoxxrinEvent[], searchCriteria: { terms: string|undefined, includePastEvents: boolean}, pinnededIds: EventId[]) {
5560
const filteredEvents = events.filter(event => {
@@ -107,16 +112,49 @@ export function firestoreListableEventToVoxxrinListableEvent(firestoreListableEv
107112
}
108113

109114
export function toVoxxrinEventTheme(firestoreTheme: EventTheme): VoxxrinEventTheme {
110-
return {
111-
colors: {
112-
...firestoreTheme.colors,
113-
primaryRGB: hexToRGB(firestoreTheme.colors.primaryHex),
114-
primaryContrastRGB: hexToRGB(firestoreTheme.colors.primaryContrastHex),
115-
secondaryRGB: hexToRGB(firestoreTheme.colors.secondaryHex),
116-
secondaryContrastRGB: hexToRGB(firestoreTheme.colors.secondaryContrastHex),
117-
tertiaryRGB: hexToRGB(firestoreTheme.colors.tertiaryHex),
118-
tertiaryContrastRGB: hexToRGB(firestoreTheme.colors.tertiaryContrastHex),
115+
const colors: VoxxrinEventTheme['colors'] = match(firestoreTheme.colors)
116+
.with({ light: P.any, dark: P.any }, ({light, dark}) => ({
117+
light: {
118+
...light,
119+
primaryRGB: hexToRGB(light.primaryHex),
120+
primaryContrastRGB: hexToRGB(light.primaryContrastHex),
121+
secondaryRGB: hexToRGB(light.secondaryHex),
122+
secondaryContrastRGB: hexToRGB(light.secondaryContrastHex),
123+
tertiaryRGB: hexToRGB(light.tertiaryHex),
124+
tertiaryContrastRGB: hexToRGB(light.tertiaryContrastHex),
119125
},
126+
dark: {
127+
...dark,
128+
primaryRGB: hexToRGB(dark.primaryHex),
129+
primaryContrastRGB: hexToRGB(dark.primaryContrastHex),
130+
secondaryRGB: hexToRGB(dark.secondaryHex),
131+
secondaryContrastRGB: hexToRGB(dark.secondaryContrastHex),
132+
tertiaryRGB: hexToRGB(dark.tertiaryHex),
133+
tertiaryContrastRGB: hexToRGB(dark.tertiaryContrastHex),
134+
}
135+
}))
136+
.otherwise(colors => ({
137+
light: {
138+
...colors,
139+
primaryRGB: hexToRGB(colors.primaryHex),
140+
primaryContrastRGB: hexToRGB(colors.primaryContrastHex),
141+
secondaryRGB: hexToRGB(colors.secondaryHex),
142+
secondaryContrastRGB: hexToRGB(colors.secondaryContrastHex),
143+
tertiaryRGB: hexToRGB(colors.tertiaryHex),
144+
tertiaryContrastRGB: hexToRGB(colors.tertiaryContrastHex),
145+
},
146+
dark: {
147+
...colors,
148+
primaryRGB: hexToRGB(colors.primaryHex),
149+
primaryContrastRGB: hexToRGB(colors.primaryContrastHex),
150+
secondaryRGB: hexToRGB(colors.secondaryHex),
151+
secondaryContrastRGB: hexToRGB(colors.secondaryContrastHex),
152+
tertiaryRGB: hexToRGB(colors.tertiaryHex),
153+
tertiaryContrastRGB: hexToRGB(colors.tertiaryContrastHex),
154+
}
155+
}))
156+
return {
157+
colors,
120158
headingCustomStyles: firestoreTheme.headingCustomStyles,
121159
headingSrcSet: firestoreTheme.headingSrcSet,
122160
customImportedFonts: firestoreTheme.customImportedFonts,

mobile/src/state/useDevUtilities.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@ import {
77
import {ISODatetime} from "@shared/type-utils";
88
import {Temporal} from "temporal-polyfill";
99
import {match, P} from "ts-pattern";
10-
import {VoxxrinConferenceDescriptor} from "@/models/VoxxrinConferenceDescriptor";
1110
import {managedRef as ref} from "@/views/vue-utils";
12-
import {ListableVoxxrinEvent} from "@/models/VoxxrinEvent";
11+
import {ListableVoxxrinEvent, toVoxxrinEventTheme} from "@/models/VoxxrinEvent";
1312
import {Ref} from "vue";
1413
import {updateLogConfigTo} from "@/services/Logger";
14+
import {ConferenceDescriptor} from "@shared/conference-descriptor.firestore";
1515

1616

1717
// if(import.meta.env.DEV) {
1818
// May be useful for debug purposes
1919

2020
type OverridableListableEventProperties = {eventId: string} & Partial<Pick<ListableVoxxrinEvent, "theming"|"location"|"backgroundUrl"|"logoUrl">>;
21-
type OverridableEventDescriptorProperties = {eventId: string} & Partial<Pick<VoxxrinConferenceDescriptor, "headingTitle"|"headingSubTitle"|"theming"|"features"|"infos"|"location"|"backgroundUrl"|"logoUrl">>;
21+
type OverridableEventDescriptorProperties = {eventId: string} & Partial<Pick<ConferenceDescriptor, "headingTitle"|"headingSubTitle"|"theming"|"features"|"infos"|"location"|"backgroundUrl"|"logoUrl">>;
2222

2323
let overridenListableEventPropertiesRef: Ref<OverridableListableEventProperties|undefined>|undefined = undefined
2424
let overridenEventDescriptorPropertiesRef: Ref<OverridableEventDescriptorProperties|undefined>|undefined = undefined
@@ -40,7 +40,13 @@ export function useDevUtilities() {
4040

4141
function overrideCurrentEventDescriptorInfos(overridenEventDescriptorProperties: OverridableEventDescriptorProperties) {
4242
overridenEventDescriptorPropertiesRef!.value = overridenEventDescriptorProperties;
43-
overrideListableEventProperties(overridenEventDescriptorProperties);
43+
44+
const { theming, ...themingLessOverridenEventDescriptorProps } = overridenEventDescriptorProperties;
45+
const voxxrinTheming = theming ? {theming: toVoxxrinEventTheme(theming) }:undefined
46+
overrideListableEventProperties({
47+
...themingLessOverridenEventDescriptorProps,
48+
...voxxrinTheming,
49+
});
4450
}
4551

4652
(window as any)._overrideCurrentEventDescriptorInfos = overrideCurrentEventDescriptorInfos;

shared/event-list.firestore.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import {HexColor, ISODatetime, ISOLocalDate} from "./type-utils";
22

3+
export type ThemeColors = {
4+
primaryHex: HexColor,
5+
primaryContrastHex: HexColor,
6+
secondaryHex: HexColor,
7+
secondaryContrastHex: HexColor,
8+
tertiaryHex: HexColor,
9+
tertiaryContrastHex: HexColor,
10+
}
11+
312
export type EventTheme = {
4-
colors: {
5-
primaryHex: HexColor,
6-
primaryContrastHex: HexColor,
7-
secondaryHex: HexColor,
8-
secondaryContrastHex: HexColor,
9-
tertiaryHex: HexColor,
10-
tertiaryContrastHex: HexColor
13+
colors: ThemeColors | {
14+
light: ThemeColors,
15+
dark: ThemeColors,
1116
},
1217
headingCustomStyles: {
1318
title: string|null,

0 commit comments

Comments
 (0)