frida-ui-runtime 2.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/README.md +181 -0
- package/lib/android-listener.d.ts +20 -0
- package/lib/android-listener.js +92 -0
- package/lib/android-runtime.d.ts +25 -0
- package/lib/android-runtime.js +310 -0
- package/lib/api.d.ts +1 -0
- package/lib/api.js +156 -0
- package/lib/component/button.d.ts +11 -0
- package/lib/component/button.js +61 -0
- package/lib/component/category.d.ts +9 -0
- package/lib/component/category.js +57 -0
- package/lib/component/checkBox.d.ts +33 -0
- package/lib/component/checkBox.js +276 -0
- package/lib/component/collapsible.d.ts +19 -0
- package/lib/component/collapsible.js +197 -0
- package/lib/component/image.d.ts +18 -0
- package/lib/component/image.js +85 -0
- package/lib/component/infoCardBox.d.ts +13 -0
- package/lib/component/infoCardBox.js +68 -0
- package/lib/component/input.d.ts +41 -0
- package/lib/component/input.js +342 -0
- package/lib/component/selector.d.ts +31 -0
- package/lib/component/selector.js +246 -0
- package/lib/component/slider.d.ts +18 -0
- package/lib/component/slider.js +156 -0
- package/lib/component/style/style.d.ts +4 -0
- package/lib/component/style/style.js +325 -0
- package/lib/component/style/theme.d.ts +33 -0
- package/lib/component/style/theme.js +24 -0
- package/lib/component/switch.d.ts +12 -0
- package/lib/component/switch.js +117 -0
- package/lib/component/text.d.ts +9 -0
- package/lib/component/text.js +36 -0
- package/lib/component/ui-components.d.ts +34 -0
- package/lib/component/ui-components.js +108 -0
- package/lib/component/views/header-view.d.ts +20 -0
- package/lib/component/views/header-view.js +198 -0
- package/lib/component/views/log-view.d.ts +41 -0
- package/lib/component/views/log-view.js +511 -0
- package/lib/component/views/tabs-view.d.ts +41 -0
- package/lib/component/views/tabs-view.js +334 -0
- package/lib/constant-config.d.ts +5 -0
- package/lib/constant-config.js +3 -0
- package/lib/event-emitter.d.ts +10 -0
- package/lib/event-emitter.js +52 -0
- package/lib/float-menu.d.ts +103 -0
- package/lib/float-menu.js +648 -0
- package/lib/index.d.ts +17 -0
- package/lib/index.js +16 -0
- package/lib/java-runtime.d.ts +2 -0
- package/lib/java-runtime.js +2 -0
- package/lib/logger.d.ts +43 -0
- package/lib/logger.js +200 -0
- package/lib/runtime/component-registry.d.ts +24 -0
- package/lib/runtime/component-registry.js +48 -0
- package/lib/runtime/launch-sequence.d.ts +5 -0
- package/lib/runtime/launch-sequence.js +20 -0
- package/lib/runtime/lifecycle-controller.d.ts +9 -0
- package/lib/runtime/lifecycle-controller.js +31 -0
- package/lib/runtime/listener-registry.d.ts +7 -0
- package/lib/runtime/listener-registry.js +40 -0
- package/lib/runtime/overlay-options.d.ts +9 -0
- package/lib/runtime/overlay-options.js +42 -0
- package/lib/runtime/safe-runtime.d.ts +3 -0
- package/lib/runtime/safe-runtime.js +20 -0
- package/lib/utils.d.ts +8 -0
- package/lib/utils.js +13 -0
- package/package.json +43 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import Java from "../../java-runtime.js";
|
|
2
|
+
import { API } from "../../api.js";
|
|
3
|
+
import { EventEmitter } from "../../event-emitter.js";
|
|
4
|
+
import { Logger } from "../../logger.js";
|
|
5
|
+
import { dp } from "../style/style.js";
|
|
6
|
+
import { createJavaListener } from "../../android-listener.js";
|
|
7
|
+
import { ListenerRegistry } from "../../runtime/listener-registry.js";
|
|
8
|
+
export class TabsView {
|
|
9
|
+
constructor(context, theme, initTabs, activeTabId) {
|
|
10
|
+
this.tabs = new Map();
|
|
11
|
+
this.logger = Logger.instance;
|
|
12
|
+
this.eventEmitter = new EventEmitter();
|
|
13
|
+
this.tabItemMap = new Map();
|
|
14
|
+
this.listeners = new ListenerRegistry();
|
|
15
|
+
this.context = context;
|
|
16
|
+
this.theme = theme;
|
|
17
|
+
this.initTabs = initTabs;
|
|
18
|
+
this.activeTabId = activeTabId;
|
|
19
|
+
}
|
|
20
|
+
createTabView(parentView) {
|
|
21
|
+
this.parentView = parentView;
|
|
22
|
+
if (this.tabs.size === 1 && this.tabs.has("default")) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
this.tabItemMap.clear();
|
|
26
|
+
try {
|
|
27
|
+
const LinearLayout = API.LinearLayout;
|
|
28
|
+
const LinearLayoutParams = API.LinearLayoutParams;
|
|
29
|
+
const ViewGroupLayoutParams = API.ViewGroupLayoutParams;
|
|
30
|
+
const TextView = API.TextView;
|
|
31
|
+
const OnClickListener = API.OnClickListener;
|
|
32
|
+
const JString = API.JString;
|
|
33
|
+
const HorizontalScrollView = API.HorizontalScrollView;
|
|
34
|
+
const GradientDrawable = API.GradientDrawable;
|
|
35
|
+
const Gravity = API.Gravity;
|
|
36
|
+
const self = this;
|
|
37
|
+
const outerScroll = HorizontalScrollView.$new(this.context);
|
|
38
|
+
outerScroll.setHorizontalScrollBarEnabled(false);
|
|
39
|
+
outerScroll.setFillViewport(false);
|
|
40
|
+
outerScroll.setPadding(dp(this.context, 12), dp(this.context, 6), dp(this.context, 12), dp(this.context, 6));
|
|
41
|
+
const scrollLp = LinearLayoutParams.$new(ViewGroupLayoutParams.MATCH_PARENT.value, ViewGroupLayoutParams.WRAP_CONTENT.value);
|
|
42
|
+
scrollLp.setMargins(0, 0, 0, 0);
|
|
43
|
+
outerScroll.setLayoutParams(scrollLp);
|
|
44
|
+
try {
|
|
45
|
+
outerScroll.setClipToPadding(false);
|
|
46
|
+
}
|
|
47
|
+
catch { }
|
|
48
|
+
try {
|
|
49
|
+
outerScroll.setClipChildren(false);
|
|
50
|
+
}
|
|
51
|
+
catch { }
|
|
52
|
+
const tabContainer = LinearLayout.$new(this.context);
|
|
53
|
+
tabContainer.setOrientation(0);
|
|
54
|
+
tabContainer.setLayoutParams(LinearLayoutParams.$new(ViewGroupLayoutParams.WRAP_CONTENT.value, ViewGroupLayoutParams.WRAP_CONTENT.value));
|
|
55
|
+
tabContainer.setPadding(0, 0, 0, 0);
|
|
56
|
+
this.tabContainer = tabContainer;
|
|
57
|
+
outerScroll.addView(tabContainer);
|
|
58
|
+
const tabBarRoot = LinearLayout.$new(this.context);
|
|
59
|
+
tabBarRoot.setOrientation(1);
|
|
60
|
+
tabBarRoot.setLayoutParams(LinearLayoutParams.$new(ViewGroupLayoutParams.MATCH_PARENT.value, ViewGroupLayoutParams.WRAP_CONTENT.value));
|
|
61
|
+
this.tabScrollView = outerScroll;
|
|
62
|
+
const indicator = API.View.$new(this.context);
|
|
63
|
+
const indicatorLp = LinearLayoutParams.$new(dp(this.context, 0), dp(this.context, 2));
|
|
64
|
+
indicatorLp.setMargins(dp(this.context, 12), dp(this.context, 2), dp(this.context, 12), dp(this.context, 0));
|
|
65
|
+
indicator.setLayoutParams(indicatorLp);
|
|
66
|
+
indicator.setBackgroundColor(0xffffffff | 0);
|
|
67
|
+
this.tabIndicatorView = indicator;
|
|
68
|
+
tabBarRoot.addView(outerScroll);
|
|
69
|
+
tabBarRoot.addView(indicator);
|
|
70
|
+
this.tabView = tabBarRoot;
|
|
71
|
+
for (const [tabId, tabInfo] of this.tabs) {
|
|
72
|
+
const tabItem = LinearLayout.$new(this.context);
|
|
73
|
+
tabItem.setOrientation(1);
|
|
74
|
+
tabItem.setGravity(Gravity.CENTER.value);
|
|
75
|
+
try {
|
|
76
|
+
tabItem.setBackground(null);
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
try {
|
|
80
|
+
tabItem.setBackgroundColor(0x00000000);
|
|
81
|
+
}
|
|
82
|
+
catch { }
|
|
83
|
+
tabItem.setPadding(dp(this.context, 12), dp(this.context, 6), dp(this.context, 12), dp(this.context, 0));
|
|
84
|
+
this.tabItemMap.set(tabId, tabItem);
|
|
85
|
+
const itemLp = LinearLayoutParams.$new(ViewGroupLayoutParams.WRAP_CONTENT.value, ViewGroupLayoutParams.WRAP_CONTENT.value);
|
|
86
|
+
itemLp.setMargins(0, 0, 0, 0);
|
|
87
|
+
tabItem.setLayoutParams(itemLp);
|
|
88
|
+
const label = TextView.$new(this.context);
|
|
89
|
+
label.setText.overload('java.lang.CharSequence').call(label, JString.$new(tabInfo.label));
|
|
90
|
+
label.setSingleLine(true);
|
|
91
|
+
label.setAllCaps(false);
|
|
92
|
+
label.setTextSize(2, this.theme.textSp.body);
|
|
93
|
+
label.setGravity(Gravity.CENTER.value);
|
|
94
|
+
try {
|
|
95
|
+
label.setBackground(null);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
try {
|
|
99
|
+
label.setBackgroundColor(0x00000000);
|
|
100
|
+
}
|
|
101
|
+
catch { }
|
|
102
|
+
}
|
|
103
|
+
const underline = API.View.$new(this.context);
|
|
104
|
+
const ulLp = LinearLayoutParams.$new(ViewGroupLayoutParams.MATCH_PARENT.value, dp(this.context, 2));
|
|
105
|
+
ulLp.setMargins(0, dp(this.context, 6), 0, 0);
|
|
106
|
+
underline.setLayoutParams(ulLp);
|
|
107
|
+
underline.setBackgroundColor(this.theme.colors.accent);
|
|
108
|
+
underline.setVisibility(API.View.GONE.value);
|
|
109
|
+
tabItem.addView(label);
|
|
110
|
+
tabItem.addView(underline);
|
|
111
|
+
this.updateTabStyle(tabItem, tabId === this.activeTabId);
|
|
112
|
+
const tabClickListener = createJavaListener({
|
|
113
|
+
key: "view-click",
|
|
114
|
+
interfaceClass: OnClickListener,
|
|
115
|
+
callbacks: {
|
|
116
|
+
onClick: () => {
|
|
117
|
+
self.selectTab(tabId);
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
this.listeners.add("tabs", () => tabClickListener.dispose());
|
|
122
|
+
tabItem.setOnClickListener(tabClickListener.instance);
|
|
123
|
+
tabContainer.addView(tabItem);
|
|
124
|
+
}
|
|
125
|
+
parentView.addView(this.tabView);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
this.logger.error("Failed to create tab view: " + error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
selectTab(tabId) {
|
|
132
|
+
if (!this.tabs.has(tabId) || tabId === this.activeTabId)
|
|
133
|
+
return;
|
|
134
|
+
const oldTabId = this.activeTabId;
|
|
135
|
+
this.activeTabId = tabId;
|
|
136
|
+
Java.scheduleOnMainThread(() => {
|
|
137
|
+
try {
|
|
138
|
+
const View = API.View;
|
|
139
|
+
for (const [id, tabInfo] of this.tabs) {
|
|
140
|
+
const root = tabInfo.root;
|
|
141
|
+
if (!root)
|
|
142
|
+
continue;
|
|
143
|
+
if (id === tabId) {
|
|
144
|
+
root.setVisibility(View.VISIBLE.value);
|
|
145
|
+
this.currentContentContainer = tabInfo.container;
|
|
146
|
+
this.currentScrollView = tabInfo.scrollView;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
root.setVisibility(View.GONE.value);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
this.refreshTabsUI();
|
|
153
|
+
this.eventEmitter.emit("tabChanged", tabId, oldTabId);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
Logger.instance.error(`Failed to switch to tab ${tabId}:`, error);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
updateTabStyle(tabItem, isActive) {
|
|
161
|
+
try {
|
|
162
|
+
const View = API.View;
|
|
163
|
+
const TextView = API.TextView;
|
|
164
|
+
const label = Java.cast(tabItem.getChildAt(0), TextView);
|
|
165
|
+
const underline = tabItem.getChildAt(1);
|
|
166
|
+
if (isActive) {
|
|
167
|
+
label.setTextColor(this.theme.colors.text);
|
|
168
|
+
try {
|
|
169
|
+
label.setTypeface(null, 1);
|
|
170
|
+
}
|
|
171
|
+
catch { }
|
|
172
|
+
underline.setVisibility(View.VISIBLE.value);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
label.setTextColor(this.theme.colors.subText);
|
|
176
|
+
try {
|
|
177
|
+
label.setTypeface(null, 0);
|
|
178
|
+
}
|
|
179
|
+
catch { }
|
|
180
|
+
underline.setVisibility(View.GONE.value);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
Logger.instance.error(e);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
updateTabIndicator(activeTabId) {
|
|
188
|
+
try {
|
|
189
|
+
if (!this.tabContainer || !this.tabIndicatorView || !this.tabScrollView)
|
|
190
|
+
return;
|
|
191
|
+
const tabIds = Array.from(this.tabs.keys());
|
|
192
|
+
const idx = tabIds.indexOf(activeTabId);
|
|
193
|
+
if (idx < 0)
|
|
194
|
+
return;
|
|
195
|
+
const child = this.tabContainer.getChildAt(idx);
|
|
196
|
+
if (!child)
|
|
197
|
+
return;
|
|
198
|
+
const indicator = this.tabIndicatorView;
|
|
199
|
+
const scroll = this.tabScrollView;
|
|
200
|
+
const runnable = createJavaListener({
|
|
201
|
+
key: "runnable",
|
|
202
|
+
interfaceClass: Java.use("java.lang.Runnable"),
|
|
203
|
+
callbacks: {
|
|
204
|
+
run: () => {
|
|
205
|
+
try {
|
|
206
|
+
const width = child.getWidth();
|
|
207
|
+
const leftInContainer = child.getLeft();
|
|
208
|
+
const scrollX = scroll.getScrollX();
|
|
209
|
+
const padL = scroll.getPaddingLeft();
|
|
210
|
+
const lp = indicator.getLayoutParams();
|
|
211
|
+
lp.width = width;
|
|
212
|
+
lp.leftMargin = padL + leftInContainer - scrollX;
|
|
213
|
+
indicator.setLayoutParams(lp);
|
|
214
|
+
}
|
|
215
|
+
catch { }
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
this.listeners.add("tabs", () => runnable.dispose());
|
|
220
|
+
scroll.post(runnable.instance);
|
|
221
|
+
}
|
|
222
|
+
catch (e) { }
|
|
223
|
+
}
|
|
224
|
+
refreshTabsUI() {
|
|
225
|
+
try {
|
|
226
|
+
if (!this.tabItemMap)
|
|
227
|
+
return;
|
|
228
|
+
for (const [tabId, tabItem] of this.tabItemMap) {
|
|
229
|
+
this.updateTabStyle(tabItem, tabId === this.activeTabId);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
Logger.instance.error(e);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
initializeTabs() {
|
|
237
|
+
this.tabs.clear();
|
|
238
|
+
if (this.initTabs && this.initTabs.length > 0) {
|
|
239
|
+
for (const tabDef of this.initTabs) {
|
|
240
|
+
this.tabs.set(tabDef.id, {
|
|
241
|
+
label: tabDef.label,
|
|
242
|
+
container: null,
|
|
243
|
+
components: new Set(),
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
if (this.activeTabId && this.tabs.has(this.activeTabId)) {
|
|
247
|
+
this.activeTabId = this.activeTabId;
|
|
248
|
+
}
|
|
249
|
+
else if (this.initTabs.length > 0) {
|
|
250
|
+
this.activeTabId = this.initTabs[0].id;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
this.tabs.set("default", {
|
|
255
|
+
label: "Default",
|
|
256
|
+
container: null,
|
|
257
|
+
components: new Set(),
|
|
258
|
+
});
|
|
259
|
+
this.activeTabId = "default";
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
createTabContainer() {
|
|
263
|
+
const ScrollView = API.ScrollView;
|
|
264
|
+
const LinearLayout = API.LinearLayout;
|
|
265
|
+
const ViewGroupLayoutParams = API.ViewGroupLayoutParams;
|
|
266
|
+
const LinearLayoutParams = API.LinearLayoutParams;
|
|
267
|
+
const View = API.View;
|
|
268
|
+
const tabRootsWrapper = LinearLayout.$new(this.context);
|
|
269
|
+
tabRootsWrapper.setOrientation(LinearLayout.VERTICAL.value);
|
|
270
|
+
tabRootsWrapper.setLayoutParams(ViewGroupLayoutParams.$new(ViewGroupLayoutParams.MATCH_PARENT.value, ViewGroupLayoutParams.WRAP_CONTENT.value));
|
|
271
|
+
if (!this.tabs || this.tabs.size === 0) {
|
|
272
|
+
Logger.instance.warn("[FloatMenu] tabs is empty, tab container will be blank.");
|
|
273
|
+
}
|
|
274
|
+
let firstTabId = null;
|
|
275
|
+
let firstTabInfo = null;
|
|
276
|
+
for (const [tabId, tabInfo] of this.tabs) {
|
|
277
|
+
if (!firstTabId) {
|
|
278
|
+
firstTabId = tabId;
|
|
279
|
+
firstTabInfo = tabInfo;
|
|
280
|
+
}
|
|
281
|
+
const sv = ScrollView.$new(this.context);
|
|
282
|
+
sv.setLayoutParams(LinearLayoutParams.$new(ViewGroupLayoutParams.MATCH_PARENT.value, 0, 1.0));
|
|
283
|
+
try {
|
|
284
|
+
sv.setBackgroundColor(0x00000000);
|
|
285
|
+
}
|
|
286
|
+
catch (e) { }
|
|
287
|
+
try {
|
|
288
|
+
sv.setFillViewport(true);
|
|
289
|
+
sv.setVerticalScrollBarEnabled(false);
|
|
290
|
+
}
|
|
291
|
+
catch (e) { }
|
|
292
|
+
const tabContainer = LinearLayout.$new(this.context);
|
|
293
|
+
tabContainer.setOrientation(LinearLayout.VERTICAL.value);
|
|
294
|
+
tabContainer.setLayoutParams(ViewGroupLayoutParams.$new(ViewGroupLayoutParams.MATCH_PARENT.value, ViewGroupLayoutParams.WRAP_CONTENT.value));
|
|
295
|
+
sv.addView(tabContainer);
|
|
296
|
+
if (tabId === this.activeTabId) {
|
|
297
|
+
sv.setVisibility(View.VISIBLE.value);
|
|
298
|
+
this.currentContentContainer = tabContainer;
|
|
299
|
+
this.currentScrollView = sv;
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
sv.setVisibility(View.GONE.value);
|
|
303
|
+
}
|
|
304
|
+
tabInfo.container = tabContainer;
|
|
305
|
+
tabInfo.scrollView = sv;
|
|
306
|
+
tabInfo.root = sv;
|
|
307
|
+
tabRootsWrapper.addView(sv);
|
|
308
|
+
}
|
|
309
|
+
if ((!this.currentContentContainer || !this.currentScrollView) &&
|
|
310
|
+
firstTabId &&
|
|
311
|
+
firstTabInfo) {
|
|
312
|
+
this.activeTabId = firstTabId;
|
|
313
|
+
if (firstTabInfo.root)
|
|
314
|
+
firstTabInfo.root.setVisibility(View.VISIBLE.value);
|
|
315
|
+
this.currentContentContainer = firstTabInfo.container;
|
|
316
|
+
this.currentScrollView = firstTabInfo.scrollView;
|
|
317
|
+
}
|
|
318
|
+
this.parentView.addView(tabRootsWrapper);
|
|
319
|
+
}
|
|
320
|
+
destroy() {
|
|
321
|
+
this.listeners.clearAll();
|
|
322
|
+
this.eventEmitter.removeAllListeners();
|
|
323
|
+
this.tabItemMap.clear();
|
|
324
|
+
this.tabs.clear();
|
|
325
|
+
this.tabContainer = null;
|
|
326
|
+
this.tabView = null;
|
|
327
|
+
this.tabScrollView = null;
|
|
328
|
+
this.tabIndicatorView = null;
|
|
329
|
+
this.currentContentContainer = null;
|
|
330
|
+
this.currentScrollView = null;
|
|
331
|
+
this.parentView = null;
|
|
332
|
+
this.menuPanelView = null;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type Listener = (...args: any[]) => void;
|
|
2
|
+
export declare class EventEmitter {
|
|
3
|
+
private events;
|
|
4
|
+
on(event: string, listener: Listener): () => void;
|
|
5
|
+
off(event: string, listener: Listener): void;
|
|
6
|
+
emit(event: string, ...args: any[]): void;
|
|
7
|
+
once(event: string, listener: Listener): () => void;
|
|
8
|
+
removeAllListeners(event?: string): void;
|
|
9
|
+
}
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Logger } from "./logger.js";
|
|
2
|
+
export class EventEmitter {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.events = new Map();
|
|
5
|
+
}
|
|
6
|
+
on(event, listener) {
|
|
7
|
+
if (!this.events.has(event)) {
|
|
8
|
+
this.events.set(event, []);
|
|
9
|
+
}
|
|
10
|
+
this.events.get(event).push(listener);
|
|
11
|
+
return () => this.off(event, listener);
|
|
12
|
+
}
|
|
13
|
+
off(event, listener) {
|
|
14
|
+
const listeners = this.events.get(event);
|
|
15
|
+
if (!listeners)
|
|
16
|
+
return;
|
|
17
|
+
const index = listeners.indexOf(listener);
|
|
18
|
+
if (index !== -1) {
|
|
19
|
+
listeners.splice(index, 1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
emit(event, ...args) {
|
|
23
|
+
const listeners = this.events.get(event);
|
|
24
|
+
if (!listeners)
|
|
25
|
+
return;
|
|
26
|
+
listeners.slice().forEach((listener) => {
|
|
27
|
+
setImmediate(() => {
|
|
28
|
+
try {
|
|
29
|
+
listener(...args);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
Logger.instance.error(`Error in event listener for ${event}:`, error);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
once(event, listener) {
|
|
38
|
+
const onceWrapper = (...args) => {
|
|
39
|
+
this.off(event, onceWrapper);
|
|
40
|
+
listener(...args);
|
|
41
|
+
};
|
|
42
|
+
return this.on(event, onceWrapper);
|
|
43
|
+
}
|
|
44
|
+
removeAllListeners(event) {
|
|
45
|
+
if (event) {
|
|
46
|
+
this.events.delete(event);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
this.events.clear();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { UIComponent } from "./component/ui-components.js";
|
|
2
|
+
import { Logger } from "./logger.js";
|
|
3
|
+
import { Theme } from "./component/style/theme.js";
|
|
4
|
+
import { LifecycleState } from "./runtime/lifecycle-controller.js";
|
|
5
|
+
export interface TabDefinition {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
}
|
|
9
|
+
export interface FloatMenuIconOptions {
|
|
10
|
+
base64: string;
|
|
11
|
+
width?: number;
|
|
12
|
+
height?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface FloatMenuOptions {
|
|
15
|
+
version?: string;
|
|
16
|
+
width?: number;
|
|
17
|
+
height?: number;
|
|
18
|
+
x?: number;
|
|
19
|
+
y?: number;
|
|
20
|
+
icon?: FloatMenuIconOptions;
|
|
21
|
+
permissionDeniedMessage?: string;
|
|
22
|
+
logMaxLines?: number;
|
|
23
|
+
theme?: Theme;
|
|
24
|
+
title?: string;
|
|
25
|
+
tabs?: TabDefinition[];
|
|
26
|
+
activeTab?: string;
|
|
27
|
+
}
|
|
28
|
+
export type MenuPresentation = "icon" | "menu";
|
|
29
|
+
export type FloatMenuSetup = (menu: FloatMenu) => void | Promise<void>;
|
|
30
|
+
export interface FloatMenuSnapshot {
|
|
31
|
+
activeTabId: string;
|
|
32
|
+
presentation: MenuPresentation;
|
|
33
|
+
components: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
export declare class FloatMenu {
|
|
36
|
+
static waitForReady(timeoutMs?: number): Promise<any>;
|
|
37
|
+
static create(options?: FloatMenuOptions, timeoutMs?: number): Promise<FloatMenu>;
|
|
38
|
+
static launch(options?: FloatMenuOptions, setup?: FloatMenuSetup, timeoutMs?: number): Promise<FloatMenu>;
|
|
39
|
+
options: FloatMenuOptions;
|
|
40
|
+
private headerView;
|
|
41
|
+
private headerComponent;
|
|
42
|
+
private iconView;
|
|
43
|
+
private readonly components;
|
|
44
|
+
private readonly componentListeners;
|
|
45
|
+
private readonly lifecycle;
|
|
46
|
+
private pendingComponents;
|
|
47
|
+
private eventEmitter;
|
|
48
|
+
private isIconMode;
|
|
49
|
+
private _context;
|
|
50
|
+
private lastTouchX;
|
|
51
|
+
private lastTouchY;
|
|
52
|
+
private screenWidth;
|
|
53
|
+
private screenHeight;
|
|
54
|
+
private readonly iconWidth;
|
|
55
|
+
private readonly iconHeight;
|
|
56
|
+
private menuWindowParams;
|
|
57
|
+
private iconWindowParams;
|
|
58
|
+
private iconContainerWin;
|
|
59
|
+
private iconAttached;
|
|
60
|
+
private menuAttached;
|
|
61
|
+
private lastPresentation;
|
|
62
|
+
private menuContainerWin;
|
|
63
|
+
private menuPanelView;
|
|
64
|
+
logger: Logger;
|
|
65
|
+
private tabsView;
|
|
66
|
+
get context(): any;
|
|
67
|
+
private _windowManager;
|
|
68
|
+
get windowManager(): any;
|
|
69
|
+
private constructor();
|
|
70
|
+
get state(): LifecycleState;
|
|
71
|
+
private runOnMainThread;
|
|
72
|
+
private reportAsyncFailure;
|
|
73
|
+
private enforceNonFocusableWindow;
|
|
74
|
+
private addDragListener;
|
|
75
|
+
private createMenuContainerWindow;
|
|
76
|
+
private updatePosition;
|
|
77
|
+
private createIconWindow;
|
|
78
|
+
private canDrawOverlays;
|
|
79
|
+
private showToastNow;
|
|
80
|
+
private concealFromHeader;
|
|
81
|
+
private mountInternal;
|
|
82
|
+
private presentInternal;
|
|
83
|
+
mount(): Promise<void>;
|
|
84
|
+
present(mode?: MenuPresentation): Promise<void>;
|
|
85
|
+
conceal(): Promise<void>;
|
|
86
|
+
toggle(): Promise<void>;
|
|
87
|
+
private detachWindows;
|
|
88
|
+
private bindComponentEvents;
|
|
89
|
+
private processPendingComponents;
|
|
90
|
+
private prepareComponentView;
|
|
91
|
+
dispose(): Promise<void>;
|
|
92
|
+
toast(msg: string, duration?: 0 | 1): Promise<void>;
|
|
93
|
+
addComponent(component: UIComponent, tabId?: string): Promise<void>;
|
|
94
|
+
addNestedComponent(ownerId: string, component: UIComponent, container: any): Promise<void>;
|
|
95
|
+
attachNestedComponent(ownerId: string, component: UIComponent, container: any): void;
|
|
96
|
+
removeComponent(id: string): Promise<void>;
|
|
97
|
+
getComponent<T extends UIComponent>(id: string): T | undefined;
|
|
98
|
+
setComponentValue(id: string, value: any): void;
|
|
99
|
+
captureState(): FloatMenuSnapshot;
|
|
100
|
+
restoreState(snapshot: FloatMenuSnapshot): Promise<void>;
|
|
101
|
+
on(event: string, callback: (...args: any[]) => void): () => void;
|
|
102
|
+
off(event: string, callback: (...args: any[]) => void): void;
|
|
103
|
+
}
|