@dusted/anqst 0.1.3 → 1.0.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/src/build-stamp.js +1 -1
- package/dist/src/emit.js +232 -13
- package/dist/src/parser.js +53 -5
- package/package.json +3 -2
- package/spec/AnQst-Spec-DSL.d.ts +327 -249
package/dist/src/build-stamp.js
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ANQST_BUILD_STAMP = void 0;
|
|
4
4
|
// Generated by scripts/build-with-stamp.js. Do not edit by hand.
|
|
5
|
-
exports.ANQST_BUILD_STAMP = "0.
|
|
5
|
+
exports.ANQST_BUILD_STAMP = "1.0.0/7bd720a_dirty_build_1/2026-03-29";
|
package/dist/src/emit.js
CHANGED
|
@@ -12,7 +12,6 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
12
12
|
const node_path_1 = __importDefault(require("node:path"));
|
|
13
13
|
const typescript_1 = __importDefault(require("typescript"));
|
|
14
14
|
const pngjs_1 = require("pngjs");
|
|
15
|
-
const build_stamp_1 = require("./build-stamp");
|
|
16
15
|
const layout_1 = require("./layout");
|
|
17
16
|
function stripAnQstType(typeText) {
|
|
18
17
|
return typeText
|
|
@@ -627,6 +626,27 @@ function buildCppTypeContext(spec) {
|
|
|
627
626
|
}
|
|
628
627
|
return normalizer.buildContext();
|
|
629
628
|
}
|
|
629
|
+
function collectDragDropMimeConstants(spec) {
|
|
630
|
+
const seen = new Set();
|
|
631
|
+
const constants = [];
|
|
632
|
+
for (const service of spec.services) {
|
|
633
|
+
for (const member of service.members) {
|
|
634
|
+
if ((member.kind === "DropTarget" || member.kind === "HoverTarget") && member.payloadTypeText) {
|
|
635
|
+
const typeName = member.payloadTypeText.replace(/\s/g, "");
|
|
636
|
+
const key = `${service.name}-${typeName}`;
|
|
637
|
+
if (seen.has(key))
|
|
638
|
+
continue;
|
|
639
|
+
seen.add(key);
|
|
640
|
+
constants.push({
|
|
641
|
+
typeName,
|
|
642
|
+
serviceName: service.name,
|
|
643
|
+
mimeType: `application/anqst-dragdropevent_${service.name}-${typeName}`
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return constants;
|
|
649
|
+
}
|
|
630
650
|
function renderTypesHeader(spec, cppTypes) {
|
|
631
651
|
const decls = cppTypes.orderedDecls.map(renderCppDecl).join("\n\n");
|
|
632
652
|
const metatypes = cppTypes.structNames
|
|
@@ -635,6 +655,11 @@ function renderTypesHeader(spec, cppTypes) {
|
|
|
635
655
|
`Q_DECLARE_METATYPE(QList<${spec.widgetName}::${name}>)`
|
|
636
656
|
])
|
|
637
657
|
.join("\n");
|
|
658
|
+
const mimeConstants = collectDragDropMimeConstants(spec);
|
|
659
|
+
const mimeConstantLines = mimeConstants
|
|
660
|
+
.map((c) => `static constexpr const char* kDragDropMime_${c.typeName} = "${c.mimeType}";`)
|
|
661
|
+
.join("\n");
|
|
662
|
+
const mimeBlock = mimeConstantLines.length > 0 ? `\n${mimeConstantLines}\n` : "";
|
|
638
663
|
return `#pragma once
|
|
639
664
|
#include <QString>
|
|
640
665
|
#include <QStringList>
|
|
@@ -647,7 +672,7 @@ function renderTypesHeader(spec, cppTypes) {
|
|
|
647
672
|
namespace ${spec.widgetName} {
|
|
648
673
|
|
|
649
674
|
${decls}
|
|
650
|
-
|
|
675
|
+
${mimeBlock}
|
|
651
676
|
} // namespace ${spec.widgetName}
|
|
652
677
|
|
|
653
678
|
${metatypes}
|
|
@@ -710,6 +735,15 @@ function renderWidgetHeader(spec, cppTypes) {
|
|
|
710
735
|
publicSlots.push(`void ${member.name}Slot(const ${cppType}& value);`);
|
|
711
736
|
}
|
|
712
737
|
}
|
|
738
|
+
else if (member.kind === "DropTarget" && member.payloadTypeText) {
|
|
739
|
+
const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
|
|
740
|
+
signals.push(`void ${member.name}(const ${cppType}& payload, double x, double y);`);
|
|
741
|
+
}
|
|
742
|
+
else if (member.kind === "HoverTarget" && member.payloadTypeText) {
|
|
743
|
+
const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
|
|
744
|
+
signals.push(`void ${member.name}(const ${cppType}& payload, double x, double y);`);
|
|
745
|
+
signals.push(`void ${member.name}Left();`);
|
|
746
|
+
}
|
|
713
747
|
}
|
|
714
748
|
}
|
|
715
749
|
return `#pragma once
|
|
@@ -860,6 +894,45 @@ function renderCppStub(spec, cppTypes) {
|
|
|
860
894
|
lines.push(` Q_UNUSED(kResourcesInitialized);`);
|
|
861
895
|
lines.push(` registerGeneratedMetaTypes();`);
|
|
862
896
|
lines.push(` installBridgeBindings();`);
|
|
897
|
+
for (const service of spec.services) {
|
|
898
|
+
for (const member of service.members) {
|
|
899
|
+
if (member.kind === "DropTarget" && member.payloadTypeText) {
|
|
900
|
+
const typeName = member.payloadTypeText.replace(/\s/g, "");
|
|
901
|
+
const mimeConst = `${spec.widgetName}::kDragDropMime_${typeName}`;
|
|
902
|
+
lines.push(` registerDropTarget(QStringLiteral("${service.name}"), QStringLiteral("${member.name}"), QString::fromUtf8(${mimeConst}));`);
|
|
903
|
+
}
|
|
904
|
+
else if (member.kind === "HoverTarget" && member.payloadTypeText) {
|
|
905
|
+
const typeName = member.payloadTypeText.replace(/\s/g, "");
|
|
906
|
+
const mimeConst = `${spec.widgetName}::kDragDropMime_${typeName}`;
|
|
907
|
+
lines.push(` registerHoverTarget(QStringLiteral("${service.name}"), QStringLiteral("${member.name}"), QString::fromUtf8(${mimeConst}), ${member.hoverThrottleMs});`);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
for (const service of spec.services) {
|
|
912
|
+
for (const member of service.members) {
|
|
913
|
+
if (member.kind === "DropTarget" && member.payloadTypeText) {
|
|
914
|
+
const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
|
|
915
|
+
lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_dropReceived, this, [this](const QString& service, const QString& member, const QVariant& payload, double x, double y) {`);
|
|
916
|
+
lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
|
|
917
|
+
lines.push(` emit ${member.name}(payload.value<${cppType}>(), x, y);`);
|
|
918
|
+
lines.push(` }`);
|
|
919
|
+
lines.push(` });`);
|
|
920
|
+
}
|
|
921
|
+
else if (member.kind === "HoverTarget" && member.payloadTypeText) {
|
|
922
|
+
const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
|
|
923
|
+
lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_hoverUpdated, this, [this](const QString& service, const QString& member, const QVariant& payload, double x, double y) {`);
|
|
924
|
+
lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
|
|
925
|
+
lines.push(` emit ${member.name}(payload.value<${cppType}>(), x, y);`);
|
|
926
|
+
lines.push(` }`);
|
|
927
|
+
lines.push(` });`);
|
|
928
|
+
lines.push(` QObject::connect(this, &AnQstWebHostBase::anQstBridge_hoverLeft, this, [this](const QString& service, const QString& member) {`);
|
|
929
|
+
lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
|
|
930
|
+
lines.push(` emit ${member.name}Left();`);
|
|
931
|
+
lines.push(` }`);
|
|
932
|
+
lines.push(` });`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
863
936
|
lines.push(` QObject::connect(this, &AnQstWebHostBase::onHostError, this, &${widgetClassName}::diagnosticsForwarded);`);
|
|
864
937
|
lines.push(` const bool rootOk = setContentRoot(QString::fromUtf8(kBootstrapContentRoot));`);
|
|
865
938
|
lines.push(` const bool bridgeOk = setBridgeObject(this, QString::fromUtf8(kBootstrapBridgeObject));`);
|
|
@@ -1007,13 +1080,7 @@ function renderCppStub(spec, cppTypes) {
|
|
|
1007
1080
|
lines.push(` }`);
|
|
1008
1081
|
}
|
|
1009
1082
|
}
|
|
1010
|
-
lines.push(` return
|
|
1011
|
-
lines.push(` {QStringLiteral("code"), QStringLiteral("HandlerNotRegisteredError")},`);
|
|
1012
|
-
lines.push(` {QStringLiteral("message"), QStringLiteral("No Call mapping found.")},`);
|
|
1013
|
-
lines.push(` {QStringLiteral("service"), service},`);
|
|
1014
|
-
lines.push(` {QStringLiteral("member"), member},`);
|
|
1015
|
-
lines.push(` {QStringLiteral("requestId"), QString()}`);
|
|
1016
|
-
lines.push(` };`);
|
|
1083
|
+
lines.push(` return QVariant();`);
|
|
1017
1084
|
lines.push(`}`);
|
|
1018
1085
|
lines.push("");
|
|
1019
1086
|
lines.push(`void ${widgetClassName}::handleGeneratedEmitter(const QString& service, const QString& member, const QVariantList& args) {`);
|
|
@@ -1172,7 +1239,24 @@ function normalizeSlashes(value) {
|
|
|
1172
1239
|
return value.split(node_path_1.default.sep).join("/");
|
|
1173
1240
|
}
|
|
1174
1241
|
function resolveActiveBuildStamp() {
|
|
1175
|
-
|
|
1242
|
+
const fromEnv = process.env.ANQST_BUILD_STAMP?.trim();
|
|
1243
|
+
if (fromEnv && fromEnv.length > 0) {
|
|
1244
|
+
return fromEnv;
|
|
1245
|
+
}
|
|
1246
|
+
const activePath = node_path_1.default.resolve(__dirname, "..", "..", ".anqstgen-version-active.json");
|
|
1247
|
+
if (!node_fs_1.default.existsSync(activePath)) {
|
|
1248
|
+
return "";
|
|
1249
|
+
}
|
|
1250
|
+
try {
|
|
1251
|
+
const parsed = JSON.parse(node_fs_1.default.readFileSync(activePath, "utf8"));
|
|
1252
|
+
if (typeof parsed.active === "string" && parsed.active.trim().length > 0) {
|
|
1253
|
+
return parsed.active.trim();
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
catch {
|
|
1257
|
+
return "";
|
|
1258
|
+
}
|
|
1259
|
+
return "";
|
|
1176
1260
|
}
|
|
1177
1261
|
function withBuildStamp(relativePath, content) {
|
|
1178
1262
|
const stamp = resolveActiveBuildStamp();
|
|
@@ -1315,6 +1399,19 @@ function renderTsService(spec, serviceName) {
|
|
|
1315
1399
|
constructorBodyLines.push(` this._bridge.onOutput("${serviceName}", "${m.name}", (value) => this._${m.name}.set(value as ${tsType}));`);
|
|
1316
1400
|
}
|
|
1317
1401
|
}
|
|
1402
|
+
if (m.kind === "DropTarget" && m.payloadTypeText) {
|
|
1403
|
+
const tsType = mapTypeTextToTs(m.payloadTypeText);
|
|
1404
|
+
fieldLines.push(` private readonly _${m.name} = signal<{ payload: ${tsType}; x: number; y: number } | null>(null);`);
|
|
1405
|
+
methodLines.push(` ${m.name}(): { payload: ${tsType}; x: number; y: number } | null { return this._${m.name}(); }`);
|
|
1406
|
+
constructorBodyLines.push(` this._bridge.onDrop("${serviceName}", "${m.name}", (payload, x, y) => this._${m.name}.set({ payload: payload as ${tsType}, x, y }));`);
|
|
1407
|
+
}
|
|
1408
|
+
if (m.kind === "HoverTarget" && m.payloadTypeText) {
|
|
1409
|
+
const tsType = mapTypeTextToTs(m.payloadTypeText);
|
|
1410
|
+
fieldLines.push(` private readonly _${m.name} = signal<{ payload: ${tsType}; x: number; y: number } | null>(null);`);
|
|
1411
|
+
methodLines.push(` ${m.name}(): { payload: ${tsType}; x: number; y: number } | null { return this._${m.name}(); }`);
|
|
1412
|
+
constructorBodyLines.push(` this._bridge.onHover("${serviceName}", "${m.name}", (payload, x, y) => this._${m.name}.set({ payload: payload as ${tsType}, x, y }));`);
|
|
1413
|
+
constructorBodyLines.push(` this._bridge.onHoverLeft("${serviceName}", "${m.name}", () => this._${m.name}.set(null));`);
|
|
1414
|
+
}
|
|
1318
1415
|
}
|
|
1319
1416
|
const constructorLines = [
|
|
1320
1417
|
" constructor() {",
|
|
@@ -1366,6 +1463,14 @@ function renderTsServiceDts(spec, serviceName) {
|
|
|
1366
1463
|
setMembers.push(` ${m.name}(value: ${tsType}): void;`);
|
|
1367
1464
|
}
|
|
1368
1465
|
}
|
|
1466
|
+
if (m.kind === "DropTarget" && m.payloadTypeText) {
|
|
1467
|
+
const tsType = mapTypeTextToTs(m.payloadTypeText);
|
|
1468
|
+
classMembers.push(` ${m.name}(): { payload: ${tsType}; x: number; y: number } | null;`);
|
|
1469
|
+
}
|
|
1470
|
+
if (m.kind === "HoverTarget" && m.payloadTypeText) {
|
|
1471
|
+
const tsType = mapTypeTextToTs(m.payloadTypeText);
|
|
1472
|
+
classMembers.push(` ${m.name}(): { payload: ${tsType}; x: number; y: number } | null;`);
|
|
1473
|
+
}
|
|
1369
1474
|
}
|
|
1370
1475
|
const setInterfaceDecl = setMembers.length > 0
|
|
1371
1476
|
? `export interface ${setInterfaceName} {\n${setMembers.join("\n")}\n}`
|
|
@@ -1396,6 +1501,9 @@ type SlotHandler = (...args: unknown[]) => unknown;
|
|
|
1396
1501
|
type OutputHandler = (value: unknown) => void;
|
|
1397
1502
|
type SlotInvocationListener = (requestId: string, service: string, member: string, args: unknown[]) => void;
|
|
1398
1503
|
type OutputListener = (service: string, member: string, value: unknown) => void;
|
|
1504
|
+
type DropListener = (service: string, member: string, payload: unknown, x: number, y: number) => void;
|
|
1505
|
+
type HoverListener = (service: string, member: string, payload: unknown, x: number, y: number) => void;
|
|
1506
|
+
type HoverLeftListener = (service: string, member: string) => void;
|
|
1399
1507
|
|
|
1400
1508
|
interface HostBridgeApi {
|
|
1401
1509
|
anQstBridge_call(service: string, member: string, args: unknown[], callback: (result: unknown) => void): void;
|
|
@@ -1407,6 +1515,9 @@ interface HostBridgeApi {
|
|
|
1407
1515
|
anQstBridge_slotInvocationRequested: {
|
|
1408
1516
|
connect: (cb: (requestId: string, service: string, member: string, args: unknown[]) => void) => void;
|
|
1409
1517
|
};
|
|
1518
|
+
anQstBridge_dropReceived: { connect: (cb: (service: string, member: string, payload: unknown, x: number, y: number) => void) => void };
|
|
1519
|
+
anQstBridge_hoverUpdated: { connect: (cb: (service: string, member: string, payload: unknown, x: number, y: number) => void) => void };
|
|
1520
|
+
anQstBridge_hoverLeft: { connect: (cb: (service: string, member: string) => void) => void };
|
|
1410
1521
|
}
|
|
1411
1522
|
|
|
1412
1523
|
interface QWebChannelCtor {
|
|
@@ -1424,6 +1535,9 @@ interface BridgeAdapter {
|
|
|
1424
1535
|
resolveSlot(requestId: string, ok: boolean, payload: unknown, error: string): void;
|
|
1425
1536
|
onOutput(handler: OutputListener): void;
|
|
1426
1537
|
onSlotInvocation(handler: SlotInvocationListener): void;
|
|
1538
|
+
onDrop(handler: DropListener): void;
|
|
1539
|
+
onHover(handler: HoverListener): void;
|
|
1540
|
+
onHoverLeft(handler: HoverLeftListener): void;
|
|
1427
1541
|
}
|
|
1428
1542
|
|
|
1429
1543
|
function isBridgeCallError(value: unknown): value is {
|
|
@@ -1511,6 +1625,18 @@ class QtWebChannelAdapter implements BridgeAdapter {
|
|
|
1511
1625
|
onSlotInvocation(handler: SlotInvocationListener): void {
|
|
1512
1626
|
this.host.anQstBridge_slotInvocationRequested.connect(handler);
|
|
1513
1627
|
}
|
|
1628
|
+
|
|
1629
|
+
onDrop(handler: DropListener): void {
|
|
1630
|
+
this.host.anQstBridge_dropReceived.connect(handler);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
onHover(handler: HoverListener): void {
|
|
1634
|
+
this.host.anQstBridge_hoverUpdated.connect(handler);
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
onHoverLeft(handler: HoverLeftListener): void {
|
|
1638
|
+
this.host.anQstBridge_hoverLeft.connect(handler);
|
|
1639
|
+
}
|
|
1514
1640
|
}
|
|
1515
1641
|
|
|
1516
1642
|
class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
@@ -1523,6 +1649,9 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
|
1523
1649
|
}>();
|
|
1524
1650
|
private readonly outputListeners: OutputListener[] = [];
|
|
1525
1651
|
private readonly slotListeners: SlotInvocationListener[] = [];
|
|
1652
|
+
private readonly dropListeners: DropListener[] = [];
|
|
1653
|
+
private readonly hoverListeners: HoverListener[] = [];
|
|
1654
|
+
private readonly hoverLeftListeners: HoverLeftListener[] = [];
|
|
1526
1655
|
private requestCounter = 0;
|
|
1527
1656
|
|
|
1528
1657
|
private constructor(private readonly socket: WebSocket) {
|
|
@@ -1562,6 +1691,34 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
|
1562
1691
|
}
|
|
1563
1692
|
return;
|
|
1564
1693
|
}
|
|
1694
|
+
if (type === "dropReceived") {
|
|
1695
|
+
const service = String(message["service"] ?? "");
|
|
1696
|
+
const member = String(message["member"] ?? "");
|
|
1697
|
+
const x = Number(message["x"] ?? 0);
|
|
1698
|
+
const y = Number(message["y"] ?? 0);
|
|
1699
|
+
for (const listener of this.dropListeners) {
|
|
1700
|
+
listener(service, member, message["payload"], x, y);
|
|
1701
|
+
}
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
if (type === "hoverUpdated") {
|
|
1705
|
+
const service = String(message["service"] ?? "");
|
|
1706
|
+
const member = String(message["member"] ?? "");
|
|
1707
|
+
const x = Number(message["x"] ?? 0);
|
|
1708
|
+
const y = Number(message["y"] ?? 0);
|
|
1709
|
+
for (const listener of this.hoverListeners) {
|
|
1710
|
+
listener(service, member, message["payload"], x, y);
|
|
1711
|
+
}
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
if (type === "hoverLeft") {
|
|
1715
|
+
const service = String(message["service"] ?? "");
|
|
1716
|
+
const member = String(message["member"] ?? "");
|
|
1717
|
+
for (const listener of this.hoverLeftListeners) {
|
|
1718
|
+
listener(service, member);
|
|
1719
|
+
}
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1565
1722
|
if (type === "hostError") {
|
|
1566
1723
|
console.error("AnQst host error:", message["payload"]);
|
|
1567
1724
|
return;
|
|
@@ -1649,6 +1806,18 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
|
1649
1806
|
onSlotInvocation(handler: SlotInvocationListener): void {
|
|
1650
1807
|
this.slotListeners.push(handler);
|
|
1651
1808
|
}
|
|
1809
|
+
|
|
1810
|
+
onDrop(handler: DropListener): void {
|
|
1811
|
+
this.dropListeners.push(handler);
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
onHover(handler: HoverListener): void {
|
|
1815
|
+
this.hoverListeners.push(handler);
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
onHoverLeft(handler: HoverLeftListener): void {
|
|
1819
|
+
this.hoverLeftListeners.push(handler);
|
|
1820
|
+
}
|
|
1652
1821
|
}
|
|
1653
1822
|
|
|
1654
1823
|
@Injectable({ providedIn: "root" })
|
|
@@ -1656,6 +1825,9 @@ class AnQstBridgeRuntime {
|
|
|
1656
1825
|
private adapter: BridgeAdapter | null = null;
|
|
1657
1826
|
private readonly slotHandlers = new Map<string, SlotHandler>();
|
|
1658
1827
|
private readonly outputHandlers = new Map<string, OutputHandler[]>();
|
|
1828
|
+
private readonly dropHandlers = new Map<string, ((payload: unknown, x: number, y: number) => void)[]>();
|
|
1829
|
+
private readonly hoverHandlers = new Map<string, ((payload: unknown, x: number, y: number) => void)[]>();
|
|
1830
|
+
private readonly hoverLeftHandlers = new Map<string, (() => void)[]>();
|
|
1659
1831
|
private readonly startup = this.init();
|
|
1660
1832
|
|
|
1661
1833
|
async ready(): Promise<void> {
|
|
@@ -1706,6 +1878,27 @@ class AnQstBridgeRuntime {
|
|
|
1706
1878
|
this.outputHandlers.set(key, existing);
|
|
1707
1879
|
}
|
|
1708
1880
|
|
|
1881
|
+
onDrop(service: string, member: string, handler: (payload: unknown, x: number, y: number) => void): void {
|
|
1882
|
+
const key = this.key(service, member);
|
|
1883
|
+
const existing = this.dropHandlers.get(key) ?? [];
|
|
1884
|
+
existing.push(handler);
|
|
1885
|
+
this.dropHandlers.set(key, existing);
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
onHover(service: string, member: string, handler: (payload: unknown, x: number, y: number) => void): void {
|
|
1889
|
+
const key = this.key(service, member);
|
|
1890
|
+
const existing = this.hoverHandlers.get(key) ?? [];
|
|
1891
|
+
existing.push(handler);
|
|
1892
|
+
this.hoverHandlers.set(key, existing);
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
onHoverLeft(service: string, member: string, handler: () => void): void {
|
|
1896
|
+
const key = this.key(service, member);
|
|
1897
|
+
const existing = this.hoverLeftHandlers.get(key) ?? [];
|
|
1898
|
+
existing.push(handler);
|
|
1899
|
+
this.hoverLeftHandlers.set(key, existing);
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1709
1902
|
private requireAdapterSync(): BridgeAdapter {
|
|
1710
1903
|
if (this.adapter === null) {
|
|
1711
1904
|
throw new Error("AnQst bridge is not ready.");
|
|
@@ -1751,6 +1944,24 @@ class AnQstBridgeRuntime {
|
|
|
1751
1944
|
this.adapter!.resolveSlot(requestId, false, undefined, message);
|
|
1752
1945
|
}
|
|
1753
1946
|
});
|
|
1947
|
+
this.adapter.onDrop((service, member, payload, x, y) => {
|
|
1948
|
+
const key = this.key(service, member);
|
|
1949
|
+
for (const handler of this.dropHandlers.get(key) ?? []) {
|
|
1950
|
+
handler(payload, x, y);
|
|
1951
|
+
}
|
|
1952
|
+
});
|
|
1953
|
+
this.adapter.onHover((service, member, payload, x, y) => {
|
|
1954
|
+
const key = this.key(service, member);
|
|
1955
|
+
for (const handler of this.hoverHandlers.get(key) ?? []) {
|
|
1956
|
+
handler(payload, x, y);
|
|
1957
|
+
}
|
|
1958
|
+
});
|
|
1959
|
+
this.adapter.onHoverLeft((service, member) => {
|
|
1960
|
+
const key = this.key(service, member);
|
|
1961
|
+
for (const handler of this.hoverLeftHandlers.get(key) ?? []) {
|
|
1962
|
+
handler();
|
|
1963
|
+
}
|
|
1964
|
+
});
|
|
1754
1965
|
for (const key of this.slotHandlers.keys()) {
|
|
1755
1966
|
const parts = key.split("::");
|
|
1756
1967
|
if (parts.length === 2) {
|
|
@@ -2790,6 +3001,7 @@ set(CMAKE_AUTOUIC ON)
|
|
|
2790
3001
|
set(CMAKE_AUTORCC ON)
|
|
2791
3002
|
|
|
2792
3003
|
find_program(ANQST_NPM_EXECUTABLE npm REQUIRED)
|
|
3004
|
+
find_program(ANQST_NPX_EXECUTABLE npx REQUIRED)
|
|
2793
3005
|
|
|
2794
3006
|
add_custom_command(
|
|
2795
3007
|
OUTPUT
|
|
@@ -2801,7 +3013,7 @@ add_custom_command(
|
|
|
2801
3013
|
"\${${generatedIncludeVar}}/${widgetName}Types.h"
|
|
2802
3014
|
"\${${generatedRootVar}}/webapp/index.html"
|
|
2803
3015
|
COMMAND "\${ANQST_NPM_EXECUTABLE}" install
|
|
2804
|
-
COMMAND "\${
|
|
3016
|
+
COMMAND "\${ANQST_NPX_EXECUTABLE}" anqst build
|
|
2805
3017
|
WORKING_DIRECTORY "\${${projectRootVar}}"
|
|
2806
3018
|
COMMENT "Generating AnQst widget library (${widgetTarget}) from Angular project"
|
|
2807
3019
|
VERBATIM
|
|
@@ -3029,7 +3241,7 @@ function renderQtDesignerPluginCpp(widgetName, widgetCategory, hasIcon) {
|
|
|
3029
3241
|
#include <QObject>
|
|
3030
3242
|
#include <QString>
|
|
3031
3243
|
#include <QWidget>
|
|
3032
|
-
#include "
|
|
3244
|
+
#include "${widgetName}.h"
|
|
3033
3245
|
|
|
3034
3246
|
class ${pluginClass} final : public QObject, public QDesignerCustomWidgetInterface {
|
|
3035
3247
|
Q_OBJECT
|
|
@@ -3045,9 +3257,10 @@ public:
|
|
|
3045
3257
|
QString toolTip() const override { return QStringLiteral("${widgetName} generated by AnQst."); }
|
|
3046
3258
|
QString whatsThis() const override { return QStringLiteral("${widgetName} generated by AnQst."); }
|
|
3047
3259
|
bool isContainer() const override { return false; }
|
|
3048
|
-
QString includeFile() const override { return QStringLiteral("
|
|
3260
|
+
QString includeFile() const override { return QStringLiteral("${widgetName}.h"); }
|
|
3049
3261
|
QWidget* createWidget(QWidget* parent) override {
|
|
3050
3262
|
auto* widget = new ${widgetClass}(parent);
|
|
3263
|
+
widget->setMinimumHeight(128);
|
|
3051
3264
|
widget->setProperty("anqstDesignerContext", true);
|
|
3052
3265
|
return widget;
|
|
3053
3266
|
}
|
|
@@ -3058,6 +3271,12 @@ public:
|
|
|
3058
3271
|
return QStringLiteral(
|
|
3059
3272
|
"<ui language=\\"c++\\">\\n"
|
|
3060
3273
|
" <widget class=\\"${widgetClass}\\" name=\\"${widgetName.toLowerCase()}\\">\\n"
|
|
3274
|
+
" <property name=\\"minimumSize\\">\\n"
|
|
3275
|
+
" <size>\\n"
|
|
3276
|
+
" <width>256</width>\\n"
|
|
3277
|
+
" <height>128</height>\\n"
|
|
3278
|
+
" </size>\\n"
|
|
3279
|
+
" </property>\\n"
|
|
3061
3280
|
" </widget>\\n"
|
|
3062
3281
|
"</ui>\\n");
|
|
3063
3282
|
}
|
package/dist/src/parser.js
CHANGED
|
@@ -55,7 +55,7 @@ function parseMemberKindFromAnQstType(typeNode) {
|
|
|
55
55
|
if (!typeName.startsWith("AnQst."))
|
|
56
56
|
return null;
|
|
57
57
|
const kind = typeName.slice("AnQst.".length);
|
|
58
|
-
if (!["Call", "Slot", "Emitter", "Output", "Input"].includes(kind))
|
|
58
|
+
if (!["Call", "Slot", "Emitter", "Output", "Input", "DropTarget", "HoverTarget"].includes(kind))
|
|
59
59
|
return null;
|
|
60
60
|
if (kind === "Emitter")
|
|
61
61
|
return { kind, payload: null };
|
|
@@ -71,7 +71,7 @@ function parseMemberKindWithConfig(typeNode) {
|
|
|
71
71
|
if (!typeName.startsWith("AnQst."))
|
|
72
72
|
return null;
|
|
73
73
|
const kind = typeName.slice("AnQst.".length);
|
|
74
|
-
if (!["Call", "Slot", "Emitter", "Output", "Input"].includes(kind))
|
|
74
|
+
if (!["Call", "Slot", "Emitter", "Output", "Input", "DropTarget", "HoverTarget"].includes(kind))
|
|
75
75
|
return null;
|
|
76
76
|
const typeArgs = typeNode.typeArguments ?? [];
|
|
77
77
|
if (kind === "Emitter") {
|
|
@@ -84,7 +84,7 @@ function parseMemberKindWithConfig(typeNode) {
|
|
|
84
84
|
return {
|
|
85
85
|
kind,
|
|
86
86
|
payload: typeArgs[0] ? typeArgs[0].getText() : null,
|
|
87
|
-
configTypeNode: kind === "Call" ? (typeArgs[1] ?? null) : null
|
|
87
|
+
configTypeNode: (kind === "Call" || kind === "HoverTarget") ? (typeArgs[1] ?? null) : null
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
function parseNumericLiteralType(node) {
|
|
@@ -156,6 +156,51 @@ function resolveMemberTimeoutMs(source, serviceName, memberName, kind, configTyp
|
|
|
156
156
|
}
|
|
157
157
|
return effectiveMs;
|
|
158
158
|
}
|
|
159
|
+
const DEFAULT_HOVER_RATE_HZ = 60;
|
|
160
|
+
const DEFAULT_HOVER_THROTTLE_MS = Math.round(1000 / DEFAULT_HOVER_RATE_HZ);
|
|
161
|
+
function resolveHoverThrottleMs(source, serviceName, memberName, kind, configTypeNode, warnings, memberLoc) {
|
|
162
|
+
if (kind !== "HoverTarget")
|
|
163
|
+
return 0;
|
|
164
|
+
if (!configTypeNode)
|
|
165
|
+
return DEFAULT_HOVER_THROTTLE_MS;
|
|
166
|
+
if (!typescript_1.default.isTypeLiteralNode(configTypeNode)) {
|
|
167
|
+
throw new errors_1.VerifyError(`HoverTarget config for '${memberName}' must be an inline object literal type.`, locFromNode(source, configTypeNode));
|
|
168
|
+
}
|
|
169
|
+
let maxRateHz = null;
|
|
170
|
+
const memberPath = `${serviceName}.${memberName}`;
|
|
171
|
+
for (const prop of configTypeNode.members) {
|
|
172
|
+
if (!typescript_1.default.isPropertySignature(prop) || !prop.name) {
|
|
173
|
+
throw new errors_1.VerifyError(`HoverTarget config for '${memberName}' only supports named properties.`, locFromNode(source, prop));
|
|
174
|
+
}
|
|
175
|
+
if (!typescript_1.default.isIdentifier(prop.name)) {
|
|
176
|
+
throw new errors_1.VerifyError(`HoverTarget config for '${memberName}' only supports identifier keys.`, locFromNode(source, prop.name));
|
|
177
|
+
}
|
|
178
|
+
const key = prop.name.text;
|
|
179
|
+
if (!prop.type) {
|
|
180
|
+
throw new errors_1.VerifyError(`HoverTarget config key '${key}' in '${memberName}' must declare a numeric literal value.`, locFromNode(source, prop));
|
|
181
|
+
}
|
|
182
|
+
if (key !== "maxRateHz") {
|
|
183
|
+
warnings.push({
|
|
184
|
+
severity: "warn",
|
|
185
|
+
message: `Unknown HoverTarget config key '${key}' ignored for '${memberPath}'.`,
|
|
186
|
+
loc: locFromNode(source, prop.name),
|
|
187
|
+
memberPath
|
|
188
|
+
});
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const numericValue = parseNumericLiteralType(prop.type);
|
|
192
|
+
if (numericValue === null || !Number.isFinite(numericValue)) {
|
|
193
|
+
throw new errors_1.VerifyError(`HoverTarget config key '${key}' in '${memberName}' must be a numeric literal >= 0.`, locFromNode(source, prop.type));
|
|
194
|
+
}
|
|
195
|
+
if (numericValue < 0) {
|
|
196
|
+
throw new errors_1.VerifyError(`HoverTarget config key '${key}' in '${memberName}' must be >= 0.`, locFromNode(source, prop.type));
|
|
197
|
+
}
|
|
198
|
+
maxRateHz = numericValue;
|
|
199
|
+
}
|
|
200
|
+
if (maxRateHz === null)
|
|
201
|
+
return DEFAULT_HOVER_THROTTLE_MS;
|
|
202
|
+
return maxRateHz === 0 ? 0 : Math.round(1000 / maxRateHz);
|
|
203
|
+
}
|
|
159
204
|
function parseServiceMember(source, serviceName, member, warnings) {
|
|
160
205
|
if (typescript_1.default.isMethodSignature(member)) {
|
|
161
206
|
if (member.questionToken)
|
|
@@ -166,7 +211,7 @@ function parseServiceMember(source, serviceName, member, warnings) {
|
|
|
166
211
|
const parsed = parseMemberKindWithConfig(returnType);
|
|
167
212
|
if (!parsed)
|
|
168
213
|
throw new errors_1.VerifyError(`Unsupported service method return type '${returnType.getText()}'.`, locFromNode(source, member));
|
|
169
|
-
if (parsed.kind === "Input" || parsed.kind === "Output") {
|
|
214
|
+
if (parsed.kind === "Input" || parsed.kind === "Output" || parsed.kind === "DropTarget" || parsed.kind === "HoverTarget") {
|
|
170
215
|
throw new errors_1.VerifyError(`${parsed.kind} must be declared as property, not method.`, locFromNode(source, member));
|
|
171
216
|
}
|
|
172
217
|
if (parsed.kind === "Emitter" && parsed.configTypeNode !== null) {
|
|
@@ -191,6 +236,7 @@ function parseServiceMember(source, serviceName, member, warnings) {
|
|
|
191
236
|
payloadTypeText: parsed.payload,
|
|
192
237
|
parameters,
|
|
193
238
|
timeoutMs,
|
|
239
|
+
hoverThrottleMs: 0,
|
|
194
240
|
loc: locFromNode(source, member)
|
|
195
241
|
};
|
|
196
242
|
}
|
|
@@ -202,19 +248,21 @@ function parseServiceMember(source, serviceName, member, warnings) {
|
|
|
202
248
|
const parsed = parseMemberKindWithConfig(member.type);
|
|
203
249
|
if (!parsed)
|
|
204
250
|
throw new errors_1.VerifyError(`Unsupported service property type '${member.type.getText()}'.`, locFromNode(source, member));
|
|
205
|
-
if (parsed.kind !== "Input" && parsed.kind !== "Output") {
|
|
251
|
+
if (parsed.kind !== "Input" && parsed.kind !== "Output" && parsed.kind !== "DropTarget" && parsed.kind !== "HoverTarget") {
|
|
206
252
|
throw new errors_1.VerifyError(`${parsed.kind} must be declared as method, not property.`, locFromNode(source, member));
|
|
207
253
|
}
|
|
208
254
|
if (!member.name || !typescript_1.default.isIdentifier(member.name)) {
|
|
209
255
|
throw new errors_1.VerifyError("Only identifier service property names are supported.", locFromNode(source, member));
|
|
210
256
|
}
|
|
211
257
|
const timeoutMs = resolveMemberTimeoutMs(source, serviceName, member.name.text, parsed.kind, parsed.configTypeNode, warnings, locFromNode(source, member));
|
|
258
|
+
const hoverThrottleMs = resolveHoverThrottleMs(source, serviceName, member.name.text, parsed.kind, parsed.configTypeNode, warnings, locFromNode(source, member));
|
|
212
259
|
return {
|
|
213
260
|
kind: parsed.kind,
|
|
214
261
|
name: member.name.text,
|
|
215
262
|
payloadTypeText: parsed.payload,
|
|
216
263
|
parameters: [],
|
|
217
264
|
timeoutMs,
|
|
265
|
+
hoverThrottleMs,
|
|
218
266
|
loc: locFromNode(source, member)
|
|
219
267
|
};
|
|
220
268
|
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dusted/anqst",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Opinionated backend generator for webapps.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nodejs",
|
|
7
7
|
"qt",
|
|
8
8
|
"angular",
|
|
9
|
-
"webapp"
|
|
9
|
+
"webapp",
|
|
10
|
+
"apigenerator"
|
|
10
11
|
],
|
|
11
12
|
"homepage": "https://github.com/DusteDdk/AnQst#readme",
|
|
12
13
|
"bugs": {
|
package/spec/AnQst-Spec-DSL.d.ts
CHANGED
|
@@ -1,249 +1,327 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AnQst-Spec Language - Canonical source of truth for the language.
|
|
3
|
-
*
|
|
4
|
-
* @remarks
|
|
5
|
-
* - DSL for Widget generation, transported as TypeScript definition.
|
|
6
|
-
* - For AnQSt Widget Specifications only.
|
|
7
|
-
* - Exactly one toplevel namespace must be declared: It names the Widget.
|
|
8
|
-
* - Service interfaces are optional. Namespace-local type declarations are valid generation roots.
|
|
9
|
-
* - Imported/external types are generation-relevant when transitively reachable from namespace declarations.
|
|
10
|
-
* - This is not an input file to the generator, it is the description of the language that describes the widget.
|
|
11
|
-
* Not for use by TypeScript application implementation.
|
|
12
|
-
* @example
|
|
13
|
-
* package.json:
|
|
14
|
-
* - "AnQst": "./AnQst/MyUserMgmtWidget.settings.json"
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
export namespace AnQst {
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Declare service `InterfaceName`
|
|
21
|
-
*
|
|
22
|
-
* @remarks
|
|
23
|
-
* Multiple allowed.
|
|
24
|
-
* Affords developers of advanced widgets the ability to create domain-informed categories.
|
|
25
|
-
* - Duplicate method declarations with identical parameter lists are invalid in normative AnQst-Spec input.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* export interface UserService extends Widget.Service { }
|
|
29
|
-
* // Generates UserService.
|
|
30
|
-
*/
|
|
31
|
-
interface Service { }
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Declare service `InterfaceName` as development-mode capable transport service.
|
|
35
|
-
*
|
|
36
|
-
* @remarks
|
|
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
|
|
39
|
-
* (Qt WebChannel + HTTP/WebSocket development bridge).
|
|
40
|
-
* - Existing generated service APIs remain unchanged.
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* export interface UserService extends AnQst.AngularHTTPBaseServerClass { }
|
|
44
|
-
*/
|
|
45
|
-
interface AngularHTTPBaseServerClass extends Service { }
|
|
46
|
-
|
|
47
|
-
type CallConfig = { timeoutSeconds: number } | { timeoutMilliseconds: number } | {};
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Declare non-blocking service method `MethodName`(`MethodArguments`): Promise<`MethodReturnType`>.
|
|
51
|
-
* @remarks
|
|
52
|
-
* - **Widget** -> Parent
|
|
53
|
-
* - Flow:
|
|
54
|
-
* - Widget: Call to service method `MethodName`(`MethodArguments`).
|
|
55
|
-
* - Widget: Returns Promise<`MethodReturnType`>.
|
|
56
|
-
* - Parent: Registered callback receives args and returns `T` synchronously.
|
|
57
|
-
* - Widget: Promise resolves with payload `T` or rejects with plain error object.
|
|
58
|
-
* - Optional config supports timeout tuning:
|
|
59
|
-
* - `AnQst.Call<T, { timeoutSeconds: N }>`
|
|
60
|
-
* - `AnQst.Call<T, { timeoutMilliseconds: N }>`
|
|
61
|
-
* - Exactly one timeout key is allowed. Value must be integer >= 0.
|
|
62
|
-
* - Default timeout is 120s. `0` means wait forever.
|
|
63
|
-
* @example
|
|
64
|
-
* // AnQst spec:
|
|
65
|
-
* getUserById(userId: string): AnQst.Call<User>
|
|
66
|
-
* //Angular app:
|
|
67
|
-
* const user: User = await this.userService.getUserById("abc");
|
|
68
|
-
*/
|
|
69
|
-
interface Call<T, Config extends CallConfig = {}> { dummy: T; config?: Config }
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Declare blocking service method onSlot.`MethodName`( handler(`MethodArguments`):`MethodReturnType` ): void
|
|
73
|
-
* @remarks
|
|
74
|
-
* - **Parent** -> Widget
|
|
75
|
-
* - Impl note: Autogenerated stub handler queues until handler is set (set method calls spools queue through handler)
|
|
76
|
-
* - Flow:
|
|
77
|
-
* - Parent: Call to generated widget method `MethodName`(`MethodArguments`).
|
|
78
|
-
* - Widget: Registered handler(`MethodArguments`) is called.
|
|
79
|
-
* - Widget: Handler return forms:
|
|
80
|
-
* - `T` -> success payload
|
|
81
|
-
* - `Promise<T>` -> awaited, success payload on resolve
|
|
82
|
-
* - `Error` -> failure
|
|
83
|
-
* - throw -> failure
|
|
84
|
-
* - rejected promise -> failure
|
|
85
|
-
* - Parent: `MethodName` call returns with result.
|
|
86
|
-
* -
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* - Widget:
|
|
108
|
-
* -
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* -
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* -
|
|
144
|
-
* -
|
|
145
|
-
*
|
|
146
|
-
* -
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
1
|
+
/**
|
|
2
|
+
* AnQst-Spec Language - Canonical source of truth for the language.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* - DSL for Widget generation, transported as TypeScript definition.
|
|
6
|
+
* - For AnQSt Widget Specifications only.
|
|
7
|
+
* - Exactly one toplevel namespace must be declared: It names the Widget.
|
|
8
|
+
* - Service interfaces are optional. Namespace-local type declarations are valid generation roots.
|
|
9
|
+
* - Imported/external types are generation-relevant when transitively reachable from namespace declarations.
|
|
10
|
+
* - This is not an input file to the generator, it is the description of the language that describes the widget.
|
|
11
|
+
* Not for use by TypeScript application implementation.
|
|
12
|
+
* @example
|
|
13
|
+
* package.json:
|
|
14
|
+
* - "AnQst": "./AnQst/MyUserMgmtWidget.settings.json"
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export namespace AnQst {
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Declare service `InterfaceName`
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* Multiple allowed.
|
|
24
|
+
* Affords developers of advanced widgets the ability to create domain-informed categories.
|
|
25
|
+
* - Duplicate method declarations with identical parameter lists are invalid in normative AnQst-Spec input.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* export interface UserService extends Widget.Service { }
|
|
29
|
+
* // Generates UserService.
|
|
30
|
+
*/
|
|
31
|
+
interface Service { }
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Declare service `InterfaceName` as development-mode capable transport service.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
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
|
|
39
|
+
* (Qt WebChannel + HTTP/WebSocket development bridge).
|
|
40
|
+
* - Existing generated service APIs remain unchanged.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* export interface UserService extends AnQst.AngularHTTPBaseServerClass { }
|
|
44
|
+
*/
|
|
45
|
+
interface AngularHTTPBaseServerClass extends Service { }
|
|
46
|
+
|
|
47
|
+
type CallConfig = { timeoutSeconds: number } | { timeoutMilliseconds: number } | {};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Declare non-blocking service method `MethodName`(`MethodArguments`): Promise<`MethodReturnType`>.
|
|
51
|
+
* @remarks
|
|
52
|
+
* - **Widget** -> Parent
|
|
53
|
+
* - Flow:
|
|
54
|
+
* - Widget: Call to service method `MethodName`(`MethodArguments`).
|
|
55
|
+
* - Widget: Returns Promise<`MethodReturnType`>.
|
|
56
|
+
* - Parent: Registered callback receives args and returns `T` synchronously.
|
|
57
|
+
* - Widget: Promise resolves with payload `T` or rejects with plain error object.
|
|
58
|
+
* - Optional config supports timeout tuning:
|
|
59
|
+
* - `AnQst.Call<T, { timeoutSeconds: N }>`
|
|
60
|
+
* - `AnQst.Call<T, { timeoutMilliseconds: N }>`
|
|
61
|
+
* - Exactly one timeout key is allowed. Value must be integer >= 0.
|
|
62
|
+
* - Default timeout is 120s. `0` means wait forever.
|
|
63
|
+
* @example
|
|
64
|
+
* // AnQst spec:
|
|
65
|
+
* getUserById(userId: string): AnQst.Call<User>
|
|
66
|
+
* //Angular app:
|
|
67
|
+
* const user: User = await this.userService.getUserById("abc");
|
|
68
|
+
*/
|
|
69
|
+
interface Call<T, Config extends CallConfig = {}> { dummy: T; config?: Config }
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Declare blocking service method onSlot.`MethodName`( handler(`MethodArguments`):`MethodReturnType` ): void
|
|
73
|
+
* @remarks
|
|
74
|
+
* - **Parent** -> Widget
|
|
75
|
+
* - Impl note: Autogenerated stub handler queues until handler is set (set method calls spools queue through handler)
|
|
76
|
+
* - Flow:
|
|
77
|
+
* - Parent: Call to generated widget method `MethodName`(`MethodArguments`).
|
|
78
|
+
* - Widget: Registered handler(`MethodArguments`) is called.
|
|
79
|
+
* - Widget: Handler return forms:
|
|
80
|
+
* - `T` -> success payload
|
|
81
|
+
* - `Promise<T>` -> awaited, success payload on resolve
|
|
82
|
+
* - `Error` -> failure
|
|
83
|
+
* - throw -> failure
|
|
84
|
+
* - rejected promise -> failure
|
|
85
|
+
* - Parent: `MethodName` call returns with result.
|
|
86
|
+
* - Default Slot timeout is 1000ms.
|
|
87
|
+
* Note: One active handler, calling will replace existing and is valid and allowed.
|
|
88
|
+
* @example
|
|
89
|
+
* // AnQst spec:
|
|
90
|
+
* getUsernameSubstring(from: number, to:number ): AnQst.Slot<string>
|
|
91
|
+
* //Angular app:
|
|
92
|
+
* this.userService.onSlot.getUsername( provider );
|
|
93
|
+
* //Parent:
|
|
94
|
+
* auto currentFormUsername = userMgmt.getUsername();
|
|
95
|
+
*/
|
|
96
|
+
interface Slot<T> { dummy: T }
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Declare message emission method `MethodName`(`MethodArguments`): void
|
|
102
|
+
* @remarks
|
|
103
|
+
* - **Widget** -> ?Parent
|
|
104
|
+
* True Qt signal semantics (Message is emitted, no return path, no registration requirement)
|
|
105
|
+
* - Flow:
|
|
106
|
+
* - Widget: Call to service method `MethodName`(`MethodArguments`).
|
|
107
|
+
* - Widget: Returns void.
|
|
108
|
+
* - Parent: Might have something connected to the signal, might not.
|
|
109
|
+
* - If no listener is connected, event is dropped.
|
|
110
|
+
* @example
|
|
111
|
+
* // AnQst spec:
|
|
112
|
+
* complain(whine: string): AnQst.Emitter;
|
|
113
|
+
* //Angular app:
|
|
114
|
+
* this.userService.complain("Why won't you LISTEN!");
|
|
115
|
+
*/
|
|
116
|
+
interface Emitter { }
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Declare reactive `PropertyName`:`OutputType` and set.`PropertyName`(arg: `OutputType`)
|
|
121
|
+
* @remarks
|
|
122
|
+
* - **Parent** -> Widget
|
|
123
|
+
* True Angular signal semantics (Property updates, signal emits, no return path, no registration requirement)
|
|
124
|
+
* - Flow:
|
|
125
|
+
* - Parent: Sets generated widget property `PropertyName`.
|
|
126
|
+
* - Widget: Service updates readonly property `PropertyName` and emits signal.
|
|
127
|
+
* @example
|
|
128
|
+
* // AnQst spec:
|
|
129
|
+
* activeUsers: AnQst.Output<number>;
|
|
130
|
+
* //Angular app template:
|
|
131
|
+
* <p>{{ userService.activeUsers() }}</p>
|
|
132
|
+
* //Parent:
|
|
133
|
+
* int users = userMgmt.activeUsers;
|
|
134
|
+
* userMgmt.activeUsers = 99;
|
|
135
|
+
*/
|
|
136
|
+
interface Output<OutputType> {}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Declare Input bindable set.`PropertyName`(input:`InputType`): void and `PropertyName`:`InputType`
|
|
141
|
+
* @remarks
|
|
142
|
+
* - **Widget** -> Parent
|
|
143
|
+
* - Convenience method, for symmetry with Output.
|
|
144
|
+
* - Flow:
|
|
145
|
+
* - Widget: Calls service set.`PropertyName`(`InputType`) to publish current value.
|
|
146
|
+
* - Parent: Mirrored generated widget property `PropertyName` is updated and emits change notification.
|
|
147
|
+
* @example
|
|
148
|
+
* // AnQst spec:
|
|
149
|
+
* currentUsername: AnQst.Input<string>;
|
|
150
|
+
* //Angular app template:
|
|
151
|
+
* <input type="text" placeholder="Your Name Here" (input)="userService.set.currentUsername(($event.target as HTMLInputElement).value)" />
|
|
152
|
+
* //Parent:
|
|
153
|
+
* QString userName = userMgmt.currentUsername;
|
|
154
|
+
*/
|
|
155
|
+
interface Input<InputType> {}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Declare drop-target `PropertyName`:`PayloadType`
|
|
160
|
+
* @remarks
|
|
161
|
+
* - **Parent** -> Widget (framework-mediated)
|
|
162
|
+
* - True Angular signal semantics.
|
|
163
|
+
* - Flow:
|
|
164
|
+
* - External: A Qt widget initiates a QDrag carrying QMimeData.
|
|
165
|
+
* - Parent: AnQstWebHostBase intercepts the drop via event filter on the
|
|
166
|
+
* embedded QWebEngineView's rendering surface.
|
|
167
|
+
* - Parent: QMimeData for the accepted format is deserialized (JSON) into
|
|
168
|
+
* the generated C++ struct and forwarded through the bridge.
|
|
169
|
+
* - Widget: Service updates signal `PropertyName` with the deserialized
|
|
170
|
+
* payload and drop coordinates. Angular components react via effect() / template binding.
|
|
171
|
+
* - MIME type is convention-derived: `application/anqst-dragdropevent_<ServiceName>-<TypeName>`.
|
|
172
|
+
* - The source QWidget must serialize the drag payload as JSON under the same MIME type.
|
|
173
|
+
* - Multiple DropTarget members per service are allowed (each accepting a different type).
|
|
174
|
+
* @example
|
|
175
|
+
* // AnQst spec:
|
|
176
|
+
* trackDropped: AnQst.DropTarget<Track>;
|
|
177
|
+
* // Angular app:
|
|
178
|
+
* effect(() => {
|
|
179
|
+
* const drop = this.service.trackDropped();
|
|
180
|
+
* if (drop !== null) { console.log(drop.payload, drop.x, drop.y); }
|
|
181
|
+
* });
|
|
182
|
+
*/
|
|
183
|
+
interface DropTarget<T> { dummy: T }
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Declare hover-target `PropertyName`:`PayloadType`
|
|
188
|
+
* @remarks
|
|
189
|
+
* - **Parent** -> Widget (framework-mediated)
|
|
190
|
+
* - True Angular signal semantics.
|
|
191
|
+
* - Flow:
|
|
192
|
+
* - External: A Qt widget initiates a QDrag carrying QMimeData.
|
|
193
|
+
* - Parent: AnQstWebHostBase intercepts drag-move events via event filter on the
|
|
194
|
+
* embedded QWebEngineView's rendering surface. Events are throttled (trailing edge).
|
|
195
|
+
* - Parent: Payload is deserialized once on DragEnter; subsequent DragMove events
|
|
196
|
+
* forward only the updated position.
|
|
197
|
+
* - Widget: Service updates signal `PropertyName` with the payload and current
|
|
198
|
+
* coordinates. Signal becomes null on DragLeave.
|
|
199
|
+
* - Shares the same MIME type convention as DropTarget: `application/anqst-dragdropevent_<ServiceName>-<TypeName>`.
|
|
200
|
+
* - A HoverTarget without a corresponding DropTarget means "show previews but reject the drop".
|
|
201
|
+
* - Optional config supports throttle tuning:
|
|
202
|
+
* - `AnQst.HoverTarget<T, { maxRateHz: N }>` — maximum rate of hover position updates
|
|
203
|
+
* forwarded across the bridge, in hertz. The generator converts this to a millisecond
|
|
204
|
+
* interval at build time (e.g. 60 Hz -> 17 ms, 10 Hz -> 100 ms).
|
|
205
|
+
* - Default is 60 Hz (~17 ms throttle interval).
|
|
206
|
+
* - `0` means no throttling: every QDragMoveEvent is forwarded immediately.
|
|
207
|
+
* - There is no upper bound on the value.
|
|
208
|
+
* @example
|
|
209
|
+
* // AnQst spec (default 60 Hz throttle):
|
|
210
|
+
* trackHovering: AnQst.HoverTarget<Track>;
|
|
211
|
+
* // AnQst spec (custom 10 Hz throttle):
|
|
212
|
+
* trackHovering: AnQst.HoverTarget<Track, { maxRateHz: 10 }>;
|
|
213
|
+
* // AnQst spec (no throttling):
|
|
214
|
+
* trackHovering: AnQst.HoverTarget<Track, { maxRateHz: 0 }>;
|
|
215
|
+
* // Angular app:
|
|
216
|
+
* effect(() => {
|
|
217
|
+
* const hover = this.service.trackHovering();
|
|
218
|
+
* if (hover !== null) { highlight(document.elementFromPoint(hover.x, hover.y)); }
|
|
219
|
+
* });
|
|
220
|
+
*/
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Configuration for HoverTarget throttle behavior.
|
|
224
|
+
* @remarks
|
|
225
|
+
* - `maxRateHz` — Maximum rate in hertz at which hover position updates are forwarded.
|
|
226
|
+
* - Default (omitted or `{}`): 60 Hz.
|
|
227
|
+
* - `0`: No throttling; every QDragMoveEvent is forwarded.
|
|
228
|
+
* - No upper bound.
|
|
229
|
+
* - Value must be a numeric literal >= 0.
|
|
230
|
+
*/
|
|
231
|
+
type HoverTargetConfig = { maxRateHz: number } | {};
|
|
232
|
+
interface HoverTarget<T, Config extends HoverTargetConfig = {}> { dummy: T; config?: Config }
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* AnQst-Spec type mapping overview and control.
|
|
238
|
+
*
|
|
239
|
+
* @remarks
|
|
240
|
+
* Any Type that be mapped between TypeScript and Qt, C++ standard types or
|
|
241
|
+
* Plain Old Data (POD) types (in that order of preference) will be mapped by default.
|
|
242
|
+
*
|
|
243
|
+
* Canonical mapping directive namespace is AnQst.Type.<type>.
|
|
244
|
+
* To express advisory mapping preference, use AnQst.Type.<type>.
|
|
245
|
+
* Advisory means generator SHOULD honor it, but MAY fall back to inferred/default mapping and emit diagnostic.
|
|
246
|
+
* Array forms are equivalent: T[] == Array<T>, and AnQst.Type.X[] == Array<AnQst.Type.X>.
|
|
247
|
+
*
|
|
248
|
+
* TypeScript definitions+classes and C++ structs are generated for each
|
|
249
|
+
* structured TypeScript type ( type = {...} or interface { ... } )
|
|
250
|
+
* referenced in an AnQst spec.
|
|
251
|
+
*
|
|
252
|
+
*/
|
|
253
|
+
enum Type {
|
|
254
|
+
object = "JavaScript Object <-> QVariantMap (JSON.stringify/parse semantics)",
|
|
255
|
+
json = "JavaScript Object <-> QJsonObject (JSON.stringify/parse semantics)",
|
|
256
|
+
string = "JavaScript String <-> QString",
|
|
257
|
+
stringArray = "JavaScript String[] <-> QStringList",
|
|
258
|
+
number = "JavaScript Number <-> double",
|
|
259
|
+
qint64 = "JavaScript BigInt <-> qint64 (Default, for symmetry, same as direct use of <BigInt> which is allowed.)",
|
|
260
|
+
quint64 = "JavaScript BigInt <-> quint64",
|
|
261
|
+
qint32 = "JavaScript Number <-> qint32",
|
|
262
|
+
quint32 = "JavaScript Number <-> quint32",
|
|
263
|
+
qint16 = "JavaScript Number <-> qint16",
|
|
264
|
+
quint16 = "JavaScript Number <-> quint16",
|
|
265
|
+
qint8 = "JavaScript Number <-> qint8",
|
|
266
|
+
quint8 = "JavaScript Number <-> quint8",
|
|
267
|
+
int32 = "JavaScript Number <-> int32_t",
|
|
268
|
+
uint32 = "JavaScript Number <-> uint32_t",
|
|
269
|
+
int16 = "JavaScript Number <-> int16_t",
|
|
270
|
+
uint16 = "JavaScript Number <-> uint16_t",
|
|
271
|
+
int8 = "JavaScript Number <-> int8_t",
|
|
272
|
+
uint8 = "JavaScript Number <-> uint8_t",
|
|
273
|
+
buffer = "JavaScript ArrayBuffer <-> QByteArray (Default, for symmetry, same as direct use of <ArrayBuffer> which is allowed.)",
|
|
274
|
+
blob = "JavaScript ArrayBuffer <-> QByteArray",
|
|
275
|
+
typedArray = "JavaScript TypedArray <-> QByteArray",
|
|
276
|
+
uint8Array = "JavaScript Uint8Array <-> QByteArray",
|
|
277
|
+
int8Array = "JavaScript Int8Array <-> QByteArray",
|
|
278
|
+
uint16Array = "JavaScript Uint16Array <-> QByteArray",
|
|
279
|
+
int16Array = "JavaScript Int16Array <-> QByteArray",
|
|
280
|
+
uint32Array = "JavaScript Uint32Array <-> QByteArray",
|
|
281
|
+
int32Array = "JavaScript Int32Array <-> QByteArray",
|
|
282
|
+
float32Array = "JavaScript Float32Array <-> QByteArray",
|
|
283
|
+
float64Array = "JavaScript Float64Array <-> QByteArray",
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* These are explicitly forbidden argument/return types.
|
|
289
|
+
*
|
|
290
|
+
* @remarks
|
|
291
|
+
* - They may not be referenced in AnQst specs or imported types.
|
|
292
|
+
* - Service methods cannot accept them as arguments
|
|
293
|
+
* - Service methods cannot return them.
|
|
294
|
+
* - Service properties of their type cannot be declared.
|
|
295
|
+
*
|
|
296
|
+
* - For Objects/Maps/Sets use AnQst.Type.<Type> instead.
|
|
297
|
+
*/
|
|
298
|
+
export enum ForbiddenType {
|
|
299
|
+
Function = "Passing callbacks across the boundary is not allowed.",
|
|
300
|
+
Class = "Passing classes across the boundary is not allowed.",
|
|
301
|
+
Type = "Passing types across the boundary is not allowed.",
|
|
302
|
+
Promise = "Passing Promises across the boundary is not allowed",
|
|
303
|
+
Callable = "Passing Callable objects across the boundary is not allowed",
|
|
304
|
+
any = "Passing 'any' type across the boundary is not allowed",
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* JS/TS Error instances can be returned, but they have special meaning.
|
|
309
|
+
*
|
|
310
|
+
* @remarks
|
|
311
|
+
* AnQst is opinionated, Errors are not for control flow. They are for signalling unrecoverable and unhandled circumstance.
|
|
312
|
+
* Use: To indicate unrecoverable program error/wrong use.
|
|
313
|
+
* Don't use: To communicate expected and/or ignorable/handable situations, define a domain-specific transport type instad.
|
|
314
|
+
* When else encountered: Unhandled Errors.
|
|
315
|
+
* Effect: The receiving end throws an exception with message `<service>.<member> emitted error: <WidgetStackTrace>`
|
|
316
|
+
* AnQst internal behavior:
|
|
317
|
+
* When AnQst type translation/mapping encounters a true Error object instance, the Error object is not transported, instead
|
|
318
|
+
* the sending AnQst code signals to the receiving AnQst code a message, and the receiving AnQst code throws a runtime exception on
|
|
319
|
+
* the call or callback site in the Parent.
|
|
320
|
+
|
|
321
|
+
*/
|
|
322
|
+
export enum ExceptionalType {
|
|
323
|
+
Error = "AnQst will not transport an Error object, but will cause an exception to be thrown on reception site."
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
}
|
|
327
|
+
|