Compare commits

...

10 Commits

  1. 3
      Gemfile
  2. 2
      Gemfile.lock
  3. 16
      src/app.rb
  4. 67
      src/public/css/stocked.css
  5. 1
      src/public/js/burning.js
  6. 388
      src/public/js/stocked.js
  7. 42
      src/public/js/stocked/editable-input.js
  8. 1796
      src/public/js/stocked/test/data-archive.js
  9. 23
      src/public/js/stocked/test/data_1.json
  10. 5
      src/views/index.erb
  11. 377
      src/views/partials/stocked.erb
  12. 6
      src/views/partials/stocked/editableInput.erb

@ -5,7 +5,8 @@ gem 'sinatra'
gem 'sinatra-contrib' gem 'sinatra-contrib'
gem 'prawn', '2.2.2' gem 'prawn', '2.2.2'
gem 'prawn-templates', '0.1.1' gem 'prawn-templates', '0.1.1'
gem 'rubyzip'
group :development do group :development do
gem 'rerun' gem 'rerun'
end end

@ -37,6 +37,7 @@ GEM
listen (~> 3.0) listen (~> 3.0)
ruby-rc4 (0.1.5) ruby-rc4 (0.1.5)
ruby_dep (1.5.0) ruby_dep (1.5.0)
rubyzip (2.3.2)
sinatra (2.0.5) sinatra (2.0.5)
mustermann (~> 1.0) mustermann (~> 1.0)
rack (~> 2.0) rack (~> 2.0)
@ -63,6 +64,7 @@ DEPENDENCIES
prawn (= 2.2.2) prawn (= 2.2.2)
prawn-templates (= 0.1.1) prawn-templates (= 0.1.1)
rerun rerun
rubyzip
sinatra sinatra
sinatra-contrib sinatra-contrib
thin thin

@ -33,6 +33,11 @@ get /\/([\w]+)_partial/ do
erb "partials/#{partial}".to_sym erb "partials/#{partial}".to_sym
end end
get /\/stocked\/([\w]+)_partial/ do
partial = params['captures'].first
erb "partials/stocked/#{partial}".to_sym
end
get '/namegen/:gender' do get '/namegen/:gender' do
if params['gender'] == 'female' if params['gender'] == 'female'
['Ada', 'Belle', 'Carmen', 'Desdemona', 'Edie'].sample ['Ada', 'Belle', 'Carmen', 'Desdemona', 'Edie'].sample
@ -118,6 +123,17 @@ post '/wiki' do
"/get_file?file=#{key}&download_name=#{data['name']} Character Sheet.wiki" "/get_file?file=#{key}&download_name=#{data['name']} Character Sheet.wiki"
end end
post '/stocked_download' do
request.body.rewind
raw = request.body.readpartial(16 * 1024)
puts raw
data = JSON.parse(raw)
key = "stock-#{Time.now.strftime('%Y%m%d%H%M%S%L')}-#{rand(1...10000)}"
CACHE.store key, data
"/get_file?file=#{key}&download_name=#{data['Name']}.stock"
end
get '/get_file' do get '/get_file' do
data = nil data = nil
if params['download_name'].match(/\.pdf$/) if params['download_name'].match(/\.pdf$/)

@ -0,0 +1,67 @@
a.panel-title:link,.panel-title a:link { text-decoration: none; }
a.panel-title:visited,.panel-title a:visited { text-decoration: none; }
a.panel-title:hover,.panel-title a:hover { text-decoration: underline; }
a.panel-title:active,.panel-title a:active { text-decoration: underline; }
input.editable-name {
color: #333;
display: inline;
width: 12em;
}
input.editable-line {
color: #333;
}
input.editable-num {
color: #333;
display: inline;
width: 3em;
}
input.editable-name.not-editing {
background: #F5F5F5;
font-weight: bold;
}
input.editable-line.not-editing {
background: #F5F5F5;
}
input.editable-num.not-editing {
background: #F5F5F5;
}
.add-skills-traits-container {
margin-top: 1em;
margin-bottom: 1em;
}
.horizontal-input-pair {
display: flex;
align-items: center;
gap: 0.5em;
}
.horizontal-input-pair label {
margin-bottom: 0;
}
.horizontal-input-pair input {
flex-grow: 1;
}
div.skill-even {
}
div.skill-odd {
background: #F5F5F5;
}
tr.trait-even {
background: lightgray;
}
tr.trait-odd {
background: #F5F5F5;
}
textarea.trait-desc {
height: 5em;
width: 98%;
margin: 1em;
}
table.traits{
width: 100%;
}
table.traits input.editable-name {
width: 100%;
}

