@ossy/resources 1.1.0 → 1.2.0
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 +2 -2
- package/src/AudioResource.jsx +27 -0
- package/src/CreateDirectory.jsx +98 -0
- package/src/CreateDirectory.page.jsx +24 -0
- package/src/CreateDocument.jsx +73 -0
- package/src/CreateDocument.page.jsx +24 -0
- package/src/Definition.js +19 -0
- package/src/DocumentEdit.jsx +48 -0
- package/src/DocumentView.jsx +32 -0
- package/src/ImageResource.jsx +22 -0
- package/src/PDFResource.jsx +33 -0
- package/src/ResourceContentPage.jsx +145 -0
- package/src/ResourceContentPage.stories.jsx +19 -0
- package/src/ResourceDescription.jsx +28 -0
- package/src/ResourceDetails.jsx +73 -0
- package/src/ResourceDialogMove.jsx +64 -0
- package/src/ResourceFactory.jsx +64 -0
- package/src/ResourceGenericView.jsx +32 -0
- package/src/ResourceList.jsx +199 -0
- package/src/ResourcePage.jsx +6 -0
- package/src/ResourcePanel.jsx +272 -0
- package/src/ResourceTags.jsx +40 -0
- package/src/ResourcesPage.jsx +66 -0
- package/src/Upload.jsx +177 -0
- package/src/Upload.page.jsx +24 -0
- package/src/UploadResources.jsx +31 -0
- package/src/VideoResource.jsx +24 -0
- package/src/index.js +25 -0
- package/src/useActivePath.jsx +23 -0
- package/src/useForm.js +61 -0
- package/src/utils/format-bytes.js +14 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react'
|
|
2
|
+
import { useWorkspace, useResource, useResourceTemplate } from '@ossy/sdk-react'
|
|
3
|
+
import {
|
|
4
|
+
Title,
|
|
5
|
+
Overlay,
|
|
6
|
+
Switch,
|
|
7
|
+
Stack,
|
|
8
|
+
Guide,
|
|
9
|
+
View,
|
|
10
|
+
Text,
|
|
11
|
+
Button,
|
|
12
|
+
InputTitle,
|
|
13
|
+
useInputValue,
|
|
14
|
+
} from '@ossy/design-system'
|
|
15
|
+
import { ResourceFactory } from './ResourceFactory.jsx'
|
|
16
|
+
import { useRouter } from '@ossy/router-react'
|
|
17
|
+
import { ResourceDetails } from './ResourceDetails.jsx'
|
|
18
|
+
import { ResourceTags } from './ResourceTags.jsx'
|
|
19
|
+
import { ResourceDescription } from './ResourceDescription.jsx'
|
|
20
|
+
import { useForm } from './useForm.js'
|
|
21
|
+
|
|
22
|
+
const ViewMode = {
|
|
23
|
+
Closed: 'Closed',
|
|
24
|
+
View: 'View',
|
|
25
|
+
Edit: 'Edit'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const Overlays = {
|
|
29
|
+
None: 'None',
|
|
30
|
+
Removedirectory : 'RemoveDirectory'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const DefaultViewModes = {
|
|
34
|
+
'panel-resource-content': ViewMode.View,
|
|
35
|
+
'panel-header': ViewMode.View,
|
|
36
|
+
'panel-resource-details': ViewMode.Closed,
|
|
37
|
+
'panel-resource-description': ViewMode.Closed,
|
|
38
|
+
'panel-resource-tags': ViewMode.Closed,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const ResourcePanel = ({
|
|
42
|
+
resourceId,
|
|
43
|
+
onClose: _onClose,
|
|
44
|
+
extraPanels = [],
|
|
45
|
+
}) => {
|
|
46
|
+
const router = useRouter()
|
|
47
|
+
const { workspace } = useWorkspace()
|
|
48
|
+
const [overlay, setOverlay] = useState(Overlays.None)
|
|
49
|
+
const [error] = useState()
|
|
50
|
+
const [resourceName, setResourceName] = useInputValue('')
|
|
51
|
+
const [panelViewModes, setPanelViewModes] = useState(DefaultViewModes)
|
|
52
|
+
|
|
53
|
+
const {
|
|
54
|
+
resource,
|
|
55
|
+
removeResource,
|
|
56
|
+
updateResourceContent,
|
|
57
|
+
renameResource
|
|
58
|
+
} = useResource(resourceId)
|
|
59
|
+
|
|
60
|
+
const form = useForm({ defaultData: resource.content })
|
|
61
|
+
|
|
62
|
+
const onCloseResource = () => {
|
|
63
|
+
if (!_onClose) return
|
|
64
|
+
setPanelViewModes(DefaultViewModes)
|
|
65
|
+
setResourceName('')
|
|
66
|
+
_onClose()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const onRenameResource = () => {
|
|
70
|
+
if (resourceName === resource.name) return
|
|
71
|
+
renameResource(resourceName)
|
|
72
|
+
.then(() => setPanelViewModes(x => ({ ...x, 'panel-header': ViewMode.View })))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const onUpdateResource = () => {
|
|
76
|
+
updateResourceContent(form.data)
|
|
77
|
+
.then(() => setPanelViewModes(x => ({ ...x, 'panel-resource-content': ViewMode.View })))
|
|
78
|
+
.catch(() => alert('Something went wrong!'))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const onRemoveResource = () => {
|
|
82
|
+
removeResource()
|
|
83
|
+
.then(onCloseResource)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const onPanelToggle = (panelId) => {
|
|
87
|
+
setPanelViewModes(prev => {
|
|
88
|
+
const currentMode = prev[panelId] || ViewMode.Closed
|
|
89
|
+
const newMode = currentMode === ViewMode.Closed ? ViewMode.View : ViewMode.Closed
|
|
90
|
+
return { ...prev, [panelId]: newMode }
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
setResourceName(resource.name)
|
|
96
|
+
}, [resource])
|
|
97
|
+
|
|
98
|
+
const panels = [
|
|
99
|
+
{
|
|
100
|
+
id: 'panel-resource-content',
|
|
101
|
+
label: 'View',
|
|
102
|
+
suffix: panelViewModes['panel-resource-content'] === ViewMode.Edit
|
|
103
|
+
? [
|
|
104
|
+
{ prefix: 'close', variant: 'command', onClick: () => setPanelViewModes(x => ({ ...x, 'panel-resource-content': ViewMode.View })) },
|
|
105
|
+
{ prefix: 'check', variant: 'command', onClick: onUpdateResource }
|
|
106
|
+
]
|
|
107
|
+
: [{ prefix: 'pen', variant: 'command', onClick: () => setPanelViewModes(x => ({ ...x, 'panel-resource-content': ViewMode.Edit })) }],
|
|
108
|
+
fill: true,
|
|
109
|
+
content: ResourceFactory
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'panel-resource-details',
|
|
113
|
+
label: 'Details',
|
|
114
|
+
content: ResourceDetails
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: 'panel-resource-description',
|
|
118
|
+
label: 'Description',
|
|
119
|
+
suffix: [{ prefix: 'copy', variant: 'command' }],
|
|
120
|
+
content: ResourceDescription
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 'panel-resource-tags',
|
|
124
|
+
label: 'Tags',
|
|
125
|
+
suffix: [{ prefix: 'copy', variant: 'command' }],
|
|
126
|
+
content: ResourceTags
|
|
127
|
+
},
|
|
128
|
+
...extraPanels,
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
if (!resourceId) {
|
|
132
|
+
return <></>
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<Stack surface="primary" bordered style={{ height: '100%', width: '50%' }}>
|
|
137
|
+
|
|
138
|
+
<style href="@design-system/scroll" precedence='low'>{`
|
|
139
|
+
[data-scroll-hide] {
|
|
140
|
+
-ms-overflow-style: none; /* For Internet Explorer and Edge */
|
|
141
|
+
scrollbar-width: none; /* For Firefox */
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.hide-scrollbar::-webkit-scrollbar {
|
|
145
|
+
display: none; /* For Chrome, Safari, and Opera */
|
|
146
|
+
}
|
|
147
|
+
`}</style>
|
|
148
|
+
|
|
149
|
+
{
|
|
150
|
+
!!error && (
|
|
151
|
+
<Stack.Item>
|
|
152
|
+
{error}
|
|
153
|
+
</Stack.Item>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
<Stack.Item>
|
|
158
|
+
|
|
159
|
+
<Stack horizontal style={{ height: '48px', alignItems: 'center', gap: '4px' }}>
|
|
160
|
+
|
|
161
|
+
<Switch on={panelViewModes['panel-header']}>
|
|
162
|
+
|
|
163
|
+
<Switch.Case match={[ViewMode.View]}>
|
|
164
|
+
|
|
165
|
+
<Stack.Item fill surface="primary" style={{ padding: '4px 8px' }}>
|
|
166
|
+
<Title as="h3" variant="tertiary">{resourceName}</Title>
|
|
167
|
+
</Stack.Item>
|
|
168
|
+
|
|
169
|
+
<Button prefix="trash-empty" variant="command-danger" onClick={onRemoveResource}/>
|
|
170
|
+
<Button prefix="pen" variant="command" onClick={() => setPanelViewModes(x => ({ ...x, 'panel-header': ViewMode.Edit }))} />
|
|
171
|
+
{ !!_onClose && (<Button prefix="close" variant="command" onClick={onCloseResource} /> ) }
|
|
172
|
+
|
|
173
|
+
</Switch.Case>
|
|
174
|
+
|
|
175
|
+
<Switch.Case match={[ViewMode.Edit]}>
|
|
176
|
+
|
|
177
|
+
<Stack.Item fill surface="primary" style={{ padding: '4px 8px' }}>
|
|
178
|
+
<InputTitle
|
|
179
|
+
id="document-name"
|
|
180
|
+
type="text"
|
|
181
|
+
style={{ display: 'block', width: '100%', padding: '16px 8px' }}
|
|
182
|
+
value={resourceName}
|
|
183
|
+
onChange={setResourceName}
|
|
184
|
+
onBlur={onRenameResource}
|
|
185
|
+
/>
|
|
186
|
+
</Stack.Item>
|
|
187
|
+
|
|
188
|
+
<Button prefix="close" variant="command" onClick={() => setPanelViewModes(x => ({ ...x, 'panel-header': ViewMode.View }))} />
|
|
189
|
+
<Button prefix="check" variant="command" onClick={onRenameResource}/>
|
|
190
|
+
|
|
191
|
+
</Switch.Case>
|
|
192
|
+
|
|
193
|
+
</Switch>
|
|
194
|
+
|
|
195
|
+
</Stack>
|
|
196
|
+
|
|
197
|
+
</Stack.Item>
|
|
198
|
+
|
|
199
|
+
<Stack.Item fill={true} data-scroll-hide style={{ display: 'flex', flexDirection: 'column', height: '100%', overflowY: 'auto' }}>
|
|
200
|
+
|
|
201
|
+
{panels.map(({ content: Content, ...panel }) => {
|
|
202
|
+
const isExpanded = [ViewMode.View, ViewMode.Edit].includes(panelViewModes[panel.id])
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<View surface="primary" roundness="xs" key={panel.id} style={{ flexShrink: 0, flexGrow: panel.fill && isExpanded ? 1 : undefined }}>
|
|
206
|
+
|
|
207
|
+
<View
|
|
208
|
+
layout="row"
|
|
209
|
+
inset="s"
|
|
210
|
+
gap="s"
|
|
211
|
+
alignItems="center"
|
|
212
|
+
style={{ height: '40px', borderBottom: isExpanded ? undefined : '1px solid var(--separator)' }}
|
|
213
|
+
>
|
|
214
|
+
|
|
215
|
+
<Button
|
|
216
|
+
variant="command"
|
|
217
|
+
prefix={isExpanded ? 'chevron-down' : 'chevron-right'}
|
|
218
|
+
onClick={() => onPanelToggle(panel.id)}
|
|
219
|
+
/>
|
|
220
|
+
|
|
221
|
+
{ panel.prefix && <Button {...panel.prefix} /> }
|
|
222
|
+
<Text variant="small" style={{ fontWeight: 'bold', flexGrow: 1 }}>{panel.label}</Text>
|
|
223
|
+
{ panel.suffix && panel.suffix.map(x => <Button {...x} />) }
|
|
224
|
+
|
|
225
|
+
</View>
|
|
226
|
+
|
|
227
|
+
{isExpanded && Content && (
|
|
228
|
+
<View style={{
|
|
229
|
+
flexGrow: 1,
|
|
230
|
+
overflowY: 'auto',
|
|
231
|
+
borderBottom: '1px solid var(--separator)',
|
|
232
|
+
padding: 'var(--space-s) var(--space-m) var(--space-m) calc(var(--space-l) + var(--space-m))' }
|
|
233
|
+
}>
|
|
234
|
+
<Content resourceId={resourceId} mode={panelViewModes[panel.id]} form={form} />
|
|
235
|
+
</View>
|
|
236
|
+
)}
|
|
237
|
+
|
|
238
|
+
</View>
|
|
239
|
+
)
|
|
240
|
+
})}
|
|
241
|
+
|
|
242
|
+
</Stack.Item>
|
|
243
|
+
|
|
244
|
+
{
|
|
245
|
+
overlay === Overlays.RemoveDirectory && (
|
|
246
|
+
<Overlay isVisible={true} onClose={() => setOverlay(Overlays.None)}>
|
|
247
|
+
<View layout="off-center-s" style={{ height: '100%' }}>
|
|
248
|
+
<View slot="content">
|
|
249
|
+
<View surface="primary" roundness="s">
|
|
250
|
+
<View surface="primary" inset="l" roundness="s">
|
|
251
|
+
<Guide
|
|
252
|
+
title="Remove directory"
|
|
253
|
+
text="Are you sure you want to remove this directory and everything in it?"
|
|
254
|
+
actions={[
|
|
255
|
+
{ label: 'Cancel', variant: "command", onClick: () => setOverlay(Overlays.None) },
|
|
256
|
+
{ label: 'Remove', variant: 'command-danger', onClick: () => {
|
|
257
|
+
removeResource(resourceId)
|
|
258
|
+
setOverlay(Overlays.None)
|
|
259
|
+
}}
|
|
260
|
+
]}
|
|
261
|
+
/>
|
|
262
|
+
</View>
|
|
263
|
+
</View>
|
|
264
|
+
</View>
|
|
265
|
+
</View>
|
|
266
|
+
</Overlay>
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
</Stack>
|
|
271
|
+
)
|
|
272
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useResource, useResourceTemplate } from '@ossy/sdk-react'
|
|
3
|
+
import { View, Tags, Text } from '@ossy/design-system'
|
|
4
|
+
|
|
5
|
+
export const ResourceTags = ({ resourceId }) => {
|
|
6
|
+
const { resource } = useResource(resourceId)
|
|
7
|
+
const template = useResourceTemplate(resource.type)
|
|
8
|
+
|
|
9
|
+
let resourceContentTags = resource?.content?.Tags || []
|
|
10
|
+
|
|
11
|
+
if (typeof resourceContentTags === 'string') {
|
|
12
|
+
resourceContentTags = resourceContentTags.split(',').map(tag => tag.trim())
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let resourceContenttags = resource?.content?.tags || []
|
|
16
|
+
|
|
17
|
+
if (typeof resourceContenttags === 'string') {
|
|
18
|
+
resourceContenttags = resourceContenttags.split(',').map(tag => tag.trim())
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
const resourceTags = resource?.tags || []
|
|
23
|
+
const allTags = [...resourceContentTags, ...resourceContenttags, ...resourceTags].sort()
|
|
24
|
+
|
|
25
|
+
if (allTags.length === 0) {
|
|
26
|
+
return (
|
|
27
|
+
<View>
|
|
28
|
+
<Text variant="small">
|
|
29
|
+
No tags available.
|
|
30
|
+
</Text>
|
|
31
|
+
</View>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<View style={{ maxWidth: '450px' }}>
|
|
37
|
+
<Tags size="s" gap="s" tags={allTags} />
|
|
38
|
+
</View>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { SDK } from '@ossy/sdk'
|
|
3
|
+
import { useResources, AsyncStatus, WorkspaceProvider } from '@ossy/sdk-react'
|
|
4
|
+
import { Title, Text, Switch, View, ImageCard, Tags } from '@ossy/design-system'
|
|
5
|
+
import { useRouter } from '@ossy/router-react'
|
|
6
|
+
import { Definition } from './Definition.js'
|
|
7
|
+
|
|
8
|
+
const sdk = SDK.of({
|
|
9
|
+
/** Ossy.se workspaceID - used to fetch free resources */
|
|
10
|
+
workspaceId: "36zDqF0TKZZ5KkJdyg7NH"
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const freeResourcesGridStyles = {
|
|
14
|
+
height: '100%',
|
|
15
|
+
columns: '6 200px',
|
|
16
|
+
columnGap: '16px'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const ResourcesPage= () => {
|
|
20
|
+
const router = useRouter()
|
|
21
|
+
const { status, resources } = useResources('/publications/images/')
|
|
22
|
+
const tags = resources.flatMap(resource => resource?.content?.tags || [])
|
|
23
|
+
const tagsCount = tags.reduce((acc, t) => ({ ...acc, [t]: (acc?.[t] ?? 0) + 1 }), {})
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Switch on={status}>
|
|
27
|
+
<Switch.Case match={[AsyncStatus.Success]}>
|
|
28
|
+
|
|
29
|
+
<main style={{
|
|
30
|
+
height: '100%',
|
|
31
|
+
gridArea: 'main',
|
|
32
|
+
padding: '32px 16px 0'
|
|
33
|
+
}}>
|
|
34
|
+
<View gap="l" style={{ margin: '0 auto' }}>
|
|
35
|
+
|
|
36
|
+
<View gap="m">
|
|
37
|
+
<Title>{Definition.title}</Title>
|
|
38
|
+
<Text style={{ maxWidth: '900px'}}>{Definition.description}</Text>
|
|
39
|
+
</View>
|
|
40
|
+
|
|
41
|
+
<WorkspaceProvider sdk={sdk}>
|
|
42
|
+
<section style={freeResourcesGridStyles}>
|
|
43
|
+
{ resources.map(resource => (
|
|
44
|
+
<ImageCard
|
|
45
|
+
href={router.getHref({ id: "@resource", params: { resourceId: resource?.id } })}
|
|
46
|
+
src={resource?.content?.sizes?.galleryMedium || resource?.content?.src}
|
|
47
|
+
placeholderSrc={resource?.content?.sizes?.['loader-square-blurred-after'] || resource?.content?.src}
|
|
48
|
+
style={{ pageBreakInside: 'avoid', cursor: 'pointer' }}
|
|
49
|
+
key={resources.id}
|
|
50
|
+
/>
|
|
51
|
+
))}
|
|
52
|
+
</section>
|
|
53
|
+
</WorkspaceProvider>
|
|
54
|
+
|
|
55
|
+
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--space-xxs)', marginTop: '24px' }}>
|
|
56
|
+
<Tags tags={Object.entries(tagsCount).map(([tag, count]) => `${count} ${tag}`)} />
|
|
57
|
+
</div>
|
|
58
|
+
</View>
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
</main>
|
|
62
|
+
|
|
63
|
+
</Switch.Case>
|
|
64
|
+
</Switch>
|
|
65
|
+
)
|
|
66
|
+
}
|
package/src/Upload.jsx
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react'
|
|
2
|
+
import { Button, View, Text, Upload as _Upload, Icon2 } from '@ossy/design-system'
|
|
3
|
+
import { useRouter } from '@ossy/router-react'
|
|
4
|
+
|
|
5
|
+
const FlowStage = {
|
|
6
|
+
Error: 'Error',
|
|
7
|
+
Preview: 'Preview',
|
|
8
|
+
Uploading: 'Uploading',
|
|
9
|
+
Done: 'Done'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const Upload = ({
|
|
13
|
+
onUpload = () => Promise.reject(),
|
|
14
|
+
onCancel: _onCancel = () => {},
|
|
15
|
+
onDone,
|
|
16
|
+
}) => {
|
|
17
|
+
const router = useRouter()
|
|
18
|
+
const location = router.searchParams.location
|
|
19
|
+
const finishUpload = onDone ?? _onCancel
|
|
20
|
+
const [files, setFilesMetadata] = useState([])
|
|
21
|
+
const flowStage = getFlowStage(files)
|
|
22
|
+
|
|
23
|
+
const uploadFiles = () => {
|
|
24
|
+
setFilesMetadata(files => files.map(x => ({ ...x, status: 'uploading' })))
|
|
25
|
+
|
|
26
|
+
const uploadRequests = files.map((file) =>
|
|
27
|
+
onUpload(file.file)
|
|
28
|
+
.then(() => ({...file, status: 'uploaded' }))
|
|
29
|
+
.catch(() => ({...file, status: 'error' }))
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return Promise.all(uploadRequests)
|
|
33
|
+
.then(setFilesMetadata)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const remove = (file) => {
|
|
37
|
+
setFilesMetadata(files => files.filter(x => x.file !== file))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const onUserInput = (e) => {
|
|
41
|
+
setFilesMetadata(Array.from(e.target.files).map(file => ({ file, status: 'preview' })))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const onCancel = () => {
|
|
45
|
+
setFilesMetadata([])
|
|
46
|
+
_onCancel()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<View gap="s" style={{ height: '100%' }}>
|
|
51
|
+
{
|
|
52
|
+
files.length > 0 && (
|
|
53
|
+
<View gap="s" style={{ flexGrow: '1', overflow: 'auto', padding: 'var(--space-m) 0' }}>
|
|
54
|
+
{
|
|
55
|
+
files.map(({ status, file }) => (
|
|
56
|
+
<View layout="row" gap="m" alignItems="center">
|
|
57
|
+
<UploadImgPreview file={file} />
|
|
58
|
+
<Text style={{ margin: '0', lineHeight: '1', textWrap: 'nowrap', textOverflow: 'ellipsis', flexGrow: '1' }}>{file?.name || 'Untitled'}</Text>
|
|
59
|
+
<View layout="row" gap="xs">
|
|
60
|
+
<UploadStatus status={status} size="8px" />
|
|
61
|
+
<Button variant="command-danger" prefix="close" size="s" onClick={() => remove(file)} />
|
|
62
|
+
</View>
|
|
63
|
+
</View>
|
|
64
|
+
))
|
|
65
|
+
}
|
|
66
|
+
</View>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
{files.length === 0 && (
|
|
71
|
+
<_Upload
|
|
72
|
+
type="file"
|
|
73
|
+
multiple
|
|
74
|
+
onChange={onUserInput}
|
|
75
|
+
style={{ flexGrow: '1', borderRadius: 'var(--space-s)' }}
|
|
76
|
+
/>
|
|
77
|
+
)}
|
|
78
|
+
|
|
79
|
+
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 'var(--space-m)' }}>
|
|
80
|
+
<Button
|
|
81
|
+
id="cancel"
|
|
82
|
+
onClick={onCancel}
|
|
83
|
+
>Cancel
|
|
84
|
+
</Button>
|
|
85
|
+
|
|
86
|
+
{
|
|
87
|
+
flowStage === FlowStage.Preview && (
|
|
88
|
+
<Button
|
|
89
|
+
id="upload"
|
|
90
|
+
variant="cta"
|
|
91
|
+
onClick={uploadFiles}
|
|
92
|
+
>Upload
|
|
93
|
+
</Button>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
{
|
|
98
|
+
flowStage === FlowStage.Uploading && (
|
|
99
|
+
<Button
|
|
100
|
+
id="upload"
|
|
101
|
+
variant="cta"
|
|
102
|
+
disabled
|
|
103
|
+
>Upploading...
|
|
104
|
+
</Button>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
{
|
|
109
|
+
flowStage === FlowStage.Error && (
|
|
110
|
+
<Button
|
|
111
|
+
id="upload"
|
|
112
|
+
variant="cta"
|
|
113
|
+
onClick={uploadFiles}
|
|
114
|
+
>Retry failed uploads
|
|
115
|
+
</Button>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
{
|
|
120
|
+
flowStage === FlowStage.Done && (
|
|
121
|
+
<Button
|
|
122
|
+
id="upload"
|
|
123
|
+
variant="cta"
|
|
124
|
+
onClick={finishUpload}
|
|
125
|
+
>Done
|
|
126
|
+
</Button>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
</div>
|
|
130
|
+
</View>
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
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
|
+
function UploadStatus({ status }) {
|
|
157
|
+
if (status === 'preview') return <></>
|
|
158
|
+
if (status === 'uploading') return <Icon2 name="spinner" size="s" animation="rotate" />
|
|
159
|
+
if (status === 'uploaded') return <Icon2 name="check" size="s" />
|
|
160
|
+
if (status === 'error') return <Icon2 name="close" size="s" />
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function getFlowStage(files = []) {
|
|
164
|
+
|
|
165
|
+
if (files.length === 0) return FlowStage.Preview
|
|
166
|
+
|
|
167
|
+
const didSomeUploadsFail = files.some(({ status }) => status === 'error')
|
|
168
|
+
if (didSomeUploadsFail) return FlowStage.Error
|
|
169
|
+
|
|
170
|
+
const areSomeUploadsInProgress = files.some(({ status }) => status === 'uploading')
|
|
171
|
+
if (areSomeUploadsInProgress) return FlowStage.Uploading
|
|
172
|
+
|
|
173
|
+
const areAllFilesUploadedSuccessfully = files.every(({ status }) => status === 'uploaded')
|
|
174
|
+
if (areAllFilesUploadedSuccessfully) return FlowStage.Done
|
|
175
|
+
|
|
176
|
+
return FlowStage.Preview
|
|
177
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { UploadResources } from './UploadResources.jsx'
|
|
3
|
+
import { View } from '@ossy/design-system'
|
|
4
|
+
|
|
5
|
+
export const metadata = {
|
|
6
|
+
id: 'upload-media',
|
|
7
|
+
path: {
|
|
8
|
+
sv: '/resources/ladda-upp',
|
|
9
|
+
en: '/resources/upload',
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export const UploadPage = () => {
|
|
15
|
+
return (
|
|
16
|
+
<View layout="off-center-s" style={{ height: '100%' }}>
|
|
17
|
+
<View slot="content" surface="primary" roundness="m" inset="m">
|
|
18
|
+
<UploadResources/>
|
|
19
|
+
</View>
|
|
20
|
+
</View>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default UploadPage
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useResources } from '@ossy/sdk-react'
|
|
3
|
+
import { Title, View, Text, Upload as _Upload } from '@ossy/design-system'
|
|
4
|
+
import { useRouter } from '@ossy/router-react'
|
|
5
|
+
import { Upload } from './Upload.jsx'
|
|
6
|
+
|
|
7
|
+
export const UploadResources = () => {
|
|
8
|
+
const router = useRouter()
|
|
9
|
+
const location = router.searchParams.location
|
|
10
|
+
const { uploadFile } = useResources()
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<View gap="s" style={{ height: '100%' }}>
|
|
14
|
+
|
|
15
|
+
<View gap="s">
|
|
16
|
+
<Title variant="tertiary" style={{ marginBottom: '0' }}>
|
|
17
|
+
Upload
|
|
18
|
+
</Title>
|
|
19
|
+
<View layout="row" gap="s">
|
|
20
|
+
<Text variant="small" >Location: {location}</Text>
|
|
21
|
+
</View>
|
|
22
|
+
</View>
|
|
23
|
+
|
|
24
|
+
<Upload
|
|
25
|
+
onUpload={(file) => uploadFile(location, file)}
|
|
26
|
+
onCancel={() => router.navigate(`@storage/home?location=${location}`)}
|
|
27
|
+
onDone={() => router.navigate(`@storage/home?location=${location}`)}
|
|
28
|
+
/>
|
|
29
|
+
</View>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { useResource } from '@ossy/sdk-react'
|
|
3
|
+
import { Stack } from '@ossy/design-system'
|
|
4
|
+
|
|
5
|
+
export const VideoResource = ({
|
|
6
|
+
resourceId
|
|
7
|
+
}) => {
|
|
8
|
+
const { resource } = useResource(resourceId)
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<Stack bordered>
|
|
12
|
+
<Stack.Item fill style={{ padding: '16px 8px' }}>
|
|
13
|
+
|
|
14
|
+
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
|
15
|
+
<video
|
|
16
|
+
controls
|
|
17
|
+
src={resource.content.src}
|
|
18
|
+
style={{ width: 'auto', height: '400px', margin: 'var(--space-l) auto' }}
|
|
19
|
+
/>
|
|
20
|
+
</div>
|
|
21
|
+
</Stack.Item>
|
|
22
|
+
</Stack>
|
|
23
|
+
)
|
|
24
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,2 +1,27 @@
|
|
|
1
1
|
export * from './resource.aggregate.js'
|
|
2
2
|
export * from './resources.events.js'
|
|
3
|
+
|
|
4
|
+
export * from './AudioResource.jsx'
|
|
5
|
+
export * from './CreateDirectory.jsx'
|
|
6
|
+
export * from './CreateDocument.jsx'
|
|
7
|
+
export * from './Definition.js'
|
|
8
|
+
export * from './DocumentEdit.jsx'
|
|
9
|
+
export * from './DocumentView.jsx'
|
|
10
|
+
export * from './ImageResource.jsx'
|
|
11
|
+
export * from './PDFResource.jsx'
|
|
12
|
+
export * from './ResourceContentPage.jsx'
|
|
13
|
+
export * from './ResourceDescription.jsx'
|
|
14
|
+
export * from './ResourceDetails.jsx'
|
|
15
|
+
export * from './ResourceDialogMove.jsx'
|
|
16
|
+
export * from './ResourceFactory.jsx'
|
|
17
|
+
export * from './ResourceGenericView.jsx'
|
|
18
|
+
export * from './ResourceList.jsx'
|
|
19
|
+
export * from './ResourcePage.jsx'
|
|
20
|
+
export * from './ResourcePanel.jsx'
|
|
21
|
+
export * from './ResourceTags.jsx'
|
|
22
|
+
export * from './ResourcesPage.jsx'
|
|
23
|
+
export * from './Upload.jsx'
|
|
24
|
+
export * from './UploadResources.jsx'
|
|
25
|
+
export * from './VideoResource.jsx'
|
|
26
|
+
export * from './useActivePath.jsx'
|
|
27
|
+
export * from './useForm.js'
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
unless,
|
|
3
|
+
startsWith,
|
|
4
|
+
o,
|
|
5
|
+
endsWith
|
|
6
|
+
} from 'ramda'
|
|
7
|
+
import { useRouter } from '@ossy/router-react'
|
|
8
|
+
import React, { useEffect } from 'react'
|
|
9
|
+
|
|
10
|
+
const prependSlash = unless(startsWith('/'), path => `/${path}`)
|
|
11
|
+
const appendSlash = unless(endsWith('/'), path => `${path}/`)
|
|
12
|
+
const addSlashes = o(prependSlash, appendSlash)
|
|
13
|
+
|
|
14
|
+
export const useActivePath = () => {
|
|
15
|
+
const router = useRouter()
|
|
16
|
+
const activePath = addSlashes(router.searchParams.location || '/')
|
|
17
|
+
|
|
18
|
+
// useEffect(() => {
|
|
19
|
+
// console.log('useActivePath', activePath)
|
|
20
|
+
// }, [activePath])
|
|
21
|
+
|
|
22
|
+
return activePath
|
|
23
|
+
}
|