diff --git a/backend/melodies/models.py b/backend/melodies/models.py index 20ef422..9364cbc 100644 --- a/backend/melodies/models.py +++ b/backend/melodies/models.py @@ -28,6 +28,7 @@ class MelodyInfo(BaseModel): color: str = "" isTrueRing: bool = False previewURL: str = "" + archetype_csv: Optional[str] = None class MelodyAttributes(BaseModel): diff --git a/frontend/src/melodies/MelodyDetail.jsx b/frontend/src/melodies/MelodyDetail.jsx index 7211742..46c89fa 100644 --- a/frontend/src/melodies/MelodyDetail.jsx +++ b/frontend/src/melodies/MelodyDetail.jsx @@ -5,6 +5,25 @@ import { useAuth } from "../auth/AuthContext"; import ConfirmDialog from "../components/ConfirmDialog"; import SpeedCalculatorModal from "./SpeedCalculatorModal"; import PlaybackModal from "./PlaybackModal"; + +function fallbackCopy(text, onSuccess) { + const ta = document.createElement("textarea"); + ta.value = text; + ta.style.cssText = "position:fixed;top:0;left:0;opacity:0"; + document.body.appendChild(ta); + ta.focus(); + ta.select(); + try { document.execCommand("copy"); onSuccess?.(); } catch (_) {} + document.body.removeChild(ta); +} + +function copyText(text, onSuccess) { + if (navigator.clipboard) { + navigator.clipboard.writeText(text).then(onSuccess).catch(() => fallbackCopy(text, onSuccess)); + } else { + fallbackCopy(text, onSuccess); + } +} import { getLocalizedValue, getLanguageName, @@ -415,12 +434,7 @@ export default function MelodyDetail() {