@blocklet/did-space-react 0.5.57

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 (102) hide show
  1. package/.turbo/turbo-build.log +50 -0
  2. package/.turbo/turbo-lint$colon$fix.log +8 -0
  3. package/LICENSE +13 -0
  4. package/README.md +52 -0
  5. package/build.config.ts +47 -0
  6. package/es/components/ConnectTo/connect-to.stories.d.ts +10 -0
  7. package/es/components/ConnectTo/index.d.ts +10 -0
  8. package/es/components/ConnectTo/index.js +138 -0
  9. package/es/components/PreviewNFT/index.d.ts +6 -0
  10. package/es/components/PreviewNFT/index.js +79 -0
  11. package/es/components/SpaceCard/index.d.ts +18 -0
  12. package/es/components/SpaceCard/index.js +198 -0
  13. package/es/components/SpaceCard/space-card.stories.d.ts +12 -0
  14. package/es/hooks/use-locale.d.ts +5 -0
  15. package/es/hooks/use-locale.js +14 -0
  16. package/es/hooks/use-mobile.d.ts +4 -0
  17. package/es/hooks/use-mobile.js +9 -0
  18. package/es/hooks/use-space-info.d.ts +8 -0
  19. package/es/hooks/use-space-info.js +30 -0
  20. package/es/icons/empty-space-nft.svg.js +200 -0
  21. package/es/icons/index.d.ts +4 -0
  22. package/es/icons/space-connect-error.svg.js +24 -0
  23. package/es/icons/space-connected.svg.js +24 -0
  24. package/es/icons/space-disconnect.svg.js +24 -0
  25. package/es/index.d.ts +12 -0
  26. package/es/index.js +10 -0
  27. package/es/libs/api.d.ts +2 -0
  28. package/es/libs/api.js +5 -0
  29. package/es/libs/gateway.d.ts +5 -0
  30. package/es/libs/gateway.js +44 -0
  31. package/es/libs/theme.d.ts +1 -0
  32. package/es/libs/util.d.ts +20 -0
  33. package/es/libs/util.js +58 -0
  34. package/es/locales/en.d.ts +2 -0
  35. package/es/locales/en.js +37 -0
  36. package/es/locales/index.d.ts +4 -0
  37. package/es/locales/index.js +9 -0
  38. package/es/locales/zh.d.ts +2 -0
  39. package/es/locales/zh.js +37 -0
  40. package/es/types/index.d.ts +31 -0
  41. package/es/types/index.js +9 -0
  42. package/lib/components/ConnectTo/connect-to.stories.d.ts +10 -0
  43. package/lib/components/ConnectTo/index.d.ts +10 -0
  44. package/lib/components/ConnectTo/index.js +140 -0
  45. package/lib/components/PreviewNFT/index.d.ts +6 -0
  46. package/lib/components/PreviewNFT/index.js +81 -0
  47. package/lib/components/SpaceCard/index.d.ts +18 -0
  48. package/lib/components/SpaceCard/index.js +200 -0
  49. package/lib/components/SpaceCard/space-card.stories.d.ts +12 -0
  50. package/lib/hooks/use-locale.d.ts +5 -0
  51. package/lib/hooks/use-locale.js +16 -0
  52. package/lib/hooks/use-mobile.d.ts +4 -0
  53. package/lib/hooks/use-mobile.js +11 -0
  54. package/lib/hooks/use-space-info.d.ts +8 -0
  55. package/lib/hooks/use-space-info.js +32 -0
  56. package/lib/icons/empty-space-nft.svg.js +215 -0
  57. package/lib/icons/index.d.ts +4 -0
  58. package/lib/icons/space-connect-error.svg.js +39 -0
  59. package/lib/icons/space-connected.svg.js +39 -0
  60. package/lib/icons/space-disconnect.svg.js +39 -0
  61. package/lib/index.d.ts +12 -0
  62. package/lib/index.js +32 -0
  63. package/lib/libs/api.d.ts +2 -0
  64. package/lib/libs/api.js +7 -0
  65. package/lib/libs/gateway.d.ts +5 -0
  66. package/lib/libs/gateway.js +47 -0
  67. package/lib/libs/theme.d.ts +1 -0
  68. package/lib/libs/util.d.ts +20 -0
  69. package/lib/libs/util.js +67 -0
  70. package/lib/locales/en.d.ts +2 -0
  71. package/lib/locales/en.js +39 -0
  72. package/lib/locales/index.d.ts +4 -0
  73. package/lib/locales/index.js +11 -0
  74. package/lib/locales/zh.d.ts +2 -0
  75. package/lib/locales/zh.js +39 -0
  76. package/lib/types/index.d.ts +31 -0
  77. package/lib/types/index.js +11 -0
  78. package/package.json +134 -0
  79. package/src/components/ConnectTo/connect-to.stories.tsx +11 -0
  80. package/src/components/ConnectTo/index.tsx +147 -0
  81. package/src/components/PreviewNFT/index.tsx +72 -0
  82. package/src/components/SpaceCard/index.tsx +230 -0
  83. package/src/components/SpaceCard/space-card.stories.tsx +13 -0
  84. package/src/hooks/use-locale.ts +15 -0
  85. package/src/hooks/use-mobile.ts +8 -0
  86. package/src/hooks/use-space-info.ts +32 -0
  87. package/src/icons/empty-space-nft.svg +59 -0
  88. package/src/icons/index.tsx +4 -0
  89. package/src/icons/space-connect-error.svg +4 -0
  90. package/src/icons/space-connected.svg +4 -0
  91. package/src/icons/space-disconnect.svg +4 -0
  92. package/src/index.ts +16 -0
  93. package/src/libs/api.ts +5 -0
  94. package/src/libs/gateway.ts +55 -0
  95. package/src/libs/theme.ts +18 -0
  96. package/src/libs/util.ts +86 -0
  97. package/src/locales/en.tsx +35 -0
  98. package/src/locales/index.tsx +7 -0
  99. package/src/locales/zh.tsx +35 -0
  100. package/src/types/index.ts +34 -0
  101. package/src/types/shims.d.ts +15 -0
  102. package/vite.config.ts +32 -0
