Skip to content

Commit

Permalink
Add types for utils (#520)
Browse files Browse the repository at this point in the history
  • Loading branch information
devmount authored Jul 15, 2024
1 parent 35b09c6 commit 5525595
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 56 deletions.
6 changes: 3 additions & 3 deletions frontend/src/components/CalendarQalendar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import CalendarEvent from '@/elements/calendar/CalendarEvent.vue';
import {
appointmentState,
bookingStatus,
colorSchemes,
ColorSchemes,
dateFormatStrings,
defaultSlotDuration,
qalendarSlotDurations,
Expand Down Expand Up @@ -363,8 +363,8 @@ watch(route, () => {
<template>
<div
class="w-full"
:style="{'color-scheme': preferredTheme === colorSchemes.dark ? 'dark' : null}"
:class="{'is-light-mode': preferredTheme === colorSchemes.light}"
:style="{'color-scheme': preferredTheme === ColorSchemes.Dark ? 'dark' : null}"
:class="{'is-light-mode': preferredTheme === ColorSchemes.Light}"
>
<qalendar
:events="calendarEvents"
Expand Down
20 changes: 9 additions & 11 deletions frontend/src/components/SettingsGeneral.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
<label class="mt-4 flex items-center pl-4">
<div class="w-full max-w-2xs">{{ t('label.theme') }}</div>
<select v-model="theme" class="w-full max-w-sm rounded-md">
<option v-for="(key, label) in colorSchemes" :key="key" :value="key">
{{ t('label.' + label) }}
<option v-for="value in Object.values(ColorSchemes)" :key="value" :value="value">
{{ t('label.' + value) }}
</option>
</select>
</label>
Expand Down Expand Up @@ -87,10 +87,8 @@
</template>

<script setup>
import { colorSchemes } from '@/definitions';
import {
ref, reactive, inject, watch,
} from 'vue';
import { ColorSchemes } from '@/definitions';
import { ref, reactive, inject, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useUserStore } from '@/stores/user-store';
import { dayjsKey } from "@/keys";
Expand All @@ -112,20 +110,20 @@ watch(locale, (newValue) => {
// handle theme mode
// TODO: move to settings store
const initialTheme = localStorage?.getItem('theme')
? colorSchemes[localStorage?.getItem('theme')]
: colorSchemes.system;
? localStorage.getItem('theme')
: ColorSchemes.System;
const theme = ref(initialTheme);
watch(theme, (newValue) => {
switch (newValue) {
case colorSchemes.dark:
case ColorSchemes.Dark:
localStorage?.setItem('theme', 'dark');
document.documentElement.classList.add('dark');
break;
case colorSchemes.light:
case ColorSchemes.Light:
localStorage?.setItem('theme', 'light');
document.documentElement.classList.remove('dark');
break;
case colorSchemes.system:
case ColorSchemes.System:
localStorage?.removeItem('theme');
if (!window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.remove('dark');
Expand Down
14 changes: 6 additions & 8 deletions frontend/src/definitions.js → frontend/src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,12 @@ export const settingsSections = {
};

/**
* available color schemes for theme
* @readonly
* @enum
* Available color schemes for theme
*/
export const colorSchemes = {
system: 1,
dark: 2,
light: 3,
export enum ColorSchemes {
System = 'system',
Dark = 'dark',
Light = 'light',
};

/**
Expand Down Expand Up @@ -311,7 +309,7 @@ export default {
appointmentViews,
bookingCalendarViews,
calendarViews,
colorSchemes,
ColorSchemes,
scheduleCreationState,
filterOptions,
listColumns,
Expand Down
31 changes: 31 additions & 0 deletions frontend/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@ export type Appointment = {
active: boolean;
};

export type CustomEvent = {
attendee?: Attendee;
slot_status: number; // TODO: definitions.bookingStatus
booking_status: number; // TODO: definitions.appointmentState
calendar_title: string;
calendar_color: string;
duration: number;
preview: boolean;
all_day: boolean;
remote: boolean;
tentative: boolean;
};

export type EventPopup = {
event: CustomEvent;
display: string;
top: string|number;
left: string|number;
};

export type Calendar = {
id?: number;
connected: boolean;
Expand Down Expand Up @@ -145,3 +165,14 @@ export type AppointmentListResponse = UseFetchReturn<Appointment[]>;
export type CalendarListResponse = UseFetchReturn<Calendar[]>;
export type ScheduleListResponse = UseFetchReturn<Schedule[]>;
export type ExternalConnectionCollectionResponse = UseFetchReturn<ExternalConnectionCollection>;

// Utility types
export type Coloring = {
border?: string;
background?: string;
}

export type HTMLElementEvent = Event & {
target: HTMLElement;
currentTarget: HTMLElement;
}
65 changes: 31 additions & 34 deletions frontend/src/utils.js → frontend/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// get the first key of given object that points to given value
import { colorSchemes } from '@/definitions';
import { ColorSchemes } from '@/definitions';
import { CustomEvent, Coloring, EventPopup, HTMLElementEvent } from './models';

export const keyByValue = (o, v) => Object.keys(o).find((k) => o[k] === v);
// find a key by a given value and return it
export const keyByValue = (o: Object, v: number|string): number|string => Object.keys(o).find((k) => o[k] === v);

// create event color for border and background, inherited from calendar color attribute
export const eventColor = (event, placeholder) => {
export const eventColor = (event: CustomEvent, placeholder: Boolean): Coloring => {
const color = {
border: null,
background: null,
Expand All @@ -23,7 +25,7 @@ export const eventColor = (event, placeholder) => {
};

// create initials from given name
export const initials = (name) => {
export const initials = (name: string): string => {
if (name) {
const parts = name.toUpperCase().split(' ');
return parts.length > 1
Expand All @@ -34,43 +36,39 @@ export const initials = (name) => {
};

// file download
export const download = (data, filename, contenttype = 'text/plain') => {
export const download = (data: BlobPart, filename: string, contenttype: string = 'text/plain'): void => {
const a = document.createElement('a');
const file = new Blob([data], { type: `${contenttype};charset=UTF-8`, endings: 'native' });
// IE10+
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(file, filename);
} else {
// other browsers
const url = URL.createObjectURL(file);
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
// TODO: use fetch or similar to programmatically trigger a download
const url = URL.createObjectURL(file);
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
};

// handle time format, return dayjs format string
// can be either set by the user (local storage) or detected from system
export const timeFormat = () => {
export const timeFormat = (): string => {
const is12HourTime = Intl.DateTimeFormat().resolvedOptions().hour12 ? 12 : 24;
const format = Number(localStorage?.getItem('timeFormat')) ?? is12HourTime;
return format === 24 ? 'HH:mm' : 'hh:mm A';
};

// event popup handling
export const initialEventPopupData = {
export const initialEventPopupData: EventPopup = {
event: null,
display: 'none',
top: 0,
left: 'initial',
};

// calculate properties of event popup for given element and show popup
export const showEventPopup = (el, event, position = 'right') => {
export const showEventPopup = (el: HTMLElementEvent, event: CustomEvent, position: string = 'right') => {
const obj = { ...initialEventPopupData };
obj.event = event;
obj.display = 'block';
Expand All @@ -91,9 +89,8 @@ export const showEventPopup = (el, event, position = 'right') => {
/**
* Returns the stored locale setting or null if none is set.
* TODO: This should be moved to a settings store
* @returns {string|null}
*/
export const getLocale = () => {
export const getLocale = (): string|null => {
const locale = localStorage?.getItem('locale');
if (!locale) {
return null;
Expand All @@ -104,30 +101,30 @@ export const getLocale = () => {
/**
* Returns the stored theme value. If the stored value does not exist, it will guess based on prefers-color-scheme.
* TODO: This should be moved to a settings store
* @returns {colorSchemes} - Colour theme value
* @returns {ColorSchemes} - Colour theme value
*/
export const getPreferredTheme = () => {
export const getPreferredTheme = (): string => {
const theme = localStorage?.getItem('theme');
if (!theme) {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? colorSchemes.dark : colorSchemes.light;
return window.matchMedia('(prefers-color-scheme: dark)').matches ? ColorSchemes.Dark : ColorSchemes.Light;
}

switch (theme) {
case 'dark':
return colorSchemes.dark;
return ColorSchemes.Dark;
case 'light':
return colorSchemes.light;
return ColorSchemes.Light;
default:
// This would be colorSchemes.system, but I feel like we need a definitive answer here.
return window.matchMedia('(prefers-color-scheme: dark)').matches ? colorSchemes.dark : colorSchemes.light;
// This would be ColorSchemes.System, but I feel like we need a definitive answer here.
return window.matchMedia('(prefers-color-scheme: dark)').matches ? ColorSchemes.Dark : ColorSchemes.Light;
}
};

/**
* via: https://stackoverflow.com/a/11868398
*/
export const getAccessibleColor = (hexcolor) => {
const defaultColor = getPreferredTheme() === colorSchemes.dark ? 'white' : 'black';
export const getAccessibleColor = (hexcolor: string): string => {
const defaultColor = getPreferredTheme() === ColorSchemes.Dark ? 'white' : 'black';
if (!hexcolor) {
return defaultColor;
}
Expand Down

0 comments on commit 5525595

Please sign in to comment.