@ossy/resources 3.9.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 (89) 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/SchemaDetailView.jsx +15 -3
  11. package/src/Upload.jsx +25 -8
  12. package/src/batch-delete.flow.js +81 -0
  13. package/src/batch-download.flow.js +69 -0
  14. package/src/cancel-upload.action.js +4 -0
  15. package/src/cancel-upload.flow.js +40 -0
  16. package/src/close-share.action.js +4 -0
  17. package/src/confirm-upload.action.js +4 -0
  18. package/src/copy-share-link.action.js +4 -0
  19. package/src/create-directory.action.js +5 -0
  20. package/src/create-directory.flow.js +30 -0
  21. package/src/create-directory.form.js +5 -0
  22. package/src/create-directory.schema.js +9 -0
  23. package/src/create-document.flow.js +36 -0
  24. package/src/create-document.form.js +5 -0
  25. package/src/create-document.schema.js +11 -0
  26. package/src/create.action.js +5 -1
  27. package/src/delete-directory.flow.js +39 -0
  28. package/src/delete-file.flow.js +32 -0
  29. package/src/done-selecting.action.js +5 -0
  30. package/src/download-file.flow.js +31 -0
  31. package/src/download-resource.action.js +4 -0
  32. package/src/download-selected.action.js +4 -0
  33. package/src/en.translations.json +18 -0
  34. package/src/finish-upload.action.js +4 -0
  35. package/src/index.js +42 -0
  36. package/src/list.task.js +17 -6
  37. package/src/list.task.spec.js +81 -0
  38. package/src/make-private-resource.flow.js +72 -0
  39. package/src/make-private.action.js +4 -0
  40. package/src/make-public.action.js +4 -0
  41. package/src/move-file.flow.js +78 -0
  42. package/src/move.form.js +5 -0
  43. package/src/move.schema.js +9 -0
  44. package/src/open-delete-directory.action.js +7 -0
  45. package/src/open-move.action.js +4 -0
  46. package/src/open-remove-selected.action.js +5 -0
  47. package/src/open-rename.action.js +4 -0
  48. package/src/open-share.action.js +4 -0
  49. package/src/open-sort.action.js +5 -0
  50. package/src/open-view.action.js +5 -0
  51. package/src/rename-file.flow.js +43 -0
  52. package/src/rename.form.js +5 -0
  53. package/src/rename.schema.js +9 -0
  54. package/src/resource-list-query.js +182 -0
  55. package/src/resource-list-query.spec.js +75 -0
  56. package/src/resource-selection.js +25 -0
  57. package/src/resource-selection.spec.js +40 -0
  58. package/src/resource.helpers.js +8 -1
  59. package/src/resources-list-options.js +169 -0
  60. package/src/resources-list-options.spec.js +141 -0
  61. package/src/resources.action-media.js +6 -0
  62. package/src/resources.queries.js +91 -6
  63. package/src/schema-field-display.js +31 -0
  64. package/src/schema-field-display.spec.js +37 -0
  65. package/src/search-payload.js +10 -0
  66. package/src/search-payload.spec.js +20 -0
  67. package/src/search.task.js +2 -1
  68. package/src/share-resource.flow.js +59 -0
  69. package/src/sort-ascending.action.js +5 -0
  70. package/src/sort-by-created.action.js +5 -0
  71. package/src/sort-by-last-updated.action.js +5 -0
  72. package/src/sort-by-name.action.js +5 -0
  73. package/src/sort-by-size.action.js +5 -0
  74. package/src/sort-by-type.action.js +5 -0
  75. package/src/sort-descending.action.js +5 -0
  76. package/src/sort-storage-created.flow.js +87 -0
  77. package/src/sort-storage-last-updated.flow.js +119 -0
  78. package/src/sort-storage-name-asc.flow.js +34 -0
  79. package/src/sort-storage-size-desc.flow.js +34 -0
  80. package/src/sort-storage-size.flow.js +87 -0
  81. package/src/sort-storage-type.flow.js +83 -0
  82. package/src/sort-storage.flow.js +86 -0
  83. package/src/sv.translations.json +18 -0
  84. package/src/toggle-selection-mode.action.js +4 -0
  85. package/src/upload-file.flow.js +44 -0
  86. package/src/useResourceList.js +94 -0
  87. package/src/view-grid.action.js +5 -0
  88. package/src/view-list.action.js +5 -0
  89. package/src/view-storage.flow.js +49 -0
