@ossy/resources 3.10.0 → 3.11.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.
Files changed (86) hide show
  1. package/package.json +4 -4
  2. package/src/CreateDirectory.jsx +47 -53
  3. package/src/CreateDocument.jsx +1 -0
  4. package/src/GenericResourceForm.jsx +49 -31
  5. package/src/ResourceDialogMove.jsx +99 -50
  6. package/src/ResourceGrid.jsx +36 -84
  7. package/src/ResourceList.jsx +10 -1
  8. package/src/ResourcePanel.jsx +96 -17
  9. package/src/ResourceShareDialog.jsx +50 -12
  10. package/src/Upload.jsx +25 -8
  11. package/src/batch-delete.flow.js +81 -0
  12. package/src/batch-download.flow.js +69 -0
  13. package/src/cancel-upload.action.js +4 -0
  14. package/src/cancel-upload.flow.js +40 -0
  15. package/src/close-share.action.js +4 -0
  16. package/src/confirm-upload.action.js +4 -0
  17. package/src/copy-share-link.action.js +4 -0
  18. package/src/create-directory.action.js +5 -0
  19. package/src/create-directory.flow.js +30 -0
  20. package/src/create-directory.form.js +5 -0
  21. package/src/create-directory.schema.js +9 -0
  22. package/src/create-document.flow.js +36 -0
  23. package/src/create-document.form.js +5 -0
  24. package/src/create-document.schema.js +11 -0
  25. package/src/create.action.js +5 -1
  26. package/src/delete-directory.flow.js +39 -0
  27. package/src/delete-file.flow.js +32 -0
  28. package/src/done-selecting.action.js +5 -0
  29. package/src/download-file.flow.js +31 -0
  30. package/src/download-resource.action.js +4 -0
  31. package/src/download-selected.action.js +4 -0
  32. package/src/en.translations.json +18 -0
  33. package/src/finish-upload.action.js +4 -0
  34. package/src/index.js +42 -0
  35. package/src/list.task.js +17 -6
  36. package/src/list.task.spec.js +81 -0
  37. package/src/make-private-resource.flow.js +72 -0
  38. package/src/make-private.action.js +4 -0
  39. package/src/make-public.action.js +4 -0
  40. package/src/move-file.flow.js +78 -0
  41. package/src/move.form.js +5 -0
  42. package/src/move.schema.js +9 -0
  43. package/src/open-delete-directory.action.js +7 -0
  44. package/src/open-move.action.js +4 -0
  45. package/src/open-remove-selected.action.js +5 -0
  46. package/src/open-rename.action.js +4 -0
  47. package/src/open-share.action.js +4 -0
  48. package/src/open-sort.action.js +5 -0
  49. package/src/open-view.action.js +5 -0
  50. package/src/rename-file.flow.js +43 -0
  51. package/src/rename.form.js +5 -0
  52. package/src/rename.schema.js +9 -0
  53. package/src/resource-list-query.js +182 -0
  54. package/src/resource-list-query.spec.js +75 -0
  55. package/src/resource-selection.js +25 -0
  56. package/src/resource-selection.spec.js +40 -0
  57. package/src/resource.helpers.js +8 -1
  58. package/src/resources-list-options.js +169 -0
  59. package/src/resources-list-options.spec.js +141 -0
  60. package/src/resources.action-media.js +6 -0
  61. package/src/resources.queries.js +91 -6
  62. package/src/search-payload.js +10 -0
  63. package/src/search-payload.spec.js +20 -0
  64. package/src/search.task.js +2 -1
  65. package/src/share-resource.flow.js +59 -0
  66. package/src/sort-ascending.action.js +5 -0
  67. package/src/sort-by-created.action.js +5 -0
  68. package/src/sort-by-last-updated.action.js +5 -0
  69. package/src/sort-by-name.action.js +5 -0
  70. package/src/sort-by-size.action.js +5 -0
  71. package/src/sort-by-type.action.js +5 -0
  72. package/src/sort-descending.action.js +5 -0
  73. package/src/sort-storage-created.flow.js +87 -0
  74. package/src/sort-storage-last-updated.flow.js +119 -0
  75. package/src/sort-storage-name-asc.flow.js +34 -0
  76. package/src/sort-storage-size-desc.flow.js +34 -0
  77. package/src/sort-storage-size.flow.js +87 -0
  78. package/src/sort-storage-type.flow.js +83 -0
  79. package/src/sort-storage.flow.js +86 -0
  80. package/src/sv.translations.json +18 -0
  81. package/src/toggle-selection-mode.action.js +4 -0
  82. package/src/upload-file.flow.js +44 -0
  83. package/src/useResourceList.js +94 -0
  84. package/src/view-grid.action.js +5 -0
  85. package/src/view-list.action.js +5 -0
  86. package/src/view-storage.flow.js +49 -0