@ -61,6 +61,7 @@ burningModule.config(function($routeProvider) {
when('/', {controller: BurningCtrl, templateUrl:'/main_partial'}). when('/', {controller: BurningCtrl, templateUrl:'/main_partial'}).
when('/config', {controller: ConfigCtrl, templateUrl:'/config_partial'}). when('/config', {controller: ConfigCtrl, templateUrl:'/config_partial'}).
when('/help', {controller: ConfigCtrl, templateUrl:'/help_partial'}). when('/help', {controller: ConfigCtrl, templateUrl:'/help_partial'}).
when('/stocked', {controller: StockedCtrl, templateUrl:'/stocked_partial'}).
otherwise({redirectTo:'/'}); otherwise({redirectTo:'/'});
}); });

@ -0,0 +1,388 @@
// class TimeLogger {
// constructor() {
// this.logs = [];
// this.unlabelled_id = 0;
// this.start = this.previous = Date.now();
// this.log('start');
// }
// log(label) {
// var now = Date.now();
// var log = {
// 'when': now,
// 'label': label,
// 'elapsed': now - this.previous,
// 'total': now - this.start
// };
// this.logs.push(log);
// this.previous = now;
// return log;
// }
// }
// const timelogger = new TimeLogger;
// console.log(timelogger.logs[0]);
//
// console.log(timelogger.log("post-data"));
// Settings
function StockedSetting(name, charredSettingData) {
this.isSubsetting = false;
if(result = name.match(/(.*) setting/i)) {
this.name = result[1];
} else if(result = name.match(/(.*) subsetting/i)) {
this.name = result[1];
this.isSubsetting = true;
} else {
this.name = name;
}
this.lifepaths = [];
for (let name in charredSettingData) {
this.lifepaths.push(new StockedLifePath(name, charredSettingData[name]));
}
}
StockedSetting.prototype.addLifepath = function() {
this.lifepaths.push(new StockedLifePath(this.newLifepathName, {}));
this.newLifepathName = "";
}
StockedSetting.prototype.removeLifepath = function(index) {
this.lifepaths.splice(index, 1);
}
StockedSetting.prototype.toCharred = function() {
// let name = this.name + (this.isSubsetting ? " Subsetting" : " Setting";
let charred = {};
for(let lp of this.lifepaths) {
charred[lp.name] = lp.toCharred();
}
// common traits
// stride
return charred;
}
function StockedLifePath(name, charredPathData) {
if(charredPathData) {
this.name = name;
this.time = charredPathData.time;
this.res = charredPathData.res;
this.requires = charredPathData.requires;
this.restrict = charredPathData.restrict;
this.stat = new StockedLifePathStats(charredPathData.stat);
this.skills = new StockedLifePathSkills(charredPathData.skills);
this.traits = new StockedLifePathTraits(charredPathData.traits);
} else {
this.stat = new StockedLifePathStats();
this.skills = new StockedLifePathSkills();
this.traits = new StockedLifePathTraits();
}
this.leads_dict = {};
if(charredPathData.leads) {
for(let lead of charredPathData.leads) { this.leads_dict[lead] = true; }
}
this.leads = () => Object.keys(this.leads_dict).filter(l => this.leads_dict[l]);
}
StockedLifePath.prototype.toCharred = function() {
let charred = {};
charred.time = this.time;
charred.res = this.res;
charred.requires = this.requires;
charred.restrict = this.restrict;
charred.stat = this.stat.toCharred();
charred.skills = this.skills.toCharred();
charred.traits = this.traits.toCharred();
charred.leads = this.leads();
return charred;
}
function StockedLifePathStats(charredStatData) {
this.P = 0;
this.M = 0;
this.PM = 0;
if(charredStatData) {
for (let stat of charredStatData) {
if(stat[1].toUpperCase() == 'P') {
this.P += stat[0];
}
else if(stat[1].toUpperCase() == 'M') {
this.M += stat[0];
}
else if(stat[1].toUpperCase() == 'PM' || stat[0].toUpperCase() == 'MP') {
this.PM += stat[0];
}
}
}
}
StockedLifePathStats.prototype.toString = function() {
let strs = [];
if (this.P) { strs.push("+" + this.P + "P"); }
if (this.M) { strs.push("+" + this.M + "M"); }
if (this.PM) { strs.push("+" + this.PM + "P/M"); }
return strs.join(",");
};
StockedLifePathStats.prototype.toCharred = function() {
let charred = [];
if(this.P > 0) charred.push([this.P, "p"]);
if(this.M > 0) charred.push([this.M, "m"]);
if(this.PM > 0) charred.push([this.PM, "pm"]);
return charred;
}
function StockedLifePathSkills(charredLpSkillData) {
this.lpPoints = 0;
this.generalPoints = 0;
this.lpSkills = [];
if (charredLpSkillData) {
for (let skill of charredLpSkillData) {
if(skill.length == 1) {
this.lpPoints = skill[0];
}
if(skill.length >= 2) {
if(skill[1].toLowerCase() == "general") {
this.generalPoints += skill[0];
} else {
this.lpPoints += skill[0];
this.lpSkills = this.lpSkills.concat(skill.slice(1));
}
}
}
}
}
StockedLifePathSkills.prototype.removeSkill = function(index) {
this.lpSkills.splice(index, 1);
}
StockedLifePathSkills.prototype.addSkill = function(skill) {
this.lpSkills.push(skill);
}
StockedLifePathSkills.prototype.toString = function() {
let strs = [];
if (this.lpPoints) { strs.push(StockedUtil.pts(this.lpPoints, this.lpSkills)); }
if (this.generalPoints) { strs.push(StockedUtil.pts(this.generalPoints, ["General"])); }
return strs.join("; ");
};
StockedLifePathSkills.prototype.toCharred = function() {
let charred = [];
if(this.lpSkills.length > 0 || this.lpPoints > 0){
charred.push([this.lpPoints].concat(this.lpSkills));
}
if(this.generalPoints > 0)
charred.push([this.generalPoints, "General"]);
return charred;
}
function StockedLifePathTraits(charredTraitData) {
this.lpTraits = [];
if(charredTraitData) {
this.points = charredTraitData[0];
this.lpTraits = charredTraitData.slice(1);
}
}
StockedLifePathTraits.prototype.removeTrait = function(index) {
this.lpTraits.splice(index, 1);
}
StockedLifePathTraits.prototype.addTrait = function(skill) {
this.lpTraits.push(skill);
}
StockedLifePathTraits.prototype.toString = function() {
return StockedUtil.pts(this.points, this.lpTraits);
}
StockedLifePathTraits.prototype.toCharred = function() {
return [this.points].concat(this.lpTraits);
}
// Skills
function StockedSkill(name, charredSkillData) {
this.name = name;
this.magic = charredSkillData.magic ? true : false;
this.roots = {
Perception: false,
Will: false,
Forte: false,
Power: false,
Agility: false,
Speed: false,
};
if(charredSkillData.roots){
charredSkillData.roots.map(rootName => this.roots[rootName] = true);
}
this.stockSpecific = "TODO";
}
// Traits
function StockedTrait(name, charredTraitData) {
this.name = name;
this.cost = charredTraitData.cost;
this.type = charredTraitData.type;
this.desc = charredTraitData.desc;
// No logic for these yet
this.bonus = charredTraitData.bonus;
this.restrict = charredTraitData.restrict;
}
//Ctrl
var testscope;
function StockedCtrl($scope, $http, burningData) {
$scope.to_id = function(input) { return input.replaceAll(/\W/g, '_'); };
$scope.StockedUtil = StockedUtil;
$scope.TRAIT_TYPES = ["character", "die", "call_on"];
$scope.settings = [];
/* testing */
testscope = $scope;
$scope.general = {
'Name': 'Foo',
'Stride': 7,
'CommonTraits': [],
'selectedTrait': ''
};
// $scope.parseStock = function (stockData){
// let settings = [];
// for (let name in stockData) {
// settings.push(new StockedSetting(name, stockData[name]));
// }
// return settings;
// };
// $scope.parseSkills = function (skillsData){
// let skills = [];
// for (let name in skillsData) {
// skills.push(new StockedSkill(name, skillsData[name]));
// }
// return skills;
// };
// $scope.parseTraits = function (traitsData){
// let traits = [];
// for (let name in traitsData) {
// traits.push(new StockedTrait(name, traitsData[name]));
// }
// return traits;
// };
/* end testing */
/* Input/Output */
/* end Input/Output */
$scope.addSetting = function (){
this.settings.push(new StockedSetting(this.newSettingName, {}));
this.newSettingName = "";
};
$scope.removeSetting = function (index) {
this.settings.splice(index, 1);
};
$scope.addSkill = function (){
this.skills.push(new StockedSkill("New", {}));
};
$scope.removeSkill = function (index) {
this.skills.splice(index, 1);
};
$scope.addTrait = function (){
this.traits.push(new StockedTrait("New", {}));
};
$scope.removeTrait = function (index) {
this.traits.splice(index, 1);
};
$scope.collapseBody = function(data_target, $event) {
$(data_target).collapse("toggle");
if($event) { $event.stopPropagation(); }
};
$scope.editField = function($event, edit) {
$($event.target).toggleClass("not-editing");
};
burningData.registerOnAllDatasetsLoaded(function() {
onLifepathsLoad_Stocked($scope, burningData);
});
$scope.editLeads = function($event) {
let container = $($event.target).closest('.path-leads');
container.find(".path-leads-read").toggle(false);
container.find(".path-leads-write").toggle(true);
};
$scope.readLeads = function($event) {
let container = $($event.target).closest('.path-leads');
container.find(".path-leads-read").toggle(true);
container.find(".path-leads-write").toggle(false);
};
$scope.stocked_loadCharredModel = function() {
file = document.getElementById("stocked_charred_file");
file.files[0].text().then((text) => loadCharredModel(this, JSON.parse(text)));
};
$scope.stocked_downLoadCharredModel = function() {
model = serializeToCharredModel(this);
$http.post("/stocked_download", model).
success(function(data,status,headers,config){
console.log("huzzah, converting stocked model to charred succeeded. File URL: " + data);
var frame = document.getElementById("downloadframe");
if ( frame ){
frame.src = data;
}
}).
error(function(data,status,headers,config){
console.log("boo, converting stocked model to charred failed. File URL: " + data);
$scope.addAlert('tools', "converting stocked model to charred failed: " + data);
});
}
}
// Accepts an object, JSON text should be parsed first
function loadCharredModel($scope, model) {
let settings = [];
for (let name in model) {
settings.push(new StockedSetting(name, model[name]));
}
$scope.settings = settings;
$scope.$apply();
}
function serializeToCharredModel($scope) {
let model = {};
model.settings = {};
model.Name = $scope.general.Name;
$scope.settings.forEach((s) =>
model.settings[s.name + (s.isSubsetting ? " Subsetting" : " Setting")] = s.toCharred())
return JSON.stringify(model);
}
function onLifepathsLoad_Stocked($scope, burningData) {
// $scope.settings = $scope.parseStock(test_data);
// $scope.skills = $scope.parseSkills(test_skills_data);
// $scope.traits = $scope.parseTraits(test_traits_data);
$scope.charredTraits = Object.keys(burningData.traits);
$scope.charredSkills = Object.keys(burningData.skills);
}
var StockedUtil = {
"pluralize": function(num, thing, withE, nospace) {
str = "";
str += num;
if (!nospace) { str += ' '; }
str += thing;
if(num != 1) {
if (withE) { str += 'e'; }
str += 's';
}
return str;
},
"pts": function(num, entries) {
let str = StockedUtil.pluralize(num, "pt") + ": ";
if(Array.isArray(entries) && entries.length > 0) { str += entries.join(", "); }
else { str += "—" }
return str;
}//, "filter$properties": (key, value) => (key.startsWith('$') ? undefined : value)
};

