diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..57b08d7e --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +# Environment Variables Template +# Copy this file to .env and fill in your values + +# Clerk Authentication +PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_example_key_here + +# Convex Database +PUBLIC_CONVEX_URL=https://example.convex.cloud diff --git a/package.json b/package.json index 18878808..994adbca 100644 --- a/package.json +++ b/package.json @@ -28,14 +28,14 @@ "bits-ui": "2.8.11", "clsx": "^2.1.1", "embla-carousel-svelte": "^8.6.0", - "layerchart": "2.0.0-next.24", + "layerchart": "2.0.0-next.31", "mode-watcher": "^1.1.0", "paneforge": "1.0.0-next.5", "prettier": "^3.6.2", "prettier-plugin-svelte": "^3.4.0", "prettier-plugin-tailwindcss": "^0.6.14", "svelte": "^5.36.2", - "svelte-check": "^4.2.2", + "svelte-check": "^4.3.0", "svelte-sonner": "^1.0.5", "sveltekit-superforms": "^2.27.1", "tailwind-merge": "^3.3.1", @@ -48,7 +48,7 @@ }, "dependencies": { "@clerk/backend": "^2.4.3", - "@clerk/themes": "^2.3.0", + "@clerk/themes": "^2.3.3", "@friendofsvelte/tipex": "0.0.7", "@lucide/svelte": "^0.525.0", "@tanstack/match-sorter-utils": "^8.19.4", @@ -60,7 +60,7 @@ "jsonwebtoken": "^9.0.2", "md5": "^2.3.0", "posthog-js": "^1.257.0", - "svelte-clerk": "^0.13.4", + "svelte-clerk": "^0.16.0", "svelte-persisted-store": "^0.12.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dcef1a8b..a49c4f4c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^2.4.3 version: 2.4.3(react@19.1.0) '@clerk/themes': - specifier: ^2.3.0 - version: 2.3.0 + specifier: ^2.3.3 + version: 2.3.3 '@friendofsvelte/tipex': specifier: 0.0.7 version: 0.0.7(highlight.js@11.8.0)(svelte@5.36.2) @@ -48,8 +48,8 @@ importers: specifier: ^1.257.0 version: 1.257.0 svelte-clerk: - specifier: ^0.13.4 - version: 0.13.4(@sveltejs/kit@2.24.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.2)(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.2)(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)))(react@19.1.0)(svelte@5.36.2) + specifier: ^0.16.0 + version: 0.16.0(@sveltejs/kit@2.24.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.2)(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.2)(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)))(react@19.1.0)(svelte@5.36.2) svelte-persisted-store: specifier: ^0.12.0 version: 0.12.0(svelte@5.36.2) @@ -97,8 +97,8 @@ importers: specifier: ^8.6.0 version: 8.6.0(svelte@5.36.2) layerchart: - specifier: 2.0.0-next.24 - version: 2.0.0-next.24(svelte@5.36.2) + specifier: 2.0.0-next.31 + version: 2.0.0-next.31(svelte@5.36.2) mode-watcher: specifier: ^1.1.0 version: 1.1.0(svelte@5.36.2) @@ -118,8 +118,8 @@ importers: specifier: ^5.36.2 version: 5.36.2 svelte-check: - specifier: ^4.2.2 - version: 4.2.2(picomatch@4.0.3)(svelte@5.36.2)(typescript@5.8.3) + specifier: ^4.3.0 + version: 4.3.0(picomatch@4.0.3)(svelte@5.36.2)(typescript@5.8.3) svelte-sonner: specifier: ^1.0.5 version: 1.0.5(svelte@5.36.2) @@ -180,14 +180,30 @@ packages: react-dom: optional: true - '@clerk/themes@2.3.0': - resolution: {integrity: sha512-L3a/Gl1EZGCUn9C/nwNudQnqq/iQNx4I2xXq6zKE8W/JX/6GSyjOff90w3h8MPdf2iIrtPZrYPpUgEKWrMgjRA==} + '@clerk/shared@3.13.0': + resolution: {integrity: sha512-M/AIVt/9m7vIlWNHlki2fuS8HO3jnbHy7EDu86VWd6JBaFRbBs2z67nwwo8dtlLDD6l/GdjTPh+a/B7UnY1Qrw==} + engines: {node: '>=18.17.0'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-0 + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-0 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + + '@clerk/themes@2.3.3': + resolution: {integrity: sha512-nXVG9tyE7xhymS45Ee1XakLvT6V4WMOrYisWGwi/wAJqUuHqZufFHWV/jZ6FuJ0AAKImmIBbi8S6Y5UFkM63Ug==} engines: {node: '>=18.17.0'} '@clerk/types@4.66.0': resolution: {integrity: sha512-c2WT6yD0kgcfIbwAmfe83xzDQtBQNOCZWqvI2JkrwSkf82ufRho4kgOqfVS2dHRojQtBAQsasqShocLMu3OGag==} engines: {node: '>=18.17.0'} + '@clerk/types@4.68.0': + resolution: {integrity: sha512-3+PoGGQgyzLZibzYleByPmx6dfPVoFUjLBy+kASHzF9g3o12WchDn1aI01vN7WNm1ik1Z5sbLhzJs8d9bz38EQ==} + engines: {node: '>=18.17.0'} + '@dagrejs/dagre@1.1.5': resolution: {integrity: sha512-Ghgrh08s12DCL5SeiR6AoyE80mQELTWhJBRmXfFoqDiFkR458vPEdgTbbjA0T+9ETNxUblnD0QW55tfdvi5pjQ==} @@ -1477,8 +1493,8 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - layerchart@2.0.0-next.24: - resolution: {integrity: sha512-06UQR1nznVOkAoDoLM0a2vSImFIkmy4ehY4wdv5RSB9mRpQFomXaWHW+aFAmaDTHEXxLBkk2JGYJNfWLTBn8mA==} + layerchart@2.0.0-next.31: + resolution: {integrity: sha512-9TghiKzN7PvGbES1yMyszl381OwrQ7X/P3SX23HC8zu3cbi07bITC48CjN6JBkkHn1ZDM1es79D3YvM9KGLGjg==} peerDependencies: svelte: ^5.0.0 @@ -1953,16 +1969,16 @@ packages: resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} engines: {node: '>=14.0.0'} - svelte-check@4.2.2: - resolution: {integrity: sha512-1+31EOYZ7NKN0YDMKusav2hhEoA51GD9Ws6o//0SphMT0ve9mBTsTUEX7OmDMadUP3KjNHsSKtJrqdSaD8CrGQ==} + svelte-check@4.3.0: + resolution: {integrity: sha512-Iz8dFXzBNAM7XlEIsUjUGQhbEE+Pvv9odb9+0+ITTgFWZBGeJRRYqHUUglwe2EkLD5LIsQaAc4IUJyvtKuOO5w==} engines: {node: '>= 18.0.0'} hasBin: true peerDependencies: svelte: ^4.0.0 || ^5.0.0-next.0 typescript: '>=5.0.0' - svelte-clerk@0.13.4: - resolution: {integrity: sha512-3FH/GF7rojQRGFkBFa4gxlaF9drlqI1nMSqmNRBZ/JXuMzZFiQm9tXV/QBnWtvzxFMt/9x265lzMn4J2ou59Vg==} + svelte-clerk@0.16.0: + resolution: {integrity: sha512-nafpD7e7wqrKSHo8uXgaI5XdeozZmAwkPC7JIubRnPeZ2mJVY4N8R5noiJ0u74I2ZRnnltuHcWqoPVVei/Yohg==} peerDependencies: '@sveltejs/kit': ^2.20.0 svelte: ^5.11.0 @@ -2242,15 +2258,30 @@ snapshots: optionalDependencies: react: 19.1.0 - '@clerk/themes@2.3.0': + '@clerk/shared@3.13.0(react@19.1.0)': dependencies: - '@clerk/types': 4.66.0 + '@clerk/types': 4.68.0 + dequal: 2.0.3 + glob-to-regexp: 0.4.1 + js-cookie: 3.0.5 + std-env: 3.9.0 + swr: 2.3.4(react@19.1.0) + optionalDependencies: + react: 19.1.0 + + '@clerk/themes@2.3.3': + dependencies: + '@clerk/types': 4.68.0 tslib: 2.8.1 '@clerk/types@4.66.0': dependencies: csstype: 3.1.3 + '@clerk/types@4.68.0': + dependencies: + csstype: 3.1.3 + '@dagrejs/dagre@1.1.5': dependencies: '@dagrejs/graphlib': 2.2.4 @@ -3402,7 +3433,7 @@ snapshots: kleur@4.1.5: {} - layerchart@2.0.0-next.24(svelte@5.36.2): + layerchart@2.0.0-next.31(svelte@5.36.2): dependencies: '@dagrejs/dagre': 1.1.5 '@layerstack/svelte-actions': 1.0.1-next.12 @@ -3856,7 +3887,7 @@ snapshots: superstruct@2.0.2: optional: true - svelte-check@4.2.2(picomatch@4.0.3)(svelte@5.36.2)(typescript@5.8.3): + svelte-check@4.3.0(picomatch@4.0.3)(svelte@5.36.2)(typescript@5.8.3): dependencies: '@jridgewell/trace-mapping': 0.3.29 chokidar: 4.0.3 @@ -3868,11 +3899,11 @@ snapshots: transitivePeerDependencies: - picomatch - svelte-clerk@0.13.4(@sveltejs/kit@2.24.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.2)(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.2)(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)))(react@19.1.0)(svelte@5.36.2): + svelte-clerk@0.16.0(@sveltejs/kit@2.24.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.2)(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.2)(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)))(react@19.1.0)(svelte@5.36.2): dependencies: '@clerk/backend': 2.4.3(react@19.1.0) - '@clerk/shared': 3.12.1(react@19.1.0) - '@clerk/types': 4.66.0 + '@clerk/shared': 3.13.0(react@19.1.0) + '@clerk/types': 4.68.0 '@sveltejs/kit': 2.24.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.36.2)(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)))(svelte@5.36.2)(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)) set-cookie-parser: 2.7.1 svelte: 5.36.2 diff --git a/src/lib/analytics.ts b/src/lib/analytics.ts new file mode 100644 index 00000000..41f9e5dc --- /dev/null +++ b/src/lib/analytics.ts @@ -0,0 +1,26 @@ +import posthog from 'posthog-js'; +import { browser } from '$app/environment'; +import { persisted } from 'svelte-persisted-store'; + +export const trackerDialogClosed = persisted('trackerDialogClosed', false); + +export function initializeAnalytics() { + if (!browser) return; + + posthog.init('phc_jg4gOdigfHQD4MSgrSaO883dp2LjNJbJO7azv61UtI0', { + api_host: 'https://us.i.posthog.com', + person_profiles: 'always', + capture_exceptions: true + }); +} + +export async function checkTrackerBlocked(): Promise { + if (!browser) return false; + + try { + await fetch('https://us-assets.i.posthog.com/static/exception-autocapture.js'); + return false; + } catch { + return navigator.onLine; + } +} diff --git a/src/lib/components/app-sidebar.svelte b/src/lib/components/app-sidebar.svelte index 9b0a5d6e..efed59bc 100644 --- a/src/lib/components/app-sidebar.svelte +++ b/src/lib/components/app-sidebar.svelte @@ -9,162 +9,59 @@ import * as Sidebar from '$lib/components/ui/sidebar/index.js'; import * as Command from '$lib/components/ui/command/index.js'; import * as Collapsible from '$lib/components/ui/collapsible/index.js'; - import { buttonVariants } from './ui/button'; import { useSidebar } from '$lib/components/ui/sidebar/index.js'; - import * as Avatar from '$lib/components/ui/avatar/index.js'; - const sidebar = useSidebar(); - import * as Dialog from '$lib/components/ui/dialog/index.js'; import SidebarAuth from './sidebar-auth.svelte'; // Lucide icons - import Wrench from '@lucide/svelte/icons/wrench'; import ChevronRight from '@lucide/svelte/icons/chevron-right'; import Search from '@lucide/svelte/icons/search'; - import Home from '@lucide/svelte/icons/home'; - import Game from '@lucide/svelte/icons/gamepad-2'; import Check from '@lucide/svelte/icons/check'; import Code from '@lucide/svelte/icons/code'; import Settings from '@lucide/svelte/icons/settings'; import PanelLeft from '@lucide/svelte/icons/panel-left'; - import Copy from '@lucide/svelte/icons/copy'; - import Server from '@lucide/svelte/icons/server'; - import Info from '@lucide/svelte/icons/info'; - import Login from '@lucide/svelte/icons/log-in'; - import Plus from '@lucide/svelte/icons/plus'; - import Logout from '@lucide/svelte/icons/log-out'; // App state and data import { preferencesStore } from '$lib/stores'; - import { gmaes } from '$lib/gmaes.js'; + import { createMainNavigation } from '$lib/navigation'; + import { handleGlobalKeydown, createSidebarShortcuts } from '$lib/keyboard-shortcuts'; + + // Games data + import { gmaes } from '$lib/gmaes'; import { settingsOpen } from '$lib/state.svelte'; - import { mode } from 'mode-watcher'; // Auth - import { - SignedIn, - SignedOut, - SignInButton, - UserButton, - useClerkContext, - SignIn, - SignUp, - UserProfile - } from 'svelte-clerk/client'; - import { dark } from '@clerk/themes'; - import History from '@lucide/svelte/icons/history'; + import { useClerkContext } from 'svelte-clerk/client'; import posthog from 'posthog-js'; const ctx = useClerkContext(); // Props let { ref = $bindable(null), ...restProps }: ComponentProps = $props(); + const sidebar = useSidebar(); + let commandOpen = $state(false); function handleKeydown(e: KeyboardEvent) { - if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { - e.preventDefault(); - commandOpen = !commandOpen; + const shortcuts = createSidebarShortcuts({ + set: (value: boolean) => { + commandOpen = value; + } + }); + if (handleGlobalKeydown(e, shortcuts)) { + return; } + if (e.key === ',' && (e.metaKey || e.ctrlKey) && $preferencesStore.experimentalFeatures) { e.preventDefault(); settingsOpen.current = !settingsOpen.current; } } - const mainNavigation: { - title: string; - url: string; - icon?: any; - experimental: boolean; - items?: { - title: string; - url: string; - }[]; - }[] = $derived( - [ - { - title: 'Home', - icon: Home, - url: '/', - experimental: false - }, - { - title: 'Tools', - icon: Wrench, - experimental: false, - url: '', - items: [ - { - title: 'Calculator', - url: '/tools/calculator' - }, - { - title: 'Converter', - url: '/tools/converter' - }, - { - title: 'Rich Text Editor', - url: '/tools/rich-text-editor' - }, - { - title: 'Word Counter', - url: '/tools/word-counter' - }, - { - title: 'Password Generator', - url: '/tools/password-generator' - }, - { - title: 'Random Number Gen', - url: '/tools/random-number-generator' - } - ] - }, - { - title: 'Gmaes', - icon: Game, - experimental: true, - url: '', - items: [ - { - title: 'All Gmaes', - url: '/g' - }, - { - title: 'Request a Gmae', - url: 'https://github.com/EducationalTools/src/issues/new?assignees=&labels=gmae%2Cenhancement&projects=&template=gmae_request.yml&title=%5BGmae+Request%5D+' - }, - ...gmaes.map((gmae) => ({ - title: gmae.name, - url: `/g/${gmae.id}` - })) - ] - }, - { - title: 'Mirrors', - experimental: true, - url: '/mirrors', - icon: Copy - }, - { - title: 'Host a mirror', - experimental: true, - icon: Server, - url: '/mirrors/host' - }, - { - title: 'Backups', - experimental: true, - icon: History, - url: '/backups' - }, - { - title: 'About', - experimental: true, - icon: Info, - url: '/about' - } - ].filter((item) => !item.experimental || $preferencesStore.experimentalFeatures) + // Filter navigation based on experimental features + const filteredMainNavigation = $derived( + createMainNavigation(gmaes).filter( + (item) => !item.experimental || $preferencesStore.experimentalFeatures + ) ); @@ -199,30 +96,26 @@ - - {#snippet child({ props })} - - {/snippet} + K + + @@ -230,7 +123,7 @@ - {#each mainNavigation as groupItem (groupItem.title)} + {#each filteredMainNavigation as groupItem (groupItem.title)} {@const Icon = groupItem.icon} {#if groupItem.items?.length} @@ -271,6 +164,7 @@ {#if groupItem.items} {#each groupItem.items as item (item.title)} + {@const SubIcon = item.icon} + {#if SubIcon} + + {/if} {item.title}
No results found. - {#each mainNavigation as groupItem (groupItem.title)} + {#each filteredMainNavigation as groupItem (groupItem.title)} {#if groupItem.items?.length} {#each groupItem.items as item (item.title)} diff --git a/src/lib/components/providers.svelte b/src/lib/components/providers.svelte new file mode 100644 index 00000000..8d555cba --- /dev/null +++ b/src/lib/components/providers.svelte @@ -0,0 +1,18 @@ + + + + + + {@render children()} + diff --git a/src/lib/components/settings.svelte b/src/lib/components/settings.svelte index 1d0af13c..eaf2ff8e 100644 --- a/src/lib/components/settings.svelte +++ b/src/lib/components/settings.svelte @@ -81,7 +81,7 @@
- + + + + + + + diff --git a/src/lib/keyboard-shortcuts.ts b/src/lib/keyboard-shortcuts.ts new file mode 100644 index 00000000..bb46d383 --- /dev/null +++ b/src/lib/keyboard-shortcuts.ts @@ -0,0 +1,32 @@ +export interface KeyboardShortcut { + key: string; + metaKey?: boolean; + handler: () => void; + description: string; +} + +export function handleGlobalKeydown(event: KeyboardEvent, shortcuts: KeyboardShortcut[]): boolean { + for (const shortcut of shortcuts) { + const metaKeyMatch = shortcut.metaKey + ? event.metaKey || event.ctrlKey + : !event.metaKey && !event.ctrlKey; + + if (event.key === shortcut.key && metaKeyMatch) { + event.preventDefault(); + shortcut.handler(); + return true; + } + } + return false; +} + +export function createSidebarShortcuts(commandOpen: { set: (value: boolean) => void }) { + return [ + { + key: 'k', + metaKey: true, + handler: () => commandOpen.set(true), + description: 'Open search' + } + ]; +} diff --git a/src/lib/navigation.ts b/src/lib/navigation.ts new file mode 100644 index 00000000..081e67ba --- /dev/null +++ b/src/lib/navigation.ts @@ -0,0 +1,119 @@ +import Home from '@lucide/svelte/icons/home'; +import Wrench from '@lucide/svelte/icons/wrench'; +import Game from '@lucide/svelte/icons/gamepad-2'; +import Code from '@lucide/svelte/icons/code'; +import Server from '@lucide/svelte/icons/server'; +import Copy from '@lucide/svelte/icons/copy'; +import History from '@lucide/svelte/icons/history'; +import ArchiveRestore from '@lucide/svelte/icons/archive-restore'; +import List from '@lucide/svelte/icons/list'; +import Plus from '@lucide/svelte/icons/plus'; +import Info from '@lucide/svelte/icons/info'; + +export interface NavigationItem { + title: string; + icon: any; + url: string; + experimental?: boolean; + items?: { + title: string; + url: string; + icon?: any; + }[]; +} + +export function createMainNavigation( + gmaes: Array<{ id: string; name: string }> = [] +): NavigationItem[] { + return [ + { + title: 'Home', + icon: Home, + url: '/', + experimental: false + }, + { + title: 'Tools', + icon: Wrench, + experimental: false, + url: '', + items: [ + { + title: 'Calculator', + url: '/tools/calculator' + }, + { + title: 'Converter', + url: '/tools/converter' + }, + { + title: 'Rich Text Editor', + url: '/tools/rich-text-editor' + }, + { + title: 'Word Counter', + url: '/tools/word-counter' + }, + { + title: 'Password Generator', + url: '/tools/password-generator' + }, + { + title: 'Random Number Gen', + url: '/tools/random-number-generator' + } + ] + }, + { + title: 'Gmaes', + icon: Game, + experimental: true, + url: '', + items: [ + { + title: 'All Gmaes', + url: '/g', + icon: List + }, + { + title: 'History', + url: '/history', + icon: History + }, + { + title: 'Request a Gmae', + url: 'https://github.com/EducationalTools/src/issues/new?template=gmae_request.yml', + icon: Plus + }, + ...gmaes.map((gmae) => ({ + title: gmae.name, + url: `/g/${gmae.id}` + })) + ] + }, + { + title: 'Mirrors', + experimental: true, + url: '/mirrors', + icon: Copy + }, + { + title: 'Host a mirror', + experimental: true, + icon: Server, + url: '/mirrors/host' + }, + { + title: 'Backups', + experimental: true, + icon: ArchiveRestore, + url: '/backups' + }, + { + title: 'About', + experimental: true, + icon: Info, + url: '/about' + } + ]; +} diff --git a/src/lib/restoreBackup.ts b/src/lib/restoreBackup.ts index 9953db81..0d2a43dd 100644 --- a/src/lib/restoreBackup.ts +++ b/src/lib/restoreBackup.ts @@ -68,5 +68,10 @@ export default function restoreBackup(backupData: string) { sessionStorage.setItem(key, value as string); }); } - location.reload(); + + if (location.pathname == '/handoff') { + window.open('/', '_self'); + } else { + location.reload(); + } } diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 71c78375..5cac0b6e 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -5,59 +5,44 @@ // Props let { children } = $props(); - // UI Components + // Core components import { Toaster } from '$lib/components/ui/sonner/index.js'; import * as Sidebar from '$lib/components/ui/sidebar/index.js'; import AppSidebar from '$lib/components/app-sidebar.svelte'; import Settings from '$lib/components/settings.svelte'; - import * as Dialog from '$lib/components/ui/dialog/index.js'; import PanicMode from '$lib/components/panic-mode.svelte'; import Cloak from '$lib/components/cloak.svelte'; + import Providers from '$lib/components/providers.svelte'; + import TrackerDialog from '$lib/components/tracker-dialog.svelte'; + import Identify from './identify.svelte'; - // Third-party utilities - import { ModeWatcher } from 'mode-watcher'; + // Utilities import clsx from 'clsx'; - import { ClerkProvider, GoogleOneTap } from 'svelte-clerk/client'; - import { PUBLIC_CLERK_PUBLISHABLE_KEY, PUBLIC_CONVEX_URL } from '$env/static/public'; - import { setupConvex } from 'convex-svelte'; - - setupConvex(PUBLIC_CONVEX_URL); - - import { persisted } from 'svelte-persisted-store'; - - import { preferencesStore } from '$lib/stores'; - import { goto } from '$app/navigation'; import { onMount } from 'svelte'; - import posthog from 'posthog-js'; - import { browser } from '$app/environment'; - import Button from '$lib/components/ui/button/button.svelte'; - import Identify from './identify.svelte'; + // Analytics and stores + import { initializeAnalytics, checkTrackerBlocked, trackerDialogClosed } from '$lib/analytics'; + import { preferencesStore } from '$lib/stores'; + // State let trackerBlockerDialog = $state(false); - const trackerDialogClosed = persisted('trackerDialogClosed', false); - - onMount(() => { + onMount(async () => { + // Handle experimental features URL parameter const urlParams = new URLSearchParams(window.location.search); if (urlParams.get('experimental') === 'true') { $preferencesStore.experimentalFeatures = true; goto('/'); } - if (browser) { - posthog.init('phc_jg4gOdigfHQD4MSgrSaO883dp2LjNJbJO7azv61UtI0', { - api_host: 'https://us.i.posthog.com', - person_profiles: 'always', - capture_exceptions: true - }); - fetch('https://us-assets.i.posthog.com/static/exception-autocapture.js').catch(() => { - console.log(navigator.onLine); - console.log($trackerDialogClosed); + // Initialize analytics + initializeAnalytics(); - if (navigator.onLine && !$trackerDialogClosed) trackerBlockerDialog = true; - }); + // Check for tracker blocking + const isBlocked = await checkTrackerBlocked(); + if (isBlocked && !$trackerDialogClosed) { + trackerBlockerDialog = true; } }); @@ -66,36 +51,20 @@ EduTools - - - - Notice - We use Posthog to track errors and usage to improve EduTools. Please disable your - tracker/ad blocker to allow this. Don't worry, we won't add any ads. - - ($trackerDialogClosed = true)}> - - - - - - - - + + - - - +
+ - + + @@ -103,4 +72,4 @@ {@render children()} -
+ diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index c0a63801..d3136888 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -43,7 +43,7 @@ History - {#each $historyStore as item} + {#each $historyStore.slice(0, 5) as item} @@ -55,12 +55,16 @@ No history saved {:else} - +
+ + +
{/if}
diff --git a/src/routes/g/columns.ts b/src/routes/g/columns.ts index 90b044b9..dc179d3b 100644 --- a/src/routes/g/columns.ts +++ b/src/routes/g/columns.ts @@ -25,7 +25,7 @@ export const columns: ColumnDef[] = [ cell: ({ row }) => { return renderComponent(Link, { children: row.original.name, - href: `/games/${row.original.id}` + href: `/g/${row.original.id}` }); }, // @ts-ignore diff --git a/src/routes/g/data-table.svelte b/src/routes/g/data-table.svelte index 92425a81..26703e08 100644 --- a/src/routes/g/data-table.svelte +++ b/src/routes/g/data-table.svelte @@ -86,8 +86,8 @@ }); -
-
+
+
Show ID
- + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} {#each headerGroup.headers as header (header.id)} diff --git a/src/routes/handoff/+page.svelte b/src/routes/handoff/+page.svelte new file mode 100644 index 00000000..a47cb4b3 --- /dev/null +++ b/src/routes/handoff/+page.svelte @@ -0,0 +1,121 @@ + + + + + Loading + + +
+ + + Importing data + + This will clear all of your existing data on {page.url.hostname} + + + + + + + +
diff --git a/src/routes/history/+page.svelte b/src/routes/history/+page.svelte new file mode 100644 index 00000000..8daf364c --- /dev/null +++ b/src/routes/history/+page.svelte @@ -0,0 +1,15 @@ + + + diff --git a/src/routes/history/columns.ts b/src/routes/history/columns.ts new file mode 100644 index 00000000..dc179d3b --- /dev/null +++ b/src/routes/history/columns.ts @@ -0,0 +1,66 @@ +import type { ColumnDef } from '@tanstack/table-core'; +import type { ParsedGmae } from '$lib/gmaes'; +import { renderComponent } from '$lib/components/ui/data-table/index.js'; +import TableActions from './table-actions.svelte'; +import Link from './link.svelte'; +import Tags from './tags.svelte'; +import HeaderButtonSort from './header-button-sort.svelte'; + +export const columns: ColumnDef[] = [ + { + accessorKey: 'id', + header: ({ column }) => + renderComponent(HeaderButtonSort, { + content: 'ID' + }) + }, + { + accessorKey: 'name', + header: ({ column }) => + renderComponent(HeaderButtonSort, { + onclick: column.getToggleSortingHandler(), + content: 'Name', + sort: true + }), + cell: ({ row }) => { + return renderComponent(Link, { + children: row.original.name, + href: `/g/${row.original.id}` + }); + }, + // @ts-ignore + filterFn: 'fuzzy' + }, + { + accessorKey: 'category', + header: ({ column }) => + renderComponent(HeaderButtonSort, { + onclick: column.getToggleSortingHandler(), + content: 'Category', + sort: true + }) + }, + { + accessorKey: 'description', + header: ({ column }) => + renderComponent(HeaderButtonSort, { + content: 'Description' + }) + }, + { + accessorKey: 'tags', + header: ({ column }) => + renderComponent(HeaderButtonSort, { + content: 'Tags' + }), + cell: ({ row }) => { + return renderComponent(Tags, { tags: row.original.tags }); + } + }, + { + id: 'actions', + cell: ({ row }) => { + return renderComponent(TableActions, { id: row.original.id }); + } + } +]; diff --git a/src/routes/history/data-table.svelte b/src/routes/history/data-table.svelte new file mode 100644 index 00000000..e7cd7efd --- /dev/null +++ b/src/routes/history/data-table.svelte @@ -0,0 +1,153 @@ + + +
+
+
+ History +
+ { + table.getColumn('name')?.setFilterValue(e.currentTarget.value); + }} + oninput={(e) => { + table.getColumn('name')?.setFilterValue(e.currentTarget.value); + }} + class="grow" + /> +
+ table.getColumn('id')?.getIsVisible(), + (v) => table.getColumn('id')?.toggleVisibility(!!v) + } + id="showid" + /> + +
+ +
+ + + {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {#if !header.isPlaceholder} + + {/if} + + {/each} + + {/each} + + + {#each table.getRowModel().rows as row (row.id)} + + {#each row.getVisibleCells() as cell (cell.id)} + + + + {/each} + + {:else} + + No results. + + {/each} + + +
diff --git a/src/routes/history/header-button-sort.svelte b/src/routes/history/header-button-sort.svelte new file mode 100644 index 00000000..7ab5a7a8 --- /dev/null +++ b/src/routes/history/header-button-sort.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/routes/history/link.svelte b/src/routes/history/link.svelte new file mode 100644 index 00000000..d82dc8b4 --- /dev/null +++ b/src/routes/history/link.svelte @@ -0,0 +1,5 @@ + + +{children} diff --git a/src/routes/history/table-actions.svelte b/src/routes/history/table-actions.svelte new file mode 100644 index 00000000..1c300bcf --- /dev/null +++ b/src/routes/history/table-actions.svelte @@ -0,0 +1,11 @@ + + + diff --git a/src/routes/history/tags.svelte b/src/routes/history/tags.svelte new file mode 100644 index 00000000..1a960f9e --- /dev/null +++ b/src/routes/history/tags.svelte @@ -0,0 +1,12 @@ + + +
+ {#each tags || [] as tag} + #{tag} + {/each} +
diff --git a/src/routes/tools/converter/+page.svelte b/src/routes/tools/converter/+page.svelte index 03c0576d..2fcee8c4 100644 --- a/src/routes/tools/converter/+page.svelte +++ b/src/routes/tools/converter/+page.svelte @@ -1,6 +1,5 @@ -
+