<div ng-controller="MainCtrl" ng-cloak="" class="" ng-app="CharApp">
    <md-toolbar>
          <div class="md-toolbar-tools">
              Sheet: {{sheetConfig.name}}
              <span flex></span>
              <md-button ng-click="newSheet($event)" aria-label="ad" class="icon-button">
                  <i class="fa fa-plus-square-o fa-2x" aria-hidden="true"></i>
              </md-button>
              <md-button ng-click="openSheet($event)" aria-label="ad" class="icon-button">
                  <i class="fa fa-folder-open-o fa-2x" aria-hidden="true"></i>
              </md-button>
         </div> 
    </md-toolbar>
    <div class="page-container" layout="row" layout-wrap>
        <md-card flex="noshrink" class="card-primary-stats" ng-controller="PrimaryStatsCtrl">
            <md-card-title>
                <md-card-title-text>
                    <span class="md-headline" layout="row" layout-align="center center">
                        Primary Stats<span flex></span>
                        <md-switch ng-model="primaryStats.gameview" aria-label="gameview">
                            <label style="font-size:.8em;">{{primaryStats.gameview ? 'Gameview' : 'Editview'}}</label>
                        </md-switch>
                    </span>
                    <span class="md-subhead" layout="column" ng-if="!primaryStats.gameview">
                        <div layout="row">
                            <md-input-container flex>
                                <label>Stat Name</label>
                                <input ng-model="newStat.name">
                            </md-input-container>
                            <md-input-container flex>
                                <label>Base Value</label>
                                <input ng-model="newStat.baseValue" type="number">
                            </md-input-container>
                            <md-input-container flex>
                                <label>Modifier</label>
                                <input min="1" ng-model="newStat.modifier" type="number">
                            </md-input-container>
                            <md-input-container>
                                <md-button aria-label="d" class="md-primary icon-button" ng-click="addStat(newStat)"><i class="fa fa-plus-square-o fa-2x" aria-hidden="true"></i></md-button>
                            </md-input-container>
                        </div> 
                        <div ng-repeat="(statName, stat) in sheetConfig.primaryStats" layout="row" ng-init="calcStat(stat)">
                            <md-input-container flex>
                                <label class="stat-text">{{statName}}</label>
                                <input ng-model="stat.currentValue" type="number" ng-change="calcStat(stat)">
                            </md-input-container>
                            <md-input-container flex="15">
                                <label class="stat-text">Base</label>
                                <input ng-model="stat.baseValue" type="number" ng-change="calcStat(stat)">
                            </md-input-container>
                            <md-input-container flex>
                                <label class="stat-text">Modifier</label>
                                <input disabled ng-model="stat.modValue">
                                <div class="hint">(Total-Base) / Modifier</div>
                            </md-input-container>
                            <md-input-container>
                                <md-button aria-label="s" class="md-button md-primary icon-button" ng-click="removeState(newStat)"><i class="fa fa-pencil-square-o fa-2x"></i></md-button>
                                <md-button aria-label="s" class="md-button md-primary icon-button" ng-click="removeStat(stat)"><i class="fa fa-trash fa-2x"></i></md-button>
                            </md-input-container>
                        </div> 
                    </span>
                    <span class="md-subhead" layout="column" ng-if="primaryStats.gameview">
                        <div class="primary-stats-gameview" ng-repeat="(statName, stat) in sheetConfig.primaryStats" layout="row" layout-align="start center" ng-init="calcStat(stat)">
                            <div layout="row" flex>
                                <md-input-container>
                                    <div class="md-title" style="height: 2em">{{statName}}: &nbsp;</div>
                                </md-input-container>
                                <md-input-container>
                                    <label></label>
                                    <input ng-model="stat.currentValue" type="number" ng-change="calcStat(stat)">
                                </md-input-container>
                            </div>
                            <div layout="row" flex>
                                <md-input-container>
                                    <div class="md-title" style="height: 2em">Modifier: &nbsp;</div>
                                </md-input-container>
                                <md-input-container>
                                    <label></label>
                                    <input disabled ng-model="stat.modValue">
                                </md-input-container>
                            </div>
                        </div> 
                    </span>
                </md-card-title-text>
            </md-card-title>
            <md-card-actions layout="row" layout-align="end center">
            </md-card-actions>
        </md-card>
        
        <md-card flex="noshrink" class="card-secondary-stats" ng-controller="SecondaryStatsCtrl">
            <md-card-title>
                <md-card-title-text>
                    <span class="md-headline" layout="row" layout-align="center center">
                        Derived Stats<span flex></span>
                        <md-switch ng-model="secondaryStats.gameview" aria-label="gameview">
                            <label style="font-size:.8em;">{{secondaryStats.gameview ? 'Gameview' : 'Editview'}}</label>
                        </md-switch>
                    </span> 
                    <span class="md-subhead" layout="column" ng-if="!secondaryStats.gameview">
                        <div layout="row"> 
                            <md-input-container flex>
                                <label>Stat Name</label>
                                <input ng-model="newStat.name">
                            </md-input-container>
                            <md-input-container flex>
                                <label>Base Value</label>
                                <input ng-model="newStat.baseValue" type="number">
                            </md-input-container>
                            <md-input-container>
                                <md-button aria-label="d" class="md-primary icon-button" ng-click="addStat(newStat)">
                                    <i class="fa fa-plus-square-o fa-2x" aria-hidden="true"></i>
                                </md-button>
                            </md-input-container>
                        </div> 
                        <div ng-repeat="(statName, stat) in sheetConfig.secondaryStats" layout="row" ng-init="calcStat(stat)">
                            <md-input-container flex>
                                <label class="stat-text">{{statName}}</label>
                                <input ng-model="stat.currentValue" type="number" ng-change="calcStat(stat)">
                            </md-input-container>
                            <md-input-container flex="20">
                                <label class="stat-text">Base</label>
                                <input ng-model="stat.baseValue" type="number" ng-change="calcStat(stat)">
                            </md-input-container> 
                            <md-input-container>
                                <md-button aria-label="s" class="md-button md-primary icon-button" ng-click="removeState(newStat)"><i class="fa fa-pencil-square-o fa-2x"></i></md-button>
                                <md-button aria-label="s" class="md-button md-primary icon-button" ng-click="removeStat(stat)"><i class="fa fa-trash fa-2x"></i></md-button>
                            </md-input-container>
                        </div> 
                    </span>
                    <span class="md-subhead" layout="column" ng-if="secondaryStats.gameview">
                        <div class="primary-stats-gameview" ng-repeat="(statName, stat) in sheetConfig.secondaryStats" layout="row" layout-align="start center" ng-init="calcStat(stat)">
                            <md-input-container>
                                <div class="md-title" style="height: 2em">{{statName}}: &nbsp;</div>
                            </md-input-container>
                            <md-input-container flex>
                                <label></label>
                                <input ng-model="stat.currentValue" type="number" ng-change="calcStat(stat)">
                            </md-input-container>
                        </div> 
                    </span>
                </md-card-title-text>
            </md-card-title>
            <md-card-actions layout="row" layout-align="end center">
            </md-card-actions>
        </md-card>
    </div>
    
    
    
    
    <script type="text/ng-template" id="new-sheet.html">
        <md-dialog aria-label="New Sheet" ng-cloak class="diag">
            <md-toolbar class="md-primary">
                <div class="md-toolbar-tools">
                    <h2>New Sheet</h2>
                    <span flex></span>
                    <md-button class=" icon-button" ng-click="cancel()">
                        X
                    </md-button>
                </div>
            </md-toolbar>
            <md-dialog-content layout-padding>
                <div layout="column" class="diag-content"> 
                    <md-input-container flex="100">
                        <label>Sheet Name</label>
                        <input ng-model="sheetConfig.name">
                    </md-input-container>
                    <div flex class=""></div>
                    <div layout="row">
                        <div flex></div>
                        <md-button ng-click="save()">Create</md-button>
                    </div>
                </div>
            </md-dialog-content>
        </md-dialog>
    </script>
    <script type="text/ng-template" id="open-sheet.html">
        <md-dialog aria-label="New Sheet" ng-cloak class="diag">
            <md-toolbar>
                <div class="md-toolbar-tools">
                    <h2>Open Sheet</h2>
                    <span flex></span>
                    <md-button class=" icon-button" ng-click="cancel()">
                        X
                    </md-button>
                </div>
            </md-toolbar>
            <md-dialog-content layout-padding>
                <div layout="column" class="diag-content"> 
                    <div ng-repeat="sheet in sheets" layout="row">
                        <md-input-container flex>
                            <label>Sheet Name</label>
                            <input ng-model="sheet.name">
                        </md-input-container>
                        <md-input-container>
                            <md-button class="md-primary md-raised" ng-click="openSheet(sheet)">Open</md-button>
                        </md-input-container>
                        <md-input-container>
                            <md-button aria-label="d" class="md-primary md-raised icon-button" ng-click="deleteSheet(sheet)"><i class="fa fa-trash fa-2x"></i></md-button>
                        </md-input-container>
                    </div>
                </div>
            </md-dialog-content>
        </md-dialog>
    </script>
