@aglyn/plugins-marketplace 1.18.0 → 1.20.0
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/package.json +15 -15
- package/src/lib/components/editor-connections-provider.component.js +15 -0
- package/src/lib/components/editor-connections-provider.component.js.map +1 -1
- package/src/lib/components/listing-content.component.d.ts +5 -5
- package/src/lib/components/listing-content.component.js +87 -70
- package/src/lib/components/listing-content.component.js.map +1 -1
- package/src/lib/components/listing-detail-page.component.d.ts +2 -4
- package/src/lib/components/listing-detail-page.component.js.map +1 -1
- package/src/lib/components/marketplace-hub.component.js +10 -3
- package/src/lib/components/marketplace-hub.component.js.map +1 -1
- package/src/lib/components/plugin-site-set-panel.component.d.ts +7 -4
- package/src/lib/components/plugin-site-set-panel.component.js +3 -2
- package/src/lib/components/plugin-site-set-panel.component.js.map +1 -1
- package/src/lib/components/plugin-site-set.component.js +11 -0
- package/src/lib/components/plugin-site-set.component.js.map +1 -1
- package/src/lib/constants/bundle-common.d.ts +6 -0
- package/src/lib/constants/bundle-common.js +5 -0
- package/src/lib/constants/bundle-common.js.map +1 -1
- package/src/lib/declarations.server.js +15 -6
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/hooks/use-marketplace-actions.js +14 -1
- package/src/lib/hooks/use-marketplace-actions.js.map +1 -1
- package/src/lib/model/editor-connections.d.ts +4 -3
- package/src/lib/model/editor-connections.js +24 -7
- package/src/lib/model/editor-connections.js.map +1 -1
- package/src/lib/model/marketplace-catalog.d.ts +25 -4
- package/src/lib/model/marketplace-catalog.js +31 -6
- package/src/lib/model/marketplace-catalog.js.map +1 -1
- package/src/lib/model/marketplace.d.ts +28 -2
- package/src/lib/model/marketplace.js +36 -0
- package/src/lib/model/marketplace.js.map +1 -1
- package/src/lib/plugin.js +8 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/repeat/connection-repeat-source.d.ts +55 -0
- package/src/lib/repeat/connection-repeat-source.js +157 -0
- package/src/lib/repeat/connection-repeat-source.js.map +1 -0
- package/src/lib/server/connections/site-records-platform.d.ts +1 -0
- package/src/lib/server/connections/site-records-platform.js +8 -1
- package/src/lib/server/connections/site-records-platform.js.map +1 -1
- package/src/lib/server/connections/site-records.d.ts +28 -0
- package/src/lib/server/connections/site-records.js +59 -1
- package/src/lib/server/connections/site-records.js.map +1 -1
- package/src/lib/server/install-plugin.js +52 -8
- package/src/lib/server/install-plugin.js.map +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { OrgInstallHost } from '../model/marketplace';
|
|
1
2
|
/**
|
|
2
3
|
* Org marketplace listing detail (AGL-772), at `{marketplace}/{listingId}`.
|
|
3
4
|
*
|
|
@@ -15,10 +16,7 @@ export declare function ListingDetailPage(props: {
|
|
|
15
16
|
basePath: string;
|
|
16
17
|
/** The site an install acts through, and every site for the target picker. */
|
|
17
18
|
actingHost: string;
|
|
18
|
-
hosts: ReadonlyArray<
|
|
19
|
-
id: string;
|
|
20
|
-
label: string;
|
|
21
|
-
}>;
|
|
19
|
+
hosts: ReadonlyArray<OrgInstallHost>;
|
|
22
20
|
permissions?: Record<string, boolean | undefined>;
|
|
23
21
|
orgSlug: string;
|
|
24
22
|
}): import("react").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/listing-detail-page.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n PageHeaderActions,\n PageHeaderHelp,\n PageHeaderRecord,\n} from '@aglyn/aglyn'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { BlockedControl } from '@aglyn/shared-ui-jsx/components/blocked-control.component'\nimport { useFirestore, useFirestoreDoc, useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Button, Stack } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { lazy, Suspense, useMemo, useState } from 'react'\nimport ListingDetailEditor from './listing-detail-editor.component'\nimport ListingReviewStatus from './listing-review-status.component'\nimport ListingVerificationRequest from './listing-verification-request.component'\nimport { MarketplaceListingContent } from './listing-content.component'\nimport { publishPluginPath } from '../model/marketplace-paths'\nimport {\n MARKETPLACE_MANAGE_LISTINGS_PERMISSION,\n marketplacePublisherPermissionRefusal,\n} from '../model/marketplace-publisher-permissions'\n\n/** The publisher's connection credentials: drawn only for an owner's plugin listing, so it loads then. */\nconst ListingConnectionCredentialsCard = lazy(() => import('./listing-connection-credentials-card.component'))\n\n/**\n * Org marketplace listing detail (AGL-772), at `{marketplace}/{listingId}`.\n *\n * It was a console route until AGL-3080, which is why it used to reach the\n * body through the `marketplaceListing` zone: an app page could not import\n * this plugin. This page IS the plugin now, so it renders the content\n * directly — the zone stays registered for the surfaces that still need it.\n *\n * Install pins validate against a site, so the detail acts through the org's\n * first site until targeting lands (AGL-773).\n */\nexport function ListingDetailPage(props: {\n listingId: string\n orgId: string | null\n basePath: string\n /** The site an install acts through, and every site for the target picker. */\n actingHost: string\n hosts: ReadonlyArray<{ id: string; label: string }>\n permissions?: Record<string, boolean | undefined>\n orgSlug: string\n}) {\n const { listingId, orgId, basePath, actingHost, hosts, permissions, orgSlug } =\n props\n const firestore = useFirestore()\n const { data: user } = useUser()\n\n // The listing itself, so the owner can edit the whole page in place\n // (AGL-869) rather than through a cramped sidebar card.\n const { data: listing } = useFirestoreDoc<any>(\n () => doc(firestore, 'marketplaceListings', listingId || '-missing-'),\n [firestore, listingId],\n { idField: '$id' },\n )\n // Listings are org-owned (AGL-652): ownership is an org comparison.\n const isOwner = Boolean(orgId && listing?.profileId === orgId)\n // Editing it asks \"Manage published listings\" (AGL-4047), as its save\n // route and the listing rules do. Off with the permission named, not\n // hidden; an absent key reads as allowed, and the route still decides.\n const canManageListings =\n permissions?.[MARKETPLACE_MANAGE_LISTINGS_PERMISSION] !== false\n const [editing, setEditing] = useState(false)\n\n // Name the thing you are looking at (AGL-1000). The page used to announce\n // itself as \"Marketplace listing\" over a breadcrumb ending in \"Listing\",\n // which made the largest text on the page the one part identical on every\n // listing. This page already reads the doc for the in-place editor, so the\n // name costs nothing; the surface's declared title stays as the fallback\n // for the beat before it resolves and for a listing that does not exist.\n const listingName = String(listing?.displayName ?? '').trim()\n const isPlugin = (listing?.artifactType ?? listing?.type) === 'plugin'\n\n /*\n * The hero's owner actions, memoized (AGL-1005). `PageHeaderActions`\n * publishes upward through an effect keyed on what it is handed, so a node\n * rebuilt every render would republish every render. The dependencies are\n * exactly the four facts the buttons read.\n */\n const ownerActions = useMemo(\n () =>\n isOwner && !editing ? (\n <Stack\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ flexWrap: 'wrap' }}\n >\n {/* Shipping a new version had no door on the listing itself\n (AGL-1008) — Edit changes metadata and View changes nothing,\n so the only reading left was \"make a second listing\". Same\n publish form, pre-bound to this listing. Plugins only: a\n bundle is the thing a new version ships. */}\n {isPlugin ? (\n <AppLink href={publishPluginPath(basePath, listingId)}>\n <Button variant=\"contained\" color=\"primary\" component=\"span\">\n {'Publish new version'}\n </Button>\n </AppLink>\n ) : null}\n <BlockedControl\n blocked={!canManageListings}\n reason={marketplacePublisherPermissionRefusal(\n MARKETPLACE_MANAGE_LISTINGS_PERMISSION,\n )}\n >\n <Button\n variant=\"outlined\"\n color=\"primary\"\n onClick={() => setEditing(true)}\n >\n {'Edit listing'}\n </Button>\n </BlockedControl>\n </Stack>\n ) : null,\n [isOwner, editing, isPlugin, basePath, listingId, canManageListings],\n )\n\n return (\n <>\n <PageHeaderRecord title={listingName} />\n <PageHeaderHelp\n topic=\"plugins\"\n anchor=\"#what-the-badges-on-a-listing-mean\"\n />\n {/* Owner action in the hero (AGL-1005), where the chrome already has a\n slot for it. It used to float in a bare right-aligned box above the\n content — unanchored to anything, and it moved depending on whether\n the org had a site yet. */}\n <PageHeaderActions>{ownerActions}</PageHeaderActions>\n {editing && isOwner && canManageListings && orgId && listing ? (\n <ListingDetailEditor\n orgId={orgId}\n listingId={listingId}\n listing={listing}\n user={user}\n onDone={() => setEditing(false)}\n />\n ) : (\n // One stack, one gap: the owner cards and the listing body share the\n // same edges and the same spacing between them.\n <Stack spacing={3}>\n {/* The publisher's own view of the queue (AGL-1079). Owner-only:\n review state, rejection reasons and what was attested are the\n publisher's business, and a buyer is never offered a version\n this card exists to explain. Above the buyer-facing body,\n because \"what is happening to my submission\" is why an owner\n opened their own listing. */}\n {isOwner ? (\n <>\n <ListingReviewStatus\n listingId={listingId}\n isPlugin={isPlugin}\n permissions={permissions}\n />\n {/* The door to the Verified badge (AGL-1217). Below the review\n card on purpose: \"where is my submission\" is the more urgent\n question, and asking for a badge only makes sense once the\n listing is live. */}\n <ListingVerificationRequest\n listingId={listingId}\n listing={listing}\n viewerOrgId={orgId ?? undefined}\n user={user}\n isPlugin={isPlugin}\n permissions={permissions}\n />\n {/* The publisher's OAuth app for each connection the latest\n version declares (AGL-4196). Draws nothing for a listing\n that declares none. */}\n {isPlugin ? (\n <Suspense fallback={null}>\n <ListingConnectionCredentialsCard\n listingId={listingId}\n permissions={permissions}\n />\n </Suspense>\n ) : null}\n </>\n ) : null}\n {!actingHost ? (\n // Owners can still edit their listing with no site of their own —\n // through the hero's Edit listing action (AGL-1005).\n <Alert severity=\"info\">\n {isOwner\n ? 'Add a site to your organization to install marketplace ' +\n 'items. You can still edit this listing above.'\n : 'Add a site to your organization to view and install ' +\n 'marketplace items.'}\n </Alert>\n ) : (\n <MarketplaceListingContent\n hostId={actingHost}\n listingId={listingId}\n permissions={permissions}\n orgScoped\n orgSlug={orgSlug}\n hosts={hosts}\n />\n )}\n </Stack>\n )}\n </>\n )\n}\n\nListingDetailPage.displayName = 'ListingDetailPage'\n\nexport default ListingDetailPage\n"],"names":["PageHeaderActions","PageHeaderHelp","PageHeaderRecord","AppLink","BlockedControl","useFirestore","useFirestoreDoc","useUser","Alert","Button","Stack","doc","lazy","Suspense","useMemo","useState","ListingDetailEditor","ListingReviewStatus","ListingVerificationRequest","MarketplaceListingContent","publishPluginPath","MARKETPLACE_MANAGE_LISTINGS_PERMISSION","marketplacePublisherPermissionRefusal","ListingConnectionCredentialsCard","ListingDetailPage","props","listingId","orgId","basePath","actingHost","hosts","permissions","orgSlug","firestore","data","user","listing","idField","isOwner","Boolean","profileId","canManageListings","editing","setEditing","listingName","String","displayName","trim","isPlugin","artifactType","type","ownerActions","direction","spacing","useFlexGap","sx","flexWrap","href","variant","color","component","blocked","reason","onClick","title","topic","anchor","onDone","viewerOrgId","undefined","fallback","severity","hostId","orgScoped"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/listing-detail-page.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n PageHeaderActions,\n PageHeaderHelp,\n PageHeaderRecord,\n} from '@aglyn/aglyn'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { BlockedControl } from '@aglyn/shared-ui-jsx/components/blocked-control.component'\nimport { useFirestore, useFirestoreDoc, useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Button, Stack } from '@mui/material'\nimport { doc } from 'firebase/firestore'\nimport { lazy, Suspense, useMemo, useState } from 'react'\nimport ListingDetailEditor from './listing-detail-editor.component'\nimport ListingReviewStatus from './listing-review-status.component'\nimport ListingVerificationRequest from './listing-verification-request.component'\nimport { MarketplaceListingContent } from './listing-content.component'\nimport type { OrgInstallHost } from '../model/marketplace'\nimport { publishPluginPath } from '../model/marketplace-paths'\nimport {\n MARKETPLACE_MANAGE_LISTINGS_PERMISSION,\n marketplacePublisherPermissionRefusal,\n} from '../model/marketplace-publisher-permissions'\n\n/** The publisher's connection credentials: drawn only for an owner's plugin listing, so it loads then. */\nconst ListingConnectionCredentialsCard = lazy(() => import('./listing-connection-credentials-card.component'))\n\n/**\n * Org marketplace listing detail (AGL-772), at `{marketplace}/{listingId}`.\n *\n * It was a console route until AGL-3080, which is why it used to reach the\n * body through the `marketplaceListing` zone: an app page could not import\n * this plugin. This page IS the plugin now, so it renders the content\n * directly — the zone stays registered for the surfaces that still need it.\n *\n * Install pins validate against a site, so the detail acts through the org's\n * first site until targeting lands (AGL-773).\n */\nexport function ListingDetailPage(props: {\n listingId: string\n orgId: string | null\n basePath: string\n /** The site an install acts through, and every site for the target picker. */\n actingHost: string\n hosts: ReadonlyArray<OrgInstallHost>\n permissions?: Record<string, boolean | undefined>\n orgSlug: string\n}) {\n const { listingId, orgId, basePath, actingHost, hosts, permissions, orgSlug } =\n props\n const firestore = useFirestore()\n const { data: user } = useUser()\n\n // The listing itself, so the owner can edit the whole page in place\n // (AGL-869) rather than through a cramped sidebar card.\n const { data: listing } = useFirestoreDoc<any>(\n () => doc(firestore, 'marketplaceListings', listingId || '-missing-'),\n [firestore, listingId],\n { idField: '$id' },\n )\n // Listings are org-owned (AGL-652): ownership is an org comparison.\n const isOwner = Boolean(orgId && listing?.profileId === orgId)\n // Editing it asks \"Manage published listings\" (AGL-4047), as its save\n // route and the listing rules do. Off with the permission named, not\n // hidden; an absent key reads as allowed, and the route still decides.\n const canManageListings =\n permissions?.[MARKETPLACE_MANAGE_LISTINGS_PERMISSION] !== false\n const [editing, setEditing] = useState(false)\n\n // Name the thing you are looking at (AGL-1000). The page used to announce\n // itself as \"Marketplace listing\" over a breadcrumb ending in \"Listing\",\n // which made the largest text on the page the one part identical on every\n // listing. This page already reads the doc for the in-place editor, so the\n // name costs nothing; the surface's declared title stays as the fallback\n // for the beat before it resolves and for a listing that does not exist.\n const listingName = String(listing?.displayName ?? '').trim()\n const isPlugin = (listing?.artifactType ?? listing?.type) === 'plugin'\n\n /*\n * The hero's owner actions, memoized (AGL-1005). `PageHeaderActions`\n * publishes upward through an effect keyed on what it is handed, so a node\n * rebuilt every render would republish every render. The dependencies are\n * exactly the four facts the buttons read.\n */\n const ownerActions = useMemo(\n () =>\n isOwner && !editing ? (\n <Stack\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ flexWrap: 'wrap' }}\n >\n {/* Shipping a new version had no door on the listing itself\n (AGL-1008) — Edit changes metadata and View changes nothing,\n so the only reading left was \"make a second listing\". Same\n publish form, pre-bound to this listing. Plugins only: a\n bundle is the thing a new version ships. */}\n {isPlugin ? (\n <AppLink href={publishPluginPath(basePath, listingId)}>\n <Button variant=\"contained\" color=\"primary\" component=\"span\">\n {'Publish new version'}\n </Button>\n </AppLink>\n ) : null}\n <BlockedControl\n blocked={!canManageListings}\n reason={marketplacePublisherPermissionRefusal(\n MARKETPLACE_MANAGE_LISTINGS_PERMISSION,\n )}\n >\n <Button\n variant=\"outlined\"\n color=\"primary\"\n onClick={() => setEditing(true)}\n >\n {'Edit listing'}\n </Button>\n </BlockedControl>\n </Stack>\n ) : null,\n [isOwner, editing, isPlugin, basePath, listingId, canManageListings],\n )\n\n return (\n <>\n <PageHeaderRecord title={listingName} />\n <PageHeaderHelp\n topic=\"plugins\"\n anchor=\"#what-the-badges-on-a-listing-mean\"\n />\n {/* Owner action in the hero (AGL-1005), where the chrome already has a\n slot for it. It used to float in a bare right-aligned box above the\n content — unanchored to anything, and it moved depending on whether\n the org had a site yet. */}\n <PageHeaderActions>{ownerActions}</PageHeaderActions>\n {editing && isOwner && canManageListings && orgId && listing ? (\n <ListingDetailEditor\n orgId={orgId}\n listingId={listingId}\n listing={listing}\n user={user}\n onDone={() => setEditing(false)}\n />\n ) : (\n // One stack, one gap: the owner cards and the listing body share the\n // same edges and the same spacing between them.\n <Stack spacing={3}>\n {/* The publisher's own view of the queue (AGL-1079). Owner-only:\n review state, rejection reasons and what was attested are the\n publisher's business, and a buyer is never offered a version\n this card exists to explain. Above the buyer-facing body,\n because \"what is happening to my submission\" is why an owner\n opened their own listing. */}\n {isOwner ? (\n <>\n <ListingReviewStatus\n listingId={listingId}\n isPlugin={isPlugin}\n permissions={permissions}\n />\n {/* The door to the Verified badge (AGL-1217). Below the review\n card on purpose: \"where is my submission\" is the more urgent\n question, and asking for a badge only makes sense once the\n listing is live. */}\n <ListingVerificationRequest\n listingId={listingId}\n listing={listing}\n viewerOrgId={orgId ?? undefined}\n user={user}\n isPlugin={isPlugin}\n permissions={permissions}\n />\n {/* The publisher's OAuth app for each connection the latest\n version declares (AGL-4196). Draws nothing for a listing\n that declares none. */}\n {isPlugin ? (\n <Suspense fallback={null}>\n <ListingConnectionCredentialsCard\n listingId={listingId}\n permissions={permissions}\n />\n </Suspense>\n ) : null}\n </>\n ) : null}\n {!actingHost ? (\n // Owners can still edit their listing with no site of their own —\n // through the hero's Edit listing action (AGL-1005).\n <Alert severity=\"info\">\n {isOwner\n ? 'Add a site to your organization to install marketplace ' +\n 'items. You can still edit this listing above.'\n : 'Add a site to your organization to view and install ' +\n 'marketplace items.'}\n </Alert>\n ) : (\n <MarketplaceListingContent\n hostId={actingHost}\n listingId={listingId}\n permissions={permissions}\n orgScoped\n orgSlug={orgSlug}\n hosts={hosts}\n />\n )}\n </Stack>\n )}\n </>\n )\n}\n\nListingDetailPage.displayName = 'ListingDetailPage'\n\nexport default ListingDetailPage\n"],"names":["PageHeaderActions","PageHeaderHelp","PageHeaderRecord","AppLink","BlockedControl","useFirestore","useFirestoreDoc","useUser","Alert","Button","Stack","doc","lazy","Suspense","useMemo","useState","ListingDetailEditor","ListingReviewStatus","ListingVerificationRequest","MarketplaceListingContent","publishPluginPath","MARKETPLACE_MANAGE_LISTINGS_PERMISSION","marketplacePublisherPermissionRefusal","ListingConnectionCredentialsCard","ListingDetailPage","props","listingId","orgId","basePath","actingHost","hosts","permissions","orgSlug","firestore","data","user","listing","idField","isOwner","Boolean","profileId","canManageListings","editing","setEditing","listingName","String","displayName","trim","isPlugin","artifactType","type","ownerActions","direction","spacing","useFlexGap","sx","flexWrap","href","variant","color","component","blocked","reason","onClick","title","topic","anchor","onDone","viewerOrgId","undefined","fallback","severity","hostId","orgScoped"],"mappings":"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"}
|
|
@@ -70,12 +70,19 @@ import PublishPluginPage from "./publish-plugin-page.component.js";
|
|
|
70
70
|
const { orgMount, basePath, sections, section, segments, permissions, viewerOrgs, org } = props;
|
|
71
71
|
const orgId = (_ref = orgMount == null ? void 0 : orgMount.orgId) != null ? _ref : null;
|
|
72
72
|
const orgSlug = (_ref1 = orgMount == null ? void 0 : orgMount.orgSlug) != null ? _ref1 : '';
|
|
73
|
+
// Each site's own switch rides along (AGL-4047): the listing's
|
|
74
|
+
// installed-sites panel must not call a site that turned a plugin off a
|
|
75
|
+
// site running it.
|
|
73
76
|
const hostList = useMemo(()=>{
|
|
74
77
|
var _ref;
|
|
75
|
-
return ((_ref = orgMount == null ? void 0 : orgMount.hosts) != null ? _ref : []).map((host)=>
|
|
78
|
+
return ((_ref = orgMount == null ? void 0 : orgMount.hosts) != null ? _ref : []).map((host)=>{
|
|
79
|
+
var _host_disabledPlugins;
|
|
80
|
+
return {
|
|
76
81
|
id: host.id,
|
|
77
|
-
label: host.name
|
|
78
|
-
|
|
82
|
+
label: host.name,
|
|
83
|
+
disabledPlugins: (_host_disabledPlugins = host.disabledPlugins) != null ? _host_disabledPlugins : null
|
|
84
|
+
};
|
|
85
|
+
});
|
|
79
86
|
}, [
|
|
80
87
|
orgMount == null ? void 0 : orgMount.hosts
|
|
81
88
|
]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/marketplace-hub.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport type { ConsolePluginPageProps } from '@aglyn/aglyn'\nimport { HubSections } from '@aglyn/shared-ui-next/components/hub-tabs'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport {\n Alert,\n Button,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useMemo, useState } from 'react'\nimport HostPluginsCard from './host-plugins-card.component'\nimport ListingDetailPage from './listing-detail-page.component'\nimport MarketplaceBrowse from './marketplace-browse.component'\nimport MarketplacePaymentsNotice from './marketplace-payments-notice.component'\nimport OrgLicencesPanel from './org-licences-panel.component'\nimport OrgPublishPanel from './org-publish-panel.component'\nimport OrgSellerPanel from './org-seller-panel.component'\nimport PublisherProfilePage from './publisher-profile-page.component'\nimport PublishPluginPage from './publish-plugin-page.component'\n\n/**\n * The Marketplace hub (AGL-772/2501), as a plugin surface (AGL-3080).\n *\n * ## What moved, and what did not\n *\n * Every section of this hub — browse, installed, licences, and the four\n * seller cards — was a hand-written console route under\n * `app/(app)/[orgSlug]/marketplace/`, ten route files whose whole job was to\n * assemble chrome around a body that already belonged to this plugin. The\n * shell's generic org plugin route does that assembly for every other\n * surface, from one declaration. So the declaration is now in `plugin.ts`\n * and this is the body.\n *\n * NO URL CHANGED. `ConsoleNavItem.ownsSubtree` is what makes that true: a\n * segment naming a declared section resolves as a section, and anything else\n * beneath the surface is handed over as `segments` for this page to read. So\n * `/{org}/marketplace/browse` is still the browse tab and\n * `/{org}/marketplace/{listingId}` is still a listing — the same addresses,\n * served by a declaration instead of by seventeen files.\n *\n * ## The gates are the shell's now, and that is the point\n *\n * The seller sections read the organization's REVENUE and its payout account,\n * and the old sections layout gated them itself — above the pages rather than\n * in the rail, because a route can be typed whether or not a tab was ever\n * offered. That is exactly what `ConsoleNavSection.permission` does, applied\n * by the same shell that hides the tab, so a hidden tab and a refused deep\n * link are one verdict instead of two copies of one. The permission map fails\n * OPEN while it loads, and the shell holds the whole page on it — so this\n * component never has to know that.\n *\n * Likewise `release_marketplace`: the sections layout wrapped itself in a\n * `FeatureGate` because the flag also feeds the plugin LOADER, and a hub that\n * went on rendering with its widgets empty made turning the feature off its\n * own broken state (AGL-2019). The shell applies the same flag through the\n * nav item's `navTabId`, around the same page.\n */\nexport function MarketplaceHub(props: ConsolePluginPageProps) {\n const {\n orgMount,\n basePath,\n sections,\n section,\n segments,\n permissions,\n viewerOrgs,\n org,\n } = props\n\n const orgId = orgMount?.orgId ?? null\n const orgSlug = orgMount?.orgSlug ?? ''\n const hostList = useMemo(\n () => (orgMount?.hosts ?? []).map((host) => ({ id: host.id, label: host.name })),\n [orgMount?.hosts],\n )\n /**\n * The site an install acts through. Installs pin to a site and pins are\n * validated against host membership, so every section that installs needs\n * the same answer — which is why it is chosen once here rather than per\n * section.\n */\n const [selectedHost, setSelectedHost] = useState('')\n const actingHost = selectedHost || hostList[0]?.id || ''\n\n const mount = basePath ?? ''\n\n if (!orgId) {\n return (\n <Alert severity=\"info\">\n {'Create your first site to start an organization, then browse and ' +\n 'install marketplace items here.'}\n </Alert>\n )\n }\n\n /*\n * An entity beneath the surface, not a section: `ownsSubtree` hands those\n * over with no `section` set. Each owns its own chrome, and none of them\n * takes the acting-site picker or the section rail — a listing is one\n * thing, not a tab of the hub.\n */\n if (!section && segments?.length) {\n if (segments[0] === 'publish' && segments[1] === 'plugin') {\n return (\n <PublishPluginPage\n orgId={orgId}\n basePath={mount}\n permissions={permissions}\n /*\n * The shell holds this whole page behind a spinner until the\n * permission map settles — it fails OPEN, so rendering on it early\n * offers a publish form to a member who is about to be refused.\n * Mounted at all therefore means settled, and saying so here keeps\n * the form's own gate honest when it is mounted anywhere else.\n */\n permissionsLoaded\n />\n )\n }\n if (segments[0] === 'publisher' && segments[1]) {\n return (\n <PublisherProfilePage\n segment={String(segments[1])}\n basePath={mount}\n actingHost={actingHost}\n permissions={permissions}\n orgSlug={orgSlug}\n />\n )\n }\n // Anything else one level down is a listing id. The surface owns its\n // subtree, so it also owns saying \"no such thing\" — which the detail page\n // has to be able to do anyway for a listing deleted while a link to it\n // was still in someone's inbox.\n if (segments.length === 1) {\n return (\n <ListingDetailPage\n listingId={String(segments[0])}\n orgId={orgId}\n basePath={mount}\n actingHost={actingHost}\n hosts={hostList}\n permissions={permissions}\n orgSlug={orgSlug}\n />\n )\n }\n return (\n <Alert severity=\"warning\">\n {'This marketplace page does not exist. It may have moved, or the ' +\n 'link may be incomplete.'}\n </Alert>\n )\n }\n\n if (!actingHost) {\n return (\n <Alert severity=\"info\">\n {'Add a site to your organization to install marketplace items — ' +\n 'installs apply to a site (or every site).'}\n </Alert>\n )\n }\n\n return (\n <Stack spacing={2}>\n {/*\n What this deployment cannot do, said BEFORE the click (AGL-2019). The\n app supplies only the fact, through the deployment-capability context\n its org layout provides; the sentence is this plugin's, because it\n names what still works without a Stripe platform. Draws nothing at all\n on a configured deployment.\n */}\n <MarketplacePaymentsNotice />\n {hostList.length > 1 ? (\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Acting site'}\n </Typography>\n <TextField\n select\n size=\"small\"\n label=\"Site\"\n value={actingHost}\n onChange={(event) => setSelectedHost(event.target.value)}\n sx={{ minWidth: 200 }}\n >\n {hostList.map((host) => (\n <MenuItem key={host.id} value={host.id}>\n {host.label}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n ) : null}\n <HubSections sections={sections ?? []}>\n {section === 'browse' ? (\n <MarketplaceBrowse\n hostId={actingHost}\n permissions={permissions}\n orgScoped\n // The shell already knows the org (AGL-867): passing it keeps\n // listing links resolving synchronously instead of via an async\n // hostIndex→org lookup that can come back empty and leave the\n // detail page unreachable from browse.\n orgSlug={orgSlug}\n />\n ) : section === 'installed' ? (\n <Stack spacing={2}>\n {/* A convenience, not the inventory (AGL-1011). Administering what\n you already run belongs in Plugins; this stays so uninstalling\n something you just installed does not send you elsewhere. */}\n <Alert\n severity=\"info\"\n action={\n <AppLink href={`/${orgSlug}/plugins`}>\n <Button size=\"small\" color=\"inherit\" component=\"span\">\n {'Open Plugins'}\n </Button>\n </AppLink>\n }\n >\n {'A quick list of what this organization installed from the ' +\n 'marketplace. Settings, per-site scope and built-in plugins ' +\n 'live in Plugins.'}\n </Alert>\n <HostPluginsCard hostId={actingHost} orgSlug={orgSlug} />\n </Stack>\n ) : section === 'licences' ? (\n <OrgLicencesPanel\n orgId={orgId}\n basePath={mount}\n viewerOrgs={viewerOrgs}\n />\n ) : section === 'upload' ? (\n <OrgPublishPanel\n orgId={orgId}\n hosts={hostList}\n basePath={mount}\n billingPath={orgMount?.billingPath}\n org={org as never}\n // The shell holds this page behind a spinner until the org doc\n // settles, so an org in hand means an org that arrived. Stated\n // rather than assumed: the fee this panel prints is a claim about\n // a publisher's plan, and the free rate is what an absent doc\n // resolves to.\n orgReady={Boolean(org)}\n />\n ) : section === 'profile' ||\n section === 'listings' ||\n section === 'payouts' ||\n section === 'sales' ? (\n <OrgSellerPanel\n orgId={orgId}\n section={section}\n basePath={mount}\n permissions={permissions}\n />\n ) : null}\n </HubSections>\n </Stack>\n )\n}\n\nMarketplaceHub.displayName = 'MarketplaceHub'\n\nexport default MarketplaceHub\n"],"names":["HubSections","AppLink","Alert","Button","MenuItem","Stack","TextField","Typography","useMemo","useState","HostPluginsCard","ListingDetailPage","MarketplaceBrowse","MarketplacePaymentsNotice","OrgLicencesPanel","OrgPublishPanel","OrgSellerPanel","PublisherProfilePage","PublishPluginPage","MarketplaceHub","props","hostList","orgMount","basePath","sections","section","segments","permissions","viewerOrgs","org","orgId","orgSlug","hosts","map","host","id","label","name","selectedHost","setSelectedHost","actingHost","mount","severity","length","permissionsLoaded","segment","String","listingId","spacing","useFlexGap","direction","sx","alignItems","flexWrap","variant","color","select","size","value","onChange","event","target","minWidth","hostId","orgScoped","action","href","component","billingPath","orgReady","Boolean","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/marketplace-hub.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport type { ConsolePluginPageProps } from '@aglyn/aglyn'\nimport { HubSections } from '@aglyn/shared-ui-next/components/hub-tabs'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport {\n Alert,\n Button,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useMemo, useState } from 'react'\nimport HostPluginsCard from './host-plugins-card.component'\nimport ListingDetailPage from './listing-detail-page.component'\nimport MarketplaceBrowse from './marketplace-browse.component'\nimport MarketplacePaymentsNotice from './marketplace-payments-notice.component'\nimport OrgLicencesPanel from './org-licences-panel.component'\nimport OrgPublishPanel from './org-publish-panel.component'\nimport OrgSellerPanel from './org-seller-panel.component'\nimport PublisherProfilePage from './publisher-profile-page.component'\nimport PublishPluginPage from './publish-plugin-page.component'\n\n/**\n * The Marketplace hub (AGL-772/2501), as a plugin surface (AGL-3080).\n *\n * ## What moved, and what did not\n *\n * Every section of this hub — browse, installed, licences, and the four\n * seller cards — was a hand-written console route under\n * `app/(app)/[orgSlug]/marketplace/`, ten route files whose whole job was to\n * assemble chrome around a body that already belonged to this plugin. The\n * shell's generic org plugin route does that assembly for every other\n * surface, from one declaration. So the declaration is now in `plugin.ts`\n * and this is the body.\n *\n * NO URL CHANGED. `ConsoleNavItem.ownsSubtree` is what makes that true: a\n * segment naming a declared section resolves as a section, and anything else\n * beneath the surface is handed over as `segments` for this page to read. So\n * `/{org}/marketplace/browse` is still the browse tab and\n * `/{org}/marketplace/{listingId}` is still a listing — the same addresses,\n * served by a declaration instead of by seventeen files.\n *\n * ## The gates are the shell's now, and that is the point\n *\n * The seller sections read the organization's REVENUE and its payout account,\n * and the old sections layout gated them itself — above the pages rather than\n * in the rail, because a route can be typed whether or not a tab was ever\n * offered. That is exactly what `ConsoleNavSection.permission` does, applied\n * by the same shell that hides the tab, so a hidden tab and a refused deep\n * link are one verdict instead of two copies of one. The permission map fails\n * OPEN while it loads, and the shell holds the whole page on it — so this\n * component never has to know that.\n *\n * Likewise `release_marketplace`: the sections layout wrapped itself in a\n * `FeatureGate` because the flag also feeds the plugin LOADER, and a hub that\n * went on rendering with its widgets empty made turning the feature off its\n * own broken state (AGL-2019). The shell applies the same flag through the\n * nav item's `navTabId`, around the same page.\n */\nexport function MarketplaceHub(props: ConsolePluginPageProps) {\n const {\n orgMount,\n basePath,\n sections,\n section,\n segments,\n permissions,\n viewerOrgs,\n org,\n } = props\n\n const orgId = orgMount?.orgId ?? null\n const orgSlug = orgMount?.orgSlug ?? ''\n // Each site's own switch rides along (AGL-4047): the listing's\n // installed-sites panel must not call a site that turned a plugin off a\n // site running it.\n const hostList = useMemo(\n () =>\n (orgMount?.hosts ?? []).map((host) => ({\n id: host.id,\n label: host.name,\n disabledPlugins: host.disabledPlugins ?? null,\n })),\n [orgMount?.hosts],\n )\n /**\n * The site an install acts through. Installs pin to a site and pins are\n * validated against host membership, so every section that installs needs\n * the same answer — which is why it is chosen once here rather than per\n * section.\n */\n const [selectedHost, setSelectedHost] = useState('')\n const actingHost = selectedHost || hostList[0]?.id || ''\n\n const mount = basePath ?? ''\n\n if (!orgId) {\n return (\n <Alert severity=\"info\">\n {'Create your first site to start an organization, then browse and ' +\n 'install marketplace items here.'}\n </Alert>\n )\n }\n\n /*\n * An entity beneath the surface, not a section: `ownsSubtree` hands those\n * over with no `section` set. Each owns its own chrome, and none of them\n * takes the acting-site picker or the section rail — a listing is one\n * thing, not a tab of the hub.\n */\n if (!section && segments?.length) {\n if (segments[0] === 'publish' && segments[1] === 'plugin') {\n return (\n <PublishPluginPage\n orgId={orgId}\n basePath={mount}\n permissions={permissions}\n /*\n * The shell holds this whole page behind a spinner until the\n * permission map settles — it fails OPEN, so rendering on it early\n * offers a publish form to a member who is about to be refused.\n * Mounted at all therefore means settled, and saying so here keeps\n * the form's own gate honest when it is mounted anywhere else.\n */\n permissionsLoaded\n />\n )\n }\n if (segments[0] === 'publisher' && segments[1]) {\n return (\n <PublisherProfilePage\n segment={String(segments[1])}\n basePath={mount}\n actingHost={actingHost}\n permissions={permissions}\n orgSlug={orgSlug}\n />\n )\n }\n // Anything else one level down is a listing id. The surface owns its\n // subtree, so it also owns saying \"no such thing\" — which the detail page\n // has to be able to do anyway for a listing deleted while a link to it\n // was still in someone's inbox.\n if (segments.length === 1) {\n return (\n <ListingDetailPage\n listingId={String(segments[0])}\n orgId={orgId}\n basePath={mount}\n actingHost={actingHost}\n hosts={hostList}\n permissions={permissions}\n orgSlug={orgSlug}\n />\n )\n }\n return (\n <Alert severity=\"warning\">\n {'This marketplace page does not exist. It may have moved, or the ' +\n 'link may be incomplete.'}\n </Alert>\n )\n }\n\n if (!actingHost) {\n return (\n <Alert severity=\"info\">\n {'Add a site to your organization to install marketplace items — ' +\n 'installs apply to a site (or every site).'}\n </Alert>\n )\n }\n\n return (\n <Stack spacing={2}>\n {/*\n What this deployment cannot do, said BEFORE the click (AGL-2019). The\n app supplies only the fact, through the deployment-capability context\n its org layout provides; the sentence is this plugin's, because it\n names what still works without a Stripe platform. Draws nothing at all\n on a configured deployment.\n */}\n <MarketplacePaymentsNotice />\n {hostList.length > 1 ? (\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Acting site'}\n </Typography>\n <TextField\n select\n size=\"small\"\n label=\"Site\"\n value={actingHost}\n onChange={(event) => setSelectedHost(event.target.value)}\n sx={{ minWidth: 200 }}\n >\n {hostList.map((host) => (\n <MenuItem key={host.id} value={host.id}>\n {host.label}\n </MenuItem>\n ))}\n </TextField>\n </Stack>\n ) : null}\n <HubSections sections={sections ?? []}>\n {section === 'browse' ? (\n <MarketplaceBrowse\n hostId={actingHost}\n permissions={permissions}\n orgScoped\n // The shell already knows the org (AGL-867): passing it keeps\n // listing links resolving synchronously instead of via an async\n // hostIndex→org lookup that can come back empty and leave the\n // detail page unreachable from browse.\n orgSlug={orgSlug}\n />\n ) : section === 'installed' ? (\n <Stack spacing={2}>\n {/* A convenience, not the inventory (AGL-1011). Administering what\n you already run belongs in Plugins; this stays so uninstalling\n something you just installed does not send you elsewhere. */}\n <Alert\n severity=\"info\"\n action={\n <AppLink href={`/${orgSlug}/plugins`}>\n <Button size=\"small\" color=\"inherit\" component=\"span\">\n {'Open Plugins'}\n </Button>\n </AppLink>\n }\n >\n {'A quick list of what this organization installed from the ' +\n 'marketplace. Settings, per-site scope and built-in plugins ' +\n 'live in Plugins.'}\n </Alert>\n <HostPluginsCard hostId={actingHost} orgSlug={orgSlug} />\n </Stack>\n ) : section === 'licences' ? (\n <OrgLicencesPanel\n orgId={orgId}\n basePath={mount}\n viewerOrgs={viewerOrgs}\n />\n ) : section === 'upload' ? (\n <OrgPublishPanel\n orgId={orgId}\n hosts={hostList}\n basePath={mount}\n billingPath={orgMount?.billingPath}\n org={org as never}\n // The shell holds this page behind a spinner until the org doc\n // settles, so an org in hand means an org that arrived. Stated\n // rather than assumed: the fee this panel prints is a claim about\n // a publisher's plan, and the free rate is what an absent doc\n // resolves to.\n orgReady={Boolean(org)}\n />\n ) : section === 'profile' ||\n section === 'listings' ||\n section === 'payouts' ||\n section === 'sales' ? (\n <OrgSellerPanel\n orgId={orgId}\n section={section}\n basePath={mount}\n permissions={permissions}\n />\n ) : null}\n </HubSections>\n </Stack>\n )\n}\n\nMarketplaceHub.displayName = 'MarketplaceHub'\n\nexport default MarketplaceHub\n"],"names":["HubSections","AppLink","Alert","Button","MenuItem","Stack","TextField","Typography","useMemo","useState","HostPluginsCard","ListingDetailPage","MarketplaceBrowse","MarketplacePaymentsNotice","OrgLicencesPanel","OrgPublishPanel","OrgSellerPanel","PublisherProfilePage","PublishPluginPage","MarketplaceHub","props","hostList","orgMount","basePath","sections","section","segments","permissions","viewerOrgs","org","orgId","orgSlug","hosts","map","host","id","label","name","disabledPlugins","selectedHost","setSelectedHost","actingHost","mount","severity","length","permissionsLoaded","segment","String","listingId","spacing","useFlexGap","direction","sx","alignItems","flexWrap","variant","color","select","size","value","onChange","event","target","minWidth","hostId","orgScoped","action","href","component","billingPath","orgReady","Boolean","displayName"],"mappings":"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"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { type OrgInstallHost } from '../model/marketplace';
|
|
1
2
|
export interface PluginSiteSetPanelProps {
|
|
2
3
|
/** Acting site — org pins still resolve their org through one. */
|
|
3
4
|
hostId: string;
|
|
4
5
|
listingId: string;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Every site, with its own `disabledPlugins` when the page has it
|
|
8
|
+
* (AGL-4047): a site that switched this listing off is pinned but does not
|
|
9
|
+
* run it, and the panel says so instead of listing it as running.
|
|
10
|
+
*/
|
|
11
|
+
hosts?: ReadonlyArray<OrgInstallHost>;
|
|
9
12
|
}
|
|
10
13
|
/**
|
|
11
14
|
* The `pluginSiteSet` slot (AGL-1007): the site-set control as something the
|
|
@@ -87,10 +87,11 @@ import PluginSiteSet from "./plugin-site-set.component.js";
|
|
|
87
87
|
firestore,
|
|
88
88
|
listingId
|
|
89
89
|
]);
|
|
90
|
-
const orgInstall = useMemo(()=>resolveOrgInstallSummary(hosts != null ? hosts : [], sitePins, orgPin != null ? orgPin : null), [
|
|
90
|
+
const orgInstall = useMemo(()=>resolveOrgInstallSummary(hosts != null ? hosts : [], sitePins, orgPin != null ? orgPin : null, listingId), [
|
|
91
91
|
hosts,
|
|
92
92
|
sitePins,
|
|
93
|
-
orgPin
|
|
93
|
+
orgPin,
|
|
94
|
+
listingId
|
|
94
95
|
]);
|
|
95
96
|
// The listing doc the actions need. The pin carries the denormalised copy
|
|
96
97
|
// the actions actually read (`$id`, `displayName`, `pluginId`), so this page
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/plugin-site-set-panel.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { Typography } from '@mui/material'\nimport { doc, getDoc } from 'firebase/firestore'\nimport { useEffect, useMemo, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreDoc,\n useHostOrgId,\n} from '@aglyn/tenant-feature-instance'\nimport { offeredPluginVersion } from '@aglyn/aglyn'\nimport { resolveOrgInstallSummary } from '../model/marketplace'\nimport { useMarketplaceActions } from '../hooks/use-marketplace-actions'\nimport PluginSiteSet from './plugin-site-set.component'\n\nexport interface PluginSiteSetPanelProps {\n /** Acting site — org pins still resolve their org through one. */\n hostId: string\n listingId: string\n hosts?: ReadonlyArray<{ id: string; label: string }>\n}\n\n/**\n * The `pluginSiteSet` slot (AGL-1007): the site-set control as something the\n * console can drop onto the installation detail page without importing this\n * plugin.\n *\n * It reads its own pins rather than taking them as props, because the page\n * that hosts it has no other use for them — unlike the listing page, which\n * also gates its pre-install picker and primary button on the same state and\n * therefore keeps ownership of it there.\n */\nexport function PluginSiteSetPanel(props: PluginSiteSetPanelProps) {\n const { hostId, listingId, hosts } = props\n const firestore = useFirestore()\n const orgId = useHostOrgId(hostId)\n const { installPlan, uninstall } = useMarketplaceActions(hostId)\n\n // Held at null while `useHostOrgId` is in flight, never `orgs/-pending-`\n // (AGL-1440): installs are member-gated, so the sentinel was a\n // guaranteed-denied listen on every mount of this panel.\n const { data: orgPin } = useFirestoreDoc<any>(\n () =>\n orgId && listingId\n ? doc(firestore, 'orgs', orgId, 'installs', listingId)\n : null,\n [firestore, orgId, listingId],\n )\n const [sitePins, setSitePins] = useState<Record<string, any>>({})\n const [pinsNonce, setPinsNonce] = useState(0)\n const hostIdsKey = (hosts ?? []).map((host) => host.id).join('|')\n useEffect(() => {\n if (!listingId || !hosts?.length) return\n let active = true\n void Promise.all(\n hosts.map(async (host) => {\n const snapshot = await getDoc(\n doc(firestore, 'hosts', host.id, 'installs', listingId),\n )\n return [host.id, snapshot.exists() ? snapshot.data() : null] as const\n }),\n )\n .then((entries) => {\n if (active) setSitePins(Object.fromEntries(entries))\n })\n .catch(() => undefined)\n return () => {\n active = false\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [firestore, listingId, hostIdsKey, pinsNonce])\n\n // The listing, for the version actually on offer (AGL-1017). This used to\n // pass a PIN's own version as `latestVersion`, which is by definition the\n // version that pin is already at — so no site ever looked stale and the\n // update control never appeared on this page at all.\n const { data: listing } = useFirestoreDoc<any>(\n () => doc(firestore, 'marketplaceListings', listingId || '-missing-'),\n [firestore, listingId],\n )\n\n // The kill switch (AGL-2368). This panel offers an UPDATE, so it is asking\n // \"what installs today\" and had only the review-verdict mirror to ask —\n // which nothing cleared on revoke, so it offered a version the install\n // route refuses. Listing-scoped and public, one read for the panel.\n const { data: revocation } = useFirestoreDoc<any>(\n () =>\n listingId ? doc(firestore, 'revocations', listingId) : null,\n [firestore, listingId],\n )\n\n const orgInstall = useMemo(\n () => resolveOrgInstallSummary(hosts ?? [], sitePins, orgPin ?? null),\n [hosts, sitePins, orgPin],\n )\n // The listing doc the actions need. The pin carries the denormalised copy\n // the actions actually read (`$id`, `displayName`, `pluginId`), so this page\n // does not pay for a second read of the listing itself. `pluginId` is what\n // the uninstall dialog scans published pages by; without it the impact is\n // unknown and every uninstall asks for the typed acknowledgement.\n const anyPin = orgPin ?? Object.values(sitePins).find(Boolean) ?? null\n const actionListing = useMemo(\n () => ({\n $id: listingId,\n displayName: listing?.displayName ?? anyPin?.displayName ?? listingId,\n artifactType: 'plugin',\n latestVersion: listing?.latestVersion ?? anyPin?.version,\n pluginId: listing?.pluginId ?? anyPin?.pluginId,\n }),\n [\n listingId,\n listing?.displayName,\n listing?.latestVersion,\n listing?.pluginId,\n anyPin?.displayName,\n anyPin?.version,\n anyPin?.pluginId,\n ],\n )\n\n if (!orgInstall.installedAnywhere) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Not installed on any site in this organization.'}\n </Typography>\n )\n }\n\n return (\n <PluginSiteSet\n listing={actionListing}\n hosts={hosts ?? []}\n orgInstall={orgInstall}\n // The newest INSTALLABLE version (AGL-1016/2368) — never `latestVersion`,\n // and never the review verdict alone, or the page offers an update the\n // install route refuses.\n latestVersion={offeredPluginVersion(listing, revocation) ?? undefined}\n installPlan={installPlan}\n uninstall={uninstall}\n onChanged={() => setPinsNonce((current) => current + 1)}\n />\n )\n}\n\nPluginSiteSetPanel.displayName = 'PluginSiteSetPanel'\n\nexport default PluginSiteSetPanel\n"],"names":["Typography","doc","getDoc","useEffect","useMemo","useState","useFirestore","useFirestoreDoc","useHostOrgId","offeredPluginVersion","resolveOrgInstallSummary","useMarketplaceActions","PluginSiteSet","PluginSiteSetPanel","props","orgPin","hostId","listingId","hosts","firestore","orgId","installPlan","uninstall","data","sitePins","setSitePins","pinsNonce","setPinsNonce","hostIdsKey","map","host","id","join","length","active","Promise","all","snapshot","exists","then","entries","Object","fromEntries","catch","undefined","listing","revocation","orgInstall","anyPin","values","find","Boolean","actionListing","$id","displayName","artifactType","latestVersion","version","pluginId","installedAnywhere","variant","color","onChanged","current"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/plugin-site-set-panel.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { Typography } from '@mui/material'\nimport { doc, getDoc } from 'firebase/firestore'\nimport { useEffect, useMemo, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreDoc,\n useHostOrgId,\n} from '@aglyn/tenant-feature-instance'\nimport { offeredPluginVersion } from '@aglyn/aglyn'\nimport { resolveOrgInstallSummary, type OrgInstallHost } from '../model/marketplace'\nimport { useMarketplaceActions } from '../hooks/use-marketplace-actions'\nimport PluginSiteSet from './plugin-site-set.component'\n\nexport interface PluginSiteSetPanelProps {\n /** Acting site — org pins still resolve their org through one. */\n hostId: string\n listingId: string\n /**\n * Every site, with its own `disabledPlugins` when the page has it\n * (AGL-4047): a site that switched this listing off is pinned but does not\n * run it, and the panel says so instead of listing it as running.\n */\n hosts?: ReadonlyArray<OrgInstallHost>\n}\n\n/**\n * The `pluginSiteSet` slot (AGL-1007): the site-set control as something the\n * console can drop onto the installation detail page without importing this\n * plugin.\n *\n * It reads its own pins rather than taking them as props, because the page\n * that hosts it has no other use for them — unlike the listing page, which\n * also gates its pre-install picker and primary button on the same state and\n * therefore keeps ownership of it there.\n */\nexport function PluginSiteSetPanel(props: PluginSiteSetPanelProps) {\n const { hostId, listingId, hosts } = props\n const firestore = useFirestore()\n const orgId = useHostOrgId(hostId)\n const { installPlan, uninstall } = useMarketplaceActions(hostId)\n\n // Held at null while `useHostOrgId` is in flight, never `orgs/-pending-`\n // (AGL-1440): installs are member-gated, so the sentinel was a\n // guaranteed-denied listen on every mount of this panel.\n const { data: orgPin } = useFirestoreDoc<any>(\n () =>\n orgId && listingId\n ? doc(firestore, 'orgs', orgId, 'installs', listingId)\n : null,\n [firestore, orgId, listingId],\n )\n const [sitePins, setSitePins] = useState<Record<string, any>>({})\n const [pinsNonce, setPinsNonce] = useState(0)\n const hostIdsKey = (hosts ?? []).map((host) => host.id).join('|')\n useEffect(() => {\n if (!listingId || !hosts?.length) return\n let active = true\n void Promise.all(\n hosts.map(async (host) => {\n const snapshot = await getDoc(\n doc(firestore, 'hosts', host.id, 'installs', listingId),\n )\n return [host.id, snapshot.exists() ? snapshot.data() : null] as const\n }),\n )\n .then((entries) => {\n if (active) setSitePins(Object.fromEntries(entries))\n })\n .catch(() => undefined)\n return () => {\n active = false\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [firestore, listingId, hostIdsKey, pinsNonce])\n\n // The listing, for the version actually on offer (AGL-1017). This used to\n // pass a PIN's own version as `latestVersion`, which is by definition the\n // version that pin is already at — so no site ever looked stale and the\n // update control never appeared on this page at all.\n const { data: listing } = useFirestoreDoc<any>(\n () => doc(firestore, 'marketplaceListings', listingId || '-missing-'),\n [firestore, listingId],\n )\n\n // The kill switch (AGL-2368). This panel offers an UPDATE, so it is asking\n // \"what installs today\" and had only the review-verdict mirror to ask —\n // which nothing cleared on revoke, so it offered a version the install\n // route refuses. Listing-scoped and public, one read for the panel.\n const { data: revocation } = useFirestoreDoc<any>(\n () =>\n listingId ? doc(firestore, 'revocations', listingId) : null,\n [firestore, listingId],\n )\n\n const orgInstall = useMemo(\n () =>\n resolveOrgInstallSummary(hosts ?? [], sitePins, orgPin ?? null, listingId),\n [hosts, sitePins, orgPin, listingId],\n )\n // The listing doc the actions need. The pin carries the denormalised copy\n // the actions actually read (`$id`, `displayName`, `pluginId`), so this page\n // does not pay for a second read of the listing itself. `pluginId` is what\n // the uninstall dialog scans published pages by; without it the impact is\n // unknown and every uninstall asks for the typed acknowledgement.\n const anyPin = orgPin ?? Object.values(sitePins).find(Boolean) ?? null\n const actionListing = useMemo(\n () => ({\n $id: listingId,\n displayName: listing?.displayName ?? anyPin?.displayName ?? listingId,\n artifactType: 'plugin',\n latestVersion: listing?.latestVersion ?? anyPin?.version,\n pluginId: listing?.pluginId ?? anyPin?.pluginId,\n }),\n [\n listingId,\n listing?.displayName,\n listing?.latestVersion,\n listing?.pluginId,\n anyPin?.displayName,\n anyPin?.version,\n anyPin?.pluginId,\n ],\n )\n\n if (!orgInstall.installedAnywhere) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Not installed on any site in this organization.'}\n </Typography>\n )\n }\n\n return (\n <PluginSiteSet\n listing={actionListing}\n hosts={hosts ?? []}\n orgInstall={orgInstall}\n // The newest INSTALLABLE version (AGL-1016/2368) — never `latestVersion`,\n // and never the review verdict alone, or the page offers an update the\n // install route refuses.\n latestVersion={offeredPluginVersion(listing, revocation) ?? undefined}\n installPlan={installPlan}\n uninstall={uninstall}\n onChanged={() => setPinsNonce((current) => current + 1)}\n />\n )\n}\n\nPluginSiteSetPanel.displayName = 'PluginSiteSetPanel'\n\nexport default PluginSiteSetPanel\n"],"names":["Typography","doc","getDoc","useEffect","useMemo","useState","useFirestore","useFirestoreDoc","useHostOrgId","offeredPluginVersion","resolveOrgInstallSummary","useMarketplaceActions","PluginSiteSet","PluginSiteSetPanel","props","orgPin","hostId","listingId","hosts","firestore","orgId","installPlan","uninstall","data","sitePins","setSitePins","pinsNonce","setPinsNonce","hostIdsKey","map","host","id","join","length","active","Promise","all","snapshot","exists","then","entries","Object","fromEntries","catch","undefined","listing","revocation","orgInstall","anyPin","values","find","Boolean","actionListing","$id","displayName","artifactType","latestVersion","version","pluginId","installedAnywhere","variant","color","onChanged","current"],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ 'use client';
|
|
17
17
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
18
|
+
import { orgPermissionLabel } from "@aglyn/aglyn/app-utils/org-permissions";
|
|
18
19
|
import { MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
19
20
|
import { mdiCheckCircle } from "@aglyn/shared-data-mdi";
|
|
20
21
|
import { Alert, Box, Button, Checkbox, Chip, Dialog, DialogActions, DialogContent, DialogTitle, Divider, FormControlLabel, FormGroup, Stack, Typography } from "@mui/material";
|
|
@@ -155,6 +156,11 @@ import UninstallImpactDialog from "./uninstall-impact-dialog.component.js";
|
|
|
155
156
|
variant: "outlined",
|
|
156
157
|
label: "Overrides org"
|
|
157
158
|
}) : null,
|
|
159
|
+
site.switchedOff ? /*#__PURE__*/ _jsx(Chip, {
|
|
160
|
+
size: "small",
|
|
161
|
+
variant: "outlined",
|
|
162
|
+
label: "Switched off on this site"
|
|
163
|
+
}) : null,
|
|
158
164
|
site.pinnedBy === 'host' && staleSites.some((stale)=>stale.hostId === site.hostId) ? /*#__PURE__*/ _jsx(Button, {
|
|
159
165
|
size: "small",
|
|
160
166
|
variant: "outlined",
|
|
@@ -183,6 +189,11 @@ import UninstallImpactDialog from "./uninstall-impact-dialog.component.js";
|
|
|
183
189
|
}, site.hostId);
|
|
184
190
|
})
|
|
185
191
|
}),
|
|
192
|
+
orgInstall.sites.some((site)=>site.switchedOff) ? /*#__PURE__*/ _jsx(Typography, {
|
|
193
|
+
variant: "caption",
|
|
194
|
+
color: "text.secondary",
|
|
195
|
+
children: 'A site that switched it off turns it back on from its own ' + 'Admin › Plugins page, which takes ' + `“${orgPermissionLabel('site.manage')}” on that site.`
|
|
196
|
+
}) : null,
|
|
186
197
|
staleSites.length ? /*#__PURE__*/ _jsxs(Stack, {
|
|
187
198
|
spacing: 1,
|
|
188
199
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/plugin-site-set.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { mdiCheckCircle } from '@aglyn/shared-data-mdi'\nimport {\n Alert,\n Box,\n Button,\n Checkbox,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Divider,\n FormControlLabel,\n FormGroup,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\n\nimport {\n resolveUninstallTargets,\n type OrgInstallSummary,\n type UninstallTarget,\n} from '../model/marketplace'\nimport {\n PLUGIN_ORG_WIDE_UPDATE_NOTE,\n pluginSiteSetSummary,\n pluginUpdateAll,\n pluginUpdatePrompt,\n staleSites as staleSitesOf,\n} from '../model/marketplace-catalog'\nimport UninstallImpactDialog from './uninstall-impact-dialog.component'\n\nexport interface PluginSiteSetProps {\n /** The listing/installation these pins belong to. */\n listing: any\n /** Every site in the org, in display order. */\n hosts: ReadonlyArray<{ id: string; label: string }>\n /** The resolved picture — see `resolveOrgInstallSummary`. */\n orgInstall: OrgInstallSummary\n /** Version the pins should be at; a site behind this can be updated. */\n latestVersion?: string | number | null\n /** Install the given concrete steps (org pin and/or named host pins). */\n installPlan: (\n listing: any,\n steps: ReadonlyArray<{ scope: 'org' | 'host'; hostId?: string }>,\n options?: { intent?: 'install' | 'update'; version?: string | number | null },\n ) => Promise<unknown>\n /** Drop a pin — org-wide, or from one named site. */\n uninstall: (\n listing: any,\n scope?: 'org' | 'host',\n hostId?: string,\n ) => Promise<unknown>\n /** Re-read the pins after a write; the caller owns that state. */\n onChanged: () => void\n}\n\n/**\n * The set of sites a plugin is installed on, and the controls to change it\n * (AGL-997), extracted from the listing page so the installation detail page\n * (AGL-1007) shows the same thing rather than a second implementation that\n * drifts.\n *\n * An org-scope install is a SET, not a boolean: an org pin covers every site\n * including ones created later, host pins name specific sites, and a host\n * pin on top of an org pin shadows it for that one site. All three facts\n * have to survive into the UI, which is why this takes the whole summary\n * rather than a scope.\n */\nexport function PluginSiteSet(props: PluginSiteSetProps) {\n const {\n listing,\n hosts,\n orgInstall,\n latestVersion,\n installPlan,\n uninstall,\n onChanged,\n } = props\n const [addHostIds, setAddHostIds] = useState<string[]>([])\n /**\n * The uninstall awaiting confirmation (AGL-1027). Uninstalling used to be a\n * bare click; nothing said which sites lost the plugin or which published\n * pages stopped rendering it.\n */\n const [pendingUninstall, setPendingUninstall] = useState<{\n scope: 'org' | 'host'\n hostId?: string\n targets: UninstallTarget[]\n } | null>(null)\n const [siteBusy, setSiteBusy] = useState(false)\n /**\n * A site is behind only when its pin is genuinely OLDER (AGL-1017). \"Not\n * equal\" also fires when a site runs something newer than the offer — a\n * publisher testing their own build, or a version withdrawn after install —\n * and offering to \"update\" those would quietly move them backwards.\n */\n const staleSites = useMemo(\n () => staleSitesOf(orgInstall.sites, latestVersion),\n [orgInstall.sites, latestVersion],\n )\n const updateAll = pluginUpdateAll(orgInstall, staleSites, String(latestVersion))\n /**\n * Update is per-site FIRST (AGL-1017). A version with breaking changes has\n * to be absorbable one site at a time — take it on a quiet site, see what\n * breaks, then decide about the rest — so each site behind the offer gets\n * its own control and the bulk action is the convenience on top.\n *\n * An org pin cannot do that: it is one pointer covering every site, so\n * moving it moves all of them at once. Rather than hide that behind a\n * button that looks per-site, the panel says so and points at the split.\n */\n const [pending, setPending] = useState<\n { hostIds: string[] | null; label: string } | null\n >(null)\n const [changelog, setChangelog] = useState<string | null>(null)\n const updatePrompt = pluginUpdatePrompt({\n name: listing?.displayName ?? 'This plugin',\n latestVersion: String(latestVersion),\n label: pending?.label ?? '',\n singleSite: Boolean(pending?.hostIds && pending.hostIds.length === 1),\n })\n useEffect(() => {\n if (!pending || !listing?.$id || !latestVersion) return\n let active = true\n setChangelog(null)\n void fetch(\n `/api/marketplace/listing-versions?listingId=${encodeURIComponent(\n String(listing.$id),\n )}`,\n )\n .then((response) => (response.ok ? response.json() : { versions: [] }))\n .then((payload) => {\n if (!active) return\n const entry = (payload?.versions ?? []).find(\n (version: { version?: string }) =>\n String(version?.version) === String(latestVersion),\n )\n setChangelog(entry?.changelog ?? '')\n })\n .catch(() => active && setChangelog(''))\n return () => {\n active = false\n }\n }, [pending, listing?.$id, latestVersion])\n const runSiteEdit = async (work: () => Promise<unknown>) => {\n setSiteBusy(true)\n try {\n await work()\n } finally {\n setSiteBusy(false)\n setAddHostIds([])\n onChanged()\n }\n }\n\n if (!orgInstall.installedAnywhere) return null\n\n return (\n <Stack spacing={1.5}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'flex-start' }}\n >\n <MdiIcon\n path={mdiCheckCircle.path}\n color=\"success\"\n sx={{ fontSize: 20, mt: '2px' }}\n />\n <Typography variant=\"body2\">\n {pluginSiteSetSummary(orgInstall, hosts.length)}\n </Typography>\n </Stack>\n\n {/* Name them. A count alone still leaves you\n guessing which sites are running the thing. */}\n <Stack spacing={0.5}>\n {orgInstall.sites.map((site) => (\n <Stack\n key={site.hostId}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Typography\n variant=\"body2\"\n sx={{ flex: 1, minWidth: 0 }}\n noWrap\n >\n {site.label}\n </Typography>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {`v${site.version ?? '—'}`}\n </Typography>\n {site.shadowed ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label=\"Overrides org\"\n />\n ) : null}\n {/* Removable only where the site owns its\n pin: dropping a site covered by the org\n pin would mean writing an exclusion the\n loader has no concept of. */}\n {/* This site alone (AGL-1017). Only where the site owns its pin —\n a site covered by the org pin has no pointer of its own to\n move, and pretending otherwise would update every site. */}\n {site.pinnedBy === 'host' &&\n staleSites.some((stale) => stale.hostId === site.hostId) ? (\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n disabled={siteBusy}\n onClick={() =>\n setPending({\n hostIds: [site.hostId],\n label: site.label,\n })\n }\n >\n {`Update to v${latestVersion}`}\n </Button>\n ) : null}\n {site.pinnedBy === 'host' ? (\n <Button\n size=\"small\"\n color=\"inherit\"\n disabled={siteBusy}\n onClick={() =>\n setPendingUninstall({\n scope: 'host',\n hostId: site.hostId,\n targets: resolveUninstallTargets(\n orgInstall,\n 'host',\n site.hostId,\n ),\n })\n }\n >\n {'Remove'}\n </Button>\n ) : null}\n </Stack>\n ))}\n </Stack>\n\n {staleSites.length ? (\n <Stack spacing={1}>\n {orgInstall.orgWide ? (\n // Said out loud rather than discovered afterwards (AGL-1017): an\n // org pin is a single pointer, so there is no half of it to move.\n // A workspace that wants to stage a breaking update has to be on\n // per-site pins, and the way there is the split below.\n <Alert severity=\"info\" variant=\"outlined\">\n {PLUGIN_ORG_WIDE_UPDATE_NOTE}\n </Alert>\n ) : null}\n <Box>\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n disabled={siteBusy}\n onClick={() =>\n setPending({ hostIds: updateAll.hostIds, label: updateAll.target })\n }\n >\n {updateAll.label}\n </Button>\n </Box>\n </Stack>\n ) : null}\n\n {/* What changes, before it changes (AGL-1017). Third-party code moving\n in a workspace deserves more than a spinner: the version pair, the\n publisher's changelog, and which sites are about to take it. */}\n <Dialog\n open={Boolean(pending)}\n onClose={() => setPending(null)}\n maxWidth=\"sm\"\n fullWidth\n >\n <DialogTitle>{updatePrompt.title}</DialogTitle>\n <DialogContent dividers>\n <Stack spacing={1.5}>\n <Typography variant=\"body2\">{updatePrompt.body}</Typography>\n {updatePrompt.others ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {updatePrompt.others}\n </Typography>\n ) : null}\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{\"What's new\"}</Typography>\n {changelog === null ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading the changelog…'}\n </Typography>\n ) : changelog ? (\n <Typography variant=\"body2\" sx={{ whiteSpace: 'pre-wrap' }}>\n {changelog}\n </Typography>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The publisher did not describe this version.'}\n </Typography>\n )}\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {updatePrompt.note}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button color=\"inherit\" onClick={() => setPending(null)}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n disabled={siteBusy}\n onClick={() => {\n const target = pending\n setPending(null)\n if (!target) return\n void runSiteEdit(() =>\n installPlan(\n listing,\n target.hostIds\n ? target.hostIds.map((hostId) => ({\n scope: 'host' as const,\n hostId,\n }))\n : [{ scope: 'org' as const }],\n { intent: 'update', version: latestVersion },\n ),\n )\n }}\n >\n {'Update'}\n </Button>\n </DialogActions>\n </Dialog>\n\n {/* Add sites after the fact. The picker used to\n render only when nothing was installed, which\n froze the set at install time. */}\n {!orgInstall.orgWide &&\n orgInstall.availableHostIds.length ? (\n <Stack spacing={0.5}>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {'Add to more sites'}\n </Typography>\n <FormGroup\n sx={{ maxHeight: 200, overflowY: 'auto' }}\n >\n {orgInstall.availableHostIds.map((hostIdOption) => (\n <FormControlLabel\n key={hostIdOption}\n control={\n <Checkbox\n size=\"small\"\n checked={addHostIds.includes(\n hostIdOption,\n )}\n onChange={(event) =>\n setAddHostIds((current) =>\n event.target.checked\n ? [...current, hostIdOption]\n : current.filter(\n (id) => id !== hostIdOption,\n ),\n )\n }\n />\n }\n label={\n hosts.find(\n (host) => host.id === hostIdOption,\n )?.label ?? hostIdOption\n }\n slotProps={{\n typography: { variant: 'body2' },\n }}\n />\n ))}\n </FormGroup>\n <Box>\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n disabled={siteBusy || !addHostIds.length}\n onClick={() =>\n void runSiteEdit(() =>\n installPlan(\n listing,\n addHostIds.map((hostIdOption) => ({\n scope: 'host' as const,\n hostId: hostIdOption,\n })),\n ),\n )\n }\n >\n {'Add to selected'}\n </Button>\n </Box>\n </Stack>\n ) : null}\n\n <Divider />\n\n {/* Change the shape of the install, not just its\n membership: one org pin covers sites made\n later, which no set of host pins can. */}\n {orgInstall.orgWide ? (\n <>\n {/* The inverse of promote (AGL-1017), and the thing that makes\n per-site updates reachable at all: one org pin cannot be moved\n for one site, so a workspace that needs to stage a breaking\n version has to hold per-site pins first. Sites added later are\n no longer covered automatically — that is the trade, and the\n button says so rather than discovering it weeks later. */}\n <Button\n fullWidth\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n disabled={siteBusy || !hosts.length}\n onClick={() =>\n void runSiteEdit(async () => {\n // Pin every site BEFORE dropping the org pin, so a failure\n // part-way leaves the plugin installed everywhere it already\n // was rather than nowhere.\n await installPlan(\n listing,\n hosts.map((host) => ({ scope: 'host' as const, hostId: host.id })),\n )\n await uninstall(listing, 'org')\n })\n }\n >\n {'Split into per-site installs'}\n </Button>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Pins each site separately so you can update them one at a time. ' +\n 'Sites you add later will not get this plugin automatically.'}\n </Typography>\n <Button\n fullWidth\n size=\"small\"\n color=\"inherit\"\n disabled={siteBusy}\n onClick={() =>\n setPendingUninstall({\n scope: 'org',\n targets: resolveUninstallTargets(orgInstall, 'org'),\n })\n }\n >\n {'Uninstall org-wide'}\n </Button>\n </>\n ) : (\n <Button\n fullWidth\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n disabled={siteBusy}\n onClick={() =>\n void runSiteEdit(async () => {\n // Promote: add the org pin, then drop the\n // per-site pins it makes redundant. In\n // this order, so a failure part-way\n // leaves the plugin installed everywhere\n // it already was rather than nowhere.\n await installPlan(listing, [\n { scope: 'org' },\n ])\n for (const hostIdPinned of orgInstall.hostPinnedIds) {\n await uninstall(\n listing,\n 'host',\n hostIdPinned,\n )\n }\n })\n }\n >\n {'Install for the whole organization'}\n </Button>\n )}\n\n {/* Only the two DIRECT uninstalls are confirmed. The promote and split\n flows above also call `uninstall`, but there the removal is one half of\n a move that leaves the plugin installed everywhere it already was —\n confirming \"this will stop working\" would be false. */}\n <UninstallImpactDialog\n open={pendingUninstall !== null}\n listing={listing}\n scope={pendingUninstall?.scope ?? 'host'}\n targets={pendingUninstall?.targets ?? []}\n onCancel={() => setPendingUninstall(null)}\n onConfirm={async () => {\n const target = pendingUninstall\n setPendingUninstall(null)\n if (!target) return\n await runSiteEdit(() =>\n uninstall(listing, target.scope, target.hostId),\n )\n }}\n />\n </Stack>\n )\n}\nPluginSiteSet.displayName = 'PluginSiteSet'\n\nexport default PluginSiteSet\n"],"names":["MdiIcon","mdiCheckCircle","Alert","Box","Button","Checkbox","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Divider","FormControlLabel","FormGroup","Stack","Typography","useEffect","useMemo","useState","resolveUninstallTargets","PLUGIN_ORG_WIDE_UPDATE_NOTE","pluginSiteSetSummary","pluginUpdateAll","pluginUpdatePrompt","staleSites","staleSitesOf","UninstallImpactDialog","PluginSiteSet","props","listing","hosts","orgInstall","latestVersion","installPlan","uninstall","onChanged","addHostIds","setAddHostIds","pendingUninstall","setPendingUninstall","siteBusy","setSiteBusy","sites","updateAll","String","pending","setPending","changelog","setChangelog","updatePrompt","name","displayName","label","singleSite","Boolean","hostIds","length","$id","active","fetch","encodeURIComponent","then","response","ok","json","versions","payload","entry","find","version","catch","runSiteEdit","work","installedAnywhere","spacing","direction","sx","alignItems","path","color","fontSize","mt","variant","map","site","flex","minWidth","noWrap","shadowed","size","pinnedBy","some","stale","hostId","disabled","onClick","scope","targets","orgWide","severity","target","open","onClose","maxWidth","fullWidth","title","dividers","body","others","whiteSpace","note","intent","availableHostIds","maxHeight","overflowY","hostIdOption","control","checked","includes","onChange","event","current","filter","id","host","slotProps","typography","hostIdPinned","hostPinnedIds","onCancel","onConfirm"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/components/plugin-site-set.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { orgPermissionLabel } from '@aglyn/aglyn/app-utils/org-permissions'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { mdiCheckCircle } from '@aglyn/shared-data-mdi'\nimport {\n Alert,\n Box,\n Button,\n Checkbox,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Divider,\n FormControlLabel,\n FormGroup,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\n\nimport {\n resolveUninstallTargets,\n type OrgInstallSummary,\n type UninstallTarget,\n} from '../model/marketplace'\nimport {\n PLUGIN_ORG_WIDE_UPDATE_NOTE,\n pluginSiteSetSummary,\n pluginUpdateAll,\n pluginUpdatePrompt,\n staleSites as staleSitesOf,\n} from '../model/marketplace-catalog'\nimport UninstallImpactDialog from './uninstall-impact-dialog.component'\n\nexport interface PluginSiteSetProps {\n /** The listing/installation these pins belong to. */\n listing: any\n /** Every site in the org, in display order. */\n hosts: ReadonlyArray<{ id: string; label: string }>\n /** The resolved picture — see `resolveOrgInstallSummary`. */\n orgInstall: OrgInstallSummary\n /** Version the pins should be at; a site behind this can be updated. */\n latestVersion?: string | number | null\n /** Install the given concrete steps (org pin and/or named host pins). */\n installPlan: (\n listing: any,\n steps: ReadonlyArray<{ scope: 'org' | 'host'; hostId?: string }>,\n options?: { intent?: 'install' | 'update'; version?: string | number | null },\n ) => Promise<unknown>\n /** Drop a pin — org-wide, or from one named site. */\n uninstall: (\n listing: any,\n scope?: 'org' | 'host',\n hostId?: string,\n ) => Promise<unknown>\n /** Re-read the pins after a write; the caller owns that state. */\n onChanged: () => void\n}\n\n/**\n * The set of sites a plugin is installed on, and the controls to change it\n * (AGL-997), extracted from the listing page so the installation detail page\n * (AGL-1007) shows the same thing rather than a second implementation that\n * drifts.\n *\n * An org-scope install is a SET, not a boolean: an org pin covers every site\n * including ones created later, host pins name specific sites, and a host\n * pin on top of an org pin shadows it for that one site. All three facts\n * have to survive into the UI, which is why this takes the whole summary\n * rather than a scope.\n */\nexport function PluginSiteSet(props: PluginSiteSetProps) {\n const {\n listing,\n hosts,\n orgInstall,\n latestVersion,\n installPlan,\n uninstall,\n onChanged,\n } = props\n const [addHostIds, setAddHostIds] = useState<string[]>([])\n /**\n * The uninstall awaiting confirmation (AGL-1027). Uninstalling used to be a\n * bare click; nothing said which sites lost the plugin or which published\n * pages stopped rendering it.\n */\n const [pendingUninstall, setPendingUninstall] = useState<{\n scope: 'org' | 'host'\n hostId?: string\n targets: UninstallTarget[]\n } | null>(null)\n const [siteBusy, setSiteBusy] = useState(false)\n /**\n * A site is behind only when its pin is genuinely OLDER (AGL-1017). \"Not\n * equal\" also fires when a site runs something newer than the offer — a\n * publisher testing their own build, or a version withdrawn after install —\n * and offering to \"update\" those would quietly move them backwards.\n */\n const staleSites = useMemo(\n () => staleSitesOf(orgInstall.sites, latestVersion),\n [orgInstall.sites, latestVersion],\n )\n const updateAll = pluginUpdateAll(orgInstall, staleSites, String(latestVersion))\n /**\n * Update is per-site FIRST (AGL-1017). A version with breaking changes has\n * to be absorbable one site at a time — take it on a quiet site, see what\n * breaks, then decide about the rest — so each site behind the offer gets\n * its own control and the bulk action is the convenience on top.\n *\n * An org pin cannot do that: it is one pointer covering every site, so\n * moving it moves all of them at once. Rather than hide that behind a\n * button that looks per-site, the panel says so and points at the split.\n */\n const [pending, setPending] = useState<\n { hostIds: string[] | null; label: string } | null\n >(null)\n const [changelog, setChangelog] = useState<string | null>(null)\n const updatePrompt = pluginUpdatePrompt({\n name: listing?.displayName ?? 'This plugin',\n latestVersion: String(latestVersion),\n label: pending?.label ?? '',\n singleSite: Boolean(pending?.hostIds && pending.hostIds.length === 1),\n })\n useEffect(() => {\n if (!pending || !listing?.$id || !latestVersion) return\n let active = true\n setChangelog(null)\n void fetch(\n `/api/marketplace/listing-versions?listingId=${encodeURIComponent(\n String(listing.$id),\n )}`,\n )\n .then((response) => (response.ok ? response.json() : { versions: [] }))\n .then((payload) => {\n if (!active) return\n const entry = (payload?.versions ?? []).find(\n (version: { version?: string }) =>\n String(version?.version) === String(latestVersion),\n )\n setChangelog(entry?.changelog ?? '')\n })\n .catch(() => active && setChangelog(''))\n return () => {\n active = false\n }\n }, [pending, listing?.$id, latestVersion])\n const runSiteEdit = async (work: () => Promise<unknown>) => {\n setSiteBusy(true)\n try {\n await work()\n } finally {\n setSiteBusy(false)\n setAddHostIds([])\n onChanged()\n }\n }\n\n if (!orgInstall.installedAnywhere) return null\n\n return (\n <Stack spacing={1.5}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'flex-start' }}\n >\n <MdiIcon\n path={mdiCheckCircle.path}\n color=\"success\"\n sx={{ fontSize: 20, mt: '2px' }}\n />\n <Typography variant=\"body2\">\n {pluginSiteSetSummary(orgInstall, hosts.length)}\n </Typography>\n </Stack>\n\n {/* Name them. A count alone still leaves you\n guessing which sites are running the thing. */}\n <Stack spacing={0.5}>\n {orgInstall.sites.map((site) => (\n <Stack\n key={site.hostId}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Typography\n variant=\"body2\"\n sx={{ flex: 1, minWidth: 0 }}\n noWrap\n >\n {site.label}\n </Typography>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {`v${site.version ?? '—'}`}\n </Typography>\n {site.shadowed ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label=\"Overrides org\"\n />\n ) : null}\n {/* Pinned but not running (AGL-4047): the site's own switch has it\n off, which the realm join honors over any pin. */}\n {site.switchedOff ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n label=\"Switched off on this site\"\n />\n ) : null}\n {/* Removable only where the site owns its\n pin: dropping a site covered by the org\n pin would mean writing an exclusion the\n loader has no concept of. */}\n {/* This site alone (AGL-1017). Only where the site owns its pin —\n a site covered by the org pin has no pointer of its own to\n move, and pretending otherwise would update every site. */}\n {site.pinnedBy === 'host' &&\n staleSites.some((stale) => stale.hostId === site.hostId) ? (\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n disabled={siteBusy}\n onClick={() =>\n setPending({\n hostIds: [site.hostId],\n label: site.label,\n })\n }\n >\n {`Update to v${latestVersion}`}\n </Button>\n ) : null}\n {site.pinnedBy === 'host' ? (\n <Button\n size=\"small\"\n color=\"inherit\"\n disabled={siteBusy}\n onClick={() =>\n setPendingUninstall({\n scope: 'host',\n hostId: site.hostId,\n targets: resolveUninstallTargets(\n orgInstall,\n 'host',\n site.hostId,\n ),\n })\n }\n >\n {'Remove'}\n </Button>\n ) : null}\n </Stack>\n ))}\n </Stack>\n {orgInstall.sites.some((site) => site.switchedOff) ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'A site that switched it off turns it back on from its own ' +\n 'Admin › Plugins page, which takes ' +\n `“${orgPermissionLabel('site.manage')}” on that site.`}\n </Typography>\n ) : null}\n\n {staleSites.length ? (\n <Stack spacing={1}>\n {orgInstall.orgWide ? (\n // Said out loud rather than discovered afterwards (AGL-1017): an\n // org pin is a single pointer, so there is no half of it to move.\n // A workspace that wants to stage a breaking update has to be on\n // per-site pins, and the way there is the split below.\n <Alert severity=\"info\" variant=\"outlined\">\n {PLUGIN_ORG_WIDE_UPDATE_NOTE}\n </Alert>\n ) : null}\n <Box>\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n disabled={siteBusy}\n onClick={() =>\n setPending({ hostIds: updateAll.hostIds, label: updateAll.target })\n }\n >\n {updateAll.label}\n </Button>\n </Box>\n </Stack>\n ) : null}\n\n {/* What changes, before it changes (AGL-1017). Third-party code moving\n in a workspace deserves more than a spinner: the version pair, the\n publisher's changelog, and which sites are about to take it. */}\n <Dialog\n open={Boolean(pending)}\n onClose={() => setPending(null)}\n maxWidth=\"sm\"\n fullWidth\n >\n <DialogTitle>{updatePrompt.title}</DialogTitle>\n <DialogContent dividers>\n <Stack spacing={1.5}>\n <Typography variant=\"body2\">{updatePrompt.body}</Typography>\n {updatePrompt.others ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {updatePrompt.others}\n </Typography>\n ) : null}\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{\"What's new\"}</Typography>\n {changelog === null ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading the changelog…'}\n </Typography>\n ) : changelog ? (\n <Typography variant=\"body2\" sx={{ whiteSpace: 'pre-wrap' }}>\n {changelog}\n </Typography>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The publisher did not describe this version.'}\n </Typography>\n )}\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {updatePrompt.note}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button color=\"inherit\" onClick={() => setPending(null)}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n disabled={siteBusy}\n onClick={() => {\n const target = pending\n setPending(null)\n if (!target) return\n void runSiteEdit(() =>\n installPlan(\n listing,\n target.hostIds\n ? target.hostIds.map((hostId) => ({\n scope: 'host' as const,\n hostId,\n }))\n : [{ scope: 'org' as const }],\n { intent: 'update', version: latestVersion },\n ),\n )\n }}\n >\n {'Update'}\n </Button>\n </DialogActions>\n </Dialog>\n\n {/* Add sites after the fact. The picker used to\n render only when nothing was installed, which\n froze the set at install time. */}\n {!orgInstall.orgWide &&\n orgInstall.availableHostIds.length ? (\n <Stack spacing={0.5}>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {'Add to more sites'}\n </Typography>\n <FormGroup\n sx={{ maxHeight: 200, overflowY: 'auto' }}\n >\n {orgInstall.availableHostIds.map((hostIdOption) => (\n <FormControlLabel\n key={hostIdOption}\n control={\n <Checkbox\n size=\"small\"\n checked={addHostIds.includes(\n hostIdOption,\n )}\n onChange={(event) =>\n setAddHostIds((current) =>\n event.target.checked\n ? [...current, hostIdOption]\n : current.filter(\n (id) => id !== hostIdOption,\n ),\n )\n }\n />\n }\n label={\n hosts.find(\n (host) => host.id === hostIdOption,\n )?.label ?? hostIdOption\n }\n slotProps={{\n typography: { variant: 'body2' },\n }}\n />\n ))}\n </FormGroup>\n <Box>\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n disabled={siteBusy || !addHostIds.length}\n onClick={() =>\n void runSiteEdit(() =>\n installPlan(\n listing,\n addHostIds.map((hostIdOption) => ({\n scope: 'host' as const,\n hostId: hostIdOption,\n })),\n ),\n )\n }\n >\n {'Add to selected'}\n </Button>\n </Box>\n </Stack>\n ) : null}\n\n <Divider />\n\n {/* Change the shape of the install, not just its\n membership: one org pin covers sites made\n later, which no set of host pins can. */}\n {orgInstall.orgWide ? (\n <>\n {/* The inverse of promote (AGL-1017), and the thing that makes\n per-site updates reachable at all: one org pin cannot be moved\n for one site, so a workspace that needs to stage a breaking\n version has to hold per-site pins first. Sites added later are\n no longer covered automatically — that is the trade, and the\n button says so rather than discovering it weeks later. */}\n <Button\n fullWidth\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n disabled={siteBusy || !hosts.length}\n onClick={() =>\n void runSiteEdit(async () => {\n // Pin every site BEFORE dropping the org pin, so a failure\n // part-way leaves the plugin installed everywhere it already\n // was rather than nowhere.\n await installPlan(\n listing,\n hosts.map((host) => ({ scope: 'host' as const, hostId: host.id })),\n )\n await uninstall(listing, 'org')\n })\n }\n >\n {'Split into per-site installs'}\n </Button>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Pins each site separately so you can update them one at a time. ' +\n 'Sites you add later will not get this plugin automatically.'}\n </Typography>\n <Button\n fullWidth\n size=\"small\"\n color=\"inherit\"\n disabled={siteBusy}\n onClick={() =>\n setPendingUninstall({\n scope: 'org',\n targets: resolveUninstallTargets(orgInstall, 'org'),\n })\n }\n >\n {'Uninstall org-wide'}\n </Button>\n </>\n ) : (\n <Button\n fullWidth\n size=\"small\"\n variant=\"outlined\"\n color=\"primary\"\n disabled={siteBusy}\n onClick={() =>\n void runSiteEdit(async () => {\n // Promote: add the org pin, then drop the\n // per-site pins it makes redundant. In\n // this order, so a failure part-way\n // leaves the plugin installed everywhere\n // it already was rather than nowhere.\n await installPlan(listing, [\n { scope: 'org' },\n ])\n for (const hostIdPinned of orgInstall.hostPinnedIds) {\n await uninstall(\n listing,\n 'host',\n hostIdPinned,\n )\n }\n })\n }\n >\n {'Install for the whole organization'}\n </Button>\n )}\n\n {/* Only the two DIRECT uninstalls are confirmed. The promote and split\n flows above also call `uninstall`, but there the removal is one half of\n a move that leaves the plugin installed everywhere it already was —\n confirming \"this will stop working\" would be false. */}\n <UninstallImpactDialog\n open={pendingUninstall !== null}\n listing={listing}\n scope={pendingUninstall?.scope ?? 'host'}\n targets={pendingUninstall?.targets ?? []}\n onCancel={() => setPendingUninstall(null)}\n onConfirm={async () => {\n const target = pendingUninstall\n setPendingUninstall(null)\n if (!target) return\n await runSiteEdit(() =>\n uninstall(listing, target.scope, target.hostId),\n )\n }}\n />\n </Stack>\n )\n}\nPluginSiteSet.displayName = 'PluginSiteSet'\n\nexport default PluginSiteSet\n"],"names":["orgPermissionLabel","MdiIcon","mdiCheckCircle","Alert","Box","Button","Checkbox","Chip","Dialog","DialogActions","DialogContent","DialogTitle","Divider","FormControlLabel","FormGroup","Stack","Typography","useEffect","useMemo","useState","resolveUninstallTargets","PLUGIN_ORG_WIDE_UPDATE_NOTE","pluginSiteSetSummary","pluginUpdateAll","pluginUpdatePrompt","staleSites","staleSitesOf","UninstallImpactDialog","PluginSiteSet","props","listing","hosts","orgInstall","latestVersion","installPlan","uninstall","onChanged","addHostIds","setAddHostIds","pendingUninstall","setPendingUninstall","siteBusy","setSiteBusy","sites","updateAll","String","pending","setPending","changelog","setChangelog","updatePrompt","name","displayName","label","singleSite","Boolean","hostIds","length","$id","active","fetch","encodeURIComponent","then","response","ok","json","versions","payload","entry","find","version","catch","runSiteEdit","work","installedAnywhere","spacing","direction","sx","alignItems","path","color","fontSize","mt","variant","map","site","flex","minWidth","noWrap","shadowed","size","switchedOff","pinnedBy","some","stale","hostId","disabled","onClick","scope","targets","orgWide","severity","target","open","onClose","maxWidth","fullWidth","title","dividers","body","others","whiteSpace","note","intent","availableHostIds","maxHeight","overflowY","hostIdOption","control","checked","includes","onChange","event","current","filter","id","host","slotProps","typography","hostIdPinned","hostPinnedIds","onCancel","onConfirm"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,kBAAkB,QAAQ,yCAAwC;AAC3E,SAASC,OAAO,QAAQ,uBAAsB;AAC9C,SAASC,cAAc,QAAQ,yBAAwB;AACvD,SACEC,KAAK,EACLC,GAAG,EACHC,MAAM,EACNC,QAAQ,EACRC,IAAI,EACJC,MAAM,EACNC,aAAa,EACbC,aAAa,EACbC,WAAW,EACXC,OAAO,EACPC,gBAAgB,EAChBC,SAAS,EACTC,KAAK,EACLC,UAAU,QACL,gBAAe;AACtB,SAASC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AAEpD,SACEC,uBAAuB,QAGlB,0BAAsB;AAC7B,SACEC,2BAA2B,EAC3BC,oBAAoB,EACpBC,eAAe,EACfC,kBAAkB,EAClBC,cAAcC,YAAY,QACrB,kCAA8B;AACrC,OAAOC,2BAA2B,yCAAqC;AA2BvE;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,cAAcC,KAAyB;;IACrD,MAAM,EACJC,OAAO,EACPC,KAAK,EACLC,UAAU,EACVC,aAAa,EACbC,WAAW,EACXC,SAAS,EACTC,SAAS,EACV,GAAGP;IACJ,MAAM,CAACQ,YAAYC,cAAc,GAAGnB,SAAmB,EAAE;IACzD;;;;GAIC,GACD,MAAM,CAACoB,kBAAkBC,oBAAoB,GAAGrB,SAItC;IACV,MAAM,CAACsB,UAAUC,YAAY,GAAGvB,SAAS;IACzC;;;;;GAKC,GACD,MAAMM,aAAaP,QACjB,IAAMQ,aAAaM,WAAWW,KAAK,EAAEV,gBACrC;QAACD,WAAWW,KAAK;QAAEV;KAAc;IAEnC,MAAMW,YAAYrB,gBAAgBS,YAAYP,YAAYoB,OAAOZ;IACjE;;;;;;;;;GASC,GACD,MAAM,CAACa,SAASC,WAAW,GAAG5B,SAE5B;IACF,MAAM,CAAC6B,WAAWC,aAAa,GAAG9B,SAAwB;IAC1D,MAAM+B,eAAe1B,mBAAmB;QACtC2B,IAAI,UAAErB,2BAAAA,QAASsB,WAAW,mBAAI;QAC9BnB,eAAeY,OAAOZ;QACtBoB,KAAK,WAAEP,2BAAAA,QAASO,KAAK,oBAAI;QACzBC,YAAYC,QAAQT,CAAAA,2BAAAA,QAASU,OAAO,KAAIV,QAAQU,OAAO,CAACC,MAAM,KAAK;IACrE;IACAxC,UAAU;QACR,IAAI,CAAC6B,WAAW,EAAChB,2BAAAA,QAAS4B,GAAG,KAAI,CAACzB,eAAe;QACjD,IAAI0B,SAAS;QACbV,aAAa;QACb,KAAKW,MACH,CAAC,4CAA4C,EAAEC,mBAC7ChB,OAAOf,QAAQ4B,GAAG,IACjB,EAEFI,IAAI,CAAC,CAACC,WAAcA,SAASC,EAAE,GAAGD,SAASE,IAAI,KAAK;gBAAEC,UAAU,EAAE;YAAC,GACnEJ,IAAI,CAAC,CAACK;;YACL,IAAI,CAACR,QAAQ;YACb,MAAMS,QAAQ,SAACD,2BAAAA,QAASD,QAAQ,mBAAI,EAAE,EAAEG,IAAI,CAC1C,CAACC,UACCzB,OAAOyB,2BAAAA,QAASA,OAAO,MAAMzB,OAAOZ;YAExCgB,sBAAamB,yBAAAA,MAAOpB,SAAS,oBAAI;QACnC,GACCuB,KAAK,CAAC,IAAMZ,UAAUV,aAAa;QACtC,OAAO;YACLU,SAAS;QACX;IACF,GAAG;QAACb;QAAShB,2BAAAA,QAAS4B,GAAG;QAAEzB;KAAc;IACzC,MAAMuC,cAAc,OAAOC;QACzB/B,YAAY;QACZ,IAAI;YACF,MAAM+B;QACR,SAAU;YACR/B,YAAY;YACZJ,cAAc,EAAE;YAChBF;QACF;IACF;IAEA,IAAI,CAACJ,WAAW0C,iBAAiB,EAAE,OAAO;IAE1C,qBACA,MAAC3D;QAAM4D,SAAS;;0BACd,MAAC5D;gBACC6D,WAAU;gBACVD,SAAS;gBACTE,IAAI;oBAAEC,YAAY;gBAAa;;kCAE/B,KAAC7E;wBACC8E,MAAM7E,eAAe6E,IAAI;wBACzBC,OAAM;wBACNH,IAAI;4BAAEI,UAAU;4BAAIC,IAAI;wBAAM;;kCAEhC,KAAClE;wBAAWmE,SAAQ;kCACjB7D,qBAAqBU,YAAYD,MAAM0B,MAAM;;;;0BAMlD,KAAC1C;gBAAM4D,SAAS;0BACb3C,WAAWW,KAAK,CAACyC,GAAG,CAAC,CAACC;wBAkBZA;yCAjBT,MAACtE;wBAEC6D,WAAU;wBACVD,SAAS;wBACTE,IAAI;4BAAEC,YAAY;wBAAS;;0CAE3B,KAAC9D;gCACCmE,SAAQ;gCACRN,IAAI;oCAAES,MAAM;oCAAGC,UAAU;gCAAE;gCAC3BC,MAAM;0CAELH,KAAKhC,KAAK;;0CAEb,KAACrC;gCACCmE,SAAQ;gCACRH,OAAM;0CAEL,CAAC,CAAC,GAAEK,gBAAAA,KAAKf,OAAO,YAAZe,gBAAgB,KAAK;;4BAE3BA,KAAKI,QAAQ,iBACZ,KAAClF;gCACCmF,MAAK;gCACLP,SAAQ;gCACR9B,OAAM;iCAEN;4BAGHgC,KAAKM,WAAW,iBACf,KAACpF;gCACCmF,MAAK;gCACLP,SAAQ;gCACR9B,OAAM;iCAEN;4BAQHgC,KAAKO,QAAQ,KAAK,UACnBnE,WAAWoE,IAAI,CAAC,CAACC,QAAUA,MAAMC,MAAM,KAAKV,KAAKU,MAAM,kBACrD,KAAC1F;gCACCqF,MAAK;gCACLP,SAAQ;gCACRH,OAAM;gCACNgB,UAAUvD;gCACVwD,SAAS,IACPlD,WAAW;wCACTS,SAAS;4CAAC6B,KAAKU,MAAM;yCAAC;wCACtB1C,OAAOgC,KAAKhC,KAAK;oCACnB;0CAGD,CAAC,WAAW,EAAEpB,eAAe;iCAE9B;4BACHoD,KAAKO,QAAQ,KAAK,uBACjB,KAACvF;gCACCqF,MAAK;gCACLV,OAAM;gCACNgB,UAAUvD;gCACVwD,SAAS,IACPzD,oBAAoB;wCAClB0D,OAAO;wCACPH,QAAQV,KAAKU,MAAM;wCACnBI,SAAS/E,wBACPY,YACA,QACAqD,KAAKU,MAAM;oCAEf;0CAGD;iCAED;;uBA7ECV,KAAKU,MAAM;;;YAiFrB/D,WAAWW,KAAK,CAACkD,IAAI,CAAC,CAACR,OAASA,KAAKM,WAAW,kBAC/C,KAAC3E;gBAAWmE,SAAQ;gBAAUH,OAAM;0BACjC,+DACC,uCACA,CAAC,CAAC,EAAEhF,mBAAmB,eAAe,eAAe,CAAC;iBAExD;YAEHyB,WAAWgC,MAAM,iBAChB,MAAC1C;gBAAM4D,SAAS;;oBACb3C,WAAWoE,OAAO,GACjB,iEAAiE;oBACjE,kEAAkE;oBAClE,iEAAiE;oBACjE,uDAAuD;kCACvD,KAACjG;wBAAMkG,UAAS;wBAAOlB,SAAQ;kCAC5B9D;yBAED;kCACJ,KAACjB;kCACC,cAAA,KAACC;4BACCqF,MAAK;4BACLP,SAAQ;4BACRH,OAAM;4BACNgB,UAAUvD;4BACVwD,SAAS,IACPlD,WAAW;oCAAES,SAASZ,UAAUY,OAAO;oCAAEH,OAAOT,UAAU0D,MAAM;gCAAC;sCAGlE1D,UAAUS,KAAK;;;;iBAIpB;0BAKJ,MAAC7C;gBACC+F,MAAMhD,QAAQT;gBACd0D,SAAS,IAAMzD,WAAW;gBAC1B0D,UAAS;gBACTC,SAAS;;kCAET,KAAC/F;kCAAauC,aAAayD,KAAK;;kCAChC,KAACjG;wBAAckG,QAAQ;kCACrB,cAAA,MAAC7F;4BAAM4D,SAAS;;8CACd,KAAC3D;oCAAWmE,SAAQ;8CAASjC,aAAa2D,IAAI;;gCAC7C3D,aAAa4D,MAAM,iBAClB,KAAC9F;oCAAWmE,SAAQ;oCAAQH,OAAM;8CAC/B9B,aAAa4D,MAAM;qCAEpB;8CACJ,MAAC/F;oCAAM4D,SAAS;;sDACd,KAAC3D;4CAAWmE,SAAQ;sDAAa;;wCAChCnC,cAAc,qBACb,KAAChC;4CAAWmE,SAAQ;4CAAQH,OAAM;sDAC/B;6CAEDhC,0BACF,KAAChC;4CAAWmE,SAAQ;4CAAQN,IAAI;gDAAEkC,YAAY;4CAAW;sDACtD/D;2DAGH,KAAChC;4CAAWmE,SAAQ;4CAAQH,OAAM;sDAC/B;;;;8CAIP,KAAChE;oCAAWmE,SAAQ;oCAAUH,OAAM;8CACjC9B,aAAa8D,IAAI;;;;;kCAIxB,MAACvG;;0CACC,KAACJ;gCAAO2E,OAAM;gCAAUiB,SAAS,IAAMlD,WAAW;0CAC/C;;0CAEH,KAAC1C;gCACC8E,SAAQ;gCACRa,UAAUvD;gCACVwD,SAAS;oCACP,MAAMK,SAASxD;oCACfC,WAAW;oCACX,IAAI,CAACuD,QAAQ;oCACb,KAAK9B,YAAY,IACftC,YACEJ,SACAwE,OAAO9C,OAAO,GACV8C,OAAO9C,OAAO,CAAC4B,GAAG,CAAC,CAACW,SAAY,CAAA;gDAC9BG,OAAO;gDACPH;4CACF,CAAA,KACA;4CAAC;gDAAEG,OAAO;4CAAe;yCAAE,EAC/B;4CAAEe,QAAQ;4CAAU3C,SAASrC;wCAAc;gCAGjD;0CAEC;;;;;;YAQN,CAACD,WAAWoE,OAAO,IACpBpE,WAAWkF,gBAAgB,CAACzD,MAAM,iBAChC,MAAC1C;gBAAM4D,SAAS;;kCACd,KAAC3D;wBACCmE,SAAQ;wBACRH,OAAM;kCAEL;;kCAEH,KAAClE;wBACC+D,IAAI;4BAAEsC,WAAW;4BAAKC,WAAW;wBAAO;kCAEvCpF,WAAWkF,gBAAgB,CAAC9B,GAAG,CAAC,CAACiC;;gCAqB5BtF;iDApBJ,KAAClB;gCAECyG,uBACE,KAAChH;oCACCoF,MAAK;oCACL6B,SAASlF,WAAWmF,QAAQ,CAC1BH;oCAEFI,UAAU,CAACC,QACTpF,cAAc,CAACqF,UACbD,MAAMpB,MAAM,CAACiB,OAAO,GAChB;mDAAII;gDAASN;6CAAa,GAC1BM,QAAQC,MAAM,CACZ,CAACC,KAAOA,OAAOR;;gCAM7BhE,KAAK,WACHtB,cAAAA,MAAMsC,IAAI,CACR,CAACyD,OAASA,KAAKD,EAAE,KAAKR,kCADxBtF,YAEGsB,KAAK,mBAAIgE;gCAEdU,WAAW;oCACTC,YAAY;wCAAE7C,SAAS;oCAAQ;gCACjC;+BAzBKkC;;;kCA6BX,KAACjH;kCACC,cAAA,KAACC;4BACCqF,MAAK;4BACLP,SAAQ;4BACRH,OAAM;4BACNgB,UAAUvD,YAAY,CAACJ,WAAWoB,MAAM;4BACxCwC,SAAS,IACP,KAAKzB,YAAY,IACftC,YACEJ,SACAO,WAAW+C,GAAG,CAAC,CAACiC,eAAkB,CAAA;4CAChCnB,OAAO;4CACPH,QAAQsB;wCACV,CAAA;sCAKL;;;;iBAIL;0BAEJ,KAACzG;YAKAoB,WAAWoE,OAAO,iBACjB;;kCAOE,KAAC/F;wBACCqG,SAAS;wBACThB,MAAK;wBACLP,SAAQ;wBACRH,OAAM;wBACNgB,UAAUvD,YAAY,CAACV,MAAM0B,MAAM;wBACnCwC,SAAS,IACP,KAAKzB,YAAY;gCACf,2DAA2D;gCAC3D,6DAA6D;gCAC7D,2BAA2B;gCAC3B,MAAMtC,YACJJ,SACAC,MAAMqD,GAAG,CAAC,CAAC0C,OAAU,CAAA;wCAAE5B,OAAO;wCAAiBH,QAAQ+B,KAAKD,EAAE;oCAAC,CAAA;gCAEjE,MAAM1F,UAAUL,SAAS;4BAC3B;kCAGD;;kCAEH,KAACd;wBAAWmE,SAAQ;wBAAUH,OAAM;kCACjC,qEACC;;kCAEJ,KAAC3E;wBACCqG,SAAS;wBACThB,MAAK;wBACLV,OAAM;wBACNgB,UAAUvD;wBACVwD,SAAS,IACPzD,oBAAoB;gCAClB0D,OAAO;gCACPC,SAAS/E,wBAAwBY,YAAY;4BAC/C;kCAGD;;;+BAIL,KAAC3B;gBACCqG,SAAS;gBACThB,MAAK;gBACLP,SAAQ;gBACRH,OAAM;gBACNgB,UAAUvD;gBACVwD,SAAS,IACP,KAAKzB,YAAY;wBACf,0CAA0C;wBAC1C,uCAAuC;wBACvC,oCAAoC;wBACpC,yCAAyC;wBACzC,sCAAsC;wBACtC,MAAMtC,YAAYJ,SAAS;4BACzB;gCAAEoE,OAAO;4BAAM;yBAChB;wBACD,KAAK,MAAM+B,gBAAgBjG,WAAWkG,aAAa,CAAE;4BACnD,MAAM/F,UACJL,SACA,QACAmG;wBAEJ;oBACF;0BAGD;;0BAQL,KAACtG;gBACC4E,MAAMhE,qBAAqB;gBAC3BT,SAASA;gBACToE,KAAK,WAAE3D,oCAAAA,iBAAkB2D,KAAK,oBAAI;gBAClCC,OAAO,WAAE5D,oCAAAA,iBAAkB4D,OAAO,oBAAI,EAAE;gBACxCgC,UAAU,IAAM3F,oBAAoB;gBACpC4F,WAAW;oBACT,MAAM9B,SAAS/D;oBACfC,oBAAoB;oBACpB,IAAI,CAAC8D,QAAQ;oBACb,MAAM9B,YAAY,IAChBrC,UAAUL,SAASwE,OAAOJ,KAAK,EAAEI,OAAOP,MAAM;gBAElD;;;;AAIN;AACAnE,cAAcwB,WAAW,GAAG;AAE5B,eAAexB,cAAa"}
|
|
@@ -6,3 +6,9 @@
|
|
|
6
6
|
* you may not use this file except in compliance with the License.
|
|
7
7
|
*/
|
|
8
8
|
export declare const BUNDLE_ID = "marketplace";
|
|
9
|
+
/**
|
|
10
|
+
* The repeat source a plugin connection's synced record answers under
|
|
11
|
+
* (AGL-4212): the editor's `RepeatSource` id and the server reader's, which
|
|
12
|
+
* `plugins.config.json` declares as this plugin's `connectionRepeatSource`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const CONNECTION_REPEAT_SOURCE_ID = "pluginConnection";
|
|
@@ -5,5 +5,10 @@
|
|
|
5
5
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
6
|
* you may not use this file except in compliance with the License.
|
|
7
7
|
*/ export const BUNDLE_ID = 'marketplace';
|
|
8
|
+
/**
|
|
9
|
+
* The repeat source a plugin connection's synced record answers under
|
|
10
|
+
* (AGL-4212): the editor's `RepeatSource` id and the server reader's, which
|
|
11
|
+
* `plugins.config.json` declares as this plugin's `connectionRepeatSource`.
|
|
12
|
+
*/ export const CONNECTION_REPEAT_SOURCE_ID = 'pluginConnection';
|
|
8
13
|
|
|
9
14
|
//# sourceMappingURL=bundle-common.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/constants/bundle-common.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n */\n\nexport const BUNDLE_ID = 'marketplace'\n"],"names":["BUNDLE_ID"],"mappings":"AAAA;;;;;;CAMC,GAED,OAAO,MAAMA,YAAY,cAAa"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/constants/bundle-common.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n */\n\nexport const BUNDLE_ID = 'marketplace'\n\n/**\n * The repeat source a plugin connection's synced record answers under\n * (AGL-4212): the editor's `RepeatSource` id and the server reader's, which\n * `plugins.config.json` declares as this plugin's `connectionRepeatSource`.\n */\nexport const CONNECTION_REPEAT_SOURCE_ID = 'pluginConnection'\n"],"names":["BUNDLE_ID","CONNECTION_REPEAT_SOURCE_ID"],"mappings":"AAAA;;;;;;CAMC,GAED,OAAO,MAAMA,YAAY,cAAa;AAEtC;;;;CAIC,GACD,OAAO,MAAMC,8BAA8B,mBAAkB"}
|
|
@@ -24,7 +24,8 @@ import { registerPluginSiteResourceEraser } from "@aglyn/aglyn/plugin-manager/pl
|
|
|
24
24
|
import { registerPluginSiteCspSource } from "@aglyn/aglyn/plugin-manager/plugin-site-csp";
|
|
25
25
|
import { registerPluginSiteStarterSource } from "@aglyn/aglyn/plugin-manager/plugin-site-starters";
|
|
26
26
|
import { registerSitePageEnricherService } from "@aglyn/aglyn/plugin-manager/site-page-enricher-services";
|
|
27
|
-
import {
|
|
27
|
+
import { registerRepeatRowReader } from "@aglyn/aglyn/plugin-manager/repeat-rows";
|
|
28
|
+
import { BUNDLE_ID, CONNECTION_REPEAT_SOURCE_ID } from "./constants/bundle-common.js";
|
|
28
29
|
import { RATING_FIELD } from "./model/rating-field.js";
|
|
29
30
|
/**
|
|
30
31
|
* The marketplace plugin's server declarations: the light registrations core
|
|
@@ -125,12 +126,17 @@ import { RATING_FIELD } from "./model/rating-field.js";
|
|
|
125
126
|
* - the variable computer gives each record's declared summary fields to
|
|
126
127
|
* the page as `{{var:…}}` values;
|
|
127
128
|
* - the CSP source admits the image hosts the site's signed plugins
|
|
128
|
-
* declare, on those sites only
|
|
129
|
+
* declare, on those sites only;
|
|
130
|
+
* - the repeat-rows reader answers a page's repeats over a connection's
|
|
131
|
+
* record (`repeatConnection`, AGL-4212), the `connection:` keys the
|
|
132
|
+
* platform routes to the source this plugin declares.
|
|
129
133
|
*
|
|
130
|
-
*
|
|
131
|
-
* and the page route share (a module-scope list filled at
|
|
132
|
-
* the route, AGL-3947), and
|
|
133
|
-
*
|
|
134
|
+
* The first three go through the plugin-service registry, which
|
|
135
|
+
* `instrumentation.ts` and the page route share (a module-scope list filled at
|
|
136
|
+
* boot is empty in the route, AGL-3947), and the reader through the
|
|
137
|
+
* repeat-rows table, which is per process for the same reason. Each names
|
|
138
|
+
* this plugin as its owner, so the page's "who contributed" narrowing still
|
|
139
|
+
* holds. Registering again replaces.
|
|
134
140
|
*/ function registerPluginConnectionsSite() {
|
|
135
141
|
registerSitePageEnricherService(async (context)=>(await siteRecords()).connectionRecordsEnricher(context), {
|
|
136
142
|
pluginId: BUNDLE_ID,
|
|
@@ -146,6 +152,9 @@ import { RATING_FIELD } from "./model/rating-field.js";
|
|
|
146
152
|
}, {
|
|
147
153
|
pluginId: BUNDLE_ID
|
|
148
154
|
});
|
|
155
|
+
registerRepeatRowReader(CONNECTION_REPEAT_SOURCE_ID, async (request)=>(await siteRecords()).connectionRepeatRowReader(request), {
|
|
156
|
+
pluginId: BUNDLE_ID
|
|
157
|
+
});
|
|
149
158
|
}
|
|
150
159
|
|
|
151
160
|
//# sourceMappingURL=declarations.server.js.map
|