Remove jQuery (#299)

* Remove jQuery

* Remove browser (vendor) prefixes for some CSS properties

* Use simple device orientation test

* Realign D-pad pointers

* Make GameBoy text unselectable

* Cleanup console.log

* Keep 90% size on mobile browsers due to gh-ribbon overlap

* Remove legacy `unselectable="on"` attributes

* Align UI buttons

* Remove not used UI elements

* Change Options button

* Don't show player change message when not in a game

* Add click/touch handler for circle-pad
This commit is contained in:
sergystepanov 2021-03-26 11:07:49 +03:00 committed by Sergey Stepanov
parent c7e67d5c3e
commit 7ebd945344
No known key found for this signature in database
GPG key ID: A56B4929BAA8556B
69 changed files with 423 additions and 309 deletions

243
web/css/main.css vendored
View file

@ -1,3 +1,9 @@
html {
/* force full size for Firefox */
width: 100%;
height: 100%;
}
body {
background-image: url('/static/img/background.jpg');
background-repeat: repeat;
@ -76,7 +82,7 @@ body {
}
.dpad-empty {
background-image: url('/static/img/ui/bt MOVE EMPTY.png') !important;
background-image: url('/static/img/ui/bt MOVE EMPTY.png') !important;
}
#guide-txt {
@ -86,6 +92,8 @@ body {
left: 30px;
width: 1000px;
position: absolute;
user-select: none;
}
#circle-pad {
@ -97,14 +105,16 @@ body {
top: 15px;
left: 15px;
pointer-events: none;
opacity: 0.5;
background-image: url('/static/img/ui/bong.png');
}
.bong-full {
opacity: 1.0 !important;
background-image: url('/static/img/ui/bong full.png') !important;
opacity: 1.0 !important;
background-image: url('/static/img/ui/bong full.png') !important;
}
.dpad {
@ -318,6 +328,20 @@ body {
background-image: url('/static/img/ui/bt Y.png');
}
#btn-settings {
width: 65px;
height: 21px;
top: 9%;
left: 55px;
padding: 0;
transform: translateY(-50%);
background-image: url('/static/img/ui/bt OPTIONS.png');
opacity: .7;
}
#lights-holder {
display: block;
@ -390,12 +414,12 @@ body {
.pressed {
filter: brightness(50%);
filter: brightness(50%);
}
.dpad-pressed {
background-color: #333 !important;
opacity: 0.75;
background-color: #333 !important;
opacity: 0.75;
}
#bottom-screen {
@ -458,7 +482,6 @@ body {
}
@font-face {
font-family: 'Roboto'; /*a name to be used later*/
src: url('/static/fonts/Roboto-Regular.ttf'); /*URL to font*/
@ -514,34 +537,64 @@ body {
@-moz-keyframes horizontally {
0% { transform: translateX(0%); }
25% { transform: translateX(-20%); }
50% { transform: translateX(0%); }
75% { transform: translateX(20%); }
100% { transform: translateX(0%); }
}
@-webkit-keyframes horizontally {
0% { transform: translateX(0%); }
25% { transform: translateX(-20%); }
50% { transform: translateX(0%); }
75% { transform: translateX(20%); }
100% { transform: translateX(0%); }
}
@keyframes horizontally {
0% { transform: translateX(0%); }
25% { transform: translateX(-20%); }
50% { transform: translateX(0%); }
75% { transform: translateX(20%); }
100% { transform: translateX(0%); }
0% {
transform: translateX(0%);
}
25% {
transform: translateX(-20%);
}
50% {
transform: translateX(0%);
}
75% {
transform: translateX(20%);
}
100% {
transform: translateX(0%);
}
}
@-webkit-keyframes horizontally {
0% {
transform: translateX(0%);
}
25% {
transform: translateX(-20%);
}
50% {
transform: translateX(0%);
}
75% {
transform: translateX(20%);
}
100% {
transform: translateX(0%);
}
}
@keyframes horizontally {
0% {
transform: translateX(0%);
}
25% {
transform: translateX(-20%);
}
50% {
transform: translateX(0%);
}
75% {
transform: translateX(20%);
}
100% {
transform: translateX(0%);
}
}
#noti-box {
display: none;
padding: 5px 10px 0 10px;
height: 20px;
background-color: #ddd;
opacity: 0.8;
position: absolute;
border: 5px #000;
@ -566,60 +619,62 @@ body {
top: 90px;
left: 20px;
width: 100px;
user-select: none;
}
/* The slider itself */
.slider {
-webkit-appearance: none; /* Override default CSS styles */
appearance: none;
width: 100%; /* Full-width */
height: 25px; /* Specified height */
outline: none; /* Remove outline */
/*opacity: 0.7; [> Set transparency (for mouse-over effects on hover) <]*/
-webkit-transition: .2s; /* 0.2 seconds transition on hover */
transition: opacity .2s;
-webkit-appearance: none; /* Override default CSS styles */
appearance: none;
width: 100%; /* Full-width */
height: 25px; /* Specified height */
outline: none; /* Remove outline */
/*opacity: 0.7; [> Set transparency (for mouse-over effects on hover) <]*/
-webkit-transition: .2s; /* 0.2 seconds transition on hover */
transition: opacity .2s;
background-color: transparent;
background-repeat: no-repeat;
background-size: contain;
background-image: url('/static/img/ui/FramePlayerIndex.png');
background-color: transparent;
background-repeat: no-repeat;
background-size: contain;
background-image: url('/static/img/ui/FramePlayerIndex.png');
}
/* Mouse-over effects */
.slider:hover {
opacity: 1; /* Fully shown on mouse-over */
opacity: 1; /* Fully shown on mouse-over */
}
/* The slider handle (use -webkit- (Chrome, Opera, Safari, Edge) and -moz- (Firefox) to override default look) */
.slider::-webkit-slider-thumb {
-webkit-appearance: none; /* Override default look */
appearance: none;
width: 25px; /* Set a specific slider handle width */
height: 25px; /* Slider handle height */
cursor: pointer; /* Cursor on hover */
-webkit-appearance: none; /* Override default look */
appearance: none;
width: 25px; /* Set a specific slider handle width */
height: 25px; /* Slider handle height */
cursor: pointer; /* Cursor on hover */
background-color: transparent;
background-repeat: no-repeat;
background-size: contain;
background-image: url('/static/img/ui/bt PlayerIndex.png');
background-color: transparent;
background-repeat: no-repeat;
background-size: contain;
background-image: url('/static/img/ui/bt PlayerIndex.png');
}
.slider::-moz-range-thumb {
appearance: none;
width: 25px; /* Set a specific slider handle width */
height: 25px; /* Slider handle height */
cursor: pointer; /* Cursor on hover */
appearance: none;
width: 25px; /* Set a specific slider handle width */
height: 25px; /* Slider handle height */
cursor: pointer; /* Cursor on hover */
border: 0px;
background-color: transparent;
background-repeat: no-repeat;
background-size: contain;
background-image: url('/static/img/ui/bt PlayerIndex.png');
border: 0px;
background-color: transparent;
background-repeat: no-repeat;
background-size: contain;
background-image: url('/static/img/ui/bt PlayerIndex.png');
}
* {
touch-action: manipulation;
}
touch-action: manipulation;
}
#stats-overlay {
position: absolute;
@ -653,7 +708,7 @@ body {
margin-bottom: 1em;
}
#stats-overlay>div>div {
#stats-overlay > div > div {
display: inline-block;
font-weight: 500;
width: 4em;
@ -675,53 +730,53 @@ body {
}
.dpad-toggle-label {
position: relative;
display: inline-block;
width: 35px;
height: 20px;
position: relative;
display: inline-block;
width: 35px;
height: 20px;
top: 44px;
left: 85px;
top: 44px;
left: 85px;
}
.dpad-toggle-label input {
opacity: 0;
width: 0;
height: 0;
opacity: 0;
width: 0;
height: 0;
}
.dpad-toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #515151;
-webkit-transition: .4s;
transition: .4s;
border-radius: 20px;
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #515151;
-webkit-transition: .4s;
transition: .4s;
border-radius: 20px;
}
.dpad-toggle-slider:before {
position: absolute;
content: "";
height: 14px;
width: 14px;
left: 3px;
bottom: 3px;
background-color: #5f5f5f;
-webkit-transition: .4s;
transition: .4s;
border-radius: 50%;
position: absolute;
content: "";
height: 14px;
width: 14px;
left: 3px;
bottom: 3px;
background-color: #5f5f5f;
-webkit-transition: .4s;
transition: .4s;
border-radius: 50%;
}
input:checked + .dpad-toggle-slider {
background-color: #515151;
background-color: #515151;
}
input:checked + .dpad-toggle-slider:before {
-webkit-transform: translateX(15px);
-ms-transform: translateX(15px);
transform: translateX(15px);
-webkit-transform: translateX(15px);
-ms-transform: translateX(15px);
transform: translateX(15px);
}

5
web/css/ui.css vendored
View file

@ -3,11 +3,6 @@
display: none;
}
#btn-settings {
top: 14px;
left: 70px;
}
.modal-window {
position: fixed;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,006 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

BIN
web/img/darkui/Ui4.jpg vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

BIN
web/img/darkui/bg.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 864 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

BIN
web/img/ui/Help-2.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,006 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

BIN
web/img/ui/Ui.jpg vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

BIN
web/img/ui/bong-2.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

BIN
web/img/ui/bt A-2.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

BIN
web/img/ui/bt B-2.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

BIN
web/img/ui/bt B.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

BIN
web/img/ui/bt MOVE.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

BIN
web/img/ui/bt OPTIONS.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

BIN
web/img/ui/bt X-2.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

BIN
web/img/ui/bt X.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 5 KiB

Before After
Before After

BIN
web/img/ui/bt Y-2.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

BIN
web/img/ui/bt Y.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Before After
Before After

39
web/index.html vendored
View file

@ -49,45 +49,43 @@
<div id="guide-txt"><b>Arrows</b>(move),<b>ZXCVAS</b>(game ABXYLR),<b>1/2</b>(1st/2nd player),<b>Shift/Enter/K/L</b>(select/start/save/load),<b>F</b>(fullscreen),<b>share</b>(copy
sharelink to clipboard)
</div>
<div id="btn-load" unselectable="on" class="btn big unselectable" value="load"></div>
<div id="btn-save" unselectable="on" class="btn big unselectable" value="save"></div>
<div id="btn-join" unselectable="on" class="btn big unselectable" value="join"></div>
<div id="btn-load" class="btn big unselectable" value="load"></div>
<div id="btn-save" class="btn big unselectable" value="save"></div>
<div id="btn-join" class="btn big unselectable" value="join"></div>
<div id="slider-playeridx" class="slidecontainer">
player choice
<input type="range" min="1" max="4" value="1" class="slider" id="playeridx" onkeydown="event.preventDefault()">
</div>
<div id="btn-quit" unselectable="on" class="btn big unselectable" value="quit"></div>
<div id="btn-select" unselectable="on" class="btn big unselectable" value="select"></div>
<div id="btn-start" unselectable="on" class="btn big unselectable" value="start"></div>
<div id="btn-quit" class="btn big unselectable" value="quit"></div>
<div id="btn-select" class="btn big unselectable" value="select"></div>
<div id="btn-start" class="btn big unselectable" value="start"></div>
<div id="color-button-holder">
<div id="btn-a" unselectable="on" class="btn unselectable" value="a"></div>
<div id="btn-b" unselectable="on" class="btn unselectable" value="b"></div>
<div id="btn-x" unselectable="on" class="btn unselectable" value="x"></div>
<div id="btn-y" unselectable="on" class="btn unselectable" value="y"></div>
<div id="btn-a" class="btn unselectable" value="a"></div>
<div id="btn-b" class="btn unselectable" value="b"></div>
<div id="btn-x" class="btn unselectable" value="x"></div>
<div id="btn-y" class="btn unselectable" value="y"></div>
</div>
<div id="btn-settings" class="btn unselectable" value="settings"></div>
<!-- TODO: remove -->
<input id="room-txt" type="text" placeholder="room id..." unselectable="on" class=" unselectable" disabled>
<input id="room-txt" type="text" placeholder="room id..." class=" unselectable" disabled>
<label class="dpad-toggle-label" title="D-pad toggle">
<input type="checkbox" id="dpad-toggle" checked>
<span class="dpad-toggle-slider"></span>
</label>
<div id="noti-box" unselectable="on" class="unselectable">Oh my god</div>
<div id="noti-box" class="unselectable">Oh my god</div>
<div id="help-overlay">
<div id="help-overlay-background"></div>
<div id="help-overlay-detail"></div>
</div>
<div id="btn-help" unselectable="on" class="btn unselectable" value="help"></div>
<div id="btn-settings" class="btn btn2 unselectable" value="settings">
Settings
</div>
<div id="btn-help" class="btn unselectable" value="help"></div>
</div>
<div id="app-settings" class="modal-window">
@ -98,7 +96,7 @@
<span title="Reset" id="settings__controls__reset" class="semi-button"></span>
<span title="Close" id="settings__controls__close" class="semi-button">X</span>
</div>
<h1>Settings</h1>
<h1>Options</h1>
<div id="settings-data"></div>
<div>
* -- applied after application restart
@ -111,11 +109,10 @@
src="https://github.blog/wp-content/uploads/2008/12/forkme_right_gray_6d6d6d.png?resize=149%2C149"
class="attachment-full size-full" alt="Fork me on GitHub" data-recalc-dims="1"></a>
<script src="/static/js/lib/jquery-3.4.1.min.js"></script>
<script src="/static/js/gui/gui.js?v=1"></script>
<script src="/static/js/log.js?v=5"></script>
<script src="/static/js/utils.js?v1"></script>
<script src="/static/js/gui/message.js?v=1"></script>
<script src="/static/js/log.js?v=5"></script>
<script src="/static/js/event/event.js?v=5"></script>
<script src="/static/js/input/keys.js?v=3"></script>
<script src="/static/js/settings/opts.js?v=1"></script>

76
web/js/controller.js vendored
View file

@ -19,15 +19,14 @@
})();
let prevDir = DIR.IDLE;
const menuScreen = $('#menu-screen');
const helpOverlay = $('#help-overlay');
const popupBox = $('#noti-box');
const menuScreen = document.getElementById('menu-screen');
const helpOverlay = document.getElementById('help-overlay');
const playerIndex = document.getElementById('playeridx');
// keymap
const keyButtons = {};
Object.keys(KEY).forEach(button => {
keyButtons[KEY[button]] = $(`#btn-${KEY[button]}`);
keyButtons[KEY[button]] = document.getElementById(`btn-${KEY[button]}`);
});
/**
@ -61,7 +60,7 @@
};
const onGameRoomAvailable = () => {
popup('Now you can share you game!');
message.show('Now you can share you game!');
};
const onConnectionReady = () => {
@ -74,7 +73,7 @@
};
const onLatencyCheck = (data) => {
popup('Connecting to fastest server...');
message.show('Connecting to fastest server...');
const timeoutMs = 1111;
// deduplicate
const addresses = [...new Set(data.addresses || [])];
@ -102,13 +101,13 @@
if (isGameScreen) {
stream.toggle(!show);
} else {
menuScreen.toggle(!show);
gui.toggle(menuScreen, !show);
}
keyButtons[KEY.SAVE].toggle(show || isGameScreen);
keyButtons[KEY.LOAD].toggle(show || isGameScreen);
gui.toggle(keyButtons[KEY.SAVE], show || isGameScreen);
gui.toggle(keyButtons[KEY.LOAD], show || isGameScreen);
helpOverlay.toggle(show);
gui.toggle(helpOverlay, show);
this.shown = show;
@ -120,23 +119,23 @@
log.debug('[control] loading menu screen');
stream.toggle(false);
keyButtons[KEY.SAVE].hide();
keyButtons[KEY.LOAD].hide();
gui.hide(keyButtons[KEY.SAVE]);
gui.hide(keyButtons[KEY.LOAD]);
gameList.show();
menuScreen.show();
gui.show(menuScreen);
setState(app.state.menu);
};
const startGame = () => {
if (!rtcp.isConnected()) {
popup('Game cannot load. Please refresh');
message.show('Game cannot load. Please refresh');
return;
}
if (!rtcp.isInputReady()) {
popup('Game is not ready yet. Please wait');
message.show('Game is not ready yet. Please wait');
return;
}
@ -155,10 +154,10 @@
// clear menu screen
input.poll().disable();
menuScreen.hide();
gui.hide(menuScreen);
stream.toggle(true);
keyButtons[KEY.SAVE].show();
keyButtons[KEY.LOAD].show();
gui.show(keyButtons[KEY.SAVE]);
gui.show(keyButtons[KEY.LOAD]);
// end clear
input.poll().enable();
};
@ -166,9 +165,6 @@
const saveGame = utils.debounce(socket.saveGame, 1000);
const loadGame = utils.debounce(socket.loadGame, 1000);
const _popup = (message) => popupBox.html(message).fadeIn().fadeOut();
const popup = utils.throttle(_popup, 1000);
const _dpadArrowKeys = [KEY.UP, KEY.DOWN, KEY.LEFT, KEY.RIGHT];
// pre-state key press handler
@ -176,9 +172,9 @@
const button = keyButtons[data.key];
if (_dpadArrowKeys.includes(data.key)) {
button.addClass('dpad-pressed');
button.classList.add('dpad-pressed');
} else {
if (button) button.addClass('pressed');
if (button) button.classList.add('pressed');
}
if (state !== app.state.settings) {
@ -192,12 +188,10 @@
const onKeyRelease = data => {
const button = keyButtons[data.key];
if (!button) return;
if (_dpadArrowKeys.includes(data.key)) {
button.removeClass('dpad-pressed');
button.classList.remove('dpad-pressed');
} else {
if (button) button.removeClass('pressed');
if (button) button.classList.remove('pressed');
}
if (state !== app.state.settings) {
@ -314,13 +308,13 @@
startGame();
break;
case KEY.QUIT:
popup('You are already in menu screen!');
message.show('You are already in menu screen!');
break;
case KEY.LOAD:
popup('Loading the game.');
message.show('Loading the game.');
break;
case KEY.SAVE:
popup('Saving the game.');
message.show('Saving the game.');
break;
case KEY.STATS:
event.pub(STATS_TOGGLE);
@ -351,7 +345,7 @@
// save when click share
saveGame();
room.copyToClipboard();
popup('Shared link copied to the clipboard!');
message.show('Shared link copied to the clipboard!');
break;
case KEY.SAVE:
saveGame();
@ -390,7 +384,7 @@
socket.quitGame(room.getId());
room.reset();
popup('Quit!');
message.show('Quit!');
window.location = window.location.pathname;
break;
@ -410,9 +404,11 @@
// subscriptions
event.sub(GAME_ROOM_AVAILABLE, onGameRoomAvailable, 2);
event.sub(GAME_SAVED, () => popup('Saved'));
event.sub(GAME_LOADED, () => popup('Loaded'));
event.sub(GAME_PLAYER_IDX, idx => popup(+idx + 1));
event.sub(GAME_SAVED, () => message.show('Saved'));
event.sub(GAME_LOADED, () => message.show('Loaded'));
event.sub(GAME_PLAYER_IDX, idx => {
if (!isNaN(+idx)) message.show(+idx + 1);
});
event.sub(MEDIA_STREAM_INITIALIZED, (data) => {
rtcp.start(data.stunturn);
@ -428,16 +424,16 @@
socket.abort();
});
event.sub(LATENCY_CHECK_REQUESTED, onLatencyCheck);
event.sub(GAMEPAD_CONNECTED, () => popup('Gamepad connected'));
event.sub(GAMEPAD_DISCONNECTED, () => popup('Gamepad disconnected'));
event.sub(GAMEPAD_CONNECTED, () => message.show('Gamepad connected'));
event.sub(GAMEPAD_DISCONNECTED, () => message.show('Gamepad disconnected'));
// touch stuff
event.sub(MENU_HANDLER_ATTACHED, (data) => {
menuScreen.on(data.event, data.handler);
menuScreen.addEventListener(data.event, data.handler);
});
event.sub(KEY_PRESSED, onKeyPress);
event.sub(KEY_RELEASED, onKeyRelease);
event.sub(KEY_STATE_UPDATED, data => rtcp.input(data));
event.sub(SETTINGS_CHANGED, () => popup('Settings have been updated'));
event.sub(SETTINGS_CHANGED, () => message.show('Settings have been updated'));
event.sub(SETTINGS_CLOSED, () => {
state.keyRelease(KEY.SETTINGS);
});
@ -446,4 +442,4 @@
// initial app state
setState(app.state.eden);
})($, document, event, env, gameList, input, KEY, log, room, settings, socket, stats, stream, utils);
})(document, event, env, gameList, input, KEY, log, message, room, settings, socket, stats, stream, utils);

68
web/js/env.js vendored
View file

@ -1,57 +1,41 @@
const env = (() => {
// UI
const doc = $(document);
const gameBoy = $('#gamebody');
const ghRibbon = $('#ribbon');
const page = document.getElementsByTagName('html')[0];
const gameBoy = document.getElementById('gamebody');
const ghRibbon = document.getElementById('ribbon');
let isLayoutSwitched = false;
// Window rerender / rotate screen if needed
const fixScreenLayout = () => {
let targetWidth = Math.round(doc.width() * 0.9 / 2) * 2,
targetHeight = Math.round(doc.height() * 0.9 / 2) * 2;
let pw = getWidth(page),
ph = getHeight(page),
targetWidth = Math.round(pw * 0.9 / 2) * 2,
targetHeight = Math.round(ph * 0.9 / 2) * 2;
// mobile == full screen
if (env.getOs() === 'android') {
targetWidth = doc.width();
targetHeight = doc.height();
}
// Should have maximum box for desktop?
// targetWidth = 800; targetHeight = 600; // test on desktop
// save page rotation
isLayoutSwitched = isPortrait();
rescaleGameBoy(targetWidth, targetHeight);
let st = isLayoutSwitched ? 'rotate(90deg)' : '';
ghRibbon.css({
'bottom': isLayoutSwitched ? 0 : '',
'top': isLayoutSwitched ? '' : 0,
'transform': st,
'-webkit-transform': st,
'-moz-transform': st
})
ghRibbon.style['bottom'] = isLayoutSwitched ? 0 : '';
ghRibbon.style['top'] = isLayoutSwitched ? '' : 0;
ghRibbon.style['transform'] = isLayoutSwitched ? 'rotate(90deg)' : '';
};
const rescaleGameBoy = (targetWidth, targetHeight) => {
const transformations = ['translate(-50%, -50%)'];
// rotate portrait layout
isLayoutSwitched = isPortrait();
if (isLayoutSwitched) {
transformations.push('rotate(90deg)');
[targetWidth, targetHeight] = [targetHeight, targetWidth]
}
// scale, fit to target size
const scale = Math.min(targetWidth / gameBoy.width(), targetHeight / gameBoy.height());
const scale = Math.min(targetWidth / getWidth(gameBoy), targetHeight / getHeight(gameBoy));
transformations.push(`scale(${scale})`);
const transform = transformations.join(' ');
gameBoy.css({
'transform': transform,
'-webkit-transform': transform,
'-moz-transform': transform
});
gameBoy.style['transform'] = transformations.join(' ');
}
const getOS = () => {
@ -74,19 +58,7 @@ const env = (() => {
return browserName;
};
// !to use more sophisticated approach / lib
const isPortrait = () => {
// ios / mobile app
switch (window.orientation) {
case 0:
case 180:
return true;
}
// desktop
const orientation = screen.msOrientation || screen.mozOrientation || (screen.orientation || {}).type;
return orientation === 'portrait-primary';
};
const isPortrait = () => getWidth(page) < getHeight(page);
const toggleFullscreen = (enable, element) => {
const el = enable ? element : document;
@ -114,6 +86,14 @@ const env = (() => {
}
};
function getHeight(el) {
return parseFloat(getComputedStyle(el, null).height.replace("px", ""));
}
function getWidth(el) {
return parseFloat(getComputedStyle(el, null).width.replace("px", ""));
}
window.addEventListener('resize', fixScreenLayout);
window.addEventListener('orientationchange', fixScreenLayout);
document.addEventListener('DOMContentLoaded', () => fixScreenLayout(), false);
@ -130,4 +110,4 @@ const env = (() => {
isLayoutSwitched: isLayoutSwitched
})
}
})($, document, log, navigator, screen, window);
})(document, log, navigator, screen, window);

39
web/js/gameList.js vendored
View file

@ -9,8 +9,8 @@ const gameList = (() => {
let gamePickTimer = null;
// UI
const listBox = $('#menu-container');
const menuItemChoice = $('#menu-item-choice');
const listBox = document.getElementById('menu-container');
const menuItemChoice = document.getElementById('menu-item-choice');
const MENU_TOP_POSITION = 102;
let menuTop = MENU_TOP_POSITION;
@ -22,22 +22,17 @@ const gameList = (() => {
const render = () => {
log.debug('[games] load game menu');
listBox.html(games
.map(game => `<div class="menu-item unselectable" unselectable="on"><div><span>${game}</span></div></div>`)
.join('')
);
listBox.innerHTML = games
.map(game => `<div class="menu-item unselectable"><div><span>${game}</span></div></div>`)
.join('');
};
const show = () => {
render();
menuItemChoice.show();
menuItemChoice.style.display = "block";
pickGame();
};
const hide = () => {
menuItemChoice.hide();
};
const pickGame = (index) => {
let idx = undefined !== index ? index : gameIndex;
@ -47,16 +42,17 @@ const gameList = (() => {
if (idx >= games.length) idx = 0;
// transition menu box
listBox.css('transition', 'top 0.2s');
listBox.css('-moz-transition', 'top 0.2s');
listBox.css('-webkit-transition', 'top 0.2s');
listBox.style['transition'] = 'top 0.2s';
menuTop = MENU_TOP_POSITION - idx * 36;
listBox.css('top', `${menuTop}px`);
listBox.style['top'] = `${menuTop}px`;
// overflow marquee
$('.menu-item .pick').removeClass('pick');
$(`.menu-item:eq(${idx}) span`).addClass('pick');
let pick = document.querySelectorAll('.menu-item .pick')[0];
if (pick) {
pick.classList.remove('pick');
}
document.querySelectorAll(`.menu-item span`)[idx].classList.add('pick');
gameIndex = idx;
};
@ -80,10 +76,8 @@ const gameList = (() => {
};
const onMenuPressed = (newPosition) => {
listBox.css('transition', '');
listBox.css('-moz-transition', '');
listBox.css('-webkit-transition', '');
listBox.css('top', `${menuTop - newPosition}px`);
listBox.style['transition'] = '';
listBox.style['top'] = `${menuTop - newPosition}px`;
};
const onMenuReleased = (position) => {
@ -100,8 +94,7 @@ const gameList = (() => {
stopGamePickerTimer: stopGamePickerTimer,
pickGame: pickGame,
show: show,
hide: hide,
set: setGames,
getCurrentGame: () => games[gameIndex]
}
})($, event, log);
})(document, event, log);

61
web/js/gui/gui.js vendored
View file

@ -56,9 +56,70 @@ const gui = (() => {
return el;
}
const show = (el) => {
el.style.display = 'block';
}
const hide = (el) => {
el.style.display = 'none';
}
const toggle = (el, what) => {
if (what) {
show(el)
} else {
hide(el)
}
}
const fadeIn = async (el, speed = .1) => {
el.style.opacity = '0';
el.style.display = 'block';
return new Promise((done) => (function fade() {
let val = parseFloat(el.style.opacity);
const proceed = ((val += 0.1) <= 1);
if (proceed) {
el.style.opacity = '' + val;
requestAnimationFrame(fade);
} else {
done();
}
})()
);
}
const fadeOut = async (el, speed = .1) => {
el.style.opacity = '1';
return new Promise((done) => (function fade() {
if ((el.style.opacity -= speed) < 0) {
el.style.display = "none";
done();
} else {
requestAnimationFrame(fade);
}
})()
)
}
const sleep = async (ms) => new Promise(resolve => setTimeout(resolve, ms));
const fadeInOut = async (el, wait = 1000, speed = .1) => {
await fadeIn(el, speed)
await sleep(wait);
await fadeOut(el, speed)
}
return {
anim: {
fadeIn,
fadeOut,
fadeInOut,
},
create: _create,
select,
binding,
show,
hide,
toggle,
}
})(document);

46
web/js/gui/message.js vendored Normal file
View file

@ -0,0 +1,46 @@
/**
* App UI message module.
*
* @version 1
*/
const message = (() => {
const popupBox = document.getElementById('noti-box');
// fifo queue
let queue = [];
const queueMaxSize = 5;
let isScreenFree = true;
const _popup = () => {
// recursion edge case:
// no messages in the queue or one on the screen
if (!(queue.length > 0 && isScreenFree)) {
return;
}
isScreenFree = false;
popupBox.innerText = queue.shift();
gui.anim.fadeInOut(popupBox, 1000, .05).finally(() => {
isScreenFree = true;
_popup();
})
}
const _storeMessage = (text) => {
if (queue.length <= queueMaxSize) {
queue.push(text);
}
}
const _proceed = (text) => {
_storeMessage(text);
_popup();
}
const show = utils.throttle(_proceed, 1000);
return Object.freeze({
show: show
})
})(document, gui, utils);