@@ -0,0 +1,119 @@
1
+ import signUpFlow from '@ossy/authentication/sign-up.flow.js'
2
+ import { metadata as ConfirmUpload } from './confirm-upload.action.js'
3
+ import { metadata as FinishUpload } from './finish-upload.action.js'
4
+ import { metadata as OpenRename } from './open-rename.action.js'
5
+ import { metadata as OpenSort } from './open-sort.action.js'
6
+ import { metadata as RenameForm } from './rename.form.js'
7
+ import { metadata as SortByLastUpdated } from './sort-by-last-updated.action.js'
8
+ import { metadata as UpdateResourceName } from './update-name.action.js'
9
+
10
+ export const metadata = {
11
+ id: '@ossy/resources/flows/sort-storage-last-updated',
12
+ feature: 'resources',
13
+ requires: ['server', 'database'],
14
+ timeout: 180_000,
15
+ }
16
+
17
+ /**
18
+ * Upload two files in name order, rename the earlier one (a→b, still before z)
19
+ * to bump lastUpdated, open the storage sort menu, switch to last updated
20
+ * ascending, and assert list order is opposite name order.
21
+ */
22
+ export default {
23
+ title: 'Sort storage resources by last updated',
24
+ description:
25
+ 'Signed-in user uploads two files, renames one to bump lastUpdated, opens sort, chooses last updated, and sees last-updated ascending order',
26
+ steps: [
27
+ ...signUpFlow.steps,
28
+ { page: '@upload-media', search: { location: '/' } },
29
+ { result: { selector: '#upload-resources', timeout: 15000 } },
30
+ { result: { selector: '[data-flow-stage="upload-idle"]', timeout: 15000 } },
31
+ {
32
+ files: {
33
+ selector: '#upload-resources',
34
+ items: [{
35
+ name: 'e2e-lu-a.txt',
36
+ mimeType: 'text/plain',
37
+ // Uploaded first so name-asc matches created order; rename later bumps lastUpdated.
38
+ content: 'last updated a',
39
+ }],
40
+ },
41
+ },
42
+ { result: { selector: '[data-flow-stage="upload-preview"]', timeout: 15000 } },
43
+ { result: { action: ConfirmUpload, timeout: 10000 } },
44
+ { action: ConfirmUpload },
45
+ { result: { selector: '[data-flow-stage="upload-done"]', timeout: 30000 } },
46
+ { result: { action: FinishUpload, timeout: 10000 } },
47
+ { action: FinishUpload },
48
+ { result: { page: '@storage/home', timeout: 20000 } },
49
+ { result: { selector: '[data-resource-name="e2e-lu-a.txt"]', timeout: 20000 } },
50
+ { page: '@upload-media', search: { location: '/' } },
51
+ { result: { selector: '#upload-resources', timeout: 15000 } },
52
+ { result: { selector: '[data-flow-stage="upload-idle"]', timeout: 15000 } },
53
+ {
54
+ files: {
55
+ selector: '#upload-resources',
56
+ items: [{
57
+ name: 'e2e-lu-z.txt',
58
+ mimeType: 'text/plain',
59
+ content: 'last updated z',
60
+ }],
61
+ },
62
+ },
63
+ { result: { selector: '[data-flow-stage="upload-preview"]', timeout: 15000 } },
64
+ { result: { action: ConfirmUpload, timeout: 10000 } },
65
+ { action: ConfirmUpload },
66
+ { result: { selector: '[data-flow-stage="upload-done"]', timeout: 30000 } },
67
+ { result: { action: FinishUpload, timeout: 10000 } },
68
+ { action: FinishUpload },
69
+ { result: { page: '@storage/home', timeout: 20000 } },
70
+ { result: { selector: '[data-resource-name="e2e-lu-a.txt"]', timeout: 20000 } },
71
+ { result: { selector: '[data-resource-name="e2e-lu-z.txt"]', timeout: 20000 } },
72
+ { result: { selector: '[data-sort-by="name"][data-sort-order="asc"]', timeout: 10000 } },
73
+ {
74
+ result: {
75
+ selector: '[data-resource-name="e2e-lu-a.txt"] ~ [data-resource-name="e2e-lu-z.txt"]',
76
+ timeout: 10000,
77
+ },
78
+ },
79
+ { click: { selector: '[data-resource-name="e2e-lu-a.txt"]' } },
80
+ { result: { action: OpenRename, timeout: 15000 } },
81
+ { action: OpenRename },
82
+ { result: { action: UpdateResourceName, timeout: 10000 } },
83
+ // Rename keeps alphabetical order before z, but bumps lastUpdated on this file.
84
+ { form: RenameForm, fields: { name: 'e2e-lu-b.txt' } },
85
+ { action: UpdateResourceName },
86
+ {
87
+ result: {
88
+ selector: '[data-resource-name="e2e-lu-b.txt"]',
89
+ timeout: 20000,
90
+ },
91
+ },
92
+ {
93
+ result: {
94
+ selector: '[data-resource-name="e2e-lu-a.txt"]',
95
+ hidden: true,
96
+ timeout: 10000,
97
+ },
98
+ },
99
+ {
100
+ result: {
101
+ selector: '[data-resource-name="e2e-lu-b.txt"] ~ [data-resource-name="e2e-lu-z.txt"]',
102
+ timeout: 10000,
103
+ },
104
+ },
105
+ { press: 'Escape' },
106
+ { result: { action: OpenSort, timeout: 10000 } },
107
+ { action: OpenSort },
108
+ { result: { action: SortByLastUpdated, timeout: 10000 } },
109
+ { action: SortByLastUpdated },
110
+ { result: { selector: '[data-sort-by="lastUpdated"][data-sort-order="asc"]', timeout: 10000 } },
111
+ {
112
+ result: {
113
+ // z was never renamed, so it sorts before the bumped b under lastUpdated asc.
114
+ selector: '[data-resource-name="e2e-lu-z.txt"] ~ [data-resource-name="e2e-lu-b.txt"]',
115
+ timeout: 10000,
116
+ },
117
+ },
118
+ ],
119
+ }
@@ -0,0 +1,34 @@
1
+ import { metadata as OpenSort } from './open-sort.action.js'
2
+ import { metadata as SortAscending } from './sort-ascending.action.js'
3
+ import sortStorageFlow from './sort-storage.flow.js'
4
+
5
+ export const metadata = {
6
+ id: '@ossy/resources/flows/sort-storage-name-asc',
7
+ feature: 'resources',
8
+ requires: ['server', 'database'],
9
+ timeout: 180_000,
10
+ }
11
+
12
+ /**
13
+ * After name-descending sort is established, flip back to ascending and assert
14
+ * the original name order under asc markers.
15
+ */
16
+ export default {
17
+ title: 'Sort storage resources by name ascending',
18
+ description:
19
+ 'Signed-in user sorts by name descending, then switches to ascending and sees the restored name order',
20
+ steps: [
21
+ ...sortStorageFlow.steps,
22
+ { result: { action: OpenSort, timeout: 10000 } },
23
+ { action: OpenSort },
24
+ { result: { action: SortAscending, timeout: 10000 } },
25
+ { action: SortAscending },
26
+ { result: { selector: '[data-sort-by="name"][data-sort-order="asc"]', timeout: 10000 } },
27
+ {
28
+ result: {
29
+ selector: '[data-resource-name="e2e-sort-a.txt"] ~ [data-resource-name="e2e-sort-z.txt"]',
30
+ timeout: 10000,
31
+ },
32
+ },
33
+ ],
34
+ }
@@ -0,0 +1,34 @@
1
+ import { metadata as OpenSort } from './open-sort.action.js'
2
+ import { metadata as SortDescending } from './sort-descending.action.js'
3
+ import sortStorageSizeFlow from './sort-storage-size.flow.js'
4
+
5
+ export const metadata = {
6
+ id: '@ossy/resources/flows/sort-storage-size-desc',
7
+ feature: 'resources',
8
+ requires: ['server', 'database'],
9
+ timeout: 180_000,
10
+ }
11
+
12
+ /**
13
+ * After size-ascending sort is established, flip to descending and assert the
14
+ * larger file precedes the smaller one under size desc markers.
15
+ */
16
+ export default {
17
+ title: 'Sort storage resources by size descending',
18
+ description:
19
+ 'Signed-in user sorts by size ascending, then switches to descending and sees the reversed size order',
20
+ steps: [
21
+ ...sortStorageSizeFlow.steps,
22
+ { result: { action: OpenSort, timeout: 10000 } },
23
+ { action: OpenSort },
24
+ { result: { action: SortDescending, timeout: 10000 } },
25
+ { action: SortDescending },
26
+ { result: { selector: '[data-sort-by="size"][data-sort-order="desc"]', timeout: 10000 } },
27
+ {
28
+ result: {
29
+ selector: '[data-resource-name="e2e-sort-a.txt"] ~ [data-resource-name="e2e-sort-z.txt"]',
30
+ timeout: 10000,
31
+ },
32
+ },
33
+ ],
34
+ }
@@ -0,0 +1,87 @@
1
+ import signUpFlow from '@ossy/authentication/sign-up.flow.js'
2
+ import { metadata as ConfirmUpload } from './confirm-upload.action.js'
3
+ import { metadata as FinishUpload } from './finish-upload.action.js'
4
+ import { metadata as OpenSort } from './open-sort.action.js'
5
+ import { metadata as SortBySize } from './sort-by-size.action.js'
6
+
7
+ export const metadata = {
8
+ id: '@ossy/resources/flows/sort-storage-size',
9
+ feature: 'resources',
10
+ requires: ['server', 'database'],
11
+ timeout: 180_000,
12
+ }
13
+
14
+ /**
15
+ * Upload two files whose name order is opposite their size order, open the
16
+ * storage sort menu, switch to size ascending, and assert list order + markers.
17
+ */
18
+ export default {
19
+ title: 'Sort storage resources by size',
20
+ description:
21
+ 'Signed-in user uploads two differently sized files, opens sort, chooses size, and sees size ascending order',
22
+ steps: [
23
+ ...signUpFlow.steps,
24
+ { page: '@upload-media', search: { location: '/' } },
25
+ { result: { selector: '#upload-resources', timeout: 15000 } },
26
+ { result: { selector: '[data-flow-stage="upload-idle"]', timeout: 15000 } },
27
+ {
28
+ files: {
29
+ selector: '#upload-resources',
30
+ items: [{
31
+ name: 'e2e-sort-a.txt',
32
+ mimeType: 'text/plain',
33
+ // Larger body so size-asc places this after the small "z" file.
34
+ content: 'LARGE-SORT-A-CONTENT-'.repeat(40),
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-sort-a.txt"]', timeout: 20000 } },
46
+ { page: '@upload-media', search: { location: '/' } },
47
+ { result: { selector: '#upload-resources', timeout: 15000 } },
48
+ { result: { selector: '[data-flow-stage="upload-idle"]', timeout: 15000 } },
49
+ {
50
+ files: {
51
+ selector: '#upload-resources',
52
+ items: [{
53
+ name: 'e2e-sort-z.txt',
54
+ mimeType: 'text/plain',
55
+ content: 'z',
56
+ }],
57
+ },
58
+ },
59
+ { result: { selector: '[data-flow-stage="upload-preview"]', timeout: 15000 } },
60
+ { result: { action: ConfirmUpload, timeout: 10000 } },
61
+ { action: ConfirmUpload },
62
+ { result: { selector: '[data-flow-stage="upload-done"]', timeout: 30000 } },
63
+ { result: { action: FinishUpload, timeout: 10000 } },
64
+ { action: FinishUpload },
65
+ { result: { page: '@storage/home', timeout: 20000 } },
66
+ { result: { selector: '[data-resource-name="e2e-sort-a.txt"]', timeout: 20000 } },
67
+ { result: { selector: '[data-resource-name="e2e-sort-z.txt"]', timeout: 20000 } },
68
+ { result: { selector: '[data-sort-by="name"][data-sort-order="asc"]', timeout: 10000 } },
69
+ {
70
+ result: {
71
+ selector: '[data-resource-name="e2e-sort-a.txt"] ~ [data-resource-name="e2e-sort-z.txt"]',
72
+ timeout: 10000,
73
+ },
74
+ },
75
+ { result: { action: OpenSort, timeout: 10000 } },
76
+ { action: OpenSort },
77
+ { result: { action: SortBySize, timeout: 10000 } },
78
+ { action: SortBySize },
79
+ { result: { selector: '[data-sort-by="size"][data-sort-order="asc"]', timeout: 10000 } },
80
+ {
81
+ result: {
82
+ selector: '[data-resource-name="e2e-sort-z.txt"] ~ [data-resource-name="e2e-sort-a.txt"]',
83
+ timeout: 10000,
84
+ },
85
+ },
86
+ ],
87
+ }
@@ -0,0 +1,83 @@
1
+ import signUpFlow from '@ossy/authentication/sign-up.flow.js'
2
+ import { metadata as ConfirmUpload } from './confirm-upload.action.js'
3
+ import { metadata as CreateResource } from './create.action.js'
4
+ import { metadata as CreateDocumentForm } from './create-document.form.js'
5
+ import { metadata as FinishUpload } from './finish-upload.action.js'
6
+ import { metadata as OpenSort } from './open-sort.action.js'
7
+ import { metadata as SortByType } from './sort-by-type.action.js'
8
+
9
+ export const metadata = {
10
+ id: '@ossy/resources/flows/sort-storage-type',
11
+ feature: 'resources',
12
+ requires: ['server', 'database'],
13
+ timeout: 180_000,
14
+ }
15
+
16
+ /**
17
+ * Create a markdown doc and upload a file whose name order is opposite their
18
+ * schema-type order, then sort by type and assert the type order wins.
19
+ *
20
+ * Type asc: `@ossy/platform/schema/file` before `@ossy/resources/schema/markdown`.
21
+ * Names: aaa-note (markdown) vs zzz-file.txt (file) so name-asc is the reverse.
22
+ */
23
+ export default {
24
+ title: 'Sort storage resources by type',
25
+ description:
26
+ 'Signed-in user creates a markdown doc and uploads a file, opens sort, chooses type, and sees type ascending order',
27
+ steps: [
28
+ ...signUpFlow.steps,
29
+ {
30
+ page: '@create-document',
31
+ search: {
32
+ location: '/',
33
+ schemaId: '@ossy/resources/schema/markdown',
34
+ },
35
+ },
36
+ { result: { selector: '[data-flow-stage="create-document"]', timeout: 15000 } },
37
+ { result: { action: CreateResource, timeout: 10000 } },
38
+ { form: CreateDocumentForm, fields: { name: 'aaa-note' } },
39
+ { action: CreateResource },
40
+ { result: { page: '@storage/home', timeout: 20000 } },
41
+ { result: { selector: '[data-resource-name="aaa-note"]', timeout: 20000 } },
42
+ { page: '@upload-media', search: { location: '/' } },
43
+ { result: { selector: '#upload-resources', timeout: 15000 } },
44
+ { result: { selector: '[data-flow-stage="upload-idle"]', timeout: 15000 } },
45
+ {
46
+ files: {
47
+ selector: '#upload-resources',
48
+ items: [{
49
+ name: 'zzz-file.txt',
50
+ mimeType: 'text/plain',
51
+ content: 'type sort file',
52
+ }],
53
+ },
54
+ },
55
+ { result: { selector: '[data-flow-stage="upload-preview"]', timeout: 15000 } },
56
+ { result: { action: ConfirmUpload, timeout: 10000 } },
57
+ { action: ConfirmUpload },
58
+ { result: { selector: '[data-flow-stage="upload-done"]', timeout: 30000 } },
59
+ { result: { action: FinishUpload, timeout: 10000 } },
60
+ { action: FinishUpload },
61
+ { result: { page: '@storage/home', timeout: 20000 } },
62
+ { result: { selector: '[data-resource-name="aaa-note"]', timeout: 20000 } },
63
+ { result: { selector: '[data-resource-name="zzz-file.txt"]', timeout: 20000 } },
64
+ { result: { selector: '[data-sort-by="name"][data-sort-order="asc"]', timeout: 10000 } },
65
+ {
66
+ result: {
67
+ selector: '[data-resource-name="aaa-note"] ~ [data-resource-name="zzz-file.txt"]',
68
+ timeout: 10000,
69
+ },
70
+ },
71
+ { result: { action: OpenSort, timeout: 10000 } },
72
+ { action: OpenSort },
73
+ { result: { action: SortByType, timeout: 10000 } },
74
+ { action: SortByType },
75
+ { result: { selector: '[data-sort-by="type"][data-sort-order="asc"]', timeout: 10000 } },
76
+ {
77
+ result: {
78
+ selector: '[data-resource-name="zzz-file.txt"] ~ [data-resource-name="aaa-note"]',
79
+ timeout: 10000,
80
+ },
81
+ },
82
+ ],
83
+ }
@@ -0,0 +1,86 @@
1
+ import signUpFlow from '@ossy/authentication/sign-up.flow.js'
2
+ import { metadata as ConfirmUpload } from './confirm-upload.action.js'
3
+ import { metadata as FinishUpload } from './finish-upload.action.js'
4
+ import { metadata as OpenSort } from './open-sort.action.js'
5
+ import { metadata as SortDescending } from './sort-descending.action.js'
6
+
7
+ export const metadata = {
8
+ id: '@ossy/resources/flows/sort-storage',
9
+ feature: 'resources',
10
+ requires: ['server', 'database'],
11
+ timeout: 180_000,
12
+ }
13
+
14
+ /**
15
+ * Upload two differently named files, open the storage sort menu, switch to
16
+ * name descending, and assert list order + sort markers.
17
+ */
18
+ export default {
19
+ title: 'Sort storage resources by name',
20
+ description:
21
+ 'Signed-in user uploads two files, opens sort, chooses name descending, and sees the reversed list order',
22
+ steps: [
23
+ ...signUpFlow.steps,
24
+ { page: '@upload-media', search: { location: '/' } },
25
+ { result: { selector: '#upload-resources', timeout: 15000 } },
26
+ { result: { selector: '[data-flow-stage="upload-idle"]', timeout: 15000 } },
27
+ {
28
+ files: {
29
+ selector: '#upload-resources',
30
+ items: [{
31
+ name: 'e2e-sort-a.txt',
32
+ mimeType: 'text/plain',
33
+ content: 'sort a',
34
+ }],
35
+ },
36
+ },
37
+ { result: { selector: '[data-flow-stage="upload-preview"]', timeout: 15000 } },
38
+ { result: { action: ConfirmUpload, timeout: 10000 } },
39
+ { action: ConfirmUpload },
40
+ { result: { selector: '[data-flow-stage="upload-done"]', timeout: 30000 } },
41
+ { result: { action: FinishUpload, timeout: 10000 } },
42
+ { action: FinishUpload },
43
+ { result: { page: '@storage/home', timeout: 20000 } },
44
+ { result: { selector: '[data-resource-name="e2e-sort-a.txt"]', timeout: 20000 } },
45
+ { page: '@upload-media', search: { location: '/' } },
46
+ { result: { selector: '#upload-resources', timeout: 15000 } },
47
+ { result: { selector: '[data-flow-stage="upload-idle"]', timeout: 15000 } },
48
+ {
49
+ files: {
50
+ selector: '#upload-resources',
51
+ items: [{
52
+ name: 'e2e-sort-z.txt',
53
+ mimeType: 'text/plain',
54
+ content: 'sort z',
55
+ }],
56
+ },
57
+ },
58
+ { result: { selector: '[data-flow-stage="upload-preview"]', timeout: 15000 } },
59
+ { result: { action: ConfirmUpload, timeout: 10000 } },
60
+ { action: ConfirmUpload },
61
+ { result: { selector: '[data-flow-stage="upload-done"]', timeout: 30000 } },
62
+ { result: { action: FinishUpload, timeout: 10000 } },
63
+ { action: FinishUpload },
64
+ { result: { page: '@storage/home', timeout: 20000 } },
65
+ { result: { selector: '[data-resource-name="e2e-sort-a.txt"]', timeout: 20000 } },
66
+ { result: { selector: '[data-resource-name="e2e-sort-z.txt"]', timeout: 20000 } },
67
+ { result: { selector: '[data-sort-by="name"][data-sort-order="asc"]', timeout: 10000 } },
68
+ {
69
+ result: {
70
+ selector: '[data-resource-name="e2e-sort-a.txt"] ~ [data-resource-name="e2e-sort-z.txt"]',
71
+ timeout: 10000,
72
+ },
73
+ },
74
+ { result: { action: OpenSort, timeout: 10000 } },
75
+ { action: OpenSort },
76
+ { result: { action: SortDescending, timeout: 10000 } },
77
+ { action: SortDescending },
78
+ { result: { selector: '[data-sort-by="name"][data-sort-order="desc"]', timeout: 10000 } },
79
+ {
80
+ result: {
81
+ selector: '[data-resource-name="e2e-sort-z.txt"] ~ [data-resource-name="e2e-sort-a.txt"]',
82
+ timeout: 10000,
83
+ },
84
+ },
85
+ ],
86
+ }
@@ -49,11 +49,29 @@
49
49
  "resources.createDirectory.submit": "Skapa mapp",
50
50
  "resources.createDirectory.errorEmpty": "Mappnamn får inte vara tomt",
51
51
  "resources.createDirectory.errorOssyPrefix": "Mappnamn får inte börja med @ossy",
52
+ "@ossy/resources/actions/create-directory.label": "Skapa mapp",
53
+ "@ossy/resources/form/create-directory.label": "Skapa mapp",
54
+ "@ossy/resources/schema/create-directory.name.label": "Mappnamn",
55
+ "@ossy/resources/form/create-document.label": "Skapa dokument",
56
+ "@ossy/resources/schema/create-document.name.label": "Dokumentnamn",
52
57
  "resources.panel.download": "Ladda ner",
58
+ "resources.panel.delete": "Ta bort",
59
+ "resources.panel.rename": "Byt namn",
60
+ "resources.panel.move": "Flytta",
61
+ "resources.move.title": "Flytta resurs",
62
+ "resources.move.errorEmpty": "Målplatsen får inte vara tom",
63
+ "resources.move.errorFailed": "Kunde inte flytta resursen",
64
+ "@ossy/resources/form/rename.label": "Byt namn på resurs",
65
+ "@ossy/resources/schema/rename.name.label": "Namn",
66
+ "@ossy/resources/form/move.label": "Flytta resurs",
67
+ "@ossy/resources/schema/move.target.label": "Ny plats",
68
+ "@ossy/resources/actions/open-move.label": "Flytta resurs",
69
+ "@ossy/resources/actions/open-move.description": "Öppna dialogen för att flytta en resurs till en ny plats",
53
70
  "resources.share.title": "Dela länk",
54
71
  "resources.share.confirmPublic": "Gör den här resursen publik så att alla med länken kan öppna den. Du kan kopiera länken efter bekräftelse.",
55
72
  "resources.share.alreadyPublic": "Den här resursen är publik. Kopiera länken nedan för att dela den.",
56
73
  "resources.share.makePublic": "Gör publik & kopiera länk",
74
+ "resources.share.makePrivate": "Gör privat",
57
75
  "resources.share.copyLink": "Kopiera länk",
58
76
  "resources.share.linkCopied": "Länk kopierad",
59
77
  "resources.share.copyFailed": "Kunde inte kopiera länken",
@@ -0,0 +1,4 @@
1
+ export const metadata = {
2
+ id: '@ossy/resources/actions/toggle-selection-mode',
3
+ access: 'workspace',
4
+ }
@@ -0,0 +1,44 @@
1
+ import signUpFlow from '@ossy/authentication/sign-up.flow.js'
2
+ import { metadata as ConfirmUpload } from './confirm-upload.action.js'
3
+ import { metadata as FinishUpload } from './finish-upload.action.js'
4
+
5
+ export const metadata = {
6
+ id: '@ossy/resources/flows/upload-file',
7
+ feature: 'resources',
8
+ requires: ['server', 'database'],
9
+ timeout: 120_000,
10
+ }
11
+
12
+ /**
13
+ * New user signs up, opens the media upload page, attaches a file via the native
14
+ * file input, confirms upload, and sees the file listed on storage home.
15
+ */
16
+ export default {
17
+ title: 'Upload file to storage',
18
+ description:
19
+ 'Signed-in user opens upload-media, selects a file, uploads it, and lands on storage home with the file listed',
20
+ steps: [
21
+ ...signUpFlow.steps,
22
+ { page: '@upload-media', search: { location: '/' } },
23
+ { result: { selector: '#upload-resources', timeout: 15000 } },
24
+ { result: { selector: '[data-flow-stage="upload-idle"]', timeout: 15000 } },
25
+ {
26
+ files: {
27
+ selector: '#upload-resources',
28
+ items: [{
29
+ name: 'e2e-upload.txt',
30
+ mimeType: 'text/plain',
31
+ content: 'hello from ossy e2e upload',
32
+ }],
33
+ },
34
+ },
35
+ { result: { selector: '[data-flow-stage="upload-preview"]', timeout: 15000 } },
36
+ { result: { action: ConfirmUpload, timeout: 10000 } },
37
+ { action: ConfirmUpload },
38
+ { result: { selector: '[data-flow-stage="upload-done"]', timeout: 30000 } },
39
+ { result: { action: FinishUpload, timeout: 10000 } },
40
+ { action: FinishUpload },
41
+ { result: { page: '@storage/home', timeout: 20000 } },
42
+ { result: { selector: '[data-resource-name="e2e-upload.txt"]', timeout: 20000 } },
43
+ ],
44
+ }
@@ -0,0 +1,94 @@
1
+ 'use client'
2
+ import { useEffect, useMemo } from 'react'
3
+ import { useCollectionView } from '@ossy/design-system'
4
+ import { useSdk } from '@ossy/sdk-react'
5
+ import { metadata as ListResources } from './list.action.js'
6
+ import { DEFAULT_RESOURCE_LIST_LIMIT } from './resources-list-options.js'
7
+ import { useResourceListQuery } from './resource-list-query.js'
8
+
9
+ /**
10
+ * Read a resource folder with shared list/grid collection controls.
11
+ *
12
+ * When `limit` is set, list options are sent to `ListResources` so Mongo
13
+ * `$sort` / `$skip` / `$limit` run server-side. Without `limit`, behavior
14
+ * matches the legacy full-folder array response.
15
+ *
16
+ * Page and sort are stored in the URL (`page`, `sort`, `order`), same as the
17
+ * open resource id (`r`).
18
+ *
19
+ * @param {{
20
+ * location: string,
21
+ * limit?: number | null,
22
+ * enabled?: boolean,
23
+ * initialSortBy?: string,
24
+ * initialSortOrder?: 'asc' | 'desc',
25
+ * }} options
26
+ */
27
+ export function useResourceList ({
28
+ location,
29
+ limit = DEFAULT_RESOURCE_LIST_LIMIT,
30
+ enabled = true,
31
+ initialSortBy = 'name',
32
+ initialSortOrder = 'asc',
33
+ } = {}) {
34
+ const sdk = useSdk()
35
+ const pageLimit = limit == null ? DEFAULT_RESOURCE_LIST_LIMIT : Math.max(1, Number(limit) || DEFAULT_RESOURCE_LIST_LIMIT)
36
+ const query = useResourceListQuery({
37
+ defaultSortBy: initialSortBy,
38
+ defaultSortOrder: initialSortOrder,
39
+ })
40
+ const {
41
+ pageIndex,
42
+ sortBy,
43
+ sortOrder,
44
+ setPage,
45
+ } = query
46
+ const offset = Math.max(0, pageIndex) * pageLimit
47
+
48
+ const controls = useMemo(() => ({
49
+ ...query,
50
+ limit: pageLimit,
51
+ offset,
52
+ }), [query, pageLimit, offset])
53
+
54
+ const payload = useMemo(() => {
55
+ const next = { location }
56
+ if (limit != null) {
57
+ next.limit = pageLimit
58
+ next.offset = offset
59
+ next.sortBy = sortBy
60
+ next.sortOrder = sortOrder
61
+ }
62
+ return next
63
+ }, [location, limit, pageLimit, offset, sortBy, sortOrder])
64
+
65
+ const {
66
+ status,
67
+ data,
68
+ error,
69
+ refetch,
70
+ } = sdk.read(ListResources, payload, { enabled: Boolean(enabled && location) })
71
+
72
+ const collection = useCollectionView(controls, { data, status, error })
73
+
74
+ useEffect(() => {
75
+ if (collection.total > 0 && pageIndex > collection.pageCount - 1) {
76
+ setPage(collection.pageCount - 1)
77
+ }
78
+ }, [collection.total, pageIndex, collection.pageCount, setPage])
79
+
80
+ return {
81
+ ...collection,
82
+ paginated: collection.paginated || limit != null,
83
+ refetch,
84
+ offset: collection.pageIndex * collection.limit,
85
+ }
86
+ }
87
+
88
+ export { normalizeCollectionResult } from '@ossy/design-system'
89
+ export {
90
+ parseResourceListQuery,
91
+ resourceListQuerySearchString,
92
+ serializeResourceListQuery,
93
+ useResourceListQuery,
94
+ } from './resource-list-query.js'
@@ -0,0 +1,5 @@
1
+ /** Client-only — switch storage resources to grid layout. */
2
+ export const metadata = {
3
+ id: '@ossy/resources/actions/view-grid',
4
+ access: 'workspace',
5
+ }
@@ -0,0 +1,5 @@
1
+ /** Client-only — switch storage resources to list layout. */
2
+ export const metadata = {
3
+ id: '@ossy/resources/actions/view-list',
4
+ access: 'workspace',
5
+ }