mirror of
https://github.com/filecoin-project/slate.git
synced 2024-11-30 02:12:38 +03:00
215 lines
5.8 KiB
JavaScript
215 lines
5.8 KiB
JavaScript
import * as Strings from "~/common/strings";
|
|
import * as Validations from "~/common/validations";
|
|
import * as Constants from "~/common/constants";
|
|
|
|
import { jsx } from "@emotion/react";
|
|
|
|
import BCrypt from "bcryptjs";
|
|
import moment from "moment";
|
|
|
|
//NOTE(martina): this file is for utility functions that do not involve API calls
|
|
//For API related utility functions, see common/user-behaviors.js
|
|
//And for uploading related utility functions, see common/file-utilities.js
|
|
|
|
export const generateNumberByStep = ({ min, max, step = 1 }) => {
|
|
var numbers = [];
|
|
for (var n = min; n <= max; n += step) {
|
|
numbers.push(n);
|
|
}
|
|
|
|
const randomIndex = Math.floor(Math.random() * numbers.length);
|
|
return numbers[randomIndex];
|
|
};
|
|
|
|
export const encryptPasswordClient = async (text) => {
|
|
const salt = "$2a$06$Yl.tEYt9ZxMcem5e6AbeUO";
|
|
let hash = text;
|
|
const rounds = 5;
|
|
|
|
for (let i = 1; i <= rounds; i++) {
|
|
hash = await BCrypt.hash(text, salt);
|
|
}
|
|
|
|
return hash;
|
|
};
|
|
|
|
export const getRandomNumberBetween = (min, max) => {
|
|
return Math.round(Math.random() * (max - min) + min);
|
|
};
|
|
|
|
export const coerceToArray = (input) => {
|
|
if (!input) {
|
|
return [];
|
|
}
|
|
if (Array.isArray(input)) {
|
|
return input;
|
|
} else {
|
|
return [input];
|
|
}
|
|
};
|
|
|
|
export const getFileExtension = (filename) => filename?.split(".").pop();
|
|
|
|
export const getTimeDifferenceFromNow = (date, format = {}) => {
|
|
const defaultFormats = {
|
|
seconds: (time) => time + "s",
|
|
minutes: (time) => time + "m",
|
|
hours: (time) => time + "h",
|
|
days: (time) => time + "d",
|
|
currentYear: (month, day) => `${month} ${day}`,
|
|
default: (month, day, year) => `${month} ${day}, ${year}`,
|
|
};
|
|
|
|
const formatDate = { ...defaultFormats, ...format };
|
|
|
|
const pastDate = new Date(date);
|
|
const now = new Date();
|
|
|
|
const differenceInSeconds = Math.floor((now - pastDate) / 1000);
|
|
if (differenceInSeconds < 60) {
|
|
return formatDate.seconds(differenceInSeconds);
|
|
}
|
|
|
|
const differenceInMinutes = Math.floor(differenceInSeconds / 60);
|
|
if (differenceInMinutes < 60) {
|
|
return formatDate.minutes(differenceInMinutes);
|
|
}
|
|
|
|
const differenceInHours = Math.floor(differenceInMinutes / 60);
|
|
if (differenceInHours < 24) {
|
|
return formatDate.hours(differenceInHours);
|
|
}
|
|
|
|
const differenceInDays = Math.floor(differenceInHours / 24);
|
|
if (differenceInDays < 24) {
|
|
return formatDate.days(differenceInDays);
|
|
}
|
|
|
|
const currentYear = now.getFullYear();
|
|
|
|
const day = pastDate.getDay();
|
|
const month = pastDate.toLocaleString("default", { month: "short" });
|
|
const year = pastDate.getFullYear();
|
|
|
|
if (year === currentYear) {
|
|
return formatDate.currentYear(month, day);
|
|
}
|
|
|
|
return formatDate.default(month, day, year);
|
|
};
|
|
|
|
const isObject = (val) => val instanceof Object;
|
|
|
|
/**
|
|
NOTE(amine): This will take a prop and return a responsive object that we can use with emotion.
|
|
Let's say we have a size prop with current values {base: 64, mobile: 120}, and a mapper function
|
|
(size)=> ({width: size, height: size}).
|
|
It will return a responsive object
|
|
{ width: 64, height: 64,
|
|
'@media (min-width: 768px':{ width: 120, height: 120 } }
|
|
*/
|
|
export function mapResponsiveProp(prop, mapper) {
|
|
if (isObject(prop)) {
|
|
const { base, ...restProps } = prop;
|
|
let initialStyles = mapper(base) || {};
|
|
|
|
return Object.keys(restProps).reduce((styles, size) => {
|
|
const media = `@media (min-width: ${Constants.sizes[size]}px)`;
|
|
const mediaStyles = mapper(restProps[size]);
|
|
styles[media] = mediaStyles;
|
|
return styles;
|
|
}, initialStyles);
|
|
}
|
|
|
|
if (prop !== null) {
|
|
return mapper(prop);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export const copyToClipboard = (text) => navigator.clipboard.writeText(text);
|
|
|
|
export function formatDateToString(date) {
|
|
const providedDate = moment(date);
|
|
const today = moment();
|
|
const yesterday = moment().subtract(1, "day");
|
|
|
|
if (today.isSame(providedDate, "day")) {
|
|
return "Today at " + providedDate.format("h:mmA");
|
|
}
|
|
|
|
if (yesterday.isSame(providedDate, "day")) {
|
|
return "Yesterday at " + providedDate.format("h:mmA");
|
|
}
|
|
|
|
return providedDate.format("MMM D, YYYY") + " at " + providedDate.format("h:mmA");
|
|
}
|
|
|
|
export const clamp = (value, min, max) => {
|
|
if (value < min) return min;
|
|
if (value > max) return max;
|
|
return value;
|
|
};
|
|
|
|
export const getImageUrlIfExists = (file, sizeLimit = null) => {
|
|
if (!file) return;
|
|
if (Validations.isPreviewableImage(file.type)) {
|
|
if (sizeLimit && file.size && file.size > sizeLimit) {
|
|
return;
|
|
}
|
|
return Strings.getURLfromCID(file.cid);
|
|
}
|
|
let { coverImage } = file;
|
|
if (coverImage) {
|
|
if (sizeLimit && coverImage.size && coverImage.size > sizeLimit) {
|
|
return;
|
|
}
|
|
return Strings.getURLfromCID(coverImage.cid);
|
|
}
|
|
if (file.linkImage) {
|
|
return file.linkImage;
|
|
}
|
|
};
|
|
|
|
export const getUserDisplayName = (user) => {
|
|
return user.name || `@${user.username}`;
|
|
};
|
|
|
|
export const mergeEvents =
|
|
(...handlers) =>
|
|
(e) => {
|
|
handlers.forEach((handler) => handler?.call?.(e));
|
|
};
|
|
|
|
export const mergeRefs = (refs) => {
|
|
return (value) => {
|
|
refs.forEach((ref) => {
|
|
if (typeof ref === "function") {
|
|
ref(value);
|
|
} else if (ref != null) {
|
|
ref.current = value;
|
|
}
|
|
});
|
|
};
|
|
};
|
|
|
|
// NOTE(amine): workaround to support css prop in cloned elements
|
|
// SOURCE(amine): https://github.com/emotion-js/emotion/issues/1404#issuecomment-504527459
|
|
export const cloneElementWithJsx = (element, config, ...children) => {
|
|
return jsx(
|
|
element.props["__EMOTION_TYPE_PLEASE_DO_NOT_USE__"]
|
|
? element.props["__EMOTION_TYPE_PLEASE_DO_NOT_USE__"]
|
|
: element.type,
|
|
{
|
|
key: element.key !== null ? element.key : undefined,
|
|
ref: element.ref,
|
|
...element.props,
|
|
...config,
|
|
style: { ...element.props?.style, ...config?.style },
|
|
css: [element.props?.css, config.css],
|
|
},
|
|
...children
|
|
);
|
|
};
|