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

Semi-modernize Lottery Symbol.

parent 5b493d64
......@@ -7,23 +7,21 @@
width: 75vmin;
height: 75vmin;
position: absolute;
top:0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
display: flex;
align-items: center;
justify-content: center;
}
#symbol-gui {
/* Work out how to get panels to size to largest... */
width: 590px;
height: 580px;
/* Work out how to get panels to size to largest... */
width: 590px;
height: 540px;
overflow-y: auto;
}
#gui-current-entries-table {
width: 100%;
width: 100%;
}
.gui-current-entries-symbol {
......@@ -40,3 +38,12 @@
.tab-pane>.active {
visibility: visible;
}
#updating {
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
display: none;
}
......@@ -8,12 +8,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:700"
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>
<body>
<div id="updating">Entering...</div>
<div id="status"></div>
<div id="representation"
......@@ -44,29 +44,26 @@
<div class="form-group">
<label class="gui-label"
for="symbol-gui-symbol">Symbol</label>
<!-- maxLength 2 *code points* because we allow emoji-->
<input class="form-control"
type="text"
maxLength="1"
maxLength="2"
id="symbol-gui-symbol"
aria-describedby="symbolGuiSymbolHelpInline">
<small id="symbolGuiSymbolHelpInline"
class="text-muted">Any unicode character</small>
</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"
id="gui-update-button">Enter</button>
&nbsp;
<button type="button" class="btn btn-default"
id="gui-cancel-button">Cancel</button>
</form>
<br />
<p><b><i>Note that entering the lottery to update the symbol will
cost gas! Like any lottery, your entry may not
win.</i></b></p>
cost gas! Like any lottery, your entry may not win.
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
above. Then press &quot;Enter&quot;.</p>
<p>If you do not wish to update the symbol, just press
......@@ -85,8 +82,6 @@
<div id="gui-gas-finalize"></div>
<p>If the current round of the lottery has not yet been finalized,
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"
id="gui-finalize-button">Finalize</button>
&nbsp;
......@@ -95,8 +90,13 @@
</div>
</div>
</div>
<div id="updating">Entering...</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>
</html>
/*
* 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.
*
......@@ -18,7 +18,6 @@
* along with Lottery Symbol. If not, see <http://www.gnu.org/licenses/>.
*/
/* global $ Shared LotterySymbol */
////////////////////////////////////////////////////////////////////////////////
// The main contract behaviour object
......@@ -30,10 +29,24 @@ LotterySymbolGui.setSymbolRepresentation = function (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 () {
this.contract.symbol.call().then(symbol =>
this.setSymbolRepresentation(String.fromCharCode(symbol.toNumber()))
);
this.contract.methods.symbol().call().then(
symbol => this.setSymbolRepresentation(
this.symbolValToUnicodeStr(symbol)
));
};
LotterySymbolGui.setGuiSymbol = function (symbol) {
......@@ -41,13 +54,30 @@ LotterySymbolGui.setGuiSymbol = function (symbol) {
};
LotterySymbolGui.initialiseGuiSymbol = function () {
this.contract.symbol.call()
.then(symbol => this.setGuiSymbol(String.fromCharCode(symbol.toNumber())));
this.contract.methods.symbol().call()
.then(symbol => this.setGuiSymbol(this.symbolValToUnicodeStr(symbol)));
};
LotterySymbolGui.finalizeRound = function () {
const account = Shared.selectedGasAccount();
this.contract.finalizeRound({ from: account, gas: 60000 });
LotterySymbolGui.tryForAccountAccess = async function () {
let account;
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 () {
......@@ -55,20 +85,31 @@ LotterySymbolGui.getGuiSymbol = function () {
};
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
};
LotterySymbolGui.commitNetworkEntry = function (symbol) {
const account = Shared.selectedGasAccount();
this.contract.enterLottery(
symbol.charCodeAt(0),
{ from: account,
gas: 130000 }
).catch((error) => {
console.log(error);
alert('Something went wrong. Maybe the account doesn\'t have enough Ether to pay for gas? See the console log for details.');
}).finally(() => Shared.hideUpdating());
LotterySymbolGui.tryForAccountAccess = async function () {
let account;
if (window.ethereum) {
try {
// Request account access if needed
account = (await ethereum.enable())[0];
} catch (error) {}
} else if (window.web3) {
account = (await web3.eth.getAccounts())[0];
}
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) {
LotterySymbolGui.nextLotteryFinishes = function () {
let previous;
this.contract.currentRoundEnds.call().then(
(currentRoundEnds) => {
this.contract.methods.currentRoundEnds().call().then(
currentRoundEnds => {
const date = new Date(currentRoundEnds * 1000);
previous = date <= (new Date().getTime());
let prefix = 'Next round ends at';
......@@ -95,10 +136,13 @@ LotterySymbolGui.nextLotteryFinishes = function () {
$('.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())}`
);
return this.contract.numEntries.call();
}).then(numEntries =>
$('#gui-finalize-button').prop('disabled',
! ((numEntries > 0) && previous)));
return this.contract.methods.numEntries().call();
}).then(
numEntries => $('#gui-finalize-button').prop(
'disabled',
! ((numEntries > 0) && previous)
)
);
};
LotterySymbolGui.clearCurrentEntries = function () {
......@@ -106,7 +150,7 @@ LotterySymbolGui.clearCurrentEntries = function () {
};
LotterySymbolGui.renderCurrentEntries = function () {
LotterySymbolGui.contract.getEntries.call()
LotterySymbolGui.contract.methods.getEntries().call()
.then((entries) => {
this.clearCurrentEntries();
const numEntries = entries[0].length;
......@@ -115,8 +159,8 @@ LotterySymbolGui.renderCurrentEntries = function () {
const symbols = entries[1];
let html = '<tr><th>Entrant</th><th class="gui-current-entries-symbol">Symbol</th></tr>';
for (var i = 0; i < numEntries; i++) {
const entrant = entrants[i]
const symbol = String.fromCharCode(symbols[i].toNumber());
const entrant = entrants[i];
const symbol = this.symbolValToUnicodeStr(symbols[i]);
html += `<tr><td>${entrant}</td><td class="gui-current-entries-symbol">${symbol}</td></tr>`;
}
$('#gui-current-entries').append(html);
......@@ -124,10 +168,9 @@ LotterySymbolGui.renderCurrentEntries = function () {
});
};
// Called from Shared, so be careful with the value of 'this'.
LotterySymbolGui.guiDisplayHook = function () {
LotterySymbolGui.showGui = function () {
$('#symbol').hide();
$('.gui').show();
this.initialiseGuiSymbol();
this.nextLotteryFinishes();
this.guiLotteryTimer = setInterval(() => {
......@@ -136,19 +179,20 @@ LotterySymbolGui.guiDisplayHook = function () {
};
LotterySymbolGui.hideGui = function () {
Shared.hideGui();
$('#symbol').show();
$('.gui').hide();
clearInterval(this.guiLotteryTimer);
this.guiLotteryTimer = null;
};
LotterySymbolGui.userSelectedUpdate = function (event) {
LotterySymbolGui.userSelectedUpdate = async function (event) {
event.stopPropagation();
const symbol = this.getGuiSymbol();
if (this.symbolIsValid(symbol)) {
this.commitNetworkEntry(symbol);
Shared.showUpdating('Entering&hellip;');
$('#updating').html('Entering&hellip;');
document.querySelector('#updating').style.setProperty('display', 'flex');
this.hideGui();
await this.commitNetworkEntry(symbol);
document.querySelector('#updating').style.setProperty('display', 'none');
$('#symbol').show();
} else {
alert('Invalid symbol. Make sure you enter a visible character in the "Symbol" field.');
......@@ -158,14 +202,16 @@ LotterySymbolGui.userSelectedUpdate = function (event) {
LotterySymbolGui.userSelectedCancel = function (event) {
event.stopPropagation();
this.hideGui();
$('#symbol').show();
};
LotterySymbolGui.userSelectedFinalize = function (event) {
LotterySymbolGui.userSelectedFinalize = async function (event) {
event.stopPropagation();
Shared.showUpdating('Finalizing&hellip;');
$('#updating').html('Finalizing&hellip;');
document.querySelector('#updating').style.setProperty('display', 'flex');
this.hideGui();
$('#symbol').show();
this.finalizeRound();
await this.finalizeRound();
};
////////////////////////////////////////////////////////////////////////////////
......@@ -173,17 +219,8 @@ LotterySymbolGui.userSelectedFinalize = function (event) {
////////////////////////////////////////////////////////////////////////////////
LotterySymbolGui.setupGui = function () {
$('#representation').click((event) => {
if (!Shared.gui_is_showing) {
Shared.showGui();
}
});
$(document).keydown((event) => {
// 27 is ESC
if (event.keyCode === 27 && Shared.gui_is_showing) {
this.userSelectedCancel(event);
}
});
$('#updating').click(event => event.stopPropagation());
$('#representation').click((event) => this.showGui());
// Arrow functions so 'this' is LotterySymbolGui not the button when called
$('#gui-update-button').click(event => this.userSelectedUpdate(event));
$('#gui-cancel-button').click(event => this.userSelectedCancel(event));
......@@ -191,46 +228,59 @@ LotterySymbolGui.setupGui = function () {
$('#gui-cancel-finalize-button')
.click(event => this.userSelectedCancel(event));
$('#gui-tabs a:first').tab('show');
// Shared assumes a single gas select, so move it between tabs as needed
$('a[aria-controls="enter"]').on('show.bs.tab', () => {
$('#gui-gas-enter').append($('#gui-gas'));
});
$('a[aria-controls="finalize"]').on('show.bs.tab', () => {
$('#gui-gas-finalize').append($('#gui-gas'));
});
$('#symbol-gui-symbol').on('input',function(e) {
// Unicode symbols are one character but n code points. Emoji are two.
// So we allow two characters and truncate if we get more than 1 symbol.
// Note the spread. This is to get the correct count.
while ([...e.target.value].length > 1) {
e.target.value = e.target.value.slice(0, -1);
}
});
};
LotterySymbolGui.initialise = function () {
Shared.init(() => this.guiDisplayHook());
LotterySymbolGui.initialise = async function () {
// 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();
// Shared.setGasAccountChangedCallback(
// () => this.gasAccountChanged(),
// true
// );
LotterySymbol.deployed().then((instance) => {
this.contract = instance;
// Race conditions between this and the event handlers
// SO we always use renderCurrentEntries in the event handlers
// to make sure we render the current state atomically
this.renderCurrentEntries();
this.contract
.SymbolChanged({}, (error) => {
if (!error) {
this.renderCurrentEntries();
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;
});
this.renderCurrentEntries();
this.contract.events.SymbolChanged().on(
'data',
event => {
this.renderCurrentEntries();
this.updateSymbol();
}
);
this.contract.events.NewEntry().on(
'data',
event => this.renderCurrentEntries()
);
this.updateSymbol();
};
$(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