0.1.6 updates and add changelog
This commit is contained in:
26
CHANGELOG.md
Normal file
26
CHANGELOG.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# CHANGELOG
|
||||||
|
## v0.1.2:
|
||||||
|
- Added local hosting for html files
|
||||||
|
- Created standalone version
|
||||||
|
|
||||||
|
## v0.1.3
|
||||||
|
- Updated Applovin Demo Upload
|
||||||
|
- Changed file selection workflow
|
||||||
|
- Added "Regenerate QRs" if in case they don't render
|
||||||
|
- Added "Save QR" and "Save All QRs"
|
||||||
|
|
||||||
|
## v0.1.5
|
||||||
|
- Better file selection for PLEC Uoload
|
||||||
|
- Multi file selection
|
||||||
|
- Auto detects iteration name (it tries its best)
|
||||||
|
|
||||||
|
Standalone .exe
|
||||||
|
- Move temp files into user temp folder
|
||||||
|
|
||||||
|
## v0.1.6
|
||||||
|
- Simplify UI
|
||||||
|
- Added beta tools (needs further testing)
|
||||||
|
- Playworks Converter
|
||||||
|
- Upload UnityAds HTML from Playworks to add injections for the other networks
|
||||||
|
- MRAID Checker
|
||||||
|
- Based on Official MRAID Document, scans the HTML files if they adhere to the documentation requirements and best practices
|
||||||
30
README.md
Normal file
30
README.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# HPL Toolbox
|
||||||
|
|
||||||
|
Bundled VS Code extension and standalone tools for playable ad workflows.
|
||||||
|
|
||||||
|
# CHANGELOG
|
||||||
|
## v0.1.2:
|
||||||
|
- Added local hosting for html files
|
||||||
|
- Created standalone version
|
||||||
|
|
||||||
|
## v0.1.3
|
||||||
|
- Updated Applovin Demo Upload
|
||||||
|
- Changed file selection workflow
|
||||||
|
- Added "Regenerate QRs" if in case they don't render
|
||||||
|
- Added "Save QR" and "Save All QRs"
|
||||||
|
|
||||||
|
## v0.1.5
|
||||||
|
- Better file selection for PLEC Uoload
|
||||||
|
- Multi file selection
|
||||||
|
- Auto detects iteration name (it tries its best)
|
||||||
|
|
||||||
|
Standalone .exe
|
||||||
|
- Move temp files into user temp folder
|
||||||
|
|
||||||
|
## v0.1.6
|
||||||
|
- Simplify UI
|
||||||
|
- Added beta tools (needs further testing)
|
||||||
|
- Playworks Converter
|
||||||
|
- Upload UnityAds HTML from Playworks to add injections for the other networks
|
||||||
|
- MRAID Checker
|
||||||
|
- Based on Official MRAID Document, scans the HTML files if they adhere to the documentation requirements and best practices
|
||||||
BIN
dist/hpl-toolbox-0.1.6.exe
vendored
BIN
dist/hpl-toolbox-0.1.6.exe
vendored
Binary file not shown.
BIN
dist/hpl-toolbox-0.1.6.vsix
vendored
BIN
dist/hpl-toolbox-0.1.6.vsix
vendored
Binary file not shown.
@@ -123,7 +123,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile": "tsc -p ./",
|
"sync-readme": "node scripts/sync-readme.js",
|
||||||
|
"compile": "npm run sync-readme && tsc -p ./",
|
||||||
"watch": "tsc -watch -p ./",
|
"watch": "tsc -watch -p ./",
|
||||||
"vscode:prepublish": "npm run compile"
|
"vscode:prepublish": "npm run compile"
|
||||||
},
|
},
|
||||||
|
|||||||
20
scripts/sync-readme.js
Normal file
20
scripts/sync-readme.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const root = path.resolve(__dirname, '..');
|
||||||
|
const changelogPath = path.join(root, 'CHANGELOG.md');
|
||||||
|
const readmePath = path.join(root, 'README.md');
|
||||||
|
|
||||||
|
let changelog = '# CHANGELOG\n\nNo changelog entries yet.';
|
||||||
|
if (fs.existsSync(changelogPath)) {
|
||||||
|
changelog = fs.readFileSync(changelogPath, 'utf8').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const readme = `# HPL Toolbox
|
||||||
|
|
||||||
|
Bundled VS Code extension and standalone tools for playable ad workflows.
|
||||||
|
|
||||||
|
${changelog}
|
||||||
|
`;
|
||||||
|
|
||||||
|
fs.writeFileSync(readmePath, readme.replace(/\r?\n/g, '\n'), 'utf8');
|
||||||
122
src/changelogView.ts
Normal file
122
src/changelogView.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
export function openChangelog(context: vscode.ExtensionContext) {
|
||||||
|
const panel = vscode.window.createWebviewPanel(
|
||||||
|
'hplToolbox.changelog',
|
||||||
|
'HPL Toolbox Changelog',
|
||||||
|
vscode.ViewColumn.One,
|
||||||
|
{ enableScripts: false }
|
||||||
|
);
|
||||||
|
const changelogPath = path.join(context.extensionPath, 'CHANGELOG.md');
|
||||||
|
let markdown = '# Changelog\n\nCHANGELOG.md was not found.';
|
||||||
|
try {
|
||||||
|
markdown = fs.readFileSync(changelogPath, 'utf8');
|
||||||
|
} catch {
|
||||||
|
// Keep the fallback text readable in packaged or development installs.
|
||||||
|
}
|
||||||
|
panel.webview.html = getHtml(renderMarkdown(markdown));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHtml(content: string): string {
|
||||||
|
return `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 18px;
|
||||||
|
font-family: var(--vscode-font-family);
|
||||||
|
font-size: var(--vscode-font-size);
|
||||||
|
background: var(--vscode-editor-background);
|
||||||
|
color: var(--vscode-foreground);
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
max-width: 840px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin: 0 0 14px;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin: 18px 0 8px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid var(--vscode-panel-border);
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
margin: 6px 0 12px;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
margin: 4px 0;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 8px 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>${content}</main>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMarkdown(markdown: string): string {
|
||||||
|
const lines = markdown.replace(/\r\n/g, '\n').split('\n');
|
||||||
|
const html: string[] = [];
|
||||||
|
let inList = false;
|
||||||
|
|
||||||
|
const closeList = () => {
|
||||||
|
if (inList) {
|
||||||
|
html.push('</ul>');
|
||||||
|
inList = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const rawLine of lines) {
|
||||||
|
const line = rawLine.trim();
|
||||||
|
if (!line) {
|
||||||
|
closeList();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const heading = line.match(/^(#{1,3})\s+(.+)$/);
|
||||||
|
if (heading) {
|
||||||
|
closeList();
|
||||||
|
const level = Math.min(heading[1].length, 2);
|
||||||
|
html.push(`<h${level}>${escapeHtml(heading[2])}</h${level}>`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const listItem = line.match(/^-\s+(.+)$/);
|
||||||
|
if (listItem) {
|
||||||
|
if (!inList) {
|
||||||
|
html.push('<ul>');
|
||||||
|
inList = true;
|
||||||
|
}
|
||||||
|
html.push(`<li>${escapeHtml(listItem[1])}</li>`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
closeList();
|
||||||
|
html.push(`<p>${escapeHtml(line)}</p>`);
|
||||||
|
}
|
||||||
|
closeList();
|
||||||
|
return html.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(value: string): string {
|
||||||
|
return value.replace(/[&<>"']/g, ch => ({
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
"'": ''',
|
||||||
|
}[ch] ?? ch));
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import { openBase64Scanner } from './tools/base64Scanner';
|
|||||||
import { openMraidChecker } from './tools/mraidChecker';
|
import { openMraidChecker } from './tools/mraidChecker';
|
||||||
import { openSendToMobile } from './tools/sendToMobile';
|
import { openSendToMobile } from './tools/sendToMobile';
|
||||||
import { openPlayworksConverter } from './tools/playworksConverter';
|
import { openPlayworksConverter } from './tools/playworksConverter';
|
||||||
|
import { openChangelog } from './changelogView';
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
@@ -15,6 +16,7 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
vscode.commands.registerCommand('hplToolbox.openMraidChecker', () => openMraidChecker(context)),
|
vscode.commands.registerCommand('hplToolbox.openMraidChecker', () => openMraidChecker(context)),
|
||||||
vscode.commands.registerCommand('hplToolbox.openSendToMobile', () => openSendToMobile(context)),
|
vscode.commands.registerCommand('hplToolbox.openSendToMobile', () => openSendToMobile(context)),
|
||||||
vscode.commands.registerCommand('hplToolbox.openPlayworksConverter', () => openPlayworksConverter(context)),
|
vscode.commands.registerCommand('hplToolbox.openPlayworksConverter', () => openPlayworksConverter(context)),
|
||||||
|
vscode.commands.registerCommand('hplToolbox.openChangelog', () => openChangelog(context)),
|
||||||
vscode.window.registerWebviewViewProvider('hplToolbox.launcher', new LauncherViewProvider(context))
|
vscode.window.registerWebviewViewProvider('hplToolbox.launcher', new LauncherViewProvider(context))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,8 @@ export class LauncherViewProvider implements vscode.WebviewViewProvider {
|
|||||||
view.webview.onDidReceiveMessage(async (msg) => {
|
view.webview.onDidReceiveMessage(async (msg) => {
|
||||||
if (msg?.type === 'open' && typeof msg.command === 'string') {
|
if (msg?.type === 'open' && typeof msg.command === 'string') {
|
||||||
vscode.commands.executeCommand(msg.command);
|
vscode.commands.executeCommand(msg.command);
|
||||||
|
} else if (msg?.type === 'openChangelog') {
|
||||||
|
vscode.commands.executeCommand('hplToolbox.openChangelog');
|
||||||
} else if (msg?.type === 'setBetaToolsEnabled' && typeof msg.enabled === 'boolean') {
|
} else if (msg?.type === 'setBetaToolsEnabled' && typeof msg.enabled === 'boolean') {
|
||||||
await this.context.globalState.update(BETA_TOOLS_ENABLED_KEY, msg.enabled);
|
await this.context.globalState.update(BETA_TOOLS_ENABLED_KEY, msg.enabled);
|
||||||
view.webview.html = getHtml(version, msg.enabled);
|
view.webview.html = getHtml(version, msg.enabled);
|
||||||
@@ -142,15 +144,15 @@ function getHtml(version: string, betaToolsEnabled: boolean): string {
|
|||||||
.footer {
|
.footer {
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto 1fr;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
color: var(--vscode-descriptionForeground);
|
color: var(--vscode-descriptionForeground);
|
||||||
font-size: 8px;
|
font-size: 8px;
|
||||||
border-top: 1px solid var(--vscode-panel-border);
|
border-top: 1px solid var(--vscode-panel-border);
|
||||||
}
|
}
|
||||||
.beta-toggle {
|
.footer-action {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--vscode-descriptionForeground);
|
color: var(--vscode-descriptionForeground);
|
||||||
@@ -161,12 +163,20 @@ function getHtml(version: string, betaToolsEnabled: boolean): string {
|
|||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
opacity: 0.72;
|
opacity: 0.72;
|
||||||
}
|
}
|
||||||
.beta-toggle:hover {
|
#betaToggle {
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
|
.footer-action:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
.footer-version {
|
.footer-version {
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.changelog-link {
|
||||||
|
justify-self: end;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -176,8 +186,9 @@ function getHtml(version: string, betaToolsEnabled: boolean): string {
|
|||||||
${toolButtons}
|
${toolButtons}
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<button id="betaToggle" class="beta-toggle" data-enabled="${betaToolsEnabled ? 'true' : 'false'}">${betaToolsEnabled ? 'Hide beta' : 'Show beta'}</button>
|
<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>
|
<span class="footer-version">${version} - JJGC 00784</span>
|
||||||
|
<button id="changelogLink" class="footer-action changelog-link">Changelog</button>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
const vscode = acquireVsCodeApi();
|
const vscode = acquireVsCodeApi();
|
||||||
@@ -190,6 +201,9 @@ ${toolButtons}
|
|||||||
const enabled = event.currentTarget.dataset.enabled === 'true';
|
const enabled = event.currentTarget.dataset.enabled === 'true';
|
||||||
vscode.postMessage({ type: 'setBetaToolsEnabled', enabled: !enabled });
|
vscode.postMessage({ type: 'setBetaToolsEnabled', enabled: !enabled });
|
||||||
});
|
});
|
||||||
|
document.getElementById('changelogLink').addEventListener('click', () => {
|
||||||
|
vscode.postMessage({ type: 'openChangelog' });
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>`;
|
</html>`;
|
||||||
|
|||||||
116
standalone/changelog.go
Normal file
116
standalone/changelog.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ChangelogPage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
markdown := readChangelog()
|
||||||
|
body := `
|
||||||
|
<header class="tool-header">
|
||||||
|
<h2 class="tool-title">Changelog</h2>
|
||||||
|
<p class="tool-description">Loaded from CHANGELOG.md.</p>
|
||||||
|
</header>
|
||||||
|
<section class="tool-panel changelog-panel">
|
||||||
|
<div class="panel-body changelog-content">
|
||||||
|
` + renderChangelogMarkdown(markdown) + `
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<style>
|
||||||
|
.changelog-panel { max-width: 840px; }
|
||||||
|
.changelog-content h1 { margin: 0 0 14px; font-size: 20px; line-height: 1.25; }
|
||||||
|
.changelog-content h2 { margin: 18px 0 8px; padding-top: 12px; border-top: 1px solid #333; font-size: 15px; line-height: 1.3; }
|
||||||
|
.changelog-content ul { margin: 6px 0 12px; padding-left: 20px; }
|
||||||
|
.changelog-content li { margin: 4px 0; line-height: 1.45; }
|
||||||
|
.changelog-content p { margin: 8px 0; line-height: 1.5; }
|
||||||
|
</style>`
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
_, _ = w.Write([]byte(Page("/changelog", "Changelog", body)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func readChangelog() string {
|
||||||
|
for _, start := range changelogSearchRoots() {
|
||||||
|
for _, candidate := range changelogCandidates(start) {
|
||||||
|
data, err := os.ReadFile(candidate)
|
||||||
|
if err == nil {
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "# Changelog\n\nCHANGELOG.md was not found."
|
||||||
|
}
|
||||||
|
|
||||||
|
func changelogSearchRoots() []string {
|
||||||
|
roots := []string{}
|
||||||
|
if wd, err := os.Getwd(); err == nil {
|
||||||
|
roots = append(roots, wd)
|
||||||
|
}
|
||||||
|
roots = append(roots, appDir())
|
||||||
|
return roots
|
||||||
|
}
|
||||||
|
|
||||||
|
func changelogCandidates(start string) []string {
|
||||||
|
candidates := []string{}
|
||||||
|
dir := filepath.Clean(start)
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
candidates = append(candidates, filepath.Join(dir, "CHANGELOG.md"))
|
||||||
|
parent := filepath.Dir(dir)
|
||||||
|
if parent == dir {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
dir = parent
|
||||||
|
}
|
||||||
|
return candidates
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderChangelogMarkdown(markdown string) string {
|
||||||
|
lines := strings.Split(strings.ReplaceAll(markdown, "\r\n", "\n"), "\n")
|
||||||
|
var out strings.Builder
|
||||||
|
inList := false
|
||||||
|
closeList := func() {
|
||||||
|
if inList {
|
||||||
|
out.WriteString("</ul>")
|
||||||
|
inList = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rawLine := range lines {
|
||||||
|
line := strings.TrimSpace(rawLine)
|
||||||
|
if line == "" {
|
||||||
|
closeList()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "### ") {
|
||||||
|
closeList()
|
||||||
|
out.WriteString("<h2>" + html.EscapeString(strings.TrimSpace(line[4:])) + "</h2>")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "## ") {
|
||||||
|
closeList()
|
||||||
|
out.WriteString("<h2>" + html.EscapeString(strings.TrimSpace(line[3:])) + "</h2>")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "# ") {
|
||||||
|
closeList()
|
||||||
|
out.WriteString("<h1>" + html.EscapeString(strings.TrimSpace(line[2:])) + "</h1>")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "- ") {
|
||||||
|
if !inList {
|
||||||
|
out.WriteString("<ul>")
|
||||||
|
inList = true
|
||||||
|
}
|
||||||
|
out.WriteString("<li>" + html.EscapeString(strings.TrimSpace(line[2:])) + "</li>")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
closeList()
|
||||||
|
out.WriteString("<p>" + html.EscapeString(line) + "</p>")
|
||||||
|
}
|
||||||
|
closeList()
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ func HomePage(w http.ResponseWriter, r *http.Request) {
|
|||||||
if n.Beta {
|
if n.Beta {
|
||||||
badge = ` <span class="beta-pill">Beta</span>`
|
badge = ` <span class="beta-pill">Beta</span>`
|
||||||
}
|
}
|
||||||
items.WriteString(`<a class="home-tool" href="` + n.Path + `"><span class="home-tool-title">` + n.Label + badge + `</span><span class="home-tool-desc">` + n.Description + `</span></a>`)
|
items.WriteString(`<button type="button" class="home-tool" data-path="` + n.Path + `"><span class="home-tool-title">` + n.Label + badge + `</span><span class="home-tool-desc">` + n.Description + `</span></button>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
body := `
|
body := `
|
||||||
@@ -31,13 +31,16 @@ func HomePage(w http.ResponseWriter, r *http.Request) {
|
|||||||
.home-tools { display:flex; flex-direction:column; gap:8px; max-width:620px; }
|
.home-tools { display:flex; flex-direction:column; gap:8px; max-width:620px; }
|
||||||
.home-tool {
|
.home-tool {
|
||||||
display:block;
|
display:block;
|
||||||
|
width:100%;
|
||||||
min-height:52px;
|
min-height:52px;
|
||||||
padding:9px 11px;
|
padding:9px 11px;
|
||||||
color:#ddd;
|
color:#ddd;
|
||||||
text-decoration:none;
|
text-align:left;
|
||||||
background:#2a2d2e;
|
background:#2a2d2e;
|
||||||
border:1px solid #333;
|
border:1px solid #333;
|
||||||
border-radius:5px;
|
border-radius:5px;
|
||||||
|
cursor:pointer;
|
||||||
|
font:inherit;
|
||||||
}
|
}
|
||||||
.home-tool:hover { background:#33373a; border-color:#007fd4; }
|
.home-tool:hover { background:#33373a; border-color:#007fd4; }
|
||||||
.home-tool-title { display:flex; align-items:center; justify-content:space-between; gap:8px; font-size:12px; font-weight:600; }
|
.home-tool-title { display:flex; align-items:center; justify-content:space-between; gap:8px; font-size:12px; font-weight:600; }
|
||||||
|
|||||||
@@ -31,9 +31,17 @@ const SharedCSS = `
|
|||||||
padding: 8px 16px; background: #252526; border-bottom: 1px solid #333;
|
padding: 8px 16px; background: #252526; border-bottom: 1px solid #333;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
.topbar a { color: #ddd; text-decoration: none; padding: 5px 10px; border-radius: 4px; }
|
.topbar .nav-link {
|
||||||
.topbar a:hover { background: #2a2d2e; }
|
background: transparent;
|
||||||
.topbar a.active { background: #094771; color: #fff; }
|
color: #ddd;
|
||||||
|
border: 0;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
.topbar .nav-link:hover { background: #2a2d2e; }
|
||||||
|
.topbar .nav-link.active { background: #094771; color: #fff; }
|
||||||
.beta-pill { margin-left: 4px; padding: 1px 4px; border: 1px solid #555; border-radius: 3px; font-size: 10px; opacity: 0.7; text-transform: uppercase; }
|
.beta-pill { margin-left: 4px; padding: 1px 4px; border: 1px solid #555; border-radius: 3px; font-size: 10px; opacity: 0.7; text-transform: uppercase; }
|
||||||
.topbar-spacer { margin-left: auto; }
|
.topbar-spacer { margin-left: auto; }
|
||||||
.content { flex: 1; width: 100%; padding: 18px; max-width: 1100px; margin: 0 auto; }
|
.content { flex: 1; width: 100%; padding: 18px; max-width: 1100px; margin: 0 auto; }
|
||||||
@@ -42,15 +50,16 @@ const SharedCSS = `
|
|||||||
max-width: 1100px;
|
max-width: 1100px;
|
||||||
margin: auto auto 0;
|
margin: auto auto 0;
|
||||||
padding: 8px 18px 14px;
|
padding: 8px 18px 14px;
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto 1fr;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
color: #a7a7a7;
|
color: #a7a7a7;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
border-top: 1px solid #333;
|
border-top: 1px solid #333;
|
||||||
}
|
}
|
||||||
.beta-tools-toggle {
|
.beta-tools-toggle {
|
||||||
|
justify-self: start;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #a7a7a7;
|
color: #a7a7a7;
|
||||||
@@ -58,9 +67,12 @@ const SharedCSS = `
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
opacity: 0.72;
|
opacity: 0.72;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
.beta-tools-toggle:hover { background: transparent; opacity: 1; text-decoration: underline; }
|
.beta-tools-toggle:hover { background: transparent; opacity: 1; text-decoration: underline; }
|
||||||
.app-footer-version { opacity: 0.75; text-align: right; }
|
.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; }
|
||||||
h2 { margin: 0; font-size: 18px; line-height: 1.25; font-weight: 600; }
|
h2 { margin: 0; font-size: 18px; line-height: 1.25; font-weight: 600; }
|
||||||
.tool-header { margin-bottom: var(--tool-gap-lg); }
|
.tool-header { margin-bottom: var(--tool-gap-lg); }
|
||||||
.tool-title { margin: 0; font-size: 18px; line-height: 1.25; font-weight: 600; }
|
.tool-title { margin: 0; font-size: 18px; line-height: 1.25; font-weight: 600; }
|
||||||
@@ -176,13 +188,13 @@ func Page(activePath, title, body string) string {
|
|||||||
for _, n := range visibleNavItems(betaToolsEnabled) {
|
for _, n := range visibleNavItems(betaToolsEnabled) {
|
||||||
cls := ""
|
cls := ""
|
||||||
if n.Path == activePath {
|
if n.Path == activePath {
|
||||||
cls = " class=\"active\""
|
cls = " active"
|
||||||
}
|
}
|
||||||
label := n.Label
|
label := n.Label
|
||||||
if n.Beta {
|
if n.Beta {
|
||||||
label += `<span class="beta-pill">Beta</span>`
|
label += `<span class="beta-pill">Beta</span>`
|
||||||
}
|
}
|
||||||
tabs.WriteString(`<a href="` + n.Path + `"` + cls + `>` + label + `</a>`)
|
tabs.WriteString(`<button type="button" class="nav-link` + cls + `" data-path="` + n.Path + `">` + label + `</button>`)
|
||||||
}
|
}
|
||||||
|
|
||||||
betaButtonLabel := "Show beta"
|
betaButtonLabel := "Show beta"
|
||||||
@@ -201,8 +213,15 @@ func Page(activePath, title, body string) string {
|
|||||||
<div class="app-footer">
|
<div class="app-footer">
|
||||||
<button id="betaToolsToggle" class="beta-tools-toggle" data-enabled="` + boolAttr(betaToolsEnabled) + `">` + betaButtonLabel + `</button>
|
<button id="betaToolsToggle" class="beta-tools-toggle" data-enabled="` + boolAttr(betaToolsEnabled) + `">` + betaButtonLabel + `</button>
|
||||||
<span class="app-footer-version">` + AppVersion + ` - JJGC 00784</span>
|
<span class="app-footer-version">` + AppVersion + ` - JJGC 00784</span>
|
||||||
|
<button type="button" class="app-footer-link" data-path="/changelog">Changelog</button>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
document.querySelectorAll('[data-path]').forEach((element) => {
|
||||||
|
element.addEventListener('click', () => {
|
||||||
|
const path = element.dataset.path;
|
||||||
|
if (path) window.location.href = path;
|
||||||
|
});
|
||||||
|
});
|
||||||
document.getElementById('betaToolsToggle').addEventListener('click', async (event) => {
|
document.getElementById('betaToolsToggle').addEventListener('click', async (event) => {
|
||||||
const enabled = event.currentTarget.dataset.enabled === 'true';
|
const enabled = event.currentTarget.dataset.enabled === 'true';
|
||||||
await fetch('/api/betaTools', {
|
await fetch('/api/betaTools', {
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ func buildMux() *http.ServeMux {
|
|||||||
mux.HandleFunc("GET /mraid", MraidPage)
|
mux.HandleFunc("GET /mraid", MraidPage)
|
||||||
mux.HandleFunc("GET /mobile", MobilePage)
|
mux.HandleFunc("GET /mobile", MobilePage)
|
||||||
mux.HandleFunc("GET /playworks", PlayworksPage)
|
mux.HandleFunc("GET /playworks", PlayworksPage)
|
||||||
|
mux.HandleFunc("GET /changelog", ChangelogPage)
|
||||||
mux.HandleFunc("GET /assets/qrcode.min.js", MobileQrScript)
|
mux.HandleFunc("GET /assets/qrcode.min.js", MobileQrScript)
|
||||||
|
|
||||||
// Shared API
|
// Shared API
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PlecConfig struct {
|
type PlecConfig struct {
|
||||||
@@ -114,12 +115,12 @@ func SaveConfig(cfg AppConfig) error {
|
|||||||
|
|
||||||
func OpenInBrowser(url string) {
|
func OpenInBrowser(url string) {
|
||||||
// `start` is a cmd builtin. Quoted empty arg is the window title.
|
// `start` is a cmd builtin. Quoted empty arg is the window title.
|
||||||
cmd := exec.Command("cmd", "/c", "start", "", url)
|
cmd := hiddenCommand("cmd", "/c", "start", "", url)
|
||||||
_ = cmd.Start()
|
_ = cmd.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func CopyToClipboard(text string) error {
|
func CopyToClipboard(text string) error {
|
||||||
cmd := exec.Command("clip")
|
cmd := hiddenCommand("clip")
|
||||||
cmd.Stdin = strings.NewReader(text)
|
cmd.Stdin = strings.NewReader(text)
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
@@ -131,6 +132,15 @@ type FileFilter struct {
|
|||||||
|
|
||||||
func psEscape(s string) string { return strings.ReplaceAll(s, "'", "''") }
|
func psEscape(s string) string { return strings.ReplaceAll(s, "'", "''") }
|
||||||
|
|
||||||
|
func hiddenCommand(name string, args ...string) *exec.Cmd {
|
||||||
|
cmd := exec.Command(name, args...)
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
HideWindow: true,
|
||||||
|
CreationFlags: 0x08000000,
|
||||||
|
}
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
func PickFiles(title string, filters []FileFilter, multi bool, initialDir string) []string {
|
func PickFiles(title string, filters []FileFilter, multi bool, initialDir string) []string {
|
||||||
filterStr := "All files|*.*"
|
filterStr := "All files|*.*"
|
||||||
if len(filters) > 0 {
|
if len(filters) > 0 {
|
||||||
@@ -159,7 +169,7 @@ if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
|
|||||||
$dlg.FileNames | ForEach-Object { Write-Output $_ }
|
$dlg.FileNames | ForEach-Object { Write-Output $_ }
|
||||||
}
|
}
|
||||||
`, psEscape(title), psEscape(filterStr), multiStr, psEscape(initialDir), psEscape(initialDir))
|
`, psEscape(title), psEscape(filterStr), multiStr, psEscape(initialDir), psEscape(initialDir))
|
||||||
out, err := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-STA", "-Command", script).Output()
|
out, err := hiddenCommand("powershell", "-NoProfile", "-NonInteractive", "-STA", "-Command", script).Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -198,7 +208,7 @@ if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
|
|||||||
Write-Output $dlg.FileName
|
Write-Output $dlg.FileName
|
||||||
}
|
}
|
||||||
`, psEscape(title), psEscape(filterStr), psEscape(defaultName), psEscape(initialDir), psEscape(initialDir))
|
`, psEscape(title), psEscape(filterStr), psEscape(defaultName), psEscape(initialDir), psEscape(initialDir))
|
||||||
out, err := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-STA", "-Command", script).Output()
|
out, err := hiddenCommand("powershell", "-NoProfile", "-NonInteractive", "-STA", "-Command", script).Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -215,7 +225,7 @@ if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
|
|||||||
Write-Output $dlg.SelectedPath
|
Write-Output $dlg.SelectedPath
|
||||||
}
|
}
|
||||||
`, psEscape(title), psEscape(initialDir), psEscape(initialDir))
|
`, psEscape(title), psEscape(initialDir), psEscape(initialDir))
|
||||||
out, err := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-STA", "-Command", script).Output()
|
out, err := hiddenCommand("powershell", "-NoProfile", "-NonInteractive", "-STA", "-Command", script).Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user