diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 6ee9a81..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/package-lock.json b/package-lock.json index dba420d..0ba1ade 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@armco/configs": "^0.0.12" }, "devDependencies": { - "@armco/types": "^0.0.20", + "@armco/types": "^0.0.21", "@types/d3": "^7.4.3", "@types/node": "^24.10.0", "@types/react": "^19.2.2", @@ -46,9 +46,9 @@ } }, "node_modules/@armco/types": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@armco/types/-/types-0.0.20.tgz", - "integrity": "sha512-7Q6iXeeYBLtE88zhDBE78zEdHZVCIq+68RsraJr98xzP0z0wUEvYr2Tfv7wynjV/qv1aH58XK/dAsqXL7IeqNg==", + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@armco/types/-/types-0.0.21.tgz", + "integrity": "sha512-ixOJBpJP9a+uDnqndaMGyCsGQTOhdwbFazb7+eAYFng2+CCLS9WF//f8ldAlXmeWapeS9+VZxyxe6apaxjQ0yA==", "license": "MIT" }, "node_modules/@babel/code-frame": { diff --git a/package.json b/package.json index 88327b0..95b7abf 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "uuid": "^9.0.0" }, "devDependencies": { - "@armco/types": "^0.0.20", + "@armco/types": "^0.0.21", "@types/d3": "^7.4.3", "@types/node": "^24.10.0", "@types/react": "^19.2.2", diff --git a/src/chartGenerators.ts b/src/chartGenerators.ts deleted file mode 100644 index 6c221b0..0000000 --- a/src/chartGenerators.ts +++ /dev/null @@ -1,45 +0,0 @@ -import * as d3 from "d3" -import { ArrayType, ObjectType } from "@armco/types" - -export const generateBubbleChart = (data: ArrayType | ObjectType) => { - const svg = d3.select("#ar-ArViz__chart-container") - if (svg && data) { - svg - .selectAll("circle") - .data(data as ArrayType) - .enter() - .append("circle") - .attr("cx", function (d) { - return (d as ObjectType).x as number - }) - .attr("cy", function (d) { - return (d as ObjectType).y as number - }) - .attr("r", function (d) { - return Math.sqrt((d as ObjectType).val as number) / Math.PI - }) - .attr("fill", function (d) { - return (d as ObjectType).color as string - }) - - // Step 5 - svg - .selectAll("text") - .data(data as ArrayType) - .enter() - .append("text") - .attr("x", function (d) { - const x = (d as ObjectType).x as number - const sqrt = Math.sqrt((d as ObjectType).val as number) as number - return x + sqrt / Math.PI - }) - .attr("y", function (d) { - return ((d as ObjectType).y as number) + 4 - }) - .text(function (d) { - return (d as ObjectType).source as string - }) - .style("font-family", "arial") - .style("font-size", "12px") - } -} diff --git a/src/dateformat.ts b/src/dateformat.ts deleted file mode 100644 index 0a633dd..0000000 --- a/src/dateformat.ts +++ /dev/null @@ -1,337 +0,0 @@ -import { ArDateMasks, FunctionType } from "@armco/types" - -const token = - /d{1,4}|D{3,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|W{1,2}|[LlopSZN]|"[^"]*"|'[^']*'/g -const timezone = - /\b(?:[A-Z]{1,3}[A-Z][TC])(?:[-+]\d{4})?|((?:Australian )?(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time)\b/g -const timezoneClip = /[^-+\dA-Z]/g - -/** - * @param {string | number | Date} date - * @param {string} mask - * @param {boolean} utc - * @param {boolean} gmt - */ -export function dateFormat( - date?: number | Date, - mask?: ArDateMasks | string, - utc?: boolean, - gmt?: boolean, -) { - // You can't provide utc if you skip other args (use the 'UTC:' mask prefix) - if (arguments.length === 1 && typeof date === "string" && !/\d/.test(date)) { - mask = date - date = undefined - } - - date = date || date === 0 ? date : new Date() - - if (isNaN(date as number)) { - throw TypeError("Invalid date") - } - - if (!(date instanceof Date)) { - date = new Date(date) - } - - mask = String( - ArDateMasksRecord[mask as string] || mask || ArDateMasksRecord["DEFAULT"], - ) - - // Allow setting the utc/gmt argument via the mask - const maskSlice = mask.slice(0, 4) - if (maskSlice === "UTC:" || maskSlice === "GMT:") { - mask = mask.slice(4) - utc = true - if (maskSlice === "GMT:") { - gmt = true - } - } - - const _ = () => (utc ? "getUTC" : "get") - const d = () => (date as Date)[(_() + "Date") as "getDate" | "getUTCDate"]() - const D = () => (date as Date)[(_() + "Day") as "getDay" | "getUTCDay"]() - const m = () => - (date as Date)[(_() + "Month") as "getMonth" | "getUTCMonth"]() - const y = () => - (date as Date)[(_() + "FullYear") as "getFullYear" | "getUTCFullYear"]() - const H = () => - (date as Date)[(_() + "Hours") as "getHours" | "getUTCHours"]() - const M = () => - (date as Date)[(_() + "Minutes") as "getMinutes" | "getUTCMinutes"]() - const s = () => - (date as Date)[(_() + "Seconds") as "getSeconds" | "getUTCSeconds"]() - const L = () => - (date as Date)[ - (_() + "Milliseconds") as "getMilliseconds" | "getUTCMilliseconds" - ]() - const o = () => (utc ? 0 : (date as Date).getTimezoneOffset()) - const W = () => date instanceof Date && getWeek(date) - const N = () => date instanceof Date && getDayOfWeek(date) - - const flags: { [key: string]: FunctionType } = { - d: () => d(), - dd: () => pad(d()), - ddd: () => i18n.dayNames[D()], - DDD: () => - getDayName({ - y: y(), - m: m(), - d: d(), - _: _(), - dayName: i18n.dayNames[D()], - short: true, - }), - dddd: () => i18n.dayNames[D() + 7], - DDDD: () => - getDayName({ - y: y(), - m: m(), - d: d(), - _: _(), - dayName: i18n.dayNames[D() + 7], - }), - m: () => m() + 1, - mm: () => pad(m() + 1), - mmm: () => i18n.monthNames[m()], - mmmm: () => i18n.monthNames[m() + 12], - yy: () => String(y()).slice(2), - yyyy: () => pad(y(), 4), - h: () => H() % 12 || 12, - hh: () => pad(H() % 12 || 12), - H: () => H(), - HH: () => pad(H()), - M: () => M(), - MM: () => pad(M()), - s: () => s(), - ss: () => pad(s()), - l: () => pad(L(), 3), - L: () => pad(Math.floor(L() / 10)), - t: () => (H() < 12 ? i18n.timeNames[0] : i18n.timeNames[1]), - tt: () => (H() < 12 ? i18n.timeNames[2] : i18n.timeNames[3]), - T: () => (H() < 12 ? i18n.timeNames[4] : i18n.timeNames[5]), - TT: () => (H() < 12 ? i18n.timeNames[6] : i18n.timeNames[7]), - Z: () => - gmt ? "GMT" : utc ? "UTC" : date instanceof Date && formatTimezone(date), - o: () => - (o() > 0 ? "-" : "+") + - pad(Math.floor(Math.abs(o()) / 60) * 100 + (Math.abs(o()) % 60), 4), - p: () => - (o() > 0 ? "-" : "+") + - pad(Math.floor(Math.abs(o()) / 60), 2) + - ":" + - pad(Math.floor(Math.abs(o()) % 60), 2), - S: () => - ["th", "st", "nd", "rd"][ - d() % 10 > 3 ? 0 : (+((d() % 100) - (d() % 10) !== 10) * d()) % 10 - ], - W: () => W(), - WW: () => { - const date = W() - date && pad(date) - }, - N: () => N(), - } - - return mask.replace(token, (match) => { - if (match in flags) { - return flags[match]() - } - return match.slice(1, match.length - 1) - }) -} - -const ArDateMasksRecord: Record = { - DEFAULT: ArDateMasks.DEFAULT, - SHORTDATE: ArDateMasks.SHORTDATE, - PADDEDSHORTDATE: ArDateMasks.PADDEDSHORTDATE, - MEDIUMDATE: ArDateMasks.MEDIUMDATE, - LONGDATE: ArDateMasks.LONGDATE, - FULLDATE: ArDateMasks.FULLDATE, - SHORTTIME: ArDateMasks.SHORTTIME, - MEDIUMTIME: ArDateMasks.MEDIUMTIME, - LONGTIME: ArDateMasks.LONGTIME, - ISODATE: ArDateMasks.ISODATE, - ISOTIME: ArDateMasks.ISOTIME, - ISODATETIME: ArDateMasks.ISODATETIME, - ISOUTCDATETIME: ArDateMasks.ISOUTCDATETIME, - EXPIRESHEADERFORMAT: ArDateMasks.EXPIRESHEADERFORMAT, -} - -// Internationalization strings -export let i18n = { - dayNames: [ - "Sun", - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat", - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", - ], - monthNames: [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ], - timeNames: ["a", "p", "am", "pm", "A", "P", "AM", "PM"], -} - -const pad = (val: number, len = 2) => String(val).padStart(len, "0") - -/** - * Get day name - * Yesterday, Today, Tomorrow if the date lies within, else fallback to Monday - Sunday - * @param {Object} - * @return {String} - */ -const getDayName = ({ - y, - m, - d, - _, - dayName, - short = false, -}: { - y: number - m: number - d: number - _: string - dayName: string - short?: boolean -}) => { - const today = new Date() - const yesterday = new Date() - const tomorrow = new Date() - const dateAccessor = (_ + "Date") as "getDate" | "getUTCDate" - const monthAccessor = (_ + "Month") as "getMonth" | "getUTCMonth" - const yearAccessor = (_ + "FullYear") as "getFullYear" | "getUTCFullYear" - yesterday.setDate(yesterday[dateAccessor]() - 1) - tomorrow.setDate(tomorrow[dateAccessor]() + 1) - const today_d = () => today[dateAccessor]() - const today_m = () => today[monthAccessor]() - const today_y = () => today[yearAccessor]() - const yesterday_d = () => yesterday[dateAccessor]() - const yesterday_m = () => yesterday[monthAccessor]() - const yesterday_y = () => yesterday[yearAccessor]() - const tomorrow_d = () => tomorrow[dateAccessor]() - const tomorrow_m = () => tomorrow[monthAccessor]() - const tomorrow_y = () => tomorrow[yearAccessor]() - - if (today_y() === y && today_m() === m && today_d() === d) { - return short ? "Tdy" : "Today" - } else if ( - yesterday_y() === y && - yesterday_m() === m && - yesterday_d() === d - ) { - return short ? "Ysd" : "Yesterday" - } else if (tomorrow_y() === y && tomorrow_m() === m && tomorrow_d() === d) { - return short ? "Tmw" : "Tomorrow" - } - return dayName -} - -/** - * Get the ISO 8601 week number - * Based on comments from - * http://techblog.procurios.nl/k/n618/news/view/33796/14863/Calculate-ISO-8601-week-and-year-in-javascript.html - * - * @param {Date} `date` - * @return {Number} - */ -const getWeek = (date: Date) => { - // Remove time components of date - const targetThursday = new Date( - date.getFullYear(), - date.getMonth(), - date.getDate(), - ) - - // Change date to Thursday same week - targetThursday.setDate( - targetThursday.getDate() - ((targetThursday.getDay() + 6) % 7) + 3, - ) - - // Take January 4th as it is always in week 1 (see ISO 8601) - const firstThursday = new Date(targetThursday.getFullYear(), 0, 4) - - // Change date to Thursday same week - firstThursday.setDate( - firstThursday.getDate() - ((firstThursday.getDay() + 6) % 7) + 3, - ) - - // Check if daylight-saving-time-switch occurred and correct for it - const ds = - targetThursday.getTimezoneOffset() - firstThursday.getTimezoneOffset() - targetThursday.setHours(targetThursday.getHours() - ds) - - // Number of weeks between target Thursday and first Thursday - const weekDiff = - (targetThursday.getTime() - firstThursday.getTime()) / (86400000 * 7) - return 1 + Math.floor(weekDiff) -} - -/** - * Get ISO-8601 numeric representation of the day of the week - * 1 (for Monday) through 7 (for Sunday) - * - * @param {Date} `date` - * @return {Number} - */ -const getDayOfWeek = (date: Date) => { - let dow = date.getDay() - if (dow === 0) { - dow = 7 - } - return dow -} - -/** - * Get proper timezone abbreviation or timezone offset. - * - * This will fall back to `GMT+xxxx` if it does not recognize the - * timezone within the `timezone` RegEx above. Currently only common - * American and Australian timezone abbreviations are supported. - * - * @param {String | Date} date - * @return {String} - */ -export const formatTimezone = (date: Date) => { - const strDate = String(date) - const match = strDate.match(timezone) || [""] - return match - .pop() - ?.replace(timezoneClip, "") - .replace(/GMT\+0000/g, "UTC") -} - -export { dateFormat as DateFormatter } \ No newline at end of file diff --git a/src/domHelper.ts b/src/domHelper.ts index 5cf85b4..03d066f 100644 --- a/src/domHelper.ts +++ b/src/domHelper.ts @@ -1,340 +1,3 @@ -import { Children } from "react" -import { - CarouselProps, - ArPopoverPositions, - ObjectType, - Position, -} from "@armco/types" - -const style = window.getComputedStyle(document.documentElement) -const fontSize = parseFloat(style.fontSize) -const markerSize = 7 - -export function outerWidth(el: HTMLElement) { - let width = el.offsetWidth - const style = getComputedStyle(el) - - width += parseInt(style.marginLeft) + parseInt(style.marginRight) - return width -} - -export function translate( - position: number, - metric: "px" | "%", - axis: "horizontal" | "vertical", -) { - const positionPercent = position === 0 ? position : position + metric - const positionCss = - axis === "horizontal" ? [positionPercent, 0, 0] : [0, positionPercent, 0] - const transitionProp = "translate3d" - - const translatedPosition = "(" + positionCss.join(",") + ")" - - return transitionProp + translatedPosition -} - -export function hexToHsl(color: string, returnRaw?: boolean) { - const [r, g, b] = hexToRgb(color) - return rgbToHsl(r, g, b, returnRaw) -} - -export function hexToRgb(color: string) { - const r = parseInt(color.substring(1, 3), 16) // Grab the hex representation of red (chars 1-2) and convert to decimal (base 10). - const g = parseInt(color.substring(3, 5), 16) - const b = parseInt(color.substring(5, 7), 16) - return [r, g, b] -} - -/** - * Converts an RGB color value to HSL. Conversion formula - * adapted from http://en.wikipedia.org/wiki/HSL_color_space. - * Assumes r, g, and b are contained in the set [0, 255] and - * returns h, s, and l in the set [0, 1]. - * - * @param Number r The red color value - * @param Number g The green color value - * @param Number b The blue color value - * @return Array The HSL representation - */ -export function rgbToHsl(r: number, g: number, b: number, returnRaw?: boolean) { - r /= 255 - g /= 255 - b /= 255 - - const max = Math.max(r, g, b), - min = Math.min(r, g, b) - let h, - s, - l = (max + min) / 2 - - if (max === min) { - h = s = 0 // achromatic - } else { - const d = max - min - s = l > 0.5 ? d / (2 - max - min) : d / (max + min) - - switch (max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0) - break - case g: - h = (b - r) / d + 2 - break - case b: - h = (r - g) / d + 4 - break - } - - // @ts-ignore - h /= 6 - } - const colorInHSL = "hsl(" + h + ", " + s + "%, " + l + "%)" - return returnRaw ? [h, s, l] : colorInHSL -} - -export function hslToRgb(h: number, s: number, l: number) { - let r, g, b - let hue2rgb - if (s === 0) { - r = g = b = l // achromatic - } else { - hue2rgb = (p: number, q: number, t: number) => { - if (t < 0) t += 1 - if (t > 1) t -= 1 - if (t < 1 / 6) return p + (q - p) * 6 * t - if (t < 1 / 2) return q - if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6 - return p - } - - const q = l < 0.5 ? l * (1 + s) : l + s - l * s - const p = 2 * l - q - - r = hue2rgb && hue2rgb(p, q, h + 1 / 3) - g = hue2rgb && hue2rgb(p, q, h) - b = hue2rgb && hue2rgb(p, q, h - 1 / 3) - } - - return [r * 255, g * 255, b * 255] -} - -export function rgbToHex(r: number, g: number, b: number) { - function componentToHex(c: number) { - var hex = c.toString(16) - return hex.length === 1 ? "0" + hex : hex - } - return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b) -} - -export function hslToHex(h: number, s: number, l: number) { - const [r, g, b] = hslToRgb(h, s, l) - return rgbToHex(Math.round(r), Math.round(g), Math.round(b)) -} - -/** - * Gets the list 'position' relative to a current index - * @param index - */ -export function getPosition(index: number, props: CarouselProps): number { - if (props.infiniteLoop) { - // index has to be added by 1 because of the first cloned slide - ++index - } - - if (index === 0) { - return 0 - } - - const childrenLength = Children.count(props.children) - if (props.centerMode && props.axis === "horizontal") { - let currentPosition = -index * props.centerSlidePercentage - const lastPosition = childrenLength - 1 - - if (index && (index !== lastPosition || props.infiniteLoop)) { - currentPosition += (100 - props.centerSlidePercentage) / 2 - } else if (index === lastPosition) { - currentPosition += 100 - props.centerSlidePercentage - } - - return currentPosition - } - - return -index * 100 -} - -/** - * Sets the 'position' transform for sliding animations - * @param position - * @param forceReflow - */ -export function setPosition( - position: number, - axis: "horizontal" | "vertical", -): React.CSSProperties { - const style = {} - ;[ - "WebkitTransform", - "MozTransform", - "MsTransform", - "OTransform", - "transform", - "msTransform", - ].forEach((prop) => { - // @ts-ignore - style[prop] = CSSTranslate(position, "%", axis) - }) - - return style -} - -export function isKeyboardEvent( - e?: React.MouseEvent | React.KeyboardEvent, -): e is React.KeyboardEvent { - return e ? e.hasOwnProperty("key") : false -} - -export function shouldUsePosition( - position: ArPopoverPositions, - anchorRect: DOMRect, - popoverRect: DOMRect, -) { - const exceeds: ObjectType = { - bottom: - anchorRect.top + anchorRect.height + popoverRect.height > - window.innerHeight, - right: - anchorRect.left + anchorRect.width + popoverRect.width > - window.innerWidth, - left: anchorRect.left - popoverRect.width < 0, - top: anchorRect.top - popoverRect.height < 0, - } - if (exceeds[position]) return false - if (position === "left" || position === "right") { - if (fontSize > anchorRect.top || fontSize > anchorRect.bottom) return false - } else { - if (fontSize > anchorRect.left || fontSize > anchorRect.right) return false - } - return position -} - -export function adjustPosition( - position: ArPopoverPositions, - anchorRect: DOMRect, - popoverRect: DOMRect, - hideMarker?: boolean, - topOffset?: number, -) { - const popoverPositions: { [key: string]: Position } = { - [ArPopoverPositions.BOTTOM]: { - top: - anchorRect.top + - anchorRect.height + - (hideMarker ? 0 : markerSize) + - (topOffset || 0), - left: anchorRect.left + (anchorRect.width - popoverRect.width) / 2, - }, - [ArPopoverPositions.RIGHT]: { - top: anchorRect.top + (anchorRect.height - popoverRect.height) / 2, - left: anchorRect.left + anchorRect.width + (hideMarker ? 0 : markerSize), - }, - [ArPopoverPositions.LEFT]: { - top: anchorRect.top + (anchorRect.height - popoverRect.height) / 2, - left: anchorRect.left - popoverRect.width - (hideMarker ? 0 : markerSize), - }, - [ArPopoverPositions.TOP]: { - top: - anchorRect.top - - popoverRect.height - - (hideMarker ? 0 : markerSize) - - (topOffset || 0), - left: anchorRect.left + (anchorRect.width - popoverRect.width) / 2, - }, - } - let { left, top } = popoverPositions[position] - if ( - position === ArPopoverPositions.BOTTOM || - position === ArPopoverPositions.TOP - ) { - if ((left as number) < 0) { - left = fontSize - } - if ((left as number) + popoverRect.width > window.innerWidth) { - left = `calc(100% - ${popoverRect.width}px - ${fontSize}px)` - } - } else { - if ((top as number) < 0) { - top = fontSize - } - if ((top as number) + popoverRect.height > window.innerHeight) { - top = `calc(100% - ${popoverRect.height}px - ${fontSize}px)` - } - } - return { left, top } -} - -export function getPositionToUse( - anchorRect: DOMRect, - popoverRect: DOMRect, - requestedPosition?: ArPopoverPositions, -) { - if (requestedPosition && requestedPosition !== ArPopoverPositions.AUTO) { - return requestedPosition - } - const positionsPriority = [ - ArPopoverPositions.BOTTOM, - ArPopoverPositions.RIGHT, - ArPopoverPositions.LEFT, - ArPopoverPositions.TOP, - ] - - for (const position of positionsPriority) { - if (shouldUsePosition(position, anchorRect, popoverRect)) { - return position - } - continue - } - - return ArPopoverPositions.BOTTOM -} - -export function calculatePopoverPosition( - anchorRect: DOMRect, - popoverRect: DOMRect, - requestedPosition?: ArPopoverPositions, - hideMarker?: boolean, - clickCoordinates?: Array | null, - topOffset?: number, -) { - anchorRect = clickCoordinates - ? { - left: clickCoordinates[0], - x: clickCoordinates[0], - top: clickCoordinates[1], - y: clickCoordinates[1], - height: 0, - width: 0, - right: window.screen.width - clickCoordinates[0], - bottom: window.screen.height - clickCoordinates[1], - toJSON: () => {}, - } - : anchorRect - const position = getPositionToUse(anchorRect, popoverRect, requestedPosition) - const { left, top } = adjustPosition( - position, - anchorRect, - popoverRect, - hideMarker, - topOffset, - ) - return { - leftTop: { - top: ("" + top).startsWith("calc") ? top : top + "px", - left: ("" + left).startsWith("calc") ? left : left + "px", - }, - position, - } -} - export function openInNewTab(url: string) { const win = window.open(url, "_blank") if (win != null) { diff --git a/src/enums.ts b/src/enums.ts new file mode 100644 index 0000000..c6803fb --- /dev/null +++ b/src/enums.ts @@ -0,0 +1,17 @@ +export enum ArThemes { + LIGHT1 = "th-light-1", + DARK1 = "th-dark-1", +} + +export enum ArAlertType { + SUCCESS = "success", + WARNING = "warning", + ERROR = "error", + INFORMATION = "information", +} + + +export enum RecusionConditionTypes { + KEY_EXISTS = "keyExists", + KEY_VALUE = "keyValue", +} \ No newline at end of file diff --git a/src/helper.tsx b/src/helper.tsx index 90ed53a..da62e50 100644 --- a/src/helper.tsx +++ b/src/helper.tsx @@ -2,27 +2,14 @@ import { JSX, lazy } from "react" import { FunctionType, ObjectType, - SearchArgs, RouteConfig, } from "@armco/types" -const validImageMimeTypes = [ - "image/jpeg", - "image/png", - "image/gif", - "image/webp", - "image/svg+xml", - "image/tiff", - "image/bmp", - "image/x-icon", -] - -interface ReplacerFunction { - (key: string, value: any): any -} - -interface StringifyOnce { - (obj: any, replacer?: ReplacerFunction | null, indent?: number): string +export interface SearchArgs { + obj: ObjectType | Array + key: string + value?: string + parent?: ObjectType | Array } export function populatePagesInRoutes( @@ -68,6 +55,17 @@ export function populateComponentsInRoutes( }) } +/** + * WHAT: Recursively filters through an array of objects based on a filter string. + * HOW: Checks specified keys in each object for a match with the filter string. + * If an object has children, it recursively filters the children as well. + * An object is included in the result if it or any of its children match the filter. + * @param data + * @param filter + * @param matchCase + * @param searchKeys + * @returns + */ export function recrusiveFilter( data: Array, filter: string, @@ -103,6 +101,14 @@ export function recrusiveFilter( return filteredItems } +/** + * WHAT: Searches through a nested object or array to find an object with a specific key-value pair. + * HOW: Recursively traverses the object/array, checking each object's keys and values. + * If the specified key is found and its value matches (if provided), the object is returned. + * If the object contains nested objects or arrays, the function calls itself on those nested structures. + * @param param0 + * @returns + */ export function search({ obj, key, @@ -127,121 +133,13 @@ export function search({ return undefined } -export function searchBy( - obj: any, - query: Record, - matchType: "AND" | "OR" = "AND", -): ObjectType | undefined { - // Helper function to check if an object matches the query - const matchesQuery = (item: any): boolean => { - const conditions = Object.entries(query).map(([key, value]) => { - return key in item && item[key] === value; - }); - - // Match all conditions (AND) or any condition (OR) - return matchType === "AND" ? conditions.every(Boolean) : conditions.some(Boolean); - }; - - if (Array.isArray(obj)) { - // If obj is an array, recursively search each item - for (const item of obj) { - const result = searchBy(item, query, matchType); - if (result) return result; - } - } else if (obj && typeof obj === "object" && !(obj instanceof Date)) { - // If obj is an object, check if it matches the query - if (matchesQuery(obj)) { - return obj; - } - - // Recursively search through the object's values - for (const value of Object.values(obj)) { - if (typeof value === "object" && !(value instanceof Date)) { - const result = searchBy(value, query, matchType); - if (result) return result; - } - } - } - - return undefined; -} - -export function filterTreeStructure( - args: SearchArgs, -): boolean | Array | ObjectType | void { - let { obj, key, value } = args - if (Array.isArray(obj)) { - return obj.filter((item, index) => { - if (typeof item === "object" && !(item instanceof Date)) { - args.obj = item - - const match = filterTreeStructure(args) - if (Array.isArray(match) && match.length > 0) { - ; (obj as unknown as Array>).splice(index, 1, match) - } - return match - } - return false - }) - } else { - if (obj) { - let found = false - if (key in obj && (value === undefined || obj[key] === value)) { - obj.hasMatched = true - } - Object.keys(obj).forEach((key) => { - const item = (obj as ObjectType)[key] - if (typeof item === "object" && !(item instanceof Date)) { - args.obj = item as ObjectType - - const match = filterTreeStructure(args) - if (Array.isArray(match) ? match.length > 0 : match) { - found = true - } else { - delete (obj as ObjectType)[key] - } - if (Array.isArray(match) && match.length > 0) { - ; (obj as ObjectType)[key] = match - } - } else (obj as ObjectType).hasMatched || delete (obj as ObjectType)[key] - }) - return (obj.hasMatched as boolean) || found - } - return false - } -} - -export function aggregate( - data: any, - aggregator: string, -): { [key: string]: Array } { - let aggregated: { [key: string]: Array } = {} - data.forEach((item: any) => { - const key = item[aggregator] - let aggregatedArray: Array | undefined = aggregated[key] - if (!aggregatedArray) { - aggregatedArray = [] - aggregated[key] = aggregatedArray - } - aggregatedArray.push(item) - }) - return aggregated -} - -export function generateCategories( - data: any, - categories: Array | undefined, -): { [key: string]: { [key: string]: Array } } { - const groups: { [key: string]: { [key: string]: Array } } = {} - if (categories && data) { - categories.forEach((key) => { - let group: { [key: string]: Array } = aggregate(data, key) - groups[key] = group - }) - } - return groups -} - +/** + * WHAT: Converts a string to camelCase format. + * @param str + * @param separater + * @param includeFirst + * @returns + */ export function toCamelCase( str: string, separater: string, @@ -257,12 +155,11 @@ export function toCamelCase( .join("") } -export function generateRandomId(length: number = 8) { - return Math.random() - .toString(36) - .substring(2, length + 2) -} - +/** + * WHAT: Copies text to clipboard or prompts user to copy if clipboard access is not available. + * @param text + * @param cb + */ export function copyOrPrompt(text?: string, cb?: FunctionType) { if (copyToClipboard(text)) { cb && cb() @@ -271,6 +168,11 @@ export function copyOrPrompt(text?: string, cb?: FunctionType) { } } +/** + * WHAT: Copies text to clipboard if supported by the browser. + * @param text + * @returns + */ export function copyToClipboard(text?: string) { const isNotSafari = typeof (window as any).safari === "undefined" if (isNotSafari) { @@ -281,40 +183,6 @@ export function copyToClipboard(text?: string) { } } -export function matchArrayFilters( - searchList: Array | undefined, - filters: Array | false | undefined, - match: string, - type?: string, -) { - if (filters && searchList) { - return type && type === "starts" - ? searchList.filter( - (item) => - filters.findIndex((al) => - item[match].toLowerCase().startsWith(al.toLowerCase()), - ) > -1, - ) - : searchList.filter( - (item) => - filters.findIndex( - (al) => item[match].toLowerCase() === al.value.toLowerCase(), - ) > -1, - ) - } - return searchList -} - -export function importComponent( - name: string, - hierarchy: string, - fallback: FunctionType, -) { - return lazy(() => - import(`../components/${hierarchy}/${name}/${name}.tsx`).catch(fallback), - ) -} - export function getStylesFromClass(className: string): Record { const styleSheet = Array.from(document.styleSheets).find((sheet) => Array.from(sheet.cssRules).some( @@ -342,6 +210,14 @@ export function getStylesFromClass(className: string): Record { return {} } +/** + * WHAT: Creates a debounced version of a function that delays its execution until after a specified wait time has elapsed since the last time it was invoked. + * HOW: Uses a timeout to track the delay period. If the debounced function is called again before the timeout expires, the previous timeout is cleared and a new one is set. + * @param func + * @param wait + * @param immediate + * @returns + */ export function debounce void>( func: T, wait?: number, @@ -368,6 +244,11 @@ export function debounce void>( } } +/** + * WHAT: Detects if the current device is a mobile device based on the user agent string. + * HOW: Uses a regular expression to match common mobile device identifiers in the user agent string. + * @returns + */ export function isMobile(): boolean { let check = false ; (function (a: string) { @@ -407,98 +288,28 @@ export function clearCookie(cname: string) { document.cookie = cname + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;" } +/** + * Pads a number with leading zeros to ensure it has at least two digits. + * Optionally appends a separator at the end. + * @param num + * @param separator + * @returns + */ export function pad(num: number, separator?: string) { return ("" + num).padStart(2, "0") + (separator ? separator : "") } +/** + * Accepts a string and converts it to a more readable format. + * HOW: Splits camelCase words and capitalizes the first letter of each word. + * + * @param str Input string to be converted + * @returns Converted string + */ export function toReadable(str?: string) { return str ?.replace(/([a-z])([A-Z])/g, "$1 $2") .split(" ") .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(" ") -} - -export function generateFileKey(file: File | { name: string }) { - return "size" in file - ? `${file.name}_${file.size}_${file.type}_${file.lastModified}` - : file.name + "-preexisting-" + Date.now() -} - -export function convertToBytes(input: string): number { - const trimmedInput = input.trim().toLowerCase() - - try { - if (trimmedInput.endsWith("kb")) { - const number = parseFloat(trimmedInput.slice(0, -2)) - return number * 1024 - } else if (trimmedInput.endsWith("mb")) { - const number = parseFloat(trimmedInput.slice(0, -2)) - return number * 1024 * 1024 - } else if (trimmedInput.endsWith("bytes")) { - const number = parseFloat(trimmedInput.slice(0, -5)) - return number - } else if (!isNaN(parseFloat(trimmedInput))) { - return parseFloat(trimmedInput) - } else { - return -1 - } - } catch (error) { - return -1 - } -} - -export function isImage(type: string) { - return !!type && validImageMimeTypes.indexOf(type) > -1 -} - -export const stringifyOnce: StringifyOnce = (obj, replacer, indent) => { - const printedObjects: any[] = [] - const printedObjectKeys: string[] = [] - - function printOnceReplacer(key: string, value: any): any { - if (printedObjects.length > 2000) { - // browsers will not print more than 20K, I don't see the point to allow 2K.. algorithm will not be fast anyway if we have too many objects - return "object too long" - } - - let printedObjIndex: number | false = false - printedObjects.forEach((obj, index) => { - if (obj === value) { - printedObjIndex = index - } - }) - - if (key === "") { - // root element - printedObjects.push(obj) - printedObjectKeys.push("root") - return value - } else if (printedObjIndex !== false && typeof value === "object") { - if (printedObjectKeys[printedObjIndex] === "root") { - return "(pointer to root)" - } else { - return ( - "(see " + - (!!value && !!value.constructor - ? value.constructor.name.toLowerCase() - : typeof value) + - " with key " + - printedObjectKeys[printedObjIndex] + - ")" - ) - } - } else { - const qualifiedKey = key || "(empty key)" - printedObjects.push(value) - printedObjectKeys.push(qualifiedKey) - if (replacer) { - return replacer(key, value) - } else { - return value - } - } - } - - return JSON.stringify(obj, printOnceReplacer, indent) -} +} \ No newline at end of file diff --git a/src/hooks.ts b/src/hooks.ts index 0784415..23d5a5e 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -24,59 +24,6 @@ export function useSlotted(componentName: string) { setSlotted([...slotted, componentName]) } -export function useStateWithHistory( - initialState?: T, -): [T | undefined, FunctionType, FunctionType, FunctionType, boolean, boolean] { - const [past, setPast] = useState([]) - const [present, setPresent] = useState() - const [future, setFuture] = useState([]) - - useEffect(() => { - !present && setPresent(initialState) - // eslint-disable-next-line - }, [initialState]) - - const undo = useCallback(() => { - if (past.length === 0) return - - const newPast = [...past] - const newPresent = newPast.pop() - - setPast(newPast) - present && setFuture([present, ...future]) - setPresent(newPresent) - }, [future, past, present]) - - const redo = useCallback(() => { - if (future.length === 0) return - - const newFuture = [...future] - const newPresent = newFuture.shift() - - past && present && setPast([...past, present]) - setFuture(newFuture) - setPresent(newPresent) - }, [future, past, present]) - - const updatePresent = useCallback( - (newState: any, skipHistory?: boolean) => { - !skipHistory && past && present && setPast([...past, present]) - setPresent(newState) - !skipHistory && setFuture([]) - }, - [past, present], - ) - - return [ - present, - updatePresent, - undo, - redo, - past.length > 0, - future.length > 0, - ] -} - export const useTheme = (): { theme: ArThemes setTheme: (theme: ArThemes) => void diff --git a/src/index.ts b/src/index.ts index fc7a97b..d3887b8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,10 @@ -export * from "./dateformat" export * from "./domHelper" export * from "./helper" export * from "./network" -export * from "./validators" +export * from "../../taskerclient/src/validators" export * from "./recursionHelper" -export * from "./chartGenerators" export * from "./hooks" export * from "./contexts" export * from "./providers" export * from "./HOC" +export * from "./enums" diff --git a/src/recursionHelper.tsx b/src/recursionHelper.tsx index 12ced38..8bfd265 100644 --- a/src/recursionHelper.tsx +++ b/src/recursionHelper.tsx @@ -1,5 +1,6 @@ import { v4 as uuid } from "uuid" -import { FunctionType, ObjectType, RecusionConditionTypes } from "@armco/types" +import { FunctionType, ObjectType } from "@armco/types" +import { RecusionConditionTypes } from "./enums" export interface RecursionArgs { data: any diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..d9d8b54 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,50 @@ +import { CSSProperties, ReactNode } from "react" +import { ArAlertType } from "./enums" +import { BaseProps, FunctionType } from "@armco/types" + +export interface DrawerProps extends BaseProps { + children?: ReactNode + collapsed?: boolean + contentClasses?: string + isCollapsible?: boolean + title?: string +} + +export interface ModalProps extends BaseProps { + body?: ReactNode + closeHandler?: FunctionType + content?: ReactNode + contentClasses?: string + footer?: ReactNode + header?: ReactNode + hideClose?: boolean + isSticky?: boolean + modalClasses?: string + primaryButtonLabel?: string + primaryHandler?: FunctionType + secondaryButtonLabel?: string + shouldScale?: boolean + shouldFadeIn?: boolean + show?: boolean +} + +// Contexts +export interface ArContextType { + theme: ArThemes + modalState?: ModalProps + notification?: AlertProps + drawerState?: DrawerProps + isLoggedIn?: boolean + user?: User | null + leftPanelContent?: PanelContent + setLeftPanelContent: (leftPanelContent: PanelContent) => void + rightPanelContent?: PanelContent + setRightPanelContent: (rightPanelContent: PanelContent) => void + setTheme: (theme: ArThemes) => void + setLoggedIn: (isLoggedIn: boolean) => void + setUser: (user: User | null) => void + clearUser: () => void + setModalState: (modalState: ModalProps | undefined) => void + notify: (notification: AlertProps | undefined) => void + setDrawerState: (drawerState: DrawerProps) => void +} diff --git a/src/validators.ts b/src/validators.ts deleted file mode 100644 index 7da11ca..0000000 --- a/src/validators.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ObjectType } from "@armco/types" - -export function validateTask(taskObj: ObjectType) { - return ( - taskObj && - taskObj.name && - taskObj.client && - taskObj.target && - (taskObj.target as ObjectType).endpoint && - taskObj.schedule && - (taskObj.schedule as ObjectType).cron - ) -} diff --git a/tsconfig.json b/tsconfig.json index 289808a..f331297 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,5 +23,5 @@ }, "include": [ "src" -, "../Calendar/src/dateHelper.ts" ] +, "../taskerclient/src/validators.ts" ], }