@ -0,0 +1,42 @@
function EditableInputController($scope, $element, $attrs) {
console.log($scope);
console.log($element);
var ctrl = this;
ctrl.isEditing = false;
ctrl.handleModeChange = function() {
if (ctrl.isEditing) {
ctrl.onUpdate({value: ctrl.modelValue});
ctrl.modelValueCopy = ctrl.modelValue;
}
ctrl.isEditing = !ctrl.isEditing;
};
ctrl.reset = function() {
ctrl.modelValue = ctrl.modelValueCopy;
};
ctrl.$onInit = function() {
// Make a copy of the initial value to be able to reset it later
ctrl.modelValueCopy = ctrl.modelValue;
// Set a default inputType
if (!ctrl.inputType) {
ctrl.inputType = 'text';
}
};
}
export function register(module) {
console.log(module);
module.component('editableInput', {
templateUrl: '/stocked/editableInput_partial',
controller: EditableInputController,
bindings: {
modelValue: '<',
inputID: '<',
inputType: '@?',
onUpdate: '&'
}
});
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,23 @@
{
"Foo Setting": {
"Born Foo": {
"time": 1,
"res": 5,
"stat": [
[1, "pm"]
],
"leads": [
"Bar"
],
"key_leads": [
"Bar Setting"
],
"skills": [
[3, "General"]
],
"traits": [1],
"common_traits": []
}
},
"Bar Setting": {}
}

@ -3,6 +3,7 @@
<head> <head>
<link href='/css/bootstrap.min.css' rel='stylesheet' type='text/css'> <link href='/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
<link href='/css/style.css' rel='stylesheet' type='text/css'> <link href='/css/style.css' rel='stylesheet' type='text/css'>
<link href='/css/stocked.css' rel='stylesheet' type='text/css'>
<script src='/js/server_settings.js'></script> <script src='/js/server_settings.js'></script>
<script src='/js/angular.min.js'></script> <script src='/js/angular.min.js'></script>
<script src='/js/angular-resource.js'></script> <script src='/js/angular-resource.js'></script>
@ -14,6 +15,7 @@
<script src='/js/burning-service.js'></script> <script src='/js/burning-service.js'></script>
<script src='/js/burning-modal.js'></script> <script src='/js/burning-modal.js'></script>
<script src='/js/burning-serialize.js'></script> <script src='/js/burning-serialize.js'></script>
<script src='/js/stocked.js'></script>
<script src='/js/burning.js'></script> <script src='/js/burning.js'></script>
<title>Charred - The Burning Wheel Gold Character Burner</title> <title>Charred - The Burning Wheel Gold Character Burner</title>
</head> </head>
@ -34,6 +36,9 @@
<li> <li>
<a target='_blank' href='https://forums.burningwheel.com/t/charred-an-unofficial-online-bwg-character-burner/14299'>Forum Thread</a> <a target='_blank' href='https://forums.burningwheel.com/t/charred-an-unofficial-online-bwg-character-burner/14299'>Forum Thread</a>
</li> </li>
<li>
<a href='#/stocked'>Stocked</a>
</li>
</ul> </ul>
</nav> </nav>
<div ng-view=''></div> <div ng-view=''></div>

@ -0,0 +1,377 @@
<div class='container' ng-controller='StockedCtrl'>
<h1 id='intro'>Stocked</h1>
<div class='well'>
Stocked (Stock&middot;ed)
<br>
<br>
<ol>
<li><i>adj.</i> Furnished with more than enough. <i>(ref https://www.vocabulary.com/dictionary/stocked)</i></li>
<li><i>n.</i> A portmanteau of <i>stock</i> and <i>editor</i>.</li>
</ol>
</div>
<div class='panel panel-default'>
<div class='panel-heading' >
<h4 class='panel-title'>
<a href='' ng-click='collapseBody("#collapse_tools")'>▸</a>
Tools
</h4>
</div>
<div class='panel-collapse collapse in' id='collapse_tools'>
<div class='panel-body'>
<div class='alert alert-danger alert-dismissable' ng-repeat="warn in alertsOfType('tools', 'warn')">
<button aria-hidden='true' class='close' ng-click="removeAlert('tools',warn)" type='button'>&times;</button>
{{warn}}
</div>
<div class='alert alert-success alert-dismissable' ng-repeat="warn in alertsOfType('tools', 'succ')">
<button aria-hidden='true' class='close' ng-click="removeAlert('tools',warn)" type='button'>&times;</button>
{{warn}}
</div>
<div class='container'>
<div class='row'>
<div class='col-md-3'>
<input type="file" id="stocked_charred_file"/>
<a href='' ng-click='stocked_loadCharredModel()'>
TODO: Load charred stock model
</a>
</div>
<div class='col-md-3'>
<a href='' ng-click='stocked_downloadCharredModel()'>
TODO: Download charred stock model
</a>
</div>
<div class='col-md-3'>
<a href='' ng-click='stocked_generateMarkdown()'>
TODO: Generate Markdown
</a>
</div>
</div>
</div>
</div>
</div>
<div class='panel-heading'>
<h4 class='panel-title'>
<a href='' ng-click='collapseBody("#collapse_general")'>▸</a>
General
</h4>
</div>
<div class='panel-collapse collapse in' id='collapse_general'>
<div class='panel-body'>
<label for='stock-name'>Name:</label>
<input class='form-control input-lg not-editing editable-name' name='stock-name' id='stock-name'
ng-model="general.Name" ng-click="$event.stopPropagation()"
ng-focus='editField($event, true)' ng-blur='editField($event, false)' />
<label for='stock-stride'>Stride:</label>
<input type="number" class='not-editing editable-num' name='stock-stride' id='stock-stride'
ng-model="general.Stride" ng-click="$event.stopPropagation()"
ng-focus='editField($event, true)' ng-blur='editField($event, false)' />
<br />
<div>
<span>Common traits: {{general.CommonTraits.join(", ")}}</span>
<select class='form-control' ng-model='general.selectedTrait' ng-options='t for t in charredTraits'></select>
</div>
<div>
<a href='' ng-click='general.CommonTraits.push(general.selectedTrait)'>Add trait</a>
</div>
<div>
<a href='' ng-click=''>TODO: Add new trait</a>
</div>
<br />
TODO: other things in General section?
</div>
</div>
<div class='panel-heading'>
<h4 class='panel-title'>
<a href='' ng-click='collapseBody("#collapse_settings")'>▸</a>
Settings
</h4>
</div>
<div class='panel-collapse collapse in' id='collapse_settings'>
<div class='panel-body'>
<span class="note-label">Note:</span>
<span class="note-content">
Settings will have " Setting" (or " Subsetting" for subsettings)
appended to the name in the generated charred model, which is how charred will display them.
</span>
<br />
<a ng-click='collapseBody(".collapse_all_settings", $event)' href=''>
collapse/expand all settings
</a>
<div class='list-group'>
<div ng-repeat="setting in settings" class='list-group-item'>
<div class='panel-heading'>
<a href='' class="panel-title" ng-click='collapseBody("#collapse_" + to_id(setting.name))'>▸</a>
<input class='form-control input-lg not-editing editable-name'
ng-model="setting.name" ng-click="$event.stopPropagation()"
ng-focus='editField($event, true)' ng-blur='editField($event, false)' />
<span class="panel-title" style="font-weight: bold;">
<input type="checkbox" ng-model="setting.isSubsetting" /> Subsetting?
</span>
<a href='' ng-click='removeSetting($index)'>[X]</a>
</div>
<div class='panel-collapse collapse in collapse_all_settings' id='collapse_{{to_id(setting.name)}}'>
<div class='panel-body'>
<div class="panel">
<div class="panel-body">
<span class="note-label">Note:</span>
<span class="note-content">
Prefix a lifepath's name with "Born " to have charred consider it a born lifepath;
i.e. selectable if and only if it is the first lifepath.
</span>
<br />
<a ng-click='collapseBody(".collapse_all_"+to_id(setting.name), $event)' href=''>
collapse/expand all paths in setting
</a>
<div class='container-fluid'>
<div class='row'>
<div class='h4 col-md-3'>Lifepath</div>
<div class='h4 col-md-1'>Time</div>
<div class='h4 col-md-1'>Res</div>
<div class='h4 col-md-3'>Stat</div>
<div class='h4 col-md-4'>Leads</div>
</div>
</div>
</div>
<div ng-repeat="path in setting.lifepaths">
<div class='panel-heading'>
<div class='container-fluid'>
<div class='row'>
<div class="col-md-3">
<a href='' class="panel-title" ng-click='collapseBody("#collapse_" + to_id(path.name))'>▸</a>
<input ng-model="path.name"
class='form-control input-lg not-editing editable-name'
ng-click="$event.stopPropagation()"
ng-focus='editField($event, true)' ng-blur='editField($event, false)' />
<a href='' ng-click='setting.removeLifepath($index)'>[X]</a>
</div>
<div class="col-md-1">
<input type="number" ng-model="path.time"
class='not-editing editable-num'
ng-click="$event.stopPropagation()"
ng-focus='editField($event, true)' ng-blur='editField($event, false)' />
<span>yrs</span>
</div>
<div class="col-md-1">
<input type="number" ng-model="path.res"
class='not-editing editable-num'
ng-click="$event.stopPropagation()"
ng-focus='editField($event, true)' ng-blur='editField($event, false)' />
</div>
<div class="col-md-3">
<label for="{{to_id(path.name)}}_M">M: </label>
<input type="number" ng-model="path.stat.M" id="{{to_id(path.name)}}_M"
class='not-editing editable-num'
ng-click="$event.stopPropagation()"
ng-focus='editField($event, true)' ng-blur='editField($event, false)' />
<label for="{{to_id(path.name)}}_P">P: </label>
<input type="number" ng-model="path.stat.P" id="{{to_id(path.name)}}_M"
class='not-editing editable-num'
ng-click="$event.stopPropagation()"
ng-focus='editField($event, true)' ng-blur='editField($event, false)' />
<label for="{{to_id(path.name)}}_PM">P/M: </label>
<input type="number" ng-model="path.stat.PM" id="{{to_id(path.name)}}_M"
class='not-editing editable-num'
ng-click="$event.stopPropagation()"
ng-focus='editField($event, true)' ng-blur='editField($event, false)' />
</div>
<div class='h5 col-md-4 path-leads'>
<div class="path-leads-read">
<span>Leads: </span><i>{{path.leads().join(", ")}}</i>
<a href='' ng-click='editLeads($event)'>Edit</a>
</div>
<div class="path-leads-write" hidden="hidden">
<div ng-repeat="setting in settings">
<input type="checkbox" id='{{to_id(path.name)}}-to-{{to_id(setting.name)}}'
ng-model='path.leads_dict[setting.name]' value='{{to_id(setting.name)}}' />
<label for='{{to_id(path.name)}}-to-{{to_id(setting.name)}}'>
{{setting.name}}
</label>
</div>
<a href='' ng-click='readLeads($event)'>Done</a>
</div>
</div>
</div>
</div>
</div>
<div class='panel-collapse collapse in collapse_all_{{to_id(setting.name)}}' id='collapse_{{to_id(path.name)}}'>
<div class='panel-body'>
<span ng-if='$first'>TODO: Born/common traits</span>
<div>
<b><i>Skills:</i></b>
<span ng-if='path.skills.lpSkills.length > 0' ng-click='editPoints($event)'>
{{StockedUtil.pluralize(path.skills.lpPoints, "pt")}}:
</span>
<div style="display: inline;" ng-repeat='skill in path.skills.lpSkills track by $index'>
{{skill}}<a href='' ng-click='path.skills.removeSkill($index)'>[X]</a><!--
--><span ng-if='!$last'>,</span></div><!--
--><span ng-if='path.skills.lpSkills.length > 0 && path.skills.generalPoints > 0'>;</span>
<span ng-if='path.skills.generalPoints > 0'>{{StockedUtil.pluralize(path.skills.generalPoints, "pt")}}: General</span>
</div>
<div>
<b><i>Traits:</i></b>
<span ng-if='path.traits.lpTraits.length == 0'>—</span>
<span ng-if='path.traits.lpTraits.length > 0' ng-click='editPoints($event)'>
{{StockedUtil.pluralize(path.traits.points, "pt")}}:
</span>
<div style="display: inline;" ng-repeat='trait in path.traits.lpTraits track by $index'>
{{trait}}<a href='' ng-click='path.traits.removeTrait($index)'>[X]</a><!--
--><span ng-if='!$last'>,</span></div>
</div>
<div ng-if="path.requires" class="horizontal-input-pair">
<label for="{{to_id(path.name)}}-requires"><b><i>Requires: </i></b></label>
<input ng-model="path.requires" id="{{to_id(path.name)}}-requires"
class='form-control not-editing editable-line'
ng-click="$event.stopPropagation()"
ng-focus='editField($event, true)' ng-blur='editField($event, false)' />
</div>
<div ng-if="path.restrict" class="horizontal-input-pair">
<label for="{{to_id(path.name)}}-restrict"><b><i>Restrictions: </i></b></label>
<input ng-model="path.restrict" id="{{to_id(path.name)}}-restrict"
class='form-control not-editing editable-line'
ng-click="$event.stopPropagation()"
ng-focus='editField($event, true)' ng-blur='editField($event, false)' />
</div>
<div class="add-skills-traits-container" class="container">
<div class='row'>
<div class='col-md-3'>
<select class='form-control' ng-model='path.selectedSkill' ng-options='s for s in charredSkills'></select>
</div>
<div class='col-md-1'>
<a href='' ng-click='path.skills.addSkill(path.selectedSkill)'>Add skill</a>
</div>
<div class='col-md-2'>
<a href='' ng-click=''>TODO: Add new skill</a>
</div>
<div class='col-md-3'>
<select class='form-control' ng-model='path.selectedTrait' ng-options='t for t in charredTraits'></select>
</div>
<div class='col-md-1'>
<a href='' ng-click='path.traits.addTrait(path.selectedTrait)'>Add trait</a>
</div>
<div class='col-md-2'>
<a href='' ng-click=''>TODO: Add new trait</a>
</div>
</div>
</div>
<span>WISHLIST: requires expression</span>
</div>
</div>
</div>
<div class='panel panel-default'>
<input class='form-control input-lg not-editing editable-name'
name='new-lifepath-name' id='new-lifepath-name'
ng-model="setting.newLifepathName" ng-click="$event.stopPropagation()"
ng-focus='editField($event, true)' ng-blur='editField($event, false)' />
<a href='' ng-click='setting.addLifepath()'>Add new lifepath</a>
</div>
</div>
</div>
</div>
</div>
<div class='panel panel-default'>
<input class='form-control input-lg not-editing editable-name'
name='new-setting-name' id='new-setting-name'
ng-model="newSettingName" ng-click="$event.stopPropagation()"
ng-focus='editField($event, true)' ng-blur='editField($event, false)' />
<a href='' ng-click='addSetting()'>Add new setting</a>
</div>
</div>
</div>
</div>
<div class='panel-heading'>
<h4 class='panel-title'>
<a href='' ng-click='collapseBody("#collapse_skills")'>▸</a>
Skills
</h4>
</div>
<div class='panel-collapse collapse in' id='collapse_skills'>
<div class='panel-body'>
<div class='container-fluid'>
<div class='row'>
<div class='h4 col-md-3'>Skill</div>
<div class='h4 col-md-1'>Sorcerous?</div>
<div class='h4 col-md-8'>Roots</div>
<%# <div class='h4 col-md-2'>Stock specific?</div> %>
</div>
</div>
<div ng-repeat='skill in skills' class='row' ng-class-even="'skill-even'" ng-class-odd="'skill-odd'">
<div class='h4 col-md-3'>
<input ng-model="skill.name"
class='form-control input-lg not-editing editable-name'
ng-click="$event.stopPropagation()"
ng-focus='editField($event, true)' ng-blur='editField($event, false)' />
</div>
<div class='h4 col-md-1'><input type="checkbox" ng-model='skill.magic'></div>
<div class='h4 col-md-8'>
<div class="skill-roots-write">
<span ng-repeat="(root,is) in skill.roots">
<input type="checkbox" id='{{to_id(skill.name)-to_id(root)}}' ng-model='is'/>
<label for='{{to_id(skill.name)-to_id(root)}}'> {{root}} </label>
</div>
</div>
</div>
</div>
<div class='row'>
<div class='h4 col-md-12'><a href='' ng-click='addSkill()'>Add new skill</a></div>
</div>
</div>
</div>
<div class='panel-heading'>
<h4 class='panel-title'>
<a href='' ng-click='collapseBody("#collapse_traits")'>▸</a>
Traits
</h4>
</div>
<div class='panel-collapse collapse in' id='collapse_traits'>
<div class='panel-body'>
<table class="traits">
<tr>
<th>Trait</th>
<th>Cost</th>
<th>Type</th>
<%# <div class='h4 col-md-2'>Bonus</div> %>
<%# <div class='h4 col-md-2'>Restrictions</div> %>
</tr>
<%# </div> %>
<tr ng-repeat-start='trait in traits' ng-class-even="'trait-even'" ng-class-odd="'trait-odd'">
<td>
<input ng-model="trait.name"
class='form-control input-lg not-editing editable-name'
ng-click="$event.stopPropagation()"
ng-focus='editField($event, true)' ng-blur='editField($event, false)' />
</td>
<td>
<input type="number" class='not-editing editable-num'
ng-model="trait.cost" ng-click="$event.stopPropagation()"
ng-focus='editField($event, true)' ng-blur='editField($event, false)' />
</td>
<td>
<select class='form-control' ng-model='trait.type' ng-options='t for t in TRAIT_TYPES'></select>
</td>
<td><a href='' ng-click='removeTrait($index)'>[X]</a></td>
</tr>
<%# <div class='h4 col-md-2'>WISHLIST</div> %>
<%# <div class='h4 col-md-2'>WISHLIST</div> %>
<tr ng-repeat-end ng-class-even="'trait-even'" ng-class-odd="'trait-odd'">
<td colspan="99"><textarea class='trait-desc' ng-model='trait.desc'></textarea></td>
</tr>
</table>
<div class='row'>
<div class='h4 col-md-12'><a href='' ng-click='addTrait()'>Add new trait</a></div>
</div>
</div>
</div>
</div>

@ -0,0 +1,6 @@
<span ng-if='$ctrl.label' for='{{$ctrl.inputID}}'>{{$ctrl.label}}</span>
<span ng-switch='$ctrl.isEditing'>
<input ng-switch-when="true" ng-model='$ctrl.modelValue' ng-blur='$ctrl.handleModeChange()'
id='{{$ctrl.inputID}}' type='{{$ctrl.inputType}}' class="editable-input editing">
<span ng-switch-default class="editable-input">{{$ctrl.modelValue}}</span>
</span>
Loading…
Cancel
Save