Spaces:
Running
Running
RemiFabre
commited on
Commit
·
cdcfe18
1
Parent(s):
1f737e5
Improved install guide
Browse files- Theremini/webui/app.js +59 -0
- Theremini/webui/style.css +30 -0
Theremini/webui/app.js
CHANGED
|
@@ -118,6 +118,7 @@ function updateHealth(health = {}) {
|
|
| 118 |
const guideHtml = typeof health.fluidsynth_guide_html === "string" ? health.fluidsynth_guide_html.trim() : "";
|
| 119 |
if (guideHtml) {
|
| 120 |
guideContentEl.innerHTML = guideHtml;
|
|
|
|
| 121 |
} else {
|
| 122 |
guideContentEl.innerHTML =
|
| 123 |
"<p class=\"meta\">Unable to load the installation guide. Please check the app logs.</p>";
|
|
@@ -125,6 +126,64 @@ function updateHealth(health = {}) {
|
|
| 125 |
}
|
| 126 |
}
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
async function pollStatus() {
|
| 129 |
try {
|
| 130 |
const response = await fetch("/api/status", { cache: "no-store" });
|
|
|
|
| 118 |
const guideHtml = typeof health.fluidsynth_guide_html === "string" ? health.fluidsynth_guide_html.trim() : "";
|
| 119 |
if (guideHtml) {
|
| 120 |
guideContentEl.innerHTML = guideHtml;
|
| 121 |
+
enhanceGuideContent();
|
| 122 |
} else {
|
| 123 |
guideContentEl.innerHTML =
|
| 124 |
"<p class=\"meta\">Unable to load the installation guide. Please check the app logs.</p>";
|
|
|
|
| 126 |
}
|
| 127 |
}
|
| 128 |
|
| 129 |
+
function enhanceGuideContent() {
|
| 130 |
+
const blocks = guideContentEl.querySelectorAll("pre");
|
| 131 |
+
blocks.forEach((pre) => {
|
| 132 |
+
if (pre.dataset.copyEnhanced === "1") {
|
| 133 |
+
return;
|
| 134 |
+
}
|
| 135 |
+
pre.dataset.copyEnhanced = "1";
|
| 136 |
+
const wrapper = document.createElement("div");
|
| 137 |
+
wrapper.className = "guide-code-wrapper";
|
| 138 |
+
pre.parentNode.insertBefore(wrapper, pre);
|
| 139 |
+
wrapper.appendChild(pre);
|
| 140 |
+
const button = document.createElement("button");
|
| 141 |
+
button.type = "button";
|
| 142 |
+
button.className = "guide-copy-button";
|
| 143 |
+
button.textContent = "Copy";
|
| 144 |
+
button.addEventListener("click", () => copyGuideText(pre.innerText, button));
|
| 145 |
+
wrapper.appendChild(button);
|
| 146 |
+
});
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
async function copyGuideText(content, button) {
|
| 150 |
+
try {
|
| 151 |
+
await navigator.clipboard.writeText(content);
|
| 152 |
+
indicateCopySuccess(button);
|
| 153 |
+
return;
|
| 154 |
+
} catch (error) {
|
| 155 |
+
console.warn("Clipboard copy failed", error);
|
| 156 |
+
}
|
| 157 |
+
const textarea = document.createElement("textarea");
|
| 158 |
+
textarea.value = content;
|
| 159 |
+
textarea.style.position = "fixed";
|
| 160 |
+
textarea.style.top = "-9999px";
|
| 161 |
+
document.body.appendChild(textarea);
|
| 162 |
+
textarea.focus();
|
| 163 |
+
textarea.select();
|
| 164 |
+
try {
|
| 165 |
+
document.execCommand("copy");
|
| 166 |
+
indicateCopySuccess(button);
|
| 167 |
+
} catch (error) {
|
| 168 |
+
console.error("execCommand copy failed", error);
|
| 169 |
+
} finally {
|
| 170 |
+
document.body.removeChild(textarea);
|
| 171 |
+
}
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
function indicateCopySuccess(button) {
|
| 175 |
+
if (!button) {
|
| 176 |
+
return;
|
| 177 |
+
}
|
| 178 |
+
const original = button.textContent;
|
| 179 |
+
button.textContent = "Copied!";
|
| 180 |
+
button.classList.add("copied");
|
| 181 |
+
setTimeout(() => {
|
| 182 |
+
button.textContent = original;
|
| 183 |
+
button.classList.remove("copied");
|
| 184 |
+
}, 1500);
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
async function pollStatus() {
|
| 188 |
try {
|
| 189 |
const response = await fetch("/api/status", { cache: "no-store" });
|
Theremini/webui/style.css
CHANGED
|
@@ -143,6 +143,11 @@ body[data-guide="hidden"] #guidePanel {
|
|
| 143 |
flex-direction: column;
|
| 144 |
gap: 0.75rem;
|
| 145 |
line-height: 1.5;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
}
|
| 147 |
|
| 148 |
.guide-content h2,
|
|
@@ -163,6 +168,7 @@ body[data-guide="hidden"] #guidePanel {
|
|
| 163 |
background: rgba(15, 23, 42, 0.85);
|
| 164 |
border: 1px solid rgba(148, 163, 184, 0.35);
|
| 165 |
overflow-x: auto;
|
|
|
|
| 166 |
}
|
| 167 |
|
| 168 |
.guide-content a {
|
|
@@ -179,6 +185,30 @@ body[data-guide="hidden"] #guidePanel {
|
|
| 179 |
margin-top: 0.2rem;
|
| 180 |
}
|
| 181 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
.card h2,
|
| 183 |
.card h3 {
|
| 184 |
margin: 0.25rem 0;
|
|
|
|
| 143 |
flex-direction: column;
|
| 144 |
gap: 0.75rem;
|
| 145 |
line-height: 1.5;
|
| 146 |
+
user-select: text;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
.guide-content * {
|
| 150 |
+
user-select: text;
|
| 151 |
}
|
| 152 |
|
| 153 |
.guide-content h2,
|
|
|
|
| 168 |
background: rgba(15, 23, 42, 0.85);
|
| 169 |
border: 1px solid rgba(148, 163, 184, 0.35);
|
| 170 |
overflow-x: auto;
|
| 171 |
+
margin: 0;
|
| 172 |
}
|
| 173 |
|
| 174 |
.guide-content a {
|
|
|
|
| 185 |
margin-top: 0.2rem;
|
| 186 |
}
|
| 187 |
|
| 188 |
+
.guide-code-wrapper {
|
| 189 |
+
position: relative;
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
.guide-copy-button {
|
| 193 |
+
position: absolute;
|
| 194 |
+
top: 0.35rem;
|
| 195 |
+
right: 0.35rem;
|
| 196 |
+
border: 1px solid rgba(148, 163, 184, 0.5);
|
| 197 |
+
background: rgba(2, 6, 23, 0.8);
|
| 198 |
+
color: var(--text);
|
| 199 |
+
border-radius: 999px;
|
| 200 |
+
padding: 0.1rem 0.7rem;
|
| 201 |
+
font-size: 0.7rem;
|
| 202 |
+
text-transform: uppercase;
|
| 203 |
+
letter-spacing: 0.05em;
|
| 204 |
+
cursor: pointer;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
.guide-copy-button.copied {
|
| 208 |
+
color: #34d399;
|
| 209 |
+
border-color: rgba(52, 211, 153, 0.7);
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
.card h2,
|
| 213 |
.card h3 {
|
| 214 |
margin: 0.25rem 0;
|