mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
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:
@@ -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>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}));
|
||||
@@ -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(){
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user