nav item
+# when rendered gets the menu item as 'item'
+# customtemplate =
+
+[menu::ddmenu::0]
+target_id = menu-left
+identity = tools
+title = Tools
+use_gettext = True
+# these classes get added to the nav-item
+# classes =
+iconclass = fa-wrench
+order = 2
+# perms =
+# html rendered from the customtemplate only replaces the inner part of the
+# so the part that is displayed in the bar
+# this item is accessible as 'menu' in the template
+# customtemplate =
+# what tag do use for the display part item, default is 'a'
+# nodetag = 'a'
+# additional css classes to add to the dropdown element, default ''
+dropclasses =
+# additional css classes to add to the dropdown element, default 'nav-link'
+triggerclasses = nav-link
+
+[menu::ddmenu::1]
+target_id = menu-right
+identity = dd-settings
+classes = ml-3
+triggerclasses = btn btn-primary btn-sm
+dropclasses = dropdown-menu-right
+nodetag = button
+# any % must be escaped by an other %
+# so jinja2 syntax ist {% if ... %}
+# but we need {%% if ... %%}
+# because % is the interpolation trigger
+customtemplate = {%% if current_user.is_authenticated and current_user.get_eve_id() is not none %%} {%% else %%} {{ _('Not loggedin') }} {%% endif %%}
+
+[menu::dditem::0]
+target_id = dd-settings
+title = Logout
+use_gettext = True
+url = logout
+url_for = True
+iconclass = fa-sign-out
+order = 9998
+need_authenticated = True
+
+[menu::dditem::1]
+target_id = dd-settings
+title = About
+use_gettext = True
+url = about
+url_for = True
+iconclass = fa-info-circle
+order = 9999
+
+[menu::dditem::2]
+target_id = tools
+title = Command Core List
+use_gettext = True
+url = accounts_cc.accounts
+url_for = True
+iconclass = fa-list
+order = 4
+perms = commandcore
+
+[menu::dddivider::0]
+target_id = tools
+order = 5
+perms = commandcore|comphistory_search
+
+[menu::dditem::3]
+target_id = tools
+title = Comp History Search
+use_gettext = True
+url = comp_history_search.index
+url_for = True
+iconclass = fa-search
+order = 6
+perms = commandcore|comphistory_search
+
+
+[menu::dditem::4]
+target_id = tools
+title = Custom Notifications
+use_gettext = True,
+url = notification.alarm_idx
+url_for = True
+order = 0
+iconclass = fa-bell-o
+
+[menu::dddivider::1]
+target_id = tools
+order = 1
+perms = commandcore
+
+[menu::dditem::5]
+target_id = dd-settings
+title = Settings
+use_gettext = True
+url = settings_overview.overview
+url_for = True
+perms = settings_access
+iconclass = fa-cog
+order = 0
+
+[menu::menuitem::1]
+target_id = menu-left
+title = X-UP
+use_gettext = True
+classes = bg-success
+url = xup.index
+url_for = True
+iconclasses = fa-user-plus
+order = 0
+
+[menu::dditem::6]
+target_id = tools
+title = Comp History
+use_gettext = True
+url = fittings.history_default
+url_for = True
+iconclass = fa-history
+order = 2
+perms = commandcore|comphistory_view
+
+[menu::dditem::7]
+target_id = tools
+title = Reform
+use_gettext = True
+url = fleet_reform.index
+url_for = True
+iconclass = fa-undo
+order = 3
+perms = commandcore|fleet_management
+
+# this is the language select
+# it needs to have id=lang-select for the js to work
+[menu::menuitem::2]
+target_id = menu-right
+order = 0
+customtemplate =
+need_authenticated = True
\ No newline at end of file
diff --git a/extract.bat b/extract.bat
new file mode 100644
index 00000000..443ac269
--- /dev/null
+++ b/extract.bat
@@ -0,0 +1 @@
+pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot .
diff --git a/main.py b/main.py
index 084230ca..8ca13179 100644
--- a/main.py
+++ b/main.py
@@ -1,8 +1,24 @@
import gevent_patch_helper
-import logging
+import logging.config
+import os
+import json
+
+# setup logging
+logger_config = os.path.join('.', 'config', 'logger.base.json')
+if os.path.isfile(logger_config):
+ with open(logger_config, 'r') as fp:
+ cfg = json.load(fp)
+ logging.config.dictConfig(cfg)
+
+logger_config = os.path.join('.', 'config', 'logger.user.json')
+if os.path.isfile(logger_config):
+ with open(logger_config, 'r') as fp:
+ cfg = json.load(fp)
+ logging.config.dictConfig(cfg)
+
from werkzeug.contrib.fixers import ProxyFix
-from logging.handlers import TimedRotatingFileHandler
from gevent.pywsgi import WSGIServer
+
from waitlist.blueprints.fittings import bp_waitlist
from waitlist.blueprints.fc_sso import bp as fc_sso_bp
from waitlist.blueprints.fleet import bp as fleet_bp
@@ -45,7 +61,6 @@
# load base app routes
from waitlist.blueprints import *
-
app.register_blueprint(bp_waitlist)
app.register_blueprint(feedback.feedback, url_prefix="/feedback")
app.register_blueprint(fc_sso_bp, url_prefix="/fc_sso")
@@ -91,59 +106,16 @@
logger = logging.getLogger(__name__)
-err_fh = None
-info_fh = None
-debug_fh = None
-
-
-class LogDedicatedLevelFilter(object):
- def __init__(self, level):
- self.__level = level
-
- def filter(self, log_record):
- return log_record.levelno == self.__level
-
def run_server():
- wsgi_logger = logging.getLogger("gevent.pywsgi.WSGIServer")
- wsgi_logger.addHandler(err_fh)
- wsgi_logger.addHandler(info_fh)
- wsgi_logger.addHandler(debug_fh)
- wsgi_logger.setLevel(logging.INFO)
app.wsgi_app = ProxyFix(app.wsgi_app)
- server = WSGIServer((config.server_bind, config.server_port), app, log=wsgi_logger, error_log=wsgi_logger)
+ wsgi_logger = logging.getLogger("gevent.pywsgi.WSGIServer")
+ server = WSGIServer((config.server_bind, config.server_port), app,
+ log=wsgi_logger, error_log=wsgi_logger)
server.serve_forever()
if __name__ == '__main__':
- err_fh = TimedRotatingFileHandler(filename=config.error_log, when="midnight", interval=1, utc=True)
- info_fh = TimedRotatingFileHandler(filename=config.info_log, when="midnight", interval=1, utc=True)
- debug_fh = TimedRotatingFileHandler(filename=config.debug_log, when="midnight", interval=1, utc=True)
-
- formatter = logging\
- .Formatter('%(asctime)s - %(name)s - %(levelname)s - %(pathname)s - %(funcName)s - %(lineno)d - %(message)s')
- err_fh.setFormatter(formatter)
- info_fh.setFormatter(formatter)
- debug_fh.setFormatter(formatter)
-
- info_fh.setLevel(logging.INFO)
- err_fh.setLevel(logging.ERROR)
- debug_fh.setLevel(logging.DEBUG)
-
- info_fh.addFilter(LogDedicatedLevelFilter(logging.INFO))
- debug_fh.addFilter(LogDedicatedLevelFilter(logging.DEBUG))
-
- waitlistlogger = logging.getLogger("waitlist")
- waitlistlogger.addHandler(err_fh)
- waitlistlogger.addHandler(info_fh)
- waitlistlogger.addHandler(debug_fh)
- waitlistlogger.setLevel(logging.INFO)
-
- app.logger.addHandler(err_fh)
- app.logger.addHandler(info_fh)
- app.logger.addHandler(debug_fh)
- app.logger.setLevel(logging.INFO)
-
# app.run(host="0.0.0.0", port=81, debug=True)
# connect account signal handler
diff --git a/migrations/versions/8ec8c7bb9459_.py b/migrations/versions/8ec8c7bb9459_.py
new file mode 100644
index 00000000..cbb25e78
--- /dev/null
+++ b/migrations/versions/8ec8c7bb9459_.py
@@ -0,0 +1,34 @@
+"""empty message
+
+Revision ID: 8ec8c7bb9459
+Revises: 9ab201421111
+Create Date: 2018-09-10 17:52:00.595675
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '8ec8c7bb9459'
+down_revision = '9ab201421111'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table('dogma_effect',
+ sa.Column('type_id', sa.Integer(), autoincrement=False, nullable=False),
+ sa.Column('effect_id', sa.Integer(), autoincrement=False, nullable=False),
+ sa.Column('is_default', sa.Boolean(name='is_default'), nullable=True),
+ sa.ForeignKeyConstraint(['type_id'], ['invtypes.type_id'], name=op.f('fk_dogma_effect_type_id_invtypes'), ondelete='CASCADE'),
+ sa.PrimaryKeyConstraint('type_id', 'effect_id', name=op.f('pk_dogma_effect'))
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_table('dogma_effect')
+ # ### end Alembic commands ###
diff --git a/migrations/versions/9ab201421111_.py b/migrations/versions/9ab201421111_.py
new file mode 100644
index 00000000..2db78d88
--- /dev/null
+++ b/migrations/versions/9ab201421111_.py
@@ -0,0 +1,83 @@
+"""empty message
+
+Revision ID: 9ab201421111
+Revises: c2b7b284ed6f
+Create Date: 2018-09-08 13:40:35.106054
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '9ab201421111'
+down_revision = 'c2b7b284ed6f'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+
+ op.drop_constraint(op.f('fk_fit_module_fit_id_fittings'), 'fit_module', type_='foreignkey')
+ op.drop_constraint(op.f('fk_fit_module_module_id_invtypes'), 'fit_module', type_='foreignkey')
+ op.drop_constraint(op.f('pk_fit_module'), 'fit_module', type_='primary')
+ op.add_column('fit_module', sa.Column('location_flag', sa.Integer(), nullable=False, server_default=sa.text('1000')))
+
+ # remove auto increment from invtypes table
+ # drop all remaining foreign keys towards the invtypes.type_id column
+ op.drop_constraint(op.f('fk_fittings_ship_type_invtypes'), 'fittings', type_='foreignkey')
+ # now we can fix the id column (remove the auto increment)
+ op.alter_column('invtypes', 'type_id', autoincrement=False, existing_type=sa.Integer, existing_nullable=False)
+ op.create_foreign_key(op.f('fk_fittings_ship_type_invtypes'), 'fittings', 'invtypes', ['ship_type'], ['type_id'], onupdate='CASCADE')
+ # done removing autoincrement from invtypes table
+
+ # readd keys we removed to change the primary key of fit_module
+ op.create_primary_key(op.f('pk_fit_module'), 'fit_module',
+ ['fit_id', 'module_id', 'location_flag'])
+ op.create_foreign_key(op.f('fk_fit_module_fit_id_fittings'), 'fit_module', 'fittings', ['fit_id'], ['id'])
+ op.create_foreign_key(op.f('fk_fit_module_module_id_invtypes'), 'fit_module', 'invtypes', ['module_id'], ['type_id'], onupdate='CASCADE')
+
+ op.create_table('invcategory',
+ sa.Column('category_id', sa.Integer(), nullable=False, autoincrement=False),
+ sa.Column('category_name', sa.String(length=255), nullable=True),
+ sa.Column('published', sa.Boolean(name='is_published'), nullable=True),
+ sa.PrimaryKeyConstraint('category_id', name=op.f('pk_invcategory'))
+ )
+ op.create_table('dogma_attributes',
+ sa.Column('type_id', sa.Integer(), nullable=False, autoincrement=False),
+ sa.Column('attribute_id', sa.Integer(), nullable=False),
+ sa.Column('value', sa.Integer(), nullable=True),
+ sa.ForeignKeyConstraint(['type_id'], ['invtypes.type_id'], name=op.f('fk_dogma_attributes_type_id_invtypes'), ondelete='CASCADE'),
+ sa.PrimaryKeyConstraint('type_id', 'attribute_id', name=op.f('pk_dogma_attributes'))
+ )
+ op.create_table('invgroup',
+ sa.Column('group_id', sa.Integer(), nullable=False, autoincrement=False),
+ sa.Column('group_name', sa.String(length=255), nullable=True),
+ sa.Column('published', sa.Boolean(name='is_published'), nullable=True),
+ sa.Column('category_id', sa.Integer(), nullable=True),
+ sa.ForeignKeyConstraint(['category_id'], ['invcategory.category_id'], name=op.f('fk_invgroup_category_id_invcategory'), ondelete='CASCADE'),
+ sa.PrimaryKeyConstraint('group_id', name=op.f('pk_invgroup'))
+ )
+
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ # we don't need to reverse the removal of the autoincrement since it is better for older versions too
+ op.drop_constraint(op.f('fk_fit_module_fit_id_fittings'), 'fit_module', type_='foreignkey')
+ op.drop_constraint(op.f('fk_fit_module_module_id_invtypes'), 'fit_module', type_='foreignkey')
+ op.drop_constraint(op.f('pk_fit_module'), 'fit_module', type_='primary')
+
+ op.drop_column('fit_module', 'location_flag')
+
+ op.create_primary_key(op.f('pk_fit_module'), 'fit_module',
+ ['fit_id', 'module_id'])
+ op.create_foreign_key(op.f('fk_fit_module_fit_id_fittings'), 'fit_module', 'fittings', ['fit_id'], ['id'])
+ op.create_foreign_key(op.f('fk_fit_module_module_id_invtypes'), 'fit_module', 'invtypes', ['module_id'], ['type_id'])
+
+ op.drop_table('invgroup')
+ op.drop_table('dogma_attributes')
+ op.drop_table('invcategory')
+ # ### end Alembic commands ###
diff --git a/migrations/versions/c2b7b284ed6f_.py b/migrations/versions/c2b7b284ed6f_.py
new file mode 100644
index 00000000..9c560922
--- /dev/null
+++ b/migrations/versions/c2b7b284ed6f_.py
@@ -0,0 +1,30 @@
+"""empty message
+
+Revision ID: c2b7b284ed6f
+Revises: 217e6df96f88
+Create Date: 2018-06-20 14:04:04.014198
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = 'c2b7b284ed6f'
+down_revision = '217e6df96f88'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column('accounts', sa.Column('language', sa.String(length=10), nullable=True))
+ op.add_column('characters', sa.Column('language', sa.String(length=10), nullable=True))
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ##
+ op.drop_column('characters', 'language')
+ op.drop_column('accounts', 'language')
+ # ### end Alembic commands ###
diff --git a/migrations/versions/edc810245f94_.py b/migrations/versions/edc810245f94_.py
index 25ac14ee..9ed3f786 100644
--- a/migrations/versions/edc810245f94_.py
+++ b/migrations/versions/edc810245f94_.py
@@ -48,7 +48,7 @@ def upgrade():
)
op.create_index(op.f('ix_ssotoken_character_id'), 'ssotoken', ['character_id'], unique=False)
op.create_index(op.f('ix_ssotoken_account_id'), 'ssotoken', ['account_id'], unique=False)
- op.create_index('ix_character_id_account_id', 'ssotoken', ['character_id', 'account_id'], unique=False)
+ op.create_index(op.f('ix_character_id_account_id'), 'ssotoken', ['character_id', 'account_id'], unique=False)
op.create_table('eveapiscope',
sa.Column('token_id', sa.Integer(), nullable=False),
diff --git a/package.json b/package.json
index dbfc33b6..3e271f0c 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,8 @@
"dependencies": {
"babel-cli": "^6.26.0",
"babel-plugin-transform-remove-console": "^6.9.0",
- "babel-preset-minify": "^0.3.0"
+ "babel-preset-minify": "^0.3.0",
+ "csso-cli": "^1.1.0"
},
"devDependencies": {},
"license": "MIT"
diff --git a/requirements.txt b/requirements.txt
index 520d008f..1b7d6d6e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,14 +1,13 @@
Flask>=1.0.2
PyYAML>=3.12
SQLAlchemy>=1.2.7
-Werkzeug>=0.11.11
+Werkzeug>=0.14.1
alembic>=0.9.9
bcrypt>=3.1.1
blinker>=1.4
gevent>=1.3.1
requests>=2.18.4
-esipy==0.4.0
-cssmin>=0.2.0
+esipy>=0.4.3
Flask-Assets>=0.12
Flask-htmlmin>=1.2
Flask-Login>=0.4.1
@@ -19,4 +18,7 @@ Flask-SQLAlchemy>=2.3.2
Flask-CDN>=1.5.3
flask-limiter>=1.0.1
ts3==1.0.6
-Flask-Script>=2.0.6
\ No newline at end of file
+Flask-Script>=2.0.6
+Flask-Babel>=0.11.2
+csscompressor>=0.9.5
+flasgger>=0.9.0
\ No newline at end of file
diff --git a/static/css/base.css b/static/css/base.css
index 090f1204..da2ac45a 100644
--- a/static/css/base.css
+++ b/static/css/base.css
@@ -4,6 +4,7 @@
.profile-img-32 {
width: 32px;
height: 32px;
+ vertical-align: inherit;
}
.pretty-dd {
margin-bottom: 0;
diff --git a/static/css/font.css b/static/css/font.css
index 19c30ce3..8d3ac996 100644
--- a/static/css/font.css
+++ b/static/css/font.css
@@ -1,108 +1,14 @@
-/*
-This Font Software is licensed under the SIL Open Font License, Version 1.1.
-This license is copied below, and is also available with a FAQ at:
-http://scripts.sil.org/OFL
-
-
------------------------------------------------------------
-SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
------------------------------------------------------------
-
-PREAMBLE
-The goals of the Open Font License (OFL) are to stimulate worldwide
-development of collaborative font projects, to support the font creation
-efforts of academic and linguistic communities, and to provide a free and
-open framework in which fonts may be shared and improved in partnership
-with others.
-
-The OFL allows the licensed fonts to be used, studied, modified and
-redistributed freely as long as they are not sold by themselves. The
-fonts, including any derivative works, can be bundled, embedded,
-redistributed and/or sold with any software provided that any reserved
-names are not used by derivative works. The fonts and derivatives,
-however, cannot be released under any other type of license. The
-requirement for fonts to remain under this license does not apply
-to any document created using the fonts or their derivatives.
-
-DEFINITIONS
-"Font Software" refers to the set of files released by the Copyright
-Holder(s) under this license and clearly marked as such. This may
-include source files, build scripts and documentation.
-
-"Reserved Font Name" refers to any names specified as such after the
-copyright statement(s).
-
-"Original Version" refers to the collection of Font Software components as
-distributed by the Copyright Holder(s).
-
-"Modified Version" refers to any derivative made by adding to, deleting,
-or substituting -- in part or in whole -- any of the components of the
-Original Version, by changing formats or by porting the Font Software to a
-new environment.
-
-"Author" refers to any designer, engineer, programmer, technical
-writer or other person who contributed to the Font Software.
-
-PERMISSION & CONDITIONS
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of the Font Software, to use, study, copy, merge, embed, modify,
-redistribute, and sell modified and unmodified copies of the Font
-Software, subject to the following conditions:
-
-1) Neither the Font Software nor any of its individual components,
-in Original or Modified Versions, may be sold by itself.
-
-2) Original or Modified Versions of the Font Software may be bundled,
-redistributed and/or sold with any software, provided that each copy
-contains the above copyright notice and this license. These can be
-included either as stand-alone text files, human-readable headers or
-in the appropriate machine-readable metadata fields within text or
-binary files as long as those fields can be easily viewed by the user.
-
-3) No Modified Version of the Font Software may use the Reserved Font
-Name(s) unless explicit written permission is granted by the corresponding
-Copyright Holder. This restriction only applies to the primary font name as
-presented to the users.
-
-4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
-Software shall not be used to promote, endorse or advertise any
-Modified Version, except to acknowledge the contribution(s) of the
-Copyright Holder(s) and the Author(s) or with their explicit written
-permission.
-
-5) The Font Software, modified or unmodified, in part or in whole,
-must be distributed entirely under this license, and must not be
-distributed under any other license. The requirement for fonts to
-remain under this license does not apply to any document created
-using the Font Software.
-
-TERMINATION
-This license becomes null and void if any of the above conditions are
-not met.
-
-DISCLAIMER
-THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
-OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
-COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
-DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
-OTHER DEALINGS IN THE FONT SOFTWARE.
-
- */
@font-face {
- font-family: 'fa-subset';
- src: url(data:application/font-woff2;charset=utf-8;base64,d09GMgABAAAAAAmwAA0AAAAAFmwAAAlYAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGiAGYACCXhEICp8gmEkBNgIkA2wLOAAEIAWDMweCFxv6EaOijI0myf4yQUdDXD1bRmqbum9hRPRbNXwir4854tCMjNPApRD21HNB/zWm++5fAMlGEZRAloXq1MhqNh6Vqozz+f/5Tb0/QCHvsXwgBKJEkIjoaWAJW4l+YB1njZKfGgneOrQOVaqfiSU+0aSiOjpR5+Hz9v7+Lk4k4kCKoiAt8iyikHd5ug4AMKhvs7FCy+wQi68H8wp5PS2Xw8BEvRAn9i3pKfn/XSvtzNz84+eBSoeqStep+cnST7KQ48wRpgSkACW5CrXZMrvK2r4KXeEqXV2F7Dm2PPCjQfsVJxNE+wOMbfv4IkCA/+oXRgDbbU/wwg8XSIIyAByRKoJndgTLhW049uZPuQTAJ7offVAY4ZAVacy5j2hRrGP/H4O6dFO9SIJsM1mDbJ1PD0hq9yC2FSAqzPVq83aL+v/f3tX6GiTTu0s50sdF+/AIBg2gzGTLSA4gvTLAvxfYVNQ7Espqj54AzkFgAQjO1nHxp/EARBwT8KUZAaVowigSkLe44JJ0AlcqxXy+8PlIKRBESnL5hVVpHIFQyeYgdEiAD55eTyFQ5uRISINYXK0sJ76txwPitJ2AfCTp202El6X0HsZ+jErfhcDXlD48YyzwXJPQ70EYIgSAON4AY2b5Hu+BfBt49nc/FxLe48YdxPe5AD0sLyHkAwjzoNT1MYFv7zLvIcNukqCdIO4CDyDE9mz1mIPg9IiIvcXY+OQDlb7F6LEfwU54fRTRzsYeD5P+wCQbRGjvyep9YdWx7034UwCe+TbRTR45yBThzW90eOHaazwbbFAleJ6Ir4Y28YULkyfWhNcTDQ9BBCPEwRdO3yaIItDpYpNoo72DP7B73wh5Rbf7o7GtClyx70YoDPt3ZbVj7B9bHpKMJlCnSPaYJQ2JvyCMHi16lwyWhzqNbhQ39pHdDUKHZ7K8H4z3rHFqEjd51rJTzYbZsg0eKzrugaV9/TDkq1XXblE7S8VOkhC7WTp5Gg8MCfuLEhExgIUa0d9BPNsQTtbDGsLIHjQJGgw4hvCunEtngNj4X3DTSO9eAIYomCse93yzNDAHqzqdy5T/rcZ2uWFJ5DH94oCvW2K4hz8gCGxs6y0FzxUh8NxGn08QbI5w6PHbz3iltP/97bBX3LtzCPlGZpkX7H1S+oqHh6P07x2b7QszZoardzGSYZU2hHWZm4eisKopG9JklnTZe3f6JIbdXind5xnjHj9nyiNe2usTlc+QT/v6nvlh8ys1q7hn/btA7zUijXw6xjVdY785KaPBHahfNNAnoFJhois5IyRHlAJm7iif1Q/oIMNPCRxHCxI94VTvahRmYK08VJmrkm/Ozd8c9tJ15LWcMRY4gHwX8xBbJbqY0s1id4tQuEjFYquECAm72azu5EvCVBYrVfiBRe/ndnvAVqVcFE2lhIenCE0lh6PkUrHwUjIEyYiLwfaFD0gNCYGAxbHTpJ7UMiUDjVNVlL66Sq+vqnbfndJP6ZXZyc0Hm5OzAyRFPgDFUIA3zlNYJsCbCL0JzNRzlTymMchTcomJeCHmPjfIKymG2JlEj+DtpHrhb5S8uzQhr0itCqmKZ+YmldpYVzHu66kdjsvSacnlNRG9VncM72zvjoRuERVupw5oFJrODrXXtaNDo1B3dD6lsxN4QraanfJ/CptiOZPs9ImM9FssffEzU5lZ6RSt08U9a2hTlJW5E4Zn43SnEkpnmdnUJJuQwQAZIki941d1uqLBJKY/PbmtWj07f4M4Y4b0f6kzMSNDvOH2LtXq5Lb0/iSmaLBO9xVsnyLVZG+wUz8vqQkYEy4mnC8oaS0swBUHWRb/odlPlVFzUyItkdH1Evr7rphCTd6FYh7Jjc4Qo/rHvv1RHPHCRnG/oBFivj8VLUdR4y9w+DPixL+2ds9u0VbbZLZTrfzhC1mOv9LTf/GPfXTzx6BZx8eDZFZTivZ+W4Ls1oXeKoO+EuqI6VnNa3zdB8rnRQXyrWs0WtHHHMLzdyzEvmJ4paw+smHfwzUFjbEtWY82rxXJzHN38HU3r5kFWiUdDU31/tTQ+zRvc4dgv4xleCF5qAukdB0h2aRMW6LSqxTWYEkoJi1vCLTDZOHckOFpX5zz9BQ0cS40HBWNHBMaAnpqSKT3HTk//22zkhvqFz/D1e8oWBktXFhUwf2/cie4vUjorp6e3ntaFiW0GMHXBPrD0ecdC9pEC6Wk5LvvjK9gkbgc1y6U1kgsXychpVC+PRRxgLE79k6SEZN77Q7mQAR5gAmOTwR2ghkd3dE90mL3jtFRZoKMmBgPMgfIEhttIvczwQmBosHRkUDimwnahDcTA3cRhMgzzH6yhmG7ndlgiCXeZtHx0U3IJubMYWTB3Jzc3KCMSTCjR0fjBUi/iXM4EzkTsnE7CIpxb2hCFkxjPAn/rzS35fSKMBvkec4vDU/79lxd3iBFDebV1Vnz2qkkCm2etb3dyk4M5lHtqNjKWpP6WZfrWbXJ5EY4Qiczsc+CT0+gNX06nbsU41L3KHkO3YBRA63TuRwIO1yXTm47xnZ3ZV9jdWNfpaussszVjV6N8Wr6XfvWYKh8w44crq+/phsQbqDj410OjHi9VajBOF1ubk+pGzbC0L6dpVpc+Wr8ERdFJWxWieiD9z/4QFjKYpUKL63KuOjepmKDeeXKfNgrOIodpRrjEEtVnmVsysEUFVsl8z5RH6zu6tBaj9VOq7oJ+KLbYofAps+DhzH5/2s6yKrz8OfTLdttHWEhyq0K5DT4aFNI8w/AGPx9Rf8iBNJfq1GtVuHceINYaWxzT7ATwGMQCupOu9mzOH7JwCtrihJ44L24tgInYQJuPIstpXFbHq1Z9lF00I9Cdh1N+uvfGonIJ21OyLDNRFz6Hp7aAmJe2oIivl4UVfUVsKhGkKwygzYnqdlmskp8D1RbQJGtLShr97OjRt7Sp0s2O6du323mAI97tt3ZbTcf3VfMaE4LdLGl7UE7F3RFR6BwBAWL75jZ3HMNsEiSL0de3m902OxzByxJ+QxleapEjt0qmpmlLtPFEVIvmG6L/Q21egzQuUyOckr8OS6LI4k2W8y2Loe5N6l7cZK2Z0A3MGDJeYEHYFn7A/BjXRgWXgEFFVJYEUUVU1wJJZVSmpBIJLEoElLRYkYvWDzYn891WubmER42a8rzkGJBwcAhIKGgYWDBhgMXXvj78wEgoGDgIgAAAAA=) format('woff2');
-
+ font-family: 'wl-awesome';
+ src:
+ url(data:application/font-woff2;charset=utf-8;base64,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) format('woff2');
font-weight: normal;
font-style: normal;
}
-.fa {
+[class^="fa-"], [class*=" fa-"] {
/* use !important to prevent issues with browser extensions that change fonts */
- font-family: 'fa-subset' !important;
+ font-family: 'wl-awesome' !important;
speak: none;
font-style: normal;
font-weight: normal;
@@ -115,6 +21,63 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
-moz-osx-font-smoothing: grayscale;
}
+.fa-user-plus:before {
+ content: "\f234";
+}
+.fa-user-times:before {
+ content: "\f235";
+}
+.fa-address-book-o:before {
+ content: "\f2ba";
+}
+.fa-rotate-left:before {
+ content: "\e918";
+}
+.fa-undo:before {
+ content: "\e918";
+}
+.fa-list:before {
+ content: "\e919";
+}
+.fa-history:before {
+ content: "\e91a";
+}
+.fa-search:before {
+ content: "\e91b";
+}
+.fa-question-circle-o:before {
+ content: "\e91c";
+}
+.fa-info-circle:before {
+ content: "\e91d";
+}
+.fa-wrench:before {
+ content: "\e91e";
+}
+.fa-cog:before {
+ content: "\f013";
+}
+.fa-gear:before {
+ content: "\f013";
+}
+.fa-sign-out:before {
+ content: "\e904";
+}
+.fa-close:before {
+ content: "\e911";
+}
+.fa-remove:before {
+ content: "\e911";
+}
+.fa-times:before {
+ content: "\e911";
+}
+.fa-bell-o:before {
+ content: "\e916";
+}
+.fa-bell-slash-o:before {
+ content: "\e917";
+}
.fa-thumbs-o-up:before {
content: "\e905";
}
@@ -142,9 +105,6 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
.fa-minus-square:before {
content: "\e903";
}
-.fa-bell-o:before {
- content: "\e904";
-}
.fa-comment:before {
content: "\e906";
}
@@ -178,16 +138,20 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
.fa-clock-o:before {
content: "\e910";
}
-.fa-close:before {
- content: "\e911";
-}
-.fa-remove:before {
- content: "\e911";
-}
-.fa-times:before {
- content: "\e911";
-}
.fa-check:before {
content: "\e912";
}
+.fa-lg {
+ font-size: 1.333em;
+ line-height: 0.75em;
+ vertical-align: -15%;
+}
+
+.fa-2x { font-size: 2em; }
+
+.fa-3x { font-size: 3em; }
+
+.fa-4x { font-size: 4em; }
+
+.fa-5x { font-size: 5em; }
diff --git a/static/css/themes/dark.css b/static/css/themes/dark.css
index 629ff375..9f69c682 100644
--- a/static/css/themes/dark.css
+++ b/static/css/themes/dark.css
@@ -1866,7 +1866,7 @@ pre code {
}
.form-control:disabled, .form-control[readonly] {
- background-color: #6c757d;
+ background-color: #bbc1c5;
opacity: 1;
}
@@ -3877,7 +3877,7 @@ input[type="button"].btn-block {
.nav-tabs .nav-link.active,
.nav-tabs .nav-item.show .nav-link {
- color: #495057;
+ color: #f8f9fa;
background-color: #495057;
border-color: #dee2e6 #dee2e6 #495057;
}
@@ -4841,7 +4841,7 @@ input[type="button"].btn-block {
.jumbotron {
padding: 2rem 1rem;
margin-bottom: 2rem;
- background-color: #e9ecef;
+ background-color: gray-600;
border-radius: 0.3rem;
}
@@ -8996,3 +8996,10 @@ a.text-dark:hover, a.text-dark:focus {
background: #495057 !important;
color: #f8f9fa !important;
}
+
+input[type="button" i]:disabled, input[type="submit" i]:disabled,
+input[type="reset" i]:disabled, input[type="file" i]:disabled,
+button:disabled, optgroup:disabled,
+option:disabled, select[disabled] > option {
+ color: inherit;
+}
diff --git a/static/css/themes/dark_purple.css b/static/css/themes/dark_purple.css
index 09884011..7b282974 100644
--- a/static/css/themes/dark_purple.css
+++ b/static/css/themes/dark_purple.css
@@ -1866,7 +1866,7 @@ pre code {
}
.form-control:disabled, .form-control[readonly] {
- background-color: #6c757d;
+ background-color: #bbc1c5;
opacity: 1;
}
@@ -3877,7 +3877,7 @@ input[type="button"].btn-block {
.nav-tabs .nav-link.active,
.nav-tabs .nav-item.show .nav-link {
- color: #495057;
+ color: #f8f9fa;
background-color: #495057;
border-color: #dee2e6 #dee2e6 #495057;
}
@@ -4841,7 +4841,7 @@ input[type="button"].btn-block {
.jumbotron {
padding: 2rem 1rem;
margin-bottom: 2rem;
- background-color: #e9ecef;
+ background-color: gray-600;
border-radius: 0.3rem;
}
@@ -8996,3 +8996,10 @@ a.text-dark:hover, a.text-dark:focus {
background: #495057 !important;
color: #f8f9fa !important;
}
+
+input[type="button" i]:disabled, input[type="submit" i]:disabled,
+input[type="reset" i]:disabled, input[type="file" i]:disabled,
+button:disabled, optgroup:disabled,
+option:disabled, select[disabled] > option {
+ color: inherit;
+}
diff --git a/static/css/themes/default.css b/static/css/themes/default.css
index 864f1e2e..e22c2a65 100644
--- a/static/css/themes/default.css
+++ b/static/css/themes/default.css
@@ -8996,3 +8996,10 @@ a.text-dark:hover, a.text-dark:focus {
background: #fff !important;
color: #212529 !important;
}
+
+input[type="button" i]:disabled, input[type="submit" i]:disabled,
+input[type="reset" i]:disabled, input[type="file" i]:disabled,
+button:disabled, optgroup:disabled,
+option:disabled, select[disabled] > option {
+ color: inherit;
+}
diff --git a/static/js/accounts/accountslist-dom.js b/static/js/accounts/accountslist-dom.js
index ab468118..e3bb3c76 100644
--- a/static/js/accounts/accountslist-dom.js
+++ b/static/js/accounts/accountslist-dom.js
@@ -8,6 +8,57 @@ let swa_client = SwaggerClient(
}
);
+class AccountRow {
+ constructor(accountId, grid) {
+ this.accountId = accountId;
+ this.id = `account-${accountId}`
+ this.element = document.getElementById(this.id);
+ this.grid = grid;
+ }
+
+ set status(value) {
+ this.setColumn('status', value)
+ this.grid.refreshGrid();
+ }
+
+ get name() {
+ return this.getColumn('account-name');
+ }
+
+ get defaultCharName() {
+ return this.getColumn('current-character');
+ }
+
+ get rolesIdx() {
+ return this.getColumnIdx('roles');
+ }
+
+ getColumnIdx(name) {
+ return this.grid.getColumnIndex(name);
+
+ }
+
+ getColumn(name) {
+ let data = this.grid.dataUnfiltered != null ? this.grid.dataUnfiltered : this.grid.data;
+ // find the data row
+ let rowdata = data.find(e => e.id == this.id);
+ console.log("Get columnIdex for "+name);
+ console.log(rowdata);
+ console.log(this.grid);
+ let columnIndex = this.grid.getColumnIndex(name);
+ console.log('Got idx '+columnIndex+' for '+name);
+ return rowdata.columns[columnIndex];
+ }
+
+ setColumn(name, value) {
+ let griddata = this.grid.dataUnfiltered != null ? this.grid.dataUnfiltered : this.grid.data;
+ // find the data row
+ let rowdata = griddata.find(e => e.id == this.id);
+ let columIndex = this.grid.getColumnIndex(name);
+ rowdata.columns[columnIndex] = value;
+ }
+}
+
class AltEntry {
constructor(account_id, character_id, character_name, can_change_link) {
if (account_id instanceof HTMLElement) {
@@ -231,7 +282,7 @@ class AltsList {
let input_field = document.createElement('input');
input_field.setAttribute('type', 'text');
input_field.setAttribute('class', 'form-control');
- input_field.setAttribute('placeholder', 'Enter -> Add, Esc -> Cancel');
+ input_field.setAttribute('placeholder', $.i18n('wl-add-alt-placehonder'));
alts_list.element.appendChild(input_field);
input_field = $(input_field);
@@ -239,11 +290,11 @@ class AltsList {
input_field.on('keyup', function(e) {
if(e.keyCode == 13) { // enter
let char_name = e.currentTarget.value;
- e.currentTarget.value = "Please wait...";
+ e.currentTarget.value = $.i18n('wl-please-wait');
client.apis.Accounts.post_accounts_account_id({'account_id': account_id,
'character_identifier': {'character_name': char_name}})
.then(function(event) {
- waitlist.base.displayMessage("Alt added", "success");
+ waitlist.base.displayMessage($.i18n('wl-alt-added'), "success");
input_field.remove();
alts_list.addAltByData(event.obj.character_id, event.obj.character_name);
})
@@ -251,9 +302,9 @@ class AltsList {
if (typeof(event.response) !== "undefined" &&
typeof(event.response.obj) !== "undefined" &&
typeof(event.response.obj.error) !== "undefined"){
- waitlist.base.displayMessage("Failed to add alt: "+event.response.obj.error, "danger");
+ waitlist.base.displayMessage($.i18n('wl-add-alt-failed', ' :' + event.response.obj.error), "danger");
} else {
- waitlist.base.displayMessage("Failed to add alt", "danger");
+ waitlist.base.displayMessage($.i18n('wl-add-alt-failed', ''), "danger");
}
input_field.remove();
});
diff --git a/static/js/accounts/commandcorelist.js b/static/js/accounts/commandcorelist.js
index 9b8406f1..f042f1f8 100644
--- a/static/js/accounts/commandcorelist.js
+++ b/static/js/accounts/commandcorelist.js
@@ -1,10 +1,11 @@
EditableGrid.prototype.initializeGrid = function() {
- this.setCellRenderer("Account Name", new AccountCellRenderer());
+ this.setCellRenderer("account-name", new AccountCellRenderer());
};
$(document).ready(function() {
- let getMetaData = waitlist.base.getMetaData;
- var canViewProfile = getMetaData('can-view-profile') === "True";
+ var oldFilter = null;
+ let getMetaData = waitlist.base.getMetaData;
+ var canViewProfile = getMetaData('can-view-profile') === "True";
var editableGrid = new EditableGrid(
"CommandCore",
{
@@ -13,30 +14,36 @@ $(document).ready(function() {
maxBars: 5
});
- editableGrid.load({
- metadata: [
- {
- name: "Account Name",
- datatype: "string",
- editable: false,
- values: [{"value": "canViewProfile", "label": canViewProfile}]
- }, {
- name: "Roles",
- datatype: "string",
- editable: false
- }, {
- name: "Known Alts",
- datatype: "string",
- editable: false
- }
- ]
- });
-
- editableGrid.attachToHTMLTable('commanderlist');
- editableGrid.initializePaginator();
- editableGrid.initializeGrid();
- editableGrid.renderGrid();
- $('#filter').on('keyup', function() {
- editableGrid.filter($('#filter').val());
+ // make sure translations are loaded
+ i18nloaded.then(() => {
+ editableGrid.load({
+ metadata: [
+ {
+ name: "account-name",
+ datatype: "string",
+ editable: false,
+ values: [{"value": "canViewProfile", "label": canViewProfile}]
+ }, {
+ name: "roles",
+ datatype: "string",
+ editable: false
+ }, {
+ name: "alts",
+ datatype: "string",
+ editable: false
+ }
+ ]
+ });
+
+ editableGrid.attachToHTMLTable('commanderlist');
+ editableGrid.initializePaginator();
+ editableGrid.initializeGrid();
+ editableGrid.renderGrid();
+ $('#filter').on('keyup', function() {
+ if (oldFilter != null) editableGrid.removeFilter(oldFilter);
+ oldFilter = new StringFilter($('#filter').val());
+ editableGrid.addFilter(oldFilter);
+ });
+ registerRoleFilterSelect(editableGrid, 'filterRole');
});
});
diff --git a/static/js/accounts/editablegrid_filter.js b/static/js/accounts/editablegrid_filter.js
new file mode 100644
index 00000000..991edb23
--- /dev/null
+++ b/static/js/accounts/editablegrid_filter.js
@@ -0,0 +1,29 @@
+class RoleFilter extends Filter {
+ constructor(roleName) {
+ super();
+ this.roleName = roleName;
+ }
+
+ shouldInclude(row, ridx, grid) {
+ let roleIdx = grid.getColumnIndex('roles');
+ let roleString = row.columns[roleIdx];
+ let accId = row.id.substring(8);
+ console.log("Account Id: "+accId+ " RIdx: "+ ridx);
+ let value = grid.getValueAt(ridx, roleIdx);
+ let roles_node = $.parseHTML('
'+value+'
')[0];
+
+ // remove the new node
+ if (roles_node.children.length > 0 && roles_node.children[0].nodeName === "SPAN") {
+ roles_node.children[0].remove()
+ }
+ let roles = roles_node.textContent;
+ roles = roles.replace(/[\t\n\r]/g, ''); // clean up tabs and newlines
+ roles = roles.split(', ');
+ roles = roles.map(role => role.trim());
+ console.log(roles);
+ console.log("Role we want: " + this.roleName);
+ let should_include = roles.includes(this.roleName);
+ console.log("Should include " + should_include);
+ return should_include;
+ }
+}
diff --git a/static/js/accounts/role_filter.js b/static/js/accounts/role_filter.js
new file mode 100644
index 00000000..c1d2af80
--- /dev/null
+++ b/static/js/accounts/role_filter.js
@@ -0,0 +1,14 @@
+function registerRoleFilterSelect(editableGrid, selectId){
+ let oldFilter = null;
+ $('#'+selectId).on('change', function(event){
+ let select = event.currentTarget;
+ let val = select.value;
+ if (oldFilter != null) editableGrid.removeFilter(oldFilter);
+ if (val != '') {
+ oldFilter = new RoleFilter(val);
+ editableGrid.addFilter(oldFilter);
+ } else {
+ oldFilter = null;
+ }
+ });
+}
\ No newline at end of file
diff --git a/static/js/accounts/settings-accounts.js b/static/js/accounts/settings-accounts.js
index f2ac03a9..aec6eab9 100644
--- a/static/js/accounts/settings-accounts.js
+++ b/static/js/accounts/settings-accounts.js
@@ -4,18 +4,21 @@ if (!waitlist) {
var waitlist = {};
}
+
waitlist.accounts = (function() {
var getMetaData = waitlist.base.getMetaData;
var displayMessage = waitlist.base.displayMessage;
var sendMail = waitlist.IGBW.sendMail;
+
+ let grid = null;
function disableAccount(accountId, onsuccess){
var settings = {
async: true,
dataType: "text",
error: function() {
- displayMessage("Disabling Account failed!", "danger");
+ displayMessage($.i18n('wl-accounts-error-disable-account-failed'), "danger");
},
method: "POST",
data: {
@@ -35,7 +38,7 @@ waitlist.accounts = (function() {
async: true,
dataType: "text",
error: function() {
- displayMessage("Enabling Account failed!", "danger");
+ displayMessage($.i18n('wl-accounts-error-enabling-account-failed'), "danger");
},
method: "POST",
data: {
@@ -54,11 +57,10 @@ waitlist.accounts = (function() {
var source = $(event.currentTarget);
var id = Number(source.data('id'));
enableAccount(id, function() {
- const td_status_field_id = "#account-"+id+"-status";
- const td = $(td_status_field_id);
- td.text('Active')
+ let accountRow = new AccountRow(id, grid);
+ accountRow.status = 'Active';
source.attr("data-type", "acc-disable");
- source.text("Disable");
+ source.text($.i18n("wl-disable"));
});
}
@@ -66,11 +68,10 @@ waitlist.accounts = (function() {
var source = $(event.currentTarget);
var id = Number(source.data('id'));
disableAccount(id, function() {
- const td_status_field_id = "#account-"+id+"-status";
- const td = $(td_status_field_id);
- td.text('Deactivated')
+ let accountRow = new AccountRow(id, grid);
+ accountRow.status = 'Deactivated';
source.attr("data-type", "acc-enable");
- source.text("Enable");
+ source.text($.i18n("wl-enable"));
});
}
@@ -103,26 +104,30 @@ waitlist.accounts = (function() {
}
function editAccount(accountId) {
- let name = $('#acc-'+accountId+"-name > a").text();
- let roles_node = document.getElementById('acc-'+accountId+'-roles');
- let has_new_tag = (roles_node.childNodes.length > 0 && roles_node.childNodes[0].nodeName === "SPAN");
- let roles = roles_node.textContent;
+ let account_node = document.getElementById(`account-${accountId}`);
+ let account_row = new AccountRow(accountId, grid);
+ let name = account_row.name;
+ let roles_node = account_node.children[account_row.rolesIdx];
+ let has_new_tag = (roles_node.children.length > 0 && roles_node.children[0].nodeName === "SPAN");
+ let roles = roles_node.textContent;
+ roles = roles.replace(/[\t\n\r]/g, ''); // clean up tabs and newlines
// if it has a new tag remove the "New" from the beginning
if (has_new_tag){
roles = roles.slice(3)
}
- let default_char_name = $('#acc-'+accountId+'-cchar').text();
+ let default_char_name = account_row.defaultCharName;
$('#acc-edit-name').val(name);
// this is more complicated
// $('#acc-edit-roles')
roles = roles.split(", ");
+ roles = roles.map(x => x.trim())
// map the roles he has to a dict so we can fast and easy check for them
// later
let has_roles = {};
- for (let role in roles) {
- has_roles[roles[role]] = true;
+ for (let idx in roles) {
+ has_roles[roles[idx]] = true;
}
let edit_roles_select = document.getElementById('acc-edit-roles');
@@ -169,7 +174,7 @@ waitlist.accounts = (function() {
}
function setUpTable() {
- var editableGrid = new EditableGrid(
+ grid = new EditableGrid(
"Accounts",
{
enableSort: true,
@@ -179,31 +184,31 @@ waitlist.accounts = (function() {
$.parseHTML('')[0],
$.parseHTML('')[0]);
- editableGrid.load({
+ grid.load({
metadata: [
{
- name: "Actions",
+ name: 'actions',
datatype: "html",
editable: false
}, {
- name: "Status",
+ name: 'status',
datatype: "string",
editable: false
}, {
- name: "Account Name",
+ name: 'account-name',
datatype: "string",
editable: false,
values: [{"value": "canViewProfile", "label": true}]
}, {
- name: "Roles",
+ name: 'roles',
datatype: "html",
editable: false
}, {
- name: "Current Char",
+ name: 'current-character',
datatype: "string",
editable: false
}, {
- name: "Alts",
+ name: 'alts',
datatype: "string",
editable: false,
values: [{"value": "canChangeLinks", "label": getMetaData('can-change-links') == 'True'}]
@@ -215,19 +220,26 @@ waitlist.accounts = (function() {
]
});
- editableGrid.attachToHTMLTable('acctable');
- editableGrid.initializePaginator();
- editableGrid.initializeGrid();
- editableGrid.renderGrid();
+ grid.attachToHTMLTable('acctable');
+ grid.initializePaginator();
+ grid.initializeGrid();
+ grid.renderGrid();
+ let oldFilter = null;
$('#filter').on('keyup', function() {
- editableGrid.filter($('#filter').val());
+ if (oldFilter != null) grid.removeFilter(oldFilter);
+ oldFilter = new StringFilter($('#filter').val());
+ grid.addFilter(oldFilter);
});
+ registerRoleFilterSelect(grid, 'filterRole');
}
function init() {
noclick();
setUpEventhandlers();
- setUpTable();
+ // make sure language data is loaded
+ i18nloaded.then(() => {
+ setUpTable();
+ });
}
$(document).ready(init);
@@ -235,6 +247,6 @@ waitlist.accounts = (function() {
})();
EditableGrid.prototype.initializeGrid = function() {
- this.setCellRenderer("Account Name", new AccountCellRenderer());
- this.setCellRenderer("Alts", new AltCellRenderer());
+ this.setCellRenderer('account-name', new AccountCellRenderer());
+ this.setCellRenderer('alts', new AltCellRenderer());
};
\ No newline at end of file
diff --git a/static/js/api/ui.js b/static/js/api/ui.js
index 5993c749..aabaed7e 100644
--- a/static/js/api/ui.js
+++ b/static/js/api/ui.js
@@ -4,7 +4,7 @@ if (!waitlist) {
var waitlist = {};
waitlist.esi = {};
} else if (!waitlist.esi) {
- waitlist.esi = {};
+ waitlist.esi = {};
}
/**
@@ -12,9 +12,9 @@ if (!waitlist) {
*/
waitlist.esi.ui = (function() {
- var urls = {
+ var urls = {
esi_ui_newmail: waitlist.base.getMetaData('api-esi-ui-newmail'),
- esi_ui_auth: waitlist.base.getMetaData('api-esi-ui-auth')
+ esi_ui_auth: waitlist.base.getMetaData('api-esi-ui-auth')
};
/**
@@ -24,7 +24,7 @@ waitlist.esi.ui = (function() {
* @param subject Mails Subject
* @param body Mails Body
*/
- function open_newmail(receipients, subject, body) {
+ function open_newmail(receipients, subject, body) {
/*
* mailRecipients => JSON String recipients=[{"recipient_id": ID, "recipient_type": "alliance|character|corporation|mailing_list"}]
* mailBody => String
@@ -49,9 +49,9 @@ waitlist.esi.ui = (function() {
waitlist.base.displayMessage(message, "danger");
}
});
- }
+ }
- return {
+ return {
newmail: open_newmail,
};
})();
diff --git a/static/js/base.js b/static/js/base.js
index 2c567cc4..bf8d91ca 100644
--- a/static/js/base.js
+++ b/static/js/base.js
@@ -8,6 +8,16 @@ waitlist.base = (function(){
function getMetaData (name) {
return $('meta[name="'+name+'"]').attr('content');
}
+
+ var wlClient = SwaggerClient(
+ {
+ url: "/spec/v1/swagger.json",
+ requestInterceptor : function(req) {
+ req.headers['X-CSRFToken'] = getMetaData('csrf-token');
+ return req;
+ }
+ }
+ );
function displayMessage(message, type, html, id) {
var alertHTML = $($.parseHTML(`
@@ -28,6 +38,31 @@ waitlist.base = (function(){
var alertArea = $('#alert-area-base');
alertArea.append(alertHTML);
}
+
+ function handleLanguageSelection(event) {
+ let langCode = event.target.value;
+ wlClient.then((client) => {
+ client.apis.I18n.put_i18n_locale({
+ 'lang': langCode
+ }).then(() => {
+ location.reload();
+ }).catch(function(event) {
+ if (typeof(event.response) !== "undefined" &&
+ typeof(event.response.obj) !== "undefined" &&
+ typeof(event.response.obj.error) !== "undefined"){
+ waitlist.base.displayMessage($.i18n('wl-lang-change-failed', event.response.obj.error), "danger");
+ } else {
+ waitlist.base.displayMessage($.i18n('wl-add-alt-failed'), "danger");
+ }
+ });
+ });
+ }
+
+ function init() {
+ $('#lang-select').on('change', handleLanguageSelection);
+ }
+
+ $(document).ready(init);
return {
getMetaData: getMetaData,
diff --git a/static/js/calendar/settings.js b/static/js/calendar/settings.js
index 4331afd2..56292cee 100644
--- a/static/js/calendar/settings.js
+++ b/static/js/calendar/settings.js
@@ -22,11 +22,11 @@ waitlist.calendar.settings = (function() {
async: true,
dataType: "text",
error: function() {
- displayMessage("Deleting event failed", "danger");
+ displayMessage($.i18n('wl-calendar-settings-error-event-delete'), "danger");
},
method: "DELETE",
success: function() {
- displayMessage("Event deleted", "success");
+ displayMessage($.i18n('wl-calendar-settings-event-deleted'), "success");
target.closest("tr").remove();
},
headers: {
diff --git a/static/js/editablegrid/editablegrid.js b/static/js/editablegrid/editablegrid.js
index 96357ca6..f075f5fa 100644
--- a/static/js/editablegrid/editablegrid.js
+++ b/static/js/editablegrid/editablegrid.js
@@ -184,7 +184,7 @@ EditableGrid.prototype.init = function (name, config)
this.nbHeaderRows = 1;
this.lastSelectedRowIndex = -1;
this.currentPageIndex = 0;
- this.currentFilter = null;
+ this.currentFilters = [];
this.currentContainerid = null;
this.currentClassName = null;
this.currentTableid = null;
@@ -211,7 +211,7 @@ EditableGrid.prototype.init = function (name, config)
this.currentPageIndex = this.localisset('pageIndex') ? parseInt(this.localget('pageIndex')) : 0;
this.sortedColumnName = this.localisset('sortColumnIndexOrName') ? this.localget('sortColumnIndexOrName') : -1;
this.sortDescending = this.localisset('sortColumnIndexOrName') && this.localisset('sortDescending') ? this.localget('sortDescending') == 'true' : false;
- this.currentFilter = this.localisset('filter') ? this.localget('filter') : null;
+ this.currentFilters = this.localisset('filters') ? this.localget('filters') : [];
};
/**
@@ -475,7 +475,6 @@ EditableGrid.prototype._addUrlParameters = function(baseUrl, dataOnly)
// add pagination, filtering and sorting parameters to the base url
return baseUrl
+ "&page=" + (this.currentPageIndex + 1)
- + "&filter=" + (this.currentFilter ? encodeURIComponent(this.currentFilter) : "")
+ "&sort=" + (this.sortedColumnName && this.sortedColumnName != -1 ? encodeURIComponent(this.sortedColumnName) : "")
+ "&asc=" + (this.sortDescending ? 0 : 1)
+ (dataOnly ? '&data_only=1' : '');
@@ -1650,6 +1649,9 @@ EditableGrid.prototype._rendergrid = function(containerid, className, tableid)
rows[i].style.display = 'none';
rows[i].hidden_by_editablegrid = true;
}
+ // update these too, so we don't have dumplicated ids in DOM when done
+ rows[i].rowId = _data[i].id;
+ rows[i].id = this._getRowDOMId(rows[i].rowId);
}
else {
if (skipped < this.pageSize * _currentPageIndex) {
@@ -1975,125 +1977,79 @@ EditableGrid.prototype.sort = function(columnIndexOrName, descending, backOnFirs
};
+EditableGrid.prototype.addFilter = function(filter)
+{
+ if (!(filter instanceof Filter)) {
+ return;
+ }
+ this.currentFilters.push(filter);
+ this.filter();
+};
+
+EditableGrid.prototype.removeFilter = function(filter) {
+ if (!(filter instanceof Filter)) {
+ return;
+ }
+ let idx = this.currentFilters.indexOf(filter);
+ this.currentFilters.splice(idx, 1);
+ this.filter();
+};
+
+
/**
* Filter the content of the table
* @param {String} filterString String string used to filter: all words must be found in the row
* @param {Array} cols Columns to sort. If cols is not specified, the filter will be done on all columns
*/
-EditableGrid.prototype.filter = function(filterString, cols)
+EditableGrid.prototype.filter = function()
{
- if (typeof filterString != 'undefined') {
- this.currentFilter = filterString;
- this.localset('filter', filterString);
- }
-
- // if filtering is done on server-side, we are done here
- if (this.serverSide) return this.setPageIndex(0);
-
- // un-filter if no or empty filter set
- if (this.currentFilter == null || this.currentFilter == "") {
- if (this.dataUnfiltered != null) {
- this.data = this.dataUnfiltered;
- this.dataUnfiltered = null;
- for (var r = 0; r < this.getRowCount(); r++) this.data[r].visible = true;
- this.setPageIndex(0);
- this.tableFiltered();
- }
- return;
- }
-
- var words = this.currentFilter.toLowerCase().split(" ");
-
- // work on unfiltered data
- if (this.dataUnfiltered != null) this.data = this.dataUnfiltered;
-
- var rowCount = this.getRowCount();
- var columnCount = typeof cols != 'undefined' ? cols.length : this.getColumnCount();
-
- for (var r = 0; r < rowCount; r++) {
- var row = this.data[r];
- row.visible = true;
- var rowContent = "";
-
- // add column values
- for (var c = 0; c < columnCount; c++) {
- if (this.getColumnType(c) == 'boolean') continue;
- var displayValue = this.getDisplayValueAt(r, typeof cols != 'undefined' ? cols[c] : c);
- var value = this.getValueAt(r, typeof cols != 'undefined' ? cols[c] : c);
- rowContent += displayValue + " " + (displayValue == value ? "" : value + " ");
- }
-
- // add attribute values
- for (var attributeName in row) {
- if (attributeName != "visible" && attributeName != "originalIndex" && attributeName != "columns") rowContent += row[attributeName];
- }
+ let self = this;
+ // if filtering is done on server-side, we are done here
+ if (this.serverSide) return this.setPageIndex(0);
+
+ if (this.currentFilters.length === 0) {
+ if (this.dataUnfiltered != null) {
+ this.data = this.dataUnfiltered;
+ this.dataUnfiltered = null;
+ for (var r = 0; r < this.getRowCount(); r++)
+ this.data[r].visible = true;
+ this.setPageIndex(0);
+ this.tableFiltered();
+ }
+ return;
+ }
- // if row contents do not match one word in the filter, hide the row
- for (var i = 0; i < words.length; i++) {
- var word = words[i];
- var match = false;
-
- // a word starting with "!" means that we want a NON match
- var invertMatch = word.startsWith("!");
- if (invertMatch) word = word.substr(1);
-
- // if word is of the form "colname/attributename=value" or "colname/attributename!=value", only this column/attribute is used
- var colindex = -1;
- var attributeName = null;
- if (word.contains("!=")) {
- var parts = word.split("!=");
- colindex = this.getColumnIndex(parts[0]);
- if (colindex >= 0) {
- word = parts[1];
- invertMatch = !invertMatch;
- }
- else if (typeof row[parts[0]] != 'undefined') {
- attributeName = parts[0];
- word = parts[1];
- invertMatch = !invertMatch;
- }
- }
- else if (word.contains("=")) {
- var parts = word.split("=");
- colindex = this.getColumnIndex(parts[0]);
- if (colindex >= 0) word = parts[1];
- else if (typeof row[parts[0]] != 'undefined') {
- attributeName = parts[0];
- word = parts[1];
- }
- }
-
- // a word ending with "!" means that a column must match this word exactly
- if (!word.endsWith("!")) {
- if (colindex >= 0) match = (this.getValueAt(r, colindex) + ' ' + this.getDisplayValueAt(r, colindex)).trim().toLowerCase().indexOf(word) >= 0;
- else if (attributeName !== null) match = (''+this.getRowAttribute(r, attributeName)).trim().toLowerCase().indexOf(word) >= 0;
- else match = rowContent.toLowerCase().indexOf(word) >= 0;
- }
- else {
- word = word.substr(0, word.length - 1);
- if (colindex >= 0) match = (''+this.getDisplayValueAt(r, colindex)).trim().toLowerCase() == word || (''+this.getValueAt(r, colindex)).trim().toLowerCase() == word;
- else if (attributeName !== null) match = (''+this.getRowAttribute(r, attributeName)).trim().toLowerCase() == word;
- else for (var c = 0; c < columnCount; c++) {
- if (this.getColumnType(typeof cols != 'undefined' ? cols[c] : c) == 'boolean') continue;
- if ((''+this.getDisplayValueAt(r, typeof cols != 'undefined' ? cols[c] : c)).trim().toLowerCase() == word || (''+this.getValueAt(r, typeof cols != 'undefined' ? cols[c] : c)).trim().toLowerCase() == word) match = true;
- }
- }
-
- if (invertMatch ? match : !match) {
- this.data[r].visible = false;
- break;
- }
- }
- }
+
+
+ // work on unfiltered data
+ if (this.dataUnfiltered != null) this.data = this.dataUnfiltered;
+
+ var rowCount = this.getRowCount();
+ var columnCount = typeof cols != 'undefined' ? cols.length : this.getColumnCount();
+
+ if (filter instanceof Filter) {
+
+ }
+
+
+ this.data.forEach(e => e.visible = true);
- // keep only visible rows in data
- this.dataUnfiltered = this.data;
- this.data = [];
- for (var r = 0; r < rowCount; r++) if (this.dataUnfiltered[r].visible) this.data.push(this.dataUnfiltered[r]);
+ // remove rows that don't pass every filter
+ this.data.forEach(function(row, index) {
+ if (!self.currentFilters.every(filter => filter.shouldInclude(row, index, self))) {
+ row.visible = false;
+ } else {
+ row.visible = true;
+ }
+ });
+ this.dataUnfiltered = this.data;
+ this.data = [];
+ // we want to keep the order
+ for (var r = 0; r < rowCount; r++) if (this.dataUnfiltered[r].visible) this.data.push(this.dataUnfiltered[r]);
- // refresh grid (back on first page) and callback
- this.setPageIndex(0);
- this.tableFiltered();
+ // refresh grid (back on first page) and callback
+ this.setPageIndex(0);
+ this.tableFiltered();
};
diff --git a/static/js/editablegrid/editablegrid_paginator.js b/static/js/editablegrid/editablegrid_paginator.js
index c80de3ca..979f3c80 100644
--- a/static/js/editablegrid/editablegrid_paginator.js
+++ b/static/js/editablegrid/editablegrid_paginator.js
@@ -4,7 +4,7 @@ EditableGrid.prototype.updatePaginator = function() {
// get interval
var interval = this.getSlidingPageInterval(this.maxBars);
if (interval === null){
- return;
+ interval = {startPageIndex: 0, endPageIndex: 0};
}
// get pages in interval (with links except for the current page)
diff --git a/static/js/editablegrid/editablegrid_utils.js b/static/js/editablegrid/editablegrid_utils.js
index 16d4f2a6..6ccb341a 100644
--- a/static/js/editablegrid/editablegrid_utils.js
+++ b/static/js/editablegrid/editablegrid_utils.js
@@ -784,3 +784,93 @@ function number_format (number, decimals, dec_point, thousands_sep) {
return s.join(dec);
}
+class Filter {
+ constructor() {
+ }
+
+ shouldInclude(row, index, grid) {
+ return true;
+ }
+}
+
+class StringFilter extends Filter {
+ constructor(filterString, cols) {
+ super();
+ this.words = filterString.toLowerCase().split(" ");
+ this.cols = cols;
+ }
+
+ shouldInclude(row, index, grid) {
+ var rowContent = "";
+ var columnCount = typeof this.cols != 'undefined' ? this.cols.length : grid.getColumnCount();
+
+ // add column values
+ for (var c = 0; c < columnCount; c++) {
+ if (grid.getColumnType(c) == 'boolean') continue;
+ var displayValue = grid.getDisplayValueAt(index, typeof cols != 'undefined' ? cols[c] : c);
+ var value = grid.getValueAt(index, typeof cols != 'undefined' ? cols[c] : c);
+ rowContent += displayValue + " " + (displayValue == value ? "" : value + " ");
+ }
+ // add attribute values
+ for (var attributeName in row) {
+ if (attributeName != "visible" && attributeName != "originalIndex" && attributeName != "columns") rowContent += row[attributeName];
+ }
+
+ // if row contents do not match one word in the filter, hide the row
+ for (var i = 0; i < this.words.length; i++) {
+ var word = this.words[i];
+ var match = false;
+
+ // a word starting with "!" means that we want a NON match
+ var invertMatch = word.startsWith("!");
+ if (invertMatch) word = word.substr(1);
+
+ // if word is of the form "colname/attributename=value" or "colname/attributename!=value", only this column/attribute is used
+ var colindex = -1;
+ var attributeName = null;
+ if (word.contains("!=")) {
+ var parts = word.split("!=");
+ colindex = grid.getColumnIndex(parts[0]);
+ if (colindex >= 0) {
+ word = parts[1];
+ invertMatch = !invertMatch;
+ }
+ else if (typeof row[parts[0]] != 'undefined') {
+ attributeName = parts[0];
+ word = parts[1];
+ invertMatch = !invertMatch;
+ }
+ }
+ else if (word.contains("=")) {
+ var parts = word.split("=");
+ colindex = grid.getColumnIndex(parts[0]);
+ if (colindex >= 0) word = parts[1];
+ else if (typeof row[parts[0]] != 'undefined') {
+ attributeName = parts[0];
+ word = parts[1];
+ }
+ }
+
+ // a word ending with "!" means that a column must match this word exactly
+ if (!word.endsWith("!")) {
+ if (colindex >= 0) match = (grid.getValueAt(r, colindex) + ' ' + grid.getDisplayValueAt(index, colindex)).trim().toLowerCase().indexOf(word) >= 0;
+ else if (attributeName !== null) match = (''+grid.getRowAttribute(index, attributeName)).trim().toLowerCase().indexOf(word) >= 0;
+ else match = rowContent.toLowerCase().indexOf(word) >= 0;
+ }
+ else {
+ word = word.substr(0, word.length - 1);
+ if (colindex >= 0) match = (''+grid.getDisplayValueAt(index, colindex)).trim().toLowerCase() == word || (''+grid.getValueAt(index, colindex)).trim().toLowerCase() == word;
+ else if (attributeName !== null) match = (''+grid.getRowAttribute(index, attributeName)).trim().toLowerCase() == word;
+ else for (var c = 0; c < columnCount; c++) {
+ if (this.getColumnType(typeof cols != 'undefined' ? cols[c] : c) == 'boolean') continue;
+ if ((''+grid.getDisplayValueAt(index, typeof cols != 'undefined' ? cols[c] : c)).trim().toLowerCase() == word || (''+grid.getValueAt(index, typeof cols != 'undefined' ? cols[c] : c)).trim().toLowerCase() == word) match = true;
+ }
+ }
+
+ if (invertMatch ? match : !match) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/static/js/eve-ui-config.js b/static/js/eve-ui-config.js
index aa3756ef..b21786f9 100644
--- a/static/js/eve-ui-config.js
+++ b/static/js/eve-ui-config.js
@@ -6,10 +6,7 @@ var eveui_imageserver = function(image_ref) {
return eve_image(encodeURI(image_ref), 'png');
};
// fix for getting names for modules from ccp api that are in the users browser language
-$.ajaxSetup({
- beforeSend: function(xhr, settings) {
- if (settings.url.includes('/universe/types/')) {
- xhr.setRequestHeader('Accept-Language', 'en-us, en;q=0.9');
- }
- }
-});
+eveui_accept_language = document.getElementById('lang-code').getAttribute('content');
+if (eveui_accept_language.startsWith('en_') || eveui_accept_language == 'en') {
+ eveui_accept_language = 'en_us';
+}
diff --git a/static/js/fleet-settings.js b/static/js/fleet-settings.js
index 12bcbecd..3b5ff780 100644
--- a/static/js/fleet-settings.js
+++ b/static/js/fleet-settings.js
@@ -42,8 +42,8 @@ waitlist.fsettings = (function() {
clearWaitlist(gid);
});
- $("#remove-diag-body").text("Do your really want to clear the Waitlist and all Xups?");
- $("#remove-diag-label").text("Clear Waitlist???");
+ $("#remove-diag-body").text($.i18n('wl-clear-list-warning-body'));
+ $("#remove-diag-label").text($.i18n('wl-clear-list-warning-label'));
}
});
}
@@ -151,8 +151,8 @@ waitlist.fsettings = (function() {
url: urls.settings_fleet_query_systems+'?term=%QUERY',
wildcard: '%QUERY',
filter: function(response) {
- return response.result;
- }
+ return response.result;
+ }
}
});
$('.hq-typeahead').typeahead({
diff --git a/static/js/fleetcomp.js b/static/js/fleetcomp.js
index c65b9bc4..4c1edbd3 100644
--- a/static/js/fleetcomp.js
+++ b/static/js/fleetcomp.js
@@ -55,7 +55,7 @@ waitlist.fleetcomp = (function() {
},
'error': function(data) {
var message = data.statusText;
- if (typeof data.responseJSON !== 'undefined'
+ if (typeof data.responseJSON !== 'undefined'
&& typeof data.responseJSON.message !== 'undefined') {
message += ": " + data.responseJSON.message;
}
@@ -118,12 +118,21 @@ waitlist.fleetcomp = (function() {
fit_id_str += ",";
}
});
- $.post(getMetaData('api-move-entry-to-wls'), {
- 'entryId': entryId,
- 'fitIds': fit_id_str,
- '_csrf_token': getMetaData('csrf-token')
- }, function() {
- }, "text");
+ $.post({
+ 'url': getMetaData('api-move-entry-to-wls'),
+ 'data': {
+ 'entryId': entryId,
+ 'fitIds': fit_id_str,
+ '_csrf_token': getMetaData('csrf-token')
+ },
+ 'dataType': 'text',
+ })
+ .fail(function(jqXHR, textStatus, errorThrown) {
+ if(jqXHR.status = 404) {
+ // something is wrong we should reload the waitlist!
+ waitlist.listdom.loadWaitlist();
+ }
+ });
}
/**
@@ -151,8 +160,17 @@ waitlist.fleetcomp = (function() {
{'_csrf_token': getMetaData('csrf-token')},
function() {},
"text"
- );
- displayMessage("You should view "+ name + "'s fit before accepting it.", "danger");
+ )
+ .fail(function(jqXHR, textStatus, errorThrown) {
+ if(jqXHR.status = 404 || jqXHR.status == 404) {
+ // 409 => fit already on target list
+ // 404 => fit doesn't exist
+ // this fit was already approved so something is wrong
+ // reload the lists
+ waitlist.listdom.loadWaitlist();
+ }
+ });
+ displayMessage($.i18n('wl-warn-checkfit', name), "danger");
}
}
@@ -164,7 +182,7 @@ waitlist.fleetcomp = (function() {
moveEntryToWaitlists(wlId, entryId);
} else {
var name = document.getElementById(`entry-${wlId}-${entryId}`).dataset.username;
- displayMessage("You should view all of "+ name + "'s fits before accepting them.", "danger");
+ displayMessage($.i18n('wl-warn-checkfits', name), "danger");
$.post(getMetaData('api-fail-approve'),
{'_csrf_token': getMetaData('csrf-token')},
function() {},
@@ -262,9 +280,9 @@ waitlist.fleetcomp = (function() {
function onViewfit(event) {
const fit = event.currentTarget.offsetParent;
- if (fit.dataset.viewed !== "y") {
- document.getElementById(fit.id).setAttribute("data-viewed", "y");
- }
+ if (fit.dataset.viewed !== "y") {
+ document.getElementById(fit.id).setAttribute("data-viewed", "y");
+ }
}
function init() {
diff --git a/static/js/fleetview/fleetview.js b/static/js/fleetview/fleetview.js
index 0c78d1f4..22f919c0 100644
--- a/static/js/fleetview/fleetview.js
+++ b/static/js/fleetview/fleetview.js
@@ -13,7 +13,7 @@ waitlist.fleetview = (function(){
const request = indexedDB.open("eve-api-cache", 2);
// setup handler to ask user why it failed :>
request.onerror = function (event) {
- alert("IndexDB is needed to cache character/ship information. Why didn't you allow my web app to use IndexedDB?!");
+ alert($.i18n("wl-fleetview-error-indexdb"));
};
// if we succeed opening it lets register a general error handler
request.onsuccess = function (event) {
@@ -21,7 +21,7 @@ waitlist.fleetview = (function(){
db.onerror = function (event) {
// Generic error handler for all errors targeted at this database's
// requests!
- alert("Database error: " + event.target.errorCode);
+ alert($.i18n('wl-fleetview-error-db', event.target.errorCode));
};
// aright we are done, lets call the next code
initializeSite();
@@ -293,26 +293,26 @@ waitlist.fleetview = (function(){
}
function getCharacterInfo(characterID) {
- return window.clientv4.apis.Character.get_characters_character_id({character_id: characterID}, {responseContentType: 'application/json'});
+ return window.clientv4.apis.Character.get_characters_character_id({character_id: characterID}, {responseContentType: 'application/json'});
}
$(document).ready(function() {
window.clientv4 = new SwaggerClient("https://esi.tech.ccp.is/v4/swagger.json")
- .catch(
- function (event) {
- console.log("SwaggerError");
- console.log(event);
+ .catch((event) => {
+ console.log("SwaggerError");
+ console.log(event);
}
- );
+ );
window.clientv2 = new SwaggerClient("https://esi.tech.ccp.is/v2/swagger.json")
- .catch(
- function (event) {
+ .catch((event) => {
console.log("SwaggerError");
console.log(event);
}
- );
-
- setupDB();
+ );
+ // make sure translations are loaded
+ i18nloaded.then(() => {
+ setupDB();
+ });
});
})();
\ No newline at end of file
diff --git a/static/js/gong.js b/static/js/gong.js
index 14e9cb59..0b6d9659 100644
--- a/static/js/gong.js
+++ b/static/js/gong.js
@@ -59,7 +59,9 @@ waitlist.gong = (function() {
gongbutton.checked = true;
gongClicked();
} else {
- displayMessage("To get informed when you are invited please enable browser notifications in the top right.", "info", false, "gong-alert");
+ i18nloaded.then(() => {
+ displayMessage($.i18n('wl-gong-info'), "info", false, "gong-alert");
+ });
gongAlert = "y";
}
}
diff --git a/static/js/history-base.js b/static/js/history-base.js
index 4a40d1fa..ef90ad70 100644
--- a/static/js/history-base.js
+++ b/static/js/history-base.js
@@ -28,37 +28,37 @@ waitlist.history.base = (function() {
function resolveAction(action) {
switch (action) {
case "xup":
- return "X-UP";
+ return $.i18n('wl-xup-uppercase');
case "comp_rm_pl":
- return "Removed a Character from Waitlists";
+ return $.i18n('wl-remove-char');
case "comp_inv_pl":
- return "Send Invitation to Character";
+ return $.i18n('wl-send-invite');
case "comp_rm_etr":
- return "Removed Entry from X-UPs";
+ return $.i18n('wl-remove-entry-from-xup');
case "self_rm_fit":
- return "Removed own fit";
+ return $.i18n('wl-remove-own-fit');
case "self_rm_entry":
- return "Removed own entry";
+ return $.i18n('wl-remove-own-entry');
case "self_rm_wls_all":
- return "Removed himself from all lists";
+ return $.i18n('wl-remove-self-all-lists');
case "comp_mv_xup_etr":
- return "Approved X-UP entry";
+ return $.i18n('wl-approved-xup-entry');
case "comp_mv_xup_fit":
- return "Approved Single Fit";
+ return $.i18n('wl-approved-single-fit');
case "comp_send_noti":
- return "Send Notification to Character";
+ return $.i18n('wl-sent-notification-to-char');
case "set_fc":
- return "Was set as FC";
+ return $.i18n('wl-was-set-as-fc');
case "set_fcomp":
- return "Was set as Fleet Comp";
+ return $.i18n('wl-was-set-as-fleetcomp');
case "auto_rm_pl":
- return "Player was removed after found in fleet";
+ return $.i18n('wl-auto-removed-after-found-in-fleet');
case "auto_inv_missed":
- return "Player missed his invite";
+ return $.i18n('wl-missed-his-invite');
case "self_rm_etr":
- return "Player removed himself from X-UPs";
+ return $.i18n('wl-player-removed-himself-from-xups');
case "comp_inv_by_name":
- return "Player was invited by Name(Reform Tool?)";
+ return $.i18n('wl-player-invited-by-name');
default:
return action;
}
@@ -85,7 +85,7 @@ waitlist.history.base = (function() {
targetA.text(entry.target.name);
if (entry.target.newbro) {
- targetTD.prepend('New ');
+ targetTD.prepend(`${$.i18n('wl-new')} `);
}
for (var i=0; i < entry.fittings.length; i++) {
fittingsTD.append(createFittingDOM(entry.fittings[i]));
@@ -93,6 +93,21 @@ waitlist.history.base = (function() {
return historyEntrySkeleton;
}
+ /**
+ * Take a string and return the same string
+ * unless the string is "#System"
+ * then return "Booby"(the bird)
+ * @param old_name inventory type name
+ * @returns old_name unless it is "#System" then it is replaced by "Booby"
+ */
+ function filterShipName(old_name) {
+ if (old_name === "#System") {
+ return "Booby";
+ }
+ return old_name;
+ }
+
+
function createFittingDOM(fit) {
var comment = "";
let skillsData = "";
@@ -106,10 +121,10 @@ waitlist.history.base = (function() {
skillsData = `3338:${bsResult[1]}`;
}
}
- if (fit.ship_type === 1) {
- return $.parseHTML(`${fit.shipName}${comment}`);
+ if (fit.ship_type === 0) {
+ return $.parseHTML(`${filterShipName(fit.shipName)}${comment}`);
} else {
- return $.parseHTML(`${fit.shipName}${comment}`);
+ return $.parseHTML(`${filterShipName(fit.shipName)}${comment}`);
}
}
diff --git a/static/js/history-search.js b/static/js/history-search.js
index 68fa4d97..38eea67f 100644
--- a/static/js/history-search.js
+++ b/static/js/history-search.js
@@ -28,7 +28,7 @@ waitlist.history.historysearch = (function() {
var hbody = $('#historybody');
hbody.empty();
if (data.history.length <= 0) {
- hbody.append('
No Results Found
');
+ hbody.append('
'+$.i18n('wl-no-result-found')+'
');
return;
}
for (var i = 0; i < data.history.length; i++) {
diff --git a/static/js/history.js b/static/js/history.js
index 9aab956c..4f63a8ad 100644
--- a/static/js/history.js
+++ b/static/js/history.js
@@ -29,8 +29,11 @@ waitlist.history.comphistory = (function() {
});
}
function init() {
+ // this is to make sure that the language data is there before we try to use it
+ i18nloaded.then(() => {
refresh();
setInterval(refresh, 10000);
+ });
}
$(document).ready(init);
diff --git a/static/js/notification/alarm.js b/static/js/notification/alarm.js
index 32e9da92..55ecd5bc 100644
--- a/static/js/notification/alarm.js
+++ b/static/js/notification/alarm.js
@@ -242,8 +242,8 @@ waitlist.alarm = (function () {
// we can just call this every 5min to update stats
// or we use sse to update
- // 5min == 300 000 ms
- setInterval(updateCache, 300000);
+ // 30s == 30 000 ms
+ setInterval(updateCache, 30000);
}
$(document).ready(init);
diff --git a/static/js/overview/overview.js b/static/js/overview/overview.js
index 3817cbf3..142e028c 100644
--- a/static/js/overview/overview.js
+++ b/static/js/overview/overview.js
@@ -2,7 +2,7 @@
function GetChartElement() {
let content_frame = document.getElementById('chart-row');
let column = document.createElement('div');
- column.setAttribute('class', 'col-6')
+ column.setAttribute('class', 'col-xl-6 col-lg-12')
content_frame.appendChild(column);
return column;
}
@@ -23,7 +23,6 @@ function AddDistinctHullCharacterCombinations(seconds, title, swa_client) {
chartFrame,
undefined, {
'height': 400,
- // 'width': 400,
}
);
let option = {
@@ -32,6 +31,9 @@ function AddDistinctHullCharacterCombinations(seconds, title, swa_client) {
top: 0,
left: 'center',
padding: 5,
+ textStyle: {
+ fontSize: 12,
+ },
},
tooltip: {},
series: [{
@@ -63,22 +65,20 @@ function AddDistinctHullCharacterCombinations(seconds, title, swa_client) {
} else if ('message' in event) {
msg = event.message;
}
- waitlist.base.displayMessage(`Failed to get distinct hull character statistics: ${msg}`, "danger");
+ waitlist.base.displayMessage($.i18n('wl-overview-stat-error-hull-char', msg), "danger");
}
);
}
);
}
-
-
function GetApprovalTable(names) {
let table = document.createElement('table');
table.setAttribute('class', 'table table-sm');
let thead = document.createElement('thead');
let th = document.createElement('th');
- th.textContent = 'Account';
+ th.textContent = $.i18n('wl-account');
thead.appendChild(th);
let tbody = document.createElement('tbody');
@@ -125,6 +125,9 @@ function AddApprovedFitsByAccount(seconds, title, swa_client) {
top: 0,
left: 'center',
padding: 5,
+ textStyle: {
+ fontSize: 12,
+ },
},
tooltip: {
formatter: '{b}: {c}',
@@ -167,7 +170,7 @@ function AddApprovedFitsByAccount(seconds, title, swa_client) {
} else if ('message' in event) {
msg = event.message;
}
- waitlist.base.displayMessage(`Failed to get approved fits by account statistics: ${msg}`, "danger");
+ waitlist.base.displayMessage($.i18n('wl-overview-stat-error-fit-by-acc', msg), "danger");
}
);
}
@@ -199,6 +202,9 @@ function AddJoinedMemebers(seconds, title, swa_client) {
top: 0,
left: 'center',
padding: 5,
+ textStyle: {
+ fontSize: 12,
+ },
},
tooltip: {
formatter: '{b}: {c}',
@@ -226,7 +232,15 @@ function AddJoinedMemebers(seconds, title, swa_client) {
type: 'line',
data: event.obj.yvalues,
itemStyle: {
- color: '#61a0a8',
+ normal: {
+ color: '#61a0a8',
+ },
+ emphasis: {
+ areaStyle: {
+ color: '#71b0b8',
+ type: 'default',
+ },
+ },
}
}],
};
@@ -240,7 +254,7 @@ function AddJoinedMemebers(seconds, title, swa_client) {
} else if ('message' in event) {
msg = event.message;
}
- waitlist.base.displayMessage(`Failed to get approved fits by account statistics: ${msg}`, "danger");
+ waitlist.base.displayMessage($.i18n('wl-overview-stat-error-fit-by-acc', msg), "danger");
}
);
}
@@ -257,9 +271,12 @@ function init_overview() {
return req;
}
});
- AddApprovedFitsByAccount(2592000, 'Top 15 commanders with approves last 30d', swa_client);
- AddDistinctHullCharacterCombinations(2592000, 'Top 15 distinct Char/Hull combinations last 30d', swa_client);
- AddDistinctHullCharacterCombinations(86400, 'Top 15 distinct Char/Hull combinations last 24h', swa_client);
- AddJoinedMemebers(123072000, 'Fleetjoins per month', swa_client);
+ // this is to make sure that the language data is there before we try to use it
+ i18nloaded.then(() => {
+ AddApprovedFitsByAccount(2592000, $.i18n('wl-overview-top-commader-time', 15, 30), swa_client);
+ AddDistinctHullCharacterCombinations(2592000, $.i18n("wl-overview-top-distinct-time", 15, 30), swa_client);
+ AddDistinctHullCharacterCombinations(86400, $.i18n("wl-overview-top-distinct-time", 15, 1), swa_client);
+ AddJoinedMemebers(123072000, $.i18n('wl-fleetjoins-per-month'), swa_client);
+ });
}
$(document).ready(init_overview);
\ No newline at end of file
diff --git a/static/js/permissions/config.js b/static/js/permissions/config.js
index 236a2780..d922f59d 100644
--- a/static/js/permissions/config.js
+++ b/static/js/permissions/config.js
@@ -32,7 +32,7 @@ waitlist.permission.config = (function () {
async: true,
dataType: "text",
error: function() {
- displayMessage("Setting Permission failed", "danger");
+ displayMessage($.i18n('wl-permissions-setting-failed'), "danger");
},
method: "POST",
headers: {
diff --git a/static/js/reform.js b/static/js/reform.js
index 4957dd00..06ca33f0 100644
--- a/static/js/reform.js
+++ b/static/js/reform.js
@@ -33,7 +33,7 @@ waitlist.reform = (function(){
if (typeof data.responseJSON !== 'undefined' && typeof data.responseJSON.message !== 'undefined') {
message += ": " + data.responseJSON.message;
}
- else if (typeof data.responseText !== 'undefined') {
+ else if (typeof data.responseText !== 'undefined') {
message += ": " + data.responseText;
}
displayMessage(message, "danger");
diff --git a/static/js/sse-dom.js b/static/js/sse-dom.js
index 81a8eafa..43ab49bd 100644
--- a/static/js/sse-dom.js
+++ b/static/js/sse-dom.js
@@ -66,9 +66,9 @@ waitlist.sse_dom = (function () {
if (!("Notification" in window)) {
return;
}
- let title = "New X-UP";
+ let title = $.i18n('wl-notifiaction-xup-title');
let options = {
- 'body': `New X-UP from ${data.entry.character.name}`
+ 'body': $.i18n('wl-notifiaction-xup-body', data.entry.character.name)
};
// if we have permission
if (Notification.permission === "granted") {
@@ -83,6 +83,10 @@ waitlist.sse_dom = (function () {
});
}
}
+
+ function reloadListener(event) {
+ window.location.reload(true);
+ }
sse.addEventListener("fit-added", fitAddedListener);
sse.addEventListener("fit-removed", fitRemovedListener);
@@ -93,11 +97,16 @@ waitlist.sse_dom = (function () {
sse.addEventListener("invite-missed", missedInviteListener);
sse.addEventListener("status-changed", statusChangedListener);
+
+ sse.addEventListener("reload", reloadListener);
function init() {
- settings.can_manage = getMetaData('can-fleetcomp') === "True";
- loadWaitlist();
+ settings.can_manage = getMetaData('can-fleetcomp') === "True";
$('body').tooltip({selector: '[data-toggle=tooltip]'});
+ // make sure translations are loaded
+ i18nloaded.then(() => {
+ loadWaitlist();
+ });
}
$(document).ready(init);
diff --git a/static/js/ticket-settings.js b/static/js/ticket-settings.js
index 2be0cc40..c9a3e9b3 100644
--- a/static/js/ticket-settings.js
+++ b/static/js/ticket-settings.js
@@ -1,29 +1,28 @@
'use strict';
-
if (!waitlist) {
var waitlist = {};
}
-waitlist.ticketsettings = (function (){
-
+waitlist.ticketsettings = (function() {
+
var sendMail = waitlist.esi.ui.newmail;
var getMetaData = waitlist.base.getMetaData;
var displayMessage = waitlist.base.displayMessage;
-
+
function getTicketElement(ticketId) {
var el = {
- id: ticketId,
- jqE: $('#fb-'+ticketId)
+ id: ticketId,
+ jqE: $('#fb-' + ticketId)
};
- el.getTitle = function(){
+ el.getTitle = function() {
return $(":nth-child(4)", this.jqE).text();
};
-
+
el.getMessage = function() {
return $(":nth-child(5)", this.jqE).text();
};
-
+
el.getCharacterId = function() {
return this.jqE.attr('data-characterid');
};
@@ -35,19 +34,28 @@ waitlist.ticketsettings = (function (){
function sendTicketMail(ticketId) {
var ticketElement = getTicketElement(ticketId);
- var title = ticketElement.getTitle();
+ let title = ticketElement.getTitle();
var message = ticketElement.getMessage();
var charID = ticketElement.getCharacterId();
var charName = ticketElement.getCharacterName();
- openMailToCharacter(charID,
- "Answer to your Waitlist Feedback",
- `Hello ${charName},\nWe read your ticket:\n${$("
").text(title).html()}\n\n${$("
").text(message).html()}\n\nregards,\n`
- );
+ title = $("
").text(title).html();
+ message = $("
").text(message).html();
+ openMailToCharacter(
+ charID,
+ $.i18n('wl-fbmail-topic'),
+ $.i18n('wl-fbmail-message', charName, title, message)
+ );
}
function openMailToCharacter(charId, subject, body) {
- // [{"recipient_id": ID, "recipient_type": "alliance|character|corporation|mailing_list"}]
- sendMail([{"recipient_id": charId, "recipient_type": "character"}], subject, body);
+ // [{"recipient_id": ID, "recipient_type":
+ // "alliance|character|corporation|mailing_list"}]
+ sendMail([
+ {
+ "recipient_id": charId,
+ "recipient_type": "character"
+ }
+ ], subject, body);
}
function changeTicketStatus(ticketID, ticketStatus, successFunc) {
@@ -61,20 +69,20 @@ waitlist.ticketsettings = (function (){
'error': function(data) {
var message = data.statusText;
if (typeof data.responseText !== 'undefined') {
- message += ": " + data.responseText;
+ message += ": " + data.responseText;
}
displayMessage(message, "danger");
},
'success': successFunc
});
}
-
+
function sendMailClickedHandler(event) {
var target = $(event.currentTarget);
var ticketId = target.attr('data-ticketId');
sendTicketMail(ticketId);
}
-
+
function changeTicketStatusClickedHandler(event) {
var target = $(event.currentTarget);
var ticketId = target.attr('data-ticketId');
@@ -83,14 +91,15 @@ waitlist.ticketsettings = (function (){
target.parent().parent().remove();
});
}
-
+
function init() {
- $('#ticket-table-body').on('click', '[data-action="sendTicketMail"]', sendMailClickedHandler);
- $('#ticket-table-body').on('click', '[data-action="changeTicketStatus"]', changeTicketStatusClickedHandler);
+ $('#ticket-table-body').on('click', '[data-action="sendTicketMail"]',
+ sendMailClickedHandler);
+ $('#ticket-table-body').on('click',
+ '[data-action="changeTicketStatus"]',
+ changeTicketStatusClickedHandler);
}
-
-
- $(document).ready(init);
+
+ $(document).ready(init);
return {};
})();
-
diff --git a/static/js/waitlist-dom.js b/static/js/waitlist-dom.js
index dc3aa81f..361e4475 100644
--- a/static/js/waitlist-dom.js
+++ b/static/js/waitlist-dom.js
@@ -20,19 +20,30 @@ waitlist.listdom = (function(){
var type = "default";
switch (name) {
case "B":
+ type = "success";
+ name = $.i18n('wl-tag-basilisk');
+ break;
case "S":
+ type = "success";
+ name = $.i18n('wl-tag-scimitar');
+ break;
case "LOGI":
type = "success";
+ name = $.i18n('wl-tag-logi');
break;
case "DPS":
type = "danger";
+ name = $.i18n('wl-tag-dps')
break;
case "SNI":
type = "warning";
+ name = $.i18n('wl-tag-sniper')
break;
default:
type = "secondary";
}
+
+
return $.parseHTML(`${name}`);
}
@@ -107,7 +118,7 @@ waitlist.listdom = (function(){
// if the current user can view fits and or it is this character and
// this entry is a rookie
if ((settings.can_view_fits || entry.character.id === settings.user_id) && entry.character.newbro) {
- newBroTag = ' New';
+ newBroTag = ' '+$.i18n('wl-new')+'';
}
var cTime = new Date(Date.now());
var xupTime = new Date(Date.parse(entry.time));
@@ -122,7 +133,7 @@ waitlist.listdom = (function(){
oldInvites =
`
- To continue using your account, you need to authenticate all connected characters, they are listed below.
+ {{ _('To continue using your account, you need to authenticate all connected characters, they are listed below.') }}