@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 +1 -1
- package/dist/runtime/app/composables/useDocBuilderRouteAdapter.d.ts +11 -0
- package/dist/runtime/app/composables/useDocBuilderRouteAdapter.js +21 -0
- package/dist/runtime/app/pages/DocBuilderDetail.vue +47 -57
- package/dist/runtime/app/pages/DocBuilderIndex.vue +25 -29
- package/package.json +13 -13
package/dist/module.json
CHANGED
|
@@ -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
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
</
|
|
14
|
-
</
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
v-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
</
|
|
41
|
-
</AFieldset>
|
|
38
|
+
</AFieldset>
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
<AFieldset label="Actions" :schema="[]" :collapsible="true">
|
|
41
|
+
<DocBuilderActionsPanel v-model="workflowConfig" />
|
|
42
|
+
</AFieldset>
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
<AFieldset label="Schema" :schema="[]" :collapsible="true">
|
|
45
|
+
<DocBuilderFieldsPanel v-model="fields" />
|
|
46
|
+
</AFieldset>
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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 {
|
|
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-
|
|
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
|
-
<
|
|
3
|
-
<
|
|
4
|
-
<div class="docbuilder-
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
</
|
|
25
|
-
</
|
|
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 {
|
|
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-
|
|
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.
|
|
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.
|
|
64
|
-
"@stonecrop/casl-middleware": "0.
|
|
65
|
-
"@stonecrop/atable": "0.
|
|
66
|
-
"@stonecrop/code-editor": "0.
|
|
67
|
-
"@stonecrop/
|
|
68
|
-
"@stonecrop/
|
|
69
|
-
"@stonecrop/
|
|
70
|
-
"@stonecrop/
|
|
71
|
-
"@stonecrop/
|
|
72
|
-
"@stonecrop/
|
|
73
|
-
"@stonecrop/stonecrop": "0.
|
|
74
|
-
"@stonecrop/themes": "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",
|