diff --git a/io-connect-desktop-launchpad/.gitignore b/io-connect-desktop-launchpad/.gitignore new file mode 100644 index 0000000..4d29575 --- /dev/null +++ b/io-connect-desktop-launchpad/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/io-connect-desktop-launchpad/.prettierignore b/io-connect-desktop-launchpad/.prettierignore new file mode 100644 index 0000000..63520da --- /dev/null +++ b/io-connect-desktop-launchpad/.prettierignore @@ -0,0 +1,3 @@ +node_modules +dist +build diff --git a/io-connect-desktop-launchpad/.prettierrc b/io-connect-desktop-launchpad/.prettierrc new file mode 100644 index 0000000..8164313 --- /dev/null +++ b/io-connect-desktop-launchpad/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": true, + "tabWidth": 2, + "trailingComma": "es5", + "printWidth": 100 +} diff --git a/io-connect-desktop-launchpad/README.md b/io-connect-desktop-launchpad/README.md new file mode 100644 index 0000000..6b023a5 --- /dev/null +++ b/io-connect-desktop-launchpad/README.md @@ -0,0 +1,220 @@ +# Launchpad App for io.Connect Desktop + +Template for creating a custom desktop Launchpad app for **io.Connect Desktop**, built on top of the +`IOLaunchpadDesktop` feature exported by [`@interopio/components-react`](../../packages/components-react). Clone +this app and customize only the pieces you need — everything else keeps working out of the box. + +## Prerequisites + +**io.Connect Desktop** is required, since this app only configures the `desktop` factory (see +[src/constants/constants.ts](src/constants/constants.ts)). + +## Setup + +- Run `npm install` to install all dependencies. +- Run `npm run start` to start the app (dev server runs on `http://localhost:4242` by default, see + [vite.config.ts](vite.config.ts)). +- Point your io.Connect Desktop configuration's Launchpad/System application entry to this app's URL, then start + io.Connect Desktop to see your customized Launchpad. Refer to your io.Connect Desktop version's documentation for + the exact application-definition file and property to update. +- Run `npm run build` to produce a production build in `dist/`. +- Start **io.Connect Desktop** to start using and modifying your Launchpad App. + +## How it's wired + +`src/components/launchpad.tsx` wires up the required providers around a single `` component. You +don't need to pass a `components` prop at all - `` already renders a fully working default header +(`DragHandle` + `LogoButton` before search, `ExtendedArea` + `NotificationsButton` + `MainContextMenu` after +search) out of the box: + +```tsx +import { + ThemeProvider, + PlatformPrefsProvider, + IOActivePanelRenderer, + IODownloadManager, + IOLaunchpadDesktop, + IONotifications, +} from "@interopio/components-react"; + +const { LaunchpadProvider, LaunchpadBodyPopupProvider, PanelPopupProvider, Launchpad } = IOLaunchpadDesktop; +const { NotificationsProvider } = IONotifications; +const { DownloadManagerProvider } = IODownloadManager; +const { PanelManagerProvider } = IOActivePanelRenderer; + +function LaunchpadWrapper() { + return ( + + + + + + + + + + + + + + + + + + ); +} +``` + +All customization happens through the `components` prop of `` - you shouldn't need to touch the +provider chain above. + +## Customization + +### 1. Replacing the default io.Connect Desktop Launchpad with a custom implementation + +For details on how io.Connect Desktop discovers and loads a custom Launchpad app, see the +[Launchpad documentation](https://docs.interop.io/desktop/capabilities/launcher/index.html#launchpad). + + +### 2. The `components` prop + +`` forwards straight to the native `IOLaunchpad.ContentsContainer`, so it only +accepts `header` (`BeforeSearch` / `AfterSearch`) and `sections` - there's no per-piece override system. +`DragHandle`, `LogoButton`, `NotificationsButton`, `MainContextMenu`, `ExtendedArea` are exported standalone so you +compose them yourself into `header.BeforeSearch` / `AfterSearch`, as shown above. + +> **Tip:** define `BeforeSearch` / `AfterSearch` (and the `components` object) **outside** your component's render +> function (module scope), or memoize them. Passing a brand-new inline function on every render forces +> `` to remount that zone on every render - wasteful in general, and it can also break in-flight +> async work started by a piece inside it (e.g. opening the docked search popup). + +### 3. Default `BeforeSearch` / `AfterSearch` + +`` fills in `header.BeforeSearch` / `AfterSearch` with its own defaults whenever you don't specify +them, so there are 3 possible outcomes per zone: + +1. **Omitted** (key not present, or `undefined`) - the library default renders (`DragHandle` + `LogoButton` for + `BeforeSearch`; `ExtendedArea` + `NotificationsButton` + `MainContextMenu` for `AfterSearch`). +2. **Explicitly empty** (e.g. `BeforeSearch: () => null`) - the default is overridden and nothing renders in that + zone. +3. **A real custom component** - that component renders in place of the default. + +```tsx +// 1. Omitted - renders the library defaults +; + +// 2. Explicitly empty - hides the zone entirely + null } }} />; + +// 3. Custom component - fully replaces the zone +function CustomBeforeSearch() { + return ; +} + +; +``` + +### 4. Removing a piece + +Since `header.BeforeSearch` / `AfterSearch` **fully replace** the zone, just omit a piece from your composition to +hide it - e.g. hide the notifications bell: + +```tsx +function AfterSearch() { + return ( + <> + + + + ); +} +``` + +### 5. Passing your own component + +Compose your own implementation alongside (or instead of) the default pieces: + +```tsx +function MyMainContextMenu() { + return ; +} + +function AfterSearch() { + return ( + <> + + + + + ); +} +``` + +### 6. Overriding `BeforeSearch` / `AfterSearch` + +Pass `components.header` to fully replace either zone with something entirely custom: + +```tsx +function CustomBeforeSearch() { + return ; +} + +function CustomAfterSearch() { + return ; +} + +const launchpadComponents = { header: { BeforeSearch: CustomBeforeSearch, AfterSearch: CustomAfterSearch } }; + +; +``` + +Since `header.BeforeSearch` / `AfterSearch` **fully replace** the zone, if you just want to add something +alongside the defaults, import the default pieces (they're also exported standalone) and compose them yourself: + +```tsx +import { IOLaunchpadDesktop } from "@interopio/components-react"; + +const { DragHandle, LogoButton } = IOLaunchpadDesktop; + +function CustomBeforeSearch() { + return ( + <> + + + + + ); +} + +const launchpadComponents = { header: { BeforeSearch: CustomBeforeSearch } }; +``` + +### 7. Customizing the `LogoButton` + +`LogoButton` itself accepts `icon`, `iconSrc`, and `onClick` props, so you don't need to replace it entirely just +to change the logo or its click behavior: + +```tsx +import { IOLaunchpadDesktop } from "@interopio/components-react"; + +const { DragHandle, LogoButton } = IOLaunchpadDesktop; + +function BeforeSearch() { + return ( + <> + + console.log("logo clicked")} /> + + ); +} +``` + +- `icon` — an icon variant name (ignored if `iconSrc` is set); defaults to `"logo"`. +- `iconSrc` — a custom image URL/data-URI to render instead of `icon`. +- `onClick` — replaces the default collapse/popup-toggle behavior; drag-to-move still works either way. + +### 7. `ExtendedArea` visibility + +`ExtendedArea` self-guards: it renders `null` unless the Launchpad is both docked and the +`LAUNCHPAD_SHOW_EXTENDED_AREA` platform pref is enabled, so it's safe to always include it in `AfterSearch` - it +simply won't render anything otherwise. diff --git a/io-connect-desktop-launchpad/index.html b/io-connect-desktop-launchpad/index.html new file mode 100644 index 0000000..2695372 --- /dev/null +++ b/io-connect-desktop-launchpad/index.html @@ -0,0 +1,15 @@ + + + + + Launchpad + + + + + + +
+ + + diff --git a/io-connect-desktop-launchpad/package.json b/io-connect-desktop-launchpad/package.json new file mode 100644 index 0000000..5345b4d --- /dev/null +++ b/io-connect-desktop-launchpad/package.json @@ -0,0 +1,29 @@ +{ + "name": "io-connect-desktop-launchpad", + "version": "4.6.0", + "homepage": "./", + "private": true, + "scripts": { + "start": "vite", + "build": "vite build" + }, + "dependencies": { + "@interopio/components-react": "~4.6.0", + "@interopio/desktop": "~6.16.0", + "@interopio/react-hooks": "~4.6.0", + "@interopio/workspaces-ui-react": "~4.6.0", + "@interopio/modals-api": "~4.6.0", + "@interopio/search-api": "^3.3.0", + "@interopio/theme": "~4.6.0", + "react": "~18.2.0", + "react-dom": "~18.2.0" + }, + "devDependencies": { + "@types/node": "~20.12.7", + "@types/react": "~18.2.79", + "@types/react-dom": "~18.2.25", + "@vitejs/plugin-react": "~3.0.0", + "typescript": "~5.8.2", + "vite": "~4.0.1" + } +} diff --git a/io-connect-desktop-launchpad/public/favicon.ico b/io-connect-desktop-launchpad/public/favicon.ico new file mode 100644 index 0000000..1a05395 Binary files /dev/null and b/io-connect-desktop-launchpad/public/favicon.ico differ diff --git a/io-connect-desktop-launchpad/src/app/app.tsx b/io-connect-desktop-launchpad/src/app/app.tsx new file mode 100644 index 0000000..40a53d7 --- /dev/null +++ b/io-connect-desktop-launchpad/src/app/app.tsx @@ -0,0 +1,41 @@ +import { lazy, Suspense, useContext, useEffect } from "react"; +import { IOConnectContext, IOConnectProvider } from "@interopio/react-hooks"; +import { ioConfig } from "../constants/constants"; +import "@interopio/workspaces-ui-react/dist/styles/workspaces.css"; +import "@interopio/components-react/dist/styles/components/ui/dropdown-menu.css"; +import "@interopio/components-react/dist/styles/components/ui/overlay-scrollbars-container.css"; +import "@interopio/components-react/dist/styles/features/active-panel-renderer/styles.css"; +import "@interopio/components-react/dist/styles/features/notifications/styles.css"; +import "@interopio/components-react/dist/styles/features/preferences/styles.css"; +import "@interopio/components-react/dist/styles/features/profile/styles.css"; +import "@interopio/components-react/dist/styles/features/launchpad/styles.css"; +import "@interopio/components-react/dist/styles/features/launchpad-desktop/styles.css"; + +const Launchpad = lazy(() => import("../components/launchpad")); + +// IOConnectProvider owns the only io.Connect initialization - read the resulting instance from context instead of initializing again. +function GlobalIOBinder() { + const io = useContext(IOConnectContext); + + useEffect(() => { + if (io) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (globalThis as any).io = io; + } + }, [io]); + + return null; +} + +export function App() { + return ( + + + + + + + ); +} + +export default App; diff --git a/io-connect-desktop-launchpad/src/components/launchpad.tsx b/io-connect-desktop-launchpad/src/components/launchpad.tsx new file mode 100644 index 0000000..115f1c4 --- /dev/null +++ b/io-connect-desktop-launchpad/src/components/launchpad.tsx @@ -0,0 +1,37 @@ +import { + ThemeProvider, + PlatformPrefsProvider, + IOActivePanelRenderer, + IODownloadManager, + IOLaunchpadDesktop, + IONotifications, +} from "@interopio/components-react"; + +const { PanelManagerProvider } = IOActivePanelRenderer; +const { DownloadManagerProvider } = IODownloadManager; +const { LaunchpadProvider, LaunchpadBodyPopupProvider, PanelPopupProvider, Launchpad } = IOLaunchpadDesktop; +const { NotificationsProvider } = IONotifications; + +function LaunchpadWrapper() { + return ( + + + + + + + + + + + + + + + + + + ); +} + +export default LaunchpadWrapper; diff --git a/io-connect-desktop-launchpad/src/constants/constants.ts b/io-connect-desktop-launchpad/src/constants/constants.ts new file mode 100644 index 0000000..8e60736 --- /dev/null +++ b/io-connect-desktop-launchpad/src/constants/constants.ts @@ -0,0 +1,28 @@ +import IODesktop from "@interopio/desktop"; +import IOSearch from "@interopio/search-api"; +import IOWorkspaces from "@interopio/workspaces-api"; +import IOModalsAPI from "@interopio/modals-api"; +import { IOConnectInitSettings } from "@interopio/react-hooks"; + +export const ioConfig = { + desktop: { + factory: IODesktop, + config: { + gateway: { + logging: { + level: "warn", + }, + }, + libraries: [IOWorkspaces, IOSearch, IOModalsAPI], + appManager: "full", + layouts: "full", + activities: "trackAll", + channels: false, + metrics: false, + contexts: true, + systemLogger: { + level: "warn", + }, + }, + }, +} as IOConnectInitSettings; diff --git a/io-connect-desktop-launchpad/src/main.tsx b/io-connect-desktop-launchpad/src/main.tsx new file mode 100644 index 0000000..784f2cf --- /dev/null +++ b/io-connect-desktop-launchpad/src/main.tsx @@ -0,0 +1,6 @@ +import * as ReactDOM from "react-dom/client"; +import App from "./app/app"; + +const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement); + +root.render(); diff --git a/io-connect-desktop-launchpad/src/styles.css b/io-connect-desktop-launchpad/src/styles.css new file mode 100644 index 0000000..fb8e8d5 --- /dev/null +++ b/io-connect-desktop-launchpad/src/styles.css @@ -0,0 +1,2 @@ +/* App-specific overrides go here. Launchpad desktop styling now ships with + @interopio/components-react/dist/styles/features/launchpad-desktop/styles.css */ diff --git a/io-connect-desktop-launchpad/tsconfig.json b/io-connect-desktop-launchpad/tsconfig.json new file mode 100644 index 0000000..d72ea2f --- /dev/null +++ b/io-connect-desktop-launchpad/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "esnext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "esModuleInterop": true, + "resolveJsonModule": true, + "allowJs": false, + "allowSyntheticDefaultImports": true, + "strict": true + } +} diff --git a/io-connect-desktop-launchpad/vite-env.d.ts b/io-connect-desktop-launchpad/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/io-connect-desktop-launchpad/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/io-connect-desktop-launchpad/vite.config.ts b/io-connect-desktop-launchpad/vite.config.ts new file mode 100644 index 0000000..4e31992 --- /dev/null +++ b/io-connect-desktop-launchpad/vite.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + base: "./", + server: { + port: 4242, + host: "localhost", + }, + build: { + commonjsOptions: { + include: [/@interopio\/components-react/, /node_modules/], + }, + }, +});