1
0
mirror of https://github.com/chinchang/web-maker.git synced 2025-07-26 00:11:13 +02:00

add basic Js13kgames mode

This commit is contained in:
Kushagra Gour
2018-06-30 11:07:55 +05:30
parent b0eee8f555
commit 800504e1fd
9 changed files with 408 additions and 74 deletions

View File

@@ -1,6 +1,41 @@
import { h, Component } from 'preact';
import { Button } from './common';
class JS13K extends Component {
constructor(props) {
super(props);
const compoDate = new Date('August 13 2018 11:00 GMT');
var now = new Date();
var daysLeft;
if (+compoDate > +now) {
daysLeft = Math.floor((compoDate - now) / 1000 / 3600 / 24);
}
this.setState({
daysLeft
});
}
render() {
return (
<div class="flex flex-v-center">
<img
src="http://js13kgames.com/img/js13kgames.png"
alt="JS13K Games logo"
height="24"
/>{' '}
<div class="footer__js13k-text">{this.state.daysLeft} days to go</div>
<div
style={{
color: this.props.codeSize > 10 ? 'crimson' : 'limegreen'
}}
>
{(this.props.codeSize / 1024).toFixed(2)} KB/ 13KB
</div>
</div>
);
}
}
export default class Footer extends Component {
constructor(props) {
super(props);
@@ -15,7 +50,83 @@ export default class Footer extends Component {
render() {
return (
<div id="footer" class="footer">
<div class="footer__right fr">
<div>
<a
href="https://webmakerapp.com/"
target="_blank"
rel="noopener noreferrer"
>
<div class="logo" />
</a>
&copy;
<span class="web-maker-with-tag">Web Maker</span> &nbsp;&nbsp;
<Button
onClick={this.props.helpBtnClickHandler}
data-event-category="ui"
data-event-action="helpButtonClick"
class="footer__link hint--rounded hint--top-right"
aria-label="Help"
>
<svg
style="width:20px; height:20px; vertical-align:text-bottom"
viewBox="0 0 24 24"
>
<path d="M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z" />
</svg>
</Button>
<Button
onClick={this.props.keyboardShortcutsBtnClickHandler}
data-event-category="ui"
data-event-action="keyboardShortcutButtonClick"
class="footer__link hint--rounded hint--top-right hide-on-mobile"
aria-label="Keyboard shortcuts"
>
<svg
style={{
width: '20px',
height: '20px',
verticalAlign: 'text-bottom'
}}
>
<use xlinkHref="#keyboard-icon" />
</svg>
</Button>
<a
class="footer__link hint--rounded hint--top-right"
aria-label="Tweet about 'Web Maker'"
href="http://twitter.com/share?url=https://webmakerapp.com/&text=Web Maker - A blazing fast %26 offline web playground! via @webmakerApp&related=webmakerApp&hashtags=web,frontend,playground,offline"
target="_blank"
rel="noopener noreferrer"
>
<svg
style={{
width: '20px',
height: '20px',
verticalAlign: 'text-bottom'
}}
>
<use xlinkHref="#twitter-icon" />
</svg>
</a>
<Button
onClick={this.props.supportDeveloperBtnClickHandler}
data-event-category="ui"
data-event-action="supportDeveloperFooterBtnClick"
class="footer__link ml-1 hint--rounded hint--top-right hide-on-mobile support-link"
aria-label="Support the developer by pledging some amount"
>
Donate
</Button>
</div>
{this.props.prefs.isJs13kModeOn ? (
<div class="flex">
<JS13K codeSize={this.props.codeSize} />
<button onClick={this.props.onJs13KBtnClick}>Help</button>
</div>
) : null}
<div class="footer__right">
<button
onClick={this.props.saveHtmlBtnClickHandler}
id="saveHtmlBtn"
@@ -150,72 +261,6 @@ export default class Footer extends Component {
</svg>
</Button>
</div>
<a
href="https://webmakerapp.com/"
target="_blank"
rel="noopener noreferrer"
>
<div class="logo" />
</a>
&copy;
<span class="web-maker-with-tag">Web Maker</span> &nbsp;&nbsp;
<Button
onClick={this.props.helpBtnClickHandler}
data-event-category="ui"
data-event-action="helpButtonClick"
class="footer__link hint--rounded hint--top-right"
aria-label="Help"
>
<svg
style="width:20px; height:20px; vertical-align:text-bottom"
viewBox="0 0 24 24"
>
<path d="M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z" />
</svg>
</Button>
<Button
onClick={this.props.keyboardShortcutsBtnClickHandler}
data-event-category="ui"
data-event-action="keyboardShortcutButtonClick"
class="footer__link hint--rounded hint--top-right hide-on-mobile"
aria-label="Keyboard shortcuts"
>
<svg
style={{
width: '20px',
height: '20px',
verticalAlign: 'text-bottom'
}}
>
<use xlinkHref="#keyboard-icon" />
</svg>
</Button>
<a
class="footer__link hint--rounded hint--top-right"
aria-label="Tweet about 'Web Maker'"
href="http://twitter.com/share?url=https://webmakerapp.com/&text=Web Maker - A blazing fast %26 offline web playground! via @webmakerApp&related=webmakerApp&hashtags=web,frontend,playground,offline"
target="_blank"
rel="noopener noreferrer"
>
<svg
style={{
width: '20px',
height: '20px',
verticalAlign: 'text-bottom'
}}
>
<use xlinkHref="#twitter-icon" />
</svg>
</a>
<Button
onClick={this.props.supportDeveloperBtnClickHandler}
data-event-category="ui"
data-event-action="supportDeveloperFooterBtnClick"
class="footer__link ml-1 hint--rounded hint--top-right hide-on-mobile support-link"
aria-label="Support the developer by pledging some amount"
>
Support the developer
</Button>
</div>
);
}

View File

@@ -0,0 +1,74 @@
import { h } from 'preact';
import Modal from './Modal';
export function Js13KModal({ show, closeHandler }) {
return (
<Modal show={show} closeHandler={closeHandler} small>
<div class="tac">
<div className="flex flex-v-center flex-h-center ">
<img
src="/icon-128.png"
alt="Web Maker logo"
height="100"
style="margin:0 0.5rem;"
/>
<h2>Web Maker</h2>
<span style="font-size:3rem;margin:0 1rem;"></span>
<h2>JS13K Games</h2>
<img
src="http://js13kgames.com/img/logo.png"
alt="JS13K Games logo"
height="100"
style="margin:0 0.5rem;"
/>
</div>
</div>
<div>
<p>
<strong>Js13kGames</strong> is a JavaScript coding competition for{' '}
<strong>HTML5 Game Developers</strong>. The fun part of the compo is
the file size limit set to <strong>13 kilobytes</strong>. The
competition will start at <strong>13:00 CEST, 13th August</strong> and
will end at <strong>13:00 CEST, 13th September 2018</strong>.
</p>
<p>
Web Maker is an frontend playground that works offline! You can play
around quickly with HTML, JavaScript & CSS to build your awesome game
right here.
</p>
<ul>
<li>
<a
href="https://gamedevelopment.tutsplus.com/articles/how-to-minify-your-html5-game-for-the-js13kgames-competition--cms-21883"
target="_blank"
rel="noopener noreferrer"
>
Read Tuts+ Gamedev intro article
</a>
</li>
<li>
<a
href="http://js13kgames.github.io/resources/"
target="_blank"
rel="noopener noreferrer"
>
Resources and useful tools
</a>
</li>
<li>
<a
href="http://2018.js13kgames.com/#rules"
target="_blank"
rel="noopener noreferrer"
>
Compo rules
</a>
</li>
</ul>
<p>Have fun building games!</p>
</div>
</Modal>
);
}

View File

@@ -56,7 +56,9 @@ export default class Modal extends Component {
return (
<Portal into="body">
<div
class={`${this.props.extraClasses || ''} modal is-modal-visible`}
class={`${this.props.extraClasses || ''} modal is-modal-visible ${
this.props.small ? 'modal--small' : ''
}`}
ref={el => (this.overlayEl = el)}
onClick={this.onOverlayClick.bind(this)}
>

View File

@@ -286,6 +286,14 @@ export default class Settings extends Component {
pref={this.props.prefs.isCodeBlastOn}
onChange={this.updateSetting.bind(this)}
/>
<CheckboxSetting
title="Get ready to build some games at JS13KGames"
label="JS13KGames Mode"
name="isJs13kModeOn"
pref={this.props.prefs.isJs13kModeOn}
onChange={this.updateSetting.bind(this)}
/>
</p>
<hr />

View File

@@ -10,13 +10,15 @@ import SavedItemPane from './SavedItemPane.jsx';
import AddLibrary from './AddLibrary.jsx';
import Modal from './Modal.jsx';
import Login from './Login.jsx';
import { computeHtml, computeCss, computeJs } from '../computes';
import {
log,
generateRandomId,
semverCompare,
saveAsHtml,
handleDownloadsPermission,
downloadFile
downloadFile,
getCompleteHtml
} from '../utils';
import { itemService } from '../itemService';
import '../db';
@@ -38,7 +40,9 @@ import { Alerts } from './Alerts';
import Portal from 'preact-portal';
import { HelpModal } from './HelpModal';
import { OnboardingModal } from './OnboardingModal';
import { Js13KModal } from './Js13KModal';
import { Icons } from './Icons';
import JSZip from 'jszip';
if (module.hot) {
require('preact/debug');
@@ -68,6 +72,7 @@ export default class App extends Component {
isKeyboardShortcutsModalOpen: false,
isAskToImportModalOpen: false,
isOnboardModalOpen: false,
isJs13KModalOpen: false,
prefs: {},
currentItem: {
title: '',
@@ -96,7 +101,8 @@ export default class App extends Component {
lightVersion: false,
lineWrap: true,
infiniteLoopTimeout: 1000,
layoutMode: 2
layoutMode: 2,
isJs13kModeOn: false
};
this.prefs = {};
@@ -493,7 +499,8 @@ export default class App extends Component {
isSupportDeveloperModalOpen: false,
isKeyboardShortcutsModalOpen: false,
isAskToImportModalOpen: false,
isOnboardModalOpen: false
isOnboardModalOpen: false,
isJs13KModalOpen: false
});
}
onExternalLibChange(newValues) {
@@ -697,6 +704,7 @@ export default class App extends Component {
});
}
}
this.calculateCodeSize();
}
onCodeSettingsChange(type, settings) {
this.state.currentItem[`${type}Settings`] = {
@@ -973,6 +981,138 @@ export default class App extends Component {
this.state.currentItem.mainSizes = this.getMainPaneSizes();
}
/**
* Calculate byte size of a text snippet
* @author Lea Verou
* MIT License
*/
calculateTextSize(text) {
if (!text) {
return 0;
}
var crlf = /(\r?\n|\r)/g,
whitespace = /(\r?\n|\r|\s+)/g;
const ByteSize = {
count: function(text, options) {
// Set option defaults
options = options || {};
options.lineBreaks = options.lineBreaks || 1;
options.ignoreWhitespace = options.ignoreWhitespace || false;
var length = text.length,
nonAscii = length - text.replace(/[\u0100-\uFFFF]/g, '').length,
lineBreaks = length - text.replace(crlf, '').length;
if (options.ignoreWhitespace) {
// Strip whitespace
text = text.replace(whitespace, '');
return text.length + nonAscii;
} else {
return (
length +
nonAscii +
Math.max(0, options.lineBreaks * (lineBreaks - 1))
);
}
},
format: function(count, plainText) {
var level = 0;
while (count > 1024) {
count /= 1024;
level++;
}
// Round to 2 decimals
count = Math.round(count * 100) / 100;
level = ['', 'K', 'M', 'G', 'T'][level];
return (
(plainText ? count : '<strong>' + count + '</strong>') +
' ' +
level +
'B'
);
}
};
return ByteSize.count(text);
}
getExternalLibCode() {
const item = this.state.currentItem;
var libs = (item.externalLibs && item.externalLibs.js) || '';
libs += ('\n' + item.externalLibs && item.externalLibs.css) || '';
libs = libs.split('\n').filter(lib => lib);
return libs.map(lib =>
fetch(lib)
.then(res => res.text())
.then(data => {
return {
code: data,
url: 'dsfds'
};
})
);
}
calculateCodeSize() {
const item = this.state.currentItem;
var htmlPromise = computeHtml(item.html, item.htmlMode);
var cssPromise = computeCss(item.css, item.cssMode);
var jsPromise = computeJs(item.js, item.jsMode, false);
Promise.all([
htmlPromise,
cssPromise,
jsPromise,
...this.getExternalLibCode()
]).then(result => {
var html = result[0].code || '',
css = result[1].code || '',
js = result[2].code || '';
var fileContent = getCompleteHtml(html, css, js, item, true);
var zip = new JSZip();
// Add an top-level, arbitrary text file with contents
zip.file('index.html', fileContent);
[result[3]].map(externalLib =>
zip.file(externalLib.name, externalLib.code)
);
console.log('ORIGINAL', this.calculateTextSize(fileContent));
var promise = null;
if (0 && JSZip.support.uint8array) {
promise = zip.generateAsync({ type: 'uint8array' });
} else {
promise = zip.generateAsync({
type: 'string',
compression: 'DEFLATE',
compressionOptions: {
level: 9
}
});
}
promise.then(data => {
const zipContent = data;
const size = this.calculateTextSize(data);
this.setState({
codeSize: size
});
});
});
}
js13KBtnClickHandler() {
this.setState({
isJs13KModalOpen: true
});
}
render() {
return (
<div>
@@ -1006,6 +1146,7 @@ export default class App extends Component {
/>
<div class="global-console-container" id="globalConsoleContainerEl" />
<Footer
prefs={this.state.prefs}
layoutBtnClickHandler={this.layoutBtnClickHandler.bind(this)}
helpBtnClickHandler={() => this.setState({ isHelpModalOpen: true })}
settingsBtnClickHandler={() =>
@@ -1028,7 +1169,9 @@ export default class App extends Component {
screenshotBtnClickHandler={this.screenshotBtnClickHandler.bind(
this
)}
onJs13KBtnClick={this.js13KBtnClickHandler.bind(this)}
hasUnseenChangelog={this.state.hasUnseenChangelog}
codeSize={this.state.codeSize}
/>
</div>
@@ -1145,6 +1288,11 @@ export default class App extends Component {
closeHandler={() => this.setState({ isOnboardModalOpen: false })}
/>
<Js13KModal
show={this.state.isJs13KModalOpen}
closeHandler={() => this.setState({ isJs13KModalOpen: false })}
/>
<Portal into="body">
<div
class="modal-overlay"

View File

@@ -573,6 +573,8 @@ body > #demo-frame {
}
.footer {
display: flex;
justify-content: space-between;
/* Because .console is 6 */
z-index: 6;
}
@@ -852,7 +854,8 @@ body > #demo-frame {
}
.pledge-modal .modal__content,
.ask-to-import-modal .modal__content {
.ask-to-import-modal .modal__content,
.modal--small .modal__content {
max-width: 800px;
}
@@ -1506,6 +1509,16 @@ body:not(.is-app) .show-when-app {
visibility: visible;
}
.footer__js13k-text {
padding: 1px 9px;
text-transform: uppercase;
background: #b12a34;
color: white;
border-radius: 3px;
letter-spacing: 0.7px;
margin-left: 1rem;
}
@media screen and (max-width: 600px) {
body {
font-size: 70%;

View File

@@ -378,7 +378,7 @@ export function saveAsHtml(item) {
var htmlPromise = computeHtml(item.html, item.htmlMode);
var cssPromise = computeCss(item.css, item.cssMode);
var jsPromise = computeJs(item.js, item.jsMode, false);
Promise.all([htmlPromise, cssPromise, jsPromise]).then(function(result) {
Promise.all([htmlPromise, cssPromise, jsPromise]).then(result => {
var html = result[0].code,
css = result[1].code,
js = result[2].code;