mon-entreprise/site/source/utils.ts

247 lines
6.6 KiB
TypeScript
Raw Normal View History

import { DottedName } from 'modele-social'
import Engine, {
PublicodesExpression,
Rule,
RuleNode,
2022-11-03 16:32:04 +00:00
formatValue,
2022-12-01 15:29:26 +00:00
isPublicodesError,
} from 'publicodes'
2022-11-03 16:32:04 +00:00
import { Situation } from './reducers/rootReducer'
export function capitalise0(name: undefined): undefined
export function capitalise0(name: string): string
export function capitalise0(name?: string) {
return name && name[0].toUpperCase() + name.slice(1)
}
2018-01-18 14:53:20 +00:00
export const debounce = <T>(waitFor: number, fn: (...args: T[]) => void) => {
let timeoutId: ReturnType<typeof setTimeout>
2022-03-28 12:57:11 +00:00
return (...args: T[]) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => fn(...args), waitFor)
}
}
export const fetcher = (url: RequestInfo) => fetch(url).then((r) => r.json())
export function inIframe(): boolean {
try {
return window.self !== window.top
} catch (e) {
return true
}
}
🔥 Migration vers TypeScript Outils ====== Ce commit retire le tooling de Flow, et ajoute le support de TypeScript pour les fichiers .ts et .tsx. Il n'est pas nécessaire de tout migrer d'un coup ce qui facilite la transition. On garde en effet le compilateur Babel avec un preset TypeScript (ce qui permet donc de retirer à la fois les types Flow et TypeScript) plutôt que d'utiliser le compilateur standard pour la conversion du code. Cela permet aussi de mieux s'intégrer avec d'autres outils, notamment les test-runners. Ajout d'une nouvelle commande `yarn run type-check`, intégrée dans CircleCI. Par ailleurs ajout du support de l'opérateur ?? pour donner des valeurs par défaut (nullish-coalescing-operator). Typage des libraires tierces ============================ Les principales libraires que nous utilisons ont un typage TypeScript de bon niveau, ce qui facilite l'intégration. J'ai mis à jour react-i18next et i18next afin de corriger un problème de typage. Typage du code ============== Le typage est loin d'être complet dans ce commit, en particulier il manque les types relatifs au state Redux, ainsi qu'au moteur (règle, explication). Néanmoins le typage des contextes fonctionne, en particulier sitePaths (avec un type récursif non trivial !) qui a déjà permis de détecter un lien mort. Le typage des "paths" (Components/, Règles/, etc.) fonctionne bien, y compris avec l'auto-complétion automatique des import par Typescript. TypeScript se révèle déjà bien agréable dans VSCode (auto-complétion, refacto, etc.) ! Reste à migrer progressivement le reste du code !
2019-10-26 16:21:09 +00:00
export function softCatch<ArgType, ReturnType>(
fn: (arg: ArgType) => ReturnType
): (arg: ArgType) => ReturnType | null {
return function (...args) {
try {
return fn(...args)
} catch (e) {
// eslint-disable-next-line no-console
console.warn(e)
2022-03-28 12:57:11 +00:00
return null
}
}
}
export function getSessionStorage() {
// In some browsers like Brave, even just reading the variable sessionStorage
// is throwing an error in the iframe, so we can't do things if sessionStorage !== undefined
// and we need to wrap it in a try { } catch { } logic
try {
return window.sessionStorage
} catch (e) {
return undefined
}
}
export const currencyFormat = (language: string) => ({
isCurrencyPrefixed: !!formatValue(12, { language, displayedUnit: '€' }).match(
/^€/
),
thousandSeparator: formatValue(1000, { language }).charAt(1),
decimalSeparator: formatValue(0.1, { language }).charAt(1),
})
export function hash(str: string): number {
let hash = 0
let chr
for (let i = 0; i < str.length; i++) {
chr = str.charCodeAt(i)
hash = (hash << 5) - hash + chr
hash |= 0 // Convert to 32bit integer
}
2022-03-28 12:57:11 +00:00
return hash
}
export function omit<T, K extends keyof T>(obj: T, key: K): Omit<T, K> {
const { [key]: _ignore, ...returnObject } = obj
2022-03-28 12:57:11 +00:00
return returnObject
}
2022-10-31 13:06:25 +00:00
/**
* Transforms an object into entries which is then passed to the transform function to be
* modified as desired with map, filter, etc., then transformed back into an object
*/
export const objectTransform = <T, U>(
object: Record<string, T>,
transform: (entries: [string, T][]) => [string, U][]
) => Object.fromEntries(transform(Object.entries(object)))
2022-05-26 16:44:01 +00:00
// TODO: This is will be included in the ES spec soon. Remove our custom
// implementation and rely on browser native support and polyfill when it is
// available.
// https://caniuse.com/?search=groupby
export function groupBy<E, G extends string>(
arr: Array<E>,
callback: (elm: E, index: number, array: Array<E>) => G
): Record<G, Array<E>> {
return arr.reduce((result, item, currentIndex) => {
const key = callback(item, currentIndex, arr)
result[key] = result[key] || []
result[key].push(item)
return result
}, {} as Record<G, Array<E>>)
}
export function isIterable<T>(obj: unknown): obj is Iterable<T> {
return Symbol.iterator in Object(obj)
}
/**
* Check if a key exists in the object and return its value or undefined,
* usefull for type check
* @param obj any object
* @param key key to get value from object
* @returns value or undefined
*/
export const getValueFrom = <
T extends Record<string | number | symbol, unknown>,
K extends string | number | symbol
>(
obj: T,
key: K
): Extract<T, { [k in K]?: unknown }>[K] | undefined =>
key in obj ? obj[key] : undefined
const isMeta = <T>(rule: Rule): rule is Rule & { meta?: T } => 'meta' in rule
/**
* Return typed meta property from a rule
* @param rule
* @param defaultValue
* @returns
*/
export const getMeta = <T>(rule: Rule, defaultValue: T) =>
(isMeta<T>(rule) ? getValueFrom(rule, 'meta') : null) ?? defaultValue
/**
2022-03-17 18:19:40 +00:00
* Wraps each event function specified in eventsToWrap (default onPress) with an
* asynchronous function that waits x ms before executing the original function
* Use this function on button props to avoid ghost click after onPress event
* See this issue https://github.com/betagouv/mon-entreprise/issues/1872
* Maybe the next version of the react-spectrum will handle that natively (issue https://github.com/adobe/react-spectrum/issues/1513)
* @param props
* @param options ms (time of debounce) and eventsToWrap (array of events to wrap with debounce)
* @returns props
*/
export const wrapperDebounceEvents = <T>(
props: T,
2022-03-17 19:07:53 +00:00
{ ms = 25, eventsToWrap = ['onPress'] } = {}
2022-03-17 18:19:40 +00:00
): T => {
if (props && typeof props === 'object') {
const castedProps = props as Record<string, unknown>
eventsToWrap.forEach((event: string) => {
if (event in castedProps) {
const original = castedProps[event]
if (typeof original === 'function') {
const debouncedFunction = async (...params: unknown[]) => {
2022-04-04 15:06:21 +00:00
await new Promise((resolve) =>
setTimeout(() => resolve(original(...params)), ms)
2022-03-17 18:19:40 +00:00
)
}
castedProps[event] = debouncedFunction
}
}
})
}
return props
}
export async function getIframeOffset(): Promise<number> {
return new Promise<number>((resolve, reject) => {
const returnOffset = (evt: MessageEvent) => {
if (evt.data.kind !== 'offset') {
return
}
window.removeEventListener('message', returnOffset)
resolve(evt.data.value)
}
if (!window.parent.postMessage) {
2022-04-04 15:06:21 +00:00
reject(new Error('No parent window'))
}
window.parent?.postMessage({ kind: 'get-offset' }, '*')
window.addEventListener('message', returnOffset)
})
}
export function evaluateQuestion(
engine: Engine,
rule: RuleNode
): string | undefined {
const question = rule.rawNode.question as Exclude<
number,
PublicodesExpression
>
if (question && typeof question === 'object') {
return engine.evaluate(question as PublicodesExpression).nodeValue as string
}
return question
}
export function buildSituationFromObject<Names extends string = DottedName>(
contextDottedName: Names,
situationObject: Record<string, PublicodesExpression>
): Situation {
return Object.fromEntries(
Object.entries(situationObject).map(
([key, value]: [string, PublicodesExpression]) => [
`${contextDottedName} . ${key}` as Names,
typeof value === 'string' ? `'${value}'` : value,
]
)
)
}
2022-12-01 15:29:26 +00:00
export const catchDivideByZeroError = <T>(func: () => T) => {
try {
return func()
} catch (err) {
if (
isPublicodesError(err, 'EvaluationError') &&
err.message === 'Division by zero'
) {
// eslint-disable-next-line no-console
console.error(err)
}
throw err
}
}
✨ Rework du comparateur de statut (#2442) * Ajoute les accidents du travail et les congés maternité paternité au comparateur de statuts * Ajoute des références sur le calcul du la retraite * Corrige les calculs des droits maladie maternité paternité adoption * Ajoute les pensions d'invalidité * Ajoute le capital décès * Ajoute les rentes ATMP et la pension de reversion * Met à jour le Changelog, les traductions, et tutti quanti * feat: Redécoupage fichiers * feat: Ajoute le mode tab à ToggleGroup * feat: Ajoute mode fullWidth à Simulation * feat: Ajoute une description + lien vers doc * feat: Modifie bouton modifier * feat: Ajoute focusstyle * feat: Ajoute l'icon edit * feat: Ajoute composants à Comparateur * feat: Add StatusCard and icons * feat: Continue implémentation StatusCard * feat: Ajoute icônes, checklist * feat: Ajoute checklist dans les cards de statut * feat: retire id * feat: Ajoute Détails et retraite santé * feat: Ajoute Accident du t * feat: Ajoute les parties manquantes * feat: Améliore liste, retire erreur ts * fix: corrige mise en page * feat: Ajoute le contenu du modal aller plus loin * feat: Retire underline en mode light * feat: Modifie couleur questions, applique modif partie bleue sur tous les simu * feat: Améliore responsiveness * feat: Ajoute impots * feat: Peu de progrès.. * fix small * feat: Implémentation nouveaux droits * feat: Aller plus loin fonctionne * feat: Prise en compte des paramètres allerplus loin * feat: Ajoute DetailsRowCard * feat: Début remplaçage * feat: Passe les engine aux when + cleaning * feat: Commente tester éligibilité * feat: Ajoute Tooltip + warning icon * feat: Ajoute seuils micro + tooltip * feat: Creation WarningTooltip * feat: Corrections + ajouter warning sasu * feat: Ajoute déplier/plier * rebase * fix: Type errors * fix: Open/Close all accordion * fix: E2E tests * feat: cache certains label si la règle n'est pas applicable * feat: Dynamic best option * refacto * Ajoute trad à la mano * feat: Ajoute chevron déplier + modifie structure aller plus loin * feat: Corrige les copy/paste + ajoute tooltip * fix: Best option tooltips * improve tooltip id logic * fix: StatusCard tooltip id * feat: Réimplémentation du tableau aller plus loin * feat: Ajoute toggle ACRE * feat: Ajoute système sauvegarde d'état d'ouverture * feat: Ajoute un timeout sur le scrollto * wtf * feat: Bestoption Revenuapresimpot * feat Ajoute Drawer * fix: Gestion état champ acre * fix: Scroll issue on confirm * fix: Repare clickoutside + améliore doc * feat: Ajoute label * feat: Affine ACRE + fix liens doc * fix: Save accordion state + disable it * feat: Ajoute CasParticuliers context * fix * feat: Affiche la documentation dans une modale * chore: Rebase + fix button color * feat: Ajoute valeur default force theme * fix: Mobile style * feat: Améliorations style mobile * feat: Amélioration darkmode comparateur * feat: Améliore accessibilité + fix doc passée ei ae * chore: clean * fix: unique id a11y error * fix: Fix divers * fix: Test e2e * fix: Dernier fix * fix: Espacement tag en vue mobile Co-authored-by: Johan Girod <johan.girod@beta.gouv.fr>
2023-01-26 13:48:00 +00:00
export const generateUuid = () => {
return Math.floor(Math.random() * Date.now()).toString(16)
}