@mideind/netskrafl-react 1.0.0-beta.5 → 1.0.0-beta.7

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 (67) hide show
  1. package/{src/css/skrafl-explo.css → dist/cjs/css/netskrafl.css} +1068 -174
  2. package/dist/cjs/index.js +32477 -364
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/{cjs/index.css → esm/css/netskrafl.css} +1043 -350
  5. package/dist/esm/index.js +32475 -364
  6. package/dist/esm/index.js.map +1 -1
  7. package/dist/types.d.ts +2 -1
  8. package/package.json +14 -2
  9. package/.eslintignore +0 -8
  10. package/.eslintrc.json +0 -13
  11. package/dist/esm/index.css +0 -6837
  12. package/rollup.config.js +0 -60
  13. package/src/components/index.ts +0 -2
  14. package/src/components/netskrafl/Netskrafl.stories.tsx +0 -66
  15. package/src/components/netskrafl/Netskrafl.tsx +0 -138
  16. package/src/components/netskrafl/Netskrafl.types.ts +0 -7
  17. package/src/components/netskrafl/index.ts +0 -2
  18. package/src/css/fonts.css +0 -4
  19. package/src/css/glyphs.css +0 -224
  20. package/src/fonts/glyphicons-regular.eot +0 -0
  21. package/src/fonts/glyphicons-regular.ttf +0 -0
  22. package/src/fonts/glyphicons-regular.woff +0 -0
  23. package/src/index.ts +0 -2
  24. package/src/messages/messages.json +0 -1576
  25. package/src/mithril/actions.ts +0 -319
  26. package/src/mithril/bag.ts +0 -65
  27. package/src/mithril/bestdisplay.ts +0 -74
  28. package/src/mithril/blankdialog.ts +0 -94
  29. package/src/mithril/board.ts +0 -339
  30. package/src/mithril/buttons.ts +0 -303
  31. package/src/mithril/challengedialog.ts +0 -186
  32. package/src/mithril/channel.ts +0 -162
  33. package/src/mithril/chat.ts +0 -228
  34. package/src/mithril/components.ts +0 -496
  35. package/src/mithril/dragdrop.ts +0 -219
  36. package/src/mithril/elopage.ts +0 -202
  37. package/src/mithril/friend.ts +0 -227
  38. package/src/mithril/game.ts +0 -1378
  39. package/src/mithril/gameview.ts +0 -111
  40. package/src/mithril/globalstate.ts +0 -33
  41. package/src/mithril/i18n.ts +0 -186
  42. package/src/mithril/localstorage.ts +0 -133
  43. package/src/mithril/login.ts +0 -122
  44. package/src/mithril/logo.ts +0 -323
  45. package/src/mithril/main.ts +0 -755
  46. package/src/mithril/mithril.ts +0 -29
  47. package/src/mithril/model.ts +0 -855
  48. package/src/mithril/movelistitem.ts +0 -226
  49. package/src/mithril/page.ts +0 -856
  50. package/src/mithril/playername.ts +0 -91
  51. package/src/mithril/promodialog.ts +0 -82
  52. package/src/mithril/recentlist.ts +0 -148
  53. package/src/mithril/request.ts +0 -52
  54. package/src/mithril/review.ts +0 -634
  55. package/src/mithril/rightcolumn.ts +0 -398
  56. package/src/mithril/searchbutton.ts +0 -118
  57. package/src/mithril/statsdisplay.ts +0 -109
  58. package/src/mithril/tabs.ts +0 -169
  59. package/src/mithril/tile.ts +0 -145
  60. package/src/mithril/twoletter.ts +0 -76
  61. package/src/mithril/types.ts +0 -384
  62. package/src/mithril/userinfodialog.ts +0 -171
  63. package/src/mithril/util.ts +0 -304
  64. package/src/mithril/wait.ts +0 -246
  65. package/src/mithril/wordcheck.ts +0 -102
  66. package/tsconfig.json +0 -28
  67. package/vite.config.ts +0 -12
