Tee Widget Launcher
A persistent launcher to manage and execute your widgets.

Code Snippet

1// ==============================================
2// 🚀 TEE WIDGET LAUNCHER
3// ==============================================
4
5const logoBase64 = "data:image/png;base64,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";
6
7// --- STATE & STORAGE ---
8let apps = await storage.get("launcher_apps");
9if (!apps) {
10    apps = [{ name: "Page Chatbot", id: "default-page-chatbot" }];
11    await storage.set("launcher_apps", apps);
12}
13let viewMode = "minimized"; // Can be "main", "add", "minimized", or "delete"
14let deletingIndex = -1;
15
16async function saveApps() {
17    await storage.set("launcher_apps", apps);
18}
19
20// --- UI VIEWS ---
21/** @returns {WidgetComponent[]} */
22function minimizedView() {
23    return [
24        { type: "image", id: "openBtn", src: logoBase64, width: "45px", height: "45px" }
25    ];
26}
27
28/** @returns {WidgetComponent[]} */
29function mainView() {
30    /** @type {WidgetComponent[]} */
31    const appCards = apps.length > 0 ? apps.map((app, idx) => ({
32        type: "row", bg: "card2", padding: "md", rounded: true, align: "center", justify: "between",
33        components: [
34            {
35                type: "column", gap: "sm",
36                components: [
37                    { type: "text", text: app.name, size: "md", weight: "bold" },
38                    { type: "text", text: app.id.substring(0, 20) + "…", size: "xs", color: "muted" }
39                ]
40            },
41            {
42                type: "row", gap: "sm", align: "center",justify:"end",
43                components: [
44                    { type: "button", icon: "Power", id: `launch_${idx}`, variant: "ghost" },
45                    { type: "button", icon: "Trash2", id: `delete_${idx}`, variant: "danger" }
46                ]
47            }
48        ]
49    })) : [
50        {
51            type: "column", align: "center", padding: "lg", gap: "sm",
52            components: [
53                { type: "image", src: logoBase64, width: "32px", height: "32px", rounded: true },
54                { type: "text", text: "No widgets yet", size: "md", weight: "medium", color: "muted", align: "center" },
55                { type: "text", text: "Add your first widget to get started.", size: "xs", color: "muted", align: "center" }
56            ]
57        }
58    ];
59
60    return [
61        {
62            type: "row", justify: "between", align: "center",
63            components: [
64                {
65                    type: "row", gap: "sm", align: "center",
66                    components: [
67                        { type: "image", src: logoBase64, width: "24px", height: "24px", rounded: true },
68                    ]
69                },
70                { type: "button", icon: "Minimize", id: "closeBtn", variant: "ghost" }
71            ]
72        },
73        { type: "divider" },
74        { type: "column", gap: "sm", scroll: true, maxHeight: "320px", components: appCards },
75        { type: "divider" },
76        { type: "button", text: "Add Widget", id: "addBtn", variant: "primary" }
77    ];
78}
79
80/** @returns {WidgetComponent[]} */
81function addView() {
82    return [
83        {
84            type: "row", align: "center", justify: "between",
85            components: [
86                { type: "image", src: logoBase64, width: "24px", height: "24px", rounded: true },
87                { type: "text", text: "Add Widget", size: "lg", weight: "bold" }
88            ]
89        },
90        { type: "divider" },
91        {
92            type: "column", gap: "md",
93            components: [
94                {
95                    type: "column", gap: "sm",
96                    components: [
97                        { type: "text", text: "Widget Name", size: "sm", weight: "medium", color: "muted" },
98                        { type: "input", id: "appName", placeholder: "e.g. Gmail Client" }
99                    ]
100                },
101                {
102                    type: "column", gap: "md",
103                    components: [
104                        { type: "text", text: "Action ID", size: "sm", weight: "medium", color: "muted" },
105                        { type: "input", id: "appId", placeholder: "Paste action ID here..." }
106                    ]
107                }
108            ]
109        },
110        { type: "divider" },
111        {
112            type: "row", gap: "sm", justify: "end",
113            components: [
114                { type: "button", text: "Cancel", id: "cancelAdd", variant: "ghost" },
115                { type: "button", text: "Save Widget", id: "saveApp", variant: "primary" }
116            ]
117        }
118    ];
119}
120
121/** @returns {WidgetComponent[]} */
122function deleteView() {
123    const app = apps[deletingIndex];
124    return [
125        {
126            type: "column", align: "center", padding: "lg", gap: "md",
127            components: [
128                { type: "icon", icon: "AlertTriangle", size: "lg", color: "error" },
129                { type: "text", text: "Delete Widget?", size: "lg", weight: "bold", align: "center" },
130                { type: "text", text: `Are you sure you want to remove "${app?.name}" from your launcher?`, size: "md", color: "muted", align: "center" }
131            ]
132        },
133        { type: "divider" },
134        {
135            type: "row", gap: "sm", justify: "end",
136            components: [
137                { type: "button", text: "Cancel", id: "cancelDelete", variant: "ghost" },
138                { type: "button", text: "Delete", id: "confirmDelete", variant: "danger" }
139            ]
140        }
141    ];
142}
143
144function render() {
145    if (viewMode === "minimized") {
146        widget.show(minimizedView(), "bottom-right", { hideCloseButton: true, transparent: true });
147    } else if (viewMode === "add") {
148        widget.show(addView(), "bottom-right", { hideCloseButton: true });
149    } else if (viewMode === "delete") {
150        widget.show(deleteView(), "bottom-right", { hideCloseButton: true });
151    } else {
152        widget.show(mainView(), "bottom-right", { hideCloseButton: true });
153    }
154}
155
156// --- EVENT ROUTING ---
157function bindAppListeners() {
158    apps.forEach((app, idx) => {
159        widget.on(`launch_${idx}`, "click", async () => {
160            widget.hide();
161            await actions.run(app.id);
162            render();
163            bindAppListeners();
164        });
165
166        widget.on(`delete_${idx}`, "click", async () => {
167            deletingIndex = idx;
168            viewMode = "delete";
169            render();
170        });
171    });
172}
173
174// Add/Cancel views
175widget.on("addBtn", "click", () => { viewMode = "add"; render(); });
176widget.on("cancelAdd", "click", () => { viewMode = "main"; render(); });
177
178// Delete Confirm views
179widget.on("cancelDelete", "click", () => { viewMode = "main"; render(); });
180widget.on("confirmDelete", "click", async () => {
181    if (deletingIndex >= 0 && deletingIndex < apps.length) {
182        apps.splice(deletingIndex, 1);
183        await saveApps();
184    }
185    viewMode = "main";
186    render();
187    bindAppListeners();
188});
189
190// Minimizing and Re-opening
191widget.on("closeBtn", "click", () => { viewMode = "minimized"; render(); });
192widget.on("openBtn", "click", () => { viewMode = "main"; render(); });
193
194widget.on("saveApp", "click", async (data) => {
195    const name = data.appName?.trim();
196    const id = data.appId?.trim();
197    if (name && id) {
198        apps.push({ name, id });
199        await saveApps();
200        viewMode = "main";
201        render();
202        bindAppListeners();
203    } else {
204        notify.error("Please enter both a Name and an Action ID.");
205    }
206});
207
208// --- BOOT ---
209render();
210bindAppListeners();
211await widget.waitForEvent("FOREVER_EVENT", "click");
Submitted on 7/6/2026