@stonecrop/nuxt 0.33.0 → 0.34.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/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stonecrop/nuxt",
3
3
  "configKey": "stonecrop",
4
- "version": "0.33.0",
4
+ "version": "0.34.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "unknown"
@@ -0,0 +1,11 @@
1
+ import type { RouteAdapter } from '@stonecrop/desktop';
2
+ import { type ComputedRef } from 'vue';
3
+ type DocBuilderRouteAdapterOptions = {
4
+ doctype?: ComputedRef<string | undefined>;
5
+ };
6
+ /**
7
+ * Minimal RouteAdapter for DocBuilder pages hosted inside Desktop.
8
+ * @public
9
+ */
10
+ export declare function useDocBuilderRouteAdapter(options?: DocBuilderRouteAdapterOptions): RouteAdapter;
11
+ export {};
@@ -0,0 +1,21 @@
1
+ import { computed } from "vue";
2
+ import { useRoute, useRouter } from "nuxt/app";
3
+ export function useDocBuilderRouteAdapter(options = {}) {
4
+ const route = useRoute();
5
+ const router = useRouter();
6
+ const doctype = options.doctype ?? computed(() => route.params.doctype);
7
+ return {
8
+ getCurrentDoctype: () => doctype.value ?? "",
9
+ getCurrentRecordId: () => "",
10
+ getCurrentView: () => "doctypes",
11
+ navigate: (target) => {
12
+ if (target.view === "doctypes") {
13
+ void router.push("/");
14
+ } else if (target.view === "records" && target.doctype) {
15
+ void router.push(`/${target.doctype}`);
16
+ } else if (target.view === "record" && target.doctype && target.recordId) {
17
+ void router.push(`/${target.doctype}/${target.recordId}`);
18
+ }
19
+ }
20
+ };
21
+ }
@@ -1,66 +1,62 @@
1
1
  <template>
2
- <div class="docbuilder-page">
3
- <div v-if="loading" style="padding: 2rem; text-align: center">Loading...</div>
4
-
5
- <div v-else>
6
- <!-- Validation Panel -->
7
- <div v-if="validationIssues.length > 0 && !warningsDismissed" class="validation-panel">
8
- <div v-if="errorCount > 0" class="validation-errors">
9
- <strong>⚠️ {{ errorCount }} Error(s) — Cannot Save</strong>
10
- <ul>
11
- <li v-for="(issue, idx) in validationIssues.filter(i => i.severity === 'error')" :key="`err-${idx}`">
12
- <code v-if="issue.fieldname">{{ issue.fieldname }}:</code> {{ issue.message }}
13
- </li>
14
- </ul>
15
- </div>
16
- <div v-if="warningCount > 0" class="validation-warnings">
17
- <strong>⚡ {{ warningCount }} Warning(s)</strong>
18
- <button class="dismiss-button" @click="warningsDismissed = true">Dismiss</button>
2
+ <ClientOnly>
3
+ <Desktop class="docbuilder-desktop" :route-adapter="routeAdapter" :host-actions="docbuilderActions">
4
+ <div v-if="loading" class="docbuilder-loading">Loading...</div>
5
+ <div v-else class="docbuilder-page">
6
+ <div v-if="validationIssues.length > 0 && !warningsDismissed" class="validation-panel">
7
+ <div v-if="errorCount > 0" class="validation-errors">
8
+ <strong>⚠️ {{ errorCount }} Error(s) — Cannot Save</strong>
9
+ <ul>
10
+ <li v-for="(issue, idx) in validationIssues.filter(i => i.severity === 'error')" :key="`err-${idx}`">
11
+ <code v-if="issue.fieldname">{{ issue.fieldname }}:</code> {{ issue.message }}
12
+ </li>
13
+ </ul>
14
+ </div>
15
+ <div v-if="warningCount > 0" class="validation-warnings">
16
+ <strong>⚡ {{ warningCount }} Warning(s)</strong>
17
+ <button class="dismiss-button" @click="warningsDismissed = true">Dismiss</button>
18
+ </div>
19
19
  </div>
20
- </div>
21
20
 
