chore(webpack): enable typescript

This commit is contained in:
Paul Chavard 2022-02-02 17:16:50 +01:00
parent 5b4b57060c
commit ea6aec8b1a
11 changed files with 548 additions and 123 deletions

View file

@ -5,6 +5,7 @@ import 'core-js/stable';
import 'regenerator-runtime/runtime';
import 'dom4';
import 'intersection-observer';
import 'whatwg-fetch';
import './polyfills/insertAdjacentElement';
import './polyfills/dataset';

View file

@ -1,4 +1,4 @@
import React, { Suspense, lazy, createElement } from 'react';
import React, { Suspense, lazy, createElement, ComponentClass } from 'react';
import { render } from 'react-dom';
// This attribute holds the name of component which should be mounted
@ -13,12 +13,12 @@ const CLASS_NAME_SELECTOR = `[${CLASS_NAME_ATTR}]`;
// helper method for the mount and unmount methods to find the
// `data-react-class` DOM elements
function findDOMNodes(searchSelector) {
function findDOMNodes(searchSelector?: string): NodeListOf<HTMLDivElement> {
const [selector, parent] = getSelector(searchSelector);
return parent.querySelectorAll(selector);
return parent.querySelectorAll<HTMLDivElement>(selector);
}
function getSelector(searchSelector) {
function getSelector(searchSelector?: string): [string, Document] {
switch (typeof searchSelector) {
case 'undefined':
return [CLASS_NAME_SELECTOR, document];
@ -39,15 +39,15 @@ function getSelector(searchSelector) {
class ReactComponentRegistry {
#components;
constructor(components) {
constructor(components: Record<string, ComponentClass>) {
this.#components = components;
}
getConstructor(className) {
return this.#components[className];
getConstructor(className: string | null) {
return className ? this.#components[className] : null;
}
mountComponents(searchSelector) {
mountComponents(searchSelector?: string) {
const nodes = findDOMNodes(searchSelector);
for (const node of nodes) {
@ -76,10 +76,10 @@ class ReactComponentRegistry {
const Loader = () => <div className="spinner left" />;
export function Loadable(loader) {
export function Loadable(loader: () => Promise<{ default: ComponentClass }>) {
const LazyComponent = lazy(loader);
return function PureComponent(props) {
return function PureComponent(props: Record<string, unknown>) {
return (
<Suspense fallback={<Loader />}>
<LazyComponent {...props} />
@ -88,7 +88,9 @@ export function Loadable(loader) {
};
}
export function registerReactComponents(components) {
export function registerReactComponents(
components: Record<string, ComponentClass>
) {
const registry = new ReactComponentRegistry(components);
addEventListener('ds:page:update', () => registry.mountComponents());

View file

@ -4,17 +4,17 @@ import debounce from 'debounce';
export { debounce };
export const { fire, csrfToken } = Rails;
export function show(el) {
export function show(el: HTMLElement) {
el && el.classList.remove('hidden');
}
export function hide(el) {
export function hide(el: HTMLElement) {
el && el.classList.add('hidden');
}
export function toggle(el, force) {
export function toggle(el: HTMLElement, force?: boolean) {
if (force == undefined) {
el & el.classList.toggle('hidden');
el && el.classList.toggle('hidden');
} else if (force) {
el && el.classList.remove('hidden');
} else {
@ -22,27 +22,31 @@ export function toggle(el, force) {
}
}
export function enable(el) {
export function enable(el: HTMLInputElement) {
el && (el.disabled = false);
}
export function disable(el) {
export function disable(el: HTMLInputElement) {
el && (el.disabled = true);
}
export function hasClass(el, cssClass) {
export function hasClass(el: HTMLElement, cssClass: string) {
return el && el.classList.contains(cssClass);
}
export function addClass(el, cssClass) {
export function addClass(el: HTMLElement, cssClass: string) {
el && el.classList.add(cssClass);
}
export function removeClass(el, cssClass) {
export function removeClass(el: HTMLElement, cssClass: string) {
el && el.classList.remove(cssClass);
}
export function delegate(eventNames, selector, callback) {
export function delegate(
eventNames: string,
selector: string,
callback: () => void
) {
eventNames
.split(' ')
.forEach((eventName) =>
@ -57,15 +61,23 @@ export function delegate(eventNames, selector, callback) {
// - rejected with an Error object otherwise.
//
// See Rails.ajax() code for more details.
export function ajax(options) {
export function ajax(options: Rails.AjaxOptions) {
return new Promise((resolve, reject) => {
Object.assign(options, {
success: (response, statusText, xhr) => {
success: (
response: unknown,
statusText: string,
xhr: { status: number }
) => {
resolve({ response, statusText, xhr });
},
error: (response, statusText, xhr) => {
error: (
response: unknown,
statusText: string,
xhr: { status: number }
) => {
// NB: on HTTP/2 connections, statusText is always empty.
let error = new Error(
const error = new Error(
`Erreur ${xhr.status}` + (statusText ? ` : ${statusText}` : '')
);
Object.assign(error, { response, statusText, xhr });
@ -76,7 +88,7 @@ export function ajax(options) {
});
}
export function getJSON(url, data, method = 'GET') {
export function getJSON(url: string, data: unknown, method = 'GET') {
const { query, ...options } = fetchOptions(data, method);
return fetch(`${url}${query}`, options).then((response) => {
@ -86,32 +98,38 @@ export function getJSON(url, data, method = 'GET') {
}
return response.json();
}
const error = new Error(response.statusText || response.status);
error.response = response;
const error = new Error(String(response.statusText || response.status));
(error as any).response = response;
throw error;
});
}
export function scrollTo(container, scrollTo) {
export function scrollTo(container: HTMLElement, scrollTo: HTMLElement) {
container.scrollTop =
offset(scrollTo).top - offset(container).top + container.scrollTop;
}
export function scrollToBottom(container) {
export function scrollToBottom(container: HTMLElement) {
container.scrollTop = container.scrollHeight;
}
export function on(selector, eventName, fn) {
export function on(
selector: string,
eventName: string,
fn: (event: Event, detail: unknown) => void
) {
[...document.querySelectorAll(selector)].forEach((element) =>
element.addEventListener(eventName, (event) => fn(event, event.detail))
element.addEventListener(eventName, (event) =>
fn(event, (event as CustomEvent).detail)
)
);
}
export function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
export function isNumeric(n: string) {
return !isNaN(parseFloat(n)) && isFinite(n as any as number);
}
function offset(element) {
function offset(element: HTMLElement) {
const rect = element.getBoundingClientRect();
return {
top: rect.top + document.body.scrollTop,
@ -120,8 +138,11 @@ function offset(element) {
}
// Takes a promise, and return a promise that times out after the given delay.
export function timeoutable(promise, timeoutDelay) {
let timeoutPromise = new Promise((resolve, reject) => {
export function timeoutable<T>(
promise: Promise<T>,
timeoutDelay: number
): Promise<T> {
const timeoutPromise = new Promise<T>((resolve, reject) => {
setTimeout(() => {
reject(new Error(`Promise timed out after ${timeoutDelay}ms`));
}, timeoutDelay);
@ -131,13 +152,16 @@ export function timeoutable(promise, timeoutDelay) {
const FETCH_TIMEOUT = 30 * 1000; // 30 sec
function fetchOptions(data, method = 'GET') {
const options = {
function fetchOptions(data: unknown, method = 'GET') {
const options: RequestInit & {
query: string;
headers: Record<string, string>;
} = {
query: '',
method: method.toUpperCase(),
headers: {
accept: 'application/json',
'x-csrf-token': csrfToken(),
'x-csrf-token': csrfToken() ?? '',
'x-requested-with': 'XMLHttpRequest'
},
credentials: 'same-origin'
@ -145,7 +169,7 @@ function fetchOptions(data, method = 'GET') {
if (data) {
if (options.method === 'GET') {
options.query = objectToQuerystring(data);
options.query = objectToQuerystring(data as Record<string, string>);
} else {
options.headers['content-type'] = 'application/json';
options.body = JSON.stringify(data);
@ -164,7 +188,7 @@ function fetchOptions(data, method = 'GET') {
return options;
}
function objectToQuerystring(obj) {
function objectToQuerystring(obj: Record<string, string>): string {
return Object.keys(obj).reduce(function (query, key, i) {
return [
query,

21
app/javascript/types.d.ts vendored Normal file
View file

@ -0,0 +1,21 @@
declare module '@tmcw/togeojson' {
import { FeatureCollection, GeoJsonProperties, Geometry } from 'geojson';
export function kml(doc: Document): FeatureCollection;
export function kml<TProperties extends GeoJsonProperties>(
doc: Document
): FeatureCollection<Geometry, TProperties>;
export function gpx(doc: Document): FeatureCollection;
export function gpx<TProperties extends GeoJsonProperties>(
doc: Document
): FeatureCollection<Geometry, TProperties>;
export function tcx(doc: Document): FeatureCollection;
export function tcx<TProperties extends GeoJsonProperties>(
doc: Document
): FeatureCollection<Geometry, TProperties>;
}
declare module 'react-coordinate-input';