@stonecrop/nuxt 0.33.0 → 0.35.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/components/DocBuilderActionsPanel.vue +1 -1
- package/dist/runtime/app/components/DocBuilderFieldsPanel.vue +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 +50 -57
- package/dist/runtime/app/pages/DocBuilderIndex.vue +25 -29
- package/package.json +13 -13
- package/templates/schema.graphql +1 -0
package/dist/module.json
CHANGED
|
@@ -174,5 +174,5 @@ function onRemoveRow(row) {
|
|
|
174
174
|
</script>
|
|
175
175
|
|
|
176
176
|
<style scoped>
|
|
177
|
-
.actions-panel{padding:.5em 1em}.actions-panel :deep(.atable-row>td){border-top:1px solid var(--sc-row-border-color
|
|
177
|
+
.actions-panel{padding:.5em 1em}.actions-panel :deep(.atable-row>td){border-top:1px solid var(--sc-row-border-color);padding:var(--sc-atable-row-padding) .75em;vertical-align:middle}.actions-panel :deep(input[type=text]){border:1px solid var(--sc-gray-20);border-radius:var(--sc-border-radius);font-family:inherit;font-size:inherit;padding:.25em .5em;width:100%}.actions-panel :deep(input[type=text]:focus){border-color:var(--sc-input-active-border-color);outline:none}.cell-readonly{color:var(--sc-gray-60);cursor:default}.badge{border-radius:9999px;display:inline-block;font-size:.75rem;font-weight:500;padding:.125em .5em}.badge-transition{background:var(--sc-badge-brand-bg);color:var(--sc-badge-brand-text)}.badge-command,.badge-self-transition,.badge-trigger{background:var(--sc-badge-neutral-bg);color:var(--sc-badge-neutral-text)}.actions-empty{color:var(--sc-header-text-color);font-style:italic;padding:1rem 0;text-align:center}.panel-footer{display:flex;gap:.5rem;padding:.75rem 0 .25rem}.add-row{background:var(--sc-primary-color);border:none;border-radius:var(--sc-border-radius);color:var(--sc-primary-text-color);cursor:pointer;font-size:.8125rem;font-weight:500;padding:.4em .9em}.row-detail{gap:.75rem}.detail-field,.row-detail{display:flex;flex-direction:column}.detail-field{gap:.25rem}.detail-label{color:var(--sc-header-text-color);font-size:.75rem;font-weight:600;letter-spacing:.05em;text-transform:uppercase}.detail-value{color:var(--sc-gray-60);font-size:.8125rem}.client-handler-editor{display:flex;flex-direction:column;gap:.5rem}.handler-label{color:var(--sc-header-text-color);font-size:.75rem;font-weight:600;letter-spacing:.05em;text-transform:uppercase}.remove-row{align-self:flex-start;background:none;border:1px solid var(--sc-badge-danger-accent);border-radius:var(--sc-border-radius);color:var(--sc-badge-danger-text);cursor:pointer;font-size:.75rem;padding:.3em .75em}
|
|
178
178
|
</style>
|
|
@@ -336,5 +336,5 @@ function bool(v) {
|
|
|
336
336
|
</script>
|
|
337
337
|
|
|
338
338
|
<style scoped>
|
|
339
|
-
.fields-panel{padding:.5em 1em}.fields-panel :deep(.atable-row>td){border-top:1px solid var(--sc-row-border-color
|
|
339
|
+
.fields-panel{padding:.5em 1em}.fields-panel :deep(.atable-row>td){border-top:1px solid var(--sc-row-border-color);padding:var(--sc-atable-row-padding) .75em;vertical-align:middle}.fields-panel :deep(input[type=text]),.fields-panel :deep(select){border:1px solid var(--sc-gray-20);border-radius:var(--sc-border-radius);font-family:inherit;font-size:inherit;padding:.25em .5em;width:100%}.fields-panel :deep(input.locked),.fields-panel :deep(select.locked){background:var(--sc-gray-10);color:var(--sc-header-text-color);cursor:not-allowed}.fields-panel :deep(input.json-invalid){background:var(--sc-badge-danger-bg);border-color:var(--sc-brand-danger)}.center{text-align:center}.badge{border-radius:9999px;display:inline-block;font-size:.75rem;font-weight:500;padding:.125em .5em}.badge-manual{background:var(--sc-badge-brand-bg);color:var(--sc-badge-brand-text)}.badge-introspected{background:var(--sc-badge-neutral-bg);color:var(--sc-badge-neutral-text)}.field-detail{display:grid;gap:.75rem 1rem;grid-template-columns:repeat(auto-fill,minmax(14rem,1fr));padding:.75rem 1rem}.field-prop{color:var(--sc-header-text-color);display:flex;flex-direction:column;font-size:.75rem;gap:.25rem}.field-prop-inline{align-items:center;flex-direction:row;gap:.5rem}.field-prop-wide{grid-column:1/-1}.field-detail-actions{align-items:center;display:flex;gap:1rem;justify-content:space-between;padding:0 1rem .75rem}.locked-note{color:var(--sc-header-text-color);font-size:.75rem;font-style:italic}.btn-add{background:none;border:1px dashed var(--sc-gray-20);border-radius:var(--sc-border-radius);color:var(--sc-primary-color);cursor:pointer;font-size:.875rem;margin-top:.5rem;padding:.4em 1em}.fields-empty{color:var(--sc-header-text-color);font-style:italic;padding:1rem 0}
|
|
340
340
|
</style>
|
|
@@ -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,65 @@
|
|
|
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-primary" 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
|
-
|
|
57
|
-
|
|
52
|
+
</Desktop>
|
|
53
|
+
<template #fallback>
|
|
54
|
+
<div class="docbuilder-loading">Loading...</div>
|
|
55
|
+
</template>
|
|
56
|
+
</ClientOnly>
|
|
58
57
|
</template>
|
|
59
58
|
|
|
60
59
|
<script setup>
|
|
61
60
|
import { AFieldset } from "@stonecrop/aform";
|
|
62
61
|
import { StateEditor } from "@stonecrop/node-editor";
|
|
63
|
-
import {
|
|
62
|
+
import { Desktop } from "@stonecrop/desktop";
|
|
64
63
|
import { WorkflowMeta } from "@stonecrop/schema";
|
|
65
64
|
import { ref, watch, computed, onMounted } from "vue";
|
|
66
65
|
import { useRoute, useRouter } from "nuxt/app";
|
|
@@ -69,6 +68,7 @@ import DocBuilderFieldsPanel from "../components/DocBuilderFieldsPanel.vue";
|
|
|
69
68
|
const route = useRoute();
|
|
70
69
|
const router = useRouter();
|
|
71
70
|
const doctypeName = computed(() => route.params.doctype);
|
|
71
|
+
const routeAdapter = useDocBuilderRouteAdapter({ doctype: doctypeName });
|
|
72
72
|
const loading = ref(true);
|
|
73
73
|
const saving = ref(false);
|
|
74
74
|
const warningsDismissed = ref(false);
|
|
@@ -128,10 +128,6 @@ async function saveToDisk() {
|
|
|
128
128
|
body: {
|
|
129
129
|
doctype: doctypeName.value,
|
|
130
130
|
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
131
|
workflow: workflowConfig.value ? { ...workflowConfig.value, ...Object.keys(layout.value).length > 0 && { layout: layout.value } } : null
|
|
136
132
|
}
|
|
137
133
|
});
|
|
@@ -151,11 +147,8 @@ const docbuilderActions = computed(() => [
|
|
|
151
147
|
},
|
|
152
148
|
{ type: "button", label: "Back", action: () => void router.push("/docbuilder") }
|
|
153
149
|
]);
|
|
154
|
-
function handleAction(_label, action) {
|
|
155
|
-
if (action) void action();
|
|
156
|
-
}
|
|
157
150
|
</script>
|
|
158
151
|
|
|
159
152
|
<style scoped>
|
|
160
|
-
.docbuilder-
|
|
153
|
+
.docbuilder-desktop{height:100vh}.docbuilder-loading{color:var(--sc-header-text-color);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:var(--sc-header-text-color);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-input-border-color);border-radius:var(--sc-border-radius);font-family:inherit;font-size:.875rem;padding:.4em .6em}.btn-primary{background:var(--sc-primary-color);border:none;border-radius:var(--sc-border-radius);color:var(--sc-primary-text-color);cursor:pointer;font-size:.875rem;font-weight:500;padding:.45em 1em}.btn-primary:disabled{cursor:not-allowed;opacity:.5}.validation-panel{margin-bottom:1rem}.validation-errors{background:var(--sc-badge-danger-bg);border:1px solid var(--sc-badge-danger-accent);border-radius:var(--sc-border-radius);color:var(--sc-badge-danger-text);margin-bottom:.5rem;padding:1rem}.validation-warnings{background:var(--sc-badge-warning-bg);border:1px solid var(--sc-badge-warning-accent);border-radius:var(--sc-border-radius);color:var(--sc-badge-warning-text);padding:1rem}.dismiss-button{background:none;border:1px solid;border-radius:var(--sc-border-radius);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:var(--sc-brand-success)}.save-message.error{color:var(--sc-brand-danger)}
|
|
161
154
|
</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-primary" :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{font-size:1.125rem;margin:0}.empty,.loading,.subtitle{color:var(--sc-header-text-color)}.empty,.loading{padding:2rem;text-align:center}.docbuilder-create{display:flex;gap:.5rem;margin-bottom:1rem}.docbuilder-create input{border:1px solid var(--sc-input-border-color);border-radius:var(--sc-border-radius);flex:1;font:inherit;padding:.5em .75em}.btn-primary{background:var(--sc-primary-color);border:none;border-radius:var(--sc-border-radius);color:var(--sc-primary-text-color);cursor:pointer;font-weight:500;padding:.5em 1.25em;white-space:nowrap}.btn-primary:disabled{cursor:not-allowed;opacity:.5}.create-error{color:var(--sc-brand-danger);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.35.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/
|
|
65
|
-
"@stonecrop/
|
|
66
|
-
"@stonecrop/code-editor": "0.
|
|
67
|
-
"@stonecrop/desktop": "0.
|
|
68
|
-
"@stonecrop/graphql-client": "0.
|
|
69
|
-
"@stonecrop/
|
|
70
|
-
"@stonecrop/node-editor": "0.
|
|
71
|
-
"@stonecrop/
|
|
72
|
-
"@stonecrop/
|
|
73
|
-
"@stonecrop/stonecrop": "0.
|
|
74
|
-
"@stonecrop/
|
|
63
|
+
"@stonecrop/aform": "0.35.0",
|
|
64
|
+
"@stonecrop/atable": "0.35.0",
|
|
65
|
+
"@stonecrop/casl-middleware": "0.35.0",
|
|
66
|
+
"@stonecrop/code-editor": "0.35.0",
|
|
67
|
+
"@stonecrop/desktop": "0.35.0",
|
|
68
|
+
"@stonecrop/graphql-client": "0.35.0",
|
|
69
|
+
"@stonecrop/schema": "0.35.0",
|
|
70
|
+
"@stonecrop/node-editor": "0.35.0",
|
|
71
|
+
"@stonecrop/nuxt-grafserv": "0.35.0",
|
|
72
|
+
"@stonecrop/themes": "0.35.0",
|
|
73
|
+
"@stonecrop/stonecrop": "0.35.0",
|
|
74
|
+
"@stonecrop/graphql-middleware": "0.35.0"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
77
|
"@eslint/js": "^10.0.1",
|