@aglyn/plugins-marketplace 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +7 -0
- package/package.json +51 -0
- package/src/index.d.ts +19 -0
- package/src/index.js +20 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/artifact-update-dialog.component.d.ts +27 -0
- package/src/lib/components/artifact-update-dialog.component.js +244 -0
- package/src/lib/components/artifact-update-dialog.component.js.map +1 -0
- package/src/lib/components/host-plugins-card.component.d.ts +23 -0
- package/src/lib/components/host-plugins-card.component.js +566 -0
- package/src/lib/components/host-plugins-card.component.js.map +1 -0
- package/src/lib/components/listing-content.component.d.ts +57 -0
- package/src/lib/components/listing-content.component.js +1880 -0
- package/src/lib/components/listing-content.component.js.map +1 -0
- package/src/lib/components/listing-image.component.d.ts +46 -0
- package/src/lib/components/listing-image.component.js +38 -0
- package/src/lib/components/listing-image.component.js.map +1 -0
- package/src/lib/components/listing-reviews.component.d.ts +22 -0
- package/src/lib/components/listing-reviews.component.js +419 -0
- package/src/lib/components/listing-reviews.component.js.map +1 -0
- package/src/lib/components/marketplace-browse.component.d.ts +38 -0
- package/src/lib/components/marketplace-browse.component.js +802 -0
- package/src/lib/components/marketplace-browse.component.js.map +1 -0
- package/src/lib/components/plugin-site-set-panel.component.d.ts +24 -0
- package/src/lib/components/plugin-site-set-panel.component.js +137 -0
- package/src/lib/components/plugin-site-set-panel.component.js.map +1 -0
- package/src/lib/components/plugin-site-set.component.d.ts +43 -0
- package/src/lib/components/plugin-site-set.component.js +425 -0
- package/src/lib/components/plugin-site-set.component.js.map +1 -0
- package/src/lib/components/rating-input.component.d.ts +3 -0
- package/src/lib/components/rating-input.component.js +47 -0
- package/src/lib/components/rating-input.component.js.map +1 -0
- package/src/lib/components/report-target.component.d.ts +36 -0
- package/src/lib/components/report-target.component.js +181 -0
- package/src/lib/components/report-target.component.js.map +1 -0
- package/src/lib/components/uninstall-impact-dialog.component.d.ts +40 -0
- package/src/lib/components/uninstall-impact-dialog.component.js +244 -0
- package/src/lib/components/uninstall-impact-dialog.component.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +8 -0
- package/src/lib/constants/bundle-common.js +9 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/hooks/use-artifact-update.d.ts +41 -0
- package/src/lib/hooks/use-artifact-update.js +169 -0
- package/src/lib/hooks/use-artifact-update.js.map +1 -0
- package/src/lib/hooks/use-marketplace-actions.d.ts +37 -0
- package/src/lib/hooks/use-marketplace-actions.js +523 -0
- package/src/lib/hooks/use-marketplace-actions.js.map +1 -0
- package/src/lib/model/artifact-merge.d.ts +154 -0
- package/src/lib/model/artifact-merge.js +228 -0
- package/src/lib/model/artifact-merge.js.map +1 -0
- package/src/lib/model/index.d.ts +43 -0
- package/src/lib/model/index.js +41 -0
- package/src/lib/model/index.js.map +1 -0
- package/src/lib/model/marketplace-props.d.ts +57 -0
- package/src/lib/model/marketplace-props.js +441 -0
- package/src/lib/model/marketplace-props.js.map +1 -0
- package/src/lib/model/marketplace.d.ts +833 -0
- package/src/lib/model/marketplace.js +929 -0
- package/src/lib/model/marketplace.js.map +1 -0
- package/src/lib/model/rating-field.d.ts +24 -0
- package/src/lib/model/rating-field.js +33 -0
- package/src/lib/model/rating-field.js.map +1 -0
- package/src/lib/plugin.d.ts +33 -0
- package/src/lib/plugin.js +86 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/server/billing-webhook.d.ts +25 -0
- package/src/lib/server/billing-webhook.js +1062 -0
- package/src/lib/server/billing-webhook.js.map +1 -0
- package/src/lib/server/checkout.d.ts +76 -0
- package/src/lib/server/checkout.js +477 -0
- package/src/lib/server/checkout.js.map +1 -0
- package/src/lib/server/connect.d.ts +24 -0
- package/src/lib/server/connect.js +205 -0
- package/src/lib/server/connect.js.map +1 -0
- package/src/lib/server/install-dataset-schema.d.ts +32 -0
- package/src/lib/server/install-dataset-schema.js +255 -0
- package/src/lib/server/install-dataset-schema.js.map +1 -0
- package/src/lib/server/install-email-starter.d.ts +65 -0
- package/src/lib/server/install-email-starter.js +279 -0
- package/src/lib/server/install-email-starter.js.map +1 -0
- package/src/lib/server/install-email-template.d.ts +34 -0
- package/src/lib/server/install-email-template.js +252 -0
- package/src/lib/server/install-email-template.js.map +1 -0
- package/src/lib/server/install-layout.d.ts +29 -0
- package/src/lib/server/install-layout.js +323 -0
- package/src/lib/server/install-layout.js.map +1 -0
- package/src/lib/server/install-pin-counts.d.ts +173 -0
- package/src/lib/server/install-pin-counts.js +248 -0
- package/src/lib/server/install-pin-counts.js.map +1 -0
- package/src/lib/server/install-plugin.d.ts +18 -0
- package/src/lib/server/install-plugin.js +405 -0
- package/src/lib/server/install-plugin.js.map +1 -0
- package/src/lib/server/install-template.d.ts +37 -0
- package/src/lib/server/install-template.js +348 -0
- package/src/lib/server/install-template.js.map +1 -0
- package/src/lib/server/install-theme.d.ts +42 -0
- package/src/lib/server/install-theme.js +313 -0
- package/src/lib/server/install-theme.js.map +1 -0
- package/src/lib/server/install.d.ts +27 -0
- package/src/lib/server/install.js +276 -0
- package/src/lib/server/install.js.map +1 -0
- package/src/lib/server/listing-versions.d.ts +26 -0
- package/src/lib/server/listing-versions.js +378 -0
- package/src/lib/server/listing-versions.js.map +1 -0
- package/src/lib/server/preview-image.d.ts +37 -0
- package/src/lib/server/preview-image.js +216 -0
- package/src/lib/server/preview-image.js.map +1 -0
- package/src/lib/server/provenance.d.ts +100 -0
- package/src/lib/server/provenance.js +118 -0
- package/src/lib/server/provenance.js.map +1 -0
- package/src/lib/server/publish-dataset-schema.d.ts +31 -0
- package/src/lib/server/publish-dataset-schema.js +202 -0
- package/src/lib/server/publish-dataset-schema.js.map +1 -0
- package/src/lib/server/publish-email-starter.d.ts +44 -0
- package/src/lib/server/publish-email-starter.js +231 -0
- package/src/lib/server/publish-email-starter.js.map +1 -0
- package/src/lib/server/publish-email-template.d.ts +33 -0
- package/src/lib/server/publish-email-template.js +211 -0
- package/src/lib/server/publish-email-template.js.map +1 -0
- package/src/lib/server/publish-layout.d.ts +33 -0
- package/src/lib/server/publish-layout.js +243 -0
- package/src/lib/server/publish-layout.js.map +1 -0
- package/src/lib/server/publish-plugin.d.ts +18 -0
- package/src/lib/server/publish-plugin.js +485 -0
- package/src/lib/server/publish-plugin.js.map +1 -0
- package/src/lib/server/publish-preconditions.d.ts +86 -0
- package/src/lib/server/publish-preconditions.js +160 -0
- package/src/lib/server/publish-preconditions.js.map +1 -0
- package/src/lib/server/publish-template.d.ts +27 -0
- package/src/lib/server/publish-template.js +215 -0
- package/src/lib/server/publish-template.js.map +1 -0
- package/src/lib/server/publish-theme.d.ts +38 -0
- package/src/lib/server/publish-theme.js +186 -0
- package/src/lib/server/publish-theme.js.map +1 -0
- package/src/lib/server/publish.d.ts +26 -0
- package/src/lib/server/publish.js +204 -0
- package/src/lib/server/publish.js.map +1 -0
- package/src/lib/server/publisher-profile-save.d.ts +30 -0
- package/src/lib/server/publisher-profile-save.js +226 -0
- package/src/lib/server/publisher-profile-save.js.map +1 -0
- package/src/lib/server/publisher-profile.d.ts +86 -0
- package/src/lib/server/publisher-profile.js +116 -0
- package/src/lib/server/publisher-profile.js.map +1 -0
- package/src/lib/server/purchase-entitlement.d.ts +83 -0
- package/src/lib/server/purchase-entitlement.js +118 -0
- package/src/lib/server/purchase-entitlement.js.map +1 -0
- package/src/lib/server/report.d.ts +32 -0
- package/src/lib/server/report.js +101 -0
- package/src/lib/server/report.js.map +1 -0
- package/src/lib/server/reviews.d.ts +41 -0
- package/src/lib/server/reviews.js +240 -0
- package/src/lib/server/reviews.js.map +1 -0
- package/src/lib/server/update-artifact.d.ts +19 -0
- package/src/lib/server/update-artifact.js +569 -0
- package/src/lib/server/update-artifact.js.map +1 -0
- package/src/lib/server/verification-request.d.ts +31 -0
- package/src/lib/server/verification-request.js +173 -0
- package/src/lib/server/verification-request.js.map +1 -0
- package/src/lib/server/version-stats.d.ts +202 -0
- package/src/lib/server/version-stats.js +305 -0
- package/src/lib/server/version-stats.js.map +1 -0
- package/src/lib/server.d.ts +30 -0
- package/src/lib/server.js +95 -0
- package/src/lib/server.js.map +1 -0
|
@@ -0,0 +1,1880 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
|
+
import { LISTING_README_MAX_CHARS, listingArtifactType, installsUnreviewedFallback, listingArtifactLabel, listingInclusions, hasLivePurchaseOf, hasOrgLicenceOf, licensedOrgIdsFor, installTargetsFor, isListingDeleted, isPrivateListing, resolveInstallPlan, resolveOrgInstallSummary, resolvePluginInstallState, resolveUninstallTargets, safePublisherHref } from "../model/marketplace.js";
|
|
20
|
+
import UninstallImpactDialog from "./uninstall-impact-dialog.component.js";
|
|
21
|
+
import { ListingImage } from "./listing-image.component.js";
|
|
22
|
+
import { buildRoute, parseMarkdownLite, PLATFORM_BRAND_NAME, offeredPluginVersion, PLUGIN_HOST_ABI_VERSION, pluginDocsHelp, resolveMarkdownLink, resolveMediaSrc, resolveUpdateState, Route, updateStateLabel } from "@aglyn/aglyn";
|
|
23
|
+
import { mdiCheckCircle, mdiCheckCircleOutline, mdiEmailOutline, mdiGithub, mdiInformationOutline, mdiLifebuoy, mdiLinkedin, mdiStorefrontOutline, mdiTwitter, mdiWeb } from "@aglyn/shared-data-mdi";
|
|
24
|
+
import { AppLink, CardDisplay, Container, GridItems, MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
25
|
+
import { NextPageTitle } from "@aglyn/shared-ui-next/contexts/next-page-title-provider";
|
|
26
|
+
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
27
|
+
import { Alert, Avatar, Box, Button, Checkbox, Chip, Dialog, DialogActions, DialogContent, DialogTitle, Divider, FormControl, FormControlLabel, FormGroup, FormHelperText, FormLabel, IconButton, Link as MuiLink, Rating, Stack, Tooltip, Typography } from "@mui/material";
|
|
28
|
+
import { collection, doc, documentId, getDoc, limit, orderBy, query, where } from "firebase/firestore";
|
|
29
|
+
import { useEffect, useMemo, useState } from "react";
|
|
30
|
+
import { useConsoleHostRoute, useFirestore, useFirestoreCollection, useFirestoreDoc, useHostOrgId, useUser, useScopeTokens } from "@aglyn/tenant-feature-instance";
|
|
31
|
+
/*
|
|
32
|
+
* The MODULE, not the barrel, for the two PURE helpers — the specs that render
|
|
33
|
+
* this page mock `@aglyn/tenant-feature-instance` wholesale to stage their
|
|
34
|
+
* Firestore hooks, and a query builder imported through that barrel disappears
|
|
35
|
+
* under the mock. Neither of these is a hook.
|
|
36
|
+
*/ import { ceilingedWindow, collectionCeiling } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
|
|
37
|
+
import ListingReviews from "./listing-reviews.component.js";
|
|
38
|
+
import ReportTarget from "./report-target.component.js";
|
|
39
|
+
import PluginSiteSet from "./plugin-site-set.component.js";
|
|
40
|
+
import { MenuItem, TextField } from "@mui/material";
|
|
41
|
+
import { useMarketplaceActions } from "../hooks/use-marketplace-actions.js";
|
|
42
|
+
import { useArtifactUpdate } from "../hooks/use-artifact-update.js";
|
|
43
|
+
import ArtifactUpdateDialog from "./artifact-update-dialog.component.js";
|
|
44
|
+
/**
|
|
45
|
+
* How many of the site's components the page reads to answer one question.
|
|
46
|
+
*
|
|
47
|
+
* A LOOKUP ceiling, not a list: nothing here renders these documents. It only
|
|
48
|
+
* has to be wide enough that a component install of this listing is inside it.
|
|
49
|
+
*/ const COMPONENT_LOOKUP_CEILING = 100;
|
|
50
|
+
/**
|
|
51
|
+
* How many of the buyer's purchase documents the page reads.
|
|
52
|
+
*
|
|
53
|
+
* Wider than the other lookups on purpose — see the query, which explains what
|
|
54
|
+
* a miss costs on the one panel that offers to take money.
|
|
55
|
+
*/ const PURCHASE_LOOKUP_CEILING = 200;
|
|
56
|
+
/**
|
|
57
|
+
* How many installs OF THIS LISTING the page reads per artifact type.
|
|
58
|
+
*
|
|
59
|
+
* Narrow because the query already is: both reads carry the listing id, so
|
|
60
|
+
* this bounds "how many times one workspace installed one thing" rather than a
|
|
61
|
+
* collection.
|
|
62
|
+
*/ const ARTIFACT_INSTALL_CEILING = 20;
|
|
63
|
+
const renderInlines = (inlines)=>inlines.map((inline, index)=>{
|
|
64
|
+
switch(inline.type){
|
|
65
|
+
case 'bold':
|
|
66
|
+
return /*#__PURE__*/ _jsx("strong", {
|
|
67
|
+
children: inline.text
|
|
68
|
+
}, index);
|
|
69
|
+
case 'italic':
|
|
70
|
+
return /*#__PURE__*/ _jsx("em", {
|
|
71
|
+
children: inline.text
|
|
72
|
+
}, index);
|
|
73
|
+
case 'link':
|
|
74
|
+
{
|
|
75
|
+
/*
|
|
76
|
+
No routing map is passed, and that is the answer rather than an
|
|
77
|
+
omission (AGL-3118). A link REFERENCE names a screen, listing, entry
|
|
78
|
+
or feed of the SITE a document was written for; a listing README is
|
|
79
|
+
read in the console, which serves none of them, so a reference here
|
|
80
|
+
resolves to nothing and renders as its words — never as an anchor
|
|
81
|
+
holding `entry:…`.
|
|
82
|
+
*/ const link = resolveMarkdownLink(inline.href);
|
|
83
|
+
if (!('href' in link)) return /*#__PURE__*/ _jsx("span", {
|
|
84
|
+
children: inline.text
|
|
85
|
+
}, index);
|
|
86
|
+
return /*#__PURE__*/ _jsx(MuiLink, {
|
|
87
|
+
href: link.href,
|
|
88
|
+
target: "_blank",
|
|
89
|
+
rel: "noopener noreferrer",
|
|
90
|
+
color: "primary",
|
|
91
|
+
underline: "hover",
|
|
92
|
+
children: inline.text
|
|
93
|
+
}, index);
|
|
94
|
+
}
|
|
95
|
+
default:
|
|
96
|
+
return /*#__PURE__*/ _jsx("span", {
|
|
97
|
+
children: inline.text
|
|
98
|
+
}, index);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
/**
|
|
102
|
+
* Publisher README (AGL-431), rendered through markdown-lite — the parser
|
|
103
|
+
* only ever emits text/bold/italic/http(s)-links/images, so publisher-
|
|
104
|
+
* written docs can't inject markup or javascript: URLs. Code blocks and
|
|
105
|
+
* tables carry text only (AGL-974); the same guarantee holds for both.
|
|
106
|
+
*/ /**
|
|
107
|
+
* What changed between versions (AGL-976).
|
|
108
|
+
*
|
|
109
|
+
* The changelog was already collected per version and already returned by the
|
|
110
|
+
* versions endpoint, but the only place it appeared was a caption in the
|
|
111
|
+
* sidebar timeline — plain text, in a narrow column, next to a date. A buyer
|
|
112
|
+
* deciding whether to upgrade reads this, and publishers write it as ordinary
|
|
113
|
+
* markdown: a config note, a list of fixes, sometimes a fenced snippet.
|
|
114
|
+
*
|
|
115
|
+
* So it renders through the same markdown-lite path as the README, which
|
|
116
|
+
* gained code fences and tables in AGL-974 — and inherits the same safety
|
|
117
|
+
* posture: no raw HTML, `safeUrl` gating every link.
|
|
118
|
+
*
|
|
119
|
+
* APPROVED VERSIONS ONLY, and that is enforced server-side rather than here
|
|
120
|
+
* (AGL-976). A filter in the renderer would leave the same data leaking
|
|
121
|
+
* through the endpoint to anything else that reads it.
|
|
122
|
+
*/ function ListingChangelog({ versions }) {
|
|
123
|
+
const entries = useMemo(()=>versions.filter((entry)=>{
|
|
124
|
+
var _entry_changelog;
|
|
125
|
+
return String((_entry_changelog = entry.changelog) != null ? _entry_changelog : '').trim();
|
|
126
|
+
}), [
|
|
127
|
+
versions
|
|
128
|
+
]);
|
|
129
|
+
// Nothing to say is different from nothing to show: a listing whose
|
|
130
|
+
// publisher has never written a changelog should not get an empty card
|
|
131
|
+
// implying they withheld one.
|
|
132
|
+
if (!entries.length) return null;
|
|
133
|
+
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
134
|
+
header: 'Changelog',
|
|
135
|
+
help: pluginDocsHelp('publisherHandbook', {
|
|
136
|
+
anchor: '#versioning--updates'
|
|
137
|
+
}),
|
|
138
|
+
contentGutterX: true,
|
|
139
|
+
contentGutterY: true,
|
|
140
|
+
children: /*#__PURE__*/ _jsx(Stack, {
|
|
141
|
+
spacing: 2.5,
|
|
142
|
+
children: entries.map((entry, index)=>{
|
|
143
|
+
var _entry_changelog;
|
|
144
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
145
|
+
spacing: 1,
|
|
146
|
+
children: [
|
|
147
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
148
|
+
direction: "row",
|
|
149
|
+
spacing: 1,
|
|
150
|
+
sx: {
|
|
151
|
+
alignItems: 'center',
|
|
152
|
+
flexWrap: 'wrap'
|
|
153
|
+
},
|
|
154
|
+
children: [
|
|
155
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
156
|
+
variant: "subtitle2",
|
|
157
|
+
children: `v${entry.version}`
|
|
158
|
+
}),
|
|
159
|
+
index === 0 ? /*#__PURE__*/ _jsx(Chip, {
|
|
160
|
+
size: "small",
|
|
161
|
+
label: "Latest"
|
|
162
|
+
}) : null,
|
|
163
|
+
entry.trust === 'realm' ? /*#__PURE__*/ _jsx(Chip, {
|
|
164
|
+
size: "small",
|
|
165
|
+
color: "success",
|
|
166
|
+
label: "Realm-trusted"
|
|
167
|
+
}) : null,
|
|
168
|
+
entry.publishedAtMs ? /*#__PURE__*/ _jsx(Typography, {
|
|
169
|
+
variant: "caption",
|
|
170
|
+
color: "text.secondary",
|
|
171
|
+
children: new Date(entry.publishedAtMs).toLocaleDateString()
|
|
172
|
+
}) : null
|
|
173
|
+
]
|
|
174
|
+
}),
|
|
175
|
+
/*#__PURE__*/ _jsx(ListingReadme, {
|
|
176
|
+
readme: String((_entry_changelog = entry.changelog) != null ? _entry_changelog : '')
|
|
177
|
+
}),
|
|
178
|
+
index < entries.length - 1 ? /*#__PURE__*/ _jsx(Divider, {}) : null
|
|
179
|
+
]
|
|
180
|
+
}, entry.version);
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
// Exported for the renderer-parity spec (AGL-1315).
|
|
186
|
+
export function ListingReadme({ readme }) {
|
|
187
|
+
const blocks = useMemo(()=>parseMarkdownLite(readme.slice(0, LISTING_README_MAX_CHARS)), [
|
|
188
|
+
readme
|
|
189
|
+
]);
|
|
190
|
+
return /*#__PURE__*/ _jsx(Stack, {
|
|
191
|
+
spacing: 1.5,
|
|
192
|
+
children: blocks.map((block, index)=>{
|
|
193
|
+
switch(block.type){
|
|
194
|
+
case 'heading':
|
|
195
|
+
return /*#__PURE__*/ _jsx(Typography, {
|
|
196
|
+
variant: block.level === 2 ? 'h6' : 'subtitle1',
|
|
197
|
+
children: renderInlines(block.inlines)
|
|
198
|
+
}, index);
|
|
199
|
+
case 'image':
|
|
200
|
+
return /*#__PURE__*/ _jsx(Box, {
|
|
201
|
+
component: "img",
|
|
202
|
+
// Resolved like `ListingImage` next door (AGL-1686). No
|
|
203
|
+
// `hostId`: a listing belongs to a publishing ORG, not to a
|
|
204
|
+
// site, so the scope the picker baked in is already the right
|
|
205
|
+
// one and there is no rendering host to re-point it at.
|
|
206
|
+
src: resolveMediaSrc(block.src),
|
|
207
|
+
alt: block.alt,
|
|
208
|
+
loading: "lazy",
|
|
209
|
+
sx: {
|
|
210
|
+
maxWidth: '100%',
|
|
211
|
+
borderRadius: 1,
|
|
212
|
+
border: 1,
|
|
213
|
+
borderColor: 'divider'
|
|
214
|
+
}
|
|
215
|
+
}, index);
|
|
216
|
+
case 'list':
|
|
217
|
+
return /*#__PURE__*/ _jsx(Stack, {
|
|
218
|
+
component: "ul",
|
|
219
|
+
spacing: 0.5,
|
|
220
|
+
sx: {
|
|
221
|
+
my: 0
|
|
222
|
+
},
|
|
223
|
+
children: block.items.map((item, itemIndex)=>/*#__PURE__*/ _jsx(Typography, {
|
|
224
|
+
component: "li",
|
|
225
|
+
variant: "body2",
|
|
226
|
+
children: renderInlines(item)
|
|
227
|
+
}, itemIndex))
|
|
228
|
+
}, index);
|
|
229
|
+
// A README's install steps (AGL-1320) — a real `<ol>` carrying
|
|
230
|
+
// `start`, so step 4 of a continued run still reads as step 4.
|
|
231
|
+
case 'orderedList':
|
|
232
|
+
return /*#__PURE__*/ _jsx(Stack, {
|
|
233
|
+
component: "ol",
|
|
234
|
+
start: block.start,
|
|
235
|
+
spacing: 0.5,
|
|
236
|
+
sx: {
|
|
237
|
+
my: 0
|
|
238
|
+
},
|
|
239
|
+
children: block.items.map((item, itemIndex)=>/*#__PURE__*/ _jsx(Typography, {
|
|
240
|
+
component: "li",
|
|
241
|
+
variant: "body2",
|
|
242
|
+
children: renderInlines(item)
|
|
243
|
+
}, itemIndex))
|
|
244
|
+
}, index);
|
|
245
|
+
// A README's example snippet (AGL-974). Scrolls rather than wraps —
|
|
246
|
+
// a wrapped command line reads as two commands.
|
|
247
|
+
case 'code':
|
|
248
|
+
return /*#__PURE__*/ _jsx(Box, {
|
|
249
|
+
component: "pre",
|
|
250
|
+
sx: {
|
|
251
|
+
my: 0,
|
|
252
|
+
p: 1.5,
|
|
253
|
+
overflowX: 'auto',
|
|
254
|
+
borderRadius: 1,
|
|
255
|
+
border: 1,
|
|
256
|
+
borderColor: 'divider',
|
|
257
|
+
bgcolor: 'action.hover',
|
|
258
|
+
typography: 'body2',
|
|
259
|
+
fontFamily: 'monospace'
|
|
260
|
+
},
|
|
261
|
+
children: /*#__PURE__*/ _jsx("code", {
|
|
262
|
+
children: block.text
|
|
263
|
+
})
|
|
264
|
+
}, index);
|
|
265
|
+
// The config table (AGL-974) — the block a plugin README is
|
|
266
|
+
// mostly made of. Wrapped in its own scroller so a wide table
|
|
267
|
+
// never pushes the page sideways.
|
|
268
|
+
case 'table':
|
|
269
|
+
return /*#__PURE__*/ _jsx(Box, {
|
|
270
|
+
sx: {
|
|
271
|
+
overflowX: 'auto'
|
|
272
|
+
},
|
|
273
|
+
children: /*#__PURE__*/ _jsxs(Box, {
|
|
274
|
+
component: "table",
|
|
275
|
+
sx: {
|
|
276
|
+
borderCollapse: 'collapse',
|
|
277
|
+
width: '100%',
|
|
278
|
+
'& th, & td': {
|
|
279
|
+
border: 1,
|
|
280
|
+
borderColor: 'divider',
|
|
281
|
+
px: 1,
|
|
282
|
+
py: 0.5
|
|
283
|
+
},
|
|
284
|
+
'& th': {
|
|
285
|
+
bgcolor: 'action.hover'
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
children: [
|
|
289
|
+
/*#__PURE__*/ _jsx("thead", {
|
|
290
|
+
children: /*#__PURE__*/ _jsx("tr", {
|
|
291
|
+
children: block.header.map((cell, cellIndex)=>{
|
|
292
|
+
var _block_align_cellIndex;
|
|
293
|
+
return /*#__PURE__*/ _jsx(Typography, {
|
|
294
|
+
component: "th",
|
|
295
|
+
variant: "body2",
|
|
296
|
+
sx: {
|
|
297
|
+
textAlign: (_block_align_cellIndex = block.align[cellIndex]) != null ? _block_align_cellIndex : 'left',
|
|
298
|
+
fontWeight: 600
|
|
299
|
+
},
|
|
300
|
+
children: renderInlines(cell)
|
|
301
|
+
}, cellIndex);
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
}),
|
|
305
|
+
/*#__PURE__*/ _jsx("tbody", {
|
|
306
|
+
children: block.rows.map((row, rowIndex)=>/*#__PURE__*/ _jsx("tr", {
|
|
307
|
+
children: row.map((cell, cellIndex)=>{
|
|
308
|
+
var _block_align_cellIndex;
|
|
309
|
+
return /*#__PURE__*/ _jsx(Typography, {
|
|
310
|
+
component: "td",
|
|
311
|
+
variant: "body2",
|
|
312
|
+
sx: {
|
|
313
|
+
textAlign: (_block_align_cellIndex = block.align[cellIndex]) != null ? _block_align_cellIndex : 'left'
|
|
314
|
+
},
|
|
315
|
+
children: renderInlines(cell)
|
|
316
|
+
}, cellIndex);
|
|
317
|
+
})
|
|
318
|
+
}, rowIndex))
|
|
319
|
+
})
|
|
320
|
+
]
|
|
321
|
+
})
|
|
322
|
+
}, index);
|
|
323
|
+
// A README quote (AGL-1315) — the surface's body size, italic
|
|
324
|
+
// behind a left accent in the theme's divider tone.
|
|
325
|
+
case 'quote':
|
|
326
|
+
return /*#__PURE__*/ _jsx(Typography, {
|
|
327
|
+
component: "blockquote",
|
|
328
|
+
variant: "body2",
|
|
329
|
+
sx: {
|
|
330
|
+
my: 0,
|
|
331
|
+
mx: 0,
|
|
332
|
+
pl: 1.5,
|
|
333
|
+
borderLeft: 3,
|
|
334
|
+
borderColor: 'divider',
|
|
335
|
+
fontStyle: 'italic',
|
|
336
|
+
color: 'text.secondary'
|
|
337
|
+
},
|
|
338
|
+
children: renderInlines(block.inlines)
|
|
339
|
+
}, index);
|
|
340
|
+
default:
|
|
341
|
+
return /*#__PURE__*/ _jsx(Typography, {
|
|
342
|
+
variant: "body2",
|
|
343
|
+
children: renderInlines(block.inlines)
|
|
344
|
+
}, index);
|
|
345
|
+
}
|
|
346
|
+
})
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Whether the detail page has nothing to show, so the zero state stands in
|
|
351
|
+
* for the listing (AGL-998).
|
|
352
|
+
*
|
|
353
|
+
* Existence is `$id`, which `useFirestoreDoc`'s `idField` stamps on every
|
|
354
|
+
* snapshot that exists and on none that does not. A listing FIELD cannot
|
|
355
|
+
* answer this question: browse asks nothing about a publisher, so gating on
|
|
356
|
+
* `profileId` takes a listing the grid is happily showing and 404s it the
|
|
357
|
+
* moment someone opens it (AGL-2700). Deletion is the shared predicate, for
|
|
358
|
+
* the reason its own docblock gives.
|
|
359
|
+
*
|
|
360
|
+
* `!== 'loading'` and not `=== 'success'` (AGL-1066): a refused read reaches
|
|
361
|
+
* 'error', and a listing page rendered over `undefined` is worse than the
|
|
362
|
+
* zero state this exists to show.
|
|
363
|
+
*/ export function listingIsMissing(listing, status) {
|
|
364
|
+
if (status === 'loading') return false;
|
|
365
|
+
return !(listing == null ? void 0 : listing.$id) || isListingDeleted(listing);
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Marketplace listing detail (AGL-95/419), relocated from the app route —
|
|
369
|
+
* the app keeps the Dashboard chrome and renders this through the
|
|
370
|
+
* 'marketplaceListing' widget slot. Full description, preview image,
|
|
371
|
+
* version history, publisher block, and the install/buy CTA.
|
|
372
|
+
*/ export function MarketplaceListingContent({ hostId, listingId, permissions, orgScoped, orgSlug: orgSlugProp, hosts }) {
|
|
373
|
+
var _ref, _ref1, _pluginState_scope, _ref2, _ref3, _ref4, _listing_ratingAverage, _listing_ratingAverage1, _ref5, _ref6, _ref7, _ref8, _ref9, _ref10, _ref11, _ref12, _ref13, _ref14, _ref15, _ref16, _ref17, _ref18, _ref19;
|
|
374
|
+
var _componentInstall_marketplace;
|
|
375
|
+
// The publisher link was `/{hostDocId}/marketplace/publisher/{id}` — the
|
|
376
|
+
// pre-AGL-621 shape, dead since. The sibling browse grid was fixed for
|
|
377
|
+
// exactly this (AGL-673) and this file was missed; both now build the
|
|
378
|
+
// route from the shared table (AGL-685).
|
|
379
|
+
const { orgSlug: resolvedOrgSlug, subdomain } = useConsoleHostRoute(hostId);
|
|
380
|
+
// Prefer the URL slug (AGL-869): synchronous and always present at org scope.
|
|
381
|
+
const orgSlug = orgSlugProp != null ? orgSlugProp : resolvedOrgSlug;
|
|
382
|
+
const firestore = useFirestore();
|
|
383
|
+
const { data: user } = useUser();
|
|
384
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
385
|
+
// Hoisted above the actions hook (AGL-2331): a purchase licenses an
|
|
386
|
+
// ORGANIZATION, so `buy` has to name the workspace it is buying for rather
|
|
387
|
+
// than let the server infer it from whichever site the surface happens to
|
|
388
|
+
// be acting through.
|
|
389
|
+
const orgId = useHostOrgId(hostId);
|
|
390
|
+
const { install, installPlan, buy, uninstall } = useMarketplaceActions(hostId, orgId);
|
|
391
|
+
/** The uninstall awaiting its impact check and confirmation (AGL-1027). */ const [pendingUninstall, setPendingUninstall] = useState(null);
|
|
392
|
+
const [installScope, setInstallScope] = useState('org');
|
|
393
|
+
// Confirm before writing install pins (AGL-867): the install is deliberate
|
|
394
|
+
// and site-scoped, so it names its targets before committing.
|
|
395
|
+
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
396
|
+
// Updating a COPIED artifact is never a plain re-install (AGL-1018): the
|
|
397
|
+
// copy has been edited in the workspace, so the update is previewed field by
|
|
398
|
+
// field before anything is written.
|
|
399
|
+
const { preview: updatePreview, loading: updateLoading, loadPreview, applyUpdate, setPreview: setUpdatePreview } = useArtifactUpdate(hostId);
|
|
400
|
+
const [updateOpen, setUpdateOpen] = useState(false);
|
|
401
|
+
// Screenshot lightbox (AGL-869): the clicked screenshot's URL, or null.
|
|
402
|
+
const [lightboxUrl, setLightboxUrl] = useState(null);
|
|
403
|
+
// Org-scope install targeting (AGL-773): All sites vs a chosen subset. Only
|
|
404
|
+
// engaged at org scope with a known site list; otherwise the per-site tab's
|
|
405
|
+
// simpler org/host choice (AGL-656) still applies.
|
|
406
|
+
const orgTargeting = Boolean(orgScoped && hosts && hosts.length > 0);
|
|
407
|
+
const [targeting, setTargeting] = useState('all-sites');
|
|
408
|
+
const [selectedHostIds, setSelectedHostIds] = useState([]);
|
|
409
|
+
const allHostIds = useMemo(()=>(hosts != null ? hosts : []).map((h)=>h.id), [
|
|
410
|
+
hosts
|
|
411
|
+
]);
|
|
412
|
+
// Listings are org-owned (AGL-652) — "did I publish this" is an org
|
|
413
|
+
// comparison, resolved from the routing mirror like the browse grid.
|
|
414
|
+
const [viewerOrgId, setViewerOrgId] = useState(null);
|
|
415
|
+
useEffect(()=>{
|
|
416
|
+
let active = true;
|
|
417
|
+
void getDoc(doc(firestore, 'hostIndex', hostId)).then((snapshot)=>{
|
|
418
|
+
var _snapshot_get;
|
|
419
|
+
if (active) setViewerOrgId((_snapshot_get = snapshot.get('orgId')) != null ? _snapshot_get : null);
|
|
420
|
+
}).catch(()=>undefined);
|
|
421
|
+
return ()=>{
|
|
422
|
+
active = false;
|
|
423
|
+
};
|
|
424
|
+
}, [
|
|
425
|
+
firestore,
|
|
426
|
+
hostId
|
|
427
|
+
]);
|
|
428
|
+
const { data: listing, status } = useFirestoreDoc(()=>doc(firestore, 'marketplaceListings', listingId || '-missing-'), [
|
|
429
|
+
firestore,
|
|
430
|
+
listingId
|
|
431
|
+
], {
|
|
432
|
+
idField: '$id'
|
|
433
|
+
});
|
|
434
|
+
// Targets this artifact type can actually install to (AGL-656) — only
|
|
435
|
+
// plugins have an org-scoped pin, so only plugins get a choice.
|
|
436
|
+
const installTargets = useMemo(()=>listing ? installTargetsFor(listing) : [
|
|
437
|
+
'host'
|
|
438
|
+
], [
|
|
439
|
+
listing
|
|
440
|
+
]);
|
|
441
|
+
const { data: profile } = useFirestoreDoc(()=>{
|
|
442
|
+
var _ref;
|
|
443
|
+
return doc(firestore, 'publisherProfiles', (_ref = listing == null ? void 0 : listing.profileId) != null ? _ref : '-anonymous-');
|
|
444
|
+
}, [
|
|
445
|
+
firestore,
|
|
446
|
+
listing == null ? void 0 : listing.profileId
|
|
447
|
+
], {
|
|
448
|
+
idField: '$id'
|
|
449
|
+
});
|
|
450
|
+
/*
|
|
451
|
+
* ORDERED AND CEILINGED (AGL-2501). The page folds this into ONE answer —
|
|
452
|
+
* is a component install of THIS listing present — so a bare `limit`, which
|
|
453
|
+
* Firestore answers in document-id order, would let an arbitrary hundred of
|
|
454
|
+
* the site's components decide it. A site past such a window reads as
|
|
455
|
+
* not-installed, and the button below then offers to install it again.
|
|
456
|
+
*/ const { data: installedRead } = useFirestoreCollection(()=>collectionCeiling(collection(firestore, 'hosts', hostId, 'components'), COMPONENT_LOOKUP_CEILING), [
|
|
457
|
+
firestore,
|
|
458
|
+
hostId
|
|
459
|
+
], {
|
|
460
|
+
idField: '$id'
|
|
461
|
+
});
|
|
462
|
+
const { rows: installedDocs, truncated: installedTruncated } = useMemo(()=>ceilingedWindow(installedRead, COMPONENT_LOOKUP_CEILING), [
|
|
463
|
+
installedRead
|
|
464
|
+
]);
|
|
465
|
+
// Held at null until the uid resolves (AGL-1440): `marketplacePurchases`
|
|
466
|
+
// is buyer/seller-gated and a LIST is evaluated against the QUERY, so the
|
|
467
|
+
// `-anonymous-` sentinel was denied wholesale on every mount and retried
|
|
468
|
+
// on the refusal cadence. A signed-out viewer simply has no purchases.
|
|
469
|
+
/*
|
|
470
|
+
* ORDERED AND CEILINGED, and the ceiling stays wide (AGL-2501).
|
|
471
|
+
*
|
|
472
|
+
* `orderBy(documentId())` costs nothing here: an equality `where` plus
|
|
473
|
+
* `orderBy(__name__)` is served by the automatic single-field index on
|
|
474
|
+
* `buyerUid`, so no composite index stands between this page and a buyer's
|
|
475
|
+
* licences. Ordering on `purchasedAt` would need one, and would also DROP
|
|
476
|
+
* any purchase written without the field.
|
|
477
|
+
*
|
|
478
|
+
* The window is wider than the others because of what a miss costs here.
|
|
479
|
+
* Every other lookup on this page decides a chip; this one decides whether
|
|
480
|
+
* the Buy button appears, so a purchase that falls outside it does not
|
|
481
|
+
* merely under-report state — it offers to sell a licence the buyer already
|
|
482
|
+
* holds. The probe row is what lets the panel say so.
|
|
483
|
+
*/ const { data: purchaseRead } = useFirestoreCollection(()=>(user == null ? void 0 : user.uid) ? query(collection(firestore, 'marketplacePurchases'), where('buyerUid', '==', user.uid), orderBy(documentId()), limit(PURCHASE_LOOKUP_CEILING + 1)) : null, [
|
|
484
|
+
firestore,
|
|
485
|
+
user == null ? void 0 : user.uid
|
|
486
|
+
], {
|
|
487
|
+
idField: '$id'
|
|
488
|
+
});
|
|
489
|
+
const { rows: purchaseDocs, truncated: purchasesTruncated } = useMemo(()=>ceilingedWindow(purchaseRead, PURCHASE_LOOKUP_CEILING), [
|
|
490
|
+
purchaseRead
|
|
491
|
+
]);
|
|
492
|
+
const artifactType = listing ? listingArtifactType(listing) : null;
|
|
493
|
+
const isPlugin = artifactType === 'plugin';
|
|
494
|
+
// Plugin installs are version PINS, not component snapshots (AGL-656): a
|
|
495
|
+
// host pin lives at `hosts/{h}/installs/{id}`, an org pin at
|
|
496
|
+
// `orgs/{o}/installs/{id}` and applies to every site. Reading only
|
|
497
|
+
// `components` above never sees either, so an installed plugin read as
|
|
498
|
+
// "not installed" here; the two pins tell the honest story, host over org.
|
|
499
|
+
// (`orgId` is resolved above, where `buy` needs it.)
|
|
500
|
+
const { data: hostPin } = useFirestoreDoc(()=>doc(firestore, 'hosts', hostId, 'installs', listingId || '-missing-'), [
|
|
501
|
+
firestore,
|
|
502
|
+
hostId,
|
|
503
|
+
listingId
|
|
504
|
+
], {
|
|
505
|
+
idField: '$id'
|
|
506
|
+
});
|
|
507
|
+
// Held at null while `useHostOrgId` is in flight, never `orgs/-pending-`
|
|
508
|
+
// (AGL-1440): installs are member-gated, so the sentinel was a
|
|
509
|
+
// guaranteed-denied listen on every listing view.
|
|
510
|
+
const { data: orgPin } = useFirestoreDoc(()=>orgId && listingId ? doc(firestore, 'orgs', orgId, 'installs', listingId) : null, [
|
|
511
|
+
firestore,
|
|
512
|
+
orgId,
|
|
513
|
+
listingId
|
|
514
|
+
], {
|
|
515
|
+
idField: '$id'
|
|
516
|
+
});
|
|
517
|
+
// Every site's pin, not just the acting one (AGL-997). At org scope the
|
|
518
|
+
// question is "which of my sites is this on", and the two subscriptions
|
|
519
|
+
// above can only answer for one arbitrary site. Read as a fan-in rather
|
|
520
|
+
// than a subscription per host: the host count is a prop, so a per-host
|
|
521
|
+
// hook would change the hook count between renders, and a collection-group
|
|
522
|
+
// query would need an index plus a rules audit to read other orgs' pins
|
|
523
|
+
// it must never see. `pinsNonce` re-reads after this page's own writes,
|
|
524
|
+
// which are the only thing that changes these docs while it is open.
|
|
525
|
+
const [sitePins, setSitePins] = useState({});
|
|
526
|
+
const [pinsNonce, setPinsNonce] = useState(0);
|
|
527
|
+
const hostIdsKey = (hosts != null ? hosts : []).map((host)=>host.id).join('|');
|
|
528
|
+
useEffect(()=>{
|
|
529
|
+
if (!orgScoped || !listingId || !(hosts == null ? void 0 : hosts.length)) return;
|
|
530
|
+
let active = true;
|
|
531
|
+
void Promise.all(hosts.map(async (host)=>{
|
|
532
|
+
const snapshot = await getDoc(doc(firestore, 'hosts', host.id, 'installs', listingId));
|
|
533
|
+
return [
|
|
534
|
+
host.id,
|
|
535
|
+
snapshot.exists() ? snapshot.data() : null
|
|
536
|
+
];
|
|
537
|
+
})).then((entries)=>{
|
|
538
|
+
if (active) setSitePins(Object.fromEntries(entries));
|
|
539
|
+
}).catch(()=>undefined);
|
|
540
|
+
return ()=>{
|
|
541
|
+
active = false;
|
|
542
|
+
};
|
|
543
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
544
|
+
}, [
|
|
545
|
+
firestore,
|
|
546
|
+
listingId,
|
|
547
|
+
hostIdsKey,
|
|
548
|
+
orgScoped,
|
|
549
|
+
pinsNonce
|
|
550
|
+
]);
|
|
551
|
+
// datasetSchema/emailTemplate installs live in neither collection above
|
|
552
|
+
// (AGL-789) — they become an org dataset or a draft email version, each
|
|
553
|
+
// stamped with the listing it came from. Scoped to this listing rather than
|
|
554
|
+
// reading the whole collection, since the detail page only speaks for one.
|
|
555
|
+
// Scoped (AGL-1044): the AGL-1041 rules reject an unfiltered list for a
|
|
556
|
+
// scoped member, so this would error rather than show fewer installs.
|
|
557
|
+
//
|
|
558
|
+
// Wait for the member doc before listing (AGL-1047). `useScopeTokens`
|
|
559
|
+
// reports `orgWide: true` while loading, so without `scopeLoaded` a scoped
|
|
560
|
+
// collaborator's first render computes `needsScope: false` and sends an
|
|
561
|
+
// UNFILTERED list that the AGL-1041 rules deny per document. It recovers
|
|
562
|
+
// on the next render, which is what makes it easy to miss: the page looks
|
|
563
|
+
// right and logs a denial every mount.
|
|
564
|
+
const { tokens: scopeTokens, orgWide: viewerOrgWide, loaded: scopeLoaded } = useScopeTokens(orgId != null ? orgId : undefined);
|
|
565
|
+
const needsScope = Boolean(orgId) && !viewerOrgWide;
|
|
566
|
+
/*
|
|
567
|
+
* ORDERED (AGL-2501). These two are already narrowed to ONE listing, so the
|
|
568
|
+
* twenty is generous rather than tight — but `artifactInstall` below takes
|
|
569
|
+
* the FIRST live row out of whatever comes back, and an unordered `limit` is
|
|
570
|
+
* answered in document-id order, which makes "the first" an arbitrary one.
|
|
571
|
+
* On an org holding more than twenty installs of a single listing the window
|
|
572
|
+
* could hold only deleted rows while a live one sat outside it, and the page
|
|
573
|
+
* would offer to install what is already installed.
|
|
574
|
+
*
|
|
575
|
+
* `documentId()` is what the existing composite index on
|
|
576
|
+
* (`source.listingId`, `visibleTo`) already orders by — Firestore appends the
|
|
577
|
+
* document name to every index — so this needs no new one.
|
|
578
|
+
*/ const { data: datasetInstalls } = useFirestoreCollection(()=>orgId && scopeLoaded ? query(collection(firestore, 'orgs', orgId, 'datasets'), where('source.listingId', '==', listingId || '-missing-'), ...needsScope ? [
|
|
579
|
+
where('visibleTo', 'array-contains-any', scopeTokens)
|
|
580
|
+
] : [], orderBy(documentId()), limit(ARTIFACT_INSTALL_CEILING)) : null, [
|
|
581
|
+
firestore,
|
|
582
|
+
orgId,
|
|
583
|
+
scopeLoaded,
|
|
584
|
+
listingId,
|
|
585
|
+
needsScope,
|
|
586
|
+
scopeTokens
|
|
587
|
+
], // Same mutable-predicate exposure as the browse datasets query:
|
|
588
|
+
// `visibleTo` is rewritten by every scope edit, and the rule requires the
|
|
589
|
+
// constraint for anyone who is not org-wide (AGL-1196).
|
|
590
|
+
{
|
|
591
|
+
idField: '$id',
|
|
592
|
+
confirmDisappearances: true
|
|
593
|
+
});
|
|
594
|
+
const { data: emailInstalls } = useFirestoreCollection(()=>query(collection(firestore, 'hosts', hostId, 'emailTemplates'), where('installedFrom.listingId', '==', listingId || '-missing-'), orderBy(documentId()), limit(ARTIFACT_INSTALL_CEILING)), [
|
|
595
|
+
firestore,
|
|
596
|
+
hostId,
|
|
597
|
+
listingId
|
|
598
|
+
], {
|
|
599
|
+
idField: '$id'
|
|
600
|
+
});
|
|
601
|
+
// A theme installs onto the host document itself (AGL-1020) rather than into
|
|
602
|
+
// a collection, so there is no per-listing query to scope — the site either
|
|
603
|
+
// runs this listing's theme or it does not.
|
|
604
|
+
const { data: themeHostDoc } = useFirestoreDoc(()=>artifactType === 'theme' ? doc(firestore, 'hosts', hostId) : null, [
|
|
605
|
+
firestore,
|
|
606
|
+
hostId,
|
|
607
|
+
artifactType
|
|
608
|
+
], {
|
|
609
|
+
idField: '$id'
|
|
610
|
+
});
|
|
611
|
+
const artifactInstall = useMemo(()=>{
|
|
612
|
+
if (artifactType === 'datasetSchema') {
|
|
613
|
+
var _ref;
|
|
614
|
+
var _hit_source;
|
|
615
|
+
const hit = (datasetInstalls != null ? datasetInstalls : []).find((entry)=>!entry.deletedAt);
|
|
616
|
+
return hit ? {
|
|
617
|
+
version: (_ref = (_hit_source = hit.source) == null ? void 0 : _hit_source.version) != null ? _ref : null
|
|
618
|
+
} : undefined;
|
|
619
|
+
}
|
|
620
|
+
if (artifactType === 'emailTemplate') {
|
|
621
|
+
var _ref1;
|
|
622
|
+
var _hit_installedFrom;
|
|
623
|
+
const hit = (emailInstalls != null ? emailInstalls : []).find((entry)=>!entry.deletedAt);
|
|
624
|
+
return hit ? {
|
|
625
|
+
version: (_ref1 = (_hit_installedFrom = hit.installedFrom) == null ? void 0 : _hit_installedFrom.version) != null ? _ref1 : null
|
|
626
|
+
} : undefined;
|
|
627
|
+
}
|
|
628
|
+
if (artifactType === 'theme') {
|
|
629
|
+
var _ref2;
|
|
630
|
+
const stamp = themeHostDoc == null ? void 0 : themeHostDoc.themeInstalledFrom;
|
|
631
|
+
return (stamp == null ? void 0 : stamp.listingId) === listingId ? {
|
|
632
|
+
version: (_ref2 = stamp == null ? void 0 : stamp.version) != null ? _ref2 : null
|
|
633
|
+
} : undefined;
|
|
634
|
+
}
|
|
635
|
+
return undefined;
|
|
636
|
+
}, [
|
|
637
|
+
artifactType,
|
|
638
|
+
datasetInstalls,
|
|
639
|
+
emailInstalls,
|
|
640
|
+
themeHostDoc,
|
|
641
|
+
listingId
|
|
642
|
+
]);
|
|
643
|
+
/**
|
|
644
|
+
* The kill switch, on the page with the Install button (AGL-2368).
|
|
645
|
+
*
|
|
646
|
+
* One document read for the whole page — the switch is listing-scoped —
|
|
647
|
+
* and public, which is what lets the marketplace loaders and the
|
|
648
|
+
* host-plugins card read it already.
|
|
649
|
+
*/ const { data: revocation } = useFirestoreDoc(()=>isPlugin && listingId ? doc(firestore, 'revocations', listingId) : null, [
|
|
650
|
+
firestore,
|
|
651
|
+
isPlugin,
|
|
652
|
+
listingId
|
|
653
|
+
]);
|
|
654
|
+
/**
|
|
655
|
+
* The version this listing actually offers (AGL-1016, AGL-2368).
|
|
656
|
+
*
|
|
657
|
+
* For a plugin that is the newest INSTALLABLE version, not `latestVersion`
|
|
658
|
+
* and not merely the newest approved one. This page used to compare against
|
|
659
|
+
* `latestVersion`, so publishing v1.1.0 lit up "Update to v1.1.0" for every
|
|
660
|
+
* workspace the moment it was uploaded — and the install route then refused
|
|
661
|
+
* it, because AGL-966 only ever hands out reviewed bytes. The badge was
|
|
662
|
+
* advertising what the marketplace would not give you.
|
|
663
|
+
*
|
|
664
|
+
* `latestApprovedVersion` fixed that and left the same defect one state
|
|
665
|
+
* over: approval is a review verdict and revocation does not clear it, so a
|
|
666
|
+
* killed version stayed the offer. This is the page with the Install and
|
|
667
|
+
* Buy buttons on it, so it cross-checks the mirror against the switch
|
|
668
|
+
* rather than trusting a cache two writes behind.
|
|
669
|
+
*/ const offeredVersion = isPlugin ? offeredPluginVersion(listing, revocation) : listing == null ? void 0 : listing.latestVersion;
|
|
670
|
+
/**
|
|
671
|
+
* The publisher installing their OWN unreviewed bytes (AGL-1083).
|
|
672
|
+
*
|
|
673
|
+
* `install-plugin` deliberately allows it — you cannot test a version you
|
|
674
|
+
* cannot install, and the AGL-969 checklist asks a publisher to confirm
|
|
675
|
+
* they tested these exact bytes on a site they control. But the affordance
|
|
676
|
+
* said nothing, so it read exactly like installing reviewed, signed code.
|
|
677
|
+
*
|
|
678
|
+
* The condition mirrors the route's fallback rather than approximating it:
|
|
679
|
+
* there, `newest ?? fallback` hands over `latestVersion` ONLY when the
|
|
680
|
+
* owner asks and no approved version exists at all. With an approved
|
|
681
|
+
* version present the server serves that one, and this is not an
|
|
682
|
+
* unreviewed install however new the pending one is.
|
|
683
|
+
*/ const unreviewedInstall = isPlugin && installsUnreviewedFallback(listing, viewerOrgId, revocation);
|
|
684
|
+
/** What would actually land — the route's fallback, named. */ const installingVersion = unreviewedInstall ? listing == null ? void 0 : listing.latestVersion : offeredVersion;
|
|
685
|
+
const pluginState = useMemo(()=>resolvePluginInstallState(offeredVersion, isPlugin ? hostPin : null, isPlugin ? orgPin : null), [
|
|
686
|
+
offeredVersion,
|
|
687
|
+
isPlugin,
|
|
688
|
+
hostPin,
|
|
689
|
+
orgPin
|
|
690
|
+
]);
|
|
691
|
+
// Public version history + trust tier (AGL-431): the pluginVersions
|
|
692
|
+
// docs are server-only, so the marketplace API exposes the buyer-safe
|
|
693
|
+
// subset (version/changelog/trust/hostAbi/date).
|
|
694
|
+
const [versions, setVersions] = useState([]);
|
|
695
|
+
const [totals, setTotals] = useState(null);
|
|
696
|
+
useEffect(()=>{
|
|
697
|
+
// Every artifact type now, not just plugins (AGL-1036): the route serves
|
|
698
|
+
// the per-version install counts for all of them, and a component's
|
|
699
|
+
// publisher wants "who is still on v1" as much as a plugin's does.
|
|
700
|
+
if (!listing || !listingId) return;
|
|
701
|
+
let active = true;
|
|
702
|
+
void fetch(`/api/marketplace/listing-versions?listingId=${encodeURIComponent(listingId)}`).then((response)=>response.ok ? response.json() : {
|
|
703
|
+
versions: []
|
|
704
|
+
}).then((payload)=>{
|
|
705
|
+
var _ref, _payload_installCount, _payload_activeInstalls, _payload_untrackedActiveInstalls, _payload_untrackedInstallCount;
|
|
706
|
+
if (!active) return;
|
|
707
|
+
setVersions((_ref = payload == null ? void 0 : payload.versions) != null ? _ref : []);
|
|
708
|
+
setTotals(typeof (payload == null ? void 0 : payload.activeInstalls) === 'number' ? {
|
|
709
|
+
installCount: Number((_payload_installCount = payload.installCount) != null ? _payload_installCount : 0),
|
|
710
|
+
activeInstalls: Number((_payload_activeInstalls = payload.activeInstalls) != null ? _payload_activeInstalls : 0),
|
|
711
|
+
untrackedActiveInstalls: Number((_payload_untrackedActiveInstalls = payload.untrackedActiveInstalls) != null ? _payload_untrackedActiveInstalls : 0),
|
|
712
|
+
// THIS PROJECTION is where the field died (AGL-2339). The
|
|
713
|
+
// route served it from both branches; the client copied its
|
|
714
|
+
// sibling and not it, so the page could never render what the
|
|
715
|
+
// server had already computed.
|
|
716
|
+
untrackedInstallCount: Number((_payload_untrackedInstallCount = payload.untrackedInstallCount) != null ? _payload_untrackedInstallCount : 0)
|
|
717
|
+
} : null);
|
|
718
|
+
}).catch(()=>undefined);
|
|
719
|
+
return ()=>{
|
|
720
|
+
active = false;
|
|
721
|
+
};
|
|
722
|
+
// Keyed on the listing's id, not its legacy `type` (AGL-864): an
|
|
723
|
+
// artifactType-only plugin has no `type`, so keying on it left the effect
|
|
724
|
+
// stuck at its undefined first value and the versions fetch never fired.
|
|
725
|
+
//
|
|
726
|
+
// `pinsNonce` re-reads after this page's own writes, so the per-version
|
|
727
|
+
// install counts (AGL-1036) are not left showing the tally from before the
|
|
728
|
+
// update the user just pressed.
|
|
729
|
+
}, [
|
|
730
|
+
listing == null ? void 0 : listing.$id,
|
|
731
|
+
listingId,
|
|
732
|
+
pinsNonce
|
|
733
|
+
]);
|
|
734
|
+
/**
|
|
735
|
+
* The install numbers this page is allowed to print (AGL-1418).
|
|
736
|
+
*
|
|
737
|
+
* The route's reconciled totals when they have arrived, and the listing
|
|
738
|
+
* document's own copy only until then. That fallback cannot reintroduce the
|
|
739
|
+
* contradiction it replaced: the version history it used to disagree with is
|
|
740
|
+
* fed by the same response, so before it lands there is nothing on screen to
|
|
741
|
+
* disagree with.
|
|
742
|
+
*/ const installTotals = useMemo(()=>{
|
|
743
|
+
var _ref, _ref1;
|
|
744
|
+
if (totals) return totals;
|
|
745
|
+
const stored = Number((_ref = listing == null ? void 0 : listing.activeInstalls) != null ? _ref : Number.NaN);
|
|
746
|
+
if (!Number.isFinite(stored)) return null;
|
|
747
|
+
return {
|
|
748
|
+
installCount: Number((_ref1 = listing == null ? void 0 : listing.installCount) != null ? _ref1 : 0),
|
|
749
|
+
activeInstalls: stored,
|
|
750
|
+
// Zero on the fallback, because the listing document holds no split at
|
|
751
|
+
// all — claiming a shortfall it cannot know would be the contradiction
|
|
752
|
+
// this fallback exists to avoid.
|
|
753
|
+
untrackedActiveInstalls: 0,
|
|
754
|
+
untrackedInstallCount: 0
|
|
755
|
+
};
|
|
756
|
+
}, [
|
|
757
|
+
totals,
|
|
758
|
+
listing == null ? void 0 : listing.activeInstalls,
|
|
759
|
+
listing == null ? void 0 : listing.installCount
|
|
760
|
+
]);
|
|
761
|
+
const latestEntry = versions[0];
|
|
762
|
+
const realmTrusted = versions.some((entry)=>entry.trust === 'realm');
|
|
763
|
+
const abiIncompatible = (latestEntry == null ? void 0 : latestEntry.hostAbi) != null && latestEntry.hostAbi !== PLUGIN_HOST_ABI_VERSION;
|
|
764
|
+
// Component listings still install into `hosts/{h}/components`; plugin
|
|
765
|
+
// state comes from the pins above. `installed` unifies the two so the CTA
|
|
766
|
+
// and the buy gate read the same for either artifact type.
|
|
767
|
+
const componentInstall = useMemo(()=>(installedDocs != null ? installedDocs : []).find((definition)=>{
|
|
768
|
+
var _definition_marketplace;
|
|
769
|
+
return (definition == null ? void 0 : (_definition_marketplace = definition.marketplace) == null ? void 0 : _definition_marketplace.listingId) === listingId && !definition.deletedAt;
|
|
770
|
+
}), [
|
|
771
|
+
installedDocs,
|
|
772
|
+
listingId
|
|
773
|
+
]);
|
|
774
|
+
// The SERVER's predicate, imported rather than restated (AGL-2158). This
|
|
775
|
+
// used to be `some(p => p.listingId === listingId)` with no refund test,
|
|
776
|
+
// while `hasLivePurchase` — the gate on all eight ways into paid content —
|
|
777
|
+
// treats a purchase carrying `refundedAt` as absent. The one buyer the two
|
|
778
|
+
// must agree about is the refunded one, and they disagreed: the page said
|
|
779
|
+
// "Purchased" and hid the buy button, the install routes answered 402, and
|
|
780
|
+
// the buyer could neither install nor buy it again.
|
|
781
|
+
//
|
|
782
|
+
// ORG-SCOPED SINCE AGL-2331. `hasLivePurchaseOf` answers "did this PERSON
|
|
783
|
+
// buy it", which is no longer the question either the install routes or the
|
|
784
|
+
// checkout guard ask: a licence belongs to a workspace, so the acting org is
|
|
785
|
+
// what decides whether this page may install. Reading the person's answer
|
|
786
|
+
// here would put the CTA back in the disagreement AGL-2158 closed — an
|
|
787
|
+
// agency owner would see "Purchased" in a client workspace that holds no
|
|
788
|
+
// licence, and the install would 402.
|
|
789
|
+
const purchased = useMemo(()=>hasOrgLicenceOf(purchaseDocs != null ? purchaseDocs : [], listingId, {
|
|
790
|
+
orgId,
|
|
791
|
+
uid: user == null ? void 0 : user.uid
|
|
792
|
+
}), [
|
|
793
|
+
purchaseDocs,
|
|
794
|
+
listingId,
|
|
795
|
+
orgId,
|
|
796
|
+
user == null ? void 0 : user.uid
|
|
797
|
+
]);
|
|
798
|
+
/**
|
|
799
|
+
* The buyer holds a licence for this listing — in some OTHER workspace
|
|
800
|
+
* (AGL-2331).
|
|
801
|
+
*
|
|
802
|
+
* The state that has no honest rendering without it: an agency developer who
|
|
803
|
+
* bought this component for one client opens it in the next client's
|
|
804
|
+
* workspace and sees a plain Buy button, with nothing to distinguish "you
|
|
805
|
+
* have never bought this" from "you own one, it just does not cover the
|
|
806
|
+
* workspace you are standing in". Both are a purchase — but only one of them
|
|
807
|
+
* is a surprise when the invoice arrives.
|
|
808
|
+
*
|
|
809
|
+
* Reads the buyer's OWN purchase documents, which the rules have always let
|
|
810
|
+
* them read, so this needs no cross-org read to answer.
|
|
811
|
+
*/ const licensedElsewhere = useMemo(()=>!purchased && hasLivePurchaseOf(purchaseDocs != null ? purchaseDocs : [], listingId) && licensedOrgIdsFor(purchaseDocs != null ? purchaseDocs : [], listingId).some((licensed)=>licensed !== orgId), [
|
|
812
|
+
purchased,
|
|
813
|
+
purchaseDocs,
|
|
814
|
+
listingId,
|
|
815
|
+
orgId
|
|
816
|
+
]);
|
|
817
|
+
const missing = listingIsMissing(listing, status);
|
|
818
|
+
// Somewhere to go from the zero state (AGL-998). Same surface split as
|
|
819
|
+
// every other link here: org route at org scope, host route otherwise,
|
|
820
|
+
// and undefined rather than a link to nowhere when the slug is unresolved.
|
|
821
|
+
const marketplaceHref = orgScoped ? orgSlug ? buildRoute(Route.ORG_MARKETPLACE, {
|
|
822
|
+
orgSlug
|
|
823
|
+
}) : undefined : // so both branches now land on the same org-scope page and the
|
|
824
|
+
// subdomain is no longer part of the URL.
|
|
825
|
+
orgSlug ? buildRoute(Route.ORG_MARKETPLACE, {
|
|
826
|
+
orgSlug
|
|
827
|
+
}) : undefined;
|
|
828
|
+
const installed = isPlugin ? pluginState.scope != null : Boolean(componentInstall != null ? componentInstall : artifactInstall);
|
|
829
|
+
const installedVersion = isPlugin ? pluginState.installedVersion : (_ref = componentInstall == null ? void 0 : (_componentInstall_marketplace = componentInstall.marketplace) == null ? void 0 : _componentInstall_marketplace.version) != null ? _ref : artifactInstall == null ? void 0 : artifactInstall.version;
|
|
830
|
+
const upToDate = isPlugin ? installed && !pluginState.updateAvailable : componentInstall && installedVersion >= (listing == null ? void 0 : listing.latestVersion);
|
|
831
|
+
/**
|
|
832
|
+
* The same update comparison the Plugins index and the installation page
|
|
833
|
+
* make (AGL-1016), so all three agree on the version pair rather than each
|
|
834
|
+
* re-deriving it. Only rendered when the viewer already has it installed —
|
|
835
|
+
* "you have v1.0.0 · v1.1.0 available" is meaningless to a first-time buyer.
|
|
836
|
+
*/ const updateStatus = useMemo(()=>resolveUpdateState(isPlugin ? hostPin != null ? hostPin : orgPin : // collections here, each with its own field for the version, so
|
|
837
|
+
// they are normalised to the one shape provenance reads. The
|
|
838
|
+
// `installedFrom` stamp wins when AGL-1015 wrote one.
|
|
839
|
+
_extends({}, (componentInstall == null ? void 0 : componentInstall.installedFrom) ? {
|
|
840
|
+
installedFrom: componentInstall.installedFrom
|
|
841
|
+
} : {}, installedVersion != null && listingId ? {
|
|
842
|
+
listingId,
|
|
843
|
+
version: installedVersion
|
|
844
|
+
} : {}), listing != null ? listing : null, artifactType, // The kill switch (AGL-2368) — this line said "Update to vX" for a
|
|
845
|
+
// version the install route refuses, because approval survives it.
|
|
846
|
+
revocation), // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
847
|
+
[
|
|
848
|
+
isPlugin,
|
|
849
|
+
hostPin,
|
|
850
|
+
orgPin,
|
|
851
|
+
componentInstall,
|
|
852
|
+
installedVersion,
|
|
853
|
+
listingId,
|
|
854
|
+
listing,
|
|
855
|
+
artifactType,
|
|
856
|
+
revocation
|
|
857
|
+
]);
|
|
858
|
+
const priceUsd = Number((_ref1 = listing == null ? void 0 : listing.priceUsd) != null ? _ref1 : 0);
|
|
859
|
+
const mustBuy = priceUsd > 0 && !purchased && (listing == null ? void 0 : listing.profileId) !== viewerOrgId && !installed;
|
|
860
|
+
// Updating an installed plugin stays at its current scope; a fresh install
|
|
861
|
+
// uses the picker (plugins) or the artifact's only target (everything else).
|
|
862
|
+
const installTargetScope = isPlugin && installed ? (_pluginState_scope = pluginState.scope) != null ? _pluginState_scope : undefined : installTargets.length > 1 ? installScope : undefined;
|
|
863
|
+
// Org-scope targeting (AGL-773): "Selected sites" is only offered when the
|
|
864
|
+
// artifact can host-pin (plugins, components, templates, layouts) — an
|
|
865
|
+
// org-only artifact can't target a subset.
|
|
866
|
+
const canSelectSites = installTargets.includes('host');
|
|
867
|
+
const installPlanSteps = useMemo(()=>listing ? resolveInstallPlan(listing, targeting, {
|
|
868
|
+
selectedHostIds,
|
|
869
|
+
allHostIds
|
|
870
|
+
}) : [], [
|
|
871
|
+
listing,
|
|
872
|
+
targeting,
|
|
873
|
+
selectedHostIds,
|
|
874
|
+
allHostIds
|
|
875
|
+
]);
|
|
876
|
+
const versionHistory = Array.isArray(listing == null ? void 0 : listing.versionHistory) ? [
|
|
877
|
+
...listing.versionHistory
|
|
878
|
+
].sort((a, b)=>b.version - a.version) : [];
|
|
879
|
+
// Human summary of exactly where this install lands (AGL-867), read from the
|
|
880
|
+
// same targeting the CTA acts on. An org pin covers every site (now and
|
|
881
|
+
// future); host pins are named so "Selected sites" can't silently mean
|
|
882
|
+
// something else.
|
|
883
|
+
const installTargetSummary = ()=>{
|
|
884
|
+
if (orgTargeting && !installed) {
|
|
885
|
+
var _installPlanSteps_;
|
|
886
|
+
if (installPlanSteps.length === 1 && ((_installPlanSteps_ = installPlanSteps[0]) == null ? void 0 : _installPlanSteps_.scope) === 'org') {
|
|
887
|
+
return 'the whole organization — every site, including sites added later';
|
|
888
|
+
}
|
|
889
|
+
const names = installPlanSteps.filter((step)=>step.scope === 'host').map((step)=>{
|
|
890
|
+
var _ref;
|
|
891
|
+
var _find;
|
|
892
|
+
return (_ref = (_find = (hosts != null ? hosts : []).find((host)=>host.id === step.hostId)) == null ? void 0 : _find.label) != null ? _ref : step.hostId;
|
|
893
|
+
});
|
|
894
|
+
if (names.length) {
|
|
895
|
+
return `${names.length} site${names.length === 1 ? '' : 's'}: ${names.join(', ')}`;
|
|
896
|
+
}
|
|
897
|
+
return 'the selected targets';
|
|
898
|
+
}
|
|
899
|
+
// Org-only artifacts (dataset schema) always land org-wide — including on
|
|
900
|
+
// re-add, where installTargetScope isn't set (AGL-867).
|
|
901
|
+
if (installTargetScope === 'org' || installTargets.length === 1 && installTargets[0] === 'org') {
|
|
902
|
+
return 'the whole organization — every site';
|
|
903
|
+
}
|
|
904
|
+
return 'this site';
|
|
905
|
+
};
|
|
906
|
+
// The org-wide picture (AGL-997): which sites run this, at which version,
|
|
907
|
+
// and which are still free to add. Only meaningful at org scope for a
|
|
908
|
+
// plugin — everything else installs INTO a site and has no pin to survey.
|
|
909
|
+
const orgPluginScope = Boolean(orgTargeting && isPlugin);
|
|
910
|
+
const orgInstall = useMemo(()=>resolveOrgInstallSummary(hosts != null ? hosts : [], sitePins, orgPin != null ? orgPin : null), [
|
|
911
|
+
hosts,
|
|
912
|
+
sitePins,
|
|
913
|
+
orgPin
|
|
914
|
+
]);
|
|
915
|
+
/** Run a pin write, then re-read every site's pin. */ const runSiteEdit = async (work)=>{
|
|
916
|
+
try {
|
|
917
|
+
await work();
|
|
918
|
+
} finally{
|
|
919
|
+
setPinsNonce((current)=>current + 1);
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
/**
|
|
923
|
+
* A copied artifact with a newer version on offer (AGL-1018).
|
|
924
|
+
*
|
|
925
|
+
* The distinction that matters: a plugin takes an update by moving a pointer,
|
|
926
|
+
* but a component, layout or dataset schema was copied in and then EDITED
|
|
927
|
+
* here, so "Update" is a merge. It goes through the review dialog, never
|
|
928
|
+
* straight to the install route.
|
|
929
|
+
*/ //
|
|
930
|
+
// Every non-current state, not just `update-available` (AGL-1034): a copy
|
|
931
|
+
// whose versions cannot be ordered, or that reports `ahead`, is still a copy
|
|
932
|
+
// this workspace may have edited, and sending it to the install route would
|
|
933
|
+
// dead-end on the divergence 409 with nothing offering the review it names.
|
|
934
|
+
const copiedUpdatable = !isPlugin && installed && // A theme is a copied artifact like a component, just stored as a field on
|
|
935
|
+
// the site rather than as its own document (AGL-1020) — so it takes the
|
|
936
|
+
// same diff-first update path, and must not fall through to the plain
|
|
937
|
+
// install that would overwrite the site's edits.
|
|
938
|
+
Boolean(componentInstall || artifactType === 'theme' && artifactInstall) && updateStatus.state !== 'current';
|
|
939
|
+
const openUpdateReview = ()=>{
|
|
940
|
+
setUpdatePreview(null);
|
|
941
|
+
setUpdateOpen(true);
|
|
942
|
+
if (listingId) void loadPreview(listingId);
|
|
943
|
+
};
|
|
944
|
+
/**
|
|
945
|
+
* "Install as a new copy", which means different things per type.
|
|
946
|
+
*
|
|
947
|
+
* Only a component or layout has a link that can be DETACHED, leaving the
|
|
948
|
+
* customised copy in place as an ordinary artifact — the update route does
|
|
949
|
+
* that. For the others a fresh copy is exactly what the ordinary install
|
|
950
|
+
* already produces (another empty dataset, another inactive draft, a replaced
|
|
951
|
+
* template bundle), quota checks included, so it goes there rather than
|
|
952
|
+
* through a second implementation that would drift from it.
|
|
953
|
+
*/ const runCopy = async ()=>{
|
|
954
|
+
if (artifactType === 'component' || artifactType === 'layout') {
|
|
955
|
+
return void await runUpdate('copy');
|
|
956
|
+
}
|
|
957
|
+
setUpdateOpen(false);
|
|
958
|
+
await runSiteEdit(()=>install(listing, installTargetScope, {
|
|
959
|
+
onDiverged: openUpdateReview
|
|
960
|
+
}));
|
|
961
|
+
};
|
|
962
|
+
const runUpdate = async (mode, takePaths = [], confirmDestructive = false)=>{
|
|
963
|
+
if (!listingId) return;
|
|
964
|
+
const done = await applyUpdate(listingId, {
|
|
965
|
+
mode,
|
|
966
|
+
takePaths,
|
|
967
|
+
confirmDestructive
|
|
968
|
+
});
|
|
969
|
+
// Left open on failure — a destructive schema update comes back asking for
|
|
970
|
+
// the confirmation tick, and closing would hide the question.
|
|
971
|
+
if (done) {
|
|
972
|
+
setUpdateOpen(false);
|
|
973
|
+
// Re-read the install state and the per-version counts this just moved.
|
|
974
|
+
setPinsNonce((current)=>current + 1);
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
// The actual pin write, run only after the confirm dialog (AGL-867).
|
|
978
|
+
const runInstall = ()=>{
|
|
979
|
+
setConfirmOpen(false);
|
|
980
|
+
// Re-read every site's pin afterwards (AGL-997). The acting host's pin is
|
|
981
|
+
// a live subscription, but the other sites' are a fan-in read — without
|
|
982
|
+
// this the page went straight from "Install" to a bare disabled
|
|
983
|
+
// "Installed" button, because the panel that explains WHERE it landed
|
|
984
|
+
// had not seen the write yet.
|
|
985
|
+
void runSiteEdit(()=>orgTargeting && !installed ? installPlan(listing, installPlanSteps) : // instead of toasting advice nothing here acts on (AGL-1034).
|
|
986
|
+
install(listing, installTargetScope, {
|
|
987
|
+
onDiverged: openUpdateReview
|
|
988
|
+
}));
|
|
989
|
+
};
|
|
990
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
991
|
+
children: [
|
|
992
|
+
/*#__PURE__*/ _jsx(NextPageTitle, {
|
|
993
|
+
screen: (_ref2 = listing == null ? void 0 : listing.displayName) != null ? _ref2 : 'Marketplace listing'
|
|
994
|
+
}),
|
|
995
|
+
/*#__PURE__*/ _jsxs(Container, {
|
|
996
|
+
gutterY: true,
|
|
997
|
+
maxWidth: "xl",
|
|
998
|
+
children: [
|
|
999
|
+
missing ? /*#__PURE__*/ _jsx(CardDisplay, {
|
|
1000
|
+
contentGutterX: true,
|
|
1001
|
+
contentGutterY: true,
|
|
1002
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
1003
|
+
spacing: 1.5,
|
|
1004
|
+
sx: {
|
|
1005
|
+
alignItems: 'center',
|
|
1006
|
+
textAlign: 'center',
|
|
1007
|
+
py: 6
|
|
1008
|
+
},
|
|
1009
|
+
children: [
|
|
1010
|
+
/*#__PURE__*/ _jsx(MdiIcon, {
|
|
1011
|
+
path: mdiStorefrontOutline.path,
|
|
1012
|
+
color: "disabled",
|
|
1013
|
+
sx: {
|
|
1014
|
+
fontSize: 48
|
|
1015
|
+
}
|
|
1016
|
+
}),
|
|
1017
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1018
|
+
variant: "h6",
|
|
1019
|
+
children: 'This listing isn’t here'
|
|
1020
|
+
}),
|
|
1021
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1022
|
+
variant: "body2",
|
|
1023
|
+
color: "text.secondary",
|
|
1024
|
+
sx: {
|
|
1025
|
+
maxWidth: 440
|
|
1026
|
+
},
|
|
1027
|
+
children: 'It may have been unpublished by its publisher, taken ' + 'down, or the link may point somewhere that no longer ' + 'exists. Anything you already installed from it keeps ' + 'working.'
|
|
1028
|
+
}),
|
|
1029
|
+
marketplaceHref ? /*#__PURE__*/ _jsx(AppLink, {
|
|
1030
|
+
href: marketplaceHref,
|
|
1031
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
1032
|
+
variant: "contained",
|
|
1033
|
+
color: "primary",
|
|
1034
|
+
component: "span",
|
|
1035
|
+
children: 'Back to the marketplace'
|
|
1036
|
+
})
|
|
1037
|
+
}) : null
|
|
1038
|
+
]
|
|
1039
|
+
})
|
|
1040
|
+
}) : /*#__PURE__*/ _jsx(GridItems, {
|
|
1041
|
+
spacing: 3,
|
|
1042
|
+
// Masonry. Source order here is body (8),
|
|
1043
|
+
// changelog (8), sidebar (4) — and two eights cannot share a
|
|
1044
|
+
// twelve-column row, so the changelog wrapped and dragged the
|
|
1045
|
+
// sidebar down with it. `Install`, the point of the page, opened
|
|
1046
|
+
// below the fold under a screen of blank space. Masonry places
|
|
1047
|
+
// the sidebar in the columns beside the body card where it
|
|
1048
|
+
// belongs, and no card can be pushed past the container's edge.
|
|
1049
|
+
masonry: true,
|
|
1050
|
+
items: [
|
|
1051
|
+
{
|
|
1052
|
+
size: {
|
|
1053
|
+
xs: 12,
|
|
1054
|
+
md: 8
|
|
1055
|
+
},
|
|
1056
|
+
// No card header (AGL-1000): the page's hero now carries the
|
|
1057
|
+
// listing's name, and the only route that renders this
|
|
1058
|
+
// widget is the org listing page — the per-site one
|
|
1059
|
+
// redirects to it (AGL-775) — so a heading here would just
|
|
1060
|
+
// print the same words twice, inches apart.
|
|
1061
|
+
children: /*#__PURE__*/ _jsx(CardDisplay, {
|
|
1062
|
+
contentGutterX: true,
|
|
1063
|
+
contentGutterY: true,
|
|
1064
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
1065
|
+
spacing: 2,
|
|
1066
|
+
children: [
|
|
1067
|
+
/*#__PURE__*/ _jsx(ListingImage, {
|
|
1068
|
+
src: listing == null ? void 0 : listing.previewImageUrl,
|
|
1069
|
+
alt: `${listing == null ? void 0 : listing.displayName} preview`,
|
|
1070
|
+
sx: {
|
|
1071
|
+
width: '100%',
|
|
1072
|
+
maxHeight: 360,
|
|
1073
|
+
objectFit: 'cover',
|
|
1074
|
+
borderRadius: 1,
|
|
1075
|
+
border: 1,
|
|
1076
|
+
borderColor: 'divider'
|
|
1077
|
+
}
|
|
1078
|
+
}),
|
|
1079
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
1080
|
+
direction: "row",
|
|
1081
|
+
spacing: 1,
|
|
1082
|
+
sx: {
|
|
1083
|
+
alignItems: 'center',
|
|
1084
|
+
flexWrap: 'wrap'
|
|
1085
|
+
},
|
|
1086
|
+
children: [
|
|
1087
|
+
(listing == null ? void 0 : listing.logoUrl) ? /*#__PURE__*/ _jsx(Box, {
|
|
1088
|
+
component: "img",
|
|
1089
|
+
src: listing.logoUrl,
|
|
1090
|
+
alt: `${listing == null ? void 0 : listing.displayName} logo`,
|
|
1091
|
+
sx: {
|
|
1092
|
+
width: 32,
|
|
1093
|
+
height: 32,
|
|
1094
|
+
borderRadius: 1,
|
|
1095
|
+
objectFit: 'cover'
|
|
1096
|
+
}
|
|
1097
|
+
}) : null,
|
|
1098
|
+
listing ? /*#__PURE__*/ _jsx(Chip, {
|
|
1099
|
+
size: "small",
|
|
1100
|
+
color: "primary",
|
|
1101
|
+
label: listingArtifactLabel(listing)
|
|
1102
|
+
}) : null,
|
|
1103
|
+
((_ref3 = listing == null ? void 0 : listing.categories) != null ? _ref3 : []).map((entry)=>/*#__PURE__*/ _jsx(Chip, {
|
|
1104
|
+
size: "small",
|
|
1105
|
+
label: entry
|
|
1106
|
+
}, entry)),
|
|
1107
|
+
(listing == null ? void 0 : listing.category) && !((_ref4 = listing == null ? void 0 : listing.categories) != null ? _ref4 : []).length ? /*#__PURE__*/ _jsx(Chip, {
|
|
1108
|
+
size: "small",
|
|
1109
|
+
label: listing.category
|
|
1110
|
+
}) : null,
|
|
1111
|
+
(listing == null ? void 0 : listing.license) ? /*#__PURE__*/ _jsx(Chip, {
|
|
1112
|
+
size: "small",
|
|
1113
|
+
variant: "outlined",
|
|
1114
|
+
label: listing.license
|
|
1115
|
+
}) : null,
|
|
1116
|
+
isPlugin ? realmTrusted ? /*#__PURE__*/ _jsx(Chip, {
|
|
1117
|
+
size: "small",
|
|
1118
|
+
color: "success",
|
|
1119
|
+
label: "Realm-trusted"
|
|
1120
|
+
}) : /*#__PURE__*/ _jsx(Chip, {
|
|
1121
|
+
size: "small",
|
|
1122
|
+
variant: "outlined",
|
|
1123
|
+
label: "Sandboxed"
|
|
1124
|
+
}) : null,
|
|
1125
|
+
(listing == null ? void 0 : listing.reviewStatus) === 'verified' ? /*#__PURE__*/ _jsx(Tooltip, {
|
|
1126
|
+
title: `A human at ${PLATFORM_BRAND_NAME} confirmed ` + 'who this ' + 'publisher is, and that the listing describes ' + 'what the code does. A claim about the ' + 'publisher, not this release — it survives a ' + 'version bump.',
|
|
1127
|
+
children: /*#__PURE__*/ _jsx(Chip, {
|
|
1128
|
+
size: "small",
|
|
1129
|
+
color: "info",
|
|
1130
|
+
label: "Verified publisher"
|
|
1131
|
+
})
|
|
1132
|
+
}) : null,
|
|
1133
|
+
(listing == null ? void 0 : listing.latestVersionReviewState) === 'approved' ? /*#__PURE__*/ _jsx(Tooltip, {
|
|
1134
|
+
title: `A human at ${PLATFORM_BRAND_NAME} read these ` + 'exact bytes — ' + 'the version on offer — against a required ' + 'checklist. Re-earned per version. Not a ' + 'security guarantee: every plugin runs in the ' + 'same sandbox either way.',
|
|
1135
|
+
children: /*#__PURE__*/ _jsx(Chip, {
|
|
1136
|
+
size: "small",
|
|
1137
|
+
color: "success",
|
|
1138
|
+
label: "Reviewed"
|
|
1139
|
+
})
|
|
1140
|
+
}) : null,
|
|
1141
|
+
isPrivateListing(listing != null ? listing : {}) ? /*#__PURE__*/ _jsx(Chip, {
|
|
1142
|
+
size: "small",
|
|
1143
|
+
variant: "outlined",
|
|
1144
|
+
label: "Private"
|
|
1145
|
+
}) : null,
|
|
1146
|
+
/*#__PURE__*/ _jsx(Chip, {
|
|
1147
|
+
size: "small",
|
|
1148
|
+
color: priceUsd > 0 ? 'primary' : 'default',
|
|
1149
|
+
label: priceUsd > 0 ? `$${priceUsd}` : 'Free'
|
|
1150
|
+
}),
|
|
1151
|
+
(listing == null ? void 0 : listing.ratingCount) ? /*#__PURE__*/ _jsxs(Stack, {
|
|
1152
|
+
direction: "row",
|
|
1153
|
+
spacing: 0.5,
|
|
1154
|
+
sx: {
|
|
1155
|
+
alignItems: 'center'
|
|
1156
|
+
},
|
|
1157
|
+
children: [
|
|
1158
|
+
/*#__PURE__*/ _jsx(Rating, {
|
|
1159
|
+
size: "small",
|
|
1160
|
+
readOnly: true,
|
|
1161
|
+
precision: 0.1,
|
|
1162
|
+
value: Number((_listing_ratingAverage = listing.ratingAverage) != null ? _listing_ratingAverage : 0)
|
|
1163
|
+
}),
|
|
1164
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1165
|
+
variant: "caption",
|
|
1166
|
+
color: "text.secondary",
|
|
1167
|
+
children: `${(_listing_ratingAverage1 = listing.ratingAverage) != null ? _listing_ratingAverage1 : 0} (${listing.ratingCount})`
|
|
1168
|
+
})
|
|
1169
|
+
]
|
|
1170
|
+
}) : null,
|
|
1171
|
+
/*#__PURE__*/ _jsx(Tooltip, {
|
|
1172
|
+
title: 'Counted once per install, whether it covers a ' + 'single site or a whole organization — and ' + 'including the publisher’s own. “All time” ' + 'counts installs that have since been removed, ' + 'so it never falls.',
|
|
1173
|
+
children: /*#__PURE__*/ _jsxs(Typography, {
|
|
1174
|
+
variant: "caption",
|
|
1175
|
+
color: "text.secondary",
|
|
1176
|
+
children: [
|
|
1177
|
+
`v${(_ref5 = listing == null ? void 0 : listing.latestVersion) != null ? _ref5 : '…'}`,
|
|
1178
|
+
(installTotals == null ? void 0 : installTotals.installCount) || (installTotals == null ? void 0 : installTotals.activeInstalls) ? ` · ${installTotals.activeInstalls} active ` + `install${installTotals.activeInstalls === 1 ? '' : 's'}` : '',
|
|
1179
|
+
(installTotals == null ? void 0 : installTotals.installCount) ? ` · ${installTotals.installCount} all time` : ''
|
|
1180
|
+
]
|
|
1181
|
+
})
|
|
1182
|
+
})
|
|
1183
|
+
]
|
|
1184
|
+
}),
|
|
1185
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1186
|
+
variant: "body2",
|
|
1187
|
+
color: "text.secondary",
|
|
1188
|
+
children: (_ref6 = listing == null ? void 0 : listing.description) != null ? _ref6 : 'No description provided.'
|
|
1189
|
+
}),
|
|
1190
|
+
isPlugin && !realmTrusted && (listing == null ? void 0 : listing.latestVersionReviewState) !== 'approved' ? /*#__PURE__*/ _jsx(Alert, {
|
|
1191
|
+
severity: "info",
|
|
1192
|
+
children: 'Marketplace plugin: runs sandboxed and cannot ' + 'access your site data directly. Review the ' + 'publisher and docs before installing.'
|
|
1193
|
+
}) : null,
|
|
1194
|
+
isPrivateListing(listing != null ? listing : {}) ? /*#__PURE__*/ _jsx(Alert, {
|
|
1195
|
+
severity: "info",
|
|
1196
|
+
children: 'Private plugin: it is never listed in the ' + 'marketplace, and only your organization’s ' + 'sites can install it. It passes the same ' + 'review as every other plugin.'
|
|
1197
|
+
}) : null,
|
|
1198
|
+
abiIncompatible ? /*#__PURE__*/ _jsx(Alert, {
|
|
1199
|
+
severity: "warning",
|
|
1200
|
+
children: `Built for platform generation ${latestEntry == null ? void 0 : latestEntry.hostAbi}; ` + `this platform runs ${PLUGIN_HOST_ABI_VERSION}. ` + 'It will not load until the publisher ships a ' + 'compatible version.'
|
|
1201
|
+
}) : null,
|
|
1202
|
+
((_ref7 = listing == null ? void 0 : listing.screenshots) != null ? _ref7 : []).length ? /*#__PURE__*/ _jsx(Stack, {
|
|
1203
|
+
direction: "row",
|
|
1204
|
+
spacing: 1,
|
|
1205
|
+
sx: {
|
|
1206
|
+
overflowX: 'auto',
|
|
1207
|
+
pb: 0.5
|
|
1208
|
+
},
|
|
1209
|
+
children: listing.screenshots.map((url)=>// Click to zoom (AGL-869) — the strip thumbnails
|
|
1210
|
+
// are too small to actually evaluate a UI from.
|
|
1211
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
1212
|
+
component: "img",
|
|
1213
|
+
src: url,
|
|
1214
|
+
alt: `${listing == null ? void 0 : listing.displayName} screenshot`,
|
|
1215
|
+
loading: "lazy",
|
|
1216
|
+
onClick: ()=>setLightboxUrl(url),
|
|
1217
|
+
sx: {
|
|
1218
|
+
height: 180,
|
|
1219
|
+
borderRadius: 1,
|
|
1220
|
+
border: 1,
|
|
1221
|
+
borderColor: 'divider',
|
|
1222
|
+
cursor: 'zoom-in'
|
|
1223
|
+
}
|
|
1224
|
+
}, url))
|
|
1225
|
+
}) : null,
|
|
1226
|
+
(listing == null ? void 0 : listing.readme) ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
1227
|
+
children: [
|
|
1228
|
+
/*#__PURE__*/ _jsx(Divider, {}),
|
|
1229
|
+
/*#__PURE__*/ _jsx(ListingReadme, {
|
|
1230
|
+
readme: listing.readme
|
|
1231
|
+
})
|
|
1232
|
+
]
|
|
1233
|
+
}) : null
|
|
1234
|
+
]
|
|
1235
|
+
})
|
|
1236
|
+
})
|
|
1237
|
+
},
|
|
1238
|
+
{
|
|
1239
|
+
size: {
|
|
1240
|
+
xs: 12,
|
|
1241
|
+
md: 8
|
|
1242
|
+
},
|
|
1243
|
+
children: /*#__PURE__*/ _jsx(ListingChangelog, {
|
|
1244
|
+
versions: versions
|
|
1245
|
+
})
|
|
1246
|
+
},
|
|
1247
|
+
{
|
|
1248
|
+
size: {
|
|
1249
|
+
xs: 12,
|
|
1250
|
+
md: 4
|
|
1251
|
+
},
|
|
1252
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
1253
|
+
spacing: 3,
|
|
1254
|
+
children: [
|
|
1255
|
+
/*#__PURE__*/ _jsx(CardDisplay, {
|
|
1256
|
+
header: 'Install',
|
|
1257
|
+
help: pluginDocsHelp('installYourFirstPlugin', {
|
|
1258
|
+
anchor: '#step-4-targeting'
|
|
1259
|
+
}),
|
|
1260
|
+
contentGutterX: true,
|
|
1261
|
+
contentGutterY: true,
|
|
1262
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
1263
|
+
spacing: 1.5,
|
|
1264
|
+
children: [
|
|
1265
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
1266
|
+
direction: "row",
|
|
1267
|
+
spacing: 1,
|
|
1268
|
+
sx: {
|
|
1269
|
+
alignItems: 'baseline'
|
|
1270
|
+
},
|
|
1271
|
+
children: [
|
|
1272
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1273
|
+
variant: "h6",
|
|
1274
|
+
children: priceUsd > 0 ? `$${priceUsd}` : 'Free'
|
|
1275
|
+
}),
|
|
1276
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1277
|
+
variant: "caption",
|
|
1278
|
+
color: "text.secondary",
|
|
1279
|
+
children: `v${(_ref8 = listing == null ? void 0 : listing.latestVersion) != null ? _ref8 : '…'}`
|
|
1280
|
+
})
|
|
1281
|
+
]
|
|
1282
|
+
}),
|
|
1283
|
+
/*#__PURE__*/ _jsx(Divider, {}),
|
|
1284
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
1285
|
+
spacing: 0.75,
|
|
1286
|
+
children: [
|
|
1287
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1288
|
+
variant: "overline",
|
|
1289
|
+
color: "text.secondary",
|
|
1290
|
+
children: "What's included"
|
|
1291
|
+
}),
|
|
1292
|
+
listingInclusions(listing != null ? listing : {}, {
|
|
1293
|
+
reviewedVersion: (listing == null ? void 0 : listing.latestVersionReviewState) === 'approved'
|
|
1294
|
+
}).map((row)=>/*#__PURE__*/ _jsxs(Stack, {
|
|
1295
|
+
direction: "row",
|
|
1296
|
+
spacing: 1,
|
|
1297
|
+
sx: {
|
|
1298
|
+
alignItems: 'flex-start'
|
|
1299
|
+
},
|
|
1300
|
+
children: [
|
|
1301
|
+
/*#__PURE__*/ _jsx(MdiIcon, {
|
|
1302
|
+
path: row.tone === 'included' ? mdiCheckCircleOutline : mdiInformationOutline,
|
|
1303
|
+
size: 0.8,
|
|
1304
|
+
color: row.tone === 'included' ? 'success' : 'disabled'
|
|
1305
|
+
}),
|
|
1306
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1307
|
+
variant: "body2",
|
|
1308
|
+
children: row.label
|
|
1309
|
+
})
|
|
1310
|
+
]
|
|
1311
|
+
}, row.label))
|
|
1312
|
+
]
|
|
1313
|
+
}),
|
|
1314
|
+
/*#__PURE__*/ _jsx(Divider, {}),
|
|
1315
|
+
unreviewedInstall ? /*#__PURE__*/ _jsxs(Alert, {
|
|
1316
|
+
severity: "warning",
|
|
1317
|
+
variant: "outlined",
|
|
1318
|
+
children: [
|
|
1319
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1320
|
+
variant: "body2",
|
|
1321
|
+
children: `No reviewer has approved v${listing == null ? void 0 : listing.latestVersion} yet.`
|
|
1322
|
+
}),
|
|
1323
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1324
|
+
variant: "caption",
|
|
1325
|
+
component: "div",
|
|
1326
|
+
children: 'You can install it because you publish it — ' + 'that is how you test a version before ' + 'submitting it. Nobody else can install it ' + 'until it is approved.'
|
|
1327
|
+
})
|
|
1328
|
+
]
|
|
1329
|
+
}) : null,
|
|
1330
|
+
installed && updateStatus.state !== 'current' && updateStatus.installedVersion ? /*#__PURE__*/ _jsx(Alert, {
|
|
1331
|
+
severity: updateStatus.state === 'update-available' ? 'info' : 'warning',
|
|
1332
|
+
variant: "outlined",
|
|
1333
|
+
children: updateStateLabel(updateStatus)
|
|
1334
|
+
}) : null,
|
|
1335
|
+
orgPluginScope ? /*#__PURE__*/ _jsx(PluginSiteSet, {
|
|
1336
|
+
listing: listing,
|
|
1337
|
+
hosts: hosts != null ? hosts : [],
|
|
1338
|
+
orgInstall: orgInstall,
|
|
1339
|
+
latestVersion: offeredVersion,
|
|
1340
|
+
installPlan: installPlan,
|
|
1341
|
+
uninstall: uninstall,
|
|
1342
|
+
onChanged: ()=>setPinsNonce((current)=>current + 1)
|
|
1343
|
+
}) : null,
|
|
1344
|
+
!orgPluginScope && isPlugin && installed ? /*#__PURE__*/ _jsx(Stack, {
|
|
1345
|
+
spacing: 1,
|
|
1346
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
1347
|
+
direction: "row",
|
|
1348
|
+
spacing: 1,
|
|
1349
|
+
sx: {
|
|
1350
|
+
alignItems: 'flex-start'
|
|
1351
|
+
},
|
|
1352
|
+
children: [
|
|
1353
|
+
/*#__PURE__*/ _jsx(MdiIcon, {
|
|
1354
|
+
path: mdiCheckCircle.path,
|
|
1355
|
+
color: "success",
|
|
1356
|
+
sx: {
|
|
1357
|
+
fontSize: 20,
|
|
1358
|
+
mt: '2px'
|
|
1359
|
+
}
|
|
1360
|
+
}),
|
|
1361
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
1362
|
+
variant: "body2",
|
|
1363
|
+
children: [
|
|
1364
|
+
pluginState.shadowed ? `Installed on this site (v${installedVersion}), ` + 'overriding the organization-wide install.' : pluginState.scope === 'org' ? `Installed for the whole organization ` + `(v${installedVersion}) — available on every site.` : `Installed on this site (v${installedVersion}).`,
|
|
1365
|
+
pluginState.updateAvailable ? ` A newer version (v${offeredVersion}) is available.` : ''
|
|
1366
|
+
]
|
|
1367
|
+
})
|
|
1368
|
+
]
|
|
1369
|
+
})
|
|
1370
|
+
}) : null,
|
|
1371
|
+
orgTargeting && !installed && !(orgPluginScope && orgInstall.installedAnywhere) ? /*#__PURE__*/ _jsxs(Stack, {
|
|
1372
|
+
spacing: 1.5,
|
|
1373
|
+
sx: {
|
|
1374
|
+
maxWidth: 420
|
|
1375
|
+
},
|
|
1376
|
+
children: [
|
|
1377
|
+
/*#__PURE__*/ _jsxs(TextField, {
|
|
1378
|
+
select: true,
|
|
1379
|
+
size: "small",
|
|
1380
|
+
label: "Install to",
|
|
1381
|
+
value: targeting,
|
|
1382
|
+
onChange: (event)=>setTargeting(event.target.value),
|
|
1383
|
+
helperText: targeting === 'all-sites' ? installTargets.includes('org') ? 'Available on every site — including sites you add later.' : `Installs to all ${allHostIds.length} site` + (allHostIds.length === 1 ? '' : 's') + ". New sites won't get it automatically." : 'Installs only to the sites you choose.',
|
|
1384
|
+
children: [
|
|
1385
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
1386
|
+
value: "all-sites",
|
|
1387
|
+
children: 'All sites'
|
|
1388
|
+
}),
|
|
1389
|
+
canSelectSites ? /*#__PURE__*/ _jsx(MenuItem, {
|
|
1390
|
+
value: "selected-sites",
|
|
1391
|
+
children: 'Selected sites'
|
|
1392
|
+
}) : null
|
|
1393
|
+
]
|
|
1394
|
+
}),
|
|
1395
|
+
targeting === 'selected-sites' && canSelectSites ? /*#__PURE__*/ _jsxs(FormControl, {
|
|
1396
|
+
component: "fieldset",
|
|
1397
|
+
error: !selectedHostIds.length,
|
|
1398
|
+
children: [
|
|
1399
|
+
/*#__PURE__*/ _jsx(FormLabel, {
|
|
1400
|
+
component: "legend",
|
|
1401
|
+
sx: {
|
|
1402
|
+
typography: 'caption'
|
|
1403
|
+
},
|
|
1404
|
+
children: 'Sites'
|
|
1405
|
+
}),
|
|
1406
|
+
/*#__PURE__*/ _jsx(FormGroup, {
|
|
1407
|
+
// Scrolls rather than pushing Install off
|
|
1408
|
+
// the page on an org with many sites.
|
|
1409
|
+
sx: {
|
|
1410
|
+
maxHeight: 240,
|
|
1411
|
+
overflowY: 'auto'
|
|
1412
|
+
},
|
|
1413
|
+
children: (hosts != null ? hosts : []).map((host)=>/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
1414
|
+
control: /*#__PURE__*/ _jsx(Checkbox, {
|
|
1415
|
+
size: "small",
|
|
1416
|
+
checked: selectedHostIds.includes(host.id),
|
|
1417
|
+
onChange: (event)=>setSelectedHostIds((current)=>event.target.checked ? [
|
|
1418
|
+
...current,
|
|
1419
|
+
host.id
|
|
1420
|
+
] : current.filter((id)=>id !== host.id))
|
|
1421
|
+
}),
|
|
1422
|
+
label: host.label,
|
|
1423
|
+
slotProps: {
|
|
1424
|
+
typography: {
|
|
1425
|
+
variant: 'body2'
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}, host.id))
|
|
1429
|
+
}),
|
|
1430
|
+
/*#__PURE__*/ _jsx(FormHelperText, {
|
|
1431
|
+
children: selectedHostIds.length ? `${selectedHostIds.length} of ${(hosts != null ? hosts : []).length} selected.` : 'Pick at least one site.'
|
|
1432
|
+
})
|
|
1433
|
+
]
|
|
1434
|
+
}) : null
|
|
1435
|
+
]
|
|
1436
|
+
}) : installTargets.length > 1 && !installed ? /*#__PURE__*/ _jsxs(TextField, {
|
|
1437
|
+
select: true,
|
|
1438
|
+
size: "small",
|
|
1439
|
+
label: "Install to",
|
|
1440
|
+
value: installScope,
|
|
1441
|
+
onChange: (event)=>setInstallScope(event.target.value),
|
|
1442
|
+
helperText: installScope === 'org' ? 'Available to every site in this organization. A site can still override it for itself.' : 'This site only.',
|
|
1443
|
+
sx: {
|
|
1444
|
+
maxWidth: 360
|
|
1445
|
+
},
|
|
1446
|
+
children: [
|
|
1447
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
1448
|
+
value: "org",
|
|
1449
|
+
children: 'This organization — all sites'
|
|
1450
|
+
}),
|
|
1451
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
1452
|
+
value: "host",
|
|
1453
|
+
children: 'This site only'
|
|
1454
|
+
})
|
|
1455
|
+
]
|
|
1456
|
+
}) : null,
|
|
1457
|
+
mustBuy && licensedElsewhere ? /*#__PURE__*/ _jsx(Alert, {
|
|
1458
|
+
severity: "info",
|
|
1459
|
+
sx: {
|
|
1460
|
+
mb: 1
|
|
1461
|
+
},
|
|
1462
|
+
children: 'You already own this in another workspace. A ' + 'license covers one organization, so this one ' + 'needs its own.'
|
|
1463
|
+
}) : null,
|
|
1464
|
+
purchasesTruncated || installedTruncated ? /*#__PURE__*/ _jsx(Alert, {
|
|
1465
|
+
severity: "warning",
|
|
1466
|
+
sx: {
|
|
1467
|
+
mb: 1
|
|
1468
|
+
},
|
|
1469
|
+
children: (purchasesTruncated ? `Checked against the first ${PURCHASE_LOOKUP_CEILING} of your purchases, ordered by id; you have more. ` : `Checked against the first ${COMPONENT_LOOKUP_CEILING} components on this site, ordered by id; there are more. `) + 'If you already own or installed this, it may ' + 'not be reflected here — check your receipts ' + 'before buying again.'
|
|
1470
|
+
}) : null,
|
|
1471
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
1472
|
+
sx: {
|
|
1473
|
+
display: orgPluginScope && orgInstall.installedAnywhere ? 'none' : undefined
|
|
1474
|
+
},
|
|
1475
|
+
children: /*#__PURE__*/ _jsx(Button, {
|
|
1476
|
+
// Full-width and contained (AGL-1005): in a
|
|
1477
|
+
// sidebar card this is the page's primary action,
|
|
1478
|
+
// and "already installed and current" is the only
|
|
1479
|
+
// state where it steps back to an outline.
|
|
1480
|
+
fullWidth: true,
|
|
1481
|
+
variant: upToDate ? 'outlined' : 'contained',
|
|
1482
|
+
color: "primary",
|
|
1483
|
+
disabled: Boolean(upToDate) || !(listing == null ? void 0 : listing.profileId) || // Nothing selected to install to (AGL-773).
|
|
1484
|
+
orgTargeting && !installed && !mustBuy && installPlanSteps.length === 0,
|
|
1485
|
+
onClick: permissions.installPlugins ? ()=>// Buying goes straight to Stripe checkout,
|
|
1486
|
+
// which is its own confirmation; a free or
|
|
1487
|
+
// entitled install confirms its targets
|
|
1488
|
+
// first (AGL-867). Updating a copied
|
|
1489
|
+
// artifact reviews the diff before writing
|
|
1490
|
+
// anything (AGL-1018).
|
|
1491
|
+
mustBuy ? buy(listing) : copiedUpdatable ? openUpdateReview() : setConfirmOpen(true) : ()=>enqueueSnackbar('Your team role does not allow installing from the marketplace', {
|
|
1492
|
+
variant: 'warning',
|
|
1493
|
+
persist: false
|
|
1494
|
+
}),
|
|
1495
|
+
children: upToDate ? `Installed (v${installedVersion})` : copiedUpdatable ? // update would do and writes nothing. Without
|
|
1496
|
+
// an ordered version pair there is no "to
|
|
1497
|
+
// vN" to promise (AGL-1034), so it says the
|
|
1498
|
+
// honest, smaller thing.
|
|
1499
|
+
updateStatus.state === 'update-available' ? `Review update to v${offeredVersion}` : 'Review this version' : artifactInstall ? // draft, so this stays enabled (AGL-789). A
|
|
1500
|
+
// theme has no "again" — there is one per
|
|
1501
|
+
// site, and re-pressing this re-applies it.
|
|
1502
|
+
artifactType === 'theme' ? `Applied (v${installedVersion}) · re-apply` : `${artifactType === 'emailTemplate' ? 'Draft added' : 'Added'} (v${installedVersion}) · add again` : installed ? `Update to v${offeredVersion}` : mustBuy ? licensedElsewhere ? `Buy for this workspace — $${priceUsd}` : `Buy for $${priceUsd}` : unreviewedInstall ? `Install unreviewed v${listing == null ? void 0 : listing.latestVersion} for testing` : orgTargeting || installTargets.length > 1 ? 'Install' : 'Add to this site'
|
|
1503
|
+
})
|
|
1504
|
+
}),
|
|
1505
|
+
!orgPluginScope && isPlugin && installed ? /*#__PURE__*/ _jsx(Button, {
|
|
1506
|
+
fullWidth: true,
|
|
1507
|
+
size: "small",
|
|
1508
|
+
color: "inherit",
|
|
1509
|
+
onClick: ()=>setPendingUninstall({
|
|
1510
|
+
scope: pluginState.scope === 'org' ? 'org' : 'host',
|
|
1511
|
+
targets: resolveUninstallTargets(orgInstall, pluginState.scope === 'org' ? 'org' : 'host', hostId)
|
|
1512
|
+
}),
|
|
1513
|
+
children: pluginState.scope === 'org' ? 'Uninstall org-wide' : 'Uninstall'
|
|
1514
|
+
}) : null
|
|
1515
|
+
]
|
|
1516
|
+
})
|
|
1517
|
+
}),
|
|
1518
|
+
/*#__PURE__*/ _jsx(CardDisplay, {
|
|
1519
|
+
header: 'Publisher',
|
|
1520
|
+
help: pluginDocsHelp('publisherHandbook', {
|
|
1521
|
+
anchor: '#before-your-first-publish'
|
|
1522
|
+
}),
|
|
1523
|
+
contentGutterX: true,
|
|
1524
|
+
contentGutterY: true,
|
|
1525
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
1526
|
+
spacing: 0.5,
|
|
1527
|
+
children: [
|
|
1528
|
+
/*#__PURE__*/ _jsx(Avatar, {
|
|
1529
|
+
src: safePublisherHref(profile == null ? void 0 : profile.avatarUrl),
|
|
1530
|
+
alt: (_ref9 = profile == null ? void 0 : profile.displayName) != null ? _ref9 : 'Publisher',
|
|
1531
|
+
variant: "rounded",
|
|
1532
|
+
sx: {
|
|
1533
|
+
width: 40,
|
|
1534
|
+
height: 40,
|
|
1535
|
+
mb: 0.5
|
|
1536
|
+
},
|
|
1537
|
+
children: String((_ref10 = (_ref11 = profile == null ? void 0 : profile.displayName) != null ? _ref11 : profile == null ? void 0 : profile.handle) != null ? _ref10 : '?').slice(0, 1).toUpperCase()
|
|
1538
|
+
}),
|
|
1539
|
+
/*#__PURE__*/ _jsx(MuiLink, {
|
|
1540
|
+
href: !(listing == null ? void 0 : listing.profileId) ? undefined : orgScoped ? orgSlug ? buildRoute(Route.ORG_MARKETPLACE_PUBLISHER, {
|
|
1541
|
+
orgSlug,
|
|
1542
|
+
// Handle, not id (AGL-1001); the id
|
|
1543
|
+
// still resolves for the beat
|
|
1544
|
+
// before the profile loads.
|
|
1545
|
+
handle: (_ref12 = profile == null ? void 0 : profile.handle) != null ? _ref12 : listing.profileId
|
|
1546
|
+
}) : undefined : orgSlug && subdomain ? buildRoute(Route.ORG_MARKETPLACE_PUBLISHER, {
|
|
1547
|
+
orgSlug,
|
|
1548
|
+
handle: (_ref13 = profile == null ? void 0 : profile.handle) != null ? _ref13 : listing.profileId
|
|
1549
|
+
}) : undefined,
|
|
1550
|
+
color: "primary",
|
|
1551
|
+
underline: "hover",
|
|
1552
|
+
variant: "body2",
|
|
1553
|
+
children: (_ref14 = profile == null ? void 0 : profile.displayName) != null ? _ref14 : (profile == null ? void 0 : profile.handle) ? `@${profile.handle}` : '…'
|
|
1554
|
+
}),
|
|
1555
|
+
(profile == null ? void 0 : profile.handle) ? /*#__PURE__*/ _jsx(Typography, {
|
|
1556
|
+
variant: "caption",
|
|
1557
|
+
color: "text.secondary",
|
|
1558
|
+
children: `@${profile.handle}`
|
|
1559
|
+
}) : null,
|
|
1560
|
+
(profile == null ? void 0 : profile.bio) ? /*#__PURE__*/ _jsx(Typography, {
|
|
1561
|
+
variant: "body2",
|
|
1562
|
+
color: "text.secondary",
|
|
1563
|
+
children: profile.bio
|
|
1564
|
+
}) : null,
|
|
1565
|
+
(()=>{
|
|
1566
|
+
const links = [
|
|
1567
|
+
{
|
|
1568
|
+
key: 'website',
|
|
1569
|
+
label: 'Website',
|
|
1570
|
+
icon: mdiWeb.path,
|
|
1571
|
+
href: safePublisherHref(profile == null ? void 0 : profile.website)
|
|
1572
|
+
},
|
|
1573
|
+
{
|
|
1574
|
+
key: 'support',
|
|
1575
|
+
label: 'Support',
|
|
1576
|
+
icon: mdiLifebuoy.path,
|
|
1577
|
+
href: safePublisherHref(profile == null ? void 0 : profile.supportUrl)
|
|
1578
|
+
},
|
|
1579
|
+
{
|
|
1580
|
+
key: 'github',
|
|
1581
|
+
label: 'GitHub',
|
|
1582
|
+
icon: mdiGithub.path,
|
|
1583
|
+
href: safePublisherHref(profile == null ? void 0 : profile.githubUrl)
|
|
1584
|
+
},
|
|
1585
|
+
{
|
|
1586
|
+
key: 'x',
|
|
1587
|
+
label: 'X',
|
|
1588
|
+
icon: mdiTwitter.path,
|
|
1589
|
+
href: safePublisherHref(profile == null ? void 0 : profile.xUrl)
|
|
1590
|
+
},
|
|
1591
|
+
{
|
|
1592
|
+
key: 'linkedin',
|
|
1593
|
+
label: 'LinkedIn',
|
|
1594
|
+
icon: mdiLinkedin.path,
|
|
1595
|
+
href: safePublisherHref(profile == null ? void 0 : profile.linkedinUrl)
|
|
1596
|
+
}
|
|
1597
|
+
].filter((entry)=>entry.href);
|
|
1598
|
+
const email = typeof (profile == null ? void 0 : profile.supportEmail) === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(profile.supportEmail) ? profile.supportEmail : undefined;
|
|
1599
|
+
if (!links.length && !email) return null;
|
|
1600
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
1601
|
+
direction: "row",
|
|
1602
|
+
spacing: 0.5,
|
|
1603
|
+
children: [
|
|
1604
|
+
email ? /*#__PURE__*/ _jsx(Tooltip, {
|
|
1605
|
+
title: `Support: ${email}`,
|
|
1606
|
+
children: /*#__PURE__*/ _jsx(IconButton, {
|
|
1607
|
+
size: "small",
|
|
1608
|
+
component: "a",
|
|
1609
|
+
href: `mailto:${email}`,
|
|
1610
|
+
"aria-label": `Email support at ${email}`,
|
|
1611
|
+
children: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
1612
|
+
path: mdiEmailOutline.path,
|
|
1613
|
+
fontSize: "small"
|
|
1614
|
+
})
|
|
1615
|
+
})
|
|
1616
|
+
}) : null,
|
|
1617
|
+
links.map((entry)=>/*#__PURE__*/ _jsx(Tooltip, {
|
|
1618
|
+
title: entry.label,
|
|
1619
|
+
children: /*#__PURE__*/ _jsx(IconButton, {
|
|
1620
|
+
size: "small",
|
|
1621
|
+
component: "a",
|
|
1622
|
+
href: entry.href,
|
|
1623
|
+
target: "_blank",
|
|
1624
|
+
rel: "noopener noreferrer",
|
|
1625
|
+
"aria-label": entry.label,
|
|
1626
|
+
children: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
1627
|
+
path: entry.icon,
|
|
1628
|
+
fontSize: "small"
|
|
1629
|
+
})
|
|
1630
|
+
})
|
|
1631
|
+
}, entry.key))
|
|
1632
|
+
]
|
|
1633
|
+
});
|
|
1634
|
+
})()
|
|
1635
|
+
]
|
|
1636
|
+
})
|
|
1637
|
+
}),
|
|
1638
|
+
(listing == null ? void 0 : listing.homepageUrl) || (listing == null ? void 0 : listing.repositoryUrl) ? /*#__PURE__*/ _jsx(CardDisplay, {
|
|
1639
|
+
header: 'Links',
|
|
1640
|
+
help: pluginDocsHelp('publisherHandbook', {
|
|
1641
|
+
anchor: '#authoring-your-listing'
|
|
1642
|
+
}),
|
|
1643
|
+
contentGutterX: true,
|
|
1644
|
+
contentGutterY: true,
|
|
1645
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
1646
|
+
spacing: 0.5,
|
|
1647
|
+
children: [
|
|
1648
|
+
(listing == null ? void 0 : listing.homepageUrl) ? /*#__PURE__*/ _jsx(MuiLink, {
|
|
1649
|
+
href: listing.homepageUrl,
|
|
1650
|
+
target: "_blank",
|
|
1651
|
+
rel: "noopener noreferrer",
|
|
1652
|
+
color: "primary",
|
|
1653
|
+
underline: "hover",
|
|
1654
|
+
variant: "body2",
|
|
1655
|
+
children: 'Homepage'
|
|
1656
|
+
}) : null,
|
|
1657
|
+
(listing == null ? void 0 : listing.repositoryUrl) ? /*#__PURE__*/ _jsx(MuiLink, {
|
|
1658
|
+
href: listing.repositoryUrl,
|
|
1659
|
+
target: "_blank",
|
|
1660
|
+
rel: "noopener noreferrer",
|
|
1661
|
+
color: "primary",
|
|
1662
|
+
underline: "hover",
|
|
1663
|
+
variant: "body2",
|
|
1664
|
+
children: 'Source repository'
|
|
1665
|
+
}) : null
|
|
1666
|
+
]
|
|
1667
|
+
})
|
|
1668
|
+
}) : null,
|
|
1669
|
+
/*#__PURE__*/ _jsx(CardDisplay, {
|
|
1670
|
+
header: 'Version history',
|
|
1671
|
+
help: pluginDocsHelp('publisherHandbook', {
|
|
1672
|
+
anchor: '#shipping-a-new-version'
|
|
1673
|
+
}),
|
|
1674
|
+
contentGutterX: true,
|
|
1675
|
+
contentGutterY: true,
|
|
1676
|
+
children: versions.length ? /*#__PURE__*/ _jsxs(Stack, {
|
|
1677
|
+
spacing: 1,
|
|
1678
|
+
children: [
|
|
1679
|
+
versions.map((entry, index)=>{
|
|
1680
|
+
var _entry_activeInstalls;
|
|
1681
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
1682
|
+
spacing: 0.25,
|
|
1683
|
+
children: [
|
|
1684
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
1685
|
+
direction: "row",
|
|
1686
|
+
spacing: 1,
|
|
1687
|
+
sx: {
|
|
1688
|
+
alignItems: 'center'
|
|
1689
|
+
},
|
|
1690
|
+
children: [
|
|
1691
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1692
|
+
variant: "body2",
|
|
1693
|
+
children: `v${entry.version}`
|
|
1694
|
+
}),
|
|
1695
|
+
index === 0 ? /*#__PURE__*/ _jsx(Chip, {
|
|
1696
|
+
size: "small",
|
|
1697
|
+
label: "Latest"
|
|
1698
|
+
}) : null,
|
|
1699
|
+
entry.trust === 'realm' ? /*#__PURE__*/ _jsx(Chip, {
|
|
1700
|
+
size: "small",
|
|
1701
|
+
color: "success",
|
|
1702
|
+
label: "Realm-trusted"
|
|
1703
|
+
}) : null,
|
|
1704
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1705
|
+
variant: "caption",
|
|
1706
|
+
color: "text.secondary",
|
|
1707
|
+
children: entry.publishedAtMs ? new Date(entry.publishedAtMs).toLocaleDateString() : ''
|
|
1708
|
+
})
|
|
1709
|
+
]
|
|
1710
|
+
}),
|
|
1711
|
+
entry.installCount || entry.activeInstalls ? /*#__PURE__*/ _jsxs(Typography, {
|
|
1712
|
+
variant: "caption",
|
|
1713
|
+
color: "text.secondary",
|
|
1714
|
+
children: [
|
|
1715
|
+
`${(_entry_activeInstalls = entry.activeInstalls) != null ? _entry_activeInstalls : 0} on this version`,
|
|
1716
|
+
entry.installCount ? ` · ${entry.installCount} all time` : ''
|
|
1717
|
+
]
|
|
1718
|
+
}) : null
|
|
1719
|
+
]
|
|
1720
|
+
}, entry.version);
|
|
1721
|
+
}),
|
|
1722
|
+
(installTotals == null ? void 0 : installTotals.untrackedActiveInstalls) || (installTotals == null ? void 0 : installTotals.untrackedInstallCount) ? /*#__PURE__*/ _jsx(Typography, {
|
|
1723
|
+
variant: "caption",
|
|
1724
|
+
color: "text.secondary",
|
|
1725
|
+
children: `${installTotals.untrackedInstallCount} ` + `install${installTotals.untrackedInstallCount === 1 ? '' : 's'} all-time and ${installTotals.untrackedActiveInstalls} still active predate per-version ` + 'tracking, and are not counted above.'
|
|
1726
|
+
}) : null
|
|
1727
|
+
]
|
|
1728
|
+
}) : versionHistory.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
|
|
1729
|
+
variant: "body2",
|
|
1730
|
+
color: "text.secondary",
|
|
1731
|
+
children: `Latest version: v${(_ref15 = listing == null ? void 0 : listing.latestVersion) != null ? _ref15 : '…'}`
|
|
1732
|
+
}) : /*#__PURE__*/ _jsx(Stack, {
|
|
1733
|
+
spacing: 0.5,
|
|
1734
|
+
children: versionHistory.map((entry, index)=>{
|
|
1735
|
+
var _entry_publishedAt;
|
|
1736
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
1737
|
+
direction: "row",
|
|
1738
|
+
spacing: 1,
|
|
1739
|
+
sx: {
|
|
1740
|
+
alignItems: 'center'
|
|
1741
|
+
},
|
|
1742
|
+
children: [
|
|
1743
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1744
|
+
variant: "body2",
|
|
1745
|
+
children: `v${entry.version}`
|
|
1746
|
+
}),
|
|
1747
|
+
index === 0 ? /*#__PURE__*/ _jsx(Chip, {
|
|
1748
|
+
size: "small",
|
|
1749
|
+
label: "Latest"
|
|
1750
|
+
}) : null,
|
|
1751
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
1752
|
+
variant: "caption",
|
|
1753
|
+
color: "text.secondary",
|
|
1754
|
+
children: ((_entry_publishedAt = entry.publishedAt) == null ? void 0 : _entry_publishedAt.toDate) ? entry.publishedAt.toDate().toLocaleDateString() : ''
|
|
1755
|
+
})
|
|
1756
|
+
]
|
|
1757
|
+
}, entry.version);
|
|
1758
|
+
})
|
|
1759
|
+
})
|
|
1760
|
+
}),
|
|
1761
|
+
/*#__PURE__*/ _jsx(ListingReviews, {
|
|
1762
|
+
listingId: listingId,
|
|
1763
|
+
listing: listing
|
|
1764
|
+
}),
|
|
1765
|
+
/*#__PURE__*/ _jsx(ReportTarget, {
|
|
1766
|
+
listingId: listingId,
|
|
1767
|
+
label: String((_ref16 = listing == null ? void 0 : listing.displayName) != null ? _ref16 : 'this listing')
|
|
1768
|
+
})
|
|
1769
|
+
]
|
|
1770
|
+
})
|
|
1771
|
+
}
|
|
1772
|
+
]
|
|
1773
|
+
}),
|
|
1774
|
+
/*#__PURE__*/ _jsxs(Dialog, {
|
|
1775
|
+
open: confirmOpen,
|
|
1776
|
+
onClose: ()=>setConfirmOpen(false),
|
|
1777
|
+
maxWidth: "xs",
|
|
1778
|
+
fullWidth: true,
|
|
1779
|
+
children: [
|
|
1780
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
1781
|
+
children: unreviewedInstall ? 'Install an unreviewed version?' : installed ? 'Update this listing?' : 'Install this listing?'
|
|
1782
|
+
}),
|
|
1783
|
+
/*#__PURE__*/ _jsx(DialogContent, {
|
|
1784
|
+
dividers: true,
|
|
1785
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
1786
|
+
spacing: 1,
|
|
1787
|
+
children: [
|
|
1788
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
1789
|
+
variant: "body2",
|
|
1790
|
+
children: [
|
|
1791
|
+
installed ? 'Update ' : 'Install ',
|
|
1792
|
+
/*#__PURE__*/ _jsx("strong", {
|
|
1793
|
+
children: listing == null ? void 0 : listing.displayName
|
|
1794
|
+
}),
|
|
1795
|
+
listing ? ` (${listingArtifactLabel(listing)}, v${listing == null ? void 0 : listing.latestVersion})` : '',
|
|
1796
|
+
'.'
|
|
1797
|
+
]
|
|
1798
|
+
}),
|
|
1799
|
+
/*#__PURE__*/ _jsxs(Typography, {
|
|
1800
|
+
variant: "body2",
|
|
1801
|
+
color: "text.secondary",
|
|
1802
|
+
children: [
|
|
1803
|
+
'This will be installed to ',
|
|
1804
|
+
/*#__PURE__*/ _jsx("strong", {
|
|
1805
|
+
children: installTargetSummary()
|
|
1806
|
+
}),
|
|
1807
|
+
'.'
|
|
1808
|
+
]
|
|
1809
|
+
}),
|
|
1810
|
+
unreviewedInstall ? /*#__PURE__*/ _jsx(Alert, {
|
|
1811
|
+
severity: "warning",
|
|
1812
|
+
children: `v${listing == null ? void 0 : listing.latestVersion} has not passed review. ` + 'Every site you install it to is publicly reachable — ' + 'there is no staging site here — so unreviewed code ' + 'will serve real visitors until you uninstall it.'
|
|
1813
|
+
}) : null
|
|
1814
|
+
]
|
|
1815
|
+
})
|
|
1816
|
+
}),
|
|
1817
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
1818
|
+
children: [
|
|
1819
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1820
|
+
onClick: ()=>setConfirmOpen(false),
|
|
1821
|
+
children: 'Cancel'
|
|
1822
|
+
}),
|
|
1823
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
1824
|
+
variant: "contained",
|
|
1825
|
+
color: "primary",
|
|
1826
|
+
onClick: runInstall,
|
|
1827
|
+
children: unreviewedInstall ? 'Install for testing' : installed ? 'Update' : 'Install'
|
|
1828
|
+
})
|
|
1829
|
+
]
|
|
1830
|
+
})
|
|
1831
|
+
]
|
|
1832
|
+
}),
|
|
1833
|
+
/*#__PURE__*/ _jsx(ArtifactUpdateDialog, {
|
|
1834
|
+
open: updateOpen,
|
|
1835
|
+
onClose: ()=>setUpdateOpen(false),
|
|
1836
|
+
artifactName: (_ref17 = listing == null ? void 0 : listing.displayName) != null ? _ref17 : 'this listing',
|
|
1837
|
+
preview: updatePreview,
|
|
1838
|
+
loading: updateLoading,
|
|
1839
|
+
onApplyMerge: (takePaths, confirmDestructive)=>void runUpdate('merge', takePaths, confirmDestructive),
|
|
1840
|
+
onApplyCopy: ()=>void runCopy()
|
|
1841
|
+
}),
|
|
1842
|
+
/*#__PURE__*/ _jsx(UninstallImpactDialog, {
|
|
1843
|
+
open: pendingUninstall !== null,
|
|
1844
|
+
listing: listing,
|
|
1845
|
+
scope: (_ref18 = pendingUninstall == null ? void 0 : pendingUninstall.scope) != null ? _ref18 : 'host',
|
|
1846
|
+
targets: (_ref19 = pendingUninstall == null ? void 0 : pendingUninstall.targets) != null ? _ref19 : [],
|
|
1847
|
+
onCancel: ()=>setPendingUninstall(null),
|
|
1848
|
+
onConfirm: async ()=>{
|
|
1849
|
+
const target = pendingUninstall;
|
|
1850
|
+
setPendingUninstall(null);
|
|
1851
|
+
if (!target) return;
|
|
1852
|
+
await uninstall(listing, target.scope);
|
|
1853
|
+
setPinsNonce((nonce)=>nonce + 1);
|
|
1854
|
+
}
|
|
1855
|
+
}),
|
|
1856
|
+
/*#__PURE__*/ _jsx(Dialog, {
|
|
1857
|
+
open: Boolean(lightboxUrl),
|
|
1858
|
+
onClose: ()=>setLightboxUrl(null),
|
|
1859
|
+
maxWidth: "lg",
|
|
1860
|
+
children: lightboxUrl ? /*#__PURE__*/ _jsx(Box, {
|
|
1861
|
+
component: "img",
|
|
1862
|
+
src: lightboxUrl,
|
|
1863
|
+
alt: `${listing == null ? void 0 : listing.displayName} screenshot`,
|
|
1864
|
+
onClick: ()=>setLightboxUrl(null),
|
|
1865
|
+
sx: {
|
|
1866
|
+
display: 'block',
|
|
1867
|
+
maxWidth: '100%',
|
|
1868
|
+
maxHeight: '85vh',
|
|
1869
|
+
cursor: 'zoom-out'
|
|
1870
|
+
}
|
|
1871
|
+
}) : null
|
|
1872
|
+
})
|
|
1873
|
+
]
|
|
1874
|
+
})
|
|
1875
|
+
]
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
export default MarketplaceListingContent;
|
|
1879
|
+
|
|
1880
|
+
//# sourceMappingURL=listing-content.component.js.map
|