Commit 23ac19b6 authored by Rob Myers's avatar Rob Myers

Semi-modernize Lottery Symbol.

parent 5b493d64
...@@ -7,23 +7,21 @@ ...@@ -7,23 +7,21 @@
width: 75vmin; width: 75vmin;
height: 75vmin; height: 75vmin;
position: absolute;
top:0; display: flex;
bottom: 0; align-items: center;
left: 0; justify-content: center;
right: 0;
margin: auto;
} }
#symbol-gui { #symbol-gui {
/* Work out how to get panels to size to largest... */ /* Work out how to get panels to size to largest... */
width: 590px; width: 590px;
height: 580px; height: 540px;
overflow-y: auto;
} }
#gui-current-entries-table { #gui-current-entries-table {
width: 100%; width: 100%;
} }
.gui-current-entries-symbol { .gui-current-entries-symbol {
...@@ -40,3 +38,12 @@ ...@@ -40,3 +38,12 @@
.tab-pane>.active { .tab-pane>.active {
visibility: visible; visibility: visible;
} }
#updating {
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
display: none;
}
...@@ -8,12 +8,12 @@ ...@@ -8,12 +8,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:700" <link href="https://fonts.googleapis.com/css?family=Open+Sans:700"
rel="stylesheet"> rel="stylesheet">
<link href="app.css" rel="stylesheet"> <link href="../../shared/css/bootstrap.css" rel="stylesheet">
<link href="../../shared/css/shared.css" rel="stylesheet">
<link href="./css/app.css" rel="stylesheet">
</head> </head>
<body> <body>
<div id="updating">Entering...</div>
<div id="status"></div> <div id="status"></div>
<div id="representation" <div id="representation"
...@@ -44,29 +44,26 @@ ...@@ -44,29 +44,26 @@
<div class="form-group"> <div class="form-group">
<label class="gui-label" <label class="gui-label"
for="symbol-gui-symbol">Symbol</label> for="symbol-gui-symbol">Symbol</label>
<!-- maxLength 2 *code points* because we allow emoji-->
<input class="form-control" <input class="form-control"
type="text" type="text"
maxLength="1" maxLength="2"
id="symbol-gui-symbol" id="symbol-gui-symbol"
aria-describedby="symbolGuiSymbolHelpInline"> aria-describedby="symbolGuiSymbolHelpInline">
<small id="symbolGuiSymbolHelpInline" <small id="symbolGuiSymbolHelpInline"
class="text-muted">Any unicode character</small> class="text-muted">Any unicode character</small>
</div> </div>
<div id="enter-gui-gas">
<div id="gui-gas" class="form-group">
<label for="gui-gas-account">Account to pay gas from</label>
<select class="form-control" id="gui-gas-account"></select>
</div>
</div>
<button type="button" class="btn btn-primary" <button type="button" class="btn btn-primary"
id="gui-update-button">Enter</button> id="gui-update-button">Enter</button>
&nbsp; &nbsp;
<button type="button" class="btn btn-default" <button type="button" class="btn btn-default"
id="gui-cancel-button">Cancel</button> id="gui-cancel-button">Cancel</button>
</form> </form>
<br />
<p><b><i>Note that entering the lottery to update the symbol will <p><b><i>Note that entering the lottery to update the symbol will
cost gas! Like any lottery, your entry may not cost gas! Like any lottery, your entry may not win.
win.</i></b></p> The only &quot;prize&quot is setting the
symbol.</i></b></p>
<p>To enter the lottery, choose a new symbol in the text field <p>To enter the lottery, choose a new symbol in the text field
above. Then press &quot;Enter&quot;.</p> above. Then press &quot;Enter&quot;.</p>
<p>If you do not wish to update the symbol, just press <p>If you do not wish to update the symbol, just press
...@@ -85,8 +82,6 @@ ...@@ -85,8 +82,6 @@
<div id="gui-gas-finalize"></div> <div id="gui-gas-finalize"></div>
<p>If the current round of the lottery has not yet been finalized, <p>If the current round of the lottery has not yet been finalized,
you can do so using this button.</p> you can do so using this button.</p>
<p>This will use the account from the &quot;Enter Lottery&quot;
tab, change it there if needed!<p>
<button type="button" class="btn btn-primary" <button type="button" class="btn btn-primary"
id="gui-finalize-button">Finalize</button> id="gui-finalize-button">Finalize</button>
&nbsp; &nbsp;
...@@ -95,8 +90,13 @@ ...@@ -95,8 +90,13 @@
</div> </div>
</div> </div>
</div> </div>
<div id="updating">Entering...</div>
</div> </div>
<script src="app.js"></script> <script src="../../shared/js/jquery.js"></script>
<script src="../../shared/js/bootstrap.js"></script>
<script src="../../shared/javascript/web3.js"></script>
<script src="./js/app.js"></script>
</body> </body>
</html> </html>
/* /*
* Lottery Symbol - A symbol you can change via a lottery. * Lottery Symbol - A symbol you can change via a lottery.
* Copyright (C) 2017 Rob Myers <[email protected]> * Copyright (C) 2017, 2020 Rob Myers <[email protected]>
* *
* This file is part of Lottery Symbol. * This file is part of Lottery Symbol.
* *
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
* along with Lottery Symbol. If not, see <http://www.gnu.org/licenses/>. * along with Lottery Symbol. If not, see <http://www.gnu.org/licenses/>.
*/ */
/* global $ Shared LotterySymbol */
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// The main contract behaviour object // The main contract behaviour object
...@@ -30,10 +29,24 @@ LotterySymbolGui.setSymbolRepresentation = function (symbol) { ...@@ -30,10 +29,24 @@ LotterySymbolGui.setSymbolRepresentation = function (symbol) {
$('#symbol').text(symbol); $('#symbol').text(symbol);
}; };
// This is complicated by:
// 1: unicode being complex.
// 2: web3 being weird.
LotterySymbolGui.symbolValToUnicodeStr = function (val) {
const symbol = String.fromCodePoint(parseInt(val, 10));
return symbol;
};
LotterySymbolGui.unicodeStrToSymbolValue = function (str) {
};
LotterySymbolGui.updateSymbol = function () { LotterySymbolGui.updateSymbol = function () {
this.contract.symbol.call().then(symbol => this.contract.methods.symbol().call().then(
this.setSymbolRepresentation(String.fromCharCode(symbol.toNumber())) symbol => this.setSymbolRepresentation(
); this.symbolValToUnicodeStr(symbol)
));
}; };
LotterySymbolGui.setGuiSymbol = function (symbol) { LotterySymbolGui.setGuiSymbol = function (symbol) {
...@@ -41,13 +54,30 @@ LotterySymbolGui.setGuiSymbol = function (symbol) { ...@@ -41,13 +54,30 @@ LotterySymbolGui.setGuiSymbol = function (symbol) {
}; };
LotterySymbolGui.initialiseGuiSymbol = function () { LotterySymbolGui.initialiseGuiSymbol = function () {
this.contract.symbol.call() this.contract.methods.symbol().call()
.then(symbol => this.setGuiSymbol(String.fromCharCode(symbol.toNumber()))); .then(symbol => this.setGuiSymbol(this.symbolValToUnicodeStr(symbol)));
}; };
LotterySymbolGui.finalizeRound = function () { LotterySymbolGui.tryForAccountAccess = async function () {
const account = Shared.selectedGasAccount(); let account;
this.contract.finalizeRound({ from: account, gas: 60000 }); if (window.ethereum) {
try {
// Request account access if needed
account = (await ethereum.enable())[0];
} catch (error) {
account = false;
}
} else if (window.web3) {
account = (await web3.eth.getAccounts())[0];
} else {
account = false;
}
return account;
};
LotterySymbolGui.finalizeRound = async function () {
const account = await this.tryForAccountAccess();
this.contract.methods.finalizeRound().send({ from: account, gas: 60000 });
}; };
LotterySymbolGui.getGuiSymbol = function () { LotterySymbolGui.getGuiSymbol = function () {
...@@ -55,20 +85,31 @@ LotterySymbolGui.getGuiSymbol = function () { ...@@ -55,20 +85,31 @@ LotterySymbolGui.getGuiSymbol = function () {
}; };
LotterySymbolGui.symbolIsValid = function (symbol) { LotterySymbolGui.symbolIsValid = function (symbol) {
return (symbol.length === 1) // Correct length // Check unicode spread length to allow emoji etc.
return ([...symbol].length === 1) // Correct length
&& (!symbol.match(/\s/)); // Not whitespace && (!symbol.match(/\s/)); // Not whitespace
}; };
LotterySymbolGui.commitNetworkEntry = function (symbol) { LotterySymbolGui.tryForAccountAccess = async function () {
const account = Shared.selectedGasAccount(); let account;
this.contract.enterLottery( if (window.ethereum) {
symbol.charCodeAt(0), try {
{ from: account, // Request account access if needed
gas: 130000 } account = (await ethereum.enable())[0];
).catch((error) => { } catch (error) {}
console.log(error); } else if (window.web3) {
alert('Something went wrong. Maybe the account doesn\'t have enough Ether to pay for gas? See the console log for details.'); account = (await web3.eth.getAccounts())[0];
}).finally(() => Shared.hideUpdating()); }
return account;
};
LotterySymbolGui.commitNetworkEntry = async function (symbol) {
const account = await this.tryForAccountAccess();
if (!account) {
return;
}
await this.contract.methods.enterLottery(symbol.codePointAt(0))
.send({ from: account,gas: 130000 });
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
...@@ -84,8 +125,8 @@ LotterySymbolGui.pad2 = function (number) { ...@@ -84,8 +125,8 @@ LotterySymbolGui.pad2 = function (number) {
LotterySymbolGui.nextLotteryFinishes = function () { LotterySymbolGui.nextLotteryFinishes = function () {
let previous; let previous;
this.contract.currentRoundEnds.call().then( this.contract.methods.currentRoundEnds().call().then(
(currentRoundEnds) => { currentRoundEnds => {
const date = new Date(currentRoundEnds * 1000); const date = new Date(currentRoundEnds * 1000);
previous = date <= (new Date().getTime()); previous = date <= (new Date().getTime());
let prefix = 'Next round ends at'; let prefix = 'Next round ends at';
...@@ -95,10 +136,13 @@ LotterySymbolGui.nextLotteryFinishes = function () { ...@@ -95,10 +136,13 @@ LotterySymbolGui.nextLotteryFinishes = function () {
$('.gui-next-round').text( $('.gui-next-round').text(
`${prefix}: ${this.pad2(date.getHours())}:${this.pad2(date.getMinutes())}:${this.pad2(date.getSeconds())} on ${date.getFullYear()}-${this.pad2(date.getMonth())}-${this.pad2(date.getDate())}` `${prefix}: ${this.pad2(date.getHours())}:${this.pad2(date.getMinutes())}:${this.pad2(date.getSeconds())} on ${date.getFullYear()}-${this.pad2(date.getMonth())}-${this.pad2(date.getDate())}`
); );
return this.contract.numEntries.call(); return this.contract.methods.numEntries().call();
}).then(numEntries => }).then(
$('#gui-finalize-button').prop('disabled', numEntries => $('#gui-finalize-button').prop(
! ((numEntries > 0) && previous))); 'disabled',
! ((numEntries > 0) && previous)
)
);
}; };
LotterySymbolGui.clearCurrentEntries = function () { LotterySymbolGui.clearCurrentEntries = function () {
...@@ -106,7 +150,7 @@ LotterySymbolGui.clearCurrentEntries = function () { ...@@ -106,7 +150,7 @@ LotterySymbolGui.clearCurrentEntries = function () {
}; };
LotterySymbolGui.renderCurrentEntries = function () { LotterySymbolGui.renderCurrentEntries = function () {
LotterySymbolGui.contract.getEntries.call() LotterySymbolGui.contract.methods.getEntries().call()
.then((entries) => { .then((entries) => {
this.clearCurrentEntries(); this.clearCurrentEntries();
const numEntries = entries[0].length; const numEntries = entries[0].length;
...@@ -115,8 +159,8 @@ LotterySymbolGui.renderCurrentEntries = function () { ...@@ -115,8 +159,8 @@ LotterySymbolGui.renderCurrentEntries = function () {
const symbols = entries[1]; const symbols = entries[1];
let html = '<tr><th>Entrant</th><th class="gui-current-entries-symbol">Symbol</th></tr>'; let html = '<tr><th>Entrant</th><th class="gui-current-entries-symbol">Symbol</th></tr>';
for (var i = 0; i < numEntries; i++) { for (var i = 0; i < numEntries; i++) {
const entrant = entrants[i] const entrant = entrants[i];
const symbol = String.fromCharCode(symbols[i].toNumber()); const symbol = this.symbolValToUnicodeStr(symbols[i]);
html += `<tr><td>${entrant}</td><td class="gui-current-entries-symbol">${symbol}</td></tr>`; html += `<tr><td>${entrant}</td><td class="gui-current-entries-symbol">${symbol}</td></tr>`;
} }
$('#gui-current-entries').append(html); $('#gui-current-entries').append(html);
...@@ -124,10 +168,9 @@ LotterySymbolGui.renderCurrentEntries = function () { ...@@ -124,10 +168,9 @@ LotterySymbolGui.renderCurrentEntries = function () {
}); });
}; };
// Called from Shared, so be careful with the value of 'this'. LotterySymbolGui.showGui = function () {
LotterySymbolGui.guiDisplayHook = function () {
$('#symbol').hide(); $('#symbol').hide();
$('.gui').show();
this.initialiseGuiSymbol(); this.initialiseGuiSymbol();
this.nextLotteryFinishes(); this.nextLotteryFinishes();
this.guiLotteryTimer = setInterval(() => { this.guiLotteryTimer = setInterval(() => {
...@@ -136,19 +179,20 @@ LotterySymbolGui.guiDisplayHook = function () { ...@@ -136,19 +179,20 @@ LotterySymbolGui.guiDisplayHook = function () {
}; };
LotterySymbolGui.hideGui = function () { LotterySymbolGui.hideGui = function () {
Shared.hideGui(); $('.gui').hide();
$('#symbol').show();
clearInterval(this.guiLotteryTimer); clearInterval(this.guiLotteryTimer);
this.guiLotteryTimer = null; this.guiLotteryTimer = null;
}; };
LotterySymbolGui.userSelectedUpdate = function (event) { LotterySymbolGui.userSelectedUpdate = async function (event) {
event.stopPropagation(); event.stopPropagation();
const symbol = this.getGuiSymbol(); const symbol = this.getGuiSymbol();
if (this.symbolIsValid(symbol)) { if (this.symbolIsValid(symbol)) {
this.commitNetworkEntry(symbol); $('#updating').html('Entering&hellip;');
Shared.showUpdating('Entering&hellip;'); document.querySelector('#updating').style.setProperty('display', 'flex');
this.hideGui(); this.hideGui();
await this.commitNetworkEntry(symbol);
document.querySelector('#updating').style.setProperty('display', 'none');
$('#symbol').show(); $('#symbol').show();
} else { } else {
alert('Invalid symbol. Make sure you enter a visible character in the "Symbol" field.'); alert('Invalid symbol. Make sure you enter a visible character in the "Symbol" field.');
...@@ -158,14 +202,16 @@ LotterySymbolGui.userSelectedUpdate = function (event) { ...@@ -158,14 +202,16 @@ LotterySymbolGui.userSelectedUpdate = function (event) {
LotterySymbolGui.userSelectedCancel = function (event) { LotterySymbolGui.userSelectedCancel = function (event) {
event.stopPropagation(); event.stopPropagation();
this.hideGui(); this.hideGui();
$('#symbol').show();
}; };
LotterySymbolGui.userSelectedFinalize = function (event) { LotterySymbolGui.userSelectedFinalize = async function (event) {
event.stopPropagation(); event.stopPropagation();
Shared.showUpdating('Finalizing&hellip;'); $('#updating').html('Finalizing&hellip;');
document.querySelector('#updating').style.setProperty('display', 'flex');
this.hideGui(); this.hideGui();
$('#symbol').show(); $('#symbol').show();
this.finalizeRound(); await this.finalizeRound();
}; };
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
...@@ -173,17 +219,8 @@ LotterySymbolGui.userSelectedFinalize = function (event) { ...@@ -173,17 +219,8 @@ LotterySymbolGui.userSelectedFinalize = function (event) {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
LotterySymbolGui.setupGui = function () { LotterySymbolGui.setupGui = function () {
$('#representation').click((event) => { $('#updating').click(event => event.stopPropagation());
if (!Shared.gui_is_showing) { $('#representation').click((event) => this.showGui());
Shared.showGui();
}
});
$(document).keydown((event) => {
// 27 is ESC
if (event.keyCode === 27 && Shared.gui_is_showing) {
this.userSelectedCancel(event);
}
});
// Arrow functions so 'this' is LotterySymbolGui not the button when called // Arrow functions so 'this' is LotterySymbolGui not the button when called
$('#gui-update-button').click(event => this.userSelectedUpdate(event)); $('#gui-update-button').click(event => this.userSelectedUpdate(event));
$('#gui-cancel-button').click(event => this.userSelectedCancel(event)); $('#gui-cancel-button').click(event => this.userSelectedCancel(event));
...@@ -191,46 +228,59 @@ LotterySymbolGui.setupGui = function () { ...@@ -191,46 +228,59 @@ LotterySymbolGui.setupGui = function () {
$('#gui-cancel-finalize-button') $('#gui-cancel-finalize-button')
.click(event => this.userSelectedCancel(event)); .click(event => this.userSelectedCancel(event));
$('#gui-tabs a:first').tab('show'); $('#gui-tabs a:first').tab('show');
// Shared assumes a single gas select, so move it between tabs as needed $('#symbol-gui-symbol').on('input',function(e) {
$('a[aria-controls="enter"]').on('show.bs.tab', () => { // Unicode symbols are one character but n code points. Emoji are two.
$('#gui-gas-enter').append($('#gui-gas')); // So we allow two characters and truncate if we get more than 1 symbol.
}); // Note the spread. This is to get the correct count.
$('a[aria-controls="finalize"]').on('show.bs.tab', () => { while ([...e.target.value].length > 1) {
$('#gui-gas-finalize').append($('#gui-gas')); e.target.value = e.target.value.slice(0, -1);
}); }
});
}; };
LotterySymbolGui.initialise = function () { LotterySymbolGui.initialise = async function () {
Shared.init(() => this.guiDisplayHook()); // Connect to Ethereum or fail.
if (window.ethereum) {
// Modern web3
window.web3 = new Web3(ethereum);
} else if (window.web3) {
// Old school web3
window.web3 = new Web3(web3.currentProvider);
} else {
document.write(
'No Ethereum access. Try an Ethereum plugin or Ethereum-enabled browser'
);
return;
}
// Make a Web3.js 1.0 contract instance of the contract.
const contractNetworkResponse = await fetch(
// Relative to index.html, not this file.
"../build/contracts/LotterySymbol.json"
);
const contractJSON = await contractNetworkResponse.json();
const abi = contractJSON.abi;
const network = await web3.eth.net.getId();
const address = contractJSON.networks[network].address;
this.contract = new web3.eth.Contract(abi, address);
this.setupGui(); this.setupGui();
// Shared.setGasAccountChangedCallback(
// () => this.gasAccountChanged(), this.renderCurrentEntries();
// true this.contract.events.SymbolChanged().on(
// ); 'data',
LotterySymbol.deployed().then((instance) => { event => {
this.contract = instance; this.renderCurrentEntries();
// Race conditions between this and the event handlers this.updateSymbol();
// SO we always use renderCurrentEntries in the event handlers }
// to make sure we render the current state atomically );
this.renderCurrentEntries(); this.contract.events.NewEntry().on(
this.contract 'data',
.SymbolChanged({}, (error) => { event => this.renderCurrentEntries()
if (!error) { );
this.renderCurrentEntries(); this.updateSymbol();
this.updateSymbol();
Shared.hideUpdating();
}
});
this.contract
.NewEntry({}, (error, event) => {
this.renderCurrentEntries();
});
this.updateSymbol();
// Silence 'a promise was created in a handler but was not returned from it'
// resulting from all the promises created in describeArt().
// http://bluebirdjs.com/docs/warning-explanations.html
return null;
});
}; };
$(window).on('load', () => LotterySymbolGui.initialise()); $(window).on('load', () => {
LotterySymbolGui.initialise();
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment