Initial commit

master
Avril 5 years ago
commit 6493f70816
Signed by: flanchan
GPG Key ID: 284488987C31F630

1
.gitignore vendored

@ -0,0 +1 @@
node_modules/

@ -0,0 +1,11 @@
const CONFIG = {
API_PORT: 23333,
API_LOCATION: "127.0.0.1",
STATIC_LOCATION: "./www",
STATIC_PORT: 8081, /* Warning: do not serve statics here. */
};
module.exports = CONFIG;

@ -0,0 +1,25 @@
const fs = require('fs'),
config = require('./config');
http = require('http');
console.log("Starting static server on port "+config.STATIC_PORT);
(async () => {
await http.createServer((req, res) => {
let url = req.url;
if(!req.url || req.url == "" || req.url == "/") url = "/index.html";
fs.readFile(config.STATIC_LOCATION + url, (err, data) => {
if(err) {
console.log("Failed to retreive requested url '"+url+"': "+err);
res.writeHead(404);
res.end(JSON.stringify(err));
return;
} else {
console.log("Writing "+url);
res.writeHead(200);
res.end(data);
}
});
}).listen(config.STATIC_PORT);
})();

11
package-lock.json generated

@ -0,0 +1,11 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"line-navigator": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/line-navigator/-/line-navigator-2.1.6.tgz",
"integrity": "sha1-s46Iq7vmEuqMBhEnugiu+dMPBg0="
}
}
}

