Start to implement a UI finite state machine

Using it should help abstract out UI state changes and avoid situations
where operations leave the UI in a muddled state. It also provides a
foundation to help make operations transactional.
This commit is contained in:
Oliver Hamlet
2016-05-28 08:32:54 +01:00
parent 0560b6d966
commit 4368d8a737
5 changed files with 405 additions and 42 deletions
+1
View File
@@ -463,6 +463,7 @@
<script src="js/l10n.js"></script>
<script src="js/plugin.js"></script>
<script src="js/query.js"></script>
<script src="js/state.js"></script>
<script src="js/translator.js"></script>
<script src="js/init.js"></script>
<script>
+5 -42
View File
@@ -147,15 +147,8 @@ function onSortPlugins() {
loot.game.plugins = loot.game.loadOrder;
filterPluginData(loot.game.plugins, loot.filters);
/* Now hide the masterlist update buttons, and display the accept and
cancel sort buttons. */
loot.dom.show('updateMasterlistButton', false);
loot.dom.show('sortButton', false);
loot.dom.show('applySortButton');
loot.dom.show('cancelSortButton');
loot.state.enterSortingState();
/* Disable changing game. */
loot.dom.enable('gameMenu', false);
loot.Dialog.closeProgress();
}).catch(handlePromiseError);
}
@@ -168,15 +161,7 @@ function onApplySort() {
delete loot.game.loadOrder;
delete loot.game.oldLoadOrder;
/* Now show the masterlist update buttons, and hide the accept and
cancel sort buttons. */
loot.dom.show('updateMasterlistButton');
loot.dom.show('sortButton');
loot.dom.show('applySortButton', false);
loot.dom.show('cancelSortButton', false);
/* Enable changing game. */
loot.dom.enable('gameMenu');
loot.state.exitSortingState();
}).catch(handlePromiseError);
}
function onCancelSort() {
@@ -190,15 +175,7 @@ function onCancelSort() {
/* Update general messages */
loot.game.globalMessages = messages;
/* Now show the masterlist update buttons, and hide the accept and
cancel sort buttons. */
loot.dom.show('updateMasterlistButton');
loot.dom.show('sortButton');
loot.dom.show('applySortButton', false);
loot.dom.show('cancelSortButton', false);
/* Enable changing game. */
loot.dom.enable('gameMenu');
loot.state.exitSortingState();
}).catch(handlePromiseError);
}
@@ -431,14 +408,7 @@ function onEditorOpen(evt) {
elements[i].addEventListener('dragstart', elements[i].onDragStart);
}
/* Disable the toolbar elements. */
loot.dom.enable('wipeUserlistButton', false);
loot.dom.enable('copyContentButton', false);
loot.dom.enable('refreshContentButton', false);
loot.dom.enable('settingsButton', false);
loot.dom.enable('gameMenu', false);
loot.dom.enable('updateMasterlistButton', false);
loot.dom.enable('sortButton', false);
loot.state.enterEditingState();
return loot.query('editorOpened').catch(handlePromiseError);
}
@@ -488,14 +458,7 @@ function onEditorClose(evt) {
elements[i].removeEventListener('dragstart', elements[i].onDragStart);
}
/* Re-enable toolbar elements. */
loot.dom.enable('wipeUserlistButton');
loot.dom.enable('copyContentButton');
loot.dom.enable('refreshContentButton');
loot.dom.enable('settingsButton');
loot.dom.enable('gameMenu');
loot.dom.enable('updateMasterlistButton');
loot.dom.enable('sortButton');
loot.state.exitEditingState();
}).catch(handlePromiseError);
}
function undoConflictsFilter() {
+125
View File
@@ -0,0 +1,125 @@
'use strict';
(function exportModule(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else {
// Browser globals
root.loot = root.loot || {};
root.loot.State = factory(root.loot.dom);
root.loot.state = new root.loot.State();
}
}(this, (dom) => {
return class State {
static get DEFAULT_STATE() {
return 0;
}
static get SORTING_STATE() {
return 1;
}
static get EDITING_STATE() {
return 2;
}
constructor() {
this.currentState = State.DEFAULT_STATE;
}
isInDefaultState() {
return this.currentState === State.DEFAULT_STATE;
}
isInEditingState() {
return this.currentState === State.EDITING_STATE;
}
isInSortingState() {
return this.currentState === State.SORTING_STATE;
}
enterSortingState() {
if (this.isInSortingState()) {
return;
}
if (this.isInEditingState()) {
throw new Error('Cannot enter sorting state from editing state');
}
/* Hide the masterlist update buttons, and display the accept and
cancel sort buttons. */
loot.dom.show('updateMasterlistButton', false);
loot.dom.show('sortButton', false);
loot.dom.show('applySortButton');
loot.dom.show('cancelSortButton');
/* Disable changing game. */
loot.dom.enable('gameMenu', false);
this.currentState = State.SORTING_STATE;
}
exitSortingState() {
if (this.isInDefaultState()) {
return;
}
if (this.isInEditingState()) {
throw new Error('Cannot exit sorting state from editing state');
}
/* Show the masterlist update buttons, and hide the accept and
cancel sort buttons. */
loot.dom.show('updateMasterlistButton');
loot.dom.show('sortButton');
loot.dom.show('applySortButton', false);
loot.dom.show('cancelSortButton', false);
/* Enable changing game. */
loot.dom.enable('gameMenu');
this.currentState = State.DEFAULT_STATE;
}
enterEditingState() {
if (this.isInEditingState()) {
return;
}
if (this.isInSortingState()) {
throw new Error('Cannot enter editing state from sorting state');
}
/* Disable the toolbar elements. */
loot.dom.enable('wipeUserlistButton', false);
loot.dom.enable('copyContentButton', false);
loot.dom.enable('refreshContentButton', false);
loot.dom.enable('settingsButton', false);
loot.dom.enable('gameMenu', false);
loot.dom.enable('updateMasterlistButton', false);
loot.dom.enable('sortButton', false);
this.currentState = State.EDITING_STATE;
}
exitEditingState() {
if (this.isInDefaultState()) {
return;
}
if (this.isInSortingState()) {
throw new Error('Cannot exit editing state from sorting state');
}
/* Re-enable toolbar elements. */
loot.dom.enable('wipeUserlistButton');
loot.dom.enable('copyContentButton');
loot.dom.enable('refreshContentButton');
loot.dom.enable('settingsButton');
loot.dom.enable('gameMenu');
loot.dom.enable('updateMasterlistButton');
loot.dom.enable('sortButton');
this.currentState = State.DEFAULT_STATE;
}
};
}));
+3
View File
@@ -12,15 +12,18 @@
<script src="../../../../../bower_components/lodash/dist/lodash.core.min.js"></script>
<script>mocha.setup('bdd')</script>
<script src="../../../../gui/html/js/dom.js"></script>
<script src="../../../../gui/html/js/filters.js"></script>
<script src="../../../../gui/html/js/game.js"></script>
<script src="../../../../gui/html/js/plugin.js"></script>
<script src="../../../../gui/html/js/query.js"></script>
<script src="../../../../gui/html/js/state.js"></script>
<script src="../../../../gui/html/js/translator.js"></script>
<script src="test_filters.js"></script>
<script src="test_game.js"></script>
<script src="test_plugin.js"></script>
<script src="test_query.js"></script>
<script src="test_state.js"></script>
<script src="test_translator.js"></script>
<script>
onload = function(){
+271
View File
@@ -0,0 +1,271 @@
'use strict';
/* Mock the DOM interactions */
loot.dom = {
elementShownStates: new Map(),
elementEnabledStates: new Map(),
show(elementId, showElement = true) {
this.elementShownStates.set(elementId, showElement);
},
enable(elementId, enableElement = true) {
this.elementEnabledStates.set(elementId, enableElement);
},
};
function getShown(elementId) {
return loot.dom.elementShownStates.get(elementId);
}
function getEnabled(elementId) {
return loot.dom.elementEnabledStates.get(elementId);
}
beforeEach(() => {
loot.dom.elementShownStates.clear();
loot.dom.elementEnabledStates.clear();
});
describe('State', () => {
describe('State()', () => {
it('should set the current state to DEFAULT_STATE', () => {
const state = new loot.State();
state.currentState.should.equal(loot.State.DEFAULT_STATE);
});
});
describe('DEFAULT_STATE', () => {
it('should not equal EDITING_STATE', () => {
loot.State.DEFAULT_STATE.should.not.equal(loot.State.EDITING_STATE);
});
it('should not equal SORTING_STATE', () => {
loot.State.DEFAULT_STATE.should.not.equal(loot.State.SORTING_STATE);
});
});
describe('EDITING_STATE', () => {
it('should not equal SORTING_STATE', () => {
loot.State.EDITING_STATE.should.not.equal(loot.State.SORTING_STATE);
});
});
describe('isInDefaultState()', () => {
it('should return true if the default state is the current state', () => {
const state = new loot.State();
state.isInDefaultState().should.be.true();
});
it('should return false if the editing state is the current state', () => {
const state = new loot.State();
state.enterEditingState();
state.isInDefaultState().should.be.false();
});
it('should return false if the sorting state is the current state', () => {
const state = new loot.State();
state.enterSortingState();
state.isInDefaultState().should.be.false();
});
});
describe('isInEditingState()', () => {
it('should return true if the editing state is the current state', () => {
const state = new loot.State();
state.enterEditingState();
state.isInEditingState().should.be.true();
});
it('should return false if the default state is the current state', () => {
const state = new loot.State();
state.isInEditingState().should.be.false();
});
it('should return false if the sorting state is the current state', () => {
const state = new loot.State();
state.enterSortingState();
state.isInEditingState().should.be.false();
});
});
describe('isInSortingState()', () => {
it('should return true if the sorting state is the current state', () => {
const state = new loot.State();
state.enterSortingState();
state.isInSortingState().should.be.true();
});
it('should return false if the editing state is the current state', () => {
const state = new loot.State();
state.enterEditingState();
state.isInSortingState().should.be.false();
});
it('should return false if the default state is the current state', () => {
const state = new loot.State();
state.isInSortingState().should.be.false();
});
});
describe('enterSortingState()', () => {
it('should change state to the sorting state from the default state', () => {
const state = new loot.State();
state.enterSortingState();
state.currentState.should.equal(loot.State.SORTING_STATE);
getShown('updateMasterlistButton').should.be.false();
getShown('sortButton').should.be.false();
getShown('applySortButton').should.be.true();
getShown('cancelSortButton').should.be.true();
getEnabled('gameMenu').should.be.false();
});
it('should throw an error if called in the editing state', () => {
const state = new loot.State();
state.enterEditingState();
should.throws(() => { state.enterSortingState(); }, Error);
});
it('should have no effect if already in the sorting state', () => {
const state = new loot.State();
state.enterSortingState();
state.enterSortingState();
state.currentState.should.equal(loot.State.SORTING_STATE);
getShown('updateMasterlistButton').should.be.false();
getShown('sortButton').should.be.false();
getShown('applySortButton').should.be.true();
getShown('cancelSortButton').should.be.true();
getEnabled('gameMenu').should.be.false();
});
});
describe('exitSortingState()', () => {
it('should change state to the default state from the sorting state', () => {
const state = new loot.State();
state.enterSortingState();
state.exitSortingState();
state.currentState.should.equal(loot.State.DEFAULT_STATE);
getShown('updateMasterlistButton').should.be.true();
getShown('sortButton').should.be.true();
getShown('applySortButton').should.be.false();
getShown('cancelSortButton').should.be.false();
getEnabled('gameMenu').should.be.true();
});
it('should throw an error if called in the editing state', () => {
const state = new loot.State();
state.enterEditingState();
should.throws(() => { state.exitSortingState(); }, Error);
});
it('should have no effect if already in the default state', () => {
const state = new loot.State();
state.exitSortingState();
state.currentState.should.equal(loot.State.DEFAULT_STATE);
should(getShown('updateMasterlistButton')).be.undefined();
should(getShown('sortButton')).be.undefined();
should(getShown('applySortButton')).be.undefined();
should(getShown('cancelSortButton')).be.undefined();
should(getEnabled('gameMenu')).be.undefined();
});
});
describe('enterEditingState()', () => {
it('should change state to the editing state from the default state', () => {
const state = new loot.State();
state.enterEditingState();
state.currentState.should.equal(loot.State.EDITING_STATE);
getEnabled('wipeUserlistButton').should.be.false();
getEnabled('copyContentButton').should.be.false();
getEnabled('refreshContentButton').should.be.false();
getEnabled('settingsButton').should.be.false();
getEnabled('gameMenu').should.be.false();
getEnabled('updateMasterlistButton').should.be.false();
getEnabled('sortButton').should.be.false();
});
it('should throw an error if called in the sorting state', () => {
const state = new loot.State();
state.enterSortingState();
should.throws(() => { state.enterEditingState(); }, Error);
});
it('should have no effect if already in the editing state', () => {
const state = new loot.State();
state.enterEditingState();
state.enterEditingState();
state.currentState.should.equal(loot.State.EDITING_STATE);
getEnabled('wipeUserlistButton').should.be.false();
getEnabled('copyContentButton').should.be.false();
getEnabled('refreshContentButton').should.be.false();
getEnabled('settingsButton').should.be.false();
getEnabled('gameMenu').should.be.false();
getEnabled('updateMasterlistButton').should.be.false();
getEnabled('sortButton').should.be.false();
});
});
describe('exitEditingState()', () => {
it('should change state to the default state from the editing state', () => {
const state = new loot.State();
state.enterEditingState();
state.exitEditingState();
state.currentState.should.equal(loot.State.DEFAULT_STATE);
getEnabled('wipeUserlistButton').should.be.true();
getEnabled('copyContentButton').should.be.true();
getEnabled('refreshContentButton').should.be.true();
getEnabled('settingsButton').should.be.true();
getEnabled('gameMenu').should.be.true();
getEnabled('updateMasterlistButton').should.be.true();
getEnabled('sortButton').should.be.true();
});
it('should throw an error if called in the sorting state', () => {
const state = new loot.State();
state.enterSortingState();
should.throws(() => { state.exitEditingState(); }, Error);
});
it('should have no effect if already in the default state', () => {
const state = new loot.State();
state.exitEditingState();
state.currentState.should.equal(loot.State.DEFAULT_STATE);
should(getEnabled('wipeUserlistButton')).be.undefined();
should(getEnabled('copyContentButton')).be.undefined();
should(getEnabled('refreshContentButton')).be.undefined();
should(getEnabled('settingsButton')).be.undefined();
should(getEnabled('gameMenu')).be.undefined();
should(getEnabled('updateMasterlistButton')).be.undefined();
should(getEnabled('sortButton')).be.undefined();
});
});
});