@ossy/resources 1.12.3 → 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 +11 -7
- package/src/AudioResource.jsx +1 -1
- package/src/CreateDirectory.page.jsx +1 -1
- package/src/CreateDocument.page.jsx +1 -1
- package/src/Definition.js +1 -1
- package/src/DocumentView.jsx +8 -18
- package/src/GenericResourceCard.jsx +2 -2
- package/src/GenericResourceForm.jsx +154 -55
- package/src/ResourceContentPage.jsx +2 -2
- package/src/ResourceDialogMove.jsx +1 -1
- package/src/SchemaDetailView.jsx +97 -0
- package/src/SchemaFormDefault.jsx +86 -0
- package/src/SchemaPresenter.jsx +40 -48
- package/src/Upload.page.jsx +1 -1
- package/src/create.task.js +3 -4
- package/src/delete.task.js +1 -2
- package/src/en.translations.json +1 -0
- package/src/index.js +4 -0
- package/src/resource-create.page.jsx +1 -1
- package/src/resource-detail.page.jsx +1 -1
- 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/sv.translations.json +1 -0
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/resources",
|
|
3
3
|
"description": "Resource domain — aggregate and events for the Ossy resource model",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.0.1",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./src/index.js",
|
|
8
8
|
"module": "./src/index.js",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": "./src/index.js",
|
|
11
|
+
"./schemaViewSlots.js": "./src/schemaViewSlots.js",
|
|
12
|
+
"./schemaFormSlots.js": "./src/schemaFormSlots.js",
|
|
13
|
+
"./resource.helpers.js": "./src/resource.helpers.js",
|
|
11
14
|
"./server": "./src/server.js"
|
|
12
15
|
},
|
|
13
16
|
"scripts": {
|
|
@@ -20,12 +23,13 @@
|
|
|
20
23
|
"src": "./src"
|
|
21
24
|
},
|
|
22
25
|
"dependencies": {
|
|
23
|
-
"@ossy/event-store": "^
|
|
24
|
-
"@ossy/fold": "^
|
|
25
|
-
"@ossy/platform": "^
|
|
26
|
-
"@ossy/schema": "^
|
|
26
|
+
"@ossy/event-store": "^3.0.1",
|
|
27
|
+
"@ossy/fold": "^3.0.1",
|
|
28
|
+
"@ossy/platform": "^3.0.1",
|
|
29
|
+
"@ossy/schema": "^3.0.1"
|
|
27
30
|
},
|
|
28
31
|
"peerDependencies": {
|
|
32
|
+
"@ossy/design-system": ">=1.0.0",
|
|
29
33
|
"@ossy/schema": "*",
|
|
30
34
|
"@ossy/sdk-react": ">=1.0.0",
|
|
31
35
|
"@ossy/workspaces": ">=1.0.0",
|
|
@@ -33,7 +37,7 @@
|
|
|
33
37
|
},
|
|
34
38
|
"devDependencies": {
|
|
35
39
|
"@jest/globals": "^30.2.0",
|
|
36
|
-
"@ossy/platform": "^
|
|
40
|
+
"@ossy/platform": "^3.0.0",
|
|
37
41
|
"casual": "^1.6.2",
|
|
38
42
|
"jest": "^30.2.0"
|
|
39
43
|
},
|
|
@@ -45,5 +49,5 @@
|
|
|
45
49
|
"/src",
|
|
46
50
|
"README.md"
|
|
47
51
|
],
|
|
48
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "4a70ec216448680d2fddc57b2a8a0077489720ae"
|
|
49
53
|
}
|
package/src/AudioResource.jsx
CHANGED
|
@@ -14,7 +14,7 @@ export const metadata = {
|
|
|
14
14
|
export const CreateDirectoryPage = () => {
|
|
15
15
|
return (
|
|
16
16
|
<View layout="off-center-s" style={{ height: '100%' }}>
|
|
17
|
-
<View
|
|
17
|
+
<View data-region="content" surface="primary" roundness="m" inset="m">
|
|
18
18
|
<CreateDirectory/>
|
|
19
19
|
</View>
|
|
20
20
|
</View>
|
|
@@ -14,7 +14,7 @@ export const metadata = {
|
|
|
14
14
|
export const CreateDocumentPage = () => {
|
|
15
15
|
return (
|
|
16
16
|
<View layout="off-center-m" style={{ height: '100%' }}>
|
|
17
|
-
<View
|
|
17
|
+
<View data-region="content" surface='primary' roundness='m' inset="m">
|
|
18
18
|
<CreateDocument />
|
|
19
19
|
</View>
|
|
20
20
|
</View>
|
package/src/Definition.js
CHANGED
package/src/DocumentView.jsx
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { pipe, keys, map } from 'ramda'
|
|
3
2
|
import { metadata as GetResource } from './get.action.js'
|
|
4
3
|
import { useSdk } from '@ossy/sdk-react'
|
|
5
4
|
import { useSchema } from './useSchemas.js'
|
|
6
|
-
import {
|
|
5
|
+
import { View } from '@ossy/design-system'
|
|
6
|
+
import { SchemaDetailView } from './SchemaDetailView.jsx'
|
|
7
7
|
|
|
8
8
|
export const DocumentView = ({
|
|
9
9
|
resourceId
|
|
@@ -14,22 +14,12 @@ export const DocumentView = ({
|
|
|
14
14
|
|
|
15
15
|
return (
|
|
16
16
|
<View style={{ padding: '16px 0' }}>
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<Text style={{ fontWeight: 'bold' }} >{field.name}</Text>
|
|
24
|
-
{
|
|
25
|
-
typeof field.value === 'object'
|
|
26
|
-
? pipe(keys, map(key => field.value[key] && <div>{key}</div>))(field.value)
|
|
27
|
-
: <Text style={{ maxWidth: '500px' }}>{field.value}</Text>
|
|
28
|
-
}
|
|
29
|
-
</View>
|
|
30
|
-
))
|
|
31
|
-
}
|
|
32
|
-
</View>
|
|
17
|
+
<SchemaDetailView
|
|
18
|
+
schemaId={resource?.type}
|
|
19
|
+
fields={template?.fields}
|
|
20
|
+
data={resource?.content ?? {}}
|
|
21
|
+
title={resource?.name}
|
|
22
|
+
/>
|
|
33
23
|
</View>
|
|
34
24
|
)
|
|
35
25
|
}
|
|
@@ -38,14 +38,14 @@ export function GenericResourceCard ({ resourceId, resource: resourceProp }) {
|
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
40
|
<Slot
|
|
41
|
-
|
|
41
|
+
view={primary}
|
|
42
42
|
resourceId={resourceId}
|
|
43
43
|
resource={resource}
|
|
44
44
|
schemaId={resource.type}
|
|
45
45
|
projection="card"
|
|
46
46
|
fallback={
|
|
47
47
|
<Slot
|
|
48
|
-
|
|
48
|
+
view={secondary}
|
|
49
49
|
resourceId={resourceId}
|
|
50
50
|
resource={resource}
|
|
51
51
|
schemaId={resource.type}
|
|
@@ -1,14 +1,25 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
1
|
+
import React, { useMemo, useState } from 'react'
|
|
2
2
|
import { useSchema } from './useSchemas.js'
|
|
3
3
|
import { useSchemaEngine } from './useSchemaEngine.js'
|
|
4
4
|
import { validateContent } from '@ossy/schema'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { resolveFormComponentChain } from '@ossy/schema/resolve'
|
|
6
|
+
import {
|
|
7
|
+
Slot,
|
|
8
|
+
View,
|
|
9
|
+
Text,
|
|
10
|
+
InputTitle,
|
|
11
|
+
Button,
|
|
12
|
+
Alert,
|
|
13
|
+
Fields,
|
|
14
|
+
applyFieldChange,
|
|
15
|
+
useInputValue,
|
|
16
|
+
useLocale,
|
|
17
|
+
} from '@ossy/design-system'
|
|
18
|
+
import { SchemaFormDefault } from './SchemaFormDefault.jsx'
|
|
7
19
|
|
|
8
|
-
|
|
20
|
+
/** CMS create-resource form — name + schema fields (resources-specific tertiary fallback). */
|
|
21
|
+
function ResourceCreateFormFallback ({
|
|
9
22
|
schemaId,
|
|
10
|
-
mode = 'create',
|
|
11
|
-
form,
|
|
12
23
|
initialName = '',
|
|
13
24
|
initialContent = {},
|
|
14
25
|
onSubmit,
|
|
@@ -22,36 +33,6 @@ function GenericResourceFormFallback ({
|
|
|
22
33
|
const [content, setContent] = useState(initialContent)
|
|
23
34
|
const [error, setError] = useState()
|
|
24
35
|
|
|
25
|
-
if (form && mode === 'edit') {
|
|
26
|
-
if (template?.fields && form.data) {
|
|
27
|
-
return (
|
|
28
|
-
<View style={{ padding: '16px 0' }}>
|
|
29
|
-
<Fields data={form.data} onChange={form.onChange} fields={template.fields} />
|
|
30
|
-
</View>
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (template?.fields && !form.data) {
|
|
35
|
-
return (
|
|
36
|
-
<DelayedRender>
|
|
37
|
-
<View style={{ padding: '16px 0' }}>
|
|
38
|
-
<Text>Loading...</Text>
|
|
39
|
-
</View>
|
|
40
|
-
</DelayedRender>
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<View style={{ padding: '16px 0' }}>
|
|
46
|
-
<View inset="m">
|
|
47
|
-
<Text>
|
|
48
|
-
This documents template has been deleted and can therefore not be edited
|
|
49
|
-
</Text>
|
|
50
|
-
</View>
|
|
51
|
-
</View>
|
|
52
|
-
)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
36
|
const handleSubmit = () => {
|
|
56
37
|
if (!name?.trim()) {
|
|
57
38
|
setError(t('resources.form.nameRequired'))
|
|
@@ -81,6 +62,7 @@ function GenericResourceFormFallback ({
|
|
|
81
62
|
onChange={setName}
|
|
82
63
|
/>
|
|
83
64
|
<Fields
|
|
65
|
+
schemaId={schemaId}
|
|
84
66
|
data={content}
|
|
85
67
|
onChange={(event) => {
|
|
86
68
|
setError(undefined)
|
|
@@ -92,20 +74,76 @@ function GenericResourceFormFallback ({
|
|
|
92
74
|
<View layout="row" gap="s" justifyContent="flex-end">
|
|
93
75
|
{onCancel && <Button variant="link" onClick={onCancel} label="design-system.cancel" />}
|
|
94
76
|
<Button variant="cta" onClick={handleSubmit}>
|
|
95
|
-
{submitLabel || (
|
|
77
|
+
{submitLabel || (template?.name ? `${t('design-system.add')} ${template.name}` : t('resources.form.createResource'))}
|
|
96
78
|
</Button>
|
|
97
79
|
</View>
|
|
98
80
|
</View>
|
|
99
81
|
)
|
|
100
82
|
}
|
|
101
83
|
|
|
84
|
+
function ResourceFormFallback ({
|
|
85
|
+
schemaId,
|
|
86
|
+
mode = 'create',
|
|
87
|
+
schemaFormOnly = false,
|
|
88
|
+
form,
|
|
89
|
+
resource,
|
|
90
|
+
initialName,
|
|
91
|
+
initialContent,
|
|
92
|
+
onSubmit,
|
|
93
|
+
onCancel,
|
|
94
|
+
submitLabel,
|
|
95
|
+
fields,
|
|
96
|
+
}) {
|
|
97
|
+
if (form && mode === 'edit' && !fields?.length) {
|
|
98
|
+
return (
|
|
99
|
+
<View style={{ padding: '16px 0' }}>
|
|
100
|
+
<View inset="m">
|
|
101
|
+
<Text text="resources.form.typeRemoved" />
|
|
102
|
+
</View>
|
|
103
|
+
</View>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (mode === 'create' && !schemaFormOnly) {
|
|
108
|
+
return (
|
|
109
|
+
<ResourceCreateFormFallback
|
|
110
|
+
schemaId={schemaId}
|
|
111
|
+
initialName={initialName ?? resource?.name}
|
|
112
|
+
initialContent={initialContent ?? resource?.content ?? {}}
|
|
113
|
+
onSubmit={onSubmit}
|
|
114
|
+
onCancel={onCancel}
|
|
115
|
+
submitLabel={submitLabel}
|
|
116
|
+
/>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<View inset="m">
|
|
122
|
+
<SchemaFormDefault
|
|
123
|
+
schemaId={schemaId}
|
|
124
|
+
fields={fields}
|
|
125
|
+
mode={mode}
|
|
126
|
+
form={form}
|
|
127
|
+
resource={resource}
|
|
128
|
+
initialContent={initialContent ?? resource?.content ?? {}}
|
|
129
|
+
onSubmit={onSubmit}
|
|
130
|
+
onCancel={onCancel}
|
|
131
|
+
submitLabel={submitLabel}
|
|
132
|
+
/>
|
|
133
|
+
</View>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
|
|
102
137
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
138
|
+
* Resolves `@ossy/…/form/{concept}` → `@ossy/resources/form/default` (edit) → fallback.
|
|
139
|
+
*
|
|
140
|
+
* Create mode skips the design-system default and falls through to {@link ResourceCreateFormFallback}
|
|
141
|
+
* unless `schemaFormOnly` is set (e.g. Storybook schema preview).
|
|
105
142
|
*/
|
|
106
|
-
|
|
143
|
+
function SchemaFormSlotPresenter ({
|
|
107
144
|
schemaId,
|
|
108
145
|
mode = 'create',
|
|
146
|
+
schemaFormOnly = false,
|
|
109
147
|
resourceId,
|
|
110
148
|
resource,
|
|
111
149
|
form,
|
|
@@ -115,34 +153,95 @@ export function GenericResourceForm ({
|
|
|
115
153
|
onCancel,
|
|
116
154
|
submitLabel,
|
|
117
155
|
}) {
|
|
118
|
-
const
|
|
156
|
+
const template = useSchema(schemaId)
|
|
157
|
+
const chain = useMemo(() => {
|
|
158
|
+
if (!schemaId) return []
|
|
159
|
+
const full = resolveFormComponentChain(schemaId)
|
|
160
|
+
if (mode === 'create' && !schemaFormOnly) return [full[0]]
|
|
161
|
+
return full
|
|
162
|
+
}, [schemaId, mode, schemaFormOnly])
|
|
163
|
+
const [primary, secondary] = chain
|
|
164
|
+
const slotProps = {
|
|
165
|
+
schemaId,
|
|
166
|
+
mode,
|
|
167
|
+
resourceId,
|
|
168
|
+
resource,
|
|
169
|
+
form,
|
|
170
|
+
fields: template?.fields,
|
|
171
|
+
initialContent: initialContent ?? resource?.content ?? {},
|
|
172
|
+
onSubmit,
|
|
173
|
+
onCancel,
|
|
174
|
+
submitLabel,
|
|
175
|
+
}
|
|
119
176
|
|
|
177
|
+
const tertiaryFallback = (
|
|
178
|
+
<ResourceFormFallback
|
|
179
|
+
schemaId={schemaId}
|
|
180
|
+
mode={mode}
|
|
181
|
+
schemaFormOnly={schemaFormOnly}
|
|
182
|
+
form={form}
|
|
183
|
+
resource={resource}
|
|
184
|
+
initialName={initialName ?? resource?.name}
|
|
185
|
+
initialContent={initialContent ?? resource?.content ?? {}}
|
|
186
|
+
onSubmit={onSubmit}
|
|
187
|
+
onCancel={onCancel}
|
|
188
|
+
submitLabel={submitLabel}
|
|
189
|
+
fields={template?.fields}
|
|
190
|
+
/>
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
const fallback = secondary
|
|
194
|
+
? (
|
|
195
|
+
<Slot
|
|
196
|
+
view={secondary}
|
|
197
|
+
{...slotProps}
|
|
198
|
+
fallback={tertiaryFallback}
|
|
199
|
+
/>
|
|
200
|
+
)
|
|
201
|
+
: tertiaryFallback
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<Slot
|
|
205
|
+
view={primary}
|
|
206
|
+
{...slotProps}
|
|
207
|
+
fallback={fallback}
|
|
208
|
+
/>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Generic resource create/edit host — ADR 0006 form component chain with CMS create fallback.
|
|
214
|
+
*/
|
|
215
|
+
export function GenericResourceForm ({
|
|
216
|
+
schemaId,
|
|
217
|
+
mode = 'create',
|
|
218
|
+
schemaFormOnly = false,
|
|
219
|
+
resourceId,
|
|
220
|
+
resource,
|
|
221
|
+
form,
|
|
222
|
+
initialName,
|
|
223
|
+
initialContent,
|
|
224
|
+
onSubmit,
|
|
225
|
+
onCancel,
|
|
226
|
+
submitLabel,
|
|
227
|
+
}) {
|
|
120
228
|
if (!schemaId) {
|
|
121
229
|
return <Text text="resources.form.selectType" />
|
|
122
230
|
}
|
|
123
231
|
|
|
124
232
|
return (
|
|
125
|
-
<
|
|
126
|
-
name={resolveFormComponentId(schemaId)}
|
|
233
|
+
<SchemaFormSlotPresenter
|
|
127
234
|
schemaId={schemaId}
|
|
128
235
|
mode={mode}
|
|
236
|
+
schemaFormOnly={schemaFormOnly}
|
|
129
237
|
resourceId={resourceId}
|
|
130
238
|
resource={resource}
|
|
131
239
|
form={form}
|
|
240
|
+
initialName={initialName}
|
|
241
|
+
initialContent={initialContent}
|
|
132
242
|
onSubmit={onSubmit}
|
|
133
243
|
onCancel={onCancel}
|
|
134
|
-
|
|
135
|
-
<GenericResourceFormFallback
|
|
136
|
-
schemaId={schemaId}
|
|
137
|
-
mode={mode}
|
|
138
|
-
form={form}
|
|
139
|
-
initialName={initialName ?? resource?.name}
|
|
140
|
-
initialContent={initialContent ?? resource?.content ?? {}}
|
|
141
|
-
onSubmit={onSubmit}
|
|
142
|
-
onCancel={onCancel}
|
|
143
|
-
submitLabel={submitLabel}
|
|
144
|
-
/>
|
|
145
|
-
}
|
|
244
|
+
submitLabel={submitLabel}
|
|
146
245
|
/>
|
|
147
246
|
)
|
|
148
247
|
}
|
|
@@ -12,7 +12,7 @@ export const ResourceContentPage = (props) => {
|
|
|
12
12
|
const { status, data: resource } = sdk.read(GetResource, { resourceId }, { enabled: !!resourceId })
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
|
-
<View {...props}
|
|
15
|
+
<View {...props} data-region="content" style={{ flexDirection: 'column', wrap: 'no-wrap', height: '100%' }}>
|
|
16
16
|
|
|
17
17
|
<style>
|
|
18
18
|
{`
|
|
@@ -66,7 +66,7 @@ export const ResourceContentPage = (props) => {
|
|
|
66
66
|
</PageSection>
|
|
67
67
|
|
|
68
68
|
<View layout="off-center" style={{ flexGrow: '1' }}>
|
|
69
|
-
<View
|
|
69
|
+
<View data-region="content" style={{ padding: 'var(--space-m) var(--space-m) var(--space-xxl)'}}>
|
|
70
70
|
<Switch on={status}>
|
|
71
71
|
|
|
72
72
|
<Switch.Case match={[AsyncStatus.Error]}>
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
Button,
|
|
6
|
+
Fields,
|
|
7
|
+
DelayedRender,
|
|
8
|
+
useLocale,
|
|
9
|
+
Form,
|
|
10
|
+
FieldFactory,
|
|
11
|
+
} from '@ossy/design-system'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generic schema form — {@link Form} + {@link FieldFactory} (platform fallback).
|
|
15
|
+
*
|
|
16
|
+
* @param {{
|
|
17
|
+
* schemaId?: string,
|
|
18
|
+
* fields?: { name: string, type?: string, label?: string, required?: boolean }[],
|
|
19
|
+
* mode?: 'create' | 'edit',
|
|
20
|
+
* form?: { data?: Record<string, unknown>, onChange?: (detail: unknown) => void },
|
|
21
|
+
* initialContent?: Record<string, unknown>,
|
|
22
|
+
* resource?: { content?: Record<string, unknown> },
|
|
23
|
+
* onSubmit?: (data: Record<string, unknown>) => void,
|
|
24
|
+
* onCancel?: () => void,
|
|
25
|
+
* submitLabel?: string,
|
|
26
|
+
* }} props
|
|
27
|
+
*/
|
|
28
|
+
export function SchemaFormDefault ({
|
|
29
|
+
schemaId,
|
|
30
|
+
fields: fieldsProp,
|
|
31
|
+
mode = 'create',
|
|
32
|
+
form,
|
|
33
|
+
initialContent = {},
|
|
34
|
+
resource,
|
|
35
|
+
onSubmit,
|
|
36
|
+
onCancel,
|
|
37
|
+
submitLabel,
|
|
38
|
+
}) {
|
|
39
|
+
const { t } = useLocale()
|
|
40
|
+
const fields = fieldsProp ?? []
|
|
41
|
+
const defaultData = Object.keys(initialContent).length
|
|
42
|
+
? initialContent
|
|
43
|
+
: (resource?.content ?? {})
|
|
44
|
+
|
|
45
|
+
if (form && mode === 'edit') {
|
|
46
|
+
if (fields.length && form.data) {
|
|
47
|
+
return (
|
|
48
|
+
<Fields
|
|
49
|
+
schemaId={schemaId}
|
|
50
|
+
data={form.data}
|
|
51
|
+
onChange={form.onChange}
|
|
52
|
+
fields={fields}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (fields.length && !form.data) {
|
|
58
|
+
return (
|
|
59
|
+
<DelayedRender>
|
|
60
|
+
<Text>Loading...</Text>
|
|
61
|
+
</DelayedRender>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Form
|
|
70
|
+
id={schemaId ? `${schemaId}/default` : 'schema-form-default'}
|
|
71
|
+
schemaId={schemaId}
|
|
72
|
+
fields={fields}
|
|
73
|
+
defaultData={defaultData}
|
|
74
|
+
onSubmit={onSubmit}
|
|
75
|
+
gap="m"
|
|
76
|
+
>
|
|
77
|
+
<FieldFactory />
|
|
78
|
+
<View layout="row" gap="s" justifyContent="flex-end">
|
|
79
|
+
{onCancel && <Button variant="link" onClick={onCancel} label="design-system.cancel" />}
|
|
80
|
+
<Button type="submit" variant="cta">
|
|
81
|
+
{submitLabel || (mode === 'edit' ? t('design-system.save') : 'Submit')}
|
|
82
|
+
</Button>
|
|
83
|
+
</View>
|
|
84
|
+
</Form>
|
|
85
|
+
)
|
|
86
|
+
}
|
package/src/SchemaPresenter.jsx
CHANGED
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
resolveMimeViewChain,
|
|
10
10
|
resolveViewComponentChain,
|
|
11
11
|
} from '@ossy/schema/resolve'
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { Guide, Text, DelayedRender, Slot, View } from '@ossy/design-system'
|
|
13
|
+
import { SchemaDetailView } from './SchemaDetailView.jsx'
|
|
14
14
|
import { useSchema } from './useSchemas.js'
|
|
15
15
|
import { ImageResource } from './ImageResource.jsx'
|
|
16
16
|
import { AudioResource } from './AudioResource.jsx'
|
|
@@ -25,30 +25,19 @@ export function resolveMediaType (resource) {
|
|
|
25
25
|
return resource?.type
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function SchemaViewFallback ({ resource, projection, resourceId }) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (template?.fields?.length) {
|
|
32
|
-
return (
|
|
33
|
-
<View stack gap="m" inset="m">
|
|
34
|
-
<Text variant="heading-default" as="h1">{resource?.name}</Text>
|
|
35
|
-
<Fields data={resource.content || {}} fields={template.fields} />
|
|
36
|
-
</View>
|
|
37
|
-
)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (resource?.content) {
|
|
41
|
-
const fields = Object.entries(resource.content)
|
|
28
|
+
function SchemaViewFallback ({ resource, projection, resourceId, fields }) {
|
|
29
|
+
if (fields?.length || resource?.content) {
|
|
42
30
|
return (
|
|
43
|
-
<View
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
31
|
+
<View inset="m">
|
|
32
|
+
<SchemaDetailView
|
|
33
|
+
schemaId={resource?.type}
|
|
34
|
+
fields={fields}
|
|
35
|
+
data={resource?.content ?? {}}
|
|
36
|
+
title={resource?.name}
|
|
37
|
+
/>
|
|
38
|
+
{projection && (
|
|
39
|
+
<Text size="s" color="secondary">projection: {projection}</Text>
|
|
40
|
+
)}
|
|
52
41
|
</View>
|
|
53
42
|
)
|
|
54
43
|
}
|
|
@@ -57,27 +46,36 @@ function SchemaViewFallback ({ resource, projection, resourceId }) {
|
|
|
57
46
|
}
|
|
58
47
|
|
|
59
48
|
function SchemaViewPresenter ({ resource, projection, resourceId }) {
|
|
49
|
+
const template = useSchema(resource?.type)
|
|
60
50
|
const chain = useMemo(
|
|
61
51
|
() => resolveViewComponentChain(resource.type, projection),
|
|
62
52
|
[resource.type, projection],
|
|
63
53
|
)
|
|
64
54
|
const [primary, secondary] = chain
|
|
55
|
+
const slotProps = {
|
|
56
|
+
resourceId,
|
|
57
|
+
resource,
|
|
58
|
+
schemaId: resource.type,
|
|
59
|
+
projection,
|
|
60
|
+
fields: template?.fields,
|
|
61
|
+
}
|
|
65
62
|
|
|
66
63
|
return (
|
|
67
64
|
<Slot
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
resource={resource}
|
|
71
|
-
schemaId={resource.type}
|
|
72
|
-
projection={projection}
|
|
65
|
+
view={primary}
|
|
66
|
+
{...slotProps}
|
|
73
67
|
fallback={
|
|
74
68
|
<Slot
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
69
|
+
view={secondary}
|
|
70
|
+
{...slotProps}
|
|
71
|
+
fallback={(
|
|
72
|
+
<SchemaViewFallback
|
|
73
|
+
resource={resource}
|
|
74
|
+
projection={projection}
|
|
75
|
+
resourceId={resourceId}
|
|
76
|
+
fields={template?.fields}
|
|
77
|
+
/>
|
|
78
|
+
)}
|
|
81
79
|
/>
|
|
82
80
|
}
|
|
83
81
|
/>
|
|
@@ -103,13 +101,13 @@ function MimeViewPresenter ({ resource, projection, resourceId, onClose }) {
|
|
|
103
101
|
const [primary, secondary] = chain
|
|
104
102
|
return (
|
|
105
103
|
<Slot
|
|
106
|
-
|
|
104
|
+
view={primary}
|
|
107
105
|
resourceId={resourceId}
|
|
108
106
|
resource={resource}
|
|
109
107
|
projection={projection}
|
|
110
108
|
fallback={
|
|
111
109
|
<Slot
|
|
112
|
-
|
|
110
|
+
view={secondary}
|
|
113
111
|
resourceId={resourceId}
|
|
114
112
|
resource={resource}
|
|
115
113
|
projection={projection}
|
|
@@ -183,7 +181,7 @@ export function SchemaPresenter ({
|
|
|
183
181
|
return <ResourceGenericView resourceId={resourceId} onClose={onClose} />
|
|
184
182
|
}
|
|
185
183
|
|
|
186
|
-
if (resource.type ===
|
|
184
|
+
if (resource.type === '@ossy/platform/schema/directory' || resource.type === 'directory') {
|
|
187
185
|
return <Text>directory</Text>
|
|
188
186
|
}
|
|
189
187
|
|
|
@@ -192,7 +190,7 @@ export function SchemaPresenter ({
|
|
|
192
190
|
}
|
|
193
191
|
|
|
194
192
|
const mime = resolveMediaType(resource)
|
|
195
|
-
if (isMimeType(mime) || resource.type ===
|
|
193
|
+
if (isMimeType(mime) || resource.type === '@ossy/platform/schema/file') {
|
|
196
194
|
return <MimeViewPresenter resource={resource} projection={projection} resourceId={resourceId} onClose={onClose} />
|
|
197
195
|
}
|
|
198
196
|
|
|
@@ -203,13 +201,7 @@ export function SchemaPresenter ({
|
|
|
203
201
|
* Create flow — resolves form component for a schema id.
|
|
204
202
|
*/
|
|
205
203
|
export function SchemaFormPresenter (props) {
|
|
206
|
-
|
|
207
|
-
return (
|
|
208
|
-
<GenericResourceForm
|
|
209
|
-
schemaId={schemaId}
|
|
210
|
-
{...rest}
|
|
211
|
-
/>
|
|
212
|
-
)
|
|
204
|
+
return <GenericResourceForm {...props} />
|
|
213
205
|
}
|
|
214
206
|
|
|
215
|
-
export { resolveFormComponentId }
|
|
207
|
+
export { resolveFormComponentId, resolveFormComponentChain } from '@ossy/schema/resolve'
|
package/src/Upload.page.jsx
CHANGED
|
@@ -14,7 +14,7 @@ export const metadata = {
|
|
|
14
14
|
export const UploadPage = () => {
|
|
15
15
|
return (
|
|
16
16
|
<View layout="off-center-s" style={{ height: '100%' }}>
|
|
17
|
-
<View
|
|
17
|
+
<View data-region="content" surface="primary" roundness="m" inset="m">
|
|
18
18
|
<UploadResources/>
|
|
19
19
|
</View>
|
|
20
20
|
</View>
|
package/src/create.task.js
CHANGED
|
@@ -3,7 +3,6 @@ import { nanoid } from 'nanoid'
|
|
|
3
3
|
import { Aggregate } from '@ossy/event-store'
|
|
4
4
|
import { Workspace } from '@ossy/workspaces/server'
|
|
5
5
|
import { schemaForWorkspace } from '@ossy/platform'
|
|
6
|
-
import { PlatformSchema } from '@ossy/platform'
|
|
7
6
|
import { originalObjectKey } from '@ossy/platform/storage-keys'
|
|
8
7
|
import { commitResource } from './resource-stream.helpers.js'
|
|
9
8
|
import { ResourcesEvents } from './resources.events.js'
|
|
@@ -33,9 +32,9 @@ export async function run ({ payload, integrations, req }) {
|
|
|
33
32
|
const engine = schemaForWorkspace(workspace.schemas)
|
|
34
33
|
const isDocumentSchema = engine.has(type)
|
|
35
34
|
|
|
36
|
-
if (type === 'directory' || type ===
|
|
35
|
+
if (type === 'directory' || type === '@ossy/platform/schema/directory') {
|
|
37
36
|
const event = ResourcesEvents.Created({
|
|
38
|
-
schemaId:
|
|
37
|
+
schemaId: '@ossy/platform/schema/directory',
|
|
39
38
|
createdBy,
|
|
40
39
|
belongsTo: workspace.id,
|
|
41
40
|
location,
|
|
@@ -71,7 +70,7 @@ export async function run ({ payload, integrations, req }) {
|
|
|
71
70
|
const resourceId = nanoid()
|
|
72
71
|
const event = ResourcesEvents.Created({
|
|
73
72
|
resourceId,
|
|
74
|
-
schemaId:
|
|
73
|
+
schemaId: '@ossy/platform/schema/file',
|
|
75
74
|
createdBy,
|
|
76
75
|
belongsTo: workspace.id,
|
|
77
76
|
location,
|
package/src/delete.task.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { PlatformSchema } from '@ossy/platform'
|
|
2
1
|
import { ResourcesEvents } from './resources.events.js'
|
|
3
2
|
import { ResourcesQueries } from './resources.queries.js'
|
|
4
3
|
import { mutateResource, viewResource } from './resource-stream.helpers.js'
|
|
@@ -14,7 +13,7 @@ export async function run ({ payload, req }) {
|
|
|
14
13
|
|
|
15
14
|
const resource = await viewResource(resourceId)
|
|
16
15
|
|
|
17
|
-
if (resource.type ===
|
|
16
|
+
if (resource.type === '@ossy/platform/schema/directory' || resource.type === 'directory') {
|
|
18
17
|
const nestedResources = await ResourcesQueries.GetNestedResources({
|
|
19
18
|
location: resource.location + resource.name + '/',
|
|
20
19
|
})
|
package/src/en.translations.json
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"resources.form.selectType": "Select a resource type to continue.",
|
|
8
8
|
"resources.form.createFailed": "Create failed",
|
|
9
9
|
"resources.form.createResource": "Create resource",
|
|
10
|
+
"resources.form.typeRemoved": "This document type has been removed and can't be edited.",
|
|
10
11
|
"@ossy/resources/actions/create.label": "Create resource",
|
|
11
12
|
"@ossy/resources/actions/create.description": "Create a new resource in the workspace",
|
|
12
13
|
"@ossy/resources/actions/list.label": "List resources",
|
package/src/index.js
CHANGED
|
@@ -39,6 +39,10 @@ export * from './VideoResource.jsx'
|
|
|
39
39
|
export * from './useActivePath.jsx'
|
|
40
40
|
export * from './useSelectedResourceId.jsx'
|
|
41
41
|
export * from './SchemaPresenter.jsx'
|
|
42
|
+
export * from './SchemaDetailView.jsx'
|
|
43
|
+
export * from './SchemaFormDefault.jsx'
|
|
44
|
+
export { DEFAULT_SCHEMA_VIEW_SLOTS } from './schemaViewSlots.js'
|
|
45
|
+
export { DEFAULT_SCHEMA_FORM_SLOTS } from './schemaFormSlots.js'
|
|
42
46
|
export * from './useSchemas.js'
|
|
43
47
|
export * from './useSchemaEngine.js'
|
|
44
48
|
export * from './useDocumentValidator.js'
|
|
@@ -35,7 +35,7 @@ export default function GenericResourceCreatePage () {
|
|
|
35
35
|
|
|
36
36
|
return (
|
|
37
37
|
<View layout="off-center-m" style={{ height: '100%' }}>
|
|
38
|
-
<View
|
|
38
|
+
<View data-region="content" surface="primary" roundness="m" inset="m">
|
|
39
39
|
<GenericResourceForm
|
|
40
40
|
schemaId={schemaId}
|
|
41
41
|
mode="create"
|
|
@@ -17,7 +17,7 @@ export default function ResourceDetailPage () {
|
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
19
|
<View layout="off-center-m" style={{ height: '100%' }}>
|
|
20
|
-
<View
|
|
20
|
+
<View data-region="content" surface="primary" roundness="m" inset="m">
|
|
21
21
|
<GenericResourceDetail resourceId={resourceId} />
|
|
22
22
|
</View>
|
|
23
23
|
</View>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Text, View } from '@ossy/design-system'
|
|
3
|
+
import { SchemaDetailView } from './SchemaDetailView.jsx'
|
|
4
|
+
|
|
5
|
+
export const metadata = { id: '@ossy/resources/view/detail' }
|
|
6
|
+
|
|
7
|
+
/** Generic schema detail view — `@ossy/resources/view/detail`. */
|
|
8
|
+
export default function SchemaDetailViewComponent ({ resource, schemaId, fields, projection }) {
|
|
9
|
+
if (!resource) return null
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<View inset="m">
|
|
13
|
+
<SchemaDetailView
|
|
14
|
+
schemaId={schemaId ?? resource.type}
|
|
15
|
+
fields={fields}
|
|
16
|
+
data={resource.content ?? {}}
|
|
17
|
+
title={resource.name}
|
|
18
|
+
/>
|
|
19
|
+
{projection && (
|
|
20
|
+
<Text size="s" color="secondary">projection: {projection}</Text>
|
|
21
|
+
)}
|
|
22
|
+
</View>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View } from '@ossy/design-system'
|
|
3
|
+
import { SchemaFormDefault } from './SchemaFormDefault.jsx'
|
|
4
|
+
|
|
5
|
+
export const metadata = { id: '@ossy/resources/form/default' }
|
|
6
|
+
|
|
7
|
+
/** Generic schema form — `@ossy/resources/form/default`. */
|
|
8
|
+
export default function SchemaFormDefaultComponent (props) {
|
|
9
|
+
return (
|
|
10
|
+
<View inset="m">
|
|
11
|
+
<SchemaFormDefault {...props} />
|
|
12
|
+
</View>
|
|
13
|
+
)
|
|
14
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import SchemaFormDefaultComponent from './schema-form-default.component.jsx'
|
|
2
|
+
|
|
3
|
+
/** Default schema form slots merged into {@link ComponentSlotsProvider} at app bootstrap. */
|
|
4
|
+
export const DEFAULT_SCHEMA_FORM_SLOTS = {
|
|
5
|
+
'@ossy/resources/form/default': SchemaFormDefaultComponent,
|
|
6
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import SchemaDetailViewComponent from './schema-detail.component.jsx'
|
|
2
|
+
|
|
3
|
+
/** Default schema view slots merged into {@link ComponentSlotsProvider} at app bootstrap. */
|
|
4
|
+
export const DEFAULT_SCHEMA_VIEW_SLOTS = {
|
|
5
|
+
'@ossy/resources/view/detail': SchemaDetailViewComponent,
|
|
6
|
+
}
|
package/src/sv.translations.json
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"resources.form.selectType": "Välj en resurstyp för att fortsätta.",
|
|
8
8
|
"resources.form.createFailed": "Skapandet misslyckades",
|
|
9
9
|
"resources.form.createResource": "Skapa resurs",
|
|
10
|
+
"resources.form.typeRemoved": "Den här dokumenttypen har tagits bort och kan inte redigeras.",
|
|
10
11
|
"@ossy/resources/actions/create.label": "Skapa resurs",
|
|
11
12
|
"@ossy/resources/actions/create.description": "Skapa en ny resurs i workspace",
|
|
12
13
|
"@ossy/resources/actions/list.label": "Lista resurser",
|