@sanity/dashboard 5.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +4 -50
- package/dist/index.d.ts +54 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +605 -0
- package/dist/index.js.map +1 -0
- package/package.json +37 -79
- package/lib/index.d.mts +0 -65
- package/lib/index.d.ts +0 -65
- package/lib/index.esm.js +0 -568
- package/lib/index.esm.js.map +0 -1
- package/lib/index.js +0 -564
- package/lib/index.js.map +0 -1
- package/lib/index.mjs +0 -568
- package/lib/index.mjs.map +0 -1
- package/sanity.json +0 -8
- package/src/components/DashboardLayout.tsx +0 -10
- package/src/components/DashboardWidgetContainer.tsx +0 -69
- package/src/components/NotFoundWidget.tsx +0 -30
- package/src/components/WidgetGroup.tsx +0 -106
- package/src/containers/Dashboard.tsx +0 -19
- package/src/containers/DashboardContext.tsx +0 -8
- package/src/containers/WidgetContainer.tsx +0 -21
- package/src/index.ts +0 -7
- package/src/plugin.tsx +0 -72
- package/src/types.ts +0 -21
- package/src/versionedClient.ts +0 -5
- package/src/widgets/projectInfo/ProjectInfo.tsx +0 -221
- package/src/widgets/projectInfo/index.ts +0 -10
- package/src/widgets/projectInfo/types.ts +0 -28
- package/src/widgets/projectUsers/ProjectUser.tsx +0 -45
- package/src/widgets/projectUsers/ProjectUsers.tsx +0 -148
- package/src/widgets/projectUsers/index.ts +0 -10
- package/src/widgets/sanityTutorials/SanityTutorials.tsx +0 -77
- package/src/widgets/sanityTutorials/Tutorial.tsx +0 -111
- package/src/widgets/sanityTutorials/dataAdapter.ts +0 -49
- package/src/widgets/sanityTutorials/index.ts +0 -10
- package/v2-incompatible.js +0 -11
package/lib/index.mjs
DELETED
|
@@ -1,568 +0,0 @@
|
|
|
1
|
-
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef, createContext, useContext, useMemo, createElement, useState, useEffect, useCallback } from "react";
|
|
3
|
-
import { Card, Box, Heading, Stack, Label, Grid, Text, Code, Button, rem, Flex, Spinner, Container } from "@sanity/ui";
|
|
4
|
-
import { styled, css } from "styled-components";
|
|
5
|
-
import { useClient, useListFormat, UserAvatar, useUserStore, definePlugin } from "sanity";
|
|
6
|
-
import { from } from "rxjs";
|
|
7
|
-
import { switchMap, map } from "rxjs/operators";
|
|
8
|
-
import { RobotIcon, PlayIcon } from "@sanity/icons";
|
|
9
|
-
import imageUrlBuilder from "@sanity/image-url";
|
|
10
|
-
const Root$3 = styled(Card)`
|
|
11
|
-
display: flex;
|
|
12
|
-
flex-direction: column;
|
|
13
|
-
justify-content: stretch;
|
|
14
|
-
height: 100%;
|
|
15
|
-
box-sizing: border-box;
|
|
16
|
-
position: relative;
|
|
17
|
-
`, Header = styled(Card)`
|
|
18
|
-
position: sticky;
|
|
19
|
-
top: 0;
|
|
20
|
-
z-index: 2;
|
|
21
|
-
border-top-left-radius: inherit;
|
|
22
|
-
border-top-right-radius: inherit;
|
|
23
|
-
`, Footer = styled(Card)`
|
|
24
|
-
position: sticky;
|
|
25
|
-
overflow: hidden;
|
|
26
|
-
bottom: 0;
|
|
27
|
-
z-index: 2;
|
|
28
|
-
border-bottom-right-radius: inherit;
|
|
29
|
-
border-bottom-left-radius: inherit;
|
|
30
|
-
margin-top: auto;
|
|
31
|
-
`, Content = styled(Box)`
|
|
32
|
-
position: relative;
|
|
33
|
-
z-index: 1;
|
|
34
|
-
height: stretch;
|
|
35
|
-
min-height: 21.5em;
|
|
36
|
-
|
|
37
|
-
@media (min-width: ${({ theme }) => theme.sanity.media[0]}px) {
|
|
38
|
-
overflow-y: auto;
|
|
39
|
-
outline: none;
|
|
40
|
-
}
|
|
41
|
-
`, DashboardWidgetContainer = forwardRef(function(props, ref) {
|
|
42
|
-
const { header, children, footer } = props;
|
|
43
|
-
return /* @__PURE__ */ jsxs(Root$3, { radius: 3, display: "flex", ref, children: [
|
|
44
|
-
header && /* @__PURE__ */ jsx(Header, { borderBottom: !0, paddingX: 3, paddingY: 4, children: /* @__PURE__ */ jsx(Heading, { size: 1, textOverflow: "ellipsis", children: header }) }),
|
|
45
|
-
children && /* @__PURE__ */ jsx(Content, { children }),
|
|
46
|
-
footer && /* @__PURE__ */ jsx(Footer, { borderTop: !0, children: footer })
|
|
47
|
-
] });
|
|
48
|
-
});
|
|
49
|
-
function useVersionedClient() {
|
|
50
|
-
return useClient({ apiVersion: "2024-08-01" });
|
|
51
|
-
}
|
|
52
|
-
const DashboardContext = createContext({ widgets: [] });
|
|
53
|
-
function useDashboardConfig() {
|
|
54
|
-
return useContext(DashboardContext);
|
|
55
|
-
}
|
|
56
|
-
function WidgetContainer(props) {
|
|
57
|
-
const config = useDashboardConfig(), layout = useMemo(
|
|
58
|
-
() => ({
|
|
59
|
-
...props.layout || {},
|
|
60
|
-
...config.layout || {}
|
|
61
|
-
}),
|
|
62
|
-
[props.layout, config.layout]
|
|
63
|
-
);
|
|
64
|
-
return /* @__PURE__ */ jsx(Card, { shadow: 1, "data-width": layout.width, "data-height": layout.height, children: createElement(props.component, {}) });
|
|
65
|
-
}
|
|
66
|
-
function isUrl(url) {
|
|
67
|
-
return url && /^https?:\/\//.test(`${url}`);
|
|
68
|
-
}
|
|
69
|
-
function getGraphQLUrl(projectId, dataset) {
|
|
70
|
-
return `https://${projectId}.api.sanity.io/v1/graphql/${dataset}/default`;
|
|
71
|
-
}
|
|
72
|
-
function getGroqUrl(projectId, dataset) {
|
|
73
|
-
return `https://${projectId}.api.sanity.io/v1/groq/${dataset}`;
|
|
74
|
-
}
|
|
75
|
-
function getManageUrl(projectId) {
|
|
76
|
-
return `https://manage.sanity.io/projects/${projectId}`;
|
|
77
|
-
}
|
|
78
|
-
const NO_EXPERIMENTAL = [], NO_DATA = [];
|
|
79
|
-
function ProjectInfo(props) {
|
|
80
|
-
const { __experimental_before = NO_EXPERIMENTAL, data = NO_DATA } = props, [studioApps, setStudioApps] = useState(), [graphQLApi, setGraphQLApi] = useState(), versionedClient = useVersionedClient(), { projectId = "unknown", dataset = "unknown" } = versionedClient.config();
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
const subscriptions = [];
|
|
83
|
-
return subscriptions.push(
|
|
84
|
-
versionedClient.observable.request({ uri: "/user-applications", tag: "dashboard.project-info" }).subscribe({
|
|
85
|
-
next: (result) => setStudioApps(result.filter((app) => app.type === "studio")),
|
|
86
|
-
error: (error) => {
|
|
87
|
-
console.error("Error while resolving user applications", error), setStudioApps({
|
|
88
|
-
error: "Something went wrong while resolving user applications. See console."
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
})
|
|
92
|
-
), subscriptions.push(
|
|
93
|
-
versionedClient.observable.request({
|
|
94
|
-
method: "HEAD",
|
|
95
|
-
uri: `/graphql/${dataset}/default`,
|
|
96
|
-
tag: "dashboard.project-info.graphql-api"
|
|
97
|
-
}).subscribe({
|
|
98
|
-
next: () => setGraphQLApi(getGraphQLUrl(projectId, dataset)),
|
|
99
|
-
error: (error) => {
|
|
100
|
-
error.statusCode === 404 ? setGraphQLApi(void 0) : (console.error("Error while looking for graphQLApi", error), setGraphQLApi({
|
|
101
|
-
error: "Something went wrong while looking up graphQLApi. See console."
|
|
102
|
-
}));
|
|
103
|
-
}
|
|
104
|
-
})
|
|
105
|
-
), () => {
|
|
106
|
-
subscriptions.forEach((s) => s.unsubscribe());
|
|
107
|
-
};
|
|
108
|
-
}, [dataset, projectId, versionedClient, setGraphQLApi]);
|
|
109
|
-
const assembleTableRows = useMemo(() => {
|
|
110
|
-
let result = [
|
|
111
|
-
{
|
|
112
|
-
title: "Sanity project",
|
|
113
|
-
rows: [
|
|
114
|
-
{ title: "Project ID", value: projectId },
|
|
115
|
-
{ title: "Dataset", value: dataset }
|
|
116
|
-
]
|
|
117
|
-
}
|
|
118
|
-
];
|
|
119
|
-
const apps = data.filter((item) => item.category === "apps");
|
|
120
|
-
(Array.isArray(studioApps) ? studioApps : []).forEach((app) => {
|
|
121
|
-
apps.push({
|
|
122
|
-
title: app.title || "Studio",
|
|
123
|
-
value: app.urlType === "internal" ? `https://${app.appHost}.sanity.studio` : app.appHost
|
|
124
|
-
});
|
|
125
|
-
}), apps.length > 0 && (result = result.concat([{ title: "Apps", rows: apps }])), result = result.concat(
|
|
126
|
-
[
|
|
127
|
-
{
|
|
128
|
-
title: "APIs",
|
|
129
|
-
rows: [
|
|
130
|
-
{ title: "GROQ", value: getGroqUrl(projectId, dataset) },
|
|
131
|
-
{
|
|
132
|
-
title: "GraphQL",
|
|
133
|
-
value: (typeof graphQLApi == "object" ? "Error" : graphQLApi) ?? "Not deployed"
|
|
134
|
-
}
|
|
135
|
-
]
|
|
136
|
-
}
|
|
137
|
-
],
|
|
138
|
-
data.filter((item) => item.category === "apis")
|
|
139
|
-
);
|
|
140
|
-
const otherStuff = {};
|
|
141
|
-
return data.forEach((item) => {
|
|
142
|
-
item.category && item.category !== "apps" && item.category !== "apis" && (otherStuff[item.category] || (otherStuff[item.category] = []), otherStuff[item.category].push(item));
|
|
143
|
-
}), Object.keys(otherStuff).forEach((category) => {
|
|
144
|
-
result.push({ title: category, rows: otherStuff[category] });
|
|
145
|
-
}), result;
|
|
146
|
-
}, [graphQLApi, studioApps, projectId, dataset, data]);
|
|
147
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
148
|
-
__experimental_before.map((widgetConfig, idx) => /* @__PURE__ */ jsx(WidgetContainer, { ...widgetConfig }, idx)),
|
|
149
|
-
/* @__PURE__ */ jsx(Box, { height: "fill", marginTop: __experimental_before?.length > 0 ? 4 : 0, children: /* @__PURE__ */ jsx(
|
|
150
|
-
DashboardWidgetContainer,
|
|
151
|
-
{
|
|
152
|
-
footer: /* @__PURE__ */ jsx(
|
|
153
|
-
Button,
|
|
154
|
-
{
|
|
155
|
-
style: { width: "100%" },
|
|
156
|
-
paddingX: 2,
|
|
157
|
-
paddingY: 4,
|
|
158
|
-
mode: "bleed",
|
|
159
|
-
tone: "primary",
|
|
160
|
-
text: "Manage project",
|
|
161
|
-
as: "a",
|
|
162
|
-
href: getManageUrl(projectId)
|
|
163
|
-
}
|
|
164
|
-
),
|
|
165
|
-
children: /* @__PURE__ */ jsx(
|
|
166
|
-
Card,
|
|
167
|
-
{
|
|
168
|
-
paddingY: 4,
|
|
169
|
-
radius: 2,
|
|
170
|
-
role: "table",
|
|
171
|
-
"aria-label": "Project info",
|
|
172
|
-
"aria-describedby": "project_info_table",
|
|
173
|
-
children: /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
|
|
174
|
-
/* @__PURE__ */ jsx(Box, { paddingX: 3, as: "header", children: /* @__PURE__ */ jsx(Heading, { size: 1, as: "h2", id: "project_info_table", children: "Project info" }) }),
|
|
175
|
-
assembleTableRows.map((item) => !item || !item.rows ? null : /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
176
|
-
/* @__PURE__ */ jsx(Card, { borderBottom: !0, padding: 3, children: /* @__PURE__ */ jsx(Label, { size: 0, muted: !0, role: "columnheader", children: item.title }) }),
|
|
177
|
-
/* @__PURE__ */ jsx(Stack, { space: 4, paddingX: 3, role: "rowgroup", children: item.rows.map((row) => /* @__PURE__ */ jsxs(Grid, { columns: 2, role: "row", children: [
|
|
178
|
-
/* @__PURE__ */ jsx(Text, { weight: "medium", role: "rowheader", children: row.title }),
|
|
179
|
-
typeof row.value == "object" && /* @__PURE__ */ jsx(Text, { size: 1, children: row.value?.error }),
|
|
180
|
-
typeof row.value == "string" && /* @__PURE__ */ jsx(Fragment, { children: isUrl(row.value) ? /* @__PURE__ */ jsx(Text, { size: 1, role: "cell", style: { wordBreak: "break-word" }, children: /* @__PURE__ */ jsx("a", { href: row.value, children: row.value }) }) : /* @__PURE__ */ jsx(Code, { size: 1, role: "cell", style: { wordBreak: "break-word" }, children: row.value }) })
|
|
181
|
-
] }, `${row.value}-${row.title}`)) })
|
|
182
|
-
] }, item.title))
|
|
183
|
-
] })
|
|
184
|
-
}
|
|
185
|
-
)
|
|
186
|
-
}
|
|
187
|
-
) })
|
|
188
|
-
] });
|
|
189
|
-
}
|
|
190
|
-
function projectInfoWidget(config) {
|
|
191
|
-
return {
|
|
192
|
-
name: "project-info",
|
|
193
|
-
component: ProjectInfo,
|
|
194
|
-
layout: config?.layout ?? { width: "medium" }
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
const Root$2 = styled(Flex)`
|
|
198
|
-
height: ${rem(33)}; // 33 = PREVIEW_SIZES.default.media.height
|
|
199
|
-
box-sizing: content-box;
|
|
200
|
-
`;
|
|
201
|
-
function ProjectUser({ user, isRobot, roles }) {
|
|
202
|
-
const listFormat = useListFormat({ style: "narrow" });
|
|
203
|
-
return /* @__PURE__ */ jsx(Root$2, { align: "center", children: /* @__PURE__ */ jsxs(Flex, { align: "center", flex: 1, gap: 2, children: [
|
|
204
|
-
/* @__PURE__ */ jsx(Box, { flex: "none", children: isRobot ? /* @__PURE__ */ jsx(Text, { size: 2, children: /* @__PURE__ */ jsx(RobotIcon, {}) }) : /* @__PURE__ */ jsx(UserAvatar, { user }) }),
|
|
205
|
-
/* @__PURE__ */ jsxs(Stack, { flex: 1, space: 2, children: [
|
|
206
|
-
/* @__PURE__ */ jsx(Text, { size: 1, style: { color: "inherit" }, textOverflow: "ellipsis", weight: "medium", children: user.displayName }),
|
|
207
|
-
/* @__PURE__ */ jsx(Text, { muted: !0, size: 1, textOverflow: "ellipsis", children: listFormat.format(roles) })
|
|
208
|
-
] })
|
|
209
|
-
] }) });
|
|
210
|
-
}
|
|
211
|
-
function getInviteUrl(projectId) {
|
|
212
|
-
return `https://manage.sanity.io/projects/${projectId}/members`;
|
|
213
|
-
}
|
|
214
|
-
function ProjectUsers() {
|
|
215
|
-
const [project, setProject] = useState(), [users, setUsers] = useState(), [error, setError] = useState(), userStore = useUserStore(), versionedClient = useVersionedClient(), fetchData = useCallback(() => {
|
|
216
|
-
const { projectId } = versionedClient.config(), subscription = versionedClient.observable.request({
|
|
217
|
-
uri: `/projects/${projectId}`,
|
|
218
|
-
tag: "dashboard.project-users"
|
|
219
|
-
}).pipe(
|
|
220
|
-
switchMap(
|
|
221
|
-
(_project) => from(userStore.getUsers(_project.members.map((mem) => mem.id))).pipe(
|
|
222
|
-
map((_users) => ({ project: _project, users: _users }))
|
|
223
|
-
)
|
|
224
|
-
)
|
|
225
|
-
).subscribe({
|
|
226
|
-
next: ({ users: _users, project: _project }) => {
|
|
227
|
-
setProject(_project), setUsers(
|
|
228
|
-
(Array.isArray(_users) ? _users : [_users]).sort(
|
|
229
|
-
(userA, userB) => sortUsersByRobotStatus(userA, userB, _project)
|
|
230
|
-
)
|
|
231
|
-
);
|
|
232
|
-
},
|
|
233
|
-
error: (e) => setError(e)
|
|
234
|
-
});
|
|
235
|
-
return () => subscription.unsubscribe();
|
|
236
|
-
}, [userStore, versionedClient]);
|
|
237
|
-
useEffect(() => fetchData(), [fetchData]);
|
|
238
|
-
const handleRetryFetch = useCallback(() => fetchData(), [fetchData]), isLoading = !users || !project;
|
|
239
|
-
return error ? /* @__PURE__ */ jsx(DashboardWidgetContainer, { header: "Project users", children: /* @__PURE__ */ jsx(Box, { padding: 4, children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
240
|
-
"Something went wrong while fetching data. You could",
|
|
241
|
-
" ",
|
|
242
|
-
/* @__PURE__ */ jsx("a", { onClick: handleRetryFetch, title: "Retry users fetch", style: { cursor: "pointer" }, children: "retry" }),
|
|
243
|
-
"..?"
|
|
244
|
-
] }) }) }) : /* @__PURE__ */ jsxs(
|
|
245
|
-
DashboardWidgetContainer,
|
|
246
|
-
{
|
|
247
|
-
header: "Project users",
|
|
248
|
-
footer: /* @__PURE__ */ jsx(
|
|
249
|
-
Button,
|
|
250
|
-
{
|
|
251
|
-
style: { width: "100%" },
|
|
252
|
-
paddingX: 2,
|
|
253
|
-
paddingY: 4,
|
|
254
|
-
mode: "bleed",
|
|
255
|
-
tone: "primary",
|
|
256
|
-
text: "Manage members",
|
|
257
|
-
as: "a",
|
|
258
|
-
loading: isLoading,
|
|
259
|
-
href: isLoading ? void 0 : getInviteUrl(project.id)
|
|
260
|
-
}
|
|
261
|
-
),
|
|
262
|
-
children: [
|
|
263
|
-
isLoading && /* @__PURE__ */ jsx(Box, { paddingY: 5, paddingX: 2, children: /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
|
|
264
|
-
/* @__PURE__ */ jsx(Text, { align: "center", muted: !0, size: 1, children: /* @__PURE__ */ jsx(Spinner, {}) }),
|
|
265
|
-
/* @__PURE__ */ jsx(Text, { align: "center", size: 1, muted: !0, children: "Loading items\u2026" })
|
|
266
|
-
] }) }),
|
|
267
|
-
!isLoading && /* @__PURE__ */ jsx(Stack, { space: 3, padding: 3, children: users?.map((user) => {
|
|
268
|
-
const membership = project.members.find((member) => member.id === user.id);
|
|
269
|
-
return /* @__PURE__ */ jsx(
|
|
270
|
-
ProjectUser,
|
|
271
|
-
{
|
|
272
|
-
user,
|
|
273
|
-
isRobot: membership?.isRobot ?? !1,
|
|
274
|
-
roles: membership?.roles.map((role) => role.title) || []
|
|
275
|
-
},
|
|
276
|
-
user.id
|
|
277
|
-
);
|
|
278
|
-
}) })
|
|
279
|
-
]
|
|
280
|
-
}
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
function sortUsersByRobotStatus(userA, userB, project) {
|
|
284
|
-
const { members } = project, membershipA = members.find((member) => member.id === userA?.id), membershipB = members.find((member) => member.id === userB?.id);
|
|
285
|
-
return membershipA?.isRobot === membershipB?.isRobot ? (membershipA?.createdAt || "") > (membershipB?.createdAt || "") ? 1 : -1 : membershipA?.isRobot ? 1 : -1;
|
|
286
|
-
}
|
|
287
|
-
function projectUsersWidget(config) {
|
|
288
|
-
return {
|
|
289
|
-
name: "project-info",
|
|
290
|
-
component: ProjectUsers,
|
|
291
|
-
layout: config?.layout
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
const PlayIconBox = styled(Box)`
|
|
295
|
-
position: absolute;
|
|
296
|
-
top: 50%;
|
|
297
|
-
left: 50%;
|
|
298
|
-
transform: translate(-50%, -50%);
|
|
299
|
-
|
|
300
|
-
&:before {
|
|
301
|
-
content: '';
|
|
302
|
-
position: absolute;
|
|
303
|
-
top: 50%;
|
|
304
|
-
left: 50%;
|
|
305
|
-
transform: translate(-50%, -50%);
|
|
306
|
-
width: 2.75em;
|
|
307
|
-
height: 2.75em;
|
|
308
|
-
border-radius: 50%;
|
|
309
|
-
background: ${({ theme }) => theme.sanity.color.card.enabled.bg};
|
|
310
|
-
opacity: 0.75;
|
|
311
|
-
}
|
|
312
|
-
`, Root$1 = styled(Flex)`
|
|
313
|
-
&:hover {
|
|
314
|
-
${PlayIconBox} {
|
|
315
|
-
&:before {
|
|
316
|
-
opacity: 1;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
`, PosterCard = styled(Card)`
|
|
321
|
-
width: 100%;
|
|
322
|
-
padding-bottom: calc(9 / 16 * 100%);
|
|
323
|
-
position: relative;
|
|
324
|
-
`, Poster = styled.img`
|
|
325
|
-
position: absolute;
|
|
326
|
-
top: 0;
|
|
327
|
-
left: 0;
|
|
328
|
-
height: 100%;
|
|
329
|
-
width: 100%;
|
|
330
|
-
object-fit: cover;
|
|
331
|
-
display: block;
|
|
332
|
-
|
|
333
|
-
&:not([src]) {
|
|
334
|
-
display: none;
|
|
335
|
-
}
|
|
336
|
-
`;
|
|
337
|
-
function Tutorial(props) {
|
|
338
|
-
const { title, posterURL, showPlayIcon, href, presenterName, presenterSubtitle } = props;
|
|
339
|
-
return /* @__PURE__ */ jsx(Root$1, { flex: 1, children: /* @__PURE__ */ jsx(
|
|
340
|
-
Card,
|
|
341
|
-
{
|
|
342
|
-
sizing: "border",
|
|
343
|
-
flex: 1,
|
|
344
|
-
padding: 2,
|
|
345
|
-
radius: 2,
|
|
346
|
-
as: "a",
|
|
347
|
-
href,
|
|
348
|
-
target: "_blank",
|
|
349
|
-
rel: "noopener noreferrer",
|
|
350
|
-
style: { position: "relative" },
|
|
351
|
-
children: /* @__PURE__ */ jsxs(Flex, { direction: "column", style: { height: "100%" }, children: [
|
|
352
|
-
posterURL && /* @__PURE__ */ jsxs(PosterCard, { marginBottom: 1, children: [
|
|
353
|
-
/* @__PURE__ */ jsx(Poster, { src: posterURL }),
|
|
354
|
-
showPlayIcon && /* @__PURE__ */ jsx(PlayIconBox, { display: "flex", children: /* @__PURE__ */ jsx(Text, { align: "center", children: /* @__PURE__ */ jsx(PlayIcon, {}) }) })
|
|
355
|
-
] }),
|
|
356
|
-
/* @__PURE__ */ jsxs(Flex, { direction: "column", justify: "space-between", paddingY: 2, flex: 1, children: [
|
|
357
|
-
/* @__PURE__ */ jsx(Heading, { as: "h3", size: 1, children: title }),
|
|
358
|
-
/* @__PURE__ */ jsx(Box, { marginTop: 4, children: /* @__PURE__ */ jsxs(Stack, { space: 2, flex: 1, children: [
|
|
359
|
-
/* @__PURE__ */ jsx(Text, { size: 1, children: presenterName }),
|
|
360
|
-
/* @__PURE__ */ jsx(Text, { size: 0, style: { opacity: 0.7 }, children: presenterSubtitle })
|
|
361
|
-
] }) })
|
|
362
|
-
] })
|
|
363
|
-
] })
|
|
364
|
-
}
|
|
365
|
-
) });
|
|
366
|
-
}
|
|
367
|
-
const tutorialsProjectConfig = {
|
|
368
|
-
projectId: "3do82whm",
|
|
369
|
-
dataset: "next"
|
|
370
|
-
};
|
|
371
|
-
function useDataAdapter() {
|
|
372
|
-
const versionedClient = useVersionedClient();
|
|
373
|
-
return useMemo(
|
|
374
|
-
() => ({
|
|
375
|
-
getFeed: (templateRepoId) => {
|
|
376
|
-
const uri = templateRepoId ? `/addons/dashboard?templateRepoId=${templateRepoId}` : "/addons/dashboard";
|
|
377
|
-
return versionedClient.observable.request({
|
|
378
|
-
uri,
|
|
379
|
-
tag: "dashboard.sanity-tutorials",
|
|
380
|
-
withCredentials: !1
|
|
381
|
-
});
|
|
382
|
-
},
|
|
383
|
-
urlBuilder: imageUrlBuilder(tutorialsProjectConfig)
|
|
384
|
-
}),
|
|
385
|
-
[versionedClient]
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
function createUrl(slug, type) {
|
|
389
|
-
return type === "tutorial" ? `https://www.sanity.io/docs/tutorials/${slug.current}` : type === "guide" ? `https://www.sanity.io/docs/guides/${slug.current}` : !1;
|
|
390
|
-
}
|
|
391
|
-
function SanityTutorials(props) {
|
|
392
|
-
const { templateRepoId } = props, [feedItems, setFeedItems] = useState([]), { getFeed, urlBuilder } = useDataAdapter();
|
|
393
|
-
return useEffect(() => {
|
|
394
|
-
const subscription = getFeed(templateRepoId).subscribe((response) => {
|
|
395
|
-
setFeedItems(response.items);
|
|
396
|
-
});
|
|
397
|
-
return () => {
|
|
398
|
-
subscription.unsubscribe();
|
|
399
|
-
};
|
|
400
|
-
}, [setFeedItems, getFeed, templateRepoId]), /* @__PURE__ */ jsx(DashboardWidgetContainer, { header: "Learn about Sanity", children: /* @__PURE__ */ jsx(Flex, { as: "ul", overflow: "auto", align: "stretch", paddingY: 2, children: feedItems?.map((feedItem, index) => {
|
|
401
|
-
if (!feedItem.title || !feedItem.guideOrTutorial && !feedItem.externalLink)
|
|
402
|
-
return null;
|
|
403
|
-
const presenter = feedItem.presenter || feedItem.guideOrTutorial?.presenter || {}, subtitle = feedItem.category, { guideOrTutorial = {} } = feedItem, href = (guideOrTutorial.slug ? createUrl(guideOrTutorial.slug, guideOrTutorial._type) : feedItem.externalLink) || feedItem.externalLink;
|
|
404
|
-
return /* @__PURE__ */ jsx(
|
|
405
|
-
Flex,
|
|
406
|
-
{
|
|
407
|
-
as: "li",
|
|
408
|
-
paddingRight: index < feedItems?.length - 1 ? 1 : 3,
|
|
409
|
-
paddingLeft: index === 0 ? 3 : 0,
|
|
410
|
-
align: "stretch",
|
|
411
|
-
style: { minWidth: 272, width: "30%" },
|
|
412
|
-
children: /* @__PURE__ */ jsx(
|
|
413
|
-
Tutorial,
|
|
414
|
-
{
|
|
415
|
-
title: feedItem.title,
|
|
416
|
-
href: href ?? "",
|
|
417
|
-
presenterName: presenter.name,
|
|
418
|
-
presenterSubtitle: subtitle,
|
|
419
|
-
showPlayIcon: feedItem.hasVideo,
|
|
420
|
-
posterURL: feedItem.poster ? urlBuilder.image(feedItem.poster).height(360).url() : void 0
|
|
421
|
-
}
|
|
422
|
-
)
|
|
423
|
-
},
|
|
424
|
-
feedItem._id
|
|
425
|
-
);
|
|
426
|
-
}) }) });
|
|
427
|
-
}
|
|
428
|
-
function sanityTutorialsWidget(config) {
|
|
429
|
-
return {
|
|
430
|
-
name: "sanity-tutorials",
|
|
431
|
-
component: SanityTutorials,
|
|
432
|
-
layout: config?.layout ?? { width: "full" }
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
function DashboardLayout(props) {
|
|
436
|
-
return /* @__PURE__ */ jsx(Container, { width: 4, padding: 4, sizing: "border", style: { height: "100%", overflowY: "auto" }, children: props.children });
|
|
437
|
-
}
|
|
438
|
-
const media = {
|
|
439
|
-
small: (...args) => css`
|
|
440
|
-
@media (min-width: ${({ theme }) => theme.sanity.media[0]}px) {
|
|
441
|
-
${css(...args)}
|
|
442
|
-
}
|
|
443
|
-
`,
|
|
444
|
-
medium: (...args) => css`
|
|
445
|
-
@media (min-width: ${({ theme }) => theme.sanity.media[2]}px) {
|
|
446
|
-
${css(...args)}
|
|
447
|
-
}
|
|
448
|
-
`
|
|
449
|
-
}, Root = styled(Grid)`
|
|
450
|
-
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
451
|
-
|
|
452
|
-
& > div {
|
|
453
|
-
overflow: hidden;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
& > div[data-width='medium'] {
|
|
457
|
-
${media.small`
|
|
458
|
-
grid-column: span 2;
|
|
459
|
-
`}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
& > div[data-width='large'] {
|
|
463
|
-
${media.small`
|
|
464
|
-
grid-column: span 2;
|
|
465
|
-
`}
|
|
466
|
-
|
|
467
|
-
${media.medium`
|
|
468
|
-
grid-column: span 3;
|
|
469
|
-
`}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
& > div[data-width='full'] {
|
|
473
|
-
${media.small`
|
|
474
|
-
grid-column: 1 / -1;
|
|
475
|
-
`}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
& > div[data-height='medium'] {
|
|
479
|
-
${media.small`
|
|
480
|
-
grid-row: span 2;
|
|
481
|
-
`}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
& > div[data-height='large'] {
|
|
485
|
-
${media.small`
|
|
486
|
-
grid-row: span 2;
|
|
487
|
-
`}
|
|
488
|
-
|
|
489
|
-
${media.medium`
|
|
490
|
-
grid-row: span 3;
|
|
491
|
-
`}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
& > div[data-height='full'] {
|
|
495
|
-
${media.medium`
|
|
496
|
-
grid-row: 1 / -1;
|
|
497
|
-
`}
|
|
498
|
-
}
|
|
499
|
-
`, NO_WIDGETS = [], NO_LAYOUT = {};
|
|
500
|
-
function WidgetGroup(props) {
|
|
501
|
-
const {
|
|
502
|
-
config: { layout = NO_LAYOUT, widgets = NO_WIDGETS }
|
|
503
|
-
} = props;
|
|
504
|
-
return /* @__PURE__ */ jsxs(
|
|
505
|
-
Root,
|
|
506
|
-
{
|
|
507
|
-
autoFlow: "row dense",
|
|
508
|
-
"data-width": layout.width || "auto",
|
|
509
|
-
"data-height": layout.height || "auto",
|
|
510
|
-
gap: 4,
|
|
511
|
-
children: [
|
|
512
|
-
widgets.length ? null : /* @__PURE__ */ jsx(Card, { padding: 4, shadow: 1, tone: "primary", children: /* @__PURE__ */ jsx(Text, { align: "center", children: "Add some widgets to populate this space." }) }),
|
|
513
|
-
widgets.map((widgetConfig, index) => widgetConfig.type === "__experimental_group" ? /* @__PURE__ */ jsx(WidgetGroup, { config: widgetConfig }, index) : widgetConfig.component ? /* @__PURE__ */ jsx(WidgetContainer, { ...widgetConfig }, index) : /* @__PURE__ */ jsxs(Box, { children: [
|
|
514
|
-
widgetConfig.name,
|
|
515
|
-
" is missing widget component"
|
|
516
|
-
] }, index))
|
|
517
|
-
]
|
|
518
|
-
}
|
|
519
|
-
);
|
|
520
|
-
}
|
|
521
|
-
function Dashboard({ config }) {
|
|
522
|
-
return config ? /* @__PURE__ */ jsx(DashboardContext.Provider, { value: config, children: /* @__PURE__ */ jsx(DashboardLayout, { children: /* @__PURE__ */ jsx(WidgetGroup, { config }) }) }) : null;
|
|
523
|
-
}
|
|
524
|
-
const strokeStyle = {
|
|
525
|
-
stroke: "currentColor",
|
|
526
|
-
strokeWidth: 1.2
|
|
527
|
-
}, DashboardIcon = () => /* @__PURE__ */ jsxs(
|
|
528
|
-
"svg",
|
|
529
|
-
{
|
|
530
|
-
"data-sanity-icon": !0,
|
|
531
|
-
viewBox: "0 0 25 25",
|
|
532
|
-
fill: "none",
|
|
533
|
-
xmlns: "http://www.w3.org/2000/svg",
|
|
534
|
-
preserveAspectRatio: "xMidYMid",
|
|
535
|
-
width: "1em",
|
|
536
|
-
height: "1em",
|
|
537
|
-
children: [
|
|
538
|
-
/* @__PURE__ */ jsx("path", { d: "M19.5 19.5H5.5V5.5H19.5V19.5Z", style: strokeStyle }),
|
|
539
|
-
/* @__PURE__ */ jsx("path", { d: "M5.5 12.5H19.5", style: strokeStyle }),
|
|
540
|
-
/* @__PURE__ */ jsx("path", { d: "M14.5 19.5V12.5M10.5 12.5V5.5", style: strokeStyle })
|
|
541
|
-
]
|
|
542
|
-
}
|
|
543
|
-
), dashboardTool = definePlugin((config = {}) => {
|
|
544
|
-
const pluginConfig = {
|
|
545
|
-
layout: config.defaultLayout ?? {},
|
|
546
|
-
widgets: config.widgets ?? []
|
|
547
|
-
}, title = config.title ?? "Dashboard", name = config.name ?? "dashboard", icon = config.icon ?? DashboardIcon;
|
|
548
|
-
return {
|
|
549
|
-
name: "dashboard",
|
|
550
|
-
tools: (prev, context) => [
|
|
551
|
-
...prev,
|
|
552
|
-
{
|
|
553
|
-
title,
|
|
554
|
-
name,
|
|
555
|
-
icon,
|
|
556
|
-
component: () => /* @__PURE__ */ jsx(Dashboard, { config: pluginConfig })
|
|
557
|
-
}
|
|
558
|
-
]
|
|
559
|
-
};
|
|
560
|
-
});
|
|
561
|
-
export {
|
|
562
|
-
DashboardWidgetContainer,
|
|
563
|
-
dashboardTool,
|
|
564
|
-
projectInfoWidget,
|
|
565
|
-
projectUsersWidget,
|
|
566
|
-
sanityTutorialsWidget
|
|
567
|
-
};
|
|
568
|
-
//# sourceMappingURL=index.mjs.map
|
package/lib/index.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../src/components/DashboardWidgetContainer.tsx","../src/versionedClient.ts","../src/containers/DashboardContext.tsx","../src/containers/WidgetContainer.tsx","../src/widgets/projectInfo/ProjectInfo.tsx","../src/widgets/projectInfo/index.ts","../src/widgets/projectUsers/ProjectUser.tsx","../src/widgets/projectUsers/ProjectUsers.tsx","../src/widgets/projectUsers/index.ts","../src/widgets/sanityTutorials/Tutorial.tsx","../src/widgets/sanityTutorials/dataAdapter.ts","../src/widgets/sanityTutorials/SanityTutorials.tsx","../src/widgets/sanityTutorials/index.ts","../src/components/DashboardLayout.tsx","../src/components/WidgetGroup.tsx","../src/containers/Dashboard.tsx","../src/plugin.tsx"],"sourcesContent":["import React, {forwardRef} from 'react'\nimport {Card, Box, Heading} from '@sanity/ui'\nimport {styled} from 'styled-components'\n\nconst Root = styled(Card)`\n display: flex;\n flex-direction: column;\n justify-content: stretch;\n height: 100%;\n box-sizing: border-box;\n position: relative;\n`\n\nconst Header = styled(Card)`\n position: sticky;\n top: 0;\n z-index: 2;\n border-top-left-radius: inherit;\n border-top-right-radius: inherit;\n`\n\nconst Footer = styled(Card)`\n position: sticky;\n overflow: hidden;\n bottom: 0;\n z-index: 2;\n border-bottom-right-radius: inherit;\n border-bottom-left-radius: inherit;\n margin-top: auto;\n`\n\nconst Content = styled(Box)`\n position: relative;\n z-index: 1;\n height: stretch;\n min-height: 21.5em;\n\n @media (min-width: ${({theme}) => theme.sanity.media[0]}px) {\n overflow-y: auto;\n outline: none;\n }\n`\n\ninterface DashboardWidgetProps {\n header?: string\n children: React.ReactNode\n footer?: React.ReactNode\n}\n\nexport const DashboardWidgetContainer = forwardRef(function DashboardWidgetContainer(\n props: DashboardWidgetProps,\n ref: React.Ref<HTMLDivElement>,\n) {\n const {header, children, footer} = props\n\n return (\n <Root radius={3} display=\"flex\" ref={ref}>\n {header && (\n <Header borderBottom paddingX={3} paddingY={4}>\n <Heading size={1} textOverflow=\"ellipsis\">\n {header}\n </Heading>\n </Header>\n )}\n {children && <Content>{children}</Content>}\n {footer && <Footer borderTop>{footer}</Footer>}\n </Root>\n )\n})\n","import {useClient} from 'sanity'\n\nexport function useVersionedClient() {\n return useClient({apiVersion: '2024-08-01'})\n}\n","import {createContext, useContext} from 'react'\nimport {DashboardConfig} from '../types'\n\nexport const DashboardContext = createContext<DashboardConfig>({widgets: []})\n\nexport function useDashboardConfig(): DashboardConfig {\n return useContext(DashboardContext)\n}\n","import React, {createElement, useMemo} from 'react'\nimport {useDashboardConfig} from './DashboardContext'\nimport {Card} from '@sanity/ui'\nimport {DashboardWidget} from '../types'\n\nexport function WidgetContainer(props: DashboardWidget) {\n const config = useDashboardConfig()\n const layout = useMemo(\n () => ({\n ...(props.layout || {}),\n ...(config.layout || {}),\n }),\n [props.layout, config.layout],\n )\n\n return (\n <Card shadow={1} data-width={layout.width} data-height={layout.height}>\n {createElement(props.component, {})}\n </Card>\n )\n}\n","import React, {useEffect, useMemo, useState} from 'react'\nimport {Box, Card, Stack, Heading, Grid, Label, Text, Code, Button} from '@sanity/ui'\nimport {useVersionedClient} from '../../versionedClient'\nimport {Subscription} from 'rxjs'\nimport {WidgetContainer} from '../../containers/WidgetContainer'\nimport {DashboardWidgetContainer} from '../../components/DashboardWidgetContainer'\nimport {type DashboardWidget} from '../../types'\nimport {type App, type ProjectInfoProps, type ProjectData, UserApplication} from './types'\n\nfunction isUrl(url?: string) {\n return url && /^https?:\\/\\//.test(`${url}`)\n}\n\nfunction getGraphQLUrl(projectId: string, dataset: string) {\n return `https://${projectId}.api.sanity.io/v1/graphql/${dataset}/default`\n}\n\nfunction getGroqUrl(projectId: string, dataset: string) {\n return `https://${projectId}.api.sanity.io/v1/groq/${dataset}`\n}\n\nfunction getManageUrl(projectId: string) {\n return `https://manage.sanity.io/projects/${projectId}`\n}\n\nconst NO_EXPERIMENTAL: DashboardWidget[] = []\nconst NO_DATA: ProjectData[] = []\n\nexport function ProjectInfo(props: ProjectInfoProps) {\n const {__experimental_before = NO_EXPERIMENTAL, data = NO_DATA} = props\n const [studioApps, setStudioApps] = useState<UserApplication[] | {error: string} | undefined>()\n const [graphQLApi, setGraphQLApi] = useState<string | {error: string} | undefined>()\n const versionedClient = useVersionedClient()\n const {projectId = 'unknown', dataset = 'unknown'} = versionedClient.config()\n\n useEffect(() => {\n const subscriptions: Subscription[] = []\n\n subscriptions.push(\n versionedClient.observable\n .request<UserApplication[]>({uri: '/user-applications', tag: 'dashboard.project-info'})\n .subscribe({\n next: (result) => setStudioApps(result.filter((app) => app.type === 'studio')),\n error: (error) => {\n console.error('Error while resolving user applications', error)\n setStudioApps({\n error: 'Something went wrong while resolving user applications. See console.',\n })\n },\n }),\n )\n\n // ping assumed graphql endpoint\n subscriptions.push(\n versionedClient.observable\n .request({\n method: 'HEAD',\n uri: `/graphql/${dataset}/default`,\n tag: 'dashboard.project-info.graphql-api',\n })\n .subscribe({\n next: () => setGraphQLApi(getGraphQLUrl(projectId, dataset)),\n error: (error) => {\n if (error.statusCode === 404) {\n setGraphQLApi(undefined)\n } else {\n console.error('Error while looking for graphQLApi', error)\n setGraphQLApi({\n error: 'Something went wrong while looking up graphQLApi. See console.',\n })\n }\n },\n }),\n )\n\n return () => {\n subscriptions.forEach((s) => s.unsubscribe())\n }\n }, [dataset, projectId, versionedClient, setGraphQLApi])\n\n const assembleTableRows = useMemo(() => {\n let result: App[] = [\n {\n title: 'Sanity project',\n rows: [\n {title: 'Project ID', value: projectId},\n {title: 'Dataset', value: dataset},\n ],\n },\n ]\n\n const apps: App[] = data.filter((item) => item.category === 'apps')\n\n // Handle studios\n ;(Array.isArray(studioApps) ? studioApps : []).forEach((app) => {\n apps.push({\n title: app.title || 'Studio',\n value: app.urlType === 'internal' ? `https://${app.appHost}.sanity.studio` : app.appHost,\n })\n })\n\n if (apps.length > 0) {\n result = result.concat([{title: 'Apps', rows: apps}])\n }\n\n // Handle APIs\n result = result.concat(\n [\n {\n title: 'APIs',\n rows: [\n {title: 'GROQ', value: getGroqUrl(projectId, dataset)},\n {\n title: 'GraphQL',\n value: (typeof graphQLApi === 'object' ? 'Error' : graphQLApi) ?? 'Not deployed',\n },\n ],\n },\n ],\n data.filter((item) => item.category === 'apis'),\n )\n\n // Handle whatever else there might be\n const otherStuff: Record<string, ProjectData[]> = {}\n data.forEach((item) => {\n if (item.category && item.category !== 'apps' && item.category !== 'apis') {\n if (!otherStuff[item.category]) {\n otherStuff[item.category] = []\n }\n otherStuff[item.category].push(item)\n }\n })\n Object.keys(otherStuff).forEach((category) => {\n result.push({title: category, rows: otherStuff[category]})\n })\n\n return result\n }, [graphQLApi, studioApps, projectId, dataset, data])\n\n return (\n <>\n {__experimental_before.map((widgetConfig, idx) => (\n <WidgetContainer key={idx} {...widgetConfig} />\n ))}\n <Box height=\"fill\" marginTop={__experimental_before?.length > 0 ? 4 : 0}>\n <DashboardWidgetContainer\n footer={\n <Button\n style={{width: '100%'}}\n paddingX={2}\n paddingY={4}\n mode=\"bleed\"\n tone=\"primary\"\n text=\"Manage project\"\n as=\"a\"\n href={getManageUrl(projectId)}\n />\n }\n >\n <Card\n paddingY={4}\n radius={2}\n role=\"table\"\n aria-label=\"Project info\"\n aria-describedby=\"project_info_table\"\n >\n <Stack space={4}>\n <Box paddingX={3} as=\"header\">\n <Heading size={1} as=\"h2\" id=\"project_info_table\">\n Project info\n </Heading>\n </Box>\n {assembleTableRows.map((item) => {\n if (!item || !item.rows) {\n return null\n }\n\n return (\n <Stack key={item.title} space={3}>\n <Card borderBottom padding={3}>\n <Label size={0} muted role=\"columnheader\">\n {item.title}\n </Label>\n </Card>\n <Stack space={4} paddingX={3} role=\"rowgroup\">\n {item.rows.map((row) => {\n return (\n <Grid key={`${row.value}-${row.title}`} columns={2} role=\"row\">\n <Text weight=\"medium\" role=\"rowheader\">\n {row.title}\n </Text>\n {typeof row.value === 'object' && (\n <Text size={1}>{row.value?.error}</Text>\n )}\n {typeof row.value === 'string' && (\n <>\n {isUrl(row.value) ? (\n <Text size={1} role=\"cell\" style={{wordBreak: 'break-word'}}>\n <a href={row.value}>{row.value}</a>\n </Text>\n ) : (\n <Code size={1} role=\"cell\" style={{wordBreak: 'break-word'}}>\n {row.value}\n </Code>\n )}\n </>\n )}\n </Grid>\n )\n })}\n </Stack>\n </Stack>\n )\n })}\n </Stack>\n </Card>\n </DashboardWidgetContainer>\n </Box>\n </>\n )\n}\n","import {ProjectInfo} from './ProjectInfo'\nimport {type LayoutConfig, type DashboardWidget} from '../../types'\n\nexport function projectInfoWidget(config?: {layout?: LayoutConfig}): DashboardWidget {\n return {\n name: 'project-info',\n component: ProjectInfo,\n layout: config?.layout ?? {width: 'medium'},\n }\n}\n","import React from 'react'\nimport {Box, Flex, rem, Stack, Text} from '@sanity/ui'\nimport {styled} from 'styled-components'\nimport {useListFormat, type User, UserAvatar} from 'sanity'\nimport {RobotIcon} from '@sanity/icons'\n\nconst Root = styled(Flex)`\n height: ${rem(33)}; // 33 = PREVIEW_SIZES.default.media.height\n box-sizing: content-box;\n`\n\nexport interface ProjectUserProps {\n user: User\n isRobot: boolean\n roles: string[]\n}\n\nexport function ProjectUser({user, isRobot, roles}: ProjectUserProps) {\n const listFormat = useListFormat({style: 'narrow'})\n return (\n <Root align=\"center\">\n <Flex align=\"center\" flex={1} gap={2}>\n <Box flex=\"none\">\n {isRobot ? (\n <Text size={2}>\n <RobotIcon />\n </Text>\n ) : (\n <UserAvatar user={user} />\n )}\n </Box>\n\n <Stack flex={1} space={2}>\n <Text size={1} style={{color: 'inherit'}} textOverflow=\"ellipsis\" weight=\"medium\">\n {user.displayName}\n </Text>\n\n <Text muted size={1} textOverflow=\"ellipsis\">\n {listFormat.format(roles)}\n </Text>\n </Stack>\n </Flex>\n </Root>\n )\n}\n","import React, {useCallback, useEffect, useState} from 'react'\nimport {from} from 'rxjs'\nimport {map, switchMap} from 'rxjs/operators'\nimport {Stack, Spinner, Box, Text, Button} from '@sanity/ui'\nimport {Role, useUserStore} from 'sanity'\nimport {useVersionedClient} from '../../versionedClient'\nimport {User} from 'sanity'\nimport {DashboardWidgetContainer} from '../../components/DashboardWidgetContainer'\nimport {ProjectUser} from './ProjectUser'\n\nfunction getInviteUrl(projectId: string) {\n return `https://manage.sanity.io/projects/${projectId}/members`\n}\n\ninterface Member {\n id: string\n roles: Role[]\n isRobot: boolean\n isCurrentUser: boolean\n createdAt: string\n}\n\ninterface Project {\n id: string\n members: Member[]\n}\n\nexport function ProjectUsers() {\n const [project, setProject] = useState<Project | undefined>()\n const [users, setUsers] = useState<User[] | undefined>()\n const [error, setError] = useState<Error | undefined>()\n\n const userStore = useUserStore()\n const versionedClient = useVersionedClient()\n\n const fetchData = useCallback(() => {\n const {projectId} = versionedClient.config()\n const subscription = versionedClient.observable\n .request<Project>({\n uri: `/projects/${projectId}`,\n tag: 'dashboard.project-users',\n })\n .pipe(\n switchMap((_project) =>\n from(userStore.getUsers(_project.members.map((mem) => mem.id))).pipe(\n map((_users) => ({project: _project, users: _users})),\n ),\n ),\n )\n .subscribe({\n next: ({users: _users, project: _project}) => {\n setProject(_project)\n setUsers(\n (Array.isArray(_users) ? _users : [_users]).sort((userA, userB) =>\n sortUsersByRobotStatus(userA, userB, _project),\n ),\n )\n },\n error: (e: Error) => setError(e),\n })\n\n return () => subscription.unsubscribe()\n }, [userStore, versionedClient])\n\n useEffect(() => fetchData(), [fetchData])\n\n const handleRetryFetch = useCallback(() => fetchData(), [fetchData])\n\n const isLoading = !users || !project\n\n if (error) {\n return (\n <DashboardWidgetContainer header=\"Project users\">\n <Box padding={4}>\n <Text>\n Something went wrong while fetching data. You could{' '}\n <a onClick={handleRetryFetch} title=\"Retry users fetch\" style={{cursor: 'pointer'}}>\n retry\n </a>\n ..?\n </Text>\n </Box>\n </DashboardWidgetContainer>\n )\n }\n\n return (\n <DashboardWidgetContainer\n header=\"Project users\"\n footer={\n <Button\n style={{width: '100%'}}\n paddingX={2}\n paddingY={4}\n mode=\"bleed\"\n tone=\"primary\"\n text=\"Manage members\"\n as=\"a\"\n loading={isLoading}\n href={isLoading ? undefined : getInviteUrl(project.id)}\n />\n }\n >\n {isLoading && (\n <Box paddingY={5} paddingX={2}>\n <Stack space={4}>\n <Text align=\"center\" muted size={1}>\n <Spinner />\n </Text>\n <Text align=\"center\" size={1} muted>\n Loading items…\n </Text>\n </Stack>\n </Box>\n )}\n\n {!isLoading && (\n <Stack space={3} padding={3}>\n {users?.map((user) => {\n const membership = project.members.find((member) => member.id === user.id)\n return (\n <ProjectUser\n key={user.id}\n user={user}\n isRobot={membership?.isRobot ?? false}\n roles={membership?.roles.map((role) => role.title) || []}\n />\n )\n })}\n </Stack>\n )}\n </DashboardWidgetContainer>\n )\n}\n\nfunction sortUsersByRobotStatus(userA: User, userB: User, project: Project) {\n const {members} = project\n const membershipA = members.find((member) => member.id === userA?.id)\n const membershipB = members.find((member) => member.id === userB?.id)\n\n // On ties, sort by when the user was added\n if (membershipA?.isRobot === membershipB?.isRobot) {\n return (membershipA?.createdAt || '') > (membershipB?.createdAt || '') ? 1 : -1\n }\n\n // Robots go to the bottom\n return membershipA?.isRobot ? 1 : -1\n}\n","import {ProjectUsers} from './ProjectUsers'\nimport {LayoutConfig, DashboardWidget} from '../../types'\n\nexport function projectUsersWidget(config?: {layout?: LayoutConfig}): DashboardWidget {\n return {\n name: 'project-info',\n component: ProjectUsers,\n layout: config?.layout,\n }\n}\n","import React from 'react'\nimport {Card, Box, Heading, Flex, Text, Stack} from '@sanity/ui'\nimport {PlayIcon} from '@sanity/icons'\nimport {styled} from 'styled-components'\n\nconst PlayIconBox = styled(Box)`\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n\n &:before {\n content: '';\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 2.75em;\n height: 2.75em;\n border-radius: 50%;\n background: ${({theme}) => theme.sanity.color.card.enabled.bg};\n opacity: 0.75;\n }\n`\n\nconst Root = styled(Flex)`\n &:hover {\n ${PlayIconBox} {\n &:before {\n opacity: 1;\n }\n }\n }\n`\n\nconst PosterCard = styled(Card)`\n width: 100%;\n padding-bottom: calc(9 / 16 * 100%);\n position: relative;\n`\n\nconst Poster = styled.img`\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n width: 100%;\n object-fit: cover;\n display: block;\n\n &:not([src]) {\n display: none;\n }\n`\n\nexport interface TutorialProps {\n title: string\n posterURL?: string\n href: string\n showPlayIcon?: boolean\n presenterName?: string\n presenterSubtitle?: string\n}\n\nexport function Tutorial(props: TutorialProps) {\n const {title, posterURL, showPlayIcon, href, presenterName, presenterSubtitle} = props\n\n return (\n <Root flex={1}>\n <Card\n sizing=\"border\"\n flex={1}\n padding={2}\n radius={2}\n as=\"a\"\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n style={{position: 'relative'}}\n >\n <Flex direction=\"column\" style={{height: '100%'}}>\n {posterURL && (\n <PosterCard marginBottom={1}>\n <Poster src={posterURL} />\n {showPlayIcon && (\n <PlayIconBox display=\"flex\">\n <Text align=\"center\">\n <PlayIcon />\n </Text>\n </PlayIconBox>\n )}\n </PosterCard>\n )}\n <Flex direction=\"column\" justify=\"space-between\" paddingY={2} flex={1}>\n <Heading as=\"h3\" size={1}>\n {title}\n </Heading>\n <Box marginTop={4}>\n <Stack space={2} flex={1}>\n <Text size={1}>{presenterName}</Text>\n <Text size={0} style={{opacity: 0.7}}>\n {presenterSubtitle}\n </Text>\n </Stack>\n </Box>\n </Flex>\n </Flex>\n </Card>\n </Root>\n )\n}\n","import {useMemo} from 'react'\nimport {useVersionedClient} from '../../versionedClient'\nimport imageUrlBuilder from '@sanity/image-url'\n\nconst tutorialsProjectConfig = {\n projectId: '3do82whm',\n dataset: 'next',\n}\n\nexport interface Guide {\n _type?: string\n slug?: {current: string}\n presenter?: {\n name?: string\n }\n}\n\nexport interface FeedItem {\n _id: string\n title?: string\n poster?: string\n category?: string\n guideOrTutorial?: Guide\n externalLink?: string\n presenter?: {\n name?: string\n }\n hasVideo?: boolean\n}\n\nexport function useDataAdapter() {\n const versionedClient = useVersionedClient()\n return useMemo(\n () => ({\n getFeed: (templateRepoId: string) => {\n const uri = templateRepoId\n ? `/addons/dashboard?templateRepoId=${templateRepoId}`\n : '/addons/dashboard'\n return versionedClient.observable.request<{items: FeedItem[]}>({\n uri,\n tag: 'dashboard.sanity-tutorials',\n withCredentials: false,\n })\n },\n urlBuilder: imageUrlBuilder(tutorialsProjectConfig),\n }),\n [versionedClient],\n )\n}\n","import React, {useEffect, useState} from 'react'\nimport {Flex} from '@sanity/ui'\nimport {Tutorial} from './Tutorial'\nimport {FeedItem, Guide, useDataAdapter} from './dataAdapter'\nimport {DashboardWidgetContainer} from '../../components/DashboardWidgetContainer'\n\nfunction createUrl(slug: {current: string}, type?: string) {\n if (type === 'tutorial') {\n return `https://www.sanity.io/docs/tutorials/${slug.current}`\n } else if (type === 'guide') {\n return `https://www.sanity.io/docs/guides/${slug.current}`\n }\n return false\n}\n\nexport interface SanityTutorialsProps {\n templateRepoId: string\n}\n\nexport function SanityTutorials(props: SanityTutorialsProps) {\n const {templateRepoId} = props\n const [feedItems, setFeedItems] = useState<FeedItem[]>([])\n\n const {getFeed, urlBuilder} = useDataAdapter()\n\n useEffect(() => {\n const subscription = getFeed(templateRepoId).subscribe((response) => {\n setFeedItems(response.items)\n })\n return () => {\n subscription.unsubscribe()\n }\n }, [setFeedItems, getFeed, templateRepoId])\n\n const title = 'Learn about Sanity'\n\n return (\n <DashboardWidgetContainer header={title}>\n <Flex as=\"ul\" overflow=\"auto\" align=\"stretch\" paddingY={2}>\n {feedItems?.map((feedItem, index) => {\n if (!feedItem.title || (!feedItem.guideOrTutorial && !feedItem.externalLink)) {\n return null\n }\n const presenter = feedItem.presenter || feedItem.guideOrTutorial?.presenter || {}\n const subtitle = feedItem.category\n const {guideOrTutorial = {} as Guide} = feedItem\n const href =\n (guideOrTutorial.slug\n ? createUrl(guideOrTutorial.slug, guideOrTutorial._type)\n : feedItem.externalLink) || feedItem.externalLink\n\n return (\n <Flex\n as=\"li\"\n key={feedItem._id}\n paddingRight={index < feedItems?.length - 1 ? 1 : 3}\n paddingLeft={index === 0 ? 3 : 0}\n align=\"stretch\"\n style={{minWidth: 272, width: '30%'}}\n >\n <Tutorial\n title={feedItem.title}\n href={href ?? ''}\n presenterName={presenter.name}\n presenterSubtitle={subtitle}\n showPlayIcon={feedItem.hasVideo}\n posterURL={\n feedItem.poster ? urlBuilder.image(feedItem.poster).height(360).url() : undefined\n }\n />\n </Flex>\n )\n })}\n </Flex>\n </DashboardWidgetContainer>\n )\n}\n","import {SanityTutorials} from './SanityTutorials'\nimport {LayoutConfig, DashboardWidget} from '../../types'\n\nexport function sanityTutorialsWidget(config?: {layout?: LayoutConfig}): DashboardWidget {\n return {\n name: 'sanity-tutorials',\n component: SanityTutorials,\n layout: config?.layout ?? {width: 'full'},\n }\n}\n","import React, {PropsWithChildren} from 'react'\nimport {Container} from '@sanity/ui'\n\nexport function DashboardLayout(props: PropsWithChildren<{}>) {\n return (\n <Container width={4} padding={4} sizing=\"border\" style={{height: '100%', overflowY: 'auto'}}>\n {props.children}\n </Container>\n )\n}\n","import React from 'react'\nimport {styled, css} from 'styled-components'\nimport {Box, Card, Grid, Text} from '@sanity/ui'\nimport {WidgetContainer} from '../containers/WidgetContainer'\nimport {DashboardConfig, LayoutConfig, DashboardWidget} from '../types'\n\nconst media = {\n small: (...args: Parameters<typeof css>) => css`\n @media (min-width: ${({theme}) => theme.sanity.media[0]}px) {\n ${css(...args)}\n }\n `,\n medium: (...args: Parameters<typeof css>) => css`\n @media (min-width: ${({theme}) => theme.sanity.media[2]}px) {\n ${css(...args)}\n }\n `,\n}\n\nconst Root = styled(Grid)`\n grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n\n & > div {\n overflow: hidden;\n }\n\n & > div[data-width='medium'] {\n ${media.small`\n grid-column: span 2;\n `}\n }\n\n & > div[data-width='large'] {\n ${media.small`\n grid-column: span 2;\n `}\n\n ${media.medium`\n grid-column: span 3;\n `}\n }\n\n & > div[data-width='full'] {\n ${media.small`\n grid-column: 1 / -1;\n `}\n }\n\n & > div[data-height='medium'] {\n ${media.small`\n grid-row: span 2;\n `}\n }\n\n & > div[data-height='large'] {\n ${media.small`\n grid-row: span 2;\n `}\n\n ${media.medium`\n grid-row: span 3;\n `}\n }\n\n & > div[data-height='full'] {\n ${media.medium`\n grid-row: 1 / -1;\n `}\n }\n`\n\nexport interface WidgetGroupProps {\n config: Partial<DashboardConfig>\n}\n\nconst NO_WIDGETS: DashboardWidget[] = []\nconst NO_LAYOUT: LayoutConfig = {}\n\nexport function WidgetGroup(props: WidgetGroupProps) {\n const {\n config: {layout = NO_LAYOUT, widgets = NO_WIDGETS},\n } = props\n return (\n <Root\n autoFlow=\"row dense\"\n data-width={layout.width || 'auto'}\n data-height={layout.height || 'auto'}\n gap={4}\n >\n {widgets.length ? null : (\n <Card padding={4} shadow={1} tone=\"primary\">\n <Text align=\"center\">Add some widgets to populate this space.</Text>\n </Card>\n )}\n {widgets.map((widgetConfig, index) => {\n if (widgetConfig.type === '__experimental_group') {\n return <WidgetGroup key={index} config={widgetConfig} />\n }\n if (widgetConfig.component) {\n return <WidgetContainer key={index} {...widgetConfig} />\n }\n return <Box key={index}>{widgetConfig.name} is missing widget component</Box>\n })}\n </Root>\n )\n}\n","import React from 'react'\nimport {DashboardLayout} from '../components/DashboardLayout'\nimport {WidgetGroup} from '../components/WidgetGroup'\nimport {DashboardContext} from './DashboardContext'\nimport {DashboardConfig} from '../types'\n\nexport function Dashboard({config}: {config: DashboardConfig}) {\n if (!config) {\n return null\n }\n\n return (\n <DashboardContext.Provider value={config}>\n <DashboardLayout>\n <WidgetGroup config={config} />\n </DashboardLayout>\n </DashboardContext.Provider>\n )\n}\n","import React, {ComponentType, CSSProperties} from 'react'\nimport {Dashboard} from './containers/Dashboard'\nimport {definePlugin} from 'sanity'\nimport {DashboardConfig, DashboardWidget, LayoutConfig} from './types'\n\nconst strokeStyle: CSSProperties = {\n stroke: 'currentColor',\n strokeWidth: 1.2,\n}\n\nconst DashboardIcon = () => (\n <svg\n data-sanity-icon\n viewBox=\"0 0 25 25\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n preserveAspectRatio=\"xMidYMid\"\n width=\"1em\"\n height=\"1em\"\n >\n <path d=\"M19.5 19.5H5.5V5.5H19.5V19.5Z\" style={strokeStyle} />\n <path d=\"M5.5 12.5H19.5\" style={strokeStyle} />\n <path d=\"M14.5 19.5V12.5M10.5 12.5V5.5\" style={strokeStyle} />\n </svg>\n)\n\nexport interface DashboardPluginConfig {\n /**\n * Dashboard tool title\n */\n title?: string\n /**\n * Dashboard tool name (used in url path)\n */\n name?: string\n /**\n * Dashboard tool icon\n */\n icon?: ComponentType\n widgets?: DashboardWidget[]\n\n /**\n * Will be used for widgets that do not define a layout directly.\n */\n defaultLayout?: LayoutConfig\n}\n\nexport const dashboardTool = definePlugin<DashboardPluginConfig>((config = {}) => {\n const pluginConfig: DashboardConfig = {\n layout: config.defaultLayout ?? {},\n widgets: config.widgets ?? [],\n }\n\n const title = config.title ?? 'Dashboard'\n const name = config.name ?? 'dashboard'\n const icon = config.icon ?? DashboardIcon\n\n return {\n name: 'dashboard',\n tools: (prev, context) => {\n return [\n ...prev,\n {\n title,\n name,\n icon,\n component: () => <Dashboard config={pluginConfig} />,\n },\n ]\n },\n }\n})\n"],"names":["Root"],"mappings":"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"}
|