tabbed menu

This commit is contained in:
ntr 2019-09-06 15:13:10 +10:00
parent 7af81c5252
commit a429f21367
16 changed files with 514 additions and 289 deletions

View File

@ -10,6 +10,9 @@
* treats * treats
* constructs jiggle when clicked * constructs jiggle when clicked
* background colour changes depending on time of day * background colour changes depending on time of day
* hit animation wobble
* combat text scale + translate
* susbcriber gold name in instance
* bot game grind * bot game grind
* stress test * stress test

View File

@ -67,6 +67,7 @@ hr {
color: #222; color: #222;
margin: 1.5em 0; margin: 1.5em 0;
width: 100%; width: 100%;
border-top: 1px solid #222;
} }
figure { figure {
@ -287,4 +288,13 @@ header {
nav { nav {
display: none; display: none;
}
ul {
margin-bottom: 1em;
list-style: inside;
}
li {
margin-bottom: 0.5em;
} }

View File

@ -1,7 +1,7 @@
const { connect } = require('preact-redux'); const { connect } = require('preact-redux');
const preact = require('preact'); const preact = require('preact');
const Team = require('./team'); const Collection = require('./collection');
const Header = require('./header'); const Header = require('./header');
const AccountManagement = require('./account.management'); const AccountManagement = require('./account.management');
@ -26,8 +26,8 @@ function Account(args) {
return ( return (
<main class="menu"> <main class="menu">
<Header /> <Header />
<Team />
<AccountManagement /> <AccountManagement />
<Collection />
</main> </main>
); );
} }

View File

@ -0,0 +1,89 @@
const preact = require('preact');
const { connect } = require('preact-redux');
const actions = require('./../actions');
const { COLOURS } = require('./../utils');
const { ConstructAvatar } = require('./construct');
const addState = connect(
function receiveState(state) {
const { ws, constructs, teamPage, teamSelect } = state;
function sendConstructSpawn(name) {
return ws.sendMtxConstructSpawn(name);
}
return {
constructs,
teamPage,
teamSelect,
};
},
function receiveDispatch(dispatch) {
function setTeam(constructIds) {
dispatch(actions.setTeamSelect(constructIds));
}
return {
setTeam,
};
}
);
function Collection(args) {
const {
constructs,
teamPage,
teamSelect,
setTeam,
} = args;
if (!constructs) return <div></div>;
// redux limitation + suggested workaround
// so much for dumb components
function selectConstruct(id) {
// remove
const i = teamSelect.findIndex(sid => sid === id);
if (i > -1) {
teamSelect[i] = null;
return setTeam(teamSelect);
}
// window insert
const insert = teamSelect.findIndex(j => j === null);
if (insert === -1) return setTeam([id, null, null]);
teamSelect[insert] = id;
return setTeam(teamSelect);
}
console.log(constructs.length);
const dispConstructs = constructs.length >= ((teamPage + 1) * 6)
? constructs.slice(teamPage * 6, (teamPage + 1) * 6)
: constructs.slice(teamPage * 6, constructs.length);
const constructPanels = dispConstructs.map(construct => {
const colour = teamSelect.indexOf(construct.id);
const selected = colour > -1;
const borderColour = selected ? COLOURS[colour] : '#000000';
return (
<div
key={construct.id}
class="construct team-select"
style={ { 'border-color': borderColour || 'whitesmoke' } }
onClick={() => selectConstruct(construct.id)} >
<ConstructAvatar construct={construct} />
<h2>{construct.name}</h2>
</div>
);
});
return (
<section class="team">
{constructPanels}
</section>
);
}
module.exports = addState(Collection);

View File

@ -2,19 +2,13 @@ const { connect } = require('preact-redux');
const preact = require('preact'); const preact = require('preact');
const actions = require('../actions'); const actions = require('../actions');
const AccountStatus = require('./account.status');
const addState = connect( const addState = connect(
function receiveState(state) { function receiveState(state) {
const { const {
ws, ws,
account, account,
instances,
team,
nav, nav,
ping,
game,
} = state; } = state;
function sendInstanceState(instance) { function sendInstanceState(instance) {
@ -28,11 +22,8 @@ const addState = connect(
return { return {
account, account,
instances,
team,
game,
ping,
nav, nav,
sendInstanceState, sendInstanceState,
sendAccountStates, sendAccountStates,
}; };
@ -53,29 +44,19 @@ const addState = connect(
return dispatch(actions.setNav(place)); return dispatch(actions.setNav(place));
} }
function hideNav() {
return dispatch(actions.setShowNav(false));
}
return { return {
setNav, setNav,
hideNav,
}; };
} }
); );
function Nav(args) { function Header(args) {
const { const {
account, account,
game,
team,
nav, nav,
sendAccountStates, sendAccountStates,
sendInstanceState,
setNav, setNav,
hideNav,
} = args; } = args;
if (!account) return false; if (!account) return false;
@ -84,12 +65,6 @@ function Nav(args) {
return setNav(p); return setNav(p);
} }
function joinInstance(i) {
sendInstanceState(i);
if (game) navTo('transition');
return true;
}
function accountClick() { function accountClick() {
sendAccountStates(); sendAccountStates();
navTo('account'); navTo('account');
@ -104,8 +79,8 @@ function Nav(args) {
MNML MNML
</button> </button>
<button <button
onClick={() => navTo('news')} onClick={() => navTo('shop')}
class={`login-btn ${nav === 'news' ? 'highlight' : ''}`}> class={`login-btn ${nav === 'shop' ? 'highlight' : ''}`}>
Shop Shop
</button> </button>
<button <button
@ -118,4 +93,4 @@ function Nav(args) {
); );
} }
module.exports = addState(Nav); module.exports = addState(Header);

View File

@ -1,100 +0,0 @@
const { connect } = require('preact-redux');
const { Elements } = require('react-stripe-elements');
const preact = require('preact');
const toast = require('izitoast');
const actions = require('./../actions');
const StripeBtns = require('./stripe.buttons');
const VERSION = process.env.npm_package_version;
const addState = connect(
function receiveState(state) {
const {
ws,
account,
shop,
} = state;
function mtxBuy(mtx) {
return ws.sendMtxBuy(mtx.variant);
}
return {
account,
shop,
mtxBuy,
};
},
function receiveDispatch(dispatch) {
function setMtxActive(mtx) {
dispatch(actions.setConstructRename(null));
dispatch(actions.setMtxActive(mtx));
return true;
}
return {
setMtxActive,
};
}
);
function Inventory(args) {
const {
account,
shop,
setMtxActive,
mtxBuy,
} = args;
if (!shop) return false;
const useMtx = (item, i) => (
<figure key={i} onClick={() => setMtxActive(item)} >
<figcaption>{item}</figcaption>
<button disabled={account.balance === 0}>¤1</button>
</figure>
);
const availableMtx = (item, i) => (
<figure key={i} onClick={() => mtxBuy(item)} >
<figcaption>{item.variant}</figcaption>
<button disabled={account.balance < item.credits}>¤{item.credits}</button>
</figure>
);
return (
<div class="inventory top">
<div class="news">
<h1>v{VERSION}</h1>
<h2>welcome to mnml</h2>
<p>use the buttons on the right to join an instance.</p>
<p>
select <b>PVP</b> to play against other players.<br />
click <b>LEARN</b> to practice the game without time controls.
</p>
<p>
if you enjoy the game please support its development by <b>subscribing</b> or purchasing <b>credits</b>.<br />
glhf
</p>
<p>--ntr & mashy</p>
</div>
<div>
<h1 class="credits">¤ {account.balance}</h1>
<Elements>
<StripeBtns account={account} />
</Elements>
<div class='list'>
{shop.owned.map(useMtx)}
</div>
<div class='list'>
{shop.available.map(availableMtx)}
</div>
</div>
</div>
);
}
module.exports = addState(Inventory);

View File

@ -0,0 +1,30 @@
const { connect } = require('preact-redux');
const preact = require('preact');
const actions = require('./../actions');
const Team = require('./team');
const Collection = require('./collection');
const addState = connect(
function receiveState(state) {
const {
nav,
} = state;
return {
nav,
};
},
);
function Bottom(args) {
const {
nav,
} = args;
if (nav === 'account') return <Collection />;
return <Team />;
}
module.exports = addState(Bottom);

View File

@ -5,14 +5,14 @@ const { connect } = require('preact-redux');
const Welcome = require('./welcome'); const Welcome = require('./welcome');
const Game = require('./game'); const Game = require('./game');
const Instance = require('./instance.component'); const Instance = require('./instance.component');
const Team = require('./team'); const Header = require('./header');
const Play = require('./play'); const Top = require('./main.top');
const Account = require('./account.page'); const Bottom = require('./main.bottom');
const addState = connect( const addState = connect(
state => { state => {
const { game, instance, account, nav, team, constructs } = state; const { game, instance, account, nav } = state;
return { game, instance, account, nav, team, constructs }; return { game, instance, account, nav };
} }
); );
@ -37,12 +37,13 @@ function Main(props) {
} }
if (nav === 'transition') return false; if (nav === 'transition') return false;
if (nav === 'play') return <Play />;
// if (nav === 'team') return <Team />;
if (nav === 'account') return <Account />;
return ( return (
<Play /> <main class="menu">
<Header />
<Top />
<Bottom />
</main>
); );
} }

View File

@ -0,0 +1,34 @@
const { connect } = require('preact-redux');
const preact = require('preact');
const actions = require('./../actions');
const AccountTop = require('./account.top');
const Play = require('./play');
const Shop = require('./shop');
const addState = connect(
function receiveState(state) {
const {
nav,
} = state;
return {
nav,
};
},
);
function Top(args) {
const {
nav,
} = args;
if (nav === 'account') return <AccountTop />;
if (nav === 'play') return <Play />
if (nav === 'shop') return <Shop />
return false;
}
module.exports = addState(Top);

View File

@ -0,0 +1,39 @@
// eslint-disable-next-line
const preact = require('preact');
const { connect } = require('preact-redux');
const Shop = require('./shop');
const Play = require('./play');
const Account = require('./account.page');
const addState = connect(
state => {
const {
account,
nav,
} = state;
return {
account,
nav,
};
}
);
function Menu(props) {
const {
account,
nav,
} = props;
// menu
if (nav === 'play') return <Play />;
if (nav === 'shop') return <Shop />;
if (nav === 'account') return <Account />;
return (
<Play />
);
}
module.exports = addState(Menu);

View File

@ -0,0 +1,31 @@
const preact = require('preact');
const { connect } = require('preact-redux');
const Shop = require('./shop');
const Play = require('./play');
const Account = require('./account.page');
const addState = connect(
state => {
const { game, instance, account, nav } = state;
return { game, instance, account, nav };
}
);
function Menu(props) {
const {
account,
nav,
} = props;
// menu
if (nav === 'play') return <Play />;
if (nav === 'shop') return <Shop />;
if (nav === 'account') return <Account />;
return (
<Play />
);
}
module.exports = addState(Menu);

View File

@ -1,120 +1,128 @@
const { connect } = require('preact-redux'); // const { connect } = require('preact-redux');
const preact = require('preact'); const preact = require('preact');
const { connect } = require('preact-redux');
const { Elements } = require('react-stripe-elements');
const { stringSort } = require('./../utils');
const { ConstructAvatar } = require('./construct');
const actions = require('./../actions');
const Inventory = require('./inventory');
const Header = require('./header'); const Header = require('./header');
const Team = require('./team');
const StripeBtns = require('./stripe.buttons');
const idSort = stringSort('id'); const actions = require('./../actions');
const VERSION = process.env.npm_package_version;
const addState = connect( const addState = connect(
function receiveState(state) { function receiveState(state) {
const { const {
ws, ws,
constructs, account,
constructRename, shop,
team,
mtxActive,
} = state; } = state;
function sendInstancePractice() { function mtxBuy(mtx) {
return ws.sendInstancePractice(); return ws.sendMtxBuy(mtx.variant);
}
function sendConstructAvatarReroll(id) {
console.log('using', mtxActive, 'on', id);
return ws.sendMtxApply(id, mtxActive, '');
}
function sendConstructRename(id, name) {
ws.sendMtxApply(id, 'Rename', name);
} }
return { return {
constructs, account,
mtxActive, shop,
constructRename, mtxBuy,
team,
sendConstructRename,
sendInstancePractice,
sendConstructAvatarReroll,
}; };
}, },
function receiveDispatch(dispatch) { function receiveDispatch(dispatch) {
function setConstructRename(id) { function setMtxActive(mtx) {
dispatch(actions.setConstructRename(id)); dispatch(actions.setConstructRename(null));
dispatch(actions.setMtxActive(mtx));
return true;
} }
function clearMtxRename() { function setNav(place) {
dispatch(actions.setConstructRename(null)); return dispatch(actions.setNav(place));
dispatch(actions.setMtxActive(null));
} }
return { return {
clearMtxRename, setMtxActive,
setConstructRename, setNav,
}; };
} }
); );
function Play(args) { function Play(args) {
const { const {
team, account,
constructRename, shop,
clearMtxRename, mtxBuy,
setConstructRename,
sendConstructRename, setMtxActive,
mtxActive, setNav,
sendConstructAvatarReroll,
} = args; } = args;
const constructPanels = team if (!shop) return false;
.map(construct => {
const constructName = constructRename === construct.id
? <input id='renameInput' type="text" style="text-align: center" placeholder="enter a new name"></input>
: <h2>{construct.name}</h2>;
const confirm = constructRename === construct.id const useMtx = (item, i) => (
? <button onClick={() => sendConstructRename(construct.id, document.getElementById('renameInput').value)}> <figure key={i} onClick={() => setMtxActive(item)} >
Confirm <figcaption>{item}</figcaption>
</button> <button disabled={account.balance === 0}>¤1</button>
: false; </figure>
);
const availableMtx = (item, i) => (
<figure key={i} onClick={() => mtxBuy(item)} >
<figcaption>{item.variant}</figcaption>
<button disabled={account.balance < item.credits}>¤{item.credits}</button>
</figure>
);
const subscription = account.subscribed
? <button
class="stripe-btn"
disabled>
Subscribed
</button>
: <button
onClick={() => setNav('shop')}
class="stripe-btn"
role="link">
Subscribe
</button>;
const cancel = constructRename === construct.id
? <button onClick={() => clearMtxRename()}>
Cancel
</button>
: false;
return (
<div
key={construct.id}
style={ mtxActive ? { cursor: 'pointer' } : {}}
onClick={() => {
if (!mtxActive) return false;
if (mtxActive === 'Rename') return setConstructRename(construct.id);
return sendConstructAvatarReroll(construct.id);
}}
class="construct">
<ConstructAvatar construct={construct} />
{constructName}
{confirm}
{cancel}
</div>
);
});
return ( return (
<main class="menu"> <div class="inventory top">
<Header /> <div class="news">
<Inventory /> <h1>mnml v{VERSION}</h1>
<div class="team"> <p>use the buttons on the right to join an instance.</p>
{constructPanels} <p>
select <b>PVP</b> to play against other players.<br />
click <b>LEARN</b> to practice the game without time controls.
</p>
<p>
if you enjoy the game please support its development by <b>subscribing</b> or purchasing <b>credits</b>.<br />
glhf
</p>
<p>--ntr & mashy</p>
</div> </div>
</main> <div>
<h1 class="credits">¤ {account.balance}</h1>
<div class='list'>
{subscription}
<button
onClick={() => setNav('shop')}
class="stripe-btn"
role="link">
Get Credits
</button>
<div id="error-message"></div>
</div>
<div class='list'>
{shop.owned.map(useMtx)}
</div>
<div class='list'>
{shop.available.map(availableMtx)}
</div>
</div>
</div>
); );
} }

View File

@ -0,0 +1,58 @@
// const { connect } = require('preact-redux');
const preact = require('preact');
const { connect } = require('preact-redux');
const { Elements } = require('react-stripe-elements');
const StripeBtns = require('./stripe.buttons');
const actions = require('./../actions');
const addState = connect(
function receiveState(state) {
const {
ws,
account,
} = state;
return {
account,
};
},
);
function Shop(args) {
const {
account,
} = args;
return (
<div class="inventory top">
<div class="news">
<h1>support the game</h1>
<p>
<b>credits</b> are in game currency that can be used to purchase:
<ul>
<li>img sets</li>
<li>construct renames</li>
<li>new constructs</li>
</ul>
</p>
<p>
<b>subscriptions</b> grant extra benefits:
<ul>
<li>additional credits</li>
<li>chat wheel (soon )</li>
<li>account icons (soon )</li>
</ul>
</p>
</div>
<div>
<h1 class="credits">¤ {account.balance}</h1>
<Elements>
<StripeBtns account={account} />
</Elements>
</div>
</div>
);
}
module.exports = addState(Shop);

View File

@ -6,8 +6,16 @@ function subPlan() {
return 'prod_FWSA8RoyMMV3st'; return 'prod_FWSA8RoyMMV3st';
} }
function bitsSku() { function bitsSku(d) {
if (window.location.host === 'mnml.gg') return 'sku_Fjdu7zOy3sLGc5'; if (window.location.host === 'mnml.gg') {
if (d === 50) return 'sku_Fl5tLCWogUsgus';
if (d === 20) return 'sku_Fl5qegnxYRv7Cy';
if (d === 10) return 'sku_Fl5qVosoDsUVgy';
if (d === 5) return 'sku_Fjdu7zOy3sLGc5';
// !!!!
return 'sku_Fjdu7zOy3sLGc5';
}
return 'sku_FjuNxONdWewjH2'; return 'sku_FjuNxONdWewjH2';
} }
@ -16,6 +24,7 @@ function BitsBtn(args) {
stripe, stripe,
account, account,
} = args; } = args;
function subscribeClick() { function subscribeClick() {
stripe.redirectToCheckout({ stripe.redirectToCheckout({
items: [{ plan: subPlan(), quantity: 1 }], items: [{ plan: subPlan(), quantity: 1 }],
@ -25,9 +34,9 @@ function BitsBtn(args) {
}); });
} }
function bitsClick() { function bitsClick(d) {
stripe.redirectToCheckout({ stripe.redirectToCheckout({
items: [{ sku: bitsSku(), quantity: 1 }], items: [{ sku: bitsSku(d), quantity: 1 }],
successUrl: window.location.origin, successUrl: window.location.origin,
cancelUrl: window.location.origin, cancelUrl: window.location.origin,
clientReferenceId: account.id, clientReferenceId: account.id,
@ -48,15 +57,29 @@ function BitsBtn(args) {
</button>; </button>;
return ( return (
<div class='list'> <div>
{subscription} <div class='list'>
<button {subscription}
onClick={bitsClick} </div>
class="stripe-btn" <div class='list'>
role="link"> <figure onClick={() => bitsClick(5)} >
Get Credits <figcaption>$5 AUD</figcaption>
</button> <button class="stripe-btn">¤50</button>
<div id="error-message"></div> </figure>
<figure onClick={() => bitsClick(10)} >
<figcaption>$10 AUD</figcaption>
<button class="stripe-btn">¤110</button>
</figure>
<figure onClick={() => bitsClick(20)} >
<figcaption>$20 AUD</figcaption>
<button class="stripe-btn">¤250</button>
</figure>
<figure onClick={() => bitsClick(50)} >
<figcaption>$50 AUD</figcaption>
<button class="stripe-btn">¤660</button>
</figure>
<div id="error-message"></div>
</div>
</div> </div>
); );
} }

View File

@ -1,88 +1,112 @@
const preact = require('preact');
const { connect } = require('preact-redux'); const { connect } = require('preact-redux');
const preact = require('preact');
const actions = require('./../actions');
const { COLOURS } = require('./../utils');
const { ConstructAvatar } = require('./construct'); const { ConstructAvatar } = require('./construct');
const actions = require('./../actions');
const addState = connect( const addState = connect(
function receiveState(state) { function receiveState(state) {
const { ws, constructs, teamPage, teamSelect } = state; const {
ws,
constructs,
constructRename,
team,
mtxActive,
} = state;
function sendConstructSpawn(name) { function sendInstancePractice() {
return ws.sendMtxConstructSpawn(name); return ws.sendInstancePractice();
}
function sendConstructAvatarReroll(id) {
console.log('using', mtxActive, 'on', id);
return ws.sendMtxApply(id, mtxActive, '');
}
function sendConstructRename(id, name) {
ws.sendMtxApply(id, 'Rename', name);
} }
return { return {
constructs, constructs,
teamPage, mtxActive,
teamSelect, constructRename,
team,
sendConstructRename,
sendInstancePractice,
sendConstructAvatarReroll,
}; };
}, },
function receiveDispatch(dispatch) { function receiveDispatch(dispatch) {
function setTeam(constructIds) { function setConstructRename(id) {
dispatch(actions.setTeamSelect(constructIds)); dispatch(actions.setConstructRename(id));
}
function clearMtxRename() {
dispatch(actions.setConstructRename(null));
dispatch(actions.setMtxActive(null));
} }
return { return {
setTeam, clearMtxRename,
setConstructRename,
}; };
} }
); );
function Team(args) { function Team(args) {
const { const {
constructs, team,
teamPage, constructRename,
teamSelect, clearMtxRename,
setTeam, setConstructRename,
sendConstructRename,
mtxActive,
sendConstructAvatarReroll,
} = args; } = args;
if (!constructs) return <div></div>; const constructPanels = team
.map(construct => {
const constructName = constructRename === construct.id
? <input id='renameInput' type="text" style="text-align: center" placeholder="enter a new name"></input>
: <h2>{construct.name}</h2>;
// redux limitation + suggested workaround const confirm = constructRename === construct.id
// so much for dumb components ? <button onClick={() => sendConstructRename(construct.id, document.getElementById('renameInput').value)}>
function selectConstruct(id) { Confirm
// remove </button>
const i = teamSelect.findIndex(sid => sid === id); : false;
if (i > -1) {
teamSelect[i] = null;
return setTeam(teamSelect);
}
// window insert const cancel = constructRename === construct.id
const insert = teamSelect.findIndex(j => j === null); ? <button onClick={() => clearMtxRename()}>
if (insert === -1) return setTeam([id, null, null]); Cancel
teamSelect[insert] = id; </button>
return setTeam(teamSelect); : false;
}
console.log(constructs.length);
const dispConstructs = constructs.length >= ((teamPage + 1) * 6)
? constructs.slice(teamPage * 6, (teamPage + 1) * 6)
: constructs.slice(teamPage * 6, constructs.length);
const constructPanels = dispConstructs.map(construct => { return (
const colour = teamSelect.indexOf(construct.id); <div
const selected = colour > -1; key={construct.id}
style={ mtxActive ? { cursor: 'pointer' } : {}}
const borderColour = selected ? COLOURS[colour] : '#000000'; onClick={() => {
if (!mtxActive) return false;
return ( if (mtxActive === 'Rename') return setConstructRename(construct.id);
<div return sendConstructAvatarReroll(construct.id);
key={construct.id} }}
class="construct team-select" class="construct">
style={ { 'border-color': borderColour || 'whitesmoke' } } <ConstructAvatar construct={construct} />
onClick={() => selectConstruct(construct.id)} > {constructName}
<ConstructAvatar construct={construct} /> {confirm}
<h2>{construct.name}</h2> {cancel}
</div> </div>
); );
}); });
return ( return (
<section class="team"> <div class="team">
{constructPanels} {constructPanels}
</section> </div>
); );
} }