Removed boilerplate, partial consolidation of components here:

This commit is contained in:
2024-09-18 17:55:17 +05:30
parent 65a889e87c
commit 85c7f844a6
969 changed files with 10 additions and 40344 deletions

View File

@@ -1,23 +0,0 @@
Features to port to new Armory:
- PWA
- Dashboard
- Compare Tool
- Server with sockets (another repository)
- Live preview of page being created
- Project, Page, Component
- React table
- Drag n drop with preview of component being dragged
- Rich Tooltips
- Logger view showing user activity
- Live code being generated for dropped component
- Component properties viewer
- Undo/redo functionality
- Login/signup page
- Landing page with login/signup prompt
New:
- Chat/conversations
- Notifications
- Modal

View File

@@ -1,4 +0,0 @@
License only feaures:
- Document versions
- Infinite number of API calls

View File

@@ -1,100 +0,0 @@
import React, { useRef, useEffect, useState } from "react"
import * as d3 from "d3"
import { GDP_DATA as data } from "./__mocks__/gdpData"
interface GDPData {
[year: string]: Array<[string, number]>
}
interface BarChartProps {
data: GDPData
currentYear: string
width: number
height: number
}
const width = 1400
const height = 400
const BarChart: React.FC<BarChartProps> = () => {
const ref = useRef(null)
const [currentView, setCurrentView] = useState<number>(0)
useEffect(() => {
if (!data[Object.keys(data)[currentView]]) return
const svg = d3.select(ref.current)
const margin = { top: 20, right: 20, bottom: 30, left: 40 }
const x = d3
.scaleBand()
.rangeRound([0, width - margin.left - margin.right])
.padding(0.1)
const y = d3
.scaleLinear()
.rangeRound([height - margin.top - margin.bottom, 0])
const currentData = data[Object.keys(data)[currentView]].map(
([country, gdp]) => ({
country,
gdp,
}),
)
x.domain(currentData?.map((d) => d.country) as Array<string>)
y.domain([0, d3.max(currentData, (d) => d.gdp) || 0])
const g = svg
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`)
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", `translate(0,${height - margin.top - margin.bottom})`)
.call(d3.axisBottom(x))
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(10, "%"))
g.selectAll(".bar")
.data(currentData)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", (d) => x(d.country as string) || 0)
.attr("y", (d) => y(d.gdp))
.attr("width", x.bandwidth())
.attr(
"height",
(d) => height - margin.top - margin.bottom - (y(d.gdp) || 0),
)
return () => {
svg.selectAll("*").remove()
}
}, [data, currentView, width, height])
useEffect(() => {
let interval: NodeJS.Timeout
const dataLength = Object.keys(data).length
interval = setInterval(() => {
setCurrentView((prevView) => {
const nextView = (prevView + 1) % dataLength
if (nextView < dataLength) {
return nextView
} else {
clearInterval(interval)
return prevView
}
})
}, 1000)
return () => clearInterval(interval)
}, [data])
return (
<div className="w-100 h-100">
<svg ref={ref} width={width} height={height} />
</div>
)
}
export default BarChart

View File

@@ -10,11 +10,9 @@
### Published at npm and publicly available
### PLACEHOLDER PACKAGE, IN DEV
### !! [PLACEHOLDER PACKAGE][BETA] !!
### Version with implementations: 1.0.0
###### IN BETA
[npm-url]: https://www.npmjs.com/package/@armco/armory-react-components
[github-license-url]: https://github.com/ReStruct-Corporate-Advantage/armory-react-components/blob/main/LICENSE
[npm-url]: https://www.npmjs.com/package/@armco/components
[github-license-url]: https://github.com/ReStruct-Corporate-Advantage/components/blob/main/LICENSE

View File

@@ -1,40 +0,0 @@
Renderer workflow
- Drag n drop components/Click add on slot and add component
- Generate config for the component
- props
- slot
- Process the config -> Parent -> props and handlers -> children
Guidelines
- Try to keep the config as flat as possible with hierarchy information
- Try minimizing entier tree render, only render what's changed
- Entire tree renders when main configuration changes
- This is majorly with form components, buttons that either need to save and update state or perform actions like API calls, or modify layout like show modals hide show drawer etc.
- To manage this rely on redux selectors as much as possible so that state change is delegated to lowest possible levels
- For eg. When a button to expand drawer is clicked and the drawer contains a form, entire form should not re-render
- Config should be decentralized with multiple middle level controllers to isolate state changes between these middle level items. Examples include:
- Any form
- Drawer
- Header
- Footer
- Main
- Card
- Widget
- At form level too, cross element communication should prevent changes at parent form level, communicate via redux
- Redux design becomes important here wherein it should be as flat as possible and might end up containing large number of fields - one for each field with qualifiers
- Create dynamic reducers and selectors for low level form fields
Features needed in renderer
- Visualize - Just display content of components (good enough for a static website)
- Perform actions:
- Show Modal
- Fetch Data
- Subscribe to data
- Publish (dispatch) actions9

View File

@@ -1,3 +0,0 @@
Features/Landing Page CTA
Share a live URL with your team and users

View File

@@ -1,8 +0,0 @@
Website builder
- Form - Name, category, optional description, target audience
- Select page layout
- Header, drawer, main, footer
- Multi row
- Custom - specify rows and columns hierarchy
- Grid layout - specify height and width of grids (to be studied)

View File

@@ -1,14 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Armory React Repository</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" style="overflow:hidden;"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

View File

@@ -1,5 +1,5 @@
{
"name": "@armco/armory-react-components",
"name": "@armco/components",
"description": "React Component Library for Armco's stack of products and services",
"version": "0.0.47",
"type": "module",
@@ -11,23 +11,17 @@
"build:publish": "./scripts/build.sh",
"build:publish:compile": "tsc --p ./tsconfig-build.json && vite build --config vite-publish.config.ts",
"generate": "plop",
"atom": "plop atom",
"molecule": "plop molecule",
"component": "plop component",
"page": "plop page",
"preview": "vite preview",
"test": "NODE_ENV=development vitest",
"format": "prettier --write .",
"lint": "eslint .",
"type-check": "tsc",
"publish:dry": "npm publish --dry-run",
"publish:local": "./scripts/publish-local.sh",
"publish:public": "./scripts/publish.sh",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
"publish:public": "./scripts/publish.sh"
},
"dependencies": {
"@armco/analytics": "^0.2.8",
"@armco/utils": "^0.0.0",
"@lottiefiles/react-lottie-player": "^3.5.3",
"@popperjs/core": "^2.11.8",
"@reduxjs/toolkit": "^1.8.1",

View File

@@ -1,33 +0,0 @@
import { Component, ErrorInfo, ReactNode } from "react"
interface Props {
children?: ReactNode
}
interface State {
hasError: boolean
}
class ErrorBoundary extends Component<Props, State> {
public state: State = {
hasError: false,
}
public static getDerivedStateFromError(_: Error): State {
return { hasError: true }
}
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error("Uncaught error:", error, errorInfo)
}
public render() {
if (this.state.hasError) {
return <h1>Sorry.. there was an error</h1>
}
return this.props.children
}
}
export default ErrorBoundary

View File

@@ -1,81 +0,0 @@
import { Suspense, useEffect } from "react"
import { useRoutes } from "react-router-dom"
import { v4 as uuid } from "uuid"
import { AlertProps, ArAlertType, ArThemes } from "@armco/types"
import Header from "./components/Header"
import { useAppDispatch, useAppSelector } from "./hooks"
import {
notification,
notify,
setLoggedIn,
setRightPanelContent,
setUser,
} from "./store"
import { Alert } from "./components"
import Helper from "./utils/helper"
import { Network } from "./utils"
import ROUTES from "./routes"
import { ENDPOINTS } from "./config/constants"
import API_CONFIG from "./config/api-config"
Helper.populatePagesInRoutes(ROUTES)
interface RouterProps {}
const Router = (props: RouterProps) => {
const alertInfo = useAppSelector<AlertProps | undefined>(notification)
const dispatch = useAppDispatch()
useEffect(() => {
document
.getElementsByTagName("html")[0]
.setAttribute("ar-theme", ArThemes.DARK1)
Network.get(
API_CONFIG.IAM[process.env.NODE_ENV] +
ENDPOINTS.USERS.ROOT +
ENDPOINTS.USERS.CHECK,
)
.then((response) => {
if (response && response.status === 200) {
dispatch(setLoggedIn(true))
if (response.body) {
dispatch(setUser(response.body))
}
} else {
dispatch(setLoggedIn(false))
}
})
.catch((error) => {
if (error.status === 403) {
dispatch(setLoggedIn(false))
}
})
window.onmessage = (e) => {
if (e.data && e.data.event === "LOGGED_IN") {
dispatch(setLoggedIn(true))
dispatch(
notify({
show: true,
message: "Logged in successfully!",
type: ArAlertType.SUCCESS,
uid: uuid(),
}),
)
dispatch(setRightPanelContent({ name: "" }))
dispatch(setUser(e.data.data.body.user))
}
}
}, [])
const matchedRouteElement = useRoutes(ROUTES)
return (
<Suspense fallback={<div>Loading...</div>}>
<Header routeDetails={matchedRouteElement?.props.match} />
{matchedRouteElement}
{alertInfo && <Alert {...alertInfo} />}
</Suspense>
)
}
export default Router

View File

@@ -1,3 +0,0 @@
.ar-AssetSelector {
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import AssetSelector from "./AssetSelector"
describe("AssetSelector", () => {
it("renders without error", () => {
})
})

View File

@@ -1,18 +0,0 @@
import { AssetSelectorProps, ArIconTileTypes } from "@armco/types"
import { ArIconsViewer } from ".."
import "./AssetSelector.component.scss"
const AssetSelector = (props: AssetSelectorProps): JSX.Element => {
const { classes } = props
return (
<div className={`ar-AssetSelector${classes ? " " + classes : ""}`}>
<ArIconsViewer
view={{ name: ArIconTileTypes.COMPACT }}
hasSearch
isDraggable
/>
</div>
)
}
export default AssetSelector

View File

@@ -1,3 +0,0 @@
import AssetSelector from "./AssetSelector"
export default AssetSelector

View File

@@ -1,3 +0,0 @@
.ar-ComponentList {
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import ComponentList from "./ComponentList"
describe("ComponentList", () => {
it("renders without error", () => {
})
})

View File

@@ -1,60 +0,0 @@
import { useNavigate } from "react-router-dom"
import { ArThemes, ComponentRepository, TreeListData } from "@armco/types"
import {
Adapter,
COMPONENTS,
ErrorBoundary,
getCurrentTheme,
Network,
TreeList,
useAppSelector,
} from ".."
import * as atoms from "../atoms"
import * as molecules from "../molecules"
import "./ComponentList.component.scss"
const Components: ComponentRepository = { ...atoms, ...molecules }
interface ComponentListProps {}
const formattedTreeData = Adapter.adaptToTreeFromComponentConfig(COMPONENTS)
const ComponentList = (props: ComponentListProps): JSX.Element => {
const navigate = useNavigate()
const theme = useAppSelector<ArThemes>(getCurrentTheme)
const handleComponentSelect = (treeNode: TreeListData) => {
const params = treeNode.data?.props
treeNode.data?.component &&
navigate(
Network.stringifyUrl(`/components/${treeNode.data?.component}`, params),
)
}
return (
<div className="ar-ComponentList h-100 w-100 px-2">
<TreeList
onItemSelect={handleComponentSelect}
data={formattedTreeData}
title="Component List"
firstExpanded={true}
theme={theme}
customTooltip={(item: TreeListData) => {
const Component =
typeof item.label === "string" && Components[item.label]
return Component ? (
<ErrorBoundary>
<Component demo />
</ErrorBoundary>
) : (
item.label
)
}}
isDraggable
showTooltip
/>
</div>
)
}
export default ComponentList

View File

@@ -1,3 +0,0 @@
import ComponentList from "./ComponentList"
export default ComponentList

View File

@@ -1,3 +0,0 @@
.ar-ConfigRowItem {
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import ConfigRowItem from "./ConfigRowItem"
describe("ConfigRowItem", () => {
it("renders without error", () => {
})
})

View File

@@ -1,97 +0,0 @@
import { ChangeEvent, useState } from "react"
import { ArButtonVariants, ConfigRowItemProps } from "@armco/types"
import { Button, LoadableIcon, TextInput } from ".."
import "./ConfigRowItem.component.scss"
const ConfigRowItem = (props: ConfigRowItemProps): JSX.Element => {
const { config, disabled, isNew, onAdd, onUpdate, onDelete } = props
const [key, setKey] = useState<string>(config?.key || "")
const [value, setValue] = useState<string>(config?.value || "")
const [edited, setEdited] = useState<boolean>()
const configIsSubmittable = (isNew || edited) && key && value
return (
<div className="ar-ConfigRowItem row">
<div className="col-3">
<TextInput
value={key}
isDisabled={disabled && !edited}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setKey(e.target.value)
}
placeholder="Config key"
/>
</div>
<div className="col-4">
<TextInput
placeholder="Enter a value, text/json etc."
isDisabled={disabled && !edited}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
setValue(e.target.value)
}
value={value}
/>
</div>
{isNew ? (
<div className="col-1 flex-v-center">
<Button
content="Add"
variant={ArButtonVariants.SUCCESS}
preIcon="io/IoMdAdd"
onClick={() => {
configIsSubmittable && onAdd && onAdd(key, value)
setKey("")
setValue("")
}}
/>
</div>
) : (
<div className="col-2 flex-v-center">
<span
className={`me-3${
configIsSubmittable ? " cursor-pointer" : " pe-none"
}`}
>
<LoadableIcon
icon="fa/FaCheck"
color={configIsSubmittable ? "green" : "rgba(0, 128, 0, 0.3)"}
onClick={() => {
config?._id &&
configIsSubmittable &&
onUpdate &&
onUpdate(config?._id, key, value)
setKey("")
setValue("")
}}
hoverShadow
/>
</span>
<span className={`me-3${edited ? " pe-none" : " cursor-pointer"}`}>
<LoadableIcon
icon={edited ? "rx/RxCross2" : "md/MdModeEditOutline"}
color={isNew || edited ? "rgba(165, 42, 42, 0.3)" : "brown"}
onClick={() => (edited ? setEdited(false) : setEdited(true))}
/>
</span>
<span className={`me-3 ${edited ? "pe-none" : "cursor-pointer"}`}>
<LoadableIcon
icon="md/MdAdd"
width="1.3rem"
color={!configIsSubmittable ? "green" : "rgba(0, 128, 0, 0.3)"}
onClick={() => onAdd && onAdd(key, value)}
/>
</span>
<span className={edited ? "pe-none" : "cursor-pointer"}>
<LoadableIcon
icon="ri/RiDeleteBin6Line"
color={isNew || edited ? "rgba(128, 0, 0, 0.3)" : "red"}
onClick={() => onDelete && onDelete(config?._id)}
/>
</span>
</div>
)}
</div>
)
}
export default ConfigRowItem

View File

@@ -1,3 +0,0 @@
import ConfigRowItem from "./ConfigRowItem"
export default ConfigRowItem

View File

@@ -1,3 +0,0 @@
.ar-ConfigurationList {
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import ConfigurationList from "./ConfigurationList"
describe("ConfigurationList", () => {
it("renders without error", () => {
})
})

View File

@@ -1,28 +0,0 @@
import { useNavigate } from "react-router-dom"
import { ConfigurationListProps, TreeListData } from "@armco/types"
import { Network, TreeList } from ".."
import "./ConfigurationList.component.scss"
const ConfigurationList = (props: ConfigurationListProps): JSX.Element => {
const { list } = props
const navigate = useNavigate()
const handleComponentSelect = (treeNode: TreeListData) => {
const params = treeNode.data?.props
treeNode.data?.component &&
navigate(
Network.stringifyUrl(`/components/${treeNode.data?.component}`, params),
)
}
return (
<div className="ar-ConfigurationList w-100 p-2">
<TreeList
onItemSelect={handleComponentSelect}
data={list || []}
title="Configurations"
firstExpanded={true}
/>
</div>
)
}
export default ConfigurationList

View File

@@ -1,3 +0,0 @@
import ConfigurationList from "./ConfigurationList"
export default ConfigurationList

View File

@@ -1,10 +0,0 @@
.ar-ConfigurationLoginPrompt {
width: 50vw;
background-color: var(--ar-bg);
border-radius: 0.25rem;
small {
color: var(--ar-color-secondary);
line-height: 1rem;
}
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import ConfigurationLoginPrompt from "./ConfigurationLoginPrompt"
describe("ConfigurationLoginPrompt", () => {
it("renders without error", () => {
})
})

View File

@@ -1,48 +0,0 @@
import { ArButtonVariants, ConfigurationLoginPromptProps } from "@armco/types"
import { Button, LoadableIcon, setRightPanelContent, useAppDispatch } from ".."
import "./ConfigurationLoginPrompt.component.scss"
const ConfigurationLoginPrompt = (
props: ConfigurationLoginPromptProps,
): JSX.Element => {
const dispatch = useAppDispatch()
return (
<div className="ar-ConfigurationLoginPrompt p-3 border">
<div className="flex-h-center flex-column">
<h6 className="flex-v-center" style={{ color: "#ffbf00" }}>
<LoadableIcon
classes="me-2"
icon="fa/FaExclamationTriangle"
color="#ffbf00"
/>
Please login to continue
</h6>
<small>
This is the configurations feature where you can create and save
configurations as key-value pairs.
</small>
<small>
These key-value pairs can then be accessed using an endpoint inside
your application.
</small>
<small className="mb-3">
<strong>
In order to be able to save and secure these configurations, you
need to create an account, if one doesn't exist already and login
using the same. Please use the button below to continue.
</strong>
</small>
</div>
<Button
content="Login"
variant={ArButtonVariants.SUCCESS}
postIcon="io5/IoArrowForwardCircle"
onClick={() => {
dispatch(setRightPanelContent({ name: "LoginProvider" }))
}}
/>
</div>
)
}
export default ConfigurationLoginPrompt

View File

@@ -1,3 +0,0 @@
import ConfigurationLoginPrompt from "./ConfigurationLoginPrompt"
export default ConfigurationLoginPrompt

View File

@@ -1,3 +0,0 @@
.ar-ConfigurationNoConfigPrompt {
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import ConfigurationNoConfigPrompt from "./ConfigurationNoConfigPrompt"
describe("ConfigurationNoConfigPrompt", () => {
it("renders without error", () => {
})
})

View File

@@ -1,102 +0,0 @@
import { ReactNode } from "react"
import {
ArButtonVariants,
ArPopoverSlots,
ArPopoverTriggers,
ConfigurationNoConfigPromptProps,
} from "@armco/types"
import {
Button,
LoadableIcon,
NamespaceOrgForm,
Popover,
setModalState,
useAppDispatch,
} from ".."
import "./ConfigurationNoConfigPrompt.component.scss"
const ConfigurationNoConfigPrompt = (
props: ConfigurationNoConfigPromptProps,
): ReactNode => {
const dispatch = useAppDispatch()
return (
<div className="ar-ConfigurationNoConfigPrompt flex-center flex-column border px-3 py-5 w-50">
<LoadableIcon
classes="mb-4"
icon="gr/GrConfigure"
size="7rem"
color="lightgrey"
/>
<p className="ar-ConfigurationNoConfigPrompt__message mx-5">
You don't have any configurations created yet, you can start by
selecting one of the options below
</p>
<div className="ar-ConfigurationNoConfigPrompt__buttons d-flex">
<Popover trigger={ArPopoverTriggers.HOVER}>
<Button
classes="me-3"
content="+ Space"
variant={ArButtonVariants.SUCCESS}
slot={ArPopoverSlots.ANCHOR}
onClick={() =>
dispatch(
setModalState({
show: true,
isSticky: true,
content: <NamespaceOrgForm context="namespace" />,
closeHandler: () => dispatch(setModalState({ show: false })),
}),
)
}
/>
<div
slot={ArPopoverSlots.POPOVER}
className="text-wrap"
style={{ maxWidth: "10rem" }}
>
<p>This is where you keep your configurations.</p>
<p className="mb-3">
Use namespaces in your project/s to avoid fetching all the
configurations for multiple projects.
</p>
<b>Namespace is required to create a configuration</b>
</div>
</Popover>
<Popover trigger={ArPopoverTriggers.HOVER}>
<Button
variant={ArButtonVariants.SUCCESS}
content="+ Organization"
slot={ArPopoverSlots.ANCHOR}
onClick={() =>
dispatch(
setModalState({
show: true,
isSticky: true,
content: <NamespaceOrgForm context="org" />,
closeHandler: () => dispatch(setModalState({ show: false })),
}),
)
}
/>
<div
slot={ArPopoverSlots.POPOVER}
className="text-wrap"
style={{ maxWidth: "10rem" }}
>
<p>
You may skip creating an organization as one will be created for
you if you don't.
</p>
<p>
Or you may choose to create one. This will allow you to control
severl aspects of an organization at creation time itself
</p>
</div>
</Popover>
</div>
</div>
)
}
export default ConfigurationNoConfigPrompt

View File

@@ -1,3 +0,0 @@
import ConfigurationNoConfigPrompt from "./ConfigurationNoConfigPrompt"
export default ConfigurationNoConfigPrompt

View File

@@ -1,13 +0,0 @@
.ar-ConfigurationViewer {
background-color: var(--ar-bg);
.ar-ConfigurationViewer__header {
background-color: var(--ar-bg-base);
border-bottom: 1px solid var(--ar-color-layout-border);
}
input:disabled {
border: none;
background: transparent;
font-weight: bold;
}
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import ConfigurationViewer from "./ConfigurationViewer"
describe("ConfigurationViewer", () => {
it("renders without error", () => {
})
})

View File

@@ -1,210 +0,0 @@
import { v4 as uuid } from "uuid"
import {
ArAlertType,
ArButtonVariants,
ArPopoverPositions,
ArPopoverSlots,
ArSizes,
ConfigurationViewerProps,
ObjectType,
} from "@armco/types"
import {
API_CONFIG,
Breadcrumb,
Button,
ConfigRowItem,
ENDPOINTS,
InlineMenu,
LoadableIcon,
NamespaceInfoBox,
NamespaceOrgForm,
Network,
notify,
Popover,
setModalState,
useAppDispatch,
} from ".."
import "./ConfigurationViewer.component.scss"
const ConfigurationViewer = (props: ConfigurationViewerProps): JSX.Element => {
const { namespace, fetchNamespaces } = props
const configurations = namespace?.configs
const dispatch = useAppDispatch()
const addConfig = (key: string, value: string, _id?: string) => {
const payload: ObjectType = {
key,
value,
namespace: namespace._id,
version: "v1",
}
_id && (payload._id = _id)
Network.post(
API_CONFIG.CONFIG[process.env.NODE_ENV] +
ENDPOINTS.CONFIG.ROOT +
ENDPOINTS.CONFIG.SAVE +
namespace._id,
payload,
)
.then((res) => {
if (res.status === 200) {
fetchNamespaces()
dispatch(
notify({
show: true,
message: "Added a new config successfully",
type: ArAlertType.SUCCESS,
uid: uuid(),
}),
)
}
})
.catch((e) => {
dispatch(
notify({
show: true,
message: "Failed to add new config",
type: ArAlertType.ERROR,
uid: uuid(),
}),
)
})
}
const deleteConfig = (_id?: string) => {
Network.get(
API_CONFIG.CONFIG[process.env.NODE_ENV] +
ENDPOINTS.CONFIG.ROOT +
ENDPOINTS.CONFIG.DELETE +
"/" +
namespace._id +
"/" +
_id,
)
.then((res) => {
if (res.status === 200) {
fetchNamespaces()
dispatch(
notify({
show: true,
message: "Removed config successfully",
type: ArAlertType.SUCCESS,
uid: uuid(),
}),
)
}
})
.catch((e) => {
dispatch(
notify({
show: true,
message: "Failed to delete config",
type: ArAlertType.ERROR,
uid: uuid(),
}),
)
})
}
return (
<div className="ar-ConfigurationViewer d-flex flex-column w-100 h-100">
<div className="ar-ConfigurationViewer__header px-3 py-2 w-100">
<Breadcrumb
classes="d-inline-block"
data={[{ label: namespace.name, route: `/config/${namespace.name}` }]}
/>
<Button
classes="float-end"
content="Edit"
color="brown"
preIcon="fa/FaRegEdit"
variant={ArButtonVariants.LINK}
size={ArSizes.SMALL}
/>
<Popover
classes="float-end"
// trigger={ArPopoverTriggers.HOVER}
position={ArPopoverPositions.BOTTOMLEFT}
version="v1"
>
<Button
content="Add"
color="green"
preIcon="md/MdAddCircleOutline"
variant={ArButtonVariants.LINK}
size={ArSizes.SMALL}
slot={ArPopoverSlots.ANCHOR}
/>
<InlineMenu
slot={ArPopoverSlots.POPOVER}
data={{
Org: {},
Namespace: {
onClick: () =>
dispatch(
setModalState({
show: true,
isSticky: true,
content: <NamespaceOrgForm context="namespace" />,
closeHandler: () =>
dispatch(setModalState({ show: false })),
}),
),
},
}}
/>
</Popover>
<LoadableIcon
classes="cursor-pointer"
icon="io/IoMdInformationCircle"
color="#0d6efd"
onClick={() =>
dispatch(
setModalState({
show: true,
isSticky: true,
content: <NamespaceInfoBox namespace={namespace} />,
closeHandler: () => dispatch(setModalState({ show: false })),
}),
)
}
/>
</div>
<div className="ar-ConfigurationViewer__content flex-1 d-flex px-3 py-2 w-100">
<div className="d-flex flex-1 flex-column">
<ConfigRowItem onAdd={addConfig} isNew />
<div className="my-3 border" />
{configurations && configurations.length > 0 ? (
configurations.map((configuration) => {
return (
<ConfigRowItem
onUpdate={(_id: string, key: string, value: string) =>
addConfig(key, value, _id)
}
onDelete={deleteConfig}
config={configuration}
disabled
/>
)
})
) : (
<div className="row flex-1">
<div className="col flex-center fw-bold">
Nothing here, start by adding configurations above
</div>
</div>
)}
</div>
</div>
<div className="ar-ConfigurationViewer__footer px-3 py-2 w-100">
<Button
classes="float-end"
content="Submit"
variant={ArButtonVariants.SUCCESS}
size={ArSizes.SMALL}
/>
</div>
</div>
)
}
export default ConfigurationViewer

View File

@@ -1,3 +0,0 @@
import ConfigurationViewer from "./ConfigurationViewer"
export default ConfigurationViewer

View File

@@ -1,41 +0,0 @@
.ar-Editor {
border: 1px solid var(--ar-color-layout-border);
.ar-Editor__tools {
background-color: var(--ar-bg-base);
border-bottom: 1px solid var(--ar-color-layout-border);
}
.ar-Editor__prop-selector {
min-height: 2.5rem;
&::-webkit-scrollbar {
height: 2px;
background-color: #fefefe;
}
/* Track */
&::-webkit-scrollbar-track {
background: #cdcdcd;
width: 10px;
height: 10px;
}
/* Handle */
&::-webkit-scrollbar-thumb {
background: #ababab;
border-radius: 2px;
width: 3rem;
height: 10px;
&:hover {
background: #888;
}
}
}
}
#root.ar-Editor__frame__main {
background-color: var(--ar-bg-tertiary);
&.background {
background-repeat: no-repeat;
background-size: 100%;
}
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import Editor from "./Editor"
describe("Editor", () => {
it("renders without error", () => {
})
})

View File

@@ -1,359 +0,0 @@
import { ReactNode, useEffect, useState } from "react"
import { useLocation, useNavigate } from "react-router-dom"
import { createPortal } from "react-dom"
import {
ArDropdownVariants,
ArSizes,
ArThemes,
ComponentDescription,
FrameContentDefinition,
FrameContentProps,
PRIMITIVES,
} from "@armco/types"
import {
Alert,
Button,
COMPONENTS,
Toggle,
Helper,
Network,
Dropdown,
} from ".."
import StyleHelper from "./StyleHelper"
import * as images from "../../static/images"
import "./Editor.component.scss"
const children = {
bg: (
<div
key="dummy-bg"
className="ar-Editor__frame__dummy-child"
style={{ height: "100vh", width: "100vw" }}
/>
),
content: (
<span
key="dummy-content"
slot="content"
className="bg"
style={{ height: "10rem", width: "10rem" }}
>
Popover Content
</span>
),
popover: (
<div
key="dummy-popover"
slot="popover"
style={{ height: "10rem", width: "10rem" }}
>
Popover Content
</div>
),
list: (
<div
key="dummy-list"
className="d-grid"
style={{
gridTemplateColumns: "repeat(auto-fill, 5rem)",
gap: "1rem",
justifyContent: "space-around",
}}
>
{Array.from({ length: 40 }, (_, i) => (
<div
key={"dummy-list-item-" + i}
className="bg border border-radius-l2"
style={{ width: "5rem", height: "5rem" }}
/>
))}
</div>
),
anchor: <Button content="Click Me!" slot="anchor" />,
}
const FrameContent = (
props: FrameContentProps,
): JSX.Element | string | null => {
const { contentDefinition, notificationProps } = props
const { component, props: componentProps, data } = contentDefinition || {}
const background: string = data?.test?.background
const SelectedComponent = component
let childRenders: Array<JSX.Element> = []
if (data?.type === "HOC") {
childRenders = data.children.map(
(name: string) => children[name as keyof object],
)
}
return SelectedComponent ? (
<div
className={`ar-Editor__frame__main h-100 w-100 overflow-auto p-5 flex-column${
background ? " background" : ""
}`}
style={{
// @ts-ignore
backgroundImage: background ? `url(${images[background]})` : "",
}}
id="root"
>
{data && data.type === "HOC" ? (
<SelectedComponent {...componentProps}>
{childRenders}
</SelectedComponent>
) : (
<SelectedComponent {...componentProps} />
)}
{notificationProps && <Alert {...notificationProps} />}
</div>
) : (
"Select an item from the side panel to view here"
)
}
const Editor = (): JSX.Element | string => {
const [contentRef, setContentRef] = useState<HTMLIFrameElement | null>(null)
const [notificationProps, setNotificationProps] = useState<any>(null)
const [selectedComponentDefinition, setSelectedComponentDefinition] =
useState<FrameContentDefinition | null>(null)
// const [theme, setTheme] = useState<string>(ArThemes.LIGHT1)
const [theme, setTheme] = useState<string>(ArThemes.DARK1)
const [portal, setPortal] = useState<ReactNode>()
const [component, setComponent] = useState<string>()
const location = useLocation()
const navigate = useNavigate()
useEffect(() => {
window.onmessage = (e) => {
"ar" in e.data && setNotificationProps(e.data?.ar)
}
}, [])
useEffect(() => {
if (theme) {
const iframeHtml =
contentRef?.contentWindow?.document?.getElementsByTagName("html")[0]
iframeHtml && iframeHtml.setAttribute("ar-theme", theme)
const iframeBody = iframeHtml?.getElementsByTagName("body")[0]
iframeBody && (iframeBody.style.backgroundColor = "var(--ar-bg)")
}
}, [contentRef, theme])
useEffect(() => {
const locationPathArr = location.pathname
.split("/")
.filter((p: string) => p)
const componentName =
locationPathArr.length > 1 && locationPathArr[locationPathArr.length - 1]
if (componentName) {
setComponent(componentName)
const { selectedItem, hierarchy } = Helper.findComponentDescription(
componentName,
COMPONENTS,
)
if (selectedItem && hierarchy) {
let props: { [key: string]: string | number | boolean | undefined } = {
demo: true,
}
if (location.search) {
const params = location.search.substring(1)?.split("&")
if (params) {
params.forEach((prop) => {
const keyValue = prop.split("=")
const name = keyValue && keyValue[0]
let value: string | boolean = keyValue && keyValue[1]
value = value === "true" ? true : decodeURIComponent(value)
value =
value === "false" ? false : decodeURIComponent(value as string)
if (prop && value && selectedItem) {
props[name] = value
}
})
}
}
if (
!selectedComponentDefinition ||
selectedComponentDefinition.componentName !== componentName ||
JSON.stringify(selectedComponentDefinition.props) !==
JSON.stringify(props)
) {
const SelectedComponent = Helper.importComponent(
componentName,
hierarchy?.toLowerCase(),
)
setSelectedComponentDefinition({
componentName,
component: SelectedComponent,
props,
data: selectedItem,
hierarchy,
})
}
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location])
useEffect(() => {
if (
contentRef &&
selectedComponentDefinition &&
process.env.NODE_ENV !== "production"
) {
StyleHelper.injectComponentStyleInFrame(
selectedComponentDefinition.data as ComponentDescription,
selectedComponentDefinition.hierarchy,
contentRef,
)
}
}, [contentRef, selectedComponentDefinition])
useEffect(() => {
if (selectedComponentDefinition) {
const { demo, theme, ...selectedProps } =
selectedComponentDefinition.props || {}
const newUrl = Network.stringifyUrl("/", selectedProps).slice(1)
selectedProps && newUrl !== location.search && navigate(newUrl)
}
}, [selectedComponentDefinition])
useEffect(() => {
if (contentRef) {
const bsLink =
contentRef?.contentWindow?.document.getElementsByTagName("link")
if (!bsLink || bsLink.length === 0) {
if (process.env.NODE_ENV === "production") {
StyleHelper.injectLinkInFrame(
["index", "ComponentsViewerPage"],
contentRef,
)
} else {
StyleHelper.injectBootstrapInFrame(contentRef)
StyleHelper.injectVariablesInFrame(contentRef)
StyleHelper.injectAnimationInFrame(contentRef)
StyleHelper.injectGlobalInFrame(contentRef)
StyleHelper.injectEditorStyleInFrame(contentRef)
StyleHelper.injectComponentStyleInFrame(
{ name: "Alert" },
"ATOMS",
contentRef,
)
}
}
}
}, [contentRef])
const mountNode = contentRef?.contentWindow?.document?.body
useEffect(() => {
if (mountNode) {
if (selectedComponentDefinition && selectedComponentDefinition.props) {
selectedComponentDefinition.props.theme = theme
}
setPortal(
createPortal(
<FrameContent
contentDefinition={selectedComponentDefinition}
notificationProps={notificationProps}
/>,
mountNode,
),
)
}
}, [selectedComponentDefinition, mountNode, notificationProps, theme])
const currentItemVariants = selectedComponentDefinition?.data.variants || []
return (
<div className="ar-Editor w-100 h-100 d-flex flex-column">
<div className="ar-Editor__tools">
<div className="row justify-content-end">
<div className="col flex-v-center justify-content-end">
<div className="ar-Editor__prop-selector flex-v-center border-right me-3 overflow-auto py-2 px-3">
{Object.keys(currentItemVariants)
.filter((prop: string) => prop !== "demo")
.map((prop: any, index: number, arr) => {
const propsValues = currentItemVariants[prop]
const isBoolean =
propsValues.length === 2 &&
propsValues.indexOf(true) > -1 &&
propsValues.indexOf(false) > -1
return isBoolean ? (
<Toggle
key={"prop-toggle-" + index + "-" + prop}
classes="me-3"
label={Helper.toReadable(prop)}
onChange={(isChecked: boolean) =>
selectedComponentDefinition?.componentName &&
setSelectedComponentDefinition({
...selectedComponentDefinition,
props: {
...(selectedComponentDefinition.props || {}),
[prop]: isChecked,
},
})
}
isOn={Boolean(
selectedComponentDefinition?.props &&
selectedComponentDefinition?.props[prop],
)}
size={ArSizes.SMALL}
hideStatus
/>
) : (
<Dropdown
key={"prop-dropdown-" + index + "-" + prop}
classes="me-2"
contentMatchAnchorWidth={false}
options={propsValues.map((propValue: PRIMITIVES) => ({
name: propValue,
isSelected:
selectedComponentDefinition?.props &&
propValue + "" ===
selectedComponentDefinition?.props[prop],
}))}
onSelectionChanged={(obj) => {
selectedComponentDefinition?.componentName &&
setSelectedComponentDefinition({
...selectedComponentDefinition,
props: {
...(selectedComponentDefinition.props || {}),
[prop]: obj.value,
},
})
}}
variant={ArDropdownVariants.SELECTIONSASPILLS}
placeholder={Helper.toReadable(prop)}
version="v2"
/>
)
})}
</div>
<span className="float-end pe-3">
<Toggle
isOn={theme === ArThemes.DARK1}
toggleOnName="Dark"
toggleOffName="Light"
onChange={(checked: boolean) =>
setTheme(checked ? ArThemes.DARK1 : ArThemes.LIGHT1)
}
value={true}
/>
</span>
</div>
</div>
</div>
{/* <FrameContent
contentDefinition={selectedComponentDefinition}
notificationProps={notificationProps}
/> */}
<iframe
className="ar-Editor__frame w-100 h-100"
ref={setContentRef}
title="Armco Component Viewer"
>
{portal}
</iframe>
</div>
)
}
export default Editor

View File

@@ -1,140 +0,0 @@
import { ComponentDescription } from "@armco/types"
//// Editor Frame Style related helpers
class StyleHelper {
static injectGlobalInFrame(frame: HTMLIFrameElement | null) {
const clipCallback = (text: string) => {
return {
clipStart: text && text.indexOf("html {\\n font-size: 16px"),
}
}
StyleHelper.injectScssInFrame(
"/src/app/static/styles/_global.scss",
frame,
undefined,
clipCallback,
)
}
static injectBootstrapInFrame(frame: HTMLIFrameElement | null) {
StyleHelper.injectLinkInFrame(
"/src/app/static/styles/bootstrap.min.css",
frame,
)
}
static injectVariablesInFrame(frame: HTMLIFrameElement | null) {
StyleHelper.injectScssInFrame(
"/src/app/static/styles/_variables.scss",
frame,
)
}
static injectAnimationInFrame(frame: HTMLIFrameElement | null) {
StyleHelper.injectScssInFrame(
"/src/app/static/styles/_animations.scss",
frame,
)
}
static injectEditorStyleInFrame(frame: HTMLIFrameElement | null) {
StyleHelper.injectScssInFrame(
"/src/app/components/Editor/Editor.component.scss",
frame,
)
}
static injectComponentStyleInFrame(
selectedComponent: ComponentDescription,
hierarchy: string,
frame: HTMLIFrameElement | null,
) {
if (hierarchy && selectedComponent && selectedComponent.name) {
const componentStyleElement =
frame?.contentWindow?.document.querySelector("style.component-style")
componentStyleElement && componentStyleElement.remove()
const dependencyComponentsToInject = selectedComponent.uses
? [
{ name: selectedComponent.name, hierarchy },
...selectedComponent.uses,
]
: [{ name: selectedComponent.name, hierarchy }]
dependencyComponentsToInject.forEach(
(component: { [key: string]: string }) => {
const scssPath = `/src/app/components/${component.hierarchy.toLowerCase()}/${
component.name
}/${component.name}.component.scss`
StyleHelper.injectScssInFrame(scssPath, frame, component)
},
)
}
}
static injectScssInFrame(
scssPath: string,
frame: HTMLIFrameElement | null,
component?: { [key: string]: string },
clipCallback?: any,
) {
import("sass").then((module) => {
const { compileString } = module
fetch(scssPath)
.then((r) => r.text())
.then((text) => {
const { clipStart, clipEnd } = clipCallback
? clipCallback(text)
: () => ({})
text = text
.substring(
clipStart ||
text.indexOf("__vite__css = ") + '__vite__css = "'.length,
clipEnd || text.indexOf("__vite__updateStyle(") - 2,
)
.replace(/\\n/g, "")
.replace(/\\"/g, '"')
const css = compileString(text).css
StyleHelper.injectStyleInFrame(css, frame, component)
})
})
}
static injectStyleInFrame(
css: string,
frame: HTMLIFrameElement | null,
component?: { [key: string]: string },
) {
const style = document.createElement("style")
if (component && component.name) {
style.setAttribute("id", component.name)
style.setAttribute("class", "component-style")
}
style.appendChild(document.createTextNode(css))
frame?.contentWindow?.document?.head.appendChild(style)
}
static injectLinkInFrame(
link: string | Array<string>,
frame: HTMLIFrameElement | null,
) {
if (Array.isArray(link)) {
const links = document.querySelectorAll("link[rel='stylesheet']")
link.forEach((item: string) => {
Array.from(links).forEach((selectedLink) => {
const cssUrl = selectedLink.getAttribute("href")
if (cssUrl && cssUrl.startsWith(`/assets/${item}`)) {
const clone = selectedLink.cloneNode(true)
frame?.contentWindow?.document?.head.appendChild(clone)
}
})
})
} else {
const cssLink = document.createElement("link")
cssLink.href = link
cssLink.rel = "stylesheet"
cssLink.type = "text/css"
frame?.contentWindow?.document?.head.appendChild(cssLink)
}
}
}
export default StyleHelper

View File

@@ -1,3 +0,0 @@
import Editor from "./Editor"
export default Editor

View File

@@ -1,28 +0,0 @@
.ar-FavoritesList {
max-width: 0;
transition: max-width 0.3s;
&.show {
max-width: 10rem;
}
.ar-FavoritesList__header {
background-color: var(--ar-color-prominent);
&.loggedIn {
background-color: var(--ar-color-success);
}
}
&.shrink {
max-width: 4rem;
}
.ar-FavoriteItem {
width: 0;
transition: width 0.3s linear;
&.show {
width: 5rem;
}
}
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import FavoritesList from "./FavoritesList"
describe("FavoritesList", () => {
it("renders without error", () => {
})
})

View File

@@ -1,125 +0,0 @@
import { useEffect, useState } from "react"
import {
ArButtonVariants,
ArPopoverPositions,
ArPopoverTriggers,
ArSizes,
FavoritesItemProps,
FavoritesListProps,
IconTileProps,
} from "@armco/types"
import { getFavorites } from "../../pages/IconsPage/IconsPage.slice"
import {
Button,
getLoggedIn,
IconTile,
LoadableIcon,
Popover,
setRightPanelContent,
useAppDispatch,
useAppSelector,
} from ".."
import "./FavoritesList.component.scss"
const FavoriteItem = (props: FavoritesItemProps): JSX.Element | null => {
const { index, favorite } = props
const [shown, show] = useState<boolean>()
useEffect(() => {
show(true)
return () => {
show(false)
setTimeout(() => {}, 300)
}
}, [])
return (
<span
className={`ar-FavoriteItem d-flex flex-column${shown ? " show" : ""}`}
>
<IconTile key={index} icon={favorite.icon} hideFooter hideBorder />
</span>
)
}
const FavoritesList = (props: FavoritesListProps): JSX.Element => {
const favorites = useAppSelector(getFavorites)
const isLoggedIn = useAppSelector<boolean | undefined>(getLoggedIn)
const dispatch = useAppDispatch()
const icons =
favorites &&
favorites.map((favorite: IconTileProps, index: number) => (
<FavoriteItem index={index} favorite={favorite} />
))
return (
<div
className={`ar-FavoritesList h-100${icons?.length > 0 ? " show" : ""}${
isLoggedIn ? " shrink" : ""
}`}
>
<LoadableIcon icon="tb/TbLayoutSidebarRightCollapse" />
{!isLoggedIn ? (
<Popover
trigger={ArPopoverTriggers.HOVER}
position={ArPopoverPositions.LEFTBOTTOM}
version="v1"
>
<span
className="ar-FavoritesList__header p-2 border-bottom d-flex justify-content-center w-100"
slot="anchor"
>
<LoadableIcon icon="io/IoIosWarning" color="white" />
</span>
<div slot="popover" className="p-2">
<strong className="mb-2 d-inline-block">
You're not logged in, all of your favorites will be lost when the
session is over.
</strong>
<p className="mb-0">
Please login to ensure your changes are saved!
</p>
</div>
</Popover>
) : (
<Popover
trigger={ArPopoverTriggers.HOVER}
position={ArPopoverPositions.LEFT}
>
<span
className="ar-FavoritesList__header loggedIn p-2 border-bottom d-flex justify-content-center w-100"
slot="anchor"
>
<LoadableIcon icon="io/IoIosCheckmarkCircleOutline" color="white" />
</span>
<div slot="popover" className="p-2">
<strong className="mb-2 d-inline-block">
Your favorites are being synchronized with your account!
</strong>
</div>
</Popover>
)}
<div
className={`ar-FavoritesList__favorites flex-v-center flex-column mt-3${
icons?.length > 0 ? " px-2" : ""
}`}
>
{icons}
</div>
{!isLoggedIn && (
<Button
classes="mx-2"
variant={ArButtonVariants.WARNING}
size={ArSizes.SMALL}
content="Login"
preIcon="ci/CiWarning"
onClick={() => {
dispatch(setRightPanelContent({ name: "LoginProvider" }))
}}
/>
)}
</div>
)
}
export default FavoritesList

View File

@@ -1,3 +0,0 @@
import FavoritesList from "./FavoritesList"
export default FavoritesList

View File

@@ -1,12 +0,0 @@
.ar-FontsList {
.ar-FontsList__header-search-upload {
background-color: var(--ar-bg);
color: var(--ar-colora);
background-color: var(--ar-bg-base);
}
.ar-FontsList__font-tile-container {
background-color: var(--ar-bg);
color: var(--ar-color);
}
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import FontsList from "./FontsList"
describe("FontsList", () => {
it("renders without error", () => {
})
})

View File

@@ -1,154 +0,0 @@
import { ChangeEvent, useEffect, useState } from "react"
import {
ArButtonVariants,
ArLoaderTypes,
ArSizes,
ArrayType,
FontsListProps,
FunctionType,
ObjectType,
} from "@armco/types"
import {
API_CONFIG,
Button,
ENDPOINTS,
FontTile,
Helper,
Loader,
Network,
Pagination,
SearchField,
Toggle,
} from ".."
import "./FontsList.component.scss"
const fetchFontsPage = (
limit: number,
from: number,
filters: ObjectType,
dataSetter: FunctionType,
pageSetter: FunctionType,
setLoading: FunctionType,
) => {
const pageApi =
API_CONFIG.STATIC_HOST[process.env.NODE_ENV] +
ENDPOINTS.STATIC.FONT.ROOT +
ENDPOINTS.STATIC.FONT.PAGE
const queryParams: ObjectType = { pageSize: limit, from, ...filters }
Network.get(pageApi, queryParams)
.then((response) => {
if (response && response.status === 200) {
if (response.body && dataSetter) {
const content = JSON.parse(response.body)
const styleElement = document.createElement("style")
styleElement.type = "text/css"
styleElement.appendChild(
document.createTextNode(content.returnFontGroups),
)
document.head.appendChild(styleElement)
dataSetter(content.fontNames)
pageSetter(content.fontNames.slice(0, 30))
}
}
setLoading(false)
})
.catch((error) => {
console.error(error)
setLoading(false)
})
}
const FontsList = (props: FontsListProps): JSX.Element => {
const [searchText, setSearchText] = useState<string | undefined>()
const [fonts, setFonts] = useState<ArrayType>()
const [page, setPage] = useState<ArrayType>()
const [loading, setLoading] = useState<boolean>()
// TODO: Fetch Fonts
useEffect(() => {
const filters: ObjectType = {}
setLoading(true)
fetchFontsPage(30, 0, filters, setFonts, setPage, setLoading)
}, [])
useEffect(() => {
const filters: ObjectType = {}
if (searchText) {
filters.search = searchText
}
setLoading(true)
fetchFontsPage(200, 0, filters, setFonts, setPage, setLoading)
}, [searchText])
return (
<div className="ar-FontsList h-100 w-100">
{!fonts && (
<Loader label="Loading Fonts..." type={ArLoaderTypes.SHAPES} />
)}
<div className="ar-FontsList__header-search-upload py-2 px-3 mb-2 border">
<div className="row">
<span className="offset-4 col-4 d-none d-md-flex flex-v-center justify-content-end">
<Button
classes="h-100 float-end me-3"
content="Upload"
size={ArSizes.SMALL}
variant={ArButtonVariants.SUCCESS}
/>
<Toggle
onChange={() => {}}
toggleOnName="Compact"
toggleOffName="Compact"
/>
</span>
<div className="col-4 offset-4 offset-md-0 flex-v-center">
<SearchField
classes="bg-white"
placeholder="Search by name, style, description"
onChange={Helper.debounce(
(event: ChangeEvent<HTMLInputElement>) =>
setSearchText(event.target.value),
1000,
)}
items={
[]
// fonts
// ? fonts.map(
// (font): SearchItem => ({
// label: font.name,
// data: font,
// }),
// )
// : []
}
/>
</div>
</div>
</div>
{page && (
<div className="ar-FontsList__font-tile-container py-2 px-3 border d-flex justify-content-between flex-wrap flex-grow-1">
{loading && (
<Loader label="Applying filters..." type={ArLoaderTypes.CIRCLE} />
)}
{(page as Array<string>).map((font) => (
<FontTile font={font} />
))}
</div>
)}
<Pagination
classes="my-3 flex-center"
data={fonts}
maxPillsToShow={5}
pageSetter={setPage}
// trigger={ArPageTriggers.SCROLL}
count={1}
load={100}
// dataFetcher={(load, count) =>
// fetchIconsPage(load, count, {}, setIcons, setPage, setLoading)
// }
/>
</div>
)
}
export default FontsList

View File

@@ -1,3 +0,0 @@
import FontsList from "./FontsList"
export default FontsList

View File

@@ -1,39 +0,0 @@
.ar-Header {
background-color: var(--ar-bg-base);
border-bottom: 1px solid var(--ar-color-layout-border);
.ar-Header__app-logo {
cursor: pointer;
.ar-Header__brand {
border-radius: 50%;
}
.ar-Header__app-name {
font-family: "Allerta Stencil";
font-weight: 700;
font-size: 1.5rem;
.ar-Header__app-name-i {
color: red;
}
.ar-Header__app-name-o {
color: green;
}
}
}
.ar-Header__app-search {
transition: width 0.3s;
@media screen and (max-width: 576px) {
flex: 1;
.ar-SearchField .ar-TextInput {
border-top-left-radius: 0.5rem;
border-bottom-left-radius: 0.5rem;
}
.ar-SearchField .ar-Button {
border-top-right-radius: 0.5rem;
border-bottom-right-radius: 0.5rem;
}
}
}
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import Header from "./Header"
describe("Header", () => {
it("renders without error", () => {
})
})

View File

@@ -1,104 +0,0 @@
import { useState } from "react"
import { useLocation, useNavigate } from "react-router-dom"
import { ArThemes, DrawerProps, HeaderProps, ObjectType } from "@armco/types"
import {
getCurrentTheme,
getDrawerState,
FlexTools,
LoadableIcon,
SearchField,
setDrawerState,
setTheme,
useAppDispatch,
useAppSelector,
} from ".."
import "./Header.component.scss"
const Header = (props: HeaderProps): JSX.Element => {
const { routeDetails } = props
const navigate = useNavigate()
const location = useLocation()
const [searchFocussed, setSearchFocussed] = useState<boolean>()
const drawerState = useAppSelector<DrawerProps | undefined>(getDrawerState)
const dispatch = useAppDispatch()
const theme = useAppSelector<string>(getCurrentTheme)
const route = (routeDetails?.route as ObjectType)?.class
const isLanding =
!routeDetails ||
["landing", "assets", "playground", "tools-and-services"].indexOf(
route as string,
) > -1
const showExpander =
location.pathname.startsWith("/components") ||
location.pathname.startsWith("/icons")
return (
<header className="ar-Header w-100 flex-v-center px-2">
<div
className="ar-Header__app-logo h-100 flex-v-center me-3 d-none d-sm-flex"
onClick={() => navigate("/")}
>
<span className="ar-Header__app-name h-100 flex-center">
Stuffle.
<span className="ar-Header__app-name-i">i</span>
<span className="ar-Header__app-name-o">o</span>
</span>
</div>
{showExpander && (
<LoadableIcon
classes="ar-Drawer__expander d-inline d-sm-none me-3"
icon={
drawerState?.collapsed
? "tb/TbLayoutSidebarLeftExpand"
: "tb/TbLayoutSidebarLeftCollapse"
}
size="2rem"
onClick={() =>
dispatch(setDrawerState({ collapsed: !drawerState?.collapsed }))
}
color={theme === ArThemes.DARK1 ? "lightgrey" : "black"}
/>
)}
{!isLanding && (
<div
tabIndex={-1}
onFocus={() => setSearchFocussed(true)}
onBlur={() => setSearchFocussed(false)}
className={`ar-Header__app-search h-100 flex-v-center me-3 ${
searchFocussed ? "w-50" : "w-25"
}`}
>
<SearchField
items={[]}
onChange={() => {}}
placeholder="Search icons, components, fonts..."
/>
</div>
)}
<LoadableIcon
classes="d-inline d-sm-none ms-auto"
icon="cg.CgDarkMode"
size="2rem"
onClick={() => {
const nextTheme =
theme === ArThemes.LIGHT1 ? ArThemes.DARK1 : ArThemes.LIGHT1
dispatch(setTheme(nextTheme))
document
.getElementsByTagName("html")[0]
.setAttribute("ar-theme", nextTheme)
}}
color={theme === ArThemes.DARK1 ? "lightgrey" : "black"}
/>
<div
className={`h-100 d-none d-sm-inline${
isLanding ? " w-100" : " ms-auto"
}`}
>
<FlexTools isLanding={isLanding} route={route as string} />
</div>
</header>
)
}
export default Header

View File

@@ -1,3 +0,0 @@
import Header from "./Header"
export default Header

View File

@@ -1,48 +0,0 @@
.ar-IconController {
.ar-IconController__main, .ar-IconController__header {
background-color: var(--ar-bg-base);
}
.ar-IconController__color-palette {
border-radius: 50%;
}
.ar-IconController__download-button, .ar-IconController__animate-button {
background-color: orange;
border-color: orange;
}
.ar-IconController__main {
border-radius: 0.3rem;
}
.ar-IconController__controls-form {
background-color: var(--ar-bg);
}
.hover-show {
cursor: pointer;
.ar-IconController__download-icon {
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
}
&:hover {
.ar-IconController__download-icon {
opacity: 1;
pointer-events: auto;
transition: box-shadow 0.3s;
&:hover {
box-shadow: 0 4px 8px var(--ar-shadow);
border-radius: 3px
}
}
}
}
.ar-IconController__meta-item {
color: var(--ar-color-secondary);
}
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import IconController from "./IconController"
describe("IconController", () => {
it("renders without error", () => {
})
})

View File

@@ -1,378 +0,0 @@
import { useNavigate } from "react-router-dom"
import { useEffect, useState } from "react"
import {
ArButtonVariants,
ArPopoverSlots,
IconControllerProps,
ObjectType,
} from "@armco/types"
import { getIconStyles } from "../../pages/IconPage/IconPage.slice"
import {
API_CONFIG,
Breadcrumb,
Button,
DomHelper,
LoadableIcon,
Network,
Select,
Suggestions,
setRightPanelContent,
Tags,
TextInput,
useAppDispatch,
useAppSelector,
} from ".."
import "./IconController.component.scss"
const complement = (hex?: string) => {
if (hex) {
const hexParts = hex.substring(1).toLowerCase().split("")
const hexMap = { a: 10, b: 11, c: 12, d: 13, e: 14, f: 15 }
const reverseMap = { 10: "a", 11: "b", 12: "c", 13: "d", 14: "e", 15: "f" }
let complementValue = "#"
hexParts.forEach((part: string) => {
let num = +part
if (isNaN(num)) {
num = hexMap[part as keyof object]
}
const complementNumeric = 15 - num
complementValue +=
complementNumeric > 9
? reverseMap[complementNumeric as keyof object]
: complementNumeric
})
return complementValue
}
return "#fff"
}
const STATIC_ROOT = API_CONFIG.STATIC_HOST[process.env.NODE_ENV]
const IconController = (props: IconControllerProps): JSX.Element => {
const { group, icon, name } = props
const [size, setSize] = useState<string>()
const [unit, setUnit] = useState<string>("rem")
const [similarIcons, setSimilarIcons] = useState<Array<string>>()
const dispatch = useAppDispatch()
const iconStyles = useAppSelector(getIconStyles)
const fontColor = complement(iconStyles?.bgColor || "white")
const navigate = useNavigate()
// const suggestions = getSuggestions(group, name)
useEffect(() => {
const filename = `${group}_${name}-black.png`
Network.getStatic(`/static/${filename}`).then((res) => {
const imgBlob = res.body
const formData = new FormData()
const file = new File([imgBlob], filename, { type: imgBlob.type })
formData.append("file", file)
formData.append("count", "20")
Network.post(
"http://localhost:5002/api/similar-icon-paths",
formData,
undefined,
{ headers: { "Content-Type": "multipart/form-data" } },
true,
true,
).then((res) => {
if (res.status === 200) {
const iconPaths = res.body.map((str: string) =>
str.replace(
"../images",
`${API_CONFIG.STATIC_HOST[process.env.NODE_ENV]}/static`,
),
)
// setSimilarIcons(iconPaths)
Network.post(STATIC_ROOT + "/icon/png-to-svg", {
urls: iconPaths,
}).then((res) => {
setSimilarIcons(res.body)
})
}
})
})
}, [name, group])
return (
<div className="ar-IconController container h-100 overflow-auto">
{group && name && (
<Breadcrumb
data={[
{ label: group, route: `/icon/${group}` },
{ label: name, route: `/icon/${group}/${name}` },
]}
/>
)}
<div className="ar-IconController__header mb-3 p-3 border d-flex">
<h6 className="mb-0 fw-bold">
{group}.{name}
</h6>
<Button
containerClasses="ms-auto"
classes="ar-IconController__animate-button me-3"
variant={ArButtonVariants.PRIMARY}
postIcon="md/MdAnimation"
content="Animate"
onClick={() =>
navigate("/icon/animate", {
state: { icon, iconStyles, name: `${group}_${name}` },
})
}
/>
<Button
classes="ar-IconController__download-button me-3"
variant={ArButtonVariants.PRIMARY}
postIcon="md/MdDownload"
content="Download"
onClick={() =>
DomHelper.downloadSvg(
(icon as ObjectType).icon as string,
name || "download.svg",
{
size: "3rem",
...iconStyles,
color: iconStyles?.strokeColor || "royalblue",
},
)
}
/>
<LoadableIcon
classes="ar-IconController__color-palette cursor-pointer hover-shadow"
icon="io/IoIosColorPalette"
color="orange"
slot={ArPopoverSlots.ANCHOR}
size="2rem"
onClick={() => dispatch(setRightPanelContent({ name: "IconEditor" }))}
/>
</div>
<div className="ar-IconController__main mb-3 p-3 border">
<div className="row">
<div
className="ar-IconController__icon-sizes col"
style={{ backgroundColor: iconStyles?.bgColor }}
>
{group && name && (
<div className="row h-100 border-right">
<div className="col flex-center flex-column border-right">
<div className="h-50 flex-center flex-column border-bottom w-100 position-relative hover-show">
<LoadableIcon
classes="ar-IconController__download-icon position-absolute top-1 end-1"
icon="md/MdDownload"
color="orange"
size="2.5rem"
onClick={() =>
DomHelper.downloadSvg(
(icon as ObjectType).icon as string,
name,
{
size: "3rem",
...iconStyles,
color: iconStyles?.strokeColor || "royalblue",
},
)
}
/>
<LoadableIcon
key={`${group}/${name}`}
icon={`${group}/${name}`}
size="3rem"
color={iconStyles?.strokeColor || "royalblue"}
fillColor={iconStyles?.fillColor}
strokeColor={iconStyles?.strokeColor}
strokeWidth={iconStyles?.strokeWidth}
/>
<span
className="fw-bold"
style={{ color: fontColor || "black" }}
>
3rem
</span>
</div>
<div className="h-50 flex-center flex-column w-100 position-relative hover-show">
<LoadableIcon
classes="ar-IconController__download-icon position-absolute top-1 end-1"
icon="md/MdDownload"
color="orange"
size="2.5rem"
onClick={() =>
DomHelper.downloadSvg(
(icon as ObjectType).icon as string,
name,
{
size: "7rem",
...iconStyles,
color: iconStyles?.strokeColor || "royalblue",
},
)
}
/>
<LoadableIcon
key={`${group}/${name}`}
icon={`${group}/${name}`}
size="7rem"
color={iconStyles?.strokeColor || "royalblue"}
fillColor={iconStyles?.fillColor}
strokeColor={iconStyles?.strokeColor}
strokeWidth={iconStyles?.strokeWidth}
/>
<span
className="fw-bold"
style={{ color: fontColor || "black" }}
>
7rem
</span>
</div>
</div>
<div className="col flex-center flex-column position-relative hover-show">
<LoadableIcon
classes="ar-IconController__download-icon position-absolute top-1 end-1"
icon="md/MdDownload"
color="orange"
size="2.5rem"
onClick={() =>
DomHelper.downloadSvg(
(icon as ObjectType).icon as string,
name,
{
size: size && unit ? size + unit : "20rem",
...iconStyles,
color: iconStyles?.strokeColor || "royalblue",
},
)
}
/>
<LoadableIcon
key={`${group}/${name}`}
icon={`${group}/${name}`}
size={`${size}${unit}` || "20rem"}
color={iconStyles?.strokeColor || "royalblue"}
fillColor={iconStyles?.fillColor}
strokeColor={iconStyles?.strokeColor}
strokeWidth={iconStyles?.strokeWidth}
/>
<span
className="fw-bold"
style={{ color: fontColor || "black" }}
>
{size && unit ? size + unit : "20rem"}
</span>
</div>
</div>
)}
</div>
<div className="ar-IconController__controls col d-flex">
<div className="row w-100">
{icon && (
<div className="ar-IconController__meta col-4">
<h6>Icon Details</h6>
<div className="ar-IconController__meta-item row lh-1-5">
<div className="col">
<strong>Created By: </strong>
{icon.createdby as string}
</div>
</div>
<div className="ar-IconController__meta-item row lh-1-5">
<div className="col">
<strong>Description: </strong>
{icon.description as string}
</div>
</div>
<div className="ar-IconController__meta-item row lh-1-5">
<div className="col">
<strong>Downloaded: </strong>
{((icon.meta as ObjectType)?.downloadedTimes as number) ||
0}
</div>
</div>
<div className="ar-IconController__meta-item row lh-1-5">
<div className="col">
<strong>Liked: </strong>
{((icon.meta as ObjectType)?.downloadedTimes as number) ||
0}
</div>
</div>
<div className="ar-IconController__meta-item row lh-1-5">
<div className="col">
<strong>Size: </strong>
{(icon.meta as ObjectType)?.size as number}
</div>
</div>
</div>
)}
<div
className={`ar-IconController__controls-form ${
icon ? "col-8" : "col-12"
} border`}
>
<div className="row h-100 py-3">
<div className="ar-IconController__controls-form-container col-12 h-25">
<div className="row">
<div className="col">
<TextInput
type="slider"
label="Icon Size"
min={1}
max={30}
onChange={(e) =>
setSize((e.target as HTMLInputElement).value)
}
/>
</div>
<div className="col">
<Select
label="Unit"
onSelectionChanged={(e: { value: string }) =>
setUnit(e.value)
}
options={[
// @ts-ignore
{ label: "rem", value: "rem" },
// @ts-ignore
{ label: "px", value: "px" },
// @ts-ignore
{ label: "vh", value: "vh" },
]}
/>
</div>
</div>
</div>
{/* <Tags classes="col-12 h-100 px-0" label={name} /> */}
{icon && (
<Tags
// clickHandler={clickHandler}
classes="col-12 h-75 px-0"
label={name}
tags={Object.fromEntries(
(
icon.tags as Array<{ name: string; verified: string }>
).map((key) => [key.name, 1]),
)}
/>
)}
</div>
</div>
</div>
</div>
</div>
</div>
<Suggestions
classes="border mb-3"
suggestions={similarIcons}
title={`Icons Similar to ${name}`}
inSvgString
/>
<Suggestions
classes="border mb-3"
suggestions={similarIcons}
title="Trending Icons"
inSvgString
// suggestions={recentlyVisited}
/>
</div>
)
}
export default IconController

View File

@@ -1,3 +0,0 @@
import IconController from "./IconController"
export default IconController

View File

@@ -1,2 +0,0 @@
.ar-IconEditor {
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import IconEditor from "./IconEditor"
describe("IconEditor", () => {
it("renders without error", () => {
})
})

View File

@@ -1,46 +0,0 @@
import { useEffect, useState } from "react"
import { v4 as uuid } from "uuid"
import { IconEditorProps } from "@armco/types"
import { setIconStyles } from "../../pages/IconPage/IconPage.slice"
import { IconStyleSelector, useAppDispatch } from ".."
import "./IconEditor.component.scss"
const iconEditorTabs = [
{
label: "Icon",
id: uuid(),
},
{
label: "Background",
id: uuid(),
},
]
const IconEditor = (props: IconEditorProps): JSX.Element => {
const { layout } = props
const [iconStyles, setIconStylesState] = useState<{
fillColor?: string
strokeColor?: string
bgColor?: string
strokeWidth?: string
}>()
const dispatch = useAppDispatch()
useEffect(() => {
iconStyles && dispatch(setIconStyles(iconStyles))
}, [iconStyles, dispatch])
return (
<div className="ar-IconEditor h-100">
<div className="ar-IconEditor__config-form h-100 overflow-auto">
<IconStyleSelector
iconStyles={iconStyles}
setIconStyles={setIconStylesState}
layout={layout}
/>
</div>
</div>
)
}
export default IconEditor

View File

@@ -1,3 +0,0 @@
import IconEditor from "./IconEditor"
export default IconEditor

View File

@@ -1,12 +0,0 @@
.ar-IconMergeContainer {
svg {
width: 5rem;
height: 5rem;
margin-left: 2rem;
margin-right: 2rem;
path:hover {
outline: 1px dotted grey;
}
}
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import IconMergeContainer from "./IconMergeContainer"
describe("IconMergeContainer", () => {
it("renders without error", () => {
})
})

View File

@@ -1,153 +0,0 @@
import { useEffect, useRef } from "react"
import { ArrayType, IconMergeContainerProps } from "@armco/types"
import { DomHelper } from ".."
import "./IconMergeContainer.component.scss"
const iconTest = [
{
name: "CiAlignBottom",
icon: '<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><g id="Align_Bottom"><g><path d="M3.548,20.922h16.9a.5.5,0,0,0,0-1H3.548a.5.5,0,0,0,0,1Z"></path><path d="M9,18.919H6.565a2.5,2.5,0,0,1-2.5-2.5V5.578a2.5,2.5,0,0,1,2.5-2.5H9a2.5,2.5,0,0,1,2.5,2.5V16.419A2.5,2.5,0,0,1,9,18.919ZM6.565,4.078a1.5,1.5,0,0,0-1.5,1.5V16.419a1.5,1.5,0,0,0,1.5,1.5H9a1.5,1.5,0,0,0,1.5-1.5V5.578A1.5,1.5,0,0,0,9,4.078Z"></path><path d="M17.437,18.919H15a2.5,2.5,0,0,1-2.5-2.5V10.55A2.5,2.5,0,0,1,15,8.05h2.434a2.5,2.5,0,0,1,2.5,2.5v5.869A2.5,2.5,0,0,1,17.437,18.919ZM15,9.05a1.5,1.5,0,0,0-1.5,1.5v5.869a1.5,1.5,0,0,0,1.5,1.5h2.434a1.5,1.5,0,0,0,1.5-1.5V10.55a1.5,1.5,0,0,0-1.5-1.5Z"></path></g></g></svg>',
createdby: "Armco",
createdAt: "2023-10-14T20:46:51.541Z",
updatedAt: "2023-10-14T20:46:51.541Z",
meta: {
size: "772 bytes",
downloadTimes: 0,
favoriteTimes: 0,
},
group: "ci",
description: "",
tags: [
{
name: "Ci",
state: "verified",
},
{
name: "Align",
state: "verified",
},
{
name: "Bottom",
state: "verified",
},
{
name: "icon",
state: "verified",
},
{
name: "armco",
state: "verified",
},
],
},
{
name: "CiAlignCenterH",
icon: '<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><g id="Align_Center-H"><path d="M17.42,4.062H12.5v-.51a.5.5,0,0,0-1,0v.51H6.58a2.507,2.507,0,0,0-2.5,2.5V9a2.5,2.5,0,0,0,2.5,2.5H11.5v1H9.06A2.507,2.507,0,0,0,6.56,15v2.44a2.507,2.507,0,0,0,2.5,2.5H11.5v.51a.5.5,0,0,0,1,0v-.51h2.43a2.5,2.5,0,0,0,2.5-2.5V15a2.5,2.5,0,0,0-2.5-2.5H12.5v-1h4.92A2.5,2.5,0,0,0,19.92,9V6.562A2.507,2.507,0,0,0,17.42,4.062ZM11.5,18.942H9.06a1.511,1.511,0,0,1-1.5-1.5V15a1.5,1.5,0,0,1,1.5-1.5H11.5Zm0-8.44H6.58A1.5,1.5,0,0,1,5.08,9V6.562a1.5,1.5,0,0,1,1.5-1.5H11.5Zm3.43,3a1.5,1.5,0,0,1,1.5,1.5v2.44a1.5,1.5,0,0,1-1.5,1.5H12.5V13.5ZM18.92,9a1.5,1.5,0,0,1-1.5,1.5H12.5V5.062h4.92a1.5,1.5,0,0,1,1.5,1.5Z"></path></g></svg>',
createdby: "Armco",
createdAt: "2023-10-14T20:46:51.541Z",
updatedAt: "2023-10-14T20:46:51.541Z",
meta: {
size: "790 bytes",
downloadTimes: 0,
favoriteTimes: 0,
},
group: "ci",
description: "",
tags: [
{
name: "Ci",
state: "verified",
},
{
name: "Align",
state: "verified",
},
{
name: "Center",
state: "verified",
},
{
name: "H",
state: "verified",
},
{
name: "icon",
state: "verified",
},
{
name: "armco",
state: "verified",
},
],
},
{
name: "CiAlignCenterV",
icon: '<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><g id="Align_Center-V"><path d="M20.446,11.5h-.51V9.07a2.5,2.5,0,0,0-2.5-2.5h-2.43a2.5,2.5,0,0,0-2.5,2.5V11.5H11.5V6.58A2.5,2.5,0,0,0,9,4.08H6.566a2.5,2.5,0,0,0-2.5,2.5V11.5h-.52a.5.5,0,0,0,0,1h.52v4.92a2.5,2.5,0,0,0,2.5,2.5H9a2.5,2.5,0,0,0,2.5-2.5V12.5h1.01v2.43a2.5,2.5,0,0,0,2.5,2.5h2.43a2.5,2.5,0,0,0,2.5-2.5V12.5h.51A.5.5,0,0,0,20.446,11.5ZM10.5,17.42A1.5,1.5,0,0,1,9,18.92H6.566a1.5,1.5,0,0,1-1.5-1.5V12.5H10.5Zm0-5.92H5.066V6.58a1.5,1.5,0,0,1,1.5-1.5H9a1.5,1.5,0,0,1,1.5,1.5Zm8.44,3.43a1.5,1.5,0,0,1-1.5,1.5h-2.43a1.5,1.5,0,0,1-1.5-1.5V12.5h5.43Zm0-3.43h-5.43V9.07a1.5,1.5,0,0,1,1.5-1.5h2.43a1.5,1.5,0,0,1,1.5,1.5Z"></path></g></svg>',
createdby: "Armco",
createdAt: "2023-10-14T20:46:51.541Z",
updatedAt: "2023-10-14T20:46:51.541Z",
meta: {
size: "784 bytes",
downloadTimes: 0,
favoriteTimes: 0,
},
group: "ci",
description: "",
tags: [
{
name: "Ci",
state: "verified",
},
{
name: "Align",
state: "verified",
},
{
name: "Center",
state: "verified",
},
{
name: "V",
state: "verified",
},
{
name: "icon",
state: "verified",
},
{
name: "armco",
state: "verified",
},
],
},
]
const IconMergeContainer = (props: IconMergeContainerProps): JSX.Element => {
const { icons } = props
const svgRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
const childList: Array<SVGSVGElement> = []
iconTest?.forEach((icon) => {
const svgElement = DomHelper.svgStringToElement(icon.icon)
if (svgElement) {
childList.push(svgElement)
}
})
svgRef.current?.replaceChildren(...childList)
const paths = childList.reduce((acc: ArrayType, svgElement) => {
const pathList = svgElement.querySelectorAll("path")
console.log(pathList)
pathList.forEach((pathItem) => {
pathItem.setAttribute("draggable", "true")
pathItem.addEventListener("dragstart", (e) => console.log(e))
})
return acc.concat(Array.from(pathList) as any)
}, [])
;(paths[0] as any).parentElement?.replaceChildren(...paths)
}, [svgRef])
return <div className="ar-IconMergeContainer" ref={svgRef} />
}
export default IconMergeContainer

View File

@@ -1,3 +0,0 @@
import IconMergeContainer from "./IconMergeContainer"
export default IconMergeContainer

View File

@@ -1,3 +0,0 @@
.ar-IconStyleSelector {
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import IconStyleSelector from "./IconStyleSelector"
describe("IconStyleSelector", () => {
it("renders without error", () => {
})
})

View File

@@ -1,85 +0,0 @@
import { v4 as uuid } from "uuid"
import { IconStyles, IconStyleSelectorProps } from "@armco/types"
import { AdvancedColorPicker, Slider } from ".."
import "./IconStyleSelector.component.scss"
const fillColorId = uuid()
const strokeColorId = uuid()
const bgColorId = uuid()
const IconStyleSelector = (props: IconStyleSelectorProps): JSX.Element => {
const { layout, setIconStyles } = props
const setIconStylesLocal = (propName: string, propValue: string) => {
console.log(propName)
setIconStyles((currentIconStyles: IconStyles) => ({
...currentIconStyles,
[propName]: propValue,
}))
}
const fill = (
<AdvancedColorPicker
key="fill-color-selector"
id={fillColorId}
displaySample
onColorSelect={(color) => setIconStylesLocal("fillColor", color)}
title="Fill Color"
/>
)
const strokeWidth = (
<Slider
containerClasses="py-2 border-top border-bottom mb-4"
label="Stroke Width"
min={1}
max={10}
onChange={(e) => setIconStylesLocal("strokeWidth", e.target.value)}
withManual
/>
)
const stroke = (
<AdvancedColorPicker
key="stoke-color-selector"
id={strokeColorId}
displaySample
onColorSelect={(color) => setIconStylesLocal("strokeColor", color)}
title="Stroke Color"
/>
)
const bg = (
<AdvancedColorPicker
key="bg-color-selector"
id={bgColorId}
displaySample
onColorSelect={(color) => setIconStylesLocal("bgColor", color)}
title="Background Color"
/>
)
return (
<div className="ar-IconStyleSelector p-3 w-100 overflow-auto">
{layout === "horizontal" ? (
<>
<div className="d-flex">
<div className="px-3">{fill}</div>
<div className="px-3">{stroke}</div>
</div>
<div className="row">
<div className="col">{strokeWidth}</div>
</div>
</>
) : (
<>
{fill}
{strokeWidth}
{stroke}
{bg}
</>
)}
</div>
)
}
export default IconStyleSelector

View File

@@ -1,3 +0,0 @@
import IconStyleSelector from "./IconStyleSelector"
export default IconStyleSelector

View File

@@ -1,67 +0,0 @@
.ar-IconsList {
.ar-IconsList__header-pagination-search-upload {
background-color: var(--ar-bg);
color: var(--ar-colora);
}
.ar-IconList__selection-manager {
background-color: var(--ar-bg);
color: var(--ar-colora);
transition: all 0.3s;
&.hide {
height: 0;
overflow: hidden;
padding: 0 !important;
border: none !important;
}
}
.ar-IconsList__icon-tile-container {
background-color: var(--ar-bg);
color: var(--ar-color);
}
.ar-IconTile {
cursor: pointer;
width: 7rem;
height: 7rem;
transition: width 0.3s, padding 0.3s;
&.compact {
width: 4rem;
height: 4rem;
}
&.list {
width: 8rem;
height: 2rem;
}
&:hover {
background-color: var(--ar-bg-hover);
}
footer {
height: 1.9rem;
border-top: 1px solid var(--bs-border-color);
background-color: var(--ar-bg-base);
}
}
.ar-IconsList__upload-button {
background-color: orange;
border-color: orange;
}
.ar-IconsList__color-palette {
border-radius: 50%;
}
@media screen and (max-width: 576px) {
.ar-IconTile {
width: 3rem;
height: 3rem;
}
}
}

View File

@@ -1,5 +0,0 @@
import IconsList from "./IconsList"
describe("IconsList", () => {
it("renders without error", () => {})
})

View File

@@ -1,430 +0,0 @@
import { ChangeEvent, useEffect, useState } from "react"
import { useNavigate } from "react-router-dom"
import { v4 as uuid } from "uuid"
import {
ArButtonVariants,
ArIconTileTypes,
ArLoaderTypes,
ArPopoverSlots,
ArPopoverTriggers,
ArSizes,
FunctionType,
IconResponse,
IconStyles,
IconTileProps,
IconsListProps,
ObjectType,
SearchItem,
SegmentData,
} from "@armco/types"
import {
getFavorites,
getSelectedTag,
removeFavorite,
setFavorites,
} from "../../pages/IconsPage/IconsPage.slice"
import {
getIconStyles,
setIconStyles,
} from "../../pages/IconPage/IconPage.slice"
import {
API_CONFIG,
Button,
ENDPOINTS,
Helper,
IconStyleSelector,
IconTile,
LoadableIcon,
Loader,
Network,
notify,
Pagination,
Pillbox,
Popover,
SearchField,
SegmentedControl,
setRightPanelContent,
useAppDispatch,
useAppSelector,
} from ".."
import "./IconsList.component.scss"
const fetchIconsPage = (
limit: number,
from: number,
filters: ObjectType,
dataSetter: FunctionType,
pageSetter: FunctionType,
setLoading: FunctionType,
) => {
const pageApi =
API_CONFIG.STATIC_HOST[process.env.NODE_ENV] +
ENDPOINTS.STATIC.ICON.ROOT +
ENDPOINTS.STATIC.ICON.PAGE
const queryParams: ObjectType = { pageSize: limit, from, ...filters }
Network.get(pageApi, queryParams)
.then((response) => {
if (response && response.status === 200) {
if (response.body && dataSetter) {
dataSetter(response.body)
pageSetter(response.body.slice(0, 100))
}
}
setLoading(false)
})
.catch((error) => {
console.error(error)
setLoading(false)
})
}
const IconsList = (props: IconsListProps): JSX.Element => {
const { variant } = props
const [searchText, setSearchText] = useState<string | undefined>()
const [icons, setIcons] = useState<Array<IconResponse>>()
const [page, setPage] = useState<Array<IconResponse>>()
const [view, setView] = useState<SegmentData>()
const [loading, setLoading] = useState<boolean>()
const [isSelectMode, toggleSelectMode] = useState<boolean>()
const [selectedIcons, setSelectedIcons] = useState<Array<IconResponse>>()
const dispatch = useAppDispatch()
const favorites = useAppSelector(getFavorites)
const selectedTag = useAppSelector<string | undefined>(getSelectedTag)
const iconStyles = useAppSelector<IconStyles | undefined>(getIconStyles)
const navigate = useNavigate()
useEffect(() => {
setView({ name: ArIconTileTypes.COMFY })
}, [])
useEffect(() => {
if (favorites) {
dispatch(
setRightPanelContent(
favorites.length > 0 ? { name: "FavoritesList" } : { name: "" },
),
)
}
}, [favorites, dispatch])
useEffect(() => {
const filters: ObjectType = {}
if (selectedTag) {
filters.tags = selectedTag
}
if (searchText) {
filters.search = searchText
}
setLoading(true)
fetchIconsPage(2000, 0, filters, setIcons, setPage, setLoading)
}, [selectedTag, searchText])
const onIconTileClick = (iconProps: IconResponse) => {
isSelectMode
? setSelectedIcons((currentIcons) => {
currentIcons = [...(currentIcons || [])]
const existingIconIndex = currentIcons?.findIndex(
(currentIcon) => currentIcon.name === iconProps.name,
)
if (existingIconIndex > -1) {
currentIcons.splice(existingIconIndex, 1)
} else {
currentIcons.push(iconProps)
}
return currentIcons
})
: view?.name === ArIconTileTypes.COMPACT &&
dispatch(
notify({
show: true,
message: `Icon link for ${iconProps.name} copied`,
uid: uuid(),
}),
)
}
return (
<div className="ar-IconsList h-100 w-100 overflow-auto position-relative d-flex flex-column">
{!icons && (
<Loader label="Loading Icons..." type={ArLoaderTypes.SHAPES} />
)}
<div className="ar-IconsList__header-pagination-search-upload py-2 px-3 mb-2 border">
<div className="row">
<div className="col d-none d-md-flex align-items-center">
<h6 className="mb-0 h-100 flex-center px-3 border-right">
<Button
variant={ArButtonVariants.LINK}
content="Categories"
size={ArSizes.SMALL}
splitOptions={[
{
label: "All",
},
{
label: "Business",
},
{
label: "Medical",
},
]}
/>
</h6>
</div>
<span className="col flex-v-center justify-content-end">
<Popover version="v2">
<LoadableIcon
classes="ar-IconsList__color-palette cursor-pointer ms-auto hover-shadow me-3"
icon="io/IoIosColorPalette"
color="orange"
slot={ArPopoverSlots.ANCHOR}
size="2rem"
onClick={() => {}}
/>
<IconStyleSelector
slot={ArPopoverSlots.POPOVER}
setIconStyles={(iconStylesUpdater) => {
const updatedStyles = iconStylesUpdater(iconStyles)
dispatch(setIconStyles(updatedStyles))
}}
layout="horizontal"
/>
</Popover>
<Button
classes="h-100 float-end me-3"
content="Create"
size={ArSizes.SMALL}
variant={ArButtonVariants.LINKHOVEREFFECT}
preIcon="io5/IoCreateOutline"
onClick={() => toggleSelectMode(true)}
/>
<Button
classes="ar-IconsList__upload-button h-100 float-end"
content="Upload"
size={ArSizes.SMALL}
variant={ArButtonVariants.PRIMARY}
/>
<SegmentedControl
classes="d-none d-sm-inline ms-3"
hasUniformSegments
segments={[
{
name: "comfy",
icon: "md/MdViewComfy",
tooltip: "View Comfortable",
},
{
name: "compact",
icon: "md/MdViewCompact",
tooltip: "View Compact",
},
{
name: "list",
icon: "io5/IoList",
tooltip: "Quick Peak",
},
]}
onChange={(view) => setView(view as SegmentData)}
/>
</span>
<div className="col d-none d-md-flex flex-v-center">
<SearchField
classes="bg-white"
placeholder="Search by name, tags, description"
onChange={Helper.debounce(
(event: ChangeEvent<HTMLInputElement>) =>
setSearchText(event.target.value),
1000,
)}
items={
icons
? icons.map(
(icon): SearchItem => ({
label: icon.name,
data: icon,
}),
)
: []
}
hidePopup
/>
</div>
</div>
</div>
<div
className={`ar-IconList__selection-manager d-flex${
isSelectMode ? " mb-2 py-2 px-3 border" : " hide"
}`}
>
{selectedIcons && selectedIcons.length > 0 && (
<Pillbox
data={(selectedIcons || []).map((icon) => ({
label: icon.name,
deletable: true,
}))}
onChange={() => {}}
/>
)}
<div className="ms-auto d-flex">
<Button
classes="me-3"
content="Cancel"
variant={ArButtonVariants.SECONDARY}
size={ArSizes.SMALL}
onClick={() => {
toggleSelectMode(false)
setSelectedIcons(undefined)
}}
/>
<Button
content="Done"
variant={ArButtonVariants.PRIMARY}
size={ArSizes.SMALL}
onClick={() =>
navigate("/icons/merge-icons", { state: selectedIcons })
}
disabled={!selectedIcons || selectedIcons.length === 0}
/>
</div>
</div>
{page && (
<div className="ar-IconsList__icon-tile-container-wrapper d-flex flex-grow-1 justify-content-between">
<div
className="ar-IconsList__icon-tile-container py-2 px-3 border d-grid w-100"
style={{
gridTemplateColumns: "repeat(10, minmax(8rem, auto))",
gridAutoRows: "max(8rem)",
}}
>
{loading && (
<Loader label="Applying filters..." type={ArLoaderTypes.CIRCLE} />
)}
{page.map((icon, index) => (
<Popover
trigger={ArPopoverTriggers.HOVER}
key={"icon-tile-popover-" + index}
classes="mb-3"
version="v2"
>
{!view || view.name !== ArIconTileTypes.LIST ? (
<span slot={ArPopoverSlots.POPOVER}>
<b className="d-block">{icon.group}</b>
{icon.name
?.match(/[A-Z][a-z]+/g)
?.slice(1)
.join(" ")}
</span>
) : null}
<IconTile
type={
view && view.name
? (view.name as ArIconTileTypes)
: ArIconTileTypes.COMFY
}
hideBorder={view?.name !== ArIconTileTypes.LIST}
classes={view?.name}
slot={ArPopoverSlots.ANCHOR}
key={icon + "-" + index}
icon={icon}
iconSize={
view?.name === ArIconTileTypes.LIST ? "1rem" : "2rem"
}
onClick={onIconTileClick}
tools={[
{
iconProps: {
icon: "sl.SlOptions",
color: "white",
hoverColor: "lightblue",
},
children: [
{
iconProps: {
icon: "io.IoIosShareAlt",
hoverColor: "lightblue",
},
name: "share",
onClick: () => {},
},
],
name: "more-options",
onClick: () => {},
},
{
iconProps: {
icon: "ai/AiFillLike",
color: "white",
hoverColor: "lightblue",
},
name: "like",
onClick: () => {},
},
{
iconProps: {
icon: "md/MdFavorite",
hoverColor: "lightblue",
toggleColor: "red",
color: "white",
toggled: false,
},
name: "favorite",
onClick: () => {
const favorite = favorites.find(
(searchedIcon: IconTileProps) =>
icon.name === searchedIcon.icon.name,
)
if (!favorites || !favorite) {
dispatch(setFavorites({ icon }))
dispatch(
notify({
message: "Icon Added to your favorites",
show: true,
uid: uuid(),
}),
)
} else {
dispatch(removeFavorite({ icon }))
dispatch(
notify({
message: "Icon removed from your favorites",
show: true,
uid: uuid(),
}),
)
}
},
},
]}
hideFooter={
(view && (view.name as ArIconTileTypes)) !==
ArIconTileTypes.LIST
}
selectable={isSelectMode}
fillColor={iconStyles?.fillColor}
strokeColor={iconStyles?.strokeColor}
strokeWidth={iconStyles?.strokeWidth}
/>
</Popover>
))}
</div>
</div>
)}
{/* {slices && ( */}
<Pagination
classes="my-3 flex-center"
data={icons}
maxPillsToShow={5}
pageSetter={setPage}
// trigger={ArPageTriggers.SCROLL}
count={1}
load={100}
dataFetcher={(load, count) =>
fetchIconsPage(load, count, {}, setIcons, setPage, setLoading)
}
/>
{/* )} */}
</div>
)
}
export default IconsList

View File

@@ -1,3 +0,0 @@
import IconsList from "./IconsList"
export default IconsList

View File

@@ -1,3 +0,0 @@
.ar-LandingContent {
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import LandingContent from "./LandingContent"
describe("LandingContent", () => {
it("renders without error", () => {
})
})

View File

@@ -1,87 +0,0 @@
import { ArPlacement, ArThemes, LandingContentProps } from "@armco/types"
import {
Hero,
ProductDescriptionTile,
getCurrentTheme,
useAppSelector,
} from ".."
import "./LandingContent.component.scss"
const toolsAndProducts = [
{
name: "Icons & Fonts",
description: [
"Dive into a world of creativity with our diverse icon assets and fonts.",
],
image: "",
imagePlacement: ArPlacement.RIGHT,
},
{
name: "Task Manager and Config Manager",
description: [
"Streamline project management and configuration tasks effortlessly.",
"Master project management and configurations with our intuitive tools.",
],
image: "",
imagePlacement: ArPlacement.RIGHT,
},
{
name: "Components Library",
description: [
"Indulge in Unmatched Creativity with Our Opinionated and highly customizable Component Library",
"Discover a component library like no other, meticulously crafted and opinionated for optimal performance. Our library isn't just about components; it's a playground for creators. Immerse yourself in the richness of our carefully curated elements, each designed with a distinct perspective on aesthetics and functionality.",
"Explore a library that doesn't just follow trends but sets them. Elevate your projects with components that speak your language, and let your creativity soar in a space that celebrates individuality. Welcome to a component library where opinion meets innovation, and your designs become a masterpiece.",
],
image: "",
imagePlacement: ArPlacement.RIGHT,
},
{
name: "Analytics Collector and Viewer",
description: [
"Showcase the analytics capabilities for data-driven decisions.",
"Turn data into insights with our integrated analytics tools.",
],
image: "",
imagePlacement: ArPlacement.RIGHT,
},
{
name: "IAM",
description: [
"Explain the identity and access management features.",
"Manage identities and access with ease using our robust IAM features.",
],
image: "",
imagePlacement: ArPlacement.RIGHT,
},
{
name: "APIs",
description: [
"Highlight the flexibility of APIs for integration into various workflows.",
"Harness the flexibility of APIs for seamless integration into your development workflow.",
],
image: "",
imagePlacement: ArPlacement.RIGHT,
},
]
const LandingContent = (props: LandingContentProps): JSX.Element => {
const theme = useAppSelector<ArThemes>(getCurrentTheme)
return (
<div className="ar-LandingContent">
<Hero classes="mb-3" theme={theme} />
<div className="ar-LandingContent__tiles-container container">
{toolsAndProducts.map((item) => {
return (
<div className="row">
<div className="col">
<ProductDescriptionTile classes="mb-3" {...item} />
</div>
</div>
)
})}
</div>
</div>
)
}
export default LandingContent

View File

@@ -1,3 +0,0 @@
import LandingContent from "./LandingContent"
export default LandingContent

View File

@@ -1,10 +0,0 @@
.ar-LottieEditor {
.ar-LottieEditor__header-download {
background-color: var(--ar-bg);
color: var(--ar-colora);
}
.ar-LottieEditor__lottie-preview {
background-color: var(--ar-bg);
color: var(--ar-color);
}
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import LottieEditor from "./LottieEditor"
describe("LottieEditor", () => {
it("renders without error", () => {
})
})

View File

@@ -1,111 +0,0 @@
import { useEffect, useState } from "react"
import { useLocation } from "react-router-dom"
import { Controls, Player } from "@lottiefiles/react-lottie-player"
import { Animation, ArButtonVariants, LottieEditorProps } from "@armco/types"
import {
API_CONFIG,
Button,
ENDPOINTS,
Helper,
LottieHelper,
Network,
} from ".."
import "./LottieEditor.component.scss"
const LottieEditor = (props: LottieEditorProps): JSX.Element => {
const { selectedPresets } = props
const location = useLocation()
const icon = location.state?.icon
const iconStyles = location.state?.iconStyles
const [lottieJson, setLottieJson] = useState<Animation>()
useEffect(() => {
const base = LottieHelper.renderStaticImage(icon, iconStyles)
if (!selectedPresets || selectedPresets.length === 0) {
setLottieJson(base)
} else {
selectedPresets.forEach(
(preset) =>
preset.property &&
preset.raw &&
LottieHelper.injectAnimationInBaseLottieBasic({
property: preset.property,
juice: preset.raw,
base,
}),
)
setLottieJson(base)
}
}, [selectedPresets])
return (
<div className="ar-LottieEditor h-100 w-100 overflow-auto position-relative d-flex flex-column">
<div className="ar-LottieEditor__header-download py-2 px-3 mb-2 border">
<div className="row">
<div className="col d-none d-md-flex align-items-center">
<Button
classes="ms-auto h-100"
content="Download"
variant={ArButtonVariants.WARNING}
splitOptions={[
{
label: "MP4",
onClick: () => {
Network.post(
API_CONFIG.STATIC_HOST[process.env.NODE_ENV] +
ENDPOINTS.STATIC.LOTTIE.ROOT +
ENDPOINTS.STATIC.LOTTIE.TOMP4,
{ lottie: lottieJson, name: location.state?.name },
).then((res) => {
if (res.status === 200) {
Helper.download(res.body, location.state?.name + ".mp4")
}
})
},
},
{
label: "GIF",
onClick: () => {
Network.post(
API_CONFIG.STATIC_HOST[process.env.NODE_ENV] +
ENDPOINTS.STATIC.LOTTIE.ROOT +
ENDPOINTS.STATIC.LOTTIE.TOGIF,
{ lottie: lottieJson, name: location.state?.name },
).then((res) => {
if (res.status === 200) {
Helper.download(res.body, location.state?.name + ".gif")
}
})
},
},
{
label: "PNG",
},
{
label: "JSON",
},
]}
/>
</div>
</div>
</div>
<div className="ar-LottieEditor__lottie-preview-wrapper d-flex flex-grow-1 justify-content-between">
<div className="ar-LottieEditor__lottie-preview py-2 px-3 border flex-center w-100">
<Player
autoplay
loop
src={lottieJson as object}
style={{ height: "300px", width: "300px" }}
>
<Controls
visible={true}
buttons={["play", "repeat", "frame", "debug"]}
/>
</Player>
</div>
</div>
</div>
)
}
export default LottieEditor

View File

@@ -1,3 +0,0 @@
import LottieEditor from "./LottieEditor"
export default LottieEditor

View File

@@ -1,18 +0,0 @@
.ar-LottieEditorTools {
width: calc(20rem + 2px + 1rem); // add border and padding
.ar-LottieEditorTools__header__close-button {
font-size: 0.7rem !important;
color: rgb(154, 92, 92) !important;
transition: color 0.4s;
&:hover {
color: rgb(242, 51, 51) !important;
}
}
.ar-LottieEditorTools__tab-content {
height: calc(100% - 2rem - 1px - 8px); // subtract header height, bottom border and bottom margin
overflow: auto;
}
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import LottieEditorTools from "./LottieEditorTools"
describe("LottieEditorTools", () => {
it("renders without error", () => {
})
})

View File

@@ -1,336 +0,0 @@
import { ReactNode, useEffect, useState } from "react"
import { v4 as uuid } from "uuid"
import {
AccordionExpansionPanelProps,
ArAccordionStyles,
ArAccordionVariants,
ArAnimationProperty,
ArButtonVariants,
ArSizes,
ArTabType,
LottieEditorToolsProps,
PresetDefinition,
TabProps,
} from "@armco/types"
import {
Accordion,
Button,
fadeOut,
LottieHelper,
LottieTile,
Pillbox,
scaleDown,
scaleUp,
slideInFromTop,
slideInFromLeft,
setRightPanelContent,
slideOutToLeft,
slideOutToTop,
slideInFromBottom,
slideOutToBottom,
slideInFromRight,
slideOutToRight,
fadeIn,
rotateClockWise,
rotateAntiClockWise,
TabBar,
useAppDispatch,
} from ".."
import "./LottieEditorTools.component.scss"
const editorToolsTabItems = [
{
label: "Customize",
id: uuid(),
},
{
label: "Presets",
id: uuid(),
isActive: true,
},
]
export const presets: Array<{
id: string
title: string
items: Array<PresetDefinition>
content: ReactNode
}> = [
{
id: uuid(),
title: "Slide In",
content: null,
items: [
{
name: "slideinfromleft",
from: "left",
raw: slideInFromLeft,
property: ArAnimationProperty.POSITION,
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
property: ArAnimationProperty.POSITION,
juice: slideInFromLeft,
}),
},
{
name: "slideinfromright",
from: "right",
raw: slideInFromRight,
property: ArAnimationProperty.POSITION,
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
property: ArAnimationProperty.POSITION,
juice: slideInFromRight,
}),
},
{
name: "slideinfromtop",
from: "top",
raw: slideInFromTop,
property: ArAnimationProperty.POSITION,
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
property: ArAnimationProperty.POSITION,
juice: slideInFromTop,
}),
},
{
name: "slideinfrombottom",
from: "bottom",
raw: slideInFromBottom,
property: ArAnimationProperty.POSITION,
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
property: ArAnimationProperty.POSITION,
juice: slideInFromBottom,
}),
},
],
},
{
id: uuid(),
title: "Slide Out",
content: null,
items: [
{
name: "slideouttoleft",
to: "left",
raw: slideOutToLeft,
property: ArAnimationProperty.POSITION,
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
property: ArAnimationProperty.POSITION,
juice: slideOutToLeft,
}),
},
{
name: "slideouttoright",
to: "right",
raw: slideOutToRight,
property: ArAnimationProperty.POSITION,
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
property: ArAnimationProperty.POSITION,
juice: slideOutToRight,
}),
},
{
name: "slideouttotop",
to: "top",
raw: slideOutToTop,
property: ArAnimationProperty.POSITION,
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
property: ArAnimationProperty.POSITION,
juice: slideOutToTop,
}),
},
{
name: "slideouttobottom",
to: "bottom",
raw: slideOutToBottom,
property: ArAnimationProperty.POSITION,
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
property: ArAnimationProperty.POSITION,
juice: slideOutToBottom,
}),
},
],
},
{
id: uuid(),
title: "Fade",
content: null,
items: [
{
name: "fadein",
raw: fadeIn,
property: ArAnimationProperty.OPACITY,
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
property: ArAnimationProperty.OPACITY,
juice: fadeIn,
}),
},
{
name: "fadeout",
raw: fadeOut,
property: ArAnimationProperty.OPACITY,
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
property: ArAnimationProperty.OPACITY,
juice: fadeOut,
}),
},
],
},
{
id: uuid(),
title: "Rotate",
content: null,
items: [
{
name: "rotateClockWise",
raw: rotateClockWise,
property: ArAnimationProperty.ROTATE,
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
property: ArAnimationProperty.ROTATE,
juice: rotateClockWise,
}),
},
{
name: "rotateAntiClockWise",
raw: rotateAntiClockWise,
property: ArAnimationProperty.ROTATE,
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
property: ArAnimationProperty.ROTATE,
juice: rotateAntiClockWise,
}),
},
],
},
{
id: uuid(),
title: "Scale",
content: null,
items: [
{
name: "expand",
raw: scaleUp,
property: ArAnimationProperty.SCALE,
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
property: ArAnimationProperty.SCALE,
juice: scaleUp,
}),
},
{
name: "shrink",
raw: scaleDown,
property: ArAnimationProperty.SCALE,
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
property: ArAnimationProperty.SCALE,
juice: scaleDown,
}),
},
],
},
// {
// name: "slidein",
// lottie: LottieHelper.injectAnimationInBaseLottieBasic({
// property: "",
// juice: slideInFromLeft,
// }),
// },
// {
// name: "slidein",
// lottie: LottieHelper.injectAnimationInBaseLottieBasic({
// property: "",
// juice: slideInFromLeft,
// }),
// },
// {
// name: "slidein",
// lottie: LottieHelper.injectAnimationInBaseLottieBasic({
// property: "",
// juice: slideInFromLeft,
// }),
// },
// {
// name: "slidein",
// lottie: LottieHelper.injectAnimationInBaseLottieBasic({
// property: "",
// juice: slideInFromLeft,
// }),
// },
// {
// name: "slidein",
// lottie: LottieHelper.injectAnimationInBaseLottieBasic({
// property: "",
// juice: slideInFromLeft,
// }),
// },
]
const LottieEditorTools = (props: LottieEditorToolsProps): JSX.Element => {
const { setSelectedPresets } = props
const [lottieTools, setLottieTools] = useState<
Array<AccordionExpansionPanelProps>
>([])
const dispatch = useAppDispatch()
const [activeTab, setActiveTab] = useState<TabProps>(editorToolsTabItems[1])
useEffect(() => {
setLottieTools([
{
id: uuid(),
title: "Effects",
content: <Pillbox data={[]} onChange={() => {}} />,
},
])
}, [])
presets.forEach(
(preset) =>
(preset.content = (
<div className="py-2">
{preset.items.map((preset, index) => (
<LottieTile
key={"lottie-preset-" + index}
preset={preset as PresetDefinition}
setSelectedPresets={setSelectedPresets}
/>
))}
</div>
)),
)
return (
<div className="ar-LottieEditorTools h-100 px-2">
<h6 className="ar-LottieEditorTools__header border-bottom d-flex">
<TabBar
classes="ar-LottieEditorTools__header__tab-bar"
data={editorToolsTabItems}
variant={ArTabType.MINIMAL}
onTabSelected={(id, data) => setActiveTab(data)}
/>
<Button
classes="ar-LottieEditorTools__header__close-button ms-auto"
size={ArSizes.SMALL}
variant={ArButtonVariants.LINK}
content="Close"
onClick={() => dispatch(setRightPanelContent({ name: "" }))}
/>
</h6>
<div className="ar-LottieEditorTools__tab-content">
{activeTab?.id === editorToolsTabItems[1].id ? (
<Accordion
data={presets}
appearance={ArAccordionStyles.STACKED}
variant={ArAccordionVariants.LIMITED}
firstExpanded
/>
) : (
<Accordion
data={lottieTools}
appearance={ArAccordionStyles.STACKED}
variant={ArAccordionVariants.LIMITED}
firstExpanded
/>
)}
</div>
</div>
)
}
export default LottieEditorTools

View File

@@ -1,3 +0,0 @@
import LottieEditorTools from "./LottieEditorTools"
export default LottieEditorTools

View File

@@ -1,3 +0,0 @@
.ar-LottieLayers {
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import LottieLayers from "./LottieLayers"
describe("LottieLayers", () => {
it("renders without error", () => {
})
})

View File

@@ -1,32 +0,0 @@
import {
ArButtonVariants,
ArPopoverSlots,
ArPopoverTriggers,
ArSizes,
LottieLayersProps,
} from "@armco/types"
import { Button, Popover } from ".."
import TreeList from "../atoms/TreeList"
import "./LottieLayers.component.scss"
const LottieLayers = (props: LottieLayersProps): JSX.Element => {
return (
<div className="ar-LottieLayers py-1 px-2 w-100">
{/* <Popover trigger={ArPopoverTriggers.HOVER}>
<Button
slot={ArPopoverSlots.ANCHOR}
content="Add"
variant={ArButtonVariants.SUCCESS}
size={ArSizes.XSMALL}
/>
<div slot={ArPopoverSlots.POPOVER}>
Add a new layer or one of the lottie configurations from the panel
towards the right
</div>
</Popover> */}
<TreeList data={[]} noSearch />
</div>
)
}
export default LottieLayers

View File

@@ -1,3 +0,0 @@
import LottieLayers from "./LottieLayers"
export default LottieLayers

View File

@@ -1,25 +0,0 @@
.ar-Main {
.ar-Drawer {
transition: width 0.3s;
width: 15%;
&.collapsed {
width: 3.5rem;
}
}
@media screen and (max-width: 576px) {
.ar-Drawer {
position: fixed !important;
left: 0;
height: 100%;
width: 75%;
z-index: 1;
background-color: var(--ar-bg);
&.collapsed {
width: 0;
}
}
}
}

View File

@@ -1,5 +0,0 @@
import Main from "./Main"
describe("Main", () => {
it("renders without error", () => {})
})

View File

@@ -1,53 +0,0 @@
import { ArPlacement, MainProps } from "@armco/types"
import { ErrorBoundary, SidePanel, Content, Drawer } from ".."
import "./Main.component.scss"
const Main = (props: MainProps): JSX.Element => {
const {
contentClasses,
drawerContent,
mainContent,
hideSidepanelCloseButton,
rightPanelContent,
leftPanelContent,
rightPanelHeader,
leftPanelHeader,
} = props
return (
<main className="ar-Main d-flex flex-grow-1 w-100">
{drawerContent && (
<Drawer classes="d-flex h-100" isCollapsible>
{drawerContent}
</Drawer>
)}
<SidePanel
key="left-panel"
header={leftPanelHeader || "Header Name"}
placement={ArPlacement.LEFT}
componentName={leftPanelContent ? leftPanelContent.name : ""}
componentProps={leftPanelContent ? leftPanelContent.props : {}}
component={leftPanelContent ? leftPanelContent.component : null}
hideCloseButton={hideSidepanelCloseButton}
/>
<ErrorBoundary>
<Content
classes={`flex-center flex-grow-1 position-relative overflow-auto${
contentClasses ? " " + contentClasses : ""
}`}
>
{mainContent}
</Content>
</ErrorBoundary>
<SidePanel
key="right-panel"
header={rightPanelHeader}
componentName={rightPanelContent ? rightPanelContent.name : ""}
componentProps={rightPanelContent ? rightPanelContent.props : {}}
component={rightPanelContent ? rightPanelContent.component : null}
hideCloseButton={hideSidepanelCloseButton}
/>
</main>
)
}
export default Main

View File

@@ -1,3 +0,0 @@
import Main from "./Main"
export default Main

View File

@@ -1,9 +0,0 @@
.ar-NamespaceInfoBox {
.ar-NamespaceInfoBox__namespace-link {
border: var(--ar-border);
background-color: var(--ar-bg-hover);
border-radius: 3px;
font-size: 0.75rem;
cursor: pointer;
}
}

View File

@@ -1,8 +0,0 @@
import React from "react"
import NamespaceInfoBox from "./NamespaceInfoBox"
describe("NamespaceInfoBox", () => {
it("renders without error", () => {
})
})

View File

@@ -1,128 +0,0 @@
import { useRef, useState } from "react"
import { v4 as uuid } from "uuid"
import hljs from "highlight.js/lib/core"
import javascript from "highlight.js/lib/languages/javascript"
import { NamespaceInfoBoxProps } from "@armco/types"
import {
Alert,
API_CONFIG,
ENDPOINTS,
Helper,
Link,
LoadableIcon,
Text,
} from ".."
import "highlight.js/styles/github.css"
import "./NamespaceInfoBox.component.scss"
hljs.registerLanguage("javascript", javascript)
const NamespaceInfoBox = (props: NamespaceInfoBoxProps): JSX.Element => {
const { namespace } = props
const codeRef = useRef<HTMLPreElement>(null)
const [displayed, show] = useState<{ displayed: boolean }>()
const [message, setMessage] = useState<string>()
const helpText =
"Now that your namespace has been created you can integrate it with your project using the below endpoint"
const nsLink =
API_CONFIG.CONFIG[process.env.NODE_ENV] +
ENDPOINTS.NAMESPACE.ROOT +
ENDPOINTS.NAMESPACE.FETCH +
namespace._id
const nsAllLink =
API_CONFIG.CONFIG[process.env.NODE_ENV] +
ENDPOINTS.NAMESPACE.ROOT +
ENDPOINTS.NAMESPACE.FETCHALL
const codeStr = `
// API to fetch configurations for the namespace ${namespace.name}
fetch("${nsLink}")
.then((res) => {
if (res.status === 200) {
console.log(res.body.namespace);
}
})
`
const code = hljs.highlight("javascript", codeStr).value
return (
<div className="ar-NamespaceInfoBox w-100">
<div className="ar-NamespaceInfoBox__content d-flex flex-1 flex-column h-100">
<header className="ar-NamespaceInfoBox__header w-100 border-bottom px-3 py-2 fw-bold f4 flex-v-center">
{namespace.name}
</header>
<main className="ar-NamespaceInfoBox__body flex-1 overflow-auto">
<div className="ar-NameSpaceModal__body p-3 h-100 flex-center flex-column">
<Text
classes="ar-help-text mb-3 w-100 text-wrap"
descriptor={{
id: "temp-text-id",
order: 0,
name: "Text",
text: helpText,
chunks: {},
}}
/>
<Link
to={nsLink}
label={nsLink}
classes="ar-NamespaceInfoBox__namespace-link px-3 py-2 mb-3"
onClick={() => {
Helper.copyOrPrompt(nsLink, () => {
show({ displayed: true })
setMessage("Link Copied!")
})
}}
preemptNavigation
/>
<small className="ar-help-text mb-3">
Or to fetch all of your namespaces
</small>
<Link
to={nsAllLink}
label={nsAllLink}
classes="ar-NamespaceInfoBox__namespace-link px-3 py-2 mb-3"
onClick={() => {
Helper.copyOrPrompt(nsAllLink, () => {
show({ displayed: true })
setMessage("Link Copied!")
})
}}
preemptNavigation
/>
<div className="ar-NamespaceInfoBox__sample-code border p-2 w-100 overflow-auto position-relative">
<LoadableIcon
classes="ar-NamespaceInfoBox__copy-button position-absolute"
icon="md/MdContentCopy"
color="grey"
hoverColor="black"
onClick={() => {
const code = codeRef.current?.innerText
Helper.copyOrPrompt(code, () => {
show({ displayed: true })
setMessage(
"Copied code for integrating your configurations",
)
})
}}
/>
<pre>
<code
dangerouslySetInnerHTML={{ __html: code }}
ref={codeRef}
/>
</pre>
</div>
</div>
</main>
<Alert
show={displayed?.displayed}
message={message || "Link Copied!"}
uid={uuid()}
closeable
/>
</div>
</div>
)
}
export default NamespaceInfoBox

View File

@@ -1,3 +0,0 @@
import NamespaceInfoBox from "./NamespaceInfoBox"
export default NamespaceInfoBox

Some files were not shown because too many files have changed in this diff Show More