mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-02 00:06:23 +00:00
feat: show calendar events in light tinted colours on request
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Pressable, StyleSheet, Text, View, type StyleProp, type ViewStyle } from 'react-native';
|
import { Pressable, StyleSheet, Text, View, type StyleProp, type ViewStyle } from 'react-native';
|
||||||
import type { EventBlockColors } from '../../lib/event-colors';
|
import { TINTED_BAR_WIDTH, type EventBlockColors } from '../../lib/event-colors';
|
||||||
|
|
||||||
// Events in the week and day grids, shared by the paged WeekView and the
|
// Events in the week and day grids, shared by the paged WeekView and the
|
||||||
// scrolling TimeGridScrollView. Every event is a solid block of its calendar
|
// scrolling TimeGridScrollView. Every event is a solid block of its calendar
|
||||||
@@ -80,9 +80,11 @@ export function TimedEventBlock({
|
|||||||
styles.block,
|
styles.block,
|
||||||
corners('vertical', continuesBefore, continuesAfter, EVENT_RADIUS),
|
corners('vertical', continuesBefore, continuesAfter, EVENT_RADIUS),
|
||||||
{ backgroundColor: colors.fill, borderColor: ringColor },
|
{ backgroundColor: colors.fill, borderColor: ringColor },
|
||||||
|
colors.bar ? styles.blockWithBar : null,
|
||||||
style,
|
style,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
{colors.bar ? <View pointerEvents="none" style={[styles.startBar, { backgroundColor: colors.bar }]} /> : null}
|
||||||
{inactive && (
|
{inactive && (
|
||||||
// The calendar-coloured outline sits inside the ring.
|
// The calendar-coloured outline sits inside the ring.
|
||||||
<View
|
<View
|
||||||
@@ -143,10 +145,12 @@ export function AllDayEventBar({
|
|||||||
styles.bar,
|
styles.bar,
|
||||||
corners('horizontal', continuesBefore, continuesAfter, EVENT_RADIUS),
|
corners('horizontal', continuesBefore, continuesAfter, EVENT_RADIUS),
|
||||||
{ backgroundColor: colors.fill },
|
{ backgroundColor: colors.fill },
|
||||||
|
colors.bar ? styles.barWithBar : null,
|
||||||
inactive && [styles.barInactive, { borderColor: colors.border ?? undefined }],
|
inactive && [styles.barInactive, { borderColor: colors.border ?? undefined }],
|
||||||
style,
|
style,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
{colors.bar ? <View pointerEvents="none" style={[styles.startBar, { backgroundColor: colors.bar }]} /> : null}
|
||||||
<Text style={[styles.barTitle, { color: colors.text }]} numberOfLines={1}>
|
<Text style={[styles.barTitle, { color: colors.text }]} numberOfLines={1}>
|
||||||
{continuesBefore ? '… ' : ''}
|
{continuesBefore ? '… ' : ''}
|
||||||
<Text style={inactive && styles.struck}>{title}</Text>
|
<Text style={inactive && styles.struck}>{title}</Text>
|
||||||
@@ -164,6 +168,11 @@ const styles = StyleSheet.create({
|
|||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
},
|
},
|
||||||
outline: { ...StyleSheet.absoluteFillObject, borderWidth: 1 },
|
outline: { ...StyleSheet.absoluteFillObject, borderWidth: 1 },
|
||||||
|
// The title clears the light style's bar.
|
||||||
|
blockWithBar: { paddingStart: 3 + TINTED_BAR_WIDTH },
|
||||||
|
barWithBar: { paddingStart: 4 + TINTED_BAR_WIDTH },
|
||||||
|
// "Light event colors": the calendar colour along the start edge.
|
||||||
|
startBar: { position: 'absolute', top: 0, bottom: 0, start: 0, width: TINTED_BAR_WIDTH },
|
||||||
blockTitle: { fontSize: 12, lineHeight: 15, fontWeight: '500' },
|
blockTitle: { fontSize: 12, lineHeight: 15, fontWeight: '500' },
|
||||||
blockTime: { fontSize: 10.5, lineHeight: 13, fontWeight: '400', opacity: 0.85 },
|
blockTime: { fontSize: 10.5, lineHeight: 13, fontWeight: '400', opacity: 0.85 },
|
||||||
struck: { textDecorationLine: 'line-through' },
|
struck: { textDecorationLine: 'line-through' },
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import type { Calendar, CalendarEvent } from '../../api/types';
|
import type { Calendar, CalendarEvent } from '../../api/types';
|
||||||
import { componentSizes, spacing, typography, type ThemePalette } from '../../theme/tokens';
|
import { componentSizes, spacing, typography, type ThemePalette } from '../../theme/tokens';
|
||||||
import { useColors } from '../../theme/colors';
|
import { useColors } from '../../theme/colors';
|
||||||
|
import { useSettingsStore } from '../../stores/settings-store';
|
||||||
import {
|
import {
|
||||||
buildEventDayIndex,
|
buildEventDayIndex,
|
||||||
eventsOnDayFromIndex,
|
eventsOnDayFromIndex,
|
||||||
@@ -26,7 +27,7 @@ import {
|
|||||||
} from '../../lib/calendar-utils';
|
} from '../../lib/calendar-utils';
|
||||||
import { dayIndexIn, monthChipShowsTime, monthKeyOf, monthMask } from '../../lib/calendar-month-scroll';
|
import { dayIndexIn, monthChipShowsTime, monthKeyOf, monthMask } from '../../lib/calendar-month-scroll';
|
||||||
import { isInactiveEvent } from '../../lib/calendar-participants';
|
import { isInactiveEvent } from '../../lib/calendar-participants';
|
||||||
import { eventBlockColors } from '../../lib/event-colors';
|
import { TINTED_BAR_WIDTH, eventBlockColors } from '../../lib/event-colors';
|
||||||
import { useCalendarLocale } from '../../lib/calendar-locale';
|
import { useCalendarLocale } from '../../lib/calendar-locale';
|
||||||
import { dayCellAccessibilityLabel } from '../../lib/calendar-a11y';
|
import { dayCellAccessibilityLabel } from '../../lib/calendar-a11y';
|
||||||
import { displayNow } from '../../lib/calendar-timezone';
|
import { displayNow } from '../../lib/calendar-timezone';
|
||||||
@@ -156,6 +157,8 @@ function MonthWeekRowInner({
|
|||||||
const c = useColors();
|
const c = useColors();
|
||||||
const { t } = useCalendarLocale();
|
const { t } = useCalendarLocale();
|
||||||
const { width } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
|
// "Light event colors" (webmail calendarTintedEvents).
|
||||||
|
const tinted = useSettingsStore((s) => s.calendarTintedEvents);
|
||||||
const chipTime = monthChipShowsTime(true, width / 7);
|
const chipTime = monthChipShowsTime(true, width / 7);
|
||||||
if (variant === 'full') {
|
if (variant === 'full') {
|
||||||
return (
|
return (
|
||||||
@@ -254,13 +257,14 @@ function MonthWeekRowInner({
|
|||||||
// Solid bars in the calendar colour; declined and
|
// Solid bars in the calendar colour; declined and
|
||||||
// cancelled events outlined (repos/branding/APP.md).
|
// cancelled events outlined (repos/branding/APP.md).
|
||||||
const inactive = isInactiveEvent(event, currentUserEmails);
|
const inactive = isInactiveEvent(event, currentUserEmails);
|
||||||
const colors = eventBlockColors(getEventColor(event, calendars), inactive, c);
|
const colors = eventBlockColors(getEventColor(event, calendars), inactive, c, tinted);
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
key={`${event.id}-${idx}`}
|
key={`${event.id}-${idx}`}
|
||||||
style={[
|
style={[
|
||||||
styles.chip,
|
styles.chip,
|
||||||
{ backgroundColor: colors.fill },
|
{ backgroundColor: colors.fill },
|
||||||
|
colors.bar ? { borderStartWidth: TINTED_BAR_WIDTH, borderStartColor: colors.bar } : null,
|
||||||
colors.border !== null && [styles.chipInactive, { borderColor: colors.border }],
|
colors.border !== null && [styles.chipInactive, { borderColor: colors.border }],
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -327,6 +331,8 @@ function FullWeekRow({
|
|||||||
}) {
|
}) {
|
||||||
const { width } = useWindowDimensions();
|
const { width } = useWindowDimensions();
|
||||||
const { t } = useCalendarLocale();
|
const { t } = useCalendarLocale();
|
||||||
|
// "Light event colors" (webmail calendarTintedEvents).
|
||||||
|
const tinted = useSettingsStore((s) => s.calendarTintedEvents);
|
||||||
const showTime = monthChipShowsTime(showTimeInMonthView, width / 7);
|
const showTime = monthChipShowsTime(showTimeInMonthView, width / 7);
|
||||||
return (
|
return (
|
||||||
<View style={[styles.fullWeekRow, { height }]}>
|
<View style={[styles.fullWeekRow, { height }]}>
|
||||||
@@ -365,7 +371,7 @@ function FullWeekRow({
|
|||||||
</View>
|
</View>
|
||||||
{dayEvents.slice(0, shown).map((event, idx) => {
|
{dayEvents.slice(0, shown).map((event, idx) => {
|
||||||
const inactive = isInactiveEvent(event, currentUserEmails);
|
const inactive = isInactiveEvent(event, currentUserEmails);
|
||||||
const colors = eventBlockColors(getEventColor(event, calendars), inactive, c);
|
const colors = eventBlockColors(getEventColor(event, calendars), inactive, c, tinted);
|
||||||
// No time on events that fill the whole day.
|
// No time on events that fill the whole day.
|
||||||
const time = showTime && !event.showWithoutTime && !isTimedEventFullDayOnDate(event, d)
|
const time = showTime && !event.showWithoutTime && !isTimedEventFullDayOnDate(event, d)
|
||||||
? `${format(getEventStartDate(event), timePattern(timeFormat), { locale })} `
|
? `${format(getEventStartDate(event), timePattern(timeFormat), { locale })} `
|
||||||
@@ -376,6 +382,7 @@ function FullWeekRow({
|
|||||||
style={[
|
style={[
|
||||||
styles.fullChip,
|
styles.fullChip,
|
||||||
{ backgroundColor: colors.fill },
|
{ backgroundColor: colors.fill },
|
||||||
|
colors.bar ? { borderStartWidth: TINTED_BAR_WIDTH, borderStartColor: colors.bar } : null,
|
||||||
colors.border !== null && [styles.chipInactive, { borderColor: colors.border }],
|
colors.border !== null && [styles.chipInactive, { borderColor: colors.border }],
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { differenceInCalendarDays, format, type Locale } from 'date-fns';
|
|||||||
import type { Calendar, CalendarEvent } from '../../api/types';
|
import type { Calendar, CalendarEvent } from '../../api/types';
|
||||||
import { spacing, typography, type ThemePalette } from '../../theme/tokens';
|
import { spacing, typography, type ThemePalette } from '../../theme/tokens';
|
||||||
import { useColors } from '../../theme/colors';
|
import { useColors } from '../../theme/colors';
|
||||||
|
import { useSettingsStore } from '../../stores/settings-store';
|
||||||
import { useCalendarLocale } from '../../lib/calendar-locale';
|
import { useCalendarLocale } from '../../lib/calendar-locale';
|
||||||
import { eventAccessibilityLabel } from '../../lib/calendar-a11y';
|
import { eventAccessibilityLabel } from '../../lib/calendar-a11y';
|
||||||
import {
|
import {
|
||||||
@@ -120,6 +121,8 @@ function TimeGridScrollViewInner({
|
|||||||
onCreateAtTime,
|
onCreateAtTime,
|
||||||
}: TimeGridScrollViewProps) {
|
}: TimeGridScrollViewProps) {
|
||||||
const c = useColors();
|
const c = useColors();
|
||||||
|
// "Light event colors" (webmail calendarTintedEvents).
|
||||||
|
const tinted = useSettingsStore((s) => s.calendarTintedEvents);
|
||||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||||
const { locale, t } = useCalendarLocale();
|
const { locale, t } = useCalendarLocale();
|
||||||
const index = React.useMemo(
|
const index = React.useMemo(
|
||||||
@@ -448,6 +451,7 @@ function TimeGridScrollViewInner({
|
|||||||
getEventColor(segment.event, calendars),
|
getEventColor(segment.event, calendars),
|
||||||
isInactiveEvent(segment.event, currentUserEmails),
|
isInactiveEvent(segment.event, currentUserEmails),
|
||||||
c,
|
c,
|
||||||
|
tinted,
|
||||||
)}
|
)}
|
||||||
continuesBefore={segment.continuesBefore}
|
continuesBefore={segment.continuesBefore}
|
||||||
continuesAfter={segment.continuesAfter}
|
continuesAfter={segment.continuesAfter}
|
||||||
@@ -606,6 +610,8 @@ const DayColumn = React.memo(function DayColumn({
|
|||||||
draftHour: number;
|
draftHour: number;
|
||||||
}) {
|
}) {
|
||||||
const { locale, t } = useCalendarLocale();
|
const { locale, t } = useCalendarLocale();
|
||||||
|
// "Light event colors" (webmail calendarTintedEvents).
|
||||||
|
const tinted = useSettingsStore((s) => s.calendarTintedEvents);
|
||||||
const handleLongPress = React.useCallback(
|
const handleLongPress = React.useCallback(
|
||||||
(e: GestureResponderEvent) => {
|
(e: GestureResponderEvent) => {
|
||||||
onLongPressAt?.(day, hourAtOffset(e.nativeEvent.locationY, HOUR_HEIGHT));
|
onLongPressAt?.(day, hourAtOffset(e.nativeEvent.locationY, HOUR_HEIGHT));
|
||||||
@@ -639,6 +645,7 @@ const DayColumn = React.memo(function DayColumn({
|
|||||||
getEventColor(event, calendars),
|
getEventColor(event, calendars),
|
||||||
isInactiveEvent(event, currentUserEmails),
|
isInactiveEvent(event, currentUserEmails),
|
||||||
colors,
|
colors,
|
||||||
|
tinted,
|
||||||
)}
|
)}
|
||||||
ringColor={colors.background}
|
ringColor={colors.background}
|
||||||
continuesBefore={continuesBefore}
|
continuesBefore={continuesBefore}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { displayNow, displayNowMinutes, isDisplayToday } from '../../lib/calenda
|
|||||||
import type { Calendar, CalendarEvent } from '../../api/types';
|
import type { Calendar, CalendarEvent } from '../../api/types';
|
||||||
import { spacing, typography, type ThemePalette } from '../../theme/tokens';
|
import { spacing, typography, type ThemePalette } from '../../theme/tokens';
|
||||||
import { useColors } from '../../theme/colors';
|
import { useColors } from '../../theme/colors';
|
||||||
|
import { useSettingsStore } from '../../stores/settings-store';
|
||||||
import { useCalendarLocale } from '../../lib/calendar-locale';
|
import { useCalendarLocale } from '../../lib/calendar-locale';
|
||||||
import { eventAccessibilityLabel } from '../../lib/calendar-a11y';
|
import { eventAccessibilityLabel } from '../../lib/calendar-a11y';
|
||||||
import {
|
import {
|
||||||
@@ -66,6 +67,8 @@ function WeekViewInner({
|
|||||||
currentUserEmails,
|
currentUserEmails,
|
||||||
}: WeekViewProps) {
|
}: WeekViewProps) {
|
||||||
const c = useColors();
|
const c = useColors();
|
||||||
|
// "Light event colors" (webmail calendarTintedEvents).
|
||||||
|
const tinted = useSettingsStore((s) => s.calendarTintedEvents);
|
||||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||||
const { locale, t } = useCalendarLocale();
|
const { locale, t } = useCalendarLocale();
|
||||||
const scrollRef = React.useRef<ScrollView>(null);
|
const scrollRef = React.useRef<ScrollView>(null);
|
||||||
@@ -212,6 +215,7 @@ function WeekViewInner({
|
|||||||
getEventColor(segment.event, calendars),
|
getEventColor(segment.event, calendars),
|
||||||
isInactiveEvent(segment.event, currentUserEmails),
|
isInactiveEvent(segment.event, currentUserEmails),
|
||||||
c,
|
c,
|
||||||
|
tinted,
|
||||||
)}
|
)}
|
||||||
continuesBefore={segment.continuesBefore}
|
continuesBefore={segment.continuesBefore}
|
||||||
continuesAfter={segment.continuesAfter}
|
continuesAfter={segment.continuesAfter}
|
||||||
@@ -287,6 +291,7 @@ function WeekViewInner({
|
|||||||
getEventColor(event, calendars),
|
getEventColor(event, calendars),
|
||||||
isInactiveEvent(event, currentUserEmails),
|
isInactiveEvent(event, currentUserEmails),
|
||||||
c,
|
c,
|
||||||
|
tinted,
|
||||||
)}
|
)}
|
||||||
ringColor={c.background}
|
ringColor={c.background}
|
||||||
continuesBefore={continuesBefore}
|
continuesBefore={continuesBefore}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export function CalendarSettings() {
|
|||||||
const timeZone = useSettingsStore((s) => s.calendarTimeZone);
|
const timeZone = useSettingsStore((s) => s.calendarTimeZone);
|
||||||
const showTimeInMonth = useSettingsStore((s) => s.calendarShowTimeInMonth);
|
const showTimeInMonth = useSettingsStore((s) => s.calendarShowTimeInMonth);
|
||||||
const showWeekNumbers = useSettingsStore((s) => s.calendarShowWeekNumbers);
|
const showWeekNumbers = useSettingsStore((s) => s.calendarShowWeekNumbers);
|
||||||
|
const tintedEvents = useSettingsStore((s) => s.calendarTintedEvents);
|
||||||
const freeScroll = useSettingsStore((s) => s.calendarFreeScroll);
|
const freeScroll = useSettingsStore((s) => s.calendarFreeScroll);
|
||||||
const birthdayCal = useSettingsStore((s) => s.showBirthdayCalendar);
|
const birthdayCal = useSettingsStore((s) => s.showBirthdayCalendar);
|
||||||
const tasksEnabled = useSettingsStore((s) => s.enableCalendarTasks);
|
const tasksEnabled = useSettingsStore((s) => s.enableCalendarTasks);
|
||||||
@@ -135,6 +136,19 @@ export function CalendarSettings() {
|
|||||||
/>
|
/>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
|
||||||
|
<SettingItem
|
||||||
|
label={t('calendar.settings.tinted_events', 'Light event colors')}
|
||||||
|
description={t(
|
||||||
|
'calendar.settings.tinted_events_desc',
|
||||||
|
'Show events as a light tint of their calendar color with a colored bar at the start, as in Outlook and on printouts, instead of solid blocks.',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ToggleSwitch
|
||||||
|
checked={tintedEvents}
|
||||||
|
onChange={(v) => update('calendarTintedEvents', v)}
|
||||||
|
/>
|
||||||
|
</SettingItem>
|
||||||
|
|
||||||
<SettingItem
|
<SettingItem
|
||||||
label={t('calendar.settings.show_week_numbers', 'Show week numbers')}
|
label={t('calendar.settings.show_week_numbers', 'Show week numbers')}
|
||||||
description={t('calendar.settings.show_week_numbers_mobile_desc', 'Display week numbers in the month view.')}
|
description={t('calendar.settings.show_week_numbers_mobile_desc', 'Display week numbers in the month view.')}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { eventBlockColors, mixColors } from '../event-colors';
|
||||||
|
import { mergeWithDefaults } from '../../stores/settings-store';
|
||||||
|
|
||||||
|
// The webmail's "Light event colors" (calendarTintedEvents) had no
|
||||||
|
// counterpart on the phone (file 10 "light tinted colours"). Webmail
|
||||||
|
// event-card eventFillStyle: color-mix(color 20%, background), foreground
|
||||||
|
// text, a calendar-coloured bar at the start.
|
||||||
|
|
||||||
|
const page = { background: '#ffffff', text: '#111111' };
|
||||||
|
const read = (rel: string) => fs.readFileSync(path.resolve(__dirname, '../..', rel), 'utf8');
|
||||||
|
|
||||||
|
describe('light event colours', () => {
|
||||||
|
it('tints active events and adds the start bar', () => {
|
||||||
|
expect(eventBlockColors('#3b82f6', false, page, true)).toEqual({
|
||||||
|
fill: mixColors('#3b82f6', '#ffffff', 0.2),
|
||||||
|
text: '#111111',
|
||||||
|
border: null,
|
||||||
|
bar: '#3b82f6',
|
||||||
|
});
|
||||||
|
expect(mixColors('#3b82f6', '#ffffff', 0.2)).toBe('#d8e6fd');
|
||||||
|
// Solid blocks stay the default.
|
||||||
|
expect(eventBlockColors('#3b82f6', false, page)).toMatchObject({ fill: '#3b82f6' });
|
||||||
|
expect(eventBlockColors('#3b82f6', false, page).bar).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps declined and cancelled events outlined', () => {
|
||||||
|
const colors = eventBlockColors('#3b82f6', true, page, true);
|
||||||
|
expect(colors.border).toBe('#3b82f6');
|
||||||
|
expect(colors.fill).toBe('#ffffff');
|
||||||
|
expect(colors.bar).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is a setting, off by default, under the webmail key', () => {
|
||||||
|
expect(mergeWithDefaults({}).calendarTintedEvents).toBe(false);
|
||||||
|
expect(mergeWithDefaults({ calendarTintedEvents: true }).calendarTintedEvents).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// No RN renderer in the test env: check the wiring.
|
||||||
|
it('is offered in the calendar settings and read by every grid', () => {
|
||||||
|
expect(read('components/settings/CalendarSettings.tsx')).toMatch(/onChange=\{\(v\) => update\('calendarTintedEvents', v\)\}/);
|
||||||
|
for (const view of ['MonthView', 'TimeGridScrollView', 'WeekView']) {
|
||||||
|
const src = read(`components/calendar/${view}.tsx`);
|
||||||
|
expect(src).toMatch(/const tinted = useSettingsStore\(\(s\) => s\.calendarTintedEvents\);/);
|
||||||
|
expect(src).toMatch(/eventBlockColors\([\s\S]*?tinted,?\s*\)/);
|
||||||
|
}
|
||||||
|
expect(read('components/calendar/EventBlock.tsx').match(/colors\.bar \? <View pointerEvents="none" style=\{\[styles\.startBar/g)).toHaveLength(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
+17
-2
@@ -149,17 +149,28 @@ export interface EventBlockColors {
|
|||||||
text: string;
|
text: string;
|
||||||
/** Inset border in the calendar colour, for declined and cancelled events only. */
|
/** Inset border in the calendar colour, for declined and cancelled events only. */
|
||||||
border: string | null;
|
border: string | null;
|
||||||
|
/** The light style's bar along the start edge, in the calendar colour; null without it. */
|
||||||
|
bar?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Share of the calendar colour in the light style's fill (webmail color-mix 20%). */
|
||||||
|
export const TINTED_FILL_MIX = 0.2;
|
||||||
|
/** Width of the light style's bar (webmail --start-bar-width). */
|
||||||
|
export const TINTED_BAR_WIDTH = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill, label and border of an event block, bar or chip. Active events are
|
* Fill, label and border of an event block, bar or chip. Active events are
|
||||||
* solid calendar colour with a computed label colour; declined and cancelled
|
* solid calendar colour with a computed label colour, or with "Light event
|
||||||
* ones sit on the page ground inside a calendar-coloured border.
|
* colors" (`tinted`) a 20% tint of it on the page ground with the text
|
||||||
|
* colour and a calendar-coloured bar at the start (webmail eventFillStyle);
|
||||||
|
* declined and cancelled ones sit on the page ground inside a
|
||||||
|
* calendar-coloured border either way.
|
||||||
*/
|
*/
|
||||||
export function eventBlockColors(
|
export function eventBlockColors(
|
||||||
calendarColor: string,
|
calendarColor: string,
|
||||||
inactive: boolean,
|
inactive: boolean,
|
||||||
page: { background: string; text: string },
|
page: { background: string; text: string },
|
||||||
|
tinted = false,
|
||||||
): EventBlockColors {
|
): EventBlockColors {
|
||||||
if (inactive) {
|
if (inactive) {
|
||||||
return {
|
return {
|
||||||
@@ -168,5 +179,9 @@ export function eventBlockColors(
|
|||||||
border: calendarColor,
|
border: calendarColor,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (tinted) {
|
||||||
|
const fill = mixColors(calendarColor, page.background, TINTED_FILL_MIX);
|
||||||
|
if (fill) return { fill, text: page.text, border: null, bar: calendarColor };
|
||||||
|
}
|
||||||
return { fill: calendarColor, text: readableTextOn(calendarColor), border: null };
|
return { fill: calendarColor, text: readableTextOn(calendarColor), border: null };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -325,6 +325,10 @@ export interface PersistedSettings {
|
|||||||
// reader: the quick reply box under a conversation can be turned off
|
// reader: the quick reply box under a conversation can be turned off
|
||||||
// (webmail #1370, same key as the webmail's for export/import).
|
// (webmail #1370, same key as the webmail's for export/import).
|
||||||
quickReplyEnabled: boolean;
|
quickReplyEnabled: boolean;
|
||||||
|
// calendar2: events as a light tint of their calendar colour with a
|
||||||
|
// coloured bar at the start, instead of solid blocks (webmail
|
||||||
|
// calendarTintedEvents, same key).
|
||||||
|
calendarTintedEvents: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_PERSISTED: PersistedSettings = {
|
const DEFAULT_PERSISTED: PersistedSettings = {
|
||||||
@@ -487,6 +491,8 @@ const DEFAULT_PERSISTED: PersistedSettings = {
|
|||||||
pushNotifyInboxOnly: false,
|
pushNotifyInboxOnly: false,
|
||||||
// reader:
|
// reader:
|
||||||
quickReplyEnabled: true,
|
quickReplyEnabled: true,
|
||||||
|
// calendar2:
|
||||||
|
calendarTintedEvents: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface SettingsState extends PersistedSettings {
|
export interface SettingsState extends PersistedSettings {
|
||||||
|
|||||||
Reference in New Issue
Block a user