diff --git a/src/lib/api/index-repository.ts b/src/lib/api/index-repository.ts index 101fa56..f292919 100644 --- a/src/lib/api/index-repository.ts +++ b/src/lib/api/index-repository.ts @@ -1,8 +1,8 @@ import * as publicEnv from "$env/static/public"; -import type { ServerDeveloper } from "./models/base"; -import type { ServerMod, ServerSimpleMod } from "./models/mod.js"; -import type { ModStatus, ServerModVersion } from "./models/mod-version.js"; +import type { ServerDeveloper, ServerModDeveloper, GithubLogin } from "./models/developer"; +import type { ServerMod, ServerSimpleMod } from "./models/mod"; +import type { ModStatus, ServerModVersion } from "./models/mod-version"; import type { ServerStats } from "./models/stats"; const BASE_URL = @@ -39,6 +39,19 @@ interface BaseRequest { type BasePaginatedRequest = BaseRequest>; +function validate(data: BaseRequest) { + if (data.error) { + throw new IndexError(data.error); + } + return data.payload; +} +function validateNoThrow(data: BaseRequest) { + if (data.error) { + console.log("ERROR: " + data.error) + return null; + } + return data.payload; +} export interface ModSearchParams { page?: number; developer?: string; @@ -517,27 +530,49 @@ export class IndexClient { this.token = null; } - async deleteAllTokens(): Promise { - this.requireAuth(); - - const r = await this.fetch(`${BASE_URL}/v1/me/tokens`, { - headers: new Headers({ - Authorization: `Bearer ${this.token}`, - }), - method: "DELETE", - }); +export async function githubAuth(): Promise { + const r = await fetch(`${BASE_URL}/v1/login/github`, { + headers: new Headers({ + "Content-Type": "application/json", + }), + method: "POST", + }); + const data = await r.json(); - if (r.status != 204) { - const data: BaseRequest = await r.json(); - throw new IndexError(data.error); - } + return validate(data); +} - this.token = null; +export async function githubAuthPoll(uuid: string): Promise { + const r = await fetch(`${BASE_URL}/v1/login/github/poll`, { + headers: new Headers({ + "Content-Type": "application/json", + }), + method: "POST", + body: JSON.stringify({ uuid }) + }); + const data = await r.json(); + + return validateNoThrow(data); +} +async deleteAllTokens(): Promise { + this.requireAuth(); + + const r = await this.fetch(`${BASE_URL}/v1/me/tokens`, { + headers: new Headers({ + Authorization: `Bearer ${this.token}`, + }), + method: "DELETE", + }); + + if (r.status != 204) { + const data: BaseRequest = await r.json(); + throw new IndexError(data.error); } - async getServerStats(): Promise { - const r = await this.fetch(`${BASE_URL}/v1/stats`); - const data = await r.json(); - return this.validate(data); - } + this.token = null; +} +export async function getServerStats(): Promise { + const r = await fetch(`${BASE_URL}/v1/stats`); + const data = await r.json(); + return this.validate(data); } diff --git a/src/lib/api/models/base.ts b/src/lib/api/models/base.ts deleted file mode 100644 index 6c1d8d5..0000000 --- a/src/lib/api/models/base.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface ServerDeveloper { - id: number; - username: string; - display_name: string; - verified: boolean; - admin: boolean; -} diff --git a/src/lib/api/models/developer.ts b/src/lib/api/models/developer.ts new file mode 100644 index 0000000..c65d03d --- /dev/null +++ b/src/lib/api/models/developer.ts @@ -0,0 +1,21 @@ +export interface ServerDeveloper { + id: number; + username: string; + display_name: string; + verified: boolean; + admin: boolean; +} + +export interface ServerModDeveloper { + id: number; + username: string; + display_name: string; + is_owner: boolean; +} + +export interface GithubLogin { + uuid: string; + interval: number; + uri: string; + code: string; +} \ No newline at end of file diff --git a/src/lib/api/models/mod-developer.ts b/src/lib/api/models/mod-developer.ts deleted file mode 100644 index 1513197..0000000 --- a/src/lib/api/models/mod-developer.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface ServerModDeveloper { - id: number; - username: string; - display_name: string; - is_owner: boolean; -} diff --git a/src/lib/api/models/mod.ts b/src/lib/api/models/mod.ts index dcb3d62..07600ea 100644 --- a/src/lib/api/models/mod.ts +++ b/src/lib/api/models/mod.ts @@ -1,4 +1,4 @@ -import type { ServerModDeveloper } from "./mod-developer.js"; +import type { ServerModDeveloper } from "./developer"; import type { ServerModVersion, ServerSimpleModVersion, diff --git a/src/lib/components/Button.svelte b/src/lib/components/Button.svelte index eb91912..e4f93bb 100644 --- a/src/lib/components/Button.svelte +++ b/src/lib/components/Button.svelte @@ -5,17 +5,27 @@ type Style = 'primary-filled-dark' | 'primary-filled' | + 'primary-hollow' | + 'secondary-filled-dark' | 'secondary-filled' | + 'secondary-hollow' | 'hollow' | - 'dark-small'; + 'dark-small' | + 'money-box'; export let style: Style = 'hollow'; export let href: string | undefined = undefined; + export let target: string | undefined = undefined; export let icon: KnownIcon | undefined = undefined; export let iconOnRight = false; export let disabled = false; + let enableTheDisabledStyle = false; + $: enableTheDisabledStyle = $$props['enable-disabled-style'] + let additionalClasses: string | undefined = undefined; + $: additionalClasses = $$props['additional-classes'] + - + {#if iconOnRight} {/if} @@ -52,7 +62,7 @@ // transform: scale(105%) translateY(-.2em); // } - &.primary-filled-dark { + &.primary-filled-dark, &.primary-filled-dark.disabled { color: var(--primary-300); background-color: var(--primary-950); border-color: var(--primary-950); @@ -63,7 +73,7 @@ border-color: var(--primary-50); } } - &.primary-filled { + &.primary-filled, &.primary-filled.disabled { color: var(--primary-950); background-color: var(--primary-300); border-color: var(--primary-300); @@ -74,7 +84,29 @@ border-color: var(--primary-50); } } - &.secondary-filled { + &.primary-hollow, &.primary-hollow.disabled { + color: var(--primary-300); + background-color: transparent; + border-color: var(--primary-300); + box-shadow: 0px .1rem .5rem color-mix(in srgb, var(--primary-950) 50%, transparent); + &:hover { + color: var(--secondary-950); + background-color: var(--primary-50); + border-color: var(--primary-50); + } + } + &.secondary-filled-dark, &.secondary-filled-dark.disabled { + color: var(--secondary-300); + background-color: var(--secondary-950); + border-color: var(--secondary-950); + box-shadow: 0px .1rem .5rem color-mix(in srgb, var(--secondary-950) 50%, transparent); + &:hover { + color: var(--secondary-950); + background-color: var(--secondary-50); + border-color: var(--secondary-50); + } + } + &.secondary-filled, &.secondary-filled.disabled { color: var(--secondary-950); background-color: var(--secondary-300); border-color: var(--secondary-300); @@ -85,7 +117,7 @@ border-color: var(--secondary-50); } } - &.hollow { + &.secondary-hollow, &.secondary-hollow.disabled, &.hollow, &.hollow.disabled{ color: var(--secondary-300); background-color: transparent; border-color: var(--secondary-300); @@ -96,7 +128,7 @@ border-color: var(--secondary-50); } } - &.dark-small { + &.dark-small, &.dark-small.disabled { padding: .3rem; padding-top: .15rem; padding-bottom: .15rem; @@ -112,8 +144,32 @@ border-color: var(--primary-50); } } + &.money-box { + border: .15rem color-mix(in srgb, var(--background-300) 50%, transparent) solid; + border-radius: .5rem; + + background-color: color-mix(in srgb, var(--background-500) 20%, transparent); + color: var(--text-100); + font-size: 1.1em; + + padding: .65rem; + + display: flex; + flex-direction: row; + align-items: center; + gap: .25em; + + &:hover { + color: var(--secondary-950); + background-color: var(--primary-50); + border-color: var(--primary-50); + } + } &.disabled { pointer-events: none; + opacity: 55%; + } + &.disabled.disable-style { color: var(--background-50); background-color: var(--background-800); border-color: var(--background-800); diff --git a/src/lib/components/ModCard.svelte b/src/lib/components/ModCard.svelte index 85e4139..75979ff 100644 --- a/src/lib/components/ModCard.svelte +++ b/src/lib/components/ModCard.svelte @@ -1,7 +1,7 @@ @@ -78,6 +81,7 @@ --link-color="var(--accent-300)"> {owner.display_name} + {#if !hideDescription}

{#if version.description} {#if version.description?.length < 110} @@ -89,20 +93,24 @@ {"Description not provided"} {/if}

+ {/if} + {#if !hideVersion} {version.version} + {/if} {abbreviateNumber( - mod.download_count, + versionDownloadCount ? version.download_count : mod.download_count, )} + {#if mod.updated_at != undefined} @@ -110,6 +118,7 @@ mod.updated_at, )} + {/if} {:else} @@ -151,13 +160,16 @@ + {#if !hideVersion} {version.version} - - {abbreviateNumber(mod.download_count)} + {/if} + + {abbreviateNumber(versionDownloadCount ? version.download_count : mod.download_count)} + {#if !hideDescription}

{#if version.description} @@ -170,6 +182,7 @@ {"Description not provided"} {/if}

+ {/if} {/if} diff --git a/src/lib/components/MoneyBox.svelte b/src/lib/components/MoneyBox.svelte index 657faa6..7e82217 100644 --- a/src/lib/components/MoneyBox.svelte +++ b/src/lib/components/MoneyBox.svelte @@ -16,6 +16,8 @@ let number: HTMLSpanElement; + $: countup.set(num); + onMount(() => { const observer = new IntersectionObserver(entries => { entries.reverse().forEach(entry => { diff --git a/src/lib/data/faqs-en.json b/src/lib/data/faqs-en.json index 93c0b61..7a6adc3 100644 --- a/src/lib/data/faqs-en.json +++ b/src/lib/data/faqs-en.json @@ -13,6 +13,10 @@ { "question": "Is Geode Free?", "answer": "Geode is *100% free* and always will be! However, individual mods may be paid." + }, + { + "question": "Why is Geode not available for iOS (nor iPadOS)", + "answer": "Geode needs to *inject code* at runtime and iOS is very very keen on making sure that *doesn't happen*.\n\nMaybe some day with something called JIT (Just-in-time) Geode could be viable on iOS, but at the moment, there is *nothing officially released*." } ] }, diff --git a/src/lib/events.ts b/src/lib/events.ts new file mode 100644 index 0000000..6e7f40d --- /dev/null +++ b/src/lib/events.ts @@ -0,0 +1,7 @@ +var events: HTMLAnchorElement; + +function startEvents(document: Document) { + if(!events) { events = document.createElement("a"); } +} + +export {events, startEvents} \ No newline at end of file diff --git a/src/lib/index.ts b/src/lib/index.ts index 24f2969..cada2e5 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -59,10 +59,16 @@ export const icons = { modify: "mdi:pencil", update: "mdi:clock-edit", info: "mdi:about", + plus: "mdi:plus", + person: "mdi:person", + admin: "mdi:hammer-wrench", web: "mdi:web", community: "mdi:account-group", image: "mdi:file-image", "image-missing": "mdi:file-image-remove", + plus: "mdi:plus", + person: "mdi:person", + admin: "mdi:hammer-wrench", "tag-universal": "mdi:globe", "tag-gameplay": "mdi:gamepad-variant", diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts new file mode 100644 index 0000000..21912ea --- /dev/null +++ b/src/routes/+layout.server.ts @@ -0,0 +1,30 @@ +import { fail, redirect } from "@sveltejs/kit"; +import type { LayoutServerLoad } from "./$types.js"; +import { + IndexError, + deleteAllTokens, + deleteToken, + getSelf, + updateProfile, +} from "$lib/api/index-repository.js"; + +export const load: LayoutServerLoad = async ({ cookies }) => { + const token = cookies.get("token"); + + let loggedIn = typeof token == "string"; + + // i'm having a js moment, honestly + let self = undefined; + + try { + if (loggedIn) { + self = await getSelf(token); + cookies.set("cached_profile", JSON.stringify(self), { path: "/" }); + } + } catch (error) { + loggedIn = false + console.log(`Unknown Error in +layout.server.ts: ${error}`) + } + + return { loggedIn, self }; +}; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 3870679..6e9cbd6 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,12 +1,50 @@
@@ -15,7 +53,38 @@
@@ -101,6 +170,65 @@ position: fixed; top: 1rem; left: 1rem; + + & > div.left-side { + position: fixed; + top: 1rem; + left: 1rem; + display: flex; + flex-direction: column; + } + & > div.right-side { + position: fixed; + top: 1rem; + right: 1rem; + display: flex; + flex-direction: column; + + & > div.dropdown { + & a .nav-dev-profile-picture { + width: var(--icon-size); + height: var(--icon-size); + pointer-events: none; + border-radius: 100%; + } + + & > :global(a) :global(.icon) > :global(svg) { + transition: transform 500ms; + } + + &:global(.show) > :global(a) :global(.icon) > :global(svg) { + transform: rotate(180deg); + } + + & > :global(div.dropdown-content) { + display: flex; + flex-direction: column; + border-radius: .15rem; + border-style: solid; + border-width: .15rem; + background-color: var(--primary-950); + border-color: var(--primary-950); + box-shadow: 0px .1rem .5rem color-mix(in srgb, var(--primary-950) 50%, transparent); + transition: opacity .7s, transform .5s; + transform: translateY(1.2em); + opacity: 0; + pointer-events: none; + } + + &:global(.show) > :global(div.dropdown-content) { + display: flex; + flex-direction: column; + transform: translateY(-1.2em); + opacity: 100%; + pointer-events: all; + } + } + } + } + + :global(.red) { + color: rgb(190, 42, 42) !important; } .waves-bottom { diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts new file mode 100644 index 0000000..8c55dde --- /dev/null +++ b/src/routes/+page.server.ts @@ -0,0 +1,9 @@ +import type { PageServerLoad } from "./$types.js"; + +export const load: PageServerLoad = async ({ cookies }) => { + const token = cookies.get("token"); + + let loggedIn = typeof token == "string" + + return { loggedIn }; +}; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index d261cda..dc13eb5 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,4 +1,7 @@ @@ -60,8 +69,10 @@ +
+ The main page Discord @@ -77,8 +88,8 @@

- Geode is a fan-made extension for Geometry Dash that adds mod support to the game. - Browse from an in-game list to seamlessly download mods on Windows, Mac, + Geode is a fan-made extension for Geometry Dash that adds mod support to the game. + Browse from an in-game list to seamlessly download mods on Windows, Mac, and Android!

@@ -96,15 +107,16 @@

- Geode is the most popular GD mod loader across all platforms. With an active community of + Geode is the most popular GD mod loader across all platforms. With an active community of both users and modders, nearly every mod you can imagine has been made or suggested!

{#await stats_promise}

Loading stats...

- {:then stats} + {:then stats} + +
diff --git a/src/routes/dash/+page.server.ts b/src/routes/dash/+page.server.ts new file mode 100644 index 0000000..37098bb --- /dev/null +++ b/src/routes/dash/+page.server.ts @@ -0,0 +1,27 @@ +import { fail, redirect } from "@sveltejs/kit"; +import type { PageServerLoad } from "./$types.js"; +import { + IndexError, + getSelf, +} from "$lib/api/index-repository.js"; + +export const load: PageServerLoad = async ({ cookies }) => { + const token = cookies.get("token"); + if (!token) { + redirect(302, "/login"); + } + + // i'm having a js moment, honestly + let self = undefined; + try { + self = await getSelf(token); + cookies.set("cached_profile", JSON.stringify(self), { path: "/" }); + } catch (_) { + cookies.delete("token", { path: "/" }); + cookies.delete("cached_profile", { path: "/" }); + + redirect(302, "/login"); + } + + return { self }; +}; diff --git a/src/routes/dash/+page.svelte b/src/routes/dash/+page.svelte new file mode 100644 index 0000000..14a6d74 --- /dev/null +++ b/src/routes/dash/+page.svelte @@ -0,0 +1,147 @@ + + + + + + + Developer Dashboard + + + +

HelloUser Profile{data.self.display_name}!

+ +
+ + + + + {#if data.self.admin != "merp"} + + {/if} +
+ + + \ No newline at end of file diff --git a/src/routes/dash/mods/+page.server.ts b/src/routes/dash/mods/+page.server.ts new file mode 100644 index 0000000..2e490a5 --- /dev/null +++ b/src/routes/dash/mods/+page.server.ts @@ -0,0 +1,24 @@ +import { fail, redirect } from "@sveltejs/kit"; +import type { PageServerLoad } from "./$types.js"; +import { + IndexError, + getSelfMods, +} from "$lib/api/index-repository.js"; + +export const load: PageServerLoad = async ({ cookies }) => { + const token = cookies.get("token"); + if (!token) { + redirect(302, "/login"); + } + + // i'm having a js moment, honestly + let mods = undefined; + try { + mods = await getSelfMods(token); + } catch (_) { + + redirect(500, "It failed idk why, reason: " + _); + } + + return { mods }; +}; diff --git a/src/routes/dash/mods/+page.svelte b/src/routes/dash/mods/+page.svelte new file mode 100644 index 0000000..51eae40 --- /dev/null +++ b/src/routes/dash/mods/+page.svelte @@ -0,0 +1,159 @@ + + + + + + + Developer Dashboard + + + +

Your Mods!

+ +{#each data.mods as mod (mod.id)} + {@const mod_version = mod.versions[0]} + + + {#each mod.versions as version} + + {/each} + +{/each} + + + \ No newline at end of file diff --git a/src/routes/install/+page.svelte b/src/routes/install/+page.svelte index d9f2580..abc98c9 100644 --- a/src/routes/install/+page.svelte +++ b/src/routes/install/+page.svelte @@ -19,7 +19,7 @@ let latestVersion = data.loader_tag; let latestLauncher = `v${data.launcher_tag}`; let showAllPlatforms = false; - let curPlatform: 'windows' | 'mac' | 'android' | 'linux' | 'unknown' | undefined = undefined; + let curPlatform: 'windows' | 'mac' | 'android' | 'linux' | 'ios' | 'unknown' | undefined = undefined; const createVersionString = (platform: 'windows' | 'mac' | 'android' | "linux"): string => { let filename = ""; @@ -44,6 +44,8 @@ } onMount(() => { + let isiOS = (window.navigator.userAgent.includes("iPad Simulator") || window.navigator.userAgent.includes("iPhone Simulator") || window.navigator.userAgent.includes("iPod Simulator") || window.navigator.userAgent.includes("iPad") || window.navigator.userAgent.includes("iPhone") || window.navigator.userAgent.includes("iPod")) + if (window.navigator.userAgent.includes("Windows")) { curPlatform = "windows"; } else if (window.navigator.userAgent.includes("Macintosh")) { @@ -52,6 +54,9 @@ curPlatform = "android"; } else if (window.navigator.userAgent.includes("Linux")) { curPlatform = "linux"; + } else if (isiOS) { + curPlatform = "ios"; + showAllPlatforms = true; } else { curPlatform = "unknown"; showAllPlatforms = true; @@ -94,7 +99,7 @@

- Geode is available for Windows, MacOS, Linux (through Wine / Proton) and Android. + Geode is available for Windows, macOS, Linux (through Wine / Proton) and Android.

@@ -115,10 +120,19 @@ {#if curPlatform === "unknown"}

Couldn't auto detect your platform. You can download Geode for your chosen platform below.

{/if} + {#if curPlatform === "ios"} +

Geode is not available on iOS right now. If you got it from another source or a YouTube video, it's a scam. Click here for more info.

+ + + {/if} {#if curPlatform === "android"} @@ -126,39 +140,39 @@ {/if} {#if !showAllPlatforms} {#if curPlatform == "windows"} - {/if} {#if curPlatform == "mac"} - {/if} {#if curPlatform == "android"} - {/if} {/if} - - - - diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte index c76a0ef..4273e45 100644 --- a/src/routes/login/+page.svelte +++ b/src/routes/login/+page.svelte @@ -1,13 +1,174 @@ -

Login

+ + Developer Login + + + + + + + +

Login

-

+ + +

+{#if status_loading || status_current_step == 0} +
+ + + {#if status_loading} +

{status_loading_message || "Loading..."}

+ {:else} +

Waiting for page...

+ {/if} +
+
+{:else if status_current_step == 1} +
+ +

When you click Sign In, a GitHub window will popup asking for a code, we will display it right on this page.

+ + +

Important: Sometimes the index login system glitches and even though you did login with GitHub, it doesn't move you on and gives you the same code. If this does happen, please wait 5-20 minutes (usually it's in the 5-10 minute range) to come back here and try to login again. Also sometimes it catches up and then logs you in automatically, that can happen as well and is normal.

+
+
+{:else if status_current_step == 2} +
+ +

Your code is

+

{ghLoginData.code || "EXAM-PLES"}

+

We also copied it to your clipboard.

+ + + +

Important: Sometimes the index login system glitches and even though you did login with GitHub, it doesn't move you on and gives you the same code. If this does happen, please wait 5-20 minutes (usually it's in the 5-10 minute range) to come back here and try to login again. Also sometimes it catches up and then logs you in automatically, that can happen as well and is normal.

+
+
+ +{:else if status_current_step == 3} +
+ +

Success!

+

You are now logged in!

+ +
+
+{/if} + +
+ + + + + + + \ No newline at end of file diff --git a/src/routes/logout/+page.server.ts b/src/routes/logout/+page.server.ts new file mode 100644 index 0000000..607a27c --- /dev/null +++ b/src/routes/logout/+page.server.ts @@ -0,0 +1,29 @@ +import { fail, redirect } from "@sveltejs/kit"; +import type { PageServerLoad } from "./$types.js"; +import { + IndexError, + deleteToken, +} from "$lib/api/index-repository.js"; +export const load: PageServerLoad = async ({ cookies }) => { + const token = cookies.get("token"); + if (!token) { + redirect(302, "/login"); + } + + try { + await deleteToken(cookies.get("token")); + } catch (e) { + if (e instanceof IndexError) { + return fail(400, { cause: e.message }); + } + + throw e; + } + + + cookies.delete("token", { path: "/" }); + cookies.delete("cached_profile", { path: "/" }); + + redirect(304, "/") + return; +}; diff --git a/src/routes/logout/+page.svelte b/src/routes/logout/+page.svelte new file mode 100644 index 0000000..b67e388 --- /dev/null +++ b/src/routes/logout/+page.svelte @@ -0,0 +1,9 @@ + + +
Redirecting... \ No newline at end of file diff --git a/src/routes/me/+page.svelte b/src/routes/me/+page.svelte index 9295a86..beba4a1 100644 --- a/src/routes/me/+page.svelte +++ b/src/routes/me/+page.svelte @@ -1,7 +1,23 @@ + + Developer Login + + + + + + +
it's you! diff --git a/src/routes/stats/+page.svelte b/src/routes/stats/+page.svelte new file mode 100644 index 0000000..ed148e7 --- /dev/null +++ b/src/routes/stats/+page.svelte @@ -0,0 +1,100 @@ + + + + + + + Live Stats + + + +

Live Stats

+ + +{#if loading} +

Loading stats...

+{:else if error} + Unable to load stats! +{:else} + + + + + + + + +{/if} +
+ + + + +