@@ -16,6 +16,7 @@ import {
16
16
  Button,
17
17
  InputTitle,
18
18
  Alert,
19
+ Form,
19
20
  useInputValue,
20
21
  useLocale,
21
22
  Dropdown,
@@ -35,6 +36,15 @@ import {
35
36
  resourceAccessIcon,
36
37
  resourceAccessOf,
37
38
  } from './resource-share.js'
39
+ import { metadata as OpenShare } from './open-share.action.js'
40
+ import { metadata as OpenRename } from './open-rename.action.js'
41
+ import { metadata as OpenMove } from './open-move.action.js'
42
+ import { metadata as DownloadResource } from './download-resource.action.js'
43
+ import { metadata as DeleteResource } from './delete.action.js'
44
+ import { metadata as UpdateResourceName } from './update-name.action.js'
45
+ import { metadata as RenameForm } from './rename.form.js'
46
+ import renameTemplate from './rename.schema.js'
47
+ import { ResourceDialogMove } from './ResourceDialogMove.jsx'
38
48
 
39
49
  const ViewMode = {
40
50
  Closed: 'Closed',
@@ -63,6 +73,7 @@ export const ResourcePanel = memo(function ResourcePanel ({
63
73
  const [accessBusy, setAccessBusy] = useState(false)
64
74
  const [accessError, setAccessError] = useState(null)
65
75
  const [shareOpen, setShareOpen] = useState(false)
76
+ const [moveOpen, setMoveOpen] = useState(false)
66
77
 
67
78
  const { data: resource = {} } = sdk.read(GetResource, { resourceId }, { enabled: !!resourceId })
68
79
  const template = useSchema(resource?.type)
@@ -89,12 +100,21 @@ export const ResourcePanel = memo(function ResourcePanel ({
89
100
  _onClose()
90
101
  }
91
102
 
92
- const onRenameResource = () => {
93
- if (resourceName === resource.name) return
94
- renameResource(sdk, resourceId, resourceName)
103
+ const commitRename = (nextName) => {
104
+ const name = String(nextName ?? '').trim()
105
+ if (!name || name === resource.name) {
106
+ setPanelViewModes(x => ({ ...x, 'panel-header': ViewMode.View }))
107
+ return
108
+ }
109
+ setResourceName(name)
110
+ renameResource(sdk, resourceId, name)
95
111
  .then(() => setPanelViewModes(x => ({ ...x, 'panel-header': ViewMode.View })))
96
112
  }
97
113
 
114
+ const onRenameSubmit = (data) => {
115
+ commitRename(data?.name)
116
+ }
117
+
98
118
  const onUpdateResource = () => {
99
119
  if (template) {
100
120
  const result = validateContent(engine, template, form.data)
@@ -242,6 +262,7 @@ export const ResourcePanel = memo(function ResourcePanel ({
242
262
  )}
243
263
  {shareable && (
244
264
  <Button
265
+ {...OpenShare}
245
266
  prefix="share"
246
267
  variant="command"
247
268
  aria-label={t('resources.share.title')}
@@ -250,6 +271,7 @@ export const ResourcePanel = memo(function ResourcePanel ({
250
271
  )}
251
272
  {downloadHref && (
252
273
  <Button
274
+ {...DownloadResource}
253
275
  prefix="software-download"
254
276
  variant="command"
255
277
  href={downloadHref}
@@ -257,27 +279,78 @@ export const ResourcePanel = memo(function ResourcePanel ({
257
279
  aria-label={t('resources.panel.download')}
258
280
  />
259
281
  )}
260
- <Button prefix="trash-empty" variant="command-danger" onClick={onRemoveResource}/>
261
- <Button prefix="pen" variant="command" onClick={() => setPanelViewModes(x => ({ ...x, 'panel-header': ViewMode.Edit }))} />
282
+ <Button
283
+ {...OpenMove}
284
+ prefix="details-more"
285
+ variant="command"
286
+ label={undefined}
287
+ aria-label={t('resources.panel.move')}
288
+ onClick={() => setMoveOpen(true)}
289
+ />
290
+ <Button
291
+ {...DeleteResource}
292
+ prefix="trash-empty"
293
+ variant="command-danger"
294
+ aria-label={t('resources.panel.delete')}
295
+ onClick={onRemoveResource}
296
+ />
297
+ <Button
298
+ {...OpenRename}
299
+ prefix="pen"
300
+ variant="command"
301
+ label={undefined}
302
+ aria-label={t('resources.panel.rename')}
303
+ onClick={() => setPanelViewModes(x => ({ ...x, 'panel-header': ViewMode.Edit }))}
304
+ />
262
305
  { !!_onClose && (<Button prefix="close" variant="command" onClick={onCloseResource} /> ) }
263
306
 
264
307
  </Switch.Case>
265
308
 
266
309
  <Switch.Case match={[ViewMode.Edit]}>
267
310
 
268
- <View.Item fill surface="primary" style={{ padding: '4px 8px' }}>
269
- <InputTitle
270
- id="document-name"
271
- type="text"
272
- style={{ display: 'block', width: '100%' }}
273
- value={resourceName}
274
- onChange={setResourceName}
275
- onBlur={onRenameResource}
311
+ <Form
312
+ id={RenameForm.id}
313
+ schemaId={RenameForm.schemaId}
314
+ fields={renameTemplate.fields}
315
+ data={{ name: resourceName }}
316
+ onChange={({ value }) => setResourceName(String(value ?? ''))}
317
+ onSubmit={onRenameSubmit}
318
+ stack
319
+ horizontal
320
+ style={{ height: '48px', alignItems: 'center', gap: '4px', flex: 1, minWidth: 0, width: '100%' }}
321
+ >
322
+ <View.Item fill surface="primary" style={{ padding: '4px 8px' }}>
323
+ <InputTitle
324
+ id="document-name"
325
+ name="name"
326
+ type="text"
327
+ required
328
+ style={{ display: 'block', width: '100%' }}
329
+ value={resourceName}
330
+ onChange={setResourceName}
331
+ aria-label={t('resources.panel.rename')}
332
+ />
333
+ </View.Item>
334
+
335
+ <Button
336
+ prefix="close"
337
+ variant="command"
338
+ type="button"
339
+ aria-label={t('design-system.cancel')}
340
+ onClick={() => {
341
+ setResourceName(resource.name || '')
342
+ setPanelViewModes(x => ({ ...x, 'panel-header': ViewMode.View }))
343
+ }}
276
344
  />
277
- </View.Item>
278
-
279
- <Button prefix="close" variant="command" onClick={() => setPanelViewModes(x => ({ ...x, 'panel-header': ViewMode.View }))} />
280
- <Button prefix="check" variant="command" onClick={onRenameResource}/>
345
+ <Button
346
+ {...UpdateResourceName}
347
+ prefix="check"
348
+ variant="command"
349
+ type="submit"
350
+ label={undefined}
351
+ aria-label={t('@ossy/resources/actions/update-name.label')}
352
+ />
353
+ </Form>
281
354
 
282
355
  </Switch.Case>
283
356
 
@@ -344,6 +417,12 @@ export const ResourcePanel = memo(function ResourcePanel ({
344
417
  onClose={() => setShareOpen(false)}
345
418
  />
346
419
 
420
+ <ResourceDialogMove
421
+ resourceId={resourceId}
422
+ isVisible={moveOpen}
423
+ onClose={() => setMoveOpen(false)}
424
+ />
425
+
347
426
  </View>
348
427
  )
349
428
  })
@@ -3,6 +3,10 @@ import React, { useEffect, useState } from 'react'
3
3
  import { Overlay, View, Text, Button, Guide, useLocale } from '@ossy/design-system'
4
4
  import { AsyncStatus, useSdk } from '@ossy/sdk-react'
5
5
  import { metadata as GetResource } from './get.action.js'
6
+ import { metadata as MakePublic } from './make-public.action.js'
7
+ import { metadata as MakePrivate } from './make-private.action.js'
8
+ import { metadata as CopyShareLink } from './copy-share-link.action.js'
9
+ import { metadata as CloseShare } from './close-share.action.js'
6
10
  import { updateResourceAccess } from './resource.helpers.js'
7
11
  import {
8
12
  getResourceShareUrl,
@@ -80,12 +84,30 @@ export function ResourceShareDialog ({
80
84
  }
81
85
  }
82
86
 
87
+ const onMakePrivate = async () => {
88
+ if (!resourceId || busy) return
89
+ setError(null)
90
+ setBusy(true)
91
+ try {
92
+ await updateResourceAccess(sdk, { id: resourceId, access: 'restricted' })
93
+ setMadePublic(false)
94
+ } catch {
95
+ setError(t('resources.share.publishError'))
96
+ } finally {
97
+ setBusy(false)
98
+ }
99
+ }
100
+
83
101
  if (!isVisible || !resourceId) return null
84
102
 
85
103
  return (
86
104
  <Overlay isVisible={true} onClose={onClose}>
87
105
  <View layout="off-center-s" style={{ height: '100%' }}>
88
- <View data-region="content" style={{ width: 'min(420px, 92vw)' }}>
106
+ <View
107
+ data-region="resource-share"
108
+ data-share-status={loading ? 'loading' : !shareable ? 'unavailable' : isPublic ? 'public' : 'private'}
109
+ style={{ width: 'min(420px, 92vw)' }}
110
+ >
89
111
  <View surface="primary" roundness="s">
90
112
  <View surface="primary" inset="l" roundness="s" gap="m">
91
113
  {loading ? (
@@ -94,6 +116,7 @@ export function ResourceShareDialog ({
94
116
  text={t('resources.share.sharing')}
95
117
  actions={[
96
118
  {
119
+ ...CloseShare,
97
120
  label: t('resources.share.close'),
98
121
  variant: 'command',
99
122
  onClick: onClose,
@@ -106,6 +129,7 @@ export function ResourceShareDialog ({
106
129
  text={t('resources.share.notShareable')}
107
130
  actions={[
108
131
  {
132
+ ...CloseShare,
109
133
  label: t('resources.share.close'),
110
134
  variant: 'command',
111
135
  onClick: onClose,
@@ -125,6 +149,7 @@ export function ResourceShareDialog ({
125
149
  <View gap="xs">
126
150
  <Text
127
151
  variant="small"
152
+ data-share-url={shareUrl}
128
153
  style={{
129
154
  wordBreak: 'break-all',
130
155
  fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
@@ -145,22 +170,35 @@ export function ResourceShareDialog ({
145
170
  )}
146
171
 
147
172
  <View layout="row" gap="s" style={{ justifyContent: 'flex-end', flexWrap: 'wrap' }}>
148
- <Button variant="command" onClick={onClose} disabled={busy}>
173
+ <Button {...CloseShare} variant="command" onClick={onClose} disabled={busy}>
149
174
  {t('resources.share.close')}
150
175
  </Button>
151
176
  {isPublic ? (
152
- <Button
153
- variant="cta"
154
- prefix="link"
155
- disabled={busy || !shareUrl}
156
- onClick={() => copyLink(shareUrl)}
157
- >
158
- {linkCopied
159
- ? t('resources.share.linkCopied')
160
- : t('resources.share.copyLink')}
161
- </Button>
177
+ <>
178
+ <Button
179
+ {...MakePrivate}
180
+ variant="neutral"
181
+ prefix="lock"
182
+ disabled={busy || !resource?.id}
183
+ onClick={onMakePrivate}
184
+ >
185
+ {t('resources.share.makePrivate')}
186
+ </Button>
187
+ <Button
188
+ {...CopyShareLink}
189
+ variant="cta"
190
+ prefix="link"
191
+ disabled={busy || !shareUrl}
192
+ onClick={() => copyLink(shareUrl)}
193
+ >
194
+ {linkCopied
195
+ ? t('resources.share.linkCopied')
196
+ : t('resources.share.copyLink')}
197
+ </Button>
198
+ </>
162
199
  ) : (
163
200
  <Button
201
+ {...MakePublic}
164
202
  variant="cta"
165
203
  prefix="share"
166
204
  disabled={busy || !resource?.id}
package/src/Upload.jsx CHANGED
@@ -1,5 +1,8 @@
1
1
  import React, { useCallback, useState } from 'react'
2
2
  import { Button, View, Text, UploadInput, Icon, LocalFilePreview, DropZone } from '@ossy/design-system'
3
+ import { metadata as CancelUpload } from './cancel-upload.action.js'
4
+ import { metadata as ConfirmUpload } from './confirm-upload.action.js'
5
+ import { metadata as FinishUpload } from './finish-upload.action.js'
3
6
 
4
7
  const FlowStage = {
5
8
  Error: 'Error',
@@ -55,7 +58,11 @@ export const Upload = ({
55
58
  }
56
59
 
57
60
  return (
58
- <View gap="s" style={{ height: '100%' }}>
61
+ <View
62
+ gap="s"
63
+ style={{ height: '100%' }}
64
+ data-flow-stage={flowStageAttr(flowStage, files)}
65
+ >
59
66
  {
60
67
  files.length > 0 && (
61
68
  <View gap="s" style={{ flexGrow: '1', overflow: 'auto', padding: 'var(--space-m) 0' }}>
@@ -83,6 +90,7 @@ export const Upload = ({
83
90
  >
84
91
  <UploadInput
85
92
  id="upload-resources"
93
+ data-ossy-upload-input
86
94
  type="file"
87
95
  multiple
88
96
  onChange={onUserInput}
@@ -94,15 +102,15 @@ export const Upload = ({
94
102
 
95
103
  <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 'var(--space-m)' }}>
96
104
  <Button
97
- id="cancel"
105
+ {...CancelUpload}
98
106
  onClick={onCancel}
99
107
  >Cancel
100
108
  </Button>
101
109
 
102
110
  {
103
- flowStage === FlowStage.Preview && (
111
+ flowStage === FlowStage.Preview && files.length > 0 && (
104
112
  <Button
105
- id="upload"
113
+ {...ConfirmUpload}
106
114
  variant="cta"
107
115
  onClick={uploadFiles}
108
116
  >Upload
@@ -113,7 +121,7 @@ export const Upload = ({
113
121
  {
114
122
  flowStage === FlowStage.Uploading && (
115
123
  <Button
116
- id="upload"
124
+ {...ConfirmUpload}
117
125
  variant="cta"
118
126
  disabled
119
127
  >Upploading...
@@ -124,7 +132,7 @@ export const Upload = ({
124
132
  {
125
133
  flowStage === FlowStage.Error && (
126
134
  <Button
127
- id="upload"
135
+ {...ConfirmUpload}
128
136
  variant="cta"
129
137
  onClick={uploadFiles}
130
138
  >Retry failed uploads
@@ -135,7 +143,7 @@ export const Upload = ({
135
143
  {
136
144
  flowStage === FlowStage.Done && (
137
145
  <Button
138
- id="upload"
146
+ {...FinishUpload}
139
147
  variant="cta"
140
148
  onClick={finishUpload}
141
149
  >Done
@@ -170,4 +178,13 @@ function getFlowStage(files = []) {
170
178
  if (areAllFilesUploadedSuccessfully) return FlowStage.Done
171
179
 
172
180
  return FlowStage.Preview
173
- }
181
+ }
182
+
183
+ function flowStageAttr(flowStage, files = []) {
184
+ if (files.length === 0) return 'upload-idle'
185
+ if (flowStage === FlowStage.Preview) return 'upload-preview'
186
+ if (flowStage === FlowStage.Uploading) return 'upload-uploading'
187
+ if (flowStage === FlowStage.Error) return 'upload-error'
188
+ if (flowStage === FlowStage.Done) return 'upload-done'
189
+ return 'upload-idle'
190
+ }
@@ -0,0 +1,81 @@
1
+ import { metadata as ConfirmUpload } from './confirm-upload.action.js'
2
+ import { metadata as FinishUpload } from './finish-upload.action.js'
3
+ import { metadata as DeleteResource } from './delete.action.js'
4
+ import { metadata as OpenRemoveSelected } from './open-remove-selected.action.js'
5
+ import { metadata as ToggleSelectionMode } from './toggle-selection-mode.action.js'
6
+ import uploadFileFlow from './upload-file.flow.js'
7
+
8
+ export const metadata = {
9
+ id: '@ossy/resources/flows/batch-delete',
10
+ feature: 'resources',
11
+ requires: ['server', 'database'],
12
+ timeout: 180_000,
13
+ }
14
+
15
+ /**
16
+ * Upload two files, enter selection mode, select both, confirm batch remove,
17
+ * and assert both leave storage home.
18
+ */
19
+ export default {
20
+ title: 'Batch delete selected files',
21
+ description:
22
+ 'Signed-in user uploads two files, selects both in selection mode, confirms remove, and sees them leave storage',
23
+ steps: [
24
+ ...uploadFileFlow.steps,
25
+ { page: '@upload-media', search: { location: '/' } },
26
+ { result: { selector: '#upload-resources', timeout: 15000 } },
27
+ { result: { selector: '[data-flow-stage="upload-idle"]', timeout: 15000 } },
28
+ {
29
+ files: {
30
+ selector: '#upload-resources',
31
+ items: [{
32
+ name: 'e2e-upload-2.txt',
33
+ mimeType: 'text/plain',
34
+ content: 'hello from ossy e2e upload two',
35
+ }],
36
+ },
37
+ },
38
+ { result: { selector: '[data-flow-stage="upload-preview"]', timeout: 15000 } },
39
+ { result: { action: ConfirmUpload, timeout: 10000 } },
40
+ { action: ConfirmUpload },
41
+ { result: { selector: '[data-flow-stage="upload-done"]', timeout: 30000 } },
42
+ { result: { action: FinishUpload, timeout: 10000 } },
43
+ { action: FinishUpload },
44
+ { result: { page: '@storage/home', timeout: 20000 } },
45
+ { result: { selector: '[data-resource-name="e2e-upload.txt"]', timeout: 20000 } },
46
+ { result: { selector: '[data-resource-name="e2e-upload-2.txt"]', timeout: 20000 } },
47
+ { result: { action: ToggleSelectionMode, timeout: 10000 } },
48
+ { action: ToggleSelectionMode },
49
+ { result: { selector: '[data-selection-mode="on"]', timeout: 10000 } },
50
+ {
51
+ click: {
52
+ selector: '[data-resource-name="e2e-upload.txt"] [role="checkbox"]',
53
+ },
54
+ },
55
+ {
56
+ click: {
57
+ selector: '[data-resource-name="e2e-upload-2.txt"] [role="checkbox"]',
58
+ },
59
+ },
60
+ { result: { selector: '[data-resource-name="e2e-upload.txt"][data-selected]', timeout: 10000 } },
61
+ { result: { selector: '[data-resource-name="e2e-upload-2.txt"][data-selected]', timeout: 10000 } },
62
+ { result: { action: OpenRemoveSelected, timeout: 10000 } },
63
+ { action: OpenRemoveSelected },
64
+ { result: { action: DeleteResource, timeout: 10000 } },
65
+ { action: DeleteResource },
66
+ {
67
+ result: {
68
+ selector: '[data-resource-name="e2e-upload.txt"]',
69
+ hidden: true,
70
+ timeout: 20000,
71
+ },
72
+ },
73
+ {
74
+ result: {
75
+ selector: '[data-resource-name="e2e-upload-2.txt"]',
76
+ hidden: true,
77
+ timeout: 20000,
78
+ },
79
+ },
80
+ ],
81
+ }
@@ -0,0 +1,69 @@
1
+ import { metadata as ConfirmUpload } from './confirm-upload.action.js'
2
+ import { metadata as FinishUpload } from './finish-upload.action.js'
3
+ import { metadata as DownloadSelected } from './download-selected.action.js'
4
+ import { metadata as ToggleSelectionMode } from './toggle-selection-mode.action.js'
5
+ import uploadFileFlow from './upload-file.flow.js'
6
+
7
+ export const metadata = {
8
+ id: '@ossy/resources/flows/batch-download',
9
+ feature: 'resources',
10
+ requires: ['server', 'database'],
11
+ timeout: 180_000,
12
+ }
13
+
14
+ /**
15
+ * Upload two files, enter selection mode, select both, and batch-download them.
16
+ */
17
+ export default {
18
+ title: 'Batch download selected files',
19
+ description:
20
+ 'Signed-in user uploads two files, selects both in selection mode, and downloads them together',
21
+ steps: [
22
+ ...uploadFileFlow.steps,
23
+ { page: '@upload-media', search: { location: '/' } },
24
+ { result: { selector: '#upload-resources', timeout: 15000 } },
25
+ { result: { selector: '[data-flow-stage="upload-idle"]', timeout: 15000 } },
26
+ {
27
+ files: {
28
+ selector: '#upload-resources',
29
+ items: [{
30
+ name: 'e2e-upload-2.txt',
31
+ mimeType: 'text/plain',
32
+ content: 'hello from ossy e2e upload two',
33
+ }],
34
+ },
35
+ },
36
+ { result: { selector: '[data-flow-stage="upload-preview"]', timeout: 15000 } },
37
+ { result: { action: ConfirmUpload, timeout: 10000 } },
38
+ { action: ConfirmUpload },
39
+ { result: { selector: '[data-flow-stage="upload-done"]', timeout: 30000 } },
40
+ { result: { action: FinishUpload, timeout: 10000 } },
41
+ { action: FinishUpload },
42
+ { result: { page: '@storage/home', timeout: 20000 } },
43
+ { result: { selector: '[data-resource-name="e2e-upload.txt"]', timeout: 20000 } },
44
+ { result: { selector: '[data-resource-name="e2e-upload-2.txt"]', timeout: 20000 } },
45
+ { result: { action: ToggleSelectionMode, timeout: 10000 } },
46
+ { action: ToggleSelectionMode },
47
+ { result: { selector: '[data-selection-mode="on"]', timeout: 10000 } },
48
+ {
49
+ click: {
50
+ selector: '[data-resource-name="e2e-upload.txt"] [role="checkbox"]',
51
+ },
52
+ },
53
+ {
54
+ click: {
55
+ selector: '[data-resource-name="e2e-upload-2.txt"] [role="checkbox"]',
56
+ },
57
+ },
58
+ { result: { action: DownloadSelected, timeout: 10000 } },
59
+ { result: { selector: '[data-resource-name="e2e-upload.txt"][data-selected]', timeout: 10000 } },
60
+ { result: { selector: '[data-resource-name="e2e-upload-2.txt"][data-selected]', timeout: 10000 } },
61
+ {
62
+ download: {
63
+ action: DownloadSelected,
64
+ filenames: ['e2e-upload.txt', 'e2e-upload-2.txt'],
65
+ timeout: 45000,
66
+ },
67
+ },
68
+ ],
69
+ }
@@ -0,0 +1,4 @@
1
+ export const metadata = {
2
+ id: '@ossy/resources/actions/cancel-upload',
3
+ access: 'workspace',
4
+ }
@@ -0,0 +1,40 @@
1
+ import signUpFlow from '@ossy/authentication/sign-up.flow.js'
2
+ import { metadata as CancelUpload } from './cancel-upload.action.js'
3
+
4
+ export const metadata = {
5
+ id: '@ossy/resources/flows/cancel-upload',
6
+ feature: 'resources',
7
+ requires: ['server', 'database'],
8
+ timeout: 120_000,
9
+ }
10
+
11
+ /**
12
+ * New user opens upload-media, attaches a file, cancels before confirming,
13
+ * and returns to storage home without the file listed.
14
+ */
15
+ export default {
16
+ title: 'Cancel upload before confirm',
17
+ description:
18
+ 'Signed-in user opens upload-media, selects a file, cancels, and lands on storage home without the file',
19
+ steps: [
20
+ ...signUpFlow.steps,
21
+ { page: '@upload-media', search: { location: '/' } },
22
+ { result: { selector: '#upload-resources', timeout: 15000 } },
23
+ { result: { selector: '[data-flow-stage="upload-idle"]', timeout: 15000 } },
24
+ {
25
+ files: {
26
+ selector: '#upload-resources',
27
+ items: [{
28
+ name: 'e2e-cancel-upload.txt',
29
+ mimeType: 'text/plain',
30
+ content: 'hello from ossy e2e cancel upload',
31
+ }],
32
+ },
33
+ },
34
+ { result: { selector: '[data-flow-stage="upload-preview"]', timeout: 15000 } },
35
+ { result: { action: CancelUpload, timeout: 10000 } },
36
+ { action: CancelUpload },
37
+ { result: { page: '@storage/home', timeout: 20000 } },
38
+ { result: { selector: '[data-resource-name="e2e-cancel-upload.txt"]', hidden: true, timeout: 10000 } },
39
+ ],
40
+ }
@@ -0,0 +1,4 @@
1
+ export const metadata = {
2
+ id: '@ossy/resources/actions/close-share',
3
+ access: 'workspace',
4
+ }
@@ -0,0 +1,4 @@
1
+ export const metadata = {
2
+ id: '@ossy/resources/actions/confirm-upload',
3
+ access: 'workspace',
4
+ }
@@ -0,0 +1,4 @@
1
+ export const metadata = {
2
+ id: '@ossy/resources/actions/copy-share-link',
3
+ access: 'workspace',
4
+ }
@@ -0,0 +1,5 @@
1
+ export const metadata = {
2
+ id: '@ossy/resources/actions/create-directory',
3
+ access: 'workspace',
4
+ label: '@ossy/resources/actions/create-directory.label',
5
+ }
@@ -0,0 +1,30 @@
1
+ import signUpFlow from '@ossy/authentication/sign-up.flow.js'
2
+ import { metadata as CreateDirectoryAction } from './create-directory.action.js'
3
+ import { metadata as CreateDirectoryForm } from './create-directory.form.js'
4
+
5
+ export const metadata = {
6
+ id: '@ossy/resources/flows/create-directory',
7
+ feature: 'resources',
8
+ requires: ['server', 'database'],
9
+ timeout: 120_000,
10
+ }
11
+
12
+ /**
13
+ * New user signs up, opens create-directory, names a folder, and sees it on
14
+ * storage home.
15
+ */
16
+ export default {
17
+ title: 'Create directory in storage',
18
+ description:
19
+ 'Signed-in user opens create-directory, submits a folder name, and lands on storage home with the folder listed',
20
+ steps: [
21
+ ...signUpFlow.steps,
22
+ { page: '@create-directory', search: { location: '/' } },
23
+ { result: { selector: '[data-flow-stage="create-directory"]', timeout: 15000 } },
24
+ { result: { action: CreateDirectoryAction, timeout: 10000 } },
25
+ { form: CreateDirectoryForm, fields: { name: 'e2e-folder' } },
26
+ { action: CreateDirectoryAction },
27
+ { result: { page: '@storage/home', timeout: 20000 } },
28
+ { result: { selector: '[data-resource-name="e2e-folder"]', timeout: 20000 } },
29
+ ],
30
+ }
@@ -0,0 +1,5 @@
1
+ export const metadata = {
2
+ id: '@ossy/resources/form/create-directory',
3
+ schemaId: '@ossy/resources/schema/create-directory',
4
+ label: '@ossy/resources/form/create-directory.label',
5
+ }
@@ -0,0 +1,9 @@
1
+ /** Create directory form — UI fields and flow faker schema. */
2
+ export default {
3
+ id: '@ossy/resources/schema/create-directory',
4
+ name: 'Create directory',
5
+ icon: 'folder-add',
6
+ fields: [
7
+ { name: 'name', type: 'text', required: true },
8
+ ],
9
+ }