first version
This commit is contained in:
94
js_client/assets/hint_both.svg
Normal file
94
js_client/assets/hint_both.svg
Normal file
@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="100"
|
||||
height="100"
|
||||
viewBox="0 0 26.458333 26.458334"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"
|
||||
sodipodi:docname="hint_both.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="4.3481303"
|
||||
inkscape:cx="49.605183"
|
||||
inkscape:cy="79.488474"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-rotation="0"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="950"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid10"
|
||||
spacingy="0.52916667"
|
||||
spacingx="0.52916667" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
sodipodi:type="star"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.529167;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="path861"
|
||||
sodipodi:sides="3"
|
||||
sodipodi:cx="13.229166"
|
||||
sodipodi:cy="23.8125"
|
||||
sodipodi:r1="2.6458333"
|
||||
sodipodi:r2="1.3229167"
|
||||
sodipodi:arg1="1.5707963"
|
||||
sodipodi:arg2="2.6179939"
|
||||
inkscape:flatsided="true"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m 13.229166,26.458333 -2.291359,-3.96875 4.582718,0 z"
|
||||
inkscape:transform-center-y="0.66145805" />
|
||||
<path
|
||||
sodipodi:type="star"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.529167;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="path863"
|
||||
sodipodi:sides="3"
|
||||
sodipodi:cx="23.8125"
|
||||
sodipodi:cy="13.229166"
|
||||
sodipodi:r1="2.6458333"
|
||||
sodipodi:r2="1.3229167"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:arg2="1.0471976"
|
||||
inkscape:flatsided="true"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m 26.458333,13.229166 -3.96875,2.291359 v -4.582718 z"
|
||||
inkscape:transform-center-x="-0.66145805" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
78
js_client/assets/hint_horizontal.svg
Normal file
78
js_client/assets/hint_horizontal.svg
Normal file
@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="100"
|
||||
height="100"
|
||||
viewBox="0 0 26.458333 26.458334"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="hint_horizontal.svg"
|
||||
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="4.3481303"
|
||||
inkscape:cx="49.605183"
|
||||
inkscape:cy="74.492628"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:document-rotation="0"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="950"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid10"
|
||||
spacingy="0.52916667"
|
||||
spacingx="0.52916667" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
sodipodi:type="star"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.529167;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="path863"
|
||||
sodipodi:sides="3"
|
||||
sodipodi:cx="23.8125"
|
||||
sodipodi:cy="13.229166"
|
||||
sodipodi:r1="2.6458333"
|
||||
sodipodi:r2="1.3229167"
|
||||
sodipodi:arg1="0"
|
||||
sodipodi:arg2="1.0471976"
|
||||
inkscape:flatsided="true"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="m 26.458333,13.229166 -3.96875,2.291359 v -4.582718 z"
|
||||
inkscape:transform-center-x="-0.66145805" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
34
js_client/assets/hint_vertical.svg
Normal file
34
js_client/assets/hint_vertical.svg
Normal file
@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100"
|
||||
height="100"
|
||||
viewBox="0 0 26.458333 26.458334"
|
||||
version="1.1"
|
||||
id="svg8">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.529167;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="path861"
|
||||
d="m 13.229166,26.458333 -2.291359,-3.96875 4.582718,0 z" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
23
js_client/cookie.js
Normal file
23
js_client/cookie.js
Normal file
@ -0,0 +1,23 @@
|
||||
export function setCookie(cname, cvalue, exdays) {
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
|
||||
var expires = "expires=" + d.toUTCString();
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||
}
|
||||
|
||||
export function getCookie(cname) {
|
||||
var name = cname + "=";
|
||||
var decodedCookie = decodeURIComponent(document.cookie);
|
||||
var ca = decodedCookie.split(';');
|
||||
for (var i = 0; i < ca.length; i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
772
js_client/grid.js
Normal file
772
js_client/grid.js
Normal file
@ -0,0 +1,772 @@
|
||||
import { html, css, LitElement, unsafeCSS } from 'https://unpkg.com/lit-element/lit-element.js?module';
|
||||
import './infoBox.js'
|
||||
|
||||
const gridType = {
|
||||
EMPTY: 0,
|
||||
HINT: 1,
|
||||
LETTER: 2
|
||||
};
|
||||
|
||||
export class GridElement {
|
||||
constructor(x, y, grid, type) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.type = type
|
||||
this.grid = grid;
|
||||
|
||||
|
||||
this.gridLetter = null;
|
||||
this.hintBox = null;
|
||||
this.emptyBox = null;
|
||||
}
|
||||
|
||||
setGridLetter(gridLetter) {
|
||||
this.gridLetter = gridLetter;
|
||||
}
|
||||
|
||||
getGridLetter() {
|
||||
return this.gridLetter;
|
||||
}
|
||||
|
||||
setHintBox(hintBox) {
|
||||
this.hintBox = hintBox;
|
||||
}
|
||||
|
||||
getHintBox() {
|
||||
return this.hintBox;
|
||||
}
|
||||
|
||||
setEmptyBox(emptyBox) {
|
||||
this.emptyBox = emptyBox;
|
||||
}
|
||||
|
||||
getEmptyBox() {
|
||||
return this.emptyBox;
|
||||
}
|
||||
|
||||
getGridType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class GridBox extends LitElement {
|
||||
static get styles() {
|
||||
return css`
|
||||
input[type='text'] {
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
text-align: center;
|
||||
border-style: solid;
|
||||
border-color: black;
|
||||
border-width: 0.2 em;
|
||||
background-color: black;
|
||||
outline: none;
|
||||
color: transparent;
|
||||
text-shadow: 0 0 1 black;
|
||||
font-size: 2em;
|
||||
user-select: none;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
x: { type: Number },
|
||||
y: { type: Number },
|
||||
grid_id: { type: String }
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
this.gridElement = null;
|
||||
this.grid_id = null;
|
||||
this.crosswordGrid = null;
|
||||
this.isRevealed = false;
|
||||
}
|
||||
|
||||
update(props) {
|
||||
super.update();
|
||||
if (props.has("grid_id")) {
|
||||
this.updateGridElement();
|
||||
}
|
||||
}
|
||||
|
||||
updateGridElement() {
|
||||
if (this.grid_id != null) {
|
||||
|
||||
this.crosswordGrid = document.getElementById(this.grid_id);
|
||||
this.gridElement = this.crosswordGrid.getGridElement(this.x, this.y);
|
||||
this.gridElement.setEmptyBox(this);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<input type="text" id="${this.x}-${this.y}" maxlength="1" value="" disabled></input>`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('grid-box', GridBox);
|
||||
|
||||
|
||||
export class GridLetter extends GridBox {
|
||||
static get styles() {
|
||||
return css`
|
||||
input[type='text'] {
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
text-align: center;
|
||||
border-style: solid;
|
||||
border-width: 0.2 em;
|
||||
border-color: black;
|
||||
outline: none;
|
||||
color: black;
|
||||
font-size: 2em;
|
||||
user-select: none;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
input[type='text']:focus {
|
||||
background-color: LightYellow;
|
||||
border-color: Yellow;
|
||||
|
||||
}
|
||||
|
||||
input[type='text']:read-only {
|
||||
background-color: LightGreen;
|
||||
}
|
||||
|
||||
input[type='text']:selection {
|
||||
background: transparent;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
letter: { type: String },
|
||||
x: { type: Number },
|
||||
y: { type: Number },
|
||||
grid_id: { type: String }
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._value = '';
|
||||
this.is_focused = false;
|
||||
this.key_delay = false;
|
||||
this.updateGridElement();
|
||||
}
|
||||
|
||||
|
||||
|
||||
set value(value) {
|
||||
const oldValue = this.value;
|
||||
console.log("update value to: " + value);
|
||||
|
||||
this._value = value;
|
||||
this.requestUpdate('value', oldValue);
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
onInput(e) {
|
||||
|
||||
if (!this.is_focused) {
|
||||
return;
|
||||
}
|
||||
|
||||
//if (this.key_delay){
|
||||
// var el = this.shadowRoot.getElementById(`${this.x}-${this.y}`);
|
||||
// el.value = this.value;
|
||||
// el.select();
|
||||
// e.handled = true;
|
||||
// return;
|
||||
//}
|
||||
|
||||
var oldVal = this.value;
|
||||
this.value = e.target.value
|
||||
|
||||
if (this.revealed) {
|
||||
this.value = oldVal;
|
||||
return
|
||||
}
|
||||
|
||||
if (this.value.length > 1) {
|
||||
this.value = str.slice(0);
|
||||
e.target.value = this.value
|
||||
e.handled = true;
|
||||
}
|
||||
|
||||
if (!e.handled && this.value === " ") {
|
||||
this.value = "";
|
||||
e.target.value = "";
|
||||
e.handled = true;
|
||||
}
|
||||
|
||||
if (!e.handled && this.value.length === 0) {
|
||||
this.crosswordGrid.focusPrevCell(this.x, this.y);
|
||||
e.handled = true;
|
||||
}
|
||||
else if (!e.handled && this.value.length === 1) {
|
||||
this.crosswordGrid.focusNextCell(this.x, this.y);
|
||||
e.handled = true;
|
||||
}
|
||||
else {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
if (oldVal != this.value) {
|
||||
this.crosswordGrid.sendMessage({
|
||||
'type': 'update',
|
||||
'x': this.x,
|
||||
'y': this.y,
|
||||
'letter': this.value
|
||||
})
|
||||
}
|
||||
|
||||
if (!e.handled){
|
||||
|
||||
e.target.select();
|
||||
|
||||
e.handled = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onKeydown(e) {
|
||||
|
||||
if (!this.is_focused) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.key_delay = false;
|
||||
|
||||
var key = e.key;
|
||||
|
||||
console.log(key);
|
||||
|
||||
|
||||
if (this.value.length == 0) {
|
||||
if (key === 'Backspace' || key === 'Delete') {
|
||||
this.crosswordGrid.focusPrevCell(this.x, this.y);
|
||||
e.handled = true;
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// check arrow keys
|
||||
if (key === "ArrowLeft") {
|
||||
// left
|
||||
this.crosswordGrid.focusPrevCellHorizontal(this.x, this.y);
|
||||
e.handled = true;
|
||||
return;
|
||||
}
|
||||
else if (key === "ArrowUp") {
|
||||
// up
|
||||
this.crosswordGrid.focusPrevCellVertical(this.x, this.y);
|
||||
e.handled = true;
|
||||
return;
|
||||
|
||||
}
|
||||
else if (key === "ArrowRight") {
|
||||
//right
|
||||
this.crosswordGrid.focusNextCellHorizontal(this.x, this.y);
|
||||
e.handled = true;
|
||||
return;
|
||||
}
|
||||
else if (key === "ArrowDown") {
|
||||
//down
|
||||
this.crosswordGrid.focusNextCellVertical(this.x, this.y);
|
||||
e.handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
e.target.select();
|
||||
|
||||
|
||||
}
|
||||
|
||||
onFocus(e) {
|
||||
e.target.select();
|
||||
this.is_focused = true;
|
||||
this.key_delay = true;
|
||||
|
||||
this.crosswordGrid.updateHints(this.x, this.y);
|
||||
}
|
||||
|
||||
focus() {
|
||||
var element = this.shadowRoot.getElementById(`${this.x}-${this.y}`);
|
||||
element.focus();
|
||||
}
|
||||
|
||||
blur() {
|
||||
var element = this.shadowRoot.getElementById(`${this.x}-${this.y}`);
|
||||
element.blur();
|
||||
this.is_focused = false;
|
||||
}
|
||||
|
||||
updateGridElement() {
|
||||
super.updateGridElement()
|
||||
|
||||
if (this.grid_id != null) {
|
||||
|
||||
this.gridElement.setGridLetter(this);
|
||||
}
|
||||
}
|
||||
|
||||
updateLetter(letter, revealed) {
|
||||
this.value = letter;
|
||||
if (revealed)
|
||||
{
|
||||
console.log("rec")
|
||||
this.shadowRoot.getElementById(`${this.x}-${this.y}`).readOnly = true;
|
||||
this.blur();
|
||||
this.revealed = true;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
this.updateGridElement();
|
||||
return html`<input type="text" id="${this.x}-${this.y}" value="${this.value}" @input=${this.onInput} @focus=${this.onFocus} @keydown=${this.onKeydown} autocomplete="off"></input>`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('grid-letter', GridLetter);
|
||||
|
||||
|
||||
|
||||
export class HintBox extends GridBox {
|
||||
|
||||
static get styles() {
|
||||
|
||||
return css`
|
||||
|
||||
.hint {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
|
||||
.triangle-down {
|
||||
position: relative;
|
||||
top: 3em;
|
||||
left: 1em;
|
||||
z-index: 10;
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 0.75em solid transparent;
|
||||
border-right: 0.75em solid transparent;
|
||||
border-top: 1.3em solid #555;
|
||||
|
||||
}
|
||||
|
||||
.triangle-right {
|
||||
position: relative;
|
||||
top: 1em;
|
||||
left: 3em;
|
||||
z-index: 10;
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 0.75em solid transparent;
|
||||
border-bottom: 0.75em solid transparent;
|
||||
border-left: 1.3em solid #555;
|
||||
|
||||
}
|
||||
|
||||
input[type='text']{
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
text-align: center;
|
||||
border-style: solid;
|
||||
border-color: black;
|
||||
border-width: 0.2 em;
|
||||
background-color: #555;
|
||||
outline: none;
|
||||
color: white;
|
||||
text-shadow: 0 0 1 black;
|
||||
font-size: 2em;
|
||||
user-select: none;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
x: { type: Number },
|
||||
y: { type: Number },
|
||||
grid_id: { type: String },
|
||||
horizontal_hint: { type: String },
|
||||
vertical_hint: { type: String },
|
||||
hint_id: { type: String }
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.vertical_hint = "";
|
||||
this.horizontal_hint = "";
|
||||
this.hint_id = 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
getVerticalHint() {
|
||||
return this.vertical_hint;
|
||||
}
|
||||
|
||||
getHorizontalHint() {
|
||||
return this.horizontal_hint;
|
||||
}
|
||||
|
||||
hasVerticalHint() {
|
||||
return this.vertical_hint.length > 0;
|
||||
}
|
||||
|
||||
hasHorizontalHint() {
|
||||
return this.horizontal_hint.length > 0;
|
||||
}
|
||||
|
||||
updateGridElement() {
|
||||
super.updateGridElement()
|
||||
|
||||
if (this.grid_id != null) {
|
||||
|
||||
this.gridElement.setHintBox(this);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
this.updateGridElement();
|
||||
|
||||
var verticalArrow = html``;
|
||||
var horizontalArrow = html``;
|
||||
|
||||
if (this.hasVerticalHint()) {
|
||||
verticalArrow = html`<div class="triangle-down"></div>`;
|
||||
}
|
||||
|
||||
if (this.hasHorizontalHint()) {
|
||||
horizontalArrow = html`<div class="triangle-right"></div>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="hint">
|
||||
<input type="text" id="${this.x}-${this.y}" value="${this.hint_id}" disabled></input>
|
||||
${verticalArrow}
|
||||
${horizontalArrow}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hint-box', HintBox);
|
||||
|
||||
export class CrosswordGrid extends LitElement {
|
||||
static get styles() {
|
||||
return css`
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
table {
|
||||
position: relative;
|
||||
top: 5em;
|
||||
border-spacing: 0px;
|
||||
border-style: solid;
|
||||
border-color: black;
|
||||
border-width: 1em;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
id: { type: String },
|
||||
width: { type: Number },
|
||||
height: { type: Number },
|
||||
infobox_id: {type: String}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.width = 10;
|
||||
this.height = 10;
|
||||
this.grid = [];
|
||||
|
||||
this.json_grid = null;
|
||||
|
||||
this.num_hints = 0;
|
||||
|
||||
this.lastMoveVertical = false;
|
||||
|
||||
this.infobox = null;
|
||||
|
||||
this.serverConnection = null;
|
||||
}
|
||||
|
||||
createGridByJson(json_grid) {
|
||||
this.num_hints = 0;
|
||||
this.json_grid = json_grid;
|
||||
this.grid = [];
|
||||
const width = json_grid.w;
|
||||
const height = json_grid.h;
|
||||
|
||||
var y;
|
||||
var x;
|
||||
|
||||
for (y = 0; y < height; y++) {
|
||||
var row = [];
|
||||
for (x = 0; x < width; x++) {
|
||||
const json_cell = json_grid.grid[y][x];
|
||||
const cell_type = json_cell.cell_type;
|
||||
|
||||
switch (cell_type) {
|
||||
case "empty": {
|
||||
row.push(new GridElement(x, y, this, gridType.EMPTY));
|
||||
break;
|
||||
}
|
||||
case "hint": {
|
||||
row.push(new GridElement(x, y, this, gridType.HINT));
|
||||
break;
|
||||
}
|
||||
case "letter": {
|
||||
row.push(new GridElement(x, y, this, gridType.LETTER));
|
||||
break;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
this.grid.push(row);
|
||||
}
|
||||
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
focusNextCellHorizontal(x, y) {
|
||||
if (x + 1 < this.width && this.grid[y][x + 1].getGridType() === gridType.LETTER) {
|
||||
if (this.grid[y][x].getGridType() === gridType.LETTER) {
|
||||
this.grid[y][x].getGridLetter().blur();
|
||||
}
|
||||
this.grid[y][x + 1].getGridLetter().focus();
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
focusPrevCellHorizontal(x, y) {
|
||||
if (x - 1 >= 0 && this.grid[y][x - 1].getGridType() === gridType.LETTER) {
|
||||
if (this.grid[y][x].getGridType() === gridType.LETTER) {
|
||||
this.grid[y][x].getGridLetter().blur();
|
||||
}
|
||||
this.grid[y][x - 1].getGridLetter().focus();
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
focusNextCellVertical(x, y) {
|
||||
if (y + 1 < this.height && this.grid[y + 1][x].getGridType() === gridType.LETTER) {
|
||||
if (this.grid[y][x].getGridType() === gridType.LETTER) {
|
||||
this.grid[y][x].getGridLetter().blur();
|
||||
}
|
||||
this.grid[y + 1][x].getGridLetter().focus();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
focusPrevCellVertical(x, y) {
|
||||
if (y - 1 >= 0 && this.grid[y - 1][x].getGridType() === gridType.LETTER) {
|
||||
if (this.grid[y][x].getGridType() === gridType.LETTER) {
|
||||
this.grid[y][x].getGridLetter().blur();
|
||||
}
|
||||
this.grid[y - 1][x].getGridLetter().focus();
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
focusNextCell(x, y) {
|
||||
if (this.lastMoveVertical) {
|
||||
if (this.focusNextCellVertical(x, y)) {
|
||||
return;
|
||||
}
|
||||
if (this.focusNextCellHorizontal(x, y)) {
|
||||
this.lastMoveVertical = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.focusNextCellHorizontal(x, y)) {
|
||||
return;
|
||||
}
|
||||
if (this.focusNextCellVertical(x, y)) {
|
||||
this.lastMoveVertical = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
focusPrevCell(x, y) {
|
||||
if (this.lastMoveVertical) {
|
||||
if (this.focusPrevCellVertical(x, y)) {
|
||||
return;
|
||||
}
|
||||
if (this.focusPrevCellHorizontal(x, y)) {
|
||||
this.lastMoveVertical = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.focusPrevCellHorizontal(x, y)) {
|
||||
return;
|
||||
}
|
||||
if (this.focusPrevCellVertical(x, y)) {
|
||||
this.lastMoveVertical = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getGridElement(x, y) {
|
||||
return this.grid[y][x];
|
||||
}
|
||||
|
||||
update(props) {
|
||||
if (props.has("width") || props.has("height")) {
|
||||
//
|
||||
}
|
||||
if (props.has("infobox_id")) {
|
||||
this.infobox = document.getElementById(this.infobox_id);
|
||||
}
|
||||
super.update()
|
||||
}
|
||||
|
||||
updateHints(x,y) {
|
||||
var cell = this.grid[y][x];
|
||||
if (cell.getGridType() != gridType.LETTER){
|
||||
console.error("cannot get hints for non letter cell");
|
||||
}
|
||||
|
||||
//get horizontal hint:
|
||||
var upper_end = cell;
|
||||
while (upper_end.getGridType() === gridType.LETTER) {
|
||||
upper_end = this.grid[upper_end.y - 1][x]
|
||||
}
|
||||
|
||||
var left_end = cell;
|
||||
while (left_end.getGridType() === gridType.LETTER) {
|
||||
left_end = this.grid[y][left_end.x - 1]
|
||||
}
|
||||
|
||||
var hHint = "";
|
||||
var vHint = "";
|
||||
|
||||
if (left_end.getGridType() === gridType.HINT) {
|
||||
var box = left_end.getHintBox();
|
||||
hHint = `${box.hint_id}: ${box.horizontal_hint}`;
|
||||
}
|
||||
|
||||
if (upper_end.getGridType() === gridType.HINT) {
|
||||
var box = upper_end.getHintBox()
|
||||
vHint = `${box.hint_id}: ${box.vertical_hint}`;
|
||||
}
|
||||
|
||||
this.infobox.horizontal_hint = hHint;
|
||||
this.infobox.vertical_hint = vHint;
|
||||
}
|
||||
|
||||
renderCell(el) {
|
||||
switch (el.type) {
|
||||
case gridType.LETTER: {
|
||||
return html`<grid-letter grid_id='${this.id}' x='${el.x}' y='${el.y}' ></grid-letter>`;
|
||||
|
||||
}
|
||||
case gridType.EMPTY: {
|
||||
return html`<grid-box grid_id='${this.id}' x='${el.x}' y='${el.y}' ></grid-box>`;
|
||||
|
||||
}
|
||||
case gridType.HINT: {
|
||||
var vertical_hint = this.json_grid.grid[el.y][el.x].vertical_hint;
|
||||
var horizontal_hint = this.json_grid.grid[el.y][el.x].horizontal_hint;
|
||||
|
||||
if (!vertical_hint){
|
||||
vertical_hint = "";
|
||||
}
|
||||
if (!horizontal_hint){
|
||||
horizontal_hint = "";
|
||||
}
|
||||
|
||||
this.num_hints += 1;
|
||||
|
||||
return html`
|
||||
<hint-box grid_id='${this.id}' x='${el.x}' y='${el.y}' horizontal_hint='${horizontal_hint}' vertical_hint='${vertical_hint}' hint_id=${this.num_hints}></hint-box>
|
||||
`;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateLetter(x,y,letter,revealed) {
|
||||
var cell = this.grid[y][x];
|
||||
if (cell.getGridType() != gridType.LETTER){
|
||||
console.error("received bad message");
|
||||
}
|
||||
cell.getGridLetter().updateLetter(letter, revealed)
|
||||
}
|
||||
|
||||
registerServerConnection(connection){
|
||||
this.serverConnection = connection
|
||||
}
|
||||
|
||||
sendMessage(msg){
|
||||
if (!this.serverConnection) {
|
||||
console.error("no server connection registered")
|
||||
}
|
||||
this.serverConnection.sendMessage(msg)
|
||||
}
|
||||
|
||||
render() {
|
||||
this.num_hints = 0;
|
||||
console.log("refreshing grid");
|
||||
return html`
|
||||
<table>
|
||||
${this.grid.map((row) => html`<tr>${row.map((el) => html`<td>${this.renderCell(el)}</td>`)}</tr>`)}
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('crossword-grid', CrosswordGrid);
|
20
js_client/index.html
Normal file
20
js_client/index.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<!-- Polyfills only needed for Firefox and Edge. -->
|
||||
<script src="https://unpkg.com/@webcomponents/webcomponentsjs@latest/webcomponents-loader.js"></script>
|
||||
<!-- Works only on browsers that support Javascript modules like
|
||||
Chrome, Safari, Firefox 60, Edge 17 -->
|
||||
|
||||
</head>
|
||||
|
||||
<body style="background-color: black;">
|
||||
<info-box id="infobox"></info-box>
|
||||
<server-connection id="server-connection" grid_id="grid" url="ws://localhost:8765"></server-connection>
|
||||
<crossword-grid id="grid" infobox_id="infobox" width="10" height="10"></crossword-grid>
|
||||
<script type="module" src="./main.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
55
js_client/infoBox.js
Normal file
55
js_client/infoBox.js
Normal file
@ -0,0 +1,55 @@
|
||||
import { html, css, LitElement, unsafeCSS } from 'https://unpkg.com/lit-element/lit-element.js?module';
|
||||
|
||||
export class InfoBox extends LitElement {
|
||||
static get styles() {
|
||||
return css`
|
||||
.hinttext {
|
||||
color: white;
|
||||
margin-left: 1em;
|
||||
margin-top: 0.8em;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.infobox {
|
||||
z-index: 20;
|
||||
position: fixed;
|
||||
top: 0em;
|
||||
left: 0em;
|
||||
right: 0em;
|
||||
height: 6em;
|
||||
|
||||
border-style: solid;
|
||||
border-color: white;
|
||||
border-width: 0.2 em;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
horizontal_hint: { type: String },
|
||||
vertical_hint: { type: String }
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.vertical_hint = "";
|
||||
this.horizontal_hint = "";
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="infobox">
|
||||
<div class="hinttext">▶ ${this.horizontal_hint}</div>
|
||||
<div class="hinttext">▼ ${this.vertical_hint}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
customElements.define('info-box', InfoBox);
|
3
js_client/main.js
Normal file
3
js_client/main.js
Normal file
@ -0,0 +1,3 @@
|
||||
import '/grid.js'
|
||||
import '/serverConnection.js'
|
||||
|
12
js_client/serve.py
Normal file
12
js_client/serve.py
Normal file
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
from http.server import HTTPServer, SimpleHTTPRequestHandler, test
|
||||
import sys
|
||||
|
||||
class CORSRequestHandler (SimpleHTTPRequestHandler):
|
||||
def end_headers (self):
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
SimpleHTTPRequestHandler.end_headers(self)
|
||||
|
||||
if __name__ == '__main__':
|
||||
test(CORSRequestHandler, HTTPServer, port=int(sys.argv[1]) if len(sys.argv) > 1 else 8000)
|
||||
|
141
js_client/serverConnection.js
Normal file
141
js_client/serverConnection.js
Normal file
@ -0,0 +1,141 @@
|
||||
import { html, css, LitElement } from 'https://unpkg.com/lit-element/lit-element.js?module';
|
||||
import { WebsocketConnection } from '/websocket.js';
|
||||
import { getCookie, setCookie } from '/cookie.js';
|
||||
|
||||
export class ServerConnection extends WebsocketConnection {
|
||||
static get styles() {
|
||||
return css``;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
url: { type: String },
|
||||
grid_id: { type: String }
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.sessionId = null;
|
||||
this.isRegistered = false;
|
||||
this.crossword_grid = null;
|
||||
}
|
||||
|
||||
update(props) {
|
||||
if (props.has("grid_id")) {
|
||||
this.crossword_grid = document.getElementById(this.grid_id);
|
||||
this.crossword_grid.registerServerConnection(this);
|
||||
}
|
||||
super.update(props)
|
||||
}
|
||||
|
||||
updateLocalSessionId() {
|
||||
// look whether the session id is given in url params
|
||||
// or stored as cookie
|
||||
const queryString = window.location.search;
|
||||
const params = new URLSearchParams(queryString);
|
||||
if (params.has('session')) {
|
||||
this.sessionId = params.get('session');
|
||||
return;
|
||||
}
|
||||
const cookie_session = getCookie('session');
|
||||
if (cookie_session != "") {
|
||||
this.sessionId = cookie_session;
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
register() {
|
||||
this.updateLocalSessionId();
|
||||
console.log("register", this.sessionId);
|
||||
this.sendMessage({
|
||||
'type': 'register',
|
||||
'sessionId': this.sessionId
|
||||
});
|
||||
}
|
||||
|
||||
onopen(event) {
|
||||
super.onopen(event);
|
||||
console.log("overloaded", this);
|
||||
this.register();
|
||||
}
|
||||
|
||||
handleRegistration(sessionId) {
|
||||
if (!sessionId) {
|
||||
console.warn("got undefined session id");
|
||||
return;
|
||||
}
|
||||
this.sessionId = sessionId;
|
||||
console.log("stored session", sessionId, "as cookie")
|
||||
setCookie("session", sessionId, 2);
|
||||
const urlparams = new URLSearchParams(window.location.search);
|
||||
if (urlparams.has('session') && this.sessionId === urlparams.get('session')){
|
||||
return;
|
||||
}
|
||||
urlparams.set('session', sessionId);
|
||||
var newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?session=' + sessionId;
|
||||
window.history.pushState({ path: newurl }, '', newurl);
|
||||
|
||||
}
|
||||
|
||||
handleCrossword(crossword) {
|
||||
this.crossword_grid.createGridByJson(crossword);
|
||||
}
|
||||
|
||||
handleUpdate(updates) {
|
||||
var i = 0;
|
||||
for (i = 0; i < updates.length; i++) {
|
||||
const item = updates[i];
|
||||
|
||||
const x = item['x'];
|
||||
const y = item['y'];
|
||||
|
||||
if (item.hasOwnProperty("user_input")) {
|
||||
const letter = item['user_input'];
|
||||
this.crossword_grid.updateLetter(x, y, letter, false);
|
||||
}
|
||||
if (item.hasOwnProperty("revealed")) {
|
||||
console.log("update");
|
||||
const letter = item['revealed'];
|
||||
this.crossword_grid.updateLetter(x, y, letter, true);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
onmessage(event) {
|
||||
super.onmessage(event)
|
||||
try {
|
||||
const msg = JSON.parse(event.data);
|
||||
if (!msg.type) {
|
||||
throw "missing type"
|
||||
}
|
||||
switch (msg.type) {
|
||||
case 'register': {
|
||||
this.handleRegistration(msg.sessionId);
|
||||
break;
|
||||
}
|
||||
case 'crossword': {
|
||||
this.handleCrossword(msg.crossword);
|
||||
break;
|
||||
}
|
||||
case 'update': {
|
||||
this.handleUpdate(msg.updates);
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
catch (err) {
|
||||
console.error("could not parse servermessage", err);
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
customElements.define('server-connection', ServerConnection);
|
88
js_client/websocket.js
Normal file
88
js_client/websocket.js
Normal file
@ -0,0 +1,88 @@
|
||||
import { html, css, LitElement } from 'https://unpkg.com/lit-element/lit-element.js?module';
|
||||
|
||||
export class WebsocketConnection extends LitElement {
|
||||
static get styles() {
|
||||
return css``;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
url: { type: String }
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.url = '';
|
||||
this.socket = null;
|
||||
}
|
||||
|
||||
|
||||
onopen(event){
|
||||
console.log("websocket connected");
|
||||
}
|
||||
|
||||
onclose(event){
|
||||
console.log("websocket closed");
|
||||
}
|
||||
|
||||
onerror(event){
|
||||
console.log("websocket error, closing...");
|
||||
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
||||
this.socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
onmessage(event){
|
||||
console.log("received message:", event.data);
|
||||
}
|
||||
|
||||
isSocketConnected(){
|
||||
if (! this.socket){
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.socket.readyState === WebSocket.OPEN) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
sendMessage(msg){
|
||||
|
||||
if (this.isSocketConnected()) {
|
||||
var string_msg = JSON.stringify(msg);
|
||||
this.socket.send(string_msg);
|
||||
}
|
||||
else {
|
||||
console.error("cannot send message, websocket disconnected");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
connect() {
|
||||
console.log(`connect to ${this.url}...`);
|
||||
this.socket = new WebSocket(this.url);
|
||||
this.socket.onopen = (event) => this.onopen(event);
|
||||
this.socket.onclose = (event) => this.onclose(event);
|
||||
this.socket.onerror = (event) => this.onerror(event);
|
||||
this.socket.onmessage = (event) => this.onmessage(event);
|
||||
}
|
||||
|
||||
update(props) {
|
||||
if (props.has("url")){
|
||||
this.connect();
|
||||
}
|
||||
super.update(props);
|
||||
}
|
||||
|
||||
|
||||
|
||||
render() {
|
||||
// do nothing
|
||||
return html``;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('websocket-connection', WebsocketConnection);
|
Reference in New Issue
Block a user