@sanity/dashboard 2.35.0 → 2.36.0-v2-studio.1

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.
Files changed (65) hide show
  1. package/LICENSE +1 -1
  2. package/dts/components/dashboardWidget.d.ts +10 -0
  3. package/dts/legacyParts.d.ts +7 -0
  4. package/lib/DashboardTool.js +2 -1
  5. package/lib/DashboardTool.js.map +1 -0
  6. package/lib/components/DashboardLayout.js +2 -1
  7. package/lib/components/DashboardLayout.js.map +1 -0
  8. package/lib/components/NotFoundWidget.js +2 -1
  9. package/lib/components/NotFoundWidget.js.map +1 -0
  10. package/lib/components/WidgetGroup.js +2 -1
  11. package/lib/components/WidgetGroup.js.map +1 -0
  12. package/lib/components/dashboardWidget.js +2 -1
  13. package/lib/components/dashboardWidget.js.map +1 -0
  14. package/lib/containers/Dashboard.js +2 -1
  15. package/lib/containers/Dashboard.js.map +1 -0
  16. package/lib/containers/WidgetContainer.js +2 -1
  17. package/lib/containers/WidgetContainer.js.map +1 -0
  18. package/lib/dashboardConfig.js +2 -1
  19. package/lib/dashboardConfig.js.map +1 -0
  20. package/lib/index.js +2 -1
  21. package/lib/index.js.map +1 -0
  22. package/lib/legacyParts.js +2 -1
  23. package/lib/legacyParts.js.map +1 -0
  24. package/lib/versionedClient.js +2 -1
  25. package/lib/versionedClient.js.map +1 -0
  26. package/lib/widgets/projectInfo/ProjectInfo.js +5 -4
  27. package/lib/widgets/projectInfo/ProjectInfo.js.map +1 -0
  28. package/lib/widgets/projectInfo/index.js +2 -1
  29. package/lib/widgets/projectInfo/index.js.map +1 -0
  30. package/lib/widgets/projectUsers/ProjectUsers.js +2 -1
  31. package/lib/widgets/projectUsers/ProjectUsers.js.map +1 -0
  32. package/lib/widgets/projectUsers/index.js +2 -1
  33. package/lib/widgets/projectUsers/index.js.map +1 -0
  34. package/lib/widgets/sanityTutorials/SanityTutorials.js +2 -1
  35. package/lib/widgets/sanityTutorials/SanityTutorials.js.map +1 -0
  36. package/lib/widgets/sanityTutorials/Tutorial.js +2 -1
  37. package/lib/widgets/sanityTutorials/Tutorial.js.map +1 -0
  38. package/lib/widgets/sanityTutorials/dataAdapter.js +2 -1
  39. package/lib/widgets/sanityTutorials/dataAdapter.js.map +1 -0
  40. package/lib/widgets/sanityTutorials/index.js +2 -1
  41. package/lib/widgets/sanityTutorials/index.js.map +1 -0
  42. package/package.json +53 -21
  43. package/sanity.json +0 -4
  44. package/src/DashboardTool.js +30 -0
  45. package/src/components/DashboardLayout.js +42 -0
  46. package/src/components/NotFoundWidget.js +41 -0
  47. package/src/components/WidgetGroup.js +98 -0
  48. package/src/components/dashboardWidget.tsx +76 -0
  49. package/src/containers/Dashboard.js +21 -0
  50. package/src/containers/WidgetContainer.js +57 -0
  51. package/src/dashboardConfig.js +13 -0
  52. package/src/index.js +2 -0
  53. package/src/legacyParts.ts +11 -0
  54. package/src/versionedClient.js +9 -0
  55. package/src/widget.css +62 -0
  56. package/src/widgets/projectInfo/ProjectInfo.js +232 -0
  57. package/src/widgets/projectInfo/index.js +7 -0
  58. package/src/widgets/projectUsers/ProjectUsers.js +176 -0
  59. package/src/widgets/projectUsers/index.js +6 -0
  60. package/src/widgets/sanityTutorials/SanityTutorials.js +152 -0
  61. package/src/widgets/sanityTutorials/Tutorial.js +158 -0
  62. package/src/widgets/sanityTutorials/dataAdapter.js +17 -0
  63. package/src/widgets/sanityTutorials/index.js +7 -0
  64. package/.babelrc +0 -4
  65. package/tsconfig.json +0 -17