@@ -1,856 +0,0 @@
1
- /*
2
-
3
- Page.ts
4
-
5
- Single page UI for Explo using the Mithril library
6
-
7
- Copyright (C) 2024 Miðeind ehf.
8
- Author: Vilhjálmur Þorsteinsson
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
- This UI is built on top of Mithril (https://mithril.js.org), a lightweight,
15
- straightforward JavaScript single-page reactive UI library.
16
-
17
- The page is structured into models, actions and views,
18
- cf. https://github.com/pakx/the-mithril-diaries/wiki/Basic-App-Structure
19
-
20
- */
21
-
22
- import { mt, t, ts } from "./i18n";
23
- import { GlobalState } from "./globalstate";
24
- import { IView, DialogViewEnum } from "./types";
25
- import { setServerUrl } from "./request";
26
- import {
27
- glyph, nbsp, getInput,
28
- scrollMovelistToBottom, coord, toVector,
29
- } from "./util";
30
- import {
31
- m, Vnode, VnodeDOM, VnodeChildren, EventHandler,
32
- } from "./mithril";
33
- import { logEvent, loginFirebase } from "./channel";
34
- import { Model, getSettings } from "./model";
35
- import { Actions, createRouteResolver } from "./actions";
36
- import { WaitDialog, AcceptDialog } from "./wait";
37
- import {
38
- FriendPromoteDialog, FriendThanksDialog,
39
- FriendCancelDialog, FriendCancelConfirmDialog
40
- } from "./friend";
41
- import { LoginError, LoginForm, loginUserByEmail } from "./login";
42
- import {
43
- DialogButton, Spinner,
44
- UserId,
45
- LeftLogo,
46
- TogglerAudio,
47
- TogglerFanfare,
48
- TogglerBeginner,
49
- TogglerFairplay,
50
- TextInput
51
- } from "./components";
52
- import { ChallengeDialog } from "./challengedialog";
53
- import { Main } from "./main";
54
- import { makeTabs, selectTab, TabVnode } from "./tabs";
55
- import { PromoDialog } from "./promodialog";
56
- import { UserInfoDialog } from "./userinfodialog";
57
- import { GameView } from "./gameview";
58
- import { vwReview } from "./review";
59
-
60
- /*
61
- // EXPERIMENTAL
62
- function insertStyleSheet(url: string) {
63
- // Insert a link rel="stylesheet" element into the document head,
64
- // if it isn't there already
65
- const head = document.head;
66
- if (!head) return;
67
- const links = head.getElementsByTagName("link");
68
- for (const link of links) {
69
- if (link.href === url) return;
70
- }
71
- const link = document.createElement("link");
72
- link.rel = "stylesheet";
73
- link.href = url;
74
- head.appendChild(link);
75
- }
76
- */
77
-
78
- function updateFontFaceUrls(serverUrl: string) {
79
- // Get all stylesheets in the document
80
- try {
81
- const styleSheets = document.styleSheets;
82
- // Iterate through each stylesheet
83
- for (const styleSheet of styleSheets) {
84
- // Iterate through each CSS rule in the stylesheet
85
- for (const rule of styleSheet.cssRules) {
86
- // Check if the rule is a @font-face rule
87
- if (rule instanceof CSSFontFaceRule) {
88
- // Update the src property with new URLs
89
- const src = rule.style.getPropertyValue("src");
90
- if (src.includes("glyphicons-")) {
91
- rule.style.setProperty("src", `
92
- url('${serverUrl}/static/glyphicons-regular.eot') format('embedded-opentype'),
93
- url('${serverUrl}/static/glyphicons-regular.woff') format('woff'),
94
- url('${serverUrl}/static/glyphicons-regular.ttf') format('truetype')
95
- `);
96
- }
97
- }
98
- }
99
- }
100
- } catch (e) {
101
- // Handle any errors that occur while accessing stylesheets
102
- console.error("Error when updating font face URLs: ", e);
103
- }
104
- }
105
-
106
- export type LoginResult = "success" | "expired" | "error";
107
-
108
- export async function main(state: GlobalState, container: HTMLElement): Promise<LoginResult> {
109
- // The main UI entry point, called from page.html
110
-
111
- if (!container) {
112
- console.error("No container element found");
113
- return "error";
114
- }
115
- // Set up Netskrafl backend server URLs
116
- setServerUrl(state.serverUrl, state.movesUrl, state.movesAccessKey);
117
-
118
- // Insert the Explo CSS stylesheet
119
- // insertStyleSheet("./index.css");
120
-
121
- // Update font URLs to point to the backend server
122
- updateFontFaceUrls(state.serverUrl);
123
-
124
- try {
125
- const loginData = await loginUserByEmail(
126
- state.userEmail,
127
- state.userNick,
128
- state.userFullname,
129
- state.token,
130
- );
131
- if (loginData.status === "expired") {
132
- // The current Málstaður JWT has expired;
133
- // we need to obtain a new one
134
- return "expired";
135
- }
136
- if (loginData.status === "success") {
137
- state.userId = loginData.user_id;
138
- // Log in to Firebase with the token passed from the server
139
- await loginFirebase(state, loginData.firebase_token);
140
- // Everything looks OK:
141
- // Create the model, view and actions objects
142
- const settings = getSettings();
143
- const model = new Model(settings, state);
144
- const view = new View(model);
145
- const actions = new Actions(model, view);
146
- // Run the Mithril router
147
- const routeResolver = createRouteResolver(actions);
148
- m.route(container, settings.defaultRoute, routeResolver);
149
- return "success";
150
- }
151
- } catch(e) {
152
- console.error("Exception during login: ", e);
153
- }
154
- m.mount(container, LoginError);
155
- return "error";
156
- }
157
-
158
- export function unmount(container: HTMLElement) {
159
- // Unmount the Mithril UI
160
- m.mount(container, null);
161
- }
162
-
163
- type DialogFunc = (view: View, args: any) => VnodeChildren;
164
-
165
- type DialogViews = Record<DialogViewEnum, DialogFunc>;
166
-
167
- interface Dialog {
168
- name: DialogViewEnum;
169
- args: any;
170
- }
171
-
172
- export class View implements IView {
173
-
174
- // The View class exposes the vwApp view function.
175
- // Each instance maintains a current dialog window stack.
176
-
177
- // The model that the view is attached to
178
- model: Model;
179
-
180
- // The currently displayed dialogs
181
- private dialogStack: Dialog[] = [];
182
-
183
- // Map of available dialogs
184
- private static dialogViews: DialogViews = {
185
- userprefs:
186
- (view) => view.vwUserPrefs(),
187
- userinfo:
188
- (view, args) => view.vwUserInfo(args),
189
- challenge:
190
- (view, args) => m(ChallengeDialog, { view, item: args }),
191
- promo:
192
- (view, args) => view.vwPromo(args),
193
- friend:
194
- (view) => m(FriendPromoteDialog, { view }),
195
- thanks:
196
- (view) => m(FriendThanksDialog, { view }),
197
- cancel:
198
- (view) => m(FriendCancelDialog, { view }),
199
- confirm:
200
- (view) => m(FriendCancelConfirmDialog, { view }),
201
- wait:
202
- (view, args) => view.vwWait(args),
203
- accept:
204
- (view, args) => view.vwAccept(args)
205
- };
206
-
207
- // The current scaling of the board
208
- boardScale: number = 1.0;
209
-
210
- constructor(model: Model) {
211
-
212
- this.model = model;
213
-
214
- // Start a blinker interval function
215
- window.setInterval(this.blinker, 500);
216
-
217
- }
218
-
219
- appView(routeName: string): VnodeChildren {
220
- // Returns a view based on the current route.
221
- // Displays the appropriate content for the route,
222
- // also considering active dialogs.
223
- const model = this.model;
224
- let views: VnodeChildren = [];
225
- switch (routeName) {
226
- case "login":
227
- // The login screen is displayed by default
228
- views.push(this.vwLogin());
229
- break;
230
- case "loginerror":
231
- views.push(m(LoginError));
232
- break;
233
- case "main":
234
- views.push(m(Main, { view: this }));
235
- break;
236
- case "game":
237
- views.push(m(GameView, { view: this }));
238
- break;
239
- case "review":
240
- const n = vwReview(this);
241
- n && views.push(n);
242
- break;
243
- case "thanks":
244
- // Display a thank-you dialog on top of the normal main screen
245
- views.push(m(Main, { view: this }));
246
- // Be careful to add the Thanks dialog only once to the stack
247
- if (!this.dialogStack.length)
248
- this.showThanks();
249
- break;
250
- case "help":
251
- // A route parameter of ?q=N goes directly to the FAQ number N
252
- // A route parameter of ?tab=N goes directly to tab N (0-based)
253
- views.push(
254
- this.vwHelp(
255
- parseInt(m.route.param("tab") || ""),
256
- parseInt(m.route.param("faq") || "")
257
- )
258
- );
259
- break;
260
- default:
261
- return [ m("div", t("Þessi vefslóð er ekki rétt")) ];
262
- }
263
- // Push any open dialogs
264
- for (const dialog of this.dialogStack) {
265
- const v = View.dialogViews[dialog.name];
266
- if (v === undefined) {
267
- console.error("Unknown dialog name: " + dialog.name);
268
- } else {
269
- const n = v(this, dialog.args);
270
- n && views.push(n);
271
- }
272
- }
273
- // Overlay a spinner, if active
274
- if (model.spinners)
275
- views.push(m(Spinner));
276
- return views;
277
- }
278
-
279
- // Dialog support
280
-
281
- pushDialog(dialogName: DialogViewEnum, dialogArgs?: any) {
282
- this.dialogStack.push({ name: dialogName, args: dialogArgs });
283
- m.redraw(); // Ensure that the dialog is shown
284
- }
285
-
286
- popDialog() {
287
- if (this.dialogStack.length > 0) {
288
- this.dialogStack.pop();
289
- m.redraw();
290
- }
291
- }
292
-
293
- popAllDialogs() {
294
- if (this.dialogStack.length > 0) {
295
- this.dialogStack = [];
296
- m.redraw();
297
- }
298
- }
299
-
300
- isDialogShown() {
301
- return this.dialogStack.length > 0;
302
- }
303
-
304
- startSpinner() {
305
- this.model.spinners++;
306
- }
307
-
308
- stopSpinner() {
309
- if (this.model.spinners) {
310
- this.model.spinners--;
311
- }
312
- }
313
-
314
- async cancelFriendship() {
315
- // Initiate cancellation of the user's friendship
316
- let spinner = true;
317
- try {
318
- this.startSpinner();
319
- if (await this.model.cancelFriendship()) {
320
- // Successfully cancelled the friendship
321
- this.stopSpinner();
322
- spinner = false;
323
- // Show a confirmation of the cancellation
324
- this.pushDialog("confirm", {});
325
- }
326
- } catch (e) {
327
- // Simply display no confirmation in this case
328
- }
329
- finally {
330
- if (spinner)
331
- this.stopSpinner();
332
- }
333
- }
334
-
335
- notifyMediaChange() {
336
- // The view is changing, between mobile and fullscreen
337
- // and/or between portrait and landscape: ensure that
338
- // we don't end up with a selected game tab that is not visible
339
- const model = this.model;
340
- if (model.game) {
341
- if (model.state?.uiFullscreen || model.state?.uiLandscape) {
342
- // In this case, there is no board tab:
343
- // show the movelist
344
- if (model.game.setSelectedTab("movelist"))
345
- setTimeout(scrollMovelistToBottom);
346
- }
347
- else {
348
- // Mobile: we default to the board tab
349
- model.game.setSelectedTab("board");
350
- }
351
- }
352
- // When switching between landscape and portrait,
353
- // close all current dialogs
354
- this.popAllDialogs();
355
- }
356
-
357
- notifyChatMessage() {
358
- // A fresh chat message has arrived
359
- // and has been added to the chat message list
360
- m.redraw();
361
- }
362
-
363
- resetScale() {
364
- // Reset the board scale (zoom) to 100% and the scroll origin to (0, 0)
365
- this.boardScale = 1.0;
366
- const boardParent = document.getElementById("board-parent");
367
- const board = boardParent?.children[0];
368
- if (board)
369
- board.setAttribute("style", "transform: scale(1.0)");
370
- if (boardParent)
371
- boardParent.scrollTo(0, 0);
372
- }
373
-
374
- updateScale() {
375
-
376
- const model = this.model;
377
- const game = model.game;
378
-
379
- // Update the board scale (zoom)
380
-
381
- function scrollIntoView(sq: string) {
382
- // Scroll a square above and to the left of the placed tile into view
383
- const offset = 3;
384
- const vec = toVector(sq);
385
- const row = Math.max(0, vec.row - offset);
386
- const col = Math.max(0, vec.col - offset);
387
- const c = coord(row, col);
388
- const boardParent = document.getElementById("board-parent");
389
- const board = boardParent?.children[0];
390
- // The following seems to be needed to ensure that
391
- // the transform and hence the size of the board has been
392
- // updated in the browser, before calculating the client rects
393
- if (board)
394
- board.setAttribute("style", "transform: scale(1.5)");
395
- const el = document.getElementById("sq_" + c);
396
- const elRect = el?.getBoundingClientRect();
397
- const boardRect = boardParent?.getBoundingClientRect();
398
- if (boardParent && elRect && boardRect) {
399
- boardParent.scrollTo(
400
- {
401
- left: elRect.left - boardRect.left,
402
- top: elRect.top - boardRect.top,
403
- behavior: "smooth"
404
- }
405
- );
406
- }
407
- }
408
-
409
- if (!game || model.state?.uiFullscreen || game.moveInProgress) {
410
- // No game or we're in full screen mode: always 100% scale
411
- // Also, as soon as a move is being processed by the server, we zoom out
412
- this.boardScale = 1.0; // Needs to be done before setTimeout() call
413
- setTimeout(this.resetScale);
414
- return;
415
- }
416
- const tp = game.tilesPlaced();
417
- const numTiles = tp.length;
418
- if (numTiles === 1 && this.boardScale === 1.0) {
419
- // Laying down first tile: zoom in & position
420
- this.boardScale = 1.5;
421
- setTimeout(() => scrollIntoView(tp[0]));
422
- }
423
- else if (numTiles === 0 && this.boardScale > 1.0) {
424
- // Removing only remaining tile: zoom out
425
- this.boardScale = 1.0; // Needs to be done before setTimeout() call
426
- setTimeout(() => this.resetScale());
427
- }
428
- }
429
-
430
- showUserInfo(userid: string, nick: string, fullname: string) {
431
- // Show a user info dialog
432
- this.pushDialog("userinfo", { userid: userid, nick: nick, fullname: fullname });
433
- }
434
-
435
- showFriendPromo() {
436
- // Show a friendship promotion
437
- this.pushDialog("friend", { });
438
- }
439
-
440
- showThanks() {
441
- // Show thanks for becoming a friend
442
- this.pushDialog("thanks", { });
443
- }
444
-
445
- showFriendCancel() {
446
- // Show a friendship cancellation dialog
447
- this.pushDialog("cancel", { });
448
- }
449
-
450
- showAcceptDialog(oppId: string, oppNick: string, challengeKey: string) {
451
- this.pushDialog("accept", { oppId, oppNick, challengeKey });
452
- }
453
-
454
- // Globally available view functions
455
-
456
- vwDialogButton(
457
- id: string, title: string, func: EventHandler,
458
- content: VnodeChildren, tabindex: number
459
- ): VnodeChildren {
460
- // Create a .modal-close dialog button
461
- const attrs = {
462
- id: id,
463
- onclick: func,
464
- title,
465
- tabindex
466
- };
467
- return m(DialogButton, attrs, content);
468
- }
469
-
470
- blinker() {
471
- // Toggle the 'over' class on all elements having the 'blinking' class
472
- const blinkers = document.getElementsByClassName('blinking');
473
- for (let b of blinkers)
474
- b.classList.toggle("over");
475
- }
476
-
477
- // A control that rigs up a tabbed view of raw HTML
478
-
479
- vwTabsFromHtml(
480
- html: string, id: string, tabNumber: number, createFunc: (vnode: TabVnode) => void
481
- ): VnodeChildren {
482
- // The function assumes that 'this' is the current view object
483
- if (!html)
484
- return "";
485
- return m("div.help-tabs",
486
- {
487
- oninit: (vnode) => { vnode.state.selected = tabNumber || 1; },
488
- oncreate: (vnode) => { makeTabs(this, id, createFunc, true, vnode); }
489
- /* onupdate: updateSelection */
490
- },
491
- m.trust(html)
492
- );
493
- }
494
-
495
- // Help screen
496
-
497
- vwHelp(tabNumber: number, faqNumber: number): Vnode {
498
-
499
- const model = this.model;
500
-
501
- function wireQuestions(vnode: TabVnode) {
502
- // Clicking on a question brings the corresponding answer into view
503
- // This is achieved by wiring up all contained a[href="#faq-*"] links
504
-
505
- function showAnswer(ev: Event, href: string) {
506
- // this points to the vnode
507
- vnode.state.selected = 1; // FAQ tab
508
- vnode.dom.querySelector(href)?.scrollIntoView();
509
- ev.preventDefault();
510
- }
511
-
512
- const anchors = vnode.dom.querySelectorAll("a");
513
- for (const anchor of anchors) {
514
- const href = anchor.getAttribute("href");
515
- if (href && href.slice(0, 5) == "#faq-")
516
- // This is a direct link to a question: wire it up
517
- anchor.onclick = (ev) => { showAnswer(ev, href); };
518
- }
519
- if (faqNumber !== undefined && !isNaN(faqNumber)) {
520
- // Go to the FAQ tab and scroll the requested question into view
521
- selectTab(vnode, 1);
522
- vnode.state.selected = 1; // FAQ tab
523
- vnode.dom.querySelector("#faq-" + faqNumber.toString())?.scrollIntoView();
524
- }
525
- }
526
-
527
- // Output literal HTML obtained from rawhelp.html on the server
528
- return m.fragment({}, [
529
- m(LeftLogo),
530
- m(UserId, { view: this }),
531
- this.vwTabsFromHtml(model.helpHTML || "", "tabs", tabNumber, wireQuestions),
532
- ]);
533
- }
534
-
535
- // User preferences screen
536
-
537
- vwUserPrefsDialog(): VnodeChildren {
538
-
539
- const model = this.model;
540
- if (!model.user) return undefined;
541
- const user = model.user;
542
- if (!model.state) return undefined;
543
- const state = model.state;
544
- const err = model.userErrors || {};
545
- const view = this;
546
-
547
- function vwErrMsg(propname: keyof typeof err) {
548
- // Show a validation error message returned from the server
549
- return err.hasOwnProperty(propname) ?
550
- m(".errinput", [glyph("arrow-up"), nbsp(), err[propname] || ""]) : "";
551
- }
552
-
553
- function getToggle(elemId: string): boolean {
554
- const cls2 = document.querySelector("#" + elemId + "-toggler #opt2")?.classList;
555
- if (!cls2) return false;
556
- return cls2.contains("selected");
557
- }
558
-
559
- function validate() {
560
- // Move data back to the model.user object
561
- // before sending it to the server
562
- user.nickname = getInput("nickname");
563
- user.full_name = getInput("full_name");
564
- user.audio = getToggle("audio");
565
- user.fanfare = getToggle("fanfare");
566
- user.beginner = getToggle("beginner");
567
- user.fairplay = getToggle("fairplay");
568
- // When done, pop the current dialog
569
- model.saveUser(() => { view.popDialog(); });
570
- }
571
-
572
- function initFocus(vnode: VnodeDOM) {
573
- // Set the focus on the nickname field when the dialog is displayed
574
- (vnode.dom.querySelector("#nickname") as HTMLElement).focus();
575
- }
576
-
577
- return m(".modal-dialog",
578
- {
579
- id: "user-dialog",
580
- oncreate: initFocus
581
- // onupdate: initFocus
582
- },
583
- m(".ui-widget.ui-widget-content.ui-corner-all", { id: 'user-form' },
584
- [
585
- m(".loginhdr", [glyph("address-book"), " " + ts("player_info")]), // "Player information"
586
- m("div",
587
- m("form", { action: '', id: 'frm1', method: 'post', name: 'frm1' },
588
- [
589
- m(".dialog-spacer",
590
- [
591
- m("span.caption", t("Einkenni:")),
592
- m(TextInput,
593
- {
594
- initialValue: user.nickname || "",
595
- class: "username",
596
- maxlength: 15,
597
- id: "nickname",
598
- // autocomplete: "nickname", // Chrome doesn't like this
599
- }
600
- ),
601
- nbsp(), m("span.asterisk", "*")
602
- ]
603
- ),
604
- m(".explain", t("Verður að vera útfyllt")),
605
- vwErrMsg("nickname"),
606
- m(".dialog-spacer",
607
- [
608
- m("span.caption", t("Fullt nafn:")),
609
- m(TextInput,
610
- {
611
- initialValue: user.full_name || "",
612
- class: "fullname",
613
- maxlength: 32,
614
- id: "full_name",
615
- autocomplete: "name",
616
- }
617
- )
618
- ]
619
- ),
620
- m(".explain", t("Valfrjálst - sýnt í notendalistum Netskrafls")),
621
- vwErrMsg("full_name"),
622
- m(".dialog-spacer",
623
- [
624
- m("span.caption.sub", t("Hljóðmerki:")),
625
- m(TogglerAudio, { view, state: user.audio, tabindex: 4 }),
626
- m("span.subcaption", t("Lúðraþytur eftir sigur:")),
627
- m(TogglerFanfare, { view, state: user.fanfare, tabindex: 5 }),
628
- ]
629
- ),
630
- m(".explain", t("explain_sound")),
631
- m(".dialog-spacer",
632
- [
633
- m("span.caption.sub", t("Sýna reitagildi:")),
634
- m(TogglerBeginner, { view, state: user.beginner, tabindex: 6 }),
635
- mt(".subexplain",
636
- [
637
- "Stillir hvort ",
638
- mt("strong", "minnismiði"),
639
- " um margföldunargildi reita er sýndur við borðið"
640
- ]
641
- )
642
- ]
643
- ),
644
- m(".dialog-spacer",
645
- [
646
- m("span.caption.sub", t("Án hjálpartækja:")),
647
- m(TogglerFairplay, { view, state: user.fairplay, tabindex: 7 }),
648
- mt(".subexplain",
649
- [
650
- "no_helpers",
651
- mt("strong", "án stafrænna hjálpartækja"),
652
- " af nokkru tagi"
653
- ]
654
- )
655
- ]
656
- )
657
- ]
658
- )
659
- ),
660
- this.vwDialogButton("user-ok", ts("Vista"), validate, glyph("ok"), 8),
661
- this.vwDialogButton("user-cancel", ts("Hætta við"),
662
- (ev) => { this.popDialog(); ev.preventDefault(); },
663
- glyph("remove"), 9),
664
- user.friend ?
665
- this.vwDialogButton("user-unfriend", ts("Hætta sem vinur"),
666
- (ev) => {
667
- ev.preventDefault();
668
- view.showFriendCancel()
669
- },
670
- [glyph("coffee-cup"), ts("Þú ert vinur Netskrafls!")], 10
671
- )
672
- :
673
- this.vwDialogButton("user-friend", ts("Gerast vinur"),
674
- (ev) => {
675
- // Invoke the friend promo dialog
676
- ev.preventDefault();
677
- logEvent("click_friend",
678
- {
679
- userid: state.userId, locale: state.locale
680
- }
681
- );
682
- view.showFriendPromo();
683
- },
684
- [glyph("coffee-cup"), nbsp(), nbsp(), ts("Gerast vinur Netskrafls")], 11
685
- )
686
- ]
687
- )
688
- );
689
- }
690
-
691
- vwUserPrefs(): VnodeChildren {
692
- const model = this.model;
693
- if (model.user === null && !model.userLoadError)
694
- model.loadUser(true); // Activate spinner while loading
695
- if (!model.user)
696
- // Nothing to edit (the spinner should be showing in this case)
697
- return m.fragment({}, []);
698
- return this.vwUserPrefsDialog();
699
- }
700
-
701
- vwUserInfo(args: { userid: string; nick: string; fullname: string; }): VnodeChildren {
702
- return m(UserInfoDialog,
703
- {
704
- view: this,
705
- userid: args.userid,
706
- nick: args.nick,
707
- fullname: args.fullname
708
- }
709
- );
710
- }
711
-
712
- vwPromo(args: { kind: string; initFunc: () => void; }): VnodeChildren {
713
- return m(PromoDialog,
714
- {
715
- view: this,
716
- kind: args.kind,
717
- initFunc: args.initFunc
718
- }
719
- );
720
- }
721
-
722
- vwWait(args: {
723
- oppId: string;
724
- oppNick: string;
725
- oppName: string;
726
- duration: number;
727
- challengeKey: string;
728
- }): VnodeChildren {
729
- return m(WaitDialog, {
730
- view: this,
731
- oppId: args.oppId,
732
- oppNick: args.oppNick,
733
- oppName: args.oppName,
734
- duration: args.duration,
735
- challengeKey: args.challengeKey,
736
- });
737
- }
738
-
739
- vwAccept(args: { oppId: string; oppNick: string; challengeKey: string; }): VnodeChildren {
740
- return m(AcceptDialog, {
741
- view: this,
742
- oppId: args.oppId,
743
- oppNick: args.oppNick,
744
- challengeKey: args.challengeKey,
745
- });
746
- }
747
-
748
- vwLogin(): VnodeChildren {
749
- const model = this.model;
750
- const loginUrl = model.state?.loginUrl || "";
751
- return m(LoginForm, { loginUrl });
752
- }
753
-
754
- vwDialogs(): VnodeChildren {
755
- // Show prompt dialogs below game board, if any
756
- const game = this.model.game;
757
- let r: Vnode[] = [];
758
- if (!game || game.showingDialog === null && !game.last_chall)
759
- return r;
760
- // The dialogs below, specifically the challenge and pass
761
- // dialogs, have priority over the last_chall dialog - since
762
- // they can be invoked while the last_chall dialog is being
763
- // displayed. We therefore allow them to cover the last_chall
764
- // dialog. On mobile, both dialogs are displayed simultaneously.
765
- if (game.last_chall)
766
- r.push(m(".chall-info", { style: { visibility: "visible" } },
767
- [
768
- glyph("info-sign"), nbsp(),
769
- // "Your opponent emptied the rack - you can challenge or pass"
770
- mt("span.pass-explain", "opponent_emptied_rack")
771
- ]
772
- ));
773
- if (game.showingDialog == "resign")
774
- r.push(m(".resign", { style: { visibility: "visible" } },
775
- [
776
- glyph("exclamation-sign"), nbsp(), ts("Viltu gefa leikinn?"), nbsp(),
777
- m("span.mobile-break", m("br")),
778
- m("span.yesnobutton", { onclick: () => game.confirmResign(true) },
779
- [glyph("ok"), ts(" Já")]
780
- ),
781
- m("span.mobile-space"),
782
- m("span.yesnobutton", { onclick: () => game.confirmResign(false) },
783
- [glyph("remove"), ts(" Nei")]
784
- )
785
- ]
786
- ));
787
- if (game.showingDialog == "pass") {
788
- if (game.last_chall)
789
- r.push(m(".pass-last", { style: { visibility: "visible" } },
790
- [
791
- glyph("forward"), nbsp(), ts("Segja pass?"),
792
- mt("span.pass-explain", "Viðureign lýkur þar með"),
793
- nbsp(),
794
- m("span.mobile-break", m("br")),
795
- m("span.yesnobutton", { onclick: () => game.confirmPass(true) },
796
- [glyph("ok"), ts(" Já")]
797
- ),
798
- m("span.mobile-space"),
799
- m("span.yesnobutton", { onclick: () => game.confirmPass(false) },
800
- [glyph("remove"), ts(" Nei")]
801
- )
802
- ]
803
- ));
804
- else
805
- r.push(m(".pass", { style: { visibility: "visible" } },
806
- [
807
- glyph("forward"), nbsp(), ts("Segja pass?"),
808
- mt("span.pass-explain", "2x3 pöss í röð ljúka viðureign"),
809
- nbsp(), m("span.mobile-break", m("br")),
810
- m("span.yesnobutton", { onclick: () => game.confirmPass(true) },
811
- [glyph("ok"), ts(" Já")]
812
- ),
813
- m("span.mobile-space"),
814
- m("span.yesnobutton", { onclick: () => game.confirmPass(false) },
815
- [glyph("remove"), ts(" Nei")]
816
- )
817
- ]
818
- ));
819
- }
820
- if (game.showingDialog == "exchange")
821
- r.push(m(".exchange", { style: { visibility: "visible" } },
822
- [
823
- glyph("refresh"), nbsp(),
824
- ts("Smelltu á flísarnar sem þú vilt skipta"), nbsp(),
825
- m("span.mobile-break", m("br")),
826
- m("span.yesnobutton",
827
- { title: ts('Skipta'), onclick: () => game.confirmExchange(true) },
828
- glyph("ok")
829
- ),
830
- m("span.mobile-space"),
831
- m("span.yesnobutton",
832
- { title: ts('Hætta við'), onclick: () => game.confirmExchange(false) },
833
- glyph("remove"))
834
- ]
835
- ));
836
- if (game.showingDialog == "chall")
837
- r.push(m(".chall", { style: { visibility: "visible" } },
838
- [
839
- glyph("ban-circle"), nbsp(), ts("Véfengja lögn?"),
840
- mt("span.pass-explain", "Röng véfenging kostar 10 stig"), nbsp(),
841
- m("span.mobile-break", m("br")),
842
- m("span.yesnobutton",
843
- { onclick: () => game.confirmChallenge(true) },
844
- [glyph("ok"), ts(" Já")]
845
- ),
846
- m("span.mobile-space"),
847
- m("span.yesnobutton",
848
- { onclick: () => game.confirmChallenge(false) },
849
- [glyph("remove"), ts(" Nei")]
850
- )
851
- ]
852
- ));
853
- return r;
854
- }
855
-
856
- } // class View