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