@wp-typia/project-tools 0.22.0 → 0.22.2
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/runtime/built-in-block-code-templates/interactivity.d.ts +1 -1
- package/dist/runtime/built-in-block-code-templates/interactivity.js +2 -2
- package/dist/runtime/cli-add-workspace-admin-view-scaffold.d.ts +9 -0
- package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +257 -0
- package/dist/runtime/cli-add-workspace-admin-view-source.d.ts +5 -0
- package/dist/runtime/cli-add-workspace-admin-view-source.js +86 -0
- package/dist/runtime/cli-add-workspace-admin-view-templates.d.ts +23 -0
- package/dist/runtime/cli-add-workspace-admin-view-templates.js +991 -0
- package/dist/runtime/cli-add-workspace-admin-view-types.d.ts +29 -0
- package/dist/runtime/cli-add-workspace-admin-view-types.js +29 -0
- package/dist/runtime/cli-add-workspace-admin-view.d.ts +1 -16
- package/dist/runtime/cli-add-workspace-admin-view.js +22 -1388
- package/dist/runtime/cli-add-workspace-ai-source-emitters.js +119 -1
- package/dist/runtime/cli-add-workspace-ai.js +253 -30
- package/dist/runtime/package-versions.d.ts +15 -0
- package/dist/runtime/package-versions.js +72 -42
- package/dist/runtime/scaffold-compatibility.d.ts +2 -0
- package/dist/runtime/scaffold-compatibility.js +2 -0
- package/dist/runtime/string-case.d.ts +7 -0
- package/dist/runtime/string-case.js +21 -11
- package/dist/runtime/typia-llm.d.ts +37 -2
- package/dist/runtime/typia-llm.js +240 -3
- package/dist/runtime/workspace-inventory.js +24 -0
- package/package.json +1 -1
|
@@ -0,0 +1,991 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { quotePhpString } from './php-utils.js';
|
|
3
|
+
import { quoteTsString } from './cli-add-shared.js';
|
|
4
|
+
import { ADMIN_VIEWS_ASSET, ADMIN_VIEWS_SCRIPT, ADMIN_VIEWS_STYLE, ADMIN_VIEWS_STYLE_RTL, formatAdminViewSourceLocator, isAdminViewCoreDataSource, } from './cli-add-workspace-admin-view-types.js';
|
|
5
|
+
import { toCamelCase, toPascalCase, toTitleCase } from './string-case.js';
|
|
6
|
+
function getAdminViewRelativeModuleSpecifier(adminViewSlug, workspaceFile) {
|
|
7
|
+
const adminViewDir = `src/admin-views/${adminViewSlug}`;
|
|
8
|
+
const normalizedFile = workspaceFile.replace(/\\/gu, '/');
|
|
9
|
+
const modulePath = normalizedFile.replace(/\.[cm]?[jt]sx?$/u, '');
|
|
10
|
+
const relativeModulePath = path.posix.relative(adminViewDir, modulePath);
|
|
11
|
+
return relativeModulePath.startsWith('.')
|
|
12
|
+
? relativeModulePath
|
|
13
|
+
: `./${relativeModulePath}`;
|
|
14
|
+
}
|
|
15
|
+
export function buildAdminViewConfigEntry(adminViewSlug, source) {
|
|
16
|
+
return [
|
|
17
|
+
'\t{',
|
|
18
|
+
`\t\tfile: ${quoteTsString(`src/admin-views/${adminViewSlug}/index.tsx`)},`,
|
|
19
|
+
`\t\tphpFile: ${quoteTsString(`inc/admin-views/${adminViewSlug}.php`)},`,
|
|
20
|
+
`\t\tslug: ${quoteTsString(adminViewSlug)},`,
|
|
21
|
+
source
|
|
22
|
+
? `\t\tsource: ${quoteTsString(formatAdminViewSourceLocator(source))},`
|
|
23
|
+
: null,
|
|
24
|
+
'\t},',
|
|
25
|
+
]
|
|
26
|
+
.filter((line) => typeof line === 'string')
|
|
27
|
+
.join('\n');
|
|
28
|
+
}
|
|
29
|
+
export function buildAdminViewRegistrySource(adminViewSlugs) {
|
|
30
|
+
const importLines = adminViewSlugs
|
|
31
|
+
.map((adminViewSlug) => `import './${adminViewSlug}';`)
|
|
32
|
+
.join('\n');
|
|
33
|
+
return `${importLines}${importLines ? '\n\n' : ''}// wp-typia add admin-view entries\n`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Build the generated admin-view item and dataset types for the selected source.
|
|
37
|
+
*/
|
|
38
|
+
export function buildAdminViewTypesSource(adminViewSlug, restResource, coreDataSource) {
|
|
39
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
40
|
+
const coreDataRecordTypeName = `${pascalName}CoreDataRecord`;
|
|
41
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
42
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
43
|
+
if (restResource) {
|
|
44
|
+
const restPascalName = toPascalCase(restResource.slug);
|
|
45
|
+
const restTypesModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.typesFile);
|
|
46
|
+
return `import type { ${restPascalName}Record } from ${quoteTsString(restTypesModule)};
|
|
47
|
+
|
|
48
|
+
export type ${itemTypeName} = ${restPascalName}Record;
|
|
49
|
+
|
|
50
|
+
export interface ${dataSetTypeName} {
|
|
51
|
+
\titems: ${itemTypeName}[];
|
|
52
|
+
\tpaginationInfo: {
|
|
53
|
+
\t\ttotalItems: number;
|
|
54
|
+
\t\ttotalPages: number;
|
|
55
|
+
\t};
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
59
|
+
if (coreDataSource) {
|
|
60
|
+
if (coreDataSource.entityKind === 'taxonomy') {
|
|
61
|
+
return `export interface ${coreDataRecordTypeName} {
|
|
62
|
+
\tcount?: number;
|
|
63
|
+
\tdescription?: string;
|
|
64
|
+
\tid: number;
|
|
65
|
+
\tlink?: string;
|
|
66
|
+
\tmeta?: Record<string, unknown>;
|
|
67
|
+
\tname?: string;
|
|
68
|
+
\tparent?: number;
|
|
69
|
+
\tslug?: string;
|
|
70
|
+
\ttaxonomy?: string;
|
|
71
|
+
\t[key: string]: unknown;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ${itemTypeName} {
|
|
75
|
+
\tcount: number;
|
|
76
|
+
\tdescription: string;
|
|
77
|
+
\tid: number;
|
|
78
|
+
\tlink: string;
|
|
79
|
+
\tname: string;
|
|
80
|
+
\tparent: number;
|
|
81
|
+
\traw: ${coreDataRecordTypeName};
|
|
82
|
+
\tslug: string;
|
|
83
|
+
\ttaxonomy: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ${dataSetTypeName} {
|
|
87
|
+
\titems: ${itemTypeName}[];
|
|
88
|
+
\tpaginationInfo: {
|
|
89
|
+
\t\ttotalItems: number;
|
|
90
|
+
\t\ttotalPages: number;
|
|
91
|
+
\t};
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
}
|
|
95
|
+
return `export interface ${coreDataRecordTypeName} {
|
|
96
|
+
\tid: number;
|
|
97
|
+
\tdate?: string;
|
|
98
|
+
\tmodified?: string;
|
|
99
|
+
\tname?: string;
|
|
100
|
+
\tslug?: string;
|
|
101
|
+
\tstatus?: string;
|
|
102
|
+
\ttitle?: string | {
|
|
103
|
+
\t\traw?: string;
|
|
104
|
+
\t\trendered?: string;
|
|
105
|
+
\t};
|
|
106
|
+
\t[key: string]: unknown;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface ${itemTypeName} {
|
|
110
|
+
\tid: number;
|
|
111
|
+
\traw: ${coreDataRecordTypeName};
|
|
112
|
+
\tslug: string;
|
|
113
|
+
\tstatus: string;
|
|
114
|
+
\ttitle: string;
|
|
115
|
+
\tupdatedAt: string;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface ${dataSetTypeName} {
|
|
119
|
+
\titems: ${itemTypeName}[];
|
|
120
|
+
\tpaginationInfo: {
|
|
121
|
+
\t\ttotalItems: number;
|
|
122
|
+
\t\ttotalPages: number;
|
|
123
|
+
\t};
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
126
|
+
}
|
|
127
|
+
return `export type ${pascalName}AdminViewStatus = 'draft' | 'published';
|
|
128
|
+
|
|
129
|
+
export interface ${itemTypeName} {
|
|
130
|
+
\tid: number;
|
|
131
|
+
\towner: string;
|
|
132
|
+
\tstatus: ${pascalName}AdminViewStatus;
|
|
133
|
+
\ttitle: string;
|
|
134
|
+
\tupdatedAt: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface ${dataSetTypeName} {
|
|
138
|
+
\titems: ${itemTypeName}[];
|
|
139
|
+
\tpaginationInfo: {
|
|
140
|
+
\t\ttotalItems: number;
|
|
141
|
+
\t\ttotalPages: number;
|
|
142
|
+
\t};
|
|
143
|
+
}
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Build the generated DataViews config source for an admin-view scaffold.
|
|
148
|
+
*/
|
|
149
|
+
export function buildAdminViewConfigSource(adminViewSlug, textDomain, source, restResource) {
|
|
150
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
151
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
152
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
153
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
154
|
+
const isCoreDataSource = isAdminViewCoreDataSource(source);
|
|
155
|
+
const isTaxonomyCoreDataSource = isAdminViewCoreDataSource(source) && source.entityKind === 'taxonomy';
|
|
156
|
+
const defaultViewFields = restResource
|
|
157
|
+
? "['id']"
|
|
158
|
+
: isTaxonomyCoreDataSource
|
|
159
|
+
? "['name', 'slug', 'count']"
|
|
160
|
+
: isCoreDataSource
|
|
161
|
+
? "['title', 'slug', 'status', 'updatedAt']"
|
|
162
|
+
: "['title', 'status', 'updatedAt']";
|
|
163
|
+
const searchEnabled = restResource ? 'false' : 'true';
|
|
164
|
+
const titleFieldSource = restResource
|
|
165
|
+
? ''
|
|
166
|
+
: isTaxonomyCoreDataSource
|
|
167
|
+
? "\ttitleField: 'name',\n"
|
|
168
|
+
: "\ttitleField: 'title',\n";
|
|
169
|
+
const defaultViewEnhancementsSource = restResource
|
|
170
|
+
? ''
|
|
171
|
+
: isTaxonomyCoreDataSource
|
|
172
|
+
? "\t\ttitleField: 'name',\n"
|
|
173
|
+
: isCoreDataSource
|
|
174
|
+
? "\t\ttitleField: 'title',\n"
|
|
175
|
+
: `\t\tsort: {
|
|
176
|
+
\t\t\tdirection: 'desc',
|
|
177
|
+
\t\t\tfield: 'updatedAt',
|
|
178
|
+
\t\t},
|
|
179
|
+
\t\ttitleField: 'title',
|
|
180
|
+
`;
|
|
181
|
+
const additionalFieldsSource = restResource
|
|
182
|
+
? '\t\t// REST-backed screens start with the guaranteed ID column. Add project-owned fields here once they are declared on the REST record type.'
|
|
183
|
+
: isTaxonomyCoreDataSource
|
|
184
|
+
? `\t\tcount: {
|
|
185
|
+
\t\t\tlabel: __( 'Count', ${quoteTsString(textDomain)} ),
|
|
186
|
+
\t\t\tschema: { type: 'integer' },
|
|
187
|
+
\t\t},
|
|
188
|
+
\t\tdescription: {
|
|
189
|
+
\t\t\tlabel: __( 'Description', ${quoteTsString(textDomain)} ),
|
|
190
|
+
\t\t\tschema: { type: 'string' },
|
|
191
|
+
\t\t},
|
|
192
|
+
\t\tlink: {
|
|
193
|
+
\t\t\tlabel: __( 'Link', ${quoteTsString(textDomain)} ),
|
|
194
|
+
\t\t\tschema: { format: 'uri', type: 'string' },
|
|
195
|
+
\t\t},
|
|
196
|
+
\t\tname: {
|
|
197
|
+
\t\t\tenableGlobalSearch: true,
|
|
198
|
+
\t\t\tlabel: __( 'Name', ${quoteTsString(textDomain)} ),
|
|
199
|
+
\t\t\tschema: { type: 'string' },
|
|
200
|
+
\t\t},
|
|
201
|
+
\t\tparent: {
|
|
202
|
+
\t\t\tlabel: __( 'Parent', ${quoteTsString(textDomain)} ),
|
|
203
|
+
\t\t\tschema: { type: 'integer' },
|
|
204
|
+
\t\t},
|
|
205
|
+
\t\tslug: {
|
|
206
|
+
\t\t\tenableGlobalSearch: true,
|
|
207
|
+
\t\t\tlabel: __( 'Slug', ${quoteTsString(textDomain)} ),
|
|
208
|
+
\t\t\tschema: { type: 'string' },
|
|
209
|
+
\t\t},
|
|
210
|
+
\t\ttaxonomy: {
|
|
211
|
+
\t\t\tlabel: __( 'Taxonomy', ${quoteTsString(textDomain)} ),
|
|
212
|
+
\t\t\tschema: { type: 'string' },
|
|
213
|
+
\t\t},`
|
|
214
|
+
: isCoreDataSource
|
|
215
|
+
? `\t\tslug: {
|
|
216
|
+
\t\t\tenableGlobalSearch: true,
|
|
217
|
+
\t\t\tlabel: __( 'Slug', ${quoteTsString(textDomain)} ),
|
|
218
|
+
\t\t\tschema: { type: 'string' },
|
|
219
|
+
\t\t},
|
|
220
|
+
\t\tstatus: {
|
|
221
|
+
\t\t\tlabel: __( 'Status', ${quoteTsString(textDomain)} ),
|
|
222
|
+
\t\t\tschema: { type: 'string' },
|
|
223
|
+
\t\t},
|
|
224
|
+
\t\ttitle: {
|
|
225
|
+
\t\t\tenableGlobalSearch: true,
|
|
226
|
+
\t\t\tlabel: __( 'Name', ${quoteTsString(textDomain)} ),
|
|
227
|
+
\t\t\tschema: { type: 'string' },
|
|
228
|
+
\t\t},
|
|
229
|
+
\t\tupdatedAt: {
|
|
230
|
+
\t\t\tlabel: __( 'Updated', ${quoteTsString(textDomain)} ),
|
|
231
|
+
\t\t\tschema: { format: 'date-time', type: 'string' },
|
|
232
|
+
\t\t\ttype: 'datetime',
|
|
233
|
+
\t\t},`
|
|
234
|
+
: `\t\towner: {
|
|
235
|
+
\t\t\tlabel: __( 'Owner', ${quoteTsString(textDomain)} ),
|
|
236
|
+
\t\t\tschema: { type: 'string' },
|
|
237
|
+
\t\t},
|
|
238
|
+
\t\tstatus: {
|
|
239
|
+
\t\t\tfilterBy: { operators: ['isAny', 'isNone'] },
|
|
240
|
+
\t\t\tlabel: __( 'Status', ${quoteTsString(textDomain)} ),
|
|
241
|
+
\t\t\tschema: {
|
|
242
|
+
\t\t\t\tenum: ['draft', 'published'],
|
|
243
|
+
\t\t\t\tenumLabels: {
|
|
244
|
+
\t\t\t\t\tdraft: __( 'Draft', ${quoteTsString(textDomain)} ),
|
|
245
|
+
\t\t\t\t\tpublished: __( 'Published', ${quoteTsString(textDomain)} ),
|
|
246
|
+
\t\t\t\t},
|
|
247
|
+
\t\t\t\ttype: 'string',
|
|
248
|
+
\t\t\t},
|
|
249
|
+
\t\t},
|
|
250
|
+
\t\ttitle: {
|
|
251
|
+
\t\t\tenableGlobalSearch: true,
|
|
252
|
+
\t\t\tenableSorting: true,
|
|
253
|
+
\t\t\tlabel: __( 'Title', ${quoteTsString(textDomain)} ),
|
|
254
|
+
\t\t\tschema: { type: 'string' },
|
|
255
|
+
\t\t},
|
|
256
|
+
\t\tupdatedAt: {
|
|
257
|
+
\t\t\tenableSorting: true,
|
|
258
|
+
\t\t\tlabel: __( 'Updated', ${quoteTsString(textDomain)} ),
|
|
259
|
+
\t\t\tschema: { format: 'date-time', type: 'string' },
|
|
260
|
+
\t\t\ttype: 'datetime',
|
|
261
|
+
\t\t},`;
|
|
262
|
+
return `import { defineDataViews } from '@wp-typia/dataviews';
|
|
263
|
+
import { __ } from '@wordpress/i18n';
|
|
264
|
+
|
|
265
|
+
import type { ${itemTypeName} } from './types';
|
|
266
|
+
|
|
267
|
+
export const ${dataViewsName} = defineDataViews<${itemTypeName}>({
|
|
268
|
+
\tidField: 'id',
|
|
269
|
+
\tsearch: ${searchEnabled},
|
|
270
|
+
\tsearchLabel: __( 'Search records', ${quoteTsString(textDomain)} ),
|
|
271
|
+
${titleFieldSource}
|
|
272
|
+
\tdefaultView: {
|
|
273
|
+
\t\tfields: ${defaultViewFields},
|
|
274
|
+
\t\tpage: 1,
|
|
275
|
+
\t\tperPage: 10,
|
|
276
|
+
${defaultViewEnhancementsSource}
|
|
277
|
+
\t\ttype: 'table',
|
|
278
|
+
\t},
|
|
279
|
+
\tfields: {
|
|
280
|
+
\t\tid: {
|
|
281
|
+
\t\t\tenableHiding: false,
|
|
282
|
+
\t\t\tlabel: __( 'ID', ${quoteTsString(textDomain)} ),
|
|
283
|
+
\t\t\treadOnly: true,
|
|
284
|
+
\t\t\tschema: { type: 'integer' },
|
|
285
|
+
\t\t},
|
|
286
|
+
${additionalFieldsSource}
|
|
287
|
+
\t},
|
|
288
|
+
});
|
|
289
|
+
`;
|
|
290
|
+
}
|
|
291
|
+
export function buildDefaultAdminViewDataSource(adminViewSlug) {
|
|
292
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
293
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
294
|
+
const title = toTitleCase(adminViewSlug);
|
|
295
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
296
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
297
|
+
const queryTypeName = `${pascalName}AdminViewQuery`;
|
|
298
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
299
|
+
const fetchName = `fetch${pascalName}AdminViewData`;
|
|
300
|
+
return `import type { DataViewsView } from '@wp-typia/dataviews';
|
|
301
|
+
|
|
302
|
+
import { ${dataViewsName} } from './config';
|
|
303
|
+
import type { ${dataSetTypeName}, ${itemTypeName} } from './types';
|
|
304
|
+
|
|
305
|
+
export interface ${queryTypeName} {
|
|
306
|
+
\tpage?: number;
|
|
307
|
+
\tperPage?: number;
|
|
308
|
+
\tsearch?: string;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const STARTER_ITEMS: ${itemTypeName}[] = [
|
|
312
|
+
\t{
|
|
313
|
+
\t\tid: 1,
|
|
314
|
+
\t\towner: 'Editorial',
|
|
315
|
+
\t\tstatus: 'published',
|
|
316
|
+
\t\ttitle: ${quoteTsString(`${title} launch checklist`)},
|
|
317
|
+
\t\tupdatedAt: '2026-04-01T10:30:00Z',
|
|
318
|
+
\t},
|
|
319
|
+
\t{
|
|
320
|
+
\t\tid: 2,
|
|
321
|
+
\t\towner: 'Design',
|
|
322
|
+
\t\tstatus: 'draft',
|
|
323
|
+
\t\ttitle: ${quoteTsString(`${title} content refresh`)},
|
|
324
|
+
\t\tupdatedAt: '2026-04-03T14:15:00Z',
|
|
325
|
+
\t},
|
|
326
|
+
\t{
|
|
327
|
+
\t\tid: 3,
|
|
328
|
+
\t\towner: 'Operations',
|
|
329
|
+
\t\tstatus: 'published',
|
|
330
|
+
\t\ttitle: ${quoteTsString(`${title} support handoff`)},
|
|
331
|
+
\t\tupdatedAt: '2026-04-08T08:45:00Z',
|
|
332
|
+
\t},
|
|
333
|
+
];
|
|
334
|
+
|
|
335
|
+
function matchesSearch(item: ${itemTypeName}, search: string | undefined): boolean {
|
|
336
|
+
\tif (!search) {
|
|
337
|
+
\t\treturn true;
|
|
338
|
+
\t}
|
|
339
|
+
|
|
340
|
+
\tconst needle = search.toLowerCase();
|
|
341
|
+
\treturn [item.title, item.owner, item.status].some((value) =>
|
|
342
|
+
\t\tvalue.toLowerCase().includes(needle),
|
|
343
|
+
\t);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export async function ${fetchName}(
|
|
347
|
+
\tview: DataViewsView<${itemTypeName}>,
|
|
348
|
+
): Promise<${dataSetTypeName}> {
|
|
349
|
+
\tconst query = ${dataViewsName}.toQueryArgs<${queryTypeName}>(view, {
|
|
350
|
+
\t\tperPageParam: 'perPage',
|
|
351
|
+
\t});
|
|
352
|
+
\tconst requestedPage = query.page ?? 1;
|
|
353
|
+
\tconst page = requestedPage > 0 ? requestedPage : 1;
|
|
354
|
+
\tconst requestedPerPage = query.perPage ?? view.perPage ?? 10;
|
|
355
|
+
\tconst perPage = requestedPerPage > 0 ? requestedPerPage : 10;
|
|
356
|
+
\tconst filteredItems = STARTER_ITEMS.filter((item) =>
|
|
357
|
+
\t\tmatchesSearch(item, query.search),
|
|
358
|
+
\t);
|
|
359
|
+
\tconst offset = (page - 1) * perPage;
|
|
360
|
+
\tconst items = filteredItems.slice(offset, offset + perPage);
|
|
361
|
+
|
|
362
|
+
\treturn {
|
|
363
|
+
\t\titems,
|
|
364
|
+
\t\tpaginationInfo: {
|
|
365
|
+
\t\t\ttotalItems: filteredItems.length,
|
|
366
|
+
\t\t\ttotalPages: Math.max(1, Math.ceil(filteredItems.length / perPage)),
|
|
367
|
+
\t\t},
|
|
368
|
+
\t};
|
|
369
|
+
}
|
|
370
|
+
`;
|
|
371
|
+
}
|
|
372
|
+
export function buildRestAdminViewDataSource(adminViewSlug, restResource) {
|
|
373
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
374
|
+
const restPascalName = toPascalCase(restResource.slug);
|
|
375
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
376
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
377
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
378
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
379
|
+
const fetchName = `fetch${pascalName}AdminViewData`;
|
|
380
|
+
const restApiModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.apiFile);
|
|
381
|
+
const restTypesModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.typesFile);
|
|
382
|
+
return `import type { DataViewsView } from '@wp-typia/dataviews';
|
|
383
|
+
|
|
384
|
+
import { listResource } from ${quoteTsString(restApiModule)};
|
|
385
|
+
import type { ${restPascalName}ListQuery } from ${quoteTsString(restTypesModule)};
|
|
386
|
+
import { ${dataViewsName} } from './config';
|
|
387
|
+
import type { ${dataSetTypeName}, ${itemTypeName} } from './types';
|
|
388
|
+
|
|
389
|
+
function resolveTotalPages(total: number, perPage: number | undefined): number {
|
|
390
|
+
\tconst resolvedPerPage = perPage && perPage > 0 ? perPage : 1;
|
|
391
|
+
\treturn Math.max(1, Math.ceil(total / resolvedPerPage));
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export async function ${fetchName}(
|
|
395
|
+
\tview: DataViewsView<${itemTypeName}>,
|
|
396
|
+
): Promise<${dataSetTypeName}> {
|
|
397
|
+
\tconst query = ${dataViewsName}.toQueryArgs<${restPascalName}ListQuery>(view, {
|
|
398
|
+
\t\tperPageParam: 'perPage',
|
|
399
|
+
\t\tsearchParam: false,
|
|
400
|
+
\t});
|
|
401
|
+
\tconst result = await listResource({
|
|
402
|
+
\t\tpage: query.page,
|
|
403
|
+
\t\tperPage: query.perPage,
|
|
404
|
+
\t});
|
|
405
|
+
\tif (!result.isValid || !result.data) {
|
|
406
|
+
\t\tthrow new Error('Unable to load REST resource records.');
|
|
407
|
+
\t}
|
|
408
|
+
|
|
409
|
+
\tconst response = result.data;
|
|
410
|
+
|
|
411
|
+
\treturn {
|
|
412
|
+
\t\titems: response.items,
|
|
413
|
+
\t\tpaginationInfo: {
|
|
414
|
+
\t\t\ttotalItems: response.total,
|
|
415
|
+
\t\t\ttotalPages: resolveTotalPages(response.total, response.perPage ?? query.perPage),
|
|
416
|
+
\t\t},
|
|
417
|
+
\t};
|
|
418
|
+
}
|
|
419
|
+
`;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Build a core-data-backed admin-view data module for a supported entity family.
|
|
423
|
+
*/
|
|
424
|
+
export function buildCoreDataAdminViewDataSource(adminViewSlug, coreDataSource) {
|
|
425
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
426
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
427
|
+
const coreDataRecordTypeName = `${pascalName}CoreDataRecord`;
|
|
428
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
429
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
430
|
+
const queryTypeName = `${pascalName}AdminViewQuery`;
|
|
431
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
432
|
+
const useEntityRecordName = `use${pascalName}EntityRecord`;
|
|
433
|
+
const useEntityRecordsName = `use${pascalName}EntityRecords`;
|
|
434
|
+
const useAdminViewDataName = `use${pascalName}AdminViewData`;
|
|
435
|
+
if (coreDataSource.entityKind === 'taxonomy') {
|
|
436
|
+
return `import type { DataViewsView } from '@wp-typia/dataviews';
|
|
437
|
+
import { useEntityRecord, useEntityRecords } from '@wordpress/core-data';
|
|
438
|
+
import { useMemo } from '@wordpress/element';
|
|
439
|
+
|
|
440
|
+
import { ${dataViewsName} } from './config';
|
|
441
|
+
import type {
|
|
442
|
+
\t${coreDataRecordTypeName},
|
|
443
|
+
\t${dataSetTypeName},
|
|
444
|
+
\t${itemTypeName},
|
|
445
|
+
} from './types';
|
|
446
|
+
|
|
447
|
+
export interface ${queryTypeName} {
|
|
448
|
+
\tpage?: number;
|
|
449
|
+
\tper_page?: number;
|
|
450
|
+
\tsearch?: string;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const CORE_DATA_ENTITY_KIND = ${quoteTsString(coreDataSource.entityKind)};
|
|
454
|
+
const CORE_DATA_ENTITY_NAME = ${quoteTsString(coreDataSource.entityName)};
|
|
455
|
+
|
|
456
|
+
function normalizeCoreDataNumber(value: unknown): number {
|
|
457
|
+
\treturn typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function normalizeCoreDataString(value: unknown): string {
|
|
461
|
+
\treturn typeof value === 'string' ? value : '';
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function normalizeTaxonomyRecord(record: ${coreDataRecordTypeName}): ${itemTypeName} {
|
|
465
|
+
\treturn {
|
|
466
|
+
\t\tcount: normalizeCoreDataNumber(record.count),
|
|
467
|
+
\t\tdescription: normalizeCoreDataString(record.description),
|
|
468
|
+
\t\tid: record.id,
|
|
469
|
+
\t\tlink: normalizeCoreDataString(record.link),
|
|
470
|
+
\t\tname: normalizeCoreDataString(record.name) || normalizeCoreDataString(record.slug),
|
|
471
|
+
\t\tparent: normalizeCoreDataNumber(record.parent),
|
|
472
|
+
\t\traw: record,
|
|
473
|
+
\t\tslug: normalizeCoreDataString(record.slug),
|
|
474
|
+
\t\ttaxonomy: normalizeCoreDataString(record.taxonomy),
|
|
475
|
+
\t};
|
|
476
|
+
\t}
|
|
477
|
+
|
|
478
|
+
export function ${useEntityRecordName}(recordId: number | undefined) {
|
|
479
|
+
\treturn useEntityRecord<${coreDataRecordTypeName}>(
|
|
480
|
+
\t\tCORE_DATA_ENTITY_KIND,
|
|
481
|
+
\t\tCORE_DATA_ENTITY_NAME,
|
|
482
|
+
\t\trecordId ?? 0,
|
|
483
|
+
\t\t{ enabled: typeof recordId === 'number' },
|
|
484
|
+
\t);
|
|
485
|
+
\t}
|
|
486
|
+
|
|
487
|
+
export function ${useEntityRecordsName}(view: DataViewsView<${itemTypeName}>) {
|
|
488
|
+
\tconst query = ${dataViewsName}.toQueryArgs<${queryTypeName}>(view, {
|
|
489
|
+
\t\tperPageParam: 'per_page',
|
|
490
|
+
\t});
|
|
491
|
+
|
|
492
|
+
\treturn useEntityRecords<${coreDataRecordTypeName}>(
|
|
493
|
+
\t\tCORE_DATA_ENTITY_KIND,
|
|
494
|
+
\t\tCORE_DATA_ENTITY_NAME,
|
|
495
|
+
\t\tquery,
|
|
496
|
+
\t);
|
|
497
|
+
\t}
|
|
498
|
+
|
|
499
|
+
export function ${useAdminViewDataName}(view: DataViewsView<${itemTypeName}>) {
|
|
500
|
+
\tconst { hasResolved, isResolving, records, totalItems, totalPages } =
|
|
501
|
+
\t\t${useEntityRecordsName}(view);
|
|
502
|
+
\tconst items = useMemo(
|
|
503
|
+
\t\t() => (records ?? []).map((record) => normalizeTaxonomyRecord(record)),
|
|
504
|
+
\t\t[records],
|
|
505
|
+
\t);
|
|
506
|
+
\tconst dataSet = useMemo<${dataSetTypeName}>(
|
|
507
|
+
\t\t() => ({
|
|
508
|
+
\t\t\titems,
|
|
509
|
+
\t\t\tpaginationInfo: {
|
|
510
|
+
\t\t\t\ttotalItems: totalItems ?? items.length,
|
|
511
|
+
\t\t\t\ttotalPages: Math.max(1, totalPages ?? 1),
|
|
512
|
+
\t\t\t},
|
|
513
|
+
\t\t}),
|
|
514
|
+
\t\t[items, totalItems, totalPages],
|
|
515
|
+
\t);
|
|
516
|
+
\tconst error =
|
|
517
|
+
\t\t!isResolving && hasResolved && records === null
|
|
518
|
+
\t\t\t? 'Unable to load core-data entity records.'
|
|
519
|
+
\t\t\t: null;
|
|
520
|
+
|
|
521
|
+
\treturn {
|
|
522
|
+
\t\tdataSet,
|
|
523
|
+
\t\terror,
|
|
524
|
+
\t\tisLoading: isResolving,
|
|
525
|
+
\t};
|
|
526
|
+
\t}
|
|
527
|
+
`;
|
|
528
|
+
}
|
|
529
|
+
return `import type { DataViewsView } from '@wp-typia/dataviews';
|
|
530
|
+
import { useEntityRecord, useEntityRecords } from '@wordpress/core-data';
|
|
531
|
+
import { useMemo } from '@wordpress/element';
|
|
532
|
+
|
|
533
|
+
import { ${dataViewsName} } from './config';
|
|
534
|
+
import type {
|
|
535
|
+
\t${coreDataRecordTypeName},
|
|
536
|
+
\t${dataSetTypeName},
|
|
537
|
+
\t${itemTypeName},
|
|
538
|
+
} from './types';
|
|
539
|
+
|
|
540
|
+
export interface ${queryTypeName} {
|
|
541
|
+
\tpage?: number;
|
|
542
|
+
\tper_page?: number;
|
|
543
|
+
\tsearch?: string;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const CORE_DATA_ENTITY_KIND = ${quoteTsString(coreDataSource.entityKind)};
|
|
547
|
+
const CORE_DATA_ENTITY_NAME = ${quoteTsString(coreDataSource.entityName)};
|
|
548
|
+
|
|
549
|
+
function normalizeCoreDataString(value: unknown): string {
|
|
550
|
+
\treturn typeof value === 'string' ? value : '';
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function normalizeCoreDataTitle(record: ${coreDataRecordTypeName}): string {
|
|
554
|
+
\tif (typeof record.title === 'string') {
|
|
555
|
+
\t\treturn record.title;
|
|
556
|
+
\t}
|
|
557
|
+
\tif (record.title && typeof record.title === 'object') {
|
|
558
|
+
\t\tif (typeof record.title.rendered === 'string') {
|
|
559
|
+
\t\t\treturn record.title.rendered;
|
|
560
|
+
\t\t}
|
|
561
|
+
\t\tif (typeof record.title.raw === 'string') {
|
|
562
|
+
\t\t\treturn record.title.raw;
|
|
563
|
+
\t\t}
|
|
564
|
+
\t}
|
|
565
|
+
|
|
566
|
+
\treturn normalizeCoreDataString(record.name) || normalizeCoreDataString(record.slug);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function normalizeCoreDataUpdatedAt(record: ${coreDataRecordTypeName}): string {
|
|
570
|
+
\treturn normalizeCoreDataString(record.modified) || normalizeCoreDataString(record.date);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function normalizeCoreDataRecord(record: ${coreDataRecordTypeName}): ${itemTypeName} {
|
|
574
|
+
\treturn {
|
|
575
|
+
\t\tid: record.id,
|
|
576
|
+
\t\traw: record,
|
|
577
|
+
\t\tslug: normalizeCoreDataString(record.slug),
|
|
578
|
+
\t\tstatus: normalizeCoreDataString(record.status),
|
|
579
|
+
\t\ttitle: normalizeCoreDataTitle(record),
|
|
580
|
+
\t\tupdatedAt: normalizeCoreDataUpdatedAt(record),
|
|
581
|
+
\t};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
export function ${useEntityRecordName}(recordId: number | undefined) {
|
|
585
|
+
\treturn useEntityRecord<${coreDataRecordTypeName}>(
|
|
586
|
+
\t\tCORE_DATA_ENTITY_KIND,
|
|
587
|
+
\t\tCORE_DATA_ENTITY_NAME,
|
|
588
|
+
\t\trecordId ?? 0,
|
|
589
|
+
\t\t{ enabled: typeof recordId === 'number' },
|
|
590
|
+
\t);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
export function ${useEntityRecordsName}(view: DataViewsView<${itemTypeName}>) {
|
|
594
|
+
\tconst query = ${dataViewsName}.toQueryArgs<${queryTypeName}>(view, {
|
|
595
|
+
\t\tperPageParam: 'per_page',
|
|
596
|
+
\t});
|
|
597
|
+
|
|
598
|
+
\treturn useEntityRecords<${coreDataRecordTypeName}>(
|
|
599
|
+
\t\tCORE_DATA_ENTITY_KIND,
|
|
600
|
+
\t\tCORE_DATA_ENTITY_NAME,
|
|
601
|
+
\t\tquery,
|
|
602
|
+
\t);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export function ${useAdminViewDataName}(view: DataViewsView<${itemTypeName}>) {
|
|
606
|
+
\tconst { hasResolved, isResolving, records, totalItems, totalPages } =
|
|
607
|
+
\t\t${useEntityRecordsName}(view);
|
|
608
|
+
\tconst items = useMemo(
|
|
609
|
+
\t\t() => (records ?? []).map((record) => normalizeCoreDataRecord(record)),
|
|
610
|
+
\t\t[records],
|
|
611
|
+
\t);
|
|
612
|
+
\tconst dataSet = useMemo<${dataSetTypeName}>(
|
|
613
|
+
\t\t() => ({
|
|
614
|
+
\t\t\titems,
|
|
615
|
+
\t\t\tpaginationInfo: {
|
|
616
|
+
\t\t\t\ttotalItems: totalItems ?? items.length,
|
|
617
|
+
\t\t\t\ttotalPages: Math.max(1, totalPages ?? 1),
|
|
618
|
+
\t\t\t},
|
|
619
|
+
\t\t}),
|
|
620
|
+
\t\t[items, totalItems, totalPages],
|
|
621
|
+
\t);
|
|
622
|
+
\tconst error =
|
|
623
|
+
\t\t!isResolving && hasResolved && records === null
|
|
624
|
+
\t\t\t? 'Unable to load core-data entity records.'
|
|
625
|
+
\t\t\t: null;
|
|
626
|
+
|
|
627
|
+
\treturn {
|
|
628
|
+
\t\tdataSet,
|
|
629
|
+
\t\terror,
|
|
630
|
+
\t\tisLoading: isResolving,
|
|
631
|
+
\t};
|
|
632
|
+
}
|
|
633
|
+
`;
|
|
634
|
+
}
|
|
635
|
+
export function buildAdminViewScreenSource(adminViewSlug, textDomain) {
|
|
636
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
637
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
638
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
639
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
640
|
+
const componentName = `${pascalName}AdminViewScreen`;
|
|
641
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
642
|
+
const fetchName = `fetch${pascalName}AdminViewData`;
|
|
643
|
+
const title = toTitleCase(adminViewSlug);
|
|
644
|
+
return `import type { DataViewsConfig, DataViewsView } from '@wp-typia/dataviews';
|
|
645
|
+
import { Button, Notice, Spinner } from '@wordpress/components';
|
|
646
|
+
import { useEffect, useState } from '@wordpress/element';
|
|
647
|
+
import { __ } from '@wordpress/i18n';
|
|
648
|
+
import { DataViews } from '@wordpress/dataviews/wp';
|
|
649
|
+
|
|
650
|
+
import { ${dataViewsName} } from './config';
|
|
651
|
+
import { ${fetchName} } from './data';
|
|
652
|
+
import type { ${dataSetTypeName}, ${itemTypeName} } from './types';
|
|
653
|
+
|
|
654
|
+
const TypedDataViews = DataViews as unknown as <TItem extends object>(
|
|
655
|
+
\tprops: DataViewsConfig<TItem>,
|
|
656
|
+
) => ReturnType<typeof DataViews>;
|
|
657
|
+
|
|
658
|
+
const EMPTY_DATA_SET: ${dataSetTypeName} = {
|
|
659
|
+
\titems: [],
|
|
660
|
+
\tpaginationInfo: {
|
|
661
|
+
\t\ttotalItems: 0,
|
|
662
|
+
\t\ttotalPages: 1,
|
|
663
|
+
\t},
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
export function ${componentName}() {
|
|
667
|
+
\tconst [view, setView] = useState<DataViewsView<${itemTypeName}>>(
|
|
668
|
+
\t\t${dataViewsName}.defaultView,
|
|
669
|
+
\t);
|
|
670
|
+
\tconst [dataSet, setDataSet] = useState<${dataSetTypeName}>(EMPTY_DATA_SET);
|
|
671
|
+
\tconst [error, setError] = useState<string | null>(null);
|
|
672
|
+
\tconst [isLoading, setIsLoading] = useState(true);
|
|
673
|
+
\tconst [reloadToken, setReloadToken] = useState(0);
|
|
674
|
+
|
|
675
|
+
\tuseEffect(() => {
|
|
676
|
+
\t\tlet isCurrentRequest = true;
|
|
677
|
+
\t\tsetIsLoading(true);
|
|
678
|
+
\t\tsetError(null);
|
|
679
|
+
|
|
680
|
+
\t\tvoid ${fetchName}(view)
|
|
681
|
+
\t\t\t.then((nextDataSet) => {
|
|
682
|
+
\t\t\t\tif (isCurrentRequest) {
|
|
683
|
+
\t\t\t\t\tsetDataSet(nextDataSet);
|
|
684
|
+
\t\t\t\t}
|
|
685
|
+
\t\t\t})
|
|
686
|
+
\t\t\t.catch((nextError: unknown) => {
|
|
687
|
+
\t\t\t\tif (isCurrentRequest) {
|
|
688
|
+
\t\t\t\t\tsetError(
|
|
689
|
+
\t\t\t\t\t\tnextError instanceof Error
|
|
690
|
+
\t\t\t\t\t\t\t? nextError.message
|
|
691
|
+
\t\t\t\t\t\t\t: __( 'Unable to load records.', ${quoteTsString(textDomain)} ),
|
|
692
|
+
\t\t\t\t\t);
|
|
693
|
+
\t\t\t\t}
|
|
694
|
+
\t\t\t})
|
|
695
|
+
\t\t\t.finally(() => {
|
|
696
|
+
\t\t\t\tif (isCurrentRequest) {
|
|
697
|
+
\t\t\t\t\tsetIsLoading(false);
|
|
698
|
+
\t\t\t\t}
|
|
699
|
+
\t\t\t});
|
|
700
|
+
|
|
701
|
+
\t\treturn () => {
|
|
702
|
+
\t\t\tisCurrentRequest = false;
|
|
703
|
+
\t\t};
|
|
704
|
+
\t}, [reloadToken, view]);
|
|
705
|
+
|
|
706
|
+
\tconst config = ${dataViewsName}.createConfig({
|
|
707
|
+
\t\tdata: dataSet.items,
|
|
708
|
+
\t\tisLoading,
|
|
709
|
+
\t\tonChangeView: setView,
|
|
710
|
+
\t\tpaginationInfo: dataSet.paginationInfo,
|
|
711
|
+
\t\tview,
|
|
712
|
+
\t});
|
|
713
|
+
|
|
714
|
+
\treturn (
|
|
715
|
+
\t\t<div className="wp-typia-admin-view-screen">
|
|
716
|
+
\t\t\t<header className="wp-typia-admin-view-screen__header">
|
|
717
|
+
\t\t\t\t<div>
|
|
718
|
+
\t\t\t\t\t<p className="wp-typia-admin-view-screen__eyebrow">
|
|
719
|
+
\t\t\t\t\t\t{ __( 'DataViews admin screen', ${quoteTsString(textDomain)} ) }
|
|
720
|
+
\t\t\t\t\t</p>
|
|
721
|
+
\t\t\t\t\t<h1>{ __( ${quoteTsString(title)}, ${quoteTsString(textDomain)} ) }</h1>
|
|
722
|
+
\t\t\t\t\t<p>
|
|
723
|
+
\t\t\t\t\t\t{ __( 'Replace the fetcher in data.ts with your project data source when this screen graduates from scaffold to product UI.', ${quoteTsString(textDomain)} ) }
|
|
724
|
+
\t\t\t\t\t</p>
|
|
725
|
+
\t\t\t\t</div>
|
|
726
|
+
\t\t\t\t<div className="wp-typia-admin-view-screen__actions">
|
|
727
|
+
\t\t\t\t\t{ isLoading ? <Spinner /> : null }
|
|
728
|
+
\t\t\t\t\t<Button
|
|
729
|
+
\t\t\t\t\t\tisBusy={ isLoading }
|
|
730
|
+
\t\t\t\t\t\tonClick={ () => setReloadToken((token) => token + 1) }
|
|
731
|
+
\t\t\t\t\t\tvariant="secondary"
|
|
732
|
+
\t\t\t\t\t>
|
|
733
|
+
\t\t\t\t\t\t{ __( 'Reload', ${quoteTsString(textDomain)} ) }
|
|
734
|
+
\t\t\t\t\t</Button>
|
|
735
|
+
\t\t\t\t</div>
|
|
736
|
+
\t\t\t</header>
|
|
737
|
+
\t\t\t{ error ? (
|
|
738
|
+
\t\t\t\t<Notice isDismissible={ false } status="error">
|
|
739
|
+
\t\t\t\t\t{ error }
|
|
740
|
+
\t\t\t\t</Notice>
|
|
741
|
+
\t\t\t) : null }
|
|
742
|
+
\t\t\t<TypedDataViews<${itemTypeName}> { ...config } />
|
|
743
|
+
\t\t</div>
|
|
744
|
+
\t);
|
|
745
|
+
}
|
|
746
|
+
`;
|
|
747
|
+
}
|
|
748
|
+
export function buildCoreDataAdminViewScreenSource(adminViewSlug, textDomain) {
|
|
749
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
750
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
751
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
752
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
753
|
+
const componentName = `${pascalName}AdminViewScreen`;
|
|
754
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
755
|
+
const useAdminViewDataName = `use${pascalName}AdminViewData`;
|
|
756
|
+
const title = toTitleCase(adminViewSlug);
|
|
757
|
+
return `import type { DataViewsConfig, DataViewsView } from '@wp-typia/dataviews';
|
|
758
|
+
import { Notice, Spinner } from '@wordpress/components';
|
|
759
|
+
import { useState } from '@wordpress/element';
|
|
760
|
+
import { __ } from '@wordpress/i18n';
|
|
761
|
+
import { DataViews } from '@wordpress/dataviews/wp';
|
|
762
|
+
|
|
763
|
+
import { ${dataViewsName} } from './config';
|
|
764
|
+
import { ${useAdminViewDataName} } from './data';
|
|
765
|
+
import type { ${dataSetTypeName}, ${itemTypeName} } from './types';
|
|
766
|
+
|
|
767
|
+
const TypedDataViews = DataViews as unknown as <TItem extends object>(
|
|
768
|
+
\tprops: DataViewsConfig<TItem>,
|
|
769
|
+
) => ReturnType<typeof DataViews>;
|
|
770
|
+
|
|
771
|
+
const EMPTY_DATA_SET: ${dataSetTypeName} = {
|
|
772
|
+
\titems: [],
|
|
773
|
+
\tpaginationInfo: {
|
|
774
|
+
\t\ttotalItems: 0,
|
|
775
|
+
\t\ttotalPages: 1,
|
|
776
|
+
\t},
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
export function ${componentName}() {
|
|
780
|
+
\tconst [view, setView] = useState<DataViewsView<${itemTypeName}>>(
|
|
781
|
+
\t\t${dataViewsName}.defaultView,
|
|
782
|
+
\t);
|
|
783
|
+
\tconst {
|
|
784
|
+
\t\tdataSet = EMPTY_DATA_SET,
|
|
785
|
+
\t\terror,
|
|
786
|
+
\t\tisLoading,
|
|
787
|
+
\t} = ${useAdminViewDataName}(view);
|
|
788
|
+
\tconst config = ${dataViewsName}.createConfig({
|
|
789
|
+
\t\tdata: dataSet.items,
|
|
790
|
+
\t\tisLoading,
|
|
791
|
+
\t\tonChangeView: setView,
|
|
792
|
+
\t\tpaginationInfo: dataSet.paginationInfo,
|
|
793
|
+
\t\tview,
|
|
794
|
+
\t});
|
|
795
|
+
|
|
796
|
+
\treturn (
|
|
797
|
+
\t\t<div className="wp-typia-admin-view-screen">
|
|
798
|
+
\t\t\t<header className="wp-typia-admin-view-screen__header">
|
|
799
|
+
\t\t\t\t<div>
|
|
800
|
+
\t\t\t\t\t<p className="wp-typia-admin-view-screen__eyebrow">
|
|
801
|
+
\t\t\t\t\t\t{ __( 'DataViews admin screen', ${quoteTsString(textDomain)} ) }
|
|
802
|
+
\t\t\t\t\t</p>
|
|
803
|
+
\t\t\t\t\t<h1>{ __( ${quoteTsString(title)}, ${quoteTsString(textDomain)} ) }</h1>
|
|
804
|
+
\t\t\t\t\t<p>
|
|
805
|
+
\t\t\t\t\t\t{ __( 'This screen reads from the WordPress core-data entity store. Extend data.ts when you need entity-specific field mapping or edit flows.', ${quoteTsString(textDomain)} ) }
|
|
806
|
+
\t\t\t\t\t</p>
|
|
807
|
+
\t\t\t\t</div>
|
|
808
|
+
\t\t\t\t<div className="wp-typia-admin-view-screen__actions">
|
|
809
|
+
\t\t\t\t\t{ isLoading ? <Spinner /> : null }
|
|
810
|
+
\t\t\t\t</div>
|
|
811
|
+
\t\t\t</header>
|
|
812
|
+
\t\t\t{ error ? (
|
|
813
|
+
\t\t\t\t<Notice isDismissible={ false } status="error">
|
|
814
|
+
\t\t\t\t\t{ error }
|
|
815
|
+
\t\t\t\t</Notice>
|
|
816
|
+
\t\t\t) : null }
|
|
817
|
+
\t\t\t<TypedDataViews<${itemTypeName}> { ...config } />
|
|
818
|
+
\t\t</div>
|
|
819
|
+
\t);
|
|
820
|
+
}
|
|
821
|
+
`;
|
|
822
|
+
}
|
|
823
|
+
export function buildAdminViewEntrySource(adminViewSlug) {
|
|
824
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
825
|
+
const componentName = `${pascalName}AdminViewScreen`;
|
|
826
|
+
const rootId = `wp-typia-admin-view-${adminViewSlug}`;
|
|
827
|
+
return `import { createRoot } from '@wordpress/element';
|
|
828
|
+
|
|
829
|
+
import '@wordpress/dataviews/build-style/style.css';
|
|
830
|
+
import { ${componentName} } from './Screen';
|
|
831
|
+
import './style.scss';
|
|
832
|
+
|
|
833
|
+
const ROOT_ELEMENT_ID = ${quoteTsString(rootId)};
|
|
834
|
+
|
|
835
|
+
function mountAdminView() {
|
|
836
|
+
\tconst rootElement = document.getElementById(ROOT_ELEMENT_ID);
|
|
837
|
+
\tif (!rootElement) {
|
|
838
|
+
\t\treturn;
|
|
839
|
+
\t}
|
|
840
|
+
|
|
841
|
+
\tcreateRoot(rootElement).render(<${componentName} />);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
if (document.readyState === 'loading') {
|
|
845
|
+
\tdocument.addEventListener('DOMContentLoaded', mountAdminView);
|
|
846
|
+
} else {
|
|
847
|
+
\tmountAdminView();
|
|
848
|
+
}
|
|
849
|
+
`;
|
|
850
|
+
}
|
|
851
|
+
export function buildAdminViewStyleSource() {
|
|
852
|
+
return `.wp-typia-admin-view-screen {
|
|
853
|
+
\tbox-sizing: border-box;
|
|
854
|
+
\tmax-width: 1180px;
|
|
855
|
+
\tpadding: 24px 24px 48px 0;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
.wp-typia-admin-view-screen__header {
|
|
859
|
+
\talign-items: flex-start;
|
|
860
|
+
\tdisplay: flex;
|
|
861
|
+
\tgap: 24px;
|
|
862
|
+
\tjustify-content: space-between;
|
|
863
|
+
\tmargin-bottom: 24px;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
.wp-typia-admin-view-screen__header h1 {
|
|
867
|
+
\tfont-size: 28px;
|
|
868
|
+
\tline-height: 1.2;
|
|
869
|
+
\tmargin: 0 0 8px;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
.wp-typia-admin-view-screen__header p {
|
|
873
|
+
\tmax-width: 680px;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
.wp-typia-admin-view-screen__eyebrow {
|
|
877
|
+
\tcolor: #3858e9;
|
|
878
|
+
\tfont-size: 11px;
|
|
879
|
+
\tfont-weight: 600;
|
|
880
|
+
\tletter-spacing: 0.08em;
|
|
881
|
+
\tmargin: 0 0 8px;
|
|
882
|
+
\ttext-transform: uppercase;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
.wp-typia-admin-view-screen__actions {
|
|
886
|
+
\talign-items: center;
|
|
887
|
+
\tdisplay: flex;
|
|
888
|
+
\tgap: 12px;
|
|
889
|
+
}
|
|
890
|
+
`;
|
|
891
|
+
}
|
|
892
|
+
export function buildAdminViewPhpSource(adminViewSlug, workspace) {
|
|
893
|
+
const workspaceBaseName = workspace.packageName.split('/').pop() ?? workspace.packageName;
|
|
894
|
+
const phpSlug = adminViewSlug.replace(/-/g, '_');
|
|
895
|
+
const functionPrefix = `${workspace.workspace.phpPrefix}_${phpSlug}`;
|
|
896
|
+
const menuSlugFunctionName = `${functionPrefix}_admin_view_menu_slug`;
|
|
897
|
+
const renderFunctionName = `${functionPrefix}_render_admin_view`;
|
|
898
|
+
const registerFunctionName = `${functionPrefix}_register_admin_view`;
|
|
899
|
+
const enqueueFunctionName = `${functionPrefix}_enqueue_admin_view`;
|
|
900
|
+
const hookGlobalName = `${functionPrefix}_admin_view_hook`;
|
|
901
|
+
const rootId = `wp-typia-admin-view-${adminViewSlug}`;
|
|
902
|
+
const title = toTitleCase(adminViewSlug);
|
|
903
|
+
return `<?php
|
|
904
|
+
if ( ! defined( 'ABSPATH' ) ) {
|
|
905
|
+
\treturn;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
if ( ! function_exists( '${menuSlugFunctionName}' ) ) {
|
|
909
|
+
\tfunction ${menuSlugFunctionName}() : string {
|
|
910
|
+
\t\treturn '${workspaceBaseName}-${adminViewSlug}';
|
|
911
|
+
\t}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
if ( ! function_exists( '${renderFunctionName}' ) ) {
|
|
915
|
+
\tfunction ${renderFunctionName}() : void {
|
|
916
|
+
\t\t?>
|
|
917
|
+
\t\t<div class="wrap">
|
|
918
|
+
\t\t\t<div id="${rootId}"></div>
|
|
919
|
+
\t\t</div>
|
|
920
|
+
\t\t<?php
|
|
921
|
+
\t}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
if ( ! function_exists( '${registerFunctionName}' ) ) {
|
|
925
|
+
\tfunction ${registerFunctionName}() : void {
|
|
926
|
+
\t\t$GLOBALS['${hookGlobalName}'] = add_submenu_page(
|
|
927
|
+
\t\t\t'tools.php',
|
|
928
|
+
\t\t\t__( ${quotePhpString(title)}, ${quotePhpString(workspace.workspace.textDomain)} ),
|
|
929
|
+
\t\t\t__( ${quotePhpString(title)}, ${quotePhpString(workspace.workspace.textDomain)} ),
|
|
930
|
+
\t\t\t'edit_posts',
|
|
931
|
+
\t\t\t${menuSlugFunctionName}(),
|
|
932
|
+
\t\t\t'${renderFunctionName}'
|
|
933
|
+
\t\t);
|
|
934
|
+
\t}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if ( ! function_exists( '${enqueueFunctionName}' ) ) {
|
|
938
|
+
\tfunction ${enqueueFunctionName}( string $hook_suffix ) : void {
|
|
939
|
+
\t\t$page_hook = isset( $GLOBALS['${hookGlobalName}'] ) && is_string( $GLOBALS['${hookGlobalName}'] )
|
|
940
|
+
\t\t\t? $GLOBALS['${hookGlobalName}']
|
|
941
|
+
\t\t\t: '';
|
|
942
|
+
|
|
943
|
+
\t\tif ( $page_hook !== $hook_suffix ) {
|
|
944
|
+
\t\t\treturn;
|
|
945
|
+
\t\t}
|
|
946
|
+
|
|
947
|
+
\t\t$plugin_file = dirname( __DIR__, 2 ) . '/${workspaceBaseName}.php';
|
|
948
|
+
\t\t$script_path = dirname( __DIR__, 2 ) . '/${ADMIN_VIEWS_SCRIPT}';
|
|
949
|
+
\t\t$asset_path = dirname( __DIR__, 2 ) . '/${ADMIN_VIEWS_ASSET}';
|
|
950
|
+
\t\t$style_path = dirname( __DIR__, 2 ) . '/${ADMIN_VIEWS_STYLE}';
|
|
951
|
+
\t\t$style_rtl_path = dirname( __DIR__, 2 ) . '/${ADMIN_VIEWS_STYLE_RTL}';
|
|
952
|
+
|
|
953
|
+
\t\tif ( ! file_exists( $script_path ) || ! file_exists( $asset_path ) ) {
|
|
954
|
+
\t\t\treturn;
|
|
955
|
+
\t\t}
|
|
956
|
+
|
|
957
|
+
\t\t$asset = require $asset_path;
|
|
958
|
+
\t\tif ( ! is_array( $asset ) ) {
|
|
959
|
+
\t\t\t$asset = array();
|
|
960
|
+
\t\t}
|
|
961
|
+
|
|
962
|
+
\t\t$dependencies = isset( $asset['dependencies'] ) && is_array( $asset['dependencies'] )
|
|
963
|
+
\t\t\t? $asset['dependencies']
|
|
964
|
+
\t\t\t: array();
|
|
965
|
+
|
|
966
|
+
\t\twp_enqueue_script(
|
|
967
|
+
\t\t\t'${workspaceBaseName}-${adminViewSlug}-admin-view',
|
|
968
|
+
\t\t\tplugins_url( '${ADMIN_VIEWS_SCRIPT}', $plugin_file ),
|
|
969
|
+
\t\t\t$dependencies,
|
|
970
|
+
\t\t\tisset( $asset['version'] ) ? $asset['version'] : filemtime( $script_path ),
|
|
971
|
+
\t\t\ttrue
|
|
972
|
+
\t\t);
|
|
973
|
+
|
|
974
|
+
\t\tif ( file_exists( $style_path ) ) {
|
|
975
|
+
\t\t\twp_enqueue_style(
|
|
976
|
+
\t\t\t\t'${workspaceBaseName}-${adminViewSlug}-admin-view',
|
|
977
|
+
\t\t\t\tplugins_url( '${ADMIN_VIEWS_STYLE}', $plugin_file ),
|
|
978
|
+
\t\t\t\tarray( 'wp-components' ),
|
|
979
|
+
\t\t\t\tisset( $asset['version'] ) ? $asset['version'] : filemtime( $style_path )
|
|
980
|
+
\t\t\t);
|
|
981
|
+
\t\t\tif ( file_exists( $style_rtl_path ) ) {
|
|
982
|
+
\t\t\t\twp_style_add_data( '${workspaceBaseName}-${adminViewSlug}-admin-view', 'rtl', 'replace' );
|
|
983
|
+
\t\t\t}
|
|
984
|
+
\t\t}
|
|
985
|
+
\t}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
add_action( 'admin_menu', '${registerFunctionName}' );
|
|
989
|
+
add_action( 'admin_enqueue_scripts', '${enqueueFunctionName}' );
|
|
990
|
+
`;
|
|
991
|
+
}
|