Merge pull request #89 from LoveEevee/songsel-add-browse-for-local-songs-button

SongSel: Add browse for local songs button
This commit is contained in:
Bui 2018-12-05 21:37:40 +00:00 committed by GitHub
commit ca90d4aea6
9 changed files with 285 additions and 74 deletions

View File

@ -19,7 +19,8 @@ body{
background-size: 30vh;
font-family: TnT, Meiryo, sans-serif;
}
#assets{
#assets,
#browse{
display: none;
}
.window{

View File

@ -51,8 +51,8 @@
ideographicComma: /[、。]/,
apostrophe: /[']/,
degree: /[゚°]/,
brackets: /[\(\)「」『』]/,
tilde: /[\-~~〜]/,
brackets: /[\(\)\[\]「」『』【】]/,
tilde: /[\-~~〜_]/,
tall: /[bdfghj-l-t♪]/,
i: /[i]/,
uppercase: /[A-Z-]/,
@ -68,7 +68,8 @@
em: /[mw]/,
emCap: /[MW]/,
rWidth: /[abdfIjo-rtv-]/,
lWidth: /[il]/
lWidth: /[il]/,
ura: /\s*[\(]裏[\)]$/
}
var numbersFull = ""
@ -276,13 +277,18 @@
var ctx = config.ctx
var inputText = config.text
var mul = config.fontSize / 40
var ura = false
var r = this.regex
var matches = inputText.match(r.ura)
if(matches){
inputText = inputText.slice(0, matches.index)
ura = matches[0]
}
var string = inputText.split("")
var drawn = []
var r = this.regex
var previousSymbol = ""
for(var i = 0; i < string.length; i++){
let symbol = string[i]
if(symbol === " "){
@ -297,6 +303,8 @@
drawn.push({text: symbol, x: 0, y: 12, h: 45})
}else if(symbol === ""){
drawn.push({realText: symbol, text: ".", x: 13, y: -7, h: 15, scale: [1.2, 0.7]})
}else if(symbol === "…"){
drawn.push({text: symbol, x: 0, y: 5, h: 25, rotate: true})
}else if(r.comma.test(symbol)){
// Comma, full stop
drawn.push({text: symbol, x: 13, y: -7, h: 15, scale: [1.2, 0.7]})
@ -408,22 +416,28 @@
}
var scaling = 1
if(config.height && drawnHeight > config.height){
var height = config.height - (ura ? 52 * mul : 0)
if(height && drawnHeight > height){
if(config.align === "bottom"){
scaling = Math.max(0.6, config.height / drawnHeight)
scaling = Math.max(0.6, height / drawnHeight)
ctx.translate(40 * mul, 0)
ctx.scale(scaling, config.height / drawnHeight)
ctx.scale(scaling, height / drawnHeight)
ctx.translate(-40 * mul, 0)
}else{
scaling = config.height / drawnHeight
scaling = height / drawnHeight
ctx.scale(1, scaling)
}
if(config.selectable){
style.transform = "scale(1, " + scaling + ")"
style.top = (config.y + (config.height - drawnHeight) / 2 - 15 / 2 * scaling) * scale + "px"
style.top = (config.y + (height - drawnHeight) / 2 - 15 / 2 * scaling) * scale + "px"
}
}
if(ura){
// Circled ura
drawn.push({realText: ura, text: "裏", x: 0, y: 18, h: 52, ura: true, scale: [1, 1 / scale]})
}
var actions = []
if(config.outline){
actions.push("stroke")
@ -492,7 +506,7 @@
config.selectable.appendChild(div)
continue
}
if(symbol.rotate || symbol.scale || symbol.svg){
if(symbol.rotate || symbol.scale || symbol.svg || symbol.ura){
saved = true
ctx.save()
@ -517,7 +531,23 @@
}else{
ctx.textAlign = "center"
}
ctx[action + "Text"](symbol.text, currentX, currentY)
if(symbol.ura){
ctx.font = (30 * mul) + "px Meiryo, sans-serif"
ctx.textBaseline = "center"
ctx.beginPath()
ctx.arc(currentX, currentY + (21.5 * mul), (18 * mul), 0, Math.PI * 2)
if(action === "stroke"){
ctx.fillStyle = config.outline
ctx.fill()
}else if(action === "fill"){
ctx.strokeStyle = config.fill
ctx.lineWidth = 2.5 * mul
ctx.fillText(symbol.text, currentX, currentY)
}
ctx.stroke()
}else{
ctx[action + "Text"](symbol.text, currentX, currentY)
}
}
if(saved){
ctx.restore()

View File

@ -78,7 +78,8 @@ class Loader{
})
this.promises.push(this.ajax("/api/songs").then(songs => {
assets.songs = JSON.parse(songs)
assets.songsDefault = JSON.parse(songs)
assets.songs = assets.songsDefault
}))
assets.views.forEach(name => {

View File

@ -66,16 +66,17 @@ class loadSong{
}
promises.push(this.loadSongBg(id))
var songObj = assets.songs.find(song => song.id === id)
promises.push(new Promise((resolve, reject) => {
var songObj
assets.songs.forEach(song => {
if(song.id == id){
songObj = song
}
})
if(songObj.sound){
songObj.sound.gain = snd.musicGain
resolve()
}else if(songObj.music){
snd.musicGain.load(songObj.music, true).then(sound => {
songObj.sound = sound
resolve()
}, reject)
}else{
snd.musicGain.load(gameConfig.songs_baseurl + id + "/main.mp3").then(sound => {
songObj.sound = sound
@ -83,9 +84,13 @@ class loadSong{
}, reject)
}
}))
promises.push(loader.ajax(this.getSongPath(song)).then(data => {
this.songData = data.replace(/\0/g, "").split("\n")
}))
if(songObj.chart){
this.songData = songObj.chart
}else{
promises.push(loader.ajax(this.getSongPath(song)).then(data => {
this.songData = data.replace(/\0/g, "").split("\n")
}))
}
Promise.all(promises).then(() => {
this.setupMultiplayer()
}, error => {

View File

@ -1,5 +1,5 @@
class ParseOsu{
constructor(fileContent, offset){
constructor(fileContent, offset, metaOnly){
this.osu = {
OFFSET: 0,
MSPERBEAT: 1,
@ -52,9 +52,11 @@ class ParseOsu{
this.metadata = this.parseMetadata()
this.editor = this.parseEditor()
this.difficulty = this.parseDifficulty()
this.timingPoints = this.parseTiming()
this.circles = this.parseCircles()
this.measures = this.parseMeasures()
if(!metaOnly){
this.timingPoints = this.parseTiming()
this.circles = this.parseCircles()
this.measures = this.parseMeasures()
}
}
getStartEndIndexes(type){
var indexes = {
@ -186,40 +188,20 @@ class ParseOsu{
return measures
}
parseGeneralInfo(){
var generalInfo = {
audioFilename: "",
audioWait: 0
}
var generalInfo = {}
var indexes = this.getStartEndIndexes("General")
for(var i = indexes.start; i<= indexes.end; i++){
var [item, key] = this.data[i].split(":")
switch(item){
case "SliderMultiple":
generalInfo.audioFilename = key
break
case "AudioWait":
generalInfo.audioWait = parseInt(key)
break
}
generalInfo[item] = key.trim()
}
return generalInfo
}
parseMetadata(){
var metadata = {
title: "",
artist: ""
}
var metadata = {}
var indexes = this.getStartEndIndexes("Metadata")
for(var i = indexes.start; i <= indexes.end; i++){
var [item, key] = this.data[i].split(":")
switch(item){
case "TitleUnicode":
metadata.title = key
break
case "ArtistUnicode":
metadata.artist = key
break
}
metadata[item] = key.trim()
}
return metadata
}

View File

@ -1,5 +1,5 @@
class ParseTja{
constructor(file, difficulty, offset){
constructor(file, difficulty, offset, metaOnly){
this.data = []
for(let line of file){
line = line.replace(/\/\/.*/, "").trim()
@ -34,10 +34,12 @@
this.metadata = this.parseMetadata()
this.measures = []
this.beatInfo = {}
this.circles = this.parseCircles()
if(!metaOnly){
this.circles = this.parseCircles()
}
}
parseMetadata(){
var metaNumbers = ["bpm", "offset"]
var metaNumbers = ["bpm", "offset", "demostart", "level"]
var inSong = false
var courses = {}
var currentCourse = {}

View File

@ -35,6 +35,12 @@ class SongSelect{
border: ["#dff0ff", "#6890b2"],
outline: "#217abb"
},
"browse": {
sort: 7,
background: "#9791ff",
border: ["#e2dfff", "#6d68b2"],
outline: "#5350ba"
},
"J-POP": {
sort: 0,
background: "#219fbb",
@ -98,7 +104,8 @@ class SongSelect{
preview: song.preview || 0,
type: song.type,
offset: song.offset,
songSkin: song.song_skin || {}
songSkin: song.song_skin || {},
music: song.music
})
}
this.songs.sort((a, b) => {
@ -139,6 +146,17 @@ class SongSelect{
action: "about",
category: "ランダム"
})
if("webkitdirectory" in HTMLInputElement.prototype && !(/Android|iPhone|iPad/.test(navigator.userAgent))){
this.browse = document.getElementById("browse")
pageEvents.add(this.browse, "change", this.browseChange.bind(this))
this.songs.push({
title: assets.customSongs ? "デフォルト曲リスト" : "参照する…",
skin: this.songSkin.browse,
action: "browse",
category: "ランダム"
})
}
this.songs.push({
title: "もどる",
skin: this.songSkin.back,
@ -193,7 +211,7 @@ class SongSelect{
this.selectedDiff = 0
assets.sounds["bgm_songsel"].playLoop(0.1, false, 0, 1.442, 3.506)
if(!fromTutorial && !("selectedSong" in localStorage)){
if(!assets.customSongs && !fromTutorial && !("selectedSong" in localStorage)){
fromTutorial = touchEnabled ? "about" : "tutorial"
}
if(p2.session){
@ -204,8 +222,10 @@ class SongSelect{
this.selectedSong = this.songs.findIndex(song => song.action === fromTutorial)
this.playBgm(true)
}else{
if((!p2.session || fadeIn) && "selectedSong" in localStorage){
this.selectedSong = Math.min(Math.max(0, localStorage["selectedSong"] |0), this.songs.length)
if(assets.customSongs){
this.selectedSong = assets.customSelected
}else if((!p2.session || fadeIn) && "selectedSong" in localStorage){
this.selectedSong = Math.min(Math.max(0, localStorage["selectedSong"] |0), this.songs.length - 1)
}
assets.sounds["song-select"].play()
snd.musicGain.fadeOut()
@ -265,6 +285,7 @@ class SongSelect{
this.state.moveHover = null
})
pageEvents.add(loader.screen, ["mousedown", "touchstart"], this.mouseDown.bind(this))
pageEvents.add(this.canvas, "touchend", this.touchEnd.bind(this))
if(touchEnabled && fullScreenSupported){
this.touchFullBtn = document.getElementById("touch-full-btn")
this.touchFullBtn.style.display = "block"
@ -404,11 +425,24 @@ class SongSelect{
}
}
}
touchEnd(event){
event.preventDefault()
if(this.state.screen === "song"){
var currentSong = this.songs[this.selectedSong]
if(currentSong.action === "browse"){
var mouse = this.mouseOffset(event.changedTouches[0].pageX, event.changedTouches[0].pageY)
var moveBy = this.songSelMouse(mouse.x, mouse.y)
if(moveBy === 0){
this.toBrowse()
}
}
}
}
mouseMove(event){
var mouse = this.mouseOffset(event.offsetX, event.offsetY)
var moveTo = null
if(this.state.screen === "song"){
if(mouse.x > 641 && mouse.y > 603 && p2.socket.readyState === 1){
if(mouse.x > 641 && mouse.y > 603 && p2.socket.readyState === 1 && !assets.customSongs){
moveTo = "session"
}else{
var moveTo = this.songSelMouse(mouse.x, mouse.y)
@ -521,6 +555,124 @@ class SongSelect{
assets.sounds["ka"].play()
}
}
browseChange(event){
var loaderDiv = document.createElement("div")
loaderDiv.innerHTML = assets.pages["loadsong"]
loader.screen.appendChild(loaderDiv)
var files = event.target.files
var promises = []
var tjaFiles = []
var osuFiles = []
var otherFiles = {}
for(var i = 0; i < files.length; i++){
var file = files[i]
var name = file.name.toLowerCase()
if(name.endsWith(".tja")){
tjaFiles.push([file, i])
}else if(name.endsWith(".osu")){
osuFiles.push([file, i])
}else{
otherFiles[file.webkitRelativePath.toLowerCase()] = file
}
}
var songs = []
var courseTypes = {"easy": 0, "normal": 1, "hard": 2, "oni": 3, "ura": 4}
for(var i = 0; i < tjaFiles.length; i++){
let file = tjaFiles[i][0]
let index = tjaFiles[i][1]
var reader = new FileReader()
promises.push(pageEvents.load(reader).then(event => {
var data = event.target.result.replace(/\0/g, "").split("\n")
var tja = new ParseTja(data, "oni", 0, true)
var songObj = {
id: index + 1,
type: "tja",
chart: data,
stars: []
}
var dir = file.webkitRelativePath.toLowerCase()
dir = dir.slice(0, dir.lastIndexOf("/") + 1)
for(var diff in tja.metadata){
var meta = tja.metadata[diff]
songObj.title = songObj.title_en = meta.title || file.name.slice(0, file.name.lastIndexOf("."))
var subtitle = meta.subtitle || ""
if(subtitle.startsWith("--")){
subtitle = subtitle.slice(2)
}
songObj.subtitle = songObj.subtitle_en = subtitle
songObj.preview = meta.demostart ? Math.floor(meta.demostart * 1000) : 0
if(meta.level){
songObj.stars[courseTypes[diff]] = meta.level
}
if(meta.wave){
songObj.music = otherFiles[dir + meta.wave.toLowerCase()]
}
}
if(songObj.music && songObj.stars.filter(star => star).length !== 0){
songs[index] = songObj
}
}).catch(() => {}))
reader.readAsText(file, "sjis")
}
for(var i = 0; i < osuFiles.length; i++){
let file = osuFiles[i][0]
let index = osuFiles[i][1]
var reader = new FileReader()
promises.push(pageEvents.load(reader).then(event => {
var data = event.target.result.replace(/\0/g, "").split("\n")
var osu = new ParseOsu(data, 0, true)
var dir = file.webkitRelativePath.toLowerCase()
dir = dir.slice(0, dir.lastIndexOf("/") + 1)
var songObj = {
id: index + 1,
type: "osu",
chart: data,
subtitle: osu.metadata.ArtistUnicode || osu.metadata.Artist,
subtitle_en: osu.metadata.Artist || osu.metadata.ArtistUnicode,
preview: osu.generalInfo.PreviewTime,
stars: [null, null, null, parseInt(osu.difficulty.overallDifficulty) || 1],
music: otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()]
}
var filename = file.name.slice(0, file.name.lastIndexOf("."))
var title = osu.metadata.TitleUnicode || osu.metadata.Title
if(title){
var suffix = ""
var matches = filename.match(/\[.+?\]$/)
if(matches){
suffix = " " + matches[0]
}
songObj.title = title + suffix
songObj.title_en = (osu.metadata.Title || osu.metadata.TitleUnicode) + suffix
}else{
songObj.title = filename
}
if(songObj.music){
songs[index] = songObj
}
}).catch(() => {}))
reader.readAsText(file)
}
Promise.all(promises).then(() => {
songs = songs.filter(song => typeof song !== "undefined")
if(songs.length){
assets.songs = songs
assets.customSongs = true
assets.customSelected = 0
assets.sounds["don"].play()
this.clean()
setTimeout(() => {
loader.screen.removeChild(loaderDiv)
new SongSelect("browse", false, this.touchEnabled)
}, 500)
}else{
loader.screen.removeChild(loaderDiv)
this.browse.parentNode.reset()
}
})
}
toSelectDifficulty(fromP2){
var currentSong = this.songs[this.selectedSong]
if(p2.session && !fromP2 && currentSong.action !== "random"){
@ -564,6 +716,8 @@ class SongSelect{
this.toTutorial()
}else if(currentSong.action === "about"){
this.toAbout()
}else if(currentSong.action === "browse"){
this.toBrowse()
}
}
this.pointer(false)
@ -593,7 +747,11 @@ class SongSelect{
assets.sounds["don"].play()
try{
localStorage["selectedSong"] = this.selectedSong
if(assets.customSongs){
assets.customSelected = this.selectedSong
}else{
localStorage["selectedSong"] = this.selectedSong
}
localStorage["selectedDiff"] = difficulty + this.diffOptions.length
}catch(e){}
@ -628,7 +786,7 @@ class SongSelect{
this.selectedDiff = 1
do{
this.state.options = this.mod(this.optionsList.length, this.state.options + moveBy)
}while(p2.socket.readyState !== 1 && this.state.options === 2)
}while((p2.socket.readyState !== 1 || assets.customSongs) && this.state.options === 2)
}
}
toTitleScreen(){
@ -655,7 +813,7 @@ class SongSelect{
}, 500)
}
toSession(){
if(p2.socket.readyState !== 1){
if(p2.socket.readyState !== 1 || assets.customSongs){
return
}
if(p2.session){
@ -670,6 +828,19 @@ class SongSelect{
}, 500)
}
}
toBrowse(){
if(assets.customSongs){
assets.customSongs = false
assets.songs = assets.songsDefault
assets.sounds["don"].play()
this.clean()
setTimeout(() => {
new SongSelect("browse", false, this.touchEnabled)
}, 500)
}else{
this.browse.click()
}
}
redraw(){
if(!this.redrawRunning){
@ -1501,7 +1672,7 @@ class SongSelect{
ctx.lineTo(x + 4, y + 4)
ctx.lineTo(x + 4, y + h)
ctx.fill()
if(screen !== "difficulty" && p2.socket.readyState === 1){
if(screen !== "difficulty" && p2.socket.readyState === 1 && !assets.customSongs){
var elapsed = (ms - this.state.screenMS) % 3100
var fade = 1
if(!p2.session && screen === "song"){
@ -1636,10 +1807,16 @@ class SongSelect{
return snd.previewGain.load(gameConfig.songs_baseurl + id + previewFilename)
}
songObj.preview_time = 0
loadPreview(previewFilename).catch(() => {
songObj.preview_time = prvTime
return loadPreview("/main.mp3")
new Promise((resolve, reject) => {
if(currentSong.music){
snd.previewGain.load(currentSong.music, true).then(resolve, reject)
}else{
songObj.preview_time = 0
loadPreview(previewFilename).catch(() => {
songObj.preview_time = prvTime
return loadPreview("/main.mp3")
}).then(resolve, reject)
}
}).then(sound => {
if(currentId === this.previewId){
songObj.preview_sound = sound
@ -1799,11 +1976,14 @@ class SongSelect{
})
pageEvents.keyRemove(this, "all")
pageEvents.remove(loader.screen, ["mousemove", "mouseleave", "mousedown", "touchstart"])
pageEvents.remove(this.canvas, "touchend")
pageEvents.remove(p2, "message")
if(this.touchEnabled && fullScreenSupported){
pageEvents.remove(this.touchFullBtn, "click")
delete this.touchFullBtn
}
pageEvents.remove(this.browse, "change")
delete this.browse
delete this.selectable
delete this.ctx
delete this.canvas

View File

@ -4,10 +4,19 @@
this.context = new AudioContext()
pageEvents.add(window, ["click", "touchend"], this.pageClicked.bind(this))
}
load(url, gain){
return loader.ajax(url, request => {
request.responseType = "arraybuffer"
}).then(response => {
load(url, local, gain){
if(local){
var reader = new FileReader()
var loadPromise = pageEvents.load(reader).then(event => {
return event.target.result
})
reader.readAsArrayBuffer(url)
}else{
var loadPromise = loader.ajax(url, request => {
request.responseType = "arraybuffer"
})
}
return loadPromise.then(response => {
return new Promise((resolve, reject) => {
return this.context.decodeAudioData(response, resolve, reject)
}).catch(error => {
@ -66,8 +75,8 @@ class SoundGain{
}
this.setVolume(1)
}
load(url){
return this.soundBuffer.load(url, this)
load(url, local){
return this.soundBuffer.load(url, local, this)
}
convertTime(time, absolute){
return this.soundBuffer.convertTime(time, absolute)

View File

@ -2,4 +2,5 @@
<canvas id="song-sel-canvas"></canvas>
<div id="song-sel-selectable" tabindex="1"></div>
<div id="touch-full-btn"></div>
<form><input id="browse" type="file" webkitdirectory multiple></form>
</div>