v0.2.2
This commit is contained in:
@@ -105,3 +105,9 @@ Fixed
|
||||
- Fixed Device Simulator Add Device and Remove Device interactions inside embedded webviews.
|
||||
- Fixed Send To Mobile browser preview spending the View button's initial tap as a premature playable click-through while preserving real user-initiated redirects.
|
||||
```
|
||||
|
||||
**v0.2.2**
|
||||
```
|
||||
Added
|
||||
- Added automatic update checking to the VS Code extension and standalone app.
|
||||
```
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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": {
|
||||
@@ -51,6 +51,10 @@
|
||||
{
|
||||
"command": "hplToolbox.openDeviceSimulator",
|
||||
"title": "HPL Toolbox: Open Device Simulator"
|
||||
},
|
||||
{
|
||||
"command": "hplToolbox.checkForUpdates",
|
||||
"title": "HPL Toolbox: Check for Updates"
|
||||
}
|
||||
],
|
||||
"viewsContainers": {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { openPlayworksConverter } from './tools/playworksConverter';
|
||||
import { openMintegralChecker } from './tools/mintegralChecker';
|
||||
import { openChangelog } from './changelogView';
|
||||
import { openDeviceSimulator } from './tools/deviceSimulator';
|
||||
import { checkForUpdates } from './updateChecker';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
@@ -21,8 +22,11 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
vscode.commands.registerCommand('hplToolbox.openMintegralChecker', () => openMintegralChecker(context)),
|
||||
vscode.commands.registerCommand('hplToolbox.openDeviceSimulator', () => openDeviceSimulator(context)),
|
||||
vscode.commands.registerCommand('hplToolbox.openChangelog', () => openChangelog(context)),
|
||||
vscode.commands.registerCommand('hplToolbox.checkForUpdates', () => checkForUpdates(context)),
|
||||
vscode.window.registerWebviewViewProvider('hplToolbox.launcher', new LauncherViewProvider(context))
|
||||
);
|
||||
|
||||
void checkForUpdates(context);
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
|
||||
@@ -62,6 +62,8 @@ export class LauncherViewProvider implements vscode.WebviewViewProvider {
|
||||
vscode.commands.executeCommand(msg.command);
|
||||
} else if (msg?.type === 'openChangelog') {
|
||||
vscode.commands.executeCommand('hplToolbox.openChangelog');
|
||||
} else if (msg?.type === 'checkForUpdates') {
|
||||
vscode.commands.executeCommand('hplToolbox.checkForUpdates');
|
||||
} else if (msg?.type === 'setBetaToolsEnabled' && typeof msg.enabled === 'boolean') {
|
||||
await this.context.globalState.update(BETA_TOOLS_ENABLED_KEY, msg.enabled);
|
||||
view.webview.html = getHtml(version, msg.enabled, tools, this.context.globalState.get<string[]>(TOOL_ORDER_KEY, []));
|
||||
@@ -204,6 +206,7 @@ function getHtml(version: string, betaToolsEnabled: boolean, tools: ToolDefiniti
|
||||
opacity: 0.75;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
justify-self: center;
|
||||
}
|
||||
.changelog-link {
|
||||
justify-self: end;
|
||||
@@ -217,7 +220,7 @@ ${toolButtons}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<button id="betaToggle" class="footer-action" data-enabled="${betaToolsEnabled ? 'true' : 'false'}">${betaToolsEnabled ? 'Hide beta' : 'Show beta'}</button>
|
||||
<span class="footer-version">${version} - JJGC 00784</span>
|
||||
<button id="updateCheck" class="footer-action footer-version" title="Check for updates">${version} - JJGC 00784</button>
|
||||
<button id="changelogLink" class="footer-action changelog-link">Changelog</button>
|
||||
</div>
|
||||
<script>
|
||||
@@ -269,6 +272,9 @@ ${toolButtons}
|
||||
document.getElementById('changelogLink').addEventListener('click', () => {
|
||||
vscode.postMessage({ type: 'openChangelog' });
|
||||
});
|
||||
document.getElementById('updateCheck').addEventListener('click', () => {
|
||||
vscode.postMessage({ type: 'checkForUpdates' });
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
80
src/updateChecker.ts
Normal file
80
src/updateChecker.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
const REMOTE_PACKAGE_JSON_URL = 'https://gitea.hesukastro.com/hesukastro/vsix-hpl-toolbox/raw/branch/main/package.json';
|
||||
|
||||
interface RemotePackageJson {
|
||||
version?: unknown;
|
||||
}
|
||||
|
||||
export async function checkForUpdates(context: vscode.ExtensionContext): Promise<void> {
|
||||
const currentVersion = String(context.extension.packageJSON.version ?? '').trim();
|
||||
|
||||
if (!currentVersion) {
|
||||
vscode.window.showWarningMessage('HPL Toolbox could not read the installed version.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const remoteVersion = await fetchRemoteVersion();
|
||||
const comparison = compareVersions(remoteVersion, currentVersion);
|
||||
|
||||
if (comparison > 0) {
|
||||
const action = await vscode.window.showInformationMessage(
|
||||
`HPL Toolbox update available: ${remoteVersion} (installed: ${currentVersion}).`,
|
||||
'Open Repository'
|
||||
);
|
||||
|
||||
if (action === 'Open Repository') {
|
||||
await vscode.env.openExternal(vscode.Uri.parse('https://gitea.hesukastro.com/hesukastro/vsix-hpl-toolbox'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
vscode.window.showInformationMessage(`HPL Toolbox is up to date (${currentVersion}).`);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
vscode.window.showWarningMessage(`HPL Toolbox update check failed: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchRemoteVersion(): Promise<string> {
|
||||
const response = await fetch(REMOTE_PACKAGE_JSON_URL, {
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`remote package.json returned HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const packageJson = await response.json() as RemotePackageJson;
|
||||
if (typeof packageJson.version !== 'string' || !packageJson.version.trim()) {
|
||||
throw new Error('remote package.json is missing a version');
|
||||
}
|
||||
|
||||
return packageJson.version.trim();
|
||||
}
|
||||
|
||||
function compareVersions(left: string, right: string): number {
|
||||
const leftParts = parseVersion(left);
|
||||
const rightParts = parseVersion(right);
|
||||
const length = Math.max(leftParts.length, rightParts.length);
|
||||
|
||||
for (let index = 0; index < length; index += 1) {
|
||||
const leftPart = leftParts[index] ?? 0;
|
||||
const rightPart = rightParts[index] ?? 0;
|
||||
|
||||
if (leftPart > rightPart) return 1;
|
||||
if (leftPart < rightPart) return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function parseVersion(version: string): number[] {
|
||||
return version
|
||||
.split(/[.-]/)
|
||||
.map(part => Number.parseInt(part, 10))
|
||||
.filter(part => Number.isFinite(part));
|
||||
}
|
||||
@@ -159,6 +159,55 @@ const SharedCSS = `
|
||||
.app-footer-version { opacity: 0.75; text-align: center; white-space: nowrap; }
|
||||
.app-footer-link { justify-self: end; padding: 0; background: transparent; color: #a7a7a7; border: 0; cursor: pointer; font: inherit; opacity: 0.72; }
|
||||
.app-footer-link:hover { background: transparent; opacity: 1; text-decoration: underline; }
|
||||
.app-footer-version-button { justify-self: center; padding: 0; background: transparent; color: #a7a7a7; border: 0; cursor: pointer; font: inherit; opacity: 0.75; text-align: center; white-space: nowrap; }
|
||||
.app-footer-version-button:hover { background: transparent; opacity: 1; text-decoration: underline; }
|
||||
.toast-box {
|
||||
position: fixed;
|
||||
right: 18px;
|
||||
bottom: 18px;
|
||||
z-index: 50;
|
||||
max-width: min(420px, calc(100vw - 36px));
|
||||
padding: 10px 12px;
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: #ddd;
|
||||
background: #252526;
|
||||
border: 1px solid #444;
|
||||
border-left: 3px solid #007fd4;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 10px 28px rgba(0,0,0,0.35);
|
||||
font-size: 12px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.toast-box.is-visible { display: flex; }
|
||||
.toast-box.is-update { border-left-color: #ffd580; }
|
||||
.toast-box.is-current { border-left-color: #89d185; }
|
||||
.toast-box.is-error { border-left-color: #f48771; }
|
||||
.toast-message { min-width: 0; flex: 1; overflow-wrap: anywhere; }
|
||||
.toast-action {
|
||||
flex: 0 0 auto;
|
||||
padding: 4px 8px;
|
||||
background: #0e639c;
|
||||
color: #fff;
|
||||
border: 0;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.toast-close {
|
||||
flex: 0 0 auto;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: transparent;
|
||||
color: #a7a7a7;
|
||||
border: 0;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
.toast-close:hover { background: rgba(255,255,255,0.08); color: #fff; }
|
||||
body.sidebar-collapsed .sidebar { align-items: stretch; }
|
||||
body.sidebar-collapsed .sidebar-title,
|
||||
body.sidebar-collapsed .nav-text,
|
||||
@@ -539,10 +588,15 @@ func Page(activePath, title, body string) string {
|
||||
<div class="content">` + body + `</div>
|
||||
<div class="app-footer">
|
||||
<button id="betaToolsToggle" class="beta-tools-toggle" data-enabled="` + boolAttr(betaToolsEnabled) + `">` + betaButtonLabel + `</button>
|
||||
<span class="app-footer-version">` + AppVersion + ` - JJGC 00784</span>
|
||||
<button id="updateCheck" type="button" class="app-footer-version-button" title="Check for updates">` + AppVersion + ` - JJGC 00784</button>
|
||||
<button type="button" class="app-footer-link" data-path="/changelog">Changelog</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="toastBox" class="toast-box" role="status" aria-live="polite">
|
||||
<span id="toastMessage" class="toast-message"></span>
|
||||
<button id="toastAction" type="button" class="toast-action is-hidden">Open Repository</button>
|
||||
<button id="toastClose" type="button" class="toast-close" aria-label="Close">x</button>
|
||||
</div>
|
||||
<script>
|
||||
if (localStorage.getItem('hplSidebarCollapsed') === 'true') {
|
||||
document.body.classList.add('sidebar-collapsed');
|
||||
@@ -628,6 +682,51 @@ document.getElementById('betaToolsToggle').addEventListener('click', async (even
|
||||
});
|
||||
window.location.reload();
|
||||
});
|
||||
const toastBox = document.getElementById('toastBox');
|
||||
const toastMessage = document.getElementById('toastMessage');
|
||||
const toastAction = document.getElementById('toastAction');
|
||||
const toastClose = document.getElementById('toastClose');
|
||||
let toastTimer = null;
|
||||
let updateRepositoryUrl = '';
|
||||
function showToast(message, status, repositoryUrl) {
|
||||
window.clearTimeout(toastTimer);
|
||||
updateRepositoryUrl = repositoryUrl || '';
|
||||
toastMessage.textContent = message || '';
|
||||
toastAction.classList.toggle('is-hidden', !updateRepositoryUrl);
|
||||
toastBox.className = 'toast-box is-visible is-' + (status || 'current');
|
||||
toastTimer = window.setTimeout(() => {
|
||||
toastBox.classList.remove('is-visible');
|
||||
}, updateRepositoryUrl ? 9000 : 5000);
|
||||
}
|
||||
async function checkForUpdates(showCurrent) {
|
||||
try {
|
||||
const response = await fetch('/api/update/check');
|
||||
const result = await response.json();
|
||||
if (result.status === 'current' && !showCurrent) return;
|
||||
showToast(result.message || 'HPL Toolbox update check finished.', result.status, result.repositoryUrl);
|
||||
} catch (error) {
|
||||
showToast('HPL Toolbox update check failed: ' + (error.message || error), 'error');
|
||||
}
|
||||
}
|
||||
document.getElementById('updateCheck').addEventListener('click', () => {
|
||||
checkForUpdates(true);
|
||||
});
|
||||
toastClose.addEventListener('click', () => {
|
||||
window.clearTimeout(toastTimer);
|
||||
toastBox.classList.remove('is-visible');
|
||||
});
|
||||
toastAction.addEventListener('click', async () => {
|
||||
if (!updateRepositoryUrl) return;
|
||||
await fetch('/api/open', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: updateRepositoryUrl }),
|
||||
});
|
||||
});
|
||||
if (sessionStorage.getItem('hplToolboxUpdateChecked') !== 'true') {
|
||||
sessionStorage.setItem('hplToolboxUpdateChecked', 'true');
|
||||
checkForUpdates(true);
|
||||
}
|
||||
</script>
|
||||
</body></html>`
|
||||
}
|
||||
|
||||
@@ -123,6 +123,7 @@ func buildMux() *http.ServeMux {
|
||||
mux.HandleFunc("POST /api/open", OpenEndpoint)
|
||||
mux.HandleFunc("POST /api/focus", FocusEndpoint)
|
||||
mux.HandleFunc("POST /api/betaTools", BetaToolsEndpoint)
|
||||
mux.HandleFunc("GET /api/update/check", UpdateCheckEndpoint)
|
||||
mux.HandleFunc("POST /api/drop/resolve", DropResolveEndpoint)
|
||||
|
||||
// PLEC
|
||||
|
||||
127
standalone/update_checker.go
Normal file
127
standalone/update_checker.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const remotePackageJSONURL = "https://gitea.hesukastro.com/hesukastro/vsix-hpl-toolbox/raw/branch/main/package.json"
|
||||
const updateRepositoryURL = "https://gitea.hesukastro.com/hesukastro/vsix-hpl-toolbox"
|
||||
|
||||
type updateCheckResponse struct {
|
||||
OK bool `json:"ok"`
|
||||
Status string `json:"status"`
|
||||
CurrentVersion string `json:"currentVersion"`
|
||||
RemoteVersion string `json:"remoteVersion,omitempty"`
|
||||
Message string `json:"message"`
|
||||
RepositoryURL string `json:"repositoryUrl,omitempty"`
|
||||
}
|
||||
|
||||
func UpdateCheckEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
remoteVersion, err := fetchRemoteVersion()
|
||||
if err != nil {
|
||||
writeJSON(w, updateCheckResponse{
|
||||
OK: false,
|
||||
Status: "error",
|
||||
CurrentVersion: AppVersion,
|
||||
Message: "HPL Toolbox update check failed: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
comparison := compareVersions(remoteVersion, AppVersion)
|
||||
if comparison > 0 {
|
||||
writeJSON(w, updateCheckResponse{
|
||||
OK: true,
|
||||
Status: "update",
|
||||
CurrentVersion: AppVersion,
|
||||
RemoteVersion: remoteVersion,
|
||||
Message: fmt.Sprintf("HPL Toolbox update available: %s (installed: %s).", remoteVersion, AppVersion),
|
||||
RepositoryURL: updateRepositoryURL,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, updateCheckResponse{
|
||||
OK: true,
|
||||
Status: "current",
|
||||
CurrentVersion: AppVersion,
|
||||
RemoteVersion: remoteVersion,
|
||||
Message: fmt.Sprintf("HPL Toolbox is up to date (%s).", AppVersion),
|
||||
})
|
||||
}
|
||||
|
||||
func fetchRemoteVersion() (string, error) {
|
||||
client := http.Client{Timeout: 10 * time.Second}
|
||||
req, err := http.NewRequest(http.MethodGet, remotePackageJSONURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return "", fmt.Errorf("remote package.json returned HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var packageJSON struct {
|
||||
Version string `json:"version"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&packageJSON); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if strings.TrimSpace(packageJSON.Version) == "" {
|
||||
return "", fmt.Errorf("remote package.json is missing a version")
|
||||
}
|
||||
|
||||
return strings.TrimSpace(packageJSON.Version), nil
|
||||
}
|
||||
|
||||
func compareVersions(left, right string) int {
|
||||
leftParts := parseVersionParts(left)
|
||||
rightParts := parseVersionParts(right)
|
||||
length := len(leftParts)
|
||||
if len(rightParts) > length {
|
||||
length = len(rightParts)
|
||||
}
|
||||
for i := 0; i < length; i++ {
|
||||
leftPart := 0
|
||||
rightPart := 0
|
||||
if i < len(leftParts) {
|
||||
leftPart = leftParts[i]
|
||||
}
|
||||
if i < len(rightParts) {
|
||||
rightPart = rightParts[i]
|
||||
}
|
||||
if leftPart > rightPart {
|
||||
return 1
|
||||
}
|
||||
if leftPart < rightPart {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func parseVersionParts(version string) []int {
|
||||
fields := strings.FieldsFunc(version, func(r rune) bool {
|
||||
return r == '.' || r == '-'
|
||||
})
|
||||
parts := make([]int, 0, len(fields))
|
||||
for _, field := range fields {
|
||||
part, err := strconv.Atoi(field)
|
||||
if err == nil {
|
||||
parts = append(parts, part)
|
||||
}
|
||||
}
|
||||
return parts
|
||||
}
|
||||
Reference in New Issue
Block a user