Compare commits
7 Commits
main
...
a1df95d3d4
| Author | SHA1 | Date | |
|---|---|---|---|
| a1df95d3d4 | |||
| 4a7c088776 | |||
| 6f3ed143e5 | |||
| c5f9da81a9 | |||
| 0decf2324c | |||
| 96999f740c | |||
| 4982e6ed49 |
@@ -1,92 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2018 Google Inc. All Rights Reserved.
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// If the loader is already loaded, just stop.
|
|
||||||
if (!self.define) {
|
|
||||||
let registry = {};
|
|
||||||
|
|
||||||
// Used for `eval` and `importScripts` where we can't get script URL by other means.
|
|
||||||
// In both cases, it's safe to use a global var because those functions are synchronous.
|
|
||||||
let nextDefineUri;
|
|
||||||
|
|
||||||
const singleRequire = (uri, parentUri) => {
|
|
||||||
uri = new URL(uri + ".js", parentUri).href;
|
|
||||||
return registry[uri] || (
|
|
||||||
|
|
||||||
new Promise(resolve => {
|
|
||||||
if ("document" in self) {
|
|
||||||
const script = document.createElement("script");
|
|
||||||
script.src = uri;
|
|
||||||
script.onload = resolve;
|
|
||||||
document.head.appendChild(script);
|
|
||||||
} else {
|
|
||||||
nextDefineUri = uri;
|
|
||||||
importScripts(uri);
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
.then(() => {
|
|
||||||
let promise = registry[uri];
|
|
||||||
if (!promise) {
|
|
||||||
throw new Error(`Module ${uri} didn’t register its module`);
|
|
||||||
}
|
|
||||||
return promise;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
self.define = (depsNames, factory) => {
|
|
||||||
const uri = nextDefineUri || ("document" in self ? document.currentScript.src : "") || location.href;
|
|
||||||
if (registry[uri]) {
|
|
||||||
// Module is already loading or loaded.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let exports = {};
|
|
||||||
const require = depUri => singleRequire(depUri, uri);
|
|
||||||
const specialDeps = {
|
|
||||||
module: { uri },
|
|
||||||
exports,
|
|
||||||
require
|
|
||||||
};
|
|
||||||
registry[uri] = Promise.all(depsNames.map(
|
|
||||||
depName => specialDeps[depName] || require(depName)
|
|
||||||
)).then(deps => {
|
|
||||||
factory(...deps);
|
|
||||||
return exports;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
define(['./workbox-5a5d9309'], (function (workbox) { 'use strict';
|
|
||||||
|
|
||||||
self.skipWaiting();
|
|
||||||
workbox.clientsClaim();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The precacheAndRoute() method efficiently caches and responds to
|
|
||||||
* requests for URLs in the manifest.
|
|
||||||
* See https://goo.gl/S9QRab
|
|
||||||
*/
|
|
||||||
workbox.precacheAndRoute([{
|
|
||||||
"url": "registerSW.js",
|
|
||||||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
|
||||||
}, {
|
|
||||||
"url": "index.html",
|
|
||||||
"revision": "0.eh0stsihuc8"
|
|
||||||
}], {});
|
|
||||||
workbox.cleanupOutdatedCaches();
|
|
||||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
|
||||||
allowlist: [/^\/$/]
|
|
||||||
}));
|
|
||||||
|
|
||||||
}));
|
|
||||||
2046
package-lock.json
generated
2046
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fireworks-js": "^2.10.8",
|
"fireworks-js": "^2.10.8",
|
||||||
@@ -16,7 +17,10 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
|
"@vue/test-utils": "^2.4.6",
|
||||||
|
"jsdom": "^28.0.0",
|
||||||
"vite": "^5.1.4",
|
"vite": "^5.1.4",
|
||||||
"vite-plugin-pwa": "^0.20.5"
|
"vite-plugin-pwa": "^0.20.5",
|
||||||
|
"vitest": "^4.0.18"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
||||||
import { usePuzzleStore } from '@/stores/puzzle';
|
import { usePuzzleStore } from '@/stores/puzzle';
|
||||||
import { useI18n } from '@/composables/useI18n';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { Gamepad2, Palette, CircleHelp, Sun, Moon, Menu, X, ChevronDown, ChevronUp } from 'lucide-vue-next';
|
import { Gamepad2, Palette, CircleHelp, Sun, Moon, Menu, X, ChevronDown, ChevronUp, Monitor } from 'lucide-vue-next';
|
||||||
|
|
||||||
const store = usePuzzleStore();
|
const store = usePuzzleStore();
|
||||||
const { t, locale, setLocale, locales } = useI18n();
|
const { t, locale, setLocale, locales } = useI18n();
|
||||||
@@ -61,7 +61,59 @@ const langToCountry = {
|
|||||||
gl: 'es-ga',
|
gl: 'es-ga',
|
||||||
cy: 'gb-wls',
|
cy: 'gb-wls',
|
||||||
gd: 'gb-sct',
|
gd: 'gb-sct',
|
||||||
eu: 'es-pv'
|
eu: 'es-pv',
|
||||||
|
af: 'za',
|
||||||
|
am: 'et',
|
||||||
|
hy: 'am',
|
||||||
|
az: 'az',
|
||||||
|
my: 'mm',
|
||||||
|
km: 'kh',
|
||||||
|
ceb: 'ph',
|
||||||
|
fa: 'ir',
|
||||||
|
gu: 'in',
|
||||||
|
ht: 'ht',
|
||||||
|
he: 'il',
|
||||||
|
ig: 'ng',
|
||||||
|
ilo: 'ph',
|
||||||
|
id: 'id',
|
||||||
|
ja: 'jp',
|
||||||
|
jv: 'id',
|
||||||
|
kn: 'in',
|
||||||
|
kk: 'kz',
|
||||||
|
rw: 'rw',
|
||||||
|
rn: 'bi',
|
||||||
|
ko: 'kr',
|
||||||
|
ku: 'tr',
|
||||||
|
ckb: 'iq',
|
||||||
|
ky: 'kg',
|
||||||
|
lo: 'la',
|
||||||
|
ms: 'my',
|
||||||
|
mr: 'in',
|
||||||
|
mn: 'mn',
|
||||||
|
ne: 'np',
|
||||||
|
om: 'et',
|
||||||
|
ps: 'af',
|
||||||
|
pa: 'in',
|
||||||
|
so: 'so',
|
||||||
|
sw: 'tz',
|
||||||
|
tl: 'ph',
|
||||||
|
ta: 'in',
|
||||||
|
te: 'in',
|
||||||
|
th: 'th',
|
||||||
|
bo: 'cn',
|
||||||
|
ti: 'er',
|
||||||
|
uz: 'uz',
|
||||||
|
vi: 'vn',
|
||||||
|
wo: 'sn',
|
||||||
|
yo: 'ng',
|
||||||
|
'pt-br': 'br',
|
||||||
|
'pt-pt': 'pt',
|
||||||
|
'fr-ca': 'ca',
|
||||||
|
'nl-be': 'be',
|
||||||
|
'es-es': 'es',
|
||||||
|
'es-419': 'mx',
|
||||||
|
'zh-hant': 'tw',
|
||||||
|
'zh-hans': 'cn'
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFlagClass = (code) => {
|
const getFlagClass = (code) => {
|
||||||
@@ -210,15 +262,18 @@ watch(isMobileMenuOpen, (val) => {
|
|||||||
<!-- Theme Menu -->
|
<!-- Theme Menu -->
|
||||||
<div class="nav-dropdown">
|
<div class="nav-dropdown">
|
||||||
<button class="btn-neon nav-btn" @click.stop="toggleThemeMenu">
|
<button class="btn-neon nav-btn" @click.stop="toggleThemeMenu">
|
||||||
<Palette :size="18" /> Theme
|
<Palette :size="18" /> {{ t('theme.label') }}
|
||||||
</button>
|
</button>
|
||||||
<transition name="slide-fade">
|
<transition name="slide-fade">
|
||||||
<div v-if="isThemeOpen" class="dropdown-menu theme-menu">
|
<div v-if="isThemeOpen" class="dropdown-menu theme-menu">
|
||||||
|
<button class="dropdown-item" @click="setTheme('system')">
|
||||||
|
<Monitor :size="16" /> {{ t('theme.system') }}
|
||||||
|
</button>
|
||||||
<button class="dropdown-item" @click="setTheme('light')">
|
<button class="dropdown-item" @click="setTheme('light')">
|
||||||
<Sun :size="16" /> Light
|
<Sun :size="16" /> {{ t('theme.light') }}
|
||||||
</button>
|
</button>
|
||||||
<button class="dropdown-item" @click="setTheme('dark')">
|
<button class="dropdown-item" @click="setTheme('dark')">
|
||||||
<Moon :size="16" /> Dark
|
<Moon :size="16" /> {{ t('theme.dark') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
@@ -301,15 +356,18 @@ watch(isMobileMenuOpen, (val) => {
|
|||||||
<!-- Mobile Theme Menu -->
|
<!-- Mobile Theme Menu -->
|
||||||
<div class="mobile-group">
|
<div class="mobile-group">
|
||||||
<button class="mobile-item-trigger" @click="toggleThemeMenu">
|
<button class="mobile-item-trigger" @click="toggleThemeMenu">
|
||||||
<span class="flex-center gap-10"><Palette :size="20" /> Theme</span>
|
<span class="flex-center gap-10"><Palette :size="20" /> {{ t('theme.label') }}</span>
|
||||||
<component :is="isThemeOpen ? ChevronUp : ChevronDown" :size="16" />
|
<component :is="isThemeOpen ? ChevronUp : ChevronDown" :size="16" />
|
||||||
</button>
|
</button>
|
||||||
<div v-if="isThemeOpen" class="mobile-sub-menu">
|
<div v-if="isThemeOpen" class="mobile-sub-menu">
|
||||||
|
<button class="mobile-sub-item" @click="setTheme('system')">
|
||||||
|
<Monitor :size="16" /> {{ t('theme.system') }}
|
||||||
|
</button>
|
||||||
<button class="mobile-sub-item" @click="setTheme('light')">
|
<button class="mobile-sub-item" @click="setTheme('light')">
|
||||||
<Sun :size="16" /> Light
|
<Sun :size="16" /> {{ t('theme.light') }}
|
||||||
</button>
|
</button>
|
||||||
<button class="mobile-sub-item" @click="setTheme('dark')">
|
<button class="mobile-sub-item" @click="setTheme('dark')">
|
||||||
<Moon :size="16" /> Dark
|
<Moon :size="16" /> {{ t('theme.dark') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -121,6 +121,11 @@
|
|||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
overflow-x: hidden;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|||||||
51
src/utils/puzzleUtils.spec.js
Normal file
51
src/utils/puzzleUtils.spec.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { calculateHints } from './puzzleUtils'
|
||||||
|
|
||||||
|
describe('puzzleUtils', () => {
|
||||||
|
it('calculateHints correctly calculates hints for a simple grid', () => {
|
||||||
|
const grid = [
|
||||||
|
[1, 0, 1],
|
||||||
|
[1, 1, 1],
|
||||||
|
[0, 1, 0]
|
||||||
|
]
|
||||||
|
// Row 0: 1, then space, then 1 -> [1, 1]
|
||||||
|
// Row 1: 1, 1, 1 -> [3]
|
||||||
|
// Row 2: space, 1, space -> [1]
|
||||||
|
|
||||||
|
// Col 0: 1, 1, 0 -> [2]
|
||||||
|
// Col 1: 0, 1, 1 -> [2] ? Wait. Col 1 is 0, 1, 1. So space, 1, 1 -> [2].
|
||||||
|
// Let's trace col 1 manually:
|
||||||
|
// r0,c1 = 0
|
||||||
|
// r1,c1 = 1 -> count=1
|
||||||
|
// r2,c1 = 1 -> count=2
|
||||||
|
// end -> push 2.
|
||||||
|
// So Col 1 is [2].
|
||||||
|
|
||||||
|
// Wait, my manual trace above for col 1:
|
||||||
|
// grid[0][1] is 0.
|
||||||
|
// grid[1][1] is 1.
|
||||||
|
// grid[2][1] is 1.
|
||||||
|
// Yes, [2].
|
||||||
|
|
||||||
|
// Col 2: 1, 1, 0 -> [2].
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
rowHints: [[1, 1], [3], [1]],
|
||||||
|
colHints: [[2], [2], [2]]
|
||||||
|
}
|
||||||
|
expect(calculateHints(grid)).toEqual(expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calculateHints handles empty rows/cols', () => {
|
||||||
|
const grid = [
|
||||||
|
[0, 0, 0],
|
||||||
|
[0, 0, 0],
|
||||||
|
[0, 0, 0]
|
||||||
|
]
|
||||||
|
const expected = {
|
||||||
|
rowHints: [[0], [0], [0]],
|
||||||
|
colHints: [[0], [0], [0]]
|
||||||
|
}
|
||||||
|
expect(calculateHints(grid)).toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
16
vitest.config.js
Normal file
16
vitest.config.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { defineConfig } from 'vitest/config'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: 'jsdom',
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user