Initial commit.
This commit is contained in:
commit
34ba8d408b
26 changed files with 51739 additions and 0 deletions
1
.babelrc.js
Normal file
1
.babelrc.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
module.exports = {};
|
170
.eslintrc.js
Normal file
170
.eslintrc.js
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
es2021: true,
|
||||||
|
},
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
ecmaFeatures: { jsx: true },
|
||||||
|
ecmaVersion: 12,
|
||||||
|
project: ['./tsconfig.json'],
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||||
|
'plugin:jsx-a11y/recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'airbnb',
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
'@typescript-eslint',
|
||||||
|
'import',
|
||||||
|
'jsx-a11y',
|
||||||
|
'react',
|
||||||
|
'react-hooks',
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/brace-style': ['error'],
|
||||||
|
'@typescript-eslint/comma-dangle': ['error', 'always-multiline'],
|
||||||
|
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
|
||||||
|
'@typescript-eslint/keyword-spacing': ['error'],
|
||||||
|
'@typescript-eslint/member-delimiter-style': ['error'],
|
||||||
|
'@typescript-eslint/method-signature-style': ['warn', 'method'],
|
||||||
|
'@typescript-eslint/naming-convention': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
selector: 'default',
|
||||||
|
format: ['camelCase', 'PascalCase'],
|
||||||
|
leadingUnderscore: 'forbid',
|
||||||
|
trailingUnderscore: 'forbid',
|
||||||
|
filter: {
|
||||||
|
// skip names requiring quotes, e.g. HTTP headers
|
||||||
|
regex: '[- ]',
|
||||||
|
match: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: ['enumMember'],
|
||||||
|
format: ['UPPER_CASE'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: ['property', 'parameter', 'parameterProperty'],
|
||||||
|
format: ['camelCase', 'PascalCase'],
|
||||||
|
leadingUnderscore: 'allow',
|
||||||
|
trailingUnderscore: 'forbid',
|
||||||
|
filter: {
|
||||||
|
// skip names requiring quotes, e.g. HTTP headers
|
||||||
|
regex: '[- ]',
|
||||||
|
match: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'typeLike',
|
||||||
|
format: ['PascalCase'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/no-duplicate-imports': ['error'],
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/no-floating-promises': ['error'],
|
||||||
|
'@typescript-eslint/no-misused-promises': ['error'],
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||||
|
'@typescript-eslint/no-shadow': ['error'],
|
||||||
|
'@typescript-eslint/no-unnecessary-condition': ['error', { allowConstantLoopConditions: true }],
|
||||||
|
'@typescript-eslint/no-unnecessary-boolean-literal-compare': ['error'],
|
||||||
|
'@typescript-eslint/no-unused-expressions': ['error'],
|
||||||
|
'@typescript-eslint/no-unused-vars': ['error'],
|
||||||
|
'@typescript-eslint/no-use-before-define': ['error'],
|
||||||
|
'@typescript-eslint/prefer-nullish-coalescing': ['warn'],
|
||||||
|
'@typescript-eslint/quotes': ['error', 'single', { avoidEscape: true }],
|
||||||
|
'@typescript-eslint/semi': ['error'],
|
||||||
|
'@typescript-eslint/space-before-function-paren': ['error', {
|
||||||
|
anonymous: 'never',
|
||||||
|
named: 'never',
|
||||||
|
asyncArrow: 'always',
|
||||||
|
}],
|
||||||
|
'@typescript-eslint/switch-exhaustiveness-check': ['error'],
|
||||||
|
'@typescript-eslint/type-annotation-spacing': ['warn', {
|
||||||
|
before: false,
|
||||||
|
after: true,
|
||||||
|
overrides: {
|
||||||
|
arrow: { before: true },
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
'brace-style': 'off',
|
||||||
|
camelcase: 'off',
|
||||||
|
'class-methods-use-this': 'off',
|
||||||
|
'comma-dangle': 'off',
|
||||||
|
'keyword-spacing': 'off',
|
||||||
|
'no-duplicate-imports': 'off',
|
||||||
|
'no-shadow': 'off',
|
||||||
|
'no-unused-expressions': 'off',
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'no-use-before-define': 'off',
|
||||||
|
quotes: 'off',
|
||||||
|
semi: 'off',
|
||||||
|
'space-before-function-paren': 'off',
|
||||||
|
'implicit-arrow-linebreak': 'off',
|
||||||
|
'import/extensions': [
|
||||||
|
'error',
|
||||||
|
'ignorePackages',
|
||||||
|
{
|
||||||
|
js: 'never',
|
||||||
|
jsx: 'never',
|
||||||
|
ts: 'never',
|
||||||
|
tsx: 'never',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indent: ['error', 2, {
|
||||||
|
CallExpression: { arguments: 'first' },
|
||||||
|
FunctionDeclaration: { parameters: 'first' },
|
||||||
|
FunctionExpression: { parameters: 'first' },
|
||||||
|
SwitchCase: 1,
|
||||||
|
VariableDeclarator: 'first',
|
||||||
|
offsetTernaryExpressions: true,
|
||||||
|
}],
|
||||||
|
'jsx-a11y/label-has-associated-control': ['warn', {
|
||||||
|
assert: 'either',
|
||||||
|
}],
|
||||||
|
'max-classes-per-file': 'off',
|
||||||
|
'max-len': ['warn', { code: 100, tabWidth: 2 }],
|
||||||
|
'no-console': 'off',
|
||||||
|
'no-underscore-dangle': 'off',
|
||||||
|
'prefer-arrow-callback': ['error'],
|
||||||
|
'react/function-component-definition': ['error', {
|
||||||
|
namedComponents: 'arrow-function',
|
||||||
|
unnamedComponents: 'arrow-function',
|
||||||
|
}],
|
||||||
|
'react/jsx-curly-newline': ['error', { multiline: 'forbid', singleline: 'forbid' }],
|
||||||
|
'react/jsx-filename-extension': ['error', { extensions: ['.jsx', '.tsx'] }],
|
||||||
|
'react/jsx-first-prop-new-line': ['error', 'multiline'],
|
||||||
|
'react/jsx-fragments': 'off',
|
||||||
|
'react/jsx-indent': ['error', 2, { checkAttributes: true, indentLogicalExpressions: true }],
|
||||||
|
'react/jsx-indent-props': ['error', 2],
|
||||||
|
'react/jsx-key': ['error', { checkFragmentShorthand: true }],
|
||||||
|
'react/jsx-max-props-per-line': ['error', { maximum: 3, when: 'always' }],
|
||||||
|
'react/jsx-no-target-blank': ['error'],
|
||||||
|
'react/jsx-wrap-multilines': ['error', {
|
||||||
|
declaration: 'parens-new-line',
|
||||||
|
assignment: 'parens-new-line',
|
||||||
|
return: 'parens-new-line',
|
||||||
|
arrow: 'parens-new-line',
|
||||||
|
condition: 'parens-new-line',
|
||||||
|
logical: 'parens-new-line',
|
||||||
|
prop: 'parens-new-line',
|
||||||
|
}],
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
'react/no-deprecated': ['error'],
|
||||||
|
'react/no-multi-comp': ['error'],
|
||||||
|
'react/no-unused-prop-types': ['error'],
|
||||||
|
'react/prefer-stateless-function': ['error'],
|
||||||
|
'react/react-in-jsx-scope': 'off',
|
||||||
|
'react-hooks/rules-of-hooks': ['error'],
|
||||||
|
'react-hooks/exhaustive-deps': ['warn'],
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
'import/resolver': {
|
||||||
|
node: { extensions: ['.js', '.jsx', '.ts', '.tsx'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
5
.firebaserc
Normal file
5
.firebaserc
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"projects": {
|
||||||
|
"default": "built-to-spell"
|
||||||
|
}
|
||||||
|
}
|
66
.gitignore
vendored
Normal file
66
.gitignore
vendored
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
firebase-debug.log*
|
||||||
|
firebase-debug.*.log*
|
||||||
|
|
||||||
|
# Firebase cache
|
||||||
|
.firebase/
|
||||||
|
|
||||||
|
# Firebase config
|
||||||
|
|
||||||
|
# Uncomment this if you'd like others to create their own Firebase project.
|
||||||
|
# For a team working on the same Firebase project(s), it is recommended to leave
|
||||||
|
# it commented so all members can deploy to the same project(s) in .firebaserc.
|
||||||
|
# .firebaserc
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Brent Schroeter
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
45
dist/custom.d5cc83c2.css
vendored
Normal file
45
dist/custom.d5cc83c2.css
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
div.hexagon {
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0 0 60px 0;
|
||||||
|
position: relative;
|
||||||
|
-o-transform: skewX(30deg);
|
||||||
|
-moz-transform: skewX(30deg);
|
||||||
|
-webkit-transform: skewX(30deg);
|
||||||
|
-ms-transform: skewX(30deg);
|
||||||
|
transform: skewX(30deg);
|
||||||
|
visibility: hidden;
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.hexagon>div {
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
-o-transform: skewY(-50deg);
|
||||||
|
-moz-transform: skewY(-50deg);
|
||||||
|
-webkit-transform: skewY(-50deg);
|
||||||
|
-ms-transform: skewY(-50deg);
|
||||||
|
transform: skewX(-50deg);
|
||||||
|
visibility: hidden;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.hexagon>div>button {
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
-o-transform: skewY(20deg);
|
||||||
|
-moz-transform: skewY(20deg);
|
||||||
|
-webkit-transform: skewY(20deg);
|
||||||
|
-ms-transform: skewY(20deg);
|
||||||
|
top: 0;
|
||||||
|
transform: skewX(30deg);
|
||||||
|
visibility: visible;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*# sourceMappingURL=/custom.d5cc83c2.css.map */
|
1
dist/custom.d5cc83c2.css.map
vendored
Normal file
1
dist/custom.d5cc83c2.css.map
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"sources":["custom.css"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"custom.d5cc83c2.css","sourceRoot":"../src","sourcesContent":["div.hexagon {\n overflow: hidden;\n padding: 0 0 60px 0;\n position: relative;\n -o-transform: skewX(30deg);\n -moz-transform: skewX(30deg);\n -webkit-transform: skewX(30deg);\n -ms-transform: skewX(30deg);\n transform: skewX(30deg);\n visibility: hidden;\n width: 70px;\n}\n\ndiv.hexagon>div {\n height: 100%;\n left: 0;\n overflow: hidden;\n position: absolute;\n top: 0;\n -o-transform: skewY(-50deg);\n -moz-transform: skewY(-50deg);\n -webkit-transform: skewY(-50deg);\n -ms-transform: skewY(-50deg);\n transform: skewX(-50deg);\n visibility: hidden;\n width: 100%;\n}\n\ndiv.hexagon>div>button {\n height: 100%;\n left: 0;\n overflow: hidden;\n position: absolute;\n -o-transform: skewY(20deg);\n -moz-transform: skewY(20deg);\n -webkit-transform: skewY(20deg);\n -ms-transform: skewY(20deg);\n top: 0;\n transform: skewX(30deg);\n visibility: visible;\n width: 100%;\n}\n"]}
|
397
dist/custom.d5cc83c2.js
vendored
Normal file
397
dist/custom.d5cc83c2.js
vendored
Normal file
|
@ -0,0 +1,397 @@
|
||||||
|
// modules are defined as an array
|
||||||
|
// [ module function, map of requires ]
|
||||||
|
//
|
||||||
|
// map of requires is short require name -> numeric require
|
||||||
|
//
|
||||||
|
// anything defined in a previous bundle is accessed via the
|
||||||
|
// orig method which is the require for previous bundles
|
||||||
|
parcelRequire = (function (modules, cache, entry, globalName) {
|
||||||
|
// Save the require from previous bundle to this closure if any
|
||||||
|
var previousRequire = typeof parcelRequire === 'function' && parcelRequire;
|
||||||
|
var nodeRequire = typeof require === 'function' && require;
|
||||||
|
|
||||||
|
function newRequire(name, jumped) {
|
||||||
|
if (!cache[name]) {
|
||||||
|
if (!modules[name]) {
|
||||||
|
// if we cannot find the module within our internal map or
|
||||||
|
// cache jump to the current global require ie. the last bundle
|
||||||
|
// that was added to the page.
|
||||||
|
var currentRequire = typeof parcelRequire === 'function' && parcelRequire;
|
||||||
|
if (!jumped && currentRequire) {
|
||||||
|
return currentRequire(name, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are other bundles on this page the require from the
|
||||||
|
// previous one is saved to 'previousRequire'. Repeat this as
|
||||||
|
// many times as there are bundles until the module is found or
|
||||||
|
// we exhaust the require chain.
|
||||||
|
if (previousRequire) {
|
||||||
|
return previousRequire(name, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try the node require function if it exists.
|
||||||
|
if (nodeRequire && typeof name === 'string') {
|
||||||
|
return nodeRequire(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
var err = new Error('Cannot find module \'' + name + '\'');
|
||||||
|
err.code = 'MODULE_NOT_FOUND';
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
localRequire.resolve = resolve;
|
||||||
|
localRequire.cache = {};
|
||||||
|
|
||||||
|
var module = cache[name] = new newRequire.Module(name);
|
||||||
|
|
||||||
|
modules[name][0].call(module.exports, localRequire, module, module.exports, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache[name].exports;
|
||||||
|
|
||||||
|
function localRequire(x){
|
||||||
|
return newRequire(localRequire.resolve(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve(x){
|
||||||
|
return modules[name][1][x] || x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Module(moduleName) {
|
||||||
|
this.id = moduleName;
|
||||||
|
this.bundle = newRequire;
|
||||||
|
this.exports = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
newRequire.isParcelRequire = true;
|
||||||
|
newRequire.Module = Module;
|
||||||
|
newRequire.modules = modules;
|
||||||
|
newRequire.cache = cache;
|
||||||
|
newRequire.parent = previousRequire;
|
||||||
|
newRequire.register = function (id, exports) {
|
||||||
|
modules[id] = [function (require, module) {
|
||||||
|
module.exports = exports;
|
||||||
|
}, {}];
|
||||||
|
};
|
||||||
|
|
||||||
|
var error;
|
||||||
|
for (var i = 0; i < entry.length; i++) {
|
||||||
|
try {
|
||||||
|
newRequire(entry[i]);
|
||||||
|
} catch (e) {
|
||||||
|
// Save first error but execute all entries
|
||||||
|
if (!error) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.length) {
|
||||||
|
// Expose entry point to Node, AMD or browser globals
|
||||||
|
// Based on https://github.com/ForbesLindesay/umd/blob/master/template.js
|
||||||
|
var mainExports = newRequire(entry[entry.length - 1]);
|
||||||
|
|
||||||
|
// CommonJS
|
||||||
|
if (typeof exports === "object" && typeof module !== "undefined") {
|
||||||
|
module.exports = mainExports;
|
||||||
|
|
||||||
|
// RequireJS
|
||||||
|
} else if (typeof define === "function" && define.amd) {
|
||||||
|
define(function () {
|
||||||
|
return mainExports;
|
||||||
|
});
|
||||||
|
|
||||||
|
// <script>
|
||||||
|
} else if (globalName) {
|
||||||
|
this[globalName] = mainExports;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override the current require with this new one
|
||||||
|
parcelRequire = newRequire;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
// throw error from earlier, _after updating parcelRequire_
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRequire;
|
||||||
|
})({"../node_modules/parcel-bundler/src/builtins/bundle-url.js":[function(require,module,exports) {
|
||||||
|
var bundleURL = null;
|
||||||
|
|
||||||
|
function getBundleURLCached() {
|
||||||
|
if (!bundleURL) {
|
||||||
|
bundleURL = getBundleURL();
|
||||||
|
}
|
||||||
|
|
||||||
|
return bundleURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBundleURL() {
|
||||||
|
// Attempt to find the URL of the current script and use that as the base URL
|
||||||
|
try {
|
||||||
|
throw new Error();
|
||||||
|
} catch (err) {
|
||||||
|
var matches = ('' + err.stack).match(/(https?|file|ftp|chrome-extension|moz-extension):\/\/[^)\n]+/g);
|
||||||
|
|
||||||
|
if (matches) {
|
||||||
|
return getBaseURL(matches[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBaseURL(url) {
|
||||||
|
return ('' + url).replace(/^((?:https?|file|ftp|chrome-extension|moz-extension):\/\/.+)\/[^/]+$/, '$1') + '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getBundleURL = getBundleURLCached;
|
||||||
|
exports.getBaseURL = getBaseURL;
|
||||||
|
},{}],"../node_modules/parcel-bundler/src/builtins/css-loader.js":[function(require,module,exports) {
|
||||||
|
var bundle = require('./bundle-url');
|
||||||
|
|
||||||
|
function updateLink(link) {
|
||||||
|
var newLink = link.cloneNode();
|
||||||
|
|
||||||
|
newLink.onload = function () {
|
||||||
|
link.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
newLink.href = link.href.split('?')[0] + '?' + Date.now();
|
||||||
|
link.parentNode.insertBefore(newLink, link.nextSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
var cssTimeout = null;
|
||||||
|
|
||||||
|
function reloadCSS() {
|
||||||
|
if (cssTimeout) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cssTimeout = setTimeout(function () {
|
||||||
|
var links = document.querySelectorAll('link[rel="stylesheet"]');
|
||||||
|
|
||||||
|
for (var i = 0; i < links.length; i++) {
|
||||||
|
if (bundle.getBaseURL(links[i].href) === bundle.getBundleURL()) {
|
||||||
|
updateLink(links[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cssTimeout = null;
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = reloadCSS;
|
||||||
|
},{"./bundle-url":"../node_modules/parcel-bundler/src/builtins/bundle-url.js"}],"custom.css":[function(require,module,exports) {
|
||||||
|
var reloadCSS = require('_css_loader');
|
||||||
|
|
||||||
|
module.hot.dispose(reloadCSS);
|
||||||
|
module.hot.accept(reloadCSS);
|
||||||
|
},{"_css_loader":"../node_modules/parcel-bundler/src/builtins/css-loader.js"}],"../node_modules/parcel-bundler/src/builtins/hmr-runtime.js":[function(require,module,exports) {
|
||||||
|
var global = arguments[3];
|
||||||
|
var OVERLAY_ID = '__parcel__error__overlay__';
|
||||||
|
var OldModule = module.bundle.Module;
|
||||||
|
|
||||||
|
function Module(moduleName) {
|
||||||
|
OldModule.call(this, moduleName);
|
||||||
|
this.hot = {
|
||||||
|
data: module.bundle.hotData,
|
||||||
|
_acceptCallbacks: [],
|
||||||
|
_disposeCallbacks: [],
|
||||||
|
accept: function (fn) {
|
||||||
|
this._acceptCallbacks.push(fn || function () {});
|
||||||
|
},
|
||||||
|
dispose: function (fn) {
|
||||||
|
this._disposeCallbacks.push(fn);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
module.bundle.hotData = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.bundle.Module = Module;
|
||||||
|
var checkedAssets, assetsToAccept;
|
||||||
|
var parent = module.bundle.parent;
|
||||||
|
|
||||||
|
if ((!parent || !parent.isParcelRequire) && typeof WebSocket !== 'undefined') {
|
||||||
|
var hostname = "" || location.hostname;
|
||||||
|
var protocol = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||||
|
var ws = new WebSocket(protocol + '://' + hostname + ':' + "51210" + '/');
|
||||||
|
|
||||||
|
ws.onmessage = function (event) {
|
||||||
|
checkedAssets = {};
|
||||||
|
assetsToAccept = [];
|
||||||
|
var data = JSON.parse(event.data);
|
||||||
|
|
||||||
|
if (data.type === 'update') {
|
||||||
|
var handled = false;
|
||||||
|
data.assets.forEach(function (asset) {
|
||||||
|
if (!asset.isNew) {
|
||||||
|
var didAccept = hmrAcceptCheck(global.parcelRequire, asset.id);
|
||||||
|
|
||||||
|
if (didAccept) {
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}); // Enable HMR for CSS by default.
|
||||||
|
|
||||||
|
handled = handled || data.assets.every(function (asset) {
|
||||||
|
return asset.type === 'css' && asset.generated.js;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (handled) {
|
||||||
|
console.clear();
|
||||||
|
data.assets.forEach(function (asset) {
|
||||||
|
hmrApply(global.parcelRequire, asset);
|
||||||
|
});
|
||||||
|
assetsToAccept.forEach(function (v) {
|
||||||
|
hmrAcceptRun(v[0], v[1]);
|
||||||
|
});
|
||||||
|
} else if (location.reload) {
|
||||||
|
// `location` global exists in a web worker context but lacks `.reload()` function.
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type === 'reload') {
|
||||||
|
ws.close();
|
||||||
|
|
||||||
|
ws.onclose = function () {
|
||||||
|
location.reload();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type === 'error-resolved') {
|
||||||
|
console.log('[parcel] ✨ Error resolved');
|
||||||
|
removeErrorOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type === 'error') {
|
||||||
|
console.error('[parcel] 🚨 ' + data.error.message + '\n' + data.error.stack);
|
||||||
|
removeErrorOverlay();
|
||||||
|
var overlay = createErrorOverlay(data);
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeErrorOverlay() {
|
||||||
|
var overlay = document.getElementById(OVERLAY_ID);
|
||||||
|
|
||||||
|
if (overlay) {
|
||||||
|
overlay.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createErrorOverlay(data) {
|
||||||
|
var overlay = document.createElement('div');
|
||||||
|
overlay.id = OVERLAY_ID; // html encode message and stack trace
|
||||||
|
|
||||||
|
var message = document.createElement('div');
|
||||||
|
var stackTrace = document.createElement('pre');
|
||||||
|
message.innerText = data.error.message;
|
||||||
|
stackTrace.innerText = data.error.stack;
|
||||||
|
overlay.innerHTML = '<div style="background: black; font-size: 16px; color: white; position: fixed; height: 100%; width: 100%; top: 0px; left: 0px; padding: 30px; opacity: 0.85; font-family: Menlo, Consolas, monospace; z-index: 9999;">' + '<span style="background: red; padding: 2px 4px; border-radius: 2px;">ERROR</span>' + '<span style="top: 2px; margin-left: 5px; position: relative;">🚨</span>' + '<div style="font-size: 18px; font-weight: bold; margin-top: 20px;">' + message.innerHTML + '</div>' + '<pre>' + stackTrace.innerHTML + '</pre>' + '</div>';
|
||||||
|
return overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getParents(bundle, id) {
|
||||||
|
var modules = bundle.modules;
|
||||||
|
|
||||||
|
if (!modules) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var parents = [];
|
||||||
|
var k, d, dep;
|
||||||
|
|
||||||
|
for (k in modules) {
|
||||||
|
for (d in modules[k][1]) {
|
||||||
|
dep = modules[k][1][d];
|
||||||
|
|
||||||
|
if (dep === id || Array.isArray(dep) && dep[dep.length - 1] === id) {
|
||||||
|
parents.push(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bundle.parent) {
|
||||||
|
parents = parents.concat(getParents(bundle.parent, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return parents;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hmrApply(bundle, asset) {
|
||||||
|
var modules = bundle.modules;
|
||||||
|
|
||||||
|
if (!modules) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modules[asset.id] || !bundle.parent) {
|
||||||
|
var fn = new Function('require', 'module', 'exports', asset.generated.js);
|
||||||
|
asset.isNew = !modules[asset.id];
|
||||||
|
modules[asset.id] = [fn, asset.deps];
|
||||||
|
} else if (bundle.parent) {
|
||||||
|
hmrApply(bundle.parent, asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hmrAcceptCheck(bundle, id) {
|
||||||
|
var modules = bundle.modules;
|
||||||
|
|
||||||
|
if (!modules) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!modules[id] && bundle.parent) {
|
||||||
|
return hmrAcceptCheck(bundle.parent, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkedAssets[id]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkedAssets[id] = true;
|
||||||
|
var cached = bundle.cache[id];
|
||||||
|
assetsToAccept.push([bundle, id]);
|
||||||
|
|
||||||
|
if (cached && cached.hot && cached.hot._acceptCallbacks.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getParents(global.parcelRequire, id).some(function (id) {
|
||||||
|
return hmrAcceptCheck(global.parcelRequire, id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hmrAcceptRun(bundle, id) {
|
||||||
|
var cached = bundle.cache[id];
|
||||||
|
bundle.hotData = {};
|
||||||
|
|
||||||
|
if (cached) {
|
||||||
|
cached.hot.data = bundle.hotData;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cached && cached.hot && cached.hot._disposeCallbacks.length) {
|
||||||
|
cached.hot._disposeCallbacks.forEach(function (cb) {
|
||||||
|
cb(bundle.hotData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
delete bundle.cache[id];
|
||||||
|
bundle(id);
|
||||||
|
cached = bundle.cache[id];
|
||||||
|
|
||||||
|
if (cached && cached.hot && cached.hot._acceptCallbacks.length) {
|
||||||
|
cached.hot._acceptCallbacks.forEach(function (cb) {
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},{}]},{},["../node_modules/parcel-bundler/src/builtins/hmr-runtime.js"], null)
|
||||||
|
//# sourceMappingURL=/custom.d5cc83c2.js.map
|
1
dist/custom.d5cc83c2.js.map
vendored
Normal file
1
dist/custom.d5cc83c2.js.map
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"version":3,"sources":["../node_modules/parcel-bundler/src/builtins/bundle-url.js","../node_modules/parcel-bundler/src/builtins/css-loader.js"],"names":["bundleURL","getBundleURLCached","getBundleURL","Error","err","matches","stack","match","getBaseURL","url","replace","exports","bundle","require","updateLink","link","newLink","cloneNode","onload","remove","href","split","Date","now","parentNode","insertBefore","nextSibling","cssTimeout","reloadCSS","setTimeout","links","document","querySelectorAll","i","length","module"],"mappings":"AAAA,ACAA,IDAIA,ACAAY,MAAM,GDAG,ACAAC,GDAG,IAAhB,ACAoB,CAAC,cAAD,CAApB;;ADCA,ACCA,SDDSZ,ACCAa,UAAT,CAAoBC,IAApB,EAA0B,CDD1B,GAA8B;AAC5B,ACCA,MDDI,ACCAC,CDDChB,MCCM,GDDX,ACCce,EDDE,ECCE,CAACE,SAAL,EAAd;ADAEjB,IAAAA,SAAS,GAAGE,YAAY,EAAxB;AACD,ACADc,EAAAA,OAAO,CAACE,MAAR,GAAiB,YAAY;AAC3BH,IAAAA,IAAI,CAACI,MAAL;ADCF,ACAC,GAFD,MDEOnB,SAAP;AACD;ACACgB,EAAAA,OAAO,CAACI,IAAR,GAAeL,IAAI,CAACK,IAAL,CAAUC,KAAV,CAAgB,GAAhB,EAAqB,CAArB,IAA0B,GAA1B,GAAgCC,IAAI,CAACC,GAAL,EAA/C;ADEF,ACDER,EAAAA,IAAI,CAACS,EDCEtB,QCDP,CAAgBuB,GDClB,GAAwB,MCDtB,CAA6BT,OAA7B,EAAsCD,IAAI,CAACW,WAA3C;ADEA,ACDD;ADEC,MAAI;AACF,ACDJ,IAAIC,MDCM,IAAIxB,ACDA,GAAG,EDCP,EAAN,ACDJ;ADEG,GAFD,CAEE,OAAOC,GAAP,EAAY;AACZ,ACFJ,QDEQC,CCFCuB,MDEM,GAAG,ACFlB,CDEmB,ECFE,GDEGxB,GAAG,CAACE,KAAV,EAAiBC,KAAjB,CAAuB,+DAAvB,CAAd;ACDF,MAAIoB,UAAJ,EAAgB;ADEd,ACDA,QDCItB,OAAJ,EAAa;AACX,ACDH,aDCUG,UAAU,CAACH,OAAO,CAAC,CAAD,CAAR,CAAjB;AACD;AACF,ACDDsB,EAAAA,UAAU,GAAGE,UAAU,CAAC,YAAY;AAClC,QAAIC,KAAK,GAAGC,QAAQ,CAACC,gBAAT,CAA0B,wBAA1B,CAAZ;ADEF,SAAO,GAAP;AACD,ACFG,SAAK,IAAIC,CAAC,GAAG,CAAb,EAAgBA,CAAC,GAAGH,KAAK,CAACI,MAA1B,EAAkCD,CAAC,EAAnC,EAAuC;AACrC,UAAIrB,MAAM,CAACJ,UAAP,CAAkBsB,KAAK,CAACG,CAAD,CAAL,CAASb,IAA3B,MAAqCR,MAAM,CAACV,YAAP,EAAzC,EAAgE;ADGtE,ACFQY,QAAAA,CDECN,SCFS,CDElB,ACFmBsB,CDECrB,GAApB,CCFwB,CDEC,ACFAwB,CAAD,CAAN,CAAV;ADGN,ACFK,SDEE,CAAC,KAAKxB,GAAN,EAAWC,OAAX,CAAmB,sEAAnB,EAA2F,IAA3F,IAAmG,GAA1G;AACD,ACFI;;ADILC,ACFIgB,IAAAA,GDEG,CAACzB,MCFM,GAAG,GDEjB,CCFI,EDEmBD,kBAAvB;AACAU,ACFG,GATsB,EASpB,EDEE,ACXkB,CDWjBH,ACXN,UDWF,GAAqBA,UAArB;ACDC;;AAED2B,MAAM,CAACxB,OAAP,GAAiBiB,SAAjB","file":"custom.d5cc83c2.js","sourceRoot":"../src","sourcesContent":["var bundleURL = null;\nfunction getBundleURLCached() {\n if (!bundleURL) {\n bundleURL = getBundleURL();\n }\n\n return bundleURL;\n}\n\nfunction getBundleURL() {\n // Attempt to find the URL of the current script and use that as the base URL\n try {\n throw new Error;\n } catch (err) {\n var matches = ('' + err.stack).match(/(https?|file|ftp|chrome-extension|moz-extension):\\/\\/[^)\\n]+/g);\n if (matches) {\n return getBaseURL(matches[0]);\n }\n }\n\n return '/';\n}\n\nfunction getBaseURL(url) {\n return ('' + url).replace(/^((?:https?|file|ftp|chrome-extension|moz-extension):\\/\\/.+)\\/[^/]+$/, '$1') + '/';\n}\n\nexports.getBundleURL = getBundleURLCached;\nexports.getBaseURL = getBaseURL;\n","var bundle = require('./bundle-url');\n\nfunction updateLink(link) {\n var newLink = link.cloneNode();\n newLink.onload = function () {\n link.remove();\n };\n newLink.href = link.href.split('?')[0] + '?' + Date.now();\n link.parentNode.insertBefore(newLink, link.nextSibling);\n}\n\nvar cssTimeout = null;\nfunction reloadCSS() {\n if (cssTimeout) {\n return;\n }\n\n cssTimeout = setTimeout(function () {\n var links = document.querySelectorAll('link[rel=\"stylesheet\"]');\n for (var i = 0; i < links.length; i++) {\n if (bundle.getBaseURL(links[i].href) === bundle.getBundleURL()) {\n updateLink(links[i]);\n }\n }\n\n cssTimeout = null;\n }, 50);\n}\n\nmodule.exports = reloadCSS;\n"]}
|
14
dist/index.html
vendored
Normal file
14
dist/index.html
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<!doctype HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
|
||||||
|
<link href="/custom.d5cc83c2.css" rel="stylesheet">
|
||||||
|
<title>Built to Spell</title>
|
||||||
|
<script src="/custom.d5cc83c2.js"></script></head>
|
||||||
|
<body class="bg-gray-50">
|
||||||
|
<div id="app-root" class="w-screen h-screen"></div>
|
||||||
|
<script type="text/javascript" src="/src.f69400ca.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
30667
dist/src.f69400ca.js
vendored
Normal file
30667
dist/src.f69400ca.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/src.f69400ca.js.map
vendored
Normal file
1
dist/src.f69400ca.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
16
firebase.json
Normal file
16
firebase.json
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"hosting": {
|
||||||
|
"public": "dist",
|
||||||
|
"ignore": [
|
||||||
|
"firebase.json",
|
||||||
|
"**/.*",
|
||||||
|
"**/node_modules/**"
|
||||||
|
],
|
||||||
|
"rewrites": [
|
||||||
|
{
|
||||||
|
"source": "**",
|
||||||
|
"destination": "/index.html"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
19728
package-lock.json
generated
Normal file
19728
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
38
package.json
Normal file
38
package.json
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"name": "built-to-spell",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "NYT Spelling Bee simulator",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint src",
|
||||||
|
"deploy": "rm -r .cache ; parcel build -d dist src/index.html && firebase deploy --only=hosting",
|
||||||
|
"start": "rm -r .cache ; parcel -d dist src/index.html"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+ssh://git@gitlab.com/brentschroeter/built-to-spell.git"
|
||||||
|
},
|
||||||
|
"author": "Brent Schroeter <brent@brentschroeter.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://gitlab.com/brentschroeter/built-to-spell/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://gitlab.com/brentschroeter/built-to-spell#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^17.0.1",
|
||||||
|
"react-dom": "^17.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.12.10",
|
||||||
|
"@types/node": "^14.14.20",
|
||||||
|
"@types/react": "^17.0.0",
|
||||||
|
"@types/react-dom": "^17.0.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^4.11.1",
|
||||||
|
"eslint": "^7.17.0",
|
||||||
|
"eslint-config-airbnb": "^18.2.1",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||||
|
"eslint-plugin-react": "^7.22.0",
|
||||||
|
"parcel-bundler": "^1.12.4",
|
||||||
|
"typescript": "^4.1.3"
|
||||||
|
}
|
||||||
|
}
|
27
src/app.tsx
Normal file
27
src/app.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import React, {
|
||||||
|
FunctionComponent,
|
||||||
|
useContext,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import GameView from './game-view.tsx';
|
||||||
|
import SetupView from './setup-view';
|
||||||
|
import { StoreContext } from './store';
|
||||||
|
|
||||||
|
const App: FunctionComponent = () => {
|
||||||
|
const { state } = useContext(StoreContext);
|
||||||
|
|
||||||
|
const { view } = state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto max-w-sm min-h-screen p-2 bg-white shadow-lg">
|
||||||
|
{view === 'GAME' && (
|
||||||
|
<GameView />
|
||||||
|
)}
|
||||||
|
{view === 'SETUP' && (
|
||||||
|
<SetupView />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
42
src/custom.css
Normal file
42
src/custom.css
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
div.hexagon {
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0 0 60px 0;
|
||||||
|
position: relative;
|
||||||
|
-o-transform: skewX(30deg);
|
||||||
|
-moz-transform: skewX(30deg);
|
||||||
|
-webkit-transform: skewX(30deg);
|
||||||
|
-ms-transform: skewX(30deg);
|
||||||
|
transform: skewX(30deg);
|
||||||
|
visibility: hidden;
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.hexagon>div {
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
-o-transform: skewY(-50deg);
|
||||||
|
-moz-transform: skewY(-50deg);
|
||||||
|
-webkit-transform: skewY(-50deg);
|
||||||
|
-ms-transform: skewY(-50deg);
|
||||||
|
transform: skewX(-50deg);
|
||||||
|
visibility: hidden;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.hexagon>div>button {
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
-o-transform: skewY(20deg);
|
||||||
|
-moz-transform: skewY(20deg);
|
||||||
|
-webkit-transform: skewY(20deg);
|
||||||
|
-ms-transform: skewY(20deg);
|
||||||
|
top: 0;
|
||||||
|
transform: skewX(30deg);
|
||||||
|
visibility: visible;
|
||||||
|
width: 100%;
|
||||||
|
}
|
97
src/game-view.tsx
Normal file
97
src/game-view.tsx
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
import React, {
|
||||||
|
FunctionComponent,
|
||||||
|
useContext,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import Honeycomb from './honeycomb';
|
||||||
|
import { StoreContext } from './store';
|
||||||
|
|
||||||
|
const GameView: FunctionComponent = () => {
|
||||||
|
const { dispatch, state } = useContext(StoreContext);
|
||||||
|
|
||||||
|
const {
|
||||||
|
currentWord,
|
||||||
|
letters,
|
||||||
|
score,
|
||||||
|
words,
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mb-6 flex justify-end items-center">
|
||||||
|
<button
|
||||||
|
className="px-6 py-4 text-xl"
|
||||||
|
onClick={() => dispatch({ type: 'SWITCH_VIEW', payload: 'SETUP' })}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
☰
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="my-6 flex justify-center">
|
||||||
|
<div
|
||||||
|
className="w-8 h-8 rounded-full bg-yellow-400 text-xs flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{score}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="m-6 px-2 py-1 border border-gray-200 rounded font-light text-sm flex flex-wrap"
|
||||||
|
>
|
||||||
|
{words.length === 0 && (
|
||||||
|
<div className="m-1 text-gray-400">
|
||||||
|
Your words...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{words.map((word) => (
|
||||||
|
<div
|
||||||
|
className="m-1"
|
||||||
|
key={word}
|
||||||
|
>
|
||||||
|
{word}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="mb-6 mt-16 h-7 font-bold text-center text-lg">
|
||||||
|
{currentWord.split('').map((ch, i) => (
|
||||||
|
<span
|
||||||
|
className={ch === letters[0] ? 'text-yellow-400' : undefined}
|
||||||
|
key={i /* eslint-disable-line react/no-array-index-key */}
|
||||||
|
>
|
||||||
|
{ch}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
<span className="text-yellow-400 animate-pulse text-2xl font-light relative">
|
||||||
|
|
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Honeycomb />
|
||||||
|
<div className="my-6 flex justify-center items-center space-x-3">
|
||||||
|
<button
|
||||||
|
className="h-10 w-20 rounded-full border border-gray-200 text-sm"
|
||||||
|
onClick={() => dispatch({ type: 'DELETE_LETTER' })}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="h-10 w-10 rounded-full border border-gray-200 text-xl pb-1"
|
||||||
|
onClick={() => dispatch({ type: 'SHUFFLE_LETTERS' })}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
⟳
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="h-10 w-20 rounded-full border border-gray-200 text-sm"
|
||||||
|
onClick={() => dispatch({ type: 'ENTER_WORD' })}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Enter
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GameView;
|
22
src/hexagon.tsx
Normal file
22
src/hexagon.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import React, { SFC } from 'react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
accent?: boolean;
|
||||||
|
onClick(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Hexagon: SFC<Props> = ({ accent, children, onClick }) => (
|
||||||
|
<div className="hexagon">
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
className={`${accent ? 'bg-yellow-400' : 'bg-gray-200'} font-bold text-xl`}
|
||||||
|
onClick={onClick}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Hexagon;
|
69
src/honeycomb.tsx
Normal file
69
src/honeycomb.tsx
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import React, {
|
||||||
|
CSSProperties,
|
||||||
|
FunctionComponent,
|
||||||
|
useContext,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import Hexagon from './hexagon';
|
||||||
|
import { StoreContext } from './store';
|
||||||
|
|
||||||
|
const hexagonSizePx = 60;
|
||||||
|
|
||||||
|
const positions: CSSProperties[] = [{
|
||||||
|
position: 'absolute',
|
||||||
|
marginLeft: hexagonSizePx * 1.1,
|
||||||
|
marginTop: hexagonSizePx * 1.0,
|
||||||
|
visibility: 'hidden',
|
||||||
|
}];
|
||||||
|
for (let i = 0; i < 6; i += 1) {
|
||||||
|
positions.push({
|
||||||
|
position: 'absolute',
|
||||||
|
marginLeft: hexagonSizePx * (1.1 + 1.1 * Math.cos((Math.PI * (i + 0.5)) / 3)),
|
||||||
|
marginTop: hexagonSizePx * (1.0 + 1.1 * Math.sin((Math.PI * (i + 0.5)) / 3)),
|
||||||
|
visibility: 'hidden',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const containerStyle: CSSProperties = {
|
||||||
|
margin: '0 auto',
|
||||||
|
height: hexagonSizePx * 3.2,
|
||||||
|
position: 'relative',
|
||||||
|
width: hexagonSizePx * 3.2,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Honeycomb: FunctionComponent = () => {
|
||||||
|
const { dispatch, state } = useContext(StoreContext);
|
||||||
|
|
||||||
|
const { letters } = state;
|
||||||
|
|
||||||
|
if (letters.length !== 7) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Waiting for letters...
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="mx-auto"
|
||||||
|
style={containerStyle}
|
||||||
|
>
|
||||||
|
{letters.map((ch, i) => (
|
||||||
|
<div
|
||||||
|
key={ch}
|
||||||
|
style={positions[i]}
|
||||||
|
>
|
||||||
|
<Hexagon
|
||||||
|
accent={i === 0}
|
||||||
|
onClick={() => dispatch({ type: 'ENTER_LETTER', payload: ch })}
|
||||||
|
>
|
||||||
|
{ch}
|
||||||
|
</Hexagon>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Honeycomb;
|
14
src/index.html
Normal file
14
src/index.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<!doctype HTML>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
|
||||||
|
<link href="/custom.css" rel="stylesheet">
|
||||||
|
<title>Built to Spell</title>
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-50">
|
||||||
|
<div id="app-root" class="w-screen h-screen"></div>
|
||||||
|
<script type="text/javascript" src="./index.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
14
src/index.tsx
Normal file
14
src/index.tsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { render } from 'react-dom';
|
||||||
|
|
||||||
|
import App from './app';
|
||||||
|
import { StoreProvider } from './store';
|
||||||
|
|
||||||
|
render(
|
||||||
|
(
|
||||||
|
<StoreProvider>
|
||||||
|
<App />
|
||||||
|
</StoreProvider>
|
||||||
|
),
|
||||||
|
document.getElementById('app-root'),
|
||||||
|
);
|
103
src/setup-view.tsx
Normal file
103
src/setup-view.tsx
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import React, {
|
||||||
|
ChangeEventHandler,
|
||||||
|
FunctionComponent,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import { StoreContext } from './store';
|
||||||
|
|
||||||
|
const range7 = [0, 1, 2, 3, 4, 5, 6];
|
||||||
|
|
||||||
|
const isValidLetter = (ch: string): boolean => ch.length === 1 && ch >= 'A' && ch <= 'Z';
|
||||||
|
|
||||||
|
const SetupView: FunctionComponent = () => {
|
||||||
|
const { dispatch, state } = useContext(StoreContext);
|
||||||
|
const { letters } = state;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const firstInput = document.getElementById('setup-view-letter-selector-0');
|
||||||
|
if (firstInput) {
|
||||||
|
firstInput.focus();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleLetterChange = (i: number): ChangeEventHandler<HTMLInputElement> => (ev) => {
|
||||||
|
let newLetter = ev.currentTarget.value.slice(-1).toUpperCase();
|
||||||
|
if (letters.length > i && newLetter === letters[i]) {
|
||||||
|
newLetter = ev.currentTarget.value[0].toUpperCase();
|
||||||
|
}
|
||||||
|
if (isValidLetter(newLetter)) {
|
||||||
|
let idx = letters.indexOf(newLetter);
|
||||||
|
if (idx === -1) {
|
||||||
|
idx = i;
|
||||||
|
}
|
||||||
|
const newLetters = [...letters];
|
||||||
|
if (newLetters.length > idx) {
|
||||||
|
newLetters.splice(idx, 1, newLetter);
|
||||||
|
} else {
|
||||||
|
newLetters.push(newLetter);
|
||||||
|
}
|
||||||
|
dispatch({ type: 'SET_LETTERS', payload: newLetters });
|
||||||
|
let nextIdx = idx + 1;
|
||||||
|
if (nextIdx > newLetters.length) {
|
||||||
|
nextIdx = newLetters.length;
|
||||||
|
}
|
||||||
|
nextIdx = Math.min(nextIdx, 6);
|
||||||
|
const nextInput = document.getElementById(`setup-view-letter-selector-${nextIdx}`);
|
||||||
|
if (nextInput) {
|
||||||
|
nextInput.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<button
|
||||||
|
className="p-3 text-lg disabled:text-gray-400"
|
||||||
|
disabled={letters.length !== 7}
|
||||||
|
onClick={() => dispatch({ type: 'SWITCH_VIEW', payload: 'GAME' })}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<h2 className="mt-6 text-center text-gray-600 font-light">
|
||||||
|
Choose letters:
|
||||||
|
</h2>
|
||||||
|
<div className="my-3 flex justify-center space-x-2">
|
||||||
|
{range7.map((i) => (
|
||||||
|
<input
|
||||||
|
className={`
|
||||||
|
w-10 py-2 appearance-none outline-none
|
||||||
|
border-2 ${i === 0 ? 'border-yellow-400' : 'border-gray-200'}
|
||||||
|
rounded text-center
|
||||||
|
`}
|
||||||
|
key={i}
|
||||||
|
id={`setup-view-letter-selector-${i}`}
|
||||||
|
onChange={handleLetterChange(i)}
|
||||||
|
type="text"
|
||||||
|
value={letters.length > i ? letters[i] : ''}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<button
|
||||||
|
className={`
|
||||||
|
my-6 px-4 py-2 rounded font-bold text-sm
|
||||||
|
bg-yellow-400 hover:bg-yellow-500 transition-all
|
||||||
|
${letters.length < 7 ? 'opacity-0 cursor-default' : ''}
|
||||||
|
`}
|
||||||
|
disabled={letters.length !== 7}
|
||||||
|
onClick={() => dispatch({ type: 'SWITCH_VIEW', payload: 'GAME' })}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Play Built to Spell
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SetupView;
|
137
src/store.tsx
Normal file
137
src/store.tsx
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
import React, {
|
||||||
|
createContext,
|
||||||
|
Dispatch,
|
||||||
|
FunctionComponent,
|
||||||
|
Reducer,
|
||||||
|
useMemo,
|
||||||
|
useReducer,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import twl06 = require('./twl06.json');
|
||||||
|
|
||||||
|
export interface State {
|
||||||
|
currentWord: string;
|
||||||
|
letters: string[];
|
||||||
|
score: number;
|
||||||
|
view: 'GAME' | 'SETUP';
|
||||||
|
words: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: State = {
|
||||||
|
currentWord: '',
|
||||||
|
letters: [],
|
||||||
|
score: 0,
|
||||||
|
view: 'SETUP',
|
||||||
|
words: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface DeleteLetterAction {
|
||||||
|
type: 'DELETE_LETTER';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnterLetterAction {
|
||||||
|
type: 'ENTER_LETTER';
|
||||||
|
payload: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnterWordAction {
|
||||||
|
type: 'ENTER_WORD';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetLettersAction {
|
||||||
|
type: 'SET_LETTERS';
|
||||||
|
payload: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShuffleLettersAction {
|
||||||
|
type: 'SHUFFLE_LETTERS';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SwitchViewAction {
|
||||||
|
type: 'SWITCH_VIEW';
|
||||||
|
payload: State['view'];
|
||||||
|
}
|
||||||
|
|
||||||
|
type Action = DeleteLetterAction
|
||||||
|
| EnterLetterAction
|
||||||
|
| EnterWordAction
|
||||||
|
| SetLettersAction
|
||||||
|
| ShuffleLettersAction
|
||||||
|
| SwitchViewAction;
|
||||||
|
|
||||||
|
const shuffle = (letters: string[]): string[] => {
|
||||||
|
const newLetters = letters.slice(0, 1);
|
||||||
|
const choices = letters.slice(1);
|
||||||
|
for (let i = 1; i < letters.length; i += 1) {
|
||||||
|
const choice = Math.floor(Math.random() * choices.length);
|
||||||
|
newLetters.push(choices.splice(choice, 1)[0]);
|
||||||
|
}
|
||||||
|
return newLetters;
|
||||||
|
};
|
||||||
|
|
||||||
|
const canSubmitWord = (state: State): boolean => {
|
||||||
|
const { currentWord, words } = state;
|
||||||
|
return currentWord.length > 3 && !words.includes(currentWord)
|
||||||
|
&& currentWord.split('').includes(state.letters[0])
|
||||||
|
&& twl06.includes(currentWord.toLowerCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
const scoreWord = (word: string): number => (word.length === 4 ? 1 : word.length);
|
||||||
|
|
||||||
|
const enterWordReducer: Reducer<State, EnterWordAction> = (state) => {
|
||||||
|
const valid = canSubmitWord(state);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
currentWord: '',
|
||||||
|
score: state.score + (valid ? scoreWord(state.currentWord) : 0),
|
||||||
|
words: valid ? [...state.words, state.currentWord] : state.words,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const reducer: Reducer<State, Action> = (state, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'DELETE_LETTER':
|
||||||
|
return { ...state, currentWord: state.currentWord.slice(0, -1) };
|
||||||
|
case 'ENTER_LETTER':
|
||||||
|
return { ...state, currentWord: `${state.currentWord}${action.payload}` };
|
||||||
|
case 'ENTER_WORD':
|
||||||
|
return enterWordReducer(state, action);
|
||||||
|
case 'SET_LETTERS':
|
||||||
|
return action.payload.length === state.letters.length
|
||||||
|
&& action.payload.every((ch, i) => state.letters[i] === ch)
|
||||||
|
? state
|
||||||
|
: {
|
||||||
|
...state,
|
||||||
|
currentWord: '',
|
||||||
|
letters: action.payload,
|
||||||
|
score: 0,
|
||||||
|
words: [],
|
||||||
|
};
|
||||||
|
case 'SHUFFLE_LETTERS':
|
||||||
|
return { ...state, letters: shuffle(state.letters) };
|
||||||
|
case 'SWITCH_VIEW':
|
||||||
|
return { ...state, view: action.payload };
|
||||||
|
default:
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ContextValue {
|
||||||
|
state: State;
|
||||||
|
dispatch: Dispatch<Action>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StoreContext = createContext({
|
||||||
|
state: initialState,
|
||||||
|
dispatch: () => undefined,
|
||||||
|
} as ContextValue);
|
||||||
|
|
||||||
|
export const StoreProvider: FunctionComponent = ({ children }) => {
|
||||||
|
const [state, dispatch] = useReducer(reducer, initialState);
|
||||||
|
const ctxVal = useMemo(() => ({ state, dispatch }), [state, dispatch]);
|
||||||
|
return (
|
||||||
|
<StoreContext.Provider value={ctxVal}>
|
||||||
|
{children}
|
||||||
|
</StoreContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
1
src/twl06.json
Normal file
1
src/twl06.json
Normal file
File diff suppressed because one or more lines are too long
42
tsconfig.json
Normal file
42
tsconfig.json
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"alwaysStrict": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"jsx": "react",
|
||||||
|
"lib": [
|
||||||
|
"es7",
|
||||||
|
"dom"
|
||||||
|
],
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"strictFunctionTypes": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"strictPropertyInitialization": true,
|
||||||
|
"target": "es6",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*",
|
||||||
|
".eslintrc.js",
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue