@mideind/netskrafl-react 1.3.0 → 1.5.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/dist/cjs/css/netskrafl.css +101 -24
- package/dist/cjs/index.js +232 -178
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/css/netskrafl.css +101 -24
- package/dist/esm/index.js +232 -178
- package/dist/esm/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -21,8 +21,8 @@ const saveAuthSettings = (settings) => {
|
|
|
21
21
|
filteredSettings.userId = settings.userId;
|
|
22
22
|
if (settings.userNick !== undefined)
|
|
23
23
|
filteredSettings.userNick = settings.userNick;
|
|
24
|
-
if (settings.
|
|
25
|
-
filteredSettings.
|
|
24
|
+
if (settings.firebaseApiKey !== undefined)
|
|
25
|
+
filteredSettings.firebaseApiKey = settings.firebaseApiKey;
|
|
26
26
|
if (settings.beginner !== undefined)
|
|
27
27
|
filteredSettings.beginner = settings.beginner;
|
|
28
28
|
if (settings.fairPlay !== undefined)
|
|
@@ -88,7 +88,7 @@ const applyPersistedSettings = (state) => {
|
|
|
88
88
|
account: state.account || persisted.account || state.userId, // Use userId as fallback
|
|
89
89
|
userId: state.userId || persisted.userId || state.userId,
|
|
90
90
|
userNick: state.userNick || persisted.userNick || state.userNick,
|
|
91
|
-
|
|
91
|
+
firebaseApiKey: state.firebaseApiKey || persisted.firebaseApiKey || state.firebaseApiKey,
|
|
92
92
|
beginner: (_a = persisted.beginner) !== null && _a !== void 0 ? _a : state.beginner,
|
|
93
93
|
fairPlay: (_b = persisted.fairPlay) !== null && _b !== void 0 ? _b : state.fairPlay,
|
|
94
94
|
ready: (_c = persisted.ready) !== null && _c !== void 0 ? _c : state.ready,
|
|
@@ -98,8 +98,8 @@ const applyPersistedSettings = (state) => {
|
|
|
98
98
|
|
|
99
99
|
const DEFAULT_STATE = {
|
|
100
100
|
projectId: "netskrafl",
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
firebaseApiKey: "",
|
|
102
|
+
databaseUrl: "",
|
|
103
103
|
firebaseSenderId: "",
|
|
104
104
|
firebaseAppId: "",
|
|
105
105
|
measurementId: "",
|
|
@@ -27213,12 +27213,12 @@ let database;
|
|
|
27213
27213
|
let analytics;
|
|
27214
27214
|
function initFirebase(state) {
|
|
27215
27215
|
try {
|
|
27216
|
-
const { projectId,
|
|
27216
|
+
const { projectId, firebaseApiKey, databaseUrl, firebaseSenderId, firebaseAppId, measurementId } = state;
|
|
27217
27217
|
const firebaseOptions = {
|
|
27218
27218
|
projectId,
|
|
27219
|
-
apiKey:
|
|
27219
|
+
apiKey: firebaseApiKey,
|
|
27220
27220
|
authDomain: `${projectId}.firebaseapp.com`,
|
|
27221
|
-
databaseURL,
|
|
27221
|
+
databaseURL: databaseUrl,
|
|
27222
27222
|
storageBucket: `${projectId}.firebasestorage.app`,
|
|
27223
27223
|
messagingSenderId: firebaseSenderId,
|
|
27224
27224
|
appId: firebaseAppId,
|
|
@@ -27649,7 +27649,7 @@ const ensureAuthenticated = async (state) => {
|
|
|
27649
27649
|
// Update the user's nickname
|
|
27650
27650
|
state.userNick = result.nickname || state.userNick;
|
|
27651
27651
|
// Use the server's Firebase API key, if provided
|
|
27652
|
-
state.
|
|
27652
|
+
state.firebaseApiKey = result.firebase_api_key || state.firebaseApiKey;
|
|
27653
27653
|
// Load state flags and preferences
|
|
27654
27654
|
state.beginner = (_b = (_a = result.prefs) === null || _a === void 0 ? void 0 : _a.beginner) !== null && _b !== void 0 ? _b : true;
|
|
27655
27655
|
state.fairPlay = (_d = (_c = result.prefs) === null || _c === void 0 ? void 0 : _c.fairplay) !== null && _d !== void 0 ? _d : false;
|
|
@@ -27660,7 +27660,7 @@ const ensureAuthenticated = async (state) => {
|
|
|
27660
27660
|
userEmail: state.userEmail, // CRITICAL: Include email to validate ownership
|
|
27661
27661
|
userId: state.userId,
|
|
27662
27662
|
userNick: state.userNick,
|
|
27663
|
-
|
|
27663
|
+
firebaseApiKey: state.firebaseApiKey,
|
|
27664
27664
|
beginner: state.beginner,
|
|
27665
27665
|
fairPlay: state.fairPlay,
|
|
27666
27666
|
ready: state.ready,
|
|
@@ -30890,14 +30890,16 @@ const Board = (initialVnode) => {
|
|
|
30890
30890
|
}
|
|
30891
30891
|
return {
|
|
30892
30892
|
view: (vnode) => {
|
|
30893
|
-
const scale = view.boardScale || 1.0;
|
|
30893
|
+
// const scale = view.boardScale || 1.0;
|
|
30894
30894
|
let attrs = {};
|
|
30895
30895
|
// Add handlers for pinch zoom functionality
|
|
30896
30896
|
// Note: resist the temptation to pass zoomIn/zoomOut directly,
|
|
30897
30897
|
// as that would not bind the 'this' pointer correctly
|
|
30898
30898
|
addPinchZoom(attrs, () => view.zoomIn(), () => view.zoomOut());
|
|
30899
|
+
/*
|
|
30899
30900
|
if (scale !== 1.0)
|
|
30900
|
-
|
|
30901
|
+
attrs.style = `transform: scale(${scale})`;
|
|
30902
|
+
*/
|
|
30901
30903
|
return m(".board", { id: "board-parent" }, m("table.board", attrs, m("tbody", allrows())));
|
|
30902
30904
|
}
|
|
30903
30905
|
};
|
|
@@ -31448,7 +31450,6 @@ const Movelist = (initialVnode) => {
|
|
|
31448
31450
|
view,
|
|
31449
31451
|
move,
|
|
31450
31452
|
info: {
|
|
31451
|
-
key: i.toString(),
|
|
31452
31453
|
leftTotal: leftTotal, rightTotal: rightTotal,
|
|
31453
31454
|
player: player, co: co, tiles: tiles, score: score
|
|
31454
31455
|
}
|
|
@@ -31666,12 +31667,12 @@ const RightColumn = (initialVnode) => {
|
|
|
31666
31667
|
*/
|
|
31667
31668
|
const Beginner = (initialVnode) => {
|
|
31668
31669
|
// Show the board color guide
|
|
31669
|
-
const { view } = initialVnode.attrs;
|
|
31670
|
+
const { view, showClose } = initialVnode.attrs;
|
|
31670
31671
|
const { model, actions } = view;
|
|
31671
31672
|
const state = model.state;
|
|
31672
31673
|
return {
|
|
31673
31674
|
view: () => m(".board-help", { title: ts("Hvernig reitirnir margfalda stigin") }, [
|
|
31674
|
-
m(".board-help-close", {
|
|
31675
|
+
showClose ? m(".board-help-close", {
|
|
31675
31676
|
title: ts("Loka þessari hjálp"),
|
|
31676
31677
|
onclick: (ev) => {
|
|
31677
31678
|
// Close the guide and set a preference not to see it again
|
|
@@ -31681,7 +31682,7 @@ const Beginner = (initialVnode) => {
|
|
|
31681
31682
|
}
|
|
31682
31683
|
ev.preventDefault();
|
|
31683
31684
|
}
|
|
31684
|
-
}, glyph("remove")),
|
|
31685
|
+
}, glyph("remove")) : null,
|
|
31685
31686
|
m(".board-colors", [
|
|
31686
31687
|
m(".board-color[id='triple-word']", ["3 x", m("br"), t("orð")]),
|
|
31687
31688
|
m(".board-color[id='double-word']", ["2 x", m("br"), t("orð")]),
|
|
@@ -31737,7 +31738,7 @@ const GameView = {
|
|
|
31737
31738
|
// These elements appear after the game container, since we want
|
|
31738
31739
|
// them to be above it in the z-order
|
|
31739
31740
|
m(LeftLogo),
|
|
31740
|
-
(state === null || state === void 0 ? void 0 : state.beginner) ? m(Beginner, { view }) : "",
|
|
31741
|
+
(state === null || state === void 0 ? void 0 : state.beginner) ? m(Beginner, { view, showClose: true }) : "",
|
|
31741
31742
|
m(Info),
|
|
31742
31743
|
]);
|
|
31743
31744
|
}
|
|
@@ -31757,79 +31758,6 @@ const GameView = {
|
|
|
31757
31758
|
For further information, see https://github.com/mideind/Netskrafl
|
|
31758
31759
|
|
|
31759
31760
|
*/
|
|
31760
|
-
const vwReview = (view) => {
|
|
31761
|
-
// A review of a finished game
|
|
31762
|
-
var _a;
|
|
31763
|
-
const model = view.model;
|
|
31764
|
-
if (!model.game)
|
|
31765
|
-
return undefined;
|
|
31766
|
-
const game = model.game;
|
|
31767
|
-
let moveIndex = (_a = model.reviewMove) !== null && _a !== void 0 ? _a : 0;
|
|
31768
|
-
let bestMoves = model.bestMoves || [];
|
|
31769
|
-
function vwRightColumn() {
|
|
31770
|
-
// A container for the right-side header and area components
|
|
31771
|
-
function vwRightHeading() {
|
|
31772
|
-
// The right-side heading on the game screen
|
|
31773
|
-
const fairplay = game.fairplay;
|
|
31774
|
-
const player = game.player;
|
|
31775
|
-
let sc0 = "";
|
|
31776
|
-
let sc1 = "";
|
|
31777
|
-
if (moveIndex) {
|
|
31778
|
-
let s0 = 0;
|
|
31779
|
-
let s1 = 0;
|
|
31780
|
-
for (let i = 0; i < moveIndex; i++) {
|
|
31781
|
-
// Add up the scores until and including this move
|
|
31782
|
-
let m = game.moves[i];
|
|
31783
|
-
if (i % 2 === 0)
|
|
31784
|
-
s0 += m[1][2];
|
|
31785
|
-
else
|
|
31786
|
-
s1 += m[1][2];
|
|
31787
|
-
}
|
|
31788
|
-
sc0 = s0.toString();
|
|
31789
|
-
sc1 = s1.toString();
|
|
31790
|
-
}
|
|
31791
|
-
return m(".heading", [
|
|
31792
|
-
m(".logowrapper", m(".header-logo", m(m.route.Link, {
|
|
31793
|
-
href: "/page",
|
|
31794
|
-
class: "backlink"
|
|
31795
|
-
}, m(NetskraflLogoOnly)))),
|
|
31796
|
-
m(".playerwrapper", [
|
|
31797
|
-
m(".leftplayer" + (player === 1 ? ".autoplayercolor" : ".humancolor"), [
|
|
31798
|
-
m(".player", m(PlayerName, { view, side: "left" })),
|
|
31799
|
-
m(".scorewrapper", m(".scoreleft", sc0)),
|
|
31800
|
-
]),
|
|
31801
|
-
m(".rightplayer" + (player === 1 ? ".humancolor" : ".autoplayercolor"), [
|
|
31802
|
-
m(".player", m(PlayerName, { view, side: "right" })),
|
|
31803
|
-
m(".scorewrapper", m(".scoreright", sc1)),
|
|
31804
|
-
]),
|
|
31805
|
-
m(".fairplay", { style: { visibility: fairplay ? "visible" : "hidden" } }, m("span.fairplay-btn.large", { title: ts("Skraflað án hjálpartækja") }))
|
|
31806
|
-
])
|
|
31807
|
-
]);
|
|
31808
|
-
}
|
|
31809
|
-
function vwRightArea() {
|
|
31810
|
-
// A container for the list of best possible moves
|
|
31811
|
-
return m(".right-area", vwBestMoves(view, moveIndex, bestMoves));
|
|
31812
|
-
}
|
|
31813
|
-
return m(".rightcol", [vwRightHeading(), vwRightArea()]);
|
|
31814
|
-
}
|
|
31815
|
-
let r = [];
|
|
31816
|
-
if (game) {
|
|
31817
|
-
// Create a list of major elements that we're showing
|
|
31818
|
-
r.push(vwRightColumn());
|
|
31819
|
-
r.push(m(BoardReview, { view, moveIndex }));
|
|
31820
|
-
if (model.reviewMove !== null && moveIndex === 0) {
|
|
31821
|
-
// Only show the stats overlay if moveIndex is 0
|
|
31822
|
-
const n = vwStatsReview(view);
|
|
31823
|
-
n && r.push(n);
|
|
31824
|
-
}
|
|
31825
|
-
}
|
|
31826
|
-
return m("div", // Removing this div messes up Mithril
|
|
31827
|
-
[
|
|
31828
|
-
m(".game-container", r),
|
|
31829
|
-
m(LeftLogo), // Button to go back to main screen
|
|
31830
|
-
m(Info) // Help button
|
|
31831
|
-
]);
|
|
31832
|
-
};
|
|
31833
31761
|
const vwBestMoves = (view, moveIndex, bestMoves) => {
|
|
31834
31762
|
// List of best moves, in a game review
|
|
31835
31763
|
const model = view.model;
|
|
@@ -31884,7 +31812,7 @@ const vwBestMoves = (view, moveIndex, bestMoves) => {
|
|
|
31884
31812
|
// and thus cannot be confused with the above abbreviations)
|
|
31885
31813
|
wrdclass = "othermove";
|
|
31886
31814
|
if (tiles == "--")
|
|
31887
|
-
dispText = ts("Stafaleif:
|
|
31815
|
+
dispText = ts("Stafaleif: engin");
|
|
31888
31816
|
else
|
|
31889
31817
|
dispText = [ts("Stafaleif: "), m("i.upper", tiles)];
|
|
31890
31818
|
}
|
|
@@ -31901,8 +31829,8 @@ const vwBestMoves = (view, moveIndex, bestMoves) => {
|
|
|
31901
31829
|
if (!game || !moveIndex || moveIndex > game.moves.length)
|
|
31902
31830
|
return r;
|
|
31903
31831
|
// Prepend a header that describes the move being reviewed
|
|
31904
|
-
const
|
|
31905
|
-
const [co, tiles, score] =
|
|
31832
|
+
const move = game.moves[moveIndex - 1];
|
|
31833
|
+
const [co, tiles, score] = move[1];
|
|
31906
31834
|
r.push(bestHeader(co, tiles, score));
|
|
31907
31835
|
const mlist = bestMoves;
|
|
31908
31836
|
for (let i = 0; i < mlist.length; i++) {
|
|
@@ -31914,7 +31842,7 @@ const vwBestMoves = (view, moveIndex, bestMoves) => {
|
|
|
31914
31842
|
}
|
|
31915
31843
|
return r;
|
|
31916
31844
|
}
|
|
31917
|
-
return m(".movelist-container",
|
|
31845
|
+
return m(".movelist-container", m(".movelist.bestmoves", bestMoveList()));
|
|
31918
31846
|
};
|
|
31919
31847
|
const vwBestMove = (view, moveIndex, bestMoveIndex, move, info) => {
|
|
31920
31848
|
// Displays a move in a list of best available moves
|
|
@@ -32213,6 +32141,81 @@ const vwButtonsReview = (view, moveIndex) => {
|
|
|
32213
32141
|
r.push(n);
|
|
32214
32142
|
return r;
|
|
32215
32143
|
};
|
|
32144
|
+
const Review = (initialVnode) => {
|
|
32145
|
+
// A review of a finished game
|
|
32146
|
+
const { view } = initialVnode.attrs;
|
|
32147
|
+
function vwRightColumn(game, moveIndex, bestMoves) {
|
|
32148
|
+
// A container for the right-side header and area components
|
|
32149
|
+
function vwRightHeading() {
|
|
32150
|
+
// The right-side heading on the game screen
|
|
32151
|
+
const fairplay = game.fairplay;
|
|
32152
|
+
const player = game.player;
|
|
32153
|
+
let sc0 = "";
|
|
32154
|
+
let sc1 = "";
|
|
32155
|
+
if (moveIndex) {
|
|
32156
|
+
let s0 = 0;
|
|
32157
|
+
let s1 = 0;
|
|
32158
|
+
for (let i = 0; i < moveIndex; i++) {
|
|
32159
|
+
// Add up the scores until and including this move
|
|
32160
|
+
let m = game.moves[i];
|
|
32161
|
+
if (i % 2 === 0)
|
|
32162
|
+
s0 += m[1][2];
|
|
32163
|
+
else
|
|
32164
|
+
s1 += m[1][2];
|
|
32165
|
+
}
|
|
32166
|
+
sc0 = s0.toString();
|
|
32167
|
+
sc1 = s1.toString();
|
|
32168
|
+
}
|
|
32169
|
+
return m(".heading", [
|
|
32170
|
+
m(".logowrapper", m(".header-logo", m(m.route.Link, {
|
|
32171
|
+
href: "/page",
|
|
32172
|
+
class: "backlink"
|
|
32173
|
+
}, m(NetskraflLogoOnly)))),
|
|
32174
|
+
m(".playerwrapper", [
|
|
32175
|
+
m(".leftplayer" + (player === 1 ? ".autoplayercolor" : ".humancolor"), [
|
|
32176
|
+
m(".player", m(PlayerName, { view, side: "left" })),
|
|
32177
|
+
m(".scorewrapper", m(".scoreleft", sc0)),
|
|
32178
|
+
]),
|
|
32179
|
+
m(".rightplayer" + (player === 1 ? ".humancolor" : ".autoplayercolor"), [
|
|
32180
|
+
m(".player", m(PlayerName, { view, side: "right" })),
|
|
32181
|
+
m(".scorewrapper", m(".scoreright", sc1)),
|
|
32182
|
+
]),
|
|
32183
|
+
m(".fairplay", { style: { visibility: fairplay ? "visible" : "hidden" } }, m("span.fairplay-btn.large", { title: ts("Skraflað án hjálpartækja") }))
|
|
32184
|
+
])
|
|
32185
|
+
]);
|
|
32186
|
+
}
|
|
32187
|
+
function vwRightArea() {
|
|
32188
|
+
// A container for the list of best possible moves
|
|
32189
|
+
return m(".right-area", vwBestMoves(view, moveIndex, bestMoves));
|
|
32190
|
+
}
|
|
32191
|
+
return m(".rightcol", [vwRightHeading(), vwRightArea()]);
|
|
32192
|
+
}
|
|
32193
|
+
return {
|
|
32194
|
+
view: () => {
|
|
32195
|
+
var _a;
|
|
32196
|
+
let r = [];
|
|
32197
|
+
const { model } = view;
|
|
32198
|
+
const { game } = model;
|
|
32199
|
+
if (game) {
|
|
32200
|
+
// Create a list of major elements that we're showing
|
|
32201
|
+
const moveIndex = (_a = model.reviewMove) !== null && _a !== void 0 ? _a : 0;
|
|
32202
|
+
const bestMoves = model.bestMoves || [];
|
|
32203
|
+
r.push(vwRightColumn(game, moveIndex, bestMoves));
|
|
32204
|
+
r.push(m(BoardReview, { view, moveIndex }));
|
|
32205
|
+
if (model.reviewMove !== null && moveIndex === 0) {
|
|
32206
|
+
// Only show the stats overlay if moveIndex is 0
|
|
32207
|
+
const n = vwStatsReview(view);
|
|
32208
|
+
n && r.push(n);
|
|
32209
|
+
}
|
|
32210
|
+
}
|
|
32211
|
+
return m("div", [
|
|
32212
|
+
m(".game-container", r),
|
|
32213
|
+
m(LeftLogo), // Button to go back to main screen
|
|
32214
|
+
m(Info) // Help button
|
|
32215
|
+
]);
|
|
32216
|
+
}
|
|
32217
|
+
};
|
|
32218
|
+
};
|
|
32216
32219
|
const BoardReview = {
|
|
32217
32220
|
// The board area within a game review screen
|
|
32218
32221
|
view: (vnode) => {
|
|
@@ -32264,6 +32267,8 @@ class View {
|
|
|
32264
32267
|
this.dialogStack = [];
|
|
32265
32268
|
// The current scaling of the board
|
|
32266
32269
|
this.boardScale = 1.0;
|
|
32270
|
+
// Pending zoom animation timeout (for cancellation across rapid zoom operations)
|
|
32271
|
+
this.zoomAnimationTimeout = null;
|
|
32267
32272
|
this.selectedTab = "movelist";
|
|
32268
32273
|
this.actions = actions;
|
|
32269
32274
|
// Initialize media listeners now that we have the view reference
|
|
@@ -32290,8 +32295,7 @@ class View {
|
|
|
32290
32295
|
views.push(m(GameView, { key: "game", view: this }));
|
|
32291
32296
|
break;
|
|
32292
32297
|
case "review":
|
|
32293
|
-
|
|
32294
|
-
n && views.push(n);
|
|
32298
|
+
views.push(m(Review, { key: "review", view: this }));
|
|
32295
32299
|
break;
|
|
32296
32300
|
case "thanks":
|
|
32297
32301
|
// Display a thank-you dialog on top of the normal main screen
|
|
@@ -32407,13 +32411,17 @@ class View {
|
|
|
32407
32411
|
}
|
|
32408
32412
|
resetScale() {
|
|
32409
32413
|
// Reset the board scale (zoom) to 100% and the scroll origin to (0, 0)
|
|
32414
|
+
// Cancel any pending zoom animation
|
|
32415
|
+
if (this.zoomAnimationTimeout !== null) {
|
|
32416
|
+
clearTimeout(this.zoomAnimationTimeout);
|
|
32417
|
+
this.zoomAnimationTimeout = null;
|
|
32418
|
+
}
|
|
32410
32419
|
this.boardScale = 1.0;
|
|
32411
32420
|
const boardParent = document.getElementById("board-parent");
|
|
32412
32421
|
const board = boardParent === null || boardParent === void 0 ? void 0 : boardParent.children[0];
|
|
32413
32422
|
if (board) {
|
|
32414
32423
|
board.style.transition = 'none';
|
|
32415
|
-
board.style.
|
|
32416
|
-
board.style.transform = `translate(0, 0) scale(1.0)`;
|
|
32424
|
+
board.style.transform = `translate(0px, 0px) scale(1)`;
|
|
32417
32425
|
}
|
|
32418
32426
|
if (boardParent)
|
|
32419
32427
|
boardParent.scrollTo(0, 0);
|
|
@@ -32424,7 +32432,7 @@ class View {
|
|
|
32424
32432
|
// Use either the regular game or the riddle (Gáta Dagsins)
|
|
32425
32433
|
const game = model.game || model.riddle;
|
|
32426
32434
|
// Update the board scale (zoom)
|
|
32427
|
-
|
|
32435
|
+
const scrollIntoView = (sq) => {
|
|
32428
32436
|
// Scroll a square above and to the left of the placed tile into view,
|
|
32429
32437
|
// with a smooth concurrent zoom and pan animation,
|
|
32430
32438
|
// taking clamping into account to ensure that the board always fills
|
|
@@ -32438,9 +32446,7 @@ class View {
|
|
|
32438
32446
|
const row = Math.max(0, vec.row - offset);
|
|
32439
32447
|
const col = Math.max(0, vec.col - offset);
|
|
32440
32448
|
const c = coord(row, col);
|
|
32441
|
-
//
|
|
32442
|
-
board.style.transformOrigin = 'top left';
|
|
32443
|
-
board.style.transform = `translate(0, 0) scale(1.0)`;
|
|
32449
|
+
// board.style.transform = `translate(0, 0) scale(1.0)`;
|
|
32444
32450
|
const el = document.getElementById("sq_" + c);
|
|
32445
32451
|
const elRect = el === null || el === void 0 ? void 0 : el.getBoundingClientRect();
|
|
32446
32452
|
const boardRect = boardParent.getBoundingClientRect();
|
|
@@ -32458,22 +32464,32 @@ class View {
|
|
|
32458
32464
|
// the top left corner of the viewport, or as close as possible
|
|
32459
32465
|
let targetScrollLeft = Math.min(elRect.left - boardRect.left, maxScrollLeft);
|
|
32460
32466
|
let targetScrollTop = Math.min(elRect.top - boardRect.top, maxScrollTop);
|
|
32467
|
+
// Cancel any pending zoom animation
|
|
32468
|
+
if (this.zoomAnimationTimeout !== null) {
|
|
32469
|
+
clearTimeout(this.zoomAnimationTimeout);
|
|
32470
|
+
this.zoomAnimationTimeout = null;
|
|
32471
|
+
}
|
|
32461
32472
|
// Now animate both translate (for pan) and scale (for zoom) concurrently
|
|
32462
|
-
board.style.transition = 'transform 0.3s ease-in-out';
|
|
32463
32473
|
// Note: transforms are applied right to left
|
|
32464
|
-
board.style.transform
|
|
32465
|
-
|
|
32466
|
-
|
|
32467
|
-
board.
|
|
32468
|
-
|
|
32474
|
+
board.style.transition = 'transform 0.3s ease-in-out';
|
|
32475
|
+
const translateValue = `${-targetScrollLeft}px, ${-targetScrollTop}px`;
|
|
32476
|
+
const scaleValue = `${ZOOM_FACTOR}`;
|
|
32477
|
+
board.style.transform = `translate(${translateValue}) scale(${scaleValue})`;
|
|
32478
|
+
// Use setTimeout as primary mechanism (transitionend is unreliable in Firefox/Safari)
|
|
32479
|
+
// 350ms = 300ms animation + 50ms buffer
|
|
32480
|
+
this.zoomAnimationTimeout = setTimeout(() => {
|
|
32481
|
+
this.zoomAnimationTimeout = null;
|
|
32482
|
+
// Reset the transform (remove translate, keep scale)
|
|
32469
32483
|
board.style.transition = 'none';
|
|
32470
|
-
board.style.transform = `translate(
|
|
32471
|
-
//
|
|
32472
|
-
|
|
32473
|
-
|
|
32474
|
-
|
|
32484
|
+
board.style.transform = `translate(0px, 0px) scale(${ZOOM_FACTOR})`;
|
|
32485
|
+
// Wait for the browser to process the style changes before setting scroll position
|
|
32486
|
+
// This is needed for Firefox and Safari to properly update the scroll position
|
|
32487
|
+
requestAnimationFrame(() => {
|
|
32488
|
+
boardParent.scrollTo(targetScrollLeft, targetScrollTop);
|
|
32489
|
+
});
|
|
32490
|
+
}, 350);
|
|
32475
32491
|
}
|
|
32476
|
-
}
|
|
32492
|
+
};
|
|
32477
32493
|
if (!game || ((_a = model.state) === null || _a === void 0 ? void 0 : _a.uiFullscreen) || game.moveInProgress) {
|
|
32478
32494
|
// No game or we're in full screen mode: always 100% scale
|
|
32479
32495
|
// Also, as soon as a move is being processed by the server, we zoom out
|
|
@@ -35857,6 +35873,8 @@ class Actions {
|
|
|
35857
35873
|
if (state && !state.uiFullscreen) {
|
|
35858
35874
|
state.uiFullscreen = true;
|
|
35859
35875
|
if (view) {
|
|
35876
|
+
// Reset zoom when switching to fullscreen (desktop always uses scale 1)
|
|
35877
|
+
view.resetScale();
|
|
35860
35878
|
view.notifyMediaChange();
|
|
35861
35879
|
}
|
|
35862
35880
|
m.redraw();
|
|
@@ -36197,6 +36215,8 @@ class Actions {
|
|
|
36197
36215
|
// Listen to user stats (if user is logged in)
|
|
36198
36216
|
if (state === null || state === void 0 ? void 0 : state.userId) {
|
|
36199
36217
|
attachFirebaseListener(`gatadagsins/users/${locale}/${state.userId}/stats`, (json, firstAttach) => this.onUserStatsUpdate(json, firstAttach));
|
|
36218
|
+
// Listen to personal best move (entire achievement object)
|
|
36219
|
+
attachFirebaseListener(basePath + `achievements/${state.userId}`, (json, firstAttach) => this.onPersonalBestScoreUpdate(json, firstAttach));
|
|
36200
36220
|
}
|
|
36201
36221
|
}
|
|
36202
36222
|
detachListenerFromRiddle(date, locale) {
|
|
@@ -36209,6 +36229,7 @@ class Actions {
|
|
|
36209
36229
|
detachFirebaseListener(basePath + "leaders");
|
|
36210
36230
|
if (state === null || state === void 0 ? void 0 : state.userId) {
|
|
36211
36231
|
detachFirebaseListener(`gatadagsins/users/${locale}/${state.userId}/stats`);
|
|
36232
|
+
detachFirebaseListener(basePath + `achievements/${state.userId}`);
|
|
36212
36233
|
}
|
|
36213
36234
|
}
|
|
36214
36235
|
onRiddleGlobalScoreUpdate(json, firstAttach) {
|
|
@@ -36259,6 +36280,35 @@ class Actions {
|
|
|
36259
36280
|
}
|
|
36260
36281
|
m.redraw();
|
|
36261
36282
|
}
|
|
36283
|
+
onPersonalBestScoreUpdate(json, firstAttach) {
|
|
36284
|
+
const { riddle } = this.model;
|
|
36285
|
+
if (!riddle || !json)
|
|
36286
|
+
return;
|
|
36287
|
+
// Extract the full move from Firebase
|
|
36288
|
+
const score = json.score || 0;
|
|
36289
|
+
const word = json.word || "";
|
|
36290
|
+
const coord = json.coord || "";
|
|
36291
|
+
const timestamp = json.timestamp || new Date().toISOString();
|
|
36292
|
+
// Only proceed if we didn't already have this score or better
|
|
36293
|
+
if (score <= riddle.personalBestScore || !word || !coord)
|
|
36294
|
+
return;
|
|
36295
|
+
riddle.personalBestScore = score;
|
|
36296
|
+
// Check if this move is already in playerMoves
|
|
36297
|
+
// (this is a safety precaution; normally the
|
|
36298
|
+
// move should not be there already)
|
|
36299
|
+
const moveExists = riddle.playerMoves.some(m => m.word === word && m.coord === coord && m.score === score);
|
|
36300
|
+
// If not already present, add it to playerMoves
|
|
36301
|
+
// This enables clicking on it to see it on the board
|
|
36302
|
+
if (!moveExists) {
|
|
36303
|
+
riddle.playerMoves.push({
|
|
36304
|
+
word,
|
|
36305
|
+
score,
|
|
36306
|
+
coord,
|
|
36307
|
+
timestamp
|
|
36308
|
+
});
|
|
36309
|
+
}
|
|
36310
|
+
m.redraw();
|
|
36311
|
+
}
|
|
36262
36312
|
async fetchRiddle(date, locale) {
|
|
36263
36313
|
// Create the game via model
|
|
36264
36314
|
if (!this.model)
|
|
@@ -36610,27 +36660,24 @@ const SunCorona = {
|
|
|
36610
36660
|
const MobileStatus = () => {
|
|
36611
36661
|
return {
|
|
36612
36662
|
view: (vnode) => {
|
|
36613
|
-
const {
|
|
36614
|
-
const {
|
|
36663
|
+
const { view, selectedMoves, bestMove, onMoveClick, onStatsClick } = vnode.attrs;
|
|
36664
|
+
const { riddle } = view.model;
|
|
36665
|
+
if (!riddle)
|
|
36666
|
+
return null;
|
|
36667
|
+
const { bestPossibleScore, globalBestScore, personalBestScore } = riddle;
|
|
36615
36668
|
// Determine if player achieved best possible score
|
|
36616
|
-
const achieved = bestMove !== undefined;
|
|
36617
36669
|
const celebrate = bestMove && bestMove.word !== "";
|
|
36618
|
-
// Get player's current best score
|
|
36619
|
-
// If the player achieved the best possible score, it's in bestMove (not selectedMoves)
|
|
36620
|
-
const playerBestScore = (bestMove && bestMove.word !== "")
|
|
36621
|
-
? bestMove.score
|
|
36622
|
-
: (selectedMoves.length > 0 ? selectedMoves[0].score : 0);
|
|
36623
36670
|
// Determine current leader score (may be this player or another)
|
|
36624
36671
|
let leaderScore = 0;
|
|
36625
36672
|
let isPlayerLeading = false;
|
|
36626
36673
|
if (globalBestScore && globalBestScore.score > 0) {
|
|
36627
36674
|
leaderScore = globalBestScore.score;
|
|
36628
36675
|
// Check if player is leading
|
|
36629
|
-
isPlayerLeading =
|
|
36676
|
+
isPlayerLeading = personalBestScore >= globalBestScore.score;
|
|
36630
36677
|
}
|
|
36631
36678
|
else {
|
|
36632
|
-
leaderScore =
|
|
36633
|
-
isPlayerLeading =
|
|
36679
|
+
leaderScore = personalBestScore;
|
|
36680
|
+
isPlayerLeading = personalBestScore > 0;
|
|
36634
36681
|
}
|
|
36635
36682
|
return m(".mobile-status-container", [
|
|
36636
36683
|
// Current word score (leftmost) - uses RiddleScore component in mobile mode
|
|
@@ -36642,12 +36689,12 @@ const MobileStatus = () => {
|
|
|
36642
36689
|
}, [
|
|
36643
36690
|
// Player's best score
|
|
36644
36691
|
m(".mobile-status-card-item.player-best", [
|
|
36645
|
-
m(".mobile-status-label", ts("Þín besta
|
|
36646
|
-
m(".mobile-status-score",
|
|
36692
|
+
m(".mobile-status-label", ts("Þín besta")),
|
|
36693
|
+
m(".mobile-status-score", personalBestScore.toString())
|
|
36647
36694
|
]),
|
|
36648
36695
|
// Current leader score
|
|
36649
36696
|
m(".mobile-status-card-item.leader" + (isPlayerLeading ? ".is-player" : ""), [
|
|
36650
|
-
m(".mobile-status-label", isPlayerLeading ? ts("Þú leiðir!") : ts("
|
|
36697
|
+
m(".mobile-status-label", isPlayerLeading ? ts("Þú leiðir!") : ts("Best til þessa")),
|
|
36651
36698
|
m(".mobile-status-score", leaderScore.toString())
|
|
36652
36699
|
]),
|
|
36653
36700
|
// Chevron indicator (overlaid at bottom center)
|
|
@@ -36655,8 +36702,7 @@ const MobileStatus = () => {
|
|
|
36655
36702
|
]),
|
|
36656
36703
|
// Best possible score
|
|
36657
36704
|
m(".mobile-status-item.right.best-possible"
|
|
36658
|
-
+ (celebrate ? ".celebrate" : "")
|
|
36659
|
-
+ (achieved ? ".achieved" : ""), {
|
|
36705
|
+
+ (celebrate ? ".celebrate" : ""), {
|
|
36660
36706
|
onclick: () => celebrate && onMoveClick(bestMove.word, bestMove.coord)
|
|
36661
36707
|
}, [
|
|
36662
36708
|
// Wrapper for score and corona to position them together
|
|
@@ -36777,25 +36823,17 @@ const BestPossibleScore = () => {
|
|
|
36777
36823
|
const { score, bestMove, onMoveClick } = vnode.attrs;
|
|
36778
36824
|
// Determine the label based on achievement status
|
|
36779
36825
|
let topLabel;
|
|
36780
|
-
if (bestMove !== undefined) {
|
|
36781
|
-
|
|
36782
|
-
|
|
36783
|
-
topLabel = removeBlankMarkers(bestMove.word);
|
|
36784
|
-
}
|
|
36785
|
-
else {
|
|
36786
|
-
// Someone else achieved it - indicate this
|
|
36787
|
-
topLabel = ts("Bestu lögn náð!");
|
|
36788
|
-
}
|
|
36826
|
+
if (bestMove !== undefined && bestMove.word) {
|
|
36827
|
+
// Current player achieved it - show their word
|
|
36828
|
+
topLabel = removeBlankMarkers(bestMove.word);
|
|
36789
36829
|
}
|
|
36790
36830
|
else {
|
|
36791
36831
|
// Not achieved yet - show default label
|
|
36792
36832
|
topLabel = ts("Besta mögulega lögn");
|
|
36793
36833
|
}
|
|
36794
|
-
const achieved = bestMove !== undefined;
|
|
36795
36834
|
const celebrate = bestMove && bestMove.word !== "";
|
|
36796
36835
|
return m(".thermometer-best-score"
|
|
36797
|
-
+ (celebrate ? ".celebrate" : "")
|
|
36798
|
-
+ (achieved ? ".achieved" : ""), m(".thermometer-best-score-container", {
|
|
36836
|
+
+ (celebrate ? ".celebrate" : ""), m(".thermometer-best-score-container", {
|
|
36799
36837
|
onclick: () => celebrate && onMoveClick(bestMove.word, bestMove.coord)
|
|
36800
36838
|
}, [
|
|
36801
36839
|
// Sun corona behind the circle when celebrating
|
|
@@ -36834,7 +36872,7 @@ const PlayerMovesOverlay = () => {
|
|
|
36834
36872
|
const allMoveElements = [];
|
|
36835
36873
|
function scoreDetails(move) {
|
|
36836
36874
|
if (move.isGlobalBestScore) {
|
|
36837
|
-
if (move.word === ""
|
|
36875
|
+
if (move.word === "") {
|
|
36838
36876
|
// Another player holds the top score
|
|
36839
36877
|
return [m(GlobalBestScore, { thisPlayer: false, score: move.score })];
|
|
36840
36878
|
}
|
|
@@ -37031,7 +37069,10 @@ const LeaderboardView = {
|
|
|
37031
37069
|
m(".leaderboard-header", [
|
|
37032
37070
|
m(".leaderboard-title", formatDate(date)),
|
|
37033
37071
|
]),
|
|
37034
|
-
m(".leaderboard-list",
|
|
37072
|
+
m(".leaderboard-list", {
|
|
37073
|
+
// Allow touch scrolling but prevent events from bubbling to backdrop
|
|
37074
|
+
ontouchmove: (e) => { e.stopPropagation(); }
|
|
37075
|
+
}, leaderboard.map((entry, index) => {
|
|
37035
37076
|
const rank = index + 1;
|
|
37036
37077
|
const isCurrentUser = entry.userId === currentUserId;
|
|
37037
37078
|
const medal = getMedalIcon(rank);
|
|
@@ -37039,7 +37080,9 @@ const LeaderboardView = {
|
|
|
37039
37080
|
m(".entry-rank", [
|
|
37040
37081
|
medal ? m("span.medal", medal) : m("span.rank-number", rank.toString())
|
|
37041
37082
|
]),
|
|
37042
|
-
m(".entry-name", isCurrentUser
|
|
37083
|
+
m(".entry-name", isCurrentUser
|
|
37084
|
+
? [m("span.entry-star", glyph("star")), ts("Þú")]
|
|
37085
|
+
: entry.displayName),
|
|
37043
37086
|
m(".entry-score", entry.score.toString())
|
|
37044
37087
|
]);
|
|
37045
37088
|
}))
|
|
@@ -37140,7 +37183,7 @@ const GataDagsinsRightSide = {
|
|
|
37140
37183
|
view: (vnode) => {
|
|
37141
37184
|
const { view, selectedMoves, bestMove, onStatsClick } = vnode.attrs;
|
|
37142
37185
|
const { riddle } = view.model;
|
|
37143
|
-
const
|
|
37186
|
+
const onMoveClick = (word, coord) => {
|
|
37144
37187
|
if (riddle && word && coord) {
|
|
37145
37188
|
// Recreate the word on the board
|
|
37146
37189
|
riddle.recreateWordOnBoard(word, coord);
|
|
@@ -37149,18 +37192,18 @@ const GataDagsinsRightSide = {
|
|
|
37149
37192
|
return m(".gatadagsins-right-side-wrapper", riddle ? [
|
|
37150
37193
|
// Mobile-only status bar (visible on mobile, hidden on desktop)
|
|
37151
37194
|
m(".gatadagsins-mobile-status", m(MobileStatus, {
|
|
37152
|
-
|
|
37195
|
+
view,
|
|
37153
37196
|
selectedMoves,
|
|
37154
37197
|
bestMove,
|
|
37155
|
-
onMoveClick
|
|
37156
|
-
onStatsClick
|
|
37198
|
+
onMoveClick,
|
|
37199
|
+
onStatsClick,
|
|
37157
37200
|
})),
|
|
37158
37201
|
// Desktop-only tabbed view (hidden on mobile, visible on desktop)
|
|
37159
37202
|
m(".gatadagsins-thermometer-column", m(RightSideTabs, {
|
|
37160
37203
|
view,
|
|
37161
37204
|
selectedMoves,
|
|
37162
37205
|
bestMove,
|
|
37163
|
-
onMoveClick
|
|
37206
|
+
onMoveClick,
|
|
37164
37207
|
})),
|
|
37165
37208
|
] : null);
|
|
37166
37209
|
}
|
|
@@ -37191,14 +37234,13 @@ const GataDagsinsHelp = {
|
|
|
37191
37234
|
ontouchmove: (e) => { e.preventDefault(); e.stopPropagation(); }
|
|
37192
37235
|
}),
|
|
37193
37236
|
m(".modal-dialog.gatadagsins-help", m(".modal-content", [
|
|
37194
|
-
//
|
|
37195
|
-
m(".
|
|
37196
|
-
|
|
37197
|
-
|
|
37198
|
-
|
|
37199
|
-
|
|
37200
|
-
|
|
37201
|
-
]),
|
|
37237
|
+
// Close button (positioned absolutely in top-right corner)
|
|
37238
|
+
m("button.close", {
|
|
37239
|
+
onclick: closeHelp,
|
|
37240
|
+
"aria-label": "Loka"
|
|
37241
|
+
}, glyph("remove")),
|
|
37242
|
+
// Header
|
|
37243
|
+
m(".modal-header", m("h2", "Um Gátu dagsins")),
|
|
37202
37244
|
// Body with help content
|
|
37203
37245
|
m(".modal-body", [
|
|
37204
37246
|
m("p", "Gáta dagsins er dagleg krossgátuþraut, svipuð skrafli, þar sem þú reynir að finna " +
|
|
@@ -37217,8 +37259,20 @@ const GataDagsinsHelp = {
|
|
|
37217
37259
|
m("ul", [
|
|
37218
37260
|
m("li", "Hver stafur gefur 1-10 stig eftir gildi hans"),
|
|
37219
37261
|
m("li", "Orð sem nota allar 7 stafaflísarnar gefa 50 stiga bónus"),
|
|
37220
|
-
m("li",
|
|
37221
|
-
|
|
37262
|
+
m("li", [
|
|
37263
|
+
"Sumir reitir á borðinu ",
|
|
37264
|
+
m("span.help-bonus-square.double-letter"),
|
|
37265
|
+
"tvöfalda eða ",
|
|
37266
|
+
m("span.help-bonus-square.triple-letter"),
|
|
37267
|
+
"þrefalda stafagildið"
|
|
37268
|
+
]),
|
|
37269
|
+
m("li", [
|
|
37270
|
+
"Sumir reitir ",
|
|
37271
|
+
m("span.help-bonus-square.double-word"),
|
|
37272
|
+
"tvöfalda eða ",
|
|
37273
|
+
m("span.help-bonus-square.triple-word"),
|
|
37274
|
+
"þrefalda heildarorðagildið"
|
|
37275
|
+
]),
|
|
37222
37276
|
]),
|
|
37223
37277
|
m("h3", "Hitamælir"),
|
|
37224
37278
|
m("p", "Hitamælirinn hægra megin (eða efst á farsímum) sýnir:"),
|
|
@@ -37294,14 +37348,14 @@ const StatsModal = () => {
|
|
|
37294
37348
|
return [
|
|
37295
37349
|
// Backdrop
|
|
37296
37350
|
m(".modal-backdrop-netskrafl", {
|
|
37297
|
-
onclick: (e) => { e.preventDefault(); },
|
|
37298
|
-
onwheel: (e) => { e.preventDefault(); e.stopPropagation(); },
|
|
37299
|
-
ontouchmove: (e) => { e.preventDefault(); e.stopPropagation(); }
|
|
37351
|
+
onclick: (e) => { e.preventDefault(); return false; },
|
|
37352
|
+
onwheel: (e) => { e.preventDefault(); e.stopPropagation(); return false; },
|
|
37353
|
+
ontouchmove: (e) => { e.preventDefault(); e.stopPropagation(); return false; }
|
|
37300
37354
|
}),
|
|
37301
37355
|
// Modal dialog
|
|
37302
37356
|
m(".modal-dialog.stats-modal", {
|
|
37303
|
-
onwheel: (e) => { e.stopPropagation(); },
|
|
37304
|
-
ontouchmove: (e) => { e.stopPropagation(); }
|
|
37357
|
+
onwheel: (e) => { e.stopPropagation(); return false; },
|
|
37358
|
+
ontouchmove: (e) => { e.stopPropagation(); return false; }
|
|
37305
37359
|
}, [
|
|
37306
37360
|
m(".modal-content", [
|
|
37307
37361
|
// Close button in top right
|
|
@@ -37434,9 +37488,9 @@ const GataDagsins$1 = () => {
|
|
|
37434
37488
|
id: "gatadagsins-background",
|
|
37435
37489
|
}, [
|
|
37436
37490
|
// The main content area
|
|
37437
|
-
|
|
37491
|
+
m(".gatadagsins-container", [
|
|
37438
37492
|
// Main display area with flex layout
|
|
37439
|
-
m(".gatadagsins-main", [
|
|
37493
|
+
riddle ? m(".gatadagsins-main", [
|
|
37440
37494
|
// Board and rack component (left side)
|
|
37441
37495
|
m(GataDagsinsBoardAndRack, { view }),
|
|
37442
37496
|
// Right-side component with scores and comparisons
|
|
@@ -37445,17 +37499,17 @@ const GataDagsins$1 = () => {
|
|
|
37445
37499
|
riddle.askingForBlank
|
|
37446
37500
|
? m(BlankDialog, { game: riddle })
|
|
37447
37501
|
: "",
|
|
37448
|
-
])
|
|
37449
|
-
|
|
37450
|
-
|
|
37451
|
-
|
|
37452
|
-
|
|
37453
|
-
|
|
37454
|
-
|
|
37455
|
-
|
|
37456
|
-
|
|
37457
|
-
|
|
37458
|
-
|
|
37502
|
+
]) : "",
|
|
37503
|
+
// The left margin elements: back button and info/help button
|
|
37504
|
+
// These elements appear after the main container for proper z-order
|
|
37505
|
+
// m(LeftLogo), // Currently no need for the logo for Gáta Dagsins
|
|
37506
|
+
// Show the Beginner component if the user is a beginner
|
|
37507
|
+
((_a = model.state) === null || _a === void 0 ? void 0 : _a.beginner) ? m(Beginner, { view, showClose: false }) : "",
|
|
37508
|
+
// Custom Info button for GataDagsins that shows help dialog
|
|
37509
|
+
m(".info", { title: ts("Upplýsingar og hjálp") }, m("a.iconlink", { href: "#", onclick: (e) => { e.preventDefault(); toggleHelp(); } }, glyph("info-sign"))),
|
|
37510
|
+
// Help dialog and backdrop
|
|
37511
|
+
showHelp ? m(GataDagsinsHelp, { onClose: toggleHelp }) : "",
|
|
37512
|
+
]),
|
|
37459
37513
|
// Stats modal and backdrop (mobile only)
|
|
37460
37514
|
showStatsModal ? m(StatsModal, { view, onClose: toggleStatsModal }) : "",
|
|
37461
37515
|
]);
|