@mideind/netskrafl-react 2.0.1 → 2.1.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/README.md +111 -36
- package/dist/esm/css/netskrafl.css +131 -3
- package/dist/esm/index.js +313 -38163
- package/dist/esm/index.js.map +1 -1
- package/package.json +6 -4
- package/dist/cjs/css/netskrafl.css +0 -9255
- package/dist/cjs/index.js +0 -40881
- package/dist/cjs/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,63 +1,138 @@
|
|
|
1
1
|
|
|
2
2
|
# netskrafl-react
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
A React front end for Netskrafl, packaged as a React component library.
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
## Components
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
(a) performs user authentication and (b) determines whether users have full access to the
|
|
10
|
-
Netskrafl functionality (typically by being paying subscribers) or limited/free access.
|
|
11
|
-
Information about (a) and (b) is passed to the component via its props.
|
|
8
|
+
This library provides two main components:
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
The backend is written in Python, using the Flask web framework.
|
|
16
|
-
The `netskrafl-react` component talks to the Netskrafl server via a
|
|
17
|
-
JSON API over HTTPS, and uses Firebase for real-time updates.
|
|
10
|
+
- **Netskrafl** - The full multiplayer crossword game
|
|
11
|
+
- **GataDagsins** - A daily crossword riddle ("Riddle of the Day")
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
written for the [Mithril](https://mithril.js.org) UI framework. The Mithril code
|
|
21
|
-
has been wrapped in a React component. Modifying the component thus requires
|
|
22
|
-
some familiarity with Mithril, as well as React. However, Mithril is fairly
|
|
23
|
-
straightforward and arguably simpler than React, so this should not be a major
|
|
24
|
-
obstacle.
|
|
13
|
+
Both components are responsive and mobile-friendly.
|
|
25
14
|
|
|
26
15
|
## Installation
|
|
27
16
|
|
|
28
|
-
|
|
17
|
+
```bash
|
|
18
|
+
npm install @mideind/netskrafl-react
|
|
19
|
+
```
|
|
29
20
|
|
|
21
|
+
Requires React 18+. This package is distributed as ESM only.
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
Import the component and its styles:
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { Netskrafl } from '@mideind/netskrafl-react';
|
|
29
|
+
import '@mideind/netskrafl-react/style.css';
|
|
30
|
+
|
|
31
|
+
function App() {
|
|
32
|
+
return (
|
|
33
|
+
<Netskrafl
|
|
34
|
+
state={{
|
|
35
|
+
userEmail: 'user@example.com',
|
|
36
|
+
userFullName: 'Example User',
|
|
37
|
+
userId: 'unique-user-id',
|
|
38
|
+
firebaseToken: 'firebase-auth-token',
|
|
39
|
+
subscriber: true,
|
|
40
|
+
locale: 'is',
|
|
41
|
+
urlPrefix: 'https://netskrafl.is',
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
30
46
|
```
|
|
31
|
-
|
|
47
|
+
|
|
48
|
+
For Gáta Dagsins:
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { GataDagsins } from '@mideind/netskrafl-react';
|
|
52
|
+
import '@mideind/netskrafl-react/style.css';
|
|
53
|
+
|
|
54
|
+
function App() {
|
|
55
|
+
return (
|
|
56
|
+
<GataDagsins
|
|
57
|
+
state={{
|
|
58
|
+
userEmail: 'user@example.com',
|
|
59
|
+
userFullName: 'Example User',
|
|
60
|
+
userId: 'unique-user-id',
|
|
61
|
+
firebaseToken: 'firebase-auth-token',
|
|
62
|
+
subscriber: true,
|
|
63
|
+
locale: 'is',
|
|
64
|
+
urlPrefix: 'https://netskrafl.is',
|
|
65
|
+
}}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
32
69
|
```
|
|
33
70
|
|
|
34
|
-
|
|
71
|
+
### Props
|
|
35
72
|
|
|
36
|
-
|
|
73
|
+
| Prop | Type | Description |
|
|
74
|
+
|------|------|-------------|
|
|
75
|
+
| `userEmail` | string | User's email address |
|
|
76
|
+
| `userFullName` | string | User's display name |
|
|
77
|
+
| `userId` | string | Unique user identifier |
|
|
78
|
+
| `firebaseToken` | string | Firebase authentication token for real-time updates |
|
|
79
|
+
| `subscriber` | boolean | Whether user has full access (subscriber) or limited/free access |
|
|
80
|
+
| `locale` | string | UI language (`'is'` for Icelandic, `'en'` for English) |
|
|
81
|
+
| `urlPrefix` | string | Base URL of the Netskrafl backend server |
|
|
37
82
|
|
|
38
|
-
|
|
39
|
-
npm run watch
|
|
40
|
-
```
|
|
83
|
+
## Features
|
|
41
84
|
|
|
42
|
-
|
|
85
|
+
- Drag-and-drop tile placement
|
|
86
|
+
- Click-to-select tile placement
|
|
87
|
+
- Keyboard tile placement (type letters to place tiles)
|
|
88
|
+
- Real-time game updates via Firebase
|
|
89
|
+
- Mobile-responsive design with touch support
|
|
90
|
+
- Pinch-to-zoom on mobile devices
|
|
43
91
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
92
|
+
## Architecture
|
|
93
|
+
|
|
94
|
+
The components are written in TypeScript. They assume that the containing
|
|
95
|
+
application handles user authentication and subscription status.
|
|
47
96
|
|
|
48
|
-
|
|
97
|
+
The code uses a hybrid React-Mithril architecture: the components are React
|
|
98
|
+
wrappers around [Mithril](https://mithril.js.org) UI code. Modifying the
|
|
99
|
+
components requires familiarity with both frameworks, though Mithril is
|
|
100
|
+
straightforward and arguably simpler than React.
|
|
101
|
+
|
|
102
|
+
The components communicate with the Netskrafl backend server via a JSON API
|
|
103
|
+
over HTTPS, and use Firebase for real-time updates.
|
|
104
|
+
|
|
105
|
+
## Backend
|
|
106
|
+
|
|
107
|
+
The Netskrafl backend server code is available at
|
|
108
|
+
[github.com/mideind/Netskrafl](https://github.com/mideind/Netskrafl).
|
|
109
|
+
The backend is written in Python using the Flask web framework.
|
|
110
|
+
|
|
111
|
+
## Development
|
|
49
112
|
|
|
50
113
|
```bash
|
|
114
|
+
# Install dependencies
|
|
115
|
+
npm install
|
|
116
|
+
|
|
117
|
+
# Run rollup in watch mode
|
|
118
|
+
npm run watch
|
|
119
|
+
|
|
120
|
+
# Run Storybook for development/testing
|
|
121
|
+
npm run storybook
|
|
122
|
+
|
|
123
|
+
# Build for production
|
|
51
124
|
npm run rollup
|
|
52
|
-
```
|
|
53
125
|
|
|
54
|
-
|
|
126
|
+
# Type-check
|
|
127
|
+
npx tsc --noEmit
|
|
128
|
+
|
|
129
|
+
# Lint
|
|
130
|
+
npx @biomejs/biome check src/
|
|
131
|
+
```
|
|
55
132
|
|
|
56
|
-
## License
|
|
133
|
+
## License
|
|
57
134
|
|
|
58
|
-
Copyright © 2025 Miðeind ehf.
|
|
59
|
-
is *Vilhjálmur Þorsteinsson*.
|
|
135
|
+
Copyright © 2025 Miðeind ehf. Original author: Vilhjálmur Þorsteinsson.
|
|
60
136
|
|
|
61
|
-
|
|
62
|
-
[CC-BY-NC 4.0 license](https://creativecommons.org/licenses/by-nc/4.0/).
|
|
137
|
+
Released under the [CC-BY-NC 4.0 license](https://creativecommons.org/licenses/by-nc/4.0/).
|
|
63
138
|
See the LICENSE file for details.
|
|
@@ -3206,22 +3206,27 @@ div.netskrafl-racktile {
|
|
|
3206
3206
|
0% {
|
|
3207
3207
|
color: white;
|
|
3208
3208
|
background-color: var(--cancel-button);
|
|
3209
|
+
border-color: var(--cancel-button);
|
|
3209
3210
|
}
|
|
3210
3211
|
45% {
|
|
3211
3212
|
color: white;
|
|
3212
3213
|
background-color: var(--cancel-button);
|
|
3214
|
+
border-color: var(--cancel-button);
|
|
3213
3215
|
}
|
|
3214
3216
|
50% {
|
|
3215
3217
|
color: black;
|
|
3216
3218
|
background-color: white;
|
|
3219
|
+
border-color: white;
|
|
3217
3220
|
}
|
|
3218
3221
|
95% {
|
|
3219
3222
|
color: black;
|
|
3220
3223
|
background-color: white;
|
|
3224
|
+
border-color: white;
|
|
3221
3225
|
}
|
|
3222
3226
|
100% {
|
|
3223
3227
|
color: white;
|
|
3224
3228
|
background-color: var(--cancel-button);
|
|
3229
|
+
border-color: var(--cancel-button);
|
|
3225
3230
|
}
|
|
3226
3231
|
}
|
|
3227
3232
|
|
|
@@ -3229,22 +3234,27 @@ div.netskrafl-racktile {
|
|
|
3229
3234
|
0% {
|
|
3230
3235
|
color: white;
|
|
3231
3236
|
background-color: var(--cancel-button);
|
|
3237
|
+
border-color: var(--cancel-button);
|
|
3232
3238
|
}
|
|
3233
3239
|
45% {
|
|
3234
3240
|
color: white;
|
|
3235
3241
|
background-color: var(--cancel-button);
|
|
3242
|
+
border-color: var(--cancel-button);
|
|
3236
3243
|
}
|
|
3237
3244
|
50% {
|
|
3238
3245
|
color: black;
|
|
3239
3246
|
background-color: white;
|
|
3247
|
+
border-color: white;
|
|
3240
3248
|
}
|
|
3241
3249
|
95% {
|
|
3242
3250
|
color: black;
|
|
3243
3251
|
background-color: white;
|
|
3252
|
+
border-color: white;
|
|
3244
3253
|
}
|
|
3245
3254
|
100% {
|
|
3246
3255
|
color: white;
|
|
3247
3256
|
background-color: var(--cancel-button);
|
|
3257
|
+
border-color: var(--cancel-button);
|
|
3248
3258
|
}
|
|
3249
3259
|
}
|
|
3250
3260
|
|
|
@@ -3252,22 +3262,27 @@ div.netskrafl-racktile {
|
|
|
3252
3262
|
0% {
|
|
3253
3263
|
color: white;
|
|
3254
3264
|
background-color: var(--cancel-button);
|
|
3265
|
+
border-color: var(--cancel-button);
|
|
3255
3266
|
}
|
|
3256
3267
|
45% {
|
|
3257
3268
|
color: white;
|
|
3258
3269
|
background-color: var(--cancel-button);
|
|
3270
|
+
border-color: var(--cancel-button);
|
|
3259
3271
|
}
|
|
3260
3272
|
50% {
|
|
3261
3273
|
color: var(--blank-tile);
|
|
3262
3274
|
background-color: white;
|
|
3275
|
+
border-color: white;
|
|
3263
3276
|
}
|
|
3264
3277
|
95% {
|
|
3265
3278
|
color: var(--blank-tile);
|
|
3266
3279
|
background-color: white;
|
|
3280
|
+
border-color: white;
|
|
3267
3281
|
}
|
|
3268
3282
|
100% {
|
|
3269
3283
|
color: white;
|
|
3270
3284
|
background-color: var(--cancel-button);
|
|
3285
|
+
border-color: var(--cancel-button);
|
|
3271
3286
|
}
|
|
3272
3287
|
}
|
|
3273
3288
|
|
|
@@ -3275,22 +3290,65 @@ div.netskrafl-racktile {
|
|
|
3275
3290
|
0% {
|
|
3276
3291
|
color: white;
|
|
3277
3292
|
background-color: var(--cancel-button);
|
|
3293
|
+
border-color: var(--cancel-button);
|
|
3278
3294
|
}
|
|
3279
3295
|
45% {
|
|
3280
3296
|
color: white;
|
|
3281
3297
|
background-color: var(--cancel-button);
|
|
3298
|
+
border-color: var(--cancel-button);
|
|
3282
3299
|
}
|
|
3283
3300
|
50% {
|
|
3284
3301
|
color: var(--blank-tile);
|
|
3285
3302
|
background-color: white;
|
|
3303
|
+
border-color: white;
|
|
3286
3304
|
}
|
|
3287
3305
|
95% {
|
|
3288
3306
|
color: var(--blank-tile);
|
|
3289
3307
|
background-color: white;
|
|
3308
|
+
border-color: white;
|
|
3290
3309
|
}
|
|
3291
3310
|
100% {
|
|
3292
3311
|
color: white;
|
|
3293
3312
|
background-color: var(--cancel-button);
|
|
3313
|
+
border-color: var(--cancel-button);
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
/* Text-only animation for tiles (background handled by parent td) */
|
|
3318
|
+
|
|
3319
|
+
@keyframes selBlinkText {
|
|
3320
|
+
0% {
|
|
3321
|
+
color: white;
|
|
3322
|
+
}
|
|
3323
|
+
45% {
|
|
3324
|
+
color: white;
|
|
3325
|
+
}
|
|
3326
|
+
50% {
|
|
3327
|
+
color: black;
|
|
3328
|
+
}
|
|
3329
|
+
95% {
|
|
3330
|
+
color: black;
|
|
3331
|
+
}
|
|
3332
|
+
100% {
|
|
3333
|
+
color: white;
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
@keyframes selBlankText {
|
|
3338
|
+
0% {
|
|
3339
|
+
color: white;
|
|
3340
|
+
}
|
|
3341
|
+
45% {
|
|
3342
|
+
color: white;
|
|
3343
|
+
}
|
|
3344
|
+
50% {
|
|
3345
|
+
color: var(--blank-tile);
|
|
3346
|
+
}
|
|
3347
|
+
95% {
|
|
3348
|
+
color: var(--blank-tile);
|
|
3349
|
+
}
|
|
3350
|
+
100% {
|
|
3351
|
+
color: white;
|
|
3294
3352
|
}
|
|
3295
3353
|
}
|
|
3296
3354
|
|
|
@@ -3367,19 +3425,83 @@ div.netskrafl-racktile {
|
|
|
3367
3425
|
}
|
|
3368
3426
|
|
|
3369
3427
|
.netskrafl-container div.sel {
|
|
3428
|
+
background-color: transparent;
|
|
3429
|
+
color: inherit;
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
div.netskrafl-blanktile.sel {
|
|
3433
|
+
background-color: transparent;
|
|
3434
|
+
color: inherit;
|
|
3435
|
+
}
|
|
3436
|
+
|
|
3437
|
+
/* Make the parent td also blink when it contains a selected tile */
|
|
3438
|
+
|
|
3439
|
+
.netskrafl-container div.rack td:has(> div.sel),
|
|
3440
|
+
.netskrafl-container div.board td:has(> div.sel),
|
|
3441
|
+
.netskrafl-container table.board td:has(> div.sel) {
|
|
3370
3442
|
animation: selBlink 1.2s infinite;
|
|
3371
3443
|
-webkit-animation: selBlink 1.2s infinite;
|
|
3372
3444
|
}
|
|
3373
3445
|
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3446
|
+
/* Make the parent td match the background of an exchange-selected tile */
|
|
3447
|
+
|
|
3448
|
+
div.rack td:has(> div.netskrafl-xchgsel) {
|
|
3449
|
+
background-color: var(--human-color);
|
|
3450
|
+
border-color: var(--human-color);
|
|
3377
3451
|
}
|
|
3378
3452
|
|
|
3379
3453
|
.netskrafl-container td.sel {
|
|
3380
3454
|
outline: var(--cancel-button) solid 3px;
|
|
3381
3455
|
}
|
|
3382
3456
|
|
|
3457
|
+
/* Container focus styling - remove default outline */
|
|
3458
|
+
|
|
3459
|
+
.netskrafl-container:focus {
|
|
3460
|
+
outline: none;
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
/* Keyboard tile placement target indicator */
|
|
3464
|
+
|
|
3465
|
+
.netskrafl-container td.keyboard-target {
|
|
3466
|
+
position: relative;
|
|
3467
|
+
background-color: rgba(102, 178, 102, 0.4); /* Light green highlight */
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3470
|
+
.netskrafl-container td.keyboard-target::after {
|
|
3471
|
+
font-family: "Glyphicons Regular";
|
|
3472
|
+
content: "\E212"; /* right-arrow */
|
|
3473
|
+
position: absolute;
|
|
3474
|
+
top: calc(50% + 1px);
|
|
3475
|
+
left: 50%;
|
|
3476
|
+
transform: translate(-50%, -50%);
|
|
3477
|
+
font-size: 15px;
|
|
3478
|
+
color: var(--cancel-button);
|
|
3479
|
+
opacity: 0.8;
|
|
3480
|
+
animation: pulse 1.5s infinite;
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
.netskrafl-container td.keyboard-target.vertical::after {
|
|
3484
|
+
content: "\E213"; /* down-arrow */
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
@keyframes pulse {
|
|
3488
|
+
0% {
|
|
3489
|
+
opacity: 0.4;
|
|
3490
|
+
}
|
|
3491
|
+
25% {
|
|
3492
|
+
opacity: 0.8;
|
|
3493
|
+
}
|
|
3494
|
+
50% {
|
|
3495
|
+
opacity: 1;
|
|
3496
|
+
}
|
|
3497
|
+
75% {
|
|
3498
|
+
opacity: 0.8;
|
|
3499
|
+
}
|
|
3500
|
+
100% {
|
|
3501
|
+
opacity: 0.4;
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3383
3505
|
.netskrafl-container div.xchg {
|
|
3384
3506
|
cursor: pointer;
|
|
3385
3507
|
}
|
|
@@ -7585,6 +7707,12 @@ div.netskrafl-container input[type="checkbox"] {
|
|
|
7585
7707
|
overflow-y: hidden;
|
|
7586
7708
|
}
|
|
7587
7709
|
|
|
7710
|
+
/* Container focus styling - remove default outline */
|
|
7711
|
+
|
|
7712
|
+
.netskrafl-container:focus {
|
|
7713
|
+
outline: none;
|
|
7714
|
+
}
|
|
7715
|
+
|
|
7588
7716
|
.netskrafl-container div.gatadagsins-main {
|
|
7589
7717
|
display: flex;
|
|
7590
7718
|
position: relative;
|