@ossy/resources 1.12.2 → 3.0.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.
- package/package.json +16 -4
- package/src/AudioResource.jsx +2 -2
- package/src/CreateDirectory.jsx +2 -10
- package/src/CreateDirectory.page.jsx +1 -1
- package/src/CreateDocument.jsx +2 -2
- package/src/CreateDocument.page.jsx +1 -1
- package/src/Definition.js +3 -2
- package/src/DocumentEdit.jsx +10 -38
- package/src/DocumentView.jsx +11 -22
- package/src/GenericResourceCard.jsx +25 -7
- package/src/GenericResourceDetail.jsx +6 -60
- package/src/GenericResourceForm.jsx +171 -37
- package/src/ImageResource.jsx +1 -1
- package/src/PDFResource.jsx +1 -1
- package/src/PlatformFileField.jsx +239 -0
- package/src/ResourceContentPage.jsx +5 -5
- package/src/ResourceDescription.jsx +1 -1
- package/src/ResourceDetails.jsx +3 -4
- package/src/ResourceDialogMove.jsx +3 -3
- package/src/ResourceFactory.jsx +11 -64
- package/src/ResourceGenericView.jsx +1 -1
- package/src/ResourceList.jsx +89 -106
- package/src/ResourcePanel.jsx +40 -57
- package/src/ResourceTags.jsx +1 -1
- package/src/ResourcesPage.jsx +26 -35
- package/src/SchemaDetailView.jsx +97 -0
- package/src/SchemaFormDefault.jsx +86 -0
- package/src/SchemaPresenter.jsx +207 -0
- package/src/Upload.jsx +6 -25
- package/src/Upload.page.jsx +1 -1
- package/src/UploadResources.jsx +2 -2
- package/src/VideoResource.jsx +1 -1
- package/src/access-filter.spec.js +1 -1
- package/src/create-page-view.action.js +1 -1
- package/src/create-page-view.task.js +10 -9
- package/src/create.action.js +1 -1
- package/src/create.task.js +27 -29
- package/src/delete.action.js +1 -1
- package/src/delete.task.js +9 -13
- package/src/en.translations.json +25 -24
- package/src/get-page-view-stats.action.js +1 -1
- package/src/get-page-view-stats.task.js +3 -4
- package/src/get-resource-alias.api.js +8 -0
- package/src/get-resource-info-alias.api.js +8 -0
- package/src/get-resource-info.api.js +36 -0
- package/src/get-resource-variant.api.js +8 -0
- package/src/get-resource.api.js +51 -0
- package/src/get.action.js +1 -1
- package/src/get.task.js +6 -6
- package/src/index.js +10 -0
- package/src/list.action.js +1 -1
- package/src/list.task.js +1 -1
- package/src/platform-file-field.component.jsx +5 -0
- package/src/platform-image-field.component.jsx +5 -0
- package/src/resource-create.page.jsx +5 -5
- package/src/resource-detail.page.jsx +1 -1
- package/src/resource-read.helpers.js +151 -0
- package/src/resource-stream.helpers.js +20 -0
- package/src/resource-stream.js +108 -0
- package/src/resource.helpers.js +9 -9
- package/src/resources.attach-media-urls.js +9 -3
- package/src/resources.events.js +42 -81
- package/src/{resources.spec.js → resources.integration.spec.js} +19 -19
- package/src/resources.queries.js +5 -2
- package/src/schema-detail.component.jsx +24 -0
- package/src/schema-form-default.component.jsx +14 -0
- package/src/schemaFormSlots.js +6 -0
- package/src/schemaViewSlots.js +6 -0
- package/src/search.action.js +1 -1
- package/src/search.task.js +1 -1
- package/src/server.js +2 -1
- package/src/sv.translations.json +25 -24
- package/src/update-access.action.js +1 -1
- package/src/update-access.task.js +9 -8
- package/src/update-content.action.js +1 -1
- package/src/update-content.task.js +25 -27
- package/src/update-location.action.js +1 -1
- package/src/update-location.task.js +9 -8
- package/src/update-name.action.js +1 -1
- package/src/update-name.task.js +9 -8
- package/src/upload-named-version.action.js +1 -1
- package/src/upload-named-version.task.js +16 -22
- package/src/useActivePath.jsx +3 -8
- package/src/useDocumentValidator.js +16 -0
- package/src/useSchemaEngine.js +9 -0
- package/src/useSchemas.js +29 -0
- package/src/useSelectedResourceId.jsx +36 -0
- package/src/ResourceContentPage.stories.jsx +0 -19
- package/src/resource.aggregate.js +0 -86
package/src/ResourceList.jsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
import React, { useRef, useEffect } from 'react'
|
|
3
|
-
import {
|
|
4
|
-
import { GetWorkspace } from '@ossy/workspaces'
|
|
2
|
+
import React, { useRef, useEffect, useMemo, useCallback, memo } from 'react'
|
|
3
|
+
import { useSchemas } from './useSchemas.js'
|
|
5
4
|
import {
|
|
6
5
|
Dropdown,
|
|
7
6
|
DropZone,
|
|
@@ -11,29 +10,37 @@ import {
|
|
|
11
10
|
Text,
|
|
12
11
|
Button,
|
|
13
12
|
ContextMenu,
|
|
13
|
+
List,
|
|
14
14
|
} from '@ossy/design-system'
|
|
15
15
|
import { formatBytes } from './utils/format-bytes.js'
|
|
16
16
|
|
|
17
|
+
function useSchemaMap() {
|
|
18
|
+
const templates = useSchemas()
|
|
19
|
+
return useMemo(
|
|
20
|
+
() => templates.reduce((acc, template) => ({ ...acc, [template.id]: template }), {}),
|
|
21
|
+
[templates],
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
export const ResourceList = ({
|
|
18
26
|
resources = [],
|
|
19
27
|
onClick = () => {},
|
|
20
28
|
inlineFolder = null,
|
|
21
29
|
}) => {
|
|
30
|
+
const templateMap = useSchemaMap()
|
|
22
31
|
|
|
23
32
|
return (
|
|
24
|
-
|
|
33
|
+
<List>
|
|
25
34
|
{inlineFolder && <InlineFolderRow {...inlineFolder} />}
|
|
26
35
|
{resources.map(resource => (
|
|
27
36
|
<ResourceListItem
|
|
28
|
-
{...resource}
|
|
29
37
|
key={resource.id}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}}
|
|
38
|
+
resource={resource}
|
|
39
|
+
templateMap={templateMap}
|
|
40
|
+
onItemClick={onClick}
|
|
34
41
|
/>
|
|
35
42
|
))}
|
|
36
|
-
|
|
43
|
+
</List>
|
|
37
44
|
)
|
|
38
45
|
}
|
|
39
46
|
|
|
@@ -64,51 +71,47 @@ function InlineFolderRow({ name, onChange, onSave, onCancel }) {
|
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
return (
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
gap="m"
|
|
70
|
-
style={{
|
|
71
|
-
height: '56px',
|
|
72
|
-
flexShrink: 0,
|
|
73
|
-
borderBottom: '1px solid var(--separator-primary)',
|
|
74
|
-
}}
|
|
74
|
+
<List.Item
|
|
75
|
+
leading={<Icon size="s" name="folder" style={{ fill: 'hsl(0, 0%, 60%)' }} />}
|
|
75
76
|
>
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
</View>
|
|
98
|
-
</View>
|
|
77
|
+
<input
|
|
78
|
+
ref={inputRef}
|
|
79
|
+
value={name}
|
|
80
|
+
onChange={event => onChange(event.target.value)}
|
|
81
|
+
onKeyDown={handleKeyDown}
|
|
82
|
+
onBlur={handleBlur}
|
|
83
|
+
style={{
|
|
84
|
+
flexGrow: 1,
|
|
85
|
+
border: 'none',
|
|
86
|
+
background: 'transparent',
|
|
87
|
+
outline: 'none',
|
|
88
|
+
fontSize: 'var(--text-default-font-size, 16px)',
|
|
89
|
+
fontFamily: 'var(--text-default-font-family, sans-serif)',
|
|
90
|
+
fontWeight: 'var(--text-default-font-weight, 400)',
|
|
91
|
+
color: 'var(--text-default-color, CanvasText)',
|
|
92
|
+
padding: 0,
|
|
93
|
+
minWidth: 0,
|
|
94
|
+
width: '100%',
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
</List.Item>
|
|
99
98
|
)
|
|
100
99
|
}
|
|
101
100
|
|
|
102
|
-
function ResourceListItem({
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
dragData,
|
|
107
|
-
...resource
|
|
101
|
+
const ResourceListItem = memo(function ResourceListItem({
|
|
102
|
+
resource,
|
|
103
|
+
templateMap,
|
|
104
|
+
onItemClick,
|
|
108
105
|
}) {
|
|
109
|
-
|
|
106
|
+
const { onClick, href, onDrop, dragData, actions, name, type, content } = resource
|
|
107
|
+
|
|
108
|
+
const handleClick = useCallback(() => {
|
|
109
|
+
onItemClick(resource)
|
|
110
|
+
onClick?.(resource)
|
|
111
|
+
}, [onItemClick, onClick, resource])
|
|
112
|
+
|
|
110
113
|
let Container = ({ children }) => <>{children}</>
|
|
111
|
-
|
|
114
|
+
|
|
112
115
|
if (onDrop) {
|
|
113
116
|
Container = DropZone
|
|
114
117
|
} else if (dragData) {
|
|
@@ -117,77 +120,58 @@ function ResourceListItem({
|
|
|
117
120
|
|
|
118
121
|
return (
|
|
119
122
|
<Container onDrop={onDrop} dragData={dragData}>
|
|
120
|
-
<
|
|
121
|
-
|
|
123
|
+
<List.Item
|
|
124
|
+
href={href}
|
|
125
|
+
onClick={handleClick}
|
|
122
126
|
selectable
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
borderBottom: '1px solid var(--separator-primary)',
|
|
128
|
-
}}
|
|
127
|
+
leading={<ResourceIcon type={type} content={content} templateMap={templateMap} />}
|
|
128
|
+
meta={<Size content={content} />}
|
|
129
|
+
trailing={<RowActions actions={actions} />}
|
|
130
|
+
data-resource-id={resource.id}
|
|
129
131
|
>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
<Text>{typeof resource.name === 'string' && resource.name}</Text>
|
|
133
|
-
<View style={{ flexGrow: 1 }}/>
|
|
134
|
-
<Size {...resource} />
|
|
135
|
-
</View>
|
|
136
|
-
|
|
137
|
-
<View style={{ padding: '12px 8px 12px 0'}}>
|
|
138
|
-
<RowActions {...resource} />
|
|
139
|
-
</View>
|
|
140
|
-
</View>
|
|
132
|
+
{typeof name === 'string' ? name : ''}
|
|
133
|
+
</List.Item>
|
|
141
134
|
</Container>
|
|
142
135
|
)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (resource.content?.ContentLength) {
|
|
149
|
-
return (
|
|
150
|
-
<Text variant="small">
|
|
151
|
-
{formatBytes(resource.content.ContentLength)}
|
|
152
|
-
</Text>
|
|
153
|
-
)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return <></>
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function ResourceIcon({ ...resource }) {
|
|
160
|
-
const sdk = useSdk()
|
|
161
|
-
const { data: workspace } = sdk.read(GetWorkspace)
|
|
162
|
-
|
|
163
|
-
const resourceTemplates = (workspace.resourceTemplates || [])
|
|
164
|
-
.reduce((acc, curr) => ({ ...acc, [curr.id]: curr }), {})
|
|
165
|
-
|
|
166
|
-
if (resource?.type === 'directory') {
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const ResourceIcon = memo(function ResourceIcon({ type, content, templateMap }) {
|
|
139
|
+
if (type === 'directory') {
|
|
167
140
|
return (
|
|
168
141
|
<Icon size="s" name="folder" style={{ fill: 'hsl(0, 0%, 60%)'}} />
|
|
169
142
|
)
|
|
170
143
|
}
|
|
171
|
-
|
|
172
|
-
|
|
144
|
+
|
|
145
|
+
if (type?.startsWith('image')) {
|
|
173
146
|
return (
|
|
174
147
|
<Image
|
|
175
|
-
src={
|
|
176
|
-
placeholderSrc={
|
|
148
|
+
src={content?.sizes?.thumbnailSmall || content?.src}
|
|
149
|
+
placeholderSrc={content?.sizes?.['loader-square-blurred-after'] || content?.src}
|
|
177
150
|
style={{ width: '24px', height: '24px', borderRadius: '25%' }}
|
|
178
151
|
/>
|
|
179
152
|
)
|
|
180
153
|
}
|
|
181
|
-
|
|
154
|
+
|
|
182
155
|
return (
|
|
183
|
-
<Icon size="s" name={
|
|
156
|
+
<Icon size="s" name={templateMap[type]?.icon || 'file'} style={{ fill: 'hsl(0, 0%, 80%)'}} />
|
|
184
157
|
)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
function Size({ content }) {
|
|
161
|
+
if (content?.ContentLength) {
|
|
162
|
+
return (
|
|
163
|
+
<Text variant="small">
|
|
164
|
+
{formatBytes(content.ContentLength)}
|
|
165
|
+
</Text>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return null
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function RowActions({ actions }) {
|
|
173
|
+
if (!actions) return null
|
|
174
|
+
|
|
191
175
|
return (
|
|
192
176
|
<Dropdown trigger={<Button prefix="more-vertical-alt" variant="command" /> } >
|
|
193
177
|
<View inset="xs" surface="primary" roundness="s">
|
|
@@ -197,5 +181,4 @@ function ResourceListItem({
|
|
|
197
181
|
</View>
|
|
198
182
|
</Dropdown>
|
|
199
183
|
)
|
|
200
|
-
|
|
201
|
-
}
|
|
184
|
+
}
|
package/src/ResourcePanel.jsx
CHANGED
|
@@ -1,24 +1,16 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react'
|
|
1
|
+
import React, { useState, useEffect, memo } from 'react'
|
|
2
2
|
import { metadata as GetResource } from './get.action.js'
|
|
3
3
|
import { removeResource, updateResourceContent, renameResource } from './resource.helpers.js'
|
|
4
4
|
import { useSdk } from '@ossy/sdk-react'
|
|
5
|
-
import {
|
|
6
|
-
Title,
|
|
7
|
-
Overlay,
|
|
8
|
-
Switch,
|
|
9
|
-
Guide,
|
|
10
|
-
View,
|
|
11
|
-
Text,
|
|
12
|
-
Button,
|
|
13
|
-
InputTitle,
|
|
14
|
-
useInputValue,
|
|
15
|
-
} from '@ossy/design-system'
|
|
5
|
+
import { Switch, View, Text, Button, InputTitle, Alert, useInputValue } from '@ossy/design-system'
|
|
16
6
|
import { ResourceFactory } from './ResourceFactory.jsx'
|
|
17
|
-
import { useRouter } from '@ossy/router-react'
|
|
18
7
|
import { ResourceDetails } from './ResourceDetails.jsx'
|
|
19
8
|
import { ResourceTags } from './ResourceTags.jsx'
|
|
20
9
|
import { ResourceDescription } from './ResourceDescription.jsx'
|
|
21
10
|
import { useForm } from './useForm.js'
|
|
11
|
+
import { useSchema } from './useSchemas.js'
|
|
12
|
+
import { useSchemaEngine } from './useSchemaEngine.js'
|
|
13
|
+
import { validateContent } from '@ossy/schema'
|
|
22
14
|
|
|
23
15
|
const ViewMode = {
|
|
24
16
|
Closed: 'Closed',
|
|
@@ -26,11 +18,6 @@ const ViewMode = {
|
|
|
26
18
|
Edit: 'Edit'
|
|
27
19
|
}
|
|
28
20
|
|
|
29
|
-
const Overlays = {
|
|
30
|
-
None: 'None',
|
|
31
|
-
Removedirectory : 'RemoveDirectory'
|
|
32
|
-
}
|
|
33
|
-
|
|
34
21
|
const DefaultViewModes = {
|
|
35
22
|
'panel-resource-content': ViewMode.View,
|
|
36
23
|
'panel-header': ViewMode.View,
|
|
@@ -39,19 +26,19 @@ const DefaultViewModes = {
|
|
|
39
26
|
'panel-resource-tags': ViewMode.Closed,
|
|
40
27
|
}
|
|
41
28
|
|
|
42
|
-
export const ResourcePanel = ({
|
|
29
|
+
export const ResourcePanel = memo(function ResourcePanel ({
|
|
43
30
|
resourceId,
|
|
44
31
|
onClose: _onClose,
|
|
45
32
|
extraPanels = [],
|
|
46
|
-
})
|
|
47
|
-
const router = useRouter()
|
|
33
|
+
}) {
|
|
48
34
|
const sdk = useSdk()
|
|
49
|
-
const [
|
|
50
|
-
const [error] = useState()
|
|
35
|
+
const [contentError, setContentError] = useState()
|
|
51
36
|
const [resourceName, setResourceName] = useInputValue('')
|
|
52
37
|
const [panelViewModes, setPanelViewModes] = useState(DefaultViewModes)
|
|
53
38
|
|
|
54
|
-
const { data: resource = {} } = sdk.read(GetResource, {
|
|
39
|
+
const { data: resource = {} } = sdk.read(GetResource, { resourceId }, { enabled: !!resourceId })
|
|
40
|
+
const template = useSchema(resource?.type)
|
|
41
|
+
const engine = useSchemaEngine()
|
|
55
42
|
|
|
56
43
|
const form = useForm({ defaultData: resource.content })
|
|
57
44
|
|
|
@@ -69,6 +56,18 @@ export const ResourcePanel = ({
|
|
|
69
56
|
}
|
|
70
57
|
|
|
71
58
|
const onUpdateResource = () => {
|
|
59
|
+
if (template) {
|
|
60
|
+
const result = validateContent(engine, template, form.data)
|
|
61
|
+
if (!result.ok) {
|
|
62
|
+
setContentError(result.message)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
setContentError(undefined)
|
|
66
|
+
updateResourceContent(sdk, resourceId, result.data)
|
|
67
|
+
.then(() => setPanelViewModes(x => ({ ...x, 'panel-resource-content': ViewMode.View })))
|
|
68
|
+
.catch(() => alert('Something went wrong!'))
|
|
69
|
+
return
|
|
70
|
+
}
|
|
72
71
|
updateResourceContent(sdk, resourceId, form.data)
|
|
73
72
|
.then(() => setPanelViewModes(x => ({ ...x, 'panel-resource-content': ViewMode.View })))
|
|
74
73
|
.catch(() => alert('Something went wrong!'))
|
|
@@ -91,16 +90,26 @@ export const ResourcePanel = ({
|
|
|
91
90
|
setResourceName(resource.name)
|
|
92
91
|
}, [resource])
|
|
93
92
|
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
setContentError(undefined)
|
|
95
|
+
}, [form.data])
|
|
96
|
+
|
|
94
97
|
const panels = [
|
|
95
98
|
{
|
|
96
99
|
id: 'panel-resource-content',
|
|
97
100
|
label: 'View',
|
|
98
101
|
suffix: panelViewModes['panel-resource-content'] === ViewMode.Edit
|
|
99
102
|
? [
|
|
100
|
-
{ prefix: 'close', variant: 'command', onClick:
|
|
103
|
+
{ prefix: 'close', variant: 'command', onClick: () => {
|
|
104
|
+
setContentError(undefined)
|
|
105
|
+
setPanelViewModes(x => ({ ...x, 'panel-resource-content': ViewMode.View }))
|
|
106
|
+
} },
|
|
101
107
|
{ prefix: 'check', variant: 'command', onClick: onUpdateResource }
|
|
102
108
|
]
|
|
103
|
-
: [{ prefix: 'pen', variant: 'command', onClick: () =>
|
|
109
|
+
: [{ prefix: 'pen', variant: 'command', onClick: () => {
|
|
110
|
+
setContentError(undefined)
|
|
111
|
+
setPanelViewModes(x => ({ ...x, 'panel-resource-content': ViewMode.Edit }))
|
|
112
|
+
} }],
|
|
104
113
|
fill: true,
|
|
105
114
|
content: ResourceFactory
|
|
106
115
|
},
|
|
@@ -143,9 +152,9 @@ export const ResourcePanel = ({
|
|
|
143
152
|
`}</style>
|
|
144
153
|
|
|
145
154
|
{
|
|
146
|
-
!!
|
|
155
|
+
!!contentError && (
|
|
147
156
|
<View.Item>
|
|
148
|
-
{
|
|
157
|
+
<Alert variant="danger">{contentError}</Alert>
|
|
149
158
|
</View.Item>
|
|
150
159
|
)
|
|
151
160
|
}
|
|
@@ -159,7 +168,7 @@ export const ResourcePanel = ({
|
|
|
159
168
|
<Switch.Case match={[ViewMode.View]}>
|
|
160
169
|
|
|
161
170
|
<View.Item fill surface="primary" style={{ padding: '4px 8px' }}>
|
|
162
|
-
<
|
|
171
|
+
<Text as="h3" variant="breadcrumb">{resourceName}</Text>
|
|
163
172
|
</View.Item>
|
|
164
173
|
|
|
165
174
|
<Button prefix="trash-empty" variant="command-danger" onClick={onRemoveResource}/>
|
|
@@ -174,7 +183,7 @@ export const ResourcePanel = ({
|
|
|
174
183
|
<InputTitle
|
|
175
184
|
id="document-name"
|
|
176
185
|
type="text"
|
|
177
|
-
style={{ display: 'block', width: '100%'
|
|
186
|
+
style={{ display: 'block', width: '100%' }}
|
|
178
187
|
value={resourceName}
|
|
179
188
|
onChange={setResourceName}
|
|
180
189
|
onBlur={onRenameResource}
|
|
@@ -237,32 +246,6 @@ export const ResourcePanel = ({
|
|
|
237
246
|
|
|
238
247
|
</View.Item>
|
|
239
248
|
|
|
240
|
-
{
|
|
241
|
-
overlay === Overlays.RemoveDirectory && (
|
|
242
|
-
<Overlay isVisible={true} onClose={() => setOverlay(Overlays.None)}>
|
|
243
|
-
<View layout="off-center-s" style={{ height: '100%' }}>
|
|
244
|
-
<View slot="content">
|
|
245
|
-
<View surface="primary" roundness="s">
|
|
246
|
-
<View surface="primary" inset="l" roundness="s">
|
|
247
|
-
<Guide
|
|
248
|
-
title="Remove directory"
|
|
249
|
-
text="Are you sure you want to remove this directory and everything in it?"
|
|
250
|
-
actions={[
|
|
251
|
-
{ label: 'Cancel', variant: "command", onClick: () => setOverlay(Overlays.None) },
|
|
252
|
-
{ label: 'Remove', variant: 'command-danger', onClick: () => {
|
|
253
|
-
removeResource(sdk, resourceId, resource?.location)
|
|
254
|
-
setOverlay(Overlays.None)
|
|
255
|
-
}}
|
|
256
|
-
]}
|
|
257
|
-
/>
|
|
258
|
-
</View>
|
|
259
|
-
</View>
|
|
260
|
-
</View>
|
|
261
|
-
</View>
|
|
262
|
-
</Overlay>
|
|
263
|
-
)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
249
|
</View>
|
|
267
250
|
)
|
|
268
|
-
}
|
|
251
|
+
})
|
package/src/ResourceTags.jsx
CHANGED
|
@@ -5,7 +5,7 @@ import { View, Tags, Text } from '@ossy/design-system'
|
|
|
5
5
|
|
|
6
6
|
export const ResourceTags = ({ resourceId }) => {
|
|
7
7
|
const sdk = useSdk()
|
|
8
|
-
const { data: resource } = sdk.read(GetResource, {
|
|
8
|
+
const { data: resource } = sdk.read(GetResource, { resourceId }, { enabled: !!resourceId })
|
|
9
9
|
|
|
10
10
|
let resourceContentTags = resource?.content?.Tags || []
|
|
11
11
|
|
package/src/ResourcesPage.jsx
CHANGED
|
@@ -2,7 +2,7 @@ import React from 'react'
|
|
|
2
2
|
import { SDK } from '@ossy/sdk'
|
|
3
3
|
import { metadata as ListResources } from './list.action.js'
|
|
4
4
|
import { AsyncStatus, WorkspaceProvider, useSdk } from '@ossy/sdk-react'
|
|
5
|
-
import {
|
|
5
|
+
import { Text, Switch, View, ImageCard, Tags, Page, useLocale } from '@ossy/design-system'
|
|
6
6
|
import { useRouter } from '@ossy/router-react'
|
|
7
7
|
|
|
8
8
|
const sdk = SDK.of({
|
|
@@ -27,41 +27,32 @@ export const ResourcesPage= () => {
|
|
|
27
27
|
return (
|
|
28
28
|
<Switch on={status}>
|
|
29
29
|
<Switch.Case match={[AsyncStatus.Success]}>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<View gap="
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
</section>
|
|
55
|
-
</WorkspaceProvider>
|
|
56
|
-
|
|
57
|
-
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--space-xxs)', marginTop: '24px' }}>
|
|
58
|
-
<Tags tags={Object.entries(tagsCount).map(([tag, count]) => `${count} ${tag}`)} />
|
|
59
|
-
</div>
|
|
30
|
+
<Page
|
|
31
|
+
maxWidth="xl"
|
|
32
|
+
title="resources.home.title"
|
|
33
|
+
description="resources.home.description"
|
|
34
|
+
descriptionMaxWidth="m"
|
|
35
|
+
>
|
|
36
|
+
<View gap="m">
|
|
37
|
+
<WorkspaceProvider sdk={sdk}>
|
|
38
|
+
<section style={freeResourcesGridStyles}>
|
|
39
|
+
{ resources.map(resource => (
|
|
40
|
+
<ImageCard
|
|
41
|
+
href={router.getHref({ id: "@resource", params: { resourceId: resource?.id } })}
|
|
42
|
+
src={resource?.content?.sizes?.galleryMedium || resource?.content?.src}
|
|
43
|
+
placeholderSrc={resource?.content?.sizes?.['loader-square-blurred-after'] || resource?.content?.src}
|
|
44
|
+
style={{ pageBreakInside: 'avoid', cursor: 'pointer' }}
|
|
45
|
+
key={resource.id}
|
|
46
|
+
/>
|
|
47
|
+
))}
|
|
48
|
+
</section>
|
|
49
|
+
</WorkspaceProvider>
|
|
50
|
+
|
|
51
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--space-xxs)', marginTop: '24px' }}>
|
|
52
|
+
<Tags tags={Object.entries(tagsCount).map(([tag, count]) => `${count} ${tag}`)} />
|
|
53
|
+
</div>
|
|
60
54
|
</View>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
</main>
|
|
64
|
-
|
|
55
|
+
</Page>
|
|
65
56
|
</Switch.Case>
|
|
66
57
|
</Switch>
|
|
67
58
|
)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Text, View, useLocale, hasTranslation } from '@ossy/design-system'
|
|
3
|
+
|
|
4
|
+
const labelStyle = { fontWeight: 600, opacity: 0.65 }
|
|
5
|
+
const valueStyle = { whiteSpace: 'pre-wrap' }
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generic read-only detail projection for schema-backed resources.
|
|
9
|
+
*
|
|
10
|
+
* @param {{
|
|
11
|
+
* schemaId?: string,
|
|
12
|
+
* fields?: { name: string, label?: string, derived?: boolean }[],
|
|
13
|
+
* data?: Record<string, unknown>,
|
|
14
|
+
* title?: string,
|
|
15
|
+
* emptyMessage?: string,
|
|
16
|
+
* }} props
|
|
17
|
+
*/
|
|
18
|
+
export function SchemaDetailView ({
|
|
19
|
+
schemaId,
|
|
20
|
+
fields: fieldsProp,
|
|
21
|
+
data = {},
|
|
22
|
+
title,
|
|
23
|
+
emptyMessage = 'No data',
|
|
24
|
+
}) {
|
|
25
|
+
const { messages } = useLocale()
|
|
26
|
+
const fields = (fieldsProp ?? []).filter((field) => !field.derived)
|
|
27
|
+
const displayTitle = title ?? data.name ?? 'Preview'
|
|
28
|
+
|
|
29
|
+
if (!fields.length) {
|
|
30
|
+
const entries = Object.entries(data)
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<View stack gap="m">
|
|
34
|
+
{displayTitle && (
|
|
35
|
+
<Text variant="heading-default" as="h1">{displayTitle}</Text>
|
|
36
|
+
)}
|
|
37
|
+
{entries.length
|
|
38
|
+
? entries.map(([name, value]) => (
|
|
39
|
+
<View gap="xs" key={name}>
|
|
40
|
+
<Text variant="small" style={labelStyle}>{name}</Text>
|
|
41
|
+
<Text variant="small" style={valueStyle}>
|
|
42
|
+
{value == null || value === ''
|
|
43
|
+
? '—'
|
|
44
|
+
: typeof value === 'boolean'
|
|
45
|
+
? (value ? 'Yes' : 'No')
|
|
46
|
+
: typeof value === 'object'
|
|
47
|
+
? JSON.stringify(value, null, 2)
|
|
48
|
+
: String(value)}
|
|
49
|
+
</Text>
|
|
50
|
+
</View>
|
|
51
|
+
))
|
|
52
|
+
: <Text variant="small" style={{ opacity: 0.6 }}>{emptyMessage}</Text>}
|
|
53
|
+
</View>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<View stack gap="m">
|
|
59
|
+
{displayTitle && (
|
|
60
|
+
<Text variant="heading-default" as="h1">{displayTitle}</Text>
|
|
61
|
+
)}
|
|
62
|
+
{fields.map((field) => {
|
|
63
|
+
const value = data[field.name]
|
|
64
|
+
const labelKey = schemaId && !field.label ? `${schemaId}.${field.name}.label` : null
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<View gap="xs" key={field.name}>
|
|
68
|
+
{field.label
|
|
69
|
+
? (
|
|
70
|
+
<Text variant="small" style={labelStyle}>{field.label}</Text>
|
|
71
|
+
)
|
|
72
|
+
: labelKey && hasTranslation(messages, labelKey)
|
|
73
|
+
? (
|
|
74
|
+
<Text
|
|
75
|
+
variant="small"
|
|
76
|
+
style={labelStyle}
|
|
77
|
+
text={labelKey}
|
|
78
|
+
/>
|
|
79
|
+
)
|
|
80
|
+
: (
|
|
81
|
+
<Text variant="small" style={labelStyle}>{field.name}</Text>
|
|
82
|
+
)}
|
|
83
|
+
<Text variant="small" style={valueStyle}>
|
|
84
|
+
{value == null || value === ''
|
|
85
|
+
? '—'
|
|
86
|
+
: typeof value === 'boolean'
|
|
87
|
+
? (value ? 'Yes' : 'No')
|
|
88
|
+
: typeof value === 'object'
|
|
89
|
+
? JSON.stringify(value, null, 2)
|
|
90
|
+
: String(value)}
|
|
91
|
+
</Text>
|
|
92
|
+
</View>
|
|
93
|
+
)
|
|
94
|
+
})}
|
|
95
|
+
</View>
|
|
96
|
+
)
|
|
97
|
+
}
|