V2 with UI
This commit is contained in:
285
index.html
285
index.html
@@ -3,69 +3,110 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@600;700&display=swap" rel="stylesheet">
|
||||||
<title>Release Notes Formatter</title>
|
<title>Release Notes Formatter</title>
|
||||||
<style>
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #f5f7fa;
|
||||||
|
--bg2: #eef2f7;
|
||||||
|
--card: #ffffff;
|
||||||
|
--text: #0f172a;
|
||||||
|
--muted: #64748b;
|
||||||
|
--border: rgba(2,6,23,0.10);
|
||||||
|
--shadow: 0 10px 26px rgba(2,6,23,0.07);
|
||||||
|
|
||||||
|
/* Palette */
|
||||||
|
--primary: #0D9488; /* teal */
|
||||||
|
--primary2: #146C94; /* deep blue */
|
||||||
|
--accent: #FF6B6B; /* coral */
|
||||||
|
--successBg: #eafff6;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--bg: #0b1220;
|
||||||
|
--bg2: #0f172a;
|
||||||
|
--card: #0f172a;
|
||||||
|
--text: #e5e7eb;
|
||||||
|
--muted: #94a3b8;
|
||||||
|
--border: rgba(226,232,240,0.14);
|
||||||
|
--shadow: 0 12px 34px rgba(0,0,0,0.40);
|
||||||
|
--successBg: rgba(13,148,136,0.16);
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
|
font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 1.35;
|
line-height: 1.45;
|
||||||
background: #f6f7f9;
|
background: radial-gradient(1200px 600px at 10% 0%, var(--bg2), var(--bg));
|
||||||
color: #111;
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 1100px;
|
width: min(92vw, 1500px);
|
||||||
|
max-width: 1500px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 28px 28px 48px;
|
padding: 26px 18px 56px;
|
||||||
}
|
}
|
||||||
.row { display: grid; grid-template-columns: 1fr; gap: 14px; }
|
.row { display: grid; grid-template-columns: 1fr; gap: 16px; }
|
||||||
.card {
|
.card {
|
||||||
border: 1px solid rgba(0,0,0,0.08);
|
border: 1px solid var(--border);
|
||||||
border-radius: 12px;
|
border-radius: 14px;
|
||||||
padding: 14px;
|
padding: 16px;
|
||||||
background: #fff;
|
background: var(--card);
|
||||||
box-shadow: 0 1px 2px rgba(0,0,0,0.06);
|
box-shadow: var(--shadow);
|
||||||
}
|
}
|
||||||
.muted { color: #666; font-size: 14px; }
|
.muted { color: var(--muted); font-size: 14px; }
|
||||||
.btn {
|
.btn {
|
||||||
padding: 10px 12px;
|
padding: 9px 12px;
|
||||||
border-radius: 10px;
|
border-radius: 12px;
|
||||||
border: 1px solid rgba(0,0,0,0.15);
|
border: 1px solid var(--border);
|
||||||
background: #fff;
|
background: color-mix(in srgb, var(--card) 92%, var(--primary) 8%);
|
||||||
|
color: var(--text);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: transform 140ms ease, background 140ms ease, border-color 140ms ease;
|
||||||
|
}
|
||||||
|
.btn:hover {
|
||||||
|
background: color-mix(in srgb, var(--card) 86%, var(--primary) 14%);
|
||||||
|
border-color: color-mix(in srgb, var(--border) 60%, var(--primary) 40%);
|
||||||
}
|
}
|
||||||
.btn:hover { background: #f2f4f7; }
|
|
||||||
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||||
.btn:active { transform: translateY(1px); }
|
.btn:active { transform: scale(0.98); }
|
||||||
textarea {
|
textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 260px;
|
min-height: 260px;
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
padding: 10px;
|
padding: 12px;
|
||||||
border-radius: 10px;
|
border-radius: 12px;
|
||||||
border: 1px solid rgba(0,0,0,0.15);
|
border: 1px solid var(--border);
|
||||||
background: #fbfcfe;
|
background: color-mix(in srgb, var(--card) 92%, var(--bg) 8%);
|
||||||
|
color: var(--text);
|
||||||
}
|
}
|
||||||
.grid2 { display: grid; grid-template-columns: 1fr; gap: 12px; }
|
.grid2 { display: grid; grid-template-columns: 1fr; gap: 14px; }
|
||||||
@media (min-width: 980px) { .grid2 { grid-template-columns: 1fr 1fr; } }
|
|
||||||
.topbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
.topbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
||||||
.navRow { display: flex; flex-wrap: wrap; gap: 10px; }
|
.navRow { display: flex; flex-wrap: wrap; gap: 10px; }
|
||||||
.navBtn {
|
.navBtn {
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
border: 1px solid rgba(0,0,0,0.12);
|
border: 1px solid var(--border);
|
||||||
background: #fff;
|
background: color-mix(in srgb, var(--card) 92%, var(--primary2) 8%);
|
||||||
|
color: var(--text);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
transition: transform 140ms ease, background 140ms ease, border-color 140ms ease;
|
||||||
}
|
}
|
||||||
.navBtn:hover { background: #f2f4f7; }
|
.navBtn:hover {
|
||||||
.ok { color: #0a7; }
|
background: color-mix(in srgb, var(--card) 86%, var(--primary2) 14%);
|
||||||
.err { color: #b00; white-space: pre-wrap; }
|
border-color: color-mix(in srgb, var(--border) 60%, var(--primary2) 40%);
|
||||||
.small { font-size: 12px; color: #666; }
|
}
|
||||||
|
.ok { color: var(--primary); }
|
||||||
|
.err { color: #ef4444; white-space: pre-wrap; }
|
||||||
|
.small { font-size: 12px; color: var(--muted); }
|
||||||
label { font-weight: 600; }
|
label { font-weight: 600; }
|
||||||
input[type="file"] { padding: 6px; }
|
input[type="file"] { padding: 6px; }
|
||||||
.ascGrid { display: grid; grid-template-columns: 1fr; gap: 12px; margin-top: 10px; }
|
.ascGrid { display: grid; grid-template-columns: 1fr; gap: 14px; margin-top: 10px; }
|
||||||
@media (min-width: 980px) { .ascGrid { grid-template-columns: 1fr 1fr; } }
|
|
||||||
.singleCol { grid-template-columns: 1fr !important; }
|
.singleCol { grid-template-columns: 1fr !important; }
|
||||||
.ascBoxHeader { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom: 8px; }
|
.ascBoxHeader { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom: 8px; }
|
||||||
.ascLocale { font-weight: 700; }
|
.ascLocale { font-weight: 700; }
|
||||||
@@ -77,7 +118,7 @@
|
|||||||
|
|
||||||
@keyframes uploadFlash {
|
@keyframes uploadFlash {
|
||||||
0% { background-color: #eafff6; }
|
0% { background-color: #eafff6; }
|
||||||
100% { background-color: #fff; }
|
100% { background-color: var(--card); }
|
||||||
}
|
}
|
||||||
#uploadCard.flash {
|
#uploadCard.flash {
|
||||||
animation: uploadFlash 5s ease;
|
animation: uploadFlash 5s ease;
|
||||||
@@ -91,7 +132,7 @@
|
|||||||
}
|
}
|
||||||
.btn.copied {
|
.btn.copied {
|
||||||
border-color: rgba(0,170,119,0.6);
|
border-color: rgba(0,170,119,0.6);
|
||||||
background: #eafff6;
|
background: var(--successBg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Section highlight pulse animation */
|
/* Section highlight pulse animation */
|
||||||
@@ -104,20 +145,79 @@
|
|||||||
.sectionHighlight {
|
.sectionHighlight {
|
||||||
animation: sectionPulse 1.4s ease;
|
animation: sectionPulse 1.4s ease;
|
||||||
}
|
}
|
||||||
|
h2 { font-family: Poppins, Inter, system-ui, sans-serif; letter-spacing: -0.02em; }
|
||||||
|
h3 { letter-spacing: -0.01em; }
|
||||||
|
|
||||||
|
.dropZone {
|
||||||
|
border: 1.5px dashed color-mix(in srgb, var(--border) 60%, var(--primary) 40%);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 12px;
|
||||||
|
background: color-mix(in srgb, var(--card) 92%, var(--bg) 8%);
|
||||||
|
transition: box-shadow 160ms ease, border-color 160ms ease, transform 160ms ease;
|
||||||
|
}
|
||||||
|
.dropZone.dragOver {
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 6px color-mix(in srgb, var(--primary) 20%, transparent 80%);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
.hintPill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: color-mix(in srgb, var(--card) 92%, var(--primary2) 8%);
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toastHost {
|
||||||
|
position: fixed;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
z-index: 9999;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.toast {
|
||||||
|
pointer-events: none;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: color-mix(in srgb, var(--card) 88%, var(--bg) 12%);
|
||||||
|
color: var(--text);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-6px);
|
||||||
|
animation: toastIn 180ms ease forwards;
|
||||||
|
max-width: min(380px, 88vw);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.toast.ok { border-color: color-mix(in srgb, var(--border) 40%, var(--primary) 60%); }
|
||||||
|
.toast.err { border-color: color-mix(in srgb, var(--border) 40%, #ef4444 60%); }
|
||||||
|
@keyframes toastIn { to { opacity: 1; transform: translateY(0); } }
|
||||||
|
@keyframes toastOut { to { opacity: 0; transform: translateY(-6px); } }
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2 style="margin: 0 0 6px 0;">Release Notes Formatter</h2>
|
<h2 style="margin: 0 0 6px 0;">🧾 Release Notes Formatter</h2>
|
||||||
<p class="muted" style="margin: 0 0 14px 0;">Upload a Gridly CSV export.</p>
|
<p class="muted" style="margin: 0 0 14px 0;">Upload a Gridly CSV export.</p>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="card" id="uploadCard">
|
<div class="card" id="uploadCard">
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
<div>
|
<div class="dropZone" id="dropZone" title="Drag & drop a CSV here">
|
||||||
<label for="file">CSV file</label><br/>
|
<label for="file">CSV file</label><br/>
|
||||||
<input id="file" type="file" accept=".csv,text/csv" />
|
<input id="file" type="file" accept=".csv,text/csv" />
|
||||||
<div id="fileInfo" class="small"></div>
|
<div class="topbar" style="margin-top:8px; gap: 8px;">
|
||||||
|
<span class="hintPill">⬇️ Drag & drop CSV</span>
|
||||||
|
<span class="hintPill">or click to browse</span>
|
||||||
|
</div>
|
||||||
|
<div id="fileInfo" class="small" style="margin-top:8px;"></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="gameVersion">Game version</label><br/>
|
<label for="gameVersion">Game version</label><br/>
|
||||||
@@ -126,10 +226,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="flex:1"></div>
|
<div style="flex:1"></div>
|
||||||
<button class="btn" id="btnRefresh" disabled>Refresh</button>
|
<button class="btn" id="btnRefresh" disabled>Refresh</button>
|
||||||
|
<button class="btn" id="btnTheme" type="button" title="Toggle dark mode">🌙</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card" style="padding: 12px;">
|
<div class="card" style="padding: 12px;">
|
||||||
|
<div class="small muted" style="margin-bottom: 6px; font-weight: 600;">Jump to</div>
|
||||||
<div class="navRow">
|
<div class="navRow">
|
||||||
<button class="navBtn" type="button" data-scroll="googleSection">Google</button>
|
<button class="navBtn" type="button" data-scroll="googleSection">Google</button>
|
||||||
<button class="navBtn" type="button" data-scroll="appleSection">Apple</button>
|
<button class="navBtn" type="button" data-scroll="appleSection">Apple</button>
|
||||||
@@ -142,7 +244,7 @@
|
|||||||
<div class="card" id="googleSection">
|
<div class="card" id="googleSection">
|
||||||
<div class="topbar" style="justify-content: space-between;">
|
<div class="topbar" style="justify-content: space-between;">
|
||||||
<div>
|
<div>
|
||||||
<h3 style="margin:0 0 6px 0;">Google Play output</h3>
|
<h3 style="margin:0 0 6px 0;">📦 Google Play output</h3>
|
||||||
<div class="muted">Tagged format like <en-US>...</en-US></div>
|
<div class="muted">Tagged format like <en-US>...</en-US></div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn" id="btnCopyGoogle" disabled><span class="btnLabel">Copy</span></button>
|
<button class="btn" id="btnCopyGoogle" disabled><span class="btnLabel">Copy</span></button>
|
||||||
@@ -151,12 +253,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card" id="appleSection">
|
<div class="card" id="appleSection">
|
||||||
<h3 style="margin:0 0 6px 0;">App Store Connect output</h3>
|
<h3 style="margin:0 0 6px 0;">🍎 App Store Connect output</h3>
|
||||||
<div id="ascBoxes" class="ascGrid"></div>
|
<div id="ascBoxes" class="ascGrid"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card" id="playfabAdcapSection">
|
<div class="card" id="playfabAdcapSection">
|
||||||
<h3 style="margin:0 0 6px 0;">PlayFab News output (AdCap only)</h3>
|
<h3 style="margin:0 0 6px 0;">📰 PlayFab News output (AdCap only)</h3>
|
||||||
<div class="muted" style="margin-bottom: 10px;">Add this JSON to: AdCap > TitleData > Primary Title Data > <code>newsfeed_mobile</code> (PlayFab).</div>
|
<div class="muted" style="margin-bottom: 10px;">Add this JSON to: AdCap > TitleData > Primary Title Data > <code>newsfeed_mobile</code> (PlayFab).</div>
|
||||||
<div id="adcapHelp" class="small muted" style="margin-bottom: 10px;">Requires a valid game version above. Uses English text only.</div>
|
<div id="adcapHelp" class="small muted" style="margin-bottom: 10px;">Requires a valid game version above. Uses English text only.</div>
|
||||||
<div class="topbar" style="justify-content: space-between;">
|
<div class="topbar" style="justify-content: space-between;">
|
||||||
@@ -167,7 +269,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card" id="playfabAdcomSection">
|
<div class="card" id="playfabAdcomSection">
|
||||||
<h3 style="margin:0 0 6px 0;">PlayFab News output (AdCom/AdAges)</h3>
|
<h3 style="margin:0 0 6px 0;">🗞️ PlayFab News output (AdCom/AdAges)</h3>
|
||||||
<div class="muted">Per-language Title + Body. Requires a valid game version above.</div>
|
<div class="muted">Per-language Title + Body. Requires a valid game version above.</div>
|
||||||
<div id="playfabBoxes" class="ascGrid singleCol"></div>
|
<div id="playfabBoxes" class="ascGrid singleCol"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -189,6 +291,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="toastHost" class="toastHost"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// --- Config: map Gridly column headers -> output tags (Google).
|
// --- Config: map Gridly column headers -> output tags (Google).
|
||||||
// Use array-of-pairs so the same Gridly column can be emitted multiple times.
|
// Use array-of-pairs so the same Gridly column can be emitted multiple times.
|
||||||
@@ -261,6 +365,9 @@
|
|||||||
const ascBoxes = document.getElementById("ascBoxes");
|
const ascBoxes = document.getElementById("ascBoxes");
|
||||||
const btnCopyGoogle = document.getElementById("btnCopyGoogle");
|
const btnCopyGoogle = document.getElementById("btnCopyGoogle");
|
||||||
const btnRefresh = document.getElementById("btnRefresh");
|
const btnRefresh = document.getElementById("btnRefresh");
|
||||||
|
const dropZone = document.getElementById("dropZone");
|
||||||
|
const btnTheme = document.getElementById("btnTheme");
|
||||||
|
const toastHost = document.getElementById("toastHost");
|
||||||
const skippedLocalesEl = document.getElementById("skippedLocales");
|
const skippedLocalesEl = document.getElementById("skippedLocales");
|
||||||
const gameVersionInput = document.getElementById("gameVersion");
|
const gameVersionInput = document.getElementById("gameVersion");
|
||||||
const versionHelpEl = document.getElementById("versionHelp");
|
const versionHelpEl = document.getElementById("versionHelp");
|
||||||
@@ -269,6 +376,33 @@
|
|||||||
const btnCopyAdcap = document.getElementById("btnCopyAdcap");
|
const btnCopyAdcap = document.getElementById("btnCopyAdcap");
|
||||||
const adcapHelpEl = document.getElementById("adcapHelp");
|
const adcapHelpEl = document.getElementById("adcapHelp");
|
||||||
clearAdcap();
|
clearAdcap();
|
||||||
|
|
||||||
|
// Restore saved settings
|
||||||
|
(function initPrefs() {
|
||||||
|
try {
|
||||||
|
const savedVersion = localStorage.getItem("rn_gameVersion");
|
||||||
|
if (savedVersion && !gameVersionInput.value) gameVersionInput.value = savedVersion;
|
||||||
|
|
||||||
|
const theme = localStorage.getItem("rn_theme") || "light";
|
||||||
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
|
if (btnTheme) btnTheme.textContent = theme === "dark" ? "☀️" : "🌙";
|
||||||
|
} catch {}
|
||||||
|
})();
|
||||||
|
|
||||||
|
gameVersionInput.addEventListener("input", () => {
|
||||||
|
try { localStorage.setItem("rn_gameVersion", gameVersionInput.value || ""); } catch {}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (btnTheme) {
|
||||||
|
btnTheme.addEventListener("click", () => {
|
||||||
|
const cur = document.documentElement.getAttribute("data-theme") || "light";
|
||||||
|
const next = cur === "dark" ? "light" : "dark";
|
||||||
|
document.documentElement.setAttribute("data-theme", next);
|
||||||
|
btnTheme.textContent = next === "dark" ? "☀️" : "🌙";
|
||||||
|
try { localStorage.setItem("rn_theme", next); } catch {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function clearAdcap() {
|
function clearAdcap() {
|
||||||
outAdcap.value = "";
|
outAdcap.value = "";
|
||||||
btnCopyAdcap.disabled = true;
|
btnCopyAdcap.disabled = true;
|
||||||
@@ -402,8 +536,8 @@
|
|||||||
const ta = document.createElement("textarea");
|
const ta = document.createElement("textarea");
|
||||||
ta.readOnly = true;
|
ta.readOnly = true;
|
||||||
ta.value = title;
|
ta.value = title;
|
||||||
ta.rows = 1;
|
ta.rows = 3;
|
||||||
ta.style.minHeight = "40px";
|
ta.style.minHeight = "72px";
|
||||||
|
|
||||||
wrap.className = "fieldBox";
|
wrap.className = "fieldBox";
|
||||||
wrap.appendChild(headerRow);
|
wrap.appendChild(headerRow);
|
||||||
@@ -458,11 +592,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setStatus(_msg, _type="") {
|
function showToast(msg, type = "ok") {
|
||||||
// Status UI removed; keep function so existing calls don't error.
|
if (!toastHost) return;
|
||||||
|
const t = document.createElement("div");
|
||||||
|
t.className = `toast ${type === "err" ? "err" : "ok"}`;
|
||||||
|
t.textContent = msg;
|
||||||
|
toastHost.appendChild(t);
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
t.style.animation = "toastOut 220ms ease forwards";
|
||||||
|
window.setTimeout(() => t.remove(), 240);
|
||||||
|
}, 2200);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCharCountCol(name) {
|
function setStatus(msg, type = "ok") {
|
||||||
|
// Back-compat: map to toast
|
||||||
|
showToast(String(msg || ""), type || "ok");
|
||||||
|
}function isCharCountCol(name) {
|
||||||
const c = (name || "").trim().toLowerCase();
|
const c = (name || "").trim().toLowerCase();
|
||||||
return c.endsWith(" chars") || c.endsWith(" chrs");
|
return c.endsWith(" chars") || c.endsWith(" chrs");
|
||||||
}
|
}
|
||||||
@@ -776,8 +922,10 @@
|
|||||||
setStatus(`✅ Generated output${recordId ? ` (Record ID: ${recordId})` : ""}`, "ok");
|
setStatus(`✅ Generated output${recordId ? ` (Record ID: ${recordId})` : ""}`, "ok");
|
||||||
}
|
}
|
||||||
|
|
||||||
fileInput.addEventListener("change", async () => {
|
|
||||||
const file = fileInput.files?.[0];
|
let __uploadCount = 0;
|
||||||
|
|
||||||
|
async function processFile(file) {
|
||||||
outGoogle.value = "";
|
outGoogle.value = "";
|
||||||
ascBoxes.innerHTML = "";
|
ascBoxes.innerHTML = "";
|
||||||
btnCopyGoogle.disabled = true;
|
btnCopyGoogle.disabled = true;
|
||||||
@@ -789,7 +937,7 @@
|
|||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
fileInfo.textContent = `${file.name} (${Math.round(file.size/1024)} KB)`;
|
fileInfo.textContent = `${file.name} (${Math.round(file.size/1024)} KB)`;
|
||||||
setStatus("Reading CSV...", "small");
|
setStatus("Parsing CSV…", "ok");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const text = await file.text();
|
const text = await file.text();
|
||||||
@@ -802,7 +950,6 @@
|
|||||||
const headers = grid[0].map(h => String(h ?? "").trim());
|
const headers = grid[0].map(h => String(h ?? "").trim());
|
||||||
const dataRows = grid.slice(1);
|
const dataRows = grid.slice(1);
|
||||||
|
|
||||||
// Convert to objects
|
|
||||||
const objects = dataRows
|
const objects = dataRows
|
||||||
.filter(r => r.some(v => String(v ?? "").trim() !== ""))
|
.filter(r => r.some(v => String(v ?? "").trim() !== ""))
|
||||||
.map(r => {
|
.map(r => {
|
||||||
@@ -823,12 +970,10 @@
|
|||||||
|
|
||||||
btnCopyGoogle.disabled = !google;
|
btnCopyGoogle.disabled = !google;
|
||||||
|
|
||||||
// ---- PlayFab output ----
|
|
||||||
const version = (gameVersionInput.value || "").trim();
|
const version = (gameVersionInput.value || "").trim();
|
||||||
renderPlayfabBoxes(rowObj, headers, version);
|
renderPlayfabBoxes(rowObj, headers, version);
|
||||||
renderAdcap(rowObj, headers, version);
|
renderAdcap(rowObj, headers, version);
|
||||||
|
|
||||||
// Update help text styling (non-blocking)
|
|
||||||
if (version && !isValidVersion(version)) {
|
if (version && !isValidVersion(version)) {
|
||||||
versionHelpEl.textContent = "Invalid version format. Use x.xx.x (e.g. 6.51.0). PlayFab output hidden.";
|
versionHelpEl.textContent = "Invalid version format. Use x.xx.x (e.g. 6.51.0). PlayFab output hidden.";
|
||||||
versionHelpEl.classList.remove("muted");
|
versionHelpEl.classList.remove("muted");
|
||||||
@@ -843,14 +988,44 @@
|
|||||||
btnRefresh.disabled = false;
|
btnRefresh.disabled = false;
|
||||||
flashUploadCard();
|
flashUploadCard();
|
||||||
|
|
||||||
flashUploadCard();
|
__uploadCount += 1;
|
||||||
|
if (__uploadCount === 6) showToast("You rock 🤘", "ok");
|
||||||
|
|
||||||
setStatus(`✅ Generated output${recordId ? ` (Record ID: ${recordId})` : ""}`, "ok");
|
setStatus("✅ Outputs generated", "ok");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setStatus(`❌ ${e.message || e}`, "err");
|
setStatus(`❌ ${e.message || e}`, "err");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInput.addEventListener("change", async () => {
|
||||||
|
const file = fileInput.files?.[0];
|
||||||
|
await processFile(file);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Drag & drop CSV
|
||||||
|
if (dropZone) {
|
||||||
|
const openPicker = () => fileInput && fileInput.click();
|
||||||
|
dropZone.addEventListener("click", openPicker);
|
||||||
|
|
||||||
|
dropZone.addEventListener("dragenter", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dropZone.classList.add("dragOver");
|
||||||
|
});
|
||||||
|
dropZone.addEventListener("dragover", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dropZone.classList.add("dragOver");
|
||||||
|
});
|
||||||
|
dropZone.addEventListener("dragleave", () => dropZone.classList.remove("dragOver"));
|
||||||
|
dropZone.addEventListener("drop", async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dropZone.classList.remove("dragOver");
|
||||||
|
const f = e.dataTransfer?.files?.[0];
|
||||||
|
if (!f) return;
|
||||||
|
await processFile(f);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
btnCopyGoogle.addEventListener("click", async () => {
|
btnCopyGoogle.addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
await copyToClipboard(outGoogle.value);
|
await copyToClipboard(outGoogle.value);
|
||||||
|
|||||||
Reference in New Issue
Block a user