@ossy/resources 1.12.2 → 1.12.3
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 -3
- package/src/AudioResource.jsx +1 -1
- package/src/CreateDirectory.jsx +2 -10
- package/src/CreateDocument.jsx +2 -2
- package/src/Definition.js +2 -1
- package/src/DocumentEdit.jsx +10 -38
- package/src/DocumentView.jsx +3 -4
- package/src/GenericResourceCard.jsx +25 -7
- package/src/GenericResourceDetail.jsx +6 -60
- package/src/GenericResourceForm.jsx +66 -31
- package/src/ImageResource.jsx +1 -1
- package/src/PDFResource.jsx +1 -1
- package/src/PlatformFileField.jsx +239 -0
- package/src/ResourceContentPage.jsx +3 -3
- package/src/ResourceDescription.jsx +1 -1
- package/src/ResourceDetails.jsx +3 -4
- package/src/ResourceDialogMove.jsx +2 -2
- 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/SchemaPresenter.jsx +215 -0
- package/src/Upload.jsx +6 -25
- 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 +28 -29
- package/src/delete.action.js +1 -1
- package/src/delete.task.js +10 -13
- package/src/en.translations.json +24 -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 +6 -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 +4 -4
- 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/search.action.js +1 -1
- package/src/search.task.js +1 -1
- package/src/server.js +2 -1
- package/src/sv.translations.json +24 -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/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,215 @@
|
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
import { metadata as GetResource } from './get.action.js'
|
|
3
|
+
import { AsyncStatus, useSdk } from '@ossy/sdk-react'
|
|
4
|
+
import { useSchemas } from './useSchemas.js'
|
|
5
|
+
import {
|
|
6
|
+
isMimeType,
|
|
7
|
+
isSchemaDocumentType,
|
|
8
|
+
resolveFormComponentId,
|
|
9
|
+
resolveMimeViewChain,
|
|
10
|
+
resolveViewComponentChain,
|
|
11
|
+
} from '@ossy/schema/resolve'
|
|
12
|
+
import { PlatformSchema } from '@ossy/platform'
|
|
13
|
+
import { Guide, Text, DelayedRender, Slot, View, Fields } from '@ossy/design-system'
|
|
14
|
+
import { useSchema } from './useSchemas.js'
|
|
15
|
+
import { ImageResource } from './ImageResource.jsx'
|
|
16
|
+
import { AudioResource } from './AudioResource.jsx'
|
|
17
|
+
import { VideoResource } from './VideoResource.jsx'
|
|
18
|
+
import { PDFResource } from './PDFResource.jsx'
|
|
19
|
+
import { ResourceGenericView } from './ResourceGenericView.jsx'
|
|
20
|
+
import { GenericResourceForm } from './GenericResourceForm.jsx'
|
|
21
|
+
|
|
22
|
+
/** MIME type for rendering — file envelope stores MIME in content. */
|
|
23
|
+
export function resolveMediaType (resource) {
|
|
24
|
+
if (resource?.content?.ContentType) return resource.content.ContentType
|
|
25
|
+
return resource?.type
|
|
26
|
+
}
|
|
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)
|
|
42
|
+
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>}
|
|
52
|
+
</View>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return <ResourceGenericView resourceId={resourceId} />
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function SchemaViewPresenter ({ resource, projection, resourceId }) {
|
|
60
|
+
const chain = useMemo(
|
|
61
|
+
() => resolveViewComponentChain(resource.type, projection),
|
|
62
|
+
[resource.type, projection],
|
|
63
|
+
)
|
|
64
|
+
const [primary, secondary] = chain
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Slot
|
|
68
|
+
name={primary}
|
|
69
|
+
resourceId={resourceId}
|
|
70
|
+
resource={resource}
|
|
71
|
+
schemaId={resource.type}
|
|
72
|
+
projection={projection}
|
|
73
|
+
fallback={
|
|
74
|
+
<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} />}
|
|
81
|
+
/>
|
|
82
|
+
}
|
|
83
|
+
/>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function MimeViewPresenter ({ resource, projection, resourceId, onClose }) {
|
|
88
|
+
const mime = resolveMediaType(resource)
|
|
89
|
+
if (['image/jpeg', 'image/png', 'image/webp'].includes(mime)) {
|
|
90
|
+
return <ImageResource resourceId={resourceId} onClose={onClose} />
|
|
91
|
+
}
|
|
92
|
+
if (mime?.startsWith('video/')) {
|
|
93
|
+
return <VideoResource resourceId={resourceId} onClose={onClose} />
|
|
94
|
+
}
|
|
95
|
+
if (mime?.startsWith('audio/')) {
|
|
96
|
+
return <AudioResource resourceId={resourceId} onClose={onClose} />
|
|
97
|
+
}
|
|
98
|
+
if (mime === 'application/pdf') {
|
|
99
|
+
return <PDFResource resourceId={resourceId} onClose={onClose} />
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const chain = resolveMimeViewChain(mime, projection)
|
|
103
|
+
const [primary, secondary] = chain
|
|
104
|
+
return (
|
|
105
|
+
<Slot
|
|
106
|
+
name={primary}
|
|
107
|
+
resourceId={resourceId}
|
|
108
|
+
resource={resource}
|
|
109
|
+
projection={projection}
|
|
110
|
+
fallback={
|
|
111
|
+
<Slot
|
|
112
|
+
name={secondary}
|
|
113
|
+
resourceId={resourceId}
|
|
114
|
+
resource={resource}
|
|
115
|
+
projection={projection}
|
|
116
|
+
fallback={<ResourceGenericView resourceId={resourceId} onClose={onClose} />}
|
|
117
|
+
/>
|
|
118
|
+
}
|
|
119
|
+
/>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* ADR 0006 host: `resourceId` → fetch → `resource.type` → view/form component chain.
|
|
125
|
+
*/
|
|
126
|
+
export function SchemaPresenter ({
|
|
127
|
+
resourceId,
|
|
128
|
+
resource: resourceProp,
|
|
129
|
+
flow = 'View',
|
|
130
|
+
projection,
|
|
131
|
+
form,
|
|
132
|
+
onClose,
|
|
133
|
+
onSubmit,
|
|
134
|
+
onCancel,
|
|
135
|
+
mode,
|
|
136
|
+
initialName,
|
|
137
|
+
initialContent,
|
|
138
|
+
submitLabel,
|
|
139
|
+
}) {
|
|
140
|
+
const sdk = useSdk()
|
|
141
|
+
const schemas = useSchemas()
|
|
142
|
+
const { data: loaded, status } = sdk.read(GetResource, { resourceId }, { enabled: !!resourceId && !resourceProp })
|
|
143
|
+
const resource = resourceProp ?? loaded
|
|
144
|
+
|
|
145
|
+
if (!resourceProp && AsyncStatus.Error === status) {
|
|
146
|
+
return (
|
|
147
|
+
<Guide
|
|
148
|
+
title="Not found"
|
|
149
|
+
text="We can't find the resource you are looking for"
|
|
150
|
+
style={{ padding: '40px 16px' }}
|
|
151
|
+
/>
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!resource && [AsyncStatus.Loading, AsyncStatus.NotInitialized].includes(status)) {
|
|
156
|
+
return (
|
|
157
|
+
<DelayedRender>
|
|
158
|
+
<Text>loading...</Text>
|
|
159
|
+
</DelayedRender>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!resource) return null
|
|
164
|
+
|
|
165
|
+
const isDocumentSchema =
|
|
166
|
+
isSchemaDocumentType(resource.type) || schemas.some(s => s.id === resource.type)
|
|
167
|
+
|
|
168
|
+
if (flow === 'Edit' || flow === 'edit') {
|
|
169
|
+
if (isDocumentSchema) {
|
|
170
|
+
return (
|
|
171
|
+
<GenericResourceForm
|
|
172
|
+
schemaId={resource.type}
|
|
173
|
+
mode="edit"
|
|
174
|
+
resourceId={resourceId}
|
|
175
|
+
resource={resource}
|
|
176
|
+
form={form}
|
|
177
|
+
onSubmit={onSubmit}
|
|
178
|
+
onCancel={onCancel}
|
|
179
|
+
submitLabel={submitLabel}
|
|
180
|
+
/>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
return <ResourceGenericView resourceId={resourceId} onClose={onClose} />
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (resource.type === PlatformSchema.directory || resource.type === 'directory') {
|
|
187
|
+
return <Text>directory</Text>
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (isDocumentSchema) {
|
|
191
|
+
return <SchemaViewPresenter resource={resource} projection={projection} resourceId={resourceId} />
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const mime = resolveMediaType(resource)
|
|
195
|
+
if (isMimeType(mime) || resource.type === PlatformSchema.file) {
|
|
196
|
+
return <MimeViewPresenter resource={resource} projection={projection} resourceId={resourceId} onClose={onClose} />
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return <ResourceGenericView resourceId={resourceId} onClose={onClose} />
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Create flow — resolves form component for a schema id.
|
|
204
|
+
*/
|
|
205
|
+
export function SchemaFormPresenter (props) {
|
|
206
|
+
const { schemaId, ...rest } = props
|
|
207
|
+
return (
|
|
208
|
+
<GenericResourceForm
|
|
209
|
+
schemaId={schemaId}
|
|
210
|
+
{...rest}
|
|
211
|
+
/>
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export { resolveFormComponentId }
|
package/src/Upload.jsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useState
|
|
2
|
-
import { Button, View, Text,
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { Button, View, Text, UploadInput, Icon, LocalFilePreview } from '@ossy/design-system'
|
|
3
3
|
import { useRouter } from '@ossy/router-react'
|
|
4
4
|
|
|
5
5
|
const FlowStage = {
|
|
@@ -53,8 +53,8 @@ export const Upload = ({
|
|
|
53
53
|
<View gap="s" style={{ flexGrow: '1', overflow: 'auto', padding: 'var(--space-m) 0' }}>
|
|
54
54
|
{
|
|
55
55
|
files.map(({ status, file }) => (
|
|
56
|
-
<View layout="row" gap="m" alignItems="center">
|
|
57
|
-
<
|
|
56
|
+
<View key={file.name} layout="row" gap="m" alignItems="center">
|
|
57
|
+
<LocalFilePreview file={file} />
|
|
58
58
|
<Text style={{ margin: '0', lineHeight: '1', textWrap: 'nowrap', textOverflow: 'ellipsis', flexGrow: '1' }}>{file?.name || 'Untitled'}</Text>
|
|
59
59
|
<View layout="row" gap="xs">
|
|
60
60
|
<UploadStatus status={status} size="8px" />
|
|
@@ -68,7 +68,8 @@ export const Upload = ({
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
{files.length === 0 && (
|
|
71
|
-
<
|
|
71
|
+
<UploadInput
|
|
72
|
+
id="upload-resources"
|
|
72
73
|
type="file"
|
|
73
74
|
multiple
|
|
74
75
|
onChange={onUserInput}
|
|
@@ -133,26 +134,6 @@ export const Upload = ({
|
|
|
133
134
|
|
|
134
135
|
|
|
135
136
|
|
|
136
|
-
function UploadImgPreview({ file, size = "32px" }) {
|
|
137
|
-
const [src, setSrc] = useState()
|
|
138
|
-
|
|
139
|
-
useEffect(() => {
|
|
140
|
-
const reader = new FileReader()
|
|
141
|
-
reader.onload = (e) => setSrc(e.target.result)
|
|
142
|
-
reader.readAsDataURL(file)
|
|
143
|
-
}, [file])
|
|
144
|
-
|
|
145
|
-
return (
|
|
146
|
-
<View
|
|
147
|
-
as="img"
|
|
148
|
-
src={src}
|
|
149
|
-
width={size}
|
|
150
|
-
height={size}
|
|
151
|
-
style={{ backgroundSize: 'cover', borderRadius: 'var(--space-s)' }}
|
|
152
|
-
/>
|
|
153
|
-
)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
137
|
function UploadStatus({ status }) {
|
|
157
138
|
if (status === 'preview') return <></>
|
|
158
139
|
if (status === 'uploading') return <Icon name="spinner" size="s" animation="rotate" />
|
package/src/UploadResources.jsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { uploadFile } from './resource.helpers.js'
|
|
3
3
|
import { useSdk } from '@ossy/sdk-react'
|
|
4
|
-
import {
|
|
4
|
+
import { View, Text, useLocale } from '@ossy/design-system'
|
|
5
5
|
import { useRouter } from '@ossy/router-react'
|
|
6
6
|
import { Upload } from './Upload.jsx'
|
|
7
7
|
|
|
@@ -14,7 +14,7 @@ export const UploadResources = () => {
|
|
|
14
14
|
return (
|
|
15
15
|
<View gap="s" style={{ height: '100%' }}>
|
|
16
16
|
<View gap="s">
|
|
17
|
-
<
|
|
17
|
+
<Text variant="heading-tertiary" style={{ marginBottom: '0' }} as="h3" text="resources.upload.title" />
|
|
18
18
|
<View layout="row" gap="s">
|
|
19
19
|
<Text variant="small">{t('resources.upload.location', { location })}</Text>
|
|
20
20
|
</View>
|
package/src/VideoResource.jsx
CHANGED
|
@@ -7,7 +7,7 @@ export const VideoResource = ({
|
|
|
7
7
|
resourceId
|
|
8
8
|
}) => {
|
|
9
9
|
const sdk = useSdk()
|
|
10
|
-
const { data: resource } = sdk.read(GetResource, {
|
|
10
|
+
const { data: resource } = sdk.read(GetResource, { resourceId }, { enabled: !!resourceId })
|
|
11
11
|
|
|
12
12
|
return (
|
|
13
13
|
<View stack bordered>
|
|
@@ -50,7 +50,7 @@ describe('accessFilter', () => {
|
|
|
50
50
|
const user = { id: 'u-1', workspaces: ['ws-1', 'ws-2'], policies: [] }
|
|
51
51
|
const filter = accessFilter(user)
|
|
52
52
|
expect(filter.$or).toContainEqual({
|
|
53
|
-
'state.access': { $in: ['public', 'workspace'] },
|
|
53
|
+
'state.access': { $in: ['public', 'workspace', 'restricted'] },
|
|
54
54
|
'state.belongsTo': { $in: ['ws-1', 'ws-2'] },
|
|
55
55
|
})
|
|
56
56
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'resources/create-page-view', access: 'workspace' }
|
|
1
|
+
export const metadata = { id: '@ossy/resources/actions/create-page-view', access: 'workspace' }
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid'
|
|
1
2
|
import { Aggregate } from '@ossy/event-store'
|
|
2
|
-
import { Resource } from './resource.aggregate.js'
|
|
3
|
-
import { ResourcesEvents } from './resources.events.js'
|
|
4
3
|
import { Workspace } from '@ossy/workspaces/server'
|
|
5
|
-
import {
|
|
4
|
+
import { ResourcesEvents } from './resources.events.js'
|
|
5
|
+
import { commitResource } from './resource-stream.helpers.js'
|
|
6
6
|
|
|
7
|
-
export const metadata = { id: 'resources/create-page-view' }
|
|
7
|
+
export const metadata = { id: '@ossy/resources/tasks/create-page-view' }
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
const PAGE_VIEW_SCHEMA_ID = '@ossy/web/schema/page-view'
|
|
10
10
|
const PAGE_VIEW_LOCATION = '/@ossy/analytics/page-views/'
|
|
11
11
|
|
|
12
|
-
export async function run({ payload, req }) {
|
|
12
|
+
export async function run ({ payload, req }) {
|
|
13
13
|
const workspaceId = payload?.workspaceId ?? req?.workspaceId
|
|
14
14
|
if (!workspaceId) throw Object.assign(new Error('workspaceId is required'), { status: 400 })
|
|
15
15
|
|
|
16
16
|
const workspace = await Aggregate.Of(Workspace, workspaceId).then(Aggregate.View())
|
|
17
17
|
|
|
18
|
-
// Page-view template is a built-in; instantiate directly without schema validation
|
|
19
18
|
const contentInput = {
|
|
20
19
|
...(payload ?? {}),
|
|
21
20
|
userAgent: payload?.userAgent ?? req?.headers?.['user-agent'],
|
|
@@ -23,8 +22,10 @@ export async function run({ payload, req }) {
|
|
|
23
22
|
path: payload?.path ?? '/',
|
|
24
23
|
}
|
|
25
24
|
|
|
25
|
+
const resourceId = nanoid()
|
|
26
26
|
const event = ResourcesEvents.Created({
|
|
27
|
-
|
|
27
|
+
resourceId,
|
|
28
|
+
schemaId: PAGE_VIEW_SCHEMA_ID,
|
|
28
29
|
createdBy: payload?.userId ?? req?.userId ?? 'public',
|
|
29
30
|
belongsTo: workspace.id,
|
|
30
31
|
location: PAGE_VIEW_LOCATION,
|
|
@@ -33,6 +34,6 @@ export async function run({ payload, req }) {
|
|
|
33
34
|
access: 'restricted',
|
|
34
35
|
})
|
|
35
36
|
|
|
36
|
-
const resource = await
|
|
37
|
+
const resource = await commitResource(event)
|
|
37
38
|
return { id: resource.id }
|
|
38
39
|
}
|
package/src/create.action.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const metadata = { id: 'resources/create', access: 'workspace' }
|
|
1
|
+
export const metadata = { id: '@ossy/resources/actions/create', access: 'workspace' }
|