</div>
.carddemoBasicUsage .card-media {
  background-color: #999999;
}

.card-primary-stats {
    width:500px;
}
.card-secondary-stats {
    width:300px;
}
.hint {
    position: absolute;
    left: 2px;
    right: auto;
    bottom: 7px;
    font-size: 12px;
    line-height: 14px;
    transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2);
    color: grey;
}
div {
    transition: all linear 200ms;
}
.page-container {
    margin: auto;
    max-width: 70%;
}
@media (max-width: 1400px) {
    .page-container {
        max-width: 80%;
    }
}
@media (max-width: 1200px) {
    .page-container {
        margin: auto;
        max-width: 90%;
    }
}
@media (max-width: 650px) {
    .page-container {
        margin: auto;
        max-width: 100%;
    }
}

.stat-text {
    font-size: 1.5em;
}

.icon-button {
    min-width: 0;
    min-height: 0;
    width: 3em;
    height: 3em;
    padding: 0;
    line-height: 0;
}
.md-button {
    height: 3em;
}
.diag-content {
    min-width: 60vw;
    min-height: 60vh;
}
.primary-stats-gameview md-input-container {
    margin-bottom: 0;
    height: 2em;
}
angular.module('CharApp', ['ngMaterial', 'ngMessages', 'material.svgAssetsCache'])
    .config(function($mdThemingProvider) {
        $mdThemingProvider.theme('default')
            .primaryPalette('brown');
    })
    .controller('MainCtrl', function($scope, $mdDialog, $window) {
        var localStoreSheets = angular.fromJson($window.localStorage.getItem('sheets'));
        var sheets = Array.isArray(localStoreSheets) ? localStoreSheets : [];
        var newSheetConfig = function() {
            return {
                name: 'Agoth the Destroyer',
                secondaryStats: {},
                primaryStats: {
                    Strength: {
                        name: 'Strength',
                        modifier: 2,
                        bonusValue: 0,
                        baseValue: 10
                    },
                    Dexterity: {
                        name: 'Dexterity',
                        modifier: 2,
                        bonusValue: 0,
                        baseValue: 10
                    },
                    Constitution: {
                        name: 'Constitution',
                        modifier: 2,
                        bonusValue: 0,
                        baseValue: 10
                    },
                    Intelligence: {
                        name: 'Intillegence',
                        modifier: 2,
                        bonusValue: 0,
                        baseValue: 10
                    },
                    Wisdom: {
                        name: 'Wisdom',
                        modifier: 2,
                        bonusValue: 0,
                        baseValue: 10
                    },
                    Charisma: {
                        name: 'Charisma',
                        modifier: 2,
                        bonusValue: 0,
                        baseValue: 10
                    }
                }
            }
        };
        var sheetConfig = sheets[0] || newSheetConfig();
        if (sheets.length < 1)
            sheets.push(sheetConfig);
        $scope.sheets = sheets;
        $scope.$watch('sheets', function(newSheets) {
            $window.localStorage.setItem('sheets', angular.toJson(newSheets));
        }, true);
        $scope.sheetConfig = sheetConfig;
        $scope.newSheet = function(ev) {
            $mdDialog.show({
                    controller: 'NewSheetController',
                    templateUrl: 'new-sheet.html',
                    parent: angular.element(document.body),
                    targetEvent: ev,
                    clickOutsideToClose: true,
                    locals: {
                        newSheet: newSheetConfig()
                    }
                })
                .then(function(newSheet) {
                    if (!newSheet) {
                        return;
                    }
                    $scope.sheetConfig = newSheet;
                    sheets.push(newSheet);
                });
        };
        $scope.openSheet = function(ev) {
            $mdDialog.show({
                    controller: 'OpenSheetController',
                    templateUrl: 'open-sheet.html',
                    parent: angular.element(document.body),
                    targetEvent: ev,
                    clickOutsideToClose: true,
                    locals: {
                        sheets: sheets
                    }
                })
                .then(function(newSheet) {
                    if (!newSheet) {
                        return;
                    }
                    $scope.sheetConfig = newSheet;
                });
        };
    })
    .controller('NewSheetController', function($scope, newSheet, $mdDialog) {
        $scope.sheetConfig = newSheet;
        $scope.cancel = function() {
            $mdDialog.cancel();
        };
        $scope.save = function() {
            $mdDialog.hide($scope.sheetConfig);
        };
    })
    .controller('OpenSheetController', function($scope, sheets, $mdDialog) {
        $scope.sheets = sheets;
        $scope.cancel = function() {
            $mdDialog.cancel();
        };
        $scope.deleteSheet = function(sheet) {
            var index = $scope.sheets.indexOf(sheet);
            if (index !== -1) {
                sheets.splice(index, 1);
            }
        };
        $scope.openSheet = function(sheet) {
            $mdDialog.hide(sheet);
        };
    })
    .controller('SecondaryStatsCtrl', function($scope) {
        $scope.addStat = function(newStat) {
            if (!newStat || !newStat.name) {
                return;
            }
            $scope.sheetConfig.secondaryStats = $scope.sheetConfig.secondaryStats || {}; 
            $scope.sheetConfig.secondaryStats[newStat.name] = {
                bonusValue: 0,
                baseValue: ~~newStat.baseValue,
                name: newStat.name
            };
            $scope.newStat = {};
        };
        $scope.removeStat = function(stat) {
            delete $scope.sheetConfig.secondaryStats[stat.name];
        };
        $scope.getStatVal = function(stat) {
            return (stat.bonusValue + stat.baseValue);
        };
        $scope.calcStat = function(stat) {
            if (stat.currentValue === undefined) {
                stat.currentValue = this.getStatVal(stat);
            }
            var dif = stat.currentValue - this.getStatVal(stat);
            stat.bonusValue += dif;
            stat.currentValue = this.getStatVal(stat);
            console.log(stat);
        };
    })
    .controller('PrimaryStatsCtrl', function($scope) {
        $scope.$watch('sheetConfig', function(newSheet) {
            for (var key in newSheet.primaryStats) {
                if (newSheet.primaryStats.hasOwnProperty(key)) {
                    $scope.calcStat(newSheet.primaryStats[key]);
                }
            }
        });
        $scope.addStat = function(newStat) {
            if (!newStat || !newStat.modifier || !newStat.baseValue || !newStat.name) {
                return;
            }
            $scope.sheetConfig.primaryStats[newStat.name] = {
                bonusValue: 0,
                baseValue: newStat.baseValue,
                modifier: newStat.modifier,
                name: newStat.name
            };
            $scope.newStat = {};
        };
        $scope.removeStat = function(stat) {
            delete $scope.sheetConfig.primaryStats[stat.name];
        };
        $scope.getModValue = function(stat) {
            return (stat.currentValue - stat.baseValue) / stat.modifier;
        };
        $scope.getStatVal = function(stat) {
            return (stat.bonusValue + stat.baseValue);
        };
        $scope.calcStat = function(stat) {
            if (stat.currentValue === undefined) {
                stat.currentValue = this.getStatVal(stat);
            }
            var dif = stat.currentValue - this.getStatVal(stat);
            stat.bonusValue += dif;
            stat.currentValue = this.getStatVal(stat);
            stat.modValue = this.getModValue(stat) >= 0 ? '+' + this.getModValue(stat) : this.getModValue(stat);
            console.log(stat);
        };
    })

External CSS

  1. https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0-rc4/angular-material.min.css
  2. https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css

External JavaScript

  1. https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.js
  2. https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-animate.min.js
  3. https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-route.min.js
  4. https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-aria.min.js
  5. https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular-messages.min.js
  6. https://s3-us-west-2.amazonaws.com/s.cdpn.io/t-114/svg-assets-cache.js
  7. https://ajax.googleapis.com/ajax/libs/angular_material/1.1.0-rc4/angular-material.min.js