@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.
Files changed (68) hide show
  1. package/.eslintignore +8 -0
  2. package/.eslintrc.json +13 -0
  3. package/README.md +63 -0
  4. package/dist/cjs/index.css +6837 -0
  5. package/dist/cjs/index.js +3046 -0
  6. package/dist/cjs/index.js.map +1 -0
  7. package/dist/esm/index.css +6837 -0
  8. package/dist/esm/index.js +3046 -0
  9. package/dist/esm/index.js.map +1 -0
  10. package/dist/types.d.ts +41 -0
  11. package/package.json +63 -0
  12. package/rollup.config.js +60 -0
  13. package/src/components/index.ts +2 -0
  14. package/src/components/netskrafl/Netskrafl.stories.tsx +66 -0
  15. package/src/components/netskrafl/Netskrafl.tsx +135 -0
  16. package/src/components/netskrafl/Netskrafl.types.ts +7 -0
  17. package/src/components/netskrafl/index.ts +2 -0
  18. package/src/css/fonts.css +4 -0
  19. package/src/css/glyphs.css +224 -0
  20. package/src/css/skrafl-explo.css +6616 -0
  21. package/src/fonts/glyphicons-regular.eot +0 -0
  22. package/src/fonts/glyphicons-regular.ttf +0 -0
  23. package/src/fonts/glyphicons-regular.woff +0 -0
  24. package/src/index.ts +2 -0
  25. package/src/messages/messages.json +1576 -0
  26. package/src/mithril/actions.ts +319 -0
  27. package/src/mithril/bag.ts +65 -0
  28. package/src/mithril/bestdisplay.ts +74 -0
  29. package/src/mithril/blankdialog.ts +94 -0
  30. package/src/mithril/board.ts +336 -0
  31. package/src/mithril/buttons.ts +303 -0
  32. package/src/mithril/challengedialog.ts +186 -0
  33. package/src/mithril/channel.ts +162 -0
  34. package/src/mithril/chat.ts +228 -0
  35. package/src/mithril/components.ts +496 -0
  36. package/src/mithril/dragdrop.ts +219 -0
  37. package/src/mithril/elopage.ts +180 -0
  38. package/src/mithril/friend.ts +227 -0
  39. package/src/mithril/game.ts +1378 -0
  40. package/src/mithril/gameview.ts +111 -0
  41. package/src/mithril/globalstate.ts +33 -0
  42. package/src/mithril/i18n.ts +186 -0
  43. package/src/mithril/localstorage.ts +133 -0
  44. package/src/mithril/login.ts +122 -0
  45. package/src/mithril/logo.ts +270 -0
  46. package/src/mithril/main.ts +737 -0
  47. package/src/mithril/mithril.ts +29 -0
  48. package/src/mithril/model.ts +817 -0
  49. package/src/mithril/movelistitem.ts +226 -0
  50. package/src/mithril/page.ts +852 -0
  51. package/src/mithril/playername.ts +91 -0
  52. package/src/mithril/promodialog.ts +82 -0
  53. package/src/mithril/recentlist.ts +148 -0
  54. package/src/mithril/request.ts +52 -0
  55. package/src/mithril/review.ts +634 -0
  56. package/src/mithril/rightcolumn.ts +398 -0
  57. package/src/mithril/searchbutton.ts +118 -0
  58. package/src/mithril/statsdisplay.ts +109 -0
  59. package/src/mithril/tabs.ts +169 -0
  60. package/src/mithril/tile.ts +145 -0
  61. package/src/mithril/twoletter.ts +76 -0
  62. package/src/mithril/types.ts +379 -0
  63. package/src/mithril/userinfodialog.ts +171 -0
  64. package/src/mithril/util.ts +304 -0
  65. package/src/mithril/wait.ts +246 -0
  66. package/src/mithril/wordcheck.ts +102 -0
  67. package/tsconfig.json +28 -0
  68. 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
+ };