diff --git a/aiAssets/wrd_mip_grhpl_simplegameplaysounddark_01_real_nougc_noseason_en_full_na_al.html b/aiAssets/wrd_mip_grhpl_simplegameplaysounddark_01_real_nougc_noseason_en_full_na_al.html new file mode 100644 index 0000000..6022461 --- /dev/null +++ b/aiAssets/wrd_mip_grhpl_simplegameplaysounddark_01_real_nougc_noseason_en_full_na_al.html @@ -0,0 +1,95 @@ + + + + + + Wordle + + + + + + + + + + \ No newline at end of file diff --git a/dist/hpl-toolbox-0.2.1.exe b/dist/hpl-toolbox-0.2.1.exe index 8f793b9..2275f0a 100644 Binary files a/dist/hpl-toolbox-0.2.1.exe and b/dist/hpl-toolbox-0.2.1.exe differ diff --git a/dist/hpl-toolbox-0.2.1.vsix b/dist/hpl-toolbox-0.2.1.vsix index 528291d..50da636 100644 Binary files a/dist/hpl-toolbox-0.2.1.vsix and b/dist/hpl-toolbox-0.2.1.vsix differ diff --git a/package.json b/package.json index 32de09d..95c6127 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "hpl-toolbox", "displayName": "HPL Toolbox", "description": "Bundled tools: PLEC Upload, AppLovin Playable Preview, Base64 Scanner, MRAID Checker. Local HTML Host.", - "version": "0.2.1", + "version": "0.2.2", "publisher": "hesukastro", "license": "UNLICENSED", "repository": { @@ -47,6 +47,10 @@ { "command": "hplToolbox.openMintegralChecker", "title": "HPL Toolbox: Open Mintegral Checker" + }, + { + "command": "hplToolbox.openDeviceSimulator", + "title": "HPL Toolbox: Open Device Simulator" } ], "viewsContainers": { diff --git a/src/extension.ts b/src/extension.ts index 5d9682d..aa47ed6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -8,6 +8,7 @@ import { openSendToMobile } from './tools/sendToMobile'; import { openPlayworksConverter } from './tools/playworksConverter'; import { openMintegralChecker } from './tools/mintegralChecker'; import { openChangelog } from './changelogView'; +import { openDeviceSimulator } from './tools/deviceSimulator'; export function activate(context: vscode.ExtensionContext) { context.subscriptions.push( @@ -18,6 +19,7 @@ export function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('hplToolbox.openSendToMobile', () => openSendToMobile(context)), vscode.commands.registerCommand('hplToolbox.openPlayworksConverter', () => openPlayworksConverter(context)), vscode.commands.registerCommand('hplToolbox.openMintegralChecker', () => openMintegralChecker(context)), + vscode.commands.registerCommand('hplToolbox.openDeviceSimulator', () => openDeviceSimulator(context)), vscode.commands.registerCommand('hplToolbox.openChangelog', () => openChangelog(context)), vscode.window.registerWebviewViewProvider('hplToolbox.launcher', new LauncherViewProvider(context)) ); diff --git a/src/tools/deviceSimulator.ts b/src/tools/deviceSimulator.ts new file mode 100644 index 0000000..e60316f --- /dev/null +++ b/src/tools/deviceSimulator.ts @@ -0,0 +1,986 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; +import { getToolWebviewStyles } from './shared'; + +const store: { panel: vscode.WebviewPanel | null } = { panel: null }; + +interface Device { + id: string; + name: string; + sw: number; sh: number; + scr: number; + notch: 'none' | 'notch' | 'island' | 'punch'; + nw?: number; nh?: number; nr?: number; ps?: number; + sat: number; sab: number; +} + +const DEVICES: Device[] = [ + // ── iPhone 17 ────────────────────────────────────────────────────────────── + { id: 'iphone-17-pro-max', name: 'iPhone 17 Pro Max', sw: 440, sh: 956, scr: 44, notch: 'island', nw: 126, nh: 37, nr: 20, sat: 62, sab: 34 }, + { id: 'iphone-17-pro', name: 'iPhone 17 Pro', sw: 402, sh: 874, scr: 42, notch: 'island', nw: 126, nh: 37, nr: 20, sat: 62, sab: 34 }, + { id: 'iphone-air', name: 'iPhone Air', sw: 420, sh: 912, scr: 42, notch: 'island', nw: 126, nh: 37, nr: 20, sat: 62, sab: 34 }, + { id: 'iphone-17', name: 'iPhone 17', sw: 402, sh: 874, scr: 42, notch: 'island', nw: 126, nh: 37, nr: 20, sat: 62, sab: 34 }, + // ── iPhone 16 ────────────────────────────────────────────────────────────── + { id: 'iphone-16-pro-max', name: 'iPhone 16 Pro Max', sw: 440, sh: 956, scr: 44, notch: 'island', nw: 126, nh: 37, nr: 20, sat: 62, sab: 34 }, + { id: 'iphone-16-pro', name: 'iPhone 16 Pro', sw: 402, sh: 874, scr: 42, notch: 'island', nw: 126, nh: 37, nr: 20, sat: 62, sab: 34 }, + { id: 'iphone-16-plus', name: 'iPhone 16 Plus', sw: 430, sh: 932, scr: 42, notch: 'island', nw: 120, nh: 36, nr: 18, sat: 59, sab: 34 }, + { id: 'iphone-16', name: 'iPhone 16', sw: 390, sh: 844, scr: 40, notch: 'island', nw: 120, nh: 36, nr: 18, sat: 59, sab: 34 }, + // ── iPhone 15 ────────────────────────────────────────────────────────────── + { id: 'iphone-15-pro-max', name: 'iPhone 15 Pro Max', sw: 430, sh: 932, scr: 42, notch: 'island', nw: 120, nh: 36, nr: 18, sat: 59, sab: 34 }, + { id: 'iphone-15-pro', name: 'iPhone 15 Pro', sw: 393, sh: 852, scr: 40, notch: 'island', nw: 120, nh: 36, nr: 18, sat: 59, sab: 34 }, + { id: 'iphone-15-plus', name: 'iPhone 15 Plus', sw: 430, sh: 932, scr: 42, notch: 'island', nw: 120, nh: 36, nr: 18, sat: 59, sab: 34 }, + { id: 'iphone-15', name: 'iPhone 15', sw: 390, sh: 844, scr: 38, notch: 'island', nw: 120, nh: 36, nr: 18, sat: 59, sab: 34 }, + // ── iPhone 14 ────────────────────────────────────────────────────────────── + { id: 'iphone-14-pro-max', name: 'iPhone 14 Pro Max', sw: 430, sh: 932, scr: 42, notch: 'island', nw: 120, nh: 36, nr: 18, sat: 59, sab: 34 }, + { id: 'iphone-14-pro', name: 'iPhone 14 Pro', sw: 393, sh: 852, scr: 40, notch: 'island', nw: 120, nh: 36, nr: 18, sat: 59, sab: 34 }, + { id: 'iphone-14-plus', name: 'iPhone 14 Plus', sw: 428, sh: 926, scr: 40, notch: 'notch', nw: 130, nh: 34, sat: 47, sab: 34 }, + { id: 'iphone-14', name: 'iPhone 14', sw: 390, sh: 844, scr: 38, notch: 'notch', nw: 130, nh: 34, sat: 47, sab: 34 }, + // ── Other ────────────────────────────────────────────────────────────────── + { id: 'iphone-se3', name: 'iPhone SE (3rd gen)', sw: 375, sh: 667, scr: 4, notch: 'none', sat: 20, sab: 0 }, + { id: 'pixel-8', name: 'Google Pixel 8', sw: 411, sh: 914, scr: 22, notch: 'punch', ps: 14, sat: 28, sab: 24 }, + { id: 'galaxy-s24', name: 'Samsung Galaxy S24', sw: 360, sh: 780, scr: 20, notch: 'punch', ps: 12, sat: 28, sab: 24 }, +]; + +export function openDeviceSimulator(_context: vscode.ExtensionContext) { + if (store.panel) { + store.panel.reveal(); + return; + } + + const panel = vscode.window.createWebviewPanel( + 'hplToolbox.deviceSimulator', + 'Device Simulator', + vscode.ViewColumn.Active, + { enableScripts: true, retainContextWhenHidden: true } + ); + + store.panel = panel; + panel.onDidDispose(() => { store.panel = null; }); + panel.webview.html = getHtml(DEVICES); + + let lastFilePath = ''; + + panel.webview.onDidReceiveMessage(async (msg) => { + if (msg.type === 'pickFile') { + const picked = await vscode.window.showOpenDialog({ + canSelectFiles: true, + canSelectFolders: false, + canSelectMany: false, + openLabel: 'Select File', + filters: { 'HTML': ['html', 'htm'] }, + }); + if (picked?.[0]) { + lastFilePath = picked[0].fsPath; + sendFile(panel, lastFilePath); + } + } else if (msg.type === 'reload') { + if (lastFilePath) sendFile(panel, lastFilePath); + } else if (msg.type === 'exportDevices') { + const target = await vscode.window.showSaveDialog({ + defaultUri: vscode.Uri.file('hpl-device-simulator-devices.json'), + filters: { 'JSON': ['json'] }, + saveLabel: 'Export Devices', + }); + if (target) { + await fs.promises.writeFile(target.fsPath, String(msg.content || ''), 'utf8'); + } + } + }); +} + +function sendFile(panel: vscode.WebviewPanel, filePath: string) { + try { + const content = fs.readFileSync(filePath, 'utf8'); + panel.webview.postMessage({ type: 'fileLoaded', content, name: path.basename(filePath) }); + } catch { + vscode.window.showErrorMessage('Device Simulator: could not read ' + path.basename(filePath)); + } +} + +function getHtml(devices: Device[]): string { + const devicesJson = JSON.stringify(devices); + return ` + + + + + + + + +
+
+
+

Device Simulator

+

Preview HTML playables inside accurate mobile device frames with notch and Dynamic Island overlays.

+
+
+
+

Input

+ +
+
+
Playable HTML
+
+ +
+
(no file selected)
+
+
+
+ + +
+ + +
+
+

Playable

+
+ + +
+
+ + + + +
+
+
+
+
+
+ +
+
+
+ + +`; +} diff --git a/standalone/device_simulator.go b/standalone/device_simulator.go new file mode 100644 index 0000000..ce24227 --- /dev/null +++ b/standalone/device_simulator.go @@ -0,0 +1,884 @@ +package main + +import ( + "encoding/json" + "net/http" + "os" + "path/filepath" +) + +type deviceSimulatorDevice struct { + ID string `json:"id"` + Name string `json:"name"` + SW int `json:"sw"` + SH int `json:"sh"` + SCR int `json:"scr"` + Notch string `json:"notch"` + NW int `json:"nw,omitempty"` + NH int `json:"nh,omitempty"` + NR int `json:"nr,omitempty"` + PS int `json:"ps,omitempty"` + SAT int `json:"sat"` + SAB int `json:"sab"` +} + +var deviceSimulatorDevices = []deviceSimulatorDevice{ + {ID: "iphone-17-pro-max", Name: "iPhone 17 Pro Max", SW: 440, SH: 956, SCR: 44, Notch: "island", NW: 126, NH: 37, NR: 20, SAT: 62, SAB: 34}, + {ID: "iphone-17-pro", Name: "iPhone 17 Pro", SW: 402, SH: 874, SCR: 42, Notch: "island", NW: 126, NH: 37, NR: 20, SAT: 62, SAB: 34}, + {ID: "iphone-air", Name: "iPhone Air", SW: 420, SH: 912, SCR: 42, Notch: "island", NW: 126, NH: 37, NR: 20, SAT: 62, SAB: 34}, + {ID: "iphone-17", Name: "iPhone 17", SW: 402, SH: 874, SCR: 42, Notch: "island", NW: 126, NH: 37, NR: 20, SAT: 62, SAB: 34}, + {ID: "iphone-16-pro-max", Name: "iPhone 16 Pro Max", SW: 440, SH: 956, SCR: 44, Notch: "island", NW: 126, NH: 37, NR: 20, SAT: 62, SAB: 34}, + {ID: "iphone-16-pro", Name: "iPhone 16 Pro", SW: 402, SH: 874, SCR: 42, Notch: "island", NW: 126, NH: 37, NR: 20, SAT: 62, SAB: 34}, + {ID: "iphone-16-plus", Name: "iPhone 16 Plus", SW: 430, SH: 932, SCR: 42, Notch: "island", NW: 120, NH: 36, NR: 18, SAT: 59, SAB: 34}, + {ID: "iphone-16", Name: "iPhone 16", SW: 390, SH: 844, SCR: 40, Notch: "island", NW: 120, NH: 36, NR: 18, SAT: 59, SAB: 34}, + {ID: "iphone-15-pro-max", Name: "iPhone 15 Pro Max", SW: 430, SH: 932, SCR: 42, Notch: "island", NW: 120, NH: 36, NR: 18, SAT: 59, SAB: 34}, + {ID: "iphone-15-pro", Name: "iPhone 15 Pro", SW: 393, SH: 852, SCR: 40, Notch: "island", NW: 120, NH: 36, NR: 18, SAT: 59, SAB: 34}, + {ID: "iphone-15-plus", Name: "iPhone 15 Plus", SW: 430, SH: 932, SCR: 42, Notch: "island", NW: 120, NH: 36, NR: 18, SAT: 59, SAB: 34}, + {ID: "iphone-15", Name: "iPhone 15", SW: 390, SH: 844, SCR: 38, Notch: "island", NW: 120, NH: 36, NR: 18, SAT: 59, SAB: 34}, + {ID: "iphone-14-pro-max", Name: "iPhone 14 Pro Max", SW: 430, SH: 932, SCR: 42, Notch: "island", NW: 120, NH: 36, NR: 18, SAT: 59, SAB: 34}, + {ID: "iphone-14-pro", Name: "iPhone 14 Pro", SW: 393, SH: 852, SCR: 40, Notch: "island", NW: 120, NH: 36, NR: 18, SAT: 59, SAB: 34}, + {ID: "iphone-14-plus", Name: "iPhone 14 Plus", SW: 428, SH: 926, SCR: 40, Notch: "notch", NW: 130, NH: 34, SAT: 47, SAB: 34}, + {ID: "iphone-14", Name: "iPhone 14", SW: 390, SH: 844, SCR: 38, Notch: "notch", NW: 130, NH: 34, SAT: 47, SAB: 34}, + {ID: "iphone-se3", Name: "iPhone SE (3rd gen)", SW: 375, SH: 667, SCR: 4, Notch: "none", SAT: 20, SAB: 0}, + {ID: "pixel-8", Name: "Google Pixel 8", SW: 411, SH: 914, SCR: 22, Notch: "punch", PS: 14, SAT: 28, SAB: 24}, + {ID: "galaxy-s24", Name: "Samsung Galaxy S24", SW: 360, SH: 780, SCR: 20, Notch: "punch", PS: 12, SAT: 28, SAB: 24}, +} + +func DeviceSimulatorPage(w http.ResponseWriter, r *http.Request) { + devicesJSON, _ := json.Marshal(deviceSimulatorDevices) + body := ` +
+

Device Simulator

+

Preview HTML playables inside mobile device frames with notch and Dynamic Island overlays.

+
+ +
+
+

Input

+ +
+
+
Playable HTML
+
+
+ +
+
or drop an HTML file here
+
+
(no file selected)
+
+
+
+ + + +
+
+

Playable

+
+ + +
+
+ + + + +
+
+
+
+ + + +` + w.Header().Set("Content-Type", "text/html; charset=utf-8") + _, _ = w.Write([]byte(Page("/device-simulator", "Device Simulator", body))) +} + +func DeviceSimulatorPick(w http.ResponseWriter, r *http.Request) { + paths := PickFiles("Select HTML file", []FileFilter{{Name: "HTML", Extensions: []string{"html", "htm"}}}, false, "") + path := "" + if len(paths) > 0 { + path = paths[0] + } + writeJSON(w, map[string]any{"path": path}) +} + +func DeviceSimulatorLoad(w http.ResponseWriter, r *http.Request) { + var req struct { + Path string `json:"path"` + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + writeJSON(w, map[string]any{"error": err.Error()}) + return + } + if req.Path == "" || !isHTMLPath(req.Path) { + writeJSON(w, map[string]any{"error": "Pick an HTML file."}) + return + } + info, err := os.Stat(req.Path) + if err != nil { + writeJSON(w, map[string]any{"error": err.Error()}) + return + } + if info.IsDir() { + writeJSON(w, map[string]any{"error": "Expected an HTML file, got a folder."}) + return + } + data, err := os.ReadFile(req.Path) + if err != nil { + writeJSON(w, map[string]any{"error": err.Error()}) + return + } + writeJSON(w, map[string]any{ + "name": filepath.Base(req.Path), + "path": req.Path, + "content": string(data), + }) +} diff --git a/standalone/layout.go b/standalone/layout.go index 8069d53..09eef91 100644 --- a/standalone/layout.go +++ b/standalone/layout.go @@ -433,6 +433,7 @@ var navItems = []navItem{ {Path: "/mraid", Label: "MRAID Checker", Description: "Check MRAID requirements and best practices"}, {Path: "/mintegral", Label: "Mintegral Checker", Description: "Validate Mintegral playable ZIPs against PlayTurbo checks"}, {Path: "/playworks", Label: "Playworks Converter", Description: "Convert Playworks HTML to per-network variants", Beta: true}, + {Path: "/device-simulator", Label: "Device Simulator", Description: "Preview HTML playables inside mobile device frames"}, } func visibleNavItems(betaToolsEnabled bool) []navItem { @@ -476,6 +477,8 @@ func navInitials(label string) string { return "APP" case "Mintegral Checker": return "MiC" + case "Device Simulator": + return "SIM" } words := strings.Fields(label) if len(words) == 0 { diff --git a/standalone/main.go b/standalone/main.go index 937edef..8ad9b09 100644 --- a/standalone/main.go +++ b/standalone/main.go @@ -114,6 +114,7 @@ func buildMux() *http.ServeMux { mux.HandleFunc("GET /mintegral", MintegralPage) mux.HandleFunc("GET /mobile", MobilePage) mux.HandleFunc("GET /playworks", PlayworksPage) + mux.HandleFunc("GET /device-simulator", DeviceSimulatorPage) mux.HandleFunc("GET /changelog", ChangelogPage) mux.HandleFunc("GET /assets/qrcode.min.js", MobileQrScript) @@ -165,6 +166,10 @@ func buildMux() *http.ServeMux { mux.HandleFunc("POST /api/playworks/pickOutput", PlayworksPickOutput) mux.HandleFunc("POST /api/playworks/convert", PlayworksConvert) + // Device Simulator + mux.HandleFunc("POST /api/device-simulator/pick", DeviceSimulatorPick) + mux.HandleFunc("POST /api/device-simulator/load", DeviceSimulatorLoad) + return mux } diff --git a/tools.json b/tools.json index 76da13b..e161fca 100644 --- a/tools.json +++ b/tools.json @@ -54,5 +54,13 @@ "command": "hplToolbox.openMintegralChecker", "path": "/mintegral", "beta": false + }, + { + "id": "device-simulator", + "title": "Device Simulator", + "description": "Preview HTML playables inside accurate mobile device frames with notch and Dynamic Island overlays", + "command": "hplToolbox.openDeviceSimulator", + "path": "/device-simulator", + "beta": false } ]