@stonecrop/nuxt 0.13.9 → 0.13.11

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.
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Template loading for CLI installers
3
+ *
4
+ * Templates live in the package's templates/ directory (shipped via the
5
+ * "files" field in package.json). There is deliberately no inline fallback:
6
+ * a missing template means a broken installation (wrong dist layout or
7
+ * corrupted install), and failing loudly beats scaffolding a silently
8
+ * broken app from stale duplicated content.
9
+ */
10
+
11
+ import { existsSync } from 'node:fs'
12
+ import { readFile } from 'node:fs/promises'
13
+ import { join, dirname } from 'pathe'
14
+ import { fileURLToPath } from 'node:url'
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url))
17
+
18
+ /**
19
+ * Load a template file from the package's templates/ directory
20
+ * @throws if the template file cannot be found
21
+ */
22
+ export async function loadTemplate(filename: string): Promise<string> {
23
+ // utils/ -> cli/ -> src/ -> package root
24
+ const templatePath = join(__dirname, '..', '..', '..', 'templates', filename)
25
+
26
+ if (!existsSync(templatePath)) {
27
+ throw new Error(
28
+ `Template not found: ${templatePath}. ` +
29
+ 'The @stonecrop/nuxt package installation appears to be incomplete — try reinstalling.'
30
+ )
31
+ }
32
+
33
+ return readFile(templatePath, 'utf-8')
34
+ }
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "Project",
3
+ "slug": "project",
4
+ "fields": [
5
+ {
6
+ "fieldname": "id",
7
+ "label": "ID",
8
+ "component": "ATextInput",
9
+ "fieldtype": "Data",
10
+ "mode": "display"
11
+ },
12
+ {
13
+ "fieldname": "title",
14
+ "label": "Title",
15
+ "component": "ATextInput",
16
+ "fieldtype": "Data",
17
+ "required": true
18
+ },
19
+ {
20
+ "fieldname": "description",
21
+ "label": "Description",
22
+ "component": "ATextarea",
23
+ "fieldtype": "Text"
24
+ },
25
+ {
26
+ "fieldname": "status",
27
+ "label": "Status",
28
+ "component": "ADropdown",
29
+ "fieldtype": "Select",
30
+ "options": ["Active", "Archived"],
31
+ "default": "Active"
32
+ },
33
+ {
34
+ "fieldname": "createdAt",
35
+ "label": "Created At",
36
+ "component": "ATextInput",
37
+ "fieldtype": "Datetime",
38
+ "mode": "display"
39
+ }
40
+ ],
41
+ "workflow": {
42
+ "states": ["Active", "Archived"],
43
+ "actions": {
44
+ "save": {
45
+ "label": "Save",
46
+ "handler": "project:save"
47
+ },
48
+ "archive": {
49
+ "label": "Archive Project",
50
+ "handler": "archive_project",
51
+ "allowedStates": ["Active"],
52
+ "confirm": true
53
+ }
54
+ }
55
+ }
56
+ }
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "Task",
3
+ "slug": "task",
4
+ "fields": [
5
+ {
6
+ "fieldname": "id",
7
+ "label": "ID",
8
+ "component": "ATextInput",
9
+ "fieldtype": "Data",
10
+ "mode": "display"
11
+ },
12
+ {
13
+ "fieldname": "title",
14
+ "label": "Title",
15
+ "component": "ATextInput",
16
+ "fieldtype": "Data",
17
+ "required": true
18
+ },
19
+ {
20
+ "fieldname": "projectId",
21
+ "label": "Project",
22
+ "component": "ACombobox",
23
+ "fieldtype": "Link",
24
+ "options": "project",
25
+ "required": true
26
+ },
27
+ {
28
+ "fieldname": "status",
29
+ "label": "Status",
30
+ "component": "ADropdown",
31
+ "fieldtype": "Select",
32
+ "options": ["Todo", "In Progress", "Done"],
33
+ "default": "Todo"
34
+ },
35
+ {
36
+ "fieldname": "description",
37
+ "label": "Description",
38
+ "component": "ATextarea",
39
+ "fieldtype": "Text"
40
+ },
41
+ {
42
+ "fieldname": "dueDate",
43
+ "label": "Due Date",
44
+ "component": "ADatepicker",
45
+ "fieldtype": "Date"
46
+ },
47
+ {
48
+ "fieldname": "createdAt",
49
+ "label": "Created At",
50
+ "component": "ATextInput",
51
+ "fieldtype": "Datetime",
52
+ "mode": "display"
53
+ }
54
+ ],
55
+ "workflow": {
56
+ "states": ["Todo", "In Progress", "Done"],
57
+ "actions": {
58
+ "save": {
59
+ "label": "Save",
60
+ "handler": "task:save"
61
+ },
62
+ "start_task": {
63
+ "label": "Start Task",
64
+ "handler": "start_task",
65
+ "allowedStates": ["Todo"]
66
+ },
67
+ "complete_task": {
68
+ "label": "Complete",
69
+ "handler": "complete_task",
70
+ "allowedStates": ["In Progress"]
71
+ }
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * In-memory data store
3
+ *
4
+ * This module holds the in-memory Maps used as the data layer for this app.
5
+ * Both server/resolvers.ts (for reading) and server/plugins/stonecrop.ts (for
6
+ * writing via action handlers) import from here.
7
+ *
8
+ * To connect a real database, replace this module with your PostGraphile + pgClient setup.
9
+ * See: https://stonecrop.io/docs/guides/postgraphile
10
+ */
11
+
12
+ export interface Project {
13
+ id: string
14
+ title: string
15
+ description: string
16
+ status: 'Active' | 'Archived'
17
+ createdAt: string
18
+ }
19
+
20
+ export interface Task {
21
+ id: string
22
+ title: string
23
+ projectId: string
24
+ status: 'Todo' | 'In Progress' | 'Done'
25
+ description: string
26
+ dueDate: string | null
27
+ createdAt: string
28
+ }
29
+
30
+ export const projects = new Map<string, Project>([
31
+ [
32
+ '1',
33
+ {
34
+ id: '1',
35
+ title: 'Website Redesign',
36
+ description: 'Redesign the company website with a modern look and feel',
37
+ status: 'Active',
38
+ createdAt: '2025-01-01T00:00:00Z',
39
+ },
40
+ ],
41
+ [
42
+ '2',
43
+ {
44
+ id: '2',
45
+ title: 'Mobile App',
46
+ description: 'Build the iOS and Android app for field staff',
47
+ status: 'Active',
48
+ createdAt: '2025-01-02T00:00:00Z',
49
+ },
50
+ ],
51
+ ])
52
+
53
+ export const tasks = new Map<string, Task>([
54
+ [
55
+ '1',
56
+ {
57
+ id: '1',
58
+ title: 'Create wireframes',
59
+ projectId: '1',
60
+ status: 'Todo',
61
+ description: 'Draft initial wireframes for all main pages',
62
+ dueDate: '2025-02-01',
63
+ createdAt: '2025-01-05T00:00:00Z',
64
+ },
65
+ ],
66
+ [
67
+ '2',
68
+ {
69
+ id: '2',
70
+ title: 'Set up design system',
71
+ projectId: '1',
72
+ status: 'In Progress',
73
+ description: 'Configure colors, typography, and component library',
74
+ dueDate: null,
75
+ createdAt: '2025-01-06T00:00:00Z',
76
+ },
77
+ ],
78
+ [
79
+ '3',
80
+ {
81
+ id: '3',
82
+ title: 'Define API contract',
83
+ projectId: '2',
84
+ status: 'Todo',
85
+ description: 'Document all API endpoints needed for the mobile app',
86
+ dueDate: '2025-01-20',
87
+ createdAt: '2025-01-07T00:00:00Z',
88
+ },
89
+ ],
90
+ ])
@@ -0,0 +1,98 @@
1
+ <template>
2
+ <ClientOnly>
3
+ <Desktop
4
+ :available-doctypes="availableDoctypes"
5
+ :route-adapter="routeAdapter"
6
+ @action="handleAction"
7
+ @load-records="handleLoadRecords"
8
+ @load-record="handleLoadRecord" />
9
+ <template #fallback>
10
+ <div class="sc-loading">
11
+ <p>Loading...</p>
12
+ </div>
13
+ </template>
14
+ </ClientOnly>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import {
19
+ Desktop,
20
+ type ActionEventPayload,
21
+ type LoadRecordEventPayload,
22
+ type LoadRecordsEventPayload,
23
+ } from '@stonecrop/desktop'
24
+ import { useStonecrop } from '@stonecrop/stonecrop'
25
+
26
+ import { useRouteAdapter } from '~/composables/useRouteAdapter'
27
+ import {
28
+ doctypeMap,
29
+ useDoctypeConfig,
30
+ fetchDoctypeRecords,
31
+ fetchDoctypeRecord,
32
+ runDoctypeAction,
33
+ } from '~/composables/useDoctypes'
34
+
35
+ const routeAdapter = useRouteAdapter()
36
+ const { stonecrop } = useStonecrop()
37
+
38
+ const availableDoctypes = computed(() => Array.from(doctypeMap.keys()))
39
+
40
+ async function handleLoadRecords(payload: LoadRecordsEventPayload) {
41
+ const doctypeConfig = useDoctypeConfig(payload.doctype)
42
+ if (!doctypeConfig || !stonecrop.value) return
43
+
44
+ try {
45
+ const { data } = await fetchDoctypeRecords({ name: doctypeConfig.name })
46
+ for (const record of data) {
47
+ const recordId = record.id as string
48
+ if (recordId) stonecrop.value.addRecord(payload.doctype, recordId, record)
49
+ }
50
+ } catch (error) {
51
+ console.error('Failed to load records:', error)
52
+ }
53
+ }
54
+
55
+ async function handleLoadRecord(payload: LoadRecordEventPayload) {
56
+ if (!stonecrop.value || payload.recordId.startsWith('new-')) return
57
+
58
+ const doctypeConfig = useDoctypeConfig(payload.doctype)
59
+ if (!doctypeConfig) return
60
+
61
+ try {
62
+ const record = await fetchDoctypeRecord({ name: doctypeConfig.name }, payload.recordId)
63
+ if (record) stonecrop.value.addRecord(payload.doctype, payload.recordId, record)
64
+ } catch (error) {
65
+ console.error('Failed to load record:', error)
66
+ }
67
+ }
68
+
69
+ async function handleAction(payload: ActionEventPayload) {
70
+ const doctypeConfig = useDoctypeConfig(payload.doctype)
71
+ if (!doctypeConfig) return
72
+
73
+ try {
74
+ const result = await runDoctypeAction(doctypeConfig, payload.name, {
75
+ id: payload.recordId,
76
+ data: payload.data,
77
+ })
78
+
79
+ if (result.success && result.data && stonecrop.value && payload.recordId) {
80
+ stonecrop.value.addRecord(payload.doctype, payload.recordId, result.data as Record<string, unknown>)
81
+ }
82
+
83
+ if (!result.success) console.error('Action failed:', result.error)
84
+ } catch (error) {
85
+ console.error('Action error:', error)
86
+ }
87
+ }
88
+ </script>
89
+
90
+ <style>
91
+ .sc-loading {
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ min-height: 50vh;
96
+ color: #666;
97
+ }
98
+ </style>