@moraby/app-launcher 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/dist/index.d.mts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +94 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,34 @@ yarn add @moraby/app-launcher
|
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
|
+
### Quick Start (Zero-Config)
|
|
16
|
+
|
|
17
|
+
Use the smart component that handles local storage and settings automatically:
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { LocalAppLauncher } from '@moraby/app-launcher';
|
|
21
|
+
import '@moraby/app-launcher/styles.css';
|
|
22
|
+
|
|
23
|
+
function Header() {
|
|
24
|
+
return (
|
|
25
|
+
<header>
|
|
26
|
+
<h1>My App</h1>
|
|
27
|
+
<LocalAppLauncher />
|
|
28
|
+
</header>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This will automatically:
|
|
34
|
+
|
|
35
|
+
1. Show a Settings button in the footer
|
|
36
|
+
2. Save custom apps to browser's LocalStorage
|
|
37
|
+
3. Allow users to add/edit/delete apps immediately
|
|
38
|
+
|
|
39
|
+
### Custom Implementation (Advanced)
|
|
40
|
+
|
|
41
|
+
If you want full control over the state or to load from a remote URL:
|
|
42
|
+
|
|
15
43
|
### With Configuration URL
|
|
16
44
|
|
|
17
45
|
```tsx
|
|
@@ -76,6 +104,10 @@ function AdminLauncher() {
|
|
|
76
104
|
|
|
77
105
|
return (
|
|
78
106
|
<>
|
|
107
|
+
{/*
|
|
108
|
+
IMPORTANT: 'renderFooter' is required to show the Settings button!
|
|
109
|
+
Without it, the launcher will just show the apps list (which might be empty).
|
|
110
|
+
*/}
|
|
79
111
|
<AppLauncher
|
|
80
112
|
apps={apps}
|
|
81
113
|
renderFooter={() => <button onClick={() => setShowSettings(true)}>Settings</button>}
|
|
@@ -96,6 +128,8 @@ function AdminLauncher() {
|
|
|
96
128
|
}
|
|
97
129
|
```
|
|
98
130
|
|
|
131
|
+
**Note:** If you forget `renderFooter`, there will be no button to open the settings!
|
|
132
|
+
|
|
99
133
|
## Configuration JSON Format
|
|
100
134
|
|
|
101
135
|
Export your configuration from the admin app and host it as a JSON file:
|
package/dist/index.d.mts
CHANGED
|
@@ -101,4 +101,29 @@ interface AppSettingsProps {
|
|
|
101
101
|
}
|
|
102
102
|
declare function AppSettings({ isOpen, onClose, apps, defaultApps, onAdd, onUpdate, onDelete, }: AppSettingsProps): react_jsx_runtime.JSX.Element | null;
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
interface LocalAppLauncherProps {
|
|
105
|
+
/**
|
|
106
|
+
* Initial default apps to display if no local apps exist or mixed with local apps
|
|
107
|
+
*/
|
|
108
|
+
defaultApps?: AppItem[];
|
|
109
|
+
/**
|
|
110
|
+
* Custom class name
|
|
111
|
+
*/
|
|
112
|
+
className?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Whether to merge default apps with local apps (true) or only show local apps if any exist (false)
|
|
115
|
+
* Default: true
|
|
116
|
+
*/
|
|
117
|
+
mergeDefaultApps?: boolean;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* A "smart" AppLauncher component that automatically handles:
|
|
121
|
+
* - LocalStorage persistence of user-defined apps
|
|
122
|
+
* - Settings UI management
|
|
123
|
+
* - State management
|
|
124
|
+
*
|
|
125
|
+
* Use this component if you want a zero-config setup that works out of the box.
|
|
126
|
+
*/
|
|
127
|
+
declare function LocalAppLauncher({ defaultApps, className, mergeDefaultApps, }: LocalAppLauncherProps): react_jsx_runtime.JSX.Element;
|
|
128
|
+
|
|
129
|
+
export { type AppItem, AppLauncher, type AppLauncherConfig, type AppLauncherProps, AppSettings, type AppSettingsProps, LocalAppLauncher, type LocalAppLauncherProps, type ResolvedApp, AppLauncher as default, getIcon, iconMap };
|
package/dist/index.d.ts
CHANGED
|
@@ -101,4 +101,29 @@ interface AppSettingsProps {
|
|
|
101
101
|
}
|
|
102
102
|
declare function AppSettings({ isOpen, onClose, apps, defaultApps, onAdd, onUpdate, onDelete, }: AppSettingsProps): react_jsx_runtime.JSX.Element | null;
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
interface LocalAppLauncherProps {
|
|
105
|
+
/**
|
|
106
|
+
* Initial default apps to display if no local apps exist or mixed with local apps
|
|
107
|
+
*/
|
|
108
|
+
defaultApps?: AppItem[];
|
|
109
|
+
/**
|
|
110
|
+
* Custom class name
|
|
111
|
+
*/
|
|
112
|
+
className?: string;
|
|
113
|
+
/**
|
|
114
|
+
* Whether to merge default apps with local apps (true) or only show local apps if any exist (false)
|
|
115
|
+
* Default: true
|
|
116
|
+
*/
|
|
117
|
+
mergeDefaultApps?: boolean;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* A "smart" AppLauncher component that automatically handles:
|
|
121
|
+
* - LocalStorage persistence of user-defined apps
|
|
122
|
+
* - Settings UI management
|
|
123
|
+
* - State management
|
|
124
|
+
*
|
|
125
|
+
* Use this component if you want a zero-config setup that works out of the box.
|
|
126
|
+
*/
|
|
127
|
+
declare function LocalAppLauncher({ defaultApps, className, mergeDefaultApps, }: LocalAppLauncherProps): react_jsx_runtime.JSX.Element;
|
|
128
|
+
|
|
129
|
+
export { type AppItem, AppLauncher, type AppLauncherConfig, type AppLauncherProps, AppSettings, type AppSettingsProps, LocalAppLauncher, type LocalAppLauncherProps, type ResolvedApp, AppLauncher as default, getIcon, iconMap };
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
AppLauncher: () => AppLauncher,
|
|
24
24
|
AppSettings: () => AppSettings,
|
|
25
|
+
LocalAppLauncher: () => LocalAppLauncher,
|
|
25
26
|
default: () => AppLauncher,
|
|
26
27
|
getIcon: () => getIcon,
|
|
27
28
|
iconMap: () => iconMap
|
|
@@ -670,10 +671,104 @@ function AppSettings({
|
|
|
670
671
|
] }) })
|
|
671
672
|
] }) });
|
|
672
673
|
}
|
|
674
|
+
|
|
675
|
+
// src/LocalAppLauncher.tsx
|
|
676
|
+
var import_react5 = require("react");
|
|
677
|
+
var import_fa3 = require("react-icons/fa");
|
|
678
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
679
|
+
var STORAGE_KEY = "app-launcher-user-apps";
|
|
680
|
+
function LocalAppLauncher({
|
|
681
|
+
defaultApps = [],
|
|
682
|
+
className,
|
|
683
|
+
mergeDefaultApps = true
|
|
684
|
+
}) {
|
|
685
|
+
const [showSettings, setShowSettings] = (0, import_react5.useState)(false);
|
|
686
|
+
const [userApps, setUserApps] = (0, import_react5.useState)([]);
|
|
687
|
+
const [isLoaded, setIsLoaded] = (0, import_react5.useState)(false);
|
|
688
|
+
(0, import_react5.useEffect)(() => {
|
|
689
|
+
if (typeof window !== "undefined") {
|
|
690
|
+
try {
|
|
691
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
692
|
+
if (stored) {
|
|
693
|
+
setUserApps(JSON.parse(stored));
|
|
694
|
+
}
|
|
695
|
+
} catch (e) {
|
|
696
|
+
console.error("Failed to load apps from localStorage", e);
|
|
697
|
+
}
|
|
698
|
+
setIsLoaded(true);
|
|
699
|
+
}
|
|
700
|
+
}, []);
|
|
701
|
+
const saveApps = (apps) => {
|
|
702
|
+
setUserApps(apps);
|
|
703
|
+
if (typeof window !== "undefined") {
|
|
704
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(apps));
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
const handleAddApp = (data) => {
|
|
708
|
+
const newApp = {
|
|
709
|
+
...data,
|
|
710
|
+
id: `custom-${Date.now()}`
|
|
711
|
+
};
|
|
712
|
+
saveApps([...userApps, newApp]);
|
|
713
|
+
};
|
|
714
|
+
const handleUpdateApp = (id, updates) => {
|
|
715
|
+
saveApps(userApps.map((app) => app.id === id ? { ...app, ...updates } : app));
|
|
716
|
+
};
|
|
717
|
+
const handleDeleteApp = (id) => {
|
|
718
|
+
saveApps(userApps.filter((app) => app.id !== id));
|
|
719
|
+
};
|
|
720
|
+
const displayApps = isLoaded ? mergeDefaultApps ? [...userApps, ...defaultApps.filter((da) => !userApps.some((ua) => ua.id === da.id))] : userApps.length > 0 ? userApps : defaultApps : [];
|
|
721
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
722
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
723
|
+
AppLauncher,
|
|
724
|
+
{
|
|
725
|
+
apps: displayApps,
|
|
726
|
+
className,
|
|
727
|
+
renderFooter: () => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
728
|
+
"button",
|
|
729
|
+
{
|
|
730
|
+
className: "app-launcher__footer-button",
|
|
731
|
+
style: {
|
|
732
|
+
display: "flex",
|
|
733
|
+
alignItems: "center",
|
|
734
|
+
gap: "8px",
|
|
735
|
+
width: "100%",
|
|
736
|
+
justifyContent: "center",
|
|
737
|
+
border: "none",
|
|
738
|
+
background: "transparent",
|
|
739
|
+
cursor: "pointer",
|
|
740
|
+
color: "var(--al-text-secondary)",
|
|
741
|
+
fontSize: "14px",
|
|
742
|
+
fontWeight: 500
|
|
743
|
+
},
|
|
744
|
+
onClick: () => setShowSettings(true),
|
|
745
|
+
children: [
|
|
746
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_fa3.FaCog, { style: { fontSize: "16px" } }),
|
|
747
|
+
"Settings"
|
|
748
|
+
]
|
|
749
|
+
}
|
|
750
|
+
)
|
|
751
|
+
}
|
|
752
|
+
),
|
|
753
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
754
|
+
AppSettings,
|
|
755
|
+
{
|
|
756
|
+
isOpen: showSettings,
|
|
757
|
+
onClose: () => setShowSettings(false),
|
|
758
|
+
apps: userApps,
|
|
759
|
+
defaultApps,
|
|
760
|
+
onAdd: handleAddApp,
|
|
761
|
+
onUpdate: handleUpdateApp,
|
|
762
|
+
onDelete: handleDeleteApp
|
|
763
|
+
}
|
|
764
|
+
)
|
|
765
|
+
] });
|
|
766
|
+
}
|
|
673
767
|
// Annotate the CommonJS export names for ESM import in node:
|
|
674
768
|
0 && (module.exports = {
|
|
675
769
|
AppLauncher,
|
|
676
770
|
AppSettings,
|
|
771
|
+
LocalAppLauncher,
|
|
677
772
|
getIcon,
|
|
678
773
|
iconMap
|
|
679
774
|
});
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/AppLauncher.tsx","../src/icons.ts","../src/AppSettings.tsx","../src/AddAppForm.tsx","../src/IconPicker.tsx"],"sourcesContent":["// Main component\r\nexport { AppLauncher, AppLauncher as default } from './AppLauncher';\r\n\r\n// Types\r\nexport type { AppLauncherProps, AppItem, AppLauncherConfig, ResolvedApp } from './types';\r\n\r\n// Icons (in case consumers want to use them)\r\nexport { iconMap, getIcon } from './icons';\r\n\r\n// Settings Component\r\nexport { AppSettings } from './AppSettings';\r\nexport type { AppSettingsProps } from './AppSettings';\r\n","'use client';\r\n\r\nimport React, { useState, useRef, useEffect } from 'react';\r\nimport { IoApps } from 'react-icons/io5';\r\nimport { AppLauncherProps, AppItem, ResolvedApp, AppLauncherConfig } from './types';\r\nimport { getIcon } from './icons';\r\nimport './styles.css';\r\n\r\n/**\r\n * A Google-style app launcher component\r\n *\r\n * @example\r\n * // With config URL\r\n * <AppLauncher configUrl=\"https://example.com/apps.json\" />\r\n *\r\n * @example\r\n * // With direct apps array\r\n * <AppLauncher apps={[{ id: '1', name: 'App', url: '/app', icon: 'FaRocket', color: '#4285F4' }]} />\r\n */\r\nexport function AppLauncher({\r\n configUrl,\r\n apps: propApps,\r\n className,\r\n onAppClick,\r\n renderFooter,\r\n}: AppLauncherProps) {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [apps, setApps] = useState<ResolvedApp[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n // Resolve apps from props or fetch from URL\r\n useEffect(() => {\r\n if (propApps) {\r\n setApps(propApps.map(resolveApp));\r\n return;\r\n }\r\n\r\n if (configUrl) {\r\n setLoading(true);\r\n setError(null);\r\n\r\n fetch(configUrl)\r\n .then((res) => {\r\n if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);\r\n return res.json();\r\n })\r\n .then((config: AppLauncherConfig) => {\r\n setApps(config.apps.map(resolveApp));\r\n })\r\n .catch((err) => {\r\n setError(err.message);\r\n console.error('AppLauncher: Failed to load config', err);\r\n })\r\n .finally(() => setLoading(false));\r\n }\r\n }, [configUrl, propApps]);\r\n\r\n // Close on click outside\r\n useEffect(() => {\r\n function handleClickOutside(event: MouseEvent) {\r\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\r\n setIsOpen(false);\r\n }\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('mousedown', handleClickOutside);\r\n }\r\n return () => document.removeEventListener('mousedown', handleClickOutside);\r\n }, [isOpen]);\r\n\r\n // Close on Escape\r\n useEffect(() => {\r\n function handleEscape(event: KeyboardEvent) {\r\n if (event.key === 'Escape') setIsOpen(false);\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('keydown', handleEscape);\r\n }\r\n return () => document.removeEventListener('keydown', handleEscape);\r\n }, [isOpen]);\r\n\r\n // Check if icon is a custom URL (path or full URL)\r\n function isCustomIconUrl(icon: string): boolean {\r\n return icon.startsWith('/') || icon.startsWith('http');\r\n }\r\n\r\n function resolveApp(app: AppItem): ResolvedApp {\r\n const isCustom = isCustomIconUrl(app.icon);\r\n return {\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: isCustom ? null : getIcon(app.icon),\r\n customIconUrl: isCustom ? app.icon : null,\r\n color: app.color,\r\n description: app.description,\r\n };\r\n }\r\n\r\n function handleAppClick(app: ResolvedApp) {\r\n if (onAppClick) {\r\n onAppClick({\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: app.customIconUrl || (app.icon?.name ?? 'FaRocket'),\r\n color: app.color,\r\n description: app.description,\r\n });\r\n } else {\r\n // Open in new tab\r\n window.open(app.url, '_blank', 'noopener,noreferrer');\r\n }\r\n }\r\n\r\n return (\r\n <div className={`app-launcher ${className || ''}`} ref={containerRef}>\r\n {/* Trigger Button */}\r\n <button\r\n className=\"app-launcher__trigger\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n aria-label=\"Open app launcher\"\r\n aria-expanded={isOpen}\r\n >\r\n <IoApps className=\"app-launcher__trigger-icon\" />\r\n </button>\r\n\r\n {/* Dropdown */}\r\n {isOpen && (\r\n <div className=\"app-launcher__dropdown\">\r\n {loading && <div className=\"app-launcher__loading\">Loading...</div>}\r\n\r\n {error && <div className=\"app-launcher__error\">{error}</div>}\r\n\r\n {!loading && !error && (\r\n <div className=\"app-launcher__grid\">\r\n {apps.map((app) => (\r\n <button\r\n key={app.id}\r\n className=\"app-launcher__item\"\r\n onClick={() => handleAppClick(app)}\r\n title={app.description || app.name}\r\n >\r\n <div className=\"app-launcher__icon-wrapper\">\r\n {app.customIconUrl ? (\r\n // Check if it's an SVG - use mask-image for color support\r\n app.customIconUrl.endsWith('.svg') ? (\r\n <div\r\n className=\"app-launcher__icon app-launcher__icon--svg\"\r\n style={{\r\n maskImage: `url(${app.customIconUrl})`,\r\n WebkitMaskImage: `url(${app.customIconUrl})`,\r\n backgroundColor: app.color === 'transparent' ? '#5f6368' : app.color,\r\n }}\r\n aria-label={app.name}\r\n />\r\n ) : (\r\n // Regular image for PNG, JPG, etc.\r\n <img\r\n src={app.customIconUrl}\r\n alt={app.name}\r\n className=\"app-launcher__icon app-launcher__icon--custom\"\r\n />\r\n )\r\n ) : app.icon ? (\r\n <app.icon className=\"app-launcher__icon\" style={{ color: app.color }} />\r\n ) : null}\r\n </div>\r\n <span className=\"app-launcher__name\">{app.name}</span>\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n\r\n {/* Custom footer (e.g., Settings button) */}\r\n {renderFooter && <div className=\"app-launcher__footer\">{renderFooter()}</div>}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nexport default AppLauncher;\r\n","import { IconType } from 'react-icons';\r\n// prettier-ignore\r\nimport {\r\n FaGoogle, FaEnvelope, FaYoutube, FaCalendarAlt, FaMapMarkerAlt,\r\n FaFile, FaBookmark, FaTable, FaNewspaper, FaImage, FaRocket,\r\n FaHome, FaUser, FaCog, FaChartBar, FaShoppingCart, FaDatabase,\r\n FaCode, FaTerminal, FaGlobe, FaLock, FaKey, FaBell, FaHeart,\r\n FaStar, FaFolder, FaClipboard, FaCalculator, FaMusic, FaCamera,\r\n FaGamepad, FaPuzzlePiece, FaBriefcase, FaGraduationCap, FaPlane,\r\n FaCar, FaBicycle, FaUtensils, FaCoffee, FaGift,\r\n} from 'react-icons/fa';\r\nimport { FaTicketSimple } from 'react-icons/fa6';\r\nimport { GrDeliver } from 'react-icons/gr';\r\nimport { IoBusinessSharp, IoApps } from 'react-icons/io5';\r\nimport { MdOutlineSecurity, MdDashboard, MdAnalytics, MdEmail, MdWork } from 'react-icons/md';\r\nimport { SiGoogledrive, SiGooglemeet } from 'react-icons/si';\r\nimport { AiOutlineSecurityScan } from 'react-icons/ai';\r\n\r\n/**\r\n * Map of icon names to icon components\r\n */\r\n// prettier-ignore\r\nexport const iconMap: Record<string, IconType> = {\r\n // Business & Work\r\n FaBriefcase, IoBusinessSharp, MdWork, FaGraduationCap,\r\n // Security\r\n MdOutlineSecurity, FaLock, FaKey, AiOutlineSecurityScan,\r\n // Communication\r\n FaEnvelope, MdEmail, FaBell,\r\n // Media\r\n FaYoutube, FaMusic, FaCamera, FaImage, FaGamepad,\r\n // Productivity\r\n FaCalendarAlt, FaClipboard, FaCalculator, FaFolder, FaFile,\r\n FaBookmark, FaTable, FaNewspaper,\r\n // Navigation\r\n FaMapMarkerAlt, FaGlobe, FaHome,\r\n // Google\r\n FaGoogle, SiGoogledrive, SiGooglemeet,\r\n // Development\r\n FaCode, FaTerminal, FaDatabase, FaPuzzlePiece,\r\n // Analytics\r\n FaChartBar, MdDashboard, MdAnalytics,\r\n // Shopping\r\n FaShoppingCart, FaGift, FaTicketSimple,\r\n // Travel\r\n FaPlane, FaCar, FaBicycle, GrDeliver,\r\n // Food\r\n FaUtensils, FaCoffee,\r\n // General\r\n FaRocket, FaUser, FaCog, FaHeart, FaStar, IoApps,\r\n};\r\n\r\n/**\r\n * Get icon component by name\r\n */\r\nexport function getIcon(name: string): IconType {\r\n return iconMap[name] || FaRocket;\r\n}\r\n","import React, { useState } from 'react';\r\nimport { FaTimes, FaPlus, FaEdit, FaTrash, FaDownload } from 'react-icons/fa';\r\nimport { AddAppForm } from './AddAppForm';\r\nimport { AppItem } from './types';\r\nimport { iconMap } from './icons';\r\n\r\nexport interface AppSettingsProps {\r\n isOpen: boolean;\r\n onClose: () => void;\r\n apps: AppItem[];\r\n defaultApps?: AppItem[];\r\n onAdd: (app: Omit<AppItem, 'id'>) => void;\r\n onUpdate: (id: string, app: Partial<Omit<AppItem, 'id'>>) => void;\r\n onDelete: (id: string) => void;\r\n}\r\n\r\nexport function AppSettings({\r\n isOpen,\r\n onClose,\r\n apps,\r\n defaultApps = [],\r\n onAdd,\r\n onUpdate,\r\n onDelete,\r\n}: AppSettingsProps) {\r\n const [showAddForm, setShowAddForm] = useState(false);\r\n const [editingApp, setEditingApp] = useState<AppItem | null>(null);\r\n\r\n if (!isOpen) return null;\r\n\r\n const handleAddSubmit = (data: Omit<AppItem, 'id'>) => {\r\n onAdd(data);\r\n setShowAddForm(false);\r\n };\r\n\r\n const handleEditSubmit = (data: Omit<AppItem, 'id'>) => {\r\n if (editingApp) {\r\n onUpdate(editingApp.id, data);\r\n setEditingApp(null);\r\n }\r\n };\r\n\r\n const handleDelete = (id: string) => {\r\n if (confirm('Are you sure you want to delete this app?')) {\r\n onDelete(id);\r\n }\r\n };\r\n\r\n const handleExportConfig = () => {\r\n // Combine default apps and user apps into exportable format\r\n const exportedApps = [\r\n // User apps first (they appear at top)\r\n ...apps,\r\n // Then default apps\r\n ...defaultApps.filter((da) => !apps.some((ua) => ua.id === da.id)), // Avoid duplicates if any\r\n ];\r\n\r\n const config = {\r\n version: '1.0',\r\n exportedAt: new Date().toISOString(),\r\n apps: exportedApps,\r\n };\r\n\r\n // Create and download JSON file\r\n const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });\r\n const url = URL.createObjectURL(blob);\r\n const a = document.createElement('a');\r\n a.href = url;\r\n a.download = 'app-launcher-config.json';\r\n document.body.appendChild(a);\r\n a.click();\r\n document.body.removeChild(a);\r\n URL.revokeObjectURL(url);\r\n };\r\n\r\n return (\r\n <div className=\"app-settings__overlay\">\r\n <div className=\"app-settings__modal\" onClick={(e) => e.stopPropagation()}>\r\n {/* Header */}\r\n <div className=\"app-settings__header\">\r\n <h2 className=\"app-settings__title\">\r\n {showAddForm ? 'Add New App' : editingApp ? 'Edit App' : 'Settings'}\r\n </h2>\r\n <button className=\"app-settings__close-button\" onClick={onClose} aria-label=\"Close\">\r\n <FaTimes />\r\n </button>\r\n </div>\r\n\r\n {/* Content */}\r\n <div className=\"app-settings__content\">\r\n {showAddForm ? (\r\n <AddAppForm\r\n onSubmit={handleAddSubmit}\r\n onCancel={() => setShowAddForm(false)}\r\n submitLabel=\"Add App\"\r\n />\r\n ) : editingApp ? (\r\n <AddAppForm\r\n initialData={editingApp}\r\n onSubmit={handleEditSubmit}\r\n onCancel={() => setEditingApp(null)}\r\n submitLabel=\"Save Changes\"\r\n />\r\n ) : (\r\n <>\r\n {/* Add App Button */}\r\n <button className=\"app-settings__add-button\" onClick={() => setShowAddForm(true)}>\r\n <FaPlus className=\"app-settings__add-icon\" />\r\n Add New App\r\n </button>\r\n\r\n {/* Apps List */}\r\n <div className=\"app-settings__list\">\r\n <h3 className=\"app-settings__section-title\">My Custom Apps</h3>\r\n\r\n {apps.length === 0 ? (\r\n <p className=\"app-settings__empty-message\">\r\n No custom apps yet. Click "Add New App" to get started.\r\n </p>\r\n ) : (\r\n <div className=\"app-settings__grid\">\r\n {apps.map((app) => {\r\n // Determine icon\r\n const isCustom = app.icon.startsWith('/') || app.icon.startsWith('http');\r\n const Icon = !isCustom ? iconMap[app.icon] || iconMap['FaRocket'] : null;\r\n\r\n return (\r\n <div key={app.id} className=\"app-settings__card\">\r\n <div className=\"app-settings__card-info\">\r\n <div\r\n className=\"app-settings__card-icon\"\r\n style={{ backgroundColor: app.color + '20' }}\r\n >\r\n {isCustom ? (\r\n app.icon.endsWith('.svg') ? (\r\n <div\r\n style={{\r\n width: 20,\r\n height: 20,\r\n maskImage: `url(${app.icon})`,\r\n WebkitMaskImage: `url(${app.icon})`,\r\n maskSize: 'contain',\r\n maskRepeat: 'no-repeat',\r\n maskPosition: 'center',\r\n WebkitMaskSize: 'contain',\r\n WebkitMaskRepeat: 'no-repeat',\r\n WebkitMaskPosition: 'center',\r\n backgroundColor:\r\n app.color === 'transparent' ? '#5f6368' : app.color,\r\n }}\r\n />\r\n ) : (\r\n <img\r\n src={app.icon}\r\n alt={app.name}\r\n style={{ width: 20, height: 20, objectFit: 'contain' }}\r\n />\r\n )\r\n ) : (\r\n Icon && <Icon style={{ color: app.color }} />\r\n )}\r\n </div>\r\n <div className=\"app-settings__card-details\">\r\n <span className=\"app-settings__card-name\">{app.name}</span>\r\n <span className=\"app-settings__card-url\">{app.url}</span>\r\n </div>\r\n </div>\r\n <div className=\"app-settings__card-actions\">\r\n <button\r\n className=\"app-settings__action-button\"\r\n onClick={() => setEditingApp(app)}\r\n title=\"Edit\"\r\n >\r\n <FaEdit />\r\n </button>\r\n <button\r\n className=\"app-settings__action-button app-settings__action-button--delete\"\r\n onClick={() => handleDelete(app.id)}\r\n title=\"Delete\"\r\n >\r\n <FaTrash />\r\n </button>\r\n </div>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Export Configuration */}\r\n <button className=\"app-settings__export-button\" onClick={handleExportConfig}>\r\n <FaDownload className=\"app-settings__export-icon\" />\r\n Export Configuration\r\n </button>\r\n\r\n {/* Info */}\r\n <div className=\"app-settings__info\">\r\n <p>\r\n Custom apps are saved in your browser. Use "Export Configuration" to\r\n share with other apps.\r\n </p>\r\n </div>\r\n </>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n}\r\n","import React, { useState } from 'react';\r\nimport { IconPicker } from './IconPicker';\r\nimport { iconMap } from './icons';\r\nimport { AppItem } from './types';\r\n\r\nexport interface AddAppFormProps {\r\n initialData?: Partial<AppItem>;\r\n onSubmit: (data: Omit<AppItem, 'id'>) => void;\r\n onCancel: () => void;\r\n submitLabel?: string;\r\n}\r\n\r\nconst presetColors = [\r\n '#4285F4', // Blue\r\n '#EA4335', // Red\r\n '#FBBC04', // Yellow\r\n '#34A853', // Green\r\n '#FF6D01', // Orange\r\n '#46BDC6', // Teal\r\n '#7B1FA2', // Purple\r\n '#E91E63', // Pink\r\n '#607D8B', // Gray\r\n '#795548', // Brown\r\n];\r\n\r\ntype IconMode = 'picker' | 'custom';\r\n\r\nexport function AddAppForm({\r\n initialData,\r\n onSubmit,\r\n onCancel,\r\n submitLabel = 'Add App',\r\n}: AddAppFormProps) {\r\n const [name, setName] = useState(initialData?.name || '');\r\n const [url, setUrl] = useState(initialData?.url || '');\r\n\r\n // Determine initial icon state\r\n const initialIcon = initialData?.icon || 'FaRocket';\r\n const isInitialCustom = initialIcon.startsWith('/') || initialIcon.startsWith('http');\r\n\r\n const [iconName, setIconName] = useState(isInitialCustom ? 'FaRocket' : initialIcon);\r\n const [customIconUrl, setCustomIconUrl] = useState(isInitialCustom ? initialIcon : '');\r\n const [iconMode, setIconMode] = useState<IconMode>(isInitialCustom ? 'custom' : 'picker');\r\n\r\n const [color, setColor] = useState(initialData?.color || '#4285F4');\r\n const [description, setDescription] = useState(initialData?.description || '');\r\n const [errors, setErrors] = useState<Record<string, string>>({});\r\n\r\n const SelectedIcon = iconMap[iconName] || iconMap['FaRocket'];\r\n\r\n const validate = (): boolean => {\r\n const newErrors: Record<string, string> = {};\r\n\r\n if (!name.trim()) {\r\n newErrors.name = 'Name is required';\r\n }\r\n\r\n if (!url.trim()) {\r\n newErrors.url = 'URL is required';\r\n } else if (!url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('/')) {\r\n newErrors.url = 'URL must start with http://, https://, or /';\r\n }\r\n\r\n if (iconMode === 'custom' && customIconUrl.trim()) {\r\n if (\r\n !customIconUrl.startsWith('http://') &&\r\n !customIconUrl.startsWith('https://') &&\r\n !customIconUrl.startsWith('/')\r\n ) {\r\n newErrors.customIconUrl = 'Icon URL must start with http://, https://, or /';\r\n }\r\n }\r\n\r\n setErrors(newErrors);\r\n return Object.keys(newErrors).length === 0;\r\n };\r\n\r\n const handleSubmit = (e: React.FormEvent) => {\r\n e.preventDefault();\r\n\r\n if (!validate()) return;\r\n\r\n const finalIcon =\r\n iconMode === 'custom' && customIconUrl.trim() ? customIconUrl.trim() : iconName;\r\n\r\n onSubmit({\r\n name: name.trim(),\r\n url: url.trim(),\r\n icon: finalIcon,\r\n color,\r\n description: description.trim() || undefined,\r\n });\r\n };\r\n\r\n return (\r\n <form onSubmit={handleSubmit} className=\"app-launcher-form\">\r\n {/* Preview */}\r\n <div className=\"app-launcher-form__preview\">\r\n <div className=\"app-launcher-form__preview-icon\" style={{ backgroundColor: color + '20' }}>\r\n {iconMode === 'custom' && customIconUrl ? (\r\n customIconUrl.endsWith('.svg') ? (\r\n <div\r\n className=\"app-launcher-form__preview-svg\"\r\n style={{\r\n maskImage: `url(${customIconUrl})`,\r\n WebkitMaskImage: `url(${customIconUrl})`,\r\n backgroundColor: color === 'transparent' ? '#5f6368' : color,\r\n }}\r\n />\r\n ) : (\r\n <img\r\n src={customIconUrl}\r\n alt=\"Icon preview\"\r\n className=\"app-launcher-form__preview-img\"\r\n />\r\n )\r\n ) : (\r\n <SelectedIcon style={{ color, fontSize: 32 }} />\r\n )}\r\n </div>\r\n <span className=\"app-launcher-form__preview-name\">{name || 'App Name'}</span>\r\n </div>\r\n\r\n {/* Name Input */}\r\n <div className=\"app-launcher-form__field\">\r\n <label htmlFor=\"app-name\" className=\"app-launcher-form__label\">\r\n Name *\r\n </label>\r\n <input\r\n id=\"app-name\"\r\n type=\"text\"\r\n value={name}\r\n onChange={(e) => setName(e.target.value)}\r\n placeholder=\"My App\"\r\n className={`app-launcher-form__input ${errors.name ? 'app-launcher-form__input--error' : ''}`}\r\n />\r\n {errors.name && <span className=\"app-launcher-form__error\">{errors.name}</span>}\r\n </div>\r\n\r\n {/* URL Input */}\r\n <div className=\"app-launcher-form__field\">\r\n <label htmlFor=\"app-url\" className=\"app-launcher-form__label\">\r\n URL *\r\n </label>\r\n <input\r\n id=\"app-url\"\r\n type=\"text\"\r\n value={url}\r\n onChange={(e) => setUrl(e.target.value)}\r\n placeholder=\"https://myapp.com or /dashboard\"\r\n className={`app-launcher-form__input ${errors.url ? 'app-launcher-form__input--error' : ''}`}\r\n />\r\n {errors.url && <span className=\"app-launcher-form__error\">{errors.url}</span>}\r\n </div>\r\n\r\n {/* Description Input */}\r\n <div className=\"app-launcher-form__field\">\r\n <label htmlFor=\"app-description\" className=\"app-launcher-form__label\">\r\n Description\r\n </label>\r\n <input\r\n id=\"app-description\"\r\n type=\"text\"\r\n value={description}\r\n onChange={(e) => setDescription(e.target.value)}\r\n placeholder=\"Optional description (shown on hover)\"\r\n className=\"app-launcher-form__input\"\r\n />\r\n </div>\r\n\r\n {/* Color Picker */}\r\n <div className=\"app-launcher-form__field\">\r\n <label className=\"app-launcher-form__label\">Color</label>\r\n <div className=\"app-launcher-form__color-picker\">\r\n {presetColors.map((presetColor) => (\r\n <button\r\n key={presetColor}\r\n type=\"button\"\r\n className={`app-launcher-form__color-button ${\r\n color === presetColor ? 'app-launcher-form__color-button--selected' : ''\r\n }`}\r\n style={{ backgroundColor: presetColor }}\r\n onClick={() => setColor(presetColor)}\r\n title={presetColor}\r\n />\r\n ))}\r\n <input\r\n type=\"color\"\r\n value={color}\r\n onChange={(e) => setColor(e.target.value)}\r\n className=\"app-launcher-form__color-input\"\r\n title=\"Custom color\"\r\n />\r\n </div>\r\n </div>\r\n\r\n {/* Icon Selection */}\r\n <div className=\"app-launcher-form__field\">\r\n <label className=\"app-launcher-form__label\">Icon</label>\r\n\r\n {/* Icon Mode Toggle */}\r\n <div className=\"app-launcher-form__icon-mode\">\r\n <button\r\n type=\"button\"\r\n className={`app-launcher-form__mode-button ${iconMode === 'picker' ? 'app-launcher-form__mode-button--active' : ''}`}\r\n onClick={() => setIconMode('picker')}\r\n >\r\n Icon Picker\r\n </button>\r\n <button\r\n type=\"button\"\r\n className={`app-launcher-form__mode-button ${iconMode === 'custom' ? 'app-launcher-form__mode-button--active' : ''}`}\r\n onClick={() => setIconMode('custom')}\r\n >\r\n Custom URL\r\n </button>\r\n </div>\r\n\r\n {iconMode === 'picker' ? (\r\n <IconPicker selectedIcon={iconName} onSelect={setIconName} />\r\n ) : (\r\n <div className=\"app-launcher-form__custom-icon\">\r\n <input\r\n id=\"app-custom-icon\"\r\n type=\"text\"\r\n value={customIconUrl}\r\n onChange={(e) => setCustomIconUrl(e.target.value)}\r\n placeholder=\"/icons/my-icon.svg or https://cdn.example.com/icon.svg\"\r\n className={`app-launcher-form__input ${errors.customIconUrl ? 'app-launcher-form__input--error' : ''}`}\r\n />\r\n {errors.customIconUrl && (\r\n <span className=\"app-launcher-form__error\">{errors.customIconUrl}</span>\r\n )}\r\n <p className=\"app-launcher-form__hint\">\r\n Enter a path from your public folder (e.g., /icons/logo.svg) or a full URL\r\n </p>\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Actions */}\r\n <div className=\"app-launcher-form__actions\">\r\n <button\r\n type=\"button\"\r\n onClick={onCancel}\r\n className=\"app-launcher-form__button app-launcher-form__button--cancel\"\r\n >\r\n Cancel\r\n </button>\r\n <button\r\n type=\"submit\"\r\n className=\"app-launcher-form__button app-launcher-form__button--submit\"\r\n >\r\n {submitLabel}\r\n </button>\r\n </div>\r\n </form>\r\n );\r\n}\r\n","import React, { useState } from 'react';\r\nimport { iconMap } from './icons';\r\n\r\ninterface IconPickerProps {\r\n selectedIcon: string;\r\n onSelect: (iconName: string) => void;\r\n}\r\n\r\n// Group icons by category/type based on their names or define categories manually\r\n// For simplicity in this package version, we'll list all available icons\r\nconst allIcons = Object.keys(iconMap);\r\n\r\nexport function IconPicker({ selectedIcon, onSelect }: IconPickerProps) {\r\n const [searchTerm, setSearchTerm] = useState('');\r\n\r\n const filteredIcons = allIcons.filter((name) =>\r\n name.toLowerCase().includes(searchTerm.toLowerCase())\r\n );\r\n\r\n return (\r\n <div className=\"app-launcher-icon-picker\">\r\n <input\r\n type=\"text\"\r\n placeholder=\"Search icons...\"\r\n value={searchTerm}\r\n onChange={(e) => setSearchTerm(e.target.value)}\r\n className=\"app-launcher-icon-picker__search\"\r\n />\r\n\r\n <div className=\"app-launcher-icon-picker__grid\">\r\n {filteredIcons.map((iconName) => {\r\n const Icon = iconMap[iconName];\r\n const isSelected = selectedIcon === iconName;\r\n\r\n return (\r\n <button\r\n key={iconName}\r\n className={`app-launcher-icon-picker__button ${\r\n isSelected ? 'app-launcher-icon-picker__button--selected' : ''\r\n }`}\r\n onClick={() => onSelect(iconName)}\r\n title={iconName}\r\n type=\"button\"\r\n >\r\n <Icon className=\"app-launcher-icon-picker__icon\" />\r\n </button>\r\n );\r\n })}\r\n </div>\r\n\r\n {filteredIcons.length === 0 && (\r\n <div className=\"app-launcher-icon-picker__empty\">No icons found</div>\r\n )}\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["import_io5","import_react","import_fa","import_react","import_react","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/AppLauncher.tsx","../src/icons.ts","../src/AppSettings.tsx","../src/AddAppForm.tsx","../src/IconPicker.tsx","../src/LocalAppLauncher.tsx"],"sourcesContent":["// Main component\r\nexport { AppLauncher, AppLauncher as default } from './AppLauncher';\r\n\r\n// Types\r\nexport type { AppLauncherProps, AppItem, AppLauncherConfig, ResolvedApp } from './types';\r\n\r\n// Icons (in case consumers want to use them)\r\nexport { iconMap, getIcon } from './icons';\r\n\r\n// Settings Component\r\nexport { AppSettings } from './AppSettings';\r\nexport type { AppSettingsProps } from './AppSettings';\r\n\r\n// Smart Component (Auto-Settings + LocalStorage)\r\nexport { LocalAppLauncher } from './LocalAppLauncher';\r\nexport type { LocalAppLauncherProps } from './LocalAppLauncher';\r\n","'use client';\r\n\r\nimport React, { useState, useRef, useEffect } from 'react';\r\nimport { IoApps } from 'react-icons/io5';\r\nimport { AppLauncherProps, AppItem, ResolvedApp, AppLauncherConfig } from './types';\r\nimport { getIcon } from './icons';\r\nimport './styles.css';\r\n\r\n/**\r\n * A Google-style app launcher component\r\n *\r\n * @example\r\n * // With config URL\r\n * <AppLauncher configUrl=\"https://example.com/apps.json\" />\r\n *\r\n * @example\r\n * // With direct apps array\r\n * <AppLauncher apps={[{ id: '1', name: 'App', url: '/app', icon: 'FaRocket', color: '#4285F4' }]} />\r\n */\r\nexport function AppLauncher({\r\n configUrl,\r\n apps: propApps,\r\n className,\r\n onAppClick,\r\n renderFooter,\r\n}: AppLauncherProps) {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [apps, setApps] = useState<ResolvedApp[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n // Resolve apps from props or fetch from URL\r\n useEffect(() => {\r\n if (propApps) {\r\n setApps(propApps.map(resolveApp));\r\n return;\r\n }\r\n\r\n if (configUrl) {\r\n setLoading(true);\r\n setError(null);\r\n\r\n fetch(configUrl)\r\n .then((res) => {\r\n if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);\r\n return res.json();\r\n })\r\n .then((config: AppLauncherConfig) => {\r\n setApps(config.apps.map(resolveApp));\r\n })\r\n .catch((err) => {\r\n setError(err.message);\r\n console.error('AppLauncher: Failed to load config', err);\r\n })\r\n .finally(() => setLoading(false));\r\n }\r\n }, [configUrl, propApps]);\r\n\r\n // Close on click outside\r\n useEffect(() => {\r\n function handleClickOutside(event: MouseEvent) {\r\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\r\n setIsOpen(false);\r\n }\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('mousedown', handleClickOutside);\r\n }\r\n return () => document.removeEventListener('mousedown', handleClickOutside);\r\n }, [isOpen]);\r\n\r\n // Close on Escape\r\n useEffect(() => {\r\n function handleEscape(event: KeyboardEvent) {\r\n if (event.key === 'Escape') setIsOpen(false);\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('keydown', handleEscape);\r\n }\r\n return () => document.removeEventListener('keydown', handleEscape);\r\n }, [isOpen]);\r\n\r\n // Check if icon is a custom URL (path or full URL)\r\n function isCustomIconUrl(icon: string): boolean {\r\n return icon.startsWith('/') || icon.startsWith('http');\r\n }\r\n\r\n function resolveApp(app: AppItem): ResolvedApp {\r\n const isCustom = isCustomIconUrl(app.icon);\r\n return {\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: isCustom ? null : getIcon(app.icon),\r\n customIconUrl: isCustom ? app.icon : null,\r\n color: app.color,\r\n description: app.description,\r\n };\r\n }\r\n\r\n function handleAppClick(app: ResolvedApp) {\r\n if (onAppClick) {\r\n onAppClick({\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: app.customIconUrl || (app.icon?.name ?? 'FaRocket'),\r\n color: app.color,\r\n description: app.description,\r\n });\r\n } else {\r\n // Open in new tab\r\n window.open(app.url, '_blank', 'noopener,noreferrer');\r\n }\r\n }\r\n\r\n return (\r\n <div className={`app-launcher ${className || ''}`} ref={containerRef}>\r\n {/* Trigger Button */}\r\n <button\r\n className=\"app-launcher__trigger\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n aria-label=\"Open app launcher\"\r\n aria-expanded={isOpen}\r\n >\r\n <IoApps className=\"app-launcher__trigger-icon\" />\r\n </button>\r\n\r\n {/* Dropdown */}\r\n {isOpen && (\r\n <div className=\"app-launcher__dropdown\">\r\n {loading && <div className=\"app-launcher__loading\">Loading...</div>}\r\n\r\n {error && <div className=\"app-launcher__error\">{error}</div>}\r\n\r\n {!loading && !error && (\r\n <div className=\"app-launcher__grid\">\r\n {apps.map((app) => (\r\n <button\r\n key={app.id}\r\n className=\"app-launcher__item\"\r\n onClick={() => handleAppClick(app)}\r\n title={app.description || app.name}\r\n >\r\n <div className=\"app-launcher__icon-wrapper\">\r\n {app.customIconUrl ? (\r\n // Check if it's an SVG - use mask-image for color support\r\n app.customIconUrl.endsWith('.svg') ? (\r\n <div\r\n className=\"app-launcher__icon app-launcher__icon--svg\"\r\n style={{\r\n maskImage: `url(${app.customIconUrl})`,\r\n WebkitMaskImage: `url(${app.customIconUrl})`,\r\n backgroundColor: app.color === 'transparent' ? '#5f6368' : app.color,\r\n }}\r\n aria-label={app.name}\r\n />\r\n ) : (\r\n // Regular image for PNG, JPG, etc.\r\n <img\r\n src={app.customIconUrl}\r\n alt={app.name}\r\n className=\"app-launcher__icon app-launcher__icon--custom\"\r\n />\r\n )\r\n ) : app.icon ? (\r\n <app.icon className=\"app-launcher__icon\" style={{ color: app.color }} />\r\n ) : null}\r\n </div>\r\n <span className=\"app-launcher__name\">{app.name}</span>\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n\r\n {/* Custom footer (e.g., Settings button) */}\r\n {renderFooter && <div className=\"app-launcher__footer\">{renderFooter()}</div>}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nexport default AppLauncher;\r\n","import { IconType } from 'react-icons';\r\n// prettier-ignore\r\nimport {\r\n FaGoogle, FaEnvelope, FaYoutube, FaCalendarAlt, FaMapMarkerAlt,\r\n FaFile, FaBookmark, FaTable, FaNewspaper, FaImage, FaRocket,\r\n FaHome, FaUser, FaCog, FaChartBar, FaShoppingCart, FaDatabase,\r\n FaCode, FaTerminal, FaGlobe, FaLock, FaKey, FaBell, FaHeart,\r\n FaStar, FaFolder, FaClipboard, FaCalculator, FaMusic, FaCamera,\r\n FaGamepad, FaPuzzlePiece, FaBriefcase, FaGraduationCap, FaPlane,\r\n FaCar, FaBicycle, FaUtensils, FaCoffee, FaGift,\r\n} from 'react-icons/fa';\r\nimport { FaTicketSimple } from 'react-icons/fa6';\r\nimport { GrDeliver } from 'react-icons/gr';\r\nimport { IoBusinessSharp, IoApps } from 'react-icons/io5';\r\nimport { MdOutlineSecurity, MdDashboard, MdAnalytics, MdEmail, MdWork } from 'react-icons/md';\r\nimport { SiGoogledrive, SiGooglemeet } from 'react-icons/si';\r\nimport { AiOutlineSecurityScan } from 'react-icons/ai';\r\n\r\n/**\r\n * Map of icon names to icon components\r\n */\r\n// prettier-ignore\r\nexport const iconMap: Record<string, IconType> = {\r\n // Business & Work\r\n FaBriefcase, IoBusinessSharp, MdWork, FaGraduationCap,\r\n // Security\r\n MdOutlineSecurity, FaLock, FaKey, AiOutlineSecurityScan,\r\n // Communication\r\n FaEnvelope, MdEmail, FaBell,\r\n // Media\r\n FaYoutube, FaMusic, FaCamera, FaImage, FaGamepad,\r\n // Productivity\r\n FaCalendarAlt, FaClipboard, FaCalculator, FaFolder, FaFile,\r\n FaBookmark, FaTable, FaNewspaper,\r\n // Navigation\r\n FaMapMarkerAlt, FaGlobe, FaHome,\r\n // Google\r\n FaGoogle, SiGoogledrive, SiGooglemeet,\r\n // Development\r\n FaCode, FaTerminal, FaDatabase, FaPuzzlePiece,\r\n // Analytics\r\n FaChartBar, MdDashboard, MdAnalytics,\r\n // Shopping\r\n FaShoppingCart, FaGift, FaTicketSimple,\r\n // Travel\r\n FaPlane, FaCar, FaBicycle, GrDeliver,\r\n // Food\r\n FaUtensils, FaCoffee,\r\n // General\r\n FaRocket, FaUser, FaCog, FaHeart, FaStar, IoApps,\r\n};\r\n\r\n/**\r\n * Get icon component by name\r\n */\r\nexport function getIcon(name: string): IconType {\r\n return iconMap[name] || FaRocket;\r\n}\r\n","import React, { useState } from 'react';\r\nimport { FaTimes, FaPlus, FaEdit, FaTrash, FaDownload } from 'react-icons/fa';\r\nimport { AddAppForm } from './AddAppForm';\r\nimport { AppItem } from './types';\r\nimport { iconMap } from './icons';\r\n\r\nexport interface AppSettingsProps {\r\n isOpen: boolean;\r\n onClose: () => void;\r\n apps: AppItem[];\r\n defaultApps?: AppItem[];\r\n onAdd: (app: Omit<AppItem, 'id'>) => void;\r\n onUpdate: (id: string, app: Partial<Omit<AppItem, 'id'>>) => void;\r\n onDelete: (id: string) => void;\r\n}\r\n\r\nexport function AppSettings({\r\n isOpen,\r\n onClose,\r\n apps,\r\n defaultApps = [],\r\n onAdd,\r\n onUpdate,\r\n onDelete,\r\n}: AppSettingsProps) {\r\n const [showAddForm, setShowAddForm] = useState(false);\r\n const [editingApp, setEditingApp] = useState<AppItem | null>(null);\r\n\r\n if (!isOpen) return null;\r\n\r\n const handleAddSubmit = (data: Omit<AppItem, 'id'>) => {\r\n onAdd(data);\r\n setShowAddForm(false);\r\n };\r\n\r\n const handleEditSubmit = (data: Omit<AppItem, 'id'>) => {\r\n if (editingApp) {\r\n onUpdate(editingApp.id, data);\r\n setEditingApp(null);\r\n }\r\n };\r\n\r\n const handleDelete = (id: string) => {\r\n if (confirm('Are you sure you want to delete this app?')) {\r\n onDelete(id);\r\n }\r\n };\r\n\r\n const handleExportConfig = () => {\r\n // Combine default apps and user apps into exportable format\r\n const exportedApps = [\r\n // User apps first (they appear at top)\r\n ...apps,\r\n // Then default apps\r\n ...defaultApps.filter((da) => !apps.some((ua) => ua.id === da.id)), // Avoid duplicates if any\r\n ];\r\n\r\n const config = {\r\n version: '1.0',\r\n exportedAt: new Date().toISOString(),\r\n apps: exportedApps,\r\n };\r\n\r\n // Create and download JSON file\r\n const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });\r\n const url = URL.createObjectURL(blob);\r\n const a = document.createElement('a');\r\n a.href = url;\r\n a.download = 'app-launcher-config.json';\r\n document.body.appendChild(a);\r\n a.click();\r\n document.body.removeChild(a);\r\n URL.revokeObjectURL(url);\r\n };\r\n\r\n return (\r\n <div className=\"app-settings__overlay\">\r\n <div className=\"app-settings__modal\" onClick={(e) => e.stopPropagation()}>\r\n {/* Header */}\r\n <div className=\"app-settings__header\">\r\n <h2 className=\"app-settings__title\">\r\n {showAddForm ? 'Add New App' : editingApp ? 'Edit App' : 'Settings'}\r\n </h2>\r\n <button className=\"app-settings__close-button\" onClick={onClose} aria-label=\"Close\">\r\n <FaTimes />\r\n </button>\r\n </div>\r\n\r\n {/* Content */}\r\n <div className=\"app-settings__content\">\r\n {showAddForm ? (\r\n <AddAppForm\r\n onSubmit={handleAddSubmit}\r\n onCancel={() => setShowAddForm(false)}\r\n submitLabel=\"Add App\"\r\n />\r\n ) : editingApp ? (\r\n <AddAppForm\r\n initialData={editingApp}\r\n onSubmit={handleEditSubmit}\r\n onCancel={() => setEditingApp(null)}\r\n submitLabel=\"Save Changes\"\r\n />\r\n ) : (\r\n <>\r\n {/* Add App Button */}\r\n <button className=\"app-settings__add-button\" onClick={() => setShowAddForm(true)}>\r\n <FaPlus className=\"app-settings__add-icon\" />\r\n Add New App\r\n </button>\r\n\r\n {/* Apps List */}\r\n <div className=\"app-settings__list\">\r\n <h3 className=\"app-settings__section-title\">My Custom Apps</h3>\r\n\r\n {apps.length === 0 ? (\r\n <p className=\"app-settings__empty-message\">\r\n No custom apps yet. Click "Add New App" to get started.\r\n </p>\r\n ) : (\r\n <div className=\"app-settings__grid\">\r\n {apps.map((app) => {\r\n // Determine icon\r\n const isCustom = app.icon.startsWith('/') || app.icon.startsWith('http');\r\n const Icon = !isCustom ? iconMap[app.icon] || iconMap['FaRocket'] : null;\r\n\r\n return (\r\n <div key={app.id} className=\"app-settings__card\">\r\n <div className=\"app-settings__card-info\">\r\n <div\r\n className=\"app-settings__card-icon\"\r\n style={{ backgroundColor: app.color + '20' }}\r\n >\r\n {isCustom ? (\r\n app.icon.endsWith('.svg') ? (\r\n <div\r\n style={{\r\n width: 20,\r\n height: 20,\r\n maskImage: `url(${app.icon})`,\r\n WebkitMaskImage: `url(${app.icon})`,\r\n maskSize: 'contain',\r\n maskRepeat: 'no-repeat',\r\n maskPosition: 'center',\r\n WebkitMaskSize: 'contain',\r\n WebkitMaskRepeat: 'no-repeat',\r\n WebkitMaskPosition: 'center',\r\n backgroundColor:\r\n app.color === 'transparent' ? '#5f6368' : app.color,\r\n }}\r\n />\r\n ) : (\r\n <img\r\n src={app.icon}\r\n alt={app.name}\r\n style={{ width: 20, height: 20, objectFit: 'contain' }}\r\n />\r\n )\r\n ) : (\r\n Icon && <Icon style={{ color: app.color }} />\r\n )}\r\n </div>\r\n <div className=\"app-settings__card-details\">\r\n <span className=\"app-settings__card-name\">{app.name}</span>\r\n <span className=\"app-settings__card-url\">{app.url}</span>\r\n </div>\r\n </div>\r\n <div className=\"app-settings__card-actions\">\r\n <button\r\n className=\"app-settings__action-button\"\r\n onClick={() => setEditingApp(app)}\r\n title=\"Edit\"\r\n >\r\n <FaEdit />\r\n </button>\r\n <button\r\n className=\"app-settings__action-button app-settings__action-button--delete\"\r\n onClick={() => handleDelete(app.id)}\r\n title=\"Delete\"\r\n >\r\n <FaTrash />\r\n </button>\r\n </div>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Export Configuration */}\r\n <button className=\"app-settings__export-button\" onClick={handleExportConfig}>\r\n <FaDownload className=\"app-settings__export-icon\" />\r\n Export Configuration\r\n </button>\r\n\r\n {/* Info */}\r\n <div className=\"app-settings__info\">\r\n <p>\r\n Custom apps are saved in your browser. Use "Export Configuration" to\r\n share with other apps.\r\n </p>\r\n </div>\r\n </>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n}\r\n","import React, { useState } from 'react';\r\nimport { IconPicker } from './IconPicker';\r\nimport { iconMap } from './icons';\r\nimport { AppItem } from './types';\r\n\r\nexport interface AddAppFormProps {\r\n initialData?: Partial<AppItem>;\r\n onSubmit: (data: Omit<AppItem, 'id'>) => void;\r\n onCancel: () => void;\r\n submitLabel?: string;\r\n}\r\n\r\nconst presetColors = [\r\n '#4285F4', // Blue\r\n '#EA4335', // Red\r\n '#FBBC04', // Yellow\r\n '#34A853', // Green\r\n '#FF6D01', // Orange\r\n '#46BDC6', // Teal\r\n '#7B1FA2', // Purple\r\n '#E91E63', // Pink\r\n '#607D8B', // Gray\r\n '#795548', // Brown\r\n];\r\n\r\ntype IconMode = 'picker' | 'custom';\r\n\r\nexport function AddAppForm({\r\n initialData,\r\n onSubmit,\r\n onCancel,\r\n submitLabel = 'Add App',\r\n}: AddAppFormProps) {\r\n const [name, setName] = useState(initialData?.name || '');\r\n const [url, setUrl] = useState(initialData?.url || '');\r\n\r\n // Determine initial icon state\r\n const initialIcon = initialData?.icon || 'FaRocket';\r\n const isInitialCustom = initialIcon.startsWith('/') || initialIcon.startsWith('http');\r\n\r\n const [iconName, setIconName] = useState(isInitialCustom ? 'FaRocket' : initialIcon);\r\n const [customIconUrl, setCustomIconUrl] = useState(isInitialCustom ? initialIcon : '');\r\n const [iconMode, setIconMode] = useState<IconMode>(isInitialCustom ? 'custom' : 'picker');\r\n\r\n const [color, setColor] = useState(initialData?.color || '#4285F4');\r\n const [description, setDescription] = useState(initialData?.description || '');\r\n const [errors, setErrors] = useState<Record<string, string>>({});\r\n\r\n const SelectedIcon = iconMap[iconName] || iconMap['FaRocket'];\r\n\r\n const validate = (): boolean => {\r\n const newErrors: Record<string, string> = {};\r\n\r\n if (!name.trim()) {\r\n newErrors.name = 'Name is required';\r\n }\r\n\r\n if (!url.trim()) {\r\n newErrors.url = 'URL is required';\r\n } else if (!url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('/')) {\r\n newErrors.url = 'URL must start with http://, https://, or /';\r\n }\r\n\r\n if (iconMode === 'custom' && customIconUrl.trim()) {\r\n if (\r\n !customIconUrl.startsWith('http://') &&\r\n !customIconUrl.startsWith('https://') &&\r\n !customIconUrl.startsWith('/')\r\n ) {\r\n newErrors.customIconUrl = 'Icon URL must start with http://, https://, or /';\r\n }\r\n }\r\n\r\n setErrors(newErrors);\r\n return Object.keys(newErrors).length === 0;\r\n };\r\n\r\n const handleSubmit = (e: React.FormEvent) => {\r\n e.preventDefault();\r\n\r\n if (!validate()) return;\r\n\r\n const finalIcon =\r\n iconMode === 'custom' && customIconUrl.trim() ? customIconUrl.trim() : iconName;\r\n\r\n onSubmit({\r\n name: name.trim(),\r\n url: url.trim(),\r\n icon: finalIcon,\r\n color,\r\n description: description.trim() || undefined,\r\n });\r\n };\r\n\r\n return (\r\n <form onSubmit={handleSubmit} className=\"app-launcher-form\">\r\n {/* Preview */}\r\n <div className=\"app-launcher-form__preview\">\r\n <div className=\"app-launcher-form__preview-icon\" style={{ backgroundColor: color + '20' }}>\r\n {iconMode === 'custom' && customIconUrl ? (\r\n customIconUrl.endsWith('.svg') ? (\r\n <div\r\n className=\"app-launcher-form__preview-svg\"\r\n style={{\r\n maskImage: `url(${customIconUrl})`,\r\n WebkitMaskImage: `url(${customIconUrl})`,\r\n backgroundColor: color === 'transparent' ? '#5f6368' : color,\r\n }}\r\n />\r\n ) : (\r\n <img\r\n src={customIconUrl}\r\n alt=\"Icon preview\"\r\n className=\"app-launcher-form__preview-img\"\r\n />\r\n )\r\n ) : (\r\n <SelectedIcon style={{ color, fontSize: 32 }} />\r\n )}\r\n </div>\r\n <span className=\"app-launcher-form__preview-name\">{name || 'App Name'}</span>\r\n </div>\r\n\r\n {/* Name Input */}\r\n <div className=\"app-launcher-form__field\">\r\n <label htmlFor=\"app-name\" className=\"app-launcher-form__label\">\r\n Name *\r\n </label>\r\n <input\r\n id=\"app-name\"\r\n type=\"text\"\r\n value={name}\r\n onChange={(e) => setName(e.target.value)}\r\n placeholder=\"My App\"\r\n className={`app-launcher-form__input ${errors.name ? 'app-launcher-form__input--error' : ''}`}\r\n />\r\n {errors.name && <span className=\"app-launcher-form__error\">{errors.name}</span>}\r\n </div>\r\n\r\n {/* URL Input */}\r\n <div className=\"app-launcher-form__field\">\r\n <label htmlFor=\"app-url\" className=\"app-launcher-form__label\">\r\n URL *\r\n </label>\r\n <input\r\n id=\"app-url\"\r\n type=\"text\"\r\n value={url}\r\n onChange={(e) => setUrl(e.target.value)}\r\n placeholder=\"https://myapp.com or /dashboard\"\r\n className={`app-launcher-form__input ${errors.url ? 'app-launcher-form__input--error' : ''}`}\r\n />\r\n {errors.url && <span className=\"app-launcher-form__error\">{errors.url}</span>}\r\n </div>\r\n\r\n {/* Description Input */}\r\n <div className=\"app-launcher-form__field\">\r\n <label htmlFor=\"app-description\" className=\"app-launcher-form__label\">\r\n Description\r\n </label>\r\n <input\r\n id=\"app-description\"\r\n type=\"text\"\r\n value={description}\r\n onChange={(e) => setDescription(e.target.value)}\r\n placeholder=\"Optional description (shown on hover)\"\r\n className=\"app-launcher-form__input\"\r\n />\r\n </div>\r\n\r\n {/* Color Picker */}\r\n <div className=\"app-launcher-form__field\">\r\n <label className=\"app-launcher-form__label\">Color</label>\r\n <div className=\"app-launcher-form__color-picker\">\r\n {presetColors.map((presetColor) => (\r\n <button\r\n key={presetColor}\r\n type=\"button\"\r\n className={`app-launcher-form__color-button ${\r\n color === presetColor ? 'app-launcher-form__color-button--selected' : ''\r\n }`}\r\n style={{ backgroundColor: presetColor }}\r\n onClick={() => setColor(presetColor)}\r\n title={presetColor}\r\n />\r\n ))}\r\n <input\r\n type=\"color\"\r\n value={color}\r\n onChange={(e) => setColor(e.target.value)}\r\n className=\"app-launcher-form__color-input\"\r\n title=\"Custom color\"\r\n />\r\n </div>\r\n </div>\r\n\r\n {/* Icon Selection */}\r\n <div className=\"app-launcher-form__field\">\r\n <label className=\"app-launcher-form__label\">Icon</label>\r\n\r\n {/* Icon Mode Toggle */}\r\n <div className=\"app-launcher-form__icon-mode\">\r\n <button\r\n type=\"button\"\r\n className={`app-launcher-form__mode-button ${iconMode === 'picker' ? 'app-launcher-form__mode-button--active' : ''}`}\r\n onClick={() => setIconMode('picker')}\r\n >\r\n Icon Picker\r\n </button>\r\n <button\r\n type=\"button\"\r\n className={`app-launcher-form__mode-button ${iconMode === 'custom' ? 'app-launcher-form__mode-button--active' : ''}`}\r\n onClick={() => setIconMode('custom')}\r\n >\r\n Custom URL\r\n </button>\r\n </div>\r\n\r\n {iconMode === 'picker' ? (\r\n <IconPicker selectedIcon={iconName} onSelect={setIconName} />\r\n ) : (\r\n <div className=\"app-launcher-form__custom-icon\">\r\n <input\r\n id=\"app-custom-icon\"\r\n type=\"text\"\r\n value={customIconUrl}\r\n onChange={(e) => setCustomIconUrl(e.target.value)}\r\n placeholder=\"/icons/my-icon.svg or https://cdn.example.com/icon.svg\"\r\n className={`app-launcher-form__input ${errors.customIconUrl ? 'app-launcher-form__input--error' : ''}`}\r\n />\r\n {errors.customIconUrl && (\r\n <span className=\"app-launcher-form__error\">{errors.customIconUrl}</span>\r\n )}\r\n <p className=\"app-launcher-form__hint\">\r\n Enter a path from your public folder (e.g., /icons/logo.svg) or a full URL\r\n </p>\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Actions */}\r\n <div className=\"app-launcher-form__actions\">\r\n <button\r\n type=\"button\"\r\n onClick={onCancel}\r\n className=\"app-launcher-form__button app-launcher-form__button--cancel\"\r\n >\r\n Cancel\r\n </button>\r\n <button\r\n type=\"submit\"\r\n className=\"app-launcher-form__button app-launcher-form__button--submit\"\r\n >\r\n {submitLabel}\r\n </button>\r\n </div>\r\n </form>\r\n );\r\n}\r\n","import React, { useState } from 'react';\r\nimport { iconMap } from './icons';\r\n\r\ninterface IconPickerProps {\r\n selectedIcon: string;\r\n onSelect: (iconName: string) => void;\r\n}\r\n\r\n// Group icons by category/type based on their names or define categories manually\r\n// For simplicity in this package version, we'll list all available icons\r\nconst allIcons = Object.keys(iconMap);\r\n\r\nexport function IconPicker({ selectedIcon, onSelect }: IconPickerProps) {\r\n const [searchTerm, setSearchTerm] = useState('');\r\n\r\n const filteredIcons = allIcons.filter((name) =>\r\n name.toLowerCase().includes(searchTerm.toLowerCase())\r\n );\r\n\r\n return (\r\n <div className=\"app-launcher-icon-picker\">\r\n <input\r\n type=\"text\"\r\n placeholder=\"Search icons...\"\r\n value={searchTerm}\r\n onChange={(e) => setSearchTerm(e.target.value)}\r\n className=\"app-launcher-icon-picker__search\"\r\n />\r\n\r\n <div className=\"app-launcher-icon-picker__grid\">\r\n {filteredIcons.map((iconName) => {\r\n const Icon = iconMap[iconName];\r\n const isSelected = selectedIcon === iconName;\r\n\r\n return (\r\n <button\r\n key={iconName}\r\n className={`app-launcher-icon-picker__button ${\r\n isSelected ? 'app-launcher-icon-picker__button--selected' : ''\r\n }`}\r\n onClick={() => onSelect(iconName)}\r\n title={iconName}\r\n type=\"button\"\r\n >\r\n <Icon className=\"app-launcher-icon-picker__icon\" />\r\n </button>\r\n );\r\n })}\r\n </div>\r\n\r\n {filteredIcons.length === 0 && (\r\n <div className=\"app-launcher-icon-picker__empty\">No icons found</div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import React, { useState, useEffect, useCallback } from 'react';\r\nimport { FaCog } from 'react-icons/fa';\r\nimport { AppLauncher } from './AppLauncher';\r\nimport { AppSettings } from './AppSettings';\r\nimport { AppItem } from './types';\r\nimport { iconMap } from './icons';\r\nimport './styles.css';\r\n\r\nconst STORAGE_KEY = 'app-launcher-user-apps';\r\n\r\nexport interface LocalAppLauncherProps {\r\n /**\r\n * Initial default apps to display if no local apps exist or mixed with local apps\r\n */\r\n defaultApps?: AppItem[];\r\n\r\n /**\r\n * Custom class name\r\n */\r\n className?: string;\r\n\r\n /**\r\n * Whether to merge default apps with local apps (true) or only show local apps if any exist (false)\r\n * Default: true\r\n */\r\n mergeDefaultApps?: boolean;\r\n}\r\n\r\n/**\r\n * A \"smart\" AppLauncher component that automatically handles:\r\n * - LocalStorage persistence of user-defined apps\r\n * - Settings UI management\r\n * - State management\r\n *\r\n * Use this component if you want a zero-config setup that works out of the box.\r\n */\r\nexport function LocalAppLauncher({\r\n defaultApps = [],\r\n className,\r\n mergeDefaultApps = true,\r\n}: LocalAppLauncherProps) {\r\n const [showSettings, setShowSettings] = useState(false);\r\n const [userApps, setUserApps] = useState<AppItem[]>([]);\r\n const [isLoaded, setIsLoaded] = useState(false);\r\n\r\n // Load user apps from localStorage on mount\r\n useEffect(() => {\r\n if (typeof window !== 'undefined') {\r\n try {\r\n const stored = localStorage.getItem(STORAGE_KEY);\r\n if (stored) {\r\n setUserApps(JSON.parse(stored));\r\n }\r\n } catch (e) {\r\n console.error('Failed to load apps from localStorage', e);\r\n }\r\n setIsLoaded(true);\r\n }\r\n }, []);\r\n\r\n // Save to localStorage whenever userApps changes\r\n const saveApps = (apps: AppItem[]) => {\r\n setUserApps(apps);\r\n if (typeof window !== 'undefined') {\r\n localStorage.setItem(STORAGE_KEY, JSON.stringify(apps));\r\n }\r\n };\r\n\r\n const handleAddApp = (data: Omit<AppItem, 'id'>) => {\r\n const newApp: AppItem = {\r\n ...data,\r\n id: `custom-${Date.now()}`,\r\n };\r\n saveApps([...userApps, newApp]);\r\n };\r\n\r\n const handleUpdateApp = (id: string, updates: Partial<Omit<AppItem, 'id'>>) => {\r\n saveApps(userApps.map((app) => (app.id === id ? { ...app, ...updates } : app)));\r\n };\r\n\r\n const handleDeleteApp = (id: string) => {\r\n saveApps(userApps.filter((app) => app.id !== id));\r\n };\r\n\r\n // Combine default apps and user apps for display\r\n const displayApps = isLoaded\r\n ? mergeDefaultApps\r\n ? [...userApps, ...defaultApps.filter((da) => !userApps.some((ua) => ua.id === da.id))]\r\n : userApps.length > 0\r\n ? userApps\r\n : defaultApps\r\n : [];\r\n\r\n return (\r\n <>\r\n <AppLauncher\r\n apps={displayApps}\r\n className={className}\r\n renderFooter={() => (\r\n <button\r\n className=\"app-launcher__footer-button\" // We need to add this style or reuse generic one\r\n style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: '8px',\r\n width: '100%',\r\n justifyContent: 'center',\r\n border: 'none',\r\n background: 'transparent',\r\n cursor: 'pointer',\r\n color: 'var(--al-text-secondary)',\r\n fontSize: '14px',\r\n fontWeight: 500,\r\n }}\r\n onClick={() => setShowSettings(true)}\r\n >\r\n <FaCog style={{ fontSize: '16px' }} />\r\n Settings\r\n </button>\r\n )}\r\n />\r\n\r\n <AppSettings\r\n isOpen={showSettings}\r\n onClose={() => setShowSettings(false)}\r\n apps={userApps}\r\n defaultApps={defaultApps}\r\n onAdd={handleAddApp}\r\n onUpdate={handleUpdateApp}\r\n onDelete={handleDeleteApp}\r\n />\r\n </>\r\n );\r\n}\r\n"],"mappings":"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","names":["import_io5","import_react","import_fa","import_react","import_react","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime","import_react","import_fa","import_jsx_runtime"]}
|
package/dist/index.mjs
CHANGED
|
@@ -681,9 +681,103 @@ function AppSettings({
|
|
|
681
681
|
] }) })
|
|
682
682
|
] }) });
|
|
683
683
|
}
|
|
684
|
+
|
|
685
|
+
// src/LocalAppLauncher.tsx
|
|
686
|
+
import { useState as useState5, useEffect as useEffect2 } from "react";
|
|
687
|
+
import { FaCog as FaCog2 } from "react-icons/fa";
|
|
688
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
689
|
+
var STORAGE_KEY = "app-launcher-user-apps";
|
|
690
|
+
function LocalAppLauncher({
|
|
691
|
+
defaultApps = [],
|
|
692
|
+
className,
|
|
693
|
+
mergeDefaultApps = true
|
|
694
|
+
}) {
|
|
695
|
+
const [showSettings, setShowSettings] = useState5(false);
|
|
696
|
+
const [userApps, setUserApps] = useState5([]);
|
|
697
|
+
const [isLoaded, setIsLoaded] = useState5(false);
|
|
698
|
+
useEffect2(() => {
|
|
699
|
+
if (typeof window !== "undefined") {
|
|
700
|
+
try {
|
|
701
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
702
|
+
if (stored) {
|
|
703
|
+
setUserApps(JSON.parse(stored));
|
|
704
|
+
}
|
|
705
|
+
} catch (e) {
|
|
706
|
+
console.error("Failed to load apps from localStorage", e);
|
|
707
|
+
}
|
|
708
|
+
setIsLoaded(true);
|
|
709
|
+
}
|
|
710
|
+
}, []);
|
|
711
|
+
const saveApps = (apps) => {
|
|
712
|
+
setUserApps(apps);
|
|
713
|
+
if (typeof window !== "undefined") {
|
|
714
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(apps));
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
const handleAddApp = (data) => {
|
|
718
|
+
const newApp = {
|
|
719
|
+
...data,
|
|
720
|
+
id: `custom-${Date.now()}`
|
|
721
|
+
};
|
|
722
|
+
saveApps([...userApps, newApp]);
|
|
723
|
+
};
|
|
724
|
+
const handleUpdateApp = (id, updates) => {
|
|
725
|
+
saveApps(userApps.map((app) => app.id === id ? { ...app, ...updates } : app));
|
|
726
|
+
};
|
|
727
|
+
const handleDeleteApp = (id) => {
|
|
728
|
+
saveApps(userApps.filter((app) => app.id !== id));
|
|
729
|
+
};
|
|
730
|
+
const displayApps = isLoaded ? mergeDefaultApps ? [...userApps, ...defaultApps.filter((da) => !userApps.some((ua) => ua.id === da.id))] : userApps.length > 0 ? userApps : defaultApps : [];
|
|
731
|
+
return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
732
|
+
/* @__PURE__ */ jsx5(
|
|
733
|
+
AppLauncher,
|
|
734
|
+
{
|
|
735
|
+
apps: displayApps,
|
|
736
|
+
className,
|
|
737
|
+
renderFooter: () => /* @__PURE__ */ jsxs5(
|
|
738
|
+
"button",
|
|
739
|
+
{
|
|
740
|
+
className: "app-launcher__footer-button",
|
|
741
|
+
style: {
|
|
742
|
+
display: "flex",
|
|
743
|
+
alignItems: "center",
|
|
744
|
+
gap: "8px",
|
|
745
|
+
width: "100%",
|
|
746
|
+
justifyContent: "center",
|
|
747
|
+
border: "none",
|
|
748
|
+
background: "transparent",
|
|
749
|
+
cursor: "pointer",
|
|
750
|
+
color: "var(--al-text-secondary)",
|
|
751
|
+
fontSize: "14px",
|
|
752
|
+
fontWeight: 500
|
|
753
|
+
},
|
|
754
|
+
onClick: () => setShowSettings(true),
|
|
755
|
+
children: [
|
|
756
|
+
/* @__PURE__ */ jsx5(FaCog2, { style: { fontSize: "16px" } }),
|
|
757
|
+
"Settings"
|
|
758
|
+
]
|
|
759
|
+
}
|
|
760
|
+
)
|
|
761
|
+
}
|
|
762
|
+
),
|
|
763
|
+
/* @__PURE__ */ jsx5(
|
|
764
|
+
AppSettings,
|
|
765
|
+
{
|
|
766
|
+
isOpen: showSettings,
|
|
767
|
+
onClose: () => setShowSettings(false),
|
|
768
|
+
apps: userApps,
|
|
769
|
+
defaultApps,
|
|
770
|
+
onAdd: handleAddApp,
|
|
771
|
+
onUpdate: handleUpdateApp,
|
|
772
|
+
onDelete: handleDeleteApp
|
|
773
|
+
}
|
|
774
|
+
)
|
|
775
|
+
] });
|
|
776
|
+
}
|
|
684
777
|
export {
|
|
685
778
|
AppLauncher,
|
|
686
779
|
AppSettings,
|
|
780
|
+
LocalAppLauncher,
|
|
687
781
|
AppLauncher as default,
|
|
688
782
|
getIcon,
|
|
689
783
|
iconMap
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/AppLauncher.tsx","../src/icons.ts","../src/AppSettings.tsx","../src/AddAppForm.tsx","../src/IconPicker.tsx"],"sourcesContent":["'use client';\r\n\r\nimport React, { useState, useRef, useEffect } from 'react';\r\nimport { IoApps } from 'react-icons/io5';\r\nimport { AppLauncherProps, AppItem, ResolvedApp, AppLauncherConfig } from './types';\r\nimport { getIcon } from './icons';\r\nimport './styles.css';\r\n\r\n/**\r\n * A Google-style app launcher component\r\n *\r\n * @example\r\n * // With config URL\r\n * <AppLauncher configUrl=\"https://example.com/apps.json\" />\r\n *\r\n * @example\r\n * // With direct apps array\r\n * <AppLauncher apps={[{ id: '1', name: 'App', url: '/app', icon: 'FaRocket', color: '#4285F4' }]} />\r\n */\r\nexport function AppLauncher({\r\n configUrl,\r\n apps: propApps,\r\n className,\r\n onAppClick,\r\n renderFooter,\r\n}: AppLauncherProps) {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [apps, setApps] = useState<ResolvedApp[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n // Resolve apps from props or fetch from URL\r\n useEffect(() => {\r\n if (propApps) {\r\n setApps(propApps.map(resolveApp));\r\n return;\r\n }\r\n\r\n if (configUrl) {\r\n setLoading(true);\r\n setError(null);\r\n\r\n fetch(configUrl)\r\n .then((res) => {\r\n if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);\r\n return res.json();\r\n })\r\n .then((config: AppLauncherConfig) => {\r\n setApps(config.apps.map(resolveApp));\r\n })\r\n .catch((err) => {\r\n setError(err.message);\r\n console.error('AppLauncher: Failed to load config', err);\r\n })\r\n .finally(() => setLoading(false));\r\n }\r\n }, [configUrl, propApps]);\r\n\r\n // Close on click outside\r\n useEffect(() => {\r\n function handleClickOutside(event: MouseEvent) {\r\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\r\n setIsOpen(false);\r\n }\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('mousedown', handleClickOutside);\r\n }\r\n return () => document.removeEventListener('mousedown', handleClickOutside);\r\n }, [isOpen]);\r\n\r\n // Close on Escape\r\n useEffect(() => {\r\n function handleEscape(event: KeyboardEvent) {\r\n if (event.key === 'Escape') setIsOpen(false);\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('keydown', handleEscape);\r\n }\r\n return () => document.removeEventListener('keydown', handleEscape);\r\n }, [isOpen]);\r\n\r\n // Check if icon is a custom URL (path or full URL)\r\n function isCustomIconUrl(icon: string): boolean {\r\n return icon.startsWith('/') || icon.startsWith('http');\r\n }\r\n\r\n function resolveApp(app: AppItem): ResolvedApp {\r\n const isCustom = isCustomIconUrl(app.icon);\r\n return {\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: isCustom ? null : getIcon(app.icon),\r\n customIconUrl: isCustom ? app.icon : null,\r\n color: app.color,\r\n description: app.description,\r\n };\r\n }\r\n\r\n function handleAppClick(app: ResolvedApp) {\r\n if (onAppClick) {\r\n onAppClick({\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: app.customIconUrl || (app.icon?.name ?? 'FaRocket'),\r\n color: app.color,\r\n description: app.description,\r\n });\r\n } else {\r\n // Open in new tab\r\n window.open(app.url, '_blank', 'noopener,noreferrer');\r\n }\r\n }\r\n\r\n return (\r\n <div className={`app-launcher ${className || ''}`} ref={containerRef}>\r\n {/* Trigger Button */}\r\n <button\r\n className=\"app-launcher__trigger\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n aria-label=\"Open app launcher\"\r\n aria-expanded={isOpen}\r\n >\r\n <IoApps className=\"app-launcher__trigger-icon\" />\r\n </button>\r\n\r\n {/* Dropdown */}\r\n {isOpen && (\r\n <div className=\"app-launcher__dropdown\">\r\n {loading && <div className=\"app-launcher__loading\">Loading...</div>}\r\n\r\n {error && <div className=\"app-launcher__error\">{error}</div>}\r\n\r\n {!loading && !error && (\r\n <div className=\"app-launcher__grid\">\r\n {apps.map((app) => (\r\n <button\r\n key={app.id}\r\n className=\"app-launcher__item\"\r\n onClick={() => handleAppClick(app)}\r\n title={app.description || app.name}\r\n >\r\n <div className=\"app-launcher__icon-wrapper\">\r\n {app.customIconUrl ? (\r\n // Check if it's an SVG - use mask-image for color support\r\n app.customIconUrl.endsWith('.svg') ? (\r\n <div\r\n className=\"app-launcher__icon app-launcher__icon--svg\"\r\n style={{\r\n maskImage: `url(${app.customIconUrl})`,\r\n WebkitMaskImage: `url(${app.customIconUrl})`,\r\n backgroundColor: app.color === 'transparent' ? '#5f6368' : app.color,\r\n }}\r\n aria-label={app.name}\r\n />\r\n ) : (\r\n // Regular image for PNG, JPG, etc.\r\n <img\r\n src={app.customIconUrl}\r\n alt={app.name}\r\n className=\"app-launcher__icon app-launcher__icon--custom\"\r\n />\r\n )\r\n ) : app.icon ? (\r\n <app.icon className=\"app-launcher__icon\" style={{ color: app.color }} />\r\n ) : null}\r\n </div>\r\n <span className=\"app-launcher__name\">{app.name}</span>\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n\r\n {/* Custom footer (e.g., Settings button) */}\r\n {renderFooter && <div className=\"app-launcher__footer\">{renderFooter()}</div>}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nexport default AppLauncher;\r\n","import { IconType } from 'react-icons';\r\n// prettier-ignore\r\nimport {\r\n FaGoogle, FaEnvelope, FaYoutube, FaCalendarAlt, FaMapMarkerAlt,\r\n FaFile, FaBookmark, FaTable, FaNewspaper, FaImage, FaRocket,\r\n FaHome, FaUser, FaCog, FaChartBar, FaShoppingCart, FaDatabase,\r\n FaCode, FaTerminal, FaGlobe, FaLock, FaKey, FaBell, FaHeart,\r\n FaStar, FaFolder, FaClipboard, FaCalculator, FaMusic, FaCamera,\r\n FaGamepad, FaPuzzlePiece, FaBriefcase, FaGraduationCap, FaPlane,\r\n FaCar, FaBicycle, FaUtensils, FaCoffee, FaGift,\r\n} from 'react-icons/fa';\r\nimport { FaTicketSimple } from 'react-icons/fa6';\r\nimport { GrDeliver } from 'react-icons/gr';\r\nimport { IoBusinessSharp, IoApps } from 'react-icons/io5';\r\nimport { MdOutlineSecurity, MdDashboard, MdAnalytics, MdEmail, MdWork } from 'react-icons/md';\r\nimport { SiGoogledrive, SiGooglemeet } from 'react-icons/si';\r\nimport { AiOutlineSecurityScan } from 'react-icons/ai';\r\n\r\n/**\r\n * Map of icon names to icon components\r\n */\r\n// prettier-ignore\r\nexport const iconMap: Record<string, IconType> = {\r\n // Business & Work\r\n FaBriefcase, IoBusinessSharp, MdWork, FaGraduationCap,\r\n // Security\r\n MdOutlineSecurity, FaLock, FaKey, AiOutlineSecurityScan,\r\n // Communication\r\n FaEnvelope, MdEmail, FaBell,\r\n // Media\r\n FaYoutube, FaMusic, FaCamera, FaImage, FaGamepad,\r\n // Productivity\r\n FaCalendarAlt, FaClipboard, FaCalculator, FaFolder, FaFile,\r\n FaBookmark, FaTable, FaNewspaper,\r\n // Navigation\r\n FaMapMarkerAlt, FaGlobe, FaHome,\r\n // Google\r\n FaGoogle, SiGoogledrive, SiGooglemeet,\r\n // Development\r\n FaCode, FaTerminal, FaDatabase, FaPuzzlePiece,\r\n // Analytics\r\n FaChartBar, MdDashboard, MdAnalytics,\r\n // Shopping\r\n FaShoppingCart, FaGift, FaTicketSimple,\r\n // Travel\r\n FaPlane, FaCar, FaBicycle, GrDeliver,\r\n // Food\r\n FaUtensils, FaCoffee,\r\n // General\r\n FaRocket, FaUser, FaCog, FaHeart, FaStar, IoApps,\r\n};\r\n\r\n/**\r\n * Get icon component by name\r\n */\r\nexport function getIcon(name: string): IconType {\r\n return iconMap[name] || FaRocket;\r\n}\r\n","import React, { useState } from 'react';\r\nimport { FaTimes, FaPlus, FaEdit, FaTrash, FaDownload } from 'react-icons/fa';\r\nimport { AddAppForm } from './AddAppForm';\r\nimport { AppItem } from './types';\r\nimport { iconMap } from './icons';\r\n\r\nexport interface AppSettingsProps {\r\n isOpen: boolean;\r\n onClose: () => void;\r\n apps: AppItem[];\r\n defaultApps?: AppItem[];\r\n onAdd: (app: Omit<AppItem, 'id'>) => void;\r\n onUpdate: (id: string, app: Partial<Omit<AppItem, 'id'>>) => void;\r\n onDelete: (id: string) => void;\r\n}\r\n\r\nexport function AppSettings({\r\n isOpen,\r\n onClose,\r\n apps,\r\n defaultApps = [],\r\n onAdd,\r\n onUpdate,\r\n onDelete,\r\n}: AppSettingsProps) {\r\n const [showAddForm, setShowAddForm] = useState(false);\r\n const [editingApp, setEditingApp] = useState<AppItem | null>(null);\r\n\r\n if (!isOpen) return null;\r\n\r\n const handleAddSubmit = (data: Omit<AppItem, 'id'>) => {\r\n onAdd(data);\r\n setShowAddForm(false);\r\n };\r\n\r\n const handleEditSubmit = (data: Omit<AppItem, 'id'>) => {\r\n if (editingApp) {\r\n onUpdate(editingApp.id, data);\r\n setEditingApp(null);\r\n }\r\n };\r\n\r\n const handleDelete = (id: string) => {\r\n if (confirm('Are you sure you want to delete this app?')) {\r\n onDelete(id);\r\n }\r\n };\r\n\r\n const handleExportConfig = () => {\r\n // Combine default apps and user apps into exportable format\r\n const exportedApps = [\r\n // User apps first (they appear at top)\r\n ...apps,\r\n // Then default apps\r\n ...defaultApps.filter((da) => !apps.some((ua) => ua.id === da.id)), // Avoid duplicates if any\r\n ];\r\n\r\n const config = {\r\n version: '1.0',\r\n exportedAt: new Date().toISOString(),\r\n apps: exportedApps,\r\n };\r\n\r\n // Create and download JSON file\r\n const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });\r\n const url = URL.createObjectURL(blob);\r\n const a = document.createElement('a');\r\n a.href = url;\r\n a.download = 'app-launcher-config.json';\r\n document.body.appendChild(a);\r\n a.click();\r\n document.body.removeChild(a);\r\n URL.revokeObjectURL(url);\r\n };\r\n\r\n return (\r\n <div className=\"app-settings__overlay\">\r\n <div className=\"app-settings__modal\" onClick={(e) => e.stopPropagation()}>\r\n {/* Header */}\r\n <div className=\"app-settings__header\">\r\n <h2 className=\"app-settings__title\">\r\n {showAddForm ? 'Add New App' : editingApp ? 'Edit App' : 'Settings'}\r\n </h2>\r\n <button className=\"app-settings__close-button\" onClick={onClose} aria-label=\"Close\">\r\n <FaTimes />\r\n </button>\r\n </div>\r\n\r\n {/* Content */}\r\n <div className=\"app-settings__content\">\r\n {showAddForm ? (\r\n <AddAppForm\r\n onSubmit={handleAddSubmit}\r\n onCancel={() => setShowAddForm(false)}\r\n submitLabel=\"Add App\"\r\n />\r\n ) : editingApp ? (\r\n <AddAppForm\r\n initialData={editingApp}\r\n onSubmit={handleEditSubmit}\r\n onCancel={() => setEditingApp(null)}\r\n submitLabel=\"Save Changes\"\r\n />\r\n ) : (\r\n <>\r\n {/* Add App Button */}\r\n <button className=\"app-settings__add-button\" onClick={() => setShowAddForm(true)}>\r\n <FaPlus className=\"app-settings__add-icon\" />\r\n Add New App\r\n </button>\r\n\r\n {/* Apps List */}\r\n <div className=\"app-settings__list\">\r\n <h3 className=\"app-settings__section-title\">My Custom Apps</h3>\r\n\r\n {apps.length === 0 ? (\r\n <p className=\"app-settings__empty-message\">\r\n No custom apps yet. Click "Add New App" to get started.\r\n </p>\r\n ) : (\r\n <div className=\"app-settings__grid\">\r\n {apps.map((app) => {\r\n // Determine icon\r\n const isCustom = app.icon.startsWith('/') || app.icon.startsWith('http');\r\n const Icon = !isCustom ? iconMap[app.icon] || iconMap['FaRocket'] : null;\r\n\r\n return (\r\n <div key={app.id} className=\"app-settings__card\">\r\n <div className=\"app-settings__card-info\">\r\n <div\r\n className=\"app-settings__card-icon\"\r\n style={{ backgroundColor: app.color + '20' }}\r\n >\r\n {isCustom ? (\r\n app.icon.endsWith('.svg') ? (\r\n <div\r\n style={{\r\n width: 20,\r\n height: 20,\r\n maskImage: `url(${app.icon})`,\r\n WebkitMaskImage: `url(${app.icon})`,\r\n maskSize: 'contain',\r\n maskRepeat: 'no-repeat',\r\n maskPosition: 'center',\r\n WebkitMaskSize: 'contain',\r\n WebkitMaskRepeat: 'no-repeat',\r\n WebkitMaskPosition: 'center',\r\n backgroundColor:\r\n app.color === 'transparent' ? '#5f6368' : app.color,\r\n }}\r\n />\r\n ) : (\r\n <img\r\n src={app.icon}\r\n alt={app.name}\r\n style={{ width: 20, height: 20, objectFit: 'contain' }}\r\n />\r\n )\r\n ) : (\r\n Icon && <Icon style={{ color: app.color }} />\r\n )}\r\n </div>\r\n <div className=\"app-settings__card-details\">\r\n <span className=\"app-settings__card-name\">{app.name}</span>\r\n <span className=\"app-settings__card-url\">{app.url}</span>\r\n </div>\r\n </div>\r\n <div className=\"app-settings__card-actions\">\r\n <button\r\n className=\"app-settings__action-button\"\r\n onClick={() => setEditingApp(app)}\r\n title=\"Edit\"\r\n >\r\n <FaEdit />\r\n </button>\r\n <button\r\n className=\"app-settings__action-button app-settings__action-button--delete\"\r\n onClick={() => handleDelete(app.id)}\r\n title=\"Delete\"\r\n >\r\n <FaTrash />\r\n </button>\r\n </div>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Export Configuration */}\r\n <button className=\"app-settings__export-button\" onClick={handleExportConfig}>\r\n <FaDownload className=\"app-settings__export-icon\" />\r\n Export Configuration\r\n </button>\r\n\r\n {/* Info */}\r\n <div className=\"app-settings__info\">\r\n <p>\r\n Custom apps are saved in your browser. Use "Export Configuration" to\r\n share with other apps.\r\n </p>\r\n </div>\r\n </>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n}\r\n","import React, { useState } from 'react';\r\nimport { IconPicker } from './IconPicker';\r\nimport { iconMap } from './icons';\r\nimport { AppItem } from './types';\r\n\r\nexport interface AddAppFormProps {\r\n initialData?: Partial<AppItem>;\r\n onSubmit: (data: Omit<AppItem, 'id'>) => void;\r\n onCancel: () => void;\r\n submitLabel?: string;\r\n}\r\n\r\nconst presetColors = [\r\n '#4285F4', // Blue\r\n '#EA4335', // Red\r\n '#FBBC04', // Yellow\r\n '#34A853', // Green\r\n '#FF6D01', // Orange\r\n '#46BDC6', // Teal\r\n '#7B1FA2', // Purple\r\n '#E91E63', // Pink\r\n '#607D8B', // Gray\r\n '#795548', // Brown\r\n];\r\n\r\ntype IconMode = 'picker' | 'custom';\r\n\r\nexport function AddAppForm({\r\n initialData,\r\n onSubmit,\r\n onCancel,\r\n submitLabel = 'Add App',\r\n}: AddAppFormProps) {\r\n const [name, setName] = useState(initialData?.name || '');\r\n const [url, setUrl] = useState(initialData?.url || '');\r\n\r\n // Determine initial icon state\r\n const initialIcon = initialData?.icon || 'FaRocket';\r\n const isInitialCustom = initialIcon.startsWith('/') || initialIcon.startsWith('http');\r\n\r\n const [iconName, setIconName] = useState(isInitialCustom ? 'FaRocket' : initialIcon);\r\n const [customIconUrl, setCustomIconUrl] = useState(isInitialCustom ? initialIcon : '');\r\n const [iconMode, setIconMode] = useState<IconMode>(isInitialCustom ? 'custom' : 'picker');\r\n\r\n const [color, setColor] = useState(initialData?.color || '#4285F4');\r\n const [description, setDescription] = useState(initialData?.description || '');\r\n const [errors, setErrors] = useState<Record<string, string>>({});\r\n\r\n const SelectedIcon = iconMap[iconName] || iconMap['FaRocket'];\r\n\r\n const validate = (): boolean => {\r\n const newErrors: Record<string, string> = {};\r\n\r\n if (!name.trim()) {\r\n newErrors.name = 'Name is required';\r\n }\r\n\r\n if (!url.trim()) {\r\n newErrors.url = 'URL is required';\r\n } else if (!url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('/')) {\r\n newErrors.url = 'URL must start with http://, https://, or /';\r\n }\r\n\r\n if (iconMode === 'custom' && customIconUrl.trim()) {\r\n if (\r\n !customIconUrl.startsWith('http://') &&\r\n !customIconUrl.startsWith('https://') &&\r\n !customIconUrl.startsWith('/')\r\n ) {\r\n newErrors.customIconUrl = 'Icon URL must start with http://, https://, or /';\r\n }\r\n }\r\n\r\n setErrors(newErrors);\r\n return Object.keys(newErrors).length === 0;\r\n };\r\n\r\n const handleSubmit = (e: React.FormEvent) => {\r\n e.preventDefault();\r\n\r\n if (!validate()) return;\r\n\r\n const finalIcon =\r\n iconMode === 'custom' && customIconUrl.trim() ? customIconUrl.trim() : iconName;\r\n\r\n onSubmit({\r\n name: name.trim(),\r\n url: url.trim(),\r\n icon: finalIcon,\r\n color,\r\n description: description.trim() || undefined,\r\n });\r\n };\r\n\r\n return (\r\n <form onSubmit={handleSubmit} className=\"app-launcher-form\">\r\n {/* Preview */}\r\n <div className=\"app-launcher-form__preview\">\r\n <div className=\"app-launcher-form__preview-icon\" style={{ backgroundColor: color + '20' }}>\r\n {iconMode === 'custom' && customIconUrl ? (\r\n customIconUrl.endsWith('.svg') ? (\r\n <div\r\n className=\"app-launcher-form__preview-svg\"\r\n style={{\r\n maskImage: `url(${customIconUrl})`,\r\n WebkitMaskImage: `url(${customIconUrl})`,\r\n backgroundColor: color === 'transparent' ? '#5f6368' : color,\r\n }}\r\n />\r\n ) : (\r\n <img\r\n src={customIconUrl}\r\n alt=\"Icon preview\"\r\n className=\"app-launcher-form__preview-img\"\r\n />\r\n )\r\n ) : (\r\n <SelectedIcon style={{ color, fontSize: 32 }} />\r\n )}\r\n </div>\r\n <span className=\"app-launcher-form__preview-name\">{name || 'App Name'}</span>\r\n </div>\r\n\r\n {/* Name Input */}\r\n <div className=\"app-launcher-form__field\">\r\n <label htmlFor=\"app-name\" className=\"app-launcher-form__label\">\r\n Name *\r\n </label>\r\n <input\r\n id=\"app-name\"\r\n type=\"text\"\r\n value={name}\r\n onChange={(e) => setName(e.target.value)}\r\n placeholder=\"My App\"\r\n className={`app-launcher-form__input ${errors.name ? 'app-launcher-form__input--error' : ''}`}\r\n />\r\n {errors.name && <span className=\"app-launcher-form__error\">{errors.name}</span>}\r\n </div>\r\n\r\n {/* URL Input */}\r\n <div className=\"app-launcher-form__field\">\r\n <label htmlFor=\"app-url\" className=\"app-launcher-form__label\">\r\n URL *\r\n </label>\r\n <input\r\n id=\"app-url\"\r\n type=\"text\"\r\n value={url}\r\n onChange={(e) => setUrl(e.target.value)}\r\n placeholder=\"https://myapp.com or /dashboard\"\r\n className={`app-launcher-form__input ${errors.url ? 'app-launcher-form__input--error' : ''}`}\r\n />\r\n {errors.url && <span className=\"app-launcher-form__error\">{errors.url}</span>}\r\n </div>\r\n\r\n {/* Description Input */}\r\n <div className=\"app-launcher-form__field\">\r\n <label htmlFor=\"app-description\" className=\"app-launcher-form__label\">\r\n Description\r\n </label>\r\n <input\r\n id=\"app-description\"\r\n type=\"text\"\r\n value={description}\r\n onChange={(e) => setDescription(e.target.value)}\r\n placeholder=\"Optional description (shown on hover)\"\r\n className=\"app-launcher-form__input\"\r\n />\r\n </div>\r\n\r\n {/* Color Picker */}\r\n <div className=\"app-launcher-form__field\">\r\n <label className=\"app-launcher-form__label\">Color</label>\r\n <div className=\"app-launcher-form__color-picker\">\r\n {presetColors.map((presetColor) => (\r\n <button\r\n key={presetColor}\r\n type=\"button\"\r\n className={`app-launcher-form__color-button ${\r\n color === presetColor ? 'app-launcher-form__color-button--selected' : ''\r\n }`}\r\n style={{ backgroundColor: presetColor }}\r\n onClick={() => setColor(presetColor)}\r\n title={presetColor}\r\n />\r\n ))}\r\n <input\r\n type=\"color\"\r\n value={color}\r\n onChange={(e) => setColor(e.target.value)}\r\n className=\"app-launcher-form__color-input\"\r\n title=\"Custom color\"\r\n />\r\n </div>\r\n </div>\r\n\r\n {/* Icon Selection */}\r\n <div className=\"app-launcher-form__field\">\r\n <label className=\"app-launcher-form__label\">Icon</label>\r\n\r\n {/* Icon Mode Toggle */}\r\n <div className=\"app-launcher-form__icon-mode\">\r\n <button\r\n type=\"button\"\r\n className={`app-launcher-form__mode-button ${iconMode === 'picker' ? 'app-launcher-form__mode-button--active' : ''}`}\r\n onClick={() => setIconMode('picker')}\r\n >\r\n Icon Picker\r\n </button>\r\n <button\r\n type=\"button\"\r\n className={`app-launcher-form__mode-button ${iconMode === 'custom' ? 'app-launcher-form__mode-button--active' : ''}`}\r\n onClick={() => setIconMode('custom')}\r\n >\r\n Custom URL\r\n </button>\r\n </div>\r\n\r\n {iconMode === 'picker' ? (\r\n <IconPicker selectedIcon={iconName} onSelect={setIconName} />\r\n ) : (\r\n <div className=\"app-launcher-form__custom-icon\">\r\n <input\r\n id=\"app-custom-icon\"\r\n type=\"text\"\r\n value={customIconUrl}\r\n onChange={(e) => setCustomIconUrl(e.target.value)}\r\n placeholder=\"/icons/my-icon.svg or https://cdn.example.com/icon.svg\"\r\n className={`app-launcher-form__input ${errors.customIconUrl ? 'app-launcher-form__input--error' : ''}`}\r\n />\r\n {errors.customIconUrl && (\r\n <span className=\"app-launcher-form__error\">{errors.customIconUrl}</span>\r\n )}\r\n <p className=\"app-launcher-form__hint\">\r\n Enter a path from your public folder (e.g., /icons/logo.svg) or a full URL\r\n </p>\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Actions */}\r\n <div className=\"app-launcher-form__actions\">\r\n <button\r\n type=\"button\"\r\n onClick={onCancel}\r\n className=\"app-launcher-form__button app-launcher-form__button--cancel\"\r\n >\r\n Cancel\r\n </button>\r\n <button\r\n type=\"submit\"\r\n className=\"app-launcher-form__button app-launcher-form__button--submit\"\r\n >\r\n {submitLabel}\r\n </button>\r\n </div>\r\n </form>\r\n );\r\n}\r\n","import React, { useState } from 'react';\r\nimport { iconMap } from './icons';\r\n\r\ninterface IconPickerProps {\r\n selectedIcon: string;\r\n onSelect: (iconName: string) => void;\r\n}\r\n\r\n// Group icons by category/type based on their names or define categories manually\r\n// For simplicity in this package version, we'll list all available icons\r\nconst allIcons = Object.keys(iconMap);\r\n\r\nexport function IconPicker({ selectedIcon, onSelect }: IconPickerProps) {\r\n const [searchTerm, setSearchTerm] = useState('');\r\n\r\n const filteredIcons = allIcons.filter((name) =>\r\n name.toLowerCase().includes(searchTerm.toLowerCase())\r\n );\r\n\r\n return (\r\n <div className=\"app-launcher-icon-picker\">\r\n <input\r\n type=\"text\"\r\n placeholder=\"Search icons...\"\r\n value={searchTerm}\r\n onChange={(e) => setSearchTerm(e.target.value)}\r\n className=\"app-launcher-icon-picker__search\"\r\n />\r\n\r\n <div className=\"app-launcher-icon-picker__grid\">\r\n {filteredIcons.map((iconName) => {\r\n const Icon = iconMap[iconName];\r\n const isSelected = selectedIcon === iconName;\r\n\r\n return (\r\n <button\r\n key={iconName}\r\n className={`app-launcher-icon-picker__button ${\r\n isSelected ? 'app-launcher-icon-picker__button--selected' : ''\r\n }`}\r\n onClick={() => onSelect(iconName)}\r\n title={iconName}\r\n type=\"button\"\r\n >\r\n <Icon className=\"app-launcher-icon-picker__icon\" />\r\n </button>\r\n );\r\n })}\r\n </div>\r\n\r\n {filteredIcons.length === 0 && (\r\n <div className=\"app-launcher-icon-picker__empty\">No icons found</div>\r\n )}\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["IoApps","IoApps","useState","useState","useState","jsx","jsxs","useState","jsx","jsxs","useState","jsx","jsxs","useState"]}
|
|
1
|
+
{"version":3,"sources":["../src/AppLauncher.tsx","../src/icons.ts","../src/AppSettings.tsx","../src/AddAppForm.tsx","../src/IconPicker.tsx","../src/LocalAppLauncher.tsx"],"sourcesContent":["'use client';\r\n\r\nimport React, { useState, useRef, useEffect } from 'react';\r\nimport { IoApps } from 'react-icons/io5';\r\nimport { AppLauncherProps, AppItem, ResolvedApp, AppLauncherConfig } from './types';\r\nimport { getIcon } from './icons';\r\nimport './styles.css';\r\n\r\n/**\r\n * A Google-style app launcher component\r\n *\r\n * @example\r\n * // With config URL\r\n * <AppLauncher configUrl=\"https://example.com/apps.json\" />\r\n *\r\n * @example\r\n * // With direct apps array\r\n * <AppLauncher apps={[{ id: '1', name: 'App', url: '/app', icon: 'FaRocket', color: '#4285F4' }]} />\r\n */\r\nexport function AppLauncher({\r\n configUrl,\r\n apps: propApps,\r\n className,\r\n onAppClick,\r\n renderFooter,\r\n}: AppLauncherProps) {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [apps, setApps] = useState<ResolvedApp[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n // Resolve apps from props or fetch from URL\r\n useEffect(() => {\r\n if (propApps) {\r\n setApps(propApps.map(resolveApp));\r\n return;\r\n }\r\n\r\n if (configUrl) {\r\n setLoading(true);\r\n setError(null);\r\n\r\n fetch(configUrl)\r\n .then((res) => {\r\n if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);\r\n return res.json();\r\n })\r\n .then((config: AppLauncherConfig) => {\r\n setApps(config.apps.map(resolveApp));\r\n })\r\n .catch((err) => {\r\n setError(err.message);\r\n console.error('AppLauncher: Failed to load config', err);\r\n })\r\n .finally(() => setLoading(false));\r\n }\r\n }, [configUrl, propApps]);\r\n\r\n // Close on click outside\r\n useEffect(() => {\r\n function handleClickOutside(event: MouseEvent) {\r\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\r\n setIsOpen(false);\r\n }\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('mousedown', handleClickOutside);\r\n }\r\n return () => document.removeEventListener('mousedown', handleClickOutside);\r\n }, [isOpen]);\r\n\r\n // Close on Escape\r\n useEffect(() => {\r\n function handleEscape(event: KeyboardEvent) {\r\n if (event.key === 'Escape') setIsOpen(false);\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('keydown', handleEscape);\r\n }\r\n return () => document.removeEventListener('keydown', handleEscape);\r\n }, [isOpen]);\r\n\r\n // Check if icon is a custom URL (path or full URL)\r\n function isCustomIconUrl(icon: string): boolean {\r\n return icon.startsWith('/') || icon.startsWith('http');\r\n }\r\n\r\n function resolveApp(app: AppItem): ResolvedApp {\r\n const isCustom = isCustomIconUrl(app.icon);\r\n return {\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: isCustom ? null : getIcon(app.icon),\r\n customIconUrl: isCustom ? app.icon : null,\r\n color: app.color,\r\n description: app.description,\r\n };\r\n }\r\n\r\n function handleAppClick(app: ResolvedApp) {\r\n if (onAppClick) {\r\n onAppClick({\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: app.customIconUrl || (app.icon?.name ?? 'FaRocket'),\r\n color: app.color,\r\n description: app.description,\r\n });\r\n } else {\r\n // Open in new tab\r\n window.open(app.url, '_blank', 'noopener,noreferrer');\r\n }\r\n }\r\n\r\n return (\r\n <div className={`app-launcher ${className || ''}`} ref={containerRef}>\r\n {/* Trigger Button */}\r\n <button\r\n className=\"app-launcher__trigger\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n aria-label=\"Open app launcher\"\r\n aria-expanded={isOpen}\r\n >\r\n <IoApps className=\"app-launcher__trigger-icon\" />\r\n </button>\r\n\r\n {/* Dropdown */}\r\n {isOpen && (\r\n <div className=\"app-launcher__dropdown\">\r\n {loading && <div className=\"app-launcher__loading\">Loading...</div>}\r\n\r\n {error && <div className=\"app-launcher__error\">{error}</div>}\r\n\r\n {!loading && !error && (\r\n <div className=\"app-launcher__grid\">\r\n {apps.map((app) => (\r\n <button\r\n key={app.id}\r\n className=\"app-launcher__item\"\r\n onClick={() => handleAppClick(app)}\r\n title={app.description || app.name}\r\n >\r\n <div className=\"app-launcher__icon-wrapper\">\r\n {app.customIconUrl ? (\r\n // Check if it's an SVG - use mask-image for color support\r\n app.customIconUrl.endsWith('.svg') ? (\r\n <div\r\n className=\"app-launcher__icon app-launcher__icon--svg\"\r\n style={{\r\n maskImage: `url(${app.customIconUrl})`,\r\n WebkitMaskImage: `url(${app.customIconUrl})`,\r\n backgroundColor: app.color === 'transparent' ? '#5f6368' : app.color,\r\n }}\r\n aria-label={app.name}\r\n />\r\n ) : (\r\n // Regular image for PNG, JPG, etc.\r\n <img\r\n src={app.customIconUrl}\r\n alt={app.name}\r\n className=\"app-launcher__icon app-launcher__icon--custom\"\r\n />\r\n )\r\n ) : app.icon ? (\r\n <app.icon className=\"app-launcher__icon\" style={{ color: app.color }} />\r\n ) : null}\r\n </div>\r\n <span className=\"app-launcher__name\">{app.name}</span>\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n\r\n {/* Custom footer (e.g., Settings button) */}\r\n {renderFooter && <div className=\"app-launcher__footer\">{renderFooter()}</div>}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nexport default AppLauncher;\r\n","import { IconType } from 'react-icons';\r\n// prettier-ignore\r\nimport {\r\n FaGoogle, FaEnvelope, FaYoutube, FaCalendarAlt, FaMapMarkerAlt,\r\n FaFile, FaBookmark, FaTable, FaNewspaper, FaImage, FaRocket,\r\n FaHome, FaUser, FaCog, FaChartBar, FaShoppingCart, FaDatabase,\r\n FaCode, FaTerminal, FaGlobe, FaLock, FaKey, FaBell, FaHeart,\r\n FaStar, FaFolder, FaClipboard, FaCalculator, FaMusic, FaCamera,\r\n FaGamepad, FaPuzzlePiece, FaBriefcase, FaGraduationCap, FaPlane,\r\n FaCar, FaBicycle, FaUtensils, FaCoffee, FaGift,\r\n} from 'react-icons/fa';\r\nimport { FaTicketSimple } from 'react-icons/fa6';\r\nimport { GrDeliver } from 'react-icons/gr';\r\nimport { IoBusinessSharp, IoApps } from 'react-icons/io5';\r\nimport { MdOutlineSecurity, MdDashboard, MdAnalytics, MdEmail, MdWork } from 'react-icons/md';\r\nimport { SiGoogledrive, SiGooglemeet } from 'react-icons/si';\r\nimport { AiOutlineSecurityScan } from 'react-icons/ai';\r\n\r\n/**\r\n * Map of icon names to icon components\r\n */\r\n// prettier-ignore\r\nexport const iconMap: Record<string, IconType> = {\r\n // Business & Work\r\n FaBriefcase, IoBusinessSharp, MdWork, FaGraduationCap,\r\n // Security\r\n MdOutlineSecurity, FaLock, FaKey, AiOutlineSecurityScan,\r\n // Communication\r\n FaEnvelope, MdEmail, FaBell,\r\n // Media\r\n FaYoutube, FaMusic, FaCamera, FaImage, FaGamepad,\r\n // Productivity\r\n FaCalendarAlt, FaClipboard, FaCalculator, FaFolder, FaFile,\r\n FaBookmark, FaTable, FaNewspaper,\r\n // Navigation\r\n FaMapMarkerAlt, FaGlobe, FaHome,\r\n // Google\r\n FaGoogle, SiGoogledrive, SiGooglemeet,\r\n // Development\r\n FaCode, FaTerminal, FaDatabase, FaPuzzlePiece,\r\n // Analytics\r\n FaChartBar, MdDashboard, MdAnalytics,\r\n // Shopping\r\n FaShoppingCart, FaGift, FaTicketSimple,\r\n // Travel\r\n FaPlane, FaCar, FaBicycle, GrDeliver,\r\n // Food\r\n FaUtensils, FaCoffee,\r\n // General\r\n FaRocket, FaUser, FaCog, FaHeart, FaStar, IoApps,\r\n};\r\n\r\n/**\r\n * Get icon component by name\r\n */\r\nexport function getIcon(name: string): IconType {\r\n return iconMap[name] || FaRocket;\r\n}\r\n","import React, { useState } from 'react';\r\nimport { FaTimes, FaPlus, FaEdit, FaTrash, FaDownload } from 'react-icons/fa';\r\nimport { AddAppForm } from './AddAppForm';\r\nimport { AppItem } from './types';\r\nimport { iconMap } from './icons';\r\n\r\nexport interface AppSettingsProps {\r\n isOpen: boolean;\r\n onClose: () => void;\r\n apps: AppItem[];\r\n defaultApps?: AppItem[];\r\n onAdd: (app: Omit<AppItem, 'id'>) => void;\r\n onUpdate: (id: string, app: Partial<Omit<AppItem, 'id'>>) => void;\r\n onDelete: (id: string) => void;\r\n}\r\n\r\nexport function AppSettings({\r\n isOpen,\r\n onClose,\r\n apps,\r\n defaultApps = [],\r\n onAdd,\r\n onUpdate,\r\n onDelete,\r\n}: AppSettingsProps) {\r\n const [showAddForm, setShowAddForm] = useState(false);\r\n const [editingApp, setEditingApp] = useState<AppItem | null>(null);\r\n\r\n if (!isOpen) return null;\r\n\r\n const handleAddSubmit = (data: Omit<AppItem, 'id'>) => {\r\n onAdd(data);\r\n setShowAddForm(false);\r\n };\r\n\r\n const handleEditSubmit = (data: Omit<AppItem, 'id'>) => {\r\n if (editingApp) {\r\n onUpdate(editingApp.id, data);\r\n setEditingApp(null);\r\n }\r\n };\r\n\r\n const handleDelete = (id: string) => {\r\n if (confirm('Are you sure you want to delete this app?')) {\r\n onDelete(id);\r\n }\r\n };\r\n\r\n const handleExportConfig = () => {\r\n // Combine default apps and user apps into exportable format\r\n const exportedApps = [\r\n // User apps first (they appear at top)\r\n ...apps,\r\n // Then default apps\r\n ...defaultApps.filter((da) => !apps.some((ua) => ua.id === da.id)), // Avoid duplicates if any\r\n ];\r\n\r\n const config = {\r\n version: '1.0',\r\n exportedAt: new Date().toISOString(),\r\n apps: exportedApps,\r\n };\r\n\r\n // Create and download JSON file\r\n const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });\r\n const url = URL.createObjectURL(blob);\r\n const a = document.createElement('a');\r\n a.href = url;\r\n a.download = 'app-launcher-config.json';\r\n document.body.appendChild(a);\r\n a.click();\r\n document.body.removeChild(a);\r\n URL.revokeObjectURL(url);\r\n };\r\n\r\n return (\r\n <div className=\"app-settings__overlay\">\r\n <div className=\"app-settings__modal\" onClick={(e) => e.stopPropagation()}>\r\n {/* Header */}\r\n <div className=\"app-settings__header\">\r\n <h2 className=\"app-settings__title\">\r\n {showAddForm ? 'Add New App' : editingApp ? 'Edit App' : 'Settings'}\r\n </h2>\r\n <button className=\"app-settings__close-button\" onClick={onClose} aria-label=\"Close\">\r\n <FaTimes />\r\n </button>\r\n </div>\r\n\r\n {/* Content */}\r\n <div className=\"app-settings__content\">\r\n {showAddForm ? (\r\n <AddAppForm\r\n onSubmit={handleAddSubmit}\r\n onCancel={() => setShowAddForm(false)}\r\n submitLabel=\"Add App\"\r\n />\r\n ) : editingApp ? (\r\n <AddAppForm\r\n initialData={editingApp}\r\n onSubmit={handleEditSubmit}\r\n onCancel={() => setEditingApp(null)}\r\n submitLabel=\"Save Changes\"\r\n />\r\n ) : (\r\n <>\r\n {/* Add App Button */}\r\n <button className=\"app-settings__add-button\" onClick={() => setShowAddForm(true)}>\r\n <FaPlus className=\"app-settings__add-icon\" />\r\n Add New App\r\n </button>\r\n\r\n {/* Apps List */}\r\n <div className=\"app-settings__list\">\r\n <h3 className=\"app-settings__section-title\">My Custom Apps</h3>\r\n\r\n {apps.length === 0 ? (\r\n <p className=\"app-settings__empty-message\">\r\n No custom apps yet. Click "Add New App" to get started.\r\n </p>\r\n ) : (\r\n <div className=\"app-settings__grid\">\r\n {apps.map((app) => {\r\n // Determine icon\r\n const isCustom = app.icon.startsWith('/') || app.icon.startsWith('http');\r\n const Icon = !isCustom ? iconMap[app.icon] || iconMap['FaRocket'] : null;\r\n\r\n return (\r\n <div key={app.id} className=\"app-settings__card\">\r\n <div className=\"app-settings__card-info\">\r\n <div\r\n className=\"app-settings__card-icon\"\r\n style={{ backgroundColor: app.color + '20' }}\r\n >\r\n {isCustom ? (\r\n app.icon.endsWith('.svg') ? (\r\n <div\r\n style={{\r\n width: 20,\r\n height: 20,\r\n maskImage: `url(${app.icon})`,\r\n WebkitMaskImage: `url(${app.icon})`,\r\n maskSize: 'contain',\r\n maskRepeat: 'no-repeat',\r\n maskPosition: 'center',\r\n WebkitMaskSize: 'contain',\r\n WebkitMaskRepeat: 'no-repeat',\r\n WebkitMaskPosition: 'center',\r\n backgroundColor:\r\n app.color === 'transparent' ? '#5f6368' : app.color,\r\n }}\r\n />\r\n ) : (\r\n <img\r\n src={app.icon}\r\n alt={app.name}\r\n style={{ width: 20, height: 20, objectFit: 'contain' }}\r\n />\r\n )\r\n ) : (\r\n Icon && <Icon style={{ color: app.color }} />\r\n )}\r\n </div>\r\n <div className=\"app-settings__card-details\">\r\n <span className=\"app-settings__card-name\">{app.name}</span>\r\n <span className=\"app-settings__card-url\">{app.url}</span>\r\n </div>\r\n </div>\r\n <div className=\"app-settings__card-actions\">\r\n <button\r\n className=\"app-settings__action-button\"\r\n onClick={() => setEditingApp(app)}\r\n title=\"Edit\"\r\n >\r\n <FaEdit />\r\n </button>\r\n <button\r\n className=\"app-settings__action-button app-settings__action-button--delete\"\r\n onClick={() => handleDelete(app.id)}\r\n title=\"Delete\"\r\n >\r\n <FaTrash />\r\n </button>\r\n </div>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Export Configuration */}\r\n <button className=\"app-settings__export-button\" onClick={handleExportConfig}>\r\n <FaDownload className=\"app-settings__export-icon\" />\r\n Export Configuration\r\n </button>\r\n\r\n {/* Info */}\r\n <div className=\"app-settings__info\">\r\n <p>\r\n Custom apps are saved in your browser. Use "Export Configuration" to\r\n share with other apps.\r\n </p>\r\n </div>\r\n </>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n}\r\n","import React, { useState } from 'react';\r\nimport { IconPicker } from './IconPicker';\r\nimport { iconMap } from './icons';\r\nimport { AppItem } from './types';\r\n\r\nexport interface AddAppFormProps {\r\n initialData?: Partial<AppItem>;\r\n onSubmit: (data: Omit<AppItem, 'id'>) => void;\r\n onCancel: () => void;\r\n submitLabel?: string;\r\n}\r\n\r\nconst presetColors = [\r\n '#4285F4', // Blue\r\n '#EA4335', // Red\r\n '#FBBC04', // Yellow\r\n '#34A853', // Green\r\n '#FF6D01', // Orange\r\n '#46BDC6', // Teal\r\n '#7B1FA2', // Purple\r\n '#E91E63', // Pink\r\n '#607D8B', // Gray\r\n '#795548', // Brown\r\n];\r\n\r\ntype IconMode = 'picker' | 'custom';\r\n\r\nexport function AddAppForm({\r\n initialData,\r\n onSubmit,\r\n onCancel,\r\n submitLabel = 'Add App',\r\n}: AddAppFormProps) {\r\n const [name, setName] = useState(initialData?.name || '');\r\n const [url, setUrl] = useState(initialData?.url || '');\r\n\r\n // Determine initial icon state\r\n const initialIcon = initialData?.icon || 'FaRocket';\r\n const isInitialCustom = initialIcon.startsWith('/') || initialIcon.startsWith('http');\r\n\r\n const [iconName, setIconName] = useState(isInitialCustom ? 'FaRocket' : initialIcon);\r\n const [customIconUrl, setCustomIconUrl] = useState(isInitialCustom ? initialIcon : '');\r\n const [iconMode, setIconMode] = useState<IconMode>(isInitialCustom ? 'custom' : 'picker');\r\n\r\n const [color, setColor] = useState(initialData?.color || '#4285F4');\r\n const [description, setDescription] = useState(initialData?.description || '');\r\n const [errors, setErrors] = useState<Record<string, string>>({});\r\n\r\n const SelectedIcon = iconMap[iconName] || iconMap['FaRocket'];\r\n\r\n const validate = (): boolean => {\r\n const newErrors: Record<string, string> = {};\r\n\r\n if (!name.trim()) {\r\n newErrors.name = 'Name is required';\r\n }\r\n\r\n if (!url.trim()) {\r\n newErrors.url = 'URL is required';\r\n } else if (!url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('/')) {\r\n newErrors.url = 'URL must start with http://, https://, or /';\r\n }\r\n\r\n if (iconMode === 'custom' && customIconUrl.trim()) {\r\n if (\r\n !customIconUrl.startsWith('http://') &&\r\n !customIconUrl.startsWith('https://') &&\r\n !customIconUrl.startsWith('/')\r\n ) {\r\n newErrors.customIconUrl = 'Icon URL must start with http://, https://, or /';\r\n }\r\n }\r\n\r\n setErrors(newErrors);\r\n return Object.keys(newErrors).length === 0;\r\n };\r\n\r\n const handleSubmit = (e: React.FormEvent) => {\r\n e.preventDefault();\r\n\r\n if (!validate()) return;\r\n\r\n const finalIcon =\r\n iconMode === 'custom' && customIconUrl.trim() ? customIconUrl.trim() : iconName;\r\n\r\n onSubmit({\r\n name: name.trim(),\r\n url: url.trim(),\r\n icon: finalIcon,\r\n color,\r\n description: description.trim() || undefined,\r\n });\r\n };\r\n\r\n return (\r\n <form onSubmit={handleSubmit} className=\"app-launcher-form\">\r\n {/* Preview */}\r\n <div className=\"app-launcher-form__preview\">\r\n <div className=\"app-launcher-form__preview-icon\" style={{ backgroundColor: color + '20' }}>\r\n {iconMode === 'custom' && customIconUrl ? (\r\n customIconUrl.endsWith('.svg') ? (\r\n <div\r\n className=\"app-launcher-form__preview-svg\"\r\n style={{\r\n maskImage: `url(${customIconUrl})`,\r\n WebkitMaskImage: `url(${customIconUrl})`,\r\n backgroundColor: color === 'transparent' ? '#5f6368' : color,\r\n }}\r\n />\r\n ) : (\r\n <img\r\n src={customIconUrl}\r\n alt=\"Icon preview\"\r\n className=\"app-launcher-form__preview-img\"\r\n />\r\n )\r\n ) : (\r\n <SelectedIcon style={{ color, fontSize: 32 }} />\r\n )}\r\n </div>\r\n <span className=\"app-launcher-form__preview-name\">{name || 'App Name'}</span>\r\n </div>\r\n\r\n {/* Name Input */}\r\n <div className=\"app-launcher-form__field\">\r\n <label htmlFor=\"app-name\" className=\"app-launcher-form__label\">\r\n Name *\r\n </label>\r\n <input\r\n id=\"app-name\"\r\n type=\"text\"\r\n value={name}\r\n onChange={(e) => setName(e.target.value)}\r\n placeholder=\"My App\"\r\n className={`app-launcher-form__input ${errors.name ? 'app-launcher-form__input--error' : ''}`}\r\n />\r\n {errors.name && <span className=\"app-launcher-form__error\">{errors.name}</span>}\r\n </div>\r\n\r\n {/* URL Input */}\r\n <div className=\"app-launcher-form__field\">\r\n <label htmlFor=\"app-url\" className=\"app-launcher-form__label\">\r\n URL *\r\n </label>\r\n <input\r\n id=\"app-url\"\r\n type=\"text\"\r\n value={url}\r\n onChange={(e) => setUrl(e.target.value)}\r\n placeholder=\"https://myapp.com or /dashboard\"\r\n className={`app-launcher-form__input ${errors.url ? 'app-launcher-form__input--error' : ''}`}\r\n />\r\n {errors.url && <span className=\"app-launcher-form__error\">{errors.url}</span>}\r\n </div>\r\n\r\n {/* Description Input */}\r\n <div className=\"app-launcher-form__field\">\r\n <label htmlFor=\"app-description\" className=\"app-launcher-form__label\">\r\n Description\r\n </label>\r\n <input\r\n id=\"app-description\"\r\n type=\"text\"\r\n value={description}\r\n onChange={(e) => setDescription(e.target.value)}\r\n placeholder=\"Optional description (shown on hover)\"\r\n className=\"app-launcher-form__input\"\r\n />\r\n </div>\r\n\r\n {/* Color Picker */}\r\n <div className=\"app-launcher-form__field\">\r\n <label className=\"app-launcher-form__label\">Color</label>\r\n <div className=\"app-launcher-form__color-picker\">\r\n {presetColors.map((presetColor) => (\r\n <button\r\n key={presetColor}\r\n type=\"button\"\r\n className={`app-launcher-form__color-button ${\r\n color === presetColor ? 'app-launcher-form__color-button--selected' : ''\r\n }`}\r\n style={{ backgroundColor: presetColor }}\r\n onClick={() => setColor(presetColor)}\r\n title={presetColor}\r\n />\r\n ))}\r\n <input\r\n type=\"color\"\r\n value={color}\r\n onChange={(e) => setColor(e.target.value)}\r\n className=\"app-launcher-form__color-input\"\r\n title=\"Custom color\"\r\n />\r\n </div>\r\n </div>\r\n\r\n {/* Icon Selection */}\r\n <div className=\"app-launcher-form__field\">\r\n <label className=\"app-launcher-form__label\">Icon</label>\r\n\r\n {/* Icon Mode Toggle */}\r\n <div className=\"app-launcher-form__icon-mode\">\r\n <button\r\n type=\"button\"\r\n className={`app-launcher-form__mode-button ${iconMode === 'picker' ? 'app-launcher-form__mode-button--active' : ''}`}\r\n onClick={() => setIconMode('picker')}\r\n >\r\n Icon Picker\r\n </button>\r\n <button\r\n type=\"button\"\r\n className={`app-launcher-form__mode-button ${iconMode === 'custom' ? 'app-launcher-form__mode-button--active' : ''}`}\r\n onClick={() => setIconMode('custom')}\r\n >\r\n Custom URL\r\n </button>\r\n </div>\r\n\r\n {iconMode === 'picker' ? (\r\n <IconPicker selectedIcon={iconName} onSelect={setIconName} />\r\n ) : (\r\n <div className=\"app-launcher-form__custom-icon\">\r\n <input\r\n id=\"app-custom-icon\"\r\n type=\"text\"\r\n value={customIconUrl}\r\n onChange={(e) => setCustomIconUrl(e.target.value)}\r\n placeholder=\"/icons/my-icon.svg or https://cdn.example.com/icon.svg\"\r\n className={`app-launcher-form__input ${errors.customIconUrl ? 'app-launcher-form__input--error' : ''}`}\r\n />\r\n {errors.customIconUrl && (\r\n <span className=\"app-launcher-form__error\">{errors.customIconUrl}</span>\r\n )}\r\n <p className=\"app-launcher-form__hint\">\r\n Enter a path from your public folder (e.g., /icons/logo.svg) or a full URL\r\n </p>\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Actions */}\r\n <div className=\"app-launcher-form__actions\">\r\n <button\r\n type=\"button\"\r\n onClick={onCancel}\r\n className=\"app-launcher-form__button app-launcher-form__button--cancel\"\r\n >\r\n Cancel\r\n </button>\r\n <button\r\n type=\"submit\"\r\n className=\"app-launcher-form__button app-launcher-form__button--submit\"\r\n >\r\n {submitLabel}\r\n </button>\r\n </div>\r\n </form>\r\n );\r\n}\r\n","import React, { useState } from 'react';\r\nimport { iconMap } from './icons';\r\n\r\ninterface IconPickerProps {\r\n selectedIcon: string;\r\n onSelect: (iconName: string) => void;\r\n}\r\n\r\n// Group icons by category/type based on their names or define categories manually\r\n// For simplicity in this package version, we'll list all available icons\r\nconst allIcons = Object.keys(iconMap);\r\n\r\nexport function IconPicker({ selectedIcon, onSelect }: IconPickerProps) {\r\n const [searchTerm, setSearchTerm] = useState('');\r\n\r\n const filteredIcons = allIcons.filter((name) =>\r\n name.toLowerCase().includes(searchTerm.toLowerCase())\r\n );\r\n\r\n return (\r\n <div className=\"app-launcher-icon-picker\">\r\n <input\r\n type=\"text\"\r\n placeholder=\"Search icons...\"\r\n value={searchTerm}\r\n onChange={(e) => setSearchTerm(e.target.value)}\r\n className=\"app-launcher-icon-picker__search\"\r\n />\r\n\r\n <div className=\"app-launcher-icon-picker__grid\">\r\n {filteredIcons.map((iconName) => {\r\n const Icon = iconMap[iconName];\r\n const isSelected = selectedIcon === iconName;\r\n\r\n return (\r\n <button\r\n key={iconName}\r\n className={`app-launcher-icon-picker__button ${\r\n isSelected ? 'app-launcher-icon-picker__button--selected' : ''\r\n }`}\r\n onClick={() => onSelect(iconName)}\r\n title={iconName}\r\n type=\"button\"\r\n >\r\n <Icon className=\"app-launcher-icon-picker__icon\" />\r\n </button>\r\n );\r\n })}\r\n </div>\r\n\r\n {filteredIcons.length === 0 && (\r\n <div className=\"app-launcher-icon-picker__empty\">No icons found</div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import React, { useState, useEffect, useCallback } from 'react';\r\nimport { FaCog } from 'react-icons/fa';\r\nimport { AppLauncher } from './AppLauncher';\r\nimport { AppSettings } from './AppSettings';\r\nimport { AppItem } from './types';\r\nimport { iconMap } from './icons';\r\nimport './styles.css';\r\n\r\nconst STORAGE_KEY = 'app-launcher-user-apps';\r\n\r\nexport interface LocalAppLauncherProps {\r\n /**\r\n * Initial default apps to display if no local apps exist or mixed with local apps\r\n */\r\n defaultApps?: AppItem[];\r\n\r\n /**\r\n * Custom class name\r\n */\r\n className?: string;\r\n\r\n /**\r\n * Whether to merge default apps with local apps (true) or only show local apps if any exist (false)\r\n * Default: true\r\n */\r\n mergeDefaultApps?: boolean;\r\n}\r\n\r\n/**\r\n * A \"smart\" AppLauncher component that automatically handles:\r\n * - LocalStorage persistence of user-defined apps\r\n * - Settings UI management\r\n * - State management\r\n *\r\n * Use this component if you want a zero-config setup that works out of the box.\r\n */\r\nexport function LocalAppLauncher({\r\n defaultApps = [],\r\n className,\r\n mergeDefaultApps = true,\r\n}: LocalAppLauncherProps) {\r\n const [showSettings, setShowSettings] = useState(false);\r\n const [userApps, setUserApps] = useState<AppItem[]>([]);\r\n const [isLoaded, setIsLoaded] = useState(false);\r\n\r\n // Load user apps from localStorage on mount\r\n useEffect(() => {\r\n if (typeof window !== 'undefined') {\r\n try {\r\n const stored = localStorage.getItem(STORAGE_KEY);\r\n if (stored) {\r\n setUserApps(JSON.parse(stored));\r\n }\r\n } catch (e) {\r\n console.error('Failed to load apps from localStorage', e);\r\n }\r\n setIsLoaded(true);\r\n }\r\n }, []);\r\n\r\n // Save to localStorage whenever userApps changes\r\n const saveApps = (apps: AppItem[]) => {\r\n setUserApps(apps);\r\n if (typeof window !== 'undefined') {\r\n localStorage.setItem(STORAGE_KEY, JSON.stringify(apps));\r\n }\r\n };\r\n\r\n const handleAddApp = (data: Omit<AppItem, 'id'>) => {\r\n const newApp: AppItem = {\r\n ...data,\r\n id: `custom-${Date.now()}`,\r\n };\r\n saveApps([...userApps, newApp]);\r\n };\r\n\r\n const handleUpdateApp = (id: string, updates: Partial<Omit<AppItem, 'id'>>) => {\r\n saveApps(userApps.map((app) => (app.id === id ? { ...app, ...updates } : app)));\r\n };\r\n\r\n const handleDeleteApp = (id: string) => {\r\n saveApps(userApps.filter((app) => app.id !== id));\r\n };\r\n\r\n // Combine default apps and user apps for display\r\n const displayApps = isLoaded\r\n ? mergeDefaultApps\r\n ? [...userApps, ...defaultApps.filter((da) => !userApps.some((ua) => ua.id === da.id))]\r\n : userApps.length > 0\r\n ? userApps\r\n : defaultApps\r\n : [];\r\n\r\n return (\r\n <>\r\n <AppLauncher\r\n apps={displayApps}\r\n className={className}\r\n renderFooter={() => (\r\n <button\r\n className=\"app-launcher__footer-button\" // We need to add this style or reuse generic one\r\n style={{\r\n display: 'flex',\r\n alignItems: 'center',\r\n gap: '8px',\r\n width: '100%',\r\n justifyContent: 'center',\r\n border: 'none',\r\n background: 'transparent',\r\n cursor: 'pointer',\r\n color: 'var(--al-text-secondary)',\r\n fontSize: '14px',\r\n fontWeight: 500,\r\n }}\r\n onClick={() => setShowSettings(true)}\r\n >\r\n <FaCog style={{ fontSize: '16px' }} />\r\n Settings\r\n </button>\r\n )}\r\n />\r\n\r\n <AppSettings\r\n isOpen={showSettings}\r\n onClose={() => setShowSettings(false)}\r\n apps={userApps}\r\n defaultApps={defaultApps}\r\n onAdd={handleAddApp}\r\n onUpdate={handleUpdateApp}\r\n onDelete={handleDeleteApp}\r\n />\r\n </>\r\n );\r\n}\r\n"],"mappings":";AAEA,SAAgB,UAAU,QAAQ,iBAAiB;AACnD,SAAS,UAAAA,eAAc;;;ACDvB;AAAA,EACE;AAAA,EAAU;AAAA,EAAY;AAAA,EAAW;AAAA,EAAe;AAAA,EAChD;AAAA,EAAQ;AAAA,EAAY;AAAA,EAAS;AAAA,EAAa;AAAA,EAAS;AAAA,EACnD;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAY;AAAA,EAAgB;AAAA,EACnD;AAAA,EAAQ;AAAA,EAAY;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAQ;AAAA,EACpD;AAAA,EAAQ;AAAA,EAAU;AAAA,EAAa;AAAA,EAAc;AAAA,EAAS;AAAA,EACtD;AAAA,EAAW;AAAA,EAAe;AAAA,EAAa;AAAA,EAAiB;AAAA,EACxD;AAAA,EAAO;AAAA,EAAW;AAAA,EAAY;AAAA,EAAU;AAAA,OACnC;AACP,SAAS,sBAAsB;AAC/B,SAAS,iBAAiB;AAC1B,SAAS,iBAAiB,cAAc;AACxC,SAAS,mBAAmB,aAAa,aAAa,SAAS,cAAc;AAC7E,SAAS,eAAe,oBAAoB;AAC5C,SAAS,6BAA6B;AAM/B,IAAM,UAAoC;AAAA;AAAA,EAE/C;AAAA,EAAa;AAAA,EAAiB;AAAA,EAAQ;AAAA;AAAA,EAEtC;AAAA,EAAmB;AAAA,EAAQ;AAAA,EAAO;AAAA;AAAA,EAElC;AAAA,EAAY;AAAA,EAAS;AAAA;AAAA,EAErB;AAAA,EAAW;AAAA,EAAS;AAAA,EAAU;AAAA,EAAS;AAAA;AAAA,EAEvC;AAAA,EAAe;AAAA,EAAa;AAAA,EAAc;AAAA,EAAU;AAAA,EACpD;AAAA,EAAY;AAAA,EAAS;AAAA;AAAA,EAErB;AAAA,EAAgB;AAAA,EAAS;AAAA;AAAA,EAEzB;AAAA,EAAU;AAAA,EAAe;AAAA;AAAA,EAEzB;AAAA,EAAQ;AAAA,EAAY;AAAA,EAAY;AAAA;AAAA,EAEhC;AAAA,EAAY;AAAA,EAAa;AAAA;AAAA,EAEzB;AAAA,EAAgB;AAAA,EAAQ;AAAA;AAAA,EAExB;AAAA,EAAS;AAAA,EAAO;AAAA,EAAW;AAAA;AAAA,EAE3B;AAAA,EAAY;AAAA;AAAA,EAEZ;AAAA,EAAU;AAAA,EAAQ;AAAA,EAAO;AAAA,EAAS;AAAA,EAAQ;AAC5C;AAKO,SAAS,QAAQ,MAAwB;AAC9C,SAAO,QAAQ,IAAI,KAAK;AAC1B;;;ADuEQ,cAaQ,YAbR;AA7GD,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA;AACF,GAAqB;AACnB,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,CAAC,MAAM,OAAO,IAAI,SAAwB,CAAC,CAAC;AAClD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AACtD,QAAM,eAAe,OAAuB,IAAI;AAGhD,YAAU,MAAM;AACd,QAAI,UAAU;AACZ,cAAQ,SAAS,IAAI,UAAU,CAAC;AAChC;AAAA,IACF;AAEA,QAAI,WAAW;AACb,iBAAW,IAAI;AACf,eAAS,IAAI;AAEb,YAAM,SAAS,EACZ,KAAK,CAAC,QAAQ;AACb,YAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,oBAAoB,IAAI,MAAM,EAAE;AAC7D,eAAO,IAAI,KAAK;AAAA,MAClB,CAAC,EACA,KAAK,CAAC,WAA8B;AACnC,gBAAQ,OAAO,KAAK,IAAI,UAAU,CAAC;AAAA,MACrC,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,iBAAS,IAAI,OAAO;AACpB,gBAAQ,MAAM,sCAAsC,GAAG;AAAA,MACzD,CAAC,EACA,QAAQ,MAAM,WAAW,KAAK,CAAC;AAAA,IACpC;AAAA,EACF,GAAG,CAAC,WAAW,QAAQ,CAAC;AAGxB,YAAU,MAAM;AACd,aAAS,mBAAmB,OAAmB;AAC7C,UAAI,aAAa,WAAW,CAAC,aAAa,QAAQ,SAAS,MAAM,MAAc,GAAG;AAChF,kBAAU,KAAK;AAAA,MACjB;AAAA,IACF;AAEA,QAAI,QAAQ;AACV,eAAS,iBAAiB,aAAa,kBAAkB;AAAA,IAC3D;AACA,WAAO,MAAM,SAAS,oBAAoB,aAAa,kBAAkB;AAAA,EAC3E,GAAG,CAAC,MAAM,CAAC;AAGX,YAAU,MAAM;AACd,aAAS,aAAa,OAAsB;AAC1C,UAAI,MAAM,QAAQ,SAAU,WAAU,KAAK;AAAA,IAC7C;AAEA,QAAI,QAAQ;AACV,eAAS,iBAAiB,WAAW,YAAY;AAAA,IACnD;AACA,WAAO,MAAM,SAAS,oBAAoB,WAAW,YAAY;AAAA,EACnE,GAAG,CAAC,MAAM,CAAC;AAGX,WAAS,gBAAgB,MAAuB;AAC9C,WAAO,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,MAAM;AAAA,EACvD;AAEA,WAAS,WAAW,KAA2B;AAC7C,UAAM,WAAW,gBAAgB,IAAI,IAAI;AACzC,WAAO;AAAA,MACL,IAAI,IAAI;AAAA,MACR,MAAM,IAAI;AAAA,MACV,KAAK,IAAI;AAAA,MACT,MAAM,WAAW,OAAO,QAAQ,IAAI,IAAI;AAAA,MACxC,eAAe,WAAW,IAAI,OAAO;AAAA,MACrC,OAAO,IAAI;AAAA,MACX,aAAa,IAAI;AAAA,IACnB;AAAA,EACF;AAEA,WAAS,eAAe,KAAkB;AACxC,QAAI,YAAY;AACd,iBAAW;AAAA,QACT,IAAI,IAAI;AAAA,QACR,MAAM,IAAI;AAAA,QACV,KAAK,IAAI;AAAA,QACT,MAAM,IAAI,kBAAkB,IAAI,MAAM,QAAQ;AAAA,QAC9C,OAAO,IAAI;AAAA,QACX,aAAa,IAAI;AAAA,MACnB,CAAC;AAAA,IACH,OAAO;AAEL,aAAO,KAAK,IAAI,KAAK,UAAU,qBAAqB;AAAA,IACtD;AAAA,EACF;AAEA,SACE,qBAAC,SAAI,WAAW,gBAAgB,aAAa,EAAE,IAAI,KAAK,cAEtD;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,SAAS,MAAM,UAAU,CAAC,MAAM;AAAA,QAChC,cAAW;AAAA,QACX,iBAAe;AAAA,QAEf,8BAACC,SAAA,EAAO,WAAU,8BAA6B;AAAA;AAAA,IACjD;AAAA,IAGC,UACC,qBAAC,SAAI,WAAU,0BACZ;AAAA,iBAAW,oBAAC,SAAI,WAAU,yBAAwB,wBAAU;AAAA,MAE5D,SAAS,oBAAC,SAAI,WAAU,uBAAuB,iBAAM;AAAA,MAErD,CAAC,WAAW,CAAC,SACZ,oBAAC,SAAI,WAAU,sBACZ,eAAK,IAAI,CAAC,QACT;AAAA,QAAC;AAAA;AAAA,UAEC,WAAU;AAAA,UACV,SAAS,MAAM,eAAe,GAAG;AAAA,UACjC,OAAO,IAAI,eAAe,IAAI;AAAA,UAE9B;AAAA,gCAAC,SAAI,WAAU,8BACZ,cAAI;AAAA;AAAA,cAEH,IAAI,cAAc,SAAS,MAAM,IAC/B;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAU;AAAA,kBACV,OAAO;AAAA,oBACL,WAAW,OAAO,IAAI,aAAa;AAAA,oBACnC,iBAAiB,OAAO,IAAI,aAAa;AAAA,oBACzC,iBAAiB,IAAI,UAAU,gBAAgB,YAAY,IAAI;AAAA,kBACjE;AAAA,kBACA,cAAY,IAAI;AAAA;AAAA,cAClB;AAAA;AAAA,gBAGA;AAAA,kBAAC;AAAA;AAAA,oBACC,KAAK,IAAI;AAAA,oBACT,KAAK,IAAI;AAAA,oBACT,WAAU;AAAA;AAAA,gBACZ;AAAA;AAAA,gBAEA,IAAI,OACN,oBAAC,IAAI,MAAJ,EAAS,WAAU,sBAAqB,OAAO,EAAE,OAAO,IAAI,MAAM,GAAG,IACpE,MACN;AAAA,YACA,oBAAC,UAAK,WAAU,sBAAsB,cAAI,MAAK;AAAA;AAAA;AAAA,QA9B1C,IAAI;AAAA,MA+BX,CACD,GACH;AAAA,MAID,gBAAgB,oBAAC,SAAI,WAAU,wBAAwB,uBAAa,GAAE;AAAA,OACzE;AAAA,KAEJ;AAEJ;;;AExLA,SAAgB,YAAAC,iBAAgB;AAChC,SAAS,SAAS,QAAQ,QAAQ,SAAS,kBAAkB;;;ACD7D,SAAgB,YAAAC,iBAAgB;;;ACAhC,SAAgB,YAAAC,iBAAgB;AAoB5B,SACE,OAAAC,MADF,QAAAC,aAAA;AAVJ,IAAM,WAAW,OAAO,KAAK,OAAO;AAE7B,SAAS,WAAW,EAAE,cAAc,SAAS,GAAoB;AACtE,QAAM,CAAC,YAAY,aAAa,IAAIC,UAAS,EAAE;AAE/C,QAAM,gBAAgB,SAAS;AAAA,IAAO,CAAC,SACrC,KAAK,YAAY,EAAE,SAAS,WAAW,YAAY,CAAC;AAAA,EACtD;AAEA,SACE,gBAAAD,MAAC,SAAI,WAAU,4BACb;AAAA,oBAAAD;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,aAAY;AAAA,QACZ,OAAO;AAAA,QACP,UAAU,CAAC,MAAM,cAAc,EAAE,OAAO,KAAK;AAAA,QAC7C,WAAU;AAAA;AAAA,IACZ;AAAA,IAEA,gBAAAA,KAAC,SAAI,WAAU,kCACZ,wBAAc,IAAI,CAAC,aAAa;AAC/B,YAAM,OAAO,QAAQ,QAAQ;AAC7B,YAAM,aAAa,iBAAiB;AAEpC,aACE,gBAAAA;AAAA,QAAC;AAAA;AAAA,UAEC,WAAW,oCACT,aAAa,+CAA+C,EAC9D;AAAA,UACA,SAAS,MAAM,SAAS,QAAQ;AAAA,UAChC,OAAO;AAAA,UACP,MAAK;AAAA,UAEL,0BAAAA,KAAC,QAAK,WAAU,kCAAiC;AAAA;AAAA,QAR5C;AAAA,MASP;AAAA,IAEJ,CAAC,GACH;AAAA,IAEC,cAAc,WAAW,KACxB,gBAAAA,KAAC,SAAI,WAAU,mCAAkC,4BAAc;AAAA,KAEnE;AAEJ;;;AD0CM,SAIQ,OAAAG,MAJR,QAAAC,aAAA;AArFN,IAAM,eAAe;AAAA,EACnB;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AAIO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAChB,GAAoB;AAClB,QAAM,CAAC,MAAM,OAAO,IAAIC,UAAS,aAAa,QAAQ,EAAE;AACxD,QAAM,CAAC,KAAK,MAAM,IAAIA,UAAS,aAAa,OAAO,EAAE;AAGrD,QAAM,cAAc,aAAa,QAAQ;AACzC,QAAM,kBAAkB,YAAY,WAAW,GAAG,KAAK,YAAY,WAAW,MAAM;AAEpF,QAAM,CAAC,UAAU,WAAW,IAAIA,UAAS,kBAAkB,aAAa,WAAW;AACnF,QAAM,CAAC,eAAe,gBAAgB,IAAIA,UAAS,kBAAkB,cAAc,EAAE;AACrF,QAAM,CAAC,UAAU,WAAW,IAAIA,UAAmB,kBAAkB,WAAW,QAAQ;AAExF,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAS,aAAa,SAAS,SAAS;AAClE,QAAM,CAAC,aAAa,cAAc,IAAIA,UAAS,aAAa,eAAe,EAAE;AAC7E,QAAM,CAAC,QAAQ,SAAS,IAAIA,UAAiC,CAAC,CAAC;AAE/D,QAAM,eAAe,QAAQ,QAAQ,KAAK,QAAQ,UAAU;AAE5D,QAAM,WAAW,MAAe;AAC9B,UAAM,YAAoC,CAAC;AAE3C,QAAI,CAAC,KAAK,KAAK,GAAG;AAChB,gBAAU,OAAO;AAAA,IACnB;AAEA,QAAI,CAAC,IAAI,KAAK,GAAG;AACf,gBAAU,MAAM;AAAA,IAClB,WAAW,CAAC,IAAI,WAAW,SAAS,KAAK,CAAC,IAAI,WAAW,UAAU,KAAK,CAAC,IAAI,WAAW,GAAG,GAAG;AAC5F,gBAAU,MAAM;AAAA,IAClB;AAEA,QAAI,aAAa,YAAY,cAAc,KAAK,GAAG;AACjD,UACE,CAAC,cAAc,WAAW,SAAS,KACnC,CAAC,cAAc,WAAW,UAAU,KACpC,CAAC,cAAc,WAAW,GAAG,GAC7B;AACA,kBAAU,gBAAgB;AAAA,MAC5B;AAAA,IACF;AAEA,cAAU,SAAS;AACnB,WAAO,OAAO,KAAK,SAAS,EAAE,WAAW;AAAA,EAC3C;AAEA,QAAM,eAAe,CAAC,MAAuB;AAC3C,MAAE,eAAe;AAEjB,QAAI,CAAC,SAAS,EAAG;AAEjB,UAAM,YACJ,aAAa,YAAY,cAAc,KAAK,IAAI,cAAc,KAAK,IAAI;AAEzE,aAAS;AAAA,MACP,MAAM,KAAK,KAAK;AAAA,MAChB,KAAK,IAAI,KAAK;AAAA,MACd,MAAM;AAAA,MACN;AAAA,MACA,aAAa,YAAY,KAAK,KAAK;AAAA,IACrC,CAAC;AAAA,EACH;AAEA,SACE,gBAAAD,MAAC,UAAK,UAAU,cAAc,WAAU,qBAEtC;AAAA,oBAAAA,MAAC,SAAI,WAAU,8BACb;AAAA,sBAAAD,KAAC,SAAI,WAAU,mCAAkC,OAAO,EAAE,iBAAiB,QAAQ,KAAK,GACrF,uBAAa,YAAY,gBACxB,cAAc,SAAS,MAAM,IAC3B,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAO;AAAA,YACL,WAAW,OAAO,aAAa;AAAA,YAC/B,iBAAiB,OAAO,aAAa;AAAA,YACrC,iBAAiB,UAAU,gBAAgB,YAAY;AAAA,UACzD;AAAA;AAAA,MACF,IAEA,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL,KAAI;AAAA,UACJ,WAAU;AAAA;AAAA,MACZ,IAGF,gBAAAA,KAAC,gBAAa,OAAO,EAAE,OAAO,UAAU,GAAG,GAAG,GAElD;AAAA,MACA,gBAAAA,KAAC,UAAK,WAAU,mCAAmC,kBAAQ,YAAW;AAAA,OACxE;AAAA,IAGA,gBAAAC,MAAC,SAAI,WAAU,4BACb;AAAA,sBAAAD,KAAC,WAAM,SAAQ,YAAW,WAAU,4BAA2B,oBAE/D;AAAA,MACA,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,QAAQ,EAAE,OAAO,KAAK;AAAA,UACvC,aAAY;AAAA,UACZ,WAAW,4BAA4B,OAAO,OAAO,oCAAoC,EAAE;AAAA;AAAA,MAC7F;AAAA,MACC,OAAO,QAAQ,gBAAAA,KAAC,UAAK,WAAU,4BAA4B,iBAAO,MAAK;AAAA,OAC1E;AAAA,IAGA,gBAAAC,MAAC,SAAI,WAAU,4BACb;AAAA,sBAAAD,KAAC,WAAM,SAAQ,WAAU,WAAU,4BAA2B,mBAE9D;AAAA,MACA,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,OAAO,EAAE,OAAO,KAAK;AAAA,UACtC,aAAY;AAAA,UACZ,WAAW,4BAA4B,OAAO,MAAM,oCAAoC,EAAE;AAAA;AAAA,MAC5F;AAAA,MACC,OAAO,OAAO,gBAAAA,KAAC,UAAK,WAAU,4BAA4B,iBAAO,KAAI;AAAA,OACxE;AAAA,IAGA,gBAAAC,MAAC,SAAI,WAAU,4BACb;AAAA,sBAAAD,KAAC,WAAM,SAAQ,mBAAkB,WAAU,4BAA2B,yBAEtE;AAAA,MACA,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,eAAe,EAAE,OAAO,KAAK;AAAA,UAC9C,aAAY;AAAA,UACZ,WAAU;AAAA;AAAA,MACZ;AAAA,OACF;AAAA,IAGA,gBAAAC,MAAC,SAAI,WAAU,4BACb;AAAA,sBAAAD,KAAC,WAAM,WAAU,4BAA2B,mBAAK;AAAA,MACjD,gBAAAC,MAAC,SAAI,WAAU,mCACZ;AAAA,qBAAa,IAAI,CAAC,gBACjB,gBAAAD;AAAA,UAAC;AAAA;AAAA,YAEC,MAAK;AAAA,YACL,WAAW,mCACT,UAAU,cAAc,8CAA8C,EACxE;AAAA,YACA,OAAO,EAAE,iBAAiB,YAAY;AAAA,YACtC,SAAS,MAAM,SAAS,WAAW;AAAA,YACnC,OAAO;AAAA;AAAA,UAPF;AAAA,QAQP,CACD;AAAA,QACD,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,OAAO;AAAA,YACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,YACxC,WAAU;AAAA,YACV,OAAM;AAAA;AAAA,QACR;AAAA,SACF;AAAA,OACF;AAAA,IAGA,gBAAAC,MAAC,SAAI,WAAU,4BACb;AAAA,sBAAAD,KAAC,WAAM,WAAU,4BAA2B,kBAAI;AAAA,MAGhD,gBAAAC,MAAC,SAAI,WAAU,gCACb;AAAA,wBAAAD;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAW,kCAAkC,aAAa,WAAW,2CAA2C,EAAE;AAAA,YAClH,SAAS,MAAM,YAAY,QAAQ;AAAA,YACpC;AAAA;AAAA,QAED;AAAA,QACA,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAW,kCAAkC,aAAa,WAAW,2CAA2C,EAAE;AAAA,YAClH,SAAS,MAAM,YAAY,QAAQ;AAAA,YACpC;AAAA;AAAA,QAED;AAAA,SACF;AAAA,MAEC,aAAa,WACZ,gBAAAA,KAAC,cAAW,cAAc,UAAU,UAAU,aAAa,IAE3D,gBAAAC,MAAC,SAAI,WAAU,kCACb;AAAA,wBAAAD;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,MAAK;AAAA,YACL,OAAO;AAAA,YACP,UAAU,CAAC,MAAM,iBAAiB,EAAE,OAAO,KAAK;AAAA,YAChD,aAAY;AAAA,YACZ,WAAW,4BAA4B,OAAO,gBAAgB,oCAAoC,EAAE;AAAA;AAAA,QACtG;AAAA,QACC,OAAO,iBACN,gBAAAA,KAAC,UAAK,WAAU,4BAA4B,iBAAO,eAAc;AAAA,QAEnE,gBAAAA,KAAC,OAAE,WAAU,2BAA0B,wFAEvC;AAAA,SACF;AAAA,OAEJ;AAAA,IAGA,gBAAAC,MAAC,SAAI,WAAU,8BACb;AAAA,sBAAAD;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,WAAU;AAAA,UACX;AAAA;AAAA,MAED;AAAA,MACA,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAU;AAAA,UAET;AAAA;AAAA,MACH;AAAA,OACF;AAAA,KACF;AAEJ;;;ADnLQ,SAyBI,UAxBF,OAAAG,MADF,QAAAC,aAAA;AA/DD,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc,CAAC;AAAA,EACf;AAAA,EACA;AAAA,EACA;AACF,GAAqB;AACnB,QAAM,CAAC,aAAa,cAAc,IAAIC,UAAS,KAAK;AACpD,QAAM,CAAC,YAAY,aAAa,IAAIA,UAAyB,IAAI;AAEjE,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,kBAAkB,CAAC,SAA8B;AACrD,UAAM,IAAI;AACV,mBAAe,KAAK;AAAA,EACtB;AAEA,QAAM,mBAAmB,CAAC,SAA8B;AACtD,QAAI,YAAY;AACd,eAAS,WAAW,IAAI,IAAI;AAC5B,oBAAc,IAAI;AAAA,IACpB;AAAA,EACF;AAEA,QAAM,eAAe,CAAC,OAAe;AACnC,QAAI,QAAQ,2CAA2C,GAAG;AACxD,eAAS,EAAE;AAAA,IACb;AAAA,EACF;AAEA,QAAM,qBAAqB,MAAM;AAE/B,UAAM,eAAe;AAAA;AAAA,MAEnB,GAAG;AAAA;AAAA,MAEH,GAAG,YAAY,OAAO,CAAC,OAAO,CAAC,KAAK,KAAK,CAAC,OAAO,GAAG,OAAO,GAAG,EAAE,CAAC;AAAA;AAAA,IACnE;AAEA,UAAM,SAAS;AAAA,MACb,SAAS;AAAA,MACT,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,MAAM;AAAA,IACR;AAGA,UAAM,OAAO,IAAI,KAAK,CAAC,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,mBAAmB,CAAC;AACrF,UAAM,MAAM,IAAI,gBAAgB,IAAI;AACpC,UAAM,IAAI,SAAS,cAAc,GAAG;AACpC,MAAE,OAAO;AACT,MAAE,WAAW;AACb,aAAS,KAAK,YAAY,CAAC;AAC3B,MAAE,MAAM;AACR,aAAS,KAAK,YAAY,CAAC;AAC3B,QAAI,gBAAgB,GAAG;AAAA,EACzB;AAEA,SACE,gBAAAF,KAAC,SAAI,WAAU,yBACb,0BAAAC,MAAC,SAAI,WAAU,uBAAsB,SAAS,CAAC,MAAM,EAAE,gBAAgB,GAErE;AAAA,oBAAAA,MAAC,SAAI,WAAU,wBACb;AAAA,sBAAAD,KAAC,QAAG,WAAU,uBACX,wBAAc,gBAAgB,aAAa,aAAa,YAC3D;AAAA,MACA,gBAAAA,KAAC,YAAO,WAAU,8BAA6B,SAAS,SAAS,cAAW,SAC1E,0BAAAA,KAAC,WAAQ,GACX;AAAA,OACF;AAAA,IAGA,gBAAAA,KAAC,SAAI,WAAU,yBACZ,wBACC,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,UAAU;AAAA,QACV,UAAU,MAAM,eAAe,KAAK;AAAA,QACpC,aAAY;AAAA;AAAA,IACd,IACE,aACF,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,aAAa;AAAA,QACb,UAAU;AAAA,QACV,UAAU,MAAM,cAAc,IAAI;AAAA,QAClC,aAAY;AAAA;AAAA,IACd,IAEA,gBAAAC,MAAA,YAEE;AAAA,sBAAAA,MAAC,YAAO,WAAU,4BAA2B,SAAS,MAAM,eAAe,IAAI,GAC7E;AAAA,wBAAAD,KAAC,UAAO,WAAU,0BAAyB;AAAA,QAAE;AAAA,SAE/C;AAAA,MAGA,gBAAAC,MAAC,SAAI,WAAU,sBACb;AAAA,wBAAAD,KAAC,QAAG,WAAU,+BAA8B,4BAAc;AAAA,QAEzD,KAAK,WAAW,IACf,gBAAAA,KAAC,OAAE,WAAU,+BAA8B,qEAE3C,IAEA,gBAAAA,KAAC,SAAI,WAAU,sBACZ,eAAK,IAAI,CAAC,QAAQ;AAEjB,gBAAM,WAAW,IAAI,KAAK,WAAW,GAAG,KAAK,IAAI,KAAK,WAAW,MAAM;AACvE,gBAAM,OAAO,CAAC,WAAW,QAAQ,IAAI,IAAI,KAAK,QAAQ,UAAU,IAAI;AAEpE,iBACE,gBAAAC,MAAC,SAAiB,WAAU,sBAC1B;AAAA,4BAAAA,MAAC,SAAI,WAAU,2BACb;AAAA,8BAAAD;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAU;AAAA,kBACV,OAAO,EAAE,iBAAiB,IAAI,QAAQ,KAAK;AAAA,kBAE1C,qBACC,IAAI,KAAK,SAAS,MAAM,IACtB,gBAAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,OAAO;AAAA,wBACL,OAAO;AAAA,wBACP,QAAQ;AAAA,wBACR,WAAW,OAAO,IAAI,IAAI;AAAA,wBAC1B,iBAAiB,OAAO,IAAI,IAAI;AAAA,wBAChC,UAAU;AAAA,wBACV,YAAY;AAAA,wBACZ,cAAc;AAAA,wBACd,gBAAgB;AAAA,wBAChB,kBAAkB;AAAA,wBAClB,oBAAoB;AAAA,wBACpB,iBACE,IAAI,UAAU,gBAAgB,YAAY,IAAI;AAAA,sBAClD;AAAA;AAAA,kBACF,IAEA,gBAAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,KAAK,IAAI;AAAA,sBACT,KAAK,IAAI;AAAA,sBACT,OAAO,EAAE,OAAO,IAAI,QAAQ,IAAI,WAAW,UAAU;AAAA;AAAA,kBACvD,IAGF,QAAQ,gBAAAA,KAAC,QAAK,OAAO,EAAE,OAAO,IAAI,MAAM,GAAG;AAAA;AAAA,cAE/C;AAAA,cACA,gBAAAC,MAAC,SAAI,WAAU,8BACb;AAAA,gCAAAD,KAAC,UAAK,WAAU,2BAA2B,cAAI,MAAK;AAAA,gBACpD,gBAAAA,KAAC,UAAK,WAAU,0BAA0B,cAAI,KAAI;AAAA,iBACpD;AAAA,eACF;AAAA,YACA,gBAAAC,MAAC,SAAI,WAAU,8BACb;AAAA,8BAAAD;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAU;AAAA,kBACV,SAAS,MAAM,cAAc,GAAG;AAAA,kBAChC,OAAM;AAAA,kBAEN,0BAAAA,KAAC,UAAO;AAAA;AAAA,cACV;AAAA,cACA,gBAAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAU;AAAA,kBACV,SAAS,MAAM,aAAa,IAAI,EAAE;AAAA,kBAClC,OAAM;AAAA,kBAEN,0BAAAA,KAAC,WAAQ;AAAA;AAAA,cACX;AAAA,eACF;AAAA,eAvDQ,IAAI,EAwDd;AAAA,QAEJ,CAAC,GACH;AAAA,SAEJ;AAAA,MAGA,gBAAAC,MAAC,YAAO,WAAU,+BAA8B,SAAS,oBACvD;AAAA,wBAAAD,KAAC,cAAW,WAAU,6BAA4B;AAAA,QAAE;AAAA,SAEtD;AAAA,MAGA,gBAAAA,KAAC,SAAI,WAAU,sBACb,0BAAAA,KAAC,OAAE,yGAGH,GACF;AAAA,OACF,GAEJ;AAAA,KACF,GACF;AAEJ;;;AGjNA,SAAgB,YAAAG,WAAU,aAAAC,kBAA8B;AACxD,SAAS,SAAAC,cAAa;AA6FlB,qBAAAC,WAsBQ,OAAAC,MAjBF,QAAAC,aALN;AAtFJ,IAAM,cAAc;AA4Bb,SAAS,iBAAiB;AAAA,EAC/B,cAAc,CAAC;AAAA,EACf;AAAA,EACA,mBAAmB;AACrB,GAA0B;AACxB,QAAM,CAAC,cAAc,eAAe,IAAIC,UAAS,KAAK;AACtD,QAAM,CAAC,UAAU,WAAW,IAAIA,UAAoB,CAAC,CAAC;AACtD,QAAM,CAAC,UAAU,WAAW,IAAIA,UAAS,KAAK;AAG9C,EAAAC,WAAU,MAAM;AACd,QAAI,OAAO,WAAW,aAAa;AACjC,UAAI;AACF,cAAM,SAAS,aAAa,QAAQ,WAAW;AAC/C,YAAI,QAAQ;AACV,sBAAY,KAAK,MAAM,MAAM,CAAC;AAAA,QAChC;AAAA,MACF,SAAS,GAAG;AACV,gBAAQ,MAAM,yCAAyC,CAAC;AAAA,MAC1D;AACA,kBAAY,IAAI;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,CAAC;AAGL,QAAM,WAAW,CAAC,SAAoB;AACpC,gBAAY,IAAI;AAChB,QAAI,OAAO,WAAW,aAAa;AACjC,mBAAa,QAAQ,aAAa,KAAK,UAAU,IAAI,CAAC;AAAA,IACxD;AAAA,EACF;AAEA,QAAM,eAAe,CAAC,SAA8B;AAClD,UAAM,SAAkB;AAAA,MACtB,GAAG;AAAA,MACH,IAAI,UAAU,KAAK,IAAI,CAAC;AAAA,IAC1B;AACA,aAAS,CAAC,GAAG,UAAU,MAAM,CAAC;AAAA,EAChC;AAEA,QAAM,kBAAkB,CAAC,IAAY,YAA0C;AAC7E,aAAS,SAAS,IAAI,CAAC,QAAS,IAAI,OAAO,KAAK,EAAE,GAAG,KAAK,GAAG,QAAQ,IAAI,GAAI,CAAC;AAAA,EAChF;AAEA,QAAM,kBAAkB,CAAC,OAAe;AACtC,aAAS,SAAS,OAAO,CAAC,QAAQ,IAAI,OAAO,EAAE,CAAC;AAAA,EAClD;AAGA,QAAM,cAAc,WAChB,mBACE,CAAC,GAAG,UAAU,GAAG,YAAY,OAAO,CAAC,OAAO,CAAC,SAAS,KAAK,CAAC,OAAO,GAAG,OAAO,GAAG,EAAE,CAAC,CAAC,IACpF,SAAS,SAAS,IAChB,WACA,cACJ,CAAC;AAEL,SACE,gBAAAF,MAAAF,WAAA,EACE;AAAA,oBAAAC;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN;AAAA,QACA,cAAc,MACZ,gBAAAC;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO;AAAA,cACL,SAAS;AAAA,cACT,YAAY;AAAA,cACZ,KAAK;AAAA,cACL,OAAO;AAAA,cACP,gBAAgB;AAAA,cAChB,QAAQ;AAAA,cACR,YAAY;AAAA,cACZ,QAAQ;AAAA,cACR,OAAO;AAAA,cACP,UAAU;AAAA,cACV,YAAY;AAAA,YACd;AAAA,YACA,SAAS,MAAM,gBAAgB,IAAI;AAAA,YAEnC;AAAA,8BAAAD,KAACI,QAAA,EAAM,OAAO,EAAE,UAAU,OAAO,GAAG;AAAA,cAAE;AAAA;AAAA;AAAA,QAExC;AAAA;AAAA,IAEJ;AAAA,IAEA,gBAAAJ;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ;AAAA,QACR,SAAS,MAAM,gBAAgB,KAAK;AAAA,QACpC,MAAM;AAAA,QACN;AAAA,QACA,OAAO;AAAA,QACP,UAAU;AAAA,QACV,UAAU;AAAA;AAAA,IACZ;AAAA,KACF;AAEJ;","names":["IoApps","IoApps","useState","useState","useState","jsx","jsxs","useState","jsx","jsxs","useState","jsx","jsxs","useState","useState","useEffect","FaCog","Fragment","jsx","jsxs","useState","useEffect","FaCog"]}
|