@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
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useState } from 'react'
|
|
2
|
+
import { useSdk } from '@ossy/sdk-react'
|
|
3
|
+
import {
|
|
4
|
+
View,
|
|
5
|
+
Text,
|
|
6
|
+
Button,
|
|
7
|
+
UploadInput,
|
|
8
|
+
LocalFilePreview,
|
|
9
|
+
acceptsFile,
|
|
10
|
+
} from '@ossy/design-system'
|
|
11
|
+
import { uploadFile } from './resource.helpers.js'
|
|
12
|
+
import { metadata as GetResource } from './get.action.js'
|
|
13
|
+
|
|
14
|
+
function commitResourceValue(name, value, onChange) {
|
|
15
|
+
onChange({
|
|
16
|
+
target: {
|
|
17
|
+
id: name,
|
|
18
|
+
name,
|
|
19
|
+
dataset: { ossyFileResourceReplace: 'true' },
|
|
20
|
+
value: JSON.stringify(value),
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resourceIdFromValue(value) {
|
|
26
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
27
|
+
return typeof value.resourceId === 'string' ? value.resourceId : null
|
|
28
|
+
}
|
|
29
|
+
return null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resourceIdsFromValue(value) {
|
|
33
|
+
if (!Array.isArray(value)) return []
|
|
34
|
+
return value
|
|
35
|
+
.map(item => (item && typeof item.resourceId === 'string' ? item.resourceId : null))
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Platform file field — eager upload on pick via SDK create + PUT.
|
|
41
|
+
* Register as `@ossy/design-system/input/file` / `@ossy/design-system/input/image` through app component slots.
|
|
42
|
+
*/
|
|
43
|
+
export function PlatformFileField({
|
|
44
|
+
name,
|
|
45
|
+
label,
|
|
46
|
+
value,
|
|
47
|
+
onChange,
|
|
48
|
+
accept,
|
|
49
|
+
min = 0,
|
|
50
|
+
max = 1,
|
|
51
|
+
disabled,
|
|
52
|
+
required,
|
|
53
|
+
uploadLocation = '/media/uploads/',
|
|
54
|
+
style = {},
|
|
55
|
+
...rest
|
|
56
|
+
}) {
|
|
57
|
+
const sdk = useSdk()
|
|
58
|
+
const multi = max > 1
|
|
59
|
+
const [localFile, setLocalFile] = useState(null)
|
|
60
|
+
const [previewUrl, setPreviewUrl] = useState(null)
|
|
61
|
+
const [uploading, setUploading] = useState(false)
|
|
62
|
+
const [error, setError] = useState(null)
|
|
63
|
+
|
|
64
|
+
const resourceId = multi ? null : resourceIdFromValue(value)
|
|
65
|
+
const resourceIds = multi ? resourceIdsFromValue(value) : []
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!resourceId || localFile) {
|
|
69
|
+
if (!localFile) setPreviewUrl(null)
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let cancelled = false
|
|
74
|
+
sdk.invoke(GetResource, { resourceId })
|
|
75
|
+
.then(resource => {
|
|
76
|
+
if (cancelled) return
|
|
77
|
+
const src = resource?.content?.src || `/r/${resourceId}`
|
|
78
|
+
setPreviewUrl(src)
|
|
79
|
+
})
|
|
80
|
+
.catch(() => {
|
|
81
|
+
if (!cancelled) setPreviewUrl(`/r/${resourceId}`)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
return () => {
|
|
85
|
+
cancelled = true
|
|
86
|
+
}
|
|
87
|
+
}, [resourceId, localFile, sdk])
|
|
88
|
+
|
|
89
|
+
const uploadSingle = useCallback(
|
|
90
|
+
async file => {
|
|
91
|
+
setUploading(true)
|
|
92
|
+
setError(null)
|
|
93
|
+
setLocalFile(file)
|
|
94
|
+
try {
|
|
95
|
+
const resource = await uploadFile(sdk, uploadLocation, file)
|
|
96
|
+
commitResourceValue(name, { resourceId: resource.id }, onChange)
|
|
97
|
+
setLocalFile(null)
|
|
98
|
+
} catch (err) {
|
|
99
|
+
setError(err?.message || 'Upload failed')
|
|
100
|
+
setLocalFile(null)
|
|
101
|
+
} finally {
|
|
102
|
+
setUploading(false)
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
[name, onChange, sdk, uploadLocation],
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const onSinglePick = useCallback(
|
|
109
|
+
e => {
|
|
110
|
+
const file = e.target.files?.[0]
|
|
111
|
+
e.target.value = ''
|
|
112
|
+
if (!file || uploading) return
|
|
113
|
+
if (!acceptsFile(file, accept)) {
|
|
114
|
+
setError(`File type not accepted: ${file.name}`)
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
uploadSingle(file)
|
|
118
|
+
},
|
|
119
|
+
[accept, uploadSingle, uploading],
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
const onMultiPick = useCallback(
|
|
123
|
+
async e => {
|
|
124
|
+
const picked = Array.from(e.target.files || [])
|
|
125
|
+
e.target.value = ''
|
|
126
|
+
if (!picked.length || uploading) return
|
|
127
|
+
const valid = picked.filter(f => acceptsFile(f, accept))
|
|
128
|
+
if (valid.length !== picked.length) {
|
|
129
|
+
setError('Some files were rejected by accept filter')
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
const remaining = max - resourceIds.length
|
|
133
|
+
const batch = valid.slice(0, remaining)
|
|
134
|
+
setUploading(true)
|
|
135
|
+
setError(null)
|
|
136
|
+
try {
|
|
137
|
+
const uploaded = []
|
|
138
|
+
for (const file of batch) {
|
|
139
|
+
const resource = await uploadFile(sdk, uploadLocation, file)
|
|
140
|
+
uploaded.push({ resourceId: resource.id })
|
|
141
|
+
}
|
|
142
|
+
commitResourceValue(name, [...resourceIds.map(id => ({ resourceId: id })), ...uploaded], onChange)
|
|
143
|
+
} catch (err) {
|
|
144
|
+
setError(err?.message || 'Upload failed')
|
|
145
|
+
} finally {
|
|
146
|
+
setUploading(false)
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
[accept, max, name, onChange, resourceIds, sdk, uploadLocation, uploading],
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
const removeMulti = useCallback(
|
|
153
|
+
id => {
|
|
154
|
+
commitResourceValue(
|
|
155
|
+
name,
|
|
156
|
+
resourceIds.filter(rid => rid !== id).map(rid => ({ resourceId: rid })),
|
|
157
|
+
onChange,
|
|
158
|
+
)
|
|
159
|
+
},
|
|
160
|
+
[name, onChange, resourceIds],
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if (multi) {
|
|
164
|
+
const canAdd = resourceIds.length < max
|
|
165
|
+
return (
|
|
166
|
+
<View gap="s" style={style} {...rest}>
|
|
167
|
+
{resourceIds.map(id => (
|
|
168
|
+
<View key={id} layout="row" gap="s" alignItems="center">
|
|
169
|
+
<View
|
|
170
|
+
as="img"
|
|
171
|
+
src={`/r/${id}`}
|
|
172
|
+
alt=""
|
|
173
|
+
width="32px"
|
|
174
|
+
height="32px"
|
|
175
|
+
style={{ objectFit: 'cover', borderRadius: 'var(--space-s)' }}
|
|
176
|
+
/>
|
|
177
|
+
<Text style={{ flex: 1, fontSize: 14 }}>{id}</Text>
|
|
178
|
+
<Button
|
|
179
|
+
type="button"
|
|
180
|
+
variant="command-danger"
|
|
181
|
+
prefix="close"
|
|
182
|
+
size="s"
|
|
183
|
+
disabled={disabled || uploading}
|
|
184
|
+
onClick={() => removeMulti(id)}
|
|
185
|
+
/>
|
|
186
|
+
</View>
|
|
187
|
+
))}
|
|
188
|
+
{canAdd && (
|
|
189
|
+
<UploadInput
|
|
190
|
+
id={name}
|
|
191
|
+
name={name}
|
|
192
|
+
accept={accept}
|
|
193
|
+
disabled={disabled || uploading}
|
|
194
|
+
required={required && resourceIds.length === 0}
|
|
195
|
+
multiple
|
|
196
|
+
onChange={onMultiPick}
|
|
197
|
+
/>
|
|
198
|
+
)}
|
|
199
|
+
{uploading && <Text style={{ fontSize: 12 }}>Uploading…</Text>}
|
|
200
|
+
{error && <Text style={{ fontSize: 12, color: 'var(--color-danger, crimson)' }}>{error}</Text>}
|
|
201
|
+
</View>
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const previewSrc = localFile ? null : previewUrl
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<View gap="s" style={style} {...rest}>
|
|
209
|
+
{(localFile || previewSrc || resourceId) && (
|
|
210
|
+
<View layout="row" gap="s" alignItems="center">
|
|
211
|
+
{localFile && <LocalFilePreview file={localFile} size="48px" />}
|
|
212
|
+
{!localFile && previewSrc && (
|
|
213
|
+
<View
|
|
214
|
+
as="img"
|
|
215
|
+
src={previewSrc}
|
|
216
|
+
alt={label || name}
|
|
217
|
+
width="48px"
|
|
218
|
+
height="48px"
|
|
219
|
+
style={{ objectFit: 'cover', borderRadius: 'var(--space-s)' }}
|
|
220
|
+
/>
|
|
221
|
+
)}
|
|
222
|
+
{localFile && (
|
|
223
|
+
<Text style={{ flex: 1, fontSize: 14 }}>{localFile.name}</Text>
|
|
224
|
+
)}
|
|
225
|
+
{uploading && <Text style={{ fontSize: 12 }}>Uploading…</Text>}
|
|
226
|
+
</View>
|
|
227
|
+
)}
|
|
228
|
+
<UploadInput
|
|
229
|
+
id={name}
|
|
230
|
+
name={name}
|
|
231
|
+
accept={accept}
|
|
232
|
+
disabled={disabled || uploading}
|
|
233
|
+
required={required && !resourceId}
|
|
234
|
+
onChange={onSinglePick}
|
|
235
|
+
/>
|
|
236
|
+
{error && <Text style={{ fontSize: 12, color: 'var(--color-danger, crimson)' }}>{error}</Text>}
|
|
237
|
+
</View>
|
|
238
|
+
)
|
|
239
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { metadata as GetResource } from './get.action.js'
|
|
3
3
|
import { AsyncStatus, useSdk } from '@ossy/sdk-react'
|
|
4
|
-
import { Switch, View, DelayedRender, Button, Icon, PageSection,
|
|
4
|
+
import { Switch, View, DelayedRender, Button, Icon, PageSection, Text, Tags, ImageCard } from '@ossy/design-system'
|
|
5
5
|
import { useRouter } from '@ossy/router-react'
|
|
6
6
|
|
|
7
7
|
|
|
@@ -9,7 +9,7 @@ export const ResourceContentPage = (props) => {
|
|
|
9
9
|
const router = useRouter()
|
|
10
10
|
const resourceId = router.params.resourceId
|
|
11
11
|
const sdk = useSdk()
|
|
12
|
-
const { status, data: resource } = sdk.read(GetResource, {
|
|
12
|
+
const { status, data: resource } = sdk.read(GetResource, { resourceId }, { enabled: !!resourceId })
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
15
|
<View {...props} slot="content" style={{ flexDirection: 'column', wrap: 'no-wrap', height: '100%' }}>
|
|
@@ -87,7 +87,7 @@ export const ResourceContentPage = (props) => {
|
|
|
87
87
|
<View gap="l" className="resource-content-page__text-view">
|
|
88
88
|
|
|
89
89
|
<View gap="m">
|
|
90
|
-
<
|
|
90
|
+
<Text variant="heading-default" as="h1">{resource?.name}</Text>
|
|
91
91
|
<Text>{resource?.content?.description}</Text>
|
|
92
92
|
</View>
|
|
93
93
|
|
|
@@ -5,7 +5,7 @@ import { View, Text } from '@ossy/design-system'
|
|
|
5
5
|
|
|
6
6
|
export const ResourceDescription = ({ 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
|
const description = resource?.content?.description;
|
|
11
11
|
|
package/src/ResourceDetails.jsx
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { metadata as GetResource } from './get.action.js'
|
|
3
3
|
import { updateResourceAccess } from './resource.helpers.js'
|
|
4
|
-
import { GetWorkspace } from '@ossy/workspaces'
|
|
5
4
|
import { useSdk } from '@ossy/sdk-react'
|
|
5
|
+
import { useSchema } from './useSchemas.js'
|
|
6
6
|
import { Text, View } from '@ossy/design-system'
|
|
7
7
|
import { formatBytes } from './utils/format-bytes.js'
|
|
8
8
|
|
|
9
9
|
export const ResourceDetails = ({ resourceId }) => {
|
|
10
10
|
const sdk = useSdk()
|
|
11
|
-
const { data: resource } = sdk.read(GetResource, {
|
|
12
|
-
const
|
|
13
|
-
const template = workspace?.resourceTemplates?.find(t => t.id === resource?.type)
|
|
11
|
+
const { data: resource } = sdk.read(GetResource, { resourceId }, { enabled: !!resourceId })
|
|
12
|
+
const template = useSchema(resource?.type)
|
|
14
13
|
const [accessBusy, setAccessBusy] = useState(false)
|
|
15
14
|
const [accessError, setAccessError] = useState(null)
|
|
16
15
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { moveResource } from './resource.helpers.js'
|
|
3
3
|
import { useSdk } from '@ossy/sdk-react'
|
|
4
|
-
import { Button, View,
|
|
4
|
+
import { Button, View, Input, Text, useInputValue } from '@ossy/design-system'
|
|
5
5
|
|
|
6
6
|
export const ResourceDialogMove = ({
|
|
7
7
|
resourceId,
|
|
@@ -32,7 +32,7 @@ export const ResourceDialogMove = ({
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
<View>
|
|
35
|
-
<
|
|
35
|
+
<Text variant="heading-tertiary" as="h3">Move Resource</Text>
|
|
36
36
|
</View>
|
|
37
37
|
|
|
38
38
|
<View>
|
package/src/ResourceFactory.jsx
CHANGED
|
@@ -1,71 +1,18 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { metadata as GetResource } from './get.action.js'
|
|
3
3
|
import { AsyncStatus, useSdk } from '@ossy/sdk-react'
|
|
4
|
-
import {
|
|
5
|
-
import { Guide, Text, DelayedRender } from '@ossy/design-system'
|
|
6
|
-
import { DocumentEdit } from './DocumentEdit.jsx'
|
|
7
|
-
import { ImageResource } from './ImageResource.jsx'
|
|
8
|
-
import { AudioResource } from './AudioResource.jsx'
|
|
9
|
-
import { VideoResource } from './VideoResource.jsx'
|
|
10
|
-
import { PDFResource } from './PDFResource.jsx'
|
|
11
|
-
import { ResourceGenericView } from './ResourceGenericView.jsx'
|
|
12
|
-
import { GenericResourceDetail } from './GenericResourceDetail.jsx'
|
|
4
|
+
import { SchemaPresenter } from './SchemaPresenter.jsx'
|
|
13
5
|
|
|
14
6
|
export const ResourceFactory = ({
|
|
15
7
|
resourceId,
|
|
16
8
|
mode,
|
|
17
|
-
onClose
|
|
18
|
-
form
|
|
19
|
-
}) =>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<Guide
|
|
29
|
-
title="Not found"
|
|
30
|
-
text="We can't find the resource you are looking for"
|
|
31
|
-
style={{ padding: '40px 16px' }}
|
|
32
|
-
/>
|
|
33
|
-
)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if ([AsyncStatus.Loading, AsyncStatus.NotInitialized].includes(status)) {
|
|
37
|
-
return (
|
|
38
|
-
<DelayedRender>
|
|
39
|
-
<Text>loading...</Text>
|
|
40
|
-
</DelayedRender>
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (workspace?.resourceTemplates?.find(({ id }) => id === resource.type)) {
|
|
45
|
-
if (mode === 'View') {
|
|
46
|
-
return <GenericResourceDetail resourceId={resourceId} />
|
|
47
|
-
}
|
|
48
|
-
if (mode === 'Edit') {
|
|
49
|
-
return <DocumentEdit resourceId={resourceId} form={form} />
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (['image/jpeg', 'image/png'].includes(resource.type)) {
|
|
54
|
-
return <ImageResource resourceId={resourceId} onClose={_onClose} />
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (resource.type.startsWith('video/')) {
|
|
58
|
-
return <VideoResource resourceId={resourceId} onClose={_onClose} />
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (resource.type.startsWith('audio/')) {
|
|
62
|
-
return <AudioResource resourceId={resourceId} onClose={_onClose} />
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (resource.type === 'application/pdf') {
|
|
66
|
-
return <PDFResource resourceId={resourceId} onClose={_onClose} />
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return <ResourceGenericView resourceId={resourceId} onClose={_onClose} />
|
|
70
|
-
|
|
71
|
-
}
|
|
9
|
+
onClose,
|
|
10
|
+
form,
|
|
11
|
+
}) => (
|
|
12
|
+
<SchemaPresenter
|
|
13
|
+
resourceId={resourceId}
|
|
14
|
+
flow={mode}
|
|
15
|
+
form={form}
|
|
16
|
+
onClose={onClose}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
@@ -10,7 +10,7 @@ export const ResourceGenericView = ({
|
|
|
10
10
|
resourceId
|
|
11
11
|
}) => {
|
|
12
12
|
const sdk = useSdk()
|
|
13
|
-
const { data: resource } = sdk.read(GetResource, {
|
|
13
|
+
const { data: resource } = sdk.read(GetResource, { resourceId }, { enabled: !!resourceId })
|
|
14
14
|
|
|
15
15
|
return (
|
|
16
16
|
<View stack bordered>
|
package/src/ResourceList.jsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
import React, { useRef, useEffect } from 'react'
|
|
3
|
-
import {
|
|
4
|
-
import { GetWorkspace } from '@ossy/workspaces'
|
|
2
|
+
import React, { useRef, useEffect, useMemo, useCallback, memo } from 'react'
|
|
3
|
+
import { useSchemas } from './useSchemas.js'
|
|
5
4
|
import {
|
|
6
5
|
Dropdown,
|
|
7
6
|
DropZone,
|
|
@@ -11,29 +10,37 @@ import {
|
|
|
11
10
|
Text,
|
|
12
11
|
Button,
|
|
13
12
|
ContextMenu,
|
|
13
|
+
List,
|
|
14
14
|
} from '@ossy/design-system'
|
|
15
15
|
import { formatBytes } from './utils/format-bytes.js'
|
|
16
16
|
|
|
17
|
+
function useSchemaMap() {
|
|
18
|
+
const templates = useSchemas()
|
|
19
|
+
return useMemo(
|
|
20
|
+
() => templates.reduce((acc, template) => ({ ...acc, [template.id]: template }), {}),
|
|
21
|
+
[templates],
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
export const ResourceList = ({
|
|
18
26
|
resources = [],
|
|
19
27
|
onClick = () => {},
|
|
20
28
|
inlineFolder = null,
|
|
21
29
|
}) => {
|
|
30
|
+
const templateMap = useSchemaMap()
|
|
22
31
|
|
|
23
32
|
return (
|
|
24
|
-
|
|
33
|
+
<List>
|
|
25
34
|
{inlineFolder && <InlineFolderRow {...inlineFolder} />}
|
|
26
35
|
{resources.map(resource => (
|
|
27
36
|
<ResourceListItem
|
|
28
|
-
{...resource}
|
|
29
37
|
key={resource.id}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}}
|
|
38
|
+
resource={resource}
|
|
39
|
+
templateMap={templateMap}
|
|
40
|
+
onItemClick={onClick}
|
|
34
41
|
/>
|
|
35
42
|
))}
|
|
36
|
-
|
|
43
|
+
</List>
|
|
37
44
|
)
|
|
38
45
|
}
|
|
39
46
|
|
|
@@ -64,51 +71,47 @@ function InlineFolderRow({ name, onChange, onSave, onCancel }) {
|
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
return (
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
gap="m"
|
|
70
|
-
style={{
|
|
71
|
-
height: '56px',
|
|
72
|
-
flexShrink: 0,
|
|
73
|
-
borderBottom: '1px solid var(--separator-primary)',
|
|
74
|
-
}}
|
|
74
|
+
<List.Item
|
|
75
|
+
leading={<Icon size="s" name="folder" style={{ fill: 'hsl(0, 0%, 60%)' }} />}
|
|
75
76
|
>
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
</View>
|
|
98
|
-
</View>
|
|
77
|
+
<input
|
|
78
|
+
ref={inputRef}
|
|
79
|
+
value={name}
|
|
80
|
+
onChange={event => onChange(event.target.value)}
|
|
81
|
+
onKeyDown={handleKeyDown}
|
|
82
|
+
onBlur={handleBlur}
|
|
83
|
+
style={{
|
|
84
|
+
flexGrow: 1,
|
|
85
|
+
border: 'none',
|
|
86
|
+
background: 'transparent',
|
|
87
|
+
outline: 'none',
|
|
88
|
+
fontSize: 'var(--text-default-font-size, 16px)',
|
|
89
|
+
fontFamily: 'var(--text-default-font-family, sans-serif)',
|
|
90
|
+
fontWeight: 'var(--text-default-font-weight, 400)',
|
|
91
|
+
color: 'var(--text-default-color, CanvasText)',
|
|
92
|
+
padding: 0,
|
|
93
|
+
minWidth: 0,
|
|
94
|
+
width: '100%',
|
|
95
|
+
}}
|
|
96
|
+
/>
|
|
97
|
+
</List.Item>
|
|
99
98
|
)
|
|
100
99
|
}
|
|
101
100
|
|
|
102
|
-
function ResourceListItem({
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
dragData,
|
|
107
|
-
...resource
|
|
101
|
+
const ResourceListItem = memo(function ResourceListItem({
|
|
102
|
+
resource,
|
|
103
|
+
templateMap,
|
|
104
|
+
onItemClick,
|
|
108
105
|
}) {
|
|
109
|
-
|
|
106
|
+
const { onClick, href, onDrop, dragData, actions, name, type, content } = resource
|
|
107
|
+
|
|
108
|
+
const handleClick = useCallback(() => {
|
|
109
|
+
onItemClick(resource)
|
|
110
|
+
onClick?.(resource)
|
|
111
|
+
}, [onItemClick, onClick, resource])
|
|
112
|
+
|
|
110
113
|
let Container = ({ children }) => <>{children}</>
|
|
111
|
-
|
|
114
|
+
|
|
112
115
|
if (onDrop) {
|
|
113
116
|
Container = DropZone
|
|
114
117
|
} else if (dragData) {
|
|
@@ -117,77 +120,58 @@ function ResourceListItem({
|
|
|
117
120
|
|
|
118
121
|
return (
|
|
119
122
|
<Container onDrop={onDrop} dragData={dragData}>
|
|
120
|
-
<
|
|
121
|
-
|
|
123
|
+
<List.Item
|
|
124
|
+
href={href}
|
|
125
|
+
onClick={handleClick}
|
|
122
126
|
selectable
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
borderBottom: '1px solid var(--separator-primary)',
|
|
128
|
-
}}
|
|
127
|
+
leading={<ResourceIcon type={type} content={content} templateMap={templateMap} />}
|
|
128
|
+
meta={<Size content={content} />}
|
|
129
|
+
trailing={<RowActions actions={actions} />}
|
|
130
|
+
data-resource-id={resource.id}
|
|
129
131
|
>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
<Text>{typeof resource.name === 'string' && resource.name}</Text>
|
|
133
|
-
<View style={{ flexGrow: 1 }}/>
|
|
134
|
-
<Size {...resource} />
|
|
135
|
-
</View>
|
|
136
|
-
|
|
137
|
-
<View style={{ padding: '12px 8px 12px 0'}}>
|
|
138
|
-
<RowActions {...resource} />
|
|
139
|
-
</View>
|
|
140
|
-
</View>
|
|
132
|
+
{typeof name === 'string' ? name : ''}
|
|
133
|
+
</List.Item>
|
|
141
134
|
</Container>
|
|
142
135
|
)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (resource.content?.ContentLength) {
|
|
149
|
-
return (
|
|
150
|
-
<Text variant="small">
|
|
151
|
-
{formatBytes(resource.content.ContentLength)}
|
|
152
|
-
</Text>
|
|
153
|
-
)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return <></>
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function ResourceIcon({ ...resource }) {
|
|
160
|
-
const sdk = useSdk()
|
|
161
|
-
const { data: workspace } = sdk.read(GetWorkspace)
|
|
162
|
-
|
|
163
|
-
const resourceTemplates = (workspace.resourceTemplates || [])
|
|
164
|
-
.reduce((acc, curr) => ({ ...acc, [curr.id]: curr }), {})
|
|
165
|
-
|
|
166
|
-
if (resource?.type === 'directory') {
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const ResourceIcon = memo(function ResourceIcon({ type, content, templateMap }) {
|
|
139
|
+
if (type === 'directory') {
|
|
167
140
|
return (
|
|
168
141
|
<Icon size="s" name="folder" style={{ fill: 'hsl(0, 0%, 60%)'}} />
|
|
169
142
|
)
|
|
170
143
|
}
|
|
171
|
-
|
|
172
|
-
|
|
144
|
+
|
|
145
|
+
if (type?.startsWith('image')) {
|
|
173
146
|
return (
|
|
174
147
|
<Image
|
|
175
|
-
src={
|
|
176
|
-
placeholderSrc={
|
|
148
|
+
src={content?.sizes?.thumbnailSmall || content?.src}
|
|
149
|
+
placeholderSrc={content?.sizes?.['loader-square-blurred-after'] || content?.src}
|
|
177
150
|
style={{ width: '24px', height: '24px', borderRadius: '25%' }}
|
|
178
151
|
/>
|
|
179
152
|
)
|
|
180
153
|
}
|
|
181
|
-
|
|
154
|
+
|
|
182
155
|
return (
|
|
183
|
-
<Icon size="s" name={
|
|
156
|
+
<Icon size="s" name={templateMap[type]?.icon || 'file'} style={{ fill: 'hsl(0, 0%, 80%)'}} />
|
|
184
157
|
)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
function Size({ content }) {
|
|
161
|
+
if (content?.ContentLength) {
|
|
162
|
+
return (
|
|
163
|
+
<Text variant="small">
|
|
164
|
+
{formatBytes(content.ContentLength)}
|
|
165
|
+
</Text>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return null
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function RowActions({ actions }) {
|
|
173
|
+
if (!actions) return null
|
|
174
|
+
|
|
191
175
|
return (
|
|
192
176
|
<Dropdown trigger={<Button prefix="more-vertical-alt" variant="command" /> } >
|
|
193
177
|
<View inset="xs" surface="primary" roundness="s">
|
|
@@ -197,5 +181,4 @@ function ResourceListItem({
|
|
|
197
181
|
</View>
|
|
198
182
|
</Dropdown>
|
|
199
183
|
)
|
|
200
|
-
|
|
201
|
-
}
|
|
184
|
+
}
|