@ossy/resources 1.11.5 → 1.11.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ossy/resources",
3
3
  "description": "Resource domain — aggregate and events for the Ossy resource model",
4
- "version": "1.11.5",
4
+ "version": "1.11.7",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "main": "./src/index.js",
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "devDependencies": {
21
21
  "@jest/globals": "^30.2.0",
22
- "@ossy/platform": "^1.38.5",
22
+ "@ossy/platform": "^1.38.7",
23
23
  "casual": "^1.6.2",
24
24
  "jest": "^30.2.0"
25
25
  },
@@ -31,5 +31,5 @@
31
31
  "/src",
32
32
  "README.md"
33
33
  ],
34
- "gitHead": "97d02b0762f2c0461abb1e6753fc3aaad30ee93f"
34
+ "gitHead": "f65df0cd6da864c314c4f424112e05e8e2dec0ff"
35
35
  }
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import { useResource } from '@ossy/sdk-react'
3
- import { Stack, View } from '@ossy/design-system'
3
+ import { View } from '@ossy/design-system'
4
4
 
5
5
  export const AudioResource = ({
6
6
  resourceId,
@@ -9,8 +9,8 @@ export const AudioResource = ({
9
9
  const { resource } = useResource(resourceId)
10
10
 
11
11
  return (
12
- <Stack bordered>
13
- <Stack.Item fill style={{ padding: '16px 8px', height: '100%' }}>
12
+ <View stack bordered>
13
+ <View.Item fill style={{ padding: '16px 8px', height: '100%' }}>
14
14
 
15
15
  <View layout="off-center-s" inset="m" style={{ height: '100%'}}>
16
16
  <View
@@ -21,7 +21,7 @@ export const AudioResource = ({
21
21
  />
22
22
  </View>
23
23
 
24
- </Stack.Item>
25
- </Stack>
24
+ </View.Item>
25
+ </View>
26
26
  )
27
27
  }
@@ -1,73 +1,36 @@
1
1
  import React, { useState } from 'react'
2
- import { useResources, useResourceTemplate } from '@ossy/sdk-react'
3
- import {
4
- Alert,
5
- InputTitle,
6
- Button,
7
- useInputValue,
8
- Fields,
9
- applyFieldChange,
10
- } from '@ossy/design-system'
2
+ import { useResources } from '@ossy/sdk-react'
3
+ import { Alert } from '@ossy/design-system'
11
4
  import { useRouter } from '@ossy/router-react'
5
+ import { GenericResourceForm } from './GenericResourceForm.jsx'
12
6
 
13
7
  export const CreateDocument = () => {
14
8
  const router = useRouter()
15
9
  const templateId = router.searchParams.templateId
16
10
  const location = router.searchParams.location
17
- const template = useResourceTemplate(templateId)
18
- const [documentData, setDocumentData] = useState({})
19
11
  const { createDocument } = useResources()
20
- const [documentName, setDocumentName] = useInputValue()
21
12
  const [error, setError] = useState()
22
13
 
23
14
  const onCancel = () => {
24
- setDocumentData({})
25
15
  const search = new URLSearchParams({ location }).toString()
26
16
  router.navigate(`@storage/home?${search}`)
27
17
  }
28
18
 
29
- const onFinish = () => {
30
- setDocumentData({})
31
- const search = new URLSearchParams({ location }).toString()
32
- router.navigate(`@storage/home?${search}`)
33
- }
34
-
35
- const onCreateDocument = () => {
36
- if (documentName === '' || documentName === undefined) {
37
- setError('Document name needs to be set')
38
- return ''
39
- }
40
-
41
- createDocument({
42
- type: templateId,
43
- location: location,
44
- name: documentName,
45
- content: documentData
46
- })
47
- .then(() => onFinish())
48
- .catch(error => { setError(error.message) })
49
- }
50
-
51
- const updateFieldData = event => {
52
- setDocumentData(prev => applyFieldChange(prev, event))
19
+ const onSubmit = ({ name, content, type }) => {
20
+ createDocument({ type, location, name, content })
21
+ .then(() => onCancel())
22
+ .catch((err) => setError(err?.message || 'Create failed'))
53
23
  }
54
24
 
55
25
  return (
56
26
  <>
57
- <InputTitle
58
- id="document-name"
59
- type="text"
60
- className="d-block"
61
- placeholder="Untitled document"
62
- value={documentName}
63
- onChange={setDocumentName}
27
+ <GenericResourceForm
28
+ templateId={templateId}
29
+ mode="create"
30
+ onSubmit={onSubmit}
31
+ onCancel={onCancel}
64
32
  />
65
- <Fields data={documentData} onChange={updateFieldData} fields={template?.fields || []} />
66
- { error && <Alert>{error}</Alert>}
67
- <div style={{ display: 'flex', justifyContent: 'flex-end', paddingTop: 'var(--space-l)'}}>
68
- <Button variant="link" onClick={onCancel}>Cancel</Button>
69
- <Button variant="cta" onClick={onCreateDocument}>Create {template?.name}</Button>
70
- </div>
33
+ {error && <Alert>{error}</Alert>}
71
34
  </>
72
35
  )
73
36
  }
@@ -0,0 +1,38 @@
1
+ import React from 'react'
2
+ import { useResource } from '@ossy/sdk-react'
3
+ import { Slot, resourceSlot, View, Title, Text, DelayedRender } from '@ossy/design-system'
4
+
5
+ function GenericResourceCardFallback ({ resource }) {
6
+ if (!resource) return null
7
+ return (
8
+ <View surface="secondary" roundness="m" inset="m" gap="xs">
9
+ <Title variant="secondary">{resource.name}</Title>
10
+ {resource.content?.description && (
11
+ <Text size="s" color="secondary">{resource.content.description}</Text>
12
+ )}
13
+ </View>
14
+ )
15
+ }
16
+
17
+ /** Card-sized resource preview — `resource:{type}/card`. */
18
+ export function GenericResourceCard ({ resourceId, resource: resourceProp }) {
19
+ const { resource: loaded } = useResource(resourceId)
20
+ const resource = resourceProp ?? loaded
21
+
22
+ if (!resource) {
23
+ return (
24
+ <DelayedRender>
25
+ <Text>Loading…</Text>
26
+ </DelayedRender>
27
+ )
28
+ }
29
+
30
+ return (
31
+ <Slot
32
+ name={resourceSlot(resource.type, 'card')}
33
+ resourceId={resourceId}
34
+ resource={resource}
35
+ fallback={<GenericResourceCardFallback resource={resource} />}
36
+ />
37
+ )
38
+ }
@@ -0,0 +1,61 @@
1
+ import React from 'react'
2
+ import { useResource, useResourceTemplate } from '@ossy/sdk-react'
3
+ import {
4
+ Slot,
5
+ resourceSlot,
6
+ View,
7
+ Title,
8
+ Text,
9
+ DelayedRender,
10
+ Fields,
11
+ } from '@ossy/design-system'
12
+ import { ResourceGenericView } from './ResourceGenericView.jsx'
13
+
14
+ function GenericResourceDetailFallback ({ resourceId }) {
15
+ const { resource } = useResource(resourceId)
16
+ const template = useResourceTemplate(resource?.type)
17
+
18
+ if (!resource) {
19
+ return (
20
+ <DelayedRender>
21
+ <Text>Loading…</Text>
22
+ </DelayedRender>
23
+ )
24
+ }
25
+
26
+ if (template?.fields?.length) {
27
+ return (
28
+ <View stack gap="m" inset="m">
29
+ <Title>{resource.name}</Title>
30
+ <Fields data={resource.content || {}} fields={template.fields} />
31
+ </View>
32
+ )
33
+ }
34
+
35
+ return <ResourceGenericView resourceId={resourceId} />
36
+ }
37
+
38
+ /**
39
+ * Generic resource detail host — resolves `resource:{type}/detail` from the slot
40
+ * registry; falls back to template fields or a minimal placeholder.
41
+ */
42
+ export function GenericResourceDetail ({ resourceId }) {
43
+ const { resource } = useResource(resourceId)
44
+
45
+ if (!resource) {
46
+ return (
47
+ <DelayedRender>
48
+ <Text>Loading…</Text>
49
+ </DelayedRender>
50
+ )
51
+ }
52
+
53
+ return (
54
+ <Slot
55
+ name={resourceSlot(resource.type, 'detail')}
56
+ resourceId={resourceId}
57
+ resource={resource}
58
+ fallback={<GenericResourceDetailFallback resourceId={resourceId} />}
59
+ />
60
+ )
61
+ }
@@ -0,0 +1,106 @@
1
+ import React, { useState } from 'react'
2
+ import { useResourceTemplate } from '@ossy/sdk-react'
3
+ import {
4
+ Slot,
5
+ resourceSlot,
6
+ View,
7
+ Title,
8
+ Text,
9
+ InputTitle,
10
+ Button,
11
+ Alert,
12
+ Fields,
13
+ applyFieldChange,
14
+ useInputValue,
15
+ } from '@ossy/design-system'
16
+
17
+ function GenericResourceFormFallback ({
18
+ templateId,
19
+ mode = 'create',
20
+ initialName = '',
21
+ initialContent = {},
22
+ onSubmit,
23
+ onCancel,
24
+ submitLabel,
25
+ }) {
26
+ const template = useResourceTemplate(templateId)
27
+ const [name, setName] = useInputValue(initialName)
28
+ const [content, setContent] = useState(initialContent)
29
+ const [error, setError] = useState()
30
+
31
+ const handleSubmit = () => {
32
+ if (!name?.trim()) {
33
+ setError('Name is required')
34
+ return
35
+ }
36
+ setError(undefined)
37
+ onSubmit?.({ name: name.trim(), content, type: templateId })
38
+ }
39
+
40
+ return (
41
+ <View stack gap="m">
42
+ <InputTitle
43
+ id="resource-name"
44
+ type="text"
45
+ placeholder="Untitled"
46
+ value={name}
47
+ onChange={setName}
48
+ />
49
+ <Fields
50
+ data={content}
51
+ onChange={(event) => setContent((prev) => applyFieldChange(prev, event))}
52
+ fields={template?.fields || []}
53
+ />
54
+ {error && <Alert>{error}</Alert>}
55
+ <View layout="row" gap="s" justifyContent="flex-end">
56
+ {onCancel && <Button variant="link" onClick={onCancel}>Cancel</Button>}
57
+ <Button variant="cta" onClick={handleSubmit}>
58
+ {submitLabel || (mode === 'edit' ? 'Save' : `Create ${template?.name || 'resource'}`)}
59
+ </Button>
60
+ </View>
61
+ </View>
62
+ )
63
+ }
64
+
65
+ /**
66
+ * Generic resource create/edit host — resolves `resource:{type}/form` from the
67
+ * slot registry; falls back to template-driven Fields.
68
+ */
69
+ export function GenericResourceForm ({
70
+ templateId,
71
+ mode = 'create',
72
+ resourceId,
73
+ resource,
74
+ initialName,
75
+ initialContent,
76
+ onSubmit,
77
+ onCancel,
78
+ submitLabel,
79
+ }) {
80
+ if (!templateId) {
81
+ return <Text>Select a resource type to continue.</Text>
82
+ }
83
+
84
+ return (
85
+ <Slot
86
+ name={resourceSlot(templateId, 'form')}
87
+ templateId={templateId}
88
+ mode={mode}
89
+ resourceId={resourceId}
90
+ resource={resource}
91
+ onSubmit={onSubmit}
92
+ onCancel={onCancel}
93
+ fallback={
94
+ <GenericResourceFormFallback
95
+ templateId={templateId}
96
+ mode={mode}
97
+ initialName={initialName ?? resource?.name}
98
+ initialContent={initialContent ?? resource?.content ?? {}}
99
+ onSubmit={onSubmit}
100
+ onCancel={onCancel}
101
+ submitLabel={submitLabel}
102
+ />
103
+ }
104
+ />
105
+ )
106
+ }
@@ -1,7 +1,6 @@
1
1
  import React from 'react'
2
2
  import { useResource } from '@ossy/sdk-react'
3
3
  import {
4
- Stack,
5
4
  Image
6
5
  } from '@ossy/design-system'
7
6
 
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import { useResource } from '@ossy/sdk-react'
3
- import { Stack } from '@ossy/design-system'
3
+ import { View } from '@ossy/design-system'
4
4
 
5
5
  export const PDFResource = ({
6
6
  resourceId
@@ -8,13 +8,13 @@ export const PDFResource = ({
8
8
  const { resource } = useResource(resourceId)
9
9
 
10
10
  return (
11
- <Stack bordered>
12
- <Stack.Item fill>
13
- <Stack bordered>
11
+ <View stack bordered>
12
+ <View.Item fill>
13
+ <View stack bordered>
14
14
 
15
15
 
16
16
 
17
- <Stack.Item fill style={{ padding: '16px 8px' }}>
17
+ <View.Item fill style={{ padding: '16px 8px' }}>
18
18
 
19
19
  <div style={{ display: 'flex', justifyContent: 'center' }}>
20
20
  <embed
@@ -24,10 +24,10 @@ export const PDFResource = ({
24
24
  height="800px"
25
25
  />
26
26
  </div>
27
- </Stack.Item>
27
+ </View.Item>
28
28
 
29
- </Stack>
30
- </Stack.Item>
31
- </Stack>
29
+ </View>
30
+ </View.Item>
31
+ </View>
32
32
  )
33
33
  }
@@ -55,7 +55,7 @@ export const ResourceContentPage = (props) => {
55
55
 
56
56
  <View layout="row" style={{ justifyContent: 'space-between', borderRadius: 'var(--space-l)', padding: 'var(--space-xxs)', background: 'transparent' }}>
57
57
  <Button variant="neutral" onClick={() => router.back()} style={{ padding: 'var(--space-xs)', borderRadius: '50%' }}>
58
- <Icon name="Previous" />
58
+ <Icon name="chevron-left" />
59
59
  </Button>
60
60
  </View>
61
61
 
@@ -1,13 +1,13 @@
1
1
  import React from 'react'
2
2
  import { useResource, AsyncStatus, useWorkspace } from '@ossy/sdk-react'
3
3
  import { Guide, Text, DelayedRender } from '@ossy/design-system'
4
- import { DocumentView } from './DocumentView.jsx'
5
4
  import { DocumentEdit } from './DocumentEdit.jsx'
6
5
  import { ImageResource } from './ImageResource.jsx'
7
6
  import { AudioResource } from './AudioResource.jsx'
8
7
  import { VideoResource } from './VideoResource.jsx'
9
8
  import { PDFResource } from './PDFResource.jsx'
10
9
  import { ResourceGenericView } from './ResourceGenericView.jsx'
10
+ import { GenericResourceDetail } from './GenericResourceDetail.jsx'
11
11
 
12
12
  export const ResourceFactory = ({
13
13
  resourceId,
@@ -39,8 +39,12 @@ export const ResourceFactory = ({
39
39
  }
40
40
 
41
41
  if (workspace?.resourceTemplates?.find(({ id }) => id === resource.type)) {
42
- if (mode === 'View') return <DocumentView resourceId={resourceId} onClose={_onClose} mode={mode} />
43
- if (mode === 'Edit') return <DocumentEdit resourceId={resourceId} onClose={_onClose} mode={mode} form={form} />
42
+ if (mode === 'View') {
43
+ return <GenericResourceDetail resourceId={resourceId} />
44
+ }
45
+ if (mode === 'Edit') {
46
+ return <DocumentEdit resourceId={resourceId} form={form} />
47
+ }
44
48
  }
45
49
 
46
50
  if (['image/jpeg', 'image/png'].includes(resource.type)) {
@@ -1,9 +1,8 @@
1
1
  import React from 'react'
2
2
  import { useResource } from '@ossy/sdk-react'
3
3
  import {
4
- Stack,
5
4
  View,
6
- Icon2,
5
+ Icon,
7
6
  } from '@ossy/design-system'
8
7
 
9
8
  export const ResourceGenericView = ({
@@ -12,21 +11,21 @@ export const ResourceGenericView = ({
12
11
  const { resource } = useResource(resourceId)
13
12
 
14
13
  return (
15
- <Stack bordered>
16
- <Stack.Item fill>
17
- <Stack bordered>
14
+ <View stack bordered>
15
+ <View.Item fill>
16
+ <View stack bordered>
18
17
 
19
- <Stack.Item fill style={{ padding: '16px 8px' }}>
18
+ <View.Item fill style={{ padding: '16px 8px' }}>
20
19
 
21
20
  <View alignItems="center" justifyContent="center" style={{ height: '100%', maxHeight: '400px' }}>
22
21
  <View roundness="m" style={{ border: '1px solid var(--separator)', padding: 'var(--space-xl) var(--space-xl)'}}>
23
- <Icon2 size="xl" name="file" />
22
+ <Icon size="xl" name="file" />
24
23
  </View>
25
24
  </View>
26
- </Stack.Item>
25
+ </View.Item>
27
26
 
28
- </Stack>
29
- </Stack.Item>
30
- </Stack>
27
+ </View>
28
+ </View.Item>
29
+ </View>
31
30
  )
32
31
  }
@@ -4,7 +4,7 @@ import { useWorkspace } from '@ossy/sdk-react'
4
4
  import {
5
5
  Dropdown,
6
6
  DropZone,
7
- Icon2,
7
+ Icon,
8
8
  Image,
9
9
  View,
10
10
  Text,
@@ -73,7 +73,7 @@ function InlineFolderRow({ name, onChange, onSave, onCancel }) {
73
73
  }}
74
74
  >
75
75
  <View gap="m" layout="row" alignItems="center" style={{ flexGrow: 1, padding: '12px 0 12px 20px' }}>
76
- <Icon2 size="s" name="folder" style={{ fill: 'hsl(0, 0%, 60%)' }} />
76
+ <Icon size="s" name="folder" style={{ fill: 'hsl(0, 0%, 60%)' }} />
77
77
  <input
78
78
  ref={inputRef}
79
79
  value={name}
@@ -163,7 +163,7 @@ function ResourceListItem({
163
163
 
164
164
  if (resource?.type === 'directory') {
165
165
  return (
166
- <Icon2 size="s" name="folder" style={{ fill: 'hsl(0, 0%, 60%)'}} />
166
+ <Icon size="s" name="folder" style={{ fill: 'hsl(0, 0%, 60%)'}} />
167
167
  )
168
168
  }
169
169
 
@@ -178,7 +178,7 @@ function ResourceListItem({
178
178
  }
179
179
 
180
180
  return (
181
- <Icon2 size="s" name={resourceTemplates[resource.type]?.icon || 'file'} style={{ fill: 'hsl(0, 0%, 80%)'}} />
181
+ <Icon size="s" name={resourceTemplates[resource.type]?.icon || 'file'} style={{ fill: 'hsl(0, 0%, 80%)'}} />
182
182
  )
183
183
  }
184
184
 
@@ -4,7 +4,6 @@ import {
4
4
  Title,
5
5
  Overlay,
6
6
  Switch,
7
- Stack,
8
7
  Guide,
9
8
  View,
10
9
  Text,
@@ -133,7 +132,7 @@ export const ResourcePanel = ({
133
132
  }
134
133
 
135
134
  return (
136
- <Stack surface="primary" bordered style={{ height: '100%', width: '50%' }}>
135
+ <View stack surface="primary" bordered style={{ height: '100%', width: '50%' }}>
137
136
 
138
137
  <style href="@design-system/scroll" precedence='low'>{`
139
138
  [data-scroll-hide] {
@@ -148,23 +147,23 @@ export const ResourcePanel = ({
148
147
 
149
148
  {
150
149
  !!error && (
151
- <Stack.Item>
150
+ <View.Item>
152
151
  {error}
153
- </Stack.Item>
152
+ </View.Item>
154
153
  )
155
154
  }
156
155
 
157
- <Stack.Item>
156
+ <View.Item>
158
157
 
159
- <Stack horizontal style={{ height: '48px', alignItems: 'center', gap: '4px' }}>
158
+ <View stack horizontal style={{ height: '48px', alignItems: 'center', gap: '4px' }}>
160
159
 
161
160
  <Switch on={panelViewModes['panel-header']}>
162
161
 
163
162
  <Switch.Case match={[ViewMode.View]}>
164
163
 
165
- <Stack.Item fill surface="primary" style={{ padding: '4px 8px' }}>
164
+ <View.Item fill surface="primary" style={{ padding: '4px 8px' }}>
166
165
  <Title as="h3" variant="tertiary">{resourceName}</Title>
167
- </Stack.Item>
166
+ </View.Item>
168
167
 
169
168
  <Button prefix="trash-empty" variant="command-danger" onClick={onRemoveResource}/>
170
169
  <Button prefix="pen" variant="command" onClick={() => setPanelViewModes(x => ({ ...x, 'panel-header': ViewMode.Edit }))} />
@@ -174,7 +173,7 @@ export const ResourcePanel = ({
174
173
 
175
174
  <Switch.Case match={[ViewMode.Edit]}>
176
175
 
177
- <Stack.Item fill surface="primary" style={{ padding: '4px 8px' }}>
176
+ <View.Item fill surface="primary" style={{ padding: '4px 8px' }}>
178
177
  <InputTitle
179
178
  id="document-name"
180
179
  type="text"
@@ -183,7 +182,7 @@ export const ResourcePanel = ({
183
182
  onChange={setResourceName}
184
183
  onBlur={onRenameResource}
185
184
  />
186
- </Stack.Item>
185
+ </View.Item>
187
186
 
188
187
  <Button prefix="close" variant="command" onClick={() => setPanelViewModes(x => ({ ...x, 'panel-header': ViewMode.View }))} />
189
188
  <Button prefix="check" variant="command" onClick={onRenameResource}/>
@@ -192,11 +191,11 @@ export const ResourcePanel = ({
192
191
 
193
192
  </Switch>
194
193
 
195
- </Stack>
194
+ </View>
196
195
 
197
- </Stack.Item>
196
+ </View.Item>
198
197
 
199
- <Stack.Item fill={true} data-scroll-hide style={{ display: 'flex', flexDirection: 'column', height: '100%', overflowY: 'auto' }}>
198
+ <View.Item fill={true} data-scroll-hide style={{ display: 'flex', flexDirection: 'column', height: '100%', overflowY: 'auto' }}>
200
199
 
201
200
  {panels.map(({ content: Content, ...panel }) => {
202
201
  const isExpanded = [ViewMode.View, ViewMode.Edit].includes(panelViewModes[panel.id])
@@ -239,7 +238,7 @@ export const ResourcePanel = ({
239
238
  )
240
239
  })}
241
240
 
242
- </Stack.Item>
241
+ </View.Item>
243
242
 
244
243
  {
245
244
  overlay === Overlays.RemoveDirectory && (
@@ -267,6 +266,6 @@ export const ResourcePanel = ({
267
266
  )
268
267
  }
269
268
 
270
- </Stack>
269
+ </View>
271
270
  )
272
271
  }
package/src/Upload.jsx CHANGED
@@ -1,5 +1,5 @@
1
1
  import React, { useState, useEffect } from 'react'
2
- import { Button, View, Text, Upload as _Upload, Icon2 } from '@ossy/design-system'
2
+ import { Button, View, Text, Upload as _Upload, Icon } from '@ossy/design-system'
3
3
  import { useRouter } from '@ossy/router-react'
4
4
 
5
5
  const FlowStage = {
@@ -155,9 +155,9 @@ function UploadImgPreview({ file, size = "32px" }) {
155
155
 
156
156
  function UploadStatus({ status }) {
157
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" />
158
+ if (status === 'uploading') return <Icon name="spinner" size="s" animation="rotate" />
159
+ if (status === 'uploaded') return <Icon name="check" size="s" />
160
+ if (status === 'error') return <Icon name="close" size="s" />
161
161
  }
162
162
 
163
163
  function getFlowStage(files = []) {
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import { useResource } from '@ossy/sdk-react'
3
- import { Stack } from '@ossy/design-system'
3
+ import { View } from '@ossy/design-system'
4
4
 
5
5
  export const VideoResource = ({
6
6
  resourceId
@@ -8,8 +8,8 @@ export const VideoResource = ({
8
8
  const { resource } = useResource(resourceId)
9
9
 
10
10
  return (
11
- <Stack bordered>
12
- <Stack.Item fill style={{ padding: '16px 8px' }}>
11
+ <View stack bordered>
12
+ <View.Item fill style={{ padding: '16px 8px' }}>
13
13
 
14
14
  <div style={{ display: 'flex', justifyContent: 'center' }}>
15
15
  <video
@@ -18,7 +18,7 @@ export const VideoResource = ({
18
18
  style={{ width: 'auto', height: '400px', margin: 'var(--space-l) auto' }}
19
19
  />
20
20
  </div>
21
- </Stack.Item>
22
- </Stack>
21
+ </View.Item>
22
+ </View>
23
23
  )
24
24
  }
package/src/index.js CHANGED
@@ -15,6 +15,9 @@ export * from './ResourceDescription.jsx'
15
15
  export * from './ResourceDetails.jsx'
16
16
  export * from './ResourceDialogMove.jsx'
17
17
  export * from './ResourceFactory.jsx'
18
+ export * from './GenericResourceDetail.jsx'
19
+ export * from './GenericResourceForm.jsx'
20
+ export * from './GenericResourceCard.jsx'
18
21
  export * from './ResourceGenericView.jsx'
19
22
  export * from './ResourceList.jsx'
20
23
  export * from './ResourcePage.jsx'
@@ -0,0 +1,47 @@
1
+ import React, { useState } from 'react'
2
+ import { useResources } from '@ossy/sdk-react'
3
+ import { View } from '@ossy/design-system'
4
+ import { useRouter } from '@ossy/router-react'
5
+ import { GenericResourceForm } from './GenericResourceForm.jsx'
6
+
7
+ export const metadata = {
8
+ id: 'resources/create/generic',
9
+ path: {
10
+ en: '/resources/create/:templateId',
11
+ sv: '/resources/skapa/:templateId',
12
+ },
13
+ }
14
+
15
+ /** Generic resource create page — slot lookup with template Fields fallback. */
16
+ export default function GenericResourceCreatePage () {
17
+ const router = useRouter()
18
+ const templateId = decodeURIComponent(router.params.templateId || '')
19
+ const location = router.searchParams.location
20
+ const { createDocument } = useResources()
21
+ const [error, setError] = useState()
22
+
23
+ const onCancel = () => {
24
+ const search = location ? new URLSearchParams({ location }).toString() : ''
25
+ router.navigate(`@storage/home${search ? `?${search}` : ''}`)
26
+ }
27
+
28
+ const onSubmit = ({ name, content, type }) => {
29
+ createDocument({ type, location, name, content })
30
+ .then(() => onCancel())
31
+ .catch((err) => setError(err?.message || 'Create failed'))
32
+ }
33
+
34
+ return (
35
+ <View layout="off-center-m" style={{ height: '100%' }}>
36
+ <View slot="content" surface="primary" roundness="m" inset="m">
37
+ <GenericResourceForm
38
+ templateId={templateId}
39
+ mode="create"
40
+ onSubmit={onSubmit}
41
+ onCancel={onCancel}
42
+ />
43
+ {error && <View inset="s"><span style={{ color: 'var(--danger)' }}>{error}</span></View>}
44
+ </View>
45
+ </View>
46
+ )
47
+ }
@@ -0,0 +1,27 @@
1
+ import React from 'react'
2
+ import { View } from '@ossy/design-system'
3
+ import { useRouter } from '@ossy/router-react'
4
+ import { GenericResourceDetail } from './GenericResourceDetail.jsx'
5
+
6
+ export const metadata = {
7
+ id: '@resource',
8
+ title: 'Resource',
9
+ path: {
10
+ en: '/resources/item/:resourceId',
11
+ sv: '/resources/objekt/:resourceId',
12
+ },
13
+ }
14
+
15
+ /** Generic resource detail page — slot lookup with template fallback. */
16
+ export default function ResourceDetailPage () {
17
+ const router = useRouter()
18
+ const resourceId = router.params.resourceId
19
+
20
+ return (
21
+ <View layout="off-center-m" style={{ height: '100%' }}>
22
+ <View slot="content" surface="primary" roundness="m" inset="m">
23
+ <GenericResourceDetail resourceId={resourceId} />
24
+ </View>
25
+ </View>
26
+ )
27
+ }