- new preview section added to "add system" dialog, shows persistent data, closed 662

This commit is contained in:
Mark Friedrich
2018-09-29 20:23:36 +02:00
parent 8fbbdaca45
commit 343eaf561b
17 changed files with 791 additions and 502 deletions

View File

@@ -273,13 +273,14 @@ class System extends Controller\AccessController {
$requestData = (array)$f3->get('POST');
$mapId = (int)$requestData['mapId'];
$systemId = (int)$requestData['systemId'];
$isCcpId = (bool)$requestData['isCcpId'];
$activeCharacter = $this->getCharacter();
$return = (object) [];
if(
!is_null($map = $activeCharacter->getMap($mapId)) &&
!is_null($system = $map->getSystemById($systemId))
!is_null($system = $isCcpId ? $map->getSystemByCCPId($systemId) : $map->getSystemById($systemId))
){
$return->system = $system->getData();
$return->system->signatures = $system->getSignaturesData();

View File

@@ -54,11 +54,6 @@ define([
connectionContextMenuId: 'pf-map-connection-contextmenu',
systemContextMenuId: 'pf-map-system-contextmenu',
// dialogs
systemDialogId: 'pf-system-dialog', // id for system dialog
systemDialogSelectClass: 'pf-system-dialog-select', // class for system select Element
systemDialogStatusSelectId: 'pf-system-dialog-status-select', // id for "status" select
// system security classes
systemSec: 'pf-system-sec'
};
@@ -1325,228 +1320,6 @@ define([
}
};
/**
* save a new system and add it to the map
* @param requestData
* @param context
*/
let saveSystem = (requestData, context) => {
$.ajax({
type: 'POST',
url: Init.path.saveSystem,
data: requestData,
dataType: 'json',
context: context
}).done(function(responseData){
let newSystemData = responseData.systemData;
if( !$.isEmptyObject(newSystemData) ){
Util.showNotify({title: 'New system', text: newSystemData.name, type: 'success'});
// draw new system to map
drawSystem(this.map, newSystemData, this.sourceSystem);
// re/arrange systems (prevent overlapping)
MagnetizerWrapper.setElements(this.map);
if(this.onSuccess){
this.onSuccess();
}
}
// show errors
if(
responseData.error &&
responseData.error.length > 0
){
for(let i = 0; i < responseData.error.length; i++){
let error = responseData.error[i];
Util.showNotify({title: error.field + ' error', text: 'System: ' + error.message, type: error.type});
}
}
}).fail(function(jqXHR, status, error){
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': saveSystem', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
}).always(function(){
if(this.onAlways){
this.onAlways(this);
}
});
};
/**
* open "new system" dialog and add the system to map
* optional the new system is connected to a "sourceSystem" (if available)
*
* @param map
* @param options
*/
let showNewSystemDialog = (map, options) => {
let mapContainer = $(map.getContainer());
// format system status for form select -----------------------------------------------------------------------
// "default" selection (id = 0) prevents status from being overwritten
// -> e.g. keep status information if system was just inactive (active = 0)
let statusData = [{id: 0, text: 'auto'}];
// get current map data ---------------------------------------------------------------------------------------
let mapData = mapContainer.getMapDataFromClient({forceData: true});
let mapSystems = mapData.data.systems;
let mapSystemCount = mapSystems.length;
let mapTypeName = mapContainer.data('typeName');
let maxAllowedSystems = Init.mapTypes[mapTypeName].defaultConfig.max_systems;
// show error if system max count reached ---------------------------------------------------------------------
if(mapSystemCount >= maxAllowedSystems){
Util.showNotify({title: 'Max system count exceeded', text: 'Limit of ' + maxAllowedSystems + ' systems reached', type: 'warning'});
return;
}
// disable systems that are already on it ---------------------------------------------------------------------
let mapSystemIds = [];
for(let i = 0; i < mapSystems.length; i++ ){
mapSystemIds.push( mapSystems[i].systemId );
}
// dialog data ------------------------------------------------------------------------------------------------
let data = {
id: config.systemDialogId,
select2Class: Util.config.select2Class,
selectClass: config.systemDialogSelectClass,
statusSelectId: config.systemDialogStatusSelectId,
statusData: statusData
};
// set current position as "default" system to add ------------------------------------------------------------
let currentCharacterLog = Util.getCurrentCharacterLog();
if(
currentCharacterLog !== false &&
mapSystemIds.indexOf( currentCharacterLog.system.id ) === -1
){
// current system is NOT already on this map
// set current position as "default" system to add
data.currentSystem = currentCharacterLog.system;
}
requirejs(['text!templates/dialog/system.html', 'mustache'], function(template, Mustache){
let content = Mustache.render(template, data);
let systemDialog = bootbox.dialog({
title: 'Add new system',
message: content,
show: false,
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
},
success: {
label: '<i class="fas fa-fw fa-check"></i> save',
className: 'btn-success',
callback: function(e){
// get form Values
let form = $('#' + config.systemDialogId).find('form');
let systemDialogData = $(form).getFormValues();
// validate form
form.validator('validate');
// check whether the form is valid
let formValid = form.isValidForm();
if(formValid === false){
// don't close dialog
return false;
}
// calculate new system position ----------------------------------------------------------
let newPosition = {
x: 0,
y: 0
};
let sourceSystem = null;
// add new position
if(options.sourceSystem !== undefined){
sourceSystem = options.sourceSystem;
// get new position
newPosition = System.calculateNewSystemPosition(sourceSystem);
}else{
// check mouse cursor position (add system to map)
newPosition = {
x: options.position.x,
y: options.position.y
};
}
systemDialogData.position = newPosition;
// ----------------------------------------------------------------------------------------
let requestData = {
systemData: systemDialogData,
mapData: {
id: mapContainer.data('id')
}
};
this.find('.modal-content').showLoadingAnimation();
saveSystem(requestData, {
map: map,
sourceSystem: sourceSystem,
systemDialog: this,
onSuccess: () => {
bootbox.hideAll();
},
onAlways: (context) => {
context.systemDialog.find('.modal-content').hideLoadingAnimation();
}
});
return false;
}
}
}
});
systemDialog.on('show.bs.modal', function(e){
let modalContent = $('#' + config.systemDialogId);
// init "status" select2
for(let [statusName, data] of Object.entries(Init.systemStatus)){
statusData.push({id: data.id, text: data.label, class: data.class});
}
modalContent.find('#' + config.systemDialogStatusSelectId).initStatusSelect({
data: statusData,
iconClass: 'fa-tag'
});
});
systemDialog.on('shown.bs.modal', function(e){
let modalContent = $('#' + config.systemDialogId);
// init system select live search - some delay until modal transition has finished
let selectElement = modalContent.find('.' + config.systemDialogSelectClass);
selectElement.delay(240).initSystemSelect({
key: 'id',
disabledOptions: mapSystemIds
});
});
// show dialog
systemDialog.modal('show');
});
};
/**
* make a system name/alias editable by x-editable
* @param system
@@ -2057,7 +1830,7 @@ define([
switch(action){
case 'add_system':
// add a new system
showNewSystemDialog(map, {sourceSystem: currentSystem} );
System.showNewSystemDialog(map, {sourceSystem: currentSystem}, saveSystemCallback);
break;
case 'lock_system':
@@ -2166,6 +1939,20 @@ define([
Util.singleDoubleClick(system, single, double);
};
/**
* callback after system save
* @param map
* @param newSystemData
* @param sourceSystem
*/
let saveSystemCallback = (map, newSystemData, sourceSystem) => {
// draw new system to map
drawSystem(map, newSystemData, sourceSystem);
// re/arrange systems (prevent overlapping)
MagnetizerWrapper.setElements(map);
};
/**
* mark a dom element (map, system, connection) as changed
*/
@@ -2480,7 +2267,7 @@ define([
position.y = dimensions[0].top;
}
showNewSystemDialog(currentMap, {position: position});
System.showNewSystemDialog(currentMap, {position: position}, saveSystemCallback);
break;
case 'select_all':
currentMapElement.selectAllSystems();
@@ -3280,7 +3067,7 @@ define([
return {
getMapInstance: getMapInstance,
loadMap: loadMap,
showNewSystemDialog: showNewSystemDialog
saveSystemCallback: saveSystemCallback
};
});

View File

@@ -29,8 +29,21 @@ define([
systemTooltipInnerIdPrefix: 'pf-system-tooltip-inner-', // id prefix for system tooltip content
systemTooltipInnerClass: 'pf-system-tooltip-inner', // class for system tooltip content
dialogRallyId: 'pf-rally-dialog', // id for "Rally point" dialog
// dialogs
dialogSystemId: 'pf-system-dialog', // id for system dialog
dialogSystemSelectClass: 'pf-system-dialog-select', // class for system select element
dialogSystemStatusSelectId: 'pf-system-dialog-status-select', // id for "status" select
dialogSystemLockId: 'pf-system-dialog-lock', // id for "locked" checkbox
dialogSystemRallyId: 'pf-system-dialog-rally', // id for "rally" checkbox
dialogSystemSectionInfoId: 'pf-system-dialog-section-info', // id for "info" section element
dialogSystemSectionInfoStatusId: 'pf-system-dialog-section-info-status', // id for "status" message in "info" element
dialogSystemAliasId: 'pf-system-dialog-alias', // id for "alias" static element
dialogSystemDescriptionId: 'pf-system-dialog-description', // id for "description" static element
dialogSystemCreatedId: 'pf-system-dialog-created', // id for "created" static element
dialogSystemUpdatedId: 'pf-system-dialog-updated', // id for "updated" static element
dialogRallyId: 'pf-rally-dialog', // id for "Rally point" dialog
dialogRallyPokeDesktopId: 'pf-rally-dialog-poke-desktop', // id for "desktop" poke checkbox
dialogRallyPokeSlackId: 'pf-rally-dialog-poke-slack', // id for "Slack" poke checkbox
dialogRallyPokeDiscordId: 'pf-rally-dialog-poke-discord', // id for "Discord" poke checkbox
@@ -43,6 +56,301 @@ define([
'- DPS and Logistic ships needed'
};
/**
* save a new system and add it to the map
* @param requestData
* @param context
* @param callback
*/
let saveSystem = (requestData, context, callback) => {
$.ajax({
type: 'POST',
url: Init.path.saveSystem,
data: requestData,
dataType: 'json',
context: context
}).done(function(responseData){
let newSystemData = responseData.systemData;
if( !$.isEmptyObject(newSystemData) ){
Util.showNotify({title: 'New system', text: newSystemData.name, type: 'success'});
callback(newSystemData);
}
// show errors
if(
responseData.error &&
responseData.error.length > 0
){
for(let i = 0; i < responseData.error.length; i++){
let error = responseData.error[i];
Util.showNotify({title: error.field + ' error', text: 'System: ' + error.message, type: error.type});
}
}
}).fail(function(jqXHR, status, error){
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': saveSystem', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
}).always(function(){
this.systemDialog.find('.modal-content').hideLoadingAnimation();
});
};
/**
* open "new system" dialog and add the system to map
* optional the new system is connected to a "sourceSystem" (if available)
* @param map
* @param options
* @param callback
*/
let showNewSystemDialog = (map, options, callback) => {
let mapContainer = $(map.getContainer());
let mapId = mapContainer.data('id');
// format system status for form select -----------------------------------------------------------------------
// "default" selection (id = 0) prevents status from being overwritten
// -> e.g. keep status information if system was just inactive (active = 0)
let statusData = [{id: 0, text: 'auto'}];
// get current map data ---------------------------------------------------------------------------------------
let mapData = mapContainer.getMapDataFromClient({forceData: true});
let mapSystems = mapData.data.systems;
let mapSystemCount = mapSystems.length;
let mapTypeName = mapContainer.data('typeName');
let maxAllowedSystems = Init.mapTypes[mapTypeName].defaultConfig.max_systems;
// show error if system max count reached ---------------------------------------------------------------------
if(mapSystemCount >= maxAllowedSystems){
Util.showNotify({title: 'Max system count exceeded', text: 'Limit of ' + maxAllowedSystems + ' systems reached', type: 'warning'});
return;
}
// disable systems that are already on it ---------------------------------------------------------------------
let mapSystemIds = mapSystems.map(systemData => systemData.systemId);
// dialog data ------------------------------------------------------------------------------------------------
let data = {
id: config.dialogSystemId,
select2Class: Util.config.select2Class,
systemSelectClass: config.dialogSystemSelectClass,
statusSelectId: config.dialogSystemStatusSelectId,
lockId: config.dialogSystemLockId,
rallyId: config.dialogSystemRallyId,
sectionInfoId: config.dialogSystemSectionInfoId,
sectionInfoStatusId: config.dialogSystemSectionInfoStatusId,
aliasId: config.dialogSystemAliasId,
descriptionId: config.dialogSystemDescriptionId,
createdId: config.dialogSystemCreatedId,
updatedId: config.dialogSystemUpdatedId,
statusData: statusData
};
// set current position as "default" system to add ------------------------------------------------------------
let currentCharacterLog = Util.getCurrentCharacterLog();
if(
currentCharacterLog !== false &&
mapSystemIds.indexOf( currentCharacterLog.system.id ) === -1
){
// current system is NOT already on this map
// set current position as "default" system to add
data.currentSystem = currentCharacterLog.system;
}
requirejs(['text!templates/dialog/system.html', 'mustache'], (template, Mustache) => {
let content = Mustache.render(template, data);
let systemDialog = bootbox.dialog({
title: 'Add new system',
message: content,
show: false,
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
},
success: {
label: '<i class="fas fa-fw fa-check"></i> save',
className: 'btn-success',
callback: function(e){
// get form Values
let form = this.find('form');
let systemDialogData = $(form).getFormValues();
// validate form
form.validator('validate');
// check whether the form is valid
let formValid = form.isValidForm();
// don't close dialog on invalid data
if(formValid === false) return false;
// calculate new system position ----------------------------------------------------------
let newPosition = {
x: 0,
y: 0
};
// add new position
let sourceSystem = null;
if(options.sourceSystem !== undefined){
sourceSystem = options.sourceSystem;
// get new position
newPosition = calculateNewSystemPosition(sourceSystem);
}else{
// check mouse cursor position (add system to map)
newPosition = {
x: options.position.x,
y: options.position.y
};
}
systemDialogData.position = newPosition;
// ----------------------------------------------------------------------------------------
let requestData = {
systemData: systemDialogData,
mapData: {
id: mapId
}
};
this.find('.modal-content').showLoadingAnimation();
saveSystem(requestData, {
systemDialog: this
}, (newSystemData) => {
// success callback
callback(map, newSystemData, sourceSystem);
bootbox.hideAll();
});
return false;
}
}
}
});
systemDialog.on('show.bs.modal', function(e){
let dialogElement = $(this);
// init "status" select2
for(let [statusName, data] of Object.entries(Init.systemStatus)){
statusData.push({id: data.id, text: data.label, class: data.class});
}
dialogElement.find('#' + config.dialogSystemStatusSelectId).initStatusSelect({
data: statusData,
iconClass: 'fa-tag'
});
});
systemDialog.on('shown.bs.modal', function(e){
let dialogElement = $(this);
// no system selected
updateDialog(dialogElement, false);
dialogElement.initTooltips();
// init system select live search - some delay until modal transition has finished
let selectElement = dialogElement.find('.' + config.dialogSystemSelectClass);
selectElement.delay(240).initSystemSelect({
key: 'id',
disabledOptions: mapSystemIds,
onChange: (systemId) => {
// on system select -> update dialog with persistent system data
if(systemId){
// show loading animation
dialogElement.find('[data-type="spinner"]').addClass('in');
MapUtil.requestSystemData({
mapId: mapId,
systemId: systemId,
isCcpId: 1
}, {
dialogElement: dialogElement
}).then(payload => updateDialog(payload.context.dialogElement, payload.data))
.catch(payload => updateDialog(payload.context.dialogElement));
}else{
// no system selected
updateDialog(dialogElement, false);
}
}
});
});
// show dialog
systemDialog.modal('show');
/**
* update new system dialog with some "additional" data
* -> if system was mapped before
* @param dialogElement
* @param systemData
*/
let updateDialog = (dialogElement, systemData = null) => {
let labelEmpty = '<span class="editable-empty">empty</span>';
let labelUnknown = '<span class="editable-empty">unknown</span>';
let labelExist = '<span class="txt-color txt-color-success">loaded</span>';
let showInfoHeadline = 'fadeOut';
let showInfoSection = 'hide';
let info = labelEmpty;
let statusId = false; // -> no value change
let alias = labelEmpty;
let description = labelEmpty;
let createdTime = labelUnknown;
let updatedTime = labelUnknown;
if(systemData){
// system data found for selected system
showInfoHeadline = 'fadeIn';
showInfoSection = 'show';
info = labelExist;
statusId = parseInt(Util.getObjVal(systemData, 'status.id')) || statusId;
alias = systemData.alias.length ? Util.htmlEncode(systemData.alias) : alias;
description = systemData.description.length ? systemData.description : description;
let dateCreated = new Date(systemData.created.created * 1000);
let dateUpdated = new Date(systemData.updated.updated * 1000);
let dateCreatedUTC = Util.convertDateToUTC(dateCreated);
let dateUpdatedUTC = Util.convertDateToUTC(dateUpdated);
createdTime = Util.convertDateToString(dateCreatedUTC);
updatedTime = Util.convertDateToString(dateUpdatedUTC);
}else if(systemData === null){
// no system found for selected system
showInfoHeadline = 'fadeIn';
}
// update new system dialog with new default data
dialogElement.find('#' + config.dialogSystemSectionInfoStatusId).html(info);
if(statusId !== false){
dialogElement.find('#' + config.dialogSystemStatusSelectId).val(statusId).trigger('change');
}
dialogElement.find('#' + config.dialogSystemAliasId).html(alias);
dialogElement.find('#' + config.dialogSystemDescriptionId).html(description);
dialogElement.find('#' + config.dialogSystemCreatedId).html('<i class="fas fa-fw fa-plus"></i>&nbsp' + createdTime);
dialogElement.find('#' + config.dialogSystemUpdatedId).html('<i class="fas fa-fw fa-pen"></i>&nbsp' + updatedTime);
dialogElement.find('#' + config.dialogSystemSectionInfoId).collapse(showInfoSection);
dialogElement.find('[data-target="#' + config.dialogSystemSectionInfoId + '"]').velocity(showInfoHeadline);
dialogElement.find('[data-type="spinner"]').removeClass('in');
};
});
};
/**
* show "set rally point" dialog for system
* @param system
@@ -535,9 +843,9 @@ define([
};
return {
showNewSystemDialog: showNewSystemDialog,
deleteSystems: deleteSystems,
removeSystems: removeSystems,
calculateNewSystemPosition: calculateNewSystemPosition,
getHeadInfoElement: getHeadInfoElement
};
});

View File

@@ -1314,10 +1314,11 @@ define([
// dynamic require Map module -> otherwise there is a require(), loop
let Map = require('app/map/map');
let System = require('app/map/system');
let map = Map.getMapInstance( mapElement.data('id'));
mapWrapper.watchKey('mapSystemAdd', (mapWrapper) => {
Map.showNewSystemDialog(map, {position: {x: 0, y: 0}});
System.showNewSystemDialog(map, {position: {x: 0, y: 0}}, Map.saveSystemCallback);
},{focus: true});
mapWrapper.watchKey('mapSystemsSelect', (mapWrapper) => {
@@ -1617,6 +1618,10 @@ define([
let requestSystemData = (requestData, context) => {
let requestSystemDataExecutor = (resolve, reject) => {
let payload = {
action: 'systemData'
};
$.ajax({
url: Init.path.getSystemData,
type: 'POST',
@@ -1624,17 +1629,20 @@ define([
data: requestData,
context: context
}).done(function(data){
payload.context = this;
if(data.system){
resolve({
action: 'systemData',
context: this,
data: data.system
});
// system data found
payload.data = data.system;
resolve(payload);
}else{
console.warn('Missing systemData in response!', requestData);
// no system data returned/found
reject(payload);
}
}).fail(function(jqXHR, status, error){
console.warn('Fail request systemData!', requestData);
payload.context = this;
reject(payload);
});
};

View File

@@ -333,11 +333,14 @@ define([
dropdownParent: selectElement.parents('.modal-body'),
minimumInputLength: 3,
templateResult: formatResultData,
placeholder: 'System name',
placeholder: 'Name or ID',
allowClear: true,
maximumSelectionLength: options.maxSelectionLength
}).on('change', function(e){
// select changed
if(options.onChange){
options.onChange(parseInt($(this).val()) || 0);
}
}).on('select2:open', function(){
// clear selected system (e.g. default system)
// => improves usability (not necessary). There is a small "x" if field can be cleared manually

View File

@@ -781,9 +781,7 @@ define([
default: console.error('insertElement: %s is not specified!', defaultOptions.insertElement);
}
//containerElement.children().first().velocity('stop').velocity('fadeIn');
$('#' + defaultOptions.messageId).velocity('stop').velocity('fadeIn');
});
};
@@ -2805,6 +2803,20 @@ define([
return instance;
};
/**
* HTML encode string
* @param value
* @returns {jQuery}
*/
let htmlEncode = value => $('<div>').text(value).html();
/**
* HTML decode string
* @param value
* @returns {jQuery}
*/
let htmlDecode = value => $('<div>').html(value).text();
/**
* get deep json object value if exists
* -> e.g. key = 'first.last.third' string
@@ -3003,6 +3015,8 @@ define([
singleDoubleClick: singleDoubleClick,
getTableId: getTableId,
getDataTableInstance: getDataTableInstance,
htmlEncode: htmlEncode,
htmlDecode: htmlDecode,
getObjVal: getObjVal,
redirect: redirect,
logout: logout,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -54,11 +54,6 @@ define([
connectionContextMenuId: 'pf-map-connection-contextmenu',
systemContextMenuId: 'pf-map-system-contextmenu',
// dialogs
systemDialogId: 'pf-system-dialog', // id for system dialog
systemDialogSelectClass: 'pf-system-dialog-select', // class for system select Element
systemDialogStatusSelectId: 'pf-system-dialog-status-select', // id for "status" select
// system security classes
systemSec: 'pf-system-sec'
};
@@ -1325,228 +1320,6 @@ define([
}
};
/**
* save a new system and add it to the map
* @param requestData
* @param context
*/
let saveSystem = (requestData, context) => {
$.ajax({
type: 'POST',
url: Init.path.saveSystem,
data: requestData,
dataType: 'json',
context: context
}).done(function(responseData){
let newSystemData = responseData.systemData;
if( !$.isEmptyObject(newSystemData) ){
Util.showNotify({title: 'New system', text: newSystemData.name, type: 'success'});
// draw new system to map
drawSystem(this.map, newSystemData, this.sourceSystem);
// re/arrange systems (prevent overlapping)
MagnetizerWrapper.setElements(this.map);
if(this.onSuccess){
this.onSuccess();
}
}
// show errors
if(
responseData.error &&
responseData.error.length > 0
){
for(let i = 0; i < responseData.error.length; i++){
let error = responseData.error[i];
Util.showNotify({title: error.field + ' error', text: 'System: ' + error.message, type: error.type});
}
}
}).fail(function(jqXHR, status, error){
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': saveSystem', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
}).always(function(){
if(this.onAlways){
this.onAlways(this);
}
});
};
/**
* open "new system" dialog and add the system to map
* optional the new system is connected to a "sourceSystem" (if available)
*
* @param map
* @param options
*/
let showNewSystemDialog = (map, options) => {
let mapContainer = $(map.getContainer());
// format system status for form select -----------------------------------------------------------------------
// "default" selection (id = 0) prevents status from being overwritten
// -> e.g. keep status information if system was just inactive (active = 0)
let statusData = [{id: 0, text: 'auto'}];
// get current map data ---------------------------------------------------------------------------------------
let mapData = mapContainer.getMapDataFromClient({forceData: true});
let mapSystems = mapData.data.systems;
let mapSystemCount = mapSystems.length;
let mapTypeName = mapContainer.data('typeName');
let maxAllowedSystems = Init.mapTypes[mapTypeName].defaultConfig.max_systems;
// show error if system max count reached ---------------------------------------------------------------------
if(mapSystemCount >= maxAllowedSystems){
Util.showNotify({title: 'Max system count exceeded', text: 'Limit of ' + maxAllowedSystems + ' systems reached', type: 'warning'});
return;
}
// disable systems that are already on it ---------------------------------------------------------------------
let mapSystemIds = [];
for(let i = 0; i < mapSystems.length; i++ ){
mapSystemIds.push( mapSystems[i].systemId );
}
// dialog data ------------------------------------------------------------------------------------------------
let data = {
id: config.systemDialogId,
select2Class: Util.config.select2Class,
selectClass: config.systemDialogSelectClass,
statusSelectId: config.systemDialogStatusSelectId,
statusData: statusData
};
// set current position as "default" system to add ------------------------------------------------------------
let currentCharacterLog = Util.getCurrentCharacterLog();
if(
currentCharacterLog !== false &&
mapSystemIds.indexOf( currentCharacterLog.system.id ) === -1
){
// current system is NOT already on this map
// set current position as "default" system to add
data.currentSystem = currentCharacterLog.system;
}
requirejs(['text!templates/dialog/system.html', 'mustache'], function(template, Mustache){
let content = Mustache.render(template, data);
let systemDialog = bootbox.dialog({
title: 'Add new system',
message: content,
show: false,
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
},
success: {
label: '<i class="fas fa-fw fa-check"></i> save',
className: 'btn-success',
callback: function(e){
// get form Values
let form = $('#' + config.systemDialogId).find('form');
let systemDialogData = $(form).getFormValues();
// validate form
form.validator('validate');
// check whether the form is valid
let formValid = form.isValidForm();
if(formValid === false){
// don't close dialog
return false;
}
// calculate new system position ----------------------------------------------------------
let newPosition = {
x: 0,
y: 0
};
let sourceSystem = null;
// add new position
if(options.sourceSystem !== undefined){
sourceSystem = options.sourceSystem;
// get new position
newPosition = System.calculateNewSystemPosition(sourceSystem);
}else{
// check mouse cursor position (add system to map)
newPosition = {
x: options.position.x,
y: options.position.y
};
}
systemDialogData.position = newPosition;
// ----------------------------------------------------------------------------------------
let requestData = {
systemData: systemDialogData,
mapData: {
id: mapContainer.data('id')
}
};
this.find('.modal-content').showLoadingAnimation();
saveSystem(requestData, {
map: map,
sourceSystem: sourceSystem,
systemDialog: this,
onSuccess: () => {
bootbox.hideAll();
},
onAlways: (context) => {
context.systemDialog.find('.modal-content').hideLoadingAnimation();
}
});
return false;
}
}
}
});
systemDialog.on('show.bs.modal', function(e){
let modalContent = $('#' + config.systemDialogId);
// init "status" select2
for(let [statusName, data] of Object.entries(Init.systemStatus)){
statusData.push({id: data.id, text: data.label, class: data.class});
}
modalContent.find('#' + config.systemDialogStatusSelectId).initStatusSelect({
data: statusData,
iconClass: 'fa-tag'
});
});
systemDialog.on('shown.bs.modal', function(e){
let modalContent = $('#' + config.systemDialogId);
// init system select live search - some delay until modal transition has finished
let selectElement = modalContent.find('.' + config.systemDialogSelectClass);
selectElement.delay(240).initSystemSelect({
key: 'id',
disabledOptions: mapSystemIds
});
});
// show dialog
systemDialog.modal('show');
});
};
/**
* make a system name/alias editable by x-editable
* @param system
@@ -2057,7 +1830,7 @@ define([
switch(action){
case 'add_system':
// add a new system
showNewSystemDialog(map, {sourceSystem: currentSystem} );
System.showNewSystemDialog(map, {sourceSystem: currentSystem}, saveSystemCallback);
break;
case 'lock_system':
@@ -2166,6 +1939,20 @@ define([
Util.singleDoubleClick(system, single, double);
};
/**
* callback after system save
* @param map
* @param newSystemData
* @param sourceSystem
*/
let saveSystemCallback = (map, newSystemData, sourceSystem) => {
// draw new system to map
drawSystem(map, newSystemData, sourceSystem);
// re/arrange systems (prevent overlapping)
MagnetizerWrapper.setElements(map);
};
/**
* mark a dom element (map, system, connection) as changed
*/
@@ -2480,7 +2267,7 @@ define([
position.y = dimensions[0].top;
}
showNewSystemDialog(currentMap, {position: position});
System.showNewSystemDialog(currentMap, {position: position}, saveSystemCallback);
break;
case 'select_all':
currentMapElement.selectAllSystems();
@@ -3280,7 +3067,7 @@ define([
return {
getMapInstance: getMapInstance,
loadMap: loadMap,
showNewSystemDialog: showNewSystemDialog
saveSystemCallback: saveSystemCallback
};
});

View File

@@ -29,8 +29,21 @@ define([
systemTooltipInnerIdPrefix: 'pf-system-tooltip-inner-', // id prefix for system tooltip content
systemTooltipInnerClass: 'pf-system-tooltip-inner', // class for system tooltip content
dialogRallyId: 'pf-rally-dialog', // id for "Rally point" dialog
// dialogs
dialogSystemId: 'pf-system-dialog', // id for system dialog
dialogSystemSelectClass: 'pf-system-dialog-select', // class for system select element
dialogSystemStatusSelectId: 'pf-system-dialog-status-select', // id for "status" select
dialogSystemLockId: 'pf-system-dialog-lock', // id for "locked" checkbox
dialogSystemRallyId: 'pf-system-dialog-rally', // id for "rally" checkbox
dialogSystemSectionInfoId: 'pf-system-dialog-section-info', // id for "info" section element
dialogSystemSectionInfoStatusId: 'pf-system-dialog-section-info-status', // id for "status" message in "info" element
dialogSystemAliasId: 'pf-system-dialog-alias', // id for "alias" static element
dialogSystemDescriptionId: 'pf-system-dialog-description', // id for "description" static element
dialogSystemCreatedId: 'pf-system-dialog-created', // id for "created" static element
dialogSystemUpdatedId: 'pf-system-dialog-updated', // id for "updated" static element
dialogRallyId: 'pf-rally-dialog', // id for "Rally point" dialog
dialogRallyPokeDesktopId: 'pf-rally-dialog-poke-desktop', // id for "desktop" poke checkbox
dialogRallyPokeSlackId: 'pf-rally-dialog-poke-slack', // id for "Slack" poke checkbox
dialogRallyPokeDiscordId: 'pf-rally-dialog-poke-discord', // id for "Discord" poke checkbox
@@ -43,6 +56,301 @@ define([
'- DPS and Logistic ships needed'
};
/**
* save a new system and add it to the map
* @param requestData
* @param context
* @param callback
*/
let saveSystem = (requestData, context, callback) => {
$.ajax({
type: 'POST',
url: Init.path.saveSystem,
data: requestData,
dataType: 'json',
context: context
}).done(function(responseData){
let newSystemData = responseData.systemData;
if( !$.isEmptyObject(newSystemData) ){
Util.showNotify({title: 'New system', text: newSystemData.name, type: 'success'});
callback(newSystemData);
}
// show errors
if(
responseData.error &&
responseData.error.length > 0
){
for(let i = 0; i < responseData.error.length; i++){
let error = responseData.error[i];
Util.showNotify({title: error.field + ' error', text: 'System: ' + error.message, type: error.type});
}
}
}).fail(function(jqXHR, status, error){
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': saveSystem', text: reason, type: 'warning'});
$(document).setProgramStatus('problem');
}).always(function(){
this.systemDialog.find('.modal-content').hideLoadingAnimation();
});
};
/**
* open "new system" dialog and add the system to map
* optional the new system is connected to a "sourceSystem" (if available)
* @param map
* @param options
* @param callback
*/
let showNewSystemDialog = (map, options, callback) => {
let mapContainer = $(map.getContainer());
let mapId = mapContainer.data('id');
// format system status for form select -----------------------------------------------------------------------
// "default" selection (id = 0) prevents status from being overwritten
// -> e.g. keep status information if system was just inactive (active = 0)
let statusData = [{id: 0, text: 'auto'}];
// get current map data ---------------------------------------------------------------------------------------
let mapData = mapContainer.getMapDataFromClient({forceData: true});
let mapSystems = mapData.data.systems;
let mapSystemCount = mapSystems.length;
let mapTypeName = mapContainer.data('typeName');
let maxAllowedSystems = Init.mapTypes[mapTypeName].defaultConfig.max_systems;
// show error if system max count reached ---------------------------------------------------------------------
if(mapSystemCount >= maxAllowedSystems){
Util.showNotify({title: 'Max system count exceeded', text: 'Limit of ' + maxAllowedSystems + ' systems reached', type: 'warning'});
return;
}
// disable systems that are already on it ---------------------------------------------------------------------
let mapSystemIds = mapSystems.map(systemData => systemData.systemId);
// dialog data ------------------------------------------------------------------------------------------------
let data = {
id: config.dialogSystemId,
select2Class: Util.config.select2Class,
systemSelectClass: config.dialogSystemSelectClass,
statusSelectId: config.dialogSystemStatusSelectId,
lockId: config.dialogSystemLockId,
rallyId: config.dialogSystemRallyId,
sectionInfoId: config.dialogSystemSectionInfoId,
sectionInfoStatusId: config.dialogSystemSectionInfoStatusId,
aliasId: config.dialogSystemAliasId,
descriptionId: config.dialogSystemDescriptionId,
createdId: config.dialogSystemCreatedId,
updatedId: config.dialogSystemUpdatedId,
statusData: statusData
};
// set current position as "default" system to add ------------------------------------------------------------
let currentCharacterLog = Util.getCurrentCharacterLog();
if(
currentCharacterLog !== false &&
mapSystemIds.indexOf( currentCharacterLog.system.id ) === -1
){
// current system is NOT already on this map
// set current position as "default" system to add
data.currentSystem = currentCharacterLog.system;
}
requirejs(['text!templates/dialog/system.html', 'mustache'], (template, Mustache) => {
let content = Mustache.render(template, data);
let systemDialog = bootbox.dialog({
title: 'Add new system',
message: content,
show: false,
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
},
success: {
label: '<i class="fas fa-fw fa-check"></i> save',
className: 'btn-success',
callback: function(e){
// get form Values
let form = this.find('form');
let systemDialogData = $(form).getFormValues();
// validate form
form.validator('validate');
// check whether the form is valid
let formValid = form.isValidForm();
// don't close dialog on invalid data
if(formValid === false) return false;
// calculate new system position ----------------------------------------------------------
let newPosition = {
x: 0,
y: 0
};
// add new position
let sourceSystem = null;
if(options.sourceSystem !== undefined){
sourceSystem = options.sourceSystem;
// get new position
newPosition = calculateNewSystemPosition(sourceSystem);
}else{
// check mouse cursor position (add system to map)
newPosition = {
x: options.position.x,
y: options.position.y
};
}
systemDialogData.position = newPosition;
// ----------------------------------------------------------------------------------------
let requestData = {
systemData: systemDialogData,
mapData: {
id: mapId
}
};
this.find('.modal-content').showLoadingAnimation();
saveSystem(requestData, {
systemDialog: this
}, (newSystemData) => {
// success callback
callback(map, newSystemData, sourceSystem);
bootbox.hideAll();
});
return false;
}
}
}
});
systemDialog.on('show.bs.modal', function(e){
let dialogElement = $(this);
// init "status" select2
for(let [statusName, data] of Object.entries(Init.systemStatus)){
statusData.push({id: data.id, text: data.label, class: data.class});
}
dialogElement.find('#' + config.dialogSystemStatusSelectId).initStatusSelect({
data: statusData,
iconClass: 'fa-tag'
});
});
systemDialog.on('shown.bs.modal', function(e){
let dialogElement = $(this);
// no system selected
updateDialog(dialogElement, false);
dialogElement.initTooltips();
// init system select live search - some delay until modal transition has finished
let selectElement = dialogElement.find('.' + config.dialogSystemSelectClass);
selectElement.delay(240).initSystemSelect({
key: 'id',
disabledOptions: mapSystemIds,
onChange: (systemId) => {
// on system select -> update dialog with persistent system data
if(systemId){
// show loading animation
dialogElement.find('[data-type="spinner"]').addClass('in');
MapUtil.requestSystemData({
mapId: mapId,
systemId: systemId,
isCcpId: 1
}, {
dialogElement: dialogElement
}).then(payload => updateDialog(payload.context.dialogElement, payload.data))
.catch(payload => updateDialog(payload.context.dialogElement));
}else{
// no system selected
updateDialog(dialogElement, false);
}
}
});
});
// show dialog
systemDialog.modal('show');
/**
* update new system dialog with some "additional" data
* -> if system was mapped before
* @param dialogElement
* @param systemData
*/
let updateDialog = (dialogElement, systemData = null) => {
let labelEmpty = '<span class="editable-empty">empty</span>';
let labelUnknown = '<span class="editable-empty">unknown</span>';
let labelExist = '<span class="txt-color txt-color-success">loaded</span>';
let showInfoHeadline = 'fadeOut';
let showInfoSection = 'hide';
let info = labelEmpty;
let statusId = false; // -> no value change
let alias = labelEmpty;
let description = labelEmpty;
let createdTime = labelUnknown;
let updatedTime = labelUnknown;
if(systemData){
// system data found for selected system
showInfoHeadline = 'fadeIn';
showInfoSection = 'show';
info = labelExist;
statusId = parseInt(Util.getObjVal(systemData, 'status.id')) || statusId;
alias = systemData.alias.length ? Util.htmlEncode(systemData.alias) : alias;
description = systemData.description.length ? systemData.description : description;
let dateCreated = new Date(systemData.created.created * 1000);
let dateUpdated = new Date(systemData.updated.updated * 1000);
let dateCreatedUTC = Util.convertDateToUTC(dateCreated);
let dateUpdatedUTC = Util.convertDateToUTC(dateUpdated);
createdTime = Util.convertDateToString(dateCreatedUTC);
updatedTime = Util.convertDateToString(dateUpdatedUTC);
}else if(systemData === null){
// no system found for selected system
showInfoHeadline = 'fadeIn';
}
// update new system dialog with new default data
dialogElement.find('#' + config.dialogSystemSectionInfoStatusId).html(info);
if(statusId !== false){
dialogElement.find('#' + config.dialogSystemStatusSelectId).val(statusId).trigger('change');
}
dialogElement.find('#' + config.dialogSystemAliasId).html(alias);
dialogElement.find('#' + config.dialogSystemDescriptionId).html(description);
dialogElement.find('#' + config.dialogSystemCreatedId).html('<i class="fas fa-fw fa-plus"></i>&nbsp' + createdTime);
dialogElement.find('#' + config.dialogSystemUpdatedId).html('<i class="fas fa-fw fa-pen"></i>&nbsp' + updatedTime);
dialogElement.find('#' + config.dialogSystemSectionInfoId).collapse(showInfoSection);
dialogElement.find('[data-target="#' + config.dialogSystemSectionInfoId + '"]').velocity(showInfoHeadline);
dialogElement.find('[data-type="spinner"]').removeClass('in');
};
});
};
/**
* show "set rally point" dialog for system
* @param system
@@ -535,9 +843,9 @@ define([
};
return {
showNewSystemDialog: showNewSystemDialog,
deleteSystems: deleteSystems,
removeSystems: removeSystems,
calculateNewSystemPosition: calculateNewSystemPosition,
getHeadInfoElement: getHeadInfoElement
};
});

View File

@@ -1314,10 +1314,11 @@ define([
// dynamic require Map module -> otherwise there is a require(), loop
let Map = require('app/map/map');
let System = require('app/map/system');
let map = Map.getMapInstance( mapElement.data('id'));
mapWrapper.watchKey('mapSystemAdd', (mapWrapper) => {
Map.showNewSystemDialog(map, {position: {x: 0, y: 0}});
System.showNewSystemDialog(map, {position: {x: 0, y: 0}}, Map.saveSystemCallback);
},{focus: true});
mapWrapper.watchKey('mapSystemsSelect', (mapWrapper) => {
@@ -1617,6 +1618,10 @@ define([
let requestSystemData = (requestData, context) => {
let requestSystemDataExecutor = (resolve, reject) => {
let payload = {
action: 'systemData'
};
$.ajax({
url: Init.path.getSystemData,
type: 'POST',
@@ -1624,17 +1629,20 @@ define([
data: requestData,
context: context
}).done(function(data){
payload.context = this;
if(data.system){
resolve({
action: 'systemData',
context: this,
data: data.system
});
// system data found
payload.data = data.system;
resolve(payload);
}else{
console.warn('Missing systemData in response!', requestData);
// no system data returned/found
reject(payload);
}
}).fail(function(jqXHR, status, error){
console.warn('Fail request systemData!', requestData);
payload.context = this;
reject(payload);
});
};

View File

@@ -333,11 +333,14 @@ define([
dropdownParent: selectElement.parents('.modal-body'),
minimumInputLength: 3,
templateResult: formatResultData,
placeholder: 'System name',
placeholder: 'Name or ID',
allowClear: true,
maximumSelectionLength: options.maxSelectionLength
}).on('change', function(e){
// select changed
if(options.onChange){
options.onChange(parseInt($(this).val()) || 0);
}
}).on('select2:open', function(){
// clear selected system (e.g. default system)
// => improves usability (not necessary). There is a small "x" if field can be cleared manually

View File

@@ -781,9 +781,7 @@ define([
default: console.error('insertElement: %s is not specified!', defaultOptions.insertElement);
}
//containerElement.children().first().velocity('stop').velocity('fadeIn');
$('#' + defaultOptions.messageId).velocity('stop').velocity('fadeIn');
});
};
@@ -2805,6 +2803,20 @@ define([
return instance;
};
/**
* HTML encode string
* @param value
* @returns {jQuery}
*/
let htmlEncode = value => $('<div>').text(value).html();
/**
* HTML decode string
* @param value
* @returns {jQuery}
*/
let htmlDecode = value => $('<div>').html(value).text();
/**
* get deep json object value if exists
* -> e.g. key = 'first.last.third' string
@@ -3003,6 +3015,8 @@ define([
singleDoubleClick: singleDoubleClick,
getTableId: getTableId,
getDataTableInstance: getDataTableInstance,
htmlEncode: htmlEncode,
htmlDecode: htmlDecode,
getObjVal: getObjVal,
redirect: redirect,
logout: logout,

View File

@@ -49,7 +49,7 @@
<div class="col-sm-12 col-md-6">
<div class="form-group">
<div class="col-sm-12 col-xs-6 checkbox">
<div class="col-xs-12 col-sm-12 col-xs-6 checkbox">
<input id="{{deleteExpiredConnectionsId}}" name="deleteExpiredConnections" value="1" type="checkbox" {{#deleteExpiredConnections}}checked{{/deleteExpiredConnections}}>
<label for="{{deleteExpiredConnectionsId}}">Auto delete outdated wormholes
<i class="fas fa-fw fa-question-circle pf-help-light" title="outdated WHs (~2 days)"></i>
@@ -60,7 +60,7 @@
<div class="col-sm-12 col-md-6">
<div class="form-group">
<div class="col-sm-12 col-xs-6 checkbox" >
<div class="col-xs-12 col-sm-12 col-xs-6 checkbox" >
<input id="{{deleteEolConnectionsId}}" name="deleteEolConnections" value="1" type="checkbox" {{#deleteEolConnections}}checked{{/deleteEolConnections}}>
<label for="{{deleteEolConnectionsId}}">Auto delete expired wormholes
<i class="fas fa-fw fa-question-circle pf-help-light" title="expired EOL WHs (~4h 15min)"></i>
@@ -69,7 +69,7 @@
</div>
</div>
<div class="col-sm-12 col-md-6">
<div class="col-xs-12 col-sm-12 col-md-6">
<div class="form-group">
<div class="col-sm-12 col-xs-6 checkbox">
<input id="{{persistentAliasesId}}" name="persistentAliases" value="1" type="checkbox" {{#persistentAliases}}checked{{/persistentAliases}}>
@@ -88,7 +88,7 @@
<div class="col-sm-12 col-md-6">
<div class="form-group">
<div class="col-sm-12 col-xs-6 checkbox">
<div class="col-xs-12 col-sm-12 col-xs-6 checkbox">
<input id="{{logHistoryId}}" name="logHistory" value="1" type="checkbox" {{#logHistory}}checked{{/logHistory}}>
<label for="{{logHistoryId}}">Save map changes to logfile
<i class="fas fa-fw fa-question-circle pf-help-light" title="Map changes will be stored in a log file"></i>
@@ -99,7 +99,7 @@
<div class="col-sm-12 col-md-6">
<div class="form-group">
<div class="col-sm-12 col-xs-6 checkbox">
<div class="col-xs-12 col-sm-12 col-xs-6 checkbox">
<input id="{{logActivityId}}" name="logActivity" value="1" type="checkbox" {{#logActivity}}checked{{/logActivity}}>
<label for="{{logActivityId}}">Store user statistics
<i class="fas fa-fw fa-question-circle pf-help-light" title="Map changes will be tracked in order to generate user statistics"></i>

View File

@@ -2,37 +2,40 @@
<form role="form" class="form-horizontal">
<div class="row">
<div class="col-xs-8">
<div class="col-xs-9">
<div class="form-group">
<label class="col-sm-2 control-label" for="form_system">System</label>
<div class="col-sm-10">
<div class="input-group">
<label for="form_system"></label>
<select id="form_system" name="systemId" class="pf-select2 {{selectClass}}" data-error="Choose a valid system" required>
<select id="form_system" name="systemId" class="pf-select2 {{systemSelectClass}}" data-error="Choose a valid system" required>
{{#currentSystem}}
<option value="{{id}}">{{name}}</option>
{{/currentSystem}}
</select>
<span class="form-control-static fade" data-type="spinner">&nbsp;&nbsp;
<i class="fas fa-fw fa-lg fa-spin fa-sync txt-color txt-color-grayLight"></i>
</span>
<span class="help-block with-errors">Search system name</span>
</div>
</div>
</div>
</div>
<div class="col-xs-4">
<div class="col-xs-3">
<div class="form-group" style="margin-bottom: 0;">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox checkbox-primary">
<input id="form_lock" name="locked" value="1" type="checkbox">
<label for="form_lock">
<input id="{{lockId}}" name="locked" value="1" type="checkbox">
<label for="{{lockId}}">
Lock system
</label>
<div class="help-block with-errors"></div>
</div>
<div class="checkbox checkbox-primary">
<input id="form_rally" name="rallyUpdated" value="1" type="checkbox">
<label for="form_rally">
<input id="{{rallyId}}" name="rallyUpdated" value="1" type="checkbox">
<label for="{{rallyId}}">
Rally point
</label>
<div class="help-block with-errors"></div>
@@ -42,13 +45,12 @@
</div>
</div>
<div class="row">
<div class="col-xs-8">
<div class="col-xs-9">
<div class="form-group">
<label class="col-sm-2 control-label" for="{{statusSelectId}}">Status</label>
<div class="col-sm-6">
<select name="statusId" id="{{statusSelectId}}" class="form-control {{select2Class}}">
<select id="{{statusSelectId}}" name="statusId" class="form-control {{select2Class}}">
{{#statusData}}
<option value="{{id}}">{{text}}</option>
{{/statusData}}
@@ -58,5 +60,49 @@
</div>
</div>
{{! system info -------------------------------------------------------- }}
<h4 class="pf-dynamic-area collapsed" data-toggle="collapse" data-target="#{{sectionInfoId}}" style="display: none;">
System intel : <span id="{{sectionInfoStatusId}}"></span>
<i class="fas fa-fw fa-question-circle pf-help-light" title="show persistent system data"></i>
</h4>
<div id="{{sectionInfoId}}" class="collapse">
<div class="row">
<div class="col-xs-12 col-sm-9">
<div class="form-group">
<label class="col-xs-4 col-sm-2 control-label">Alias</label>
<div class="col-xs-8 col-sm-10">
<div class="controls">
<div id="{{aliasId}}" class="form-control-static"></div>
</div>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-3">
<div id="{{createdId}}" class="well well-sm text-center" title="first create"></div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-sm-9">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label">Description</label>
<div class="col-xs-12 col-sm-10">
<div class="controls">
<div id="{{descriptionId}}" class="form-control-static"></div>
</div>
</div>
</div>
</div>
<div class="col-xs-12 col-sm-3">
<div id="{{updatedId}}" class="well well-sm text-center" title="last update/delete"></div>
</div>
</div>
</div>
</form>
</div>

View File

@@ -41,6 +41,7 @@
color: $orange;
top: 10px;
right: 6px;
will-change: transform, color;
@extend .pf-animate-rotate;
@extend .fa-fw;

View File

@@ -109,6 +109,7 @@
.pf-popover-character{
.table>tbody>tr>td{
border: none;
white-space: nowrap;
&:first-child + td{
// 2nd column