From a49a6fee65669db18760d06fb83cc6be79411f4f Mon Sep 17 00:00:00 2001 From: mohiit1502 Date: Sat, 5 Oct 2024 23:09:35 +0530 Subject: [PATCH] Fixed compile errors, added publish scripts --- build-tools/build.sh | 17 - build-tools/generate-module.js | 2 +- package-lock.json | 34 +- package.json | 8 +- publish-local.sh | 16 + publish.sh | 10 +- src/adapters.ts | 182 ++-- src/contexts.ts | 4 +- src/dateHelper.ts | 8 +- src/dateformat.ts | 5 +- src/domHelper.ts | 810 +++++++++--------- src/gridHelper.ts | 1444 ++++++++++++++++---------------- src/helper.tsx | 978 +++++++++++---------- src/index.ts | 16 +- src/network.ts | 56 +- src/providers.tsx | 4 +- src/recursionHelper.tsx | 110 ++- src/validators.ts | 24 +- vite.config.ts | 4 +- 19 files changed, 1860 insertions(+), 1872 deletions(-) create mode 100755 publish-local.sh diff --git a/build-tools/build.sh b/build-tools/build.sh index 43bf4ce..0daf942 100755 --- a/build-tools/build.sh +++ b/build-tools/build.sh @@ -4,26 +4,9 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" rm -rf build - -# Quality check npx tsc - -# Generate Styles (with unneeded js) -# vite build --config vite-css.config.ts - vite build -# Generated source bundle -# tsc -p tsconfig.cjs.build.json -# tsc -p tsconfig.es.build.json - -# Cleanup unneeded js -# rm -f build/*.js - -# # Copy image files -# find src/static -type f \( -name "*.jpg" -o -name "*.jpeg" -o -name "*.png" -o -name "*.gif" -o -name "*.svg" \) -exec cp {} build/cjs/static/ \; -# find src/static -type f \( -name "*.jpg" -o -name "*.jpeg" -o -name "*.png" -o -name "*.gif" -o -name "*.svg" \) -exec cp {} build/es/static/ \; - # Run Post processors: Update style imports in .js files, create component modules node "$SCRIPT_DIR/post-processor.js" build/cjs node "$SCRIPT_DIR/post-processor.js" build/es diff --git a/build-tools/generate-module.js b/build-tools/generate-module.js index 699fa72..802cda7 100644 --- a/build-tools/generate-module.js +++ b/build-tools/generate-module.js @@ -5,7 +5,7 @@ import { fileURLToPath } from "url" const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) -const exclusions = ["enums.js", "navigator.js", "v4.js", "index.js"] +const exclusions = ["enums.js", "v4.js", "index.js"] async function generateModule(fileName) { if (!exclusions.includes(fileName)) { diff --git a/package-lock.json b/package-lock.json index d837560..884ac06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@armco/utils", - "version": "0.0.7", + "version": "0.0.18", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@armco/utils", - "version": "0.0.7", + "version": "0.0.18", "license": "ISC", "dependencies": { - "@armco/configs": "^0.0.3", + "@armco/configs": "^0.0.7", "@armco/types": "^0.0.9", "d3": "^7.9.0", "uuid": "^10.0.0" @@ -25,6 +25,7 @@ "typescript": "^5.0.2", "vite": "^5.4.6", "vite-plugin-dts": "^4.2.1", + "vite-plugin-externalize-deps": "^0.8.0", "vitest": "^2.1.1" }, "peerDependencies": { @@ -46,15 +47,21 @@ } }, "node_modules/@armco/configs": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@armco/configs/-/configs-0.0.3.tgz", - "integrity": "sha512-1gdJmMIkAVttA8D2gZmKlVEs5mEnSkr94BjZfyQa0/A6oQgVt2EMEgJmCUkHy2YzitFm8a8ZtOfM3cvdKTQoWg==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@armco/configs/-/configs-0.0.7.tgz", + "integrity": "sha512-iniTmpR0kaOmRXAKw7cmXMyVnd1M+gOG/gNEOvmNAxRt8AuvW8WBra96o6oHnk0LFPgZhzmKSHvWzO1ADErxZQ==", "license": "ISC", "dependencies": { - "@armco/types": "^0.0.9", + "@armco/types": "^0.0.10", "uuid": "^10.0.0" } }, + "node_modules/@armco/configs/node_modules/@armco/types": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/@armco/types/-/types-0.0.10.tgz", + "integrity": "sha512-PKmehb5PsX6o6b3yaItCyDJxYy5nyXBduONnWv6slQwBMareFquGUcrCORPQhvnVLprCTs5aIZyPerZdjVuuxg==", + "license": "MIT" + }, "node_modules/@armco/types": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/@armco/types/-/types-0.0.9.tgz", @@ -4070,6 +4077,19 @@ } } }, + "node_modules/vite-plugin-externalize-deps": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/vite-plugin-externalize-deps/-/vite-plugin-externalize-deps-0.8.0.tgz", + "integrity": "sha512-MdC8kRNQ1ZjhUicU2HcqGVhL0UUFqv83Zp1JZdHjE82PoPR8wsSWZ3axpot7B6img3sW6g8shYJikE0CKA0chA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/voracious" + }, + "peerDependencies": { + "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, "node_modules/vitest": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.1.tgz", diff --git a/package.json b/package.json index cf767d3..e1d7f92 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,13 @@ { "name": "@armco/utils", - "version": "0.0.7", + "version": "0.0.18", "type": "module", "scripts": { "build": "./build-tools/build.sh", "format": "prettier --write .", "lint": "eslint .", - "publish:sh": "./publish.sh" + "publish:sh": "./publish.sh", + "publish:local": "./publish-local.sh" }, "devDependencies": { "@types/d3": "^7.4.3", @@ -19,10 +20,11 @@ "typescript": "^5.0.2", "vite": "^5.4.6", "vite-plugin-dts": "^4.2.1", + "vite-plugin-externalize-deps": "^0.8.0", "vitest": "^2.1.1" }, "dependencies": { - "@armco/configs": "^0.0.3", + "@armco/configs": "^0.0.7", "@armco/types": "^0.0.9", "d3": "^7.9.0", "uuid": "^10.0.0" diff --git a/publish-local.sh b/publish-local.sh new file mode 100755 index 0000000..46b66a6 --- /dev/null +++ b/publish-local.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +semver=${1:-patch} + +set -e + +npm run build +cp package.json build/ +sed -i '' -E 's/"build"/"*"/' build/package.json + +sed -i '' 's#"build/cjs/index.js"#"cjs/index.js"#' build/package.json +sed -i '' 's#"build/es/index.js"#"es/index.js"#' build/package.json +sed -i '' 's#"build/types/index.d.ts"#"types/index.d.ts"#' build/package.json + +cd build +npm pack --pack-destination ~/__Projects__/Common \ No newline at end of file diff --git a/publish.sh b/publish.sh index a0d2fb4..94621ea 100755 --- a/publish.sh +++ b/publish.sh @@ -2,7 +2,15 @@ semver=${1:-patch} -npm run build set -e npm --no-git-tag-version version ${semver} +npm run build +cp package.json build/ +sed -i '' -E 's/"build"/"*"/' build/package.json + +sed -i '' 's#"build/cjs/index.js"#"cjs/index.js"#' build/package.json +sed -i '' 's#"build/es/index.js"#"es/index.js"#' build/package.json +sed -i '' 's#"build/types/index.d.ts"#"types/index.d.ts"#' build/package.json + +cd build npm publish --access public --loglevel verbose diff --git a/src/adapters.ts b/src/adapters.ts index fa4fb7d..3d81edf 100644 --- a/src/adapters.ts +++ b/src/adapters.ts @@ -6,7 +6,7 @@ import { TreeListData, RecusionConditionTypes, } from "@armco/types" -import RecursionHelper from "./recursionHelper" +import { injectIds } from "./recursionHelper" const years = Array.from({ length: 60 }, (_, i) => 1964 + i) const countries = ["IN", "US", "RU", "CN", "UK", "JP", "FR", "IT", "SP"] @@ -29,100 +29,96 @@ const dummyProgressiveChartData: ThreeDChartDataArrayFormat = countries.flatMap( }, ) -class Adapter { - static adaptToTreeFromComponentConfig(data: any): Array { - const returnTreeList: Array = [] - Object.keys(data).forEach((key) => { - const groupConfig = data[key as keyof any] - const components = groupConfig.components - components.sort((c1: ComponentDescription, c2: ComponentDescription) => - (c1.name as any) > (c2.name as any) ? 1 : -1, - ) - const obj: TreeListData = { - label: groupConfig.label, - children: [], - data: { ...groupConfig }, - } - components.forEach((item: any) => { - const treeItem: TreeListData = { - label: typeof item === "string" ? item : item.name, - data: { - component: typeof item === "string" ? item : item.name, - hierarchy: key, - }, - } - const children: Array = [] - treeItem.children = children - if (item.variants && Object.keys(item.variants.length > 0)) { - Object.keys(item.variants).forEach((variantKey) => { - const variants = item.variants[variantKey] - treeItem.children && - treeItem.children.push({ - label: variantKey, - data: { name: typeof item === "string" ? item : item.name }, - children: variants.map((v: string) => ({ - label: v, - data: { - component: typeof item === "string" ? item : item.name, - props: { [variantKey]: v }, - hierarchy: key, - }, - })), - }) - }) - } - obj.children && obj.children.push(treeItem) - }) - returnTreeList.push(obj) - }) - RecursionHelper.injectIds({ - data: returnTreeList, - condition: { type: RecusionConditionTypes.KEY_EXISTS, key: "label" }, - iterateOn: "children", - }) - return returnTreeList - } - - static adaptToProgressiveChart( - inputData: ProgressiveChartData, - demo?: boolean, - ): ThreeDChartDataObjectFormat { - if (demo && !inputData) { - inputData = dummyProgressiveChartData +export function adaptToTreeFromComponentConfig(data: any): Array { + const returnTreeList: Array = [] + Object.keys(data).forEach((key) => { + const groupConfig = data[key as keyof any] + const components = groupConfig.components + components.sort((c1: ComponentDescription, c2: ComponentDescription) => + (c1.name as any) > (c2.name as any) ? 1 : -1, + ) + const obj: TreeListData = { + label: groupConfig.label, + children: [], + data: { ...groupConfig }, } - let unsortedData: ThreeDChartDataObjectFormat = {} - - // Transform the data into the desired format - if (Array.isArray(inputData)) { - inputData.forEach(([key, xValue, yValue]) => { - if (!unsortedData[key]) { - unsortedData[key] = [[xValue, yValue]] - } else { - unsortedData[key].push([xValue, yValue]) - } - }) - } else { - unsortedData = inputData - } - - // Sort the keys - const keys = Object.keys(unsortedData) - keys.sort((a, b) => { - if (typeof a === "number" && typeof b === "number") { - return a - b - } else { - return a.localeCompare(b) + components.forEach((item: any) => { + const treeItem: TreeListData = { + label: typeof item === "string" ? item : item.name, + data: { + component: typeof item === "string" ? item : item.name, + hierarchy: key, + }, } + const children: Array = [] + treeItem.children = children + if (item.variants && Object.keys(item.variants.length > 0)) { + Object.keys(item.variants).forEach((variantKey) => { + const variants = item.variants[variantKey] + treeItem.children && + treeItem.children.push({ + label: variantKey, + data: { name: typeof item === "string" ? item : item.name }, + children: variants.map((v: string) => ({ + label: v, + data: { + component: typeof item === "string" ? item : item.name, + props: { [variantKey]: v }, + hierarchy: key, + }, + })), + }) + }) + } + obj.children && obj.children.push(treeItem) }) - - // Create a new object with the sorted keys - let sortedData: ThreeDChartDataObjectFormat = {} - keys.forEach((key) => { - sortedData[key] = unsortedData[key] - }) - - return sortedData - } + returnTreeList.push(obj) + }) + injectIds({ + data: returnTreeList, + condition: { type: RecusionConditionTypes.KEY_EXISTS, key: "label" }, + iterateOn: "children", + }) + return returnTreeList } -export default Adapter +export function adaptToProgressiveChart( + inputData: ProgressiveChartData, + demo?: boolean, +): ThreeDChartDataObjectFormat { + if (demo && !inputData) { + inputData = dummyProgressiveChartData + } + let unsortedData: ThreeDChartDataObjectFormat = {} + + // Transform the data into the desired format + if (Array.isArray(inputData)) { + inputData.forEach(([key, xValue, yValue]) => { + if (!unsortedData[key]) { + unsortedData[key] = [[xValue, yValue]] + } else { + unsortedData[key].push([xValue, yValue]) + } + }) + } else { + unsortedData = inputData + } + + // Sort the keys + const keys = Object.keys(unsortedData) + keys.sort((a, b) => { + if (typeof a === "number" && typeof b === "number") { + return a - b + } else { + return a.localeCompare(b) + } + }) + + // Create a new object with the sorted keys + let sortedData: ThreeDChartDataObjectFormat = {} + keys.forEach((key) => { + sortedData[key] = unsortedData[key] + }) + + return sortedData +} diff --git a/src/contexts.ts b/src/contexts.ts index 8a27bc8..fa50951 100644 --- a/src/contexts.ts +++ b/src/contexts.ts @@ -1,6 +1,6 @@ import { createContext } from "react" import { ArContextType, ArThemes, FunctionType } from "@armco/types" -import Helper from "./helper" +import { isMobile } from "./helper" export const SlotterContext = createContext<{ slotted: Array @@ -9,7 +9,7 @@ export const SlotterContext = createContext<{ export const ArContext = createContext({ theme: ArThemes.DARK1, - drawerState: { collapsed: Helper.isMobile() }, + drawerState: { collapsed: isMobile() }, notify: () => {}, setDrawerState: () => {}, setLeftPanelContent: () => {}, diff --git a/src/dateHelper.ts b/src/dateHelper.ts index 17aca73..7c529f0 100644 --- a/src/dateHelper.ts +++ b/src/dateHelper.ts @@ -1,6 +1,6 @@ import { CalendarDate, Event, ArDateFormats } from "@armco/types" -import { MONTH_INDEX } from "@armco/configs" -import Helper from "./helper" +import { MONTH_INDEX } from "@armco/configs/constants" +import { pad } from "./helper" export interface CalendarOptions { /** @@ -402,8 +402,8 @@ class Calendar { separator: string = "/", format: string = "DDMMYYYY", ) { - const day = Helper.pad(date.day, separator) - const month = Helper.pad(date.month + 1, separator) + const day = pad(date.day, separator) + const month = pad(date.month + 1, separator) const year = date.year switch (format) { diff --git a/src/dateformat.ts b/src/dateformat.ts index e7b31cb..0a633dd 100644 --- a/src/dateformat.ts +++ b/src/dateformat.ts @@ -1,5 +1,4 @@ -import { FunctionType } from "@armco/types" -import { ArDateMasks } from "@armco/types" +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 @@ -334,3 +333,5 @@ export const formatTimezone = (date: Date) => { ?.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 343c893..4079032 100644 --- a/src/domHelper.ts +++ b/src/domHelper.ts @@ -6,451 +6,447 @@ import { ObjectType, Position, } from "@armco/types" -import Helper from "./helper" +import { getStylesFromClass } from "./helper" -class DomHelper { - static style = window.getComputedStyle(document.documentElement) - static fontSize = parseFloat(DomHelper.style.fontSize) - static markerSize = 7 +const style = window.getComputedStyle(document.documentElement) +const fontSize = parseFloat(style.fontSize) +const markerSize = 7 - static outerWidth(el: HTMLElement) { - let width = el.offsetWidth - const style = getComputedStyle(el) +export function outerWidth(el: HTMLElement) { + let width = el.offsetWidth + const style = getComputedStyle(el) - width += parseInt(style.marginLeft) + parseInt(style.marginRight) - return width - } + width += parseInt(style.marginLeft) + parseInt(style.marginRight) + return width +} - static 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" +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(",") + ")" + const translatedPosition = "(" + positionCss.join(",") + ")" - return transitionProp + translatedPosition - } + return transitionProp + translatedPosition +} - static hexToHsl(color: string, returnRaw?: boolean) { - const [r, g, b] = DomHelper.hexToRgb(color) - return DomHelper.rgbToHsl(r, g, b, returnRaw) - } +export function hexToHsl(color: string, returnRaw?: boolean) { + const [r, g, b] = hexToRgb(color) + return rgbToHsl(r, g, b, returnRaw) +} - static 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] - } +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 - */ - static rgbToHsl(r: number, g: number, b: number, returnRaw?: boolean) { - r /= 255 - g /= 255 - b /= 255 +/** + * 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 + 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) + 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 - } - - static 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) + 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 } - return [r * 255, g * 255, b * 255] + // @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) } - static 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) + 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)) +} + +export function implementSvgStyles( + svg: SVGSVGElement, + svgProps?: IconProps["attributes"], + flags?: Array, +) { + const { size, classes, colors, strokeWidth } = svgProps || {} + const { fillColor, strokeColor } = colors || {} + const [skipPathColorFill] = flags || [] + let styles = null + if (classes) { + styles = getStylesFromClass(classes) + styles = Object.entries(styles || {}) + .map(([property, value]) => `${property}: ${value}`) + .join("; ") + } + svg.setAttribute("height", size || "1rem") + svg.setAttribute("width", size || "1rem") + svg.setAttribute("class", classes || "") + svg.setAttribute("style", styles || "") + svg.setAttribute("stroke", strokeColor || "black") + strokeWidth + ? svg.setAttribute("stroke-width", strokeWidth) + : strokeColor && svg.setAttribute("stroke-width", "1") + let path: SVGPathElement | NodeListOf = + svg.querySelectorAll("path") + if (path.length === 1 && !skipPathColorFill) { + path = path[0] + // Override stroke and color of "path" node inside SVG at below line. + path && + strokeColor && + !!path.getAttribute("stroke") && + path.setAttribute("stroke", strokeColor) + path && + strokeWidth && + !!path.getAttribute("stroke-width") && + path.setAttribute("stroke-width", strokeWidth) + path && + fillColor && + !!path.getAttribute("fill") && + path.setAttribute("fill", fillColor) + } + svg.style.color = fillColor || "black" + return svg +} + +/** + * 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 } - static hslToHex(h: number, s: number, l: number) { - const [r, g, b] = DomHelper.hslToRgb(h, s, l) - return DomHelper.rgbToHex(Math.round(r), Math.round(g), Math.round(b)) + if (index === 0) { + return 0 } - static implementSvgStyles( - svg: SVGSVGElement, - svgProps?: IconProps["attributes"], - flags?: Array, - ) { - const { size, classes, colors, strokeWidth } = svgProps || {} - const { fillColor, strokeColor } = colors || {} - const [skipPathColorFill] = flags || [] - let styles = null - if (classes) { - styles = Helper.getStylesFromClass(classes) - styles = Object.entries(styles || {}) - .map(([property, value]) => `${property}: ${value}`) - .join("; ") + 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 } - svg.setAttribute("height", size || "1rem") - svg.setAttribute("width", size || "1rem") - svg.setAttribute("class", classes || "") - svg.setAttribute("style", styles || "") - svg.setAttribute("stroke", strokeColor || "black") - strokeWidth - ? svg.setAttribute("stroke-width", strokeWidth) - : strokeColor && svg.setAttribute("stroke-width", "1") - let path: SVGPathElement | NodeListOf = - svg.querySelectorAll("path") - if (path.length === 1 && !skipPathColorFill) { - path = path[0] - // Override stroke and color of "path" node inside SVG at below line. - path && - strokeColor && - !!path.getAttribute("stroke") && - path.setAttribute("stroke", strokeColor) - path && - strokeWidth && - !!path.getAttribute("stroke-width") && - path.setAttribute("stroke-width", strokeWidth) - path && - fillColor && - !!path.getAttribute("fill") && - path.setAttribute("fill", fillColor) - } - svg.style.color = fillColor || "black" - return svg + + return currentPosition } - /** - * Gets the list 'position' relative to a current index - * @param index - */ - static getPosition(index: number, props: CarouselProps): number { - if (props.infiniteLoop) { - // index has to be added by 1 because of the first cloned slide - ++index + 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 getDocumentElement(demo?: boolean) { + // Check if the component is running inside an iframe + if (demo) { + const iframeElement = + document.querySelector(".ar-Editor__frame") + if (iframeElement) { + return iframeElement.contentDocument } - - 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 } + // Return the parent document element by default + return document +} - /** - * Sets the 'position' transform for sliding animations - * @param position - * @param forceReflow - */ - static 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) - }) +export function getWindowElement(demo?: boolean) { + // Check if the component is running inside an iframe + if (demo) { + const iframeElement = + document.querySelector(".ar-Editor__frame") - return style - } - - static isKeyboardEvent( - e?: React.MouseEvent | React.KeyboardEvent, - ): e is React.KeyboardEvent { - return e ? e.hasOwnProperty("key") : false - } - - static getDocumentElement(demo?: boolean) { - // Check if the component is running inside an iframe - if (demo) { - const iframeElement = - document.querySelector(".ar-Editor__frame") - if (iframeElement) { - return iframeElement.contentDocument - } + if (iframeElement) { + return iframeElement.contentWindow } - // Return the parent document element by default - return document } + // Return the parent document element by default + return window +} - static getWindowElement(demo?: boolean) { - // Check if the component is running inside an iframe - if (demo) { - const iframeElement = - document.querySelector(".ar-Editor__frame") - - if (iframeElement) { - return iframeElement.contentWindow - } - } - // Return the parent document element by default - return window +export function shouldUsePosition( + position: ArPopoverPositions, + anchorRect: DOMRect, + popoverRect: DOMRect, + demo?: boolean, +) { + const windowObj = getWindowElement(demo) || window + const exceeds: ObjectType = { + bottom: + anchorRect.top + anchorRect.height + popoverRect.height > + windowObj.innerHeight, + right: + anchorRect.left + anchorRect.width + popoverRect.width > + windowObj.innerWidth, + left: anchorRect.left - popoverRect.width < 0, + top: anchorRect.top - popoverRect.height < 0, } - - static shouldUsePosition( - position: ArPopoverPositions, - anchorRect: DOMRect, - popoverRect: DOMRect, - demo?: boolean, - ) { - const windowObj = DomHelper.getWindowElement(demo) || window - const exceeds: ObjectType = { - bottom: - anchorRect.top + anchorRect.height + popoverRect.height > - windowObj.innerHeight, - right: - anchorRect.left + anchorRect.width + popoverRect.width > - windowObj.innerWidth, - left: anchorRect.left - popoverRect.width < 0, - top: anchorRect.top - popoverRect.height < 0, - } - if (exceeds[position]) return false - if (position === "left" || position === "right") { - if ( - DomHelper.fontSize > anchorRect.top || - DomHelper.fontSize > anchorRect.bottom - ) - return false - } else { - if ( - DomHelper.fontSize > anchorRect.left || - DomHelper.fontSize > anchorRect.right - ) - return false - } - return position - } - - static 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 : DomHelper.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 : DomHelper.markerSize), - }, - [ArPopoverPositions.LEFT]: { - top: anchorRect.top + (anchorRect.height - popoverRect.height) / 2, - left: - anchorRect.left - - popoverRect.width - - (hideMarker ? 0 : DomHelper.markerSize), - }, - [ArPopoverPositions.TOP]: { - top: - anchorRect.top - - popoverRect.height - - (hideMarker ? 0 : DomHelper.markerSize) - - (topOffset || 0), - left: anchorRect.left + (anchorRect.width - popoverRect.width) / 2, - }, - } - let { left, top } = popoverPositions[position] + if (exceeds[position]) return false + if (position === "left" || position === "right") { if ( - position === ArPopoverPositions.BOTTOM || - position === ArPopoverPositions.TOP + 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, + demo?: boolean, +) { + 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, demo) ) { - if ((left as number) < 0) { - left = DomHelper.fontSize - } - if ((left as number) + popoverRect.width > window.innerWidth) { - left = `calc(100% - ${popoverRect.width}px - ${DomHelper.fontSize}px)` - } - } else { - if ((top as number) < 0) { - top = DomHelper.fontSize - } - if ((top as number) + popoverRect.height > window.innerHeight) { - top = `calc(100% - ${popoverRect.height}px - ${DomHelper.fontSize}px)` - } + return position } - return { left, top } + continue } - static getPositionToUse( - anchorRect: DOMRect, - popoverRect: DOMRect, - requestedPosition?: ArPopoverPositions, - demo?: boolean, - ) { - if (requestedPosition && requestedPosition !== ArPopoverPositions.AUTO) { - return requestedPosition - } - const positionsPriority = [ - ArPopoverPositions.BOTTOM, - ArPopoverPositions.RIGHT, - ArPopoverPositions.LEFT, - ArPopoverPositions.TOP, - ] + return ArPopoverPositions.BOTTOM +} - for (const position of positionsPriority) { - if ( - DomHelper.shouldUsePosition(position, anchorRect, popoverRect, demo) - ) { - return position +export function calculatePopoverPosition( + anchorRect: DOMRect, + popoverRect: DOMRect, + requestedPosition?: ArPopoverPositions, + hideMarker?: boolean, + clickCoordinates?: Array | null, + demo?: boolean, + topOffset?: number, +) { + const windowObj = getWindowElement(demo) || window + anchorRect = clickCoordinates + ? { + left: clickCoordinates[0], + x: clickCoordinates[0], + top: clickCoordinates[1], + y: clickCoordinates[1], + height: 0, + width: 0, + right: windowObj.screen.width - clickCoordinates[0], + bottom: windowObj.screen.height - clickCoordinates[1], + toJSON: () => {}, } - continue - } - - return ArPopoverPositions.BOTTOM - } - - static calculatePopoverPosition( - anchorRect: DOMRect, - popoverRect: DOMRect, - requestedPosition?: ArPopoverPositions, - hideMarker?: boolean, - clickCoordinates?: Array | null, - demo?: boolean, - topOffset?: number, - ) { - const windowObj = DomHelper.getWindowElement(demo) || window - anchorRect = clickCoordinates - ? { - left: clickCoordinates[0], - x: clickCoordinates[0], - top: clickCoordinates[1], - y: clickCoordinates[1], - height: 0, - width: 0, - right: windowObj.screen.width - clickCoordinates[0], - bottom: windowObj.screen.height - clickCoordinates[1], - toJSON: () => {}, - } - : anchorRect - const position = DomHelper.getPositionToUse( - anchorRect, - popoverRect, - requestedPosition, - demo, - ) - const { left, top } = DomHelper.adjustPosition( - position, - anchorRect, - popoverRect, - hideMarker, - topOffset, - ) - return { - leftTop: { - top: ("" + top).startsWith("calc") ? top : top + "px", - left: ("" + left).startsWith("calc") ? left : left + "px", - }, - position, - } - } - - static openInNewTab(url: string) { - const win = window.open(url, "_blank") - if (win != null) { - win.focus() - } - } - - static download(dataURL: any) { - const anchor = document.createElement("a") - anchor.href = dataURL - anchor.download = "edited-image.png" - document.body.appendChild(anchor) - anchor.click() - document.body.removeChild(anchor) + : anchorRect + const position = getPositionToUse( + anchorRect, + popoverRect, + requestedPosition, + demo, + ) + 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 default DomHelper +export function openInNewTab(url: string) { + const win = window.open(url, "_blank") + if (win != null) { + win.focus() + } +} + +export function download(data: string | Blob, filename?: string) { + const anchor = document.createElement("a") + anchor.href = typeof data === "string" ? data : window.URL.createObjectURL(data) + anchor.download = filename || "edited-image.png" + document.body.appendChild(anchor) + anchor.click() + document.body.removeChild(anchor) +} diff --git a/src/gridHelper.ts b/src/gridHelper.ts index 56bf8ad..28d3d88 100644 --- a/src/gridHelper.ts +++ b/src/gridHelper.ts @@ -1,737 +1,733 @@ import { v4 as uuid } from "uuid" import { FunctionType, GridToolbarSpecs, SlotDescriptor } from "@armco/types" -import DomHelper from "./domHelper" +import { getDocumentElement } from "./domHelper" -class GridHelper { // For x and y toolbars only - static generateGridToolbarSpecs( - gridArea: Array>, - rowHeight?: Array | string, - colWidth?: Array | string, - ) { - const rowToolsGridArea = gridArea.map((row) => [ - row.every((cell) => cell === row[0]) ? row[0] : "ga-" + uuid(), - ]) - const colToolsGridArea = gridArea[0].map((_, i) => - gridArea.every((row) => row[i] === gridArea[0][i]) - ? gridArea[0][i] - : "ga-" + uuid(), - ) - const rowSlots = GridHelper.countOccurrences( - rowToolsGridArea.map((ga) => ga[0]), - ).map((slotConfig) => { +export function generateGridToolbarSpecs( + gridArea: Array>, + rowHeight?: Array | string, + colWidth?: Array | string, +) { + const rowToolsGridArea = gridArea.map((row) => [ + row.every((cell) => cell === row[0]) ? row[0] : "ga-" + uuid(), + ]) + const colToolsGridArea = gridArea[0].map((_, i) => + gridArea.every((row) => row[i] === gridArea[0][i]) + ? gridArea[0][i] + : "ga-" + uuid(), + ) + const rowSlots = countOccurrences( + rowToolsGridArea.map((ga) => ga[0]), + ).map((slotConfig) => { + return { + slot: slotConfig.slotId, + gridArea: slotConfig.slotId, + row: slotConfig.location, + column: 0, + rowSpan: slotConfig.span, + colSpan: 1, + style: { gridArea: slotConfig.slotId }, + } + }) + const colSlots = countOccurrences(colToolsGridArea).map( + (slotConfig) => { return { slot: slotConfig.slotId, gridArea: slotConfig.slotId, - row: slotConfig.location, - column: 0, - rowSpan: slotConfig.span, - colSpan: 1, + row: 0, + column: slotConfig.location, + rowSpan: 1, + colSpan: slotConfig.span, style: { gridArea: slotConfig.slotId }, } - }) - const colSlots = GridHelper.countOccurrences(colToolsGridArea).map( - (slotConfig) => { - return { - slot: slotConfig.slotId, - gridArea: slotConfig.slotId, - row: 0, - column: slotConfig.location, - rowSpan: 1, - colSpan: slotConfig.span, - style: { gridArea: slotConfig.slotId }, - } - }, - ) - return { - rowTools: { - slots: rowSlots, - gridTemplate: this.generateGridTemplate( - rowToolsGridArea, - rowHeight || "minmax(auto, 1fr)", - colWidth || "1fr", - ), - }, - colTools: { - slots: colSlots, - gridTemplate: this.generateGridTemplate( - [colToolsGridArea], - "1fr", - "1fr", - ), - }, - } - } - - static calculateRowHeights( - slots: Array, - rowHeights?: Array | string, - demo?: boolean, - ): Array { - const totalRows = Math.max(...slots.map((slot) => slot.row + slot.rowSpan)) - const allRowHeights: Array = [] - const doc = DomHelper.getDocumentElement(demo) - rowHeights = Array.isArray(rowHeights) - ? rowHeights - : (Array.from( - { length: totalRows }, - () => rowHeights || "minmax(2rem, auto)", - ) as Array) - - for (let rowIndex = 0; rowIndex < totalRows; rowIndex++) { - if ( - rowHeights[rowIndex] && - rowHeights[rowIndex] !== "" && - rowHeights[rowIndex].indexOf("auto") === -1 - ) { - allRowHeights[rowIndex] = rowHeights[rowIndex] - continue - } - - const rowSlots = slots.filter( - (slot) => slot.row <= rowIndex && rowIndex < slot.row + slot.rowSpan, - ) - const heights = rowSlots.map((slot) => { - const element = doc?.getElementById(slot.slot) - return element - ? Math.floor(element.getBoundingClientRect().height / slot.rowSpan) - : 0 - }) - const maxHeight = Math.max(...heights) - allRowHeights[rowIndex] = - maxHeight === 0 ? "minmax(2rem, auto)" : `${maxHeight}px` - } - - return allRowHeights - } - - static generateGridAreaAndSizes( - slots: Array, - currentRowHeights?: string | Array, - ): { gridArea: Array>; rowHeights: Array } { - let gridArea: Array> = [[]] - const rowHeights: Array = [] - slots.forEach((slot) => { - let endRow = slot.row + slot.rowSpan - let endColumn = slot.column + slot.colSpan - for (let row = slot.row; row < endRow; row++) { - const currentRowHeight = Array.isArray(currentRowHeights) - ? currentRowHeights[row] - : currentRowHeights - if (slot.content) { - rowHeights[row] = currentRowHeight || "auto" - } else { - rowHeights[row] = "minmax(2rem, auto)" - } - if (!gridArea[row]) { - gridArea[row] = [] - } - for (let col = slot.column; col < endColumn; col++) { - gridArea[row][col] = slot.gridArea - } - } - }) - - return { gridArea, rowHeights } - } - - static generateGridTemplate( - gridArea: Array>, - rowHeight?: Array | string, - colWidth?: Array | string, - ): string { - let areas = gridArea - .map( - (row, i) => - `"${row.join(" ")}" ${ - Array.isArray(rowHeight) - ? rowHeight[i] || "auto" - : rowHeight || "minmax(3rem, 1fr)" - }`, - ) - .join(" ") - - const gridTemplateColumns = - " / " + - (Array.isArray(colWidth) - ? colWidth.join(" ") - : Array.from( - { length: gridArea[0].length }, - () => colWidth || "1fr", - ).join(" ")) - areas += gridTemplateColumns - - return areas.trim() - } - - static mergeHandler( - setSlots: FunctionType, - slotConfigs: Array, - minMaxSelections: { - minRow: number - maxRow: number - minColumn: number - maxColumn: number }, - primarySlotConfig?: SlotDescriptor, - ) { - if (slotConfigs) { - slotConfigs = JSON.parse(JSON.stringify(slotConfigs)) - const selectedSlotConfigs = slotConfigs.filter((sc) => sc.isSelected) - selectedSlotConfigs.sort((a, b) => { - if (a.row !== b.row) { - return a.row - b.row - } else { - return a.column - b.column - } - }) - const firstSlotConfig = selectedSlotConfigs[0] - if (primarySlotConfig) { - primarySlotConfig = slotConfigs.find( - (s) => s.slot === primarySlotConfig?.slot, - ) as SlotDescriptor - primarySlotConfig.row = firstSlotConfig.row - primarySlotConfig.column = firstSlotConfig.column - } else { - primarySlotConfig = firstSlotConfig - } - primarySlotConfig.rowSpan = - minMaxSelections.maxRow - minMaxSelections.minRow + 1 - primarySlotConfig.colSpan = - minMaxSelections.maxColumn - minMaxSelections.minColumn + 1 - selectedSlotConfigs.forEach((ssc) => { - const matchedSlotConfigIndex = slotConfigs.findIndex( - (sc) => sc.slot === ssc.slot, - ) - const matchedSlotConfig = slotConfigs[matchedSlotConfigIndex] - if ( - matchedSlotConfig && - matchedSlotConfig.slot !== primarySlotConfig?.slot - ) { - slotConfigs.splice(matchedSlotConfigIndex, 1) - } - }) - // slotConfigs.forEach((slotConfig) => (slotConfig.isSelected = false)) - setSlots(slotConfigs) - } - } - - static splitHandler( - setSlots: FunctionType, - slotConfigs: Array, - orientation: "horizontal" | "vertical", - placement: "before" | "after", - slot?: SlotDescriptor, - ) { - slotConfigs = JSON.parse(JSON.stringify(slotConfigs)) - let selectedSlots = [] - if (slot) { - slot = slotConfigs.find((s) => s.slot === slot?.slot) - slot && selectedSlots.push(slot) - } else { - selectedSlots = slotConfigs.filter((sc) => sc.isSelected) - } - selectedSlots.forEach((selectedSlot) => { - const { - row, - slot: selectedSlotId, - column, - colSpan, - rowSpan, - } = selectedSlot - const isHorizontal = orientation === "horizontal" - const isAfter = placement === "after" - const slotId = uuid() - const gridAreaValue = `ga-${slotId}` - const span = isHorizontal ? "rowSpan" : "colSpan" - const dimension = isHorizontal ? "row" : "column" - let newDimSpan = selectedSlot[span] - if (newDimSpan > 1) { - newDimSpan = selectedSlot[span] - 1 - selectedSlot[span] = newDimSpan - } - const newSlot: SlotDescriptor = { - slot: slotId, - row: isHorizontal ? (isAfter ? newDimSpan + row : row) : row, - column: isHorizontal ? column : isAfter ? newDimSpan + column : column, - rowSpan: isHorizontal ? 1 : rowSpan, - colSpan: isHorizontal ? colSpan : 1, - gridArea: gridAreaValue, - splitFrom: selectedSlotId, - } - - const shouldUpdateOtherSlots = isHorizontal - ? rowSpan === 1 - : colSpan === 1 - - // Update slot configs - shouldUpdateOtherSlots && - slotConfigs.forEach((slot) => { - const { - row: currRow, - column: currColumn, - slot: currSlot, - colSpan: currColSpan, - rowSpan: currRowSpan, - } = slot - if (isHorizontal) { - if ( - row >= currRow && - row < currRow + currRowSpan && - currSlot !== selectedSlotId - ) { - slot.rowSpan += 1 - } else if (currRow > row) { - slot.row += 1 - } - } else { - if ( - column >= currColumn && - column < currColumn + currColSpan && - currSlot !== selectedSlotId - ) { - slot.colSpan += 1 - } else if (currColumn > column) { - slot.column += 1 - } - } - }) - if (!isAfter) { - selectedSlot[dimension] += 1 - } - slotConfigs.push(newSlot) - }) - - setSlots(slotConfigs) - } - - static removeHandler( - slotConfigs: Array, - gridToolbarSpecs: GridToolbarSpecs, - type: string, - isInlineDelete?: boolean, - ): Array { - slotConfigs = JSON.parse(JSON.stringify(slotConfigs)) - const isRow = type === "row" - const toolbarSlots = isRow - ? gridToolbarSpecs.rowTools.slots - : gridToolbarSpecs.colTools.slots - // Find and sort selected slots from toolbarSpecs - const selectedToolbarSlots = toolbarSlots - .filter((slot) => - isInlineDelete ? slot.isSelectedForInlineDelete : slot.isSelected, - ) - .sort((a, b) => (isRow ? a.row - b.row : a.column - b.column)) - - // Iterate over each selected slot - selectedToolbarSlots.forEach((toolbarSlot) => { - // Find intersecting slots from slotConfigs - const intersectingSlots = slotConfigs.filter((slot) => - isRow - ? toolbarSlot.row >= slot.row && - toolbarSlot.row < slot.row + slot.rowSpan - : toolbarSlot.column >= slot.column && - toolbarSlot.column < slot.column + slot.colSpan, - ) - - // Delete slots with colSpan 1, decrement colSpan of rest by 1 - intersectingSlots.forEach((intersectingSlot) => { - if ( - isRow - ? intersectingSlot.rowSpan <= toolbarSlot.rowSpan - : intersectingSlot.colSpan <= toolbarSlot.colSpan - ) { - const index = slotConfigs.indexOf(intersectingSlot) - if (index !== -1) slotConfigs.splice(index, 1) - } else { - isRow - ? (intersectingSlot.rowSpan -= 1) - : (intersectingSlot.colSpan -= 1) - } - }) - - // Find next slots and decrement their row/column value by 1 - let succeedingSlots = slotConfigs.filter((slot) => - isRow ? slot.row > toolbarSlot.row : slot.column > toolbarSlot.column, - ) - succeedingSlots.forEach((succeedingSlot) => - isRow ? (succeedingSlot.row -= 1) : (succeedingSlot.column -= 1), - ) - - // Find succeeding toolbar slot and decrement their row/column value by 1 - succeedingSlots = toolbarSlots.filter((currToolbarSlot) => - isRow - ? currToolbarSlot.row > toolbarSlot.row - : currToolbarSlot.column > toolbarSlot.column, - ) - succeedingSlots.forEach((succeedingSlot) => - isRow ? (succeedingSlot.row -= 1) : (succeedingSlot.column -= 1), - ) - toolbarSlots.splice(toolbarSlots.indexOf(toolbarSlot), 1) - }) - - return slotConfigs - } - static checkIfAdjacent(slotConfigs: Array) { - const selectedSlotConfigs = slotConfigs.filter((sc) => sc.isSelected) - let minRow = Infinity - let maxRow = -Infinity - let minColumn = Infinity - let maxColumn = -Infinity - let totalCells = 0 - selectedSlotConfigs.forEach((config: SlotDescriptor) => { - if (config.row !== undefined && config.column !== undefined) { - minRow = Math.min(minRow, config.row) - maxRow = Math.max(maxRow, config.row + (config.rowSpan - 1)) - minColumn = Math.min(minColumn, config.column) - maxColumn = Math.max(maxColumn, config.column + (config.colSpan - 1)) - totalCells += config.rowSpan * config.colSpan - } - }) - const rectangleArea = (maxRow - minRow + 1) * (maxColumn - minColumn + 1) - - return { - areAdjacent: - selectedSlotConfigs.length > 1 && rectangleArea === totalCells, - minRow, - maxRow, - minColumn, - maxColumn, - } - } - - static isSlotSelected( - slot: SlotDescriptor, - rowSlots: Array, - colSlots: Array, - selectForDelete?: boolean, - ) { - const { row, rowSpan, column, colSpan } = slot - // For cells spanning multiple rows or columns we check if all rows or columns (toolbar cells) - // corresponding to this slot are selected, if either or all rows or all columns intersecting this cell - // are selected, we mark this cell selected. - const allRowsSelected = rowSlots - .filter((ts) => ts.row < row + rowSpan && ts.row + ts.rowSpan > row) - .every((ts) => - selectForDelete !== undefined - ? ts.isSelectedForInlineDelete - : ts.isSelected, - ) - const allColumnSelected = colSlots - .filter( - (ts) => ts.column < column + colSpan && ts.column + ts.colSpan > column, - ) - .every((ts) => - selectForDelete !== undefined - ? ts.isSelectedForInlineDelete - : ts.isSelected, - ) - return allRowsSelected || allColumnSelected - } - - static selectCellsInSelectedRowCol( - gridToolbarSpecs: GridToolbarSpecs, - slots: Array, - selectForDelete?: boolean, - ) { - const rowSlots = gridToolbarSpecs.rowTools.slots - const colSlots = gridToolbarSpecs.colTools.slots - slots.forEach((slot) => { - slot[ - selectForDelete !== undefined - ? "isSelectedForInlineDelete" - : "isSelected" - ] = this.isSlotSelected(slot, rowSlots, colSlots, selectForDelete) - }) - return [...slots] - } - - static countOccurrences(array: Array) { - const slots: Array<{ slotId: string; span: number; location: number }> = [] - for (let i = 0; i < array.length; i++) { - const value = array[i] - const existingObject = slots.find((obj) => obj.slotId === value) - if (existingObject === undefined) { - slots.push({ slotId: value, span: 1, location: i }) - } else { - existingObject.span++ - } - } - return slots - } - - static isIntersecting( - toolSlot: SlotDescriptor, - slot: SlotDescriptor, - isRow: boolean, - ) { - const dimension = isRow ? "row" : "column" - const span = isRow ? "rowSpan" : "colSpan" - const sRow = slot[dimension] - const sSpan = slot[span] - const tRow = toolSlot[dimension] - const tSpan = toolSlot[span] - return sRow <= tRow && sRow + sSpan >= tRow + tSpan - } - - static findIntersectingSlots( - slots: Array, - toolSlot: SlotDescriptor, - isRow: boolean, - ) { - return slots.filter((slot) => this.isIntersecting(toolSlot, slot, isRow)) - } - - static getCreateOrExtend( - slot: SlotDescriptor, - selectedToolSlot: SlotDescriptor, - placement: "before" | "after", - isRow: boolean, - ) { - const dimension = isRow ? "row" : "column" - const span = isRow ? "rowSpan" : "colSpan" - return placement === "before" - ? slot[dimension] === selectedToolSlot[dimension] - : slot[dimension] + slot[span] === - selectedToolSlot[dimension] + selectedToolSlot[span] - } - - static insertDimension( - type: "row" | "column", - gridToolbarSpecs: GridToolbarSpecs, - slots: Array, - setSlots: FunctionType, - placement: "before" | "after", - selection?: number, - ) { - slots = JSON.parse(JSON.stringify(slots)) - const isRow = type === "row" - let referenceSlots = gridToolbarSpecs[isRow ? "colTools" : "rowTools"].slots - let newSlots: Array = [] - if (referenceSlots.length > 0) { - const insertAt: Array = this.generateInsertionIndexes( - gridToolbarSpecs, - isRow, - placement, - selection, - ) - insertAt.sort() - const trackedSlotsForSpanIncrease: { - [key: string]: { d: SlotDescriptor; count: number } - } = {} - insertAt.forEach((insertionIndex, index) => { - // Insertion indexes represent which row/column new item will end up, and not what was actually selected, - // Below line gets the selected slot by adjusting index back - const selectedToolSlot = - gridToolbarSpecs[isRow ? "rowTools" : "colTools"].slots[ - insertionIndex - index - (placement === "after" ? 1 : 0) - ] - const intersectedSlots = this.findIntersectingSlots( - slots, - selectedToolSlot, - isRow, - ) - const newSlotsAtCurrentDimension = referenceSlots - .map((referenceSlot) => { - const lateralRefToolIntersectedSlot = intersectedSlots.find((s) => - this.isIntersecting(referenceSlot, s, !isRow), - ) - if (lateralRefToolIntersectedSlot) { - const shouldCreate = this.getCreateOrExtend( - lateralRefToolIntersectedSlot, - selectedToolSlot, - placement, - isRow, - ) - if (shouldCreate) { - const slotId = uuid() - const gridArea = `ga-${slotId}` - return { - slot: slotId, - row: isRow ? insertionIndex : referenceSlot.row, - column: isRow ? referenceSlot.column : insertionIndex, - rowSpan: isRow ? 1 : referenceSlot.rowSpan, - colSpan: isRow ? referenceSlot.colSpan : 1, - gridArea, - } - } else { - if ( - !trackedSlotsForSpanIncrease[ - lateralRefToolIntersectedSlot.slot - ] - ) { - trackedSlotsForSpanIncrease[ - lateralRefToolIntersectedSlot.slot - ] = { d: lateralRefToolIntersectedSlot, count: 1 } - } else { - trackedSlotsForSpanIncrease[ - lateralRefToolIntersectedSlot.slot - ].count += 1 - } - } - } - }) - .filter((s) => !!s) as Array - newSlots = newSlots.concat(newSlotsAtCurrentDimension) - }) - insertAt.forEach((dimNum) => { - const fixDim = isRow ? "row" : "column" - const correctableSlots = slots.filter((s) => s[fixDim] >= dimNum) - correctableSlots.forEach((s) => s[fixDim]++) - }) - Object.values(trackedSlotsForSpanIncrease).forEach( - (obj) => (obj.d[isRow ? "rowSpan" : "colSpan"] += obj.count), - ) - } else { - const slotId = uuid() - const gridArea = `ga-${slotId}` - newSlots.push({ - slot: uuid(), - row: 0, - column: 0, - rowSpan: 1, - colSpan: 1, - gridArea, - }) - } - slots = slots.concat(newSlots) - setSlots(slots) - } - - static generateInsertionIndexes( - gridToolbarSpecs: GridToolbarSpecs, - isRow: boolean, - placement: "before" | "after", - selection?: number, - ) { - let insertAt: Array = [] - if (selection !== undefined) { - insertAt.push(placement === "after" ? selection + 1 : selection) - } else { - if (isRow) { - insertAt = this.generateInsertionIndexesForDimension( - gridToolbarSpecs, - "rowTools", - "row", - placement, - isRow, - ) - } else { - insertAt = this.generateInsertionIndexesForDimension( - gridToolbarSpecs, - "colTools", - "column", - placement, - isRow, - ) - } - } - insertAt.sort() - // If multiple row/col to be added, indexes of all rows (columns) except the first one will change by a value - // equal to their index value in insertAt array (second should be incremented by 1, third by 2 and so on hence we can use indexes) - insertAt = insertAt.map((dimNum, index) => dimNum + index) - return insertAt - } - - static generateInsertionIndexesForDimension( - gridToolbarSpecs: GridToolbarSpecs, - tools: "rowTools" | "colTools", - dimension: "row" | "column", - placement: "before" | "after", - isRow: boolean, - ) { - let insertAt: Array = [] - const reverseDimSlots = - gridToolbarSpecs[isRow ? "rowTools" : "colTools"].slots - const selections = gridToolbarSpecs[tools].slots - .filter((s) => s.isSelected) - .map((s) => s[dimension] + (placement === "before" ? 0 : 1)) - if (selections.length > 0) { - insertAt = insertAt.concat(selections) - } else { - if (placement === "before") { - insertAt.push(0) - } else { - insertAt.push( - Math.max(...reverseDimSlots.map((rS) => rS[dimension])) + 1, - ) - } - } - return insertAt - } - - static generateSlots(rows: Array): Array { - const slotConfigs: Array = [] - const processedGridAreas = new Set() - - rows.forEach((row, rowIndex) => { - const parts = row.split(" ") - - parts.forEach((part, partIndex) => { - const gridArea = part.trim() - - if (!processedGridAreas.has(gridArea)) { - processedGridAreas.add(gridArea) - - // Calculate rowSpan and colSpan based on the repetitions in the grid template - const rowSpan = rows.filter((row) => row.includes(gridArea)).length - const colSpan = parts.filter((part) => part === gridArea).length - - slotConfigs.push({ - slot: gridArea, - gridArea, - row: rowIndex, - column: partIndex, - rowSpan, - colSpan, - }) - } - }) - }) - - return slotConfigs - } - - static generateSlotConfigs(gridTemplate: string): { - slotConfigs: Array - rowHeights: Array - colWidths: Array - } { - // Split by double quotes to separate row and column definitions - const parts = gridTemplate - .split('"') - .map((p) => p.trim()) - .filter((p) => p) - let colWidths - - const rowsAndHeights: { [key: string]: string } = {} - - // Identify row and column parts - parts.forEach((part, index) => { - if (part.startsWith("ga-")) { - rowsAndHeights[part] = "auto" - } else { - if (part.includes("calc(")) { - const calcEndIndex = part.lastIndexOf(")") - const calcExpression = part.substring(0, calcEndIndex + 1) - rowsAndHeights[parts[index - 1]] = calcExpression - - const remainingPart = part.substring(calcEndIndex + 1).trim() - if (remainingPart.includes("/")) { - const colPart = remainingPart.split("/")[1].trim() - colWidths = colPart.split(" ").map((p) => p.trim()) - } - } else { - rowsAndHeights[parts[index - 1]] = part - } - } - }) - - const rowHeights = Object.values(rowsAndHeights) - const slotConfigs = this.generateSlots(Object.keys(rowsAndHeights)) - - return { slotConfigs, rowHeights, colWidths: colWidths || [] } + ) + return { + rowTools: { + slots: rowSlots, + gridTemplate: generateGridTemplate( + rowToolsGridArea, + rowHeight || "minmax(auto, 1fr)", + colWidth || "1fr", + ), + }, + colTools: { + slots: colSlots, + gridTemplate: generateGridTemplate( + [colToolsGridArea], + "1fr", + "1fr", + ), + }, } } -export default GridHelper +export function calculateRowHeights( + slots: Array, + rowHeights?: Array | string, + demo?: boolean, +): Array { + const totalRows = Math.max(...slots.map((slot) => slot.row + slot.rowSpan)) + const allRowHeights: Array = [] + const doc = getDocumentElement(demo) + rowHeights = Array.isArray(rowHeights) + ? rowHeights + : (Array.from( + { length: totalRows }, + () => rowHeights || "minmax(2rem, auto)", + ) as Array) + + for (let rowIndex = 0; rowIndex < totalRows; rowIndex++) { + if ( + rowHeights[rowIndex] && + rowHeights[rowIndex] !== "" && + rowHeights[rowIndex].indexOf("auto") === -1 + ) { + allRowHeights[rowIndex] = rowHeights[rowIndex] + continue + } + + const rowSlots = slots.filter( + (slot) => slot.row <= rowIndex && rowIndex < slot.row + slot.rowSpan, + ) + const heights = rowSlots.map((slot) => { + const element = doc?.getElementById(slot.slot) + return element + ? Math.floor(element.getBoundingClientRect().height / slot.rowSpan) + : 0 + }) + const maxHeight = Math.max(...heights) + allRowHeights[rowIndex] = + maxHeight === 0 ? "minmax(2rem, auto)" : `${maxHeight}px` + } + + return allRowHeights +} + +export function generateGridAreaAndSizes( + slots: Array, + currentRowHeights?: string | Array, +): { gridArea: Array>; rowHeights: Array } { + let gridArea: Array> = [[]] + const rowHeights: Array = [] + slots.forEach((slot) => { + let endRow = slot.row + slot.rowSpan + let endColumn = slot.column + slot.colSpan + for (let row = slot.row; row < endRow; row++) { + const currentRowHeight = Array.isArray(currentRowHeights) + ? currentRowHeights[row] + : currentRowHeights + if (slot.content) { + rowHeights[row] = currentRowHeight || "auto" + } else { + rowHeights[row] = "minmax(2rem, auto)" + } + if (!gridArea[row]) { + gridArea[row] = [] + } + for (let col = slot.column; col < endColumn; col++) { + gridArea[row][col] = slot.gridArea + } + } + }) + + return { gridArea, rowHeights } +} + +export function generateGridTemplate( + gridArea: Array>, + rowHeight?: Array | string, + colWidth?: Array | string, +): string { + let areas = gridArea + .map( + (row, i) => + `"${row.join(" ")}" ${ + Array.isArray(rowHeight) + ? rowHeight[i] || "auto" + : rowHeight || "minmax(3rem, 1fr)" + }`, + ) + .join(" ") + + const gridTemplateColumns = + " / " + + (Array.isArray(colWidth) + ? colWidth.join(" ") + : Array.from( + { length: gridArea[0].length }, + () => colWidth || "1fr", + ).join(" ")) + areas += gridTemplateColumns + + return areas.trim() +} + +export function mergeHandler( + setSlots: FunctionType, + slotConfigs: Array, + minMaxSelections: { + minRow: number + maxRow: number + minColumn: number + maxColumn: number + }, + primarySlotConfig?: SlotDescriptor, +) { + if (slotConfigs) { + slotConfigs = JSON.parse(JSON.stringify(slotConfigs)) + const selectedSlotConfigs = slotConfigs.filter((sc) => sc.isSelected) + selectedSlotConfigs.sort((a, b) => { + if (a.row !== b.row) { + return a.row - b.row + } else { + return a.column - b.column + } + }) + const firstSlotConfig = selectedSlotConfigs[0] + if (primarySlotConfig) { + primarySlotConfig = slotConfigs.find( + (s) => s.slot === primarySlotConfig?.slot, + ) as SlotDescriptor + primarySlotConfig.row = firstSlotConfig.row + primarySlotConfig.column = firstSlotConfig.column + } else { + primarySlotConfig = firstSlotConfig + } + primarySlotConfig.rowSpan = + minMaxSelections.maxRow - minMaxSelections.minRow + 1 + primarySlotConfig.colSpan = + minMaxSelections.maxColumn - minMaxSelections.minColumn + 1 + selectedSlotConfigs.forEach((ssc) => { + const matchedSlotConfigIndex = slotConfigs.findIndex( + (sc) => sc.slot === ssc.slot, + ) + const matchedSlotConfig = slotConfigs[matchedSlotConfigIndex] + if ( + matchedSlotConfig && + matchedSlotConfig.slot !== primarySlotConfig?.slot + ) { + slotConfigs.splice(matchedSlotConfigIndex, 1) + } + }) + // slotConfigs.forEach((slotConfig) => (slotConfig.isSelected = false)) + setSlots(slotConfigs) + } +} + +export function splitHandler( + setSlots: FunctionType, + slotConfigs: Array, + orientation: "horizontal" | "vertical", + placement: "before" | "after", + slot?: SlotDescriptor, +) { + slotConfigs = JSON.parse(JSON.stringify(slotConfigs)) + let selectedSlots = [] + if (slot) { + slot = slotConfigs.find((s) => s.slot === slot?.slot) + slot && selectedSlots.push(slot) + } else { + selectedSlots = slotConfigs.filter((sc) => sc.isSelected) + } + selectedSlots.forEach((selectedSlot) => { + const { + row, + slot: selectedSlotId, + column, + colSpan, + rowSpan, + } = selectedSlot + const isHorizontal = orientation === "horizontal" + const isAfter = placement === "after" + const slotId = uuid() + const gridAreaValue = `ga-${slotId}` + const span = isHorizontal ? "rowSpan" : "colSpan" + const dimension = isHorizontal ? "row" : "column" + let newDimSpan = selectedSlot[span] + if (newDimSpan > 1) { + newDimSpan = selectedSlot[span] - 1 + selectedSlot[span] = newDimSpan + } + const newSlot: SlotDescriptor = { + slot: slotId, + row: isHorizontal ? (isAfter ? newDimSpan + row : row) : row, + column: isHorizontal ? column : isAfter ? newDimSpan + column : column, + rowSpan: isHorizontal ? 1 : rowSpan, + colSpan: isHorizontal ? colSpan : 1, + gridArea: gridAreaValue, + splitFrom: selectedSlotId, + } + + const shouldUpdateOtherSlots = isHorizontal + ? rowSpan === 1 + : colSpan === 1 + + // Update slot configs + shouldUpdateOtherSlots && + slotConfigs.forEach((slot) => { + const { + row: currRow, + column: currColumn, + slot: currSlot, + colSpan: currColSpan, + rowSpan: currRowSpan, + } = slot + if (isHorizontal) { + if ( + row >= currRow && + row < currRow + currRowSpan && + currSlot !== selectedSlotId + ) { + slot.rowSpan += 1 + } else if (currRow > row) { + slot.row += 1 + } + } else { + if ( + column >= currColumn && + column < currColumn + currColSpan && + currSlot !== selectedSlotId + ) { + slot.colSpan += 1 + } else if (currColumn > column) { + slot.column += 1 + } + } + }) + if (!isAfter) { + selectedSlot[dimension] += 1 + } + slotConfigs.push(newSlot) + }) + + setSlots(slotConfigs) +} + +export function removeHandler( + slotConfigs: Array, + gridToolbarSpecs: GridToolbarSpecs, + type: string, + isInlineDelete?: boolean, +): Array { + slotConfigs = JSON.parse(JSON.stringify(slotConfigs)) + const isRow = type === "row" + const toolbarSlots = isRow + ? gridToolbarSpecs.rowTools.slots + : gridToolbarSpecs.colTools.slots + // Find and sort selected slots from toolbarSpecs + const selectedToolbarSlots = toolbarSlots + .filter((slot) => + isInlineDelete ? slot.isSelectedForInlineDelete : slot.isSelected, + ) + .sort((a, b) => (isRow ? a.row - b.row : a.column - b.column)) + + // Iterate over each selected slot + selectedToolbarSlots.forEach((toolbarSlot) => { + // Find intersecting slots from slotConfigs + const intersectingSlots = slotConfigs.filter((slot) => + isRow + ? toolbarSlot.row >= slot.row && + toolbarSlot.row < slot.row + slot.rowSpan + : toolbarSlot.column >= slot.column && + toolbarSlot.column < slot.column + slot.colSpan, + ) + + // Delete slots with colSpan 1, decrement colSpan of rest by 1 + intersectingSlots.forEach((intersectingSlot) => { + if ( + isRow + ? intersectingSlot.rowSpan <= toolbarSlot.rowSpan + : intersectingSlot.colSpan <= toolbarSlot.colSpan + ) { + const index = slotConfigs.indexOf(intersectingSlot) + if (index !== -1) slotConfigs.splice(index, 1) + } else { + isRow + ? (intersectingSlot.rowSpan -= 1) + : (intersectingSlot.colSpan -= 1) + } + }) + + // Find next slots and decrement their row/column value by 1 + let succeedingSlots = slotConfigs.filter((slot) => + isRow ? slot.row > toolbarSlot.row : slot.column > toolbarSlot.column, + ) + succeedingSlots.forEach((succeedingSlot) => + isRow ? (succeedingSlot.row -= 1) : (succeedingSlot.column -= 1), + ) + + // Find succeeding toolbar slot and decrement their row/column value by 1 + succeedingSlots = toolbarSlots.filter((currToolbarSlot) => + isRow + ? currToolbarSlot.row > toolbarSlot.row + : currToolbarSlot.column > toolbarSlot.column, + ) + succeedingSlots.forEach((succeedingSlot) => + isRow ? (succeedingSlot.row -= 1) : (succeedingSlot.column -= 1), + ) + toolbarSlots.splice(toolbarSlots.indexOf(toolbarSlot), 1) + }) + + return slotConfigs +} +export function checkIfAdjacent(slotConfigs: Array) { + const selectedSlotConfigs = slotConfigs.filter((sc) => sc.isSelected) + let minRow = Infinity + let maxRow = -Infinity + let minColumn = Infinity + let maxColumn = -Infinity + let totalCells = 0 + selectedSlotConfigs.forEach((config: SlotDescriptor) => { + if (config.row !== undefined && config.column !== undefined) { + minRow = Math.min(minRow, config.row) + maxRow = Math.max(maxRow, config.row + (config.rowSpan - 1)) + minColumn = Math.min(minColumn, config.column) + maxColumn = Math.max(maxColumn, config.column + (config.colSpan - 1)) + totalCells += config.rowSpan * config.colSpan + } + }) + const rectangleArea = (maxRow - minRow + 1) * (maxColumn - minColumn + 1) + + return { + areAdjacent: + selectedSlotConfigs.length > 1 && rectangleArea === totalCells, + minRow, + maxRow, + minColumn, + maxColumn, + } +} + +export function isSlotSelected( + slot: SlotDescriptor, + rowSlots: Array, + colSlots: Array, + selectForDelete?: boolean, +) { + const { row, rowSpan, column, colSpan } = slot + // For cells spanning multiple rows or columns we check if all rows or columns (toolbar cells) + // corresponding to this slot are selected, if either or all rows or all columns intersecting this cell + // are selected, we mark this cell selected. + const allRowsSelected = rowSlots + .filter((ts) => ts.row < row + rowSpan && ts.row + ts.rowSpan > row) + .every((ts) => + selectForDelete !== undefined + ? ts.isSelectedForInlineDelete + : ts.isSelected, + ) + const allColumnSelected = colSlots + .filter( + (ts) => ts.column < column + colSpan && ts.column + ts.colSpan > column, + ) + .every((ts) => + selectForDelete !== undefined + ? ts.isSelectedForInlineDelete + : ts.isSelected, + ) + return allRowsSelected || allColumnSelected +} + +export function selectCellsInSelectedRowCol( + gridToolbarSpecs: GridToolbarSpecs, + slots: Array, + selectForDelete?: boolean, +) { + const rowSlots = gridToolbarSpecs.rowTools.slots + const colSlots = gridToolbarSpecs.colTools.slots + slots.forEach((slot) => { + slot[ + selectForDelete !== undefined + ? "isSelectedForInlineDelete" + : "isSelected" + ] = isSlotSelected(slot, rowSlots, colSlots, selectForDelete) + }) + return [...slots] +} + +export function countOccurrences(array: Array) { + const slots: Array<{ slotId: string; span: number; location: number }> = [] + for (let i = 0; i < array.length; i++) { + const value = array[i] + const existingObject = slots.find((obj) => obj.slotId === value) + if (existingObject === undefined) { + slots.push({ slotId: value, span: 1, location: i }) + } else { + existingObject.span++ + } + } + return slots +} + +export function isIntersecting( + toolSlot: SlotDescriptor, + slot: SlotDescriptor, + isRow: boolean, +) { + const dimension = isRow ? "row" : "column" + const span = isRow ? "rowSpan" : "colSpan" + const sRow = slot[dimension] + const sSpan = slot[span] + const tRow = toolSlot[dimension] + const tSpan = toolSlot[span] + return sRow <= tRow && sRow + sSpan >= tRow + tSpan +} + +export function findIntersectingSlots( + slots: Array, + toolSlot: SlotDescriptor, + isRow: boolean, +) { + return slots.filter((slot) => isIntersecting(toolSlot, slot, isRow)) +} + +export function getCreateOrExtend( + slot: SlotDescriptor, + selectedToolSlot: SlotDescriptor, + placement: "before" | "after", + isRow: boolean, +) { + const dimension = isRow ? "row" : "column" + const span = isRow ? "rowSpan" : "colSpan" + return placement === "before" + ? slot[dimension] === selectedToolSlot[dimension] + : slot[dimension] + slot[span] === + selectedToolSlot[dimension] + selectedToolSlot[span] +} + +export function insertDimension( + type: "row" | "column", + gridToolbarSpecs: GridToolbarSpecs, + slots: Array, + setSlots: FunctionType, + placement: "before" | "after", + selection?: number, +) { + slots = JSON.parse(JSON.stringify(slots)) + const isRow = type === "row" + let referenceSlots = gridToolbarSpecs[isRow ? "colTools" : "rowTools"].slots + let newSlots: Array = [] + if (referenceSlots.length > 0) { + const insertAt: Array = generateInsertionIndexes( + gridToolbarSpecs, + isRow, + placement, + selection, + ) + insertAt.sort() + const trackedSlotsForSpanIncrease: { + [key: string]: { d: SlotDescriptor; count: number } + } = {} + insertAt.forEach((insertionIndex, index) => { + // Insertion indexes represent which row/column new item will end up, and not what was actually selected, + // Below line gets the selected slot by adjusting index back + const selectedToolSlot = + gridToolbarSpecs[isRow ? "rowTools" : "colTools"].slots[ + insertionIndex - index - (placement === "after" ? 1 : 0) + ] + const intersectedSlots = findIntersectingSlots( + slots, + selectedToolSlot, + isRow, + ) + const newSlotsAtCurrentDimension = referenceSlots + .map((referenceSlot) => { + const lateralRefToolIntersectedSlot = intersectedSlots.find((s) => + isIntersecting(referenceSlot, s, !isRow), + ) + if (lateralRefToolIntersectedSlot) { + const shouldCreate = getCreateOrExtend( + lateralRefToolIntersectedSlot, + selectedToolSlot, + placement, + isRow, + ) + if (shouldCreate) { + const slotId = uuid() + const gridArea = `ga-${slotId}` + return { + slot: slotId, + row: isRow ? insertionIndex : referenceSlot.row, + column: isRow ? referenceSlot.column : insertionIndex, + rowSpan: isRow ? 1 : referenceSlot.rowSpan, + colSpan: isRow ? referenceSlot.colSpan : 1, + gridArea, + } + } else { + if ( + !trackedSlotsForSpanIncrease[ + lateralRefToolIntersectedSlot.slot + ] + ) { + trackedSlotsForSpanIncrease[ + lateralRefToolIntersectedSlot.slot + ] = { d: lateralRefToolIntersectedSlot, count: 1 } + } else { + trackedSlotsForSpanIncrease[ + lateralRefToolIntersectedSlot.slot + ].count += 1 + } + } + } + }) + .filter((s) => !!s) as Array + newSlots = newSlots.concat(newSlotsAtCurrentDimension) + }) + insertAt.forEach((dimNum) => { + const fixDim = isRow ? "row" : "column" + const correctableSlots = slots.filter((s) => s[fixDim] >= dimNum) + correctableSlots.forEach((s) => s[fixDim]++) + }) + Object.values(trackedSlotsForSpanIncrease).forEach( + (obj) => (obj.d[isRow ? "rowSpan" : "colSpan"] += obj.count), + ) + } else { + const slotId = uuid() + const gridArea = `ga-${slotId}` + newSlots.push({ + slot: uuid(), + row: 0, + column: 0, + rowSpan: 1, + colSpan: 1, + gridArea, + }) + } + slots = slots.concat(newSlots) + setSlots(slots) +} + +export function generateInsertionIndexes( + gridToolbarSpecs: GridToolbarSpecs, + isRow: boolean, + placement: "before" | "after", + selection?: number, +) { + let insertAt: Array = [] + if (selection !== undefined) { + insertAt.push(placement === "after" ? selection + 1 : selection) + } else { + if (isRow) { + insertAt = generateInsertionIndexesForDimension( + gridToolbarSpecs, + "rowTools", + "row", + placement, + isRow, + ) + } else { + insertAt = generateInsertionIndexesForDimension( + gridToolbarSpecs, + "colTools", + "column", + placement, + isRow, + ) + } + } + insertAt.sort() + // If multiple row/col to be added, indexes of all rows (columns) except the first one will change by a value + // equal to their index value in insertAt array (second should be incremented by 1, third by 2 and so on hence we can use indexes) + insertAt = insertAt.map((dimNum, index) => dimNum + index) + return insertAt +} + +export function generateInsertionIndexesForDimension( + gridToolbarSpecs: GridToolbarSpecs, + tools: "rowTools" | "colTools", + dimension: "row" | "column", + placement: "before" | "after", + isRow: boolean, +) { + let insertAt: Array = [] + const reverseDimSlots = + gridToolbarSpecs[isRow ? "rowTools" : "colTools"].slots + const selections = gridToolbarSpecs[tools].slots + .filter((s) => s.isSelected) + .map((s) => s[dimension] + (placement === "before" ? 0 : 1)) + if (selections.length > 0) { + insertAt = insertAt.concat(selections) + } else { + if (placement === "before") { + insertAt.push(0) + } else { + insertAt.push( + Math.max(...reverseDimSlots.map((rS) => rS[dimension])) + 1, + ) + } + } + return insertAt +} + +export function generateSlots(rows: Array): Array { + const slotConfigs: Array = [] + const processedGridAreas = new Set() + + rows.forEach((row, rowIndex) => { + const parts = row.split(" ") + + parts.forEach((part, partIndex) => { + const gridArea = part.trim() + + if (!processedGridAreas.has(gridArea)) { + processedGridAreas.add(gridArea) + + // Calculate rowSpan and colSpan based on the repetitions in the grid template + const rowSpan = rows.filter((row) => row.includes(gridArea)).length + const colSpan = parts.filter((part) => part === gridArea).length + + slotConfigs.push({ + slot: gridArea, + gridArea, + row: rowIndex, + column: partIndex, + rowSpan, + colSpan, + }) + } + }) + }) + + return slotConfigs +} + +export function generateSlotConfigs(gridTemplate: string): { + slotConfigs: Array + rowHeights: Array + colWidths: Array +} { + // Split by double quotes to separate row and column definitions + const parts = gridTemplate + .split('"') + .map((p) => p.trim()) + .filter((p) => p) + let colWidths + + const rowsAndHeights: { [key: string]: string } = {} + + // Identify row and column parts + parts.forEach((part, index) => { + if (part.startsWith("ga-")) { + rowsAndHeights[part] = "auto" + } else { + if (part.includes("calc(")) { + const calcEndIndex = part.lastIndexOf(")") + const calcExpression = part.substring(0, calcEndIndex + 1) + rowsAndHeights[parts[index - 1]] = calcExpression + + const remainingPart = part.substring(calcEndIndex + 1).trim() + if (remainingPart.includes("/")) { + const colPart = remainingPart.split("/")[1].trim() + colWidths = colPart.split(" ").map((p) => p.trim()) + } + } else { + rowsAndHeights[parts[index - 1]] = part + } + } + }) + + const rowHeights = Object.values(rowsAndHeights) + const slotConfigs = generateSlots(Object.keys(rowsAndHeights)) + + return { slotConfigs, rowHeights, colWidths: colWidths || [] } +} \ No newline at end of file diff --git a/src/helper.tsx b/src/helper.tsx index 7a5eb23..700afda 100644 --- a/src/helper.tsx +++ b/src/helper.tsx @@ -28,522 +28,502 @@ interface StringifyOnce { (obj: any, replacer?: ReplacerFunction | null, indent?: number): string } -class Helper { - static populatePagesInRoutes(routes: RouteConfig[], fallback: FunctionType) { - routes && - routes.forEach((route) => { - if (typeof route.element === "string") { - const elementName = route.element - const Component: JSX.ElementType = lazy(() => - import(`../pages/${elementName}/${elementName}.tsx`).catch( - fallback, - ), - ) - route.element = - if (route.children) { - Helper.populateComponentsInRoutes(route.children, fallback) - } - } - }) - } +/** + * + * @param pathOrComponentName + * @param fallback + * @returns Lazy component, can be used as + */ +export function lazyImport(pathOrComponentName: string, fallback: FunctionType) { + return lazy(() => + import(pathOrComponentName) + .catch(() => import(`@armco/components/${pathOrComponentName}`)) + .catch(() => import(`@armco/shared-components/${pathOrComponentName}`)) + .catch(fallback), + ) +} - static populateComponentsInRoutes( - routes: RouteConfig[], - fallback: FunctionType, - ) { - routes && - routes.forEach((route) => { - const hierarchy = route.hierarchy - if (typeof route.element === "string") { - const elementName = route.element - const Component: JSX.ElementType = lazy( - () => - import( - `../components/${hierarchy}/${elementName}/${elementName}.tsx` - ).catch(fallback), - // () => import(`../components/atoms/Component_404`) - ) - route.element = - if (route.children) { - Helper.populateComponentsInRoutes(route.children, fallback) - } +export function populatePagesInRoutes(routes: RouteConfig[], fallback: FunctionType) { + routes && + routes.forEach((route) => { + if (typeof route.element === "string") { + const elementName = route.element + const Component: JSX.ElementType = lazy(() => + import(`../pages/${elementName}/${elementName}.tsx`).catch( + fallback, + ), + ) + route.element = + if (route.children) { + populateComponentsInRoutes(route.children, fallback) } - }) - } - - static recrusiveFilter( - data: Array, - filter: string, - matchCase?: boolean, - searchKeys?: false | Array, - ) { - if (!filter || !data || data.length === 0) { - return data - } - if (!searchKeys || searchKeys.length === 0) { - searchKeys = ["label", "name", "id", "value"] - } - const dataClone = JSON.parse(JSON.stringify(data)) - const filteredItems = dataClone.filter((obj: any) => { - return ( - searchKeys && - searchKeys.reduce((acc, key) => { - let isMatch = - obj[key] && - (matchCase - ? obj[key].indexOf(filter) > -1 - : obj[key] && - obj[key] - .toString() - .toLowerCase() - .indexOf(filter.toLowerCase()) > -1) - if (obj.children) { - obj.children = Helper.recrusiveFilter( - obj.children, - filter, - matchCase, - ) - isMatch = isMatch || obj.children.length > 0 - } - return acc || isMatch - }, false) - ) + } }) - return filteredItems - } +} - // static search(args: SearchArgs): ObjectType | undefined { - // const { obj, key, value } = args - // if (Array.isArray(obj)) { - // return obj.find((item) => { - // args.obj = item - // return Helper.search(args) - // }) - // } else { - // if (obj) { - // if (key in obj && (value === undefined || obj[key] === value)) { - // return obj - // } - // return Object.values(obj).find((item) => { - // if (typeof item === "object" && !(item instanceof Date)) { - // args.obj = item as ObjectType - // return Helper.search(args) - // } - // }) as ObjectType - // } - // } - // } - static search({ obj, key, value }: SearchArgs): ObjectType | undefined { - if (Array.isArray(obj)) { - for (const item of obj) { - const result = Helper.search({ obj: item, key, value }) +export function populateComponentsInRoutes( + routes: RouteConfig[], + fallback: FunctionType, +) { + routes && + routes.forEach((route) => { + const hierarchy = route.hierarchy + if (typeof route.element === "string") { + const elementName = route.element + const Component: JSX.ElementType = lazy( + () => + import( + `../components/${hierarchy}/${elementName}/${elementName}.tsx` + ).catch(fallback), + // () => import(`../components/Component_404`) + ) + route.element = + if (route.children) { + populateComponentsInRoutes(route.children, fallback) + } + } + }) +} + +export function recrusiveFilter( + data: Array, + filter: string, + matchCase?: boolean, + searchKeys?: false | Array, +) { + if (!filter || !data || data.length === 0) { + return data + } + if (!searchKeys || searchKeys.length === 0) { + searchKeys = ["label", "name", "id", "value"] + } + const dataClone = JSON.parse(JSON.stringify(data)) + const filteredItems = dataClone.filter((obj: any) => { + return ( + searchKeys && + searchKeys.reduce((acc, key) => { + let isMatch = + obj[key] && + (matchCase + ? obj[key].indexOf(filter) > -1 + : obj[key] && + obj[key] + .toString() + .toLowerCase() + .indexOf(filter.toLowerCase()) > -1) + if (obj.children) { + obj.children = recrusiveFilter( + obj.children, + filter, + matchCase, + ) + isMatch = isMatch || obj.children.length > 0 + } + return acc || isMatch + }, false) + ) + }) + return filteredItems +} + +export function search({ obj, key, value }: SearchArgs): ObjectType | undefined { + if (Array.isArray(obj)) { + for (const item of obj) { + const result = search({ obj: item, key, value }) + if (result) return result + } + } else if (obj && typeof obj === "object" && !(obj instanceof Date)) { + if (key in obj && (value === undefined || obj[key] === value)) { + return obj + } + for (const item of Object.values(obj)) { + if (typeof item === "object" && !(item instanceof Date)) { + const result = search({ obj: item as ObjectType, key, value }) if (result) return result } - } else if (obj && typeof obj === "object" && !(obj instanceof Date)) { - if (key in obj && (value === undefined || obj[key] === value)) { - return obj - } - for (const item of Object.values(obj)) { - if (typeof item === "object" && !(item instanceof Date)) { - const result = Helper.search({ obj: item as ObjectType, key, value }) - if (result) return result - } - } } - return undefined } + return undefined +} - static 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 +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 = Helper.filterTreeStructure(args) - if (Array.isArray(match) && match.length > 0) { - ;(obj as unknown as Array>).splice( - index, - 1, - match, - ) - } - return match + const match = filterTreeStructure(args) + if (Array.isArray(match) && match.length > 0) { + ;(obj as unknown as Array>).splice( + index, + 1, + 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 = Helper.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 match } return false - } - } - - static generateSlices( - count: number, - sliceLength: number, - labelFormat: string, - ) { - let i = 0, - j = 0 - const slices: Array = [] - while (i < count) { - let addUp = sliceLength - if (i + sliceLength > count) { - addUp = count % sliceLength - } - const label = - labelFormat === "range" ? i + 1 + " - " + (i + addUp) : "" + (j + 1) - slices.push({ - label, - name: label.replace(/ /g, "").replace(/-/g, "to"), - sliceIndex: j, - startIndex: i, - endIndex: i + addUp - 1, - }) - i += sliceLength - j++ - } - return slices - } - - static 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 - } + } 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 - static 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 } = Helper.aggregate( - data, - key, - ) - groups[key] = group + 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 groups - } - - static toCamelCase(str: string, separater: string, includeFirst: boolean) { - const strParts = str.split(separater || " ") - return strParts - .map((part, i) => - i === 0 && includeFirst - ? part.charAt(0).toUpperCase() + part.slice(1) - : part, - ) - .join("") - } - - static generateRandomId(length: number = 8) { - return Math.random() - .toString(36) - .substring(2, length + 2) - } - - static copyOrPrompt(text?: string, cb?: FunctionType) { - if (Helper.copyToClipboard(text)) { - cb && cb() - } else { - prompt("Clipboard (Select: ⌘+a > Copy: ⌘+c)", text) - } - } - - static copyToClipboard(text?: string) { - const isNotSafari = typeof (window as any).safari === "undefined" - if (isNotSafari) { - text && navigator.clipboard.writeText(text) - return true - } else { - return false - } - } - - static findComponentDescription( - componentName: string, - components: ComponentList, - ) { - let hierarchy - let selectedItem = components.ATOMS.components.find( - (item: ComponentDescription) => - (typeof item === "string" ? item : item.name) === componentName, - ) - if (!selectedItem) { - selectedItem = components.MOLECULES.components.find( - (item: ComponentDescription) => - (typeof item === "string" ? item : item.name) === componentName, - ) - if (selectedItem) { - hierarchy = "MOLECULES" - } - } else { - hierarchy = "ATOMS" - } - return { - selectedItem: JSON.parse( - JSON.stringify(selectedItem), - ) as ComponentDescription, - hierarchy, - component: componentName, - } - } - - static 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 - } - - static importComponent( - name: string, - hierarchy: string, - fallback: FunctionType, - ) { - return lazy( - () => - import(`../components/${hierarchy}/${name}/${name}.tsx`).catch( - fallback, - ), - // () => import(`../components/atoms/Component_404`) - ) - } - - static getStylesFromClass(className: string): Record { - const styleSheet = Array.from(document.styleSheets).find((sheet) => - Array.from(sheet.cssRules).some( - (rule) => - rule instanceof CSSStyleRule && rule.selectorText === `.${className}`, - ), - ) - - if (styleSheet) { - const styleRule = Array.from(styleSheet.cssRules).find( - (rule) => - rule instanceof CSSStyleRule && rule.selectorText === `.${className}`, - ) as CSSStyleRule - - if (styleRule) { - const styles: Record = {} - for (let i = 0; i < styleRule.style.length; i++) { - const prop = styleRule.style[i] - styles[prop] = styleRule.style.getPropertyValue(prop) - } - return styles - } - } - - return {} - } - - static debounce void>( - func: T, - wait?: number, - immediate: boolean = false, - ): (...args: Parameters) => void { - let timeout: NodeJS.Timeout - - return function (...args: Parameters): void { - const later = () => { - timeout = undefined! - - if (!immediate) { - func(...args) - } - } - - clearTimeout(timeout) - - if (immediate && !timeout) { - func(...args) - } - - timeout = setTimeout(later, wait || 1000) - } - } - - static isMobile(): boolean { - let check = false - ;(function (a: string) { - if ( - /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test( - a, - ) || - /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test( - a.substr(0, 4), - ) - ) - check = true - })( - (navigator.userAgent || - navigator.vendor || - ("opera" in window && window.opera)) as string, - ) - return check - } - - static download(blob: Blob, filename: string) { - const link = document.createElement("a") - link.href = window.URL.createObjectURL(blob) - link.download = filename - document.body.appendChild(link) - link.click() - document.body.removeChild(link) - } - - static pad(num: number, separator?: string) { - return ("" + num).padStart(2, "0") + (separator ? separator : "") - } - - static toReadable(str?: string) { - return str - ?.replace(/([a-z])([A-Z])/g, "$1 $2") - .split(" ") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" ") - } - - static generateFileKey(file: File | { name: string }) { - return "size" in file - ? `${file.name}_${file.size}_${file.type}_${file.lastModified}` - : file.name + "-preexisting-" + Date.now() - } - - static 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 - } - } - - static isImage(type: string) { - return !!type && validImageMimeTypes.indexOf(type) > -1 - } - - static 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) + return false } } -export default Helper +export function generateSlices( + count: number, + sliceLength: number, + labelFormat: string, +) { + let i = 0, + j = 0 + const slices: Array = [] + while (i < count) { + let addUp = sliceLength + if (i + sliceLength > count) { + addUp = count % sliceLength + } + const label = + labelFormat === "range" ? i + 1 + " - " + (i + addUp) : "" + (j + 1) + slices.push({ + label, + name: label.replace(/ /g, "").replace(/-/g, "to"), + sliceIndex: j, + startIndex: i, + endIndex: i + addUp - 1, + }) + i += sliceLength + j++ + } + return slices +} + +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 +} + +export function toCamelCase(str: string, separater: string, includeFirst: boolean) { + const strParts = str.split(separater || " ") + return strParts + .map((part, i) => + i === 0 && includeFirst + ? part.charAt(0).toUpperCase() + part.slice(1) + : part, + ) + .join("") +} + +export function generateRandomId(length: number = 8) { + return Math.random() + .toString(36) + .substring(2, length + 2) +} + +export function copyOrPrompt(text?: string, cb?: FunctionType) { + if (copyToClipboard(text)) { + cb && cb() + } else { + prompt("Clipboard (Select: ⌘+a > Copy: ⌘+c)", text) + } +} + +export function copyToClipboard(text?: string) { + const isNotSafari = typeof (window as any).safari === "undefined" + if (isNotSafari) { + text && navigator.clipboard.writeText(text) + return true + } else { + return false + } +} + +export function findComponentDescription( + componentName: string, + components: ComponentList, +) { + let hierarchy + let selectedItem = components.ATOMS.components.find( + (item: ComponentDescription) => + (typeof item === "string" ? item : item.name) === componentName, + ) + if (!selectedItem) { + selectedItem = components.MOLECULES.components.find( + (item: ComponentDescription) => + (typeof item === "string" ? item : item.name) === componentName, + ) + if (selectedItem) { + hierarchy = "MOLECULES" + } + } else { + hierarchy = "ATOMS" + } + return { + selectedItem: JSON.parse( + JSON.stringify(selectedItem), + ) as ComponentDescription, + hierarchy, + component: componentName, + } +} + +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( + (rule) => + rule instanceof CSSStyleRule && rule.selectorText === `.${className}`, + ), + ) + + if (styleSheet) { + const styleRule = Array.from(styleSheet.cssRules).find( + (rule) => + rule instanceof CSSStyleRule && rule.selectorText === `.${className}`, + ) as CSSStyleRule + + if (styleRule) { + const styles: Record = {} + for (let i = 0; i < styleRule.style.length; i++) { + const prop = styleRule.style[i] + styles[prop] = styleRule.style.getPropertyValue(prop) + } + return styles + } + } + + return {} +} + +export function debounce void>( + func: T, + wait?: number, + immediate: boolean = false, +): (...args: Parameters) => void { + let timeout: NodeJS.Timeout + + return function (...args: Parameters): void { + const later = () => { + timeout = undefined! + + if (!immediate) { + func(...args) + } + } + + clearTimeout(timeout) + + if (immediate && !timeout) { + func(...args) + } + + timeout = setTimeout(later, wait || 1000) + } +} + +export function isMobile(): boolean { + let check = false + ;(function (a: string) { + if ( + /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test( + a, + ) || + /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i.test( + a.substr(0, 4), + ) + ) + check = true + })( + (navigator.userAgent || + navigator.vendor || + ("opera" in window && window.opera)) as string, + ) + return check +} + +export function pad(num: number, separator?: string) { + return ("" + num).padStart(2, "0") + (separator ? separator : "") +} + +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) +} diff --git a/src/index.ts b/src/index.ts index 61e6a74..4b80c79 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,11 @@ -export { dateFormat as DateFormatter } from "./dateformat" -export { default as DomHelper } from "./domHelper" -export { default as Adapter } from "./adapters" -export { default as Helper } from "./helper" -export { default as Network } from "./network" -export { default as Validator } from "./validators" -export { default as GridHelper } from "./gridHelper" -export { default as RecursionHelper } from "./recursionHelper" +export * from "./dateformat" +export * from "./domHelper" +export * from "./adapters" +export * from "./helper" +export * from "./network" +export * from "./validators" +export * from "./gridHelper" +export * from "./recursionHelper" export * from "./dateHelper" export * from "./chartGenerators" export * from "./hooks" diff --git a/src/network.ts b/src/network.ts index dfb3679..92e2bde 100644 --- a/src/network.ts +++ b/src/network.ts @@ -1,5 +1,5 @@ import { FunctionType, ObjectType } from "@armco/types" -import { API_CONFIG } from "@armco/configs" +import { HOST, STATIC_HOST } from "@armco/configs/endpoints" interface Options extends RequestInit { // headers?: { @@ -36,30 +36,29 @@ class Error { } } -export default class Network { - static MAX_ATTEMPTS_LIMIT = 10 + const MAX_ATTEMPTS_LIMIT = 10 - static async get(url: string, queryParams?: object, options?: Options) { - const urlString = Network.stringifyUrl(url, queryParams) + export async function get(url: string, queryParams?: object, options?: Options) { + const urlString = stringifyUrl(url, queryParams) options = { method: "GET", mode: "cors", credentials: "include", ...options, } - return Network.crud(urlString, options) + return crud(urlString, options) } - static async getStatic( + export async function getStatic( url: string, queryParams?: object | null, options?: Options | null, ) { - let urlString = Network.stringifyUrl(url, queryParams) + let urlString = stringifyUrl(url, queryParams) const environment = process.env.NODE_ENV || "development" const host = urlString.startsWith("http") ? "" - : API_CONFIG.STATIC_HOST[environment as keyof object] + : STATIC_HOST[environment as keyof object] urlString = host ? host + urlString : urlString options = { method: "GET", @@ -67,10 +66,10 @@ export default class Network { credentials: "include", ...options, } - return Network.crud(urlString, options) + return crud(urlString, options) } - static async post( + export async function post( url: string, data: object, queryParams?: object, @@ -78,7 +77,7 @@ export default class Network { noStringify?: boolean, noHeaders?: boolean, ) { - const urlString = Network.stringifyUrl(url, queryParams) + const urlString = stringifyUrl(url, queryParams) options = { method: "POST", // @ts-ignore @@ -90,20 +89,20 @@ export default class Network { options && (options.headers = { "Content-Type": "application/json" }) // @ts-ignore - return Network.crud(urlString, options) + return crud(urlString, options) } - static async upload( + export async function upload( url: string, file: File, queryParams?: object, options?: Options, ) { - let urlString = Network.stringifyUrl(url, queryParams) + let urlString = stringifyUrl(url, queryParams) const environment = process.env.NODE_ENV || "development" const host = urlString.startsWith("http") ? "" - : API_CONFIG.STATIC_HOST[environment as keyof object] + : STATIC_HOST[environment as keyof object] urlString = host ? host + urlString : urlString const formData = new FormData() formData.append("file", file) @@ -114,11 +113,11 @@ export default class Network { credentials: "include", ...options, } - return Network.crud(urlString, options) + return crud(urlString, options) } - static async put(url: string, data: object, queryParams?: object) { - const urlString = Network.stringifyUrl(url, queryParams) + export async function put(url: string, data: object, queryParams?: object) { + const urlString = stringifyUrl(url, queryParams) const options: Options = { method: "PUT", body: JSON.stringify(data), @@ -128,23 +127,23 @@ export default class Network { "Content-Type": "application/json", }, } - return Network.crud(urlString, options) + return crud(urlString, options) } - static async delete(url: string, queryParams: object) { - const urlString = Network.stringifyUrl(url, queryParams) + export async function deleteRec(url: string, queryParams: object) { + const urlString = stringifyUrl(url, queryParams) const options = { method: "DELETE", noInject: true, } - return Network.crud(urlString, options) + return crud(urlString, options) } - static async crud(urlString: string, options: Options) { + export async function crud(urlString: string, options: Options) { const environment = process.env.NODE_ENV || "development" const host = urlString.startsWith("http") ? "" - : API_CONFIG.HOST[environment as keyof object] + : HOST[environment as keyof object] const extras = options?.extras urlString = host ? host + urlString : urlString if (!options.headers) { @@ -183,7 +182,7 @@ export default class Network { throw new Error(err.error || err.message, status, extras) } - static stringifyUrl(url: string, queryParams?: any) { + export function stringifyUrl(url: string, queryParams?: any) { if (!queryParams) { return url || "" } @@ -203,7 +202,7 @@ export default class Network { : "" } - static async retry( + export async function retry( fn: FunctionType, retryOptions: RetryOptions | undefined = {}, ) { @@ -214,7 +213,7 @@ export default class Network { onSuccess, onFailure, } = retryOptions - maxAttempts = Math.min(maxAttempts, Network.MAX_ATTEMPTS_LIMIT) + maxAttempts = Math.min(maxAttempts, MAX_ATTEMPTS_LIMIT) let attempts = 0 const attempt = async () => { @@ -244,4 +243,3 @@ export default class Network { return attempt() } -} diff --git a/src/providers.tsx b/src/providers.tsx index 3714569..3b0758c 100644 --- a/src/providers.tsx +++ b/src/providers.tsx @@ -8,7 +8,7 @@ import { User, } from "@armco/types" import { ArContext } from "./contexts" -import Helper from "./helper" +import { isMobile } from "./helper" export const ArProvider = ({ children }: { children: ReactNode }) => { const [theme, setTheme] = useState(ArThemes.DARK1) @@ -19,7 +19,7 @@ export const ArProvider = ({ children }: { children: ReactNode }) => { ) const [notification, notify] = useState(undefined) const [drawerState, setDrawerState] = useState({ - collapsed: Helper.isMobile(), + collapsed: isMobile(), }) const [leftPanelContent, setLeftPanelContent] = useState< PanelContent | undefined diff --git a/src/recursionHelper.tsx b/src/recursionHelper.tsx index dd185e7..f06e81c 100644 --- a/src/recursionHelper.tsx +++ b/src/recursionHelper.tsx @@ -1,66 +1,62 @@ import { v4 as uuid } from "uuid" import { RecursionArgs, ObjectType, RecusionConditionTypes } from "@armco/types" -class RecursionHelper { - static recur(args: RecursionArgs) { - const { condition, data, isBrute, preop, postop, iterateOn } = args - if (isBrute) { - if (Array.isArray(data)) { - data.forEach((item) => { - RecursionHelper.recur({ ...args, data: item }) - }) - } else if (typeof data === "object") { - const preopResult = preop && preop(data) - Object.values(data).forEach((item: any) => { - RecursionHelper.recur({ ...args, data: item }) - }) - return (postop && postop(data)) || preopResult - } - } else { - if (Array.isArray(data)) { - data.forEach((item) => { - RecursionHelper.recur({ ...args, data: item }) - }) - } else if ( - typeof data === "object" && - (!condition || RecursionHelper.evaluateCondition(data, condition)) - ) { - const preopResult = preop && preop(data) - if (iterateOn) { - if (data[iterateOn]) { - RecursionHelper.recur({ ...args, data: data[iterateOn] }) - } - } else { - Object.values(data).forEach((item) => { - RecursionHelper.recur({ ...args, data: item }) - }) +export function recur(args: RecursionArgs) { + const { condition, data, isBrute, preop, postop, iterateOn } = args + if (isBrute) { + if (Array.isArray(data)) { + data.forEach((item) => { + recur({ ...args, data: item }) + }) + } else if (typeof data === "object") { + const preopResult = preop && preop(data) + Object.values(data).forEach((item: any) => { + recur({ ...args, data: item }) + }) + return (postop && postop(data)) || preopResult + } + } else { + if (Array.isArray(data)) { + data.forEach((item) => { + recur({ ...args, data: item }) + }) + } else if ( + typeof data === "object" && + (!condition || evaluateCondition(data, condition)) + ) { + const preopResult = preop && preop(data) + if (iterateOn) { + if (data[iterateOn]) { + recur({ ...args, data: data[iterateOn] }) } - return (postop && postop(data)) || preopResult + } else { + Object.values(data).forEach((item) => { + recur({ ...args, data: item }) + }) } + return (postop && postop(data)) || preopResult } - return null - } - - static injectIds(args: RecursionArgs) { - args.postop = (item) => !item.id && (item.id = uuid()) - RecursionHelper.recur(args) - } - - static evaluateCondition(data: ObjectType, condition: ObjectType): boolean { - if (!data || !condition) { - return false - } - if (condition.type === RecusionConditionTypes.KEY_EXISTS) { - return !!(condition.key && (condition.key as string) in data) - } else if (condition.type === RecusionConditionTypes.KEY_VALUE) { - return !!( - condition.key && - condition.value && - data[condition.key as string] === condition.value - ) - } - return false } + return null } -export default RecursionHelper +export function injectIds(args: RecursionArgs) { + args.postop = (item) => !item.id && (item.id = uuid()) + recur(args) +} + +export function evaluateCondition(data: ObjectType, condition: ObjectType): boolean { + if (!data || !condition) { + return false + } + if (condition.type === RecusionConditionTypes.KEY_EXISTS) { + return !!(condition.key && (condition.key as string) in data) + } else if (condition.type === RecusionConditionTypes.KEY_VALUE) { + return !!( + condition.key && + condition.value && + data[condition.key as string] === condition.value + ) + } + return false +} diff --git a/src/validators.ts b/src/validators.ts index 560e574..7da11ca 100644 --- a/src/validators.ts +++ b/src/validators.ts @@ -1,17 +1,13 @@ import { ObjectType } from "@armco/types" -class Validator { - static validateTask(taskObj: ObjectType) { - return ( - taskObj && - taskObj.name && - taskObj.client && - taskObj.target && - (taskObj.target as ObjectType).endpoint && - taskObj.schedule && - (taskObj.schedule as ObjectType).cron - ) - } +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 + ) } - -export default Validator diff --git a/vite.config.ts b/vite.config.ts index 93bd73b..9622bba 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,10 +3,11 @@ import {glob} from "glob" import { defineConfig } from "vitest/config" import react from "@vitejs/plugin-react" import dts from "vite-plugin-dts" +import { externalizeDeps } from "vite-plugin-externalize-deps" // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react(), dts({ outDir: "build/types" })], + plugins: [react(), dts({ outDir: "build/types" }), externalizeDeps()], build: { outDir: "build", lib: { @@ -14,7 +15,6 @@ export default defineConfig({ }, rollupOptions: { treeshake: true, - external: ["react", "react/jsx-runtime", "react-dom"], output: [ { format: "es",