mirror of
https://github.com/jiojciojsioe3/a3cjroijsiojiorj.git
synced 2024-11-25 12:21:51 +08:00
Plugins: Add plugin settings
- Add support for plugin settings, they appear in the same menu as the plugins, indented from the left to emphasize which plugin the setting belongs to - Note that plugin settings can still be changed even when the plugins are stopped - Add tooltips to plugin menu to view the plugin descriptions, description_lang can also be used - Fix scolling not working on song select when returning from game settings - Let instance owners set default plugin files in config.py, to make them easier to maintain - plugins.add() can now add plugins using a url - Plugins can be hidden from the plugin menu using PluginLoader.hide, an option in plugins.add(), or in config.py - Make p2.disable() incremental so that multiple plugins can disable multiplayer independently - Server no longer crashes if certain optional config fields were not copied over from an updated example config - Fix not being able to unload plugins if one was imported with errors
This commit is contained in:
parent
78fe7062dc
commit
7d818877f8
59
app.py
59
app.py
@ -3,7 +3,10 @@
|
|||||||
import base64
|
import base64
|
||||||
import bcrypt
|
import bcrypt
|
||||||
import hashlib
|
import hashlib
|
||||||
import config
|
try:
|
||||||
|
import config
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
raise FileNotFoundError('No such file or directory: \'config.py\'. Copy the example config file config.example.py to config.py')
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
@ -20,23 +23,32 @@ from ffmpy import FFmpeg
|
|||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
from redis import Redis
|
from redis import Redis
|
||||||
|
|
||||||
app = Flask(__name__)
|
def take_config(name, required=False):
|
||||||
client = MongoClient(host=config.MONGO['host'])
|
if hasattr(config, name):
|
||||||
|
return getattr(config, name)
|
||||||
|
elif required:
|
||||||
|
raise ValueError('Required option is not defined in the config.py file: {}'.format(name))
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
app.secret_key = config.SECRET_KEY
|
app = Flask(__name__)
|
||||||
|
client = MongoClient(host=take_config('MONGO', required=True)['host'])
|
||||||
|
|
||||||
|
app.secret_key = take_config('SECRET_KEY') or 'change-me'
|
||||||
app.config['SESSION_TYPE'] = 'redis'
|
app.config['SESSION_TYPE'] = 'redis'
|
||||||
|
redis_config = take_config('REDIS', required=True)
|
||||||
app.config['SESSION_REDIS'] = Redis(
|
app.config['SESSION_REDIS'] = Redis(
|
||||||
host=config.REDIS['CACHE_REDIS_HOST'],
|
host=redis_config['CACHE_REDIS_HOST'],
|
||||||
port=config.REDIS['CACHE_REDIS_PORT'],
|
port=redis_config['CACHE_REDIS_PORT'],
|
||||||
password=config.REDIS['CACHE_REDIS_PASSWORD'],
|
password=redis_config['CACHE_REDIS_PASSWORD'],
|
||||||
db=config.REDIS['CACHE_REDIS_DB']
|
db=redis_config['CACHE_REDIS_DB']
|
||||||
)
|
)
|
||||||
app.cache = Cache(app, config=config.REDIS)
|
app.cache = Cache(app, config=redis_config)
|
||||||
sess = Session()
|
sess = Session()
|
||||||
sess.init_app(app)
|
sess.init_app(app)
|
||||||
csrf = CSRFProtect(app)
|
csrf = CSRFProtect(app)
|
||||||
|
|
||||||
db = client[config.MONGO['database']]
|
db = client[take_config('MONGO', required=True)['database']]
|
||||||
db.users.create_index('username', unique=True)
|
db.users.create_index('username', unique=True)
|
||||||
db.songs.create_index('id', unique=True)
|
db.songs.create_index('id', unique=True)
|
||||||
db.scores.create_index('username')
|
db.scores.create_index('username')
|
||||||
@ -53,12 +65,12 @@ def api_error(message):
|
|||||||
def generate_hash(id, form):
|
def generate_hash(id, form):
|
||||||
md5 = hashlib.md5()
|
md5 = hashlib.md5()
|
||||||
if form['type'] == 'tja':
|
if form['type'] == 'tja':
|
||||||
urls = ['%s%s/main.tja' % (config.SONGS_BASEURL, id)]
|
urls = ['%s%s/main.tja' % (take_config('SONGS_BASEURL', required=True), id)]
|
||||||
else:
|
else:
|
||||||
urls = []
|
urls = []
|
||||||
for diff in ['easy', 'normal', 'hard', 'oni', 'ura']:
|
for diff in ['easy', 'normal', 'hard', 'oni', 'ura']:
|
||||||
if form['course_' + diff]:
|
if form['course_' + diff]:
|
||||||
urls.append('%s%s/%s.osu' % (config.SONGS_BASEURL, id, diff))
|
urls.append('%s%s/%s.osu' % (take_config('SONGS_BASEURL', required=True), id, diff))
|
||||||
|
|
||||||
for url in urls:
|
for url in urls:
|
||||||
if url.startswith("http://") or url.startswith("https://"):
|
if url.startswith("http://") or url.startswith("https://"):
|
||||||
@ -117,22 +129,24 @@ def before_request_func():
|
|||||||
|
|
||||||
def get_config(credentials=False):
|
def get_config(credentials=False):
|
||||||
config_out = {
|
config_out = {
|
||||||
'songs_baseurl': config.SONGS_BASEURL,
|
'songs_baseurl': take_config('SONGS_BASEURL', required=True),
|
||||||
'assets_baseurl': config.ASSETS_BASEURL,
|
'assets_baseurl': take_config('ASSETS_BASEURL', required=True),
|
||||||
'email': config.EMAIL,
|
'email': take_config('EMAIL'),
|
||||||
'accounts': config.ACCOUNTS,
|
'accounts': take_config('ACCOUNTS'),
|
||||||
'custom_js': config.CUSTOM_JS,
|
'custom_js': take_config('CUSTOM_JS'),
|
||||||
'preview_type': config.PREVIEW_TYPE or 'mp3'
|
'plugins': take_config('PLUGINS') and [x for x in take_config('PLUGINS') if x['url']],
|
||||||
|
'preview_type': take_config('PREVIEW_TYPE') or 'mp3'
|
||||||
}
|
}
|
||||||
if credentials:
|
if credentials:
|
||||||
min_level = config.GOOGLE_CREDENTIALS['min_level'] or 0
|
google_credentials = take_config('GOOGLE_CREDENTIALS')
|
||||||
|
min_level = google_credentials['min_level'] or 0
|
||||||
if not session.get('username'):
|
if not session.get('username'):
|
||||||
user_level = 0
|
user_level = 0
|
||||||
else:
|
else:
|
||||||
user = db.users.find_one({'username': session.get('username')})
|
user = db.users.find_one({'username': session.get('username')})
|
||||||
user_level = user['user_level']
|
user_level = user['user_level']
|
||||||
if user_level >= min_level:
|
if user_level >= min_level:
|
||||||
config_out['google_credentials'] = config.GOOGLE_CREDENTIALS
|
config_out['google_credentials'] = google_credentials
|
||||||
else:
|
else:
|
||||||
config_out['google_credentials'] = {
|
config_out['google_credentials'] = {
|
||||||
'gdrive_enabled': False
|
'gdrive_enabled': False
|
||||||
@ -146,9 +160,8 @@ def get_config(credentials=False):
|
|||||||
config_out['_version'] = get_version()
|
config_out['_version'] = get_version()
|
||||||
return config_out
|
return config_out
|
||||||
|
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
version = {'commit': None, 'commit_short': '', 'version': None, 'url': config.URL}
|
version = {'commit': None, 'commit_short': '', 'version': None, 'url': take_config('URL')}
|
||||||
if os.path.isfile('version.json'):
|
if os.path.isfile('version.json'):
|
||||||
try:
|
try:
|
||||||
ver = json.load(open('version.json', 'r'))
|
ver = json.load(open('version.json', 'r'))
|
||||||
@ -669,7 +682,7 @@ def route_api_scores_get():
|
|||||||
@app.route('/privacy')
|
@app.route('/privacy')
|
||||||
def route_api_privacy():
|
def route_api_privacy():
|
||||||
last_modified = time.strftime('%d %B %Y', time.gmtime(os.path.getmtime('templates/privacy.txt')))
|
last_modified = time.strftime('%d %B %Y', time.gmtime(os.path.getmtime('templates/privacy.txt')))
|
||||||
integration = config.GOOGLE_CREDENTIALS['gdrive_enabled']
|
integration = take_config('GOOGLE_CREDENTIALS')['gdrive_enabled'] if take_config('GOOGLE_CREDENTIALS') else False
|
||||||
|
|
||||||
response = make_response(render_template('privacy.txt', last_modified=last_modified, config=get_config(), integration=integration))
|
response = make_response(render_template('privacy.txt', last_modified=last_modified, config=get_config(), integration=integration))
|
||||||
response.headers['Content-type'] = 'text/plain; charset=utf-8'
|
response.headers['Content-type'] = 'text/plain; charset=utf-8'
|
||||||
|
@ -13,6 +13,13 @@ ACCOUNTS = True
|
|||||||
# Custom JavaScript file to load with the simulator.
|
# Custom JavaScript file to load with the simulator.
|
||||||
CUSTOM_JS = ''
|
CUSTOM_JS = ''
|
||||||
|
|
||||||
|
# Default plugins to load with the simulator.
|
||||||
|
PLUGINS = [{
|
||||||
|
'url': '',
|
||||||
|
'start': False,
|
||||||
|
'hide': False
|
||||||
|
}]
|
||||||
|
|
||||||
# Filetype to use for song previews. (mp3/ogg)
|
# Filetype to use for song previews. (mp3/ogg)
|
||||||
PREVIEW_TYPE = 'mp3'
|
PREVIEW_TYPE = 'mp3'
|
||||||
|
|
||||||
|
@ -293,7 +293,7 @@ kbd{
|
|||||||
#settings-latency .view{
|
#settings-latency .view{
|
||||||
width: 30em;
|
width: 30em;
|
||||||
}
|
}
|
||||||
#settings-latency .setting-value{
|
.setting-value{
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.setting-value:not(.selected) .latency-buttons{
|
.setting-value:not(.selected) .latency-buttons{
|
||||||
|
@ -56,6 +56,10 @@ function browserSupport(){
|
|||||||
},
|
},
|
||||||
"KeyboardEvent.key": function(){
|
"KeyboardEvent.key": function(){
|
||||||
return "key" in KeyboardEvent.prototype
|
return "key" in KeyboardEvent.prototype
|
||||||
|
},
|
||||||
|
"Module import": function(){
|
||||||
|
eval("import('data:text/javascript,')")
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
failedTests = []
|
failedTests = []
|
||||||
@ -107,10 +111,12 @@ function showUnsupported(strings){
|
|||||||
var warn = document.createElement("div")
|
var warn = document.createElement("div")
|
||||||
warn.id = "unsupportedWarn"
|
warn.id = "unsupportedWarn"
|
||||||
warn.innerText = "!"
|
warn.innerText = "!"
|
||||||
|
warn.textContent = "!"
|
||||||
div.appendChild(warn)
|
div.appendChild(warn)
|
||||||
var hide = document.createElement("div")
|
var hide = document.createElement("div")
|
||||||
hide.id = "unsupportedHide"
|
hide.id = "unsupportedHide"
|
||||||
hide.innerText = "x"
|
hide.innerText = "x"
|
||||||
|
hide.textContent = "x"
|
||||||
div.appendChild(hide)
|
div.appendChild(hide)
|
||||||
|
|
||||||
var span = document.createElement("span")
|
var span = document.createElement("span")
|
||||||
@ -119,6 +125,7 @@ function showUnsupported(strings){
|
|||||||
if(i !== 0){
|
if(i !== 0){
|
||||||
var link = document.createElement("a")
|
var link = document.createElement("a")
|
||||||
link.innerText = strings.browserSupport.details
|
link.innerText = strings.browserSupport.details
|
||||||
|
link.textContent = strings.browserSupport.details
|
||||||
span.appendChild(link)
|
span.appendChild(link)
|
||||||
}
|
}
|
||||||
span.appendChild(document.createTextNode(browserWarning[i]))
|
span.appendChild(document.createTextNode(browserWarning[i]))
|
||||||
@ -133,6 +140,7 @@ function showUnsupported(strings){
|
|||||||
for(var i = 0; i < failedTests.length; i++){
|
for(var i = 0; i < failedTests.length; i++){
|
||||||
var li = document.createElement("li")
|
var li = document.createElement("li")
|
||||||
li.innerText = failedTests[i]
|
li.innerText = failedTests[i]
|
||||||
|
li.textContent = failedTests[i]
|
||||||
ul.appendChild(li)
|
ul.appendChild(li)
|
||||||
}
|
}
|
||||||
details.appendChild(ul)
|
details.appendChild(ul)
|
||||||
@ -143,6 +151,7 @@ function showUnsupported(strings){
|
|||||||
var chrome = document.createElement("a")
|
var chrome = document.createElement("a")
|
||||||
chrome.href = "https://www.google.com/chrome/"
|
chrome.href = "https://www.google.com/chrome/"
|
||||||
chrome.innerText = "Google Chrome"
|
chrome.innerText = "Google Chrome"
|
||||||
|
chrome.textContent = "Google Chrome"
|
||||||
details.appendChild(chrome)
|
details.appendChild(chrome)
|
||||||
}
|
}
|
||||||
details.appendChild(document.createTextNode(supportedBrowser[i]))
|
details.appendChild(document.createTextNode(supportedBrowser[i]))
|
||||||
|
@ -108,7 +108,10 @@
|
|||||||
)
|
)
|
||||||
))){
|
))){
|
||||||
this.plugins.forEach(obj => {
|
this.plugins.forEach(obj => {
|
||||||
var plugin = plugins.add(obj.data, obj.name)
|
var plugin = plugins.add(obj.data, {
|
||||||
|
name: obj.name,
|
||||||
|
raw: true
|
||||||
|
})
|
||||||
if(plugin){
|
if(plugin){
|
||||||
pluginAmount++
|
pluginAmount++
|
||||||
plugin.imported = true
|
plugin.imported = true
|
||||||
|
@ -183,7 +183,7 @@ class Loader{
|
|||||||
var image = document.createElement("img")
|
var image = document.createElement("img")
|
||||||
var url = gameConfig.assets_baseurl + "img/" + name
|
var url = gameConfig.assets_baseurl + "img/" + name
|
||||||
categoryPromises.push(pageEvents.load(image).catch(response => {
|
categoryPromises.push(pageEvents.load(image).catch(response => {
|
||||||
this.errorMsg(response, url)
|
return this.errorMsg(response, url)
|
||||||
}))
|
}))
|
||||||
image.id = name
|
image.id = name
|
||||||
image.src = url
|
image.src = url
|
||||||
@ -325,6 +325,26 @@ class Loader{
|
|||||||
|
|
||||||
promises.push(this.canvasTest.drawAllImages())
|
promises.push(this.canvasTest.drawAllImages())
|
||||||
|
|
||||||
|
if(gameConfig.plugins){
|
||||||
|
gameConfig.plugins.forEach(obj => {
|
||||||
|
if(obj.url){
|
||||||
|
var plugin = plugins.add(obj.url, {
|
||||||
|
hide: obj.hide
|
||||||
|
})
|
||||||
|
if(plugin){
|
||||||
|
plugin.loadErrors = true
|
||||||
|
promises.push(plugin.load(true).then(() => {
|
||||||
|
if(obj.start){
|
||||||
|
plugin.start()
|
||||||
|
}
|
||||||
|
}, response => {
|
||||||
|
return this.errorMsg(response, obj.url)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
Promise.all(promises).then(result => {
|
Promise.all(promises).then(result => {
|
||||||
perf.allImg = result
|
perf.allImg = result
|
||||||
perf.load = Date.now() - this.startTime
|
perf.load = Date.now() - this.startTime
|
||||||
@ -332,8 +352,8 @@ class Loader{
|
|||||||
this.clean()
|
this.clean()
|
||||||
this.callback(songId)
|
this.callback(songId)
|
||||||
pageEvents.send("ready", readyEvent)
|
pageEvents.send("ready", readyEvent)
|
||||||
})
|
}, () => this.errorMsg())
|
||||||
}, this.errorMsg.bind(this))
|
}, () => this.errorMsg())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
addPromise(promise, url){
|
addPromise(promise, url){
|
||||||
@ -433,6 +453,7 @@ class Loader{
|
|||||||
}
|
}
|
||||||
var percentage = Math.floor(this.loadedAssets * 100 / (this.promises.length + this.afterJSCount))
|
var percentage = Math.floor(this.loadedAssets * 100 / (this.promises.length + this.afterJSCount))
|
||||||
this.errorTxt.element[this.errorTxt.method] = "```\n" + this.errorMessages.join("\n") + "\nPercentage: " + percentage + "%\n```"
|
this.errorTxt.element[this.errorTxt.method] = "```\n" + this.errorMessages.join("\n") + "\nPercentage: " + percentage + "%\n```"
|
||||||
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
assetLoaded(){
|
assetLoaded(){
|
||||||
if(!this.error){
|
if(!this.error){
|
||||||
|
@ -11,6 +11,7 @@ class P2Connection{
|
|||||||
this.allEvents = new Map()
|
this.allEvents = new Map()
|
||||||
this.addEventListener("message", this.message.bind(this))
|
this.addEventListener("message", this.message.bind(this))
|
||||||
this.currentHash = ""
|
this.currentHash = ""
|
||||||
|
this.disabled = 0
|
||||||
pageEvents.add(window, "hashchange", this.onhashchange.bind(this))
|
pageEvents.add(window, "hashchange", this.onhashchange.bind(this))
|
||||||
}
|
}
|
||||||
addEventListener(type, callback){
|
addEventListener(type, callback){
|
||||||
@ -257,11 +258,11 @@ class P2Connection{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
enable(){
|
enable(){
|
||||||
this.disabled = false
|
this.disabled = Math.max(0, this.disabled - 1)
|
||||||
this.open()
|
setTimeout(this.open.bind(this), 100)
|
||||||
}
|
}
|
||||||
disable(){
|
disable(){
|
||||||
this.disabled = true
|
this.disabled++
|
||||||
this.close()
|
this.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,18 +8,39 @@ class Plugins{
|
|||||||
this.hashes = []
|
this.hashes = []
|
||||||
this.startOrder = []
|
this.startOrder = []
|
||||||
}
|
}
|
||||||
add(script, name){
|
add(script, options){
|
||||||
|
options = options || {}
|
||||||
var hash = md5.base64(script.toString())
|
var hash = md5.base64(script.toString())
|
||||||
|
var isUrl = typeof script === "string" && !options.raw
|
||||||
|
if(isUrl){
|
||||||
|
hash = "url " + hash
|
||||||
|
}else if(typeof script !== "string"){
|
||||||
|
hash = "class " + hash
|
||||||
|
}
|
||||||
|
var name = options.name
|
||||||
|
if(!name && isUrl){
|
||||||
|
name = script
|
||||||
|
var index = name.lastIndexOf("/")
|
||||||
|
if(index !== -1){
|
||||||
|
name = name.slice(index + 1)
|
||||||
|
}
|
||||||
|
if(name.endsWith(".taikoweb.js")){
|
||||||
|
name = name.slice(0, -".taikoweb.js".length)
|
||||||
|
}else if(name.endsWith(".js")){
|
||||||
|
name = name.slice(0, -".js".length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name = name || "plugin"
|
||||||
if(this.hashes.indexOf(hash) !== -1){
|
if(this.hashes.indexOf(hash) !== -1){
|
||||||
console.warn("Skip adding an already addded plugin: " + name)
|
console.warn("Skip adding an already addded plugin: " + name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
name = name || "plugin"
|
|
||||||
var baseName = name
|
var baseName = name
|
||||||
for(var i = 2; name in this.pluginMap; i++){
|
for(var i = 2; name in this.pluginMap; i++){
|
||||||
name = baseName + i.toString()
|
name = baseName + i.toString()
|
||||||
}
|
}
|
||||||
var plugin = new PluginLoader(script, name, hash)
|
var plugin = new PluginLoader(script, name, hash, options.raw)
|
||||||
|
plugin.hide = !!options.hide
|
||||||
this.allPlugins.push({
|
this.allPlugins.push({
|
||||||
name: name,
|
name: name,
|
||||||
plugin: plugin
|
plugin: plugin
|
||||||
@ -41,10 +62,14 @@ class Plugins{
|
|||||||
if(index !== -1){
|
if(index !== -1){
|
||||||
this.allPlugins.splice(index, 1)
|
this.allPlugins.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
var index = this.startOrder.indexOf(name)
|
||||||
|
if(index !== -1){
|
||||||
|
this.startOrder.splice(index, 1)
|
||||||
|
}
|
||||||
delete this.pluginMap[name]
|
delete this.pluginMap[name]
|
||||||
}
|
}
|
||||||
load(name){
|
load(name){
|
||||||
this.pluginMap[name].load()
|
return this.pluginMap[name].load()
|
||||||
}
|
}
|
||||||
loadAll(){
|
loadAll(){
|
||||||
for(var i = 0; i < this.allPlugins.length; i++){
|
for(var i = 0; i < this.allPlugins.length; i++){
|
||||||
@ -52,7 +77,7 @@ class Plugins{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
start(name){
|
start(name){
|
||||||
this.pluginMap[name].start()
|
return this.pluginMap[name].start()
|
||||||
}
|
}
|
||||||
startAll(){
|
startAll(){
|
||||||
for(var i = 0; i < this.allPlugins.length; i++){
|
for(var i = 0; i < this.allPlugins.length; i++){
|
||||||
@ -60,7 +85,7 @@ class Plugins{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
stop(name){
|
stop(name){
|
||||||
this.pluginMap[name].stop()
|
return this.pluginMap[name].stop()
|
||||||
}
|
}
|
||||||
stopAll(){
|
stopAll(){
|
||||||
for(var i = this.startOrder.length; i--;){
|
for(var i = this.startOrder.length; i--;){
|
||||||
@ -68,7 +93,7 @@ class Plugins{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
unload(name){
|
unload(name){
|
||||||
this.pluginMap[name].unload()
|
return this.pluginMap[name].unload()
|
||||||
}
|
}
|
||||||
unloadAll(){
|
unloadAll(){
|
||||||
for(var i = this.startOrder.length; i--;){
|
for(var i = this.startOrder.length; i--;){
|
||||||
@ -127,12 +152,39 @@ class Plugins{
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSettings(){
|
getSettings(){
|
||||||
var items = {}
|
var items = []
|
||||||
for(var i = 0; i < this.allPlugins.length; i++){
|
for(var i = 0; i < this.allPlugins.length; i++){
|
||||||
var obj = this.allPlugins[i]
|
var obj = this.allPlugins[i]
|
||||||
let plugin = obj.plugin
|
let plugin = obj.plugin
|
||||||
items[obj.name] = {
|
if(!plugin.loaded){
|
||||||
name: plugin.module ? this.getLocalTitle(plugin.module.name || obj.name, plugin.module.name_lang) : obj.name,
|
continue
|
||||||
|
}
|
||||||
|
if(!plugin.hide){
|
||||||
|
let description
|
||||||
|
let description_lang
|
||||||
|
var module = plugin.module
|
||||||
|
if(module){
|
||||||
|
description = [
|
||||||
|
module.description,
|
||||||
|
module.author ? strings.plugins.author.replace("%s", module.author) : null,
|
||||||
|
module.version ? strings.plugins.version.replace("%s", module.version) : null
|
||||||
|
].filter(Boolean).join("\n")
|
||||||
|
description_lang = {}
|
||||||
|
languageList.forEach(lang => {
|
||||||
|
description_lang[lang] = [
|
||||||
|
this.getLocalTitle(module.description, module.description_lang, lang),
|
||||||
|
module.author ? allStrings[lang].plugins.author.replace("%s", module.author) : null,
|
||||||
|
module.version ? allStrings[lang].plugins.version.replace("%s", module.version) : null
|
||||||
|
].filter(Boolean).join("\n")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var name = module && module.name || obj.name
|
||||||
|
var name_lang = module && module.name_lang
|
||||||
|
items.push({
|
||||||
|
name: name,
|
||||||
|
name_lang: name_lang,
|
||||||
|
description: description,
|
||||||
|
description_lang: description_lang,
|
||||||
type: "toggle",
|
type: "toggle",
|
||||||
default: true,
|
default: true,
|
||||||
getItem: () => plugin.started,
|
getItem: () => plugin.started,
|
||||||
@ -143,14 +195,36 @@ class Plugins{
|
|||||||
this.start(plugin.name)
|
this.start(plugin.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var settings = plugin.settings()
|
||||||
|
if(settings){
|
||||||
|
settings.forEach(setting => {
|
||||||
|
if(!setting.name){
|
||||||
|
setting.name = name
|
||||||
|
if(!setting.name_lang){
|
||||||
|
setting.name_lang = name_lang
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(typeof setting.getItem !== "function"){
|
||||||
|
setting.getItem = () => {}
|
||||||
|
}
|
||||||
|
if(typeof setting.setItem !== "function"){
|
||||||
|
setting.setItem = () => {}
|
||||||
|
}
|
||||||
|
if(!("indent" in setting) && !plugin.hide){
|
||||||
|
setting.indent = 1
|
||||||
|
}
|
||||||
|
items.push(setting)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
getLocalTitle(title, titleLang){
|
getLocalTitle(title, titleLang, lang){
|
||||||
if(titleLang){
|
if(titleLang){
|
||||||
for(var id in titleLang){
|
for(var id in titleLang){
|
||||||
if(id === strings.id && titleLang[id]){
|
if(id === (lang || strings.id) && titleLang[id]){
|
||||||
return titleLang[id]
|
return titleLang[id]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,20 +237,30 @@ class PluginLoader{
|
|||||||
constructor(...args){
|
constructor(...args){
|
||||||
this.init(...args)
|
this.init(...args)
|
||||||
}
|
}
|
||||||
init(script, name, hash){
|
init(script, name, hash, raw){
|
||||||
this.name = name
|
this.name = name
|
||||||
this.hash = hash
|
this.hash = hash
|
||||||
if(typeof script === "string"){
|
if(typeof script === "string"){
|
||||||
|
if(raw){
|
||||||
this.url = URL.createObjectURL(new Blob([script], {
|
this.url = URL.createObjectURL(new Blob([script], {
|
||||||
type: "application/javascript"
|
type: "application/javascript"
|
||||||
}))
|
}))
|
||||||
|
}else{
|
||||||
|
this.url = script
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
this.class = script
|
this.class = script
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
load(){
|
load(loadErrors){
|
||||||
if(this.loaded || !this.url && !this.class){
|
if(this.loaded){
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
|
}else if(!this.url && !this.class){
|
||||||
|
if(loadErrors){
|
||||||
|
return Promise.reject()
|
||||||
|
}else{
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
return (this.url ? import(this.url) : Promise.resolve({
|
return (this.url ? import(this.url) : Promise.resolve({
|
||||||
default: this.class
|
default: this.class
|
||||||
@ -209,7 +293,11 @@ class PluginLoader{
|
|||||||
}, e => {
|
}, e => {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
this.error()
|
this.error()
|
||||||
|
if(loadErrors){
|
||||||
|
return Promise.reject(e)
|
||||||
|
}else{
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -300,6 +388,20 @@ class PluginLoader{
|
|||||||
}
|
}
|
||||||
this.unload(true)
|
this.unload(true)
|
||||||
}
|
}
|
||||||
|
settings(){
|
||||||
|
if(this.module && this.module.settings){
|
||||||
|
try{
|
||||||
|
var settings = this.module.settings()
|
||||||
|
}catch(e){
|
||||||
|
console.error(e)
|
||||||
|
this.error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(Array.isArray(settings)){
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EditValue{
|
class EditValue{
|
||||||
@ -308,6 +410,9 @@ class EditValue{
|
|||||||
}
|
}
|
||||||
init(parent, name){
|
init(parent, name){
|
||||||
if(name){
|
if(name){
|
||||||
|
if(!parent){
|
||||||
|
throw new Error("Parent is not defined")
|
||||||
|
}
|
||||||
this.name = [parent, name]
|
this.name = [parent, name]
|
||||||
this.delete = !(name in parent)
|
this.delete = !(name in parent)
|
||||||
}else{
|
}else{
|
||||||
|
@ -253,27 +253,72 @@ class SettingsView{
|
|||||||
}
|
}
|
||||||
var settingBox = document.createElement("div")
|
var settingBox = document.createElement("div")
|
||||||
settingBox.classList.add("setting-box")
|
settingBox.classList.add("setting-box")
|
||||||
|
if(current.indent){
|
||||||
|
settingBox.style.marginLeft = (2 * current.indent || 0).toString() + "em"
|
||||||
|
}
|
||||||
var nameDiv = document.createElement("div")
|
var nameDiv = document.createElement("div")
|
||||||
nameDiv.classList.add("setting-name", "stroke-sub")
|
nameDiv.classList.add("setting-name", "stroke-sub")
|
||||||
var name = current.name || strings.settings[i].name
|
if(current.name || current.name_lang){
|
||||||
|
var name = this.getLocalTitle(current.name, current.name_lang)
|
||||||
|
}else{
|
||||||
|
var name = strings.settings[i].name
|
||||||
|
}
|
||||||
this.setAltText(nameDiv, name)
|
this.setAltText(nameDiv, name)
|
||||||
|
if(current.description || current.description_lang){
|
||||||
|
nameDiv.title = this.getLocalTitle(current.description, current.description_lang) || ""
|
||||||
|
}
|
||||||
settingBox.appendChild(nameDiv)
|
settingBox.appendChild(nameDiv)
|
||||||
var valueDiv = document.createElement("div")
|
var valueDiv = document.createElement("div")
|
||||||
valueDiv.classList.add("setting-value")
|
valueDiv.classList.add("setting-value")
|
||||||
this.getValue(i, valueDiv)
|
let outputObject = {
|
||||||
|
id: i,
|
||||||
|
settingBox: settingBox,
|
||||||
|
nameDiv: nameDiv,
|
||||||
|
valueDiv: valueDiv,
|
||||||
|
name: current.name,
|
||||||
|
name_lang: current.name_lang,
|
||||||
|
description: current.description,
|
||||||
|
description_lang: current.description_lang
|
||||||
|
}
|
||||||
|
if(current.type === "number"){
|
||||||
|
["min", "max", "fixedPoint", "step", "sign", "format", "format_lang"].forEach(opt => {
|
||||||
|
if(opt in current){
|
||||||
|
outputObject[opt] = current[opt]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
outputObject.valueText = document.createTextNode("")
|
||||||
|
valueDiv.appendChild(outputObject.valueText)
|
||||||
|
var buttons = document.createElement("div")
|
||||||
|
buttons.classList.add("latency-buttons")
|
||||||
|
var buttonMinus = document.createElement("span")
|
||||||
|
buttonMinus.innerText = "-"
|
||||||
|
buttons.appendChild(buttonMinus)
|
||||||
|
this.addTouchRepeat(buttonMinus, event => {
|
||||||
|
this.numberAdjust(outputObject, -1)
|
||||||
|
})
|
||||||
|
var buttonPlus = document.createElement("span")
|
||||||
|
buttonPlus.innerText = "+"
|
||||||
|
buttons.appendChild(buttonPlus)
|
||||||
|
this.addTouchRepeat(buttonPlus, event => {
|
||||||
|
this.numberAdjust(outputObject, 1)
|
||||||
|
})
|
||||||
|
valueDiv.appendChild(buttons)
|
||||||
|
this.addTouch(settingBox, event => {
|
||||||
|
if(event.target.tagName !== "SPAN"){
|
||||||
|
this.setValue(i)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
this.addTouchEnd(settingBox, event => this.setValue(i))
|
||||||
|
}
|
||||||
settingBox.appendChild(valueDiv)
|
settingBox.appendChild(valueDiv)
|
||||||
content.appendChild(settingBox)
|
content.appendChild(settingBox)
|
||||||
if(!toSetting && this.items.length === this.selected || toSetting === i){
|
if(!toSetting && this.items.length === this.selected || toSetting === i){
|
||||||
this.selected = this.items.length
|
this.selected = this.items.length
|
||||||
settingBox.classList.add("selected")
|
settingBox.classList.add("selected")
|
||||||
}
|
}
|
||||||
this.addTouchEnd(settingBox, event => this.setValue(i))
|
this.items.push(outputObject)
|
||||||
this.items.push({
|
this.getValue(i, valueDiv)
|
||||||
id: i,
|
|
||||||
settingBox: settingBox,
|
|
||||||
nameDiv: nameDiv,
|
|
||||||
valueDiv: valueDiv
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
this.items.push({
|
this.items.push({
|
||||||
id: "default",
|
id: "default",
|
||||||
@ -443,6 +488,9 @@ class SettingsView{
|
|||||||
pageEvents.remove(element, ["mousedown", "touchend"])
|
pageEvents.remove(element, ["mousedown", "touchend"])
|
||||||
}
|
}
|
||||||
getValue(name, valueDiv){
|
getValue(name, valueDiv){
|
||||||
|
if(!this.items){
|
||||||
|
return
|
||||||
|
}
|
||||||
var current = this.settingsItems[name]
|
var current = this.settingsItems[name]
|
||||||
if(current.getItem){
|
if(current.getItem){
|
||||||
var value = current.getItem()
|
var value = current.getItem()
|
||||||
@ -482,6 +530,17 @@ class SettingsView{
|
|||||||
}
|
}
|
||||||
value += string
|
value += string
|
||||||
})
|
})
|
||||||
|
}else if(current.type === "number"){
|
||||||
|
var mul = Math.pow(10, current.fixedPoint || 0)
|
||||||
|
this.items[name].value = value * mul
|
||||||
|
value = Intl.NumberFormat(strings.intl, current.sign ? {
|
||||||
|
signDisplay: "always"
|
||||||
|
} : undefined).format(value)
|
||||||
|
if(current.format || current.format_lang){
|
||||||
|
value = this.getLocalTitle(current.format, current.format_lang).replace("%s", value)
|
||||||
|
}
|
||||||
|
this.items[name].valueText.data = value
|
||||||
|
return
|
||||||
}
|
}
|
||||||
valueDiv.innerText = value
|
valueDiv.innerText = value
|
||||||
}
|
}
|
||||||
@ -496,6 +555,9 @@ class SettingsView{
|
|||||||
var selectedIndex = this.items.findIndex(item => item.id === name)
|
var selectedIndex = this.items.findIndex(item => item.id === name)
|
||||||
var selected = this.items[selectedIndex]
|
var selected = this.items[selectedIndex]
|
||||||
if(this.mode !== "settings"){
|
if(this.mode !== "settings"){
|
||||||
|
if(this.mode === "number"){
|
||||||
|
return this.numberBack(this.items[this.selected])
|
||||||
|
}
|
||||||
if(this.selected === selectedIndex){
|
if(this.selected === selectedIndex){
|
||||||
this.keyboardBack(selected)
|
this.keyboardBack(selected)
|
||||||
this.playSound("se_don")
|
this.playSound("se_don")
|
||||||
@ -530,6 +592,12 @@ class SettingsView{
|
|||||||
this.latencySet()
|
this.latencySet()
|
||||||
this.playSound("se_don")
|
this.playSound("se_don")
|
||||||
return
|
return
|
||||||
|
}else if(current.type === "number"){
|
||||||
|
this.mode = "number"
|
||||||
|
selected.settingBox.style.animation = "none"
|
||||||
|
selected.valueDiv.classList.add("selected")
|
||||||
|
this.playSound("se_don")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if(current.setItem){
|
if(current.setItem){
|
||||||
promise = current.setItem(value)
|
promise = current.setItem(value)
|
||||||
@ -633,6 +701,19 @@ class SettingsView{
|
|||||||
this.playSound(name === "confirm" ? "se_don" : "se_cancel")
|
this.playSound(name === "confirm" ? "se_don" : "se_cancel")
|
||||||
}else if(name === "up" || name === "right" || name === "down" || name === "left"){
|
}else if(name === "up" || name === "right" || name === "down" || name === "left"){
|
||||||
this.latencySetAdjust(latencySelected, (name === "up" || name === "right") ? 1 : -1)
|
this.latencySetAdjust(latencySelected, (name === "up" || name === "right") ? 1 : -1)
|
||||||
|
if(event){
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else if(this.mode === "number"){
|
||||||
|
if(name === "confirm" || name === "back"){
|
||||||
|
this.numberBack(selected)
|
||||||
|
this.playSound(name === "confirm" ? "se_don" : "se_cancel")
|
||||||
|
}else if(name === "up" || name === "right" || name === "down" || name === "left"){
|
||||||
|
this.numberAdjust(selected, (name === "up" || name === "right") ? 1 : -1)
|
||||||
|
if(event){
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -833,6 +914,42 @@ class SettingsView{
|
|||||||
this.latencySettings.style.display = ""
|
this.latencySettings.style.display = ""
|
||||||
this.mode = "settings"
|
this.mode = "settings"
|
||||||
}
|
}
|
||||||
|
numberAdjust(selected, add){
|
||||||
|
var selectedItem = this.items[this.selected]
|
||||||
|
var mul = Math.pow(10, selected.fixedPoint || 0)
|
||||||
|
selectedItem.value += add * ("step" in selected ? selected.step : 1)
|
||||||
|
if("max" in selected && selectedItem.value > selected.max * mul){
|
||||||
|
selectedItem.value = selected.max * mul
|
||||||
|
}else if("min" in selected && selectedItem.value < selected.min * mul){
|
||||||
|
selectedItem.value = selected.min * mul
|
||||||
|
}else{
|
||||||
|
this.playSound("se_ka")
|
||||||
|
}
|
||||||
|
var valueText = Intl.NumberFormat(strings.intl, selected.sign ? {
|
||||||
|
signDisplay: "always"
|
||||||
|
} : undefined).format(selectedItem.value / mul)
|
||||||
|
if(selected.format || selected.format_lang){
|
||||||
|
valueText = this.getLocalTitle(selected.format, selected.format_lang).replace("%s", valueText)
|
||||||
|
}
|
||||||
|
selectedItem.valueText.data = valueText
|
||||||
|
}
|
||||||
|
numberBack(selected){
|
||||||
|
this.mode = "settings"
|
||||||
|
selected.settingBox.style.animation = ""
|
||||||
|
selected.valueDiv.classList.remove("selected")
|
||||||
|
var current = this.settingsItems[selected.id]
|
||||||
|
var promise
|
||||||
|
var mul = Math.pow(10, selected.fixedPoint || 0)
|
||||||
|
var value = selected.value / mul
|
||||||
|
if(current.setItem){
|
||||||
|
promise = current.setItem(value)
|
||||||
|
}else{
|
||||||
|
settings.setItem(selected.id, value)
|
||||||
|
}
|
||||||
|
(promise || Promise.resolve()).then(() => {
|
||||||
|
this.getValue(selected.id, selected.valueText)
|
||||||
|
})
|
||||||
|
}
|
||||||
addMs(input){
|
addMs(input){
|
||||||
var split = strings.calibration.ms.split("%s")
|
var split = strings.calibration.ms.split("%s")
|
||||||
var index = 0
|
var index = 0
|
||||||
@ -869,6 +986,9 @@ class SettingsView{
|
|||||||
this.playSound("se_don")
|
this.playSound("se_don")
|
||||||
}
|
}
|
||||||
onEnd(){
|
onEnd(){
|
||||||
|
if(this.mode === "number"){
|
||||||
|
this.numberBack(this.items[this.selected])
|
||||||
|
}
|
||||||
this.clean()
|
this.clean()
|
||||||
this.playSound("se_don")
|
this.playSound("se_don")
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -882,6 +1002,16 @@ class SettingsView{
|
|||||||
}
|
}
|
||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
|
getLocalTitle(title, titleLang){
|
||||||
|
if(titleLang){
|
||||||
|
for(var id in titleLang){
|
||||||
|
if(id === strings.id && titleLang[id]){
|
||||||
|
return titleLang[id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return title
|
||||||
|
}
|
||||||
setLang(lang){
|
setLang(lang){
|
||||||
settings.setLang(lang)
|
settings.setLang(lang)
|
||||||
if(failedTests.length !== 0){
|
if(failedTests.length !== 0){
|
||||||
@ -890,8 +1020,15 @@ class SettingsView{
|
|||||||
for(var i in this.items){
|
for(var i in this.items){
|
||||||
var item = this.items[i]
|
var item = this.items[i]
|
||||||
if(item.valueDiv){
|
if(item.valueDiv){
|
||||||
|
if(item.name || item.name_lang){
|
||||||
|
var name = this.getLocalTitle(item.name, item.name_lang)
|
||||||
|
}else{
|
||||||
var name = strings.settings[item.id].name
|
var name = strings.settings[item.id].name
|
||||||
|
}
|
||||||
this.setAltText(item.nameDiv, name)
|
this.setAltText(item.nameDiv, name)
|
||||||
|
if(item.description || item.description_lang){
|
||||||
|
item.nameDiv.title = this.getLocalTitle(item.description, item.description_lang) || ""
|
||||||
|
}
|
||||||
this.getValue(item.id, item.valueDiv)
|
this.getValue(item.id, item.valueDiv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1161,9 +1161,9 @@ class SongSelect{
|
|||||||
selectedWidth = this.songAsset.selectedWidth
|
selectedWidth = this.songAsset.selectedWidth
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastMoveMul = Math.pow(Math.abs(this.state.lastMove), 1 / 4)
|
var lastMoveMul = Math.pow(Math.abs(this.state.lastMove || 0), 1 / 4)
|
||||||
var changeSpeed = this.songSelecting.speed * lastMoveMul
|
var changeSpeed = this.songSelecting.speed * lastMoveMul
|
||||||
var resize = changeSpeed * this.songSelecting.resize / lastMoveMul
|
var resize = changeSpeed * (lastMoveMul === 0 ? 0 : this.songSelecting.resize / lastMoveMul)
|
||||||
var scrollDelay = changeSpeed * this.songSelecting.scrollDelay
|
var scrollDelay = changeSpeed * this.songSelecting.scrollDelay
|
||||||
var resize2 = changeSpeed - resize
|
var resize2 = changeSpeed - resize
|
||||||
var scroll = resize2 - resize - scrollDelay * 2
|
var scroll = resize2 - resize - scrollDelay * 2
|
||||||
|
@ -1323,6 +1323,14 @@ var translations = {
|
|||||||
one: "%s plugin",
|
one: "%s plugin",
|
||||||
other: "%s plugins"
|
other: "%s plugins"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
author: {
|
||||||
|
ja: null,
|
||||||
|
en: "By %s"
|
||||||
|
},
|
||||||
|
version: {
|
||||||
|
ja: null,
|
||||||
|
en: "Version %s"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
0
tools/hooks/post-checkout
Normal file → Executable file
0
tools/hooks/post-checkout
Normal file → Executable file
0
tools/hooks/post-commit
Normal file → Executable file
0
tools/hooks/post-commit
Normal file → Executable file
0
tools/hooks/post-merge
Normal file → Executable file
0
tools/hooks/post-merge
Normal file → Executable file
0
tools/hooks/post-rewrite
Normal file → Executable file
0
tools/hooks/post-rewrite
Normal file → Executable file
Loading…
Reference in New Issue
Block a user