Skip to content

Commit

Permalink
refactor(frontend/utils): typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
c0rydoras committed Sep 4, 2024
1 parent 2b33c93 commit 1f91400
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,18 @@
* @submodule timed-utils
* @public
*/
import moment from "moment";
import moment, { type Duration } from "moment";
import { pad2joincolon } from "timed/utils/pad";

const { floor, abs } = Math;

/**
* Converts a moment duration into a string with zeropadded digits
*
* @function formatDuration
* @param {moment.duration} duration The duration to format
* @param {Boolean} seconds Whether to show seconds
* @return {String} The formatted duration
* @public
*/
export default function formatDuration(duration, seconds = true) {
export default function formatDuration(
duration: Duration | number,
seconds: boolean = true
): string {
if (typeof duration === "number") {
duration = moment.duration(duration);
}
Expand All @@ -26,7 +23,7 @@ export default function formatDuration(duration, seconds = true) {
return seconds ? "--:--:--" : "--:--";
}

const prefix = duration < 0 ? "-" : "";
const prefix = +duration < 0 ? "-" : "";

const hours = floor(abs(duration.asHours()));
const minutes = abs(duration.minutes());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,23 @@
* @public
*/

import type { Duration } from "moment";

const { abs, floor } = Math;

/**
* Converts a moment duration into a string with hours minutes and optionally
* seconds
*
* @function humanizeDuration
* @param {moment.duration} duration The duration to format
* @param {Boolean} seconds Whether to show seconds
* @return {String} The formatted duration
* @public
*/
export default function humanizeDuration(duration, seconds = false) {
export default function humanizeDuration(
duration: Duration,
seconds: boolean = false
): string {
if (!duration || duration.milliseconds() < 0) {
return seconds ? "0h 0m 0s" : "0h 0m";
}

const prefix = duration < 0 ? "-" : "";
const prefix = +duration < 0 ? "-" : "";

// TODO: The locale should be defined by the browser
const h = floor(abs(duration.asHours())).toLocaleString("de-CH");
Expand Down
7 changes: 1 addition & 6 deletions frontend/app/utils/pad.js → frontend/app/utils/pad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,8 @@

/**
* Pad items with 0 and join them with a colon
*
* @function pad2joincolon
* @param {any[]} items - The items to pad and join
* @return {String} The joined string
* @public
*/
function pad2joincolon(...items) {
function pad2joincolon(...items: unknown[]): string {
return items.map((v) => String(v).padStart(2, "0")).join(":");
}

Expand Down
34 changes: 0 additions & 34 deletions frontend/app/utils/parse-django-duration.js

This file was deleted.

54 changes: 54 additions & 0 deletions frontend/app/utils/parse-django-duration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* @module timed
* @submodule timed-utils
* @public
*/
import moment, { type Duration } from "moment";

interface Groups {
days?: number;
hours: number;
minutes: number;
seconds: number;
microseconds?: number;
}

/**
* Converts a django duration string to a moment duration
*/
export default function parseDjangoDuration(str: string): Duration | null {
if (!str) {
return null;
}

const re =
/^(?<days>-?\d+)?\s?(?<hours>\d{2}):(?<minutes>\d{2}):(?<seconds>\d{2})(?<microseconds>\.\d{6})?$/;

const matches = str.match(re) as {
groups: Groups;
} | null;

if (!matches) {
return null;
}

const {
days: _days,
hours,
minutes,
seconds,
microseconds: _microseconds,
} = matches.groups;

const [days, microseconds] = [_days, _microseconds].map(
(v) => Number(v) || 0
) as [number, number];

return moment.duration({
days,
hours,
minutes,
seconds,
milliseconds: microseconds * 1000,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
const REGEX =
/filename[^;=\n]*=(?<filename>(?<quote>['"]).*?\k<quote>|[^;\n]*)/;

const parseFileName = (contentDisposition) => {
const parseFileName = (contentDisposition: string) => {
const { quote, filename } = REGEX.exec(contentDisposition)?.groups ?? {};
if (!filename) return "Unknown file";
const _filename = filename.startsWith("utf-8''")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type Controller from "@ember/controller";
import { get } from "@ember/object";
import { underscore } from "@ember/string";
import {
Expand All @@ -8,56 +9,61 @@ import {
/**
* Filter params by key
*/
export const filterQueryParams = (params, ...keys) => {
return Object.keys(params).reduce((obj, key) => {
return keys.includes(key) ? obj : { ...obj, [key]: get(params, key) };
}, {});
export const filterQueryParams = <
T extends Record<string, unknown>,
K extends string[]
>(
params: T,
...keys: K
) => {
return Object.fromEntries(
Object.entries(params).filter(([k]) => !keys.includes(k))
) as Omit<T, K[number]>;
};

/**
* Underscore all object keys
*/
export const underscoreQueryParams = (params) => {
return Object.keys(params).reduce((obj, key) => {
return { ...obj, [underscore(key)]: get(params, key) };
}, {});
};
export const underscoreQueryParams = (params: Record<string, unknown>) =>
Object.fromEntries(
Object.entries(params).map(([k, v]) => [underscore(k), v])
);

export const serializeQueryParams = (params, queryParamsObject) => {
export const serializeQueryParams = <T extends Record<string, unknown>>(
params: T,
queryParamsObject: { [K in keyof T]?: (deserialized: T[K]) => string }
) => {
return Object.keys(params).reduce((parsed, key) => {
const serializeFn = get(queryParamsObject, key)?.serialize;
const value = get(params, key);
const serializeFn = queryParamsObject[key as keyof T];
const value = params[key as keyof T];

return key === "type"
? parsed
: {
...parsed,
[key]: serializeFn ? serializeFn(value) : value,
};
}, {});
}, {} as Record<keyof T, string>);
};

/**
*
* @param {string} param
* @returns {string} | {undefined}
* ? in all controllers, the only parameter that have the default value is `ordering`, and the value is "-date"
*/
export function getDefaultQueryParamValue(param) {
export function getDefaultQueryParamValue(param: string) {
if (param === "ordering") return "-date";
else if (param === "type") return "year";
return undefined;
}

export function allQueryParams(controller) {
export function allQueryParams<C extends Controller>(controller: C) {
const params = {};
for (const qpKey of controller.queryParams) {
params[qpKey] = controller[qpKey];
}
return params;
}

export function queryParamsState(controller) {
export function queryParamsState<C extends Controller>(controller: C) {
const states = {};
for (const param of controller.queryParams) {
const defaultValue = getDefaultQueryParamValue(param);
Expand Down Expand Up @@ -94,7 +100,10 @@ export function queryParamsState(controller) {
return states;
}

export function resetQueryParams(controller, ...args) {
export function resetQueryParams<C extends Controller>(
controller: C,
...args: string[]
) {
if (!args[0]) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import moment from "moment";
import moment, { type Moment, type MomentInput } from "moment";

export const DATE_FORMAT = "YYYY-MM-DD";

export function serializeMoment(momentObject) {
export function serializeMoment(momentObject: Moment) {
if (momentObject) {
momentObject = moment(momentObject);
}
return (momentObject && momentObject.format(DATE_FORMAT)) || null;
}
export function deserializeMoment(momentString) {
export function deserializeMoment(momentString: MomentInput) {
return (momentString && moment(momentString, DATE_FORMAT)) || null;
}
11 changes: 0 additions & 11 deletions frontend/app/utils/url.js

This file was deleted.

25 changes: 25 additions & 0 deletions frontend/app/utils/url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
type ExcludeNullOrUndefined<T> = T extends null | undefined ? never : T;

type CleanedObject<T extends object> = {
[K in keyof T as ExcludeNullOrUndefined<T[K]> extends never
? never
: K]: ExcludeNullOrUndefined<T[K]>;
};

const notNullOrUndefined = <T>(value: T): value is ExcludeNullOrUndefined<T> =>
value !== null && value !== undefined;

export const cleanParams = <T extends object>(params: T): CleanedObject<T> => {
const cleanedEntries = Object.entries(params).filter(([, value]) =>
notNullOrUndefined(value)
);
return Object.fromEntries(cleanedEntries) as CleanedObject<T>;
};

export const toQueryString = (params: Record<string, unknown>): string =>
Object.entries(params)
.map(
([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`
)
.join("&");

0 comments on commit 1f91400

Please sign in to comment.