@dusted/anqst 1.5.1 → 1.7.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/README.md +45 -4
- package/dist/src/app.js +78 -15
- package/dist/src/boundary-codec-analysis.js +4 -2
- package/dist/src/emit.js +1556 -120
- package/dist/src/layout.js +9 -3
- package/dist/src/parser.js +161 -6
- package/dist/src/project.js +3 -3
- package/dist/src/verify.js +4 -2
- package/package.json +1 -1
- package/spec/AnQst-Spec-DSL.d.ts +22 -24
package/dist/src/layout.js
CHANGED
|
@@ -41,8 +41,12 @@ function anqstSettingsFileName(widgetName) {
|
|
|
41
41
|
function anqstSettingsRelativePath(widgetName) {
|
|
42
42
|
return `./${exports.ANQST_ROOT_DIRNAME}/${anqstSettingsFileName(widgetName)}`;
|
|
43
43
|
}
|
|
44
|
-
function generatedFrontendDirName(widgetName) {
|
|
45
|
-
|
|
44
|
+
function generatedFrontendDirName(widgetName, target) {
|
|
45
|
+
if (target === "AngularService")
|
|
46
|
+
return `${widgetName}_Angular`;
|
|
47
|
+
if (target === "VanillaTS")
|
|
48
|
+
return `${widgetName}_VanillaTS`;
|
|
49
|
+
return `${widgetName}_VanillaJS`;
|
|
46
50
|
}
|
|
47
51
|
function generatedNodeExpressDirName(widgetName) {
|
|
48
52
|
return `${widgetName}_anQst`;
|
|
@@ -56,7 +60,9 @@ function resolveGeneratedLayoutPaths(cwd, widgetName) {
|
|
|
56
60
|
const designerPluginRoot = node_path_1.default.join(cppQtWidgetRoot, "designerPlugin");
|
|
57
61
|
return {
|
|
58
62
|
generatedRoot,
|
|
59
|
-
|
|
63
|
+
angularFrontendRoot: node_path_1.default.join(generatedRoot, "frontend", generatedFrontendDirName(widgetName, "AngularService")),
|
|
64
|
+
vanillaTsFrontendRoot: node_path_1.default.join(generatedRoot, "frontend", generatedFrontendDirName(widgetName, "VanillaTS")),
|
|
65
|
+
vanillaJsFrontendRoot: node_path_1.default.join(generatedRoot, "frontend", generatedFrontendDirName(widgetName, "VanillaJS")),
|
|
60
66
|
nodeExpressRoot: node_path_1.default.join(generatedRoot, "backend", "node", "express", generatedNodeExpressDirName(widgetName)),
|
|
61
67
|
cppCmakeRoot: node_path_1.default.join(generatedRoot, "backend", "cpp", "cmake"),
|
|
62
68
|
cppQtWidgetRoot,
|
package/dist/src/parser.js
CHANGED
|
@@ -24,6 +24,22 @@ function qNameToText(name) {
|
|
|
24
24
|
return name.text;
|
|
25
25
|
return `${qNameToText(name.left)}.${name.right.text}`;
|
|
26
26
|
}
|
|
27
|
+
function textToEntityName(text) {
|
|
28
|
+
const parts = text.split(".");
|
|
29
|
+
let current = typescript_1.default.factory.createIdentifier(parts[0] ?? text);
|
|
30
|
+
for (const part of parts.slice(1)) {
|
|
31
|
+
current = typescript_1.default.factory.createQualifiedName(current, typescript_1.default.factory.createIdentifier(part));
|
|
32
|
+
}
|
|
33
|
+
return current;
|
|
34
|
+
}
|
|
35
|
+
function textToExpressionName(text) {
|
|
36
|
+
const parts = text.split(".");
|
|
37
|
+
let current = typescript_1.default.factory.createIdentifier(parts[0] ?? text);
|
|
38
|
+
for (const part of parts.slice(1)) {
|
|
39
|
+
current = typescript_1.default.factory.createPropertyAccessExpression(current, typescript_1.default.factory.createIdentifier(part));
|
|
40
|
+
}
|
|
41
|
+
return current;
|
|
42
|
+
}
|
|
27
43
|
function collectReferencedTypeNames(node) {
|
|
28
44
|
const refs = new Set();
|
|
29
45
|
const visit = (n) => {
|
|
@@ -33,6 +49,9 @@ function collectReferencedTypeNames(node) {
|
|
|
33
49
|
else if (typescript_1.default.isExpressionWithTypeArguments(n) && typescript_1.default.isIdentifier(n.expression)) {
|
|
34
50
|
refs.add(n.expression.text);
|
|
35
51
|
}
|
|
52
|
+
else if (typescript_1.default.isTypeQueryNode(n)) {
|
|
53
|
+
refs.add(qNameToText(n.exprName));
|
|
54
|
+
}
|
|
36
55
|
typescript_1.default.forEachChild(n, visit);
|
|
37
56
|
};
|
|
38
57
|
visit(node);
|
|
@@ -294,10 +313,118 @@ function requiresLocalImportResolution(moduleName) {
|
|
|
294
313
|
return false;
|
|
295
314
|
return moduleName.includes("/");
|
|
296
315
|
}
|
|
297
|
-
function
|
|
316
|
+
function collectTopLevelTypeDecls(source) {
|
|
317
|
+
const out = new Map();
|
|
318
|
+
for (const stmt of source.statements) {
|
|
319
|
+
if ((typescript_1.default.isInterfaceDeclaration(stmt) || typescript_1.default.isTypeAliasDeclaration(stmt)) && stmt.name) {
|
|
320
|
+
out.set(stmt.name.text, stmt);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
325
|
+
function collectReachableImportedTypeNames(topLevelDecls, rootNames) {
|
|
326
|
+
const queue = [...rootNames];
|
|
327
|
+
const seen = new Set();
|
|
328
|
+
const ordered = [];
|
|
329
|
+
while (queue.length > 0) {
|
|
330
|
+
const current = queue.shift();
|
|
331
|
+
if (seen.has(current))
|
|
332
|
+
continue;
|
|
333
|
+
seen.add(current);
|
|
334
|
+
const node = topLevelDecls.get(current);
|
|
335
|
+
if (!node)
|
|
336
|
+
continue;
|
|
337
|
+
ordered.push(current);
|
|
338
|
+
const decl = {
|
|
339
|
+
referencedTypeNames: collectReferencedTypeNames(node)
|
|
340
|
+
};
|
|
341
|
+
for (const ref of decl.referencedTypeNames) {
|
|
342
|
+
if (topLevelDecls.has(ref) && !seen.has(ref)) {
|
|
343
|
+
queue.push(ref);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return ordered;
|
|
348
|
+
}
|
|
349
|
+
function allocateSyntheticImportedTypeName(sourceName, usedNames) {
|
|
350
|
+
const cleaned = sourceName
|
|
351
|
+
.replace(/[^A-Za-z0-9_]/g, "_")
|
|
352
|
+
.replace(/_+/g, "_")
|
|
353
|
+
.replace(/^_+|_+$/g, "");
|
|
354
|
+
const base = `AnQstImported_${cleaned || "Type"}`;
|
|
355
|
+
let candidate = base;
|
|
356
|
+
let i = 2;
|
|
357
|
+
while (usedNames.has(candidate)) {
|
|
358
|
+
candidate = `${base}_${i}`;
|
|
359
|
+
i += 1;
|
|
360
|
+
}
|
|
361
|
+
usedNames.add(candidate);
|
|
362
|
+
return candidate;
|
|
363
|
+
}
|
|
364
|
+
function rewriteImportedTypeDecl(importedSource, node, finalName, nameMap) {
|
|
365
|
+
const renamed = typescript_1.default.isInterfaceDeclaration(node)
|
|
366
|
+
? typescript_1.default.factory.updateInterfaceDeclaration(node, node.modifiers, typescript_1.default.factory.createIdentifier(finalName), node.typeParameters, node.heritageClauses, node.members)
|
|
367
|
+
: typescript_1.default.factory.updateTypeAliasDeclaration(node, node.modifiers, typescript_1.default.factory.createIdentifier(finalName), node.typeParameters, node.type);
|
|
368
|
+
const transformed = typescript_1.default.transform(renamed, [(context) => {
|
|
369
|
+
const visitor = (child) => {
|
|
370
|
+
if (typescript_1.default.isTypeReferenceNode(child)) {
|
|
371
|
+
const mapped = nameMap.get(qNameToText(child.typeName));
|
|
372
|
+
if (mapped) {
|
|
373
|
+
return typescript_1.default.factory.updateTypeReferenceNode(child, textToEntityName(mapped), child.typeArguments);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
else if (typescript_1.default.isExpressionWithTypeArguments(child)) {
|
|
377
|
+
const exprText = typescript_1.default.isIdentifier(child.expression) || typescript_1.default.isPropertyAccessExpression(child.expression)
|
|
378
|
+
? child.expression.getText(importedSource)
|
|
379
|
+
: null;
|
|
380
|
+
const mapped = exprText ? nameMap.get(exprText) : null;
|
|
381
|
+
if (mapped) {
|
|
382
|
+
return typescript_1.default.factory.updateExpressionWithTypeArguments(child, textToExpressionName(mapped), child.typeArguments);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
else if (typescript_1.default.isTypeQueryNode(child)) {
|
|
386
|
+
const mapped = nameMap.get(qNameToText(child.exprName));
|
|
387
|
+
if (mapped) {
|
|
388
|
+
return typescript_1.default.factory.updateTypeQueryNode(child, textToEntityName(mapped), child.typeArguments);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return typescript_1.default.visitEachChild(child, visitor, context);
|
|
392
|
+
};
|
|
393
|
+
return (root) => typescript_1.default.visitNode(root, visitor);
|
|
394
|
+
}]);
|
|
395
|
+
const rewritten = transformed.transformed[0];
|
|
396
|
+
transformed.dispose();
|
|
397
|
+
const printer = typescript_1.default.createPrinter({ newLine: typescript_1.default.NewLineKind.LineFeed });
|
|
398
|
+
const rewrittenSource = typescript_1.default.createSourceFile("__anqst_imported_decl.ts", "", typescript_1.default.ScriptTarget.Latest, true, typescript_1.default.ScriptKind.TS);
|
|
399
|
+
const nodeText = printer.printNode(typescript_1.default.EmitHint.Unspecified, rewritten, rewrittenSource);
|
|
400
|
+
return {
|
|
401
|
+
name: finalName,
|
|
402
|
+
kind: typescript_1.default.isInterfaceDeclaration(rewritten) ? "interface" : "type",
|
|
403
|
+
nodeText,
|
|
404
|
+
referencedTypeNames: collectReferencedTypeNames(rewritten),
|
|
405
|
+
loc: locFromNode(importedSource, node)
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function createImportedAliasDecl(aliasName, targetName, loc) {
|
|
409
|
+
const nodeText = `type ${aliasName} = ${targetName};`;
|
|
410
|
+
const source = typescript_1.default.createSourceFile("__anqst_import_alias.ts", nodeText, typescript_1.default.ScriptTarget.Latest, true, typescript_1.default.ScriptKind.TS);
|
|
411
|
+
const stmt = source.statements.find(typescript_1.default.isTypeAliasDeclaration);
|
|
412
|
+
if (!stmt) {
|
|
413
|
+
throw new Error(`Unable to synthesize imported alias declaration for ${aliasName}.`);
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
name: aliasName,
|
|
417
|
+
kind: "type",
|
|
418
|
+
nodeText,
|
|
419
|
+
referencedTypeNames: collectReferencedTypeNames(stmt),
|
|
420
|
+
loc
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function parseImportedTypeDecls(specFilePath, source, reservedTypeNames = new Set()) {
|
|
298
424
|
const importedTypeDecls = new Map();
|
|
299
425
|
const importedTypeSymbols = new Set();
|
|
300
426
|
const specImports = [];
|
|
427
|
+
const usedImportedNames = new Set(reservedTypeNames);
|
|
301
428
|
for (const stmt of source.statements) {
|
|
302
429
|
if (!typescript_1.default.isImportDeclaration(stmt) || !stmt.importClause || !typescript_1.default.isStringLiteral(stmt.moduleSpecifier))
|
|
303
430
|
continue;
|
|
@@ -335,10 +462,38 @@ function parseImportedTypeDecls(specFilePath, source) {
|
|
|
335
462
|
}
|
|
336
463
|
const text = node_fs_1.default.readFileSync(resolved, "utf8");
|
|
337
464
|
const importedSource = typescript_1.default.createSourceFile(resolved, text, typescript_1.default.ScriptTarget.Latest, true, typescript_1.default.ScriptKind.TS);
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
465
|
+
const topLevelDecls = collectTopLevelTypeDecls(importedSource);
|
|
466
|
+
const directAliasesBySourceName = new Map();
|
|
467
|
+
for (const namedImport of importModel.namedImports) {
|
|
468
|
+
if (!topLevelDecls.has(namedImport.importedName))
|
|
469
|
+
continue;
|
|
470
|
+
const aliases = directAliasesBySourceName.get(namedImport.importedName) ?? [];
|
|
471
|
+
aliases.push(namedImport.localName);
|
|
472
|
+
directAliasesBySourceName.set(namedImport.importedName, aliases);
|
|
473
|
+
}
|
|
474
|
+
const reachableSourceNames = collectReachableImportedTypeNames(topLevelDecls, directAliasesBySourceName.keys());
|
|
475
|
+
if (reachableSourceNames.length === 0)
|
|
476
|
+
continue;
|
|
477
|
+
const canonicalNameBySourceName = new Map();
|
|
478
|
+
for (const sourceName of reachableSourceNames) {
|
|
479
|
+
const directAliases = directAliasesBySourceName.get(sourceName);
|
|
480
|
+
if (directAliases && directAliases.length > 0) {
|
|
481
|
+
canonicalNameBySourceName.set(sourceName, directAliases[0]);
|
|
482
|
+
usedImportedNames.add(directAliases[0]);
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
canonicalNameBySourceName.set(sourceName, allocateSyntheticImportedTypeName(sourceName, usedImportedNames));
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
for (const sourceName of reachableSourceNames) {
|
|
489
|
+
const node = topLevelDecls.get(sourceName);
|
|
490
|
+
const finalName = canonicalNameBySourceName.get(sourceName);
|
|
491
|
+
if (!node || !finalName)
|
|
492
|
+
continue;
|
|
493
|
+
importedTypeDecls.set(finalName, rewriteImportedTypeDecl(importedSource, node, finalName, canonicalNameBySourceName));
|
|
494
|
+
const directAliases = directAliasesBySourceName.get(sourceName) ?? [];
|
|
495
|
+
for (const alias of directAliases.slice(1)) {
|
|
496
|
+
importedTypeDecls.set(alias, createImportedAliasDecl(alias, finalName, locFromNode(importedSource, node)));
|
|
342
497
|
}
|
|
343
498
|
}
|
|
344
499
|
}
|
|
@@ -398,7 +553,7 @@ function parseSpecFileAst(specFilePath) {
|
|
|
398
553
|
namespaceTypeDecls.push(parseTypeDecl(source, stmt));
|
|
399
554
|
}
|
|
400
555
|
}
|
|
401
|
-
const importInfo = parseImportedTypeDecls(specFilePath, source);
|
|
556
|
+
const importInfo = parseImportedTypeDecls(specFilePath, source, new Set(namespaceTypeDecls.map((decl) => decl.name)));
|
|
402
557
|
return {
|
|
403
558
|
filePath: specFilePath,
|
|
404
559
|
widgetName: ns.name.text,
|
package/dist/src/project.js
CHANGED
|
@@ -16,7 +16,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
16
16
|
const node_path_1 = __importDefault(require("node:path"));
|
|
17
17
|
const errors_1 = require("./errors");
|
|
18
18
|
const layout_1 = require("./layout");
|
|
19
|
-
exports.DEFAULT_ANQST_GENERATE_TARGETS = ["QWidget", "AngularService", "node_express_ws"];
|
|
19
|
+
exports.DEFAULT_ANQST_GENERATE_TARGETS = ["QWidget", "AngularService", "VanillaTS", "VanillaJS", "node_express_ws"];
|
|
20
20
|
const ANQST_DSL_IMPORT_LINE = 'import type { AnQst } from "@dusted/anqst";';
|
|
21
21
|
const ANQST_BUILD_HOOK = "npx anqst build";
|
|
22
22
|
function readJsonFile(filePath) {
|
|
@@ -153,7 +153,7 @@ function updateTsConfig(cwd, widgetName) {
|
|
|
153
153
|
? {}
|
|
154
154
|
: ensureObject(compilerOptions.paths, "Invalid tsconfig.json: expected object at 'compilerOptions.paths'.");
|
|
155
155
|
compilerOptions.paths = pathsObject;
|
|
156
|
-
const generatedAliasPath = `AnQst/generated/frontend/${(0, layout_1.generatedFrontendDirName)(widgetName)}/*`;
|
|
156
|
+
const generatedAliasPath = `AnQst/generated/frontend/${(0, layout_1.generatedFrontendDirName)(widgetName, "AngularService")}/*`;
|
|
157
157
|
const existingAlias = pathsObject["anqst-generated/*"];
|
|
158
158
|
const aliasList = Array.isArray(existingAlias)
|
|
159
159
|
? existingAlias.filter((entry) => typeof entry === "string")
|
|
@@ -164,7 +164,7 @@ function updateTsConfig(cwd, widgetName) {
|
|
|
164
164
|
pathsObject["anqst-generated/*"] = [...new Set(aliasList)];
|
|
165
165
|
if (Array.isArray(tsConfig.include)) {
|
|
166
166
|
const includeList = tsConfig.include.filter((entry) => typeof entry === "string");
|
|
167
|
-
const generatedTypesPattern = `AnQst/generated/frontend/${(0, layout_1.generatedFrontendDirName)(widgetName)}/**/*.d.ts`;
|
|
167
|
+
const generatedTypesPattern = `AnQst/generated/frontend/${(0, layout_1.generatedFrontendDirName)(widgetName, "AngularService")}/**/*.d.ts`;
|
|
168
168
|
if (!includeList.includes(generatedTypesPattern)) {
|
|
169
169
|
includeList.push(generatedTypesPattern);
|
|
170
170
|
}
|
package/dist/src/verify.js
CHANGED
|
@@ -135,8 +135,10 @@ function collectReachableTypeNames(spec) {
|
|
|
135
135
|
const allDecls = new Map();
|
|
136
136
|
for (const d of spec.namespaceTypeDecls)
|
|
137
137
|
allDecls.set(d.name, d);
|
|
138
|
-
for (const [name, d] of spec.importedTypeDecls)
|
|
139
|
-
allDecls.
|
|
138
|
+
for (const [name, d] of spec.importedTypeDecls) {
|
|
139
|
+
if (!allDecls.has(name))
|
|
140
|
+
allDecls.set(name, d);
|
|
141
|
+
}
|
|
140
142
|
const queue = [];
|
|
141
143
|
const seen = new Set();
|
|
142
144
|
for (const d of spec.namespaceTypeDecls)
|
package/package.json
CHANGED
package/spec/AnQst-Spec-DSL.d.ts
CHANGED
|
@@ -31,13 +31,15 @@ export namespace AnQst {
|
|
|
31
31
|
interface Service { }
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Declare service `InterfaceName` as development-mode capable transport service.
|
|
34
|
+
* Declare service `InterfaceName` as development-mode capable browser transport service.
|
|
35
35
|
*
|
|
36
36
|
* @remarks
|
|
37
37
|
* - Extends the same method/property semantics as `AnQst.Service`.
|
|
38
|
-
* - Signals to the generator/runtime that this widget should emit dual-transport bridge support
|
|
38
|
+
* - Signals to the generator/runtime that this widget should emit dual-transport browser bridge support
|
|
39
39
|
* (Qt WebChannel + HTTP/WebSocket development bridge).
|
|
40
40
|
* - Existing generated service APIs remain unchanged.
|
|
41
|
+
* - The name is historical. The capability applies to browser frontend profiles generally,
|
|
42
|
+
* not only Angular-specific generation.
|
|
41
43
|
*
|
|
42
44
|
* @example
|
|
43
45
|
* export interface UserService extends AnQst.AngularHTTPBaseServerClass { }
|
|
@@ -63,7 +65,7 @@ export namespace AnQst {
|
|
|
63
65
|
* @example
|
|
64
66
|
* // AnQst spec:
|
|
65
67
|
* getUserById(userId: string): AnQst.Call<User>
|
|
66
|
-
* //
|
|
68
|
+
* //Browser app:
|
|
67
69
|
* const user: User = await this.userService.getUserById("abc");
|
|
68
70
|
*/
|
|
69
71
|
interface Call<T, Config extends CallConfig = {}> { dummy: T; config?: Config }
|
|
@@ -89,7 +91,7 @@ export namespace AnQst {
|
|
|
89
91
|
* @example
|
|
90
92
|
* // AnQst spec:
|
|
91
93
|
* getUsernameSubstring(from: number, to:number ): AnQst.Slot<string>
|
|
92
|
-
* //
|
|
94
|
+
* //Browser app:
|
|
93
95
|
* this.userService.onSlot.getUsername( provider );
|
|
94
96
|
* //Parent:
|
|
95
97
|
* auto currentFormUsername = userMgmt.getUsername();
|
|
@@ -111,7 +113,7 @@ export namespace AnQst {
|
|
|
111
113
|
* @example
|
|
112
114
|
* // AnQst spec:
|
|
113
115
|
* complain(whine: string): AnQst.Emitter;
|
|
114
|
-
* //
|
|
116
|
+
* //Browser app:
|
|
115
117
|
* this.userService.complain("Why won't you LISTEN!");
|
|
116
118
|
*/
|
|
117
119
|
interface Emitter { }
|
|
@@ -121,14 +123,14 @@ export namespace AnQst {
|
|
|
121
123
|
* Declare reactive `PropertyName`:`OutputType` and set.`PropertyName`(arg: `OutputType`)
|
|
122
124
|
* @remarks
|
|
123
125
|
* - **Parent** -> Widget
|
|
124
|
-
* True
|
|
126
|
+
* True frontend reactive semantics (Property updates, accessor reflects latest value, no return path, no registration requirement)
|
|
125
127
|
* - Flow:
|
|
126
128
|
* - Parent: Sets generated widget property `PropertyName`.
|
|
127
129
|
* - Widget: Service updates readonly property `PropertyName` and emits signal.
|
|
128
130
|
* @example
|
|
129
131
|
* // AnQst spec:
|
|
130
132
|
* activeUsers: AnQst.Output<number>;
|
|
131
|
-
* //
|
|
133
|
+
* //Browser app:
|
|
132
134
|
* <p>{{ userService.activeUsers() }}</p>
|
|
133
135
|
* //Parent:
|
|
134
136
|
* int users = userMgmt.activeUsers;
|
|
@@ -148,7 +150,7 @@ export namespace AnQst {
|
|
|
148
150
|
* @example
|
|
149
151
|
* // AnQst spec:
|
|
150
152
|
* currentUsername: AnQst.Input<string>;
|
|
151
|
-
* //
|
|
153
|
+
* //Browser app:
|
|
152
154
|
* <input type="text" placeholder="Your Name Here" (input)="userService.set.currentUsername(($event.target as HTMLInputElement).value)" />
|
|
153
155
|
* //Parent:
|
|
154
156
|
* QString userName = userMgmt.currentUsername;
|
|
@@ -160,15 +162,15 @@ export namespace AnQst {
|
|
|
160
162
|
* Declare drop-target `PropertyName`:`PayloadType`
|
|
161
163
|
* @remarks
|
|
162
164
|
* - **Parent** -> Widget (framework-mediated)
|
|
163
|
-
* - True
|
|
165
|
+
* - True frontend reactive semantics.
|
|
164
166
|
* - Flow:
|
|
165
167
|
* - External: A Qt widget initiates a QDrag carrying QMimeData.
|
|
166
168
|
* - Parent: AnQstWebHostBase intercepts the drop via event filter on the
|
|
167
169
|
* embedded QWebEngineView's rendering surface.
|
|
168
170
|
* - Parent: QMimeData for the accepted format is deserialized from a JSON
|
|
169
171
|
* array of normalized AnQst wire items and forwarded through the bridge.
|
|
170
|
-
* - Widget: Service updates
|
|
171
|
-
* payload and drop coordinates.
|
|
172
|
+
* - Widget: Service updates the generated accessor for `PropertyName` with the deserialized
|
|
173
|
+
* payload and drop coordinates. Browser application code reacts through the generated frontend API.
|
|
172
174
|
* - MIME type is convention-derived: `application/anqst-dragdropevent_<ServiceName>-<TypeName>`.
|
|
173
175
|
* - The source QWidget must serialize the generated AnQst wire payload as a
|
|
174
176
|
* JSON array under the same MIME type. Generated Qt widgets expose helper
|
|
@@ -178,11 +180,9 @@ export namespace AnQst {
|
|
|
178
180
|
* @example
|
|
179
181
|
* // AnQst spec:
|
|
180
182
|
* trackDropped: AnQst.DropTarget<Track>;
|
|
181
|
-
* //
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* if (drop !== null) { console.log(drop.payload, drop.x, drop.y); }
|
|
185
|
-
* });
|
|
183
|
+
* // Browser app:
|
|
184
|
+
* const drop = frontend.services.DragService.trackDropped();
|
|
185
|
+
* if (drop !== null) { console.log(drop.payload, drop.x, drop.y); }
|
|
186
186
|
*/
|
|
187
187
|
interface DropTarget<T> { dummy: T }
|
|
188
188
|
|
|
@@ -191,7 +191,7 @@ export namespace AnQst {
|
|
|
191
191
|
* Declare hover-target `PropertyName`:`PayloadType`
|
|
192
192
|
* @remarks
|
|
193
193
|
* - **Parent** -> Widget (framework-mediated)
|
|
194
|
-
* - True
|
|
194
|
+
* - True frontend reactive semantics.
|
|
195
195
|
* - Flow:
|
|
196
196
|
* - External: A Qt widget initiates a QDrag carrying QMimeData.
|
|
197
197
|
* - Parent: AnQstWebHostBase intercepts drag-move events via event filter on the
|
|
@@ -199,8 +199,8 @@ export namespace AnQst {
|
|
|
199
199
|
* - Parent: Payload is deserialized once on DragEnter from a JSON array of
|
|
200
200
|
* normalized AnQst wire items; subsequent DragMove events forward only
|
|
201
201
|
* the updated position.
|
|
202
|
-
* - Widget: Service updates
|
|
203
|
-
* coordinates.
|
|
202
|
+
* - Widget: Service updates the generated accessor for `PropertyName` with the payload and current
|
|
203
|
+
* coordinates. The accessor becomes null on DragLeave.
|
|
204
204
|
* - Shares the same MIME type convention as DropTarget: `application/anqst-dragdropevent_<ServiceName>-<TypeName>`.
|
|
205
205
|
* - A HoverTarget without a corresponding DropTarget means "show previews but reject the drop".
|
|
206
206
|
* - Optional config supports throttle tuning:
|
|
@@ -217,11 +217,9 @@ export namespace AnQst {
|
|
|
217
217
|
* trackHovering: AnQst.HoverTarget<Track, { maxRateHz: 10 }>;
|
|
218
218
|
* // AnQst spec (no throttling):
|
|
219
219
|
* trackHovering: AnQst.HoverTarget<Track, { maxRateHz: 0 }>;
|
|
220
|
-
* //
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
* if (hover !== null) { highlight(document.elementFromPoint(hover.x, hover.y)); }
|
|
224
|
-
* });
|
|
220
|
+
* // Browser app:
|
|
221
|
+
* const hover = frontend.services.DragService.trackHovering();
|
|
222
|
+
* if (hover !== null) { highlight(document.elementFromPoint(hover.x, hover.y)); }
|
|
225
223
|
*/
|
|
226
224
|
|
|
227
225
|
/**
|