22
- <!-- `schema` is empty on purpose: AFieldset renders an AForm from it only as slot fallback,
23
- and each of these fieldsets fills the slot with its own panel. -->
24
- <AFieldset label="Workflow" :schema="[]" :collapsible="true">
25
- <div class="builder-workflow">
26
- <StateEditor
27
- v-if="workflowConfig && workflowConfig.states && workflowConfig.states.length > 0"
28
- v-model="workflowConfig"
29
- v-model:layout="layout"
30
- node-container-class="node-editor" />
31
- <div v-else class="empty-workflow">
32
- <p class="empty-workflow-hint">No workflow yet. Name the first state to start building the workflow.</p>
33
- <div class="empty-workflow-form">
34
- <input v-model="newStateName" type="text" placeholder="e.g. Draft" @keyup.enter="seedWorkflow" />
35
- <button class="btn-seed" type="button" :disabled="!newStateName.trim()" @click="seedWorkflow">
36
- Add first state
37
- </button>
21
+ <AFieldset label="Workflow" :schema="[]" :collapsible="true">
22
+ <div class="builder-workflow">
23
+ <StateEditor
24
+ v-if="workflowConfig && workflowConfig.states && workflowConfig.states.length > 0"
25
+ v-model="workflowConfig"
26
+ v-model:layout="layout"
27
+ node-container-class="node-editor" />
28
+ <div v-else class="empty-workflow">
29
+ <p class="empty-workflow-hint">No workflow yet. Name the first state to start building the workflow.</p>
30
+ <div class="empty-workflow-form">
31
+ <input v-model="newStateName" type="text" placeholder="e.g. Draft" @keyup.enter="seedWorkflow" />
32
+ <button class="btn-seed" type="button" :disabled="!newStateName.trim()" @click="seedWorkflow">
33
+ Add first state
34
+ </button>
35
+ </div>
38
36
  </div>
39
37
  </div>
40
- </div>
41
- </AFieldset>
38
+ </AFieldset>
42
39
 
43
- <AFieldset label="Actions" :schema="[]" :collapsible="true">
44
- <DocBuilderActionsPanel v-model="workflowConfig" />
45
- </AFieldset>
40
+ <AFieldset label="Actions" :schema="[]" :collapsible="true">
41
+ <DocBuilderActionsPanel v-model="workflowConfig" />
42
+ </AFieldset>
46
43
 
47
- <AFieldset label="Schema" :schema="[]" :collapsible="true">
48
- <DocBuilderFieldsPanel v-model="fields" />
49
- </AFieldset>
44
+ <AFieldset label="Schema" :schema="[]" :collapsible="true">
45
+ <DocBuilderFieldsPanel v-model="fields" />
46
+ </AFieldset>
50
47
 
51
- <div v-if="saveMessage" class="builder-actions">
52
- <span class="save-message" :class="saveMessage.type">{{ saveMessage.text }}</span>
48
+ <div v-if="saveMessage" class="builder-actions">
49
+ <span class="save-message" :class="saveMessage.type">{{ saveMessage.text }}</span>
50
+ </div>
53
51
  </div>
54
-
55
- <ActionSet :elements="docbuilderActions" @action-click="handleAction" />
56
- </div>
57
- </div>
52
+ </Desktop>
53
+ </ClientOnly>
58
54
  </template>
59
55
 
60
56
  <script setup>
61
57
  import { AFieldset } from "@stonecrop/aform";
62
58
  import { StateEditor } from "@stonecrop/node-editor";
63
- import { ActionSet } from "@stonecrop/desktop";
59
+ import { Desktop } from "@stonecrop/desktop";
64
60
  import { WorkflowMeta } from "@stonecrop/schema";
65
61
  import { ref, watch, computed, onMounted } from "vue";
66
62
  import { useRoute, useRouter } from "nuxt/app";
@@ -69,6 +65,7 @@ import DocBuilderFieldsPanel from "../components/DocBuilderFieldsPanel.vue";
69
65
  const route = useRoute();
70
66
  const router = useRouter();
71
67
  const doctypeName = computed(() => route.params.doctype);
