@mideind/netskrafl-react 2.1.0 → 2.2.0
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 +50 -26
- package/dist/esm/index.js +313 -38546
- package/dist/esm/index.js.map +1 -1
- package/package.json +6 -4
- package/dist/cjs/css/netskrafl.css +0 -9383
- package/dist/cjs/index.js +0 -41264
- 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.
|
|
@@ -319,9 +319,6 @@
|
|
|
319
319
|
|
|
320
320
|
*/
|
|
321
321
|
|
|
322
|
-
/*
|
|
323
|
-
*/
|
|
324
|
-
|
|
325
322
|
div.netskrafl-container {
|
|
326
323
|
--logo-primary: #c94314;
|
|
327
324
|
--logo-primary-light: hsl(from var(--logo-primary) h s 75%);
|
|
@@ -507,7 +504,6 @@ div.netskrafl-container * {
|
|
|
507
504
|
|
|
508
505
|
.netskrafl-container .ui-tabs {
|
|
509
506
|
position: relative;
|
|
510
|
-
/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
|
|
511
507
|
padding: 3px;
|
|
512
508
|
border-width: 0;
|
|
513
509
|
}
|
|
@@ -711,11 +707,11 @@ div.netskrafl-container * {
|
|
|
711
707
|
text-transform: uppercase;
|
|
712
708
|
}
|
|
713
709
|
|
|
714
|
-
@media all and (max-width:
|
|
715
|
-
/*
|
|
710
|
+
@media all and (max-width: 374px) and (orientation: portrait) {
|
|
711
|
+
/* Scale down for phones narrower than the 375px design width */
|
|
716
712
|
div.netskrafl-container {
|
|
717
713
|
transform: scale(0.96, 1);
|
|
718
|
-
/* 0.96 = 360/375 */
|
|
714
|
+
/* 0.96 = 360/375, scales 375px design to fit 360px viewport */
|
|
719
715
|
transform-origin: left top;
|
|
720
716
|
}
|
|
721
717
|
}
|
|
@@ -6083,13 +6079,14 @@ div.netskrafl-container input[type="checkbox"] {
|
|
|
6083
6079
|
|
|
6084
6080
|
/* ================= MOBILE LANDSCAPE UI ====================== */
|
|
6085
6081
|
|
|
6086
|
-
@media all and (min-width: 667px) {
|
|
6087
|
-
/* Mobile screen, landscape mode */
|
|
6082
|
+
@media all and (min-width: 667px) and (orientation: landscape) {
|
|
6083
|
+
/* Mobile screen, landscape mode (width >= 667px AND landscape orientation) */
|
|
6088
6084
|
div.netskrafl-container {
|
|
6089
6085
|
/* Reference width */
|
|
6090
6086
|
width: 667px;
|
|
6091
6087
|
/* Reference height */
|
|
6092
|
-
height:
|
|
6088
|
+
height: calc(100dvh - var(--header-size, 3rem));
|
|
6089
|
+
max-height: 686px;
|
|
6093
6090
|
overflow-y: hidden;
|
|
6094
6091
|
}
|
|
6095
6092
|
.netskrafl-container div.tabbed-page {
|
|
@@ -6311,14 +6308,15 @@ div.netskrafl-container input[type="checkbox"] {
|
|
|
6311
6308
|
}
|
|
6312
6309
|
}
|
|
6313
6310
|
|
|
6314
|
-
@media all and (min-width: 667px) and (max-height: 360px) {
|
|
6311
|
+
@media all and (min-width: 667px) and (orientation: landscape) and (max-height: 360px) {
|
|
6312
|
+
/* Very short landscape screens - scale board down slightly */
|
|
6315
6313
|
.netskrafl-container div.board {
|
|
6316
6314
|
transform: scale(0.96);
|
|
6317
6315
|
transform-origin: top left;
|
|
6318
6316
|
}
|
|
6319
6317
|
}
|
|
6320
6318
|
|
|
6321
|
-
/*
|
|
6319
|
+
/* Large screen (iPad and larger) */
|
|
6322
6320
|
|
|
6323
6321
|
@media all and (min-width: 1024px) {
|
|
6324
6322
|
.netskrafl-container .ui-helper-reset {
|
|
@@ -6370,19 +6368,16 @@ div.netskrafl-container input[type="checkbox"] {
|
|
|
6370
6368
|
padding-bottom: 1px;
|
|
6371
6369
|
background-color: var(--tab-background);
|
|
6372
6370
|
}
|
|
6373
|
-
/* Large screen (iPad and larger) */
|
|
6374
6371
|
div.netskrafl-container {
|
|
6375
6372
|
width: 1024px;
|
|
6376
|
-
height:
|
|
6377
|
-
/* Was 748 */
|
|
6373
|
+
height: 686px;
|
|
6378
6374
|
overflow-y: hidden;
|
|
6379
6375
|
}
|
|
6380
6376
|
.netskrafl-container div.game-container {
|
|
6381
|
-
height:
|
|
6377
|
+
height: 100%;
|
|
6382
6378
|
}
|
|
6383
6379
|
.netskrafl-container div.heading {
|
|
6384
6380
|
height: auto;
|
|
6385
|
-
/* background-color: @heading-background; */
|
|
6386
6381
|
}
|
|
6387
6382
|
.netskrafl-container div.logo {
|
|
6388
6383
|
display: block;
|
|
@@ -6410,7 +6405,7 @@ div.netskrafl-container input[type="checkbox"] {
|
|
|
6410
6405
|
.netskrafl-container div.info {
|
|
6411
6406
|
display: block;
|
|
6412
6407
|
position: absolute;
|
|
6413
|
-
bottom:
|
|
6408
|
+
bottom: 52px;
|
|
6414
6409
|
top: auto;
|
|
6415
6410
|
left: 28px;
|
|
6416
6411
|
width: 50px;
|
|
@@ -6482,7 +6477,7 @@ div.netskrafl-container input[type="checkbox"] {
|
|
|
6482
6477
|
.netskrafl-container div.board-area {
|
|
6483
6478
|
margin: 0;
|
|
6484
6479
|
/* width should not be set here */
|
|
6485
|
-
height:
|
|
6480
|
+
height: 746px;
|
|
6486
6481
|
}
|
|
6487
6482
|
.netskrafl-container div.board {
|
|
6488
6483
|
position: absolute;
|
|
@@ -7607,7 +7602,6 @@ div.netskrafl-container input[type="checkbox"] {
|
|
|
7607
7602
|
width: 428px;
|
|
7608
7603
|
height: 292px;
|
|
7609
7604
|
padding-top: 12px;
|
|
7610
|
-
/* Override */
|
|
7611
7605
|
}
|
|
7612
7606
|
.netskrafl-container span.left-to-move,
|
|
7613
7607
|
.netskrafl-container span.right-to-move {
|
|
@@ -7993,6 +7987,20 @@ div.gatadagsins-board-area.celebrate div.netskrafl-tile.netskrafl-racktile {
|
|
|
7993
7987
|
color: #333;
|
|
7994
7988
|
}
|
|
7995
7989
|
|
|
7990
|
+
.netskrafl-container .date-navigator .solver-count-pill {
|
|
7991
|
+
background-color: var(--malfridur-green);
|
|
7992
|
+
color: white;
|
|
7993
|
+
font-family: var(--number-font);
|
|
7994
|
+
font-size: 11px;
|
|
7995
|
+
font-weight: bold;
|
|
7996
|
+
padding: 2px 8px;
|
|
7997
|
+
border-radius: 10px;
|
|
7998
|
+
margin-left: 10px;
|
|
7999
|
+
min-width: 20px;
|
|
8000
|
+
text-align: center;
|
|
8001
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
|
8002
|
+
}
|
|
8003
|
+
|
|
7996
8004
|
.netskrafl-container .mobile-date-nav-container {
|
|
7997
8005
|
display: flex;
|
|
7998
8006
|
justify-content: center;
|
|
@@ -8004,10 +8012,15 @@ div.gatadagsins-board-area.celebrate div.netskrafl-tile.netskrafl-racktile {
|
|
|
8004
8012
|
padding: 0;
|
|
8005
8013
|
width: 100%;
|
|
8006
8014
|
justify-content: center;
|
|
8007
|
-
gap: 20px;
|
|
8008
8015
|
margin-bottom: 0;
|
|
8009
8016
|
}
|
|
8010
8017
|
|
|
8018
|
+
/* Add spacing between date navigator children (replacing gap) */
|
|
8019
|
+
|
|
8020
|
+
.netskrafl-container .mobile-date-nav-container .date-navigator > * + * {
|
|
8021
|
+
margin-left: 16px;
|
|
8022
|
+
}
|
|
8023
|
+
|
|
8011
8024
|
/* Mobile status bar - visible on mobile only */
|
|
8012
8025
|
|
|
8013
8026
|
.netskrafl-container div.gatadagsins-mobile-status {
|
|
@@ -9227,7 +9240,17 @@ div.gatadagsins-board-area.celebrate div.netskrafl-tile.netskrafl-racktile {
|
|
|
9227
9240
|
}
|
|
9228
9241
|
|
|
9229
9242
|
.netskrafl-container div.gatadagsins-container {
|
|
9230
|
-
height
|
|
9243
|
+
/* Dynamic viewport height minus header, adapts to browser chrome */
|
|
9244
|
+
height: calc(100dvh - var(--header-size, 3rem));
|
|
9245
|
+
max-height: 686px; /* Cap on large screens */
|
|
9246
|
+
}
|
|
9247
|
+
|
|
9248
|
+
.netskrafl-container div.gatadagsins-board-rack-wrapper {
|
|
9249
|
+
justify-content: flex-start; /* Align to the top of the screen */
|
|
9250
|
+
}
|
|
9251
|
+
|
|
9252
|
+
.netskrafl-container div.gatadagsins-board-area {
|
|
9253
|
+
flex: 0 0 auto; /* Don't expand - just fit the board */
|
|
9231
9254
|
}
|
|
9232
9255
|
|
|
9233
9256
|
.netskrafl-container div.gatadagsins-container div.board {
|
|
@@ -9256,7 +9279,7 @@ div.gatadagsins-board-area.celebrate div.netskrafl-tile.netskrafl-racktile {
|
|
|
9256
9279
|
}
|
|
9257
9280
|
|
|
9258
9281
|
.netskrafl-container .desktop-date-nav-container {
|
|
9259
|
-
margin-bottom:
|
|
9282
|
+
margin-bottom: 6px;
|
|
9260
9283
|
}
|
|
9261
9284
|
|
|
9262
9285
|
/* Desktop tab styling adjustments */
|
|
@@ -9319,13 +9342,14 @@ div.gatadagsins-board-area.celebrate div.netskrafl-tile.netskrafl-racktile {
|
|
|
9319
9342
|
flex-direction: column;
|
|
9320
9343
|
order: 0;
|
|
9321
9344
|
margin-top: 0;
|
|
9345
|
+
margin-right: 12px;
|
|
9322
9346
|
padding-top: 8px;
|
|
9323
9347
|
padding-bottom: 24px;
|
|
9324
9348
|
overflow: hidden;
|
|
9325
9349
|
}
|
|
9326
9350
|
|
|
9327
9351
|
.netskrafl-container div.gatadagsins-rack-area {
|
|
9328
|
-
flex: 0 0
|
|
9352
|
+
flex: 0 0 104px;
|
|
9329
9353
|
/* Push left to compensate for row ids
|
|
9330
9354
|
on the left side of the board */
|
|
9331
9355
|
padding-left: 36px;
|
|
@@ -9377,7 +9401,7 @@ div.gatadagsins-board-area.celebrate div.netskrafl-tile.netskrafl-racktile {
|
|
|
9377
9401
|
margin-right: 5px;
|
|
9378
9402
|
}
|
|
9379
9403
|
|
|
9380
|
-
.netskrafl-container div.
|
|
9381
|
-
bottom:
|
|
9404
|
+
.netskrafl-container div.info {
|
|
9405
|
+
bottom: 72px;
|
|
9382
9406
|
}
|
|
9383
9407
|
}
|