139
web/js/input/touch.js vendored
View file

@ -16,37 +16,36 @@ const touch = (() => {
let vpadTouchIdx = null;
let vpadTouchDrag = null;
let vpadHolder = $("#circle-pad-holder");
let vpadCircle = $("#circle-pad");
let vpadHolder = document.getElementById('circle-pad-holder');
let vpadCircle = document.getElementById('circle-pad');
const window_ = $(window);
const buttons = $(".btn");
const playerSlider = $("#playeridx")
const dpad = $(".dpad");
const buttons = Array.from(document.getElementsByClassName('btn'));
const playerSlider = document.getElementById('playeridx');
const dpad = Array.from(document.getElementsByClassName('dpad'));
const dpadToggle = document.getElementById('dpad-toggle')
dpadToggle.addEventListener('change', (e) => {
event.pub(DPAD_TOGGLE, {checked: e.target.checked});
event.pub(DPAD_TOGGLE, {checked: e.target.checked});
});
let dpadMode = true;
const deadZone = 0.1;
function onDpadToggle(checked) {
if (dpadMode === checked) {
return //error?
}
if (dpadMode) {
dpadMode = false;
vpadHolder.addClass('dpad-empty');
vpadCircle.addClass('bong-full');
// reset dpad keys pressed before moving to analog stick mode
resetVpadState()
} else {
dpadMode = true;
vpadHolder.removeClass('dpad-empty');
vpadCircle.removeClass('bong-full');
}
if (dpadMode === checked) {
return //error?
}
if (dpadMode) {
dpadMode = false;
vpadHolder.classList.add('dpad-empty');
vpadCircle.classList.add('bong-full');
// reset dpad keys pressed before moving to analog stick mode
resetVpadState()
} else {
dpadMode = true;
vpadHolder.classList.remove('dpad-empty');
vpadCircle.classList.remove('bong-full');
}
}
function resetVpadState() {
@ -63,7 +62,8 @@ const touch = (() => {
vpadTouchDrag = null;
vpadTouchIdx = null;
dpad.removeClass('pressed');
dpad.forEach(arrow => arrow.classList.remove('pressed'));
}
function checkVpadState(axis, state) {
@ -82,9 +82,7 @@ const touch = (() => {
}
function handleVpadJoystickDown(event) {
vpadCircle.css('transition', '0s');
vpadCircle.css('-moz-transition', '0s');
vpadCircle.css('-webkit-transition', '0s');
vpadCircle.style['transition'] = '0s';
if (event.changedTouches) {
resetVpadState();
@ -99,12 +97,8 @@ const touch = (() => {
function handleVpadJoystickUp() {
if (vpadTouchDrag === null) return;
vpadCircle.css('transition', '.2s');
vpadCircle.css('-moz-transition', '.2s');
vpadCircle.css('-webkit-transition', '.2s');
vpadCircle.css('transform', 'translate3d(0px, 0px, 0px)');
vpadCircle.css('-moz-transform', 'translate3d(0px, 0px, 0px)');
vpadCircle.css('-webkit-transform', 'translate3d(0px, 0px, 0px)');
vpadCircle.style['transition'] = '.2s';
vpadCircle.style['transform'] = 'translate3d(0px, 0px, 0px)';
resetVpadState();
}
@ -137,10 +131,7 @@ const touch = (() => {
yNew = -tmp;
}
let style = `translate(${xNew}px, ${yNew}px)`;
vpadCircle.css('transform', style);
vpadCircle.css('-webkit-transform', style);
vpadCircle.css('-moz-transform', style);
vpadCircle.style['transform'] = `translate(${xNew}px, ${yNew}px)`;
let xRatio = xNew / MAX_DIFF;
let yRatio = yNew / MAX_DIFF;
@ -156,33 +147,29 @@ const touch = (() => {
}
}
/*
Right side - Control buttons
*/
// right side - control buttons
const _handleButton = (key, state) => checkVpadState(key, state)
function handleButtonDown() {
checkVpadState($(this).attr('value'), true);
// add touchIdx?
_handleButton(this.getAttribute('value'), true);
}
function handleButtonUp() {
checkVpadState($(this).attr('value'), false);
_handleButton(this.getAttribute('value'), false);
}
/*
Player index slider
*/
function handleButtonClick() {
_handleButton(this.getAttribute('value'), true);
setTimeout(() => {
_handleButton(this.getAttribute('value'), false);
}, 30);
}
function handlePlayerSlider() {
socket.updatePlayerIndex($(this).val() - 1);
socket.updatePlayerIndex(this.value - 1);
}
/*
Touch menu
*/
// Touch menu
let menuTouchIdx = null;
let menuTouchDrag = null;
let menuTouchTime = null;
@ -240,9 +227,7 @@ const touch = (() => {
menuTouchDrag = null;
}
/*
Common events
*/
// Common events
function handleWindowMove(event) {
event.preventDefault();
handleVpadJoystickMove(event);
@ -255,10 +240,11 @@ const touch = (() => {
// check class
let elem = document.elementFromPoint(event.changedTouches[i].clientX, event.changedTouches[i].clientY);
if (elem.classList.contains('btn')) {
$(elem).trigger('touchstart');
elem.dispatchEvent(new Event('touchstart'));
} else {
buttons.trigger('touchend');
elem.dispatchEvent(new Event('touchend'));
}
}
}
@ -268,23 +254,31 @@ const touch = (() => {
function handleWindowUp(ev) {
handleVpadJoystickUp(ev);
handleMenuUp(ev);
buttons.trigger('touchend');
buttons.forEach((btn) => {
btn.dispatchEvent(new Event('touchend'));
});
}
// touch/mouse events for control buttons. mouseup events is binded to window.
buttons.on('mousedown', handleButtonDown);
buttons.on('touchstart', handleButtonDown);
buttons.on('touchend', handleButtonUp);
buttons.forEach((btn) => {
btn.addEventListener('mousedown', handleButtonDown);
btn.addEventListener('touchstart', handleButtonDown);
btn.addEventListener('touchend', handleButtonUp);
});
// touch/mouse events for dpad. mouseup events is binded to window.
vpadHolder.on('mousedown', handleVpadJoystickDown);
vpadHolder.on('touchstart', handleVpadJoystickDown);
vpadHolder.on('touchend', handleVpadJoystickUp);
vpadHolder.addEventListener('mousedown', handleVpadJoystickDown);
vpadHolder.addEventListener('touchstart', handleVpadJoystickDown);
vpadHolder.addEventListener('touchend', handleVpadJoystickUp);
dpad.forEach((arrow) => {
arrow.addEventListener('click', handleButtonClick);
});
// touch/mouse events for player slider.
playerSlider.on('oninput', handlePlayerSlider);
playerSlider.on('onchange', handlePlayerSlider);
playerSlider.on('mouseup', handlePlayerSlider);
playerSlider.addEventListener('oninput', handlePlayerSlider);
playerSlider.addEventListener('onchange', handlePlayerSlider);
playerSlider.addEventListener('mouseup', handlePlayerSlider);
// Bind events for menu
// TODO change this flow
@ -297,16 +291,15 @@ const touch = (() => {
return {
init: () => {
// add buttons into the state 🤦
$('.btn, .btn-big').each((_, el) => {
vpadState[$(el).attr('value')] = false;
Array.from(document.querySelectorAll('.btn,.btn-big')).forEach((el) => {
vpadState[el.getAttribute('value')] = false;
});
// Bind events for window
window_.on('mousemove', handleWindowMove);
window_[0].addEventListener('touchmove', handleWindowMove, {passive: false});
window_.on('mouseup', handleWindowUp);
window.addEventListener('mousemove', handleWindowMove);
window.addEventListener('touchmove', handleWindowMove, {passive: false});
window.addEventListener('mouseup', handleWindowUp);
log.info('[input] touch input has been initialized');
}
}
})($, document, event, KEY, window);
})(document, event, KEY, window);

File diff suppressed because one or more lines are too long

View file

@ -145,4 +145,4 @@ const socket = (() => {
quitGame: quitGame,
toggleMultitap: toggleMultitap,
}
})($, event, log);
})(event, log);

12
web/js/room.js vendored
View file

@ -6,14 +6,14 @@ const room = (() => {
let id = '';
// UI
const roomLabel = $('#room-txt');
const roomLabel = document.getElementById('room-txt');
// !to rewrite
const parseURLForRoom = () => {
let queryDict = {};
let regex = /^\/?([A-Za-z]*)\/?/g;
var zone = regex.exec(location.pathname)[1]
var room = null
const zone = regex.exec(location.pathname)[1];
let room = null;
// get room from URL
location.search.substr(1)
@ -23,7 +23,7 @@ const room = (() => {
});
if (typeof queryDict.id === 'string') {
room = decodeURIComponent(queryDict.id);
room = decodeURIComponent(queryDict.id);
}
return [room, zone];
@ -38,11 +38,11 @@ const room = (() => {
getId: () => id,
setId: (id_) => {
id = id_;
roomLabel.val(id);
roomLabel.value = id;
},
reset: () => {
id = '';
roomLabel.val(id);
roomLabel.value = id;
},
save: (roomIndex) => {
localStorage.setItem('roomID', roomIndex);