Big update
This commit is contained in:
6
.babelrc
6
.babelrc
@@ -4,5 +4,9 @@
|
|||||||
"@babel/env",
|
"@babel/env",
|
||||||
"@babel/preset-react"
|
"@babel/preset-react"
|
||||||
],
|
],
|
||||||
"plugins": ["@babel/plugin-proposal-class-properties"]
|
"plugins": [
|
||||||
|
"@babel/plugin-transform-runtime",
|
||||||
|
["@babel/plugin-proposal-decorators", {"legacy": true}],
|
||||||
|
["@babel/plugin-proposal-class-properties", {"loose": true}]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
264
package-lock.json
generated
264
package-lock.json
generated
@@ -571,6 +571,201 @@
|
|||||||
"@babel/helper-plugin-utils": "^7.8.3"
|
"@babel/helper-plugin-utils": "^7.8.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/plugin-proposal-decorators": {
|
||||||
|
"version": "7.10.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.10.5.tgz",
|
||||||
|
"integrity": "sha512-Sc5TAQSZuLzgY0664mMDn24Vw2P8g/VhyLyGPaWiHahhgLqeZvcGeyBZOrJW0oSKIK2mvQ22a1ENXBIQLhrEiQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-create-class-features-plugin": "^7.10.5",
|
||||||
|
"@babel/helper-plugin-utils": "^7.10.4",
|
||||||
|
"@babel/plugin-syntax-decorators": "^7.10.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/code-frame": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
|
||||||
|
"integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/highlight": "^7.10.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/generator": {
|
||||||
|
"version": "7.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.0.tgz",
|
||||||
|
"integrity": "sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.11.0",
|
||||||
|
"jsesc": "^2.5.1",
|
||||||
|
"source-map": "^0.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-create-class-features-plugin": {
|
||||||
|
"version": "7.10.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz",
|
||||||
|
"integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-function-name": "^7.10.4",
|
||||||
|
"@babel/helper-member-expression-to-functions": "^7.10.5",
|
||||||
|
"@babel/helper-optimise-call-expression": "^7.10.4",
|
||||||
|
"@babel/helper-plugin-utils": "^7.10.4",
|
||||||
|
"@babel/helper-replace-supers": "^7.10.4",
|
||||||
|
"@babel/helper-split-export-declaration": "^7.10.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-function-name": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz",
|
||||||
|
"integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-get-function-arity": "^7.10.4",
|
||||||
|
"@babel/template": "^7.10.4",
|
||||||
|
"@babel/types": "^7.10.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-get-function-arity": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz",
|
||||||
|
"integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.10.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-member-expression-to-functions": {
|
||||||
|
"version": "7.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz",
|
||||||
|
"integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.11.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-optimise-call-expression": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz",
|
||||||
|
"integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.10.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-plugin-utils": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
|
||||||
|
"integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@babel/helper-replace-supers": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz",
|
||||||
|
"integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-member-expression-to-functions": "^7.10.4",
|
||||||
|
"@babel/helper-optimise-call-expression": "^7.10.4",
|
||||||
|
"@babel/traverse": "^7.10.4",
|
||||||
|
"@babel/types": "^7.10.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-split-export-declaration": {
|
||||||
|
"version": "7.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz",
|
||||||
|
"integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.11.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-validator-identifier": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
|
||||||
|
"integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@babel/highlight": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
|
||||||
|
"integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-validator-identifier": "^7.10.4",
|
||||||
|
"chalk": "^2.0.0",
|
||||||
|
"js-tokens": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/parser": {
|
||||||
|
"version": "7.11.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.3.tgz",
|
||||||
|
"integrity": "sha512-REo8xv7+sDxkKvoxEywIdsNFiZLybwdI7hcT5uEPyQrSMB4YQ973BfC9OOrD/81MaIjh6UxdulIQXkjmiH3PcA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@babel/template": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz",
|
||||||
|
"integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/code-frame": "^7.10.4",
|
||||||
|
"@babel/parser": "^7.10.4",
|
||||||
|
"@babel/types": "^7.10.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/traverse": {
|
||||||
|
"version": "7.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.0.tgz",
|
||||||
|
"integrity": "sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/code-frame": "^7.10.4",
|
||||||
|
"@babel/generator": "^7.11.0",
|
||||||
|
"@babel/helper-function-name": "^7.10.4",
|
||||||
|
"@babel/helper-split-export-declaration": "^7.11.0",
|
||||||
|
"@babel/parser": "^7.11.0",
|
||||||
|
"@babel/types": "^7.11.0",
|
||||||
|
"debug": "^4.1.0",
|
||||||
|
"globals": "^11.1.0",
|
||||||
|
"lodash": "^4.17.19"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/types": {
|
||||||
|
"version": "7.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz",
|
||||||
|
"integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-validator-identifier": "^7.10.4",
|
||||||
|
"lodash": "^4.17.19",
|
||||||
|
"to-fast-properties": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ms": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lodash": {
|
||||||
|
"version": "4.17.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||||
|
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/plugin-proposal-dynamic-import": {
|
"@babel/plugin-proposal-dynamic-import": {
|
||||||
"version": "7.8.3",
|
"version": "7.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz",
|
||||||
@@ -661,6 +856,23 @@
|
|||||||
"@babel/helper-plugin-utils": "^7.8.0"
|
"@babel/helper-plugin-utils": "^7.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/plugin-syntax-decorators": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.10.4.tgz",
|
||||||
|
"integrity": "sha512-2NaoC6fAk2VMdhY1eerkfHV+lVYC1u8b+jmRJISqANCJlTxYy19HGdIkkQtix2UtkcPuPu+IlDgrVseZnU03bw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-plugin-utils": "^7.10.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-plugin-utils": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
|
||||||
|
"integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/plugin-syntax-dynamic-import": {
|
"@babel/plugin-syntax-dynamic-import": {
|
||||||
"version": "7.8.3",
|
"version": "7.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
|
||||||
@@ -1052,6 +1264,58 @@
|
|||||||
"@babel/helper-plugin-utils": "^7.8.3"
|
"@babel/helper-plugin-utils": "^7.8.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@babel/plugin-transform-runtime": {
|
||||||
|
"version": "7.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.11.0.tgz",
|
||||||
|
"integrity": "sha512-LFEsP+t3wkYBlis8w6/kmnd6Kb1dxTd+wGJ8MlxTGzQo//ehtqlVL4S9DNUa53+dtPSQobN2CXx4d81FqC58cw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-module-imports": "^7.10.4",
|
||||||
|
"@babel/helper-plugin-utils": "^7.10.4",
|
||||||
|
"resolve": "^1.8.1",
|
||||||
|
"semver": "^5.5.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/helper-module-imports": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz",
|
||||||
|
"integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.10.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-plugin-utils": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
|
||||||
|
"integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@babel/helper-validator-identifier": {
|
||||||
|
"version": "7.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
|
||||||
|
"integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@babel/types": {
|
||||||
|
"version": "7.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz",
|
||||||
|
"integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@babel/helper-validator-identifier": "^7.10.4",
|
||||||
|
"lodash": "^4.17.19",
|
||||||
|
"to-fast-properties": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lodash": {
|
||||||
|
"version": "4.17.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||||
|
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@babel/plugin-transform-shorthand-properties": {
|
"@babel/plugin-transform-shorthand-properties": {
|
||||||
"version": "7.8.3",
|
"version": "7.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build-dev": "webpack --mode development && npm postbuild",
|
"build-dev": "webpack --mode development && npm postbuild",
|
||||||
"build": "webpack --mode production",
|
"build": "webpack --mode production",
|
||||||
"postbuild": "rsync -avu --delete dist/ ../kadi_backend/static/frontend",
|
"postbuild": "rsync -avu --delete dist/ ../backend/static/frontend",
|
||||||
"start": "webpack-dev-server --mode development",
|
"start": "webpack-dev-server --mode development",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
@@ -16,6 +16,8 @@
|
|||||||
"@babel/cli": "^7.8.4",
|
"@babel/cli": "^7.8.4",
|
||||||
"@babel/core": "^7.9.6",
|
"@babel/core": "^7.9.6",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||||
|
"@babel/plugin-proposal-decorators": "^7.10.5",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.11.0",
|
||||||
"@babel/preset-env": "^7.9.6",
|
"@babel/preset-env": "^7.9.6",
|
||||||
"@babel/preset-react": "^7.9.4",
|
"@babel/preset-react": "^7.9.4",
|
||||||
"@babel/preset-typescript": "^7.9.0",
|
"@babel/preset-typescript": "^7.9.0",
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
import App from './App';
|
|
||||||
|
|
||||||
test('renders learn react link', () => {
|
|
||||||
const { getByText } = render(<App />);
|
|
||||||
const linkElement = getByText(/learn react/i);
|
|
||||||
expect(linkElement).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
43
src/App.tsx
43
src/App.tsx
@@ -1,5 +1,5 @@
|
|||||||
import React, {ReactNode} from "react";
|
import React, {ReactNode} from "react";
|
||||||
import {BrowserRouter as Router, Route} from "react-router-dom";
|
import {BrowserRouter as Router, Route, Switch} from "react-router-dom";
|
||||||
import {Redirect} from "react-router";
|
import {Redirect} from "react-router";
|
||||||
import {IntlStrings} from "./static/strings";
|
import {IntlStrings} from "./static/strings";
|
||||||
import {PageId, SupportedLang, supportedLangToIntlDTF} from "./enums";
|
import {PageId, SupportedLang, supportedLangToIntlDTF} from "./enums";
|
||||||
@@ -9,6 +9,8 @@ import HomePage from "./Components/HomePage";
|
|||||||
import {SERVER_BASE_NAME} from "./index";
|
import {SERVER_BASE_NAME} from "./index";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import UserContext, {IUserContext} from "./Contexts/UserContext";
|
import UserContext, {IUserContext} from "./Contexts/UserContext";
|
||||||
|
import KadiPageRoute from "./Components/KadiPageRoute";
|
||||||
|
import KadiStatsService from "./Services/KadiStatsService";
|
||||||
|
|
||||||
interface AppState {
|
interface AppState {
|
||||||
userContext: IUserContext;
|
userContext: IUserContext;
|
||||||
@@ -24,25 +26,18 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
|
|
||||||
this.updateUserContext = (username, loggedIn) => {
|
this.updateUserContext = (username, loggedIn) => {
|
||||||
this.setState({userContext: {
|
this.setState({userContext: {
|
||||||
|
...this.state.userContext,
|
||||||
username: username,
|
username: username,
|
||||||
loggedIn: loggedIn,
|
loggedIn: loggedIn,
|
||||||
updateUserContext: this.updateUserContext,
|
|
||||||
dateTimeFormatter: this.state.userContext.dateTimeFormatter,
|
|
||||||
currentLang: this.state.userContext.currentLang,
|
|
||||||
strings: this.state.userContext.strings,
|
|
||||||
changeLang: this.state.userContext.changeLang,
|
|
||||||
}});
|
}});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.changeLang = (lang: SupportedLang, submit=true) => {
|
this.changeLang = (lang: SupportedLang, submit=true) => {
|
||||||
this.setState({userContext: {
|
this.setState({userContext: {
|
||||||
|
...this.state.userContext,
|
||||||
dateTimeFormatter: supportedLangToIntlDTF[lang],
|
dateTimeFormatter: supportedLangToIntlDTF[lang],
|
||||||
strings: IntlStrings[lang],
|
strings: IntlStrings[lang],
|
||||||
currentLang: lang,
|
currentLang: lang,
|
||||||
changeLang: this.changeLang,
|
|
||||||
username: this.state.userContext.username,
|
|
||||||
loggedIn: this.state.userContext.loggedIn,
|
|
||||||
updateUserContext: this.state.userContext.updateUserContext,
|
|
||||||
}});
|
}});
|
||||||
if (submit) {
|
if (submit) {
|
||||||
this.submitLanguagePreference(lang);
|
this.submitLanguagePreference(lang);
|
||||||
@@ -78,7 +73,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
.catch(err => console.log(err));
|
.catch(err => console.log(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
submitLanguagePreference(lang: SupportedLang) {
|
submitLanguagePreference(lang: SupportedLang): void {
|
||||||
axios.put(SERVER_BASE_NAME + "/api/lang",
|
axios.put(SERVER_BASE_NAME + "/api/lang",
|
||||||
{lang: lang},
|
{lang: lang},
|
||||||
{headers: {"Content-Type": "application/json"}}
|
{headers: {"Content-Type": "application/json"}}
|
||||||
@@ -100,34 +95,12 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
<KadiPageRoute pageId={PageId.stats}/>
|
<KadiPageRoute pageId={PageId.stats}/>
|
||||||
<KadiPageRoute pageId={PageId.profile}/>
|
<KadiPageRoute pageId={PageId.profile}/>
|
||||||
<KadiPageRoute pageId={PageId.rulesets}/>
|
<KadiPageRoute pageId={PageId.rulesets}/>
|
||||||
<Route path={"/"}>
|
<Redirect strict={true} from="/stats/" to="/stats" />
|
||||||
<Redirect
|
<Redirect to={{pathname: "/"}} />
|
||||||
to={{
|
|
||||||
pathname: "/",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Route>
|
|
||||||
</Router>
|
</Router>
|
||||||
</UserContext.Provider>
|
</UserContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface KadiPageRouteProps {
|
|
||||||
pageId: PageId
|
|
||||||
}
|
|
||||||
|
|
||||||
const KadiPageRoute: React.FunctionComponent<KadiPageRouteProps> = (props: KadiPageRouteProps) => {
|
|
||||||
const {pageId} = props;
|
|
||||||
const PageComponent = pageComponentFromId[pageId];
|
|
||||||
return (
|
|
||||||
<Route path={"/" + pageId}>
|
|
||||||
<KadiPage activePage={pageId}>
|
|
||||||
<PageComponent/>
|
|
||||||
</KadiPage>
|
|
||||||
</Route>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
138
src/Components/CreateRulesetPanel.tsx
Normal file
138
src/Components/CreateRulesetPanel.tsx
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import React, {useContext, useState} from "react";
|
||||||
|
import {CellDef, RulesetSchemaDto} from "../Services/RulesetSchemaDto";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Form,
|
||||||
|
Header,
|
||||||
|
Segment,
|
||||||
|
Table
|
||||||
|
} from "semantic-ui-react";
|
||||||
|
import UserContext from "../Contexts/UserContext";
|
||||||
|
import RulesetBlockTable from "./RulesetBlockTable";
|
||||||
|
import RulesetDisplayPanel from "./RulesetDisplayPanel";
|
||||||
|
|
||||||
|
interface RulesetDisplayPanelProps {
|
||||||
|
onSubmitRuleset: (ruleset: RulesetSchemaDto) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CreateRulesetPanel: React.FunctionComponent<RulesetDisplayPanelProps> = (props) => {
|
||||||
|
const {onSubmitRuleset} = props;
|
||||||
|
const {strings: Locale} = useContext(UserContext);
|
||||||
|
const [currentRulesetBuild, updateCurrentRulesetBuild] = useState<RulesetSchemaDto>({
|
||||||
|
id: "",
|
||||||
|
label: Locale.rulesetsPage.newRuleset,
|
||||||
|
blocks: {},
|
||||||
|
});
|
||||||
|
const [newBlockInput, updateNewBlockInput] = useState({
|
||||||
|
label: "",
|
||||||
|
hasBonus: false,
|
||||||
|
bonusScore: 35,
|
||||||
|
bonusFor: 63,
|
||||||
|
cells: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAddBlock = () => {
|
||||||
|
if (newBlockInput.label) {
|
||||||
|
updateCurrentRulesetBuild({
|
||||||
|
...currentRulesetBuild,
|
||||||
|
blocks: {
|
||||||
|
...currentRulesetBuild.blocks,
|
||||||
|
[newBlockInput.label]: {
|
||||||
|
...newBlockInput,
|
||||||
|
bonusFor: newBlockInput.hasBonus ? newBlockInput.bonusFor : undefined,
|
||||||
|
bonusScore: newBlockInput.hasBonus ? newBlockInput.bonusScore : undefined,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddCell = (cellDef: CellDef, blockId: string) => {
|
||||||
|
if (cellDef.label) {
|
||||||
|
updateCurrentRulesetBuild({
|
||||||
|
...currentRulesetBuild,
|
||||||
|
blocks: {
|
||||||
|
...currentRulesetBuild.blocks,
|
||||||
|
[blockId]: {
|
||||||
|
...currentRulesetBuild.blocks[blockId],
|
||||||
|
cells: {
|
||||||
|
...currentRulesetBuild.blocks[blockId].cells,
|
||||||
|
[cellDef.label]: cellDef
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header size={"tiny"}>
|
||||||
|
{Locale.rulesetsPage.newRuleset}
|
||||||
|
</Header>
|
||||||
|
<RulesetDisplayPanel
|
||||||
|
ruleset={currentRulesetBuild}
|
||||||
|
editable={true}
|
||||||
|
loading={false}
|
||||||
|
onAddCell={handleAddCell}
|
||||||
|
/>
|
||||||
|
<Header size={"tiny"}>
|
||||||
|
{Locale.rulesetsPage.newBlock}
|
||||||
|
</Header>
|
||||||
|
<Segment>
|
||||||
|
<Form>
|
||||||
|
<Form.Field>
|
||||||
|
<label>{Locale.rulesetsPage.blockName + ": "}</label>
|
||||||
|
<input
|
||||||
|
placeholder={Locale.rulesetsPage.blockNamePlaceholder}
|
||||||
|
onChange={(e) => updateNewBlockInput({...newBlockInput, label: e.target.value})}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field>
|
||||||
|
<Checkbox
|
||||||
|
toggle={true}
|
||||||
|
onChange={(e, c) => updateNewBlockInput({...newBlockInput, hasBonus: c.checked ?? false})}
|
||||||
|
label={Locale.rulesetsPage.bonus}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
{newBlockInput.hasBonus && (
|
||||||
|
<>
|
||||||
|
<Form.Field width={4}>
|
||||||
|
<label>{Locale.rulesetsPage.bonusScore + ": "}</label>
|
||||||
|
<input
|
||||||
|
type={"number"}
|
||||||
|
value={newBlockInput.bonusScore}
|
||||||
|
onChange={(e) => updateNewBlockInput({...newBlockInput, bonusScore: Number(e.target.value)})}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field width={4}>
|
||||||
|
<label>{Locale.rulesetsPage.bonusThreshold + ": "}</label>
|
||||||
|
<input
|
||||||
|
type={"number"}
|
||||||
|
value={newBlockInput.bonusFor}
|
||||||
|
onChange={(e) => updateNewBlockInput({...newBlockInput, bonusFor: Number(e.target.value)})}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Form.Field>
|
||||||
|
<Button
|
||||||
|
onClick={handleAddBlock}
|
||||||
|
>
|
||||||
|
{Locale.rulesetsPage.addBlock}
|
||||||
|
</Button>
|
||||||
|
</Form.Field>
|
||||||
|
</Form>
|
||||||
|
</Segment>
|
||||||
|
<Button
|
||||||
|
fluid={true}
|
||||||
|
onClick={() => onSubmitRuleset(currentRulesetBuild)}
|
||||||
|
>
|
||||||
|
{Locale.rulesetsPage.submit}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateRulesetPanel;
|
||||||
@@ -10,10 +10,11 @@ interface GamesListProps {
|
|||||||
const GamesList: React.FunctionComponent<GamesListProps> = (props) => {
|
const GamesList: React.FunctionComponent<GamesListProps> = (props) => {
|
||||||
const {loading, gamesList} = props;
|
const {loading, gamesList} = props;
|
||||||
const Uctx = React.useContext(UserContext);
|
const Uctx = React.useContext(UserContext);
|
||||||
const listItems = gamesList.map(listing =>
|
const listItems = gamesList.map(listing => (
|
||||||
<ListItem key={listing.createdAt}>
|
<ListItem key={listing.id}>
|
||||||
Game played on: {Uctx.dateTimeFormatter.format(new Date(listing.createdAt))}
|
Game: {JSON.stringify(listing)}
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import {Header, List, ListItem} from "semantic-ui-react";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import UserContext from "../Contexts/UserContext";
|
import UserContext from "../Contexts/UserContext";
|
||||||
import {Guest} from "./ProfilePage";
|
import {Guest} from "./ProfilePage";
|
||||||
import HeaderSubHeader from "semantic-ui-react/dist/commonjs/elements/Header/HeaderSubheader";
|
|
||||||
import {SERVER_BASE_NAME} from "../index";
|
|
||||||
|
|
||||||
|
|
||||||
interface GuestListProps {
|
interface GuestListProps {
|
||||||
@@ -15,17 +13,17 @@ interface GuestListProps {
|
|||||||
const GuestList: React.FunctionComponent<GuestListProps> = (props) => {
|
const GuestList: React.FunctionComponent<GuestListProps> = (props) => {
|
||||||
const {loading, guestList, deleteGuest} = props;
|
const {loading, guestList, deleteGuest} = props;
|
||||||
const Uctx = React.useContext(UserContext);
|
const Uctx = React.useContext(UserContext);
|
||||||
const listItems = guestList.map(guest =>
|
const listItems = guestList.map(guest => (
|
||||||
<ListItem key={guest.id}>
|
<ListItem key={guest.id}>
|
||||||
{guest.nick} - <a onClick={() => deleteGuest(guest.id)}>{Uctx.strings.general.deleteCommand}</a>
|
{guest.nick} - <a onClick={() => deleteGuest(guest.id)}>{Uctx.strings.general.deleteCommand}</a>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
));
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header size={"medium"}>
|
<Header size={"medium"}>
|
||||||
{Uctx.strings.profilePage.guestsHeader}
|
{Uctx.strings.profilePage.guestsHeader}
|
||||||
</Header>
|
</Header>
|
||||||
{loading ? (
|
{loading && guestList.length === 0 ? (
|
||||||
<p>{Uctx.strings.profilePage.loadingGuests}</p>
|
<p>{Uctx.strings.profilePage.loadingGuests}</p>
|
||||||
) : (
|
) : (
|
||||||
<List bulleted={true}>
|
<List bulleted={true}>
|
||||||
|
|||||||
24
src/Components/KadiPageRoute.tsx
Normal file
24
src/Components/KadiPageRoute.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import {PageId} from "../enums";
|
||||||
|
import React from "react";
|
||||||
|
import {pageComponentFromId} from "../pageListings";
|
||||||
|
import {Route, useRouteMatch} from "react-router-dom";
|
||||||
|
import KadiPage from "./KadiPage";
|
||||||
|
|
||||||
|
interface KadiPageRouteProps {
|
||||||
|
pageId: PageId
|
||||||
|
}
|
||||||
|
|
||||||
|
const KadiPageRoute: React.FunctionComponent<KadiPageRouteProps> = (props: KadiPageRouteProps) => {
|
||||||
|
const {pageId} = props;
|
||||||
|
const {path} = useRouteMatch();
|
||||||
|
const PageComponent = pageComponentFromId[pageId];
|
||||||
|
return (
|
||||||
|
<Route path={`${path}${pageId}`}>
|
||||||
|
<KadiPage activePage={pageId}>
|
||||||
|
<PageComponent/>
|
||||||
|
</KadiPage>
|
||||||
|
</Route>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default KadiPageRoute;
|
||||||
14
src/Components/Loading.tsx
Normal file
14
src/Components/Loading.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {Dimmer, Loader} from "semantic-ui-react";
|
||||||
|
|
||||||
|
interface LoadingProps {}
|
||||||
|
|
||||||
|
const Loading: React.FunctionComponent<LoadingProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<Dimmer inverted={true} active={true} style={{height: "30vh"}}>
|
||||||
|
<Loader>Loading</Loader>
|
||||||
|
</Dimmer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Loading;
|
||||||
@@ -18,9 +18,7 @@ const KadiPageMainContent: React.FunctionComponent<KadiPageMainContentProps> = (
|
|||||||
<Container
|
<Container
|
||||||
className={"mainPageContentContainer"}
|
className={"mainPageContentContainer"}
|
||||||
>
|
>
|
||||||
<Segment>
|
|
||||||
{children}
|
{children}
|
||||||
</Segment>
|
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
207
src/Components/RulesetBlockTable.tsx
Normal file
207
src/Components/RulesetBlockTable.tsx
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
import {BlockDef, CellDef} from "../Services/RulesetSchemaDto";
|
||||||
|
import React, {useContext, useState} from "react";
|
||||||
|
import RulesetCellTableRow from "./RulesetCellTableRow";
|
||||||
|
import {Button, Dropdown, DropdownItemProps, Grid, Input, Table} from "semantic-ui-react";
|
||||||
|
import UserContext from "../Contexts/UserContext";
|
||||||
|
import {FieldType} from "../enums";
|
||||||
|
|
||||||
|
interface RulesetBlockTableProps {
|
||||||
|
id: string;
|
||||||
|
blockDef: BlockDef | null;
|
||||||
|
editable?: boolean;
|
||||||
|
onAddCell?: (cellDef: CellDef, blockId: string) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RulesetBlockTable: React.FunctionComponent<RulesetBlockTableProps> = (props) => {
|
||||||
|
const {id, blockDef, editable, onAddCell} = props;
|
||||||
|
const [currentCellInput, updateCurrentCellInput] = useState({
|
||||||
|
label: "",
|
||||||
|
maxMultiples: 0,
|
||||||
|
multiplier: 0,
|
||||||
|
fieldType: FieldType.number,
|
||||||
|
score: 0,
|
||||||
|
});
|
||||||
|
const {strings: Locale} = useContext(UserContext);
|
||||||
|
|
||||||
|
if (!blockDef) {
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
attached={true}
|
||||||
|
fixed={true}
|
||||||
|
celled={true}
|
||||||
|
>
|
||||||
|
<Table.Header>
|
||||||
|
<Table.Row>
|
||||||
|
<Table.HeaderCell
|
||||||
|
colSpan={"3"}
|
||||||
|
textAlign={"center"}
|
||||||
|
>
|
||||||
|
{Locale.rulesetsPage.noBlocks}
|
||||||
|
</Table.HeaderCell>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Header>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const idAndCellList = Object.entries(blockDef.cells);
|
||||||
|
let subText = `${Locale.rulesetsPage.bonus}: `;
|
||||||
|
|
||||||
|
if (blockDef.hasBonus) {
|
||||||
|
subText += `${Locale.general.yes},
|
||||||
|
${Locale.rulesetsPage.bonusScore}: ${blockDef.bonusScore},
|
||||||
|
${Locale.rulesetsPage.bonusThreshold}: ${blockDef.bonusFor}`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
subText += Locale.general.no;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldTypeOptions: DropdownItemProps[] = [
|
||||||
|
{value: FieldType.bool, key: FieldType.bool, text: Locale.rulesetsPage[FieldType.bool]},
|
||||||
|
{value: FieldType.multiplier, key: FieldType.multiplier, text: Locale.rulesetsPage[FieldType.multiplier]},
|
||||||
|
{value: FieldType.number, key: FieldType.number, text: Locale.rulesetsPage[FieldType.number]},
|
||||||
|
{value: FieldType.superkadi, key: FieldType.superkadi, text: Locale.rulesetsPage[FieldType.superkadi]},
|
||||||
|
];
|
||||||
|
|
||||||
|
const onChangeDropdown = (value: FieldType) => {
|
||||||
|
const newCell = {
|
||||||
|
...currentCellInput,
|
||||||
|
fieldType: value,
|
||||||
|
};
|
||||||
|
if (value === FieldType.bool) {
|
||||||
|
newCell.score = 0;
|
||||||
|
}
|
||||||
|
else if (value === FieldType.multiplier) {
|
||||||
|
newCell.multiplier = 0;
|
||||||
|
newCell.maxMultiples = 5;
|
||||||
|
}
|
||||||
|
else if (value === FieldType.superkadi) {
|
||||||
|
newCell.maxMultiples = 5;
|
||||||
|
newCell.score = 50;
|
||||||
|
}
|
||||||
|
updateCurrentCellInput(newCell);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table
|
||||||
|
attached={true}
|
||||||
|
fixed={true}
|
||||||
|
celled={true}
|
||||||
|
>
|
||||||
|
<Table.Header>
|
||||||
|
<Table.Row>
|
||||||
|
<Table.HeaderCell
|
||||||
|
colSpan={"3"}
|
||||||
|
textAlign={"center"}
|
||||||
|
>
|
||||||
|
{blockDef.label}
|
||||||
|
</Table.HeaderCell>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Header>
|
||||||
|
<Table.Body>
|
||||||
|
{idAndCellList.map(idAndCell => (
|
||||||
|
<RulesetCellTableRow
|
||||||
|
key={idAndCell[0]}
|
||||||
|
id={idAndCell[0]}
|
||||||
|
cellDef={idAndCell[1]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{editable && (
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Cell
|
||||||
|
style={{overflow: "visible"}}
|
||||||
|
colSpan={"3"}
|
||||||
|
>
|
||||||
|
<Grid>
|
||||||
|
<Grid.Row
|
||||||
|
columns={3}
|
||||||
|
verticalAlign={"middle"}
|
||||||
|
>
|
||||||
|
<Grid.Column width={4}>
|
||||||
|
<Input
|
||||||
|
fluid={true}
|
||||||
|
value={currentCellInput.label}
|
||||||
|
label={Locale.rulesetsPage.cellName}
|
||||||
|
placeholder={Locale.rulesetsPage.cellNamePlaceholder}
|
||||||
|
onChange={(e) => updateCurrentCellInput({...currentCellInput, label: e.target.value})}
|
||||||
|
/>
|
||||||
|
</Grid.Column>
|
||||||
|
<Grid.Column width={4}>
|
||||||
|
<Dropdown
|
||||||
|
selectedLabel={currentCellInput.fieldType}
|
||||||
|
placeholder={Locale.rulesetsPage.fieldTypePlaceholder}
|
||||||
|
onChange={(e, c) => onChangeDropdown(c.value as FieldType)}
|
||||||
|
options={fieldTypeOptions}
|
||||||
|
button={true}
|
||||||
|
/>
|
||||||
|
</Grid.Column>
|
||||||
|
<Grid.Column width={4}>
|
||||||
|
{currentCellInput.fieldType === FieldType.multiplier && (
|
||||||
|
<Input
|
||||||
|
label={Locale.rulesetsPage.multiplierPlaceholder}
|
||||||
|
fluid={true}
|
||||||
|
type={"number"}
|
||||||
|
value={currentCellInput.multiplier}
|
||||||
|
onChange={(e) => updateCurrentCellInput(
|
||||||
|
{...currentCellInput, multiplier: Number(e.target.value)})}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(currentCellInput.fieldType === FieldType.superkadi
|
||||||
|
|| currentCellInput.fieldType === FieldType.multiplier) && (
|
||||||
|
<Input
|
||||||
|
label={Locale.rulesetsPage.maxMultiplesPlaceholder}
|
||||||
|
fluid={true}
|
||||||
|
type={"number"}
|
||||||
|
value={currentCellInput.maxMultiples}
|
||||||
|
onChange={(e) => updateCurrentCellInput({
|
||||||
|
...currentCellInput,
|
||||||
|
maxMultiples: Number(e.target.value),
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{(currentCellInput.fieldType === FieldType.bool
|
||||||
|
|| currentCellInput.fieldType === FieldType.superkadi
|
||||||
|
|| currentCellInput.fieldType === FieldType.number) && (
|
||||||
|
<Input
|
||||||
|
label={Locale.rulesetsPage.valuePlaceholder}
|
||||||
|
fluid={true}
|
||||||
|
type={"number"}
|
||||||
|
value={currentCellInput.score}
|
||||||
|
onChange={(e) => updateCurrentCellInput(
|
||||||
|
{...currentCellInput, score: Number(e.target.value)})}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Grid.Column>
|
||||||
|
<Grid.Column
|
||||||
|
width={4}
|
||||||
|
textAlign={"right"}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (onAddCell) {
|
||||||
|
onAddCell(currentCellInput, id)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Locale.rulesetsPage.addCell}
|
||||||
|
</Button>
|
||||||
|
</Grid.Column>
|
||||||
|
</Grid.Row>
|
||||||
|
</Grid>
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
)}
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Cell
|
||||||
|
colSpan={"3"}
|
||||||
|
textAlign={"center"}
|
||||||
|
>
|
||||||
|
{subText}
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Body>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RulesetBlockTable;
|
||||||
31
src/Components/RulesetCellTableRow.tsx
Normal file
31
src/Components/RulesetCellTableRow.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import {BoolCellDef, CellDef, MultiplierCellDef, SuperkadiCellDef} from "../Services/RulesetSchemaDto";
|
||||||
|
import React, {useContext} from "react";
|
||||||
|
import UserContext from "../Contexts/UserContext";
|
||||||
|
import {Table} from "semantic-ui-react";
|
||||||
|
|
||||||
|
interface TableCellRowProps {
|
||||||
|
id: string;
|
||||||
|
cellDef: CellDef;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RulesetCellTableRow: React.FunctionComponent<TableCellRowProps> = (props) => {
|
||||||
|
const {id, cellDef} = props;
|
||||||
|
const {strings: Locale} = useContext(UserContext);
|
||||||
|
const displayValue = (cellDef as MultiplierCellDef).multiplier ??
|
||||||
|
(cellDef as SuperkadiCellDef | BoolCellDef).score ?? Locale.general.nA;
|
||||||
|
return (
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Cell key={id}>
|
||||||
|
{cellDef.label}
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
{Locale.rulesetsPage[cellDef.fieldType]}
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell disabled={displayValue === Locale.general.nA}>
|
||||||
|
{displayValue}
|
||||||
|
</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RulesetCellTableRow;
|
||||||
67
src/Components/RulesetDisplayPanel.tsx
Normal file
67
src/Components/RulesetDisplayPanel.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import React, {useContext} from "react";
|
||||||
|
import {
|
||||||
|
BlockDef,
|
||||||
|
BoolCellDef,
|
||||||
|
CellDef,
|
||||||
|
MultiplierCellDef,
|
||||||
|
RulesetSchemaDto,
|
||||||
|
SuperkadiCellDef
|
||||||
|
} from "../Services/RulesetSchemaDto";
|
||||||
|
import {Header, List, Table, TableBody} from "semantic-ui-react";
|
||||||
|
import UserContext from "../Contexts/UserContext";
|
||||||
|
import Loading from "./Loading";
|
||||||
|
import RulesetBlockTable from "./RulesetBlockTable";
|
||||||
|
|
||||||
|
interface RulesetDisplayPanelProps {
|
||||||
|
ruleset: RulesetSchemaDto;
|
||||||
|
loading: boolean;
|
||||||
|
editable?: boolean;
|
||||||
|
onAddCell?: (cellDef: CellDef, blockId: string) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RulesetDisplayPanel: React.FunctionComponent<RulesetDisplayPanelProps> = (props) => {
|
||||||
|
const {ruleset, loading, editable, onAddCell} = props;
|
||||||
|
const {strings: Locale} = useContext(UserContext);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Loading/>;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Table attached={true} celled={true}>
|
||||||
|
<Table.Header>
|
||||||
|
<Table.Row>
|
||||||
|
<Table.HeaderCell>
|
||||||
|
{Locale.rulesetsPage.fieldLabelHeader}
|
||||||
|
</Table.HeaderCell>
|
||||||
|
<Table.HeaderCell>
|
||||||
|
{Locale.rulesetsPage.fieldTypeHeader}
|
||||||
|
</Table.HeaderCell>
|
||||||
|
<Table.HeaderCell>
|
||||||
|
{Locale.rulesetsPage.fieldValueHeader}
|
||||||
|
</Table.HeaderCell>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Header>
|
||||||
|
</Table>
|
||||||
|
{Object.entries(ruleset.blocks).length > 0 ?
|
||||||
|
Object.entries(ruleset.blocks).map(idAndBlock => (
|
||||||
|
<RulesetBlockTable
|
||||||
|
key={idAndBlock[0]}
|
||||||
|
id={idAndBlock[0]}
|
||||||
|
blockDef={idAndBlock[1]}
|
||||||
|
editable={editable}
|
||||||
|
onAddCell={onAddCell}
|
||||||
|
/>
|
||||||
|
)) : (
|
||||||
|
<RulesetBlockTable
|
||||||
|
id={"noBlocks"}
|
||||||
|
blockDef={null}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RulesetDisplayPanel;
|
||||||
39
src/Components/RulesetList.tsx
Normal file
39
src/Components/RulesetList.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React, {useContext, useState} from "react";
|
||||||
|
import {Icon, List, Menu} from "semantic-ui-react";
|
||||||
|
import UserContext from "../Contexts/UserContext";
|
||||||
|
|
||||||
|
interface RulesetListProps {
|
||||||
|
rulesetNames: string[];
|
||||||
|
onItemChange: (newItemId: string | "addNewRuleset") => any;
|
||||||
|
selectedItemIndex: number;
|
||||||
|
creatingRuleset: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RulesetList: React.FunctionComponent<RulesetListProps> = (props) => {
|
||||||
|
const {rulesetNames, onItemChange, selectedItemIndex, creatingRuleset} = props;
|
||||||
|
const {strings: Locale} = useContext(UserContext);
|
||||||
|
|
||||||
|
const selectedItem = creatingRuleset ? "" : rulesetNames[selectedItemIndex];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu pointing={true} vertical={true}>
|
||||||
|
{rulesetNames.map(name => (
|
||||||
|
<Menu.Item
|
||||||
|
key={name}
|
||||||
|
name={name}
|
||||||
|
active={selectedItem === name}
|
||||||
|
onClick={() => onItemChange(name)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<Menu.Item
|
||||||
|
active={creatingRuleset}
|
||||||
|
onClick={() => onItemChange("addNewRuleset")}
|
||||||
|
>
|
||||||
|
{Locale.rulesetsPage.newRuleset}
|
||||||
|
<Icon name={"add"} />
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RulesetList;
|
||||||
@@ -1,10 +1,19 @@
|
|||||||
import React, {ReactElement} from "react";
|
import React, {ReactElement} from "react";
|
||||||
import {Header} from "semantic-ui-react";
|
import {Container, Grid, Header, List, Segment} from "semantic-ui-react";
|
||||||
import UserContext from "../Contexts/UserContext";
|
import UserContext from "../Contexts/UserContext";
|
||||||
|
import KadiStatsService from "../Services/KadiStatsService";
|
||||||
|
import RulesetList from "./RulesetList";
|
||||||
|
import RulesetDisplayPanel from "./RulesetDisplayPanel";
|
||||||
|
import {RulesetSchemaDto} from "../Services/RulesetSchemaDto";
|
||||||
|
import CreateRulesetPanel from "./CreateRulesetPanel";
|
||||||
|
|
||||||
interface RulesetsPageProps {}
|
interface RulesetsPageProps {}
|
||||||
|
|
||||||
interface RulesetsPageState {
|
interface RulesetsPageState {
|
||||||
|
loading: boolean;
|
||||||
|
addingNewRuleset: boolean;
|
||||||
|
rulesets: RulesetSchemaDto[];
|
||||||
|
selectedRulesetIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
class RulesetsPage extends React.Component<RulesetsPageProps, RulesetsPageState> {
|
class RulesetsPage extends React.Component<RulesetsPageProps, RulesetsPageState> {
|
||||||
@@ -12,15 +21,70 @@ class RulesetsPage extends React.Component<RulesetsPageProps, RulesetsPageState>
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
loading: true,
|
||||||
|
addingNewRuleset: false,
|
||||||
|
rulesets: [],
|
||||||
|
selectedRulesetIndex: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.getRulesets();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRulesets() {
|
||||||
|
const rulesets = await KadiStatsService.getAllRulesets();
|
||||||
|
this.setState({rulesets, loading: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
onRulesetSelect(newRulesetId: string | "addNewRuleset") {
|
||||||
|
if (newRulesetId === "addNewRuleset") {
|
||||||
|
this.setState({addingNewRuleset: true});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.setState({
|
||||||
|
addingNewRuleset: false,
|
||||||
|
selectedRulesetIndex: this.state.rulesets.findIndex(item => item.label === newRulesetId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
submitNewRuleset(ruleset: RulesetSchemaDto) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
render(): ReactElement {
|
render(): ReactElement {
|
||||||
const Locale = this.context.strings;
|
const Locale = this.context.strings;
|
||||||
return (
|
return (
|
||||||
<Header>
|
<>
|
||||||
|
<Header size={"huge"}>
|
||||||
{Locale.rulesetsPage.title}
|
{Locale.rulesetsPage.title}
|
||||||
</Header>
|
</Header>
|
||||||
|
<Grid>
|
||||||
|
<Grid.Row>
|
||||||
|
<Grid.Column width={4}>
|
||||||
|
<RulesetList
|
||||||
|
onItemChange={(r) => this.onRulesetSelect(r)}
|
||||||
|
selectedItemIndex={this.state.selectedRulesetIndex}
|
||||||
|
creatingRuleset={this.state.addingNewRuleset}
|
||||||
|
rulesetNames={this.state.rulesets.map(ruleset => ruleset.label)}
|
||||||
|
/>
|
||||||
|
</Grid.Column>
|
||||||
|
<Grid.Column width={12}>
|
||||||
|
{this.state.addingNewRuleset ? (
|
||||||
|
<CreateRulesetPanel
|
||||||
|
onSubmitRuleset={(r) => this.submitNewRuleset(r)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<RulesetDisplayPanel
|
||||||
|
loading={this.state.loading}
|
||||||
|
ruleset={this.state.rulesets[this.state.selectedRulesetIndex]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Grid.Column>
|
||||||
|
</Grid.Row>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,30 @@
|
|||||||
import React, {ReactNode} from "react";
|
import React, {ReactNode} from "react";
|
||||||
import {Header} from "semantic-ui-react";
|
import {
|
||||||
|
Container,
|
||||||
|
Dropdown, DropdownItemProps, DropdownMenuProps,
|
||||||
|
Grid,
|
||||||
|
GridColumn,
|
||||||
|
GridRow,
|
||||||
|
Header, Segment,
|
||||||
|
} from "semantic-ui-react";
|
||||||
import UserContext from "../Contexts/UserContext";
|
import UserContext from "../Contexts/UserContext";
|
||||||
|
import {StatsTable} from "./StatsTable";
|
||||||
|
import KadiStatsService from "../Services/KadiStatsService";
|
||||||
|
import {ServiceError} from "../errors";
|
||||||
|
import Loading from "./Loading";
|
||||||
|
import KadiStatsServiceSingleton from "../Services/KadiStatsService";
|
||||||
|
import {RulesetSchemaDto} from "../Services/RulesetSchemaDto";
|
||||||
|
import {StatsDto} from "../Services/StatsDto";
|
||||||
|
|
||||||
interface StatsPageProps {}
|
interface StatsPageProps {}
|
||||||
|
|
||||||
interface StatsPageState {
|
interface StatsPageState {
|
||||||
|
error: boolean;
|
||||||
|
stats: any;
|
||||||
|
loadingStats: boolean;
|
||||||
|
rulesetChoices: DropdownItemProps[];
|
||||||
|
selectedRulesetId: string;
|
||||||
|
rulesetSchemas: Record<string, RulesetSchemaDto>;
|
||||||
}
|
}
|
||||||
|
|
||||||
class StatsPage extends React.Component<StatsPageProps, StatsPageState> {
|
class StatsPage extends React.Component<StatsPageProps, StatsPageState> {
|
||||||
@@ -12,15 +32,97 @@ class StatsPage extends React.Component<StatsPageProps, StatsPageState> {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
selectedRulesetId: "",
|
||||||
|
error: false,
|
||||||
|
rulesetChoices: [],
|
||||||
|
rulesetSchemas: {},
|
||||||
|
stats: {},
|
||||||
|
loadingStats: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount(): void {
|
||||||
|
this.getStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getStats(): Promise<void> {
|
||||||
|
this.setState({loadingStats: true}, async () => {
|
||||||
|
try {
|
||||||
|
await this.loadStatsAndRulesetChoices();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (e instanceof ServiceError) {
|
||||||
|
this.setState({error: true});
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.setState({loadingStats: false});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadStatsAndRulesetChoices(): Promise<void> {
|
||||||
|
const stats = await KadiStatsService.getStats();
|
||||||
|
const rulesetIds = Object.keys(stats.pStats[0].stats.statsByRuleset);
|
||||||
|
const schemas: Record<string, RulesetSchemaDto> = {};
|
||||||
|
for (const rulesetId of rulesetIds) {
|
||||||
|
schemas[rulesetId] = await KadiStatsService.getRulesetById(rulesetId);
|
||||||
|
}
|
||||||
|
const rulesetChoices = rulesetIds.map(rulesetId =>
|
||||||
|
({key: rulesetId, value: rulesetId, text: schemas[rulesetId].label}));
|
||||||
|
this.setState({stats, rulesetChoices, rulesetSchemas: schemas, selectedRulesetId: rulesetIds[0]});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleError(error: any): void {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeRulesetSelection(rulesetId: string): void {
|
||||||
|
this.setState({selectedRulesetId: rulesetId});
|
||||||
|
}
|
||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
const Locale = this.context.strings;
|
const Locale = this.context.strings;
|
||||||
return (
|
return (
|
||||||
<Header>
|
<>
|
||||||
|
<Grid>
|
||||||
|
<GridRow columns={2}>
|
||||||
|
<GridColumn>
|
||||||
|
<Header size={"huge"}>
|
||||||
{Locale.statsPage.title}
|
{Locale.statsPage.title}
|
||||||
</Header>
|
</Header>
|
||||||
|
</GridColumn>
|
||||||
|
<GridColumn textAlign={"right"}>
|
||||||
|
{Locale.statsPage.pickRuleset + " "}
|
||||||
|
<Dropdown
|
||||||
|
loading={this.state.loadingStats}
|
||||||
|
selection={true}
|
||||||
|
options={this.state.rulesetChoices}
|
||||||
|
defaultValue={this.state.rulesetChoices[0]?.value}
|
||||||
|
onChange={(e, choice) => this.changeRulesetSelection(choice.value as string)}
|
||||||
|
/>
|
||||||
|
</GridColumn>
|
||||||
|
</GridRow>
|
||||||
|
<GridRow columns={1} style={{overflow: "auto"}}>
|
||||||
|
<GridColumn>
|
||||||
|
{this.state.error ?
|
||||||
|
<p>{Locale.general.databaseError}</p> :
|
||||||
|
this.state.loadingStats ?
|
||||||
|
<Loading/> : (
|
||||||
|
<StatsTable
|
||||||
|
data={this.state.stats}
|
||||||
|
displayedRulesetSchema={this.state.rulesetSchemas[this.state.selectedRulesetId]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</GridColumn>
|
||||||
|
</GridRow>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
95
src/Components/StatsTable.tsx
Normal file
95
src/Components/StatsTable.tsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import {Table, TableBody, TableCell, TableHeader, TableHeaderCell, TableRow} from "semantic-ui-react";
|
||||||
|
import React, {useContext} from "react";
|
||||||
|
import {AccountStatsDto, PlayerStatsDto, StatsDto} from "../Services/StatsDto";
|
||||||
|
import UserContext from "../Contexts/UserContext";
|
||||||
|
import KadiStatsServiceSingleton from "../Services/KadiStatsService";
|
||||||
|
import {RulesetSchemaDto} from "../Services/RulesetSchemaDto";
|
||||||
|
|
||||||
|
interface StatsTableProps {
|
||||||
|
data: StatsDto;
|
||||||
|
displayedRulesetSchema: RulesetSchemaDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StatsTable: React.FunctionComponent<StatsTableProps> = (props) => {
|
||||||
|
const {data, displayedRulesetSchema} = props;
|
||||||
|
console.log(data, displayedRulesetSchema);
|
||||||
|
const rulesetId = displayedRulesetSchema.id;
|
||||||
|
const {strings: Locale} = useContext(UserContext);
|
||||||
|
const headerCellLocations: {blockId: string, cellId: string}[] = [];
|
||||||
|
for (const blockId in data.pStats[0].stats.statsByRuleset[rulesetId].blockStats) {
|
||||||
|
for (const cellId in data.pStats[0].stats.statsByRuleset[rulesetId].blockStats[blockId].cellStats) {
|
||||||
|
headerCellLocations.push({blockId, cellId});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const rows = data.pStats.map(statsEntry => (
|
||||||
|
<StatsTablePlayerRow key={statsEntry.playerId} rowData={statsEntry} displayedRuleset={rulesetId} cellOrder={headerCellLocations}/>
|
||||||
|
));
|
||||||
|
return (
|
||||||
|
<Table celled={true}>
|
||||||
|
<Table.Header>
|
||||||
|
<Table.Row>
|
||||||
|
<Table.HeaderCell>
|
||||||
|
Player
|
||||||
|
</Table.HeaderCell>
|
||||||
|
{headerCellLocations.map(loc => (
|
||||||
|
<>
|
||||||
|
<Table.HeaderCell key={"h" + loc.blockId + loc.cellId}>
|
||||||
|
{displayedRulesetSchema.blocks[loc.blockId].cells[loc.cellId].label}
|
||||||
|
</Table.HeaderCell>
|
||||||
|
<Table.HeaderCell textAlign={"center"} error={true} key={"h" + loc.blockId + loc.cellId + "X"}>
|
||||||
|
{Locale.statsPage.struckCellHeader}
|
||||||
|
</Table.HeaderCell>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Header>
|
||||||
|
<Table.Body>
|
||||||
|
{rows}
|
||||||
|
</Table.Body>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface StatsTablePlayerRowProps {
|
||||||
|
rowData: any;
|
||||||
|
displayedRuleset: string;
|
||||||
|
cellOrder: {blockId: string, cellId: string}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const StatsTablePlayerRow: React.FunctionComponent<StatsTablePlayerRowProps> = (props) => {
|
||||||
|
const {rowData, displayedRuleset, cellOrder} = props;
|
||||||
|
const cellStatTableCells = [];
|
||||||
|
for (const location of cellOrder) {
|
||||||
|
const currentCellStats = rowData.stats.statsByRuleset[displayedRuleset].blockStats[location.blockId].cellStats[location.cellId];
|
||||||
|
const average = Math.round(currentCellStats.runningTotal / rowData.stats.gamesPlayed * 10) / 10;
|
||||||
|
cellStatTableCells.push(
|
||||||
|
<CellStatsTableCell key={location.blockId + location.cellId} average={average} timesStruck={currentCellStats.timesStruck}/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>
|
||||||
|
{rowData.nick}
|
||||||
|
</TableCell>
|
||||||
|
{cellStatTableCells}
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface CellStatsTableCellProps {
|
||||||
|
average: number;
|
||||||
|
timesStruck: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CellStatsTableCell: React.FunctionComponent<CellStatsTableCellProps> = ({average, timesStruck}) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TableCell>
|
||||||
|
{average}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell negative={true}>
|
||||||
|
{timesStruck}
|
||||||
|
</TableCell>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
62
src/Services/KadiStatsService.ts
Normal file
62
src/Services/KadiStatsService.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import {SERVER_BASE_NAME} from "../index";
|
||||||
|
import {StatsDto} from "./StatsDto";
|
||||||
|
import {RulesetSchemaDto} from "./RulesetSchemaDto";
|
||||||
|
|
||||||
|
const dummyRulesets = [
|
||||||
|
{"id":"DEFAULT_RULESET","label":"Standard Kadi Rules (en)","blocks":{"top":{"label":"Upper","hasBonus":true,"bonusScore":35,"bonusFor":63,"cells":{"aces":{"fieldType":"multiplierField","label":"Aces","multiplier":1,"maxMultiples":5},"twos":{"fieldType":"multiplierField","label":"Twos","multiplier":2,"maxMultiples":5},"threes":{"fieldType":"multiplierField","label":"Threes","multiplier":3,"maxMultiples":5},"fours":{"fieldType":"multiplierField","label":"Fours","multiplier":4,"maxMultiples":5},"fives":{"fieldType":"multiplierField","label":"Fives","multiplier":5,"maxMultiples":5},"sixes":{"fieldType":"multiplierField","label":"Sixes","multiplier":6,"maxMultiples":5}}},"bottom":{"label":"Lower","hasBonus":false,"cells":{"threeKind":{"fieldType":"numberField","label":"Three of a Kind"},"fourKind":{"fieldType":"numberField","label":"Four of a Kind"},"fullHouse":{"fieldType":"boolField","label":"Full House","score":25},"smlStraight":{"fieldType":"boolField","label":"Small Straight","score":30},"lgSraight":{"fieldType":"boolField","label":"Large Straight","score":40},"superkadi":{"fieldType":"superkadiField","label":"Super Kadis","score":50,"maxSuperkadis":5},"chance":{"fieldType":"numberField","label":"Chance"}}}}}
|
||||||
|
];
|
||||||
|
|
||||||
|
class KadiStatsService {
|
||||||
|
private userStats: StatsDto | null = null;
|
||||||
|
private rulesets: Record<string, RulesetSchemaDto> = {};
|
||||||
|
private allRulesetsLoaded: boolean = false;
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
private async loadStats(): Promise<StatsDto> {
|
||||||
|
const statsSlug = await axios.get(SERVER_BASE_NAME + "/api/stats");
|
||||||
|
this.userStats = statsSlug.data as StatsDto;
|
||||||
|
return this.userStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getStats(): Promise<StatsDto> {
|
||||||
|
if (this.userStats) {
|
||||||
|
return this.userStats;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return this.loadStats();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRulesetById(id: string): Promise<RulesetSchemaDto> {
|
||||||
|
if (this.rulesets[id]) {
|
||||||
|
return this.rulesets[id];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const rulesetSchema = (await axios.get(SERVER_BASE_NAME + "/api/ruleset/" + id)).data as RulesetSchemaDto;
|
||||||
|
this.rulesets[rulesetSchema.id] = rulesetSchema;
|
||||||
|
return this.rulesets[rulesetSchema.id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async refreshStats(): Promise<void> {
|
||||||
|
await this.loadStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllRulesets(): Promise<RulesetSchemaDto[]> {
|
||||||
|
return dummyRulesets as RulesetSchemaDto[];
|
||||||
|
if (this.allRulesetsLoaded) {
|
||||||
|
return Object.values(this.rulesets);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const rulesetSchemas = (await axios.get(SERVER_BASE_NAME + "/api/rulesets/")).data as RulesetSchemaDto[];
|
||||||
|
rulesetSchemas.forEach(schema => this.rulesets[schema.id] = schema);
|
||||||
|
this.allRulesetsLoaded = true;
|
||||||
|
return rulesetSchemas;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const KadiStatsServiceSingleton = new KadiStatsService();
|
||||||
|
|
||||||
|
export default KadiStatsServiceSingleton;
|
||||||
53
src/Services/RulesetSchemaDto.ts
Normal file
53
src/Services/RulesetSchemaDto.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
export interface RulesetSchemaDto {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
blocks: Record<string, BlockDef>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BlockDef = BonusBlockDef | NoBonusBlockDef;
|
||||||
|
|
||||||
|
export interface NoBonusBlockDef extends DefaultBlockDef {
|
||||||
|
hasBonus: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BonusBlockDef extends DefaultBlockDef {
|
||||||
|
hasBonus: true;
|
||||||
|
bonusScore: number;
|
||||||
|
bonusFor: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DefaultBlockDef {
|
||||||
|
label: string;
|
||||||
|
cells: Record<string, CellDef>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CellDef =
|
||||||
|
| BoolCellDef
|
||||||
|
| MultiplierCellDef
|
||||||
|
| NumberCellDef
|
||||||
|
| SuperkadiCellDef;
|
||||||
|
|
||||||
|
export interface BoolCellDef extends DefaultCellDef {
|
||||||
|
fieldType: FieldType.bool;
|
||||||
|
score: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MultiplierCellDef extends DefaultCellDef {
|
||||||
|
fieldType: FieldType.multiplier;
|
||||||
|
multiplier: number;
|
||||||
|
maxMultiples: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuperkadiCellDef extends DefaultCellDef {
|
||||||
|
fieldType: FieldType.superkadi;
|
||||||
|
score: number;
|
||||||
|
maxSuperkadis: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NumberCellDef extends DefaultCellDef {
|
||||||
|
fieldType: FieldType.number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DefaultCellDef {
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
22
src/Services/Service.ts
Normal file
22
src/Services/Service.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import {ServiceError} from "../errors";
|
||||||
|
|
||||||
|
type ClassDef = { new (...args: any[]): {} };
|
||||||
|
|
||||||
|
export function Service<T extends ClassDef>(constructor: T): T {
|
||||||
|
for (const property of Object.getOwnPropertyNames(constructor.prototype)) {
|
||||||
|
if (typeof constructor.prototype[property] === "function") {
|
||||||
|
const originalFunction = constructor.prototype[property];
|
||||||
|
constructor.prototype[property] = async (...args: any[]) => {
|
||||||
|
try {
|
||||||
|
// @ts-ignore
|
||||||
|
const result = await originalFunction.apply(this, ...args);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
throw new ServiceError(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return constructor;
|
||||||
|
}
|
||||||
43
src/Services/StatsDto.ts
Normal file
43
src/Services/StatsDto.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
export interface StatsDto {
|
||||||
|
pStats: {
|
||||||
|
nick: string,
|
||||||
|
playerId: string,
|
||||||
|
stats: PlayerStatsDto
|
||||||
|
}[];
|
||||||
|
accStats: AccountStatsDto;
|
||||||
|
}
|
||||||
|
export interface PlayerStatsDto extends BaseStatsDto {}
|
||||||
|
export interface AccountStatsDto extends BaseStatsDto {}
|
||||||
|
export interface BaseStatsDto {
|
||||||
|
statsByRuleset: Record<string, RulesetStatsDto>
|
||||||
|
gamesPlayed: number;
|
||||||
|
}
|
||||||
|
export interface RulesetStatsDto {
|
||||||
|
blockStats: Record<string, BlockStatsDto>;
|
||||||
|
wins: number;
|
||||||
|
runnerUps: number;
|
||||||
|
draws: number;
|
||||||
|
losses: number;
|
||||||
|
grandTotal: TotalFieldStatsDto;
|
||||||
|
}
|
||||||
|
export interface BlockStatsDto {
|
||||||
|
cellStats: Record<string, CellStatsDto>;
|
||||||
|
timesHadBonus?: number;
|
||||||
|
total: TotalFieldStatsDto;
|
||||||
|
}
|
||||||
|
export interface BaseCellStatsDto {
|
||||||
|
runningTotal: number;
|
||||||
|
}
|
||||||
|
export interface StrikeableFieldStatsDto extends BaseCellStatsDto {
|
||||||
|
timesStruck: number;
|
||||||
|
}
|
||||||
|
export interface BestableFieldStatsDto extends BaseCellStatsDto {
|
||||||
|
best: number;
|
||||||
|
worst: number;
|
||||||
|
}
|
||||||
|
export type TotalFieldStatsDto = BestableFieldStatsDto;
|
||||||
|
export type BoolFieldStatsDto = StrikeableFieldStatsDto & { total: number };
|
||||||
|
export type NumberFieldStatsDto = StrikeableFieldStatsDto & BestableFieldStatsDto;
|
||||||
|
export type MultiplierFieldStatsDto = NumberFieldStatsDto;
|
||||||
|
export type SuperkadiFieldStatsDto = NumberFieldStatsDto;
|
||||||
|
export type CellStatsDto = BoolFieldStatsDto | NumberFieldStatsDto | MultiplierFieldStatsDto | SuperkadiFieldStatsDto;
|
||||||
11
src/enums.ts
11
src/enums.ts
@@ -18,3 +18,14 @@ export const supportedLangToIntlDTF: Record<SupportedLang, Intl.DateTimeFormat>
|
|||||||
de: Intl.DateTimeFormat('de-DE'),
|
de: Intl.DateTimeFormat('de-DE'),
|
||||||
it: Intl.DateTimeFormat('it-IT'),
|
it: Intl.DateTimeFormat('it-IT'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum FieldType {
|
||||||
|
number = "numberField",
|
||||||
|
bool = "boolField",
|
||||||
|
bonus = "bonusField",
|
||||||
|
subtotal = "subtotalField",
|
||||||
|
globalTotal = "globalTotalField",
|
||||||
|
total = "totalField",
|
||||||
|
superkadi = "superkadiField",
|
||||||
|
multiplier = "multiplierField",
|
||||||
|
}
|
||||||
5
src/errors.ts
Normal file
5
src/errors.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export class ServiceError extends Error {
|
||||||
|
constructor(message?: string) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,9 @@ import React from "react";
|
|||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
import "semantic-ui-css/semantic.min.css";
|
import "semantic-ui-css/semantic.min.css";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import * as serviceWorker from "./serviceWorker";
|
|
||||||
export {homepage as SERVER_BASE_NAME} from "../package.json";
|
export {homepage as SERVER_BASE_NAME} from "../package.json";
|
||||||
|
|
||||||
|
|
||||||
ReactDOM.render((
|
ReactDOM.render((
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<App />
|
||||||
@@ -13,7 +13,3 @@ ReactDOM.render((
|
|||||||
document.getElementById('root')
|
document.getElementById('root')
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want your app to work offline and load faster, you can change
|
|
||||||
// unregister() to register() below. Note this comes with some pitfalls.
|
|
||||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
|
||||||
serviceWorker.unregister();
|
|
||||||
|
|||||||
@@ -1,141 +0,0 @@
|
|||||||
// This optional code is used to register a service worker.
|
|
||||||
// register() is not called by default.
|
|
||||||
|
|
||||||
// This lets the app load faster on subsequent visits in production, and gives
|
|
||||||
// it offline capabilities. However, it also means that developers (and users)
|
|
||||||
// will only see deployed updates on subsequent visits to a page, after all the
|
|
||||||
// existing tabs open on the page have been closed, since previously cached
|
|
||||||
// resources are updated in the background.
|
|
||||||
|
|
||||||
// To learn more about the benefits of this model and instructions on how to
|
|
||||||
// opt-in, read https://bit.ly/CRA-PWA
|
|
||||||
|
|
||||||
const isLocalhost = Boolean(
|
|
||||||
window.location.hostname === 'localhost' ||
|
|
||||||
// [::1] is the IPv6 localhost address.
|
|
||||||
window.location.hostname === '[::1]' ||
|
|
||||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
|
||||||
window.location.hostname.match(
|
|
||||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
export function register(config) {
|
|
||||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
|
||||||
// The URL constructor is available in all browsers that support SW.
|
|
||||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
|
||||||
if (publicUrl.origin !== window.location.origin) {
|
|
||||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
|
||||||
// from what our page is served on. This might happen if a CDN is used to
|
|
||||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
|
||||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
|
||||||
|
|
||||||
if (isLocalhost) {
|
|
||||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
|
||||||
checkValidServiceWorker(swUrl, config);
|
|
||||||
|
|
||||||
// Add some additional logging to localhost, pointing developers to the
|
|
||||||
// service worker/PWA documentation.
|
|
||||||
navigator.serviceWorker.ready.then(() => {
|
|
||||||
console.log(
|
|
||||||
'This web app is being served cache-first by a service ' +
|
|
||||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Is not localhost. Just register service worker
|
|
||||||
registerValidSW(swUrl, config);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerValidSW(swUrl, config) {
|
|
||||||
navigator.serviceWorker
|
|
||||||
.register(swUrl)
|
|
||||||
.then(registration => {
|
|
||||||
registration.onupdatefound = () => {
|
|
||||||
const installingWorker = registration.installing;
|
|
||||||
if (installingWorker == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
installingWorker.onstatechange = () => {
|
|
||||||
if (installingWorker.state === 'installed') {
|
|
||||||
if (navigator.serviceWorker.controller) {
|
|
||||||
// At this point, the updated precached content has been fetched,
|
|
||||||
// but the previous service worker will still serve the older
|
|
||||||
// content until all client tabs are closed.
|
|
||||||
console.log(
|
|
||||||
'New content is available and will be used when all ' +
|
|
||||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Execute callback
|
|
||||||
if (config && config.onUpdate) {
|
|
||||||
config.onUpdate(registration);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// At this point, everything has been precached.
|
|
||||||
// It's the perfect time to display a
|
|
||||||
// "Content is cached for offline use." message.
|
|
||||||
console.log('Content is cached for offline use.');
|
|
||||||
|
|
||||||
// Execute callback
|
|
||||||
if (config && config.onSuccess) {
|
|
||||||
config.onSuccess(registration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error during service worker registration:', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkValidServiceWorker(swUrl, config) {
|
|
||||||
// Check if the service worker can be found. If it can't reload the page.
|
|
||||||
fetch(swUrl, {
|
|
||||||
headers: { 'Service-Worker': 'script' },
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
// Ensure service worker exists, and that we really are getting a JS file.
|
|
||||||
const contentType = response.headers.get('content-type');
|
|
||||||
if (
|
|
||||||
response.status === 404 ||
|
|
||||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
|
||||||
) {
|
|
||||||
// No service worker found. Probably a different app. Reload the page.
|
|
||||||
navigator.serviceWorker.ready.then(registration => {
|
|
||||||
registration.unregister().then(() => {
|
|
||||||
window.location.reload();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Service worker found. Proceed as normal.
|
|
||||||
registerValidSW(swUrl, config);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
console.log(
|
|
||||||
'No internet connection found. App is running in offline mode.'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function unregister() {
|
|
||||||
if ('serviceWorker' in navigator) {
|
|
||||||
navigator.serviceWorker.ready
|
|
||||||
.then(registration => {
|
|
||||||
registration.unregister();
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error(error.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
|
||||||
// allows you to do things like:
|
|
||||||
// expect(element).toHaveTextContent(/react/i)
|
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
|
||||||
import '@testing-library/jest-dom/extend-expect';
|
|
||||||
@@ -30,6 +30,10 @@ export const IntlStrings = {
|
|||||||
gb: {
|
gb: {
|
||||||
general: {
|
general: {
|
||||||
deleteCommand: "Delete",
|
deleteCommand: "Delete",
|
||||||
|
databaseError: "An error occurred communicating with the database.",
|
||||||
|
yes: "Yes",
|
||||||
|
no: "No",
|
||||||
|
nA: "N/A",
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
profileTab: "Profile",
|
profileTab: "Profile",
|
||||||
@@ -52,9 +56,37 @@ export const IntlStrings = {
|
|||||||
},
|
},
|
||||||
statsPage: {
|
statsPage: {
|
||||||
title: "Stats",
|
title: "Stats",
|
||||||
|
struckCellHeader: "X",
|
||||||
|
pickRuleset: "Filter by ruleset",
|
||||||
},
|
},
|
||||||
rulesetsPage: {
|
rulesetsPage: {
|
||||||
title: "Rulesets",
|
title: "Rulesets",
|
||||||
|
myRulesets: "My Rulesets",
|
||||||
|
newRuleset: "New Ruleset",
|
||||||
|
blocksHeader: "Blocks",
|
||||||
|
fieldLabelHeader: "Label",
|
||||||
|
fieldTypeHeader: "Type",
|
||||||
|
fieldValueHeader: "Value",
|
||||||
|
superkadiField: "Superkadi",
|
||||||
|
multiplierField: "Multiplier Cell",
|
||||||
|
boolField: "Boolean Cell",
|
||||||
|
numberField: "Number Cell",
|
||||||
|
bonus: "Bonus",
|
||||||
|
newBlock: "New Block",
|
||||||
|
blockName: "Name",
|
||||||
|
cellName: "Name",
|
||||||
|
bonusScore: "Score for Bonus",
|
||||||
|
bonusThreshold: "Score Required",
|
||||||
|
blockNamePlaceholder: "My New Block",
|
||||||
|
addBlock: "Add Block",
|
||||||
|
noBlocks: "No Blocks",
|
||||||
|
addCell: "Add Cell",
|
||||||
|
cellNamePlaceholder: "My New Cell",
|
||||||
|
fieldTypePlaceholder: "Select a field type",
|
||||||
|
multiplierPlaceholder: "Multiplier",
|
||||||
|
maxMultiplesPlaceholder: "Max of kind",
|
||||||
|
valuePlaceholder: "Value",
|
||||||
|
submit: "Submit",
|
||||||
},
|
},
|
||||||
friendsPage: {
|
friendsPage: {
|
||||||
title: "Friends",
|
title: "Friends",
|
||||||
@@ -67,6 +99,10 @@ export const IntlStrings = {
|
|||||||
de: {
|
de: {
|
||||||
general: {
|
general: {
|
||||||
deleteCommand: "Löschen",
|
deleteCommand: "Löschen",
|
||||||
|
databaseError: "Ein Fehler ist während der Datenbankabfrage aufgetreten.",
|
||||||
|
yes: "Ja",
|
||||||
|
no: "Nein",
|
||||||
|
nA: "k.A.",
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
profileTab: "Profil",
|
profileTab: "Profil",
|
||||||
@@ -89,9 +125,38 @@ export const IntlStrings = {
|
|||||||
},
|
},
|
||||||
statsPage: {
|
statsPage: {
|
||||||
title: "Statistiken",
|
title: "Statistiken",
|
||||||
|
struckCellHeader: "X",
|
||||||
|
pickRuleset: "Nach Regelwerk filtern:",
|
||||||
},
|
},
|
||||||
rulesetsPage: {
|
rulesetsPage: {
|
||||||
title: "Regelwerke",
|
title: "Regelwerke",
|
||||||
|
myRulesets: "Meine Regelwerke",
|
||||||
|
newRuleset: "Neues Regelwerk",
|
||||||
|
blocksHeader: "Blöcke",
|
||||||
|
fieldLabelHeader: "Name",
|
||||||
|
fieldTypeHeader: "Typ",
|
||||||
|
fieldValueHeader: "Wert",
|
||||||
|
superkadiField: "Superkadi",
|
||||||
|
multiplierField: "Multiplikator-Feld",
|
||||||
|
boolField: "Bool'sches Feld",
|
||||||
|
numberField: "Zahleingabefeld",
|
||||||
|
value: "Wert",
|
||||||
|
bonus: "Bonus",
|
||||||
|
newBlock: "Neuer Block",
|
||||||
|
blockName: "Name",
|
||||||
|
cellName: "Name",
|
||||||
|
bonusScore: "Bonuspunkte",
|
||||||
|
bonusThreshold: "Punkte zu erreichen",
|
||||||
|
blockNamePlaceholder: "Mein neuer Block",
|
||||||
|
addBlock: "Block hinzufügen",
|
||||||
|
noBlocks: "Keine Blöcke vorhanden",
|
||||||
|
addCell: "Feld hinzufügen",
|
||||||
|
cellNamePlaceholder: "Mein neues Feld",
|
||||||
|
fieldTypePlaceholder: "Feldtyp auswählen",
|
||||||
|
multiplierPlaceholder: "Multiplikator",
|
||||||
|
maxMultiplesPlaceholder: "Max. Anzahl",
|
||||||
|
valuePlaceholder: "Wert",
|
||||||
|
submit: "Fertig",
|
||||||
},
|
},
|
||||||
friendsPage: {
|
friendsPage: {
|
||||||
title: "Freunde",
|
title: "Freunde",
|
||||||
@@ -104,6 +169,10 @@ export const IntlStrings = {
|
|||||||
it: {
|
it: {
|
||||||
general: {
|
general: {
|
||||||
deleteCommand: "Cancella",
|
deleteCommand: "Cancella",
|
||||||
|
databaseError: "===TRANSLATE ME===",
|
||||||
|
yes: "Sì",
|
||||||
|
no: "No",
|
||||||
|
nA: "===TRANSLATE ME===",
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
profileTab: "Profilo",
|
profileTab: "Profilo",
|
||||||
@@ -126,9 +195,30 @@ export const IntlStrings = {
|
|||||||
},
|
},
|
||||||
statsPage: {
|
statsPage: {
|
||||||
title: "Statistiche",
|
title: "Statistiche",
|
||||||
|
struckCellHeader: "X",
|
||||||
|
pickRuleset: "===TRANSLATE ME===",
|
||||||
},
|
},
|
||||||
rulesetsPage: {
|
rulesetsPage: {
|
||||||
title: "Regolamenti",
|
title: "Regolamenti",
|
||||||
|
myRulesets: "I miei Regolamenti",
|
||||||
|
newRuleset: "===TRANSLATE ME===",
|
||||||
|
blocksHeader: "===TRANSLATE ME===",
|
||||||
|
fieldLabelHeader: "===TRANSLATE ME===",
|
||||||
|
fieldTypeHeader: "===TRANSLATE ME===",
|
||||||
|
fieldValueHeader: "===TRANSLATE ME===",
|
||||||
|
superkadiField: "===TRANSLATE ME===",
|
||||||
|
multiplierField: "===TRANSLATE ME===",
|
||||||
|
boolField: "===TRANSLATE ME===",
|
||||||
|
numberField: "===TRANSLATE ME===",
|
||||||
|
value: "Valore",
|
||||||
|
bonus: "Bonus",
|
||||||
|
newBlock: "===TRANSLATE ME===",
|
||||||
|
blockName: "===TRANSLATE ME===",
|
||||||
|
bonusScore: "===TRANSLATE ME===",
|
||||||
|
bonusThreshold: "===TRANSLATE ME===",
|
||||||
|
blockNamePlaceholder: "===TRANSLATE ME===",
|
||||||
|
addBlock: "===TRANSLATE ME===",
|
||||||
|
noBlocks: "===TRANSLATE ME===",
|
||||||
},
|
},
|
||||||
friendsPage: {
|
friendsPage: {
|
||||||
title: "Amici",
|
title: "Amici",
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true
|
"isolatedModules": true,
|
||||||
|
"experimentalDecorators": true
|
||||||
},
|
},
|
||||||
"lib": [
|
"lib": [
|
||||||
"dom",
|
"dom",
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
"prefer-readonly": true,
|
"prefer-readonly": true,
|
||||||
"typedef": [
|
"typedef": [
|
||||||
true,
|
true,
|
||||||
"call-signature",
|
|
||||||
"property-declaration"
|
"property-declaration"
|
||||||
],
|
],
|
||||||
"ordered-imports": false,
|
"ordered-imports": false,
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
devServer: {
|
devServer: {
|
||||||
contentBase: path.join(__dirname, "public/"),
|
contentBase: path.join(__dirname, "public/"),
|
||||||
|
historyApiFallback: {
|
||||||
|
index: '/kadi/',
|
||||||
|
},
|
||||||
contentBasePublicPath: SERVER_ROOT + "/",
|
contentBasePublicPath: SERVER_ROOT + "/",
|
||||||
port: 3000,
|
port: 3000,
|
||||||
publicPath: "http://localhost:3000" + SERVER_ROOT + "/static/frontend/",
|
publicPath: "http://localhost:3000" + SERVER_ROOT + "/static/frontend/",
|
||||||
|
|||||||
Reference in New Issue
Block a user