@ossy/resources 1.12.3 → 3.0.2

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 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": "1.12.3",
4
+ "version": "3.0.2",
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": "^1.8.3",
24
- "@ossy/fold": "^1.0.1",
25
- "@ossy/platform": "^1.39.3",
26
- "@ossy/schema": "^1.0.1"
26
+ "@ossy/event-store": "^3.0.2",
27
+ "@ossy/fold": "^3.0.2",
28
+ "@ossy/platform": "^3.0.2",
29
+ "@ossy/schema": "^3.0.2"
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": "^1.39.2",
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": "a0d89185a17f8de8ce328c3a648c108ff1d61d8f"
52
+ "gitHead": "f0a8af3c370bbaffe4f7388c14f9345a5c6c56e5"
49
53
  }
@@ -18,7 +18,7 @@ export const AudioResource = ({
18
18
  <View
19
19
  as="audio"
20
20
  controls
21
- slot="content"
21
+ data-region="content"
22
22
  src={resource.content.src}
23
23
  />
24
24
  </View>
@@ -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 slot="content" surface="primary" roundness="m" inset="m">
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 slot="content" surface='primary' roundness='m' inset="m">
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
@@ -9,6 +9,6 @@ export const Definition = {
9
9
  We hope you find them useful, and we're here if you have any questions!
10
10
  `,
11
11
  icon: 'image',
12
- statuses: ['beta'],
12
+ status: ['beta'],
13
13
  entitlementRequired: false,
14
14
  }
@@ -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 { Text, View } from '@ossy/design-system'
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
- <View gap="m">
18
- {
19
- !!resource?.content && (template?.fields || [])
20
- .map(field => ({ name: field.name, value: resource?.content?.[field.name] }))
21
- .map(field => (
22
- <View gap="s" key={field.name}>
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
- name={primary}
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
- name={secondary}
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 { resolveFormComponentId } from '@ossy/schema/resolve'
6
- import { Slot, View, Text, InputTitle, Button, Alert, Fields, applyFieldChange, useInputValue, useLocale, DelayedRender } from '@ossy/design-system'
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
- function GenericResourceFormFallback ({
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 || (mode === 'edit' ? t('design-system.save') : (template?.name ? `${t('design-system.add')} ${template.name}` : t('resources.form.createResource')))}
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
- * Generic resource create/edit host — resolves `@ossy/…/form/{concept}` via
104
- * slot registry; falls back to template-driven Fields.
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
- export function GenericResourceForm ({
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 { t } = useLocale()
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
- <Slot
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
- fallback={
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} slot="content" style={{ flexDirection: 'column', wrap: 'no-wrap', height: '100%' }}>
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 slot="content" style={{ padding: 'var(--space-m) var(--space-m) var(--space-xxl)'}}>
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]}>
@@ -26,7 +26,7 @@ export const ResourceDialogMove = ({
26
26
  layout="off-center-s" style={{ height: '100%' }}
27
27
  >
28
28
 
29
- <View slot="content">
29
+ <View data-region="content">
30
30
  <View surface="primary" roundness="s">
31
31
  <View surface="primary" inset="l" roundness="s" gap="m">
32
32
 
@@ -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
+ }
@@ -9,8 +9,8 @@ import {
9
9
  resolveMimeViewChain,
10
10
  resolveViewComponentChain,
11
11
  } from '@ossy/schema/resolve'
12
- import { PlatformSchema } from '@ossy/platform'
13
- import { Guide, Text, DelayedRender, Slot, View, Fields } from '@ossy/design-system'
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
- const template = useSchema(resource?.type)
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 stack gap="m" inset="m">
44
- <Text variant="heading-default" as="h1">{resource?.name}</Text>
45
- {fields.map(([name, value]) => (
46
- <View gap="xs" key={name}>
47
- <Text style={{ fontWeight: 'bold' }}>{name}</Text>
48
- <Text>{typeof value === 'object' ? JSON.stringify(value) : String(value ?? '')}</Text>
49
- </View>
50
- ))}
51
- {projection && <Text size="s" color="secondary">projection: {projection}</Text>}
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
- name={primary}
69
- resourceId={resourceId}
70
- resource={resource}
71
- schemaId={resource.type}
72
- projection={projection}
65
+ view={primary}
66
+ {...slotProps}
73
67
  fallback={
74
68
  <Slot
75
- name={secondary}
76
- resourceId={resourceId}
77
- resource={resource}
78
- schemaId={resource.type}
79
- projection={projection}
80
- fallback={<SchemaViewFallback resource={resource} projection={projection} resourceId={resourceId} />}
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
- name={primary}
104
+ view={primary}
107
105
  resourceId={resourceId}
108
106
  resource={resource}
109
107
  projection={projection}
110
108
  fallback={
111
109
  <Slot
112
- name={secondary}
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 === PlatformSchema.directory || resource.type === 'directory') {
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 === PlatformSchema.file) {
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
- const { schemaId, ...rest } = props
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'
@@ -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 slot="content" surface="primary" roundness="m" inset="m">
17
+ <View data-region="content" surface="primary" roundness="m" inset="m">
18
18
  <UploadResources/>
19
19
  </View>
20
20
  </View>
@@ -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 === PlatformSchema.directory) {
35
+ if (type === 'directory' || type === '@ossy/platform/schema/directory') {
37
36
  const event = ResourcesEvents.Created({
38
- schemaId: PlatformSchema.directory,
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: PlatformSchema.file,
73
+ schemaId: '@ossy/platform/schema/file',
75
74
  createdBy,
76
75
  belongsTo: workspace.id,
77
76
  location,
@@ -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 === PlatformSchema.directory || resource.type === 'directory') {
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
  })
@@ -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 slot="content" surface="primary" roundness="m" inset="m">
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 slot="content" surface="primary" roundness="m" inset="m">
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
+ }
@@ -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",