@@ -0,0 +1,41 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+ import {Card, Stack, Heading, Box} from '@sanity/ui'
4
+ import styled from 'styled-components'
5
+
6
+ const Root = styled(Card)`
7
+ display: flex;
8
+ flex-direction: column;
9
+ justify-content: stretch;
10
+ height: 100%;
11
+ `
12
+
13
+ function NotFoundWidget(props) {
14
+ const {title, children} = props
15
+ return (
16
+ <Root radius={3} paddingX={3} paddingY={4} tone="critical">
17
+ <Stack space={2}>
18
+ {title && (
19
+ <Heading size={1} as="h2">
20
+ {title}
21
+ </Heading>
22
+ )}
23
+ {children && <Box>{children}</Box>}
24
+ </Stack>
25
+ </Root>
26
+ )
27
+ }
28
+
29
+ NotFoundWidget.propTypes = {
30
+ // eslint-disable-next-line react/forbid-prop-types
31
+ children: PropTypes.any,
32
+ // eslint-disable-next-line react/forbid-prop-types
33
+ title: PropTypes.any,
34
+ }
35
+
36
+ NotFoundWidget.defaultProps = {
37
+ children: null,
38
+ title: null,
39
+ }
40
+
41
+ export default NotFoundWidget
@@ -0,0 +1,98 @@
1
+ /* eslint-disable react/prop-types */
2
+ import React from 'react'
3
+ import styled, {css} from 'styled-components'
4
+ import {Grid} from '@sanity/ui'
5
+ import {WidgetContainer} from '../legacyParts'
6
+
7
+ const media = {
8
+ small: (...args) =>
9
+ css`
10
+ @media (min-width: ${({theme}) => theme.sanity.media[0]}px) {
11
+ ${css(...args)}
12
+ }
13
+ `,
14
+ medium: (...args) =>
15
+ css`
16
+ @media (min-width: ${({theme}) => theme.sanity.media[2]}px) {
17
+ ${css(...args)}
18
+ }
19
+ `,
20
+ }
21
+
22
+ const Root = styled(Grid)`
23
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
24
+
25
+ & > div {
26
+ overflow: hidden;
27
+ }
28
+
29
+ & > div[data-width='medium'] {
30
+ ${media.small`
31
+ grid-column: span 2;
32
+ `}
33
+ }
34
+
35
+ & > div[data-width='large'] {
36
+ ${media.small`
37
+ grid-column: span 2;
38
+ `}
39
+
40
+ ${media.medium`
41
+ grid-column: span 3;
42
+ `}
43
+ }
44
+
45
+ & > div[data-width='full'] {
46
+ ${media.small`
47
+ grid-column: 1 / -1;
48
+ `}
49
+ }
50
+
51
+ & > div[data-height='medium'] {
52
+ ${media.small`
53
+ grid-row: span 2;
54
+ `}
55
+ }
56
+
57
+ & > div[data-height='large'] {
58
+ ${media.small`
59
+ grid-row: span 2;
60
+ `}
61
+
62
+ ${media.medium`
63
+ grid-row: span 3;
64
+ `}
65
+ }
66
+
67
+ & > div[data-height='full'] {
68
+ ${media.medium`
69
+ grid-row: 1 / -1;
70
+ `}
71
+ }
72
+ `
73
+
74
+ function WidgetGroup(props) {
75
+ const config = props.config || {}
76
+ const widgets = config.widgets || []
77
+ const layout = config.layout || {}
78
+
79
+ return (
80
+ <Root
81
+ autoFlow="dense"
82
+ data-width={layout.width || 'auto'}
83
+ data-height={layout.height || 'auto'}
84
+ data-name="sanity-dashboard-widget-group"
85
+ gap={4}
86
+ >
87
+ {widgets.map((widgetConfig, index) => {
88
+ if (widgetConfig.type === '__experimental_group') {
89
+ return <WidgetGroup key={String(index)} config={widgetConfig} />
90
+ }
91
+
92
+ return <WidgetContainer key={String(index)} config={widgetConfig} />
93
+ })}
94
+ </Root>
95
+ )
96
+ }
97
+
98
+ export default WidgetGroup
@@ -0,0 +1,76 @@
1
+ import React, {forwardRef} from 'react'
2
+ import {Card, Box, Heading} from '@sanity/ui'
3
+ import styled from 'styled-components'
4
+
5
+ const Root = styled(Card)`
6
+ display: flex;
7
+ flex-direction: column;
8
+ justify-content: stretch;
9
+ height: 100%;
10
+ box-sizing: border-box;
11
+ position: relative;
12
+ `
13
+
14
+ const Header = styled(Card)`
15
+ position: sticky;
16
+ top: 0;
17
+ z-index: 2;
18
+ border-top-left-radius: inherit;
19
+ border-top-right-radius: inherit;
20
+ `
21
+
22
+ const Footer = styled(Card)`
23
+ position: sticky;
24
+ overflow: hidden;
25
+ bottom: 0;
26
+ z-index: 2;
27
+ border-bottom-right-radius: inherit;
28
+ border-bottom-left-radius: inherit;
29
+ margin-top: auto;
30
+ `
31
+
32
+ const Content = styled(Box)`
33
+ position: relative;
34
+ z-index: 1;
35
+ height: stretch;
36
+ min-height: 21.5em;
37
+
38
+ @media (min-width: ${({theme}) => theme.sanity.media[0]}px) {
39
+ overflow-y: auto;
40
+ outline: none;
41
+ }
42
+ `
43
+
44
+ interface DashboardWidgetProps {
45
+ header?: string
46
+ className?: string
47
+ children: React.ReactNode
48
+ footer?: React.ReactNode
49
+ hideFooterBorder?: boolean
50
+ }
51
+
52
+ export const DashboardWidget = forwardRef(
53
+ (props: DashboardWidgetProps, ref: React.Ref<HTMLDivElement>) => {
54
+ const {header, children, footer, hideFooterBorder, className} = props
55
+
56
+ return (
57
+ <Root radius={3} display="flex" ref={ref} className={className}>
58
+ {header && (
59
+ <Header borderBottom paddingX={3} paddingY={4}>
60
+ <Heading size={1} textOverflow="ellipsis">
61
+ {header}
62
+ </Heading>
63
+ </Header>
64
+ )}
65
+ {children && <Content data-name="content">{children}</Content>}
66
+ {footer && (
67
+ <Footer sizing="border" borderTop={!hideFooterBorder}>
68
+ {footer}
69
+ </Footer>
70
+ )}
71
+ </Root>
72
+ )
73
+ }
74
+ )
75
+
76
+ DashboardWidget.displayName = 'DashboardWidget'
@@ -0,0 +1,21 @@
1
+ import React from 'react'
2
+ import DashboardLayout from '../components/DashboardLayout'
3
+ import WidgetGroup from '../components/WidgetGroup'
4
+ import {dashboardConfig} from '../legacyParts'
5
+
6
+ function Dashboard() {
7
+ if (!dashboardConfig) {
8
+ return null
9
+ }
10
+
11
+ const widgetConfigs = dashboardConfig.widgets || []
12
+ const layoutWidth = dashboardConfig?.layout?.width || 'large'
13
+
14
+ return (
15
+ <DashboardLayout width={layoutWidth}>
16
+ <WidgetGroup config={{widgets: widgetConfigs}} />
17
+ </DashboardLayout>
18
+ )
19
+ }
20
+
21
+ export default Dashboard
@@ -0,0 +1,57 @@
1
+ import PropTypes from 'prop-types'
2
+ import React from 'react'
3
+ import NotFoundWidget from '../components/NotFoundWidget'
4
+ import {definitions} from '../legacyParts'
5
+
6
+ function WidgetContainer(props) {
7
+ const config = props.config || {}
8
+ const definition = Array.isArray(definitions)
9
+ ? definitions.find((wid) => wid.name === config.name)
10
+ : null
11
+
12
+ if (definition) {
13
+ const options = {
14
+ ...(definition.options || {}),
15
+ ...(config.options || {}),
16
+ }
17
+ const layout = {
18
+ ...(definition.layout || {}),
19
+ ...(config.layout || {}),
20
+ }
21
+
22
+ return (
23
+ <div
24
+ data-width={layout.width}
25
+ data-height={layout.height}
26
+ data-widget-name={config.name}
27
+ data-name="sanity-dashboard-widget-container"
28
+ >
29
+ {React.createElement(definition.component, options)}
30
+ </div>
31
+ )
32
+ }
33
+
34
+ const layout = config.layout || {}
35
+
36
+ return (
37
+ <div data-width={layout.width} data-height={layout.height}>
38
+ <NotFoundWidget title={<>Not found: "{config.name}"</>}>
39
+ <p>
40
+ Make sure your <code>sanity.json</code> file mentions such a widget and that it’s an
41
+ implementation of <code>part:@sanity/dashboard/widget</code>.
42
+ </p>
43
+ </NotFoundWidget>
44
+ </div>
45
+ )
46
+ }
47
+
48
+ WidgetContainer.propTypes = {
49
+ // eslint-disable-next-line react/forbid-prop-types
50
+ config: PropTypes.any,
51
+ }
52
+
53
+ WidgetContainer.defaultProps = {
54
+ config: null,
55
+ }
56
+
57
+ export default WidgetContainer
@@ -0,0 +1,13 @@
1
+ export default {
2
+ widgets: [
3
+ {
4
+ name: 'sanity-tutorials',
5
+ layout: {
6
+ width: 'full',
7
+ height: 'full',
8
+ },
9
+ },
10
+ {name: 'project-info'},
11
+ {name: 'project-users'},
12
+ ],
13
+ }
package/src/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './components/dashboardWidget'
2
+ export {default} from './DashboardTool'
@@ -0,0 +1,11 @@
1
+ // @todo: remove the following line when part imports has been removed from this file
2
+ ///<reference types="@sanity/types/parts" />
3
+
4
+ import WidgetContainer from 'part:@sanity/dashboard/widget-container'
5
+ import dashboardConfig from 'part:@sanity/dashboard/config?'
6
+ import sanityClient from 'part:@sanity/base/client'
7
+ import definitions from 'all:part:@sanity/dashboard/widget?'
8
+ import DefaultPreview from 'part:@sanity/components/previews/default'
9
+ import userStore from 'part:@sanity/base/user'
10
+
11
+ export {WidgetContainer, dashboardConfig, sanityClient, definitions, DefaultPreview, userStore}
@@ -0,0 +1,9 @@
1
+ import {sanityClient} from './legacyParts'
2
+
3
+ /**
4
+ * Only for use inside of @sanity/dashboard
5
+ * Don't import this from external modules.
6
+ *
7
+ * @internal
8
+ */
9
+ export const versionedClient = sanityClient.withConfig({apiVersion: '1'})
package/src/widget.css ADDED
@@ -0,0 +1,62 @@
1
+ @import 'part:@sanity/base/theme/variables-style';
2
+
3
+ .container {
4
+ display: flex;
5
+ flex-direction: column;
6
+ justify-content: stretch;
7
+ height: 100%;
8
+ border-radius: var(--border-radius-base);
9
+ background-color: var(--component-bg);
10
+ box-sizing: border-box;
11
+ position: relative;
12
+ }
13
+
14
+ .containerWithPadding {
15
+ composes: container;
16
+ padding: var(--medium-padding);
17
+ }
18
+
19
+ .header {
20
+ padding: var(--small-padding) 0;
21
+ }
22
+
23
+ .title {
24
+ composes: heading4 from 'part:@sanity/base/theme/typography/headings-style';
25
+ margin: var(--small-padding) var(--medium-padding);
26
+ }
27
+
28
+ .content {
29
+ display: block;
30
+ margin: 0;
31
+ padding: 0;
32
+ min-height: 21.5em;
33
+
34
+ @media (--screen-medium) {
35
+ height: stretch;
36
+ overflow-y: auto;
37
+ }
38
+ }
39
+
40
+ .footer {
41
+ display: flex;
42
+ text-align: center;
43
+ font-weight: 600;
44
+ min-height: 4em;
45
+ height: 4em;
46
+ margin-top: auto;
47
+ overflow: hidden;
48
+
49
+ @nest & > * {
50
+ width: 100%;
51
+ }
52
+ }
53
+
54
+ /* TODO: remove after changing document list plugin */
55
+ .listContainer {
56
+ composes: content;
57
+ }
58
+
59
+ /* TODO: remove after changing document list plugin */
60
+ .bottomButtonContainer {
61
+ composes: footer;
62
+ }
@@ -0,0 +1,232 @@
1
+ /* eslint-disable react/forbid-prop-types, no-console */
2
+ import React from 'react'
3
+ import {isPlainObject} from 'lodash'
4
+ import PropTypes from 'prop-types'
5
+ import {Box, Card, Stack, Heading, Grid, Label, Text, Code, Button} from '@sanity/ui'
6
+ import {versionedClient} from '../../versionedClient'
7
+ import {DashboardWidget} from '../../'
8
+ import {WidgetContainer} from '../../legacyParts'
9
+
10
+ const {projectId, dataset} = versionedClient.config()
11
+
12
+ function isUrl(url) {
13
+ return /^https?:\/\//.test(`${url}`)
14
+ }
15
+
16
+ function getGraphQlUrl() {
17
+ return `https://${projectId}.api.sanity.io/v1/graphql/${dataset}/default`
18
+ }
19
+
20
+ function getGroqUrl() {
21
+ return `https://${projectId}.api.sanity.io/v1/groq/${dataset}`
22
+ }
23
+
24
+ function getManageUrl() {
25
+ return `https://manage.sanity.io/projects/${projectId}`
26
+ }
27
+
28
+ class ProjectInfo extends React.PureComponent {
29
+ static propTypes = {
30
+ // eslint-disable-next-line camelcase
31
+ __experimental_before: PropTypes.array,
32
+ data: PropTypes.array,
33
+ }
34
+ static defaultProps = {
35
+ // eslint-disable-next-line camelcase
36
+ __experimental_before: [],
37
+ data: [],
38
+ }
39
+
40
+ state = {
41
+ studioHost: null,
42
+ graphqlApi: null,
43
+ }
44
+
45
+ componentDidMount() {
46
+ // fetch project data
47
+ this.subscriptions = []
48
+
49
+ this.subscriptions.push(
50
+ versionedClient.observable.request({uri: `/projects/${projectId}`}).subscribe({
51
+ next: (result) => {
52
+ const {studioHost} = result
53
+ this.setState({studioHost: studioHost ? `https://${studioHost}.sanity.studio` : null})
54
+ },
55
+ error: (error) => {
56
+ console.log('Error while looking for studioHost', error)
57
+ this.setState({
58
+ studioHost: {
59
+ error: 'Something went wrong while looking up studioHost. See console.',
60
+ },
61
+ })
62
+ },
63
+ })
64
+ )
65
+
66
+ // ping assumed graphql endpoint
67
+ this.subscriptions.push(
68
+ versionedClient.observable
69
+ .request({
70
+ method: 'HEAD',
71
+ uri: `/graphql/${dataset}/default`,
72
+ })
73
+ .subscribe({
74
+ next: () => this.setState({graphqlApi: getGraphQlUrl()}),
75
+ error: (error) => {
76
+ if (error.statusCode === 404) {
77
+ this.setState({graphqlApi: null})
78
+ } else {
79
+ console.log('Error while looking for graphqlApi', error)
80
+ this.setState({
81
+ graphqlApi: {
82
+ error: 'Something went wrong while looking up graphqlApi. See console.',
83
+ },
84
+ })
85
+ }
86
+ },
87
+ })
88
+ )
89
+ }
90
+
91
+ componentWillUnmount() {
92
+ this.subscriptions.forEach((sub) => sub.unsubscribe())
93
+ }
94
+
95
+ assembleTableRows() {
96
+ const {graphqlApi, studioHost} = this.state
97
+ const propsData = this.props.data
98
+
99
+ let result = [
100
+ {
101
+ title: 'Sanity project',
102
+ rows: [
103
+ {title: 'Project ID', value: projectId},
104
+ {title: 'Dataset', value: dataset},
105
+ ],
106
+ },
107
+ ]
108
+
109
+ // Handle any apps
110
+ const apps = [studioHost ? {title: 'Studio', value: studioHost} : null]
111
+ .concat(propsData.filter((item) => item.category === 'apps'))
112
+ .filter(Boolean)
113
+ if (apps.length > 0) {
114
+ result = result.concat([{title: 'Apps', rows: apps}])
115
+ }
116
+
117
+ // Handle APIs
118
+ result = result.concat(
119
+ [
120
+ {
121
+ title: 'APIs',
122
+ rows: [
123
+ {title: 'GROQ', value: getGroqUrl()},
124
+ {title: 'GraphQL', value: graphqlApi || 'Not deployed'},
125
+ ],
126
+ },
127
+ ],
128
+ propsData.filter((item) => item.category === 'apis')
129
+ )
130
+
131
+ // Handle whatever else there might be
132
+ const otherStuff = {}
133
+ propsData.forEach((item) => {
134
+ if (item.category !== 'apps' && item.category !== 'apis') {
135
+ if (!otherStuff[item.category]) {
136
+ otherStuff[item.category] = []
137
+ }
138
+ otherStuff[item.category].push(item)
139
+ }
140
+ })
141
+ Object.keys(otherStuff).forEach((category) => {
142
+ result.push({title: category, rows: otherStuff[category]})
143
+ })
144
+
145
+ return result
146
+ }
147
+
148
+ render() {
149
+ return (
150
+ <>
151
+ {this.props.__experimental_before &&
152
+ this.props.__experimental_before.map((widgetConfig, idx) => (
153
+ <WidgetContainer key={String(idx)} config={widgetConfig} />
154
+ ))}
155
+ <Box height="fill" marginTop={this.props.__experimental_before?.length > 0 ? 4 : 0}>
156
+ <DashboardWidget
157
+ footer={
158
+ <Button
159
+ style={{width: '100%'}}
160
+ paddingX={2}
161
+ paddingY={4}
162
+ mode="bleed"
163
+ tone="primary"
164
+ text="Manage project"
165
+ as="a"
166
+ href={getManageUrl()}
167
+ />
168
+ }
169
+ >
170
+ <Card
171
+ paddingY={4}
172
+ radius={2}
173
+ role="table"
174
+ aria-label="Project info"
175
+ aria-describedby="project_info_table"
176
+ >
177
+ <Stack space={4}>
178
+ <Box paddingX={3} as="header">
179
+ <Heading size={1} as="h2" id="project_info_table">
180
+ Project info
181
+ </Heading>
182
+ </Box>
183
+ {this.assembleTableRows().map((item) => {
184
+ if (!item || !item.rows) {
185
+ return null
186
+ }
187
+
188
+ return (
189
+ <Stack key={item.title} space={3}>
190
+ <Card borderBottom padding={3}>
191
+ <Label size={0} muted role="columnheader">
192
+ {item.title}
193
+ </Label>
194
+ </Card>
195
+ <Stack space={4} paddingX={3} role="rowgroup">
196
+ {item.rows.map((row) => {
197
+ return (
198
+ <Grid key={row.title} columns={2} role="row">
199
+ <Text weight="medium" role="rowheader">
200
+ {row.title}
201
+ </Text>
202
+ {isPlainObject(row.value) && <Text size={1}>{row.value.error}</Text>}
203
+ {!isPlainObject(row.value) && (
204
+ <>
205
+ {isUrl(row.value) ? (
206
+ <Text size={1} role="cell" style={{wordBreak: 'break-word'}}>
207
+ <a href={row.value}>{row.value}</a>
208
+ </Text>
209
+ ) : (
210
+ <Code size={1} role="cell" style={{wordBreak: 'break-word'}}>
211
+ {row.value}
212
+ </Code>
213
+ )}
214
+ </>
215
+ )}
216
+ </Grid>
217
+ )
218
+ })}
219
+ </Stack>
220
+ </Stack>
221
+ )
222
+ })}
223
+ </Stack>
224
+ </Card>
225
+ </DashboardWidget>
226
+ </Box>
227
+ </>
228
+ )
229
+ }
230
+ }
231
+
232
+ export default ProjectInfo
@@ -0,0 +1,7 @@
1
+ import ProjectInfo from './ProjectInfo'
2
+
3
+ export default {
4
+ name: 'project-info',
5
+ component: ProjectInfo,
6
+ layout: {width: 'medium'},
7
+ }