@dusted/anqst 1.7.1 → 1.7.3
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/AnQstWebBase/AnQstWebBaseAbi.cmake +1 -0
- package/AnQstWebBase/CMakeLists.txt +116 -0
- package/AnQstWebBase/CMakeUserPresets.json +14 -0
- package/AnQstWebBase/README.md +65 -0
- package/AnQstWebBase/src/AnQstBase93.cpp +91 -0
- package/AnQstWebBase/src/AnQstBase93.h +15 -0
- package/AnQstWebBase/src/AnQstBridgeProxy.cpp +30 -0
- package/AnQstWebBase/src/AnQstBridgeProxy.h +41 -0
- package/AnQstWebBase/src/AnQstHostBridgeFacade.cpp +345 -0
- package/AnQstWebBase/src/AnQstHostBridgeFacade.h +99 -0
- package/AnQstWebBase/src/AnQstWebBaseAbi.h +4 -0
- package/AnQstWebBase/src/AnQstWebBaseAbi.h.in +4 -0
- package/AnQstWebBase/src/AnQstWebHostBase.cpp +1822 -0
- package/AnQstWebBase/src/AnQstWebHostBase.h +227 -0
- package/AnQstWebBase/src/AnQstWidgetDebugDialog.cpp +425 -0
- package/AnQstWebBase/src/AnQstWidgetDebugDialog.h +105 -0
- package/AnQstWebBase/src/AngularHttpBaseServer.cpp +965 -0
- package/AnQstWebBase/src/AngularHttpBaseServer.h +97 -0
- package/AnQstWebBase/src/UI/AnQstWidgetDebugDialog.ui +235 -0
- package/AnQstWebBase/tests/CMakeLists.txt +22 -0
- package/AnQstWebBase/tests/test_AnQstWebHostBase.cpp +1102 -0
- package/dist/src/abi-hash-stamp.js +5 -0
- package/dist/src/abi-hash.js +33 -0
- package/dist/src/app.js +67 -19
- package/dist/src/boundary-codec-render.js +17 -10
- package/dist/src/emit.js +76 -30
- package/dist/src/project.js +11 -1
- package/dist/src/webbase.js +94 -0
- package/package.json +2 -1
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "AnQstWebBaseAbi.h"
|
|
4
|
+
|
|
5
|
+
#include <QDateTime>
|
|
6
|
+
#include <QHash>
|
|
7
|
+
#include <QObject>
|
|
8
|
+
#include <QPoint>
|
|
9
|
+
#include <QString>
|
|
10
|
+
#include <QStringList>
|
|
11
|
+
#include <QUrl>
|
|
12
|
+
#include <QVariantMap>
|
|
13
|
+
#include <QVariantList>
|
|
14
|
+
#include <QWidget>
|
|
15
|
+
#include <functional>
|
|
16
|
+
|
|
17
|
+
class QLabel;
|
|
18
|
+
class QMimeData;
|
|
19
|
+
class QPushButton;
|
|
20
|
+
class QTimer;
|
|
21
|
+
class QWebChannel;
|
|
22
|
+
|
|
23
|
+
namespace ANQST_WEBBASE_NAMESPACE {
|
|
24
|
+
|
|
25
|
+
class AnQstBridgeProxy;
|
|
26
|
+
class AnQstHostBridgeFacade;
|
|
27
|
+
class AnQstWidgetDebugDialog;
|
|
28
|
+
class AngularHttpBaseServer;
|
|
29
|
+
class LocalWebView;
|
|
30
|
+
|
|
31
|
+
class AnQstWebHostBase : public QWidget {
|
|
32
|
+
Q_OBJECT
|
|
33
|
+
|
|
34
|
+
public:
|
|
35
|
+
enum class ContentRootMode {
|
|
36
|
+
Unset,
|
|
37
|
+
Qrc,
|
|
38
|
+
Filesystem
|
|
39
|
+
};
|
|
40
|
+
Q_ENUM(ContentRootMode)
|
|
41
|
+
|
|
42
|
+
enum class AnQstWidgetResourceProvider {
|
|
43
|
+
Qrc,
|
|
44
|
+
Dir,
|
|
45
|
+
Http
|
|
46
|
+
};
|
|
47
|
+
Q_ENUM(AnQstWidgetResourceProvider)
|
|
48
|
+
|
|
49
|
+
enum class AnQstAngularAppHost {
|
|
50
|
+
Application,
|
|
51
|
+
Browser
|
|
52
|
+
};
|
|
53
|
+
Q_ENUM(AnQstAngularAppHost)
|
|
54
|
+
|
|
55
|
+
explicit AnQstWebHostBase(QWidget* parent = nullptr);
|
|
56
|
+
|
|
57
|
+
static constexpr int kMaxQueuedSlotInvocations = 1024;
|
|
58
|
+
|
|
59
|
+
using CallHandler = std::function<QVariant(const QString& service, const QString& member, const QVariantList& args)>;
|
|
60
|
+
using EmitterHandler = std::function<void(const QString& service, const QString& member, const QVariantList& args)>;
|
|
61
|
+
using InputHandler = std::function<void(const QString& service, const QString& member, const QVariant&)>;
|
|
62
|
+
|
|
63
|
+
bool setContentRoot(const QString& rootPath);
|
|
64
|
+
bool loadEntryPoint(const QString& entryPoint);
|
|
65
|
+
bool setBridgeObject(QObject* bridgeObject = nullptr, const QString& objectName = QStringLiteral("anqstBridge"));
|
|
66
|
+
QUrl resolveAssetPath(const QString& relativePath) const;
|
|
67
|
+
bool installBridgeBootstrapScript(const QString& scriptSource = QString(), bool forceReinstall = false);
|
|
68
|
+
bool enableDebug();
|
|
69
|
+
bool isDevelopmentModeEnabled() const;
|
|
70
|
+
QString developmentModeUrl() const;
|
|
71
|
+
void setDevelopmentModeAllowLan(bool allowLan);
|
|
72
|
+
bool developmentModeAllowLan() const;
|
|
73
|
+
|
|
74
|
+
void setContextMenuEnabled(bool enabled);
|
|
75
|
+
void setTextSelectionEnabled(bool enabled);
|
|
76
|
+
void setScrollbarsEnabled(bool enabled);
|
|
77
|
+
void setRemoteNavigationBlocked(bool blocked);
|
|
78
|
+
bool remoteNavigationBlocked() const;
|
|
79
|
+
|
|
80
|
+
void setCallHandler(const CallHandler& handler);
|
|
81
|
+
void setEmitterHandler(const EmitterHandler& handler);
|
|
82
|
+
void setInputHandler(const InputHandler& handler);
|
|
83
|
+
void setOutputValue(const QString& service, const QString& member, const QVariant& value);
|
|
84
|
+
bool invokeSlot(const QString& service, const QString& member, const QVariantList& args, QVariant* result = nullptr, QString* error = nullptr);
|
|
85
|
+
void setSlotInvocationTimeoutMs(int timeoutMs);
|
|
86
|
+
int slotInvocationTimeoutMs() const;
|
|
87
|
+
|
|
88
|
+
void registerDropTarget(const QString& service, const QString& member, const QString& mimeType);
|
|
89
|
+
void registerHoverTarget(const QString& service, const QString& member, const QString& mimeType, int throttleIntervalMs);
|
|
90
|
+
|
|
91
|
+
bool eventFilter(QObject* obj, QEvent* event) override;
|
|
92
|
+
|
|
93
|
+
QString contentRoot() const;
|
|
94
|
+
ContentRootMode contentRootMode() const;
|
|
95
|
+
bool isBridgeSet() const;
|
|
96
|
+
|
|
97
|
+
// Web->Qt behavior channels exposed to QWebChannel.
|
|
98
|
+
Q_INVOKABLE void anQstBridge_registerSlot(const QString& service, const QString& member);
|
|
99
|
+
Q_INVOKABLE QVariant anQstBridge_call(const QString& service, const QString& member, const QVariantList& args);
|
|
100
|
+
Q_INVOKABLE void anQstBridge_emit(const QString& service, const QString& member, const QVariantList& args);
|
|
101
|
+
Q_INVOKABLE void anQstBridge_setInput(const QString& service, const QString& member, const QVariant& value);
|
|
102
|
+
Q_INVOKABLE void anQstBridge_resolveSlot(const QString& requestId, bool ok, const QVariant& payload, const QString& error);
|
|
103
|
+
|
|
104
|
+
signals:
|
|
105
|
+
void onHostReady();
|
|
106
|
+
void onHostError(const QVariantMap& errorPayload);
|
|
107
|
+
void onWebEngineError(const QString& channel, const QString& detail);
|
|
108
|
+
void anQstBridge_outputUpdated(const QString& service, const QString& member, const QVariant& value);
|
|
109
|
+
void anQstBridge_slotInvocationRequested(const QString& requestId, const QString& service, const QString& member, const QVariantList& args);
|
|
110
|
+
void slotInvocationResolved(const QString& requestId);
|
|
111
|
+
void developmentModeEnabled(const QString& url);
|
|
112
|
+
void anQstBridge_dropReceived(const QString& service, const QString& member, const QVariant& payload, double x, double y);
|
|
113
|
+
void anQstBridge_hoverUpdated(const QString& service, const QString& member, const QVariant& payload, double x, double y);
|
|
114
|
+
void anQstBridge_hoverLeft(const QString& service, const QString& member);
|
|
115
|
+
void jsConsoleLineAppended(const QString& line);
|
|
116
|
+
|
|
117
|
+
private slots:
|
|
118
|
+
void handleLoadFinished(bool ok);
|
|
119
|
+
void handleNavigationPolicyError(const QUrl& blockedUrl);
|
|
120
|
+
void handleNetworkPolicyError(const QUrl& blockedUrl);
|
|
121
|
+
void handleWebEngineDiagnostic(const QString& channel, const QString& detail);
|
|
122
|
+
void handleJavaScriptConsoleLine(const QString& line);
|
|
123
|
+
void executeDebugJavaScript(const QString& source);
|
|
124
|
+
void handleDebugShortcut();
|
|
125
|
+
void handleReattachRequested();
|
|
126
|
+
|
|
127
|
+
protected:
|
|
128
|
+
void emitHostError(
|
|
129
|
+
const QString& code,
|
|
130
|
+
const QString& category,
|
|
131
|
+
const QString& severity,
|
|
132
|
+
bool recoverable,
|
|
133
|
+
const QString& message,
|
|
134
|
+
const QVariantMap& context = QVariantMap());
|
|
135
|
+
|
|
136
|
+
private:
|
|
137
|
+
struct DebugState {
|
|
138
|
+
AnQstWidgetResourceProvider provider = AnQstWidgetResourceProvider::Qrc;
|
|
139
|
+
AnQstAngularAppHost host = AnQstAngularAppHost::Application;
|
|
140
|
+
QString resourceUrl;
|
|
141
|
+
QString resourceDir;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
struct DebugDialogResult {
|
|
145
|
+
bool accepted = false;
|
|
146
|
+
DebugState nextState;
|
|
147
|
+
bool openBrowser = false;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
DebugState currentDebugState() const;
|
|
151
|
+
DebugDialogResult runDebugDialog(const DebugState& initialState);
|
|
152
|
+
void openDebugDialogModeless(const DebugState& initialState);
|
|
153
|
+
bool applyDebugStateChange(const DebugState& previousState, const DebugDialogResult& dialogResult);
|
|
154
|
+
bool applyApplicationHostState(const DebugState& previousState, const DebugState& nextState);
|
|
155
|
+
bool applyBrowserHostState(const DebugState& previousState, const DebugState& nextState, bool openBrowser);
|
|
156
|
+
bool configureServerForProvider(const DebugState& nextState);
|
|
157
|
+
bool ensureDirectoryProviderValid(const QString& directoryInput, QString* normalizedRoot = nullptr) const;
|
|
158
|
+
bool ensureHttpProviderValid(const QString& urlText, QUrl* normalizedUrl = nullptr) const;
|
|
159
|
+
QUrl resolveEntryPointForProvider(const DebugState& state, bool* requiresServer) const;
|
|
160
|
+
void showEmbeddedView(const QUrl& targetUrl);
|
|
161
|
+
void showBrowserPlaceholder(const QString& browserUrl);
|
|
162
|
+
bool openUrlInBrowser(const QString& urlText) const;
|
|
163
|
+
QString normalizedDirectoryRoot(const QString& directoryInput) const;
|
|
164
|
+
QString browserUrl() const;
|
|
165
|
+
QString debugWidgetName() const;
|
|
166
|
+
void appendJsConsoleLine(const QString& line);
|
|
167
|
+
void appendJsConsoleCommandHistoryEntry(const QString& source);
|
|
168
|
+
void applyDebugBorderHint();
|
|
169
|
+
void emitWebEngineError(const QString& channel, const QString& detail);
|
|
170
|
+
bool isBlockedScheme(const QUrl& url) const;
|
|
171
|
+
bool isContentRootSet() const;
|
|
172
|
+
bool isEntryPointLoaded() const;
|
|
173
|
+
bool shouldEmitReady() const;
|
|
174
|
+
void emitOutputSnapshotIfReady();
|
|
175
|
+
QString loadDefaultBridgeBootstrapScript() const;
|
|
176
|
+
void applyTextSelectionPolicy();
|
|
177
|
+
void applyScrollbarPolicy();
|
|
178
|
+
|
|
179
|
+
struct DragTargetBinding {
|
|
180
|
+
QString service;
|
|
181
|
+
QString member;
|
|
182
|
+
int throttleIntervalMs = 0;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
void installDragDropEventFilter();
|
|
186
|
+
bool matchDropMimeType(const QMimeData* mime, QString* matchedMimeType) const;
|
|
187
|
+
QVariant deserializeMimePayload(const QMimeData* mime, const QString& mimeType);
|
|
188
|
+
void dispatchHoverThrottle();
|
|
189
|
+
|
|
190
|
+
LocalWebView* m_view;
|
|
191
|
+
QLabel* m_devPlaceholder;
|
|
192
|
+
QPushButton* m_reattachButton;
|
|
193
|
+
QWebChannel* m_webChannel;
|
|
194
|
+
AnQstHostBridgeFacade* m_bridgeFacade;
|
|
195
|
+
AnQstBridgeProxy* m_bridgeProxy;
|
|
196
|
+
AngularHttpBaseServer* m_devServer;
|
|
197
|
+
QString m_contentRoot;
|
|
198
|
+
ContentRootMode m_contentRootMode;
|
|
199
|
+
QString m_entryPoint;
|
|
200
|
+
QObject* m_bridgeObject;
|
|
201
|
+
QString m_bridgeObjectName;
|
|
202
|
+
bool m_bridgeAttached;
|
|
203
|
+
bool m_contentRootSet;
|
|
204
|
+
bool m_entryPointLoaded;
|
|
205
|
+
bool m_bridgeBootstrapInstalled;
|
|
206
|
+
bool m_developmentModeEnabled;
|
|
207
|
+
bool m_developmentModeAllowLan;
|
|
208
|
+
bool m_textSelectionEnabled;
|
|
209
|
+
bool m_scrollbarsEnabled;
|
|
210
|
+
QString m_developmentModeUrl;
|
|
211
|
+
DebugState m_debugState;
|
|
212
|
+
bool m_remoteNavigationBlocked;
|
|
213
|
+
QStringList m_jsConsoleLines;
|
|
214
|
+
QStringList m_jsConsoleCommandHistory;
|
|
215
|
+
AnQstWidgetDebugDialog* m_activeDebugDialog;
|
|
216
|
+
|
|
217
|
+
QHash<QString, DragTargetBinding> m_dropTargets;
|
|
218
|
+
QHash<QString, DragTargetBinding> m_hoverTargets;
|
|
219
|
+
QTimer* m_hoverThrottleTimer;
|
|
220
|
+
QPoint m_pendingHoverPos;
|
|
221
|
+
QVariant m_cachedHoverPayload;
|
|
222
|
+
QString m_cachedHoverService;
|
|
223
|
+
QString m_cachedHoverMember;
|
|
224
|
+
bool m_dragDropFilterInstalled;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
} // namespace ANQST_WEBBASE_NAMESPACE
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
#include "AnQstWidgetDebugDialog.h"
|
|
2
|
+
|
|
3
|
+
#include "ui_AnQstWidgetDebugDialog.h"
|
|
4
|
+
|
|
5
|
+
#include <QAbstractButton>
|
|
6
|
+
#include <QCheckBox>
|
|
7
|
+
#include <QComboBox>
|
|
8
|
+
#include <QDialogButtonBox>
|
|
9
|
+
#include <QDir>
|
|
10
|
+
#include <QEvent>
|
|
11
|
+
#include <QFileDialog>
|
|
12
|
+
#include <QFileInfo>
|
|
13
|
+
#include <QKeyEvent>
|
|
14
|
+
#include <QLayout>
|
|
15
|
+
#include <QLineEdit>
|
|
16
|
+
#include <QNetworkAccessManager>
|
|
17
|
+
#include <QNetworkReply>
|
|
18
|
+
#include <QNetworkRequest>
|
|
19
|
+
#include <QPlainTextEdit>
|
|
20
|
+
#include <QPushButton>
|
|
21
|
+
#include <QScrollBar>
|
|
22
|
+
#include <QTabWidget>
|
|
23
|
+
#include <QTimer>
|
|
24
|
+
|
|
25
|
+
namespace ANQST_WEBBASE_NAMESPACE {
|
|
26
|
+
|
|
27
|
+
namespace {
|
|
28
|
+
|
|
29
|
+
constexpr int kUrlProbeDebounceMs = 400;
|
|
30
|
+
constexpr int kStatusPayloadPreviewLength = 64;
|
|
31
|
+
constexpr int kMaxJsConsoleMessages = 20000;
|
|
32
|
+
|
|
33
|
+
} // namespace
|
|
34
|
+
|
|
35
|
+
AnQstWidgetDebugDialog::AnQstWidgetDebugDialog(const InitialState& initialState, QWidget* parent)
|
|
36
|
+
: QDialog(parent)
|
|
37
|
+
, m_ui(new Ui::AnQstWidgetBaseClassDialog())
|
|
38
|
+
, m_networkManager(new QNetworkAccessManager(this))
|
|
39
|
+
, m_urlProbeDebounceTimer(new QTimer(this))
|
|
40
|
+
, m_activeProbeReply(nullptr)
|
|
41
|
+
, m_probeGeneration(0)
|
|
42
|
+
, m_isUrlProbeOk(false)
|
|
43
|
+
, m_jsConsoleCommandHistoryIndex(0) {
|
|
44
|
+
m_ui->setupUi(this);
|
|
45
|
+
|
|
46
|
+
const QString widgetName = initialState.widgetName.trimmed().isEmpty()
|
|
47
|
+
? QStringLiteral("Unknown")
|
|
48
|
+
: initialState.widgetName.trimmed();
|
|
49
|
+
setWindowTitle(QStringLiteral("Debug Widget %1").arg(widgetName));
|
|
50
|
+
|
|
51
|
+
m_ui->cbAnQstAngularAppHost->setCurrentIndex(static_cast<int>(initialState.hostMode));
|
|
52
|
+
m_ui->cbWidgetResource->setCurrentIndex(static_cast<int>(initialState.resourceProvider));
|
|
53
|
+
m_ui->leURL->setText(initialState.resourceUrl);
|
|
54
|
+
m_ui->leDirectory->setText(initialState.resourceDirectory);
|
|
55
|
+
m_jsConsoleHistory = initialState.jsConsoleHistory;
|
|
56
|
+
if (m_jsConsoleHistory.size() > kMaxJsConsoleMessages) {
|
|
57
|
+
m_jsConsoleHistory = m_jsConsoleHistory.mid(m_jsConsoleHistory.size() - kMaxJsConsoleMessages);
|
|
58
|
+
}
|
|
59
|
+
m_jsConsoleCommandHistory = initialState.jsConsoleCommandHistory;
|
|
60
|
+
if (m_jsConsoleCommandHistory.size() > kMaxJsConsoleMessages) {
|
|
61
|
+
m_jsConsoleCommandHistory =
|
|
62
|
+
m_jsConsoleCommandHistory.mid(m_jsConsoleCommandHistory.size() - kMaxJsConsoleMessages);
|
|
63
|
+
}
|
|
64
|
+
m_jsConsoleCommandHistoryIndex = m_jsConsoleCommandHistory.size();
|
|
65
|
+
m_ui->txtEditJSLog->setPlainText(m_jsConsoleHistory.join(QLatin1Char('\n')));
|
|
66
|
+
m_ui->txtEditJSLog->verticalScrollBar()->setValue(m_ui->txtEditJSLog->verticalScrollBar()->maximum());
|
|
67
|
+
m_ui->lineEditJSConsoleInput->setFocusPolicy(Qt::StrongFocus);
|
|
68
|
+
m_ui->lineEditJSConsoleInput->installEventFilter(this);
|
|
69
|
+
|
|
70
|
+
m_urlProbeDebounceTimer->setSingleShot(true);
|
|
71
|
+
m_urlProbeDebounceTimer->setInterval(kUrlProbeDebounceMs);
|
|
72
|
+
|
|
73
|
+
connect(m_ui->cbWidgetResource, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int) {
|
|
74
|
+
onResourceProviderChanged();
|
|
75
|
+
});
|
|
76
|
+
connect(m_ui->cbAnQstAngularAppHost, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int) {
|
|
77
|
+
onHostModeChanged();
|
|
78
|
+
});
|
|
79
|
+
connect(m_ui->leURL, &QLineEdit::textChanged, this, &AnQstWidgetDebugDialog::onUrlInputChanged);
|
|
80
|
+
connect(m_ui->leDirectory, &QLineEdit::textChanged, this, &AnQstWidgetDebugDialog::onDirectoryInputChanged);
|
|
81
|
+
connect(m_urlProbeDebounceTimer, &QTimer::timeout, this, &AnQstWidgetDebugDialog::onUrlProbeTimeout);
|
|
82
|
+
connect(m_ui->btnBrowseDirectory, &QAbstractButton::clicked, this, &AnQstWidgetDebugDialog::onBrowseDirectoryRequested);
|
|
83
|
+
connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, &AnQstWidgetDebugDialog::onTabChanged);
|
|
84
|
+
|
|
85
|
+
updateDynamicVisibility();
|
|
86
|
+
onUrlInputChanged();
|
|
87
|
+
onDirectoryInputChanged();
|
|
88
|
+
focusJsConsoleInput();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
AnQstWidgetDebugDialog::~AnQstWidgetDebugDialog() {
|
|
92
|
+
stopPendingProbe();
|
|
93
|
+
delete m_ui;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
AnQstWidgetDebugDialog::ResultState AnQstWidgetDebugDialog::resultState() const {
|
|
97
|
+
ResultState result;
|
|
98
|
+
result.accepted = (this->result() == QDialog::Accepted);
|
|
99
|
+
result.hostMode = static_cast<HostMode>(m_ui->cbAnQstAngularAppHost->currentIndex());
|
|
100
|
+
result.resourceProvider = static_cast<ResourceProvider>(m_ui->cbWidgetResource->currentIndex());
|
|
101
|
+
result.resourceUrl = m_ui->leURL->text().trimmed();
|
|
102
|
+
result.resourceDirectory = normalizedDirectoryRoot(m_ui->leDirectory->text());
|
|
103
|
+
result.openBrowserChecked =
|
|
104
|
+
(result.hostMode == HostMode::Browser) && m_ui->rbOpenBrowser->isChecked();
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
void AnQstWidgetDebugDialog::appendJsConsoleLine(const QString& line) {
|
|
109
|
+
m_jsConsoleHistory.append(line);
|
|
110
|
+
bool rebuilt = false;
|
|
111
|
+
if (m_jsConsoleHistory.size() > kMaxJsConsoleMessages) {
|
|
112
|
+
m_jsConsoleHistory.removeFirst();
|
|
113
|
+
rebuilt = true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (rebuilt) {
|
|
117
|
+
m_ui->txtEditJSLog->setPlainText(m_jsConsoleHistory.join(QLatin1Char('\n')));
|
|
118
|
+
} else {
|
|
119
|
+
m_ui->txtEditJSLog->appendPlainText(line);
|
|
120
|
+
}
|
|
121
|
+
m_ui->txtEditJSLog->verticalScrollBar()->setValue(m_ui->txtEditJSLog->verticalScrollBar()->maximum());
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
void AnQstWidgetDebugDialog::onResourceProviderChanged() {
|
|
125
|
+
updateDynamicVisibility();
|
|
126
|
+
onUrlInputChanged();
|
|
127
|
+
onDirectoryInputChanged();
|
|
128
|
+
updateValidationState();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
void AnQstWidgetDebugDialog::onHostModeChanged() {
|
|
132
|
+
updateDynamicVisibility();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
void AnQstWidgetDebugDialog::onUrlInputChanged() {
|
|
136
|
+
if (!isHttpProviderSelected()) {
|
|
137
|
+
stopPendingProbe();
|
|
138
|
+
m_isUrlProbeOk = true;
|
|
139
|
+
setUrlStatusMessage(QString());
|
|
140
|
+
updateValidationState();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
QUrl normalized;
|
|
145
|
+
if (!parseAndNormalizeHttpUrl(m_ui->leURL->text().trimmed(), &normalized)) {
|
|
146
|
+
stopPendingProbe();
|
|
147
|
+
completeProbeAsInvalid(QStringLiteral("Error: Invalid HTTP URL"));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
m_isUrlProbeOk = false;
|
|
152
|
+
setUrlStatusMessage(QStringLiteral("Checking..."));
|
|
153
|
+
m_urlProbeDebounceTimer->start();
|
|
154
|
+
updateValidationState();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
void AnQstWidgetDebugDialog::onUrlProbeTimeout() {
|
|
158
|
+
if (!isHttpProviderSelected()) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
QUrl normalized;
|
|
162
|
+
if (!parseAndNormalizeHttpUrl(m_ui->leURL->text().trimmed(), &normalized)) {
|
|
163
|
+
completeProbeAsInvalid(QStringLiteral("Error: Invalid HTTP URL"));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
startUrlProbe(normalized);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
void AnQstWidgetDebugDialog::onBrowseDirectoryRequested() {
|
|
170
|
+
const QString selected = QFileDialog::getExistingDirectory(
|
|
171
|
+
this,
|
|
172
|
+
QStringLiteral("Select Directory"),
|
|
173
|
+
normalizedDirectoryRoot(m_ui->leDirectory->text()));
|
|
174
|
+
if (selected.trimmed().isEmpty()) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
m_ui->leDirectory->setText(QDir::cleanPath(QFileInfo(selected).absoluteFilePath()));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
void AnQstWidgetDebugDialog::onDirectoryInputChanged() {
|
|
181
|
+
updateValidationState();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
void AnQstWidgetDebugDialog::onTabChanged(int index) {
|
|
185
|
+
if (m_ui->tabWidget->widget(index) == m_ui->tabJSConsole) {
|
|
186
|
+
focusJsConsoleInput();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
void AnQstWidgetDebugDialog::onJsConsoleInputSubmitted() {
|
|
191
|
+
const QString source = m_ui->lineEditJSConsoleInput->text();
|
|
192
|
+
if (source.isEmpty()) {
|
|
193
|
+
focusJsConsoleInput();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
appendJsConsoleCommandHistoryEntry(source);
|
|
198
|
+
emit jsConsoleCommandSubmitted(source);
|
|
199
|
+
m_ui->lineEditJSConsoleInput->clear();
|
|
200
|
+
m_jsConsoleCommandHistoryIndex = m_jsConsoleCommandHistory.size();
|
|
201
|
+
focusJsConsoleInput();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
void AnQstWidgetDebugDialog::setLayoutVisible(QLayout* layout, bool visible) {
|
|
205
|
+
if (layout == nullptr) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
for (int i = 0; i < layout->count(); ++i) {
|
|
209
|
+
QLayoutItem* item = layout->itemAt(i);
|
|
210
|
+
if (item == nullptr) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (item->widget() != nullptr) {
|
|
214
|
+
item->widget()->setVisible(visible);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (item->layout() != nullptr) {
|
|
218
|
+
setLayoutVisible(item->layout(), visible);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (item->spacerItem() != nullptr) {
|
|
222
|
+
item->spacerItem()->changeSize(
|
|
223
|
+
visible ? item->spacerItem()->sizeHint().width() : 0,
|
|
224
|
+
visible ? item->spacerItem()->sizeHint().height() : 0);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
bool AnQstWidgetDebugDialog::eventFilter(QObject* watched, QEvent* event) {
|
|
230
|
+
if (watched == m_ui->lineEditJSConsoleInput && event != nullptr && event->type() == QEvent::KeyPress) {
|
|
231
|
+
auto* keyEvent = static_cast<QKeyEvent*>(event);
|
|
232
|
+
if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
|
|
233
|
+
onJsConsoleInputSubmitted();
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
if (keyEvent->key() == Qt::Key_Up) {
|
|
237
|
+
showPreviousJsConsoleCommand();
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
if (keyEvent->key() == Qt::Key_Down) {
|
|
241
|
+
showNextJsConsoleCommand();
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return QDialog::eventFilter(watched, event);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
void AnQstWidgetDebugDialog::updateDynamicVisibility() {
|
|
249
|
+
const bool showUrl = isHttpProviderSelected();
|
|
250
|
+
const bool showDirectory = isDirProviderSelected();
|
|
251
|
+
const bool showOpenBrowser = (m_ui->cbAnQstAngularAppHost->currentIndex() == static_cast<int>(HostMode::Browser));
|
|
252
|
+
|
|
253
|
+
setLayoutVisible(m_ui->vlayoutURL, showUrl);
|
|
254
|
+
setLayoutVisible(m_ui->vlayoutDIR, showDirectory);
|
|
255
|
+
m_ui->rbOpenBrowser->setVisible(showOpenBrowser);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
void AnQstWidgetDebugDialog::updateValidationState() {
|
|
259
|
+
bool okEnabled = true;
|
|
260
|
+
if (isHttpProviderSelected()) {
|
|
261
|
+
okEnabled = okEnabled && m_isUrlProbeOk;
|
|
262
|
+
}
|
|
263
|
+
if (isDirProviderSelected()) {
|
|
264
|
+
okEnabled = okEnabled && isDirectoryInputValid();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
QPushButton* okButton = m_ui->buttonBox->button(QDialogButtonBox::Ok);
|
|
268
|
+
if (okButton != nullptr) {
|
|
269
|
+
okButton->setEnabled(okEnabled);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
void AnQstWidgetDebugDialog::setUrlStatusMessage(const QString& message) {
|
|
274
|
+
m_ui->lblURLStatusMsg->setText(message);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
void AnQstWidgetDebugDialog::focusJsConsoleInput() {
|
|
278
|
+
if (m_ui->tabWidget->currentWidget() != m_ui->tabJSConsole) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
m_ui->lineEditJSConsoleInput->setFocus(Qt::OtherFocusReason);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
void AnQstWidgetDebugDialog::appendJsConsoleCommandHistoryEntry(const QString& source) {
|
|
285
|
+
if (source.isEmpty()) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
m_jsConsoleCommandHistory.append(source);
|
|
289
|
+
if (m_jsConsoleCommandHistory.size() > kMaxJsConsoleMessages) {
|
|
290
|
+
m_jsConsoleCommandHistory.removeFirst();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
void AnQstWidgetDebugDialog::showPreviousJsConsoleCommand() {
|
|
295
|
+
if (m_jsConsoleCommandHistory.isEmpty()) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (m_jsConsoleCommandHistoryIndex > 0) {
|
|
299
|
+
--m_jsConsoleCommandHistoryIndex;
|
|
300
|
+
}
|
|
301
|
+
m_ui->lineEditJSConsoleInput->setText(m_jsConsoleCommandHistory.at(m_jsConsoleCommandHistoryIndex));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
void AnQstWidgetDebugDialog::showNextJsConsoleCommand() {
|
|
305
|
+
if (m_jsConsoleCommandHistory.isEmpty()) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (m_jsConsoleCommandHistoryIndex < m_jsConsoleCommandHistory.size() - 1) {
|
|
309
|
+
++m_jsConsoleCommandHistoryIndex;
|
|
310
|
+
m_ui->lineEditJSConsoleInput->setText(m_jsConsoleCommandHistory.at(m_jsConsoleCommandHistoryIndex));
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
m_jsConsoleCommandHistoryIndex = m_jsConsoleCommandHistory.size();
|
|
314
|
+
m_ui->lineEditJSConsoleInput->clear();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
bool AnQstWidgetDebugDialog::isHttpProviderSelected() const {
|
|
318
|
+
return m_ui->cbWidgetResource->currentIndex() == static_cast<int>(ResourceProvider::Http);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
bool AnQstWidgetDebugDialog::isDirProviderSelected() const {
|
|
322
|
+
return m_ui->cbWidgetResource->currentIndex() == static_cast<int>(ResourceProvider::Dir);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
QString AnQstWidgetDebugDialog::normalizedDirectoryRoot(const QString& input) const {
|
|
326
|
+
const QString trimmed = input.trimmed();
|
|
327
|
+
if (trimmed.isEmpty()) {
|
|
328
|
+
return QString();
|
|
329
|
+
}
|
|
330
|
+
const QFileInfo info(trimmed);
|
|
331
|
+
if (info.isAbsolute()) {
|
|
332
|
+
return QDir::cleanPath(info.absoluteFilePath());
|
|
333
|
+
}
|
|
334
|
+
return QDir::cleanPath(QDir::current().absoluteFilePath(trimmed));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
bool AnQstWidgetDebugDialog::isDirectoryInputValid() const {
|
|
338
|
+
const QString normalized = normalizedDirectoryRoot(m_ui->leDirectory->text());
|
|
339
|
+
if (normalized.isEmpty()) {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
const QFileInfo info(normalized);
|
|
343
|
+
return info.exists() && info.isDir();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
bool AnQstWidgetDebugDialog::parseAndNormalizeHttpUrl(const QString& input, QUrl* normalized) const {
|
|
347
|
+
if (normalized != nullptr) {
|
|
348
|
+
normalized->clear();
|
|
349
|
+
}
|
|
350
|
+
if (input.trimmed().isEmpty()) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
QUrl url(input.trimmed());
|
|
354
|
+
if (!url.isValid() || url.scheme().trimmed().isEmpty() || url.host().trimmed().isEmpty()) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
if (url.scheme().toLower() != QStringLiteral("http")) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
url.setPath(QStringLiteral("/"));
|
|
361
|
+
url.setQuery(QString());
|
|
362
|
+
url.setFragment(QString());
|
|
363
|
+
if (normalized != nullptr) {
|
|
364
|
+
*normalized = url;
|
|
365
|
+
}
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
void AnQstWidgetDebugDialog::startUrlProbe(const QUrl& urlToProbe) {
|
|
370
|
+
stopPendingProbe();
|
|
371
|
+
++m_probeGeneration;
|
|
372
|
+
const quint64 generation = m_probeGeneration;
|
|
373
|
+
|
|
374
|
+
QNetworkRequest request(urlToProbe);
|
|
375
|
+
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
|
|
376
|
+
m_activeProbeReply = m_networkManager->get(request);
|
|
377
|
+
connect(m_activeProbeReply, &QNetworkReply::finished, this, [this, generation]() {
|
|
378
|
+
if (generation != m_probeGeneration || m_activeProbeReply == nullptr) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
QNetworkReply* reply = m_activeProbeReply;
|
|
382
|
+
m_activeProbeReply = nullptr;
|
|
383
|
+
|
|
384
|
+
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
|
385
|
+
const QNetworkReply::NetworkError networkError = reply->error();
|
|
386
|
+
const QString errorText = reply->errorString();
|
|
387
|
+
const QString payload = QString::fromUtf8(reply->readAll()).trimmed();
|
|
388
|
+
reply->deleteLater();
|
|
389
|
+
|
|
390
|
+
if (networkError != QNetworkReply::NoError) {
|
|
391
|
+
completeProbeAsInvalid(QStringLiteral("Error: %1").arg(errorText));
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (statusCode >= 400) {
|
|
395
|
+
QString compactPayload = payload.left(kStatusPayloadPreviewLength);
|
|
396
|
+
compactPayload.replace('\n', ' ');
|
|
397
|
+
compactPayload.replace('\r', ' ');
|
|
398
|
+
setUrlStatusMessage(QStringLiteral("HTTP Error: %1 %2").arg(statusCode).arg(compactPayload));
|
|
399
|
+
m_isUrlProbeOk = false;
|
|
400
|
+
updateValidationState();
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
setUrlStatusMessage(QStringLiteral("OK %1").arg(statusCode));
|
|
404
|
+
m_isUrlProbeOk = true;
|
|
405
|
+
updateValidationState();
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
void AnQstWidgetDebugDialog::stopPendingProbe() {
|
|
410
|
+
m_urlProbeDebounceTimer->stop();
|
|
411
|
+
++m_probeGeneration;
|
|
412
|
+
if (m_activeProbeReply != nullptr) {
|
|
413
|
+
m_activeProbeReply->abort();
|
|
414
|
+
m_activeProbeReply->deleteLater();
|
|
415
|
+
m_activeProbeReply = nullptr;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
void AnQstWidgetDebugDialog::completeProbeAsInvalid(const QString& message) {
|
|
420
|
+
m_isUrlProbeOk = false;
|
|
421
|
+
setUrlStatusMessage(message);
|
|
422
|
+
updateValidationState();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
} // namespace ANQST_WEBBASE_NAMESPACE
|