@mideind/netskrafl-react 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +8 -0
- package/.eslintrc.json +13 -0
- package/README.md +63 -0
- package/dist/cjs/index.css +6837 -0
- package/dist/cjs/index.js +3046 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.css +6837 -0
- package/dist/esm/index.js +3046 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/types.d.ts +41 -0
- package/package.json +63 -0
- package/rollup.config.js +60 -0
- package/src/components/index.ts +2 -0
- package/src/components/netskrafl/Netskrafl.stories.tsx +66 -0
- package/src/components/netskrafl/Netskrafl.tsx +135 -0
- package/src/components/netskrafl/Netskrafl.types.ts +7 -0
- package/src/components/netskrafl/index.ts +2 -0
- package/src/css/fonts.css +4 -0
- package/src/css/glyphs.css +224 -0
- package/src/css/skrafl-explo.css +6616 -0
- package/src/fonts/glyphicons-regular.eot +0 -0
- package/src/fonts/glyphicons-regular.ttf +0 -0
- package/src/fonts/glyphicons-regular.woff +0 -0
- package/src/index.ts +2 -0
- package/src/messages/messages.json +1576 -0
- package/src/mithril/actions.ts +319 -0
- package/src/mithril/bag.ts +65 -0
- package/src/mithril/bestdisplay.ts +74 -0
- package/src/mithril/blankdialog.ts +94 -0
- package/src/mithril/board.ts +336 -0
- package/src/mithril/buttons.ts +303 -0
- package/src/mithril/challengedialog.ts +186 -0
- package/src/mithril/channel.ts +162 -0
- package/src/mithril/chat.ts +228 -0
- package/src/mithril/components.ts +496 -0
- package/src/mithril/dragdrop.ts +219 -0
- package/src/mithril/elopage.ts +180 -0
- package/src/mithril/friend.ts +227 -0
- package/src/mithril/game.ts +1378 -0
- package/src/mithril/gameview.ts +111 -0
- package/src/mithril/globalstate.ts +33 -0
- package/src/mithril/i18n.ts +186 -0
- package/src/mithril/localstorage.ts +133 -0
- package/src/mithril/login.ts +122 -0
- package/src/mithril/logo.ts +270 -0
- package/src/mithril/main.ts +737 -0
- package/src/mithril/mithril.ts +29 -0
- package/src/mithril/model.ts +817 -0
- package/src/mithril/movelistitem.ts +226 -0
- package/src/mithril/page.ts +852 -0
- package/src/mithril/playername.ts +91 -0
- package/src/mithril/promodialog.ts +82 -0
- package/src/mithril/recentlist.ts +148 -0
- package/src/mithril/request.ts +52 -0
- package/src/mithril/review.ts +634 -0
- package/src/mithril/rightcolumn.ts +398 -0
- package/src/mithril/searchbutton.ts +118 -0
- package/src/mithril/statsdisplay.ts +109 -0
- package/src/mithril/tabs.ts +169 -0
- package/src/mithril/tile.ts +145 -0
- package/src/mithril/twoletter.ts +76 -0
- package/src/mithril/types.ts +379 -0
- package/src/mithril/userinfodialog.ts +171 -0
- package/src/mithril/util.ts +304 -0
- package/src/mithril/wait.ts +246 -0
- package/src/mithril/wordcheck.ts +102 -0
- package/tsconfig.json +28 -0
- package/vite.config.ts +12 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
Playername.ts
|
|
4
|
+
|
|
5
|
+
Player name component
|
|
6
|
+
|
|
7
|
+
Copyright (C) 2024 Miðeind ehf.
|
|
8
|
+
Author: Vilhjalmur Thorsteinsson
|
|
9
|
+
|
|
10
|
+
The Creative Commons Attribution-NonCommercial 4.0
|
|
11
|
+
International Public License (CC-BY-NC 4.0) applies to this software.
|
|
12
|
+
For further information, see https://github.com/mideind/Netskrafl
|
|
13
|
+
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { IGame, IView } from "./types";
|
|
17
|
+
import { ComponentFunc, m } from "./mithril";
|
|
18
|
+
import { glyph, nbsp } from "./util";
|
|
19
|
+
|
|
20
|
+
interface IAttributes {
|
|
21
|
+
view: IView;
|
|
22
|
+
side: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const PlayerName: ComponentFunc<IAttributes> = (initialVnode) => {
|
|
26
|
+
// Displays a player name, handling both human and robot players
|
|
27
|
+
// as well as left and right side, and local and remote colors
|
|
28
|
+
const { view } = initialVnode.attrs;
|
|
29
|
+
const model = view.model;
|
|
30
|
+
|
|
31
|
+
function lookAtPlayer(game: IGame, ev: Event, player: number | null, side: number) {
|
|
32
|
+
if (!(model.state?.uiFullscreen))
|
|
33
|
+
// Don't do anything on mobile, and allow the click
|
|
34
|
+
// to propagate to the parent
|
|
35
|
+
return;
|
|
36
|
+
if (player === side) {
|
|
37
|
+
// The player is clicking on himself:
|
|
38
|
+
// overlay a user preference dialog
|
|
39
|
+
view.pushDialog("userprefs");
|
|
40
|
+
}
|
|
41
|
+
else if (!game.autoplayer[side]) {
|
|
42
|
+
// Clicking on another human player: show their track record
|
|
43
|
+
view.showUserInfo(
|
|
44
|
+
game.userid[side],
|
|
45
|
+
game.nickname[side],
|
|
46
|
+
game.fullname[side]
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
ev.stopPropagation();
|
|
50
|
+
ev.preventDefault();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
view: (vnode) => {
|
|
55
|
+
const game = model.game;
|
|
56
|
+
if (!game) return undefined;
|
|
57
|
+
const apl0 = game.autoplayer[0];
|
|
58
|
+
const apl1 = game.autoplayer[1];
|
|
59
|
+
const nick0 = game.nickname[0];
|
|
60
|
+
const nick1 = game.nickname[1];
|
|
61
|
+
const player = game.player;
|
|
62
|
+
const localturn = game.localturn;
|
|
63
|
+
const gameover = game.over;
|
|
64
|
+
const side = vnode.attrs.side;
|
|
65
|
+
if (side == "left") {
|
|
66
|
+
// Left side player
|
|
67
|
+
if (apl0)
|
|
68
|
+
// Player 0 is a robot (autoplayer)
|
|
69
|
+
return m(".robot-btn.left", [glyph("cog"), nbsp(), nick0]);
|
|
70
|
+
const tomove = gameover || (localturn !== (player === 0)) ? "" : ".tomove";
|
|
71
|
+
return m(
|
|
72
|
+
".player-btn.left" + tomove,
|
|
73
|
+
{ id: "player-0", onclick: (ev: Event) => lookAtPlayer(game, ev, player, 0) },
|
|
74
|
+
[m("span.left-to-move"), nick0]
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Right side player
|
|
79
|
+
if (apl1)
|
|
80
|
+
// Player 1 is a robot (autoplayer)
|
|
81
|
+
return m(".robot-btn.right", [glyph("cog"), nbsp(), nick1]);
|
|
82
|
+
const tomove = gameover || (localturn !== (player === 1)) ? "" : ".tomove";
|
|
83
|
+
return m(
|
|
84
|
+
".player-btn.right" + tomove,
|
|
85
|
+
{ id: "player-1", onclick: (ev: Event) => lookAtPlayer(game, ev, player, 1) },
|
|
86
|
+
[m("span.right-to-move"), nick1]
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
PromoDialog.ts
|
|
4
|
+
|
|
5
|
+
Promotion dialog component
|
|
6
|
+
|
|
7
|
+
Copyright (C) 2024 Miðeind ehf.
|
|
8
|
+
Author: Vilhjalmur Thorsteinsson
|
|
9
|
+
|
|
10
|
+
The Creative Commons Attribution-NonCommercial 4.0
|
|
11
|
+
International Public License (CC-BY-NC 4.0) applies to this software.
|
|
12
|
+
For further information, see https://github.com/mideind/Netskrafl
|
|
13
|
+
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { IView } from "./types";
|
|
17
|
+
import { ComponentFunc, m, VnodeDOM } from "./mithril";
|
|
18
|
+
import { buttonOut, buttonOver } from "./util";
|
|
19
|
+
|
|
20
|
+
interface IAttributes {
|
|
21
|
+
view: IView;
|
|
22
|
+
kind: string;
|
|
23
|
+
initFunc: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const PromoDialog: ComponentFunc<IAttributes> = (initialVnode) => {
|
|
27
|
+
|
|
28
|
+
// A dialog showing promotional content fetched from the server
|
|
29
|
+
|
|
30
|
+
const view = initialVnode.attrs.view;
|
|
31
|
+
const model = view.model;
|
|
32
|
+
let html = "";
|
|
33
|
+
|
|
34
|
+
function _fetchContent(vnode: typeof initialVnode) {
|
|
35
|
+
// Fetch the content
|
|
36
|
+
model.loadPromoContent(
|
|
37
|
+
vnode.attrs.kind, (contentHtml) => { html = contentHtml; }
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function _onUpdate(vnode: VnodeDOM, initFunc: () => void) {
|
|
42
|
+
var noButtons = vnode.dom.getElementsByClassName("btn-promo-no") as HTMLCollectionOf<HTMLElement>;
|
|
43
|
+
// Override onclick, onmouseover and onmouseout for No buttons
|
|
44
|
+
for (let i = 0; i < noButtons.length; i++) {
|
|
45
|
+
noButtons[i].onclick = () => view.popDialog();
|
|
46
|
+
noButtons[i].onmouseover = buttonOver;
|
|
47
|
+
noButtons[i].onmouseout = buttonOut;
|
|
48
|
+
}
|
|
49
|
+
// Override onmouseover and onmouseout for Yes buttons
|
|
50
|
+
var yesButtons = vnode.dom.getElementsByClassName("btn-promo-yes") as HTMLCollectionOf<HTMLElement>;
|
|
51
|
+
for (let i = 0; i < yesButtons.length; i++) {
|
|
52
|
+
yesButtons[i].onmouseover = buttonOver;
|
|
53
|
+
yesButtons[i].onmouseout = buttonOut;
|
|
54
|
+
}
|
|
55
|
+
// Run an initialization function, if specified
|
|
56
|
+
if (initFunc !== undefined)
|
|
57
|
+
initFunc();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
|
|
62
|
+
oninit: _fetchContent,
|
|
63
|
+
|
|
64
|
+
view: (vnode) => {
|
|
65
|
+
let initFunc = vnode.attrs.initFunc;
|
|
66
|
+
return m(".modal-dialog",
|
|
67
|
+
{ id: "promo-dialog", style: { visibility: "visible" } },
|
|
68
|
+
m(".ui-widget.ui-widget-content.ui-corner-all",
|
|
69
|
+
{ id: "promo-form", className: "promo-" + vnode.attrs.kind },
|
|
70
|
+
m("div",
|
|
71
|
+
{
|
|
72
|
+
id: "promo-content",
|
|
73
|
+
onupdate: (vnode) => _onUpdate(vnode, initFunc)
|
|
74
|
+
},
|
|
75
|
+
m.trust(html)
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
Recentlist.ts
|
|
4
|
+
|
|
5
|
+
List of recent games display component
|
|
6
|
+
|
|
7
|
+
Copyright (C) 2024 Miðeind ehf.
|
|
8
|
+
Author: Vilhjalmur Thorsteinsson
|
|
9
|
+
|
|
10
|
+
The Creative Commons Attribution-NonCommercial 4.0
|
|
11
|
+
International Public License (CC-BY-NC 4.0) applies to this software.
|
|
12
|
+
For further information, see https://github.com/mideind/Netskrafl
|
|
13
|
+
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { IView, RecentListItem } from "./types";
|
|
17
|
+
import { ts } from "./i18n";
|
|
18
|
+
import { ComponentFunc, VnodeChildren, m } from "./mithril";
|
|
19
|
+
import { gameUrl, glyph, glyphGrayed, nbsp } from "./util";
|
|
20
|
+
|
|
21
|
+
interface IAttributes {
|
|
22
|
+
view: IView;
|
|
23
|
+
recentList: RecentListItem[];
|
|
24
|
+
id: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const RecentList: ComponentFunc<IAttributes> = () => {
|
|
28
|
+
// Shows a list of recent games, stored in vnode.attrs.recentList
|
|
29
|
+
|
|
30
|
+
function itemize(item: RecentListItem, i: number): VnodeChildren {
|
|
31
|
+
|
|
32
|
+
// Generate a list item about a recently completed game
|
|
33
|
+
|
|
34
|
+
function durationDescription(): VnodeChildren {
|
|
35
|
+
// Format the game duration
|
|
36
|
+
let duration: VnodeChildren = "";
|
|
37
|
+
if (!item.duration) {
|
|
38
|
+
// Regular (non-timed) game
|
|
39
|
+
if (item.days || item.hours || item.minutes) {
|
|
40
|
+
if (item.days > 1)
|
|
41
|
+
duration = item.days.toString() + ts(" dagar");
|
|
42
|
+
else
|
|
43
|
+
if (item.days == 1)
|
|
44
|
+
duration = ts("1 dagur");
|
|
45
|
+
if (item.hours > 0) {
|
|
46
|
+
if (duration.length)
|
|
47
|
+
duration += ts(" og ");
|
|
48
|
+
if (item.hours == 1)
|
|
49
|
+
duration += ts("1 klst");
|
|
50
|
+
else
|
|
51
|
+
duration += item.hours.toString() + ts(" klst");
|
|
52
|
+
}
|
|
53
|
+
if (item.days === 0) {
|
|
54
|
+
if (duration.length)
|
|
55
|
+
duration += ts(" og ");
|
|
56
|
+
if (item.minutes == 1)
|
|
57
|
+
duration += ts("1 mínúta");
|
|
58
|
+
else
|
|
59
|
+
duration += item.minutes.toString() + ts(" mínútur");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// This was a timed game
|
|
65
|
+
duration = [
|
|
66
|
+
m("span.timed-btn", { title: ts('Viðureign með klukku') }),
|
|
67
|
+
" 2 x " + item.duration + ts(" mínútur")
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
return duration;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Show the Elo point adjustments resulting from the game
|
|
74
|
+
let eloAdj: VnodeChildren = item.elo_adj ? item.elo_adj.toString() : "";
|
|
75
|
+
let eloAdjHuman: VnodeChildren = item.human_elo_adj ? item.human_elo_adj.toString() : "";
|
|
76
|
+
let eloAdjClass = "";
|
|
77
|
+
let eloAdjHumanClass = "";
|
|
78
|
+
// Find out the appropriate class to use depending on the adjustment sign
|
|
79
|
+
if (item.elo_adj !== null) {
|
|
80
|
+
if (item.elo_adj > 0) {
|
|
81
|
+
eloAdj = "+" + eloAdj;
|
|
82
|
+
eloAdjClass = "elo-win";
|
|
83
|
+
}
|
|
84
|
+
else
|
|
85
|
+
if (item.elo_adj < 0)
|
|
86
|
+
eloAdjClass = "elo-loss";
|
|
87
|
+
else {
|
|
88
|
+
eloAdjClass = "elo-neutral";
|
|
89
|
+
eloAdj = glyph("stroller", { title: 'Byrjandi' });
|
|
90
|
+
}
|
|
91
|
+
if (item.human_elo_adj !== null)
|
|
92
|
+
if (item.human_elo_adj > 0) {
|
|
93
|
+
eloAdjHuman = "+" + eloAdjHuman;
|
|
94
|
+
eloAdjHumanClass = "elo-win";
|
|
95
|
+
}
|
|
96
|
+
else
|
|
97
|
+
if (item.human_elo_adj < 0)
|
|
98
|
+
eloAdjHumanClass = "elo-loss";
|
|
99
|
+
else {
|
|
100
|
+
eloAdjHumanClass = "elo-neutral";
|
|
101
|
+
eloAdjHuman = glyph("stroller", { title: 'Byrjandi' });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
eloAdj = m("span",
|
|
105
|
+
{ class: 'elo-btn right ' + eloAdjClass + (eloAdj == "" ? " invisible" : "") },
|
|
106
|
+
eloAdj
|
|
107
|
+
);
|
|
108
|
+
eloAdjHuman = m("span",
|
|
109
|
+
{ class: 'elo-btn left ' + eloAdjHumanClass + (eloAdjHuman == "" ? " invisible" : "") },
|
|
110
|
+
eloAdjHuman
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return m(".listitem" + (i % 2 === 0 ? ".oddlist" : ".evenlist"),
|
|
114
|
+
m(m.route.Link,
|
|
115
|
+
// Clicking on the link opens up the game
|
|
116
|
+
{ href: gameUrl(item.url) },
|
|
117
|
+
[
|
|
118
|
+
m("span.list-win",
|
|
119
|
+
item.sc0 >= item.sc1 ?
|
|
120
|
+
glyph("bookmark", { title: item.sc0 == item.sc1 ? ts("Jafntefli") : ts("Sigur") }) :
|
|
121
|
+
glyphGrayed("bookmark", { title: ts("Tap") })
|
|
122
|
+
),
|
|
123
|
+
m("span.list-ts-short", item.ts_last_move),
|
|
124
|
+
m("span.list-nick",
|
|
125
|
+
item.opp_is_robot ? [glyph("cog"), nbsp(), item.opp] : item.opp
|
|
126
|
+
),
|
|
127
|
+
m("span.list-s0", item.sc0),
|
|
128
|
+
m("span.list-colon", ":"),
|
|
129
|
+
m("span.list-s1", item.sc1),
|
|
130
|
+
m("div.list-elo-adj", eloAdjHuman),
|
|
131
|
+
m("div.list-elo-adj", eloAdj),
|
|
132
|
+
m("span.list-duration", durationDescription()),
|
|
133
|
+
m("span.list-manual",
|
|
134
|
+
item.manual ? { title: ts("Keppnishamur") } : {},
|
|
135
|
+
glyph("lightbulb", undefined, !item.manual)
|
|
136
|
+
)
|
|
137
|
+
]
|
|
138
|
+
)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
view: (vnode) => {
|
|
144
|
+
const list = vnode.attrs.recentList;
|
|
145
|
+
return m("div", { id: vnode.attrs.id }, !list ? "" : list.map(itemize));
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
|
|
2
|
+
import m, { RequestOptions } from "mithril";
|
|
3
|
+
|
|
4
|
+
let BACKEND_SERVER_PREFIX = "http://127.0.0.1:3000"; // Default for development
|
|
5
|
+
let MOVES_SERVER_PREFIX = "https://moves-dot-explo-dev.appspot.com"; // Default for development
|
|
6
|
+
let MOVES_ACCESS_KEY = "None";
|
|
7
|
+
|
|
8
|
+
export const setServerUrl = (backendUrl: string, movesUrl: string, movesAccessKey: string) => {
|
|
9
|
+
// If the last character of the url is a slash, cut it off,
|
|
10
|
+
// since path URLs always start with a slash
|
|
11
|
+
const cleanupUrl = (url: string) => {
|
|
12
|
+
if (url.length > 0 && url[url.length - 1] === "/") {
|
|
13
|
+
url = url.slice(0, -1);
|
|
14
|
+
}
|
|
15
|
+
return url;
|
|
16
|
+
};
|
|
17
|
+
if (backendUrl)
|
|
18
|
+
BACKEND_SERVER_PREFIX = cleanupUrl(backendUrl);
|
|
19
|
+
if (movesUrl)
|
|
20
|
+
MOVES_SERVER_PREFIX = cleanupUrl(movesUrl);
|
|
21
|
+
if (movesAccessKey)
|
|
22
|
+
MOVES_ACCESS_KEY = movesAccessKey;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const serverUrl = (path: string): string => {
|
|
26
|
+
return `${BACKEND_SERVER_PREFIX}${path}`;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const request = <T>(options: RequestOptions<T> & { url: string }) => {
|
|
30
|
+
// Call the default service on the Google App Engine backend
|
|
31
|
+
return m.request<T>({
|
|
32
|
+
withCredentials: true,
|
|
33
|
+
...options,
|
|
34
|
+
url: serverUrl(options.url),
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const requestMoves = <T>(options: RequestOptions<T> & { url: string }) => {
|
|
39
|
+
// Call the moves service on the Google App Engine backend
|
|
40
|
+
const headers = {
|
|
41
|
+
"Content-Type": "application/json; charset=UTF-8",
|
|
42
|
+
Authorization: `Bearer ${MOVES_ACCESS_KEY}`,
|
|
43
|
+
...options?.headers,
|
|
44
|
+
};
|
|
45
|
+
return m.request<T>({
|
|
46
|
+
withCredentials: false,
|
|
47
|
+
method: "POST",
|
|
48
|
+
...options,
|
|
49
|
+
url: `${MOVES_SERVER_PREFIX}${options.url}`,
|
|
50
|
+
headers,
|
|
51
|
+
});
|
|
52
|
+
};
|