@ -0,0 +1,119 @@
body, pre {
font-family: Arial, Helvetica, sans-serif;
font-size: 10pt;
}
body {
background-color: #1d1f21;
color: #c5c8c6;
}
h1 {
font: bolder 28px Tahoma;
letter-spacing: -2px;
}
h1, h2 {
text-align: center;
}
article, aside, .popout, .page {
background-color: #282a2e;
border: 1px solid #393b3f;
}
.page {
padding: 2px;
padding-left: 4px;
display: inline-block;
vertical-align: top;
}
.popout {
display: inline;
}
article, aside {
display: table;
border-color: #282a2e;
margin: 2px;
padding: 4px;
}
.indent {
border-left-color: red;
border-left-style: solid;
border-radius: 2px;
padding-left: 5px;
}
#selector {
padding: 10px;
display: block;
}
article header {
font-weight: bolder;
}
blockquote {
margin-top: 2px;
}
.osufile {
width: 100%;
line-height: 150%;
}
.collapse {
cursor: pointer;
display: inline-block;
height: 12px;
left: -1px;
top: 2px;
margin-top: -3px;
width: 12px;
position:relative;
text-align: center;
font-weight: normal;
border: 1px solid white;
border-radius: 50%;
transition: background-color 0.5s;
transition: color 1s;
-webkit-transition: color 1s;
-webkit-transition: background-color 0.5s;
}
.collapse:hover {
color: orange;
background-color: red;
}
.collapsed:hover {
background-color: green !important;
}
.osufile:after {
cursor: pointer;
content: "X";
color: red;
transition: color 1s;
-webkit-transition: color 1s;
margin: -1px;
float: right;
text-align: right;
}
.osufile:hover:after {
color: white;
}
#metadata {
right: 5px;
white-space: nowrap;
position: fixed;
}

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="41.756px" height="41.756px" viewBox="0 0 41.756 41.756" style="enable-background:new 0 0 41.756 41.756;"
xml:space="preserve">
<g>
<path d="M27.948,20.878L40.291,8.536c1.953-1.953,1.953-5.119,0-7.071c-1.951-1.952-5.119-1.952-7.07,0L20.878,13.809L8.535,1.465
c-1.951-1.952-5.119-1.952-7.07,0c-1.953,1.953-1.953,5.119,0,7.071l12.342,12.342L1.465,33.22c-1.953,1.953-1.953,5.119,0,7.071
C2.44,41.268,3.721,41.755,5,41.755c1.278,0,2.56-0.487,3.535-1.464l12.343-12.342l12.343,12.343
c0.976,0.977,2.256,1.464,3.535,1.464s2.56-0.487,3.535-1.464c1.953-1.953,1.953-5.119,0-7.071L27.948,20.878z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -0,0 +1,79 @@
<html>
<head>
<link rel='stylesheet' type='text/css' href='css/main.css'></link>
<script src='js/vendor/file-wrapper.min.js'></script>
<script src='js/vendor/line-navigator.min.js'></script>
<script src='js/stage.js'></script>
<script src='js/util.js'></script>
<script src='js/osu.js'></script>
<script src='js/client.js'></script>
</head>
<body onload='_onload()'>
<h1>osu!merge</h1>
<title>osu!merge</title>
<div class='indent'>
Merge <i>.osu</i> files together.
</div>
<hr></hr>
<div>
<div class='page'>
<b>Select .osu files</b>
<div id='selector'>
<div id='_file1' data-index='0' class='osufile' onclick='_removeFile("_file1")'>test</div>
<div id='_file2' data-index='0' class='osufile' onclick='_removeFile("_file2")'>test2</div>
</div>
Upload file: <input type='file' accept='text/plain' onchange='_readfile(event)'></input>
</div>
<div class='page' id='metadata'>
<div style='display:inline-block; width: 100%;'>
<b style='float:left;margin-top:1px;'>Metadata</b>
<span class='collapse' style='float: right; margin:1px;margin-left:5px;margin-bottom:4px;' data-collapse='meta-internal'></span>
</div>
<div id='meta-internal'>
<article>
<header>
<span class='collapse'></span>
General
</header>
<blockquote>
<div>Audio Filename: <span class='metadata' id='meta-audio-filename'></span></div>
<div>Lead-in: <span class='metadata' id='meta-audio-lead-in'></span></div>
<div>Preview Time: <span class='metadata' id='meta-preview-time'></span></div>
<div>Countdown: <span class='metadata' id='meta-countdown'></span></div>
<div>Default sample set: <span class='metadata' id='meta-sample-set'></span></div>
<div>Stack leniency: <span class='metadata' id='meta-stacking'></span></div>
<div>Mode: <span class='metadata' id='meta-mode'></span></div>
<div>Letterbox: <span class='metadata' id='meta-letterbox'></span></div>
<div>Widescreen: <span class='metadata' id='meta-widescreen'></span></div>
</blockquote>
</article>
<article>
<header>
<span class='collapse'></span>
Song
</header>
<blockquote>
<div>Title: <span class='metadata' id='meta-title'></span></div>
<div>Title (Raomanised): <span class='metadata' id='meta-title-ascii'></span></div>
<div>Artist: <span class='metadata' id='meta-artist'></span></div>
<div>Artist (Romanised): <span class='metadata' id='meta-artist-ascii'></span></div>
<div>Mapper: <span class='metadata' id='meta-mapper'></span></div>
<div>Difficulty: <span class='metadata' id='meta-difficulty'></span></div>
<div>Source: <span class='metadata' id='meta-source'></span></div>
<div>Tags: <span class='metadata' id='meta-tags'></span></div>
<div>ID: <span class='metadata' id='meta-beatmap-id'></span></div>
<div>Set ID: <span class='metadata' id='meta-beatmap-set-id'></span></div>
</blockquote>
</article>
Viewing:
<select>
<option value='Aggregate'>Aggregate</option>
</select>
</div>
</div>
</div>
<div style='margin: auto; margin-top: 5px; border-radius:5px; background: linear-gradient(90deg, green 25%, 25%, blue 50%, 50%, red 75%); height: 25px; width: 90%;'></div>
</body>
</html>

@ -0,0 +1,72 @@
var OSU_FILES = [];
const global = new Dispatcher();
function _removeFile(id) {
//TODO: Remove from bars too
let elem = document.getElementById(id);
const file_id = elem.dataset.index;
elem.remove();
if(file_id!==undefined && OSU_FILES[file_id])
{
global.signal("FILE_REMOVE", {id: file_id, osu: OSU_FILES[file_id]});
OSU_FILES[file_id] = null;
}
}
const READ_WHOLE_BUFFER = true;
function _readfile(event) {
var input = event.target;
if(READ_WHOLE_BUFFER) {
var reader = new FileReader();
reader.onload = () => {
var osu = new OsuFile();
osu.LoadString(reader.result).then(result => {
console.log("Read version "+osu.version);
if(osu.okay()) {
//TODO: Push to read osu files
OSU_FILES.push(osu);
global.signal("FILE_ADD", {id: OSU_FILES.length-1, osu: osu});
} else {
var er = osu.errors;
console.log("Parsing .osu file failed: "+JSON.stringify(er));
//TODO: Signal error
}
});
};
reader.readAsText(input.files[0]);
} else {
var nav = new LineNavigator(input.files[0]);
}
}
function _onload() {
// --- Set up collapsers --- //
const collapsers = document.getElementsByClassName('collapse');
for(const elem of collapsers) {
elem.addEventListener('click', function() {
let block;
if(this.dataset.collapse) {
block = document.getElementById(this.dataset.collapse);
}
else block = this.parentElement.nextSibling.nextSibling;
if(block.style.display === "none") {
removeClass(this, "collapsed");
block.style.display = "";
}
else {
addClass(this, "collapsed");
block.style.display = "none";
}
}, false);
}
// --- End collapsers --- //
}