68
+ const routeAdapter = useDocBuilderRouteAdapter({ doctype: doctypeName });
72
69
  const loading = ref(true);
73
70
  const saving = ref(false);
74
71
  const warningsDismissed = ref(false);
@@ -128,10 +125,6 @@ async function saveToDisk() {
128
125
  body: {
129
126
  doctype: doctypeName.value,
130
127
  fields: fields.value,
131
- // Merge the author's node arrangement back into the workflow (WorkflowMeta.layout) so it
132
- // persists. The layout ref is kept separate in memory (topology-only workflowConfig) and
133
- // only rejoined here at the I/O boundary; omitted when empty to avoid churn on doctypes
134
- // that were never manually arranged.
135
128
  workflow: workflowConfig.value ? { ...workflowConfig.value, ...Object.keys(layout.value).length > 0 && { layout: layout.value } } : null
136
129
  }
137
130
  });
@@ -151,11 +144,8 @@ const docbuilderActions = computed(() => [
151
144
  },
152
145
  { type: "button", label: "Back", action: () => void router.push("/docbuilder") }
153
146
  ]);
154
- function handleAction(_label, action) {
155
- if (action) void action();
156
- }
157
147
  </script>
158
148
 
159
149
  <style scoped>
160
- .docbuilder-page{background:var(--sc-form-background,#fff);box-sizing:border-box;min-height:100vh;padding:2rem}.builder-workflow{min-height:8rem;padding:.5em 1em}:deep(.node-editor){height:40vh;overflow:hidden;width:100%}.empty-workflow{padding:1rem 0}.empty-workflow-hint{color:#9ca3af;font-style:italic;margin:0 0 .75rem}.empty-workflow-form{align-items:center;display:flex;gap:.5rem}.empty-workflow-form input{border:1px solid var(--sc-gray-20,#d1d5db);border-radius:4px;font-family:inherit;font-size:.875rem;padding:.4em .6em}.btn-seed{background:var(--sc-blue-40,#3b82f6);border:none;border-radius:.4rem;color:#fff;cursor:pointer;font-size:.875rem;font-weight:500;padding:.45em 1em}.btn-seed:disabled{cursor:not-allowed;opacity:.5}.validation-panel{margin-bottom:1rem}.validation-errors{background:#fee2e2;border:1px solid #ef4444;border-radius:6px;color:#991b1b;margin-bottom:.5rem;padding:1rem}.validation-warnings{background:#fef9c3;border:1px solid #eab308;border-radius:6px;color:#713f12;padding:1rem}.dismiss-button{background:none;border:1px solid;border-radius:3px;cursor:pointer;font-size:.75rem;margin-left:1rem;padding:.125em .5em}.builder-actions{align-items:center;display:flex;gap:1rem;padding:1rem}.btn-primary{background:var(--sc-blue-40,#3b82f6);border:none;border-radius:.5rem;color:#fff;cursor:pointer;font-weight:500;padding:.5rem 1.5rem}.btn-primary:disabled{cursor:not-allowed;opacity:.6}.save-message{font-size:.875rem}.save-message.success{color:#065f46}.save-message.error{color:#991b1b}
150
+ .docbuilder-desktop{height:100vh}.docbuilder-loading{padding:2rem;text-align:center}.docbuilder-page{box-sizing:border-box;padding:2rem}.builder-workflow{min-height:8rem;padding:.5em 1em}:deep(.node-editor){height:40vh;overflow:hidden;width:100%}.empty-workflow{padding:1rem 0}.empty-workflow-hint{color:#9ca3af;font-style:italic;margin:0 0 .75rem}.empty-workflow-form{align-items:center;display:flex;gap:.5rem}.empty-workflow-form input{border:1px solid var(--sc-gray-20,#d1d5db);border-radius:4px;font-family:inherit;font-size:.875rem;padding:.4em .6em}.btn-seed{background:var(--sc-blue-40,#3b82f6);border:none;border-radius:.4rem;color:#fff;cursor:pointer;font-size:.875rem;font-weight:500;padding:.45em 1em}.btn-seed:disabled{cursor:not-allowed;opacity:.5}.validation-panel{margin-bottom:1rem}.validation-errors{background:#fee2e2;border:1px solid #ef4444;border-radius:6px;color:#991b1b;margin-bottom:.5rem;padding:1rem}.validation-warnings{background:#fef9c3;border:1px solid #eab308;border-radius:6px;color:#713f12;padding:1rem}.dismiss-button{background:none;border:1px solid;border-radius:3px;cursor:pointer;font-size:.75rem;margin-left:1rem;padding:.125em .5em}.builder-actions{align-items:center;display:flex;gap:1rem;padding:1rem}.save-message{font-size:.875rem}.save-message.success{color:#065f46}.save-message.error{color:#991b1b}
161
151
  </style>
@@ -1,38 +1,37 @@
1
1
  <template>
2
- <div class="docbuilder-index">
3
- <div class="docbuilder-index-inner">
4
- <div class="docbuilder-header">
5
- <h1>DocType Builder</h1>
6
- <p class="subtitle">Select a DocType to view and edit its schema</p>
7
- </div>
8
- <div class="docbuilder-create">
9
- <input
10
- v-model="newName"
11
- type="text"
12
- placeholder="New doctype name (e.g. Invoice)"
13
- :disabled="creating"
14
- @keyup.enter="createDoctype" />
15
- <button type="button" class="btn-create" :disabled="creating || !newName.trim()" @click="createDoctype">
16
- {{ creating ? 'Creating\u2026' : '+ New DocType' }}
17
- </button>
18
- </div>
19
- <p v-if="createError" class="create-error">{{ createError }}</p>
20
- <ClientOnly>
2
+ <ClientOnly>
3
+ <Desktop class="docbuilder-desktop" :route-adapter="routeAdapter" :host-actions="indexActions">
4
+ <div class="docbuilder-index-inner">
5
+ <div class="docbuilder-header">
6
+ <h1>DocType Builder</h1>
7
+ <p class="subtitle">Select a DocType to view and edit its schema</p>
8
+ </div>
9
+ <div class="docbuilder-create">
10
+ <input
11
+ v-model="newName"
12
+ type="text"
13
+ placeholder="New doctype name (e.g. Invoice)"
14
+ :disabled="creating"
15
+ @keyup.enter="createDoctype" />
16
+ <button type="button" class="btn-create" :disabled="creating || !newName.trim()" @click="createDoctype">
17
+ {{ creating ? 'Creating\u2026' : '+ New DocType' }}
18
+ </button>
19
+ </div>
20
+ <p v-if="createError" class="create-error">{{ createError }}</p>
21
21
  <div v-if="loading" class="loading">Loading doctypes...</div>
22
22
  <p v-else-if="!doctypes.length" class="empty">No doctypes yet — create one above.</p>
23
23
  <ATable v-else :columns="columns" :rows="doctypes" :config="config" @row:click="handleRowClick" />
24
- </ClientOnly>
25
- </div>
26
-
27
- <ActionSet :elements="indexActions" @action-click="handleAction" />
28
- </div>
24
+ </div>
25
+ </Desktop>
26
+ </ClientOnly>
29
27
  </template>
30
28
 
31
29
  <script setup>
32
- import { ActionSet } from "@stonecrop/desktop";
30
+ import { Desktop } from "@stonecrop/desktop";
33
31
  import { ref, computed, onMounted } from "vue";
34
32
  import { useRouter } from "nuxt/app";
35
33
  const router = useRouter();
34
+ const routeAdapter = useDocBuilderRouteAdapter();
36
35
  const doctypes = ref([]);
37
36
  const loading = ref(true);
38
37
  async function loadDoctypes() {
@@ -93,11 +92,8 @@ async function createDoctype() {
93
92
  }
94
93
  }
95
94
  const indexActions = computed(() => [{ type: "button", label: "Home", action: () => void router.push("/") }]);
96
- function handleAction(_label, action) {
97
- if (action) void action();
98
- }
99
95
  </script>
100
96
 
101
97
  <style scoped>
102
- .docbuilder-index{background:var(--sc-form-background,#fff);box-sizing:border-box;min-height:100vh}.docbuilder-index-inner{margin:0 auto;max-width:1200px;padding:2rem}.docbuilder-header{padding:2rem 0 3rem;text-align:center}.docbuilder-header h1{font-size:2.5rem;font-weight:700;margin:0 0 1rem}.subtitle{color:#6b7280;font-size:1.125rem;margin:0}.empty,.loading{color:#6b7280;padding:2rem;text-align:center}.docbuilder-create{display:flex;gap:.5rem;margin-bottom:1rem}.docbuilder-create input{border:1px solid var(--sc-gray-20,#d1d5db);border-radius:4px;flex:1;font:inherit;padding:.5em .75em}.btn-create{background:var(--sc-blue-40,#3b82f6);border:none;border-radius:.4rem;color:#fff;cursor:pointer;font-weight:500;padding:.5em 1.25em;white-space:nowrap}.btn-create:disabled{cursor:not-allowed;opacity:.5}.create-error{color:#b91c1c;font-size:.875rem;margin:-.5rem 0 1rem}
98
+ .docbuilder-desktop{height:100vh}.docbuilder-index-inner{margin:0 auto;max-width:1200px;padding:2rem}.docbuilder-header{padding:2rem 0 3rem;text-align:center}.docbuilder-header h1{font-size:2.5rem;font-weight:700;margin:0 0 1rem}.subtitle{color:#6b7280;font-size:1.125rem;margin:0}.empty,.loading{color:#6b7280;padding:2rem;text-align:center}.docbuilder-create{display:flex;gap:.5rem;margin-bottom:1rem}.docbuilder-create input{border:1px solid var(--sc-gray-20,#d1d5db);border-radius:4px;flex:1;font:inherit;padding:.5em .75em}.btn-create{background:var(--sc-blue-40,#3b82f6);border:none;border-radius:.4rem;color:#fff;cursor:pointer;font-weight:500;padding:.5em 1.25em;white-space:nowrap}.btn-create:disabled{cursor:not-allowed;opacity:.5}.create-error{color:#b91c1c;font-size:.875rem;margin:-.5rem 0 1rem}
103
99
  </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stonecrop/nuxt",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "Nuxt module for Stonecrop",
5
5
  "keywords": [
6
6
  "nuxt",
@@ -60,18 +60,18 @@
60
60
  "pathe": "^2.0.3",
61
61
  "pinia": "^3.0.4",
62
62
  "prompts": "^2.4.2",
63
- "@stonecrop/aform": "0.33.0",
64
- "@stonecrop/casl-middleware": "0.33.0",
65
- "@stonecrop/atable": "0.33.0",
66
- "@stonecrop/code-editor": "0.33.0",
67
- "@stonecrop/desktop": "0.33.0",
68
- "@stonecrop/graphql-client": "0.33.0",
69
- "@stonecrop/graphql-middleware": "0.33.0",
70
- "@stonecrop/node-editor": "0.33.0",
71
- "@stonecrop/schema": "0.33.0",
72
- "@stonecrop/nuxt-grafserv": "0.33.0",
73
- "@stonecrop/stonecrop": "0.33.0",
74
- "@stonecrop/themes": "0.33.0"
63
+ "@stonecrop/aform": "0.34.0",
64
+ "@stonecrop/casl-middleware": "0.34.0",
65
+ "@stonecrop/atable": "0.34.0",
66
+ "@stonecrop/code-editor": "0.34.0",
67
+ "@stonecrop/graphql-client": "0.34.0",
68
+ "@stonecrop/desktop": "0.34.0",
69
+ "@stonecrop/node-editor": "0.34.0",
70
+ "@stonecrop/graphql-middleware": "0.34.0",
71
+ "@stonecrop/nuxt-grafserv": "0.34.0",
72
+ "@stonecrop/schema": "0.34.0",
73
+ "@stonecrop/stonecrop": "0.34.0",
74
+ "@stonecrop/themes": "0.34.0"
75
75
  },
76
76
  "devDependencies": {
77
77
  "@eslint/js": "^10.0.1",