@stonecrop/desktop 0.16.6 → 0.18.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/README.md +62 -47
- package/dist/desktop.d.ts +2 -12
- package/dist/desktop.js +411 -388
- package/dist/desktop.js.map +1 -1
- package/dist/src/types/index.d.ts +8 -9
- package/dist/src/types/index.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/components/Desktop.vue +143 -110
- package/src/types/index.ts +8 -9
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @stonecrop/desktop
|
|
2
2
|
|
|
3
|
-
A three-view UI shell for Stonecrop applications. Renders a doctype list → records list → record form layout driven
|
|
3
|
+
A three-view UI shell for Stonecrop applications. Renders a doctype list → records list → record form layout driven by the host application's Registry and HST state.
|
|
4
|
+
|
|
5
|
+
Desktop reads through Stonecrop — on navigating to a list or a record it calls `Stonecrop.getRecords` / `Stonecrop.getRecord`, which fetch through the host's registered `DataClient` and write into HST. Writes are the host's: Desktop emits `action` and the host dispatches it.
|
|
4
6
|
|
|
5
7
|
## Features
|
|
6
8
|
|
|
@@ -20,32 +22,42 @@ Desktop requires `@stonecrop/stonecrop` to be installed and the `StonecropPlugin
|
|
|
20
22
|
|
|
21
23
|
```typescript
|
|
22
24
|
import { createApp } from 'vue'
|
|
23
|
-
import Stonecrop from '@stonecrop/stonecrop'
|
|
24
|
-
import {
|
|
25
|
+
import Stonecrop, { Doctype } from '@stonecrop/stonecrop'
|
|
26
|
+
import { RestDataClient } from './client'
|
|
27
|
+
import planDoctype from './doctypes/plan.json'
|
|
28
|
+
|
|
29
|
+
const app = createApp(App)
|
|
30
|
+
|
|
31
|
+
// The plugin constructs the Registry itself and provides it as `$registry` — it does not
|
|
32
|
+
// accept one. Register doctypes on that instance, after install.
|
|
33
|
+
app.use(Stonecrop, { router, client: new RestDataClient() })
|
|
34
|
+
|
|
35
|
+
const registry = app.config.globalProperties.$registry
|
|
36
|
+
registry.addDoctype(Doctype.fromObject(planDoctype))
|
|
25
37
|
|
|
26
|
-
|
|
38
|
+
app.mount('#app')
|
|
27
39
|
```
|
|
28
40
|
|
|
41
|
+
`client` is the `DataClient` Desktop reads through. It can also be supplied later with
|
|
42
|
+
`stonecrop.setClient(client)` — Nuxt hosts do this from a plugin via `useStonecropSetup().registerClient`.
|
|
43
|
+
|
|
29
44
|
## Basic Usage
|
|
30
45
|
|
|
31
46
|
```vue
|
|
32
47
|
<script setup lang="ts">
|
|
33
48
|
import { Desktop } from '@stonecrop/desktop'
|
|
34
|
-
import
|
|
35
|
-
import { useStonecrop } from '@stonecrop/stonecrop'
|
|
36
|
-
|
|
37
|
-
const { stonecrop } = useStonecrop()
|
|
49
|
+
import { useClientAction } from '@stonecrop/stonecrop'
|
|
38
50
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
51
|
+
// Runs an action's clientHandler when the doctype declares one, dispatches to the server
|
|
52
|
+
// otherwise, and reconciles the store and the route with the identity the server settled on.
|
|
53
|
+
// In a Nuxt host this is auto-imported — drop the import line.
|
|
54
|
+
const { run } = useClientAction()
|
|
43
55
|
</script>
|
|
44
56
|
|
|
45
57
|
<template>
|
|
46
58
|
<Desktop
|
|
47
59
|
:available-doctypes="['plan', 'recipe', 'resource']"
|
|
48
|
-
@action="
|
|
60
|
+
@action="run"
|
|
49
61
|
/>
|
|
50
62
|
</template>
|
|
51
63
|
```
|
|
@@ -56,25 +68,27 @@ async function handleAction(payload: ActionEventPayload) {
|
|
|
56
68
|
|------|------|---------|-------------|
|
|
57
69
|
| `availableDoctypes` | `string[]` | `[]` | Doctype slugs to display in the doctypes list |
|
|
58
70
|
| `routeAdapter` | `RouteAdapter` | — | Custom routing layer (required for Nuxt/custom hosts) |
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
|
|
72
|
+
Record identity is not a prop. It is declared per doctype (`primaryKey`, falling back to `id`) and
|
|
73
|
+
resolved through `Doctype.getRecordId`, so a row's link always matches the key the record is stored
|
|
74
|
+
under. One shell renders many doctypes, so a single prop could never answer this correctly.
|
|
61
75
|
|
|
62
76
|
## Emitted Events
|
|
63
77
|
|
|
64
78
|
| Event | When |
|
|
65
79
|
|-------|------|
|
|
66
|
-
| `action` | User triggers an FSM transition or
|
|
80
|
+
| `action` | User triggers a declared action — an FSM transition or a Command |
|
|
67
81
|
| `navigate` | Desktop wants to change views |
|
|
68
82
|
| `record:open` | User opens a specific record |
|
|
69
|
-
| `load-records` | Desktop
|
|
70
|
-
| `load-record` | Desktop
|
|
83
|
+
| `load-records` | Desktop is about to read a records list (notification — Desktop performs the read) |
|
|
84
|
+
| `load-record` | Desktop is about to read a single record (notification — Desktop performs the read) |
|
|
71
85
|
|
|
72
86
|
See [api.md](./api.md) for payload type definitions.
|
|
73
87
|
|
|
74
88
|
### Event Handling Notes
|
|
75
89
|
|
|
76
|
-
- **action**: Desktop
|
|
77
|
-
- **load-records / load-record**:
|
|
90
|
+
- **action**: Desktop merges `Doctype.getAvailableTransitions` and `Doctype.getAvailableCommands`, both resolved against `Stonecrop.getRecordState`, into one Actions dropdown. **Desktop never dispatches** — that is the host application's responsibility.
|
|
91
|
+
- **load-records / load-record**: notifications, not fetch requests. Desktop reads through `Stonecrop.getRecords` / `Stonecrop.getRecord` itself, using the registered `DataClient`; these events announce that read so a host can hang analytics off it. A host that fetches here races Desktop's own read into the same HST key. `load-record` is not emitted for a draft, which has nothing to fetch.
|
|
78
92
|
|
|
79
93
|
## Router Adapter
|
|
80
94
|
|
|
@@ -111,37 +125,36 @@ function useCustomRouteAdapter(): RouteAdapter {
|
|
|
111
125
|
|
|
112
126
|
## Handling `action` Events
|
|
113
127
|
|
|
114
|
-
|
|
128
|
+
Dispatching is not the whole job: the result has to land in HST under the identity the *server*
|
|
129
|
+
settled on, which for a newly created record is not the id that was dispatched.
|
|
115
130
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
131
|
+
Bind `@action` to `useClientAction`'s `run`, as in Basic Usage above. It runs an action's
|
|
132
|
+
`clientHandler` when it has one, dispatches otherwise, and reconciles the store and the route. It
|
|
133
|
+
lives in `@stonecrop/stonecrop`, so every Vue 3 host gets the same one; Nuxt hosts also get it as an
|
|
134
|
+
auto-import from `@stonecrop/nuxt`.
|
|
119
135
|
|
|
120
|
-
|
|
136
|
+
Three things are adjustable, for the cases that genuinely differ between applications:
|
|
121
137
|
|
|
122
|
-
|
|
123
|
-
|
|
138
|
+
| Option | Replaces | Use it for |
|
|
139
|
+
|--------|----------|------------|
|
|
140
|
+
| `buildArgs` | the `[{ id, data }]` envelope | a backend expecting another argument shape |
|
|
141
|
+
| `followRecord` | `router.replace('/{doctype}/{id}')` | a locale prefix, a nested route, or staying put |
|
|
142
|
+
| `onError` | a blocking `window.alert` | your own notification system |
|
|
124
143
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
for (const [field, value] of Object.entries(payload.data)) {
|
|
128
|
-
const path = `${payload.doctype}.${payload.recordId}.${field}`
|
|
129
|
-
if (store.has(path) && store.get(path) !== value) {
|
|
130
|
-
store.set(path, value)
|
|
131
|
-
}
|
|
132
|
-
}
|
|
144
|
+
`args` is an opaque JSON array: nothing validates it, so both ends of your own stack have to agree.
|
|
145
|
+
`examples/desktop` uses positional `[recordId, data]` and supplies `buildArgs` to say so.
|
|
133
146
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
147
|
+
Resolving a record's identity and keying it into HST are deliberately **not** adjustable. That rule
|
|
148
|
+
is declared on the doctype and re-derived server-side by the adapter, and every host that re-derived
|
|
149
|
+
it client-side got it wrong. If you dispatch through `Stonecrop.dispatchAction` directly instead of
|
|
150
|
+
using this composable, that method still files the returned record under the settled identity — you
|
|
151
|
+
cannot store it under the wrong key by accident. What you lose is the stale-key cleanup and the
|
|
152
|
+
route-follow, which need the id you dispatched.
|
|
138
153
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
```
|
|
154
|
+
Do not copy form data into HST before dispatching. Desktop already hands you the current form
|
|
155
|
+
snapshot in `payload.data`, and an unsaved record has no HST node to write to.
|
|
156
|
+
|
|
157
|
+
See the [host integration guide](../docs/guides/desktop-integration.md) for the full wiring.
|
|
145
158
|
|
|
146
159
|
## Provide / Inject
|
|
147
160
|
|
|
@@ -150,8 +163,10 @@ Desktop provides a `desktopMethods` object that child components (slot content)
|
|
|
150
163
|
```typescript
|
|
151
164
|
import { inject } from 'vue'
|
|
152
165
|
|
|
153
|
-
const { navigateToDoctype, openRecord, createNewRecord,
|
|
166
|
+
const { navigateToDoctype, openRecord, createNewRecord, emitAction } =
|
|
154
167
|
inject('desktopMethods')!
|
|
155
168
|
```
|
|
156
169
|
|
|
157
|
-
`emitAction(name, data?)` is a convenience wrapper for emitting an `action` event from deeply nested slot content without passing refs down manually.
|
|
170
|
+
`emitAction(name, data?)` is a convenience wrapper for emitting an `action` event from deeply nested slot content without passing refs down manually.
|
|
171
|
+
|
|
172
|
+
Desktop blesses no action name. It used to expose a `handleDelete` method and a `confirmFn` prop, which together emitted a hardcoded `DELETE` action and prompted before it — but no doctype declares `DELETE`, so it failed on every click, and only the host knows which of its actions are destructive. Removal is a workflow outcome: declare an action with a `nextState` such as `Archived` or `CANCELLED`, and confirm inside your own `@action` handler before dispatching.
|
package/dist/desktop.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ActionEventPayload } from '@stonecrop/stonecrop';
|
|
1
2
|
import ActionSet from './components/ActionSet.vue';
|
|
2
3
|
import CommandPalette from './components/CommandPalette.vue';
|
|
3
4
|
import Desktop from './components/Desktop.vue';
|
|
@@ -10,18 +11,7 @@ import SheetNav from './components/SheetNav.vue';
|
|
|
10
11
|
*/
|
|
11
12
|
export declare type ActionElements = ButtonElement | DropdownElement;
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
* Payload emitted with the 'action' event when the user triggers an FSM transition
|
|
15
|
-
* @public
|
|
16
|
-
*/
|
|
17
|
-
export declare type ActionEventPayload = {
|
|
18
|
-
/** The FSM transition name (e.g. 'SAVE', 'SUBMIT', 'APPROVE') */
|
|
19
|
-
name: string;
|
|
20
|
-
doctype: string;
|
|
21
|
-
recordId: string;
|
|
22
|
-
/** Snapshot of the form data at the time the action was triggered */
|
|
23
|
-
data: Record<string, any>;
|
|
24
|
-
};
|
|
14
|
+
export { ActionEventPayload }
|
|
25
15
|
|
|
26
16
|
export { ActionSet }
|
|
27
17
|
|