/* Copyright (c) William Toohey * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ (function(window, document) { "use strict"; function HuesEditor(core) { this.buildEditSize = 80; // pixels, including header this.buildEdit = null; this.loopEdit = null; this.editArea = null; this.wrapAt = 16; this.hilightWidth = 0; this.hilightHeight = 0; this.undoBuffer = []; this.redoBuffer = []; // for storing respacks created with "new" this.respack = null; // when we're actually following the playing song this.linked = false; this.core = core; this.root = document.getElementById("huesEditor"); if(!this.root) { return; } if(!core.settings.defaults.noUI) { this.initUI(); core.addEventListener("beat", this.onBeat.bind(this)); core.addEventListener("newsong", this.onNewSong.bind(this)); } } HuesEditor.prototype.initUI = function() { var titleButtons = document.createElement("div"); titleButtons.id = "edit-titlebuttons"; this.root.appendChild(titleButtons); this.saveBtn = this.createButton("Save XML", titleButtons, true); this.saveBtn.onclick = this.saveXML.bind(this); this.copyBtn = this.createButton("Copy XML", titleButtons, true); this.copyBtn.onclick = this.copyXML.bind(this); this.undoBtn = this.createButton("Undo", titleButtons, true); this.redoBtn = this.createButton("Redo", titleButtons, true); var help = this.createButton("Help?", titleButtons); help.style.backgroundColor = "rgba(0,160,0,0.3)"; help.onclick = function() { window.open("http://0x40hues.blogspot.com/p/0x40-hues-creation-tutorial.html", '_blank'); }; this.topBar = document.createElement("div"); this.topBar.id = "edit-topbar"; this.root.appendChild(this.topBar); this.uiCreateInfo(); this.uiCreateImport(); this.root.appendChild(document.createElement("hr")); this.uiCreateEditArea(); this.uiCreateControls(); this.uiCreateVisualiser(); window.addEventListener('resize', this.resize.bind(this)); // Fix Chrome rendering - redraw on tab load document.getElementById("tab-editor").addEventListener("change", this.resize.bind(this)); this.resize(); }; HuesEditor.prototype.resize = function(noHilightCalc) { this.root.style.height = (window.innerHeight - 200) + "px"; var boxHeight = this.editArea.offsetHeight; var bHeadHeight = this.buildEdit._header.offsetHeight; var lHeadHeight = this.loopEdit._header.offsetHeight; var handleHeight = this.resizeHandle.offsetHeight; var minHeight = bHeadHeight; var maxHeight = boxHeight - handleHeight - lHeadHeight - bHeadHeight; var buildHeight = Math.min(maxHeight, Math.max(minHeight, this.buildEditSize - handleHeight)); this.buildEdit.style.height = buildHeight + "px"; this.buildEdit._box.style.height = (buildHeight - bHeadHeight) + "px"; var loopHeight = maxHeight - buildHeight + lHeadHeight; this.loopEdit.style.height = loopHeight + "px"; this.loopEdit._box.style.height = (loopHeight - lHeadHeight) + "px"; // For window resizing down situation if(this.editArea.offsetHeight != boxHeight) { this.resize(); } // Resize the time lock this.timeLock.style.height = (buildHeight + handleHeight) + "px"; // Save to fix Chrome rendering and to enable right click to seek // We only resize on a window resize event, not when dragging the handle if(!noHilightCalc) { var hilight = document.createElement("div"); hilight.className = "beat-hilight"; hilight.innerHTML = "█"; this.root.appendChild(hilight); this.hilightWidth = hilight.clientWidth; this.hilightHeight = hilight.clientHeight; this.editorWidth = this.loopEdit._beatmap.clientWidth; this.root.removeChild(hilight); } } HuesEditor.prototype.createTextInput = function(label, id, subtitle, parent) { var div = document.createElement("div"); div.className = "edit-label"; var caption = document.createElement("label"); caption.innerHTML = label; caption.htmlFor = id; div.appendChild(caption); var container = document.createElement("span"); container.className = "edit-textbox-container"; var input = document.createElement("input"); input.className = "edit-textbox"; input.type = "text"; input.id = id; input.value = subtitle; container.appendChild(input); div.appendChild(container); parent.appendChild(div); return input; } HuesEditor.prototype.createButton = function(label, parent, disabled, extraClass) { var button = document.createElement("span"); button.className = "hues-button"; if(disabled) { button.className += " disabled"; } if(extraClass) { button.className += " " + extraClass; } button.innerHTML = label.toUpperCase(); parent.appendChild(button); return button; } HuesEditor.prototype.uiCreateInfo = function() { var info = document.createElement("div"); this.topBar.appendChild(info); info.id = "edit-info"; var songUpdate = function(name) { if(!this.song ) { return; } this.song[name] = this[name].value; if(this.song != this.core.currentSong) { return; } this.core.callEventListeners("newsong", this.song); } this.title = this.createTextInput("Title:", "edit-title", "Song name", info); this.title.oninput = songUpdate.bind(this, "title"); this.source = this.createTextInput("Link: ", "edit-source", "Source link", info); this.source.oninput = songUpdate.bind(this, "source"); }; HuesEditor.prototype.onNewSong = function(song) { if(this.linked) { if(song == this.song) { // Because you can "edit current" before it loads this.updateInfo(); } else { this.linked = false; // Clear beat hilight this.buildEdit._hilight.innerHTML = "█"; this.loopEdit._hilight.innerHTML = "█"; this.buildEdit._hilight.className = "beat-hilight hidden"; this.loopEdit._hilight.className = "beat-hilight hidden"; } } } HuesEditor.prototype.onBeat = function(map, index) { if(!this.song || this.core.currentSong != this.song) { return; } var editor; if(index < 0) { index += this.core.currentSong.buildupRhythm.length; editor = this.buildEdit; this.loopEdit._hilight.className = "beat-hilight hidden"; } else { editor = this.loopEdit; if(this.song.buildup) { this.buildEdit._hilight.className = "beat-hilight hidden"; } } editor._hilight.className = "beat-hilight"; var offsetX = index % editor._breakAt; var offsetY = Math.floor(index / editor._breakAt); // Not computing width/height here due to Chrome bug editor._hilight.style.left = (offsetX * this.hilightWidth) + "px"; editor._hilight.style.top = (offsetY * this.hilightHeight) + "px"; } HuesEditor.prototype.reflow = function(editor, map) { if(!map) { // NOTHING TO SEE HERE editor._beatmap.textContent = ""; editor._hilight.textContent = "[none]"; editor._hilight.className = "beat-hilight"; editor._hilight.style.top = "0"; editor._hilight.style.left = "0"; editor._beatCount.textContent = "0 beats"; return; } else { editor._hilight.innerHTML = "█"; } var charsPerLine = Math.floor(this.editorWidth / this.hilightWidth); // if it's too long to wrap, just give up var wrap = Math.min(this.wrapAt, charsPerLine); charsPerLine -= charsPerLine % wrap; editor._beatCount.textContent = map.length + " beats"; // http://stackoverflow.com/a/27012001 var regex = new RegExp("(.{" + charsPerLine + "})", "g"); editor._beatmap.innerHTML = map.replace(regex, "$1
"); editor._breakAt = charsPerLine; } HuesEditor.prototype.loadAudio = function(editor) { if(editor._fileInput.files.length < 1) { return; } // Disable load button TODO var file = editor._fileInput.files[0]; // load audio this.blobToArrayBuffer(file) .then(buffer => { // If first load, this makes fresh, gets the core synced up this.newSong(this.song); this.song[editor._sound] = buffer; // Save filename for XML export var noExt = file.name.replace(/\.[^/.]+$/, ""); if(editor._sound == "sound") { this.song.name = noExt; } else { this.song.buildupName = noExt; } // make empty map if needed if(!this.getText(editor)) { this.setText(editor, "x...o...x...o..."); } // Do we have a loop to play? if(this.song.sound) { // Force refresh return this.core.soundManager.playSong(this.song, true, true); } }).then(() => { this.updateInfo(); this.reflow(editor, this.getText(editor)); this.core.updateBeatLength(); // We may have to go backwards in time this.core.recalcBeatIndex(); }).catch(error => { console.log(error); alert("Couldn't load song! Is it a LAME encoded MP3?"); }); } HuesEditor.prototype.blobToArrayBuffer = function(blob) { return new Promise((resolve, reject) => { var fr = new FileReader(); fr.onload = () => { resolve(fr.result); }; fr.onerror = () => { reject(new Error("File read failed!")); }; fr.readAsArrayBuffer(blob); }); } HuesEditor.prototype.newSong = function(song) { if(!song) { song = {"name":"Name", "title":"Title", "rhythm":"", "source":"", "sound":null, "enabled":true, "filename":null, "charsPerBeat": null}; if(!this.respack) { this.respack = new Respack(); this.respack.name = "Editor Respack"; this.respack.author = "You!"; this.respack.description = "An internal resourcepack for editing new songs"; this.core.resourceManager.addPack(this.respack); } this.respack.songs.push(song); this.core.resourceManager.rebuildArrays(); this.core.resourceManager.rebuildEnabled(); this.core.setSongOject(song); } // Clear instructions this.buildEdit._hilight.className = "beat-hilight hidden"; this.loopEdit._hilight.className = "beat-hilight hidden"; // Clear helpful glows this.newSongBtn.classList.remove("glow"); this.fromSongBtn.classList.remove("glow"); this.clearUndoRedo(); this.song = song; this.reflow(this.buildEdit, song.buildupRhythm); this.reflow(this.loopEdit, song.rhythm); this.title.value = song.title; this.source.value = song.source; // Unlock beatmap lengths this.setLocked(this.buildEdit, 0); this.setLocked(this.loopEdit, 0); this.linked = true; this.updateInfo(); } HuesEditor.prototype.updateInfo = function() { if(!this.linked) { return; } var loopLen = this.core.soundManager.loopLength; var buildLen = this.core.soundManager.buildLength; var beatLen = (loopLen / this.song.rhythm.length) * 1000; this.loopLen.textContent = loopLen.toFixed(2); this.buildLen.textContent = buildLen.toFixed(2); this.beatLen.textContent = beatLen.toFixed(2); // Avoid a bunch of nested elses this.seekStart.className = "hues-button disabled"; this.seekLoop.className = "hues-button disabled"; this.saveBtn.className = "hues-button disabled"; this.copyBtn.className = "hues-button disabled"; this.buildEdit._removeBtn.className = "hues-button disabled"; this.loopEdit._removeBtn.className = "hues-button disabled"; if(this.song) { this.saveBtn.className = "hues-button"; this.copyBtn.className = "hues-button"; if(this.song.sound) { this.seekLoop.className = "hues-button"; this.loopEdit._removeBtn.className = "hues-button"; if(this.song.buildup) { this.seekStart.className = "hues-button"; this.buildEdit._removeBtn.className = "hues-button"; } } } } HuesEditor.prototype.pushUndo = function(name, editor, oldText, newText) { if(oldText == newText) { return; } this.redoBuffer = []; this.undoBuffer.push({songVar: name, editor: editor, text: oldText}); while(this.undoBuffer.length > 50) { this.undoBuffer.pop(); } this.updateUndoUI(); } HuesEditor.prototype.undo = function() { this.undoRedo(this.undoBuffer, this.redoBuffer); } HuesEditor.prototype.redo = function() { this.undoRedo(this.redoBuffer, this.undoBuffer); } HuesEditor.prototype.undoRedo = function(from, to) { if(from.length == 0 || !this.song) { return; } // Remove old data var fromData = from.pop(); // Make restore from current to.push({songVar: fromData.songVar, editor: fromData.editor, text: this.song[fromData.songVar]}); // Restore to editor this.song[fromData.songVar] = fromData.text; this.reflow(fromData.editor, this.song[fromData.songVar]); this.updateUndoUI(); this.updateHalveDoubleButtons(); this.core.updateBeatLength(); this.core.recalcBeatIndex(); } HuesEditor.prototype.clearUndoRedo = function() { this.undoBuffer = []; this.redoBuffer = []; this.updateUndoUI(); } HuesEditor.prototype.updateUndoUI = function() { this.undoBtn.className = "hues-button disabled"; this.undoBtn.onclick = null; this.redoBtn.className = "hues-button disabled"; this.redoBtn.onclick = null; if(this.undoBuffer.length > 0) { this.undoBtn.classList.remove("disabled"); this.undoBtn.onclick = this.undo.bind(this); } if(this.redoBuffer.length > 0) { this.redoBtn.classList.remove("disabled"); this.redoBtn.onclick = this.redo.bind(this); } } HuesEditor.prototype.halveBeats = function(editor) { if(!this.song || this.getText(editor).length < 2) { return; } this.setText(editor, this.song[editor._rhythm].replace(/(.)./g, "$1")); } HuesEditor.prototype.doubleBeats = function(editor) { if(!this.song || this.getText(editor).length == 0) { return; } this.setText(editor, this.song[editor._rhythm].replace(/(.)/g, "$1.")); } HuesEditor.prototype.uiCreateImport = function() { var imports = document.createElement("div"); this.topBar.appendChild(imports); imports.id = "edit-imports"; var songEdits = document.createElement("div"); imports.appendChild(songEdits); var newSongBtn = this.createButton("New song", songEdits, false, "glow"); newSongBtn.onclick = () => { this.newSong(); }; this.newSongBtn = newSongBtn; var fromSong = this.createButton("Edit current song", songEdits, false, "glow"); fromSong.onclick = () => { if(this.core.currentSong) { this.newSong(this.core.currentSong); } }; this.fromSongBtn = fromSong; var songInfos = document.createElement("div"); songInfos.className = "settings-individual"; songInfos.id = "edit-songstats"; imports.appendChild(songInfos); this.loopLen = this.uiCreateSongStat("Loop length (s):", "0.00", songInfos); this.buildLen = this.uiCreateSongStat("Build length (s):", "0.00", songInfos); this.beatLen = this.uiCreateSongStat("Beat length (ms):", "0.00", songInfos); }; HuesEditor.prototype.uiCreateSongStat = function(name, value, parent) { var container = document.createElement("div"); parent.appendChild(container); var label = document.createElement("span"); label.textContent = name; container.appendChild(label); var valueSpan = document.createElement("span"); valueSpan.textContent = value; valueSpan.className = "edit-songstat-value"; container.appendChild(valueSpan); return valueSpan; } HuesEditor.prototype.uiCreateEditArea = function() { var editArea = document.createElement("div"); this.editArea = editArea; editArea.id = "edit-area"; this.root.appendChild(editArea); // Lock build/loop lengths this.timeLock = document.createElement("div"); editArea.appendChild(this.timeLock); this.timeLock.id = "edit-timelock"; this.timeLock.className = "hues-icon"; // CHAIN, use  for CHAIN-BROKEN this.createButton("", this.timeLock) this.buildEdit = this.uiCreateSingleEditor("Buildup ", "buildup", "buildupRhythm", "edit-build", editArea); // drag handle var handleContainer = document.createElement("div"); handleContainer.id = "edit-resize-handle-container"; editArea.appendChild(handleContainer); var handle = document.createElement("div"); handle.id = 'edit-resize-handle'; handle.className = 'hues-icon'; handle.innerHTML = ""; // DRAG HANDLE handleContainer.appendChild(handle); this.resizeHandle = handleContainer; handleContainer.addEventListener("mousedown", (e) => { e.preventDefault(); var editTop = this.editArea.getBoundingClientRect().top; var handleSize = this.resizeHandle.clientHeight; var resizer = (e) => { this.buildEditSize = Math.floor(e.clientY - editTop + handleSize/2); this.resize(true); }; var mouseup = function(e) { document.removeEventListener("mousemove", resizer); document.removeEventListener("mouseup", mouseup); }; document.addEventListener("mousemove", resizer); document.addEventListener("mouseup", mouseup); }); this.loopEdit = this.uiCreateSingleEditor("Rhythm  ", "sound", "rhythm", "edit-loop", editArea); this.buildEdit._hilight.textContent = "[none]"; this.loopEdit._hilight.innerHTML = '
\ Click [LOAD RHYTHM] to load a loop! LAME encoded MP3s work best.
\ (LAME is important for seamless MP3 loops)
\
\ [DOUBLE] doubles the selected map length by padding it with "."s.
\ [HALVE] shortens the map length by removing every other character.
\ [<< START] starts the song from the beginning.
\ [< LOOP] starts the song from the start of the loop.
\
\ You can also add a buildup with [LOAD BUILDUP], or remove it
\ with [REMOVE].
\
\ [NEW SONG] adds a completely empty song for you to edit, and
\ [EDIT CURRENT SONG] takes the current playing song to the editor.
\
\ [COPY/SAVE XML] allow for storing the rhythms and easy
\ inclusion into a Resource Pack!'; }; HuesEditor.prototype.uiCreateSingleEditor = function(title, soundName, rhythmName, id, parent) { var container = document.createElement("div"); container.id = id; parent.appendChild(container); var header = document.createElement("div"); header.className = "edit-area-header"; container.appendChild(header); var nameLabel = document.createElement("span"); header.appendChild(nameLabel); nameLabel.innerHTML = title; var beatCount = document.createElement("span"); header.appendChild(beatCount); beatCount.className = "beat-count"; beatCount.textContent = "0 beats"; container._lockedBtn = this.createButton("", header, false, "hues-icon"); container._lockedBtn.onclick = () => { if(container._locked) { this.setLocked(container, 0); } else { var textLen = this.getText(container).length; this.setLocked(container, textLen); } }; var rightHeader = document.createElement("span"); rightHeader.className = "edit-area-right-header"; header.appendChild(rightHeader); container._halveBtn = this.createButton("Halve", rightHeader); container._halveBtn.onclick = this.halveBeats.bind(this, container) container._doubleBtn = this.createButton("Double", rightHeader); container._doubleBtn.onclick = this.doubleBeats.bind(this, container) var fileInput = document.createElement("input"); fileInput.type ="file"; fileInput.accept="audio/mpeg3"; fileInput.multiple = false; fileInput.onchange = this.loadAudio.bind(this, container); var load = this.createButton("Load " + title.replace(/ /g,""), rightHeader); load.onclick = () => {fileInput.click()}; container._removeBtn = this.createButton("Remove", rightHeader, true); var editBox = document.createElement("div"); editBox.className = "edit-box"; var beatmap = document.createElement("div"); beatmap.className = "beatmap"; beatmap.contentEditable = true; beatmap.spellcheck = false; beatmap.oninput = this.textUpdated.bind(this, container); beatmap.oncontextmenu = this.rightClick.bind(this, container); var beatHilight = document.createElement("div"); beatHilight.className = "beat-hilight"; editBox.appendChild(beatHilight); editBox.appendChild(beatmap); container.appendChild(editBox); container._header = header; container._beatCount = beatCount; container._box = editBox; container._beatmap = beatmap; container._hilight = beatHilight; container._fileInput = fileInput; container._sound = soundName; container._rhythm = rhythmName; // Are we in insert mode? Default = no this.setLocked(container, 0); return container; } HuesEditor.prototype.rightClick = function(editor, event) { // We abuse the fact that right clicking moves the caret. Hooray! var caret = this.getCaret(editor._beatmap); var totalLen = this.getText(editor).length; var percent = caret / totalLen; if(caret <= totalLen) { var seekTime = 0; if(editor._rhythm == "rhythm") { // loop seekTime = this.core.soundManager.loopLength * percent; } else { // build var bLen = this.core.soundManager.buildLength; seekTime = -bLen + bLen * percent; } this.core.soundManager.seek(seekTime); event.preventDefault(); return false; } else { return true; } } HuesEditor.prototype.textUpdated = function(editor) { if(!this.song || !this.song[editor._sound]) { this.reflow(editor, ""); return; } // Space at start of line is nonbreaking, get it with \u00a0 var input = editor._beatmap.textContent.replace(/ |\u00a0/g, ""); if(input.length == 0) { input = "."; } this.setText(editor, input); } HuesEditor.prototype.getText = function(editor) { if(!this.song || !this.song[editor._rhythm]) { return ""; } else { return this.song[editor._rhythm]; } }; HuesEditor.prototype.setText = function(editor, text) { if(!this.song || !this.song[editor._sound]) { this.reflow(editor, ""); return; } var caret = this.getCaret(editor._beatmap); if(editor._locked) { caret = Math.min(editor._locked, caret); if(text.length > editor._locked) { // Works for pastes too! Removes the different between sizes from the caret position text = text.slice(0, caret) + text.slice(caret + (text.length - editor._locked), text.length) } else { while(text.length < editor._locked) { text += "."; } } } this.pushUndo(editor._rhythm, editor, this.song[editor._rhythm], text); this.song[editor._rhythm] = text this.reflow(editor, this.song[editor._rhythm]); this.setCaret(editor._beatmap, caret); this.updateHalveDoubleButtons(editor); this.core.updateBeatLength(); // We may have to go backwards in time this.core.recalcBeatIndex(); this.updateInfo(); } HuesEditor.prototype.getCaret = function(editable) { var caret = 0; var sel = window.getSelection(); if (sel.rangeCount) { var range = sel.getRangeAt(0); //
elements are empty, and pastes do weird things. // So don't go up in multiples of 2 for getCaret for(var i = 0; i < editable.childNodes.length; i++) { if (range.commonAncestorContainer == editable.childNodes[i]) { caret += range.endOffset; return caret; } else { caret += editable.childNodes[i].textContent.length; } } } return 0; } HuesEditor.prototype.setCaret = function(editable, caret) { var range = document.createRange(); var sel = window.getSelection(); //
elements mean children go up in multiples of 2 for(var i = 0; i < editable.childNodes.length; i+= 2) { var textLen = editable.childNodes[i].textContent.length; if(caret > textLen) { caret -= textLen; } else { range.setStart(editable.childNodes[i], caret); range.collapse(true); sel.removeAllRanges(); sel.addRange(range); break; } } } HuesEditor.prototype.updateHalveDoubleButtons = function(editor) { editor._halveBtn.className = "hues-button disabled"; editor._doubleBtn.className = "hues-button disabled"; if(!editor._locked) { var txtLen = this.getText(editor).length; if(txtLen > 0) { editor._doubleBtn.className = "hues-button"; } if(txtLen > 1) { editor._halveBtn.className = "hues-button"; } } } HuesEditor.prototype.setLocked = function(editor, locked) { editor._locked = locked; if(locked) { editor._lockedBtn.innerHTML = ""; // LOCKED } else { editor._lockedBtn.innerHTML = ""; // UNLOCKED } this.updateHalveDoubleButtons(editor); } HuesEditor.prototype.uiCreateControls = function() { var controls = document.createElement("div"); controls.id = "edit-controls"; this.root.appendChild(controls); this.seekStart = this.createButton("<< Start", controls, true); this.seekStart.onclick = () => { this.core.soundManager.seek(-this.core.soundManager.buildLength); }; this.seekLoop = this.createButton("< Loop", controls, true); this.seekLoop.onclick = () => { this.core.soundManager.seek(0); }; var playRateLab = document.createElement("span"); playRateLab.className = "settings-individual"; playRateLab.textContent = "1.00x"; controls.appendChild(playRateLab); var changeRate = function(change) { var rate = this.core.soundManager.playbackRate; rate += change; this.core.soundManager.setRate(rate); // In case it gets clamped, check var newRate = this.core.soundManager.playbackRate; playRateLab.textContent = newRate.toFixed(2) + "x"; } // BACKWARD var speedDown = this.createButton("", controls, false, "hues-icon"); speedDown.onclick = changeRate.bind(this, -0.25); // FORWARD var speedUp = this.createButton("", controls, false, "hues-icon"); speedUp.onclick = changeRate.bind(this, 0.25); var wrapLab = document.createElement("span"); wrapLab.className = "settings-individual"; wrapLab.textContent = "New line at beat "; controls.appendChild(wrapLab); var wrapAt = document.createElement("input"); wrapAt.className = "settings-input"; wrapAt.value = this.wrapAt; wrapAt.type = "text"; wrapAt.oninput = () => { wrapAt.value = wrapAt.value.replace(/\D/g,''); if(wrapAt.value == "" || wrapAt.value < 1) { wrapAt.value = ""; return; } this.wrapAt = parseInt(wrapAt.value); this.reflow(this.buildEdit, this.song.buildupRhythm); this.reflow(this.loopEdit, this.song.rhythm); }; controls.appendChild(wrapAt); }; HuesEditor.prototype.uiCreateVisualiser = function() { // TODO placeholder var waveDiv = document.createElement("div"); waveDiv.id = "edit-waveform"; this.root.appendChild(waveDiv); }; HuesEditor.prototype.generateXML = function() { if(!this.song) { return null; } // Yes, this is just a bunch of strings. Simple XML, simple method. var result = " \n"; result += " " + this.song.title + "\n"; if(this.song.source) { result += " " + this.song.source + "\n"; } result += " " + this.song.rhythm + "\n"; if(this.song.buildup) { result += " " + this.song.buildupName + "\n"; result += " " + this.song.buildupRhythm + "\n"; if(this.song.independentBuild) { result += " true\n"; } } result += " \n"; return result; } HuesEditor.prototype.saveXML = function() { var xml = this.generateXML(); if(!xml) { return; } var result = "\n"; result += xml; result += "\n"; // http://stackoverflow.com/a/18197341 var element = document.createElement('a'); element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result)); element.setAttribute('download', "0x40Hues - " + this.song.name + ".xml"); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element); } // http://stackoverflow.com/a/30810322 HuesEditor.prototype.copyXML = function() { var text = this.generateXML(); // Clicking when disabled if(!text) { return; } var textArea = document.createElement("textarea"); textArea.id = "edit-copybox"; textArea.value = text; document.body.appendChild(textArea); textArea.select(); var success; try { success = document.execCommand('copy'); } catch (err) { success = false; } document.body.removeChild(textArea); if(success) { alert("Beatmap XML copied to clipboard!"); } else { alert("Copy failed! Try saving instead"); } } window.HuesEditor = HuesEditor; })(window, document);