@@ -0,0 +1,2 @@
1
+ declare const _default: unknown;
2
+ export default _default;
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ const flat = require('flat');
4
+
5
+ const zh = flat.flatten({
6
+ common: {
7
+ open: "\u6253\u5F00",
8
+ delete: "\u5220\u9664",
9
+ confirm: "\u786E\u8BA4",
10
+ cancel: "\u53D6\u6D88",
11
+ error: "\u9519\u8BEF"
12
+ },
13
+ storage: {
14
+ spaces: {
15
+ connect: {
16
+ useSpaceGateway: "\u4F7F\u7528 DID Spaces \u7F51\u5173\u8FDE\u63A5",
17
+ useWallet: "\u4F7F\u7528 DID Wallet \u8FDE\u63A5"
18
+ },
19
+ connected: {
20
+ tag: "\u5DF2\u8FDE\u63A5"
21
+ },
22
+ disconnected: {
23
+ tag: "\u8FDE\u63A5\u5DF2\u65AD\u5F00"
24
+ },
25
+ expired: {
26
+ guide: "\u9700\u8981\u6CE8\u610F"
27
+ },
28
+ gateway: {
29
+ add: {
30
+ title: "\u6DFB\u52A0 DID Spaces",
31
+ label: "\u8F93\u5165 DID Spaces \u7684\u5730\u5740",
32
+ invalidUrl: "\u65E0\u6548\u7684 DID Spaces \u7F51\u5173\u5730\u5740"
33
+ }
34
+ }
35
+ }
36
+ }
37
+ });
38
+
39
+ module.exports = zh;
@@ -0,0 +1,31 @@
1
+ export type $TSFixMe = any;
2
+ export interface AuthorizeConnect {
3
+ prefix?: string;
4
+ baseUrl?: string;
5
+ open: boolean;
6
+ action: string;
7
+ checkTimeout: number;
8
+ messages: {
9
+ title: string;
10
+ scan: string;
11
+ confirm: string;
12
+ success: React.ReactNode;
13
+ };
14
+ extraParams?: Record<string, any>;
15
+ checkFn?: Function;
16
+ onClose?: Function;
17
+ }
18
+ export declare enum SpaceStatus {
19
+ UNKNOWN = "unknown",
20
+ CONNECTED = "connected",
21
+ DISCONNECTED = "disconnected",
22
+ EXPIRED = "expired"
23
+ }
24
+ export interface SpaceGateway {
25
+ did: string;
26
+ name: string;
27
+ url: string;
28
+ endpoint: string;
29
+ protected?: boolean;
30
+ loading?: boolean;
31
+ }
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ var SpaceStatus = /* @__PURE__ */ ((SpaceStatus2) => {
4
+ SpaceStatus2["UNKNOWN"] = "unknown";
5
+ SpaceStatus2["CONNECTED"] = "connected";
6
+ SpaceStatus2["DISCONNECTED"] = "disconnected";
7
+ SpaceStatus2["EXPIRED"] = "expired";
8
+ return SpaceStatus2;
9
+ })(SpaceStatus || {});
10
+
11
+ exports.SpaceStatus = SpaceStatus;
package/package.json ADDED
@@ -0,0 +1,134 @@
1
+ {
2
+ "name": "@blocklet/did-space-react",
3
+ "version": "0.5.57",
4
+ "description": "Reusable react components for did space",
5
+ "keywords": [
6
+ "react",
7
+ "components",
8
+ "did-space"
9
+ ],
10
+ "author": "wangshijun<wangshijun2010@gmail.com>",
11
+ "homepage": "https://github.com/ArcBlock/did-spaces#readme",
12
+ "license": "Apache-2.0",
13
+ "main": "lib/index.js",
14
+ "module": "es/index.js",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/ArcBlock/did-spaces.git"
18
+ },
19
+ "scripts": {
20
+ "lint": "tsc --noEmit && eslint src tests --ext js --ext jsx --ext ts --ext tsx",
21
+ "lint:fix": "npm run lint -- --fix",
22
+ "build": "unbuild && node tools/auto-exports.js && npm run cpfiles && node tools/build-types.js",
23
+ "watch": "CONSOLA_LEVEL=1 nodemon -e .jsx,.js,.ts,.tsx -w src -x 'yalc publish --push'",
24
+ "precommit": "CI=1 npm run lint",
25
+ "prepush": "CI=1 npm run lint",
26
+ "prepublish": "npm run build",
27
+ "test": "vitest",
28
+ "coverage": "vitest run --coverage",
29
+ "start": "storybook dev -p 6006",
30
+ "build-storybook": "storybook build",
31
+ "cpfiles": "copyfiles -u 1 './src/**/*.css' lib/ && copyfiles -u 1 './src/**/*.css' es/"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/ArcBlock/did-spaces/issues"
35
+ },
36
+ "exports": {
37
+ ".": {
38
+ "import": "./es/index.js",
39
+ "require": "./lib/index.js"
40
+ },
41
+ "./lib/hooks/*": {
42
+ "import": "./es/hooks/*.js",
43
+ "require": "./lib/hooks/*.js"
44
+ },
45
+ "./lib/components/ConnectTo": {
46
+ "import": "./es/components/ConnectTo/index.js",
47
+ "require": "./lib/components/ConnectTo/index.js"
48
+ },
49
+ "./lib/components/PreviewNFT": {
50
+ "import": "./es/components/PreviewNFT/index.js",
51
+ "require": "./lib/components/PreviewNFT/index.js"
52
+ },
53
+ "./lib/components/SpaceCard": {
54
+ "import": "./es/components/SpaceCard/index.js",
55
+ "require": "./lib/components/SpaceCard/index.js"
56
+ },
57
+ "./lib/icons": {
58
+ "import": "./es/icons/index.js",
59
+ "require": "./lib/icons/index.js"
60
+ },
61
+ "./lib/locales": {
62
+ "import": "./es/locales/index.js",
63
+ "require": "./lib/locales/index.js"
64
+ },
65
+ "./lib/types": {
66
+ "import": "./es/types/index.js",
67
+ "require": "./lib/types/index.js"
68
+ }
69
+ },
70
+ "dependencies": {
71
+ "@abtnode/util": "1.16.32",
72
+ "@arcblock/did": "^1.18.135",
73
+ "@arcblock/ux": "^2.10.33",
74
+ "@blocklet/js-sdk": "1.16.32",
75
+ "@mui/icons-material": "^5.16.6",
76
+ "@mui/lab": "^5.0.0-alpha.173",
77
+ "@mui/material": "^5.16.6",
78
+ "@mui/system": "^5.16.6",
79
+ "@ocap/util": "^1.18.135",
80
+ "ahooks": "^3.8.0",
81
+ "axios": "^1.7.5",
82
+ "flat": "^5.0.2",
83
+ "is-url": "^1.2.4",
84
+ "lodash": "^4.17.21",
85
+ "p-wait-for": "3",
86
+ "react-error-boundary": "^4.0.13",
87
+ "ufo": "^1.5.4"
88
+ },
89
+ "peerDependencies": {
90
+ "react": ">=18.1.0"
91
+ },
92
+ "publishConfig": {
93
+ "access": "public"
94
+ },
95
+ "devDependencies": {
96
+ "@abtnode/client": "^1.16.32",
97
+ "@arcblock/eslint-config-ts": "^0.3.2",
98
+ "@babel/cli": "^7.24.8",
99
+ "@babel/core": "^7.25.2",
100
+ "@babel/preset-env": "^7.25.8",
101
+ "@babel/preset-react": "^7.25.7",
102
+ "@babel/preset-typescript": "^7.25.7",
103
+ "@storybook/addon-essentials": "^7.6.20",
104
+ "@storybook/addon-interactions": "^7.6.20",
105
+ "@storybook/addon-links": "^7.6.20",
106
+ "@storybook/addon-onboarding": "^1.0.11",
107
+ "@storybook/blocks": "^7.6.20",
108
+ "@storybook/react": "^7.6.20",
109
+ "@storybook/react-vite": "^7.6.20",
110
+ "@storybook/test": "^7.6.20",
111
+ "@svgr/rollup": "^8.1.0",
112
+ "@types/react": "^18.3.3",
113
+ "@types/react-dom": "^18.3.0",
114
+ "@vitejs/plugin-legacy": "^5.4.1",
115
+ "@vitest/coverage-v8": "^2.1.2",
116
+ "babel-plugin-inline-react-svg": "^2.0.2",
117
+ "copyfiles": "^2.4.1",
118
+ "eslint": "^8.57.0",
119
+ "glob": "^10.4.5",
120
+ "prettier": "^2.8.8",
121
+ "react": "^18.3.1",
122
+ "react-dom": "^18.3.1",
123
+ "rollup-plugin-node-builtins": "^2.1.2",
124
+ "storybook": "^7.6.20",
125
+ "type-fest": "^4.23.0",
126
+ "typescript": "~5.5.4",
127
+ "unbuild": "^2.0.0",
128
+ "vite": "^5.4.8",
129
+ "vite-plugin-babel": "^1.2.0",
130
+ "vite-plugin-node-polyfills": "^0.22.0",
131
+ "vitest": "^2.1.2"
132
+ },
133
+ "gitHead": "4ead6cc11feaff06936aba21cf3919e01cd12e97"
134
+ }
@@ -0,0 +1,11 @@
1
+ import Basic from './stories/basic';
2
+
3
+ export default {
4
+ title: 'Connect/ConnectTo',
5
+ parameters: {
6
+ layout: 'padded',
7
+ },
8
+ tags: ['autodocs'],
9
+ };
10
+
11
+ export { Basic };
@@ -0,0 +1,147 @@
1
+ import { useState } from 'react';
2
+ import { isValid as isValidDid } from '@arcblock/did';
3
+ import { CircularProgress, DialogContentText, TextField, Typography } from '@mui/material';
4
+ import Button from '@arcblock/ux/lib/Button';
5
+ import SplitButton, { SplitButtonProps } from '@arcblock/ux/lib/SplitButton';
6
+ import Dialog from '@arcblock/ux/lib/Dialog';
7
+
8
+ import { extraDIDSpacesCoreUrl, getSpaceDidFromGatewayUrl } from '../../libs/util';
9
+ import { getSpaceGatewayUrl, isValidSpaceGatewayUrl } from '../../libs/gateway';
10
+ import useLocale from '../../hooks/use-locale';
11
+
12
+ export interface ConnectToProps extends Omit<SplitButtonProps, 'menu' | 'onClick'> {
13
+ onWalletConnect?: () => void;
14
+ onGatewayConnect?: (params: { spaceDid: string; spaceGatewayUrl: string }) => void;
15
+ }
16
+
17
+ function ConnectTo({ style, onWalletConnect, onGatewayConnect, ...rest }: ConnectToProps) {
18
+ const { t } = useLocale();
19
+ const [url, setUrl] = useState('');
20
+ const [loading, setLoading] = useState(false);
21
+ const [open, setOpen] = useState(false);
22
+ const [errorMessage, setErrorMessage] = useState('');
23
+
24
+ const handleUseSpaceGatewayConnect = async () => {
25
+ try {
26
+ setLoading(true);
27
+
28
+ const spaceGatewayUrl = await getSpaceGatewayUrl(url);
29
+ const didSpacesCoreUrl = extraDIDSpacesCoreUrl(spaceGatewayUrl);
30
+ const spaceDid = getSpaceDidFromGatewayUrl(url);
31
+
32
+ if (!isValidDid(spaceDid!) || !(await isValidSpaceGatewayUrl(didSpacesCoreUrl))) {
33
+ throw new Error(t('storage.spaces.gateway.add.invalidUrl'));
34
+ }
35
+
36
+ onGatewayConnect?.({
37
+ spaceDid: spaceDid!,
38
+ spaceGatewayUrl: didSpacesCoreUrl,
39
+ });
40
+ setOpen(false);
41
+ } catch (err) {
42
+ console.error(err);
43
+ setErrorMessage((err as Error).message);
44
+ } finally {
45
+ setLoading(false);
46
+ }
47
+ };
48
+
49
+ const openGatewayInput = () => {
50
+ setErrorMessage('');
51
+ setUrl('');
52
+ setOpen(true);
53
+ };
54
+
55
+ return (
56
+ <>
57
+ <SplitButton
58
+ menu={[
59
+ // @ts-expect-error
60
+ <SplitButton.Item
61
+ sx={{
62
+ textTransform: 'none',
63
+ }}
64
+ key="1"
65
+ onClick={openGatewayInput}
66
+ size="small"
67
+ {...rest}
68
+ >
69
+ {t('storage.spaces.connect.useSpaceGateway')}
70
+ </SplitButton.Item>,
71
+ ]}
72
+ onClick={onWalletConnect}
73
+ color="primary"
74
+ // @ts-expect-error
75
+ style={{ textTransform: 'none !important', fontSize: '1rem', ...style }}
76
+ size="small"
77
+ {...rest}
78
+ >
79
+ <Typography sx={{ fontWeight: 'bold', textTransform: 'none' }}>
80
+ {t('storage.spaces.connect.useWallet')}
81
+ </Typography>
82
+ </SplitButton>
83
+ <Dialog
84
+ title={t('storage.spaces.gateway.add.title')}
85
+ fullWidth
86
+ maxWidth="md"
87
+ open={open}
88
+ onClose={() => setOpen(false)}
89
+ PaperProps={{ style: { minHeight: 'auto' } }}
90
+ actions={
91
+ <>
92
+ <Button
93
+ variant="outlined"
94
+ onClick={(e) => {
95
+ e.stopPropagation();
96
+ setOpen(false);
97
+ }}
98
+ color="inherit"
99
+ >
100
+ {t('common.cancel')}
101
+ </Button>
102
+ <Button
103
+ onClick={handleUseSpaceGatewayConnect}
104
+ color="primary"
105
+ disabled={loading || !url}
106
+ variant="contained"
107
+ autoFocus
108
+ >
109
+ {loading && <CircularProgress size={16} />}
110
+ {t('common.confirm')}
111
+ </Button>
112
+ </>
113
+ }
114
+ >
115
+ <div style={{ paddingTop: 12, overflowY: 'hidden' }}>
116
+ <DialogContentText component="div">
117
+ <Typography component="div">
118
+ <TextField
119
+ label={t('storage.spaces.gateway.add.label')}
120
+ autoComplete="off"
121
+ variant="outlined"
122
+ name="url"
123
+ fullWidth
124
+ value={url}
125
+ onChange={(e) => {
126
+ setErrorMessage('');
127
+ setUrl(e.target.value);
128
+ }}
129
+ disabled={loading}
130
+ error={Boolean(errorMessage)}
131
+ helperText={errorMessage}
132
+ onKeyDown={async (e) => {
133
+ if (e.key === 'Enter') {
134
+ await handleUseSpaceGatewayConnect();
135
+ }
136
+ }}
137
+ autoFocus
138
+ />
139
+ </Typography>
140
+ </DialogContentText>
141
+ </div>
142
+ </Dialog>
143
+ </>
144
+ );
145
+ }
146
+
147
+ export default ConnectTo;
@@ -0,0 +1,72 @@
1
+ import { useState } from 'react';
2
+ import { Dialog, DialogContent, IconButton } from '@mui/material';
3
+ import CloseOutlinedIcon from '@mui/icons-material/CloseOutlined';
4
+ import { EmptySpacesNFT } from '../../icons';
5
+
6
+ function PreviewSpaceNft({ src, width = '58px', height = '58px' }: { src: string; width?: string; height?: string }) {
7
+ const [open, setOpen] = useState(false);
8
+
9
+ const handleOpen = () => setOpen(true);
10
+ const handleClose = () => setOpen(false);
11
+
12
+ return (
13
+ <>
14
+ <div style={{ position: 'relative' }} onClick={handleOpen}>
15
+ <object data={src} width={width} height={height}>
16
+ <EmptySpacesNFT viewBox="0 0 228 258" style={{ cursor: 'pointer', width: '64px', height: '64px' }} />
17
+ </object>
18
+ <div
19
+ style={{
20
+ position: 'absolute',
21
+ top: 0,
22
+ left: 0,
23
+ width: '100%',
24
+ height: '100%',
25
+ zIndex: '1',
26
+ cursor: 'pointer',
27
+ }}
28
+ onClick={handleOpen}
29
+ />
30
+ </div>
31
+ <Dialog
32
+ open={open}
33
+ onClose={handleClose}
34
+ aria-labelledby="preview-space-nft-display"
35
+ aria-describedby="preview space nft display"
36
+ fullWidth
37
+ maxWidth="md"
38
+ >
39
+ <DialogContent style={{ padding: '8px 8px', backgroundColor: 'rgba(0,0,0,0.8)' }}>
40
+ <IconButton
41
+ color="inherit"
42
+ onClick={handleClose}
43
+ aria-label="close"
44
+ style={{ position: 'absolute', top: 8, right: 8, color: 'white' }}
45
+ >
46
+ <CloseOutlinedIcon />
47
+ </IconButton>
48
+ (
49
+ <object
50
+ data={src}
51
+ style={{
52
+ width: '100%',
53
+ height: '75vh',
54
+ objectFit: 'contain',
55
+ }}
56
+ >
57
+ <EmptySpacesNFT
58
+ viewBox="0 0 228 258"
59
+ style={{
60
+ width: '100%',
61
+ height: '75vh',
62
+ }}
63
+ />
64
+ </object>
65
+ )
66
+ </DialogContent>
67
+ </Dialog>
68
+ </>
69
+ );
70
+ }
71
+
72
+ export default PreviewSpaceNft;
@@ -0,0 +1,230 @@
1
+ import isUndefined from 'lodash/isUndefined';
2
+ import { joinURL, withQuery } from 'ufo';
3
+ import { Box, BoxProps, Link, Stack } from '@mui/material';
4
+ import DidAddress from '@arcblock/ux/lib/DID';
5
+ import { styled } from '@arcblock/ux/lib/Theme';
6
+
7
+ import { useEffect, useRef, useState } from 'react';
8
+ import useMobile from '../../hooks/use-mobile';
9
+ import useSpaceInfo from '../../hooks/use-space-info';
10
+ import useLocale from '../../hooks/use-locale';
11
+ import {
12
+ classNames,
13
+ getDIDSpaceDidFromEndpoint,
14
+ getDIDSpaceUrlFromEndpoint,
15
+ getSpaceGatewayUrlFromEndpoint,
16
+ getSpaceNftDisplayUrlFromEndpoint,
17
+ } from '../../libs/util';
18
+ import { SpaceGateway, SpaceStatus } from '../../types';
19
+ import { SpacesConnected, SpaceDisconnected, SpaceConnectError } from '../../icons';
20
+ import PreviewNft from '../PreviewNFT';
21
+
22
+ function Status({
23
+ spaceUrl,
24
+ status,
25
+ refresh,
26
+ sx,
27
+ ...rest
28
+ }: { spaceUrl: string; status: SpaceStatus; refresh: () => void } & BoxProps) {
29
+ const { t } = useLocale();
30
+ const iconStyle = { marginLeft: '8px', marginRight: '4px' };
31
+
32
+ // error
33
+ let icon: React.ReactNode = null;
34
+ let text: React.ReactNode = null;
35
+
36
+ if (status === SpaceStatus.CONNECTED) {
37
+ icon = <SpacesConnected style={iconStyle} />;
38
+ text = <span style={{ color: '#047857' }}>{t('storage.spaces.connected.tag')}</span>;
39
+ }
40
+
41
+ if (status === SpaceStatus.DISCONNECTED) {
42
+ icon = <SpaceDisconnected style={iconStyle} />;
43
+ text = <span style={{ color: '#626a77' }}>{t('storage.spaces.disconnected.tag')}</span>;
44
+ }
45
+
46
+ if (status === SpaceStatus.EXPIRED) {
47
+ icon = <SpaceConnectError style={iconStyle} />;
48
+ text = (
49
+ <Box component="span" sx={{ display: 'flex', alignItems: 'center', color: 'error.main' }}>
50
+ <span>{t('storage.spaces.expired.guide')}</span>
51
+ <Link
52
+ href={withQuery(joinURL(spaceUrl, 'overview'), { guide: 1 })}
53
+ target="_blank"
54
+ underline="always"
55
+ color="error"
56
+ sx={{ ml: 0.5 }}
57
+ >
58
+ [{t('common.open')}]
59
+ </Link>
60
+ </Box>
61
+ );
62
+ }
63
+
64
+ // 如果 Space 为 Error 状态,则在页面重新可见时刷新 Space 状态(此时 error 可能已经在外部处理了)
65
+ useEffect(() => {
66
+ const handleVisibilityChange = () => {
67
+ if (!document.hidden) {
68
+ refresh();
69
+ }
70
+ };
71
+ if (status === SpaceStatus.EXPIRED) {
72
+ document.addEventListener('visibilitychange', handleVisibilityChange);
73
+ } else {
74
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
75
+ }
76
+
77
+ return () => document.removeEventListener('visibilitychange', handleVisibilityChange);
78
+ }, [status, refresh]);
79
+
80
+ return (
81
+ <Box
82
+ component="span"
83
+ sx={{ display: 'flex', alignItems: 'center', color: 'primary.main', fontSize: 14, ...sx }}
84
+ {...rest}
85
+ >
86
+ {icon} {text}
87
+ </Box>
88
+ );
89
+ }
90
+
91
+ export type Action =
92
+ | React.ReactNode
93
+ | ((props: {
94
+ spaceGateway: SpaceGateway;
95
+ spaceStatus: SpaceStatus;
96
+ selected: boolean;
97
+ compat: boolean;
98
+ refresh: () => void;
99
+ }) => React.ReactNode);
100
+
101
+ export interface SpaceCardProps extends BoxProps {
102
+ endpoint: string; // 形如 ${domain}/app/api/space/${spaceDid}/app/${appDid}/object/
103
+ selected?: boolean;
104
+ compat?: boolean;
105
+ action?: Action;
106
+ deps?: any[];
107
+ }
108
+
109
+ function SpaceCard({ endpoint, selected = false, compat, action, className, deps, ...rest }: SpaceCardProps) {
110
+ const isMobile = useMobile();
111
+ let isCompact = compat;
112
+
113
+ if (isUndefined(isCompact)) {
114
+ isCompact = isMobile;
115
+ }
116
+
117
+ const spaceDid = getDIDSpaceDidFromEndpoint(endpoint);
118
+ const spaceUrl = getDIDSpaceUrlFromEndpoint(endpoint);
119
+ const gatewayUrl = getSpaceGatewayUrlFromEndpoint(endpoint);
120
+ const [refreshSpaceInfo, setRefreshSpaceInfo] = useState(false);
121
+ const refresh = () => setRefreshSpaceInfo((p) => !p);
122
+ const { data: spaceInfo, loading } = useSpaceInfo({
123
+ endpoint,
124
+ deps: [refreshSpaceInfo].concat(deps ?? []),
125
+ });
126
+ const spaceName = spaceInfo?.spaceName ?? '';
127
+
128
+ // 是否授权连接
129
+ const hasPermission = spaceInfo?.hasPermission ?? false;
130
+ // 是否在订阅期内
131
+ const isAvailable = spaceInfo?.isAvailable ?? false;
132
+
133
+ const spaceStatus = useRef(SpaceStatus.UNKNOWN);
134
+ if (spaceInfo && !loading) {
135
+ if (!isAvailable) {
136
+ spaceStatus.current = SpaceStatus.EXPIRED;
137
+ } else if (hasPermission) {
138
+ spaceStatus.current = SpaceStatus.CONNECTED;
139
+ } else {
140
+ spaceStatus.current = SpaceStatus.DISCONNECTED;
141
+ }
142
+ }
143
+
144
+ const renderAction = () => {
145
+ if (loading) return null;
146
+ if (typeof action === 'function') {
147
+ return action({
148
+ spaceGateway: {
149
+ did: spaceDid!,
150
+ name: spaceName,
151
+ url: gatewayUrl,
152
+ endpoint,
153
+ },
154
+ spaceStatus: spaceStatus.current,
155
+ selected,
156
+ compat: isCompact,
157
+ refresh,
158
+ });
159
+ }
160
+
161
+ return action;
162
+ };
163
+
164
+ return (
165
+ <BoxContainer
166
+ className={classNames(className, {
167
+ selected,
168
+ error: spaceStatus.current === SpaceStatus.EXPIRED || spaceStatus.current === SpaceStatus.DISCONNECTED,
169
+ })}
170
+ {...rest}
171
+ >
172
+ <Box display="flex" alignItems="center">
173
+ <PreviewNft src={getSpaceNftDisplayUrlFromEndpoint(endpoint)} width="72px" height="72px" />
174
+ <Stack ml={2} flex={1} spacing={1} minWidth={0}>
175
+ <Box display="flex" alignItems="center" sx={{ whiteSpace: 'nowrap' }}>
176
+ <Box className="space-name">{spaceName}</Box>
177
+ {!isCompact && selected && (
178
+ <Status spaceUrl={spaceUrl} status={spaceStatus.current} refresh={refresh} sx={{ mr: 1 }} />
179
+ )}
180
+ </Box>
181
+ <DidAddress
182
+ copyable={false}
183
+ size={14}
184
+ compact
185
+ responsive={false}
186
+ did={spaceDid}
187
+ sx={{
188
+ '.did-address-text': {
189
+ color: 'text.secondary',
190
+ },
191
+ }}
192
+ />
193
+ </Stack>
194
+ {!isCompact && renderAction()}
195
+ </Box>
196
+ {isCompact && (
197
+ <Box display="flex" alignItems="center" marginTop={0.5}>
198
+ {selected && <Status spaceUrl={spaceUrl} status={spaceStatus.current} refresh={refresh} flex={1} />}
199
+ <Box flex={1} />
200
+ {renderAction()}
201
+ </Box>
202
+ )}
203
+ </BoxContainer>
204
+ );
205
+ }
206
+
207
+ const BoxContainer = styled(Box)`
208
+ display: flex;
209
+ flex-direction: column;
210
+ padding: 16px;
211
+ box-sizing: border-box;
212
+ position: relative;
213
+ border: 1px solid ${({ theme }) => theme.palette.grey['200'] || '#f8f8f8'};
214
+ border-radius: 8px;
215
+
216
+ &.selected {
217
+ border-color: ${({ theme }) => theme.palette.primary.main || '#3b82f6'};
218
+ &.error {
219
+ border-color: ${({ theme }) => theme.palette.error.main || '#d32f2f'};
220
+ }
221
+ }
222
+
223
+ .space-name {
224
+ white-space: nowrap;
225
+ overflow: hidden;
226
+ text-overflow: ellipsis;
227
+ }
228
+ `;
229
+
230
+ export default SpaceCard;