@ -0,0 +1,334 @@
function OsuFile() {
}
var OSU = OsuFile.prototype;
const get_bpm = len => (1.0 / len * 60000.00);
const get_beat = bpm => 60000.00 / bpm;
const get_sv = inverse => 100.00 / (-1.0 * inverse);
const get_inverse = sv => (sv / 100.00) * -1.0;
OSU.LoadString = async function(contents) {
const lines = contents.split(/(\n|\r|\f)+/).where(line => line && line.trim() != "");
var stage = new Stage();
stage.giveMany(lines);
stage.commit();
await this.LoadTokens(stage);
};
OSU.LoadTokens = async function(tokens) {
const stages = {
"General": new Stage(),
"Editor": new Stage(),
"Metadata": new Stage(),
"Difficulty": new Stage(),
"Events": new Stage(),
"TimingPoints": new Stage(),
"HitObjects": new Stage(),
};
var line;
var group = null;
this.errors = [];
this.data = {};
this.futsuu(stages["General"]);
this.han(stages["Editor"]);
this.meta(stages["Metadata"]);
this.muzukashisa(stages["Difficulty"]);
this.jiken(stages["Events"]);
this.keiji(stages["TimingPoints"]);
this.maru(stages["HitObjects"]);
while(line = await tokens.take()) {
if(!this.version)
{
if(/^osu file format v\d+$/.test(line))
{
this.version = line.match(/\d+$/)[0];
}
else return ["no version present"];
continue;
}
if(/^\/\//.test(line)) continue;
if(line.trim() === "") continue;
let new_Group = line.match(/^\[(\w+)\]$/i);
console.log(new_Group);
if(new_Group && new_Group[1]) {
console.log("Setting new group `"+new_Group[1]+"' from old group "+group);
if(group) {
stages[group].commit();
}
group = new_Group[1];
this.data[group] = {};
stages[group].give(group);
continue;
}
if(!group) continue;
if(!stages[group]) return ["unknown group "+group];
stages[group].give(line);
}
console.log("----END OF FILE");
if(group)
stages[group].commit();
for(const key of Object.keys(stages))
{
stages[key].forceClose();
}
console.log(this.data);
return this.errors;
};
/// --- Parsers --- ///
OSU.futsuu = async function(tokens) {
const group = await tokens.take();
if(group) {
var data = this.data[group];
var tok;
while(tok = await tokens.take()) {
var match = tok.match(/^(\w+):\s(.*)$/i);
if(match && match[0])
{
data[match[1]] = match[2];
}
else this.errors.push("futsuu: parsing line `"+tok+"' failed.");
}
}
};
OSU.han = async function(tokens) {
const group = await tokens.take();
if(group) {
var data = this.data[group];
var tok;
while(tok = await tokens.take()) {
var match = tok.match(/^(\w+):\s(.*)$/i);
if(match && match[0])
{
if(match[1] === "Bookmarks")
{
data[match[1]] = match[2].split(/,/);
}
else data[match[1]] = match[2];
}
else this.errors.push("han: parsing line `"+tok+"' failed.");
}
}
};
OSU.meta = async function(tokens) {
const group = await tokens.take();
if(group) {
var data = this.data[group];
var tok;
while(tok = await tokens.take()) {
var match = tok.match(/^(\w+):\s?(.*)$/i);
if(match && match[0])
{
if(match[1] === "Tags")
{
data[match[1]] = match[2].split(/\s/);
}
else data[match[1]] = match[2];
}
else this.errors.push("meta: parsing line `"+tok+"' failed.");
}
}
};
OSU.muzukashisa = async function(tokens) {
const group = await tokens.take();
if(group) {
var data = this.data[group];
var tok;
while(tok = await tokens.take()) {
var match = tok.match(/^(\w+):\s?(.*)$/i);
if(match && match[0])
{
data[match[1]] = match[2];
}
else this.errors.push("muzukashisa: parsing line `"+tok+"' failed.");
}
}
};
OSU.jiken = async function(tokens) {
const group = await tokens.take();
if(group) {
var data = this.data[group];
var tok;
while(tok = await tokens.take()) {
var match = tok.match(/^(\w+|\d+),.+$/i);
if(match && match[0])
{
//convenience?
const ar = JSON.parse("[" + tok + "]");
data[match[1]] = {time: ar[1], params: ar.slice(2)};
}
else this.errors.push("jiken: parsing line `"+tok+"' failed");
}
}
};
OSU.keiji = async function(tokens) {
const group = await tokens.take();
if(group) {
var _data = this.data[group] = [];
var tok;
while(tok = await tokens.take()) {
var match = tok.match(/^(\d+),(-?\d+(?:\.\d+)?),(\d+),(\d+),(\d+),(\d+),([01]),(\d+)$/i);
let data = {};
if(match && match[0])
{
data.time = match[1];
data.beatLength = match[2];
data.meter = match[3];
data.sampleSet = match[4];
data.sampleIndex = match[5];
data.volume = match[6];
data.uninherited = match[7] == "1";
data.effects = match[8];
}
else this.errors.push("keiji: parsing line `"+tok+"' failed.");
_data.push(data);
}
}
};
const BEZIER = 0;
const CENTRIPETAL_CATMULL_ROM = 1;
const LINEAR = 2;
const PERFECT_CIRCLE = 3;
const SLIDER_UNKNOWN = 4;
const parse_params = (params) => {
const re_slider_1 = /^(B|C|L|P)\|(.+)/i; //slider type
if(!params[0]) {
//Is circle
return {type: "circle"};
}
else if(re_slider_1 .test(params[0]))
{
//Is slider
const type = params[0].match(re_slider_1)[1].il_switch({'B': BEZIER,
'C': CENTRIPETAL_CATMULL_ROM,
'L': LINEAR,
'P': PERFECT_CIRCLE
}, SLIDER_UNKNOWN);
const points = params[0].split(/\|/).slice(1).map(x=> x.split(/:/));
const slides = params[1];
const length = params[2];
const edgeSounds = params[3].split(/\|/);
const edgeSets = params[4].split(/\|/).map(x=> x.split(/:/));
return {
type: "slider",
sliderType: type,
points: points,
slides: slides,
length: length,
edgeSounds: edgeSounds,
edgeSets: edgeSets,
};
} else {
//Spinner (or hold? TODO)
return {type: "spinner", endTime: params[0]};
}
};
OSU.maru = async function(tokens) {
const group = await tokens.take();
if(group) {
var _data = this.data[group] = [];
var tok;
while(tok = await tokens.take()) {
let data = {};
var match = tok.match(/^(\d+),(\d+),(\d+),(\d+),(.+)$/i); //i honestly have no fucking idea what regex to get all these so we'll cheat
if(match && match[0])
{
data.x = match[1];
data.y = match[2];
data.time = match[2];
data.hitSound = match[3];
//Parse params and sample
if(/:i(?:.*\.wav)?$/.test(match[4]))
{
//Sample is there
let param = match[4].split(/,/);
data.hitSample = param.pop().split(/:/).where(/\d+/.test).associate([
"normalSet",
"additionalSet",
"index",
"volume",
"filename",
]);
data.params = parse_params(param);
}
else {
//No sample, rest is params
data.params = parse_params(match[4].split(/,/));
data.hitSample = {
normalSet: "0",
additionalSet: "0",
index: "0",
volume: "0",
};
}
_data.push(data);
}
else this.errors.push("maru: parsing line `"+tok+"' failed.");
}
}
};
/// --- End parsers --- ///
OSU.okay = function() {
return !!this.version && this.errors.length<1;
};
OsuFile.Unserialise = (json) => {
if(typeof(json) === 'string') return OsuFile.Unserialise(JSON.parse(json));
var osu = new OsuFile();
osu.errors = [];
osu.version = json.version;
osu.data = json.data;
};
OSU.serialise = function() {
return {version: this.version, data: this.data};
};
OSU.toString = function() {
return JSON.stringify(this.serialise());
};

@ -0,0 +1,106 @@
function Stage(from) {
var base = this;
this.array = from || [];
this.over = false;
this.acceptInvalid = false;
var next = {
waiters: [],
wait: function() {
return new Promise(resolve => next.waiters.push(function() { resolve(); }));
},
signal: function() {
if(next.waiters.length>0) {
next.waiters.shift()();
}
},
flush: function() {
while(next.waiters.length>0) {
next.waiters.shift()();
}
}
};
this.take = async function() {
if(base.over && base.array.length<=0)
return undefined;
if(base.array.length<=0)
await next.wait();
if(base.over && base.array.length<=0)
return undefined;
return base.array.shift();
};
this.poll = function() {
return base.array;
};
this.takeNB = function() {
if(base.array.length<=0)
return null;
return base.array.shift();
};
this.swallow0 = async function() {
var ar = [];
var token;
while(token = await base.take()) {
ar.push(token);
}
return ar;
};
this.swallow = function(timeout) {
if(!timeout) return base.swallow0();
else {
return new Promise(function(_resolve,_reject) {
var running=true;
var resolve = function(v) {
if(running) _resolve(v);
running=false;
};
var reject= function(v) {
if(running) _reject(v);
running=false;
};
base.swallow0().then(resolve);
if(timeout>0)
setTimeout(()=> reject(new Error("swallow timeout reached ("+timeout+")")), timeout);
});
}
};
this.giveMany= function(values) {
for(let value of values)
base.give(value);
};
this.give = function(value) {
if(!value && !base.acceptInvalid)
return;
base.array.push(value);
next.signal();
};
this.commit = function() {
base.over = true;
next.flush();
return base;
};
this.forceClose = function() {
if(base.over) return base;
else return base.commit();
};
};
Stage.commitAll = (stages) => {
for(const st of stages)
st.commit();
};

@ -0,0 +1,93 @@
function addGlobalStyle(text)
{
var style = document.createElement('style');
style.type='text/css';
if(style.styleSheet) {
style.styleSheet.cssText = text;
} else {
style.appendChild(document.createTextNode(text));
}
document.getElementsByTagName('head')[0].appendChild(style);
return style;
}
function hasClass(ele, cls)
{
return !!ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
}
function addClass(ele, cls)
{
if(!hasClass(ele,cls)) ele.className += " "+cls;
}
Array.prototype.where = function(predicate) {
output = [];
for(const elem of this) {
if(predicate(elem)) output.push(elem);
}
return output;
};
Array.prototype.associate = function(keys) {
var output = {};
for(let i=0;i<this.length;i++)
{
output[keys[i]] = this[i];
}
return output;
};
String.prototype.il_switch = function(obj, def) {
for(const key of obj)
{
if(this === key) return obj[key];
}
return def;
};
function removeClass(ele,cls)
{
if(hasClass(ele,cls))
{
const reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
ele.className = ele.className.replace(reg, ' ');
}
}
function Dispatcher() {
this.hooks = {};
}
Dispatcher.prototype.hook = function(name, value) {
if(this.hooks[name]) {
this.hooks[name].push(value);
return {id: name, index: this.hooks[name].length-1};
}
else {
this.hooks[name] = [value];
return {id: name, index: 0};
}
};
Dispatcher.prototype.signal = function(name,value) {
if(this.hooks[name]) {
for(const hook of this.hooks[name])i
if(hook)
hook(value);
}
};
Dispatcher.prototype.unhook = function(hook) {
const name = hook.id;
if(name && hook.index && this.hooks[name]) {
this.hooks[name][hook.index] = null;
}
};
Dispatcher.prototype.clear = function(name) {
if(name) this.hooks[name] = null;
else this.hooks = {};
};

@ -0,0 +1 @@
../../../node_modules/line-navigator/file-wrapper.min.js

@ -0,0 +1 @@
../../../node_modules/line-navigator/line-navigator.min.js
Loading…
Cancel
Save