sanity-plugin-workspace-home 1.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 +21 -0
- package/README.md +72 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.esm.js +6161 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +6161 -0
- package/dist/index.js.map +1 -0
- package/package.json +88 -0
- package/sanity.json +8 -0
- package/src/components/WorkspaceHome.tsx +41 -0
- package/src/components/WorkspacePreview.tsx +77 -0
- package/src/index.ts +29 -0
- package/src/tool/index.ts +10 -0
- package/v2-incompatible.js +11 -0
package/package.json
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sanity-plugin-workspace-home",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A home screen for your multi-workspace Sanity Studio",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"sanity",
|
|
7
|
+
"sanity-plugin"
|
|
8
|
+
],
|
|
9
|
+
"homepage": "https://github.com/sanity-io/sanity-plugin-workspace-home#readme",
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/sanity-io/sanity-plugin-workspace-home/issues"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git@github.com:sanity-io/sanity-plugin-workspace-home.git"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "Sanity <hello@sanity.io>",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"source": "./src/index.ts",
|
|
23
|
+
"require": "./dist/index.js",
|
|
24
|
+
"import": "./dist/index.esm.js",
|
|
25
|
+
"default": "./dist/index.esm.js"
|
|
26
|
+
},
|
|
27
|
+
"./package.json": "./package.json"
|
|
28
|
+
},
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"module": "./dist/index.esm.js",
|
|
31
|
+
"source": "./src/index.ts",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"sanity.json",
|
|
36
|
+
"src",
|
|
37
|
+
"v2-incompatible.js"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "run-s clean && plugin-kit verify-package --silent && pkg-utils build --strict && pkg-utils --strict",
|
|
41
|
+
"clean": "rimraf dist",
|
|
42
|
+
"format": "prettier --write --cache --ignore-unknown .",
|
|
43
|
+
"link-watch": "plugin-kit link-watch",
|
|
44
|
+
"lint": "eslint .",
|
|
45
|
+
"prepublishOnly": "run-s build",
|
|
46
|
+
"watch": "pkg-utils watch --strict",
|
|
47
|
+
"prepare": "husky install"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@sanity/incompatible-plugin": "^1.0.4"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@commitlint/cli": "^17.5.0",
|
|
54
|
+
"@commitlint/config-conventional": "^17.4.4",
|
|
55
|
+
"@sanity/pkg-utils": "^2.2.13",
|
|
56
|
+
"@sanity/plugin-kit": "^3.1.7",
|
|
57
|
+
"@sanity/semantic-release-preset": "^4.0.2",
|
|
58
|
+
"@types/react": "^18.0.28",
|
|
59
|
+
"@types/styled-components": "^5.1.26",
|
|
60
|
+
"@typescript-eslint/eslint-plugin": "^5.56.0",
|
|
61
|
+
"@typescript-eslint/parser": "^5.56.0",
|
|
62
|
+
"eslint": "^8.36.0",
|
|
63
|
+
"eslint-config-prettier": "^8.8.0",
|
|
64
|
+
"eslint-config-sanity": "^6.0.0",
|
|
65
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
66
|
+
"eslint-plugin-react": "^7.32.2",
|
|
67
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
68
|
+
"husky": "^8.0.3",
|
|
69
|
+
"lint-staged": "^13.2.0",
|
|
70
|
+
"npm-run-all": "^4.1.5",
|
|
71
|
+
"prettier": "^2.8.6",
|
|
72
|
+
"prettier-plugin-packagejson": "^2.4.3",
|
|
73
|
+
"react": "^18.2.0",
|
|
74
|
+
"react-dom": "^18.2.0",
|
|
75
|
+
"react-is": "^18.2.0",
|
|
76
|
+
"rimraf": "^4.4.0",
|
|
77
|
+
"sanity": "^3.7.1",
|
|
78
|
+
"typescript": "^5.0.2"
|
|
79
|
+
},
|
|
80
|
+
"peerDependencies": {
|
|
81
|
+
"react": "^18",
|
|
82
|
+
"sanity": "^3",
|
|
83
|
+
"styled-components": "^5.3.9"
|
|
84
|
+
},
|
|
85
|
+
"engines": {
|
|
86
|
+
"node": ">=14"
|
|
87
|
+
}
|
|
88
|
+
}
|
package/sanity.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {useActiveWorkspace, useWorkspaces} from 'sanity'
|
|
2
|
+
import {Button, Flex, Container, Stack, Heading, Card} from '@sanity/ui'
|
|
3
|
+
|
|
4
|
+
import WorkspacePreview from './WorkspacePreview'
|
|
5
|
+
import {useEffect} from 'react'
|
|
6
|
+
|
|
7
|
+
export default function WorkspaceHome() {
|
|
8
|
+
const [home, ...workspaces] = useWorkspaces()
|
|
9
|
+
const {setActiveWorkspace} = useActiveWorkspace()
|
|
10
|
+
|
|
11
|
+
// Listen to keypress of workspace index
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const handleKeypress = (event: KeyboardEvent) => {
|
|
14
|
+
const index = parseInt(event.key, 10) - 1
|
|
15
|
+
if (index >= 0 && index < workspaces.length) {
|
|
16
|
+
setActiveWorkspace(workspaces[index].name)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
window.addEventListener('keypress', handleKeypress)
|
|
21
|
+
|
|
22
|
+
return () => {
|
|
23
|
+
window.removeEventListener('keypress', handleKeypress)
|
|
24
|
+
}
|
|
25
|
+
}, [workspaces, setActiveWorkspace])
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Card tone="transparent" height="fill">
|
|
29
|
+
<Flex direction="column" height="fill" padding={[4, 5, 6, 6]}>
|
|
30
|
+
<Container>
|
|
31
|
+
<Stack space={4}>
|
|
32
|
+
<Heading>Workspaces</Heading>
|
|
33
|
+
{workspaces.map((workspace, index) => (
|
|
34
|
+
<WorkspacePreview key={workspace.name} workspace={workspace} index={index} />
|
|
35
|
+
))}
|
|
36
|
+
</Stack>
|
|
37
|
+
</Container>
|
|
38
|
+
</Flex>
|
|
39
|
+
</Card>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {Button, Hotkeys, Stack, Box, Flex, Heading, Card, Text} from '@sanity/ui'
|
|
2
|
+
// import {StarIcon} from '@sanity/icons'
|
|
3
|
+
import React, {createElement, isValidElement, useMemo} from 'react'
|
|
4
|
+
import {isValidElementType} from 'react-is'
|
|
5
|
+
import {useActiveWorkspace, WorkspaceSummary} from 'sanity'
|
|
6
|
+
import styled from 'styled-components'
|
|
7
|
+
|
|
8
|
+
const createIcon = (icon: React.ComponentType | React.ReactNode) => {
|
|
9
|
+
if (isValidElementType(icon)) return createElement(icon)
|
|
10
|
+
if (isValidElement(icon)) return icon
|
|
11
|
+
return undefined
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const MediaCard = styled(Card)`
|
|
15
|
+
width: 35px;
|
|
16
|
+
height: 35px;
|
|
17
|
+
|
|
18
|
+
svg {
|
|
19
|
+
width: 100%;
|
|
20
|
+
height: 100%;
|
|
21
|
+
}
|
|
22
|
+
`
|
|
23
|
+
|
|
24
|
+
export type WorkspacePreviewProps = {
|
|
25
|
+
workspace: WorkspaceSummary
|
|
26
|
+
index: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function WorkspacePreview(props: WorkspacePreviewProps) {
|
|
30
|
+
const {icon, title, name, subtitle} = props.workspace
|
|
31
|
+
const {setActiveWorkspace} = useActiveWorkspace()
|
|
32
|
+
const iconComponent = useMemo(() => createIcon(icon), [icon])
|
|
33
|
+
|
|
34
|
+
// TODO: Favorites feature
|
|
35
|
+
// get favorite workspace name from localStorage
|
|
36
|
+
// const favoriteWorkspace = localStorage.getItem('favoriteWorkspace')
|
|
37
|
+
// save local state to force re-render
|
|
38
|
+
// const [isFavourite, setIsFavourite] = React.useState(favoriteWorkspace === name)
|
|
39
|
+
|
|
40
|
+
// set favorite workspace name to localStorage
|
|
41
|
+
// const setFavoriteWorkspace = useCallback(() => {
|
|
42
|
+
// localStorage.setItem('favoriteWorkspace', name)
|
|
43
|
+
// }, [name])
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Card shadow={1} radius={2}>
|
|
47
|
+
<Flex align="center" gap={[2, 3, 4, 4]} paddingX={[2, 3, 4, 4]}>
|
|
48
|
+
<Hotkeys keys={[String(props.index + 1)]} padding={2} />
|
|
49
|
+
<Box flex={1}>
|
|
50
|
+
<Stack>
|
|
51
|
+
<Button mode="bleed" tone="default" onClick={() => setActiveWorkspace(name)}>
|
|
52
|
+
<Flex align="center" gap={[2, 3, 4, 4]}>
|
|
53
|
+
{iconComponent ? <MediaCard>{createIcon(iconComponent)}</MediaCard> : null}
|
|
54
|
+
<Box flex={1}>
|
|
55
|
+
<Stack space={[1, 2, 3, 3]}>
|
|
56
|
+
<Heading>{title || name}</Heading>
|
|
57
|
+
{subtitle ? <Text muted>{subtitle}</Text> : null}
|
|
58
|
+
</Stack>
|
|
59
|
+
</Box>
|
|
60
|
+
</Flex>
|
|
61
|
+
</Button>
|
|
62
|
+
</Stack>
|
|
63
|
+
</Box>
|
|
64
|
+
{/* <Button
|
|
65
|
+
mode={isFavourite ? 'default' : 'bleed'}
|
|
66
|
+
tone={isFavourite ? 'primary' : 'default'}
|
|
67
|
+
icon={StarIcon}
|
|
68
|
+
title="Favorite"
|
|
69
|
+
onClick={() => {
|
|
70
|
+
setIsFavourite(!isFavourite)
|
|
71
|
+
setFavoriteWorkspace()
|
|
72
|
+
}}
|
|
73
|
+
/> */}
|
|
74
|
+
</Flex>
|
|
75
|
+
</Card>
|
|
76
|
+
)
|
|
77
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {Config, definePlugin} from 'sanity'
|
|
2
|
+
import {HomeIcon} from '@sanity/icons'
|
|
3
|
+
|
|
4
|
+
import {workspaceHomeTool} from './tool'
|
|
5
|
+
|
|
6
|
+
export const workspaceHome = definePlugin(() => {
|
|
7
|
+
return {
|
|
8
|
+
name: 'sanity-plugin-workspace-home',
|
|
9
|
+
tools: [workspaceHomeTool],
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
type WorkspaceHomeConfigProps = {
|
|
14
|
+
projectId: string
|
|
15
|
+
dataset: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const workspaceHomeConfig = ({
|
|
19
|
+
projectId = ``,
|
|
20
|
+
dataset = ``,
|
|
21
|
+
}: WorkspaceHomeConfigProps): Config => ({
|
|
22
|
+
name: 'home',
|
|
23
|
+
title: 'Home',
|
|
24
|
+
basePath: '/home',
|
|
25
|
+
projectId,
|
|
26
|
+
dataset,
|
|
27
|
+
icon: HomeIcon,
|
|
28
|
+
plugins: [workspaceHome()],
|
|
29
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const {showIncompatiblePluginDialog} = require('@sanity/incompatible-plugin')
|
|
2
|
+
const {name, version, sanityExchangeUrl} = require('./package.json')
|
|
3
|
+
|
|
4
|
+
export default showIncompatiblePluginDialog({
|
|
5
|
+
name: name,
|
|
6
|
+
versions: {
|
|
7
|
+
v3: version,
|
|
8
|
+
v2: undefined,
|
|
9
|
+
},
|
|
10
|
+
sanityExchangeUrl,
|
|
